Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a597162ae | |||
f49f386569 | |||
008ad490a7 | |||
3533ee8dac | |||
2b9abda873 | |||
4bbb558c8b | |||
8ebbc6559f | |||
0af872e743 | |||
c20f9a534a | |||
10ce1fc245 | |||
b43874d151 | |||
1428bec3da | |||
f9791dd349 | |||
1a7f28413c | |||
d22309bb2e | |||
b901a12699 | |||
914b4125d8 | |||
95216ecc54 | |||
4d83bf8fce | |||
e125ca4a10 | |||
5880d3a6ba | |||
cda3a87dd9 | |||
4bcacea9d7 | |||
838d062eea | |||
77cdbf7649 | |||
f33a58cffe | |||
7ca536b216 | |||
f0ec352538 | |||
e336633c38 | |||
a130ebd67d | |||
4c789a8b37 | |||
7ab5f451f5 | |||
545f274a47 | |||
9bea54b703 | |||
35333469c7 | |||
a9e0d9290a | |||
e459ceb2fa | |||
2615debe35 | |||
6113a436b0 | |||
2d5158465f | |||
3af52b5464 | |||
c116d672d1 | |||
e1cc54d188 | |||
761d570d39 | |||
22ade61af8 | |||
928054458a | |||
2e67bd1d57 | |||
83d7aee944 | |||
8bcd27a076 | |||
0912d672fe | |||
01d7c8adb5 | |||
69076d5878 | |||
3d939ad733 | |||
f2112cb344 | |||
f88fb8241b | |||
6eed5f143b | |||
64e582d0e5 | |||
5d0f02eb18 | |||
fbc1eb4e31 | |||
700f00f48d | |||
9a2f86fed7 | |||
63fd80e750 | |||
b106ebfa20 |
@ -1 +1 @@
|
||||
<!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 active"><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"><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-4"><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(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.7.1)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" 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 active"><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"><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-4"><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(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Average runtime:</div><div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div></div><div class="row mb-3"><a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">View error log</a></div><script>function loadLog(){$("#logContent").load("/log")}$("#log-btn").click(function(o){loadLog()}),setInterval(function(){loadLog()},3e3)</script><div class="collapse" id="collapseLog"><div class="card card-body"><pre><code id="logContent"></code></pre></div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var e="/api/device";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#app-ver").text(e["app-ver"]+" (html 0.8.0)"),$("#mdns").text(e.mdns),$("#id").text(e.id),$("#runtime").text(e["runtime-average"]+" seconds")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
BIN
bin/firmware.bin
@ -1,2 +1,2 @@
|
||||
<!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="/config.htm">Back to configuration</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(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#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">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","gravmon2"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
||||
<!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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" 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="/config.htm">Back to configuration</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(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#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">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","testing"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${token}","a-token"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
@ -1 +1 @@
|
||||
{ "project":"gravmon", "version":"0.7.1", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "calibration.min.htm", "format.min.htm", "about.min.htm" ] }
|
||||
{ "project":"gravmon", "version":"0.8.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "calibration.min.htm", "format.min.htm", "about.min.htm" ] }
|
@ -5,9 +5,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
@ -274,6 +274,10 @@
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
|
||||
chartDataForm.sort(function (a, b) {
|
||||
return a.x - b.x;
|
||||
});
|
||||
}
|
||||
|
||||
function populateChartCalc(a, g) {
|
||||
|
122
html/config.htm
@ -5,9 +5,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
@ -93,6 +93,7 @@
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<input type="text" name="runtime-average" id="runtime-average" hidden>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingOne">
|
||||
@ -134,7 +135,7 @@
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="10" max="3600" class="form-control" name="sleep-interval" id="sleep-interval">
|
||||
</div>
|
||||
<label for="sleep-interval" class="col-sm-3 col-form-label" id="sleep-interval-info"></label>
|
||||
<label for="sleep-interval" class="col-sm-7 col-form-label" id="sleep-interval-info"></label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-3">
|
||||
@ -170,17 +171,37 @@
|
||||
<div class="card-body">
|
||||
<form action="/api/config/push" method="post">
|
||||
<input type="text" name="id" id="id2" hidden>
|
||||
<input type="text" name="http-push-h1" id="http-push-h1" hidden>
|
||||
<input type="text" name="http-push-h2" id="http-push-h2" hidden>
|
||||
<input type="text" name="http-push2-h1" id="http-push2-h1" hidden>
|
||||
<input type="text" name="http-push2-h2" id="http-push2-h2" hidden>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="http-push" class="col-sm-2 col-form-label">Http URL 1:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="col-sm-8">
|
||||
<input type="url" maxlength="120" class="form-control" name="http-push" id="http-push">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-info" data-field1="#http-push-h1" data-field2="#http-push-h2" data-toggle="modal" data-target="#modal-http">Headers</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="http-push2" class="col-sm-2 col-form-label">Http URL 2:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="col-sm-8">
|
||||
<input type="url" maxlength="120" class="form-control" name="http-push2" id="http-push2">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-info" data-field1="#http-push2-h1" data-field2="#http-push2-h2" data-toggle="modal" data-target="#modal-http">Headers</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="token" class="col-sm-2 col-form-label">Token:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="50" class="form-control" name="token" id="token">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
@ -212,8 +233,6 @@
|
||||
<input type="text" maxlength="50" class="form-control" name="influxdb2-bucket" id="influxdb2-bucket">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="influxdb2-auth" class="col-sm-2 col-form-label">InfluxDB v2 Auth:</label>
|
||||
<div class="col-sm-4">
|
||||
@ -257,7 +276,7 @@
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button class="btn btn-secondary" id="format-btn">Format editor</button>
|
||||
<button class="btn btn-info" id="format-btn">Format editor</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -375,6 +394,57 @@
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-http" tabindex="-1" role="dialog" aria-labelledby="modal-header" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modal-header">Define HTTP headers</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label for="http-header" class="col-form-label">Header 1 (Header: value)</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="header1" oninput="checkHeader(this)">
|
||||
<label for="http-header" class="col-form-label">Header 2 (Header: value)</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="header2" oninput="checkHeader(this)">
|
||||
<input type="text" id="field1" hidden>
|
||||
<input type="text" id="field2" hidden>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="btn-close" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#modal-http').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget)
|
||||
var field1 = button.data('field1')
|
||||
var field2 = button.data('field2')
|
||||
var modal = $(this)
|
||||
modal.find('.modal-body #header1').val($(field1).val())
|
||||
modal.find('.modal-body #header2').val($(field2).val())
|
||||
modal.find('.modal-body #field1').val(field1)
|
||||
modal.find('.modal-body #field2').val(field2)
|
||||
})
|
||||
$('#modal-http').on('hide.bs.modal', function (event) {
|
||||
var modal = $(this)
|
||||
field1 = modal.find('.modal-body #field1').val()
|
||||
field2 = modal.find('.modal-body #field2').val()
|
||||
$(field1).val(modal.find('.modal-body #header1').val())
|
||||
$(field2).val(modal.find('.modal-body #header2').val())
|
||||
})
|
||||
function checkHeader(input) {
|
||||
console.log( input.value );
|
||||
if (input.value != "" && input.value.indexOf(":") == -1) {
|
||||
$("#btn-close").prop("disabled", true);
|
||||
$(input).removeClass("is-valid").addClass("is-invalid");
|
||||
} else {
|
||||
$("#btn-close").prop("disabled", false);
|
||||
$(input).removeClass("is-invalid").addClass("is-valid");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
@ -405,9 +475,35 @@
|
||||
window.location.href = "/format.htm";
|
||||
});
|
||||
|
||||
function estimateBatteryLife(interval) {
|
||||
// ESP8266 consumes between 140-170mA when WIFI is on. Deep sleep is 20uA.
|
||||
// MPU-6050 consumes 4mA
|
||||
// DS18B20 consumes 1mA
|
||||
// For this estimation we use an average of 160mA
|
||||
|
||||
var pwrActive = 160; // mA per hour
|
||||
var pwrSleep = 5; // mA per day
|
||||
var batt = 2200; // mA
|
||||
var rt = parseInt($("#runtime-average").val());
|
||||
|
||||
if(rt<1) rt = 2;
|
||||
|
||||
// The deep sleep will consume approx 1mA per day.
|
||||
var powerPerDay = (24*3600)/(interval+rt)*(rt/3600)*pwrActive + pwrSleep;
|
||||
return batt/powerPerDay;
|
||||
}
|
||||
|
||||
function updateSleepInfo() {
|
||||
var i = $("#sleep-interval").val()
|
||||
$("#sleep-interval-info").text( Math.floor(i/60) + " m " + (i%60) + " s" )
|
||||
var i = parseInt($("#sleep-interval").val());
|
||||
|
||||
var j = estimateBatteryLife(i);
|
||||
|
||||
var t1 = Math.floor(i/60) + " m " + (i%60) + " s";
|
||||
var t2 = Math.floor(j/7) + " weeks " + (i%7) + " days";
|
||||
|
||||
$("#sleep-interval-info").text(t1);
|
||||
//$("#sleep-interval-info").text( t1 + " - Estimated life: " + t2);
|
||||
console.log( "Estimated life: " + t2);
|
||||
|
||||
hideWarningGyro();
|
||||
if(i>0 && i<300) {
|
||||
@ -451,8 +547,13 @@
|
||||
if( cfg["gravity-format"] == "G" ) $("#gravity-format-g").click();
|
||||
else $("#gravity-format-p").click();
|
||||
$("#ota-url").val(cfg["ota-url"]);
|
||||
$("#token").val(cfg["token"]);
|
||||
$("#http-push").val(cfg["http-push"]);
|
||||
$("#http-push-h1").val(cfg["http-push-h1"]);
|
||||
$("#http-push-h2").val(cfg["http-push-h2"]);
|
||||
$("#http-push2").val(cfg["http-push2"]);
|
||||
$("#http-push2-h1").val(cfg["http-push2-h1"]);
|
||||
$("#http-push2-h2").val(cfg["http-push2-h2"]);
|
||||
$("#brewfather-push").val(cfg["brewfather-push"]);
|
||||
$("#influxdb2-push").val(cfg["influxdb2-push"]);
|
||||
$("#influxdb2-org").val(cfg["influxdb2-org"]);
|
||||
@ -471,6 +572,7 @@
|
||||
$("#gyro-calibration-data").text( cfg["gyro-calibration-data"]["ax"] + "," + cfg["gyro-calibration-data"]["ay"] + "," + cfg["gyro-calibration-data"]["az"] + "," + cfg["gyro-calibration-data"]["gx"] + "," + cfg["gyro-calibration-data"]["gy"] + "," + cfg["gyro-calibration-data"]["gz"] );
|
||||
$("#battery").text(cfg["battery"] + " V");
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#runtime-average").val(cfg["runtime-average"]);
|
||||
//$("#gravity").text(cfg["gravity"] + " SG");
|
||||
})
|
||||
.fail(function () {
|
||||
|
@ -6,9 +6,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
@ -88,7 +88,39 @@
|
||||
<div class="col-md-8 themed-grid-col bg-light">Device ID:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">
|
||||
View error log
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 3000); //5 seconds
|
||||
|
||||
function loadLog() {
|
||||
$("#logContent").load("/log");
|
||||
//$("#logContent").load("/test/log");
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="collapse" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code id="logContent"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -100,9 +132,10 @@
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.7.1)");
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.8.0)");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
|
@ -1 +1 @@
|
||||
<!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 active"><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"><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-4"><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(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.7.1)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" 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 active"><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"><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-4"><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(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Average runtime:</div><div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div></div><div class="row mb-3"><a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">View error log</a></div><script>function loadLog(){$("#logContent").load("/log")}$("#log-btn").click(function(o){loadLog()}),setInterval(function(){loadLog()},3e3)</script><div class="collapse" id="collapseLog"><div class="card card-body"><pre><code id="logContent"></code></pre></div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var e="/api/device";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#app-ver").text(e["app-ver"]+" (html 0.8.0)"),$("#mdns").text(e.mdns),$("#id").text(e.id),$("#runtime").text(e["runtime-average"]+" seconds")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
@ -5,9 +5,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
@ -148,10 +148,11 @@
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var doc = $("#format").val();
|
||||
doc = doc.replaceAll("${mdns}", "gravmon2");
|
||||
doc = doc.replaceAll("${mdns}", "testing");
|
||||
doc = doc.replaceAll("${id}", "e4a344");
|
||||
doc = doc.replaceAll("${sleep-interval}", "300");
|
||||
doc = doc.replaceAll("${temp}", "21.1");
|
||||
doc = doc.replaceAll("${token}", "a-token");
|
||||
doc = doc.replaceAll("${temp-c}", "21.1");
|
||||
doc = doc.replaceAll("${temp-f}", "51.3");
|
||||
doc = doc.replaceAll("${temp-unit}", "C");
|
||||
|
@ -1,2 +1,2 @@
|
||||
<!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="/config.htm">Back to configuration</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(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#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">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","gravmon2"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
||||
<!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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" 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="/config.htm">Back to configuration</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(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#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">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","testing"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${token}","a-token"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
@ -5,9 +5,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
@ -122,14 +122,37 @@
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#gravity").text("Gyro moving");
|
||||
} else {
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
}
|
||||
|
||||
$("#battery").text(cfg["battery"] + " V");
|
||||
var batt = cfg["battery"];
|
||||
var charge = 0;
|
||||
|
||||
if(batt>4.15) charge = 100;
|
||||
else if(batt>4.05) charge = 90;
|
||||
else if(batt>3.97) charge = 80;
|
||||
else if(batt>3.91) charge = 70;
|
||||
else if(batt>3.86) charge = 60;
|
||||
else if(batt>3.81) charge = 50;
|
||||
else if(batt>3.78) charge = 40;
|
||||
else if(batt>3.76) charge = 30;
|
||||
else if(batt>3.73) charge = 20;
|
||||
else if(batt>3.67) charge = 10;
|
||||
else if(batt>3.44) charge = 5;
|
||||
|
||||
$("#battery").text(batt + " V (" + charge + "%)" );
|
||||
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
|
@ -5,9 +5,9 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
@ -34,7 +34,7 @@ build_flags =
|
||||
-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.7.1\""
|
||||
-D CFG_APPVER="\"0.8.0\""
|
||||
lib_deps = # Switched to forks for better version control.
|
||||
# Using local copy of these libraries
|
||||
#https://github.com/jrowberg/i2cdevlib.git#<document>
|
||||
|
21
src/calc.cpp
@ -49,7 +49,8 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
#endif
|
||||
|
||||
if (!noAngles) {
|
||||
Log.error(F("CALC: Not enough values for deriving formula" CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
|
||||
return ERR_FORMULA_NOTENOUGHVALUES;
|
||||
} else {
|
||||
double coeffs[order + 1];
|
||||
@ -103,7 +104,10 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
Log.error(F("CALC: Deviation to large, formula rejected." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("CALC: Error validating created formula. Deviation to large, "
|
||||
"formula rejected."));
|
||||
return ERR_FORMULA_UNABLETOFFIND;
|
||||
}
|
||||
|
||||
@ -112,7 +116,8 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Internal error finding formula." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Internal error finding formula."));
|
||||
return ERR_FORMULA_INTERNAL;
|
||||
}
|
||||
|
||||
@ -157,7 +162,8 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
return g;
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Failed to parse expression %d." CR), err);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -203,9 +209,10 @@ double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
return g;
|
||||
}
|
||||
|
||||
Log.error(
|
||||
F("CALC: Failed to parse expression %d, no correction has been made." CR),
|
||||
err);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
"CALC: Failed to parse expression for gravity temperature correction " +
|
||||
String(err));
|
||||
return gravity;
|
||||
}
|
||||
|
||||
|
107
src/config.cpp
@ -34,11 +34,11 @@ HardwareConfig myHardwareConfig;
|
||||
Config::Config() {
|
||||
// Assiging default values
|
||||
char buf[30];
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", (unsigned int)ESP.getChipId());
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i+8) {
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
|
||||
@ -52,21 +52,11 @@ Config::Config() {
|
||||
_mDNS.c_str());
|
||||
#endif
|
||||
|
||||
setTempFormat('C');
|
||||
setGravityFormat('G');
|
||||
setSleepInterval(900); // 15 minutes
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
setVoltageFactor(1.59); // Conversion factor for battery on ESP8266
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
setVoltageFactor(1.43); // Conversion factor for battery on ESP32
|
||||
#endif
|
||||
setTempSensorAdjC(0.0);
|
||||
setGravityTempAdj(false);
|
||||
_gyroCalibration = {0, 0, 0, 0, 0, 0};
|
||||
_formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
|
||||
_gyroTemp = false;
|
||||
_saveNeeded = false;
|
||||
_mqttPort = 1883;
|
||||
}
|
||||
|
||||
//
|
||||
@ -75,14 +65,20 @@ Config::Config() {
|
||||
//
|
||||
void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[PARAM_MDNS] = getMDNS();
|
||||
//doc[PARAM_CONFIG_VER] = getConfigVersion();
|
||||
doc[PARAM_ID] = getID();
|
||||
doc[PARAM_OTA] = getOtaURL();
|
||||
doc[PARAM_SSID] = getWifiSSID();
|
||||
doc[PARAM_PASS] = getWifiPass();
|
||||
doc[PARAM_TEMPFORMAT] = String(getTempFormat());
|
||||
doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpPushUrl();
|
||||
doc[PARAM_PUSH_HTTP2] = getHttpPushUrl2();
|
||||
doc[PARAM_TOKEN] = getToken();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpUrl();
|
||||
doc[PARAM_PUSH_HTTP_H1] = getHttpHeader(0);
|
||||
doc[PARAM_PUSH_HTTP_H2] = getHttpHeader(1);
|
||||
doc[PARAM_PUSH_HTTP2] = getHttp2Url();
|
||||
doc[PARAM_PUSH_HTTP2_H1] = getHttp2Header(0);
|
||||
doc[PARAM_PUSH_HTTP2_H2] = getHttp2Header(1);
|
||||
doc[PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
|
||||
doc[PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
|
||||
doc[PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
|
||||
@ -139,7 +135,8 @@ bool Config::saveFile() {
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to save configuration."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -156,7 +153,6 @@ bool Config::saveFile() {
|
||||
configFile.close();
|
||||
|
||||
_saveNeeded = false;
|
||||
myConfig.debug();
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
@ -170,15 +166,16 @@ bool Config::loadFile() {
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_FILENAME)) {
|
||||
Log.error(
|
||||
F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Configuration file does not exist."));
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to load configuration."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -194,8 +191,8 @@ bool Config::loadFile() {
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR),
|
||||
err.c_str(), doc.capacity());
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -215,8 +212,17 @@ bool Config::loadFile() {
|
||||
if (!doc[PARAM_PUSH_BREWFATHER].isNull())
|
||||
setBrewfatherPushUrl(doc[PARAM_PUSH_BREWFATHER]);
|
||||
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpPushUrl(doc[PARAM_PUSH_HTTP]);
|
||||
if (!doc[PARAM_PUSH_HTTP2].isNull()) setHttpPushUrl2(doc[PARAM_PUSH_HTTP2]);
|
||||
if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]);
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]);
|
||||
if (!doc[PARAM_PUSH_HTTP_H1].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP_H2].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H2], 1);
|
||||
if (!doc[PARAM_PUSH_HTTP2].isNull()) setHttp2Url(doc[PARAM_PUSH_HTTP2]);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H1].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H2].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H2], 1);
|
||||
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2].isNull())
|
||||
setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]);
|
||||
@ -287,7 +293,13 @@ bool Config::loadFile() {
|
||||
if (!doc[PARAM_FORMULA_DATA]["g5"].isNull())
|
||||
_formulaData.g[4] = doc[PARAM_FORMULA_DATA]["g5"].as<double>();
|
||||
|
||||
myConfig.debug();
|
||||
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
|
||||
// If this parameter is missing we need to reset the gyrocalibaration due to bug #29
|
||||
_gyroCalibration.ax = _gyroCalibration.ay = _gyroCalibration.az = 0;
|
||||
_gyroCalibration.gx = _gyroCalibration.gy = _gyroCalibration.gz = 0;
|
||||
Log.warning(F("CFG : Old configuration format, clearing gyro calibration." CR));
|
||||
}*/
|
||||
|
||||
_saveNeeded = false; // Reset save flag
|
||||
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
|
||||
return true;
|
||||
@ -317,34 +329,6 @@ void Config::checkFileSystem() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Dump the configuration to the serial port
|
||||
//
|
||||
void Config::debug() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR));
|
||||
Log.verbose(F("CFG : ID; '%s'." CR), getID());
|
||||
Log.verbose(F("CFG : WIFI; '%s', '%s'." CR), getWifiSSID(), getWifiPass());
|
||||
Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS());
|
||||
Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval());
|
||||
Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL());
|
||||
Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat());
|
||||
Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdjC());
|
||||
Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor());
|
||||
Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula());
|
||||
Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat());
|
||||
Log.verbose(F("CFG : Gravity temp adj; %s." CR),
|
||||
isGravityTempAdj() ? "true" : "false");
|
||||
Log.verbose(F("CFG : Gyro temp; %s." CR), isGyroTemp() ? "true" : "false");
|
||||
Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl());
|
||||
Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl());
|
||||
Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2());
|
||||
Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR),
|
||||
getInfluxDb2PushUrl(), getInfluxDb2PushOrg(),
|
||||
getInfluxDb2PushBucket(), getInfluxDb2PushToken());
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
@ -356,7 +340,8 @@ bool HardwareConfig::saveFile() {
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open file " CFG_HW_FILENAME " for save." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -399,7 +384,8 @@ bool HardwareConfig::loadFile() {
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open " CFG_HW_FILENAME "." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -415,9 +401,8 @@ bool HardwareConfig::loadFile() {
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
Log.error(
|
||||
F("CFG : Failed to parse " CFG_HW_FILENAME " file, Err: %s, %d." CR),
|
||||
err.c_str(), doc.capacity());
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -440,6 +425,8 @@ bool HardwareConfig::loadFile() {
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].as<float>());
|
||||
if (!doc[PARAM_HW_WIFI_PORTALTIMEOUT].isNull())
|
||||
this->setWifiPortalTimeout(doc[PARAM_HW_WIFI_PORTALTIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_TIMEOUT].isNull())
|
||||
this->setPushTimeout(doc[PARAM_HW_PUSH_TIMEOUT].as<int>());
|
||||
|
||||
Log.notice(F("CFG : Configuration file " CFG_HW_FILENAME " loaded." CR));
|
||||
return true;
|
||||
|
108
src/config.hpp
@ -29,7 +29,7 @@ SOFTWARE.
|
||||
|
||||
#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_HW_FILENAME "/hardware.json" // Name of config file for hw
|
||||
|
||||
@ -60,6 +60,7 @@ class HardwareConfig {
|
||||
int _gyroSensorMovingThreashold = 500;
|
||||
int _gyroReadCount = 50;
|
||||
int _gyroReadDelay = 3150; // us, empirical, to hold sampling to 200 Hz
|
||||
int _pushTimeout = 10; // seconds
|
||||
|
||||
public:
|
||||
int getWifiPortalTimeout() { return _wifiPortalTimeout; }
|
||||
@ -78,6 +79,8 @@ class HardwareConfig {
|
||||
void setGyroReadCount(int c) { _gyroReadCount = c; }
|
||||
int getGyroReadDelay() { return _gyroReadDelay; }
|
||||
void setGyroReadDelay(int d) { _gyroReadDelay = d; }
|
||||
int getPushTimeout() { return _pushTimeout; }
|
||||
void setPushTimeout(int t) { _pushTimeout = t; }
|
||||
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
@ -85,48 +88,52 @@ class HardwareConfig {
|
||||
|
||||
class Config {
|
||||
private:
|
||||
bool _saveNeeded;
|
||||
bool _saveNeeded = false;
|
||||
int _configVersion = 2;
|
||||
|
||||
// Device configuration
|
||||
String _id;
|
||||
String _mDNS;
|
||||
String _otaURL;
|
||||
char _tempFormat;
|
||||
float _voltageFactor;
|
||||
float _tempSensorAdjC;
|
||||
int _sleepInterval;
|
||||
bool _gyroTemp;
|
||||
String _id = "";
|
||||
String _mDNS = "";
|
||||
String _otaURL = "";
|
||||
char _tempFormat = 'C';
|
||||
float _voltageFactor = 0;
|
||||
float _tempSensorAdjC = 0;
|
||||
int _sleepInterval = 900;
|
||||
bool _gyroTemp = false;
|
||||
|
||||
// Wifi Config
|
||||
String _wifiSSID;
|
||||
String _wifiPASS;
|
||||
String _wifiSSID = "";
|
||||
String _wifiPASS = "";
|
||||
|
||||
// Push target settings
|
||||
String _brewfatherPushUrl;
|
||||
String _brewfatherPushUrl = "";
|
||||
|
||||
String _httpPushUrl;
|
||||
String _httpPushUrl2;
|
||||
String _token = "";
|
||||
|
||||
String _influxDb2Url;
|
||||
String _influxDb2Org;
|
||||
String _influxDb2Bucket;
|
||||
String _influxDb2Token;
|
||||
String _httpUrl = "";
|
||||
String _httpHeader[2] = {"Content-Type: application/json", ""};
|
||||
String _http2Url = "";
|
||||
String _http2Header[2] = {"Content-Type: application/json", ""};
|
||||
|
||||
String _mqttUrl;
|
||||
int _mqttPort;
|
||||
String _mqttUser;
|
||||
String _mqttPass;
|
||||
String _influxDb2Url = "";
|
||||
String _influxDb2Org = "";
|
||||
String _influxDb2Bucket = "";
|
||||
String _influxDb2Token = "";
|
||||
|
||||
String _mqttUrl = "";
|
||||
int _mqttPort = 1883;
|
||||
String _mqttUser = "";
|
||||
String _mqttPass = "";
|
||||
|
||||
// Gravity and temperature calculations
|
||||
String _gravityFormula;
|
||||
bool _gravityTempAdj;
|
||||
char _gravityFormat;
|
||||
String _gravityFormula = "";
|
||||
bool _gravityTempAdj = false;
|
||||
char _gravityFormat = 'G';
|
||||
|
||||
// Gyro calibration and formula calculation data
|
||||
RawGyroData _gyroCalibration;
|
||||
RawFormulaData _formulaData;
|
||||
RawGyroData _gyroCalibration = {0, 0, 0, 0, 0, 0};
|
||||
RawFormulaData _formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
|
||||
|
||||
void debug();
|
||||
void formatFileSystem();
|
||||
|
||||
public:
|
||||
@ -139,6 +146,8 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
int getConfigVersion() { return _configVersion; }
|
||||
|
||||
const bool isGyroTemp() { return _gyroTemp; }
|
||||
void setGyroTemp(bool b) {
|
||||
_gyroTemp = b;
|
||||
@ -151,6 +160,7 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isOtaActive() { return _otaURL.length() ? true : false; }
|
||||
bool isOtaSSL() { return _otaURL.startsWith("https://"); }
|
||||
|
||||
const char* getWifiSSID() { return _wifiSSID.c_str(); }
|
||||
void setWifiSSID(String s) {
|
||||
@ -173,19 +183,39 @@ class Config {
|
||||
return _brewfatherPushUrl.length() ? true : false;
|
||||
}
|
||||
|
||||
// Token parameter
|
||||
const char* getToken() { return _token.c_str(); }
|
||||
void setToken(String s) {
|
||||
_token = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Standard HTTP
|
||||
const char* getHttpPushUrl() { return _httpPushUrl.c_str(); }
|
||||
void setHttpPushUrl(String s) {
|
||||
_httpPushUrl = s;
|
||||
const char* getHttpUrl() { return _httpUrl.c_str(); }
|
||||
void setHttpUrl(String s) {
|
||||
_httpUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive() { return _httpPushUrl.length() ? true : false; }
|
||||
const char* getHttpPushUrl2() { return _httpPushUrl2.c_str(); }
|
||||
void setHttpPushUrl2(String s) {
|
||||
_httpPushUrl2 = s;
|
||||
const char* getHttpHeader(int idx) { return _httpHeader[idx].c_str(); }
|
||||
void setHttpHeader(String s, int idx) {
|
||||
_httpHeader[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive2() { return _httpPushUrl2.length() ? true : false; }
|
||||
bool isHttpActive() { return _httpUrl.length() ? true : false; }
|
||||
bool isHttpSSL() { return _httpUrl.startsWith("https://"); }
|
||||
|
||||
const char* getHttp2Url() { return _http2Url.c_str(); }
|
||||
void setHttp2Url(String s) {
|
||||
_http2Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getHttp2Header(int idx) { return _http2Header[idx].c_str(); }
|
||||
void setHttp2Header(String s, int idx) {
|
||||
_http2Header[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttp2Active() { return _http2Url.length() ? true : false; }
|
||||
bool isHttp2SSL() { return _http2Url.startsWith("https://"); }
|
||||
|
||||
// InfluxDB2
|
||||
const char* getInfluxDb2PushUrl() { return _influxDb2Url.c_str(); }
|
||||
@ -211,12 +241,14 @@ class Config {
|
||||
}
|
||||
|
||||
// MQTT
|
||||
bool isMqttActive() { return _mqttUrl.length() ? true : false; }
|
||||
const char* getMqttUrl() { return _mqttUrl.c_str(); }
|
||||
void setMqttUrl(String s) {
|
||||
_mqttUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isMqttActive() { return _mqttUrl.length() ? true : false; }
|
||||
bool isMqttSSL() { return _mqttPort > 8000 ? true : false; }
|
||||
|
||||
int getMqttPort() { return _mqttPort; }
|
||||
void setMqttPort(String s) {
|
||||
_mqttPort = s.toInt();
|
||||
|
25
src/gyro.cpp
@ -44,7 +44,8 @@ bool GyroSensor::setup() {
|
||||
// compilation difficulties
|
||||
|
||||
if (!accelgyro.testConnection()) {
|
||||
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("GYRO: Failed to connect to gyro, is it connected?"));
|
||||
_sensorConnected = false;
|
||||
} else {
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
@ -195,11 +196,15 @@ float GyroSensor::calculateAngle(RawGyroData &raw) {
|
||||
az = (static_cast<float>(raw.az)) / 16384;
|
||||
|
||||
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
|
||||
float v = (acos(ay / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
float vY = (acos(abs(ay) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
//float vZ = (acos(abs(az) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
//float vX = (acos(abs(ax) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: angle = %F." CR), v);
|
||||
//Log.notice(F("GYRO: angleX= %F." CR), vX);
|
||||
Log.notice(F("GYRO: angleY= %F." CR), vY);
|
||||
//Log.notice(F("GYRO: angleZ= %F." CR), vZ);
|
||||
#endif
|
||||
return v;
|
||||
return vY;
|
||||
}
|
||||
|
||||
//
|
||||
@ -240,16 +245,16 @@ bool GyroSensor::read() {
|
||||
// If the sensor is unstable we return false to signal we dont have valid
|
||||
// value
|
||||
if (isSensorMoving(_lastGyroData)) {
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Sensor is moving." CR));
|
||||
#endif
|
||||
_validValue = false;
|
||||
} else {
|
||||
_validValue = true;
|
||||
_angle = calculateAngle(_lastGyroData);
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), _lastGyroData.ax,
|
||||
_lastGyroData.ay, _lastGyroData.az, _angle);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Sensor values %d,%d,%d\t%F" CR), _lastGyroData.ax,
|
||||
_lastGyroData.ay, _lastGyroData.az, _angle);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -286,7 +291,9 @@ void GyroSensor::applyCalibration() {
|
||||
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
|
||||
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
|
||||
0) {
|
||||
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("GYRO: No valid calibration values, please calibrate the device."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
197
src/helper.cpp
@ -21,12 +21,12 @@ 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.
|
||||
*/
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include <config.hpp>
|
||||
@ -39,6 +39,14 @@ SOFTWARE.
|
||||
SerialDebug mySerial;
|
||||
BatteryVoltage myBatteryVoltage;
|
||||
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
struct tcp_pcb;
|
||||
extern struct tcp_pcb* tcp_tw_pcbs;
|
||||
extern "C" void tcp_abort(struct tcp_pcb* pcb);
|
||||
void tcp_cleanup() {
|
||||
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
//
|
||||
// Convert sg to plato
|
||||
//
|
||||
@ -59,20 +67,108 @@ float convertCtoF(float c) { return (c * 1.8) + 32.0; }
|
||||
//
|
||||
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
|
||||
|
||||
//
|
||||
// Load error log from disk
|
||||
//
|
||||
ErrorFileLog::ErrorFileLog() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "r");
|
||||
int i = 0;
|
||||
|
||||
if (errFile) {
|
||||
do {
|
||||
_errors[i] = errFile.readStringUntil('\n');
|
||||
_errors[i].replace("\r", "");
|
||||
_errors[i].replace("\n", "");
|
||||
} while (_errors[i++].length());
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add new entry to top of error log
|
||||
//
|
||||
void ErrorFileLog::addEntry(String err) {
|
||||
for (int i = (ERR_COUNT - 1); i > 0; i--) {
|
||||
_errors[i] = _errors[i - 1];
|
||||
}
|
||||
_errors[0] = err;
|
||||
Log.errorln(err.c_str());
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save error log
|
||||
//
|
||||
void ErrorFileLog::save() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "w");
|
||||
if (errFile) {
|
||||
for (int i = 0; i < ERR_COUNT; i++) {
|
||||
errFile.println(_errors[i]);
|
||||
}
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Load history log of floats
|
||||
//
|
||||
FloatHistoryLog::FloatHistoryLog(String fName) {
|
||||
_fName = fName;
|
||||
|
||||
File runFile = LittleFS.open(_fName, "r");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
_runTime[i] = runFile.readStringUntil('\n').toFloat();
|
||||
if (_runTime[i]) {
|
||||
_average += _runTime[i];
|
||||
_count++;
|
||||
}
|
||||
}
|
||||
runFile.close();
|
||||
_average = _average / _count;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add entry to top of log
|
||||
//
|
||||
void FloatHistoryLog::addEntry(float time) {
|
||||
for (int i = (10 - 1); i > 0; i--) {
|
||||
_runTime[i] = _runTime[i - 1];
|
||||
}
|
||||
_runTime[0] = time;
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save log
|
||||
//
|
||||
void FloatHistoryLog::save() {
|
||||
File runFile = LittleFS.open(_fName, "w");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
runFile.println(_runTime[i], 2);
|
||||
}
|
||||
runFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the heap information.
|
||||
//
|
||||
void printHeap() {
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
#if defined (ESP8266)
|
||||
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
||||
ESP.getFreeSketchSpace() / 1024);
|
||||
#else // defined (ESP32)
|
||||
void printHeap(String prefix) {
|
||||
#if defined(ESP8266)
|
||||
Log.notice(
|
||||
F("%s: Free-heap %d kb, Heap-rag %d %%, Max-block %d kb Stack=%d b." CR),
|
||||
prefix.c_str(), ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
||||
ESP.getMaxFreeBlockSize() / 1024, ESP.getFreeContStack());
|
||||
// Log.notice(F("%s: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
||||
// prefix.c_str(), ESP.getFreeHeap() / 1024,
|
||||
// ESP.getHeapFragmentation(), ESP.getFreeSketchSpace() / 1024);
|
||||
#else // defined (ESP32)
|
||||
Log.verbose(F("HELP: Heap %d kb, FreeSketch %d kb." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
@ -141,11 +237,11 @@ void BatteryVoltage::read() {
|
||||
// An ESP8266 has a ADC range of 0-1023 and a maximum voltage of 3.3V
|
||||
// An ESP32 has an ADC range of 0-4095 and a maximum voltage of 3.3V
|
||||
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
_batteryLevel = ((3.3 / 1023) * v) * factor;
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
_batteryLevel = ((3.3 / 4095) * v) * factor;
|
||||
#endif
|
||||
#endif
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
|
||||
@ -217,22 +313,24 @@ void PerfLogging::print() {
|
||||
void PerfLogging::pushInflux() {
|
||||
if (!myConfig.isInfluxDb2Active()) return;
|
||||
|
||||
WiFiClient wifi;
|
||||
HTTPClient http;
|
||||
String serverPath =
|
||||
String(myConfig.getInfluxDb2PushUrl()) +
|
||||
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
|
||||
// Create body for influxdb2, format used
|
||||
// key,host=mdns value=0.0
|
||||
String body;
|
||||
body.reserve(500);
|
||||
|
||||
// Create the payload with performance data.
|
||||
// ------------------------------------------------------------------------------------------
|
||||
PerfEntry* pe = first;
|
||||
char buf[100];
|
||||
char buf[150];
|
||||
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
|
||||
myConfig.getID());
|
||||
body += &buf[0];
|
||||
@ -254,15 +352,23 @@ void PerfLogging::pushInflux() {
|
||||
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
|
||||
myConfig.getMDNS(), myConfig.getID());
|
||||
body += &buf[0];
|
||||
#if defined (ESP8266)
|
||||
snprintf(
|
||||
&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f",
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f,heap=%d,heap-frag=%d,heap-max=%d,stack=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()));
|
||||
body += &buf[0];
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), ESP.getHeapFragmentation(), ESP.getMaxFreeBlockSize(), ESP.getFreeContStack());
|
||||
#else // defined (ESP32)
|
||||
snprintf(
|
||||
&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f,heap=%d,heap-frag=%d,heap-max=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), 0, ESP.getMaxAllocHeap());
|
||||
#endif
|
||||
|
||||
// Log.notice(F("PERF: data %s." CR), body.c_str() );
|
||||
body += &buf[0];
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
|
||||
@ -272,6 +378,7 @@ void PerfLogging::pushInflux() {
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout());
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
@ -286,7 +393,8 @@ void PerfLogging::pushInflux() {
|
||||
}
|
||||
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
#endif // COLLECT_PERFDATA
|
||||
@ -315,52 +423,59 @@ float reduceFloatPrecision(float f, int dec) {
|
||||
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
|
||||
//
|
||||
String urlencode(String str) {
|
||||
String encodedString = "";
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length()*2);
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i =0; i < static_cast<int>(str.length()); i++) {
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (isalnum(c)){
|
||||
if (isalnum(c)) {
|
||||
encodedString += c;
|
||||
} else {
|
||||
code1 = (c & 0xf) + '0';
|
||||
if ((c & 0xf) >9) {
|
||||
code1 = (c & 0xf) - 10 + 'A';
|
||||
if ((c & 0xf) > 9) {
|
||||
code1 = (c & 0xf) - 10 + 'A';
|
||||
}
|
||||
c = (c>>4) & 0xf;
|
||||
c = (c >> 4) & 0xf;
|
||||
code0 = c + '0';
|
||||
if (c > 9) {
|
||||
code0 = c - 10 + 'A';
|
||||
code0 = c - 10 + 'A';
|
||||
}
|
||||
encodedString += '%';
|
||||
encodedString += code0;
|
||||
encodedString += code1;
|
||||
}
|
||||
}
|
||||
//Log.verbose(F("HELP: encode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
// Log.verbose(F("HELP: encode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
unsigned char h2int(char c) {
|
||||
if (c >= '0' && c <='9') {
|
||||
return((unsigned char)c - '0');
|
||||
if (c >= '0' && c <= '9') {
|
||||
return ((unsigned char)c - '0');
|
||||
}
|
||||
if (c >= 'a' && c <='f') {
|
||||
return((unsigned char)c - 'a' + 10);
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return ((unsigned char)c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <='F') {
|
||||
return((unsigned char)c - 'A' + 10);
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return ((unsigned char)c - 'A' + 10);
|
||||
}
|
||||
return(0);
|
||||
return (0);
|
||||
}
|
||||
|
||||
//
|
||||
// urlencode string
|
||||
//
|
||||
String urldecode(String str) {
|
||||
String encodedString = "";
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length());
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++){
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (c == '%') {
|
||||
i++;
|
||||
@ -372,9 +487,9 @@ String urldecode(String str) {
|
||||
} else {
|
||||
encodedString += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
|
||||
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,14 @@ SOFTWARE.
|
||||
// Includes
|
||||
#include <main.hpp>
|
||||
|
||||
#define ERR_FILENAME "/error.log"
|
||||
#define ERR_COUNT 15
|
||||
|
||||
#define RUNTIME_FILENAME "/runtime.log"
|
||||
|
||||
// tcp cleanup
|
||||
void tcp_cleanup();
|
||||
|
||||
// Sleep mode
|
||||
void deepSleep(int t);
|
||||
|
||||
@ -50,7 +58,7 @@ float reduceFloatPrecision(float f, int dec = 2);
|
||||
// Logging via serial
|
||||
void printTimestamp(Print* _logOutput, int _logLevel);
|
||||
void printNewline(Print* _logOutput);
|
||||
void printHeap();
|
||||
void printHeap(String prefix = "HELP");
|
||||
|
||||
// Classes
|
||||
class SerialDebug {
|
||||
@ -59,6 +67,30 @@ class SerialDebug {
|
||||
static Logging* getLog() { return &Log; }
|
||||
};
|
||||
|
||||
class ErrorFileLog {
|
||||
private:
|
||||
String _errors[ERR_COUNT];
|
||||
|
||||
public:
|
||||
ErrorFileLog();
|
||||
void addEntry(String error);
|
||||
void save();
|
||||
};
|
||||
|
||||
class FloatHistoryLog {
|
||||
private:
|
||||
String _fName;
|
||||
float _average = 0;
|
||||
float _runTime[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
int _count = 0;
|
||||
void save();
|
||||
|
||||
public:
|
||||
explicit FloatHistoryLog(String fName);
|
||||
void addEntry(float time);
|
||||
float getAverage() { return _average; }
|
||||
};
|
||||
|
||||
class BatteryVoltage {
|
||||
private:
|
||||
float _batteryLevel;
|
||||
@ -138,7 +170,7 @@ extern PerfLogging myPerfLogging;
|
||||
// Use these to collect performance data from various parts of the code
|
||||
#define LOG_PERF_START(s) myPerfLogging.start(s)
|
||||
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
|
||||
// #define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
// #define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR() myPerfLogging.clear()
|
||||
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
|
||||
|
57
src/main.cpp
@ -40,6 +40,7 @@ int interval = 200; // ms, time to wait between changes to output
|
||||
bool sleepModeAlwaysSkip =
|
||||
false; // Flag set in web interface to override normal behaviour
|
||||
uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
|
||||
uint32_t pushMillis = 0; // Used to control how often we will send push data
|
||||
uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
|
||||
uint32_t stableGyroMillis; // Used to calculate the total time since last
|
||||
// stable gyro reading
|
||||
@ -110,19 +111,19 @@ void setup() {
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
// Add a delay so that serial is started.
|
||||
// delay(3000);
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
#endif
|
||||
// Main startup
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
Log.notice(F("Main: Started setup for %s." CR),
|
||||
String(ESP.getChipId(), HEX).c_str());
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
char buf[20];
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i+8) {
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
|
||||
@ -138,10 +139,10 @@ void setup() {
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
ESP.wdtDisable();
|
||||
ESP.wdtEnable(5000); // 5 seconds
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
|
||||
// No stored config, move to portal
|
||||
@ -173,7 +174,9 @@ void setup() {
|
||||
LOG_PERF_STOP("main-temp-setup");
|
||||
|
||||
if (!myGyro.setup()) {
|
||||
Log.error(F("Main: Failed to initialize the gyro." CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("MAIN: Failed to initialize the gyro, is it connected?"));
|
||||
} else {
|
||||
LOG_PERF_START("main-gyro-read");
|
||||
myGyro.read();
|
||||
@ -207,7 +210,8 @@ void setup() {
|
||||
|
||||
LOG_PERF_STOP("main-setup");
|
||||
Log.notice(F("Main: Setup completed." CR));
|
||||
stableGyroMillis = millis(); // Dont include time for wifi connection
|
||||
pushMillis = stableGyroMillis =
|
||||
millis(); // Dont include time for wifi connection
|
||||
}
|
||||
|
||||
//
|
||||
@ -244,15 +248,24 @@ bool loopReadGravity() {
|
||||
angle, tempC, gravity, corrGravity);
|
||||
#endif
|
||||
|
||||
LOG_PERF_START("loop-push");
|
||||
// Force the transmission if we are going to sleep
|
||||
myPushTarget.send(angle, gravitySG, corrGravitySG, tempC,
|
||||
(millis() - runtimeMillis) / 1000,
|
||||
runMode == RunMode::gravityMode ? true : false);
|
||||
LOG_PERF_STOP("loop-push");
|
||||
bool pushExpired = (abs((int32_t)(millis() - pushMillis)) >
|
||||
(myConfig.getSleepInterval() * 1000));
|
||||
|
||||
if (pushExpired || runMode == RunMode::gravityMode) {
|
||||
pushMillis = millis();
|
||||
LOG_PERF_START("loop-push");
|
||||
PushTarget push;
|
||||
push.send(angle, gravitySG, corrGravitySG, tempC,
|
||||
(millis() - runtimeMillis) / 1000);
|
||||
LOG_PERF_STOP("loop-push");
|
||||
// Send stats to influx after each push run.
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
LOG_PERF_PUSH();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Log.error(F("Main: No gyro value." CR));
|
||||
Log.error(F("MAIN: No gyro value found, the device might be moving."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -265,7 +278,7 @@ void loopGravityOnInterval() {
|
||||
if (abs((int32_t)(millis() - loopMillis)) > interval) {
|
||||
loopReadGravity();
|
||||
loopMillis = millis();
|
||||
printHeap();
|
||||
// printHeap("MAIN");
|
||||
LOG_PERF_START("loop-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("loop-gyro-read");
|
||||
@ -274,6 +287,8 @@ void loopGravityOnInterval() {
|
||||
}
|
||||
}
|
||||
|
||||
bool skipRunTimeLog = false;
|
||||
|
||||
//
|
||||
// Main loop that determines if device should go to sleep
|
||||
//
|
||||
@ -281,6 +296,11 @@ void goToSleep(int sleepInterval) {
|
||||
float volt = myBatteryVoltage.getVoltage();
|
||||
float runtime = (millis() - runtimeMillis);
|
||||
|
||||
if (!skipRunTimeLog) {
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
runLog.addEntry(runtime);
|
||||
}
|
||||
|
||||
Log.notice(F("MAIN: Entering deep sleep for %ds, run time %Fs, "
|
||||
"battery=%FV." CR),
|
||||
sleepInterval, reduceFloatPrecision(runtime / 1000, 2), volt);
|
||||
@ -301,6 +321,9 @@ void loop() {
|
||||
myWebServerHandler.loop();
|
||||
myWifi.loop();
|
||||
loopGravityOnInterval();
|
||||
|
||||
// If we switched mode, dont include this in the log.
|
||||
if (runMode != RunMode::configurationMode) skipRunTimeLog = true;
|
||||
break;
|
||||
|
||||
case RunMode::gravityMode:
|
||||
|
@ -22,10 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
#include <MQTT.h>
|
||||
|
||||
@ -34,35 +32,14 @@ SOFTWARE.
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
PushTarget myPushTarget;
|
||||
|
||||
//
|
||||
// Send the data to targets
|
||||
//
|
||||
void PushTarget::send(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime, bool force) {
|
||||
uint32_t timePassed = abs((int32_t)(millis() - _ms));
|
||||
uint32_t interval = myConfig.getSleepInterval() * 1000;
|
||||
|
||||
if ((timePassed < interval) && !force) {
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed,
|
||||
interval);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_ms = millis();
|
||||
|
||||
#if defined(ESP8266)
|
||||
if (ESP.getFreeContStack() < 1500) {
|
||||
Log.error(F("PUSH: Low on memory, skipping push since it will crasch. "
|
||||
"(stack=%d, heap=%d)." CR),
|
||||
ESP.getFreeContStack(), ESP.getFreeHeap());
|
||||
myWifi.closeWifiClient();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
float tempC, float runTime) {
|
||||
printHeap("PUSH");
|
||||
http.setReuse(false);
|
||||
httpSecure.setReuse(false);
|
||||
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
|
||||
@ -75,13 +52,13 @@ void PushTarget::send(float angle, float gravitySG, float corrGravitySG,
|
||||
|
||||
if (myConfig.isHttpActive()) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttp(engine, 0);
|
||||
sendHttp(engine, myConfig.isHttpSSL(), 0);
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if (myConfig.isHttpActive2()) {
|
||||
if (myConfig.isHttp2Active()) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttp(engine, 1);
|
||||
sendHttp(engine, myConfig.isHttp2SSL(), 1);
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
@ -93,11 +70,9 @@ void PushTarget::send(float angle, float gravitySG, float corrGravitySG,
|
||||
|
||||
if (myConfig.isMqttActive()) {
|
||||
LOG_PERF_START("push-mqtt");
|
||||
sendMqtt(engine);
|
||||
sendMqtt(engine, myConfig.isMqttSSL());
|
||||
LOG_PERF_STOP("push-mqtt");
|
||||
}
|
||||
|
||||
LOG_PERF_PUSH();
|
||||
}
|
||||
|
||||
//
|
||||
@ -114,15 +89,14 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_INFLUX);
|
||||
|
||||
HTTPClient http;
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
int httpResponseCode = http.POST(doc);
|
||||
@ -131,12 +105,14 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Influxdb push failed response=" +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
@ -150,16 +126,14 @@ void PushTarget::sendBrewfather(TemplatingEngine& engine) {
|
||||
String serverPath = myConfig.getBrewfatherPushUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_BREWFATHER);
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
HTTPClient http;
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json"));
|
||||
int httpResponseCode = http.POST(doc);
|
||||
|
||||
@ -167,83 +141,151 @@ void PushTarget::sendBrewfather(TemplatingEngine& engine) {
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: Brewfather push failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Brewfather push failed response=" +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
if (!header.length()) return;
|
||||
|
||||
int i = header.indexOf(":");
|
||||
if (i) {
|
||||
String name = header.substring(0, i);
|
||||
String value = header.substring(i + 1);
|
||||
value.trim();
|
||||
Log.notice(F("PUSH: Adding header '%s': '%s'" CR), name.c_str(),
|
||||
value.c_str());
|
||||
http.addHeader(name, value);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Unable to set header, invalid value " + header);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendHttp(TemplatingEngine& engine, int index) {
|
||||
void PushTarget::sendHttp(TemplatingEngine& engine, bool isSecure, int index) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to http (%s)" CR),
|
||||
index ? "http2" : "http1");
|
||||
index ? "http2" : "http");
|
||||
#endif
|
||||
|
||||
String serverPath, doc;
|
||||
|
||||
if (index == 0) {
|
||||
serverPath = myConfig.getHttpPushUrl();
|
||||
serverPath = myConfig.getHttpUrl();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP1);
|
||||
} else {
|
||||
serverPath = myConfig.getHttpPushUrl2();
|
||||
serverPath = myConfig.getHttp2Url();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP2);
|
||||
}
|
||||
|
||||
HTTPClient http;
|
||||
if (serverPath.startsWith("https://")) {
|
||||
myWifi.getWifiClientSecure().setInsecure();
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
http.begin(myWifi.getWifiClientSecure(), serverPath);
|
||||
} else {
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
}
|
||||
int httpResponseCode;
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json"));
|
||||
int httpResponseCode = http.POST(doc);
|
||||
if (isSecure) {
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
wifiSecure.setInsecure();
|
||||
|
||||
#if defined (ESP8266)
|
||||
String host = serverPath.substring(8); // remove the prefix or the probe will fail, it needs a pure host name.
|
||||
int idx = host.indexOf("/");
|
||||
if (idx!=-1)
|
||||
host = host.substring(0, idx);
|
||||
|
||||
if (wifiSecure.probeMaxFragmentLength(host, 443, 512)) {
|
||||
Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR));
|
||||
wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
httpSecure.begin(wifiSecure, serverPath);
|
||||
httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(httpSecure, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(httpSecure, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(httpSecure, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(httpSecure, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
httpResponseCode = httpSecure.POST(doc);
|
||||
} else {
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(http, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(http, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(http, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(http, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
httpResponseCode = http.POST(doc);
|
||||
}
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("PUSH: HTTP push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
"PUSH: HTTP push failed response=" + String(httpResponseCode) +
|
||||
String(index == 0 ? " (http)" : " (http2)"));
|
||||
}
|
||||
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
if (isSecure) {
|
||||
httpSecure.end();
|
||||
wifiSecure.stop();
|
||||
} else {
|
||||
http.end();
|
||||
wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine) {
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to mqtt." CR));
|
||||
#endif
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
String url = myConfig.getMqttUrl();
|
||||
String host = myConfig.getMqttUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_MQTT);
|
||||
int port = myConfig.getMqttPort();
|
||||
|
||||
// if (url.endsWith(":8883")) {
|
||||
if (port > 8000) {
|
||||
// Allow secure channel, but without certificate validation
|
||||
myWifi.getWifiClientSecure().setInsecure();
|
||||
if (myConfig.isMqttSSL()) {
|
||||
Log.notice(F("PUSH: MQTT, SSL enabled without validation." CR));
|
||||
mqtt.begin(url.c_str(), port, myWifi.getWifiClientSecure());
|
||||
wifiSecure.setInsecure();
|
||||
|
||||
#if defined (ESP8266)
|
||||
if (wifiSecure.probeMaxFragmentLength(host, port, 512)) {
|
||||
Log.notice(F("PUSH: MQTT server supports smaller SSL buffer." CR));
|
||||
wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
mqtt.begin(host.c_str(), port, wifiSecure);
|
||||
} else {
|
||||
mqtt.begin(myConfig.getMqttUrl(), port, myWifi.getWifiClient());
|
||||
mqtt.begin(host.c_str(), port, wifi);
|
||||
}
|
||||
|
||||
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
|
||||
@ -255,7 +297,7 @@ void PushTarget::sendMqtt(TemplatingEngine& engine) {
|
||||
#endif
|
||||
|
||||
// Send MQQT message(s)
|
||||
mqtt.setTimeout(10); // 10 seconds timeout
|
||||
mqtt.setTimeout(myHardwareConfig.getPushTimeout()); // 10 seconds timeout
|
||||
|
||||
int lines = 1;
|
||||
// Find out how many lines are in the document. Each line is one
|
||||
@ -279,8 +321,9 @@ void PushTarget::sendMqtt(TemplatingEngine& engine) {
|
||||
if (mqtt.publish(topic, value)) {
|
||||
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
|
||||
} else {
|
||||
Log.error(F("PUSH: MQTT publish failed err=%d, ret=%d" CR),
|
||||
mqtt.lastError(), mqtt.returnCode());
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: MQTT push on " + topic +
|
||||
" failed error=" + String(mqtt.lastError()));
|
||||
}
|
||||
|
||||
index = next + 1;
|
||||
@ -288,7 +331,12 @@ void PushTarget::sendMqtt(TemplatingEngine& engine) {
|
||||
}
|
||||
|
||||
mqtt.disconnect();
|
||||
myWifi.closeWifiClient();
|
||||
if (isSecure) {
|
||||
wifiSecure.stop();
|
||||
} else {
|
||||
wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -26,23 +26,31 @@ SOFTWARE.
|
||||
|
||||
#include <templating.hpp>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
class PushTarget {
|
||||
private:
|
||||
uint32_t _ms; // Used to check that we do not post to often
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
HTTPClient httpSecure;
|
||||
|
||||
void sendBrewfather(TemplatingEngine& engine);
|
||||
void sendHttp(TemplatingEngine& engine, int index);
|
||||
void sendHttp(TemplatingEngine& engine, bool isSecure, int index);
|
||||
void sendInfluxDb2(TemplatingEngine& engine);
|
||||
void sendMqtt(TemplatingEngine& engine);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure);
|
||||
void addHttpHeader(HTTPClient& http, String header);
|
||||
|
||||
public:
|
||||
PushTarget() { _ms = millis(); }
|
||||
void send(float angle, float gravitySG, float corrGravitySG, float tempC,
|
||||
float runTime, bool force = false);
|
||||
float runTime);
|
||||
};
|
||||
|
||||
extern PushTarget myPushTarget;
|
||||
|
||||
#endif // SRC_PUSHTARGET_HPP_
|
||||
|
||||
// EOF
|
||||
|
@ -27,12 +27,19 @@ SOFTWARE.
|
||||
// Common strings used in json formats.
|
||||
#define PARAM_ID "id"
|
||||
#define PARAM_MDNS "mdns"
|
||||
#define PARAM_CONFIG_VER "config-version"
|
||||
#define PARAM_OTA "ota-url"
|
||||
#define PARAM_SSID "wifi-ssid"
|
||||
#define PARAM_PASS "wifi-pass"
|
||||
#define PARAM_RUNTIME_AVERAGE "runtime-average"
|
||||
#define PARAM_PUSH_BREWFATHER "brewfather-push"
|
||||
#define PARAM_TOKEN "token"
|
||||
#define PARAM_PUSH_HTTP "http-push"
|
||||
#define PARAM_PUSH_HTTP_H1 "http-push-h1"
|
||||
#define PARAM_PUSH_HTTP_H2 "http-push-h2"
|
||||
#define PARAM_PUSH_HTTP2 "http-push2"
|
||||
#define PARAM_PUSH_HTTP2_H1 "http-push2-h1"
|
||||
#define PARAM_PUSH_HTTP2_H2 "http-push2-h2"
|
||||
#define PARAM_PUSH_INFLUXDB2 "influxdb2-push"
|
||||
#define PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
|
||||
#define PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
|
||||
@ -51,6 +58,9 @@ SOFTWARE.
|
||||
#define PARAM_GYRO_CALIBRATION "gyro-calibration-data"
|
||||
#define PARAM_GYRO_TEMP "gyro-temp"
|
||||
#define PARAM_FORMULA_DATA "formula-calculation-data"
|
||||
#define PARAM_FILES "files"
|
||||
#define PARAM_FILE_NAME "file-name"
|
||||
#define PARAM_FILE_SIZE "file-size"
|
||||
#define PARAM_APP_NAME "app-name"
|
||||
#define PARAM_APP_VER "app-ver"
|
||||
#define PARAM_ANGLE "angle"
|
||||
@ -67,6 +77,7 @@ SOFTWARE.
|
||||
#define PARAM_HW_FORMULA_DEVIATION "formula-max-deviation"
|
||||
#define PARAM_HW_FORMULA_CALIBRATION_TEMP "formula-calibration-temp"
|
||||
#define PARAM_HW_WIFI_PORTALTIMEOUT "wifi-portaltimeout"
|
||||
#define PARAM_HW_PUSH_TIMEOUT "push-timeout"
|
||||
#define PARAM_FORMAT_HTTP1 "http-1"
|
||||
#define PARAM_FORMAT_HTTP2 "http-2"
|
||||
#define PARAM_FORMAT_BREWFATHER "brewfather"
|
||||
|
@ -35,7 +35,7 @@ const char iSpindleFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\" : \"${mdns}\", "
|
||||
"\"ID\": \"${id}\", "
|
||||
"\"token\" : \"gravmon\", "
|
||||
"\"token\" : \"${token}\", "
|
||||
"\"interval\": ${sleep-interval}, "
|
||||
"\"temperature\": ${temp}, "
|
||||
"\"temp-units\": \"${temp-unit}\", "
|
||||
@ -88,6 +88,7 @@ void TemplatingEngine::initialize(float angle, float gravitySG, float corrGravit
|
||||
// Names
|
||||
setVal(TPL_MDNS, myConfig.getMDNS());
|
||||
setVal(TPL_ID, myConfig.getID());
|
||||
setVal(TPL_TOKEN, myConfig.getToken());
|
||||
|
||||
// Temperature
|
||||
if (myConfig.isTempC()) {
|
||||
@ -138,6 +139,7 @@ void TemplatingEngine::initialize(float angle, float gravitySG, float corrGravit
|
||||
//
|
||||
const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
String fname;
|
||||
baseTemplate.reserve(600);
|
||||
|
||||
// Load templates from memory
|
||||
switch (idx) {
|
||||
|
@ -26,30 +26,32 @@ SOFTWARE.
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
#include <main.hpp>
|
||||
#include <helper.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
// Templating variables
|
||||
#define TPL_MDNS "${mdns}"
|
||||
#define TPL_ID "${id}"
|
||||
#define TPL_TOKEN "${token}"
|
||||
#define TPL_SLEEP_INTERVAL "${sleep-interval}"
|
||||
#define TPL_TEMP "${temp}"
|
||||
#define TPL_TEMP_C "${temp-c}"
|
||||
#define TPL_TEMP_F "${temp-f}"
|
||||
#define TPL_TEMP_UNITS "${temp-unit}" // C or F
|
||||
#define TPL_TEMP_UNITS "${temp-unit}" // C or F
|
||||
#define TPL_BATTERY "${battery}"
|
||||
#define TPL_RSSI "${rssi}"
|
||||
#define TPL_RUN_TIME "${run-time}"
|
||||
#define TPL_ANGLE "${angle}"
|
||||
#define TPL_TILT "${tilt}" // same as angle
|
||||
#define TPL_TILT "${tilt}" // same as angle
|
||||
#define TPL_GRAVITY "${gravity}"
|
||||
#define TPL_GRAVITY_G "${gravity-sg}"
|
||||
#define TPL_GRAVITY_P "${gravity-plato}"
|
||||
#define TPL_GRAVITY_CORR "${corr-gravity}"
|
||||
#define TPL_GRAVITY_CORR_G "${corr-gravity-sg}"
|
||||
#define TPL_GRAVITY_CORR_P "${corr-gravity-plato}"
|
||||
#define TPL_GRAVITY_UNIT "${gravity-unit}" // G or P
|
||||
#define TPL_GRAVITY_UNIT "${gravity-unit}" // G or P
|
||||
|
||||
#define TPL_FNAME_HTTP1 "/http-1.tpl"
|
||||
#define TPL_FNAME_HTTP2 "/http-2.tpl"
|
||||
@ -70,36 +72,29 @@ class TemplatingEngine {
|
||||
String val;
|
||||
};
|
||||
|
||||
KeyVal items[19] = {
|
||||
{ TPL_MDNS, "" },
|
||||
{ TPL_ID, "" },
|
||||
{ TPL_SLEEP_INTERVAL, "" },
|
||||
{ TPL_TEMP, "" },
|
||||
{ TPL_TEMP_C, "" },
|
||||
{ TPL_TEMP_F, "" },
|
||||
{ TPL_TEMP_UNITS, "" },
|
||||
{ TPL_BATTERY, "" },
|
||||
{ TPL_RSSI, "" },
|
||||
{ TPL_RUN_TIME, "" },
|
||||
{ TPL_ANGLE, "" },
|
||||
{ TPL_TILT, "" },
|
||||
{ TPL_GRAVITY, "" },
|
||||
{ TPL_GRAVITY_G, "" },
|
||||
{ TPL_GRAVITY_P, "" },
|
||||
{ TPL_GRAVITY_CORR, "" },
|
||||
{ TPL_GRAVITY_CORR_G, "" },
|
||||
{ TPL_GRAVITY_CORR_P, "" },
|
||||
{ TPL_GRAVITY_UNIT, "" }
|
||||
};
|
||||
KeyVal items[20] = {{TPL_MDNS, ""}, {TPL_ID, ""},
|
||||
{TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""},
|
||||
{TPL_TEMP_C, ""}, {TPL_TEMP_F, ""},
|
||||
{TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""},
|
||||
{TPL_RSSI, ""}, {TPL_RUN_TIME, ""},
|
||||
{TPL_ANGLE, ""}, {TPL_TILT, ""},
|
||||
{TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""},
|
||||
{TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""},
|
||||
{TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""},
|
||||
{TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""}};
|
||||
|
||||
char buffer[20];
|
||||
String baseTemplate;
|
||||
|
||||
void setVal(String key, float val, int dec = 2) { String s = convertFloatToString(val, &buffer[0], dec); s.trim(); setVal(key, s); }
|
||||
void setVal(String key, float val, int dec = 2) {
|
||||
String s = convertFloatToString(val, &buffer[0], dec);
|
||||
s.trim();
|
||||
setVal(key, s);
|
||||
}
|
||||
void setVal(String key, int val) { setVal(key, String(val)); }
|
||||
void setVal(String key, char val) { setVal(key, String(val)); }
|
||||
void setVal(String key, String val) {
|
||||
int max = sizeof(items)/sizeof(KeyVal);
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (items[i].key.equals(key)) {
|
||||
items[i].val = val;
|
||||
@ -107,11 +102,11 @@ class TemplatingEngine {
|
||||
}
|
||||
}
|
||||
|
||||
Log.error(F("TPL : Key not found %s." CR), key.c_str());
|
||||
Log.warning(F("TPL : Key not found %s." CR), key.c_str());
|
||||
}
|
||||
|
||||
void transform(String& s) {
|
||||
int max = sizeof(items)/sizeof(KeyVal);
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
while (s.indexOf(items[i].key) != -1)
|
||||
s.replace(items[i].key, items[i].val);
|
||||
@ -119,13 +114,13 @@ class TemplatingEngine {
|
||||
}
|
||||
|
||||
void dumpAll() {
|
||||
int max = sizeof(items)/sizeof(KeyVal);
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Serial.print( "Key=\'" );
|
||||
Serial.print( items[i].key.c_str() );
|
||||
Serial.print( "\', Val=\'" );
|
||||
Serial.print( items[i].val.c_str() );
|
||||
Serial.println( "\'" );
|
||||
Serial.print("Key=\'");
|
||||
Serial.print(items[i].key.c_str());
|
||||
Serial.print("\', Val=\'");
|
||||
Serial.print(items[i].val.c_str());
|
||||
Serial.println("\'");
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +133,8 @@ class TemplatingEngine {
|
||||
TEMPLATE_MQTT = 4
|
||||
};
|
||||
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG, float tempC, float runTime);
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime);
|
||||
const String& create(TemplatingEngine::Templates idx);
|
||||
};
|
||||
|
||||
|
@ -42,7 +42,7 @@ extern bool sleepModeAlwaysSkip;
|
||||
void WebServerHandler::webHandleDevice() {
|
||||
LOG_PERF_START("webserver-api-device");
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
Log.verbose(F("WEB : webServer callback for /api/device." CR));
|
||||
Log.verbose(F("WEB : webServer callback for /api/device(get)." CR));
|
||||
#endif
|
||||
|
||||
DynamicJsonDocument doc(100);
|
||||
@ -50,11 +50,17 @@ void WebServerHandler::webHandleDevice() {
|
||||
doc[PARAM_APP_NAME] = CFG_APPNAME;
|
||||
doc[PARAM_APP_VER] = CFG_APPVER;
|
||||
doc[PARAM_MDNS] = myConfig.getMDNS();
|
||||
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
doc[PARAM_RUNTIME_AVERAGE] = reduceFloatPrecision(
|
||||
runLog.getAverage() ? runLog.getAverage() / 1000 : 0, 1);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
String out;
|
||||
out.reserve(100);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-device");
|
||||
@ -65,14 +71,18 @@ void WebServerHandler::webHandleDevice() {
|
||||
//
|
||||
void WebServerHandler::webHandleConfig() {
|
||||
LOG_PERF_START("webserver-api-config");
|
||||
Log.notice(F("WEB : webServer callback for /api/config." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
myConfig.createJson(doc);
|
||||
|
||||
doc[PARAM_PASS] = ""; // dont show the wifi password
|
||||
|
||||
double angle = myGyro.getAngle();
|
||||
double angle = 0;
|
||||
|
||||
if (myGyro.hasValue())
|
||||
angle = myGyro.getAngle();
|
||||
|
||||
double tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
double gravity = calculateGravity(angle, tempC);
|
||||
|
||||
@ -92,12 +102,17 @@ void WebServerHandler::webHandleConfig() {
|
||||
|
||||
doc[PARAM_BATTERY] = reduceFloatPrecision(myBatteryVoltage.getVoltage());
|
||||
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
doc[PARAM_RUNTIME_AVERAGE] = reduceFloatPrecision(
|
||||
runLog.getAverage() ? runLog.getAverage() / 1000 : 0, 1);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(CFG_JSON_BUFSIZE);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-config");
|
||||
@ -109,7 +124,7 @@ void WebServerHandler::webHandleConfig() {
|
||||
void WebServerHandler::webHandleUpload() {
|
||||
LOG_PERF_START("webserver-api-upload");
|
||||
Log.notice(F("WEB : webServer callback for /api/upload." CR));
|
||||
DynamicJsonDocument doc(100);
|
||||
DynamicJsonDocument doc(300);
|
||||
|
||||
doc["index"] = checkHtmlFile(WebServerHandler::HTML_INDEX);
|
||||
doc["device"] = checkHtmlFile(WebServerHandler::HTML_DEVICE);
|
||||
@ -118,12 +133,29 @@ void WebServerHandler::webHandleUpload() {
|
||||
doc["format"] = checkHtmlFile(WebServerHandler::HTML_FORMAT);
|
||||
doc["about"] = checkHtmlFile(WebServerHandler::HTML_ABOUT);
|
||||
|
||||
#if defined(ESP8266)
|
||||
JsonArray files = doc.createNestedArray(PARAM_FILES);
|
||||
|
||||
// Show files in the filessytem at startup
|
||||
FSInfo fs;
|
||||
LittleFS.info(fs);
|
||||
Dir dir = LittleFS.openDir("/");
|
||||
while (dir.next()) {
|
||||
JsonObject obj = files.createNestedObject();
|
||||
obj[PARAM_FILE_NAME] = dir.fileName();
|
||||
obj[PARAM_FILE_SIZE] = dir.fileSize();
|
||||
}
|
||||
#else // defined(ESP32)
|
||||
#warning "TODO: Implement file listing for ESP32"
|
||||
#endif
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(300);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-upload");
|
||||
@ -199,13 +231,21 @@ void WebServerHandler::webHandleCalibrate() {
|
||||
//
|
||||
// Callback from webServer when / has been accessed.
|
||||
//
|
||||
void WebServerHandler::webHandleFactoryReset() {
|
||||
void WebServerHandler::webHandleFactoryDefaults() {
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/factory." CR));
|
||||
|
||||
if (!id.compareTo(myConfig.getID())) {
|
||||
_server->send(200, "text/plain", "Doing reset...");
|
||||
_server->send(200, "text/plain",
|
||||
"Removing configuration and restarting...");
|
||||
LittleFS.remove(CFG_FILENAME);
|
||||
LittleFS.remove(CFG_HW_FILENAME);
|
||||
LittleFS.remove(ERR_FILENAME);
|
||||
LittleFS.remove(RUNTIME_FILENAME);
|
||||
LittleFS.remove(TPL_FNAME_HTTP1);
|
||||
LittleFS.remove(TPL_FNAME_HTTP2);
|
||||
LittleFS.remove(TPL_FNAME_INFLUXDB);
|
||||
LittleFS.remove(TPL_FNAME_MQTT);
|
||||
LittleFS.end();
|
||||
delay(500);
|
||||
ESP_RESET();
|
||||
@ -219,11 +259,15 @@ void WebServerHandler::webHandleFactoryReset() {
|
||||
//
|
||||
void WebServerHandler::webHandleStatus() {
|
||||
LOG_PERF_START("webserver-api-status");
|
||||
Log.notice(F("WEB : webServer callback for /api/status." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/status(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(256);
|
||||
|
||||
double angle = myGyro.getAngle();
|
||||
double angle = 0;
|
||||
|
||||
if (myGyro.hasValue())
|
||||
angle = myGyro.getAngle();
|
||||
|
||||
double tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
double gravity = calculateGravity(angle, tempC);
|
||||
|
||||
@ -251,6 +295,7 @@ void WebServerHandler::webHandleStatus() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(300);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-status");
|
||||
@ -280,7 +325,7 @@ void WebServerHandler::webHandleClearWIFI() {
|
||||
void WebServerHandler::webHandleStatusSleepmode() {
|
||||
LOG_PERF_START("webserver-api-sleepmode");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/status/sleepmode." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/status/sleepmode(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -308,7 +353,7 @@ void WebServerHandler::webHandleStatusSleepmode() {
|
||||
void WebServerHandler::webHandleConfigDevice() {
|
||||
LOG_PERF_START("webserver-api-config-device");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/config/device." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/device(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -322,9 +367,12 @@ void WebServerHandler::webHandleConfigDevice() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
myConfig.setMDNS(_server->arg(PARAM_MDNS).c_str());
|
||||
myConfig.setTempFormat(_server->arg(PARAM_TEMPFORMAT).charAt(0));
|
||||
myConfig.setSleepInterval(_server->arg(PARAM_SLEEP_INTERVAL).c_str());
|
||||
if (_server->hasArg(PARAM_MDNS))
|
||||
myConfig.setMDNS(_server->arg(PARAM_MDNS).c_str());
|
||||
if (_server->hasArg(PARAM_TEMPFORMAT))
|
||||
myConfig.setTempFormat(_server->arg(PARAM_TEMPFORMAT).charAt(0));
|
||||
if (_server->hasArg(PARAM_SLEEP_INTERVAL))
|
||||
myConfig.setSleepInterval(_server->arg(PARAM_SLEEP_INTERVAL).c_str());
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseOne", true);
|
||||
_server->send(302, "text/plain", "Device config updated");
|
||||
@ -337,7 +385,7 @@ void WebServerHandler::webHandleConfigDevice() {
|
||||
void WebServerHandler::webHandleConfigPush() {
|
||||
LOG_PERF_START("webserver-api-config-push");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/config/push." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/push(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -350,19 +398,41 @@ void WebServerHandler::webHandleConfigPush() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
myConfig.setHttpPushUrl(_server->arg(PARAM_PUSH_HTTP).c_str());
|
||||
myConfig.setHttpPushUrl2(_server->arg(PARAM_PUSH_HTTP2).c_str());
|
||||
myConfig.setBrewfatherPushUrl(_server->arg(PARAM_PUSH_BREWFATHER).c_str());
|
||||
myConfig.setInfluxDb2PushUrl(_server->arg(PARAM_PUSH_INFLUXDB2).c_str());
|
||||
myConfig.setInfluxDb2PushOrg(_server->arg(PARAM_PUSH_INFLUXDB2_ORG).c_str());
|
||||
myConfig.setInfluxDb2PushBucket(
|
||||
_server->arg(PARAM_PUSH_INFLUXDB2_BUCKET).c_str());
|
||||
myConfig.setInfluxDb2PushToken(
|
||||
_server->arg(PARAM_PUSH_INFLUXDB2_AUTH).c_str());
|
||||
myConfig.setMqttUrl(_server->arg(PARAM_PUSH_MQTT).c_str());
|
||||
myConfig.setMqttPort(_server->arg(PARAM_PUSH_MQTT_PORT).c_str());
|
||||
myConfig.setMqttUser(_server->arg(PARAM_PUSH_MQTT_USER).c_str());
|
||||
myConfig.setMqttPass(_server->arg(PARAM_PUSH_MQTT_PASS).c_str());
|
||||
if (_server->hasArg(PARAM_TOKEN))
|
||||
myConfig.setToken(_server->arg(PARAM_TOKEN).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP))
|
||||
myConfig.setHttpUrl(_server->arg(PARAM_PUSH_HTTP).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP_H1))
|
||||
myConfig.setHttpHeader(_server->arg(PARAM_PUSH_HTTP_H1).c_str(), 0);
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP_H2))
|
||||
myConfig.setHttpHeader(_server->arg(PARAM_PUSH_HTTP_H2).c_str(), 1);
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP2))
|
||||
myConfig.setHttp2Url(_server->arg(PARAM_PUSH_HTTP2).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP2_H1))
|
||||
myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H1).c_str(), 0);
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP2_H2))
|
||||
myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H2).c_str(), 1);
|
||||
if (_server->hasArg(PARAM_PUSH_BREWFATHER))
|
||||
myConfig.setBrewfatherPushUrl(_server->arg(PARAM_PUSH_BREWFATHER).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2))
|
||||
myConfig.setInfluxDb2PushUrl(_server->arg(PARAM_PUSH_INFLUXDB2).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2_ORG))
|
||||
myConfig.setInfluxDb2PushOrg(
|
||||
_server->arg(PARAM_PUSH_INFLUXDB2_ORG).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2_BUCKET))
|
||||
myConfig.setInfluxDb2PushBucket(
|
||||
_server->arg(PARAM_PUSH_INFLUXDB2_BUCKET).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2_AUTH))
|
||||
myConfig.setInfluxDb2PushToken(
|
||||
_server->arg(PARAM_PUSH_INFLUXDB2_AUTH).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_MQTT))
|
||||
myConfig.setMqttUrl(_server->arg(PARAM_PUSH_MQTT).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_MQTT_PORT))
|
||||
myConfig.setMqttPort(_server->arg(PARAM_PUSH_MQTT_PORT).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_MQTT_USER))
|
||||
myConfig.setMqttUser(_server->arg(PARAM_PUSH_MQTT_USER).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_MQTT_PASS))
|
||||
myConfig.setMqttPass(_server->arg(PARAM_PUSH_MQTT_PASS).c_str());
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseTwo", true);
|
||||
_server->send(302, "text/plain", "Push config updated");
|
||||
@ -394,7 +464,7 @@ String WebServerHandler::getRequestArguments() {
|
||||
void WebServerHandler::webHandleConfigGravity() {
|
||||
LOG_PERF_START("webserver-api-config-gravity");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/config/gravity." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/gravity(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -408,11 +478,14 @@ void WebServerHandler::webHandleConfigGravity() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
myConfig.setGravityFormat(_server->arg(PARAM_GRAVITY_FORMAT).charAt(0));
|
||||
myConfig.setGravityFormula(_server->arg(PARAM_GRAVITY_FORMULA).c_str());
|
||||
myConfig.setGravityTempAdj(
|
||||
_server->arg(PARAM_GRAVITY_TEMP_ADJ).equalsIgnoreCase("on") ? true
|
||||
: false);
|
||||
if (_server->hasArg(PARAM_GRAVITY_FORMAT))
|
||||
myConfig.setGravityFormat(_server->arg(PARAM_GRAVITY_FORMAT).charAt(0));
|
||||
if (_server->hasArg(PARAM_GRAVITY_FORMULA))
|
||||
myConfig.setGravityFormula(_server->arg(PARAM_GRAVITY_FORMULA).c_str());
|
||||
if (_server->hasArg(PARAM_GRAVITY_TEMP_ADJ))
|
||||
myConfig.setGravityTempAdj(
|
||||
_server->arg(PARAM_GRAVITY_TEMP_ADJ).equalsIgnoreCase("on") ? true
|
||||
: false);
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseThree", true);
|
||||
_server->send(302, "text/plain", "Gravity config updated");
|
||||
@ -425,7 +498,7 @@ void WebServerHandler::webHandleConfigGravity() {
|
||||
void WebServerHandler::webHandleConfigHardware() {
|
||||
LOG_PERF_START("webserver-api-config-hardware");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/config/hardware." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/hardware(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -439,15 +512,20 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
myConfig.setVoltageFactor(_server->arg(PARAM_VOLTAGEFACTOR).toFloat());
|
||||
if (myConfig.isTempC()) {
|
||||
myConfig.setTempSensorAdjC(_server->arg(PARAM_TEMP_ADJ));
|
||||
} else {
|
||||
myConfig.setTempSensorAdjF(_server->arg(PARAM_TEMP_ADJ));
|
||||
if (_server->hasArg(PARAM_VOLTAGEFACTOR))
|
||||
myConfig.setVoltageFactor(_server->arg(PARAM_VOLTAGEFACTOR).toFloat());
|
||||
if (_server->hasArg(PARAM_TEMP_ADJ)) {
|
||||
if (myConfig.isTempC()) {
|
||||
myConfig.setTempSensorAdjC(_server->arg(PARAM_TEMP_ADJ));
|
||||
} else {
|
||||
myConfig.setTempSensorAdjF(_server->arg(PARAM_TEMP_ADJ));
|
||||
}
|
||||
}
|
||||
myConfig.setOtaURL(_server->arg(PARAM_OTA).c_str());
|
||||
myConfig.setGyroTemp(
|
||||
_server->arg(PARAM_GYRO_TEMP).equalsIgnoreCase("on") ? true : false);
|
||||
if (_server->hasArg(PARAM_OTA))
|
||||
myConfig.setOtaURL(_server->arg(PARAM_OTA).c_str());
|
||||
if (_server->hasArg(PARAM_GYRO_TEMP))
|
||||
myConfig.setGyroTemp(
|
||||
_server->arg(PARAM_GYRO_TEMP).equalsIgnoreCase("on") ? true : false);
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseFour", true);
|
||||
_server->send(302, "text/plain", "Hardware config updated");
|
||||
@ -460,7 +538,7 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
void WebServerHandler::webHandleDeviceParam() {
|
||||
LOG_PERF_START("webserver-api-device-param");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/device/param." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/device/param(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
@ -491,6 +569,8 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
myHardwareConfig.SetDefaultCalibrationTemp(s.toFloat());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_WIFI_PORTALTIMEOUT))
|
||||
myHardwareConfig.setWifiPortalTimeout(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_PUSH_TIMEOUT))
|
||||
myHardwareConfig.setPushTimeout(s.toInt());
|
||||
}
|
||||
|
||||
myHardwareConfig.saveFile();
|
||||
@ -514,6 +594,7 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(512);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-device-param");
|
||||
@ -580,6 +661,7 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(256);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-formula-read");
|
||||
@ -626,7 +708,8 @@ void WebServerHandler::webHandleConfigFormatWrite() {
|
||||
_server->sendHeader("Location", "/format.htm", true);
|
||||
_server->send(302, "text/plain", "Format updated");
|
||||
} else {
|
||||
Log.error(F("WEB : Unable to store format file" CR));
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("WEB : Unable to store format file"));
|
||||
_server->send(400, "text/plain", "Unable to store format in file.");
|
||||
}
|
||||
|
||||
@ -725,6 +808,7 @@ void WebServerHandler::webHandleConfigFormatRead() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(2048);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-config-format-read");
|
||||
@ -920,6 +1004,8 @@ bool WebServerHandler::setupWebServer() {
|
||||
_server->on("/", std::bind(&WebServerHandler::webReturnUploadHtm, this));
|
||||
}
|
||||
#endif
|
||||
_server->serveStatic("/log", LittleFS, ERR_FILENAME);
|
||||
_server->serveStatic("/runtime", LittleFS, RUNTIME_FILENAME);
|
||||
|
||||
// Dynamic content
|
||||
_server->on(
|
||||
@ -938,7 +1024,7 @@ bool WebServerHandler::setupWebServer() {
|
||||
std::bind(&WebServerHandler::webHandleCalibrate,
|
||||
this)); // Run calibration routine (param id)
|
||||
_server->on("/api/factory", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleFactoryReset,
|
||||
std::bind(&WebServerHandler::webHandleFactoryDefaults,
|
||||
this)); // Reset the device
|
||||
_server->on("/api/status", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleStatus,
|
||||
|
@ -64,7 +64,7 @@ class WebServerHandler {
|
||||
void webHandleStatusSleepmode();
|
||||
void webHandleClearWIFI();
|
||||
void webHandleStatus();
|
||||
void webHandleFactoryReset();
|
||||
void webHandleFactoryDefaults();
|
||||
void webHandleCalibrate();
|
||||
void webHandleUploadFile();
|
||||
void webHandleUpload();
|
||||
|
75
src/wifi.cpp
@ -21,24 +21,28 @@ 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.
|
||||
*/
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <main.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// Settings for DRD
|
||||
#if defined (ESP8266)
|
||||
#if defined(ESP8266)
|
||||
#define ESP_DRD_USE_LITTLEFS true
|
||||
#define ESP_DRD_USE_SPIFFS false
|
||||
#else // defined (ESP32)
|
||||
#else // defined (ESP32)
|
||||
#define ESP_DRD_USE_LITTLEFS false
|
||||
#define ESP_DRD_USE_SPIFFS true
|
||||
#endif
|
||||
@ -191,8 +195,9 @@ bool WifiConnection::waitForConnection(int maxTime) {
|
||||
|
||||
if (i++ >
|
||||
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
|
||||
Log.error(F("WIFI: Failed to connect to wifi %d, aborting %s." CR),
|
||||
WiFi.status(), getIPAddress().c_str());
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to connect to wifi " +
|
||||
String(WiFi.status()));
|
||||
WiFi.disconnect();
|
||||
Serial.print(CR);
|
||||
return false; // Return to main that we have failed to connect.
|
||||
@ -236,32 +241,36 @@ bool WifiConnection::updateFirmware() {
|
||||
Log.verbose(F("WIFI: Updating firmware." CR));
|
||||
#endif
|
||||
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPUpdateResult ret;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "firmware.bin";
|
||||
HTTPUpdateResult ret;
|
||||
|
||||
if (serverPath.startsWith("https://")) {
|
||||
myWifi.getWifiClientSecure().setInsecure();
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
ret = ESPhttpUpdate.update(myWifi.getWifiClientSecure(), serverPath);
|
||||
ret = ESPhttpUpdate.update(wifiSecure, serverPath);
|
||||
} else {
|
||||
ret = ESPhttpUpdate.update(myWifi.getWifiClient(), serverPath);
|
||||
ret = ESPhttpUpdate.update(wifi, serverPath);
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
Log.error(F("WIFI: OTA update failed %d, %s." CR),
|
||||
ESPhttpUpdate.getLastError(),
|
||||
ESPhttpUpdate.getLastErrorString().c_str());
|
||||
break;
|
||||
case HTTP_UPDATE_FAILED: {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: OTA update failed " +
|
||||
String(ESPhttpUpdate.getLastError()));
|
||||
} break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
case HTTP_UPDATE_OK: {
|
||||
Log.notice("WIFI: OTA Update sucesfull, rebooting.");
|
||||
delay(100);
|
||||
ESP_RESET();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -272,16 +281,18 @@ void WifiConnection::downloadFile(const char *fname) {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Download file %s." CR), fname);
|
||||
#endif
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += fname;
|
||||
|
||||
if (serverPath.startsWith("https://")) {
|
||||
myWifi.getWifiClientSecure().setInsecure();
|
||||
if (myConfig.isOtaSSL()) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
http.begin(myWifi.getWifiClientSecure(), serverPath);
|
||||
http.begin(wifiSecure, serverPath);
|
||||
} else {
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
}
|
||||
|
||||
int httpResponseCode = http.GET();
|
||||
@ -292,11 +303,11 @@ void WifiConnection::downloadFile(const char *fname) {
|
||||
f.close();
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
|
||||
} else {
|
||||
Log.error(F("WIFI: Failed to download file, respone=%d" CR),
|
||||
httpResponseCode);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to download html-file " +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
}
|
||||
|
||||
//
|
||||
@ -306,17 +317,19 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Checking if new version exist." CR));
|
||||
#endif
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "version.json";
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
if (serverPath.startsWith("https://")) {
|
||||
myWifi.getWifiClientSecure().setInsecure();
|
||||
if (myConfig.isOtaSSL()) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
http.begin(myWifi.getWifiClientSecure(), serverPath);
|
||||
http.begin(wifiSecure, serverPath);
|
||||
} else {
|
||||
http.begin(myWifi.getWifiClient(), serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
}
|
||||
|
||||
// Send HTTP GET request
|
||||
@ -332,7 +345,8 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
DynamicJsonDocument ver(300);
|
||||
DeserializationError err = deserializeJson(ver, payload);
|
||||
if (err) {
|
||||
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("WIFI: Failed to parse version.json"));
|
||||
} else {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Project %s version %s." CR),
|
||||
@ -378,11 +392,12 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
myWifi.closeWifiClient();
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: OTA found new version %s." CR),
|
||||
_newFirmware ? "true" : "false");
|
||||
#endif
|
||||
|
||||
return _newFirmware;
|
||||
}
|
||||
|
||||
|
24
src/wifi.hpp
@ -24,28 +24,14 @@ SOFTWARE.
|
||||
#ifndef SRC_WIFI_HPP_
|
||||
#define SRC_WIFI_HPP_
|
||||
|
||||
#if defined (ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
|
||||
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
|
||||
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
|
||||
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
|
||||
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
struct tcp_pcb;
|
||||
extern struct tcp_pcb* tcp_tw_pcbs;
|
||||
extern "C" void tcp_abort(struct tcp_pcb* pcb);
|
||||
|
||||
class WifiConnection {
|
||||
private:
|
||||
// WIFI
|
||||
WiFiClient _client;
|
||||
WiFiClientSecure _secureClient;
|
||||
|
||||
// OTA
|
||||
bool _newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char* version);
|
||||
@ -67,16 +53,6 @@ class WifiConnection {
|
||||
void startPortal();
|
||||
void loop();
|
||||
|
||||
WiFiClient& getWifiClient() { return _client; }
|
||||
WiFiClientSecure& getWifiClientSecure() { return _secureClient; }
|
||||
void closeWifiClient() {
|
||||
_client.stop();
|
||||
_secureClient.stop();
|
||||
|
||||
// Cleanup memory allocated by open tcp connetions.
|
||||
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
// OTA
|
||||
bool updateFirmware();
|
||||
bool checkFirmwareVersion();
|
||||
|
@ -23,6 +23,7 @@ Other parameters are the same as in the configuration guide.
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"token": "token",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
"http-push-h1": "header: value",
|
||||
"http-push-h2": "header: value",
|
||||
@ -54,7 +55,8 @@ Other parameters are the same as in the configuration guide.
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04
|
||||
"battery": 0.04,
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
@ -69,7 +71,8 @@ Retrive the current device settings via an HTTP GET command. Payload is in JSON
|
||||
"app-name": "GravityMon",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon"
|
||||
"mdns": "gravmon",
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
@ -125,6 +128,18 @@ Retrive the data used for formula calculation data via an HTTP GET command. Payl
|
||||
}
|
||||
|
||||
|
||||
GET: /api/factory
|
||||
================
|
||||
|
||||
Will do a reset to factory defaults and delete all data except wifi settings.
|
||||
|
||||
For this to work you will need to supply the device id as a parameter in the request:
|
||||
|
||||
::
|
||||
|
||||
http://mygravity.local/api/factory?id=<mydeviceid>
|
||||
|
||||
|
||||
POST: /api/config/device
|
||||
========================
|
||||
|
||||
@ -271,6 +286,7 @@ The requests package converts the json to standard form post format.
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"token": "",
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"http-push-h1": "",
|
||||
|
@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.7.1'
|
||||
release = '0.8.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -41,6 +41,10 @@ URL: (http://gravmon.local/device)
|
||||
:width: 800
|
||||
:alt: Device Settings
|
||||
|
||||
.. tip::
|
||||
|
||||
The button `view error log` will show the last 15 errors on the device. This can be useful for checking errors without
|
||||
the need to connect to the serial port or to check what errors has occured while in `gravity mode`.
|
||||
|
||||
* **Version:**
|
||||
|
||||
@ -55,6 +59,12 @@ URL: (http://gravmon.local/device)
|
||||
This is unique identifier for the device (ESP8266 id), this is required when using the API as an API Key to safeguard
|
||||
against faulty requests. This is the ESP8266 chip ID, so it should be unique.
|
||||
|
||||
* **Average runtime:**
|
||||
|
||||
This shows the average time a gravity measurement takes. Under optimal setting this should be
|
||||
around 1.5 - 2.0 seconds. If this is higher than 2 seconds this is most likley connected to slow wifi
|
||||
connection. It will show 0 if data has not been collected yet.
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
@ -121,6 +131,11 @@ Push Settings
|
||||
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
* **Token:**
|
||||
|
||||
The token is included in the iSpindle JSON format and will be used for both HTTP targets. If you
|
||||
need to have 2 different tokens please use the :ref:`format-editor` to customize the data format.
|
||||
|
||||
* **Brewfather URL:**
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
@ -161,6 +176,28 @@ Push Settings
|
||||
|
||||
Password or blank if anonymous is accepted
|
||||
|
||||
* **HTTP Headers**
|
||||
|
||||
.. image:: images/config-popup1.png
|
||||
:width: 300
|
||||
:alt: HTTP Headers
|
||||
|
||||
You can define 2 http headers per push target. This is available via a pop-up window but dont forget
|
||||
to press the save buttons on the post section to save the values. One common header is content type which is the
|
||||
default setting for http targets.
|
||||
|
||||
The input must have the format **'<header>: <value>'** for it to work. The UI will accept any value so errors
|
||||
will not show until the device tries to push data.
|
||||
|
||||
|
||||
::
|
||||
|
||||
Content-Type: application/json
|
||||
X-Auth-Token: <api-token>
|
||||
|
||||
|
||||
Mozilla has a good guide on what headers are valid; `HTTP Headers <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers>`_
|
||||
|
||||
|
||||
Gravity Settings
|
||||
++++++++++++++++
|
||||
@ -234,5 +271,6 @@ Hardware Settings
|
||||
.. code-block::
|
||||
|
||||
http://192.168.1.1/firmware/gravmon/
|
||||
https://192.168.1.1/firmware/gravmon/
|
||||
|
||||
|
||||
|
@ -12,8 +12,12 @@ The main features
|
||||
angle/tile, temperature, calculates gravity and pushes the data to defined endpoints.
|
||||
|
||||
In ``configuration mode`` the device is always active and the webserver is active. Here you can view the
|
||||
angle/tilt values, change configuration options and more. When in this mode you can also interact with the device
|
||||
via an REST API so data can be pushed to the device via scripts (see API section for more information).
|
||||
angle/tilt values, change configuration, update the gravity formula. When in this mode you can also interact
|
||||
with the device via an REST API so data can be pushed to the device via scripts (see API section for more information).
|
||||
|
||||
.. image:: images/index.png
|
||||
:width: 700
|
||||
:alt: UI example
|
||||
|
||||
You can force the device into ``configuration mode`` while measuring gravity. This is useful when calibrating
|
||||
the device so you don't needs to wait for the device to wake up and push the data. The entire calibration
|
||||
@ -21,61 +25,83 @@ The main features
|
||||
|
||||
See the :ref:`setting-up-device` section for more information on how to trigger the configuration mode.
|
||||
|
||||
* **Can send data to multiple endpoints at once**
|
||||
* **Can send data to multiple endpoints**
|
||||
|
||||
The original iSpindle can only have one destination, this software will push data to all defined endpoints so
|
||||
in theory you can use them all. However this will consume more battery power so use only as many as needed.
|
||||
in theory you can use them all. However this will consume more battery power so use only as many as needed. Its much
|
||||
more efficient to have the endpoints on your local network than on the internet.
|
||||
|
||||
Currently the device supports the following endpoints: http (2 different), influxdb2, Brewfather and MQTT.
|
||||
Currently the device supports the following endpoints.
|
||||
|
||||
If you want additional targets please raise a feature request in the github repo.
|
||||
* http or https
|
||||
* influxdb v2
|
||||
* Brewfather
|
||||
* MQTT
|
||||
* Home Assistant
|
||||
* Brew Spy
|
||||
* Brewers Friend
|
||||
* Fermentrack
|
||||
* Ubidots
|
||||
* Thingsspeak
|
||||
|
||||
|
||||
Under the :ref:`services` section you can find guides for how to connect GravityMon to these services. For a
|
||||
description of what data is transmitted you can see :ref:`data-formats`.
|
||||
|
||||
The software support SSL endpoints but currently without CA validation, this means that the data is encrypted
|
||||
but it does not validate if the remote endpoint is who it claims to be.
|
||||
|
||||
if you require CA validation please leave a comment on GitHub and I will make that a priority. Adding this function
|
||||
will dramatically reduce the battery life of the device.
|
||||
|
||||
.. note::
|
||||
|
||||
Using SSL on a small device such as the esp8266 can be unstable since it requires a lot of RAM to work. And running out
|
||||
of RAM will cause the device to crash. So enable SSL with caution and only when you really need it. GravityMon will try
|
||||
to minimize the needed RAM but the remote service might not support that feature.
|
||||
|
||||
* **Create gravity formulas on the device**
|
||||
|
||||
Another big difference is that this software can create the gravity formula in the device, just enter the
|
||||
angle/gravity data that you have collected. You will also see a graph simulating how the formula would work.
|
||||
|
||||
.. note::
|
||||
Currently the device can handle 5 data points which should be enough to get a accurate formula. At least 3 data points
|
||||
is needed to get an accurate formula.
|
||||
|
||||
This feature needs more testing to be validated.
|
||||
If there is a need for more data points, raise a comment on github.
|
||||
|
||||
* **Customize the data format beeing sent to push targets**
|
||||
|
||||
In order to make it easier to support more targets there is a built in format editor that can be used to
|
||||
customize the data that is to be sent. This way you can easily adapt the software to new targets without coding.
|
||||
If you have a good template please share it on the girhub repository and I will add it to the documentation
|
||||
for other users to enjoy. See the :ref:`format-editor` for more information.
|
||||
|
||||
.. note::
|
||||
|
||||
This feature needs more testing to be validated.
|
||||
If you have a good template please share it on the github repository and I will add it to the documentation
|
||||
for other users to enjoy. See the :ref:`format-editor` for more information. See :ref:`services` for a list of
|
||||
services currently validated.
|
||||
|
||||
* **Automatic temperature adjustment of gravity reading**
|
||||
|
||||
If you want to correct gravity based on beer temperature you can do this in the formula but here is a nice
|
||||
feature that can correct the gravity as a second step making this independant of the formula.
|
||||
|
||||
.. note::
|
||||
* **OTA support from webserver**
|
||||
|
||||
This feature needs more testing to be validated.
|
||||
|
||||
* **OTA support from local webserver**
|
||||
|
||||
When starting up in configuration mode the device will check for a software update from a local webserver.
|
||||
When starting up in configuration mode the device will check for a software update from a webserver. This is an easily
|
||||
way to keep the software up to date. In the future I might add a hosted endpoint for providing updates.
|
||||
|
||||
* **DS18B20 temperature adjustments**
|
||||
|
||||
You can adjust the temperature reading of the temperature sensor.
|
||||
You can adjust the temperature reading of the temperature sensor. In normal cases this should not be needed since
|
||||
the sensors should be calibrated.
|
||||
|
||||
* **Gyro Movement**
|
||||
|
||||
The software will detect if the gyro is moving and if this is the case it will go back to sleep for 60seconds.
|
||||
This way we should avoid faulty measurements.
|
||||
This way we should avoid faulty measurements and peaks in the graphs.
|
||||
|
||||
* **WIFI connection issues**
|
||||
|
||||
The software will not wait indefiently for a wifi connection. If it takes longer than 20 seconds to connect then
|
||||
the device will go into deep sleep for 60 seoncds and then retry.
|
||||
the device will go into deep sleep for 60 seoncds and then retry later. This to conserve batter as much as possible.
|
||||
|
||||
* **Use gyro temperature sensor**
|
||||
|
||||
@ -93,21 +119,36 @@ The main features
|
||||
:width: 800
|
||||
:alt: Gyro temp vs DS18B20
|
||||
|
||||
Other features
|
||||
--------------
|
||||
* **Celsius or Farenheigt**
|
||||
|
||||
* Support for Celcius and Farenheigt as temperature formats.
|
||||
You can switch between different temperature formats. GravityMon will always use C for it's internal calculations and
|
||||
convert to F when displayed.
|
||||
|
||||
* Support SG (Plato is not yet supported)
|
||||
* **SG or Plato**
|
||||
|
||||
* Gyro data is read 50 times to ensure good accuracy
|
||||
You can switch between different gravity formats. GravityMon will always use SG for it's internal calculations and
|
||||
convert to Plato when displayed.
|
||||
|
||||
Experimental features
|
||||
---------------------
|
||||
* **Stable gyro data**
|
||||
|
||||
The device will read the gyro 50 times to get an accurate reading. If the standad deviation is to high it will not
|
||||
use the data since this is inacurate and the device is probably moving, probably do to active fermentation or movement of
|
||||
fermentation vessel. This sequence takes 900 ms seconds to execute and besides wifi connection this is what consumes the most
|
||||
battery. With more testing this might be changes to either speed up or provide more stable readings.
|
||||
|
||||
* **Performance measurements**
|
||||
|
||||
I've also create a small library to measure execution code in some areas of the code that i know is time consuming. This way I can find a good balance between performace and quality.
|
||||
I've also create a small library to measure execution code in some areas of the code that i know is time consuming. This
|
||||
way I can find a good balance between performace and quality. This is a lot of help trying to figure out where bottlenecks
|
||||
are in the code and where to put optimization efforts. Examples of real measurements:
|
||||
|
||||
* Reading the gyro: 885 ms
|
||||
* Reading DS18B20 temperature sensor: 546 ms
|
||||
* Connect to WIFI: 408 ms
|
||||
* Send data to local influxdb v2: 25 ms
|
||||
* Send data to local mqtt server: 35 ms
|
||||
* Send data to local http server: 40 ms
|
||||
* Send data to http server on internet: 0.2 - 5 seconds
|
||||
|
||||
See the :ref:`compiling-the-software` for more information.
|
||||
|
||||
@ -119,15 +160,12 @@ Experimental features
|
||||
Battery life
|
||||
------------
|
||||
|
||||
I'm currently measuring battery life of v0.5 but previous versions have been able to measure gravity for
|
||||
a 2-3 weeks without issues (Using 900 seconds as interval).
|
||||
The long term battery test has now been completed. Using a 2200 mA battery and sending data every 5 minutes to a local server on my network. The battery lasted 47 days which is excellet battery life.
|
||||
|
||||
I had a device running with an sleep interval of only 30s with ok wifi connection. The device lasted
|
||||
12 days which i think is excellent considering the short sleep interval. From what I have discovered
|
||||
it's the WIFI connection that has the most impact on the battery life. In a perfect scenario it
|
||||
can take around 400ms but can in some cases take up to 8 seconds.
|
||||
In another test I had a device running with an sleep interval of only 30s with ok wifi connection. The device lasted 12 days which i think is excellent considering the short sleep interval.
|
||||
|
||||
From what I have discovered it's the WIFI connection or latency to internet hosted that has the most impact on the battery life. The typical runtime in the tests above was around 2 seconds.
|
||||
|
||||
*More on this topics once my tests are done*
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
BIN
src_docs/source/images/config-popup1.png
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
src_docs/source/images/fermentation.png
Normal file
After Width: | Height: | Size: 40 KiB |
@ -7,46 +7,55 @@ Welcome to GravityMon's documentation!
|
||||
######################################
|
||||
|
||||
.. note::
|
||||
This documentation reflects **v0.7.1**. Last updated 2022-01-30
|
||||
This documentation reflects **v0.8**. Last updated 2022-03-05
|
||||
|
||||
GravityMon is used to measure gravity and temperature during fermentation of beer and report the progress. The graph below is
|
||||
an example on how the fermentation process can be tracked. This is from my last brew that was over on a few days. The graph is rendered using
|
||||
Fermentrack.
|
||||
|
||||
GravityMon is a replacement firmare for the iSpindle firmware, it uses the same hardware configuration so
|
||||
you can easily switch between them.
|
||||
.. image:: images/fermentation.png
|
||||
:width: 500
|
||||
:alt: Example fermentation
|
||||
|
||||
It's used to measure gravity in beer and show the progress of fermentation.
|
||||
|
||||
For more information on this topic and function please visit `iSpindel Homepage <https://www.ispindel.de>`_ .
|
||||
GravityMon is a replacement firmare for the iSpindle and uses the same hardware configuration and is 100% compatible. It
|
||||
implements a lot of the features that has been requested in the orginal iSpindle project but has been rejected for
|
||||
various reasons. Here is a list of :ref:`main_features`.
|
||||
|
||||
I started GravityMon because i like to create software and wanted to do some low level programming. I had done a few
|
||||
projects based on esp8266 and also started to brew beer so this combination was quite natural.
|
||||
|
||||
The hardware design comes from the fantastic iSpindle project so that is not covered in this documentation.
|
||||
The hardware design comes from the fantastic iSpindle project so that is not covered in this documentation. For more
|
||||
information on this topic and function please visit `iSpindel Homepage <https://www.ispindel.de>`_ .
|
||||
|
||||
My approach to this software is a little different from that the original ispindle firmware. The github repository can
|
||||
be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
My approach to this software is a little different from that the original ispindle firmware. The github repository
|
||||
can be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
|
||||
.. note::
|
||||
This software is in the early stages even though its more than one year old so if you find issues, please
|
||||
open a ticket on github.
|
||||
|
||||
I dont take responsibility for any errors that can cause problems with the use. I have tested v0.4 on 5+ brews
|
||||
over the last 6 months without any issues.
|
||||
.. note::
|
||||
I dont take responsibility for any errors or issues caused by the software. The software is provided as-is. I will however
|
||||
try my best to fix issues that might occur.
|
||||
|
||||
The main differences:
|
||||
---------------------
|
||||
I have tested this software over the last year on 20+ brews with good results.
|
||||
|
||||
* Operates in two modes gravity monitoring and configuration mode (simplify calibration)
|
||||
* Modern web based UI for configuration (in config mode)
|
||||
* REST API
|
||||
* Send data to multiple endpoints when pushing data (2xhttp, brewfather, influxdb v2, mqtt supported)
|
||||
.. _main_features:
|
||||
|
||||
Main features:
|
||||
--------------
|
||||
|
||||
* Operates in two modes gravity monitoring and configuration mode (simplify calibration). Gravity mode
|
||||
is comparable to how the iSpindle works.
|
||||
* Modern web based UI when in configuration mode. No need to start the access point changing settings.
|
||||
* REST API to enable scripted configuration
|
||||
* Send data to multiple endpoints and services at once
|
||||
* Setup guides for how to send data to many popular services. Currently 8+ are documented.
|
||||
* Automatic temperature adjustment of gravity reading
|
||||
* OTA support from local webserver
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
|
||||
* Visual graph showing how formula will be interpreted
|
||||
* OTA support from webserver
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and
|
||||
let GravityMon create the formula.
|
||||
* Visual graph showing how formula will be interpreted based on entered values
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* Built in performance measurements (used to optimise code)
|
||||
* SSL support in standard HTTP and MQTT connections.
|
||||
* Option to customize data posted to endpoints using template from the UI.
|
||||
* Built in performance measurements (used to optimise code)
|
||||
|
||||
For a complete breakdown see the :ref:`functionallity`
|
||||
|
||||
@ -121,9 +130,10 @@ the following libraries and without these this would have been much more difficu
|
||||
:caption: Contents:
|
||||
|
||||
license
|
||||
releases
|
||||
functionallity
|
||||
intro
|
||||
installation
|
||||
releases
|
||||
configuration
|
||||
formula
|
||||
services
|
||||
@ -132,6 +142,7 @@ the following libraries and without these this would have been much more difficu
|
||||
data
|
||||
compiling
|
||||
contributing
|
||||
troubleshooting
|
||||
q_and_a
|
||||
|
||||
Indices and tables
|
||||
|
@ -1,3 +1,5 @@
|
||||
.. _installation:
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
@ -57,6 +59,9 @@ Just select a baud rate of 115200, 8N1.
|
||||
:width: 800
|
||||
:alt: Serial output
|
||||
|
||||
|
||||
.. _setup_wifi:
|
||||
|
||||
Configuring WIFI
|
||||
================
|
||||
|
||||
|
73
src_docs/source/intro.rst
Normal file
@ -0,0 +1,73 @@
|
||||
.. _getting_started:
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
First you need a completed iSpindle hardware, there are several resouces around that topic so it
|
||||
will not be covered in this documentation. Please visit `iSpindel Homepage <https://www.ispindel.de>`_ for
|
||||
more information.
|
||||
|
||||
Step 1 - Flash the device
|
||||
-------------------------
|
||||
|
||||
The first step is to flash the firmware, I recommend using Brewflasher as the easy option. Detailed
|
||||
instructions can be found here :ref:`installation`
|
||||
|
||||
Step 2 - Setup WIFI
|
||||
-------------------
|
||||
|
||||
When the device starts up the first time it will first start an WIFI access point so that the WIFI Settings
|
||||
can be configured. The instructions for that can be found here :ref:`setup_wifi`
|
||||
|
||||
Step 3 - Configuration
|
||||
----------------------
|
||||
|
||||
Once the device can connect to WIFI it will go into `configuration mode` and start a web server for
|
||||
doing the initial configuration. In order to access the device you will need to find its name or ip adress.
|
||||
|
||||
It will broadcast a name like gravitymonXXXXXX.local over mDNS. Where the XXXXXX is the unique device id. You can
|
||||
find the name via an mDNS browser, check your router or connect the device to a serial monitor. On windows mDNS
|
||||
might not work so then use the IP address instead. Once connected you will meet a web interface that looks like this.
|
||||
|
||||
.. image:: images/index.png
|
||||
:width: 800
|
||||
:alt: Index page
|
||||
|
||||
The next step is then to configure the device, most settings should work but there are a few that should be changed.
|
||||
|
||||
Configuration - Device Settings - Device Name
|
||||
+++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Give your device a good name.
|
||||
|
||||
Configuration - Device Settings - Gyro Calibration
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
You need to place the device on a flat surface and then press the
|
||||
calibrate button. It will take a few seconds for this to complete and the angle should be close to 90 degress. Without
|
||||
calibration the device will not go into gravity mode.
|
||||
|
||||
Configuration - Push Settings
|
||||
+++++++++++++++++++++++++++++
|
||||
Add the endpoints where you want data to be transmitted. All URLs that contain a valid endpoint will receive the data.
|
||||
|
||||
Calibration
|
||||
+++++++++++
|
||||
|
||||
I recommend to use the calibration feature to create a gravity formula. If you have values from a
|
||||
previous calibration then you can add them here, if not follow the calibration guidelines on the iSpindle site.
|
||||
|
||||
There are several guides for how to calibrate the device (`iSpindle Calibration <https://www.ispindel.de/docs/Calibration_en.html>`_)
|
||||
|
||||
This will get the data points needed to create the formula, and the datapoints will be stored on the device so you can
|
||||
adjust them when needed.
|
||||
|
||||
Step 4 - Completed
|
||||
------------------
|
||||
|
||||
You are now done and can enjoy the GravityMon software. Check out the :ref:`setting-up-device` section for other configuration options.
|
||||
|
||||
If you want to enter the configuration mode place the device flat on a surface and do a reset (or wait until it wakes up).
|
||||
Its recommended to attach the device to power when you have it in `configuration mode` so the battery is not drained.
|
||||
|
||||
**If you have suggestions for more awesome features, head over to the github repository and make a request.**
|
@ -3,6 +3,27 @@
|
||||
Releases
|
||||
########
|
||||
|
||||
v0.8.0
|
||||
------
|
||||
|
||||
* Added option to set http headers (2 per http endpoint), these can be used for
|
||||
other http formats than json (default) and for adding authentication headers.
|
||||
* Added possibility to view last 10 errors on device page.
|
||||
* Added possibility to define token parameter used in iSpindle format.
|
||||
* Added instructions for how to configure integration with Brewspy
|
||||
* Added instructions for how to configure integration with Thingspeak
|
||||
* Added option to do a factory reset via API.
|
||||
* Logging the runtime, how long a measurement take (last 10 are stored). This can be
|
||||
used to check how good the wifi connection is and estimate the lifetime when on battery.
|
||||
Check the device page in the UI for this information.
|
||||
* Refactored code to free up more RAM to make SSL more stable.
|
||||
* Before connecting to an SSL endpoint the device will try to use a new SSL feature
|
||||
called MFLN (Maximum Fragment Length Negotiation) that allow us to reduce the buffers
|
||||
from 16k to 2k. This can make a huge difference on a device with only 40k RAM. Not all
|
||||
servers might support this feature.
|
||||
* Updated documentation pages.
|
||||
* Tested batterylife, 47 days using an update frequency of 5 min
|
||||
|
||||
v0.7.1
|
||||
------
|
||||
|
||||
|
@ -48,6 +48,27 @@ Enter the following as URL:
|
||||
http://industrial.api.ubidots.com/api/v1.6/devices/<devicename>/?token=<api-token>
|
||||
|
||||
|
||||
This is the less secure option.
|
||||
|
||||
**Option 2** - token as the http header
|
||||
|
||||
Enter the following as URL, use either standard or ssl.
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://industrial.api.ubidots.com/api/v1.6/devices/<devicename>
|
||||
https://industrial.api.ubidots.com/api/v1.6/devices/<devicename>
|
||||
|
||||
|
||||
Under `Headers` (button after the http url in the UI) enter the following string:
|
||||
|
||||
.. code-block::
|
||||
|
||||
X-Auth-Token: <api-token>
|
||||
|
||||
|
||||
This is the more secure option.
|
||||
|
||||
Even though ubidots can handle the default ispindle format it probably better to just post the data you want. This is an example of a
|
||||
format template that can be used. For information on customizing the format see :ref:`format-editor`.
|
||||
|
||||
@ -105,7 +126,7 @@ Brewer's friend is an all in one service that allows you to manage you recepies
|
||||
|
||||
.. warning::
|
||||
I dont have an account for brewers friend so I have not been able to verfy this completely. Its based on
|
||||
the available documentation.
|
||||
the available documentation. If this works please let
|
||||
|
||||
You can find you API key when logged in to the service. Follow these `instructions <https://docs.brewersfriend.com/devices/ispindel>`_
|
||||
|
||||
@ -134,3 +155,43 @@ format for the endpoint. Just add you API key after token.
|
||||
"battery": ${battery},
|
||||
"rssi": ${rssi}
|
||||
}
|
||||
|
||||
|
||||
Brewspy
|
||||
+++++++
|
||||
|
||||
BrewSpy is a service that can show the history and manage the brew.
|
||||
|
||||
You need to enter the Token found in brewspy. The field is found under the field for http configuration.
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://brew-spy.com/api/ispindel
|
||||
|
||||
|
||||
Thingspeak
|
||||
++++++++++
|
||||
|
||||
Thingspeak is an IoT platform for receiving data which can be visualized.
|
||||
|
||||
In order to use this platform you need to create a channel (channel = device) and get the APIKEY for
|
||||
writing to the channel. Each channel can handle up to 8 measurements. In the http field enter the following URL.
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://api.thingspeak.com/update.json
|
||||
|
||||
|
||||
You also need to create a custom format for the selected endpoint where the field1-field8 contains the data
|
||||
you want to include. The example below sends 5 different values to the channel identified by the API key.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"api_key": "<your write api key for channel>",
|
||||
"field1": ${gravity},
|
||||
"field2": ${temp},
|
||||
"field3": ${angle},
|
||||
"field4": ${battery},
|
||||
"field5": ${rssi}
|
||||
}
|
||||
|
48
src_docs/source/troubleshooting.rst
Normal file
@ -0,0 +1,48 @@
|
||||
.. _troubleshooting:
|
||||
|
||||
Troubleshooting
|
||||
###############
|
||||
|
||||
Log errors
|
||||
++++++++++
|
||||
|
||||
* Not enough values for deriving formula
|
||||
|
||||
To create a formula its required to have at least 3 measurements.
|
||||
|
||||
* Error validating created formula. Deviation to large, formula rejected
|
||||
|
||||
The device will try to create formulas with different complexities. It will try to
|
||||
validate the formula using the supplied values. If the differnce is more than 1.6 SG on any point
|
||||
the formula will be rejected. Check the entered values if they seams to be resonable.
|
||||
|
||||
* No valid calibration values, please calibrate the device.
|
||||
|
||||
The gyro needs to be calibrated at 90 degress (flat). This is done on the configration page.
|
||||
|
||||
* Low on memory, skipping push
|
||||
|
||||
The arduino libraries sometimes leak memory, this only occurs when running in configuration mode. To avoid
|
||||
crashes the device will skip pushing data if the memory drops to much. Network connections seams to be connected
|
||||
to memory leaks.
|
||||
|
||||
* Unable to set header, invalid value
|
||||
|
||||
Check the format for your custom header. This means it has not a correct format.
|
||||
|
||||
* Influxdb push failed response
|
||||
* Brewfather push failed response
|
||||
* HTTP push failed response
|
||||
|
||||
All these errors are standard http error codes. This are the commone ones;
|
||||
|
||||
* 400 - Bad request. Probably an issue with the post format. Check format in the format editor.
|
||||
* 401 - Unathorized. The service needs an token or other means to authenticate the device.
|
||||
* 403 - Forbidden. Could be an issue with token or URL.
|
||||
* 404 - Not found. Probably a wrong URL.
|
||||
|
||||
* MQTT push on <topic> failed error
|
||||
|
||||
* -3 - Network failed connected
|
||||
* -10 - Connection denied
|
||||
|
BIN
stl/build-1.png
Before Width: | Height: | Size: 112 KiB |
BIN
stl/build-2.png
Before Width: | Height: | Size: 110 KiB |
BIN
stl/sled.stl
BIN
stl/spacer.stl
Before Width: | Height: | Size: 115 KiB |
BIN
stl/spindle.png
Before Width: | Height: | Size: 114 KiB |
@ -5,7 +5,12 @@
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=KfkJU43jUFfj",
|
||||
"http-push": "http://192.168.1.10:9090/api/v1/ZYfjlUNeiuyu9N/telemetry",
|
||||
"http-push-h1": "Auth: Basic T7IF9DD9fF3RDddE=",
|
||||
"http-push-h2": "Auth: Advanced T7IF9DD9fF3RDddE=",
|
||||
"http-push2": "http://192.168.1.10/ispindel",
|
||||
"http-push2-h1": "Second",
|
||||
"http-push2-h2": "First",
|
||||
"token": "mytoken",
|
||||
"influxdb2-push": "http://192.168.1.10:8086",
|
||||
"influxdb2-org": "hello",
|
||||
"influxdb2-bucket": "spann",
|
||||
@ -31,5 +36,6 @@
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04
|
||||
"battery": 0.04,
|
||||
"runtime-average": 2.0
|
||||
}
|
@ -30,8 +30,13 @@ set_config( url, json )
|
||||
#
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"token": "",
|
||||
"http-push": "http://192.168.1.1/ispindel", # HTTP endpoint
|
||||
"http-push2": "", # HTTP endpoint2
|
||||
"http-push-h1": "Content-Type: application/json",
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": "Content-Type: application/json",
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "", # Brewfather URL
|
||||
"influxdb2-push": "", # InfluxDB2 settings
|
||||
"influxdb2-org": "",
|
||||
@ -51,6 +56,7 @@ set_config( url, json )
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "", # If you want to set the gravity formula
|
||||
"gravity-format": "G",
|
||||
"gravity-temp-adjustment": "off" # on or off
|
||||
}
|
||||
set_config( url, json )
|
||||
|
@ -2,5 +2,6 @@
|
||||
"app-name": "GravityMon ",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "7376ef",
|
||||
"mdns": "gravmon"
|
||||
"mdns": "gravmon",
|
||||
"runtime-average": 3.12
|
||||
}
|
@ -2,13 +2,13 @@
|
||||
"gravity-formula": "0.00000166*tilt^3+-0.00024799*tilt^2+0.01344400*tilt+0.79358248",
|
||||
"angle": 45,
|
||||
"a1": 25,
|
||||
"a2": 35,
|
||||
"a3": 45,
|
||||
"a3": 35,
|
||||
"a2": 45,
|
||||
"a4": 55,
|
||||
"a5": 65,
|
||||
"a5": 30,
|
||||
"g1": 1.000,
|
||||
"g2": 1.010,
|
||||
"g3": 1.025,
|
||||
"g3": 1.010,
|
||||
"g2": 1.025,
|
||||
"g4": 1.040,
|
||||
"g5": 1.060
|
||||
"g5": 1.005
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
{
|
||||
"id": "7376ef",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"gravity-tempcorr": 1.1031,
|
||||
"temp-c": 0,
|
||||
"angle": 22.4,
|
||||
"gravity": 1.044,
|
||||
"gravity-tempcorr": 1.031,
|
||||
"gravity-format": "G",
|
||||
"temp-c": 12,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"battery": 3.81,
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"rssi": -56
|
||||
|