diff --git a/README.md b/README.md index 86e117c..fa8c0fc 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ I started this project out of curiosity for how a motion sensor is working and s After 6 months of testing I believe this is working as planned. It give accurate readings same as the orginal iSpindel software. Version history +* v0.5 Added support for creating formula on device + bug fixes. * v0.4 First official version with 5+ brews on record. Lower priority @@ -127,11 +128,19 @@ http://192.168.1.1/firmware/gravmon/ Contents version.json ``` -{ "project":"gravmon", "version":"0.3.0" } +{ "project":"gravmon", "version":"0.5.0" } ``` ![Config - Hardware](img/config4.png) +### Calibration page + +http://gravmon.local/calibration.htm + +The device page is used to enter gravity + angle/tilt into the device in order to create a formula. Data will be saved on the device and a formula will be checked against the entered numbers for accuracy. Support currently up to 5 values and the more readings the more accurate the formula will be. Its prefeered to use at least 3 values. + +![Device](img/calibration.png) + # Building a device See the iSpindle documentation for building a device. diff --git a/html/calibration.htm b/html/calibration.htm new file mode 100644 index 0000000..84d978b --- /dev/null +++ b/html/calibration.htm @@ -0,0 +1,243 @@ + + + + + + + Beer Gravity Monitor + + + + + + + + + + + + +
+ +
+ + + + + +
+ +
+
+

+ +

+
+ +
+
+
+ + +
+ Here you can create your gravity formula by entering angles and the corresponding gravity (SG). These values + will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the + formula and if the deviation is more than 1.5 SG then the forumla will be rejected. +
+ +
+ + + +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+ + + + + +
(C) Copyright 2021-22 Magnus Persson
+ + \ No newline at end of file diff --git a/html/calibration.min.htm b/html/calibration.min.htm new file mode 100644 index 0000000..a8b420d --- /dev/null +++ b/html/calibration.min.htm @@ -0,0 +1 @@ +Beer Gravity Monitor

Here you can create your gravity formula by entering angles and the corresponding gravity (SG). These values will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the formula and if the deviation is more than 1.5 SG then the forumla will be rejected.

