Prel code for formula creation

This commit is contained in:
Magnus Persson 2022-01-03 18:30:57 +01:00
parent 48f71bd59a
commit d075fe2117
26 changed files with 626 additions and 61 deletions

View File

@ -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. After 6 months of testing I believe this is working as planned. It give accurate readings same as the orginal iSpindel software.
Version history Version history
* v0.5 Added support for creating formula on device + bug fixes.
* v0.4 First official version with 5+ brews on record. * v0.4 First official version with 5+ brews on record.
Lower priority Lower priority
@ -127,11 +128,19 @@ http://192.168.1.1/firmware/gravmon/
Contents version.json Contents version.json
``` ```
{ "project":"gravmon", "version":"0.3.0" } { "project":"gravmon", "version":"0.5.0" }
``` ```
![Config - Hardware](img/config4.png) ![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 # Building a device
See the iSpindle documentation for building a device. See the iSpindle documentation for building a device.

243
html/calibration.htm Normal file
View File

@ -0,0 +1,243 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/calibration.htm">Calibration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about.htm">About</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-2">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('.alert').addClass('d-none').removeClass('show')
});
</script>
<div class="accordion" id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Formula calculation
</button>
</h2>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body">
<form action="/api/formula" method="post">
<input type="text" name="id" id="id" hidden>
<div class="row mb-3">
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.
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">#:</label>
<label class="col-sm-4 col-form-label">Angle:</label>
<label class="col-sm-4 col-form-label">Gravity (SG):</label>
</div>
<div class="form-group row">
<label for="angle1" class="col-sm-2 col-form-label">1.</label>
<div class="col-sm-4">
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a1" id="a1">
</div>
<div class="col-sm-4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g1" id="g1">
</div>
</div>
<div class="form-group row">
<label for="angle2" class="col-sm-2 col-form-label">2.</label>
<div class="col-sm-4">
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a2" id="a2">
</div>
<div class="col-sm-4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g2" id="g2">
</div>
</div>
<div class="form-group row">
<label for="angle3" class="col-sm-2 col-form-label">3.</label>
<div class="col-sm-4">
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a3" id="a3">
</div>
<div class="col-sm-4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g3" id="g3">
</div>
</div>
<div class="form-group row">
<label for="angle4" class="col-sm-2 col-form-label">4.</label>
<div class="col-sm-4">
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a4" id="a4">
</div>
<div class="col-sm-4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g4" id="g4">
</div>
</div>
<div class="form-group row">
<label for="angle5" class="col-sm-2 col-form-label">5.</label>
<div class="col-sm-4">
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a5" id="a5">
</div>
<div class="col-sm-4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g5" id="g5">
</div>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-0">
<button type="submit" class="btn btn-primary" id="calculate-btn">Save & Calculate</button>
</div>
</div>
<div class="form-group row">
<label for="calculate-btn" class="col-sm-2 col-form-label">Current angle: </label>
<label for="calculate-btn" class="col-sm-2 col-form-label" id="angle"></label>
</div>
<div class="form-group row">
<label for="calculate-btn" class="col-sm-2 col-form-label">Formula: </label>
<label for="calculate-btn" class="col-sm-8 col-form-label" id="formula">Loading...</label>
</div>
</form>
</div>
</div>
</div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
g1.onchange = setFourNumberDecimal
g2.onchange = setFourNumberDecimal
g3.onchange = setFourNumberDecimal
g4.onchange = setFourNumberDecimal
g5.onchange = setFourNumberDecimal
a1.onchange = setTwoNumberDecimal
a2.onchange = setTwoNumberDecimal
a3.onchange = setTwoNumberDecimal
a4.onchange = setTwoNumberDecimal
a5.onchange = setTwoNumberDecimal
window.onload = getConfig;
setButtonDisabled( true );
function setTwoNumberDecimal(event) {
this.value = parseFloat(this.value).toFixed(2);
}
function setFourNumberDecimal(event) {
this.value = parseFloat(this.value).toFixed(4);
}
function setButtonDisabled( b ) {
$("#calculate-btn").prop("disabled", b);
}
// Get the configuration values from the API
function getConfig() {
setButtonDisabled( true );
var url = "/api/formula";
//var url = "/test/formula.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#id").val(cfg["id"]);
$("#angle").text(cfg["angle"]);
$("#formula").text(cfg["gravity-formula"]);
$("#a1").val( parseFloat(cfg["a1"]).toFixed(2) );
$("#a2").val( parseFloat(cfg["a2"]).toFixed(2) );
$("#a3").val( parseFloat(cfg["a3"]).toFixed(2) );
$("#a4").val( parseFloat(cfg["a4"]).toFixed(2) );
$("#a5").val( parseFloat(cfg["a5"]).toFixed(2) );
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

1
html/calibration.min.htm Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -15,12 +15,13 @@ include_dir = lib
[common_env_data] [common_env_data]
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
platform = espressif8266 #platform = espressif8266 @ 2.6.3
platform = espressif8266 @ 3.2.0
framework = arduino framework = arduino
board = d1_mini board = d1_mini
build_unflags = build_unflags =
0src_build_flags = -Wunused-variable -Wregister -Wchar-subscripts #src_build_flags = -Wunused-variable -Wregister -Wchar-subscripts
build_flags = #-O0 -Wl,-Map,output.map build_flags =
-D BAUD=${common_env_data.monitor_speed} -D BAUD=${common_env_data.monitor_speed}
-D ACTIVATE_OTA -D ACTIVATE_OTA
#-D USE_GYRO_TEMP # If this is enabled the DS18 will not be used, temp is read from the gyro. #-D USE_GYRO_TEMP # If this is enabled the DS18 will not be used, temp is read from the gyro.
@ -39,12 +40,13 @@ lib_deps = # Switched to forks for better version control.
#https://github.com/mp-se/i2cdevlib # https://github.com/jrowberg/i2cdevlib.git #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/tinyexpr # https://github.com/codeplea/tinyexpr
https://github.com/mp-se/incbin # https://github.com/graphitemaster/incbin 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/ESP_DoubleResetDetector#v1.2.1 # https://github.com/khoih-prog/ESP_DoubleResetDetector
https://github.com/mp-se/WiFiManager # https://github.com/tzapu/WiFiManager https://github.com/mp-se/WiFiManager#2.0.5-beta # https://github.com/tzapu/WiFiManager
https://github.com/mp-se/Arduino-Log # https://github.com/thijse/Arduino-Log https://github.com/mp-se/Arduino-Log#1.1.1 # https://github.com/thijse/Arduino-Log
https://github.com/mp-se/ArduinoJson # https://github.com/bblanchon/ArduinoJson https://github.com/mp-se/ArduinoJson#v6.18.5 # https://github.com/bblanchon/ArduinoJson
https://github.com/mp-se/OneWire # https://github.com/PaulStoffregen/OneWire https://github.com/mp-se/OneWire#v2.3.6 # 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/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] [env:gravity-debug]
upload_speed = ${common_env_data.upload_speed} upload_speed = ${common_env_data.upload_speed}
@ -52,10 +54,14 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework} framework = ${common_env_data.framework}
platform = ${common_env_data.platform} platform = ${common_env_data.platform}
extra_scripts = extra_scripts =
script/copy_html.py
script/copy_firmware.py script/copy_firmware.py
script/create_versionjson.py script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags} build_unflags =
${common_env_data.build_unflags}
build_flags = build_flags =
-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
#-D DEACTIVATE_SLEEPMODE
${common_env_data.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 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 LOG_LEVEL=6 # Maximum log level for the debug build.
@ -64,6 +70,7 @@ lib_deps =
board = ${common_env_data.board} board = ${common_env_data.board}
build_type = debug build_type = debug
board_build.filesystem = littlefs board_build.filesystem = littlefs
monitor_filters = esp8266_exception_decoder
[env:gravity-release] [env:gravity-release]
upload_speed = ${common_env_data.upload_speed} upload_speed = ${common_env_data.upload_speed}
@ -71,6 +78,7 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework} framework = ${common_env_data.framework}
platform = ${common_env_data.platform} platform = ${common_env_data.platform}
extra_scripts = extra_scripts =
script/copy_html.py
script/copy_firmware.py script/copy_firmware.py
script/create_versionjson.py script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags} build_unflags = ${common_env_data.build_unflags}
@ -89,6 +97,7 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework} framework = ${common_env_data.framework}
platform = ${common_env_data.platform} platform = ${common_env_data.platform}
extra_scripts = extra_scripts =
script/copy_html.py
script/copy_firmware.py script/copy_firmware.py
script/create_versionjson.py script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags} build_unflags = ${common_env_data.build_unflags}

View File

@ -5,25 +5,25 @@ print( "Executing custom step " )
dir = env.GetLaunchDir() dir = env.GetLaunchDir()
source = dir + "/html/" source = dir + "/html/"
target = dir + "/data/" target = dir + "/data/"
print( "Copy file : " + source + " -> " + target ) print( "Copy html-files from " + source + " -> " + target )
file = "about.min.htm" file = "about.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
file = "calibration.min.htm" file = "calibration.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
file = "config.min.htm" file = "config.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
file = "device.min.htm" file = "device.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
file = "index.min.htm" file = "index.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
file = "upload.min.htm" file = "upload.min.htm"
print( "Copy file: " + source + file + "->" + target + file) #print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file ) shutil.copyfile( source + file, target + file )
#print( "Adding custom build step (copy ./heml/*.min.html to ./data/): ") #print( "Adding custom build step (copy ./heml/*.min.html to ./data/): ")

View File

@ -36,6 +36,12 @@ def after_build(source, target, env):
print( "Copy file : " + source + " -> " + target ) print( "Copy file : " + source + " -> " + target )
shutil.copyfile( 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" target = dir + "\\bin\\version.json"
ver = get_build_flag_value("CFG_APPVER") ver = get_build_flag_value("CFG_APPVER")

View File

@ -24,14 +24,91 @@ SOFTWARE.
#include "calc.h" #include "calc.h"
#include "helper.h" #include "helper.h"
#include "config.h" #include "config.h"
#include "tinyexpr.h" #include <tinyexpr.h>
#include "tempsensor.h" #include "tempsensor.h"
#include <curveFitting.h>
#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 // 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(); 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 #if LOG_LEVEL==6
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp); Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp);
Log.verbose(F("CALC: Formula %s." CR), formula); Log.verbose(F("CALC: Formula %s." CR), formula);

View File

@ -26,10 +26,16 @@ SOFTWARE.
// Includes // Includes
#include "helper.h" #include "helper.h"
#include "config.h"
#define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3
// Functions // 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 ); double gravityTemperatureCorrection( double gravity, double temp, char tempFormat, double calTemp = 20 );
int createFormula( RawFormulaData& fd, char *formulaBuffer, int order);
#endif // _CALC_H #endif // _CALC_H

View File

@ -32,11 +32,16 @@ Config myConfig;
// //
Config::Config() { Config::Config() {
// Assiging default values // Assiging default values
char buf[20]; char buf[30];
sprintf(&buf[0], "%6x", (unsigned int) ESP.getChipId() ); sprintf(&buf[0], "%6x", (unsigned int) ESP.getChipId() );
id = &buf[0]; id = String( &buf[0] );
sprintf(&buf[0], "" WIFI_MDNS "%s", getID() ); 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'); setTempFormat('C');
setGravityFormat('G'); setGravityFormat('G');
setSleepInterval(900); // 15 minutes setSleepInterval(900); // 15 minutes
@ -44,6 +49,7 @@ Config::Config() {
setTempSensorAdj(0.0); setTempSensorAdj(0.0);
setGravityTempAdj(false); setGravityTempAdj(false);
gyroCalibration = { 0, 0, 0, 0, 0 ,0 }; gyroCalibration = { 0, 0, 0, 0, 0 ,0 };
formulaData = {{ 0, 0, 0, 0, 0},{ 1, 1, 1, 1, 1}};
saveNeeded = false; saveNeeded = false;
} }
@ -77,6 +83,19 @@ void Config::createJson(DynamicJsonDocument& doc) {
cal["gx"] = gyroCalibration.gx; cal["gx"] = gyroCalibration.gx;
cal["gy"] = gyroCalibration.gy; cal["gy"] = gyroCalibration.gy;
cal["gz"] = gyroCalibration.gz; cal["gz"] = gyroCalibration.gz;
JsonObject cal2 = doc.createNestedObject( CFG_PARAM_FORMULA_DATA );
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);
} }
// //
@ -207,6 +226,28 @@ bool Config::loadFile() {
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"].isNull() ) if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"].isNull() )
gyroCalibration.gz = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"]; gyroCalibration.gz = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"];
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "a1" ].isNull() )
formulaData.a[0] = doc[ CFG_PARAM_FORMULA_DATA ][ "a1" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].isNull() )
formulaData.a[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].isNull() )
formulaData.a[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].isNull() )
formulaData.a[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].isNull() )
formulaData.a[4] = doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].isNull() )
formulaData.g[0] = doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].isNull() )
formulaData.g[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].isNull() )
formulaData.g[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].isNull() )
formulaData.g[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].as<double>();
if( !doc[ CFG_PARAM_FORMULA_DATA ][ "g5" ].isNull() )
formulaData.g[4] = doc[ CFG_PARAM_FORMULA_DATA ][ "g5" ];
myConfig.debug(); myConfig.debug();
saveNeeded = false; // Reset save flag saveNeeded = false; // Reset save flag
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR)); Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));

View File

@ -31,7 +31,7 @@ SOFTWARE.
#include <stdlib.h> #include <stdlib.h>
// defintions // defintions
#define CFG_JSON_BUFSIZE 2000 #define CFG_JSON_BUFSIZE 3192
#define CFG_APPNAME "GravityMon " // Name of firmware #define CFG_APPNAME "GravityMon " // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file #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_TEMP_ADJ "temp-adjustment-value" // Correction value for temp sensor
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY #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 // These are used in API's
#define CFG_PARAM_APP_NAME "app-name" #define CFG_PARAM_APP_NAME "app-name"
#define CFG_PARAM_APP_VER "app-ver" #define CFG_PARAM_APP_VER "app-ver"
@ -87,6 +89,12 @@ struct RawGyroData {
int16_t temp; // Only for information (temperature of chip) 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 // Main configuration class
class Config { class Config {
private: private:
@ -119,6 +127,7 @@ class Config {
// Gyro calibration data // 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 debug();
void formatFileSystem(); void formatFileSystem();
@ -189,6 +198,9 @@ class Config {
const RawGyroData& getGyroCalibration() { return gyroCalibration; } const RawGyroData& getGyroCalibration() { return gyroCalibration; }
void setGyroCalibration( const RawGyroData &r ) { gyroCalibration = r; saveNeeded = true; } 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 // IO functions
void createJson(DynamicJsonDocument& doc); void createJson(DynamicJsonDocument& doc);
bool saveFile(); bool saveFile();

View File

@ -28,7 +28,7 @@ SOFTWARE.
//#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE //#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
// Includes // Includes
#include <arduino.h> #include <Arduino.h>
#include "MPU6050.h" #include "MPU6050.h"
#include "config.h" #include "config.h"

View File

@ -39,7 +39,7 @@ SOFTWARE.
DoubleResetDetector *drd; DoubleResetDetector *drd;
// Define constats for this program // Define constats for this program
#if LOG_LEVEL==6 #ifdef DEACTIVATE_SLEEPMODE
const int interval = 1000; // ms, time to wait between changes to output const int interval = 1000; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
#else #else
@ -127,6 +127,11 @@ void setup() {
if( dt ) if( dt )
Log.notice(F("Main: Detected doubletap on reset." CR)); 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"); LOG_PERF_START("main-wifi-connect");
myWifi.connect( dt ); // This will return false if unable to connect to wifi, will be handled in loop() 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_STOP("main-wifi-connect");
@ -241,7 +246,7 @@ void loop() {
// Enter sleep mode if the conditions are right // 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 ); 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(); LittleFS.end();
myGyro.enterSleep(); myGyro.enterSleep();
@ -267,10 +272,10 @@ void loop() {
//LOG_PERF_STOP("loop-batt-read"); //LOG_PERF_STOP("loop-batt-read");
loopMillis = millis(); 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: Heap %d kb FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getFreeSketchSpace()/1024 );
Log.verbose(F("Main: HeapFrag %d %%." CR), ESP.getHeapFragmentation() ); Log.verbose(F("Main: HeapFrag %d %%." CR), ESP.getHeapFragmentation() );
#endif //#endif
} }
myWebServer.loop(); myWebServer.loop();

View File

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#define INCBIN_OUTPUT_SECTION ".irom.text"
#include <incbin.h> #include <incbin.h>
#if defined( EMBED_HTML ) #if defined( EMBED_HTML )
@ -29,6 +30,7 @@ SOFTWARE.
INCBIN(IndexHtm, "data/index.min.htm" ); INCBIN(IndexHtm, "data/index.min.htm" );
INCBIN(DeviceHtm, "data/device.min.htm" ); INCBIN(DeviceHtm, "data/device.min.htm" );
INCBIN(ConfigHtm, "data/config.min.htm" ); INCBIN(ConfigHtm, "data/config.min.htm" );
INCBIN(CalibrationHtm, "data/calibration.min.htm" );
INCBIN(AboutHtm, "data/about.min.htm" ); INCBIN(AboutHtm, "data/about.min.htm" );
#else #else

View File

@ -58,8 +58,10 @@ void TempSensor::setup() {
Log.notice(F("TSEN: Using temperature from gyro." CR)); Log.notice(F("TSEN: Using temperature from gyro." CR));
#else #else
// This code is used to read the DS18 temp sensor // 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; return;
}*/
#if LOG_LEVEL==6 #if LOG_LEVEL==6
Log.verbose(F("TSEN: Looking for temp sensors." CR)); 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); Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
#endif #endif
#else #else
// If we dont have sensors just return 0
if( !mySensors.getDS18Count() ) {
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
return -273;
}
// Read the sensors // Read the sensors
//LOG_PERF_START("temp-request"); //LOG_PERF_START("temp-request");
mySensors.requestTemperatures(); mySensors.requestTemperatures();

View File

@ -39,6 +39,7 @@ SOFTWARE.
INCBIN_EXTERN(IndexHtm); INCBIN_EXTERN(IndexHtm);
INCBIN_EXTERN(DeviceHtm); INCBIN_EXTERN(DeviceHtm);
INCBIN_EXTERN(ConfigHtm); INCBIN_EXTERN(ConfigHtm);
INCBIN_EXTERN(CalibrationHtm);
INCBIN_EXTERN(AboutHtm); INCBIN_EXTERN(AboutHtm);
#else #else
INCBIN_EXTERN(UploadHtm); INCBIN_EXTERN(UploadHtm);
@ -46,6 +47,7 @@ INCBIN_EXTERN(UploadHtm);
WebServer myWebServer; // My wrapper class fr webserver functions WebServer myWebServer; // My wrapper class fr webserver functions
ESP8266WebServer server(80); ESP8266WebServer server(80);
int lastFormulaCreateError = 0;
extern bool sleepModeActive; extern bool sleepModeActive;
extern bool sleepModeAlwaysSkip; extern bool sleepModeAlwaysSkip;
@ -119,6 +121,7 @@ void webHandleUpload() {
doc[ "index" ] = myWebServer.checkHtmlFile( WebServer::HTML_INDEX ); doc[ "index" ] = myWebServer.checkHtmlFile( WebServer::HTML_INDEX );
doc[ "device" ] = myWebServer.checkHtmlFile( WebServer::HTML_DEVICE ); doc[ "device" ] = myWebServer.checkHtmlFile( WebServer::HTML_DEVICE );
doc[ "config" ] = myWebServer.checkHtmlFile( WebServer::HTML_CONFIG ); doc[ "config" ] = myWebServer.checkHtmlFile( WebServer::HTML_CONFIG );
doc[ "calibration" ] = myWebServer.checkHtmlFile( WebServer::HTML_CALIBRATION );
doc[ "about" ] = myWebServer.checkHtmlFile( WebServer::HTML_ABOUT ); doc[ "about" ] = myWebServer.checkHtmlFile( WebServer::HTML_ABOUT );
#if LOG_LEVEL==6 #if LOG_LEVEL==6
@ -145,7 +148,7 @@ void webHandleUploadFile() {
String f = upload.filename; String f = upload.filename;
bool validFilename = false; 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") ) { f.equalsIgnoreCase("config.min.htm") || f.equalsIgnoreCase("about.min.htm") ) {
validFilename = true; validFilename = true;
} }
@ -408,6 +411,127 @@ void webHandleConfigHardware() {
LOG_PERF_STOP("webserver-api-config-hardware"); 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. // Helper function to check if files exist on file system.
// //
@ -422,6 +546,8 @@ const char* WebServer::getHtmlFileName( HtmlFile item ) {
return "device.min.htm"; return "device.min.htm";
case HTML_CONFIG: case HTML_CONFIG:
return "config.min.htm"; return "config.min.htm";
case HTML_CALIBRATION:
return "calibration.min.htm";
case HTML_ABOUT: case HTML_ABOUT:
return "about.min.htm"; return "about.min.htm";
} }
@ -467,6 +593,9 @@ bool WebServer::setupWebServer() {
server.on("/config.htm",[]() { server.on("/config.htm",[]() {
server.send_P(200, "text/html", (const char*) gConfigHtmData, gConfigHtmSize ); 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.on("/about.htm",[]() {
server.send_P(200, "text/html", (const char*) gAboutHtmData, gAboutHtmSize ); 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("/device.htm", LittleFS, "/device.min.htm" );
server.serveStatic("/config.htm", LittleFS, "/config.min.htm" ); server.serveStatic("/config.htm", LittleFS, "/config.min.htm" );
server.serveStatic("/about.htm", LittleFS, "/about.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. // Also add the static upload view in case we we have issues that needs to be fixed.
server.on("/upload.htm",[]() { server.on("/upload.htm",[]() {
@ -507,6 +637,8 @@ bool WebServer::setupWebServer() {
// Dynamic content // Dynamic content
server.on("/api/config", HTTP_GET, webHandleConfig); // Get config.json server.on("/api/config", HTTP_GET, webHandleConfig); // Get config.json
server.on("/api/device", HTTP_GET, webHandleDevice); // Get device.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/calibrate", HTTP_POST, webHandleCalibrate); // Run calibration routine (param id)
server.on("/api/factory", HTTP_GET, webHandleFactoryReset); // Reset the device server.on("/api/factory", HTTP_GET, webHandleFactoryReset); // Reset the device
server.on("/api/status", HTTP_GET, webHandleStatus); // Get the status.json server.on("/api/status", HTTP_GET, webHandleStatus); // Get the status.json

View File

@ -33,7 +33,8 @@ class WebServer {
HTML_INDEX = 0, HTML_INDEX = 0,
HTML_DEVICE = 1, HTML_DEVICE = 1,
HTML_CONFIG = 2, HTML_CONFIG = 2,
HTML_ABOUT = 3 HTML_ABOUT = 3,
HTML_CALIBRATION = 4
}; };
bool setupWebServer(); bool setupWebServer();

View File

@ -32,36 +32,31 @@ SOFTWARE.
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h> #include <ESP8266httpUpdate.h>
#include <WiFiManager.h> #include <WiFiManager.h>
//#include <ESP_WiFiManager.h> // TEST
//#include <ESP_WiFiManager-Impl.h> // TEST
#include <LittleFS.h> #include <LittleFS.h>
#include <incbin.h> #include <incbin.h>
Wifi myWifi; Wifi myWifi;
WiFiManager myWifiManager;
bool shouldSaveConfig = false;
const char* userSSID= USER_SSID; const char* userSSID= USER_SSID;
const char* userPWD = USER_SSID_PWD; 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. // Connect to last known access point or create one if connection is not working.
// //
bool Wifi::connect( bool showPortal ) { bool Wifi::connect( bool showPortal ) {
#if LOG_LEVEL==6 #if LOG_LEVEL==6
Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false"); Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false");
WiFiManager myWifiManager;
myWifiManager.setDebugOutput(true); myWifiManager.setDebugOutput(true);
#endif #endif
if( strlen(userSSID)==0 && showPortal ) { if( strlen(userSSID)==0 && showPortal ) {
Log.notice(F("WIFI: Starting wifi portal." CR)); Log.notice(F("WIFI: Starting wifi portal." CR));
myWifiManager.setBreakAfterConfig( true ); myWifiManager.setBreakAfterConfig( true );
myWifiManager.setSaveConfigCallback(saveConfigCallback); myWifiManager.setSaveConfigCallback(std::bind(&Wifi::setSaveConfig, this));
myWifiManager.setMinimumSignalQuality(10); myWifiManager.setMinimumSignalQuality(10);
myWifiManager.setClass("invert"); myWifiManager.setClass("invert");
myWifiManager.setHostname( myConfig.getMDNS() ); myWifiManager.setHostname( myConfig.getMDNS() );
@ -82,6 +77,7 @@ bool Wifi::connect( bool showPortal ) {
int i = 0; int i = 0;
Log.notice(F("WIFI: Connecting to WIFI." CR)); Log.notice(F("WIFI: Connecting to WIFI." CR));
//WiFi.persistent(false);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
if( strlen(userSSID) ) { if( strlen(userSSID) ) {
Log.notice(F("WIFI: Connecting to wifi using predefined settings %s." CR), 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); delay(100);
Serial.print( "." ); Serial.print( "." );
// if( i++ > 60 ) { // Try for 6 seconds.
if( i++ > 200 ) { // Try for 20 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. return connectedFlag; // Return to main that we have failed to connect.
} }
} }
Serial.print( CR ); Serial.print( CR );
connectedFlag = true; connectedFlag = true;
Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str() ); 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; return connectedFlag;
} }

View File

@ -32,11 +32,13 @@ class Wifi {
private: private:
// WIFI // WIFI
bool connectedFlag = false; bool connectedFlag = false;
bool shouldSaveConfig = false;
// OTA // OTA
bool newFirmware = false; bool newFirmware = false;
bool parseFirmwareVersionString( int (&num)[3], const char *version ); bool parseFirmwareVersionString( int (&num)[3], const char *version );
void downloadFile(const char *fname); void downloadFile(const char *fname);
void setSaveConfig() { shouldSaveConfig = true; }
public: public:
// WIFI // WIFI

14
test/formula.json Normal file
View File

@ -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
}

View File

@ -2,5 +2,6 @@
"index": false, "index": false,
"device": false, "device": false,
"config": false, "config": false,
"calibration": false,
"about": true "about": true
} }