Prel code for formula creation
11
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" }
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
# Building a device
|
||||
|
||||
See the iSpindle documentation for building a device.
|
||||
|
243
html/calibration.htm
Normal 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">×</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
BIN
img/config1.png
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 26 KiB |
BIN
img/config2.png
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 37 KiB |
BIN
img/config3.png
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 22 KiB |
BIN
img/config4.png
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 23 KiB |
BIN
img/device.png
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
img/index.png
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
@ -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}
|
||||
|
@ -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/): ")
|
||||
|
@ -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")
|
||||
|
||||
|
81
src/calc.cpp
@ -24,14 +24,91 @@ SOFTWARE.
|
||||
#include "calc.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "tinyexpr.h"
|
||||
#include <tinyexpr.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
|
||||
//
|
||||
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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<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();
|
||||
saveNeeded = false; // Reset save flag
|
||||
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
|
||||
|
18
src/config.h
@ -31,7 +31,7 @@ SOFTWARE.
|
||||
#include <stdlib.h>
|
||||
|
||||
// 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();
|
||||
|
@ -28,7 +28,7 @@ SOFTWARE.
|
||||
//#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
|
||||
|
||||
// Includes
|
||||
#include <arduino.h>
|
||||
#include <Arduino.h>
|
||||
#include "MPU6050.h"
|
||||
#include "config.h"
|
||||
|
||||
|
13
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();
|
||||
|
@ -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 <incbin.h>
|
||||
|
||||
#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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
22
src/wifi.cpp
@ -32,36 +32,31 @@ SOFTWARE.
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#include <WiFiManager.h>
|
||||
//#include <ESP_WiFiManager.h> // TEST
|
||||
//#include <ESP_WiFiManager-Impl.h> // TEST
|
||||
#include <LittleFS.h>
|
||||
#include <incbin.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
14
test/formula.json
Normal 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
|
||||
}
|
@ -2,5 +2,6 @@
|
||||
"index": false,
|
||||
"device": false,
|
||||
"config": false,
|
||||
"calibration": false,
|
||||
"about": true
|
||||
}
|