(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file diff --git a/img/config1.png b/img/config1.png index f10c5a2..af5ab28 100644 Binary files a/img/config1.png and b/img/config1.png differ diff --git a/img/config2.png b/img/config2.png index 720bffe..f1fbde6 100644 Binary files a/img/config2.png and b/img/config2.png differ diff --git a/img/config3.png b/img/config3.png index 4905580..568e17c 100644 Binary files a/img/config3.png and b/img/config3.png differ diff --git a/img/config4.png b/img/config4.png index 12a15ea..d7c9c60 100644 Binary files a/img/config4.png and b/img/config4.png differ diff --git a/img/device.png b/img/device.png index 154cc56..6d445a0 100644 Binary files a/img/device.png and b/img/device.png differ diff --git a/img/index.png b/img/index.png index ddc079d..290f037 100644 Binary files a/img/index.png and b/img/index.png differ diff --git a/platformio.ini b/platformio.ini index 9594a3a..3acf69d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,12 +15,13 @@ include_dir = lib [common_env_data] upload_speed = 921600 monitor_speed = 115200 -platform = espressif8266 +#platform = espressif8266 @ 2.6.3 +platform = espressif8266 @ 3.2.0 framework = arduino board = d1_mini build_unflags = -0src_build_flags = -Wunused-variable -Wregister -Wchar-subscripts -build_flags = #-O0 -Wl,-Map,output.map +#src_build_flags = -Wunused-variable -Wregister -Wchar-subscripts +build_flags = -D BAUD=${common_env_data.monitor_speed} -D ACTIVATE_OTA #-D USE_GYRO_TEMP # If this is enabled the DS18 will not be used, temp is read from the gyro. @@ -30,21 +31,22 @@ build_flags = #-O0 -Wl,-Map,output.map #-D SKIP_SLEEPMODE #-D DOUBLERESETDETECTOR_DEBUG true -D USE_LITTLEFS=true - -D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded) + -D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded) -D USER_SSID=\""\"" # =\""myssid\"" -D USER_SSID_PWD=\""\"" # =\""mypwd\"" -D CFG_APPVER="\"0.4.0\"" lib_deps = # Switched to forks for better version control. # Using local copy of this library - #https://github.com/mp-se/i2cdevlib # https://github.com/jrowberg/i2cdevlib.git - https://github.com/mp-se/tinyexpr # https://github.com/codeplea/tinyexpr - https://github.com/mp-se/incbin # https://github.com/graphitemaster/incbin - https://github.com/mp-se/ESP_DoubleResetDetector # https://github.com/khoih-prog/ESP_DoubleResetDetector - https://github.com/mp-se/WiFiManager # https://github.com/tzapu/WiFiManager - https://github.com/mp-se/Arduino-Log # https://github.com/thijse/Arduino-Log - https://github.com/mp-se/ArduinoJson # https://github.com/bblanchon/ArduinoJson - https://github.com/mp-se/OneWire # https://github.com/PaulStoffregen/OneWire - https://github.com/mp-se/Arduino-Temperature-Control-Library # https://github.com/milesburton/Arduino-Temperature-Control-Library + #https://github.com/mp-se/i2cdevlib # https://github.com/jrowberg/i2cdevlib.git + https://github.com/mp-se/tinyexpr # https://github.com/codeplea/tinyexpr + https://github.com/mp-se/incbin # https://github.com/graphitemaster/incbin + https://github.com/mp-se/ESP_DoubleResetDetector#v1.2.1 # https://github.com/khoih-prog/ESP_DoubleResetDetector + https://github.com/mp-se/WiFiManager#2.0.5-beta # https://github.com/tzapu/WiFiManager + https://github.com/mp-se/Arduino-Log#1.1.1 # https://github.com/thijse/Arduino-Log + https://github.com/mp-se/ArduinoJson#v6.18.5 # https://github.com/bblanchon/ArduinoJson + https://github.com/mp-se/OneWire#v2.3.6 # https://github.com/PaulStoffregen/OneWire + https://github.com/mp-se/Arduino-Temperature-Control-Library#3.9.1 # https://github.com/milesburton/Arduino-Temperature-Control-Library + https://github.com/mp-se/arduinoCurveFitting#v1.0.6 # https://github.com/Rotario/arduinoCurveFitting [env:gravity-debug] upload_speed = ${common_env_data.upload_speed} @@ -52,18 +54,23 @@ monitor_speed = ${common_env_data.monitor_speed} framework = ${common_env_data.framework} platform = ${common_env_data.platform} extra_scripts = + script/copy_html.py script/copy_firmware.py script/create_versionjson.py -build_unflags = ${common_env_data.build_unflags} +build_unflags = + ${common_env_data.build_unflags} build_flags = - ${common_env_data.build_flags} - -D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb - -D LOG_LEVEL=6 # Maximum log level for the debug build. + -D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS + #-D DEACTIVATE_SLEEPMODE + ${common_env_data.build_flags} + -D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb + -D LOG_LEVEL=6 # Maximum log level for the debug build. lib_deps = ${common_env_data.lib_deps} board = ${common_env_data.board} build_type = debug board_build.filesystem = littlefs +monitor_filters = esp8266_exception_decoder [env:gravity-release] upload_speed = ${common_env_data.upload_speed} @@ -71,6 +78,7 @@ monitor_speed = ${common_env_data.monitor_speed} framework = ${common_env_data.framework} platform = ${common_env_data.platform} extra_scripts = + script/copy_html.py script/copy_firmware.py script/create_versionjson.py build_unflags = ${common_env_data.build_unflags} @@ -89,6 +97,7 @@ monitor_speed = ${common_env_data.monitor_speed} framework = ${common_env_data.framework} platform = ${common_env_data.platform} extra_scripts = + script/copy_html.py script/copy_firmware.py script/create_versionjson.py build_unflags = ${common_env_data.build_unflags} diff --git a/script/copy_html.py b/script/copy_html.py index 001091a..4a82414 100644 --- a/script/copy_html.py +++ b/script/copy_html.py @@ -5,25 +5,25 @@ print( "Executing custom step " ) dir = env.GetLaunchDir() source = dir + "/html/" target = dir + "/data/" -print( "Copy file : " + source + " -> " + target ) +print( "Copy html-files from " + source + " -> " + target ) file = "about.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) file = "calibration.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) file = "config.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) file = "device.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) file = "index.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) file = "upload.min.htm" -print( "Copy file: " + source + file + "->" + target + file) +#print( "Copy file: " + source + file + "->" + target + file) shutil.copyfile( source + file, target + file ) #print( "Adding custom build step (copy ./heml/*.min.html to ./data/): ") diff --git a/script/create_versionjson.py b/script/create_versionjson.py index 2416a3f..7747abc 100644 --- a/script/create_versionjson.py +++ b/script/create_versionjson.py @@ -36,6 +36,12 @@ def after_build(source, target, env): print( "Copy file : " + source + " -> " + target ) shutil.copyfile( source, target ) + # Copy file 4 + source = dir + "\\data\\calibration.min.htm" + target = dir + "\\bin\\calibration.min.htm" + print( "Copy file : " + source + " -> " + target ) + shutil.copyfile( source, target ) + target = dir + "\\bin\\version.json" ver = get_build_flag_value("CFG_APPVER") diff --git a/src/calc.cpp b/src/calc.cpp index e919d7b..fbf3408 100644 --- a/src/calc.cpp +++ b/src/calc.cpp @@ -24,14 +24,91 @@ SOFTWARE. #include "calc.h" #include "helper.h" #include "config.h" -#include "tinyexpr.h" +#include #include "tempsensor.h" +#include + +#define FORMULA_MAX_DEVIATION 1.5 + + +// +// Use values to derive a formula +// +int createFormula( RawFormulaData& fd, char *formulaBuffer, 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( noAngles==5 ) + sprintf( formulaBuffer, "%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2], coeffs[3] ); + else if( noAngles==4 ) + sprintf( formulaBuffer, "%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2] ); + else // ==3 + sprintf( formulaBuffer, "%.8f*tilt+%.8f", coeffs[0], coeffs[1] ); + + 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 ) { +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); diff --git a/src/calc.h b/src/calc.h index 8b07562..812721b 100644 --- a/src/calc.h +++ b/src/calc.h @@ -26,10 +26,16 @@ SOFTWARE. // Includes #include "helper.h" +#include "config.h" + +#define ERR_FORMULA_NOTENOUGHVALUES -1 +#define ERR_FORMULA_INTERNAL -2 +#define ERR_FORMULA_UNABLETOFFIND -3 // Functions -double calculateGravity( double angle, double temp ); +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 order); #endif // _CALC_H diff --git a/src/config.cpp b/src/config.cpp index ba66fc1..b5026f3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -32,11 +32,16 @@ Config myConfig; // Config::Config() { // Assiging default values - char buf[20]; + char buf[30]; sprintf(&buf[0], "%6x", (unsigned int) ESP.getChipId() ); - id = &buf[0]; + id = String( &buf[0] ); sprintf(&buf[0], "" WIFI_MDNS "%s", getID() ); - mDNS = &buf[0]; + mDNS = String( &buf[0] ); + +#if LOG_LEVEL==6 + 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 @@ -44,6 +49,7 @@ Config::Config() { 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; } @@ -77,6 +83,19 @@ void Config::createJson(DynamicJsonDocument& doc) { 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); + + 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); } // @@ -144,7 +163,7 @@ bool Config::loadFile() { #if LOG_LEVEL==6 serializeJson(doc, Serial); Serial.print( CR ); -#endif +#endif configFile.close(); if( err ) { @@ -207,6 +226,28 @@ bool Config::loadFile() { 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 ][ "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)); diff --git a/src/config.h b/src/config.h index cf3e8a7..1d41549 100644 --- a/src/config.h +++ b/src/config.h @@ -31,7 +31,7 @@ SOFTWARE. #include // defintions -#define CFG_JSON_BUFSIZE 2000 +#define CFG_JSON_BUFSIZE 3192 #define CFG_APPNAME "GravityMon " // Name of firmware #define CFG_FILENAME "/gravitymon.json" // Name of config file @@ -63,6 +63,8 @@ SOFTWARE. #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" @@ -87,6 +89,12 @@ struct RawGyroData { 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: @@ -100,7 +108,7 @@ class Config { float voltageFactor; float tempSensorAdj; // This value will be added to the read sensor value int sleepInterval; - + // Push target settings String brewfatherPushUrl; // URL For brewfather @@ -118,7 +126,8 @@ class Config { char gravityFormat; // G, P // Gyro calibration data - RawGyroData gyroCalibration; // Holds the gyro calibration constants (6 * int16_t) + RawGyroData gyroCalibration; // Holds the gyro calibration constants (6 * int16_t) + RawFormulaData formulaData; // Used for creating formula void debug(); void formatFileSystem(); @@ -189,6 +198,9 @@ class Config { 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(); diff --git a/src/gyro.h b/src/gyro.h index 89633c9..8b1b081 100644 --- a/src/gyro.h +++ b/src/gyro.h @@ -28,7 +28,7 @@ SOFTWARE. //#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE // Includes -#include +#include #include "MPU6050.h" #include "config.h" diff --git a/src/main.cpp b/src/main.cpp index a4a9cfe..ee48e85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ SOFTWARE. DoubleResetDetector *drd; // Define constats for this program -#if LOG_LEVEL==6 +#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 @@ -127,6 +127,11 @@ void setup() { if( dt ) Log.notice(F("Main: Detected doubletap on reset." CR)); +#ifdef DEACTIVATE_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"); @@ -241,7 +246,7 @@ void loop() { // Enter sleep mode if the conditions are right // ------------------------------------------------------------------------------------------------ // - if( goToSleep ) { + 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(); @@ -267,10 +272,10 @@ void loop() { //LOG_PERF_STOP("loop-batt-read"); loopMillis = millis(); -#if LOG_LEVEL==6 +//#if LOG_LEVEL==6 Log.verbose(F("Main: Heap %d kb FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getFreeSketchSpace()/1024 ); Log.verbose(F("Main: HeapFrag %d %%." CR), ESP.getHeapFragmentation() ); -#endif +//#endif } myWebServer.loop(); diff --git a/src/resources.cpp b/src/resources.cpp index b62e9a3..4746241 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -21,6 +21,7 @@ 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 ) @@ -29,6 +30,7 @@ SOFTWARE. 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 diff --git a/src/tempsensor.cpp b/src/tempsensor.cpp index f3e907e..17e18e5 100644 --- a/src/tempsensor.cpp +++ b/src/tempsensor.cpp @@ -58,8 +58,10 @@ void TempSensor::setup() { Log.notice(F("TSEN: Using temperature from gyro." CR)); #else // This code is used to read the DS18 temp sensor - if( mySensors.getDS18Count() ) + /*if( !mySensors.getDS18Count() ) { + Log.error(F("TSEN: No temperature sensors found." CR)); return; + }*/ #if LOG_LEVEL==6 Log.verbose(F("TSEN: Looking for temp sensors." CR)); @@ -107,6 +109,12 @@ float TempSensor::getValue() { 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(); diff --git a/src/webserver.cpp b/src/webserver.cpp index ef50e31..ee6d4fe 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -39,6 +39,7 @@ SOFTWARE. INCBIN_EXTERN(IndexHtm); INCBIN_EXTERN(DeviceHtm); INCBIN_EXTERN(ConfigHtm); +INCBIN_EXTERN(CalibrationHtm); INCBIN_EXTERN(AboutHtm); #else INCBIN_EXTERN(UploadHtm); @@ -46,6 +47,7 @@ INCBIN_EXTERN(UploadHtm); WebServer myWebServer; // My wrapper class fr webserver functions ESP8266WebServer server(80); +int lastFormulaCreateError = 0; extern bool sleepModeActive; extern bool sleepModeAlwaysSkip; @@ -116,10 +118,11 @@ void webHandleUpload() { 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[ "about" ] = myWebServer.checkHtmlFile( WebServer::HTML_ABOUT ); + 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 serializeJson(doc, Serial); @@ -145,7 +148,7 @@ void webHandleUploadFile() { String f = upload.filename; bool validFilename = false; - if( f.equalsIgnoreCase("index.min.htm") || f.equalsIgnoreCase("device.min.htm") || + 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; } @@ -408,6 +411,127 @@ void webHandleConfigHardware() { LOG_PERF_STOP("webserver-api-config-hardware"); } +// +// Callback from webServer when / has been accessed. +// +void webHandleFormulaRead() { + LOG_PERF_START("webserver-api-formula-read"); +#if LOG_LEVEL==6 + Log.verbose(F("WEB : webServer callback for /api/formula/get." CR)); +#endif + DynamicJsonDocument doc(250); + const RawFormulaData& fd = myConfig.getFormulaData(); + +#if LOG_LEVEL==6 + 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 + 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 webHandleFormulaWrite() { + LOG_PERF_START("webserver-api-formula-write"); + String id = server.arg( CFG_PARAM_ID ); +#if LOG_LEVEL==6 + Log.verbose(F("WEB : webServer callback for /api/formula/post." CR) ); +#endif + 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 + 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], 4 ); + + if( e ) { + // If we fail with order=4 try with 3 + Log.warning(F("WEB : Failed to find formula with order 4." CR), e ); + e = createFormula( fd, &buf[0], 3 ); + } + + if( e ) { + // If we fail with order=3 try with 2 + Log.warning(F("WEB : Failed to find formula with order 3." CR), e ); + e = createFormula( fd, &buf[0], 2 ); + } + + if( e ) { + // If we fail with order=2 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. // @@ -422,6 +546,8 @@ const char* WebServer::getHtmlFileName( HtmlFile item ) { 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"; } @@ -467,6 +593,9 @@ bool WebServer::setupWebServer() { server.on("/config.htm",[]() { server.send_P(200, "text/html", (const char*) gConfigHtmData, gConfigHtmSize ); } ); + server.on("/calibration.htm",[]() { + server.send_P(200, "text/html", (const char*) gCalibrationHtmData, gCalibrationHtmSize ); + } ); server.on("/about.htm",[]() { server.send_P(200, "text/html", (const char*) gAboutHtmData, gAboutHtmSize ); } ); @@ -490,6 +619,7 @@ bool WebServer::setupWebServer() { 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",[]() { @@ -507,6 +637,8 @@ bool WebServer::setupWebServer() { // Dynamic content server.on("/api/config", HTTP_GET, webHandleConfig); // Get config.json server.on("/api/device", HTTP_GET, webHandleDevice); // Get device.json + server.on("/api/formula", HTTP_GET, webHandleFormulaRead); // Get formula.json (calibration page) + server.on("/api/formula", HTTP_POST, webHandleFormulaWrite); // Get formula.json (calibration page) server.on("/api/calibrate", HTTP_POST, webHandleCalibrate); // Run calibration routine (param id) server.on("/api/factory", HTTP_GET, webHandleFactoryReset); // Reset the device server.on("/api/status", HTTP_GET, webHandleStatus); // Get the status.json diff --git a/src/webserver.h b/src/webserver.h index 5252055..673e859 100644 --- a/src/webserver.h +++ b/src/webserver.h @@ -33,7 +33,8 @@ class WebServer { HTML_INDEX = 0, HTML_DEVICE = 1, HTML_CONFIG = 2, - HTML_ABOUT = 3 + HTML_ABOUT = 3, + HTML_CALIBRATION = 4 }; bool setupWebServer(); diff --git a/src/wifi.cpp b/src/wifi.cpp index 933dc89..01da5ab 100644 --- a/src/wifi.cpp +++ b/src/wifi.cpp @@ -32,36 +32,31 @@ SOFTWARE. #include #include #include +//#include // TEST +//#include // TEST #include #include Wifi myWifi; -WiFiManager myWifiManager; -bool shouldSaveConfig = false; const char* userSSID= USER_SSID; const char* userPWD = USER_SSID_PWD; -// -// Callback notifying us of the need to save config -// -void saveConfigCallback () { - shouldSaveConfig = true; -} - // // Connect to last known access point or create one if connection is not working. // bool Wifi::connect( bool showPortal ) { #if LOG_LEVEL==6 Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false"); + + WiFiManager myWifiManager; myWifiManager.setDebugOutput(true); #endif if( strlen(userSSID)==0 && showPortal ) { Log.notice(F("WIFI: Starting wifi portal." CR)); myWifiManager.setBreakAfterConfig( true ); - myWifiManager.setSaveConfigCallback(saveConfigCallback); + myWifiManager.setSaveConfigCallback(std::bind(&Wifi::setSaveConfig, this)); myWifiManager.setMinimumSignalQuality(10); myWifiManager.setClass("invert"); myWifiManager.setHostname( myConfig.getMDNS() ); @@ -82,6 +77,7 @@ bool Wifi::connect( bool showPortal ) { int i = 0; Log.notice(F("WIFI: Connecting to WIFI." CR)); + //WiFi.persistent(false); WiFi.mode(WIFI_STA); if( strlen(userSSID) ) { Log.notice(F("WIFI: Connecting to wifi using predefined settings %s." CR), userSSID); @@ -97,16 +93,16 @@ bool Wifi::connect( bool showPortal ) { delay(100); Serial.print( "." ); -// if( i++ > 60 ) { // Try for 6 seconds. if( i++ > 200 ) { // Try for 20 seconds. - Log.error(F("WIFI: Failed to connect to wifi, aborting." CR)); + Log.error(F("WIFI: Failed to connect to wifi %d, aborting." CR), WiFi.status() ); + 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%s." CR), myConfig.getMDNS() ); + Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS() ); return connectedFlag; } diff --git a/src/wifi.h b/src/wifi.h index bf719e0..c413f04 100644 --- a/src/wifi.h +++ b/src/wifi.h @@ -31,12 +31,14 @@ SOFTWARE. class Wifi { private: // WIFI - bool connectedFlag = false; + bool connectedFlag = false; + bool shouldSaveConfig = false; // OTA bool newFirmware = false; bool parseFirmwareVersionString( int (&num)[3], const char *version ); void downloadFile(const char *fname); + void setSaveConfig() { shouldSaveConfig = true; } public: // WIFI diff --git a/test/formula.json b/test/formula.json new file mode 100644 index 0000000..88a8ea6 --- /dev/null +++ b/test/formula.json @@ -0,0 +1,14 @@ +{ + "gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436", + "angle": 45, + "a1": 25, + "a2": 35, + "a3": 45, + "a4": 55, + "a5": 65, + "g1": 1.000, + "g2": 1.010, + "g3": 1.020, + "g4": 1.030, + "g5": 1.040 +} \ No newline at end of file diff --git a/test/upload.json b/test/upload.json index 38dc931..10d8f2b 100644 --- a/test/upload.json +++ b/test/upload.json @@ -2,5 +2,6 @@ "index": false, "device": false, "config": false, + "calibration": false, "about": true } \ No newline at end of file