Compare commits
73 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 | |||
044bfcddad | |||
1fd3b1911d | |||
9727e87e33 | |||
3390ebc5ab | |||
e076de022c | |||
4e0980e814 | |||
b8959ae165 | |||
4d6b4b7fd6 | |||
64e582d0e5 | |||
5d0f02eb18 | |||
fbc1eb4e31 | |||
700f00f48d | |||
9a2f86fed7 | |||
63fd80e750 | |||
b106ebfa20 | |||
dc5979dd28 | |||
22d5e4fad7 |
53
.github/workflows/pio-build-patch.yaml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: PlatformIO CI Patch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- patch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
git config --global advice.detachedHead false
|
||||
|
||||
- name: Run PlatformIO
|
||||
#run: pio run -e gravity-release -e gravity-perf -e gravity-debug
|
||||
run: pio run -e gravity-release -e gravity-perf
|
||||
#run: pio run -e gravity-release
|
||||
|
||||
- uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: 'bin'
|
||||
author_name: GitHub Action
|
||||
author_email: mp-se@noreply.github.com
|
||||
|
||||
branch: patch
|
||||
|
||||
default_author: github_actor
|
||||
message: 'GitHub Action Build'
|
||||
pathspec_error_handling: ignore
|
@ -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.0)"),$("#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.0", "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) {
|
||||
|
126
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,16 +171,36 @@
|
||||
<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">
|
||||
<input type="url" maxlength="100" class="form-control" name="http-push" id="http-push">
|
||||
<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">
|
||||
<input type="url" maxlength="100" class="form-control" name="http-push2" id="http-push2">
|
||||
<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>
|
||||
|
||||
@ -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.0)");
|
||||
$("#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.0)"),$("#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.0\""
|
||||
-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,84 +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));
|
||||
url.replace(":8883", "");
|
||||
mqtt.begin(url.c_str(), 8883, 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(), myWifi.getWifiClient());
|
||||
mqtt.begin(host.c_str(), port, wifi);
|
||||
}
|
||||
|
||||
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
|
||||
@ -256,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
|
||||
@ -280,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;
|
||||
@ -289,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"
|
||||
|
@ -33,9 +33,9 @@ SOFTWARE.
|
||||
// Use iSpindle format for compatibility
|
||||
const char iSpindleFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\" : \"gravmon\", "
|
||||
"\"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();
|
||||
|
92
src_docs/source/advanced.rst
Normal file
@ -0,0 +1,92 @@
|
||||
Advanced Configuration
|
||||
######################
|
||||
|
||||
.. _format-editor:
|
||||
|
||||
Format editor
|
||||
+++++++++++++
|
||||
|
||||
To reduce the need for adding custom endpoints for various services there is an built in format editor that allows the user to customize the format being sent to the push target.
|
||||
|
||||
.. warning::
|
||||
|
||||
Since the format templates can be big this function can be quite slow on a small device such as the esp8266.
|
||||
|
||||
.. image:: images/format.png
|
||||
:width: 800
|
||||
:alt: Format editor
|
||||
|
||||
You enter the format data in the text field and the test button will show an example on what the output would look like. If the data cannot be formatted in json it will just be displayed as a long string.
|
||||
The save button will save the current formla and reload the data from the device.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you save a blank string the default template will be loaded.
|
||||
|
||||
These are the format keys available for use in the format.
|
||||
|
||||
.. list-table:: Directory structure
|
||||
:widths: 30 50 20
|
||||
:header-rows: 1
|
||||
|
||||
* - key
|
||||
- description
|
||||
- example
|
||||
* - ${mdns}
|
||||
- Name of the device
|
||||
- gravmon2
|
||||
* - ${id}
|
||||
- Unique id of the device
|
||||
- e422a3
|
||||
* - ${sleep-interval}
|
||||
- Seconds between data is pushed
|
||||
- 900
|
||||
* - ${temp}
|
||||
- Temperature in format configured on device, one decimal
|
||||
- 21.2
|
||||
* - ${temp-c}
|
||||
- Temperature in C, one decimal
|
||||
- 21.2
|
||||
* - ${temp-f}
|
||||
- Temperature in F, one decimal
|
||||
- 58.0
|
||||
* - ${temp-unit}
|
||||
- Temperature format `C` or `F`
|
||||
- C
|
||||
* - ${battery}
|
||||
- Battery voltage, two decimals
|
||||
- 3.89
|
||||
* - ${rssi}
|
||||
- Wifi signal strength
|
||||
- -75
|
||||
* - ${run-time}
|
||||
- How long the last measurement took, two decimals
|
||||
- 3.87
|
||||
* - ${angle}
|
||||
- Angle of the gyro, two decimals
|
||||
- 28.67
|
||||
* - ${tilt}
|
||||
- Same as angle.
|
||||
- 28.67
|
||||
* - ${gravity}
|
||||
- Calculated gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${gravity-sg}
|
||||
- Calculated gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${gravity-plato}
|
||||
- Calculated gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${corr-gravity}
|
||||
- Temperature corrected gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${corr-gravity-sg}
|
||||
- Temperature corrected gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${corr-gravity-plato}
|
||||
- Temperature corrected gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${gravity-unit}
|
||||
- Gravity format, `G` or `P`
|
||||
- G
|
||||
|
338
src_docs/source/api.rst
Normal file
@ -0,0 +1,338 @@
|
||||
.. _rest-api:
|
||||
|
||||
REST API
|
||||
########
|
||||
|
||||
All the API's use a key called ``ID`` which is the unique device id (chip id). This is used as an API key when sending requests to the device.
|
||||
|
||||
GET: /api/config
|
||||
================
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``gravity-format`` is always ``G`` (plato is not yet supported)
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"mdns": "gravmon",
|
||||
"id": "ee1bfc",
|
||||
"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",
|
||||
"http-push2": "http://192.168.1.50/ispindel",
|
||||
"http-push2-h1": "header: value",
|
||||
"http-push2-h2": "header: value",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "org",
|
||||
"influxdb2-bucket": "bucket_id",
|
||||
"influxdb2-auth": "token",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "user",
|
||||
"mqtt-pass": "pass",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "G",
|
||||
"temp-adjustment-value": 0,
|
||||
"gravity-temp-adjustment": false,
|
||||
"gyro-temp": true,
|
||||
"gyro-calibration-data": {
|
||||
"ax": -330,
|
||||
"ay": -2249,
|
||||
"az": 1170,
|
||||
"gx": 99,
|
||||
"gy": -6,
|
||||
"gz": 4
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04,
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
GET: /api/device
|
||||
================
|
||||
|
||||
Retrive the current device settings via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"app-name": "GravityMon",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon",
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"gravity-tempcorr": 1.1031,
|
||||
"temp-c": 0,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"rssi": -56
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1,
|
||||
"gravity-format": "G",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436"
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
========================
|
||||
|
||||
Used to update device settings via an HTTP POST command.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
* ``temp-format`` can be either ``C`` (Celcius) or ``F`` (Farenheight)
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
mdns=gravmon
|
||||
temp-format=C
|
||||
sleep-interval=30
|
||||
|
||||
|
||||
POST: /api/config/push
|
||||
======================
|
||||
|
||||
Used to update push settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
http-push=http://192.168.1.50/ispindel
|
||||
http-push2=
|
||||
http-push-h1=
|
||||
http-push-h2=
|
||||
http-push2-h1=
|
||||
http-push2-h2=
|
||||
brewfather-push=
|
||||
influxdb2-push=http://192.168.1.50:8086
|
||||
influxdb2-org=
|
||||
influxdb2-bucket=
|
||||
influxdb2-auth=
|
||||
mqtt-push=192.168.1.50
|
||||
mqtt-port=1883
|
||||
mqtt-user=
|
||||
mqtt-pass=
|
||||
|
||||
|
||||
POST: /api/config/gravity
|
||||
=========================
|
||||
|
||||
Used to update gravity settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``gravity-formula`` keywords ``temp`` and ``tilt`` are supported.
|
||||
* ``gravity-format`` can be either ``G`` (SG) or ``P`` (PLATO)
|
||||
|
||||
.. note::
|
||||
``gravity-temp-adjustment`` is defined as "on" or "off" when posting since this is the output values
|
||||
from a checkbox, when reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
gravity-formula=0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436,
|
||||
gravity-format=P
|
||||
gravity-temp-adjustment=off
|
||||
|
||||
|
||||
POST: /api/config/hardware
|
||||
==========================
|
||||
|
||||
Used to update hardware settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. note::
|
||||
``gyro-temp`` is defined as "on" or "off" when posting since this is the output values from a checkbox, when
|
||||
reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
voltage-factor=1.59
|
||||
temp-adjustment=0
|
||||
gyro-temp=off
|
||||
ota-url=http://192.168.1.50/firmware/gravmon/
|
||||
|
||||
|
||||
POST: /api/config/formula
|
||||
=========================
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
a1=22.4
|
||||
a2=54.4
|
||||
a3=58
|
||||
a4=0
|
||||
a5=0
|
||||
g1=1.000
|
||||
g2=1.053
|
||||
g3=1.062
|
||||
g4=1
|
||||
g5=1
|
||||
|
||||
|
||||
Calling the API's from Python
|
||||
=============================
|
||||
|
||||
Here is some example code for how to access the API's from a python script. Keys should always be
|
||||
present or the API call will fail.
|
||||
|
||||
The requests package converts the json to standard form post format.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
id = "ee1bfc" # Device ID (shown in serial console during startup or in UI)
|
||||
|
||||
def set_config( url, json ):
|
||||
headers = { "ContentType": "application/json" }
|
||||
print( url )
|
||||
resp = requests.post( url, headers=headers, data=json )
|
||||
if resp.status_code != 200 :
|
||||
print ( "Failed " )
|
||||
else :
|
||||
print ( "Success " )
|
||||
|
||||
url = "http://" + host + "/api/config/device"
|
||||
json = { "id": id,
|
||||
"mdns": "gravmon", # Name of the device
|
||||
"temp-format": "C", # Temperature format C or F
|
||||
"sleep-interval": 30 # Sleep interval in seconds
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"token": "",
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"http-push-h1": "",
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": "",
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "",
|
||||
"gravity-format": "P",
|
||||
"gravity-temp-adjustment": "off" # Adjust gravity (on/off)
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/hardware"
|
||||
json = { "id": id,
|
||||
"voltage-factor": 1.59, # Default value for voltage calculation
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/formula"
|
||||
json = { "id": id,
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
set_config( url, json )
|
@ -1,20 +0,0 @@
|
||||
Backlog of changes
|
||||
##################
|
||||
|
||||
This is a list of potential ideas to implemnt in the software.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Write contribution instructions
|
||||
- Example project for creating integrations and instructions
|
||||
- Add instructions for other services
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
- Support for plato
|
||||
- Use pre-commit for validating check-in
|
||||
- Show indicated battery life based on interval (check if its feasable)
|
||||
- Add possibility to add one certificate for proper SSL authentication.
|
||||
- Add configurable Authentication/Token header to http/https requests
|
@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.7.0'
|
||||
release = '0.8.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -1,7 +1,7 @@
|
||||
.. _setting-up-device:
|
||||
|
||||
Setting up device
|
||||
#################
|
||||
Configuration
|
||||
#############
|
||||
|
||||
The device can operate in two modes and must be in ``configuration mode`` in order for the web server to be active.
|
||||
|
||||
@ -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:**
|
||||
|
||||
@ -48,11 +52,18 @@ URL: (http://gravmon.local/device)
|
||||
|
||||
* **Device name:**
|
||||
|
||||
This is unique name of the device.
|
||||
This is unique name of the device which is set in the configuration, also known as MDNS name.
|
||||
|
||||
* **Device ID:**
|
||||
|
||||
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 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
|
||||
@ -73,15 +84,19 @@ Device Setting
|
||||
|
||||
* **Temperature format:**
|
||||
|
||||
Choose between Celsius and Farenheight
|
||||
Choose between Celsius and Farenheight when displaying temperature.
|
||||
|
||||
* **Interval:**
|
||||
|
||||
This defines how long the device should be sleeping between the readings when in `gravity monitoring` mode. You will also see the values in minutes/seconds to easier set the interval. 900s is a recommended interval.
|
||||
This defines how long the device should be sleeping between the readings when in `gravity monitoring` mode. You will also see
|
||||
the values in minutes/seconds to easier set the interval. 900s is a recommended interval. The sleep interval can
|
||||
be set between 10 - 3600 seconds (60 minutes).
|
||||
|
||||
.. note::
|
||||
|
||||
The sleep interval can be set between 10 - 3600 seconds (60 minutes).
|
||||
A low value such as 30s will give a lifespan of 1-2 weeks and 300s (5 min) would last for 3+ weeks. This assumes that
|
||||
there is good wifi connection that takes less than 1s to reconnect. Poor wifi connection is the main reason for battery drain.
|
||||
|
||||
|
||||
* **Calibration values:**
|
||||
|
||||
@ -116,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`
|
||||
@ -156,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
|
||||
++++++++++++++++
|
||||
@ -166,16 +208,20 @@ Gravity Settings
|
||||
|
||||
* **Gravity format:**
|
||||
|
||||
Gravity format can be eihter `SG` or `Plato`. The device will use SG Internally and convert to Plato when displaying data.
|
||||
Gravity format can be eihter `SG` or `Plato`. The device will use SG Internally and convert to Plato when displaying or sending data.
|
||||
|
||||
* **Gravity formula:**
|
||||
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. You can also use
|
||||
the feature to create the formula by supplying the raw data. See :ref:`create-formula`
|
||||
|
||||
The gravity formula accepts to paramaters, **tilt** for the angle or **temp** for temperature (temperature inserted into the formula
|
||||
will be in celsius). I would recommend to use the formula calculation feature instead since this is much easier.
|
||||
|
||||
* **Temperature correct gravity:**
|
||||
|
||||
Will apply a temperature calibration formula to the gravity as a second step.
|
||||
Will apply a temperature calibration formula to the gravity as a second step after gravity has been calculated. It's also possible to
|
||||
build this into the gravity formula.
|
||||
|
||||
.. warning::
|
||||
This formula assumes that the calibration has been done at 20°C / 68°F.
|
||||
@ -213,7 +259,7 @@ Hardware Settings
|
||||
|
||||
* **OTA URL:**
|
||||
|
||||
Should point to a URL where the .bin file + version.json file is located.
|
||||
Should point to a URL where the firmware.bin file + version.json file are located.
|
||||
|
||||
For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the
|
||||
code the update will be done during startup.
|
||||
@ -225,578 +271,6 @@ Hardware Settings
|
||||
.. code-block::
|
||||
|
||||
http://192.168.1.1/firmware/gravmon/
|
||||
|
||||
https://192.168.1.1/firmware/gravmon/
|
||||
|
||||
.. _format-editor:
|
||||
|
||||
Format editor
|
||||
#############
|
||||
|
||||
To reduce the need for adding custom endpoints for various services there is an built in format editor that allows the user to customize the format being sent to the push target.
|
||||
|
||||
.. warning::
|
||||
|
||||
Since the format templates can be big this function can be quite slow on a small device such as the esp8266.
|
||||
|
||||
.. image:: images/format.png
|
||||
:width: 800
|
||||
:alt: Format editor
|
||||
|
||||
You enter the format data in the text field and the test button will show an example on what the output would look like. If the data cannot be formatted in json it will just be displayed as a long string.
|
||||
The save button will save the current formla and reload the data from the device.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you save a blank string the default template will be loaded.
|
||||
|
||||
These are the format keys available for use in the format.
|
||||
|
||||
.. list-table:: Directory structure
|
||||
:widths: 30 50 20
|
||||
:header-rows: 1
|
||||
|
||||
* - key
|
||||
- description
|
||||
- example
|
||||
* - ${mdns}
|
||||
- Name of the device
|
||||
- gravmon2
|
||||
* - ${id}
|
||||
- Unique id of the device
|
||||
- e422a3
|
||||
* - ${sleep-interval}
|
||||
- Seconds between data is pushed
|
||||
- 900
|
||||
* - ${temp}
|
||||
- Temperature in format configured on device, one decimal
|
||||
- 21.2
|
||||
* - ${temp-c}
|
||||
- Temperature in C, one decimal
|
||||
- 21.2
|
||||
* - ${temp-f}
|
||||
- Temperature in F, one decimal
|
||||
- 58.0
|
||||
* - ${temp-unit}
|
||||
- Temperature format `C` or `F`
|
||||
- C
|
||||
* - ${battery}
|
||||
- Battery voltage, two decimals
|
||||
- 3.89
|
||||
* - ${rssi}
|
||||
- Wifi signal strength
|
||||
- -75
|
||||
* - ${run-time}
|
||||
- How long the last measurement took, two decimals
|
||||
- 3.87
|
||||
* - ${angle}
|
||||
- Angle of the gyro, two decimals
|
||||
- 28.67
|
||||
* - ${tilt}
|
||||
- Same as angle.
|
||||
- 28.67
|
||||
* - ${gravity}
|
||||
- Calculated gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${gravity-sg}
|
||||
- Calculated gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${gravity-plato}
|
||||
- Calculated gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${corr-gravity}
|
||||
- Temperature corrected gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${corr-gravity-sg}
|
||||
- Temperature corrected gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${corr-gravity-plato}
|
||||
- Temperature corrected gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${gravity-unit}
|
||||
- Gravity format, `G` or `P`
|
||||
- G
|
||||
|
||||
|
||||
.. _create-formula:
|
||||
|
||||
Create formula
|
||||
##############
|
||||
|
||||
.. image:: images/formula1.png
|
||||
:width: 800
|
||||
:alt: Formula data
|
||||
|
||||
Here you can enter up to 5 values (angles + gravity) that is then used to create the formula. Angles equal to zero will be regarded as empty even if there is a gravity reading.
|
||||
|
||||
.. image:: images/formula2.png
|
||||
:width: 800
|
||||
:alt: Formula graph
|
||||
|
||||
Once the formula is created a graph over the entered values and a simulation of the formula will give you a nice overview on how the formula will work.
|
||||
|
||||
.. _rest-api:
|
||||
|
||||
REST API
|
||||
########
|
||||
|
||||
All the API's use a key called ``ID`` which is the unique device id (chip id). This is used as an API key when sending requests to the device.
|
||||
|
||||
GET: /api/config
|
||||
================
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``gravity-format`` is always ``G`` (plato is not yet supported)
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"mdns": "gravmon",
|
||||
"id": "ee1bfc",
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
"http-push2": "http://192.168.1.50/ispindel",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "G",
|
||||
"temp-adjustment-value": 0,
|
||||
"gravity-temp-adjustment": false,
|
||||
"gyro-temp": true,
|
||||
"gyro-calibration-data": {
|
||||
"ax": -330,
|
||||
"ay": -2249,
|
||||
"az": 1170,
|
||||
"gx": 99,
|
||||
"gy": -6,
|
||||
"gz": 4
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04
|
||||
}
|
||||
|
||||
|
||||
GET: /api/device
|
||||
================
|
||||
|
||||
Retrive the current device settings via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"app-name": "GravityMon ",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon"
|
||||
}
|
||||
|
||||
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"gravity-tempcorr": 1.1031,
|
||||
"temp-c": 0,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"rssi": -56
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1,
|
||||
"gravity-format": "G",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436"
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/device
|
||||
========================
|
||||
|
||||
Used to update device settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` (Celcius) or ``F`` (Farenheight)
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon",
|
||||
"temp-format": "C",
|
||||
"sleep-interval": 30
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/push
|
||||
======================
|
||||
|
||||
Used to update push settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"http-push": "http://192.168.1.50/ispindel",
|
||||
"http-push2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty"
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/gravity
|
||||
=========================
|
||||
|
||||
Used to update gravity settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``gravity-formula`` keywords ``temp`` and ``tilt`` are supported.
|
||||
* ``gravity-format`` can be either ``G`` (SG) or ``P`` (PLATO)
|
||||
|
||||
.. note::
|
||||
``gravity-temp-adjustment`` is defined as "on" or "off" when posting since this is the output values
|
||||
from a checkbox, when reading data it's sent as boolean (true,false).
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "P",
|
||||
"gravity-temp-adjustment": "off"
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/hardware
|
||||
==========================
|
||||
|
||||
Used to update hardware settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. note::
|
||||
``gyro-temp`` is defined as "on" or "off" when posting since this is the output values from a checkbox, when
|
||||
reading data it's sent as boolean (true,false).
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"voltage-factor": 1.59,
|
||||
"temp-adjustment": 0,
|
||||
"gyro-temp": "off",
|
||||
"ota-url": "http://192.168.1.50/firmware/gravmon/"
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/formula
|
||||
=========================
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
|
||||
|
||||
Calling the API's from Python
|
||||
=============================
|
||||
|
||||
Here is some example code for how to access the API's from a python script. Keys should always be
|
||||
present or the API call will fail.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
id = "ee1bfc" # Device ID (shown in serial console during startup or in UI)
|
||||
|
||||
def set_config( url, json ):
|
||||
headers = { "ContentType": "application/json" }
|
||||
print( url )
|
||||
resp = requests.post( url, headers=headers, data=json )
|
||||
if resp.status_code != 200 :
|
||||
print ( "Failed " )
|
||||
else :
|
||||
print ( "Success " )
|
||||
|
||||
url = "http://" + host + "/api/config/device"
|
||||
json = { "id": id,
|
||||
"mdns": "gravmon", # Name of the device
|
||||
"temp-format": "C", # Temperature format C or F
|
||||
"sleep-interval": 30 # Sleep interval in seconds
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "",
|
||||
"gravity-format": "P",
|
||||
"gravity-temp-adjustment": "off" # Adjust gravity (on/off)
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/hardware"
|
||||
json = { "id": id,
|
||||
"voltage-factor": 1.59, # Default value for voltage calculation
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/formula"
|
||||
json = { "id": id,
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
|
||||
.. _data-formats:
|
||||
|
||||
Data Formats
|
||||
############
|
||||
|
||||
.. _data-formats-ispindle:
|
||||
|
||||
iSpindle format
|
||||
===============
|
||||
|
||||
This is the format used for standard http posts.
|
||||
|
||||
* ``corr-gravity`` is an extended parameter containing a temperature corrected gravity reading.
|
||||
* ``gravity-format`` is an extended parameter containing the gravity format (G or P).
|
||||
* ``run-time`` is an extended parameter containing the number of seconds the execution took.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"ID": "2E6753",
|
||||
"token" : "gravmon",
|
||||
"interval": 900,
|
||||
"temperature": 20.5,
|
||||
"temp-units": "C",
|
||||
"gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
|
||||
"corr-gravity": 1.0050,
|
||||
"gravity-unit": "G",
|
||||
"run-time": 6
|
||||
}
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"name" : "gravmon", "
|
||||
"ID": "${id}", "
|
||||
"token" : "gravmon", "
|
||||
"interval": ${sleep-interval}, "
|
||||
"temperature": ${temp}, "
|
||||
"temp-units": "${temp-unit}", "
|
||||
"gravity": ${gravity}, "
|
||||
"angle": ${angle}, "
|
||||
"battery": ${battery}, "
|
||||
"rssi": ${rssi}, "
|
||||
"corr-gravity": ${corr-gravity}, "
|
||||
"gravity-unit": "${gravity-unit}", "
|
||||
"run-time": ${run-time} "
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
This is the format for Brewfather. See: `Brewfather API docs <https://docs.brewfather.app/integrations/custom-stream>`_
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"temp": 20.5,
|
||||
"temp_unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G",
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
Influx DB v2
|
||||
============
|
||||
|
||||
This is the format for InfluxDB v2
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-format=${gravity-unit} gravity=${gravity},corr-gravity=${corr-gravity},angle=${angle},temp=${temp},battery=${battery},rssi=${rssi}
|
||||
|
||||
|
||||
.. _data-formats-mqtt:
|
||||
|
||||
MQTT
|
||||
====
|
||||
|
||||
This is the format used to send data to MQTT. Each of the lines are specific topics
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/device_name/tilt 89.96796
|
||||
ispindel/device_name/temperature 21.375
|
||||
ispindel/device_name/temp_units C
|
||||
ispindel/device_name/battery 0.04171
|
||||
ispindel/device_name/gravity 33.54894
|
||||
ispindel/device_name/interval 1
|
||||
ispindel/device_name/RSSI -58
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. tip::
|
||||
|
||||
Each line in the format is treated as one topic. The `|` is used as separator between lines and the first `:` is used as separator between topic and value. Each line is formatted as `<topic>:<value>`
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/${mdns}/tilt:${angle}|
|
||||
ispindel/${mdns}/temperature:${temp}|
|
||||
ispindel/${mdns}/temp_units:${temp-unit}|
|
||||
ispindel/${mdns}/battery:${battery}|
|
||||
ispindel/${mdns}/gravity:${gravity}|
|
||||
ispindel/${mdns}/interval:${sleep-interval}|
|
||||
ispindel/${mdns}/RSSI:${rssi}|
|
||||
|
||||
This is a format template that is compatible with v0.6. Just replace the `topic` with the topic you want to post data to.
|
||||
|
||||
.. code-block::
|
||||
|
||||
topic:{"name":"gravmon","ID":"${id}","token":"gravmon","interval": ${sleep-interval},"temperature": ${temp},"temp-units": "${temp-unit}","gravity":${gravity},"angle": ${angle},"battery":${battery},"rssi": ${rssi},"corr-gravity":${corr-gravity},"gravity-unit": "${gravity-unit}","run-time": ${run-time}}|
|
||||
|
||||
|
||||
version.json
|
||||
============
|
||||
|
||||
Contents version.json. The version is used by the device to check if the this version is newer. The html files will also be downloaded if the are present on the server. This way it's easy to
|
||||
upgrade to a version that serve the html files from the file system. If they dont exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
they can be uploaded manually afterwards.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"project":"gravmon",
|
||||
"version":"0.7.0",
|
||||
"html": [
|
||||
"index.min.htm",
|
||||
"device.min.htm",
|
||||
"config.min.htm",
|
||||
"format.min.htm",
|
||||
"calibration.min.htm",
|
||||
"about.min.htm"
|
||||
]
|
||||
}
|
||||
|
156
src_docs/source/data.rst
Normal file
@ -0,0 +1,156 @@
|
||||
.. _data-formats:
|
||||
|
||||
Data Formats
|
||||
############
|
||||
|
||||
.. _data-formats-ispindle:
|
||||
|
||||
iSpindle format
|
||||
===============
|
||||
|
||||
This is the format used for standard http posts.
|
||||
|
||||
* ``corr-gravity`` is an extended parameter containing a temperature corrected gravity reading.
|
||||
* ``gravity-format`` is an extended parameter containing the gravity format (G or P).
|
||||
* ``run-time`` is an extended parameter containing the number of seconds the execution took.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"ID": "2E6753",
|
||||
"token" : "gravmon",
|
||||
"interval": 900,
|
||||
"temperature": 20.5,
|
||||
"temp-units": "C",
|
||||
"gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
|
||||
"corr-gravity": 1.0050,
|
||||
"gravity-unit": "G",
|
||||
"run-time": 6
|
||||
}
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"name" : "${mdns}",
|
||||
"ID": "${id}",
|
||||
"token" : "gravmon",
|
||||
"interval": ${sleep-interval},
|
||||
"temperature": ${temp},
|
||||
"temp-units": "${temp-unit}",
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"battery": ${battery},
|
||||
"rssi": ${rssi},
|
||||
"corr-gravity": ${corr-gravity},
|
||||
"gravity-unit": "${gravity-unit}",
|
||||
"run-time": ${run-time}
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
This is the format for Brewfather. See: `Brewfather API docs <https://docs.brewfather.app/integrations/custom-stream>`_
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"temp": 20.5,
|
||||
"temp_unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G",
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
Influx DB v2
|
||||
============
|
||||
|
||||
This is the format for InfluxDB v2
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-format=${gravity-unit} gravity=${gravity},corr-gravity=${corr-gravity},angle=${angle},temp=${temp},battery=${battery},rssi=${rssi}
|
||||
|
||||
|
||||
.. _data-formats-mqtt:
|
||||
|
||||
MQTT
|
||||
====
|
||||
|
||||
This is the format used to send data to MQTT. Each of the lines are specific topics
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/device_name/tilt 89.96796
|
||||
ispindel/device_name/temperature 21.375
|
||||
ispindel/device_name/temp_units C
|
||||
ispindel/device_name/battery 0.04171
|
||||
ispindel/device_name/gravity 33.54894
|
||||
ispindel/device_name/interval 1
|
||||
ispindel/device_name/RSSI -58
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. tip::
|
||||
|
||||
Each line in the format is treated as one topic. The `|` is used as separator between lines and the first `:` is used as separator between topic and value. Each line is formatted as `<topic>:<value>`
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/${mdns}/tilt:${angle}|
|
||||
ispindel/${mdns}/temperature:${temp}|
|
||||
ispindel/${mdns}/temp_units:${temp-unit}|
|
||||
ispindel/${mdns}/battery:${battery}|
|
||||
ispindel/${mdns}/gravity:${gravity}|
|
||||
ispindel/${mdns}/interval:${sleep-interval}|
|
||||
ispindel/${mdns}/RSSI:${rssi}|
|
||||
|
||||
This is a format template that is compatible with v0.6. Just replace the `topic` with the topic you want to post data to.
|
||||
|
||||
.. code-block::
|
||||
|
||||
topic:{"name":"gravmon","ID":"${id}","token":"gravmon","interval": ${sleep-interval},"temperature": ${temp},"temp-units": "${temp-unit}","gravity":${gravity},"angle": ${angle},"battery":${battery},"rssi": ${rssi},"corr-gravity":${corr-gravity},"gravity-unit": "${gravity-unit}","run-time": ${run-time}}|
|
||||
|
||||
|
||||
version.json
|
||||
============
|
||||
|
||||
Contents version.json. The version is used by the device to check if the this version is newer. The html files will also be downloaded if the are present on the server. This way it's easy to
|
||||
upgrade to a version that serve the html files from the file system. If they dont exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
they can be uploaded manually afterwards.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"project":"gravmon",
|
||||
"version":"0.7.0",
|
||||
"html": [
|
||||
"index.min.htm",
|
||||
"device.min.htm",
|
||||
"config.min.htm",
|
||||
"format.min.htm",
|
||||
"calibration.min.htm",
|
||||
"about.min.htm"
|
||||
]
|
||||
}
|
16
src_docs/source/formula.rst
Normal file
@ -0,0 +1,16 @@
|
||||
.. _create-formula:
|
||||
|
||||
Create formula
|
||||
##############
|
||||
|
||||
.. image:: images/formula1.png
|
||||
:width: 800
|
||||
:alt: Formula data
|
||||
|
||||
Here you can enter up to 5 values (angles + gravity) that is then used to create the formula. Angles equal to zero will be regarded as empty even if there is a gravity reading.
|
||||
|
||||
.. image:: images/formula2.png
|
||||
:width: 800
|
||||
:alt: Formula graph
|
||||
|
||||
Once the formula is created a graph over the entered values and a simulation of the formula will give you a nice overview on how the formula will work.
|
@ -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**. Last updated 2022-01-23
|
||||
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,14 +130,20 @@ the following libraries and without these this would have been much more difficu
|
||||
:caption: Contents:
|
||||
|
||||
license
|
||||
releases
|
||||
functionallity
|
||||
intro
|
||||
installation
|
||||
releases
|
||||
configuration
|
||||
formula
|
||||
services
|
||||
advanced
|
||||
api
|
||||
data
|
||||
compiling
|
||||
contributing
|
||||
troubleshooting
|
||||
q_and_a
|
||||
backlog
|
||||
|
||||
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,10 +3,42 @@
|
||||
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
|
||||
------
|
||||
|
||||
* Added instructions for how to configure integration with Fermentrack
|
||||
* Added instructions for how to configure integration with Ubidots
|
||||
* Added instructions for how to configure integration with HomeAssistant
|
||||
* Added instructions for how to configure integration with Brewers Friend (not verified)
|
||||
* BUG: Defined mqtt port was ignored, used default values.
|
||||
* BUG: Extended length of HTTP url fields from 100 to 120 chars.
|
||||
* BUG: Fixed issue with default template so it now includes the device name correctly.
|
||||
|
||||
v0.7.0
|
||||
------
|
||||
|
||||
Latest stable version. `Release v0.6 on Github <https://github.com/mp-se/gravitymon/releases/tag/v0.6.0>`_
|
||||
Latest stable version. `Release v0.7 on Github <https://github.com/mp-se/gravitymon/releases/tag/v0.7.0>`_
|
||||
|
||||
* SSL support for HTTP targets
|
||||
* SSL support for MQTT targets
|
||||
|
197
src_docs/source/services.rst
Normal file
@ -0,0 +1,197 @@
|
||||
.. _services:
|
||||
|
||||
Service Integration
|
||||
###################
|
||||
|
||||
This chapter contains a list of targets and what configuration is needed to interact with them.
|
||||
|
||||
Brewfather
|
||||
++++++++++
|
||||
|
||||
Brewfather is an all in one service that allows you to manage you recepies and brews.
|
||||
|
||||
Just enter the http adress found on brewfather, not other settings are needed. The endpoint has the following format:
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://log.brewfather.net/http://log.brewfather.net/stream?id=<yourid>
|
||||
|
||||
|
||||
The URL is found under settings.
|
||||
|
||||
Fermentrack
|
||||
+++++++++++
|
||||
|
||||
`Fermentrack <https://www.fermentrack.com>`_ is a open source brewing software to monitor and control fermentation.
|
||||
|
||||
GravityMon can be installed and used as an iSpindle. Just register the device as an iSpindle and use the defined endpoint which normally is:
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://myservername/ispindel
|
||||
|
||||
|
||||
UBIdots
|
||||
+++++++
|
||||
|
||||
`UBIdots <https://www.ubidots.com>`_ is a IoT service that display data collected various sources.
|
||||
|
||||
For this service there are two options to configure the integration. First you will need your default token which is found under `API Credentials` (<api-tokem> in the example below).
|
||||
Swap the text <devicename> with the name you want to show in ubidots.
|
||||
|
||||
**Option 1** - token as an URL parameter
|
||||
|
||||
Enter the following as URL:
|
||||
|
||||
.. code-block::
|
||||
|
||||
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`.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"temperature": ${temp},
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"battery": ${battery},
|
||||
"rssi": ${rssi}
|
||||
}
|
||||
|
||||
|
||||
Home Assistant
|
||||
+++++++++++++++
|
||||
|
||||
`HomeAssistant <https://www.homeassistant.com>`_ is a platform for home automation and can collect sensor data
|
||||
from multiple devices.
|
||||
|
||||
This setup uses the MQTT integration with home assistant to collect values from the device.
|
||||
|
||||
This part of the configuration goes into the home assistant configuration.yaml file. The example assumes that the
|
||||
device is named `gravmon2`
|
||||
|
||||
::
|
||||
|
||||
sensor:
|
||||
- platform: mqtt
|
||||
name: "gravmon2_gravity"
|
||||
state_topic: "gravmon/gravmon2/gravity"
|
||||
- platform: mqtt
|
||||
name: "gravmon2_battery"
|
||||
state_topic: "gravmon/gravmon2/battery"
|
||||
- platform: mqtt
|
||||
name: "gravmon2_rssi"
|
||||
state_topic: "gravmon/gravmon2/rssi"
|
||||
|
||||
|
||||
Enter the name of the MQTT server in Home Assistant in the URL. You might need to install that option
|
||||
first. This is the format needed to submit the data to the correct topics as needed above. You can add as
|
||||
many sensors / topics as you want.
|
||||
|
||||
::
|
||||
|
||||
gravmon/${mdns}/tilt:${angle}|
|
||||
gravmon/${mdns}/temperature:${temp}|
|
||||
gravmon/${mdns}/temp_units:${temp-unit}|
|
||||
|
||||
|
||||
Brewer's Friend
|
||||
+++++++++++++++
|
||||
|
||||
Brewer's friend is an all in one service that allows you to manage you recepies and brews.
|
||||
|
||||
.. 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. 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>`_
|
||||
|
||||
**Note there are different URLs if you are using plato or specific gravity!**
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://log.brewersfriend.com/ispindel/[API KEY]
|
||||
http://log.brewersfriend.com/ispindel_sg/[API KEY]
|
||||
|
||||
|
||||
From what I can read in the documentation you need to add the API key as a token as well. This can be done using a custom
|
||||
format for the endpoint. Just add you API key after token.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"name" : "${mdns}",
|
||||
"ID": "${id}",
|
||||
"token" : "[API KEY]",
|
||||
"interval": ${sleep-interval},
|
||||
"temperature": ${temp},
|
||||
"temp-units": "${temp-unit}",
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"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
|
||||
|