2 Commits
v0.9.0 ... ssl

Author SHA1 Message Date
03e6fb6b22 Full ssl implementation (buggy) 2022-01-17 20:13:48 +01:00
2532f50215 Added script to extract CA 2022-01-16 22:08:25 +01:00
123 changed files with 4368 additions and 9627 deletions

View File

@ -1,53 +0,0 @@
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

View File

@ -36,7 +36,8 @@ jobs:
git config --global advice.detachedHead false
- name: Run PlatformIO
run: pio run -e gravity-release -e gravity-perf -e gravity32-release -e gravity32-perf
#run: pio run -e gravity-release -e gravity-perf -e gravity-debug
run: pio run -e gravity-release -e gravity-perf
- uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. https://github.com/marketplace/actions/add-commit
with:

View File

@ -3,8 +3,6 @@ repos:
rev: 9a5aa38207bf557961110d6a4f7e3a9d352911f9
hooks:
- id: clang-format
files: ^src/
- id: cpplint
files: ^src/
- id: cppcheck
files: ^src/

View File

@ -1,39 +1,5 @@
![download](https://img.shields.io/github/downloads/mp-se/gravitymon/total)
![release](https://img.shields.io/github/v/release/mp-se/gravitymon?label=latest%20release)
![issues](https://img.shields.io/github/issues/mp-se/gravitymon)
![pr](https://img.shields.io/github/issues-pr/mp-se/gravitymon)
![dev_build](https://img.shields.io/github/workflow/status/mp-se/gravitymon/PlatformIO%20CI/dev?label=dev%20build)
![doc_build](https://img.shields.io/github/workflow/status/mp-se/gravitymon/Sphinx%20Build/dev?label=doc%20build)
# Gravity Monitor for Beer Brewing
GravityMon is a replacement firmware for the iSpindle firmware. It's 100% compatible with the iSpindle hardware design so it does not require any hardware changes.
This software can be used with iSpindle hardware and utilizes the same hardware configuration. No code has been reused from the iSpindle project.
Now also works with ESP32 (use ESP32 d1 mini which is compatible with ESP8266)
Installation can be made using https://www.brewflasher.com
Note! If its being flagged as malware, try the older version.
The main differences:
---------------------
* Modern web based user interface for configuration when connected to WIFI
* Efficient software, long lifespan (+45 days with 5min update frequencey)
* Send data to multiple endpoints (http-post, http-get, influxdb v2, mqtt)
* Instructions for service such as; Brewfather, Fermentrack, Ubidots, Home Assistant, Brewers Friend, Brewspy, Thingspeak, Blynk.
* SSL support in standard HTTP and MQTT connections.
* ESP32 support with Bluetooth push
* Customize data format to be pushed
* Automatic temperature adjustment of gravity when enabled
* Use the temperature sensor in gyro instead of DS18B20
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
* Visual graph showing how gravity formula will be interpreted
* OTA support
* Built in performance measurements (used to optimise code)
* REST API for scripting
No code has been reused from the iSpindle project.
The documenation can be found here: https://mp-se.github.io/gravitymon/index.html
The documenation is now moved to https://mp-se.github.io/gravitymon/index.html

View File

@ -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"><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 active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><hr class="my-4"></div><!-- 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 href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
bin/device.min.htm Normal file
View File

@ -0,0 +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">&times;</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">Certificates:</div><div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var t="/api/device";$("#spinner").show(),$.getJSON(t,function(t){console.log(t),$("#app-ver").text(t["app-ver"]+" (html 0.6.0)"),$("#mdns").text(t.mdns),$("#id").text(t.id),t.certs?$("#certs").text("CA Store installed."):$("#certs").text("CA Store NOT installed.")}).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>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,2 +0,0 @@
<!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="javascript:history.back()">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">&times;</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="headingFormat"><h2 class="mb-0"><button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">Push Format Templates</button></h2></div><div id="collapseFormat" class="collapse show" aria-labelledby="headingFormat" 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="http-3" id="http-3" 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 (post)</option><option value="http-2">HTTP option 2 (post)</option><option value="http-3">HTTP option 3 (get)</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(e){$("#format-btn").prop("disabled",e),$("#test-btn").prop("disabled",e)}function selectFormat(){var e="#"+$("#push-target").val();console.log(e),e=decodeURIComponent($(e).val()),console.log(e),e=e.replaceAll("|","|\n"),console.log(e),$("#format").val(e),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var e="/api/config/format";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#id").val(e.id),$("#http-1").val(e["http-1"]),$("#http-2").val(e["http-2"]),$("#http-3").val(e["http-3"]),$("#influxdb").val(e.influxdb),$("#mqtt").val(e.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(e){console.log(e),selectFormat()}),$("#format-btn").click(function(e){var l=$("#format").val();l=l.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(l);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(e){showSuccess("Format stored successfully."),getConfig()},error:function(e){showError("Unable to store format.")}})}),$("#test-btn").click(function(e){var l="/api/status";$("#spinner").show(),$.getJSON(l,function(e){console.log(e);var l=$("#format").val();if(l="C"==e["temp-format"]?l.replaceAll("${temp}",e["temp-c"]):l.replaceAll("${temp}",e["temp-f"]),"G"==e["gravity-format"]){var t=e.gravity;l=l.replaceAll("${gravity-sg}",t),l=l.replaceAll("${corr-gravity-sg}",t);var a=259-(259-t);l=l.replaceAll("${gravity-plato}",a),l=l.replaceAll("${corr-gravity-plato}",a)}else{a=e.gravity;l=l.replaceAll("${gravity-plato}",a),l=l.replaceAll("${corr-gravity-plato}",a);t=259/(259-a);l=l.replaceAll("${gravity-sg}",t),l=l.replaceAll("${corr-gravity-sg}",t)}l=l.replaceAll("${mdns}",e.mdns),l=l.replaceAll("${id}",e.id),l=l.replaceAll("${sleep-interval}",e["sleep-interval"]),l=l.replaceAll("${token}",e.token),l=l.replaceAll("${token2}",e.token2),l=l.replaceAll("${temp-c}",e["temp-c"]),l=l.replaceAll("${temp-f}",e["temp-f"]),l=l.replaceAll("${temp-unit}",e["temp-format"]),l=l.replaceAll("${battery}",e.battery),l=l.replaceAll("${rssi}",e.rssi),l=l.replaceAll("${run-time}",e["runtime-average"]),l=l.replaceAll("${gravity}",e.gravity),l=l.replaceAll("${gravity-unit}",e["gravity-format"]),l=l.replaceAll("${corr-gravity}",e.gravity),l=l.replaceAll("${angle}",e.angle),l=l.replaceAll("${tilt}",e.angle);try{var r=JSON.parse(l);l=JSON.stringify(r,null,2)}catch(e){console.log("Not a javascript object!")}$("#preview").text(l)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
<!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="javascript:history.back()">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">&times;</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><div class="card"><div class="card-body"><pre class="card-preview" id="preview" name="preview"></pre></div></div><div class="form-group row"></div><div class="form-group row"><div class="col-sm-8"><button class="btn btn-primary" id="test-btn">Test</button></div></div><hr class="my-4"></div><script type="text/javascript">function clearLog(){$("#preview").text("")}function appendLog(t){doc=$("#preview").text(),doc+=t+"\n",$("#preview").text(doc)}function testMqtt(t){var e="/api/test/push";e+="?id="+t+"&format=mqtt",$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target 'mqtt' successful":-3==e?"Push target 'mqtt' failed to connect":-4==e?"Push target 'mqtt' failed with error timeout":-10==e?"Push target 'mqtt' failed with error denied":"Push target 'mqtt' failed with error code "+e:"Push target 'mqtt' is not configured/used")}).fail(function(){appendLog("Failed to test push target 'influxdb'")})}function testInfluxdb(t){var e="/api/test/push";e+="?id="+t+"&format=influxdb",$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target 'influxdb' successful":400==e?"Push target 'influxdb' failed with error code 400, bad request":401==e?"Push target 'influxdb' failed with error code 401, unauthorized":404==e?"Push target 'influxdb' failed with error code 404, url not found":"Push target 'influxdb' failed with error code "+e:"Push target 'influxdb' is not configured/used")}).fail(function(){appendLog("Failed to test push target 'influxdb'")})}function testHttp(t,s){var e="/api/test/push";e+="?id="+t+"&format="+s,$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target '"+s+"' successful":400==e?"Push target '"+s+"' failed with error code 400, bad request":401==e?"Push target '"+s+"' failed with error code 401, unauthorized":404==e?"Push target '"+s+"' failed with error code 404, url not found":"Push target '"+s+"' failed with error code "+e:"Push target '"+s+"' is not configured/used")}).fail(function(){appendLog("Failed to test push target '"+s+"'")})}$("#spinner").hide(),$("#test-btn").click(function(t){clearLog(),appendLog("Starting test of push targets");var e="/api/status";$("#test-btn").prop("disabled",!0),$("#spinner").show(),$.getJSON(e,function(t){var e=t.id;console.log(e),testHttp(e,"http-1"),testHttp(e,"http-2"),testHttp(e,"http-3"),testHttp(e,"brewfather"),testInfluxdb(e),testMqtt(e),$("#spinner").hide(),$("#test-btn").prop("disabled",!1)}).fail(function(){showError("Unable to get data from the device."),$("#spinner").hide(),$("#test-btn").prop("disabled",!1)}).always(function(){})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>

View File

@ -1 +1 @@
{ "project":"gravmon", "version":"0.9.0", "html": [ ] }
{ "project":"gravmon", "version":"0.6.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "calibration.min.htm", "about.min.htm" ] }

BIN
data/certs.ar Normal file

Binary file not shown.

View File

@ -25,6 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>

View File

@ -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"><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 active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><hr class="my-4"></div><!-- 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 href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>

View File

@ -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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<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://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://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="py-4">
@ -26,6 +26,9 @@
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
@ -74,29 +77,28 @@
<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="#collapseCalibration" aria-expanded="true" aria-controls="collapseCalibration">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Formula calculation
</button>
</h2>
</div>
<div id="collapseCalibration" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body">
<form action="/api/formula" method="post">
<input type="text" name="gravity-format" id="gravity-format" hidden>
<input type="text" name="id" id="id" hidden>
<div class="row mb-3">
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity. These values
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity (SG). These values
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
formula and if the deviation is more than 1.5SG / 0.38P on any of the provided points then the forumla will be
rejected. On the bottom of the page you can see a graph over the entered values + values calcualated by the formula.
formula and if the deviation is more than 1.5 SG then the forumla will be rejected. On the bottom of the page you can
see a graph over the entered values + values calcualated by the formula.
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">#:</label>
<label class="col-sm-4 col-form-label">Angle/Tilt:</label>
<label class="col-sm-4 col-form-label" id="gravity-header">Gravity (SG):</label>
<label class="col-sm-4 col-form-label">Gravity (SG):</label>
</div>
<div class="form-group row">
@ -105,7 +107,7 @@
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a1" id="a1">
</div>
<div class="col-sm-4">
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g1" id="g1">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g1" id="g1">
</div>
</div>
@ -115,7 +117,7 @@
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a2" id="a2">
</div>
<div class="col-sm-4">
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g2" id="g2">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g2" id="g2">
</div>
</div>
@ -125,7 +127,7 @@
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a3" id="a3">
</div>
<div class="col-sm-4">
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g3" id="g3">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g3" id="g3">
</div>
</div>
@ -135,7 +137,7 @@
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a4" id="a4">
</div>
<div class="col-sm-4">
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g4" id="g4">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g4" id="g4">
</div>
</div>
@ -145,7 +147,7 @@
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a5" id="a5">
</div>
<div class="col-sm-4">
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g5" id="g5">
<input type="number" min="1" max="2" step="0.0001" class="form-control" name="g5" id="g5">
</div>
</div>
@ -231,60 +233,40 @@
</script>
<script type="text/javascript">
g1.onchange = setGravityDecimal
g2.onchange = setGravityDecimal
g3.onchange = setGravityDecimal
g4.onchange = setGravityDecimal
g5.onchange = setGravityDecimal
g1.onchange = setFourNumberDecimal
g2.onchange = setFourNumberDecimal
g3.onchange = setFourNumberDecimal
g4.onchange = setFourNumberDecimal
g5.onchange = setFourNumberDecimal
a1.onchange = setAngleDecimal
a2.onchange = setAngleDecimal
a3.onchange = setAngleDecimal
a4.onchange = setAngleDecimal
a5.onchange = setAngleDecimal
a1.onchange = setTwoNumberDecimal
a2.onchange = setTwoNumberDecimal
a3.onchange = setTwoNumberDecimal
a4.onchange = setTwoNumberDecimal
a5.onchange = setTwoNumberDecimal
window.onload = getConfig;
setButtonDisabled( true );
function convertToPlato(sg) {
return 259-(259/sg);
}
function convertToSG(plato) {
return 259/(259-plato);
}
function setAngleDecimal(event) {
function setTwoNumberDecimal(event) {
this.value = parseFloat(this.value).toFixed(2);
populateChart();
}
function setGravityDecimal(event) {
if(isPlato())
this.value = parseFloat(this.value).toFixed(1);
else
this.value = parseFloat(this.value).toFixed(4);
function setFourNumberDecimal(event) {
this.value = parseFloat(this.value).toFixed(4);
populateChart();
}
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) {
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
}
function isPlato() {
return $("#gravity-format").text() == "P";
}
function populateChart() {
chartDataCalc.length = 0
@ -296,10 +278,6 @@
formula=formula.replaceAll( "tilt^2", angle+"*"+angle );
formula=formula.replaceAll( "tilt", angle );
var g = eval( formula );
if(isPlato())
g = convertToPlato(g);
populateChartCalc( i, g );
}
@ -336,23 +314,6 @@
$("#id").val(cfg["id"]);
$("#angle").text(cfg["angle"]);
$("#formula").text(cfg["gravity-formula"]);
$("#gravity-format").text(cfg["gravity-format"]); // Sets the variable used by isPlato()
if(isPlato()) {
$("#gravity-header").text("Gravity (Plato):");
$("#g1").val( parseFloat(cfg["g1"]).toFixed(1) );
$("#g2").val( parseFloat(cfg["g2"]).toFixed(1) );
$("#g3").val( parseFloat(cfg["g3"]).toFixed(1) );
$("#g4").val( parseFloat(cfg["g4"]).toFixed(1) );
$("#g5").val( parseFloat(cfg["g5"]).toFixed(1) );
} else {
$("#gravity-header").text("Gravity (SG):");
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
}
$("#a1").val( parseFloat(cfg["a1"]).toFixed(2) );
$("#a2").val( parseFloat(cfg["a2"]).toFixed(2) );
@ -360,9 +321,11 @@
$("#a4").val( parseFloat(cfg["a4"]).toFixed(2) );
$("#a5").val( parseFloat(cfg["a5"]).toFixed(2) );
if( cfg["error"]!="" ) {
showError(cfg["error"]);
}
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
populateChart();
})

File diff suppressed because one or more lines are too long

View File

@ -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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<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://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://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
@ -25,6 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
@ -90,17 +93,16 @@
</script>
<div class="accordion" id="accordion">
<input type="text" name="runtime-average" id="runtime-average" hidden>
<div class="card">
<div class="card-header" id="headingDevice">
<div class="card-header" id="headingOne">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" onclick="window.location.href = '#collapseDevice'" type="button" data-toggle="collapse" data-target="#collapseDevice" aria-expanded="true" aria-controls="collapseDevice">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Device settings
</button>
</h2>
</div>
<div id="collapseDevice" class="collapse show" aria-labelledby="headingDevice" data-parent="#accordion">
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/device" method="post">
<input type="text" name="id" id="id1" hidden>
@ -132,7 +134,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-4 col-form-label" id="sleep-interval-info"></label>
<label for="sleep-interval" class="col-sm-3 col-form-label" id="sleep-interval-info"></label>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-3">
@ -157,62 +159,27 @@
</div>
<div class="card">
<div class="card-header" id="headingPush">
<div class="card-header" id="headingTwo">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" onclick="window.location.href = '#collapsePush'" type="button" data-toggle="collapse" data-target="#collapsePush" aria-expanded="false" aria-controls="collapsePush">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Push settings
</button>
</h2>
</div>
<div id="collapsePush" class="collapse" aria-labelledby="headingPush" data-parent="#accordion">
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/push" method="post">
<input type="text" name="id" id="id2" hidden>
<input type="text" name="section" value="collapsePush" 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 1 (POST):</label>
<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>
<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>
</div>
<div class="form-group row">
<label for="http-push2" class="col-sm-2 col-form-label">HTTP 2 (POST):</label>
<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>
<div class="form-group row">
<label for="token" class="col-sm-2 col-form-label">Token:</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="token" id="token">
</div>
</div>
<hr class="my-2">
<div class="form-group row">
<label for="http-push3" class="col-sm-2 col-form-label">HTTP 3 (GET):</label>
<div class="col-sm-8">
<input type="url" maxlength="120" class="form-control" name="http-push3" id="http-push3">
</div>
</div>
<div class="form-group row">
<label for="token2" class="col-sm-2 col-form-label">Token 2:</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="token2" id="token2">
<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>
</div>
@ -225,37 +192,7 @@
</div>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-2">
<button type="submit" class="btn btn-primary" id="push-btn">Save</button>
</div>
</div>
</form>
<div class="form-group row">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-info" id="format-btn">Format editor</button>
<button class="btn btn-info" id="test-btn">Test Push</button>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingPush2">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" onclick="window.location.href = '#collapsePush2'" type="button" data-toggle="collapse" data-target="#collapsePush2" aria-expanded="false" aria-controls="collapsePush2">
Push settings (2)
</button>
</h2>
</div>
<div id="collapsePush2" class="collapse" aria-labelledby="headingPush2" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/push" method="post">
<input type="text" name="id" id="id5" hidden>
<input type="text" name="section" value="collapsePush2" hidden>
<hr class="my-2">
<div class="form-group row">
<label for="influxdb2-push" class="col-sm-2 col-form-label">InfluxDB v2 URL:</label>
@ -275,6 +212,8 @@
<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">
@ -291,9 +230,9 @@
</div>
</div>
<div class="form-group row">
<label for="mqtt-topic" class="col-sm-2 col-form-label">MQTT Port:</label>
<label for="mqtt-topic" class="col-sm-2 col-form-label">MQTT Topic:</label>
<div class="col-sm-4">
<input type="number" min="1" max="65535" class="form-control" name="mqtt-port" id="mqtt-port">
<input type="text" maxlength="30" class="form-control" name="mqtt-topic" id="mqtt-topic">
</div>
</div>
<div class="form-group row">
@ -311,53 +250,28 @@
<div class="form-group row">
<div class="col-sm-8 offset-sm-2">
<button type="submit" class="btn btn-primary" id="push-btn2">Save</button>
<button type="submit" class="btn btn-primary" id="push-btn">Save</button>
</div>
</div>
</form>
<div class="form-group row">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-info" id="format-btn2">Format editor</button>
<button class="btn btn-info" id="test-btn2">Test Push</button>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingGravity">
<div class="card-header" id="headingThree">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" onclick="window.location.href = '#collapseGravity'" type="button" data-toggle="collapse" data-target="#collapseGravity" aria-expanded="false" aria-controls="collapseGravity">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Gravity
</button>
</h2>
</div>
<div id="collapseGravity" class="collapse" aria-labelledby="headingGravity" data-parent="#accordion">
<div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/gravity" method="post">
<input type="text" name="id" id="id3" hidden>
<fieldset class="form-group row">
<legend class="col-form-label col-sm-2 float-sm-left pt-0">Gravity Format:</legend>
<div class="col-sm-4">
<div class="form-check">
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-g" value="G" checked>
<label class="form-check-label" for="gravity-format-g">
SG
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-p" value="P">
<label class="form-check-label" for="gravity-format-p">
Plato
</label>
</div>
</div>
</fieldset>
<div class="form-group row">
<label for="gravity-formula" class="col-sm-2 col-form-label">Formula (SG)</label>
<label for="gravity-formula" class="col-sm-2 col-form-label">Formula</label>
<div class="col-sm-10">
<input type="text" maxlength="200" class="form-control" name="gravity-formula" id="gravity-formula">
</div>
@ -383,14 +297,14 @@
</div>
<div class="card">
<div class="card-header" id="headingHardware">
<div class="card-header" id="headingFour">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" onclick="window.location.href = '#collapseHardware'" type="button" data-toggle="collapse" data-target="#collapseHardware" aria-expanded="false" aria-controls="collapseHardware">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Hardware settings
</button>
</h2>
</div>
<div id="collapseHardware" class="collapse" aria-labelledby="headingHardware" data-parent="#accordion">
<div id="collapseFour" class="collapse" aria-labelledby="headingFour" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/hardware" method="post">
<input type="text" name="id" id="id4" hidden>
@ -417,22 +331,6 @@
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label" for="ble">Bluetooth tilt color:</label>
<div class="col-sm-2">
<select class="form-control" id="ble" name="ble" disabled>
<option value="">-not active-</option>
<option value="red">red</option>
<option value="green">green</option>
<option value="black">black</option>
<option value="purple">purple</option>
<option value="orange">orange</option>
<option value="blue">blue</option>
<option value="yellow">yellow</option>
<option value="pink">pink</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="ota-url" class="col-sm-2 col-form-label">OTA base URL:</label>
<div class="col-sm-10">
@ -445,11 +343,6 @@
</div>
</div>
</form>
<div class="form-group row">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-info" id="firmware-btn">Upload firmware</button>
</div>
</div>
</div>
</div>
</div>
@ -458,63 +351,12 @@
<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;
setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseXXX)
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
@ -534,60 +376,9 @@ function checkHeader(input) {
} );
});
$("#firmware-btn").click(function(e){
window.location.href = "/firmware.htm";
});
// Open the format editor
$("#format-btn").click(function(e){
window.location.href = "/format.htm";
});
// Open the format editor
$("#format-btn2").click(function(e){
window.location.href = "/format.htm";
});
$("#test-btn").click(function(e){
window.location.href = "/test.htm";
});
$("#test-btn2").click(function(e){
window.location.href = "/test.htm";
});
function estimateBatteryLife(interval, rt) {
// 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 = 170; // mA per hour
var pwrSleep = 15; // mA per day
var batt = 2000; // mA
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 = parseInt($("#sleep-interval").val());
var rt = parseInt($("#runtime-average").val());
var j = 0;
if( rt>0 )
j = estimateBatteryLife(i, rt);
var t1 = Math.floor(i/60) + " m " + (i%60) + " s";
var t2 = Math.floor(j/7) + " weeks " + Math.floor(j%7) + " days";
if( j )
$("#sleep-interval-info").text( t1 + " - Estimated runtime: " + t2);
else
$("#sleep-interval-info").text( t1 );
var i = $("#sleep-interval").val()
$("#sleep-interval-info").text( Math.floor(i/60) + " m " + (i%60) + " s" )
hideWarningGyro();
if(i>0 && i<300) {
@ -607,13 +398,8 @@ function checkHeader(input) {
$("#device-btn").prop("disabled", b);
$("#calibrate-btn").prop("disabled", b);
$("#push-btn").prop("disabled", b);
$("#format-btn").prop("disabled", b);
$("#test-btn").prop("disabled", b);
$("#gravity-btn").prop("disabled", b);
$("#hardware-btn").prop("disabled", b);
$("#push-btn2").prop("disabled", b);
$("#format-btn2").prop("disabled", b);
$("#test-btn2").prop("disabled", b);
}
// Get the configuration values from the API
@ -625,39 +411,25 @@ function checkHeader(input) {
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
if(cfg["platform"]=="esp32") {
$('#ble').prop('disabled', false);
$("#ble").val(cfg["ble"]);
}
$("#id1").val(cfg["id"]);
$("#id2").val(cfg["id"]);
$("#id3").val(cfg["id"]);
$("#id4").val(cfg["id"]);
$("#id5").val(cfg["id"]);
$("#mdns").val(cfg["mdns"]);
if( cfg["temp-format"] == "C" ) $("#temp-format-c").click();
else $("#temp-format-f").click();
if( cfg["gravity-format"] == "G" ) $("#gravity-format-g").click();
else $("#gravity-format-p").click();
if( cfg["temp-format"] == "C" )
$("#temp-format-c").click();
else
$("#temp-format-f").click();
$("#ota-url").val(cfg["ota-url"]);
$("#token").val(cfg["token"]);
$("#token2").val(cfg["token2"]);
$("#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"]);
$("#http-push3").val(cfg["http-push3"]);
$("#brewfather-push").val(cfg["brewfather-push"]);
$("#influxdb2-push").val(cfg["influxdb2-push"]);
$("#influxdb2-org").val(cfg["influxdb2-org"]);
$("#influxdb2-bucket").val(cfg["influxdb2-bucket"]);
$("#influxdb2-auth").val(cfg["influxdb2-auth"]);
$("#mqtt-push").val(cfg["mqtt-push"]);
$("#mqtt-port").val(cfg["mqtt-port"]);
$("#mqtt-topic").val(cfg["mqtt-topic"]);
$("#mqtt-user").val(cfg["mqtt-user"]);
$("#mqtt-pass").val(cfg["mqtt-pass"]);
$("#sleep-interval").val(cfg["sleep-interval"]);
@ -669,7 +441,6 @@ function checkHeader(input) {
$("#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 () {

File diff suppressed because one or more lines are too long

129
html/device.htm Normal file
View File

@ -0,0 +1,129 @@
<!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">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('.alert').addClass('d-none').removeClass('show')
});
</script>
<div class="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">Certificates:</div>
<div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
window.onload = getConfig;
function getConfig() {
var url = "/api/device";
//var url = "/test/device.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#app-ver").text(cfg["app-ver"] + " (html 0.6.0)");
$("#mdns").text(cfg["mdns"]);
$("#id").text(cfg["id"]);
if( cfg["certs"] )
$("#certs").text("CA Store installed.");
else
$("#certs").text("CA Store NOT installed.");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function () {
$('#spinner').hide();
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

1
html/device.min.htm Normal file
View File

@ -0,0 +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">&times;</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">Certificates:</div><div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var t="/api/device";$("#spinner").show(),$.getJSON(t,function(t){console.log(t),$("#app-ver").text(t["app-ver"]+" (html 0.6.0)"),$("#mdns").text(t.mdns),$("#id").text(t.id),t.certs?$("#certs").text("CA Store installed."):$("#certs").text("CA Store NOT installed.")}).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>

View File

@ -1,188 +0,0 @@
<!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="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</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="javascript:history.back()">Back to configuration</a>
</li>
</ul>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</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">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('.alert').addClass('d-none').removeClass('show')
});
</script>
<div class="row mb-3">
<div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.
</div>
</div>
<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">
<div class="col-md-8 themed-grid-col bg-light">Platform:</div>
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div>
</div>
<div class="row mb-3">
<!--
<form action="/api/upload" method="post" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
</form>
-->
<form id="uploadForm" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
</form>
</div>
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
window.onload = getStatus;
$(document).ready(function() {
$("#uploadForm").on('submit', function(e) {
e.preventDefault();
$.ajax( {
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
progressHandler(evt);
}
}, false);
return xhr;
},
type: 'POST',
url: '/api/upload',
data: new FormData(this),
contentType: false,
cache: false,
processData:false,
beforeSend: function() {
setProgress(0);
},
error:function() {
showError("Upload failed");
},
success: function(resp) {
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
setTimeout(() => {
window.location = "/";
}, 10000);
}
});
});
});
function checkName() {
setButtonDisabled( $("#name").val()!="" ? false : true );
}
function progressHandler(event) {
var percent = (event.loaded / event.total) * 100;
setProgress(Math.round(percent));
}
function setProgress(val) {
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
}
function setButtonDisabled( b ) {
$("#upload-btn").prop("disabled", b);
}
function getStatus() {
setButtonDisabled( true );
var url = "/api/status";
//var url = "/test/status.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#app-ver").text(cfg["app-ver"]);
$("#platform").text(cfg["platform"]);
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
function start() {
setInterval(getStatus, 3000);
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!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="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</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="javascript:history.back()">Back to configuration</a></li></ul><div class="spinner-border text-light" id="spinner" role="status"></div></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">&times;</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-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><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"><div class="col-md-8 themed-grid-col bg-light">Platform:</div><div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div></div><div class="row mb-3"><!--
<form action="/api/upload" method="post" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
</form>
--><form id="uploadForm" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button></form></div><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div><hr class="my-4"></div><script type="text/javascript">window.onload = getStatus;
$(document).ready(function() {
$("#uploadForm").on('submit', function(e) {
e.preventDefault();
$.ajax( {
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
progressHandler(evt);
}
}, false);
return xhr;
},
type: 'POST',
url: '/api/upload',
data: new FormData(this),
contentType: false,
cache: false,
processData:false,
beforeSend: function() {
setProgress(0);
},
error:function() {
showError("Upload failed");
},
success: function(resp) {
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
setTimeout(() => {
window.location = "/";
}, 10000);
}
});
});
});
function checkName() {
setButtonDisabled( $("#name").val()!="" ? false : true );
}
function progressHandler(event) {
var percent = (event.loaded / event.total) * 100;
setProgress(Math.round(percent));
}
function setProgress(val) {
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
}
function setButtonDisabled( b ) {
$("#upload-btn").prop("disabled", b);
}
function getStatus() {
setButtonDisabled( true );
var url = "/api/status";
//var url = "/test/status.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#app-ver").text(cfg["app-ver"]);
$("#platform").text(cfg["platform"]);
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
function start() {
setInterval(getStatus, 3000);
}</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>

View File

@ -1,268 +0,0 @@
<!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="javascript:history.back()">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">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('#alert').addClass('d-none').removeClass('show');
});
</script>
<div class="accordion" id="accordion">
<div class="card">
<div class="card-header" id="headingFormat">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">
Push Format Templates
</button>
</h2>
</div>
<div id="collapseFormat" class="collapse show" aria-labelledby="headingFormat" 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="http-3" id="http-3" 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 (post)</option>
<option value="http-2">HTTP option 2 (post)</option>
<option value="http-3">HTTP option 3 (get)</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">
window.onload = getConfig;
setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
$(location.hash + '.collapse').collapse('show');
}
});
$("#push-target").change(function(e){
console.log(e)
selectFormat();
});
// Store the format
$("#format-btn").click(function(e) {
var s = $("#format").val();
s = s.replaceAll("\n", "");
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
console.log(obj);
$.ajax( {
type: "POST",
url: "/api/config/format",
data: obj,
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
error: function(result) { showError('Unable to store format.'); }
} );
});
// Test the calibration
$("#test-btn").click(function(e) {
var url = "/api/status";
//var url = "/test/status.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
var doc = $("#format").val();
if (cfg["temp-format"]=="C")
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
else
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
if (cfg["gravity-format"]=="G") {
var sg = cfg["gravity"];
doc = doc.replaceAll("${gravity-sg}", sg);
doc = doc.replaceAll("${corr-gravity-sg}", sg);
var plato = 259 - (259 - sg);
doc = doc.replaceAll("${gravity-plato}", plato);
doc = doc.replaceAll("${corr-gravity-plato}", plato);
}
else {
var plato = cfg["gravity"];
doc = doc.replaceAll("${gravity-plato}", plato);
doc = doc.replaceAll("${corr-gravity-plato}", plato);
var sg = 259 / (259 - plato);
doc = doc.replaceAll("${gravity-sg}", sg);
doc = doc.replaceAll("${corr-gravity-sg}", sg);
}
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
doc = doc.replaceAll("${id}", cfg["id"]);
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
doc = doc.replaceAll("${token}", cfg["token"]);
doc = doc.replaceAll("${token2}", cfg["token2"]);
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
doc = doc.replaceAll("${battery}", cfg["battery"]);
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
doc = doc.replaceAll("${angle}", cfg["angle"]);
doc = doc.replaceAll("${tilt}", cfg["angle"]);
// Format in a readable json string.
try {
var json = JSON.parse(doc);
doc = JSON.stringify(json, null, 2);
} catch(e) {
console.log("Not a javascript object!")
}
$("#preview").text(doc);
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
});
function setButtonDisabled( b ) {
$("#format-btn").prop("disabled", b);
$("#test-btn").prop("disabled", b);
}
function selectFormat() {
var s = "#" + $("#push-target").val()
console.log(s);
s = decodeURIComponent($(s).val());
console.log(s);
s = s.replaceAll("|", "|\n");
console.log(s);
$("#format").val(s);
$("#preview").text("");
}
// Get the configuration values from the API
function getConfig() {
setButtonDisabled( true );
var url = "/api/config/format";
//var url = "/test/format.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#id").val(cfg["id"]);
$("#http-1").val(cfg["http-1"]);
$("#http-2").val(cfg["http-2"]);
$("#http-3").val(cfg["http-3"]);
//$("#brewfather").val(cfg["brewfather"]);
$("#influxdb").val(cfg["influxdb"]);
$("#mqtt").val(cfg["mqtt"]);
selectFormat();
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

View File

@ -1,2 +0,0 @@
<!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="javascript:history.back()">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">&times;</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="headingFormat"><h2 class="mb-0"><button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">Push Format Templates</button></h2></div><div id="collapseFormat" class="collapse show" aria-labelledby="headingFormat" 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="http-3" id="http-3" 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 (post)</option><option value="http-2">HTTP option 2 (post)</option><option value="http-3">HTTP option 3 (get)</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(e){$("#format-btn").prop("disabled",e),$("#test-btn").prop("disabled",e)}function selectFormat(){var e="#"+$("#push-target").val();console.log(e),e=decodeURIComponent($(e).val()),console.log(e),e=e.replaceAll("|","|\n"),console.log(e),$("#format").val(e),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var e="/api/config/format";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#id").val(e.id),$("#http-1").val(e["http-1"]),$("#http-2").val(e["http-2"]),$("#http-3").val(e["http-3"]),$("#influxdb").val(e.influxdb),$("#mqtt").val(e.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(e){console.log(e),selectFormat()}),$("#format-btn").click(function(e){var l=$("#format").val();l=l.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(l);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(e){showSuccess("Format stored successfully."),getConfig()},error:function(e){showError("Unable to store format.")}})}),$("#test-btn").click(function(e){var l="/api/status";$("#spinner").show(),$.getJSON(l,function(e){console.log(e);var l=$("#format").val();if(l="C"==e["temp-format"]?l.replaceAll("${temp}",e["temp-c"]):l.replaceAll("${temp}",e["temp-f"]),"G"==e["gravity-format"]){var t=e.gravity;l=l.replaceAll("${gravity-sg}",t),l=l.replaceAll("${corr-gravity-sg}",t);var a=259-(259-t);l=l.replaceAll("${gravity-plato}",a),l=l.replaceAll("${corr-gravity-plato}",a)}else{a=e.gravity;l=l.replaceAll("${gravity-plato}",a),l=l.replaceAll("${corr-gravity-plato}",a);t=259/(259-a);l=l.replaceAll("${gravity-sg}",t),l=l.replaceAll("${corr-gravity-sg}",t)}l=l.replaceAll("${mdns}",e.mdns),l=l.replaceAll("${id}",e.id),l=l.replaceAll("${sleep-interval}",e["sleep-interval"]),l=l.replaceAll("${token}",e.token),l=l.replaceAll("${token2}",e.token2),l=l.replaceAll("${temp-c}",e["temp-c"]),l=l.replaceAll("${temp-f}",e["temp-f"]),l=l.replaceAll("${temp-unit}",e["temp-format"]),l=l.replaceAll("${battery}",e.battery),l=l.replaceAll("${rssi}",e.rssi),l=l.replaceAll("${run-time}",e["runtime-average"]),l=l.replaceAll("${gravity}",e.gravity),l=l.replaceAll("${gravity-unit}",e["gravity-format"]),l=l.replaceAll("${corr-gravity}",e.gravity),l=l.replaceAll("${angle}",e.angle),l=l.replaceAll("${tilt}",e.angle);try{var r=JSON.parse(l);l=JSON.stringify(r,null,2)}catch(e){console.log("Not a javascript object!")}$("#preview").text(l)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>

View File

@ -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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<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://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://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
@ -25,6 +25,9 @@
<li class="nav-item active">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
@ -69,54 +72,7 @@
});
</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">Platform:</div>
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div>
</div>
<script>
$("#log-btn").click(function(e){
loadLog();
});
setInterval(function() {
loadLog();
}, 3000); //5 seconds
function loadLog() {
$("#logContent").load("/log");
//$("#logContent").load("/test/status.json");
};
</script>
<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>
<div class="collapse" id="collapseLog">
<div class="card card-body">
<pre><code id="logContent"></code></pre>
</div>
</div>
<hr class="my-4">
<div class="" id="id" hidden></div>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">Gravity:</div>
@ -135,11 +91,6 @@
<div class="col-md-4 themed-grid-col bg-light" id="battery">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">
<div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
@ -148,7 +99,6 @@
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
@ -171,50 +121,15 @@
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
//$("#app-ver").text(cfg["app-ver"] + " (html 0.8.0)");
$("#app-ver").text(cfg["app-ver"] + " (" + cfg["app-build"] + ")");
$("#mdns").text(cfg["mdns"]);
$("#id").text(cfg["id"]);
$("#platform").text(cfg["platform"]);
$("#runtime").text(cfg["runtime-average"] + " seconds");
var angle = cfg["angle"];
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");
}
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 + "%)" );
$("#angle").text(cfg["angle"]);
$("#gravity").text(cfg["gravity"] + " SG");
$("#battery").text(cfg["battery"] + " V");
if( cfg["temp-format"] == "C")
$("#temp").text(cfg["temp-c"] + " C");
else
$("#temp").text(cfg["temp-f"] + " F");
//console.log(cfg["sleep-mode"] );
if( cfg["sleep-mode"] )
$("#sleep-mode").attr("checked", true );
else

File diff suppressed because one or more lines are too long

View File

@ -1,217 +0,0 @@
<!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="javascript:history.back()">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">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('#alert').addClass('d-none').removeClass('show');
});
</script>
<div>
<div class="card">
<div class="card-body">
<pre class="card-preview" id="preview" name="preview"></pre>
</div>
</div>
<div class="form-group row">
</div>
<div class="form-group row">
<div class="col-sm-8">
<button class="btn btn-primary" id="test-btn">Test</button>
</div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
$('#spinner').hide();
function clearLog() {
$("#preview").text("");
}
function appendLog(log) {
doc = $("#preview").text();
doc += log + "\n";
$("#preview").text(doc);
}
// Get the configuration values from the API
$("#test-btn").click(function(e) {
clearLog();
appendLog( "Starting test of push targets" );
var url = "/api/status";
//var url = "/test/status.json";
$("#test-btn").prop("disabled", true);
$('#spinner').show();
$.getJSON(url, function (cfg) {
var id = cfg["id"];
console.log( id );
testHttp( id, "http-1" );
testHttp( id, "http-2" );
testHttp( id, "http-3" );
testHttp( id, "brewfather" );
testInfluxdb( id );
testMqtt( id );
$('#spinner').hide();
$("#test-btn").prop("disabled", false);
})
.fail(function () {
showError('Unable to get data from the device.');
$('#spinner').hide();
$("#test-btn").prop("disabled", false);
})
.always(function() {
});
});
function testMqtt(id) {
var url = "/api/test/push";
url += "?id=" + id + "&format=mqtt";
//var url = "/test/push.json";
$.getJSON(url, function (cfg) {
var code = cfg["code"];
var success = cfg["success"];
var enabled = cfg["enabled"];
if(!enabled) {
appendLog( "Push target 'mqtt' is not configured/used" );
} else if(success) {
appendLog( "Push target 'mqtt' successful" );
} else{
if(code==-3)
appendLog( "Push target 'mqtt' failed to connect" );
else if(code==-4)
appendLog( "Push target 'mqtt' failed with error timeout" );
else if(code==-10)
appendLog( "Push target 'mqtt' failed with error denied" );
else
appendLog( "Push target 'mqtt' failed with error code " + code );
}
})
.fail(function () {
appendLog( "Failed to test push target 'influxdb'");
})
}
function testInfluxdb(id) {
var url = "/api/test/push";
url += "?id=" + id + "&format=influxdb";
//var url = "/test/push.json";
$.getJSON(url, function (cfg) {
var code = cfg["code"];
var success = cfg["success"];
var enabled = cfg["enabled"];
if(!enabled) {
appendLog( "Push target 'influxdb' is not configured/used" );
} else if(success) {
appendLog( "Push target 'influxdb' successful" );
} else{
if(code==400)
appendLog( "Push target 'influxdb' failed with error code 400, bad request" );
else if(code==401)
appendLog( "Push target 'influxdb' failed with error code 401, unauthorized" );
else if(code==404)
appendLog( "Push target 'influxdb' failed with error code 404, url not found" );
else
appendLog( "Push target 'influxdb' failed with error code " + code );
}
})
.fail(function () {
appendLog( "Failed to test push target 'influxdb'");
})
}
function testHttp(id, target) {
var url = "/api/test/push";
url += "?id=" + id + "&format=" + target;
//var url = "/test/push.json";
$.getJSON(url, function (cfg) {
var code = cfg["code"];
var success = cfg["success"];
var enabled = cfg["enabled"];
if(!enabled) {
appendLog( "Push target '" + target + "' is not configured/used" );
} else if(success) {
appendLog( "Push target '" + target + "' successful" );
} else{
if(code==400)
appendLog( "Push target '" + target + "' failed with error code 400, bad request" );
else if(code==401)
appendLog( "Push target '" + target + "' failed with error code 401, unauthorized" );
else if(code==404)
appendLog( "Push target '" + target + "' failed with error code 404, url not found" );
else
appendLog( "Push target '" + target + "' failed with error code " + code );
}
})
.fail(function () {
appendLog( "Failed to test push target '" + target + "'");
})
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!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="javascript:history.back()">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">&times;</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><div class="card"><div class="card-body"><pre class="card-preview" id="preview" name="preview"></pre></div></div><div class="form-group row"></div><div class="form-group row"><div class="col-sm-8"><button class="btn btn-primary" id="test-btn">Test</button></div></div><hr class="my-4"></div><script type="text/javascript">function clearLog(){$("#preview").text("")}function appendLog(t){doc=$("#preview").text(),doc+=t+"\n",$("#preview").text(doc)}function testMqtt(t){var e="/api/test/push";e+="?id="+t+"&format=mqtt",$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target 'mqtt' successful":-3==e?"Push target 'mqtt' failed to connect":-4==e?"Push target 'mqtt' failed with error timeout":-10==e?"Push target 'mqtt' failed with error denied":"Push target 'mqtt' failed with error code "+e:"Push target 'mqtt' is not configured/used")}).fail(function(){appendLog("Failed to test push target 'influxdb'")})}function testInfluxdb(t){var e="/api/test/push";e+="?id="+t+"&format=influxdb",$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target 'influxdb' successful":400==e?"Push target 'influxdb' failed with error code 400, bad request":401==e?"Push target 'influxdb' failed with error code 401, unauthorized":404==e?"Push target 'influxdb' failed with error code 404, url not found":"Push target 'influxdb' failed with error code "+e:"Push target 'influxdb' is not configured/used")}).fail(function(){appendLog("Failed to test push target 'influxdb'")})}function testHttp(t,s){var e="/api/test/push";e+="?id="+t+"&format="+s,$.getJSON(e,function(t){var e=t.code,r=t.success,i=t.enabled;appendLog(i?r?"Push target '"+s+"' successful":400==e?"Push target '"+s+"' failed with error code 400, bad request":401==e?"Push target '"+s+"' failed with error code 401, unauthorized":404==e?"Push target '"+s+"' failed with error code 404, url not found":"Push target '"+s+"' failed with error code "+e:"Push target '"+s+"' is not configured/used")}).fail(function(){appendLog("Failed to test push target '"+s+"'")})}$("#spinner").hide(),$("#test-btn").click(function(t){clearLog(),appendLog("Starting test of push targets");var e="/api/status";$("#test-btn").prop("disabled",!0),$("#spinner").show(),$.getJSON(e,function(t){var e=t.id;console.log(e),testHttp(e,"http-1"),testHttp(e,"http-2"),testHttp(e,"http-3"),testHttp(e,"brewfather"),testInfluxdb(e),testMqtt(e),$("#spinner").hide(),$("#test-btn").prop("disabled",!1)}).fail(function(){showError("Unable to get data from the device."),$("#spinner").hide(),$("#test-btn").prop("disabled",!1)}).always(function(){})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>

View File

@ -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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<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://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://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
@ -66,6 +66,10 @@
<div class="col-md-2 themed-grid-col bg-light">index.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">device.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">config.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div>
@ -74,23 +78,19 @@
<div class="col-md-2 themed-grid-col bg-light">calibration.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="calibration">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">format.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="format">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">test.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="test">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">certs.ar</div>
<div class="col-md-6 themed-grid-col bg-light" id="certs">Checking...</div>
</div>
<div class="row mb-3">
<form action="/api/upload" method="post" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" accept=".min.htm" class="custom-file-input" name="name" id="name">
<input type="file" class="custom-file-input" name="name" id="name">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button>
@ -121,31 +121,30 @@ function getUpload() {
else
$("#index").text("File is missing.");
if( cfg["device"] )
$("#device").text("Completed.");
else
$("#device").text("File is missing.");
if( cfg["config"] )
$("#config").text("Completed.");
else
$("#config").text("File is missing.");
if( cfg["calibration"] )
if( cfg["calibration"] )
$("#calibration").text("Completed.");
else
$("#calibration").text("File is missing.");
if( cfg["test"] )
$("#test").text("Completed.");
else
$("#test").text("File is missing.");
if( cfg["format"] )
$("#format").text("Completed.");
else
$("#format").text("File is missing.");
if( cfg["about"] )
$("#about").text("Completed.");
else
$("#about").text("File is missing.");
if( cfg["certs"] )
$("#certs").text("Completed.");
else
$("#certs").text("File is missing (optional).");
})
.fail(function () {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,323 +0,0 @@
#ifndef DallasTemperature_h
#define DallasTemperature_h
#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// set to true to include code for new and delete operators
#ifndef REQUIRESNEW
#define REQUIRESNEW false
#endif
// set to true to include code implementing alarm search functions
#ifndef REQUIRESALARMS
#define REQUIRESALARMS true
#endif
#include <inttypes.h>
#ifdef __STM32F1__
#include <OneWireSTM.h>
#else
#include <OneWire.h>
#endif
// Model IDs
#define DS18S20MODEL 0x10 // also DS1820
#define DS18B20MODEL 0x28 // also MAX31820
#define DS1822MODEL 0x22
#define DS1825MODEL 0x3B
#define DS28EA00MODEL 0x42
// Error Codes
#define DEVICE_DISCONNECTED_C -127
#define DEVICE_DISCONNECTED_F -196.6
#define DEVICE_DISCONNECTED_RAW -7040
// For readPowerSupply on oneWire bus
// definition of nullptr for C++ < 11, using official workaround:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
#if __cplusplus < 201103L
const class
{
public:
template <class T>
operator T *() const
{
return 0;
}
template <class C, class T>
operator T C::*() const
{
return 0;
}
private:
void operator&() const;
} nullptr = {};
#endif
typedef uint8_t DeviceAddress[8];
class DallasTemperature {
public:
DallasTemperature();
DallasTemperature(OneWire*);
DallasTemperature(OneWire*, uint8_t);
void setOneWire(OneWire*);
void setPullupPin(uint8_t);
// initialise bus
void begin(void);
// returns the number of devices found on the bus
uint8_t getDeviceCount(void);
// returns the number of DS18xxx Family devices on bus
uint8_t getDS18Count(void);
// returns true if address is valid
bool validAddress(const uint8_t*);
// returns true if address is of the family of sensors the lib supports.
bool validFamily(const uint8_t* deviceAddress);
// finds an address at a given index on the bus
bool getAddress(uint8_t*, uint8_t);
// attempt to determine if the device at the given address is connected to the bus
bool isConnected(const uint8_t*);
// attempt to determine if the device at the given address is connected to the bus
// also allows for updating the read scratchpad
bool isConnected(const uint8_t*, uint8_t*);
// read device's scratchpad
bool readScratchPad(const uint8_t*, uint8_t*);
// write device's scratchpad
void writeScratchPad(const uint8_t*, const uint8_t*);
// read device's power requirements
bool readPowerSupply(const uint8_t* deviceAddress = nullptr);
// get global resolution
uint8_t getResolution();
// set global resolution to 9, 10, 11, or 12 bits
void setResolution(uint8_t);
// returns the device resolution: 9, 10, 11, or 12 bits
uint8_t getResolution(const uint8_t*);
// set resolution of a device to 9, 10, 11, or 12 bits
bool setResolution(const uint8_t*, uint8_t,
bool skipGlobalBitResolutionCalculation = false);
// sets/gets the waitForConversion flag
void setWaitForConversion(bool);
bool getWaitForConversion(void);
// sets/gets the checkForConversion flag
void setCheckForConversion(bool);
bool getCheckForConversion(void);
// sends command for all devices on the bus to perform a temperature conversion
void requestTemperatures(void);
// sends command for one device to perform a temperature conversion by address
bool requestTemperaturesByAddress(const uint8_t*);
// sends command for one device to perform a temperature conversion by index
bool requestTemperaturesByIndex(uint8_t);
// returns temperature raw value (12 bit integer of 1/128 degrees C)
int16_t getTemp(const uint8_t*);
// returns temperature in degrees C
float getTempC(const uint8_t*);
// returns temperature in degrees F
float getTempF(const uint8_t*);
// Get temperature for device index (slow)
float getTempCByIndex(uint8_t);
// Get temperature for device index (slow)
float getTempFByIndex(uint8_t);
// returns true if the bus requires parasite power
bool isParasitePowerMode(void);
// Is a conversion complete on the wire? Only applies to the first sensor on the wire.
bool isConversionComplete(void);
static uint16_t millisToWaitForConversion(uint8_t);
uint16_t millisToWaitForConversion();
// Sends command to one device to save values from scratchpad to EEPROM by index
// Returns true if no errors were encountered, false indicates failure
bool saveScratchPadByIndex(uint8_t);
// Sends command to one or more devices to save values from scratchpad to EEPROM
// Returns true if no errors were encountered, false indicates failure
bool saveScratchPad(const uint8_t* = nullptr);
// Sends command to one device to recall values from EEPROM to scratchpad by index
// Returns true if no errors were encountered, false indicates failure
bool recallScratchPadByIndex(uint8_t);
// Sends command to one or more devices to recall values from EEPROM to scratchpad
// Returns true if no errors were encountered, false indicates failure
bool recallScratchPad(const uint8_t* = nullptr);
// Sets the autoSaveScratchPad flag
void setAutoSaveScratchPad(bool);
// Gets the autoSaveScratchPad flag
bool getAutoSaveScratchPad(void);
#if REQUIRESALARMS
typedef void AlarmHandler(const uint8_t*);
// sets the high alarm temperature for a device
// accepts a int8_t. valid range is -55C - 125C
void setHighAlarmTemp(const uint8_t*, int8_t);
// sets the low alarm temperature for a device
// accepts a int8_t. valid range is -55C - 125C
void setLowAlarmTemp(const uint8_t*, int8_t);
// returns a int8_t with the current high alarm temperature for a device
// in the range -55C - 125C
int8_t getHighAlarmTemp(const uint8_t*);
// returns a int8_t with the current low alarm temperature for a device
// in the range -55C - 125C
int8_t getLowAlarmTemp(const uint8_t*);
// resets internal variables used for the alarm search
void resetAlarmSearch(void);
// search the wire for devices with active alarms
bool alarmSearch(uint8_t*);
// returns true if ia specific device has an alarm
bool hasAlarm(const uint8_t*);
// returns true if any device is reporting an alarm on the bus
bool hasAlarm(void);
// runs the alarm handler for all devices returned by alarmSearch()
void processAlarms(void);
// sets the alarm handler
void setAlarmHandler(const AlarmHandler *);
// returns true if an AlarmHandler has been set
bool hasAlarmHandler();
#endif
// if no alarm handler is used the two bytes can be used as user data
// example of such usage is an ID.
// note if device is not connected it will fail writing the data.
// note if address cannot be found no error will be reported.
// in short use carefully
void setUserData(const uint8_t*, int16_t);
void setUserDataByIndex(uint8_t, int16_t);
int16_t getUserData(const uint8_t*);
int16_t getUserDataByIndex(uint8_t);
// convert from Celsius to Fahrenheit
static float toFahrenheit(float);
// convert from Fahrenheit to Celsius
static float toCelsius(float);
// convert from raw to Celsius
static float rawToCelsius(int16_t);
// convert from Celsius to raw
static int16_t celsiusToRaw(float);
// convert from raw to Fahrenheit
static float rawToFahrenheit(int16_t);
#if REQUIRESNEW
// initialize memory area
void* operator new (unsigned int);
// delete memory reference
void operator delete(void*);
#endif
void blockTillConversionComplete(uint8_t);
private:
typedef uint8_t ScratchPad[9];
// parasite power on or off
bool parasite;
// external pullup
bool useExternalPullup;
uint8_t pullupPin;
// used to determine the delay amount needed to allow for the
// temperature conversion to take place
uint8_t bitResolution;
// used to requestTemperature with or without delay
bool waitForConversion;
// used to requestTemperature to dynamically check if a conversion is complete
bool checkForConversion;
// used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write
bool autoSaveScratchPad;
// count of devices on the bus
uint8_t devices;
// count of DS18xxx Family devices on bus
uint8_t ds18Count;
// Take a pointer to one wire instance
OneWire* _wire;
// reads scratchpad and returns the raw temperature
int16_t calculateTemperature(const uint8_t*, uint8_t*);
// Returns true if all bytes of scratchPad are '\0'
bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9);
// External pullup control
void activateExternalPullup(void);
void deactivateExternalPullup(void);
#if REQUIRESALARMS
// required for alarmSearch
uint8_t alarmSearchAddress[8];
int8_t alarmSearchJunction;
uint8_t alarmSearchExhausted;
// the alarm handler function pointer
AlarmHandler *_AlarmHandler;
#endif
};
#endif

View File

@ -9,7 +9,7 @@
Built by Khoi Hoang https://github.com/khoih-prog/ESP_DoubleResetDetector
Licensed under MIT license
Version: 1.3.1
Version: 1.2.1
Version Modified By Date Comments
------- ----------- ---------- -----------
@ -22,8 +22,6 @@
1.1.2 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
1.2.0 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
1.2.1 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
1.3.0 K Hoang 10/02/2022 Add support to new ESP32-S3
1.3.1 K Hoang 04/03/2022 Add waitingForDRD() function to signal in DRD wating period
*****************************************************************************************************************************/
#pragma once
@ -31,26 +29,13 @@
#ifndef ESP_DoubleResetDetector_H
#define ESP_DoubleResetDetector_H
#ifndef DOUBLERESETDETECTOR_DEBUG
#define DOUBLERESETDETECTOR_DEBUG false
#endif
#if defined(ARDUINO) && (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#ifndef ESP_DOUBLE_RESET_DETECTOR_VERSION
#define ESP_DOUBLE_RESET_DETECTOR_VERSION "ESP_DoubleResetDetector v1.3.1"
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_MAJOR 1
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_MINOR 3
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_PATCH 1
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_INT 1003001
#endif
#define ESP_DOUBLE_RESET_DETECTOR_VERSION "ESP_DoubleResetDetector v1.2.1"
#define ESP_DOUBLERESETDETECTOR_VERSION ESP_DOUBLE_RESET_DETECTOR_VERSION
//#define ESP_DRD_USE_EEPROM false
@ -60,11 +45,7 @@
#ifdef ESP32
#if (!ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
#if (DOUBLERESETDETECTOR_DEBUG)
#warning Neither EEPROM, SPIFFS nor LittleFS selected. Default to EEPROM
#endif
#warning Neither EEPROM, SPIFFS nor LittleFS selected. Default to EEPROM
#ifdef ESP_DRD_USE_EEPROM
#undef ESP_DRD_USE_EEPROM
#define ESP_DRD_USE_EEPROM true
@ -74,10 +55,7 @@
#ifdef ESP8266
#if (!ESP8266_DRD_USE_RTC && !ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
#if (DOUBLERESETDETECTOR_DEBUG)
#warning Neither RTC, EEPROM, LITTLEFS nor SPIFFS selected. Default to EEPROM
#endif
#warning Neither RTC, EEPROM, LITTLEFS nor SPIFFS selected. Default to EEPROM
#ifdef ESP_DRD_USE_EEPROM
#undef ESP_DRD_USE_EEPROM
#define ESP_DRD_USE_EEPROM true
@ -109,20 +87,14 @@
// Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h
//#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2)
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) )
#if (DOUBLERESETDETECTOR_DEBUG)
#warning Using ESP32 Core 1.0.6 or 2.0.0+
#endif
#warning Using ESP32 Core 1.0.6 or 2.0.0+
// The library has been merged into esp32 core from release 1.0.6
#include <LittleFS.h>
#define FileFS LittleFS
#define FS_Name "LittleFS"
#else
#if (DOUBLERESETDETECTOR_DEBUG)
#warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library
#endif
#warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library
// The library has been merged into esp32 core from release 1.0.6
#include <LITTLEFS.h> // https://github.com/lorol/LITTLEFS
@ -153,7 +125,9 @@
#endif //#if ESP_DRD_USE_EEPROM
#ifndef DOUBLERESETDETECTOR_DEBUG
#define DOUBLERESETDETECTOR_DEBUG false
#endif
#define DOUBLERESETDETECTOR_FLAG_SET 0xD0D01234
#define DOUBLERESETDETECTOR_FLAG_CLEAR 0xD0D04321
@ -220,11 +194,6 @@ class DoubleResetDetector
return doubleResetDetected;
};
bool waitingForDRD()
{
return waitingForDoubleReset;
}
void loop()
{

View File

@ -16,7 +16,7 @@
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
Licensed under MIT license
Version: 1.10.1
Version: 1.8.0
Version Modified By Date Comments
------- ----------- ---------- -----------
@ -56,10 +56,7 @@
1.7.6 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
1.7.7 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
*****************************************************************************************************************************/
#pragma once

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
Licensed under MIT license
Version: 1.10.1
Version: 1.8.0
Version Modified By Date Comments
------- ----------- ---------- -----------
@ -56,10 +56,7 @@
1.7.6 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
1.7.7 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
*****************************************************************************************************************************/
#pragma once

View File

@ -71,6 +71,8 @@
////////////////////////////////////////////////////
#if 1
#define TZ_Africa_Abidjan ("GMT0")
#define TZ_Africa_Accra ("GMT0")
#define TZ_Africa_Addis_Ababa ("EAT-3")
@ -532,11 +534,479 @@
#define TZ_Etc_Universal ("UTC0")
#define TZ_Etc_Zulu ("UTC0")
#else
#define TZ_Africa_Abidjan PSTR("GMT0")
#define TZ_Africa_Accra PSTR("GMT0")
#define TZ_Africa_Addis_Ababa PSTR("EAT-3")
#define TZ_Africa_Algiers PSTR("CET-1")
#define TZ_Africa_Asmara PSTR("EAT-3")
#define TZ_Africa_Bamako PSTR("GMT0")
#define TZ_Africa_Bangui PSTR("WAT-1")
#define TZ_Africa_Banjul PSTR("GMT0")
#define TZ_Africa_Bissau PSTR("GMT0")
#define TZ_Africa_Blantyre PSTR("CAT-2")
#define TZ_Africa_Brazzaville PSTR("WAT-1")
#define TZ_Africa_Bujumbura PSTR("CAT-2")
#define TZ_Africa_Cairo PSTR("EET-2")
#define TZ_Africa_Casablanca PSTR("<+01>-1")
#define TZ_Africa_Ceuta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Africa_Conakry PSTR("GMT0")
#define TZ_Africa_Dakar PSTR("GMT0")
#define TZ_Africa_Dar_es_Salaam PSTR("EAT-3")
#define TZ_Africa_Djibouti PSTR("EAT-3")
#define TZ_Africa_Douala PSTR("WAT-1")
#define TZ_Africa_El_Aaiun PSTR("<+01>-1")
#define TZ_Africa_Freetown PSTR("GMT0")
#define TZ_Africa_Gaborone PSTR("CAT-2")
#define TZ_Africa_Harare PSTR("CAT-2")
#define TZ_Africa_Johannesburg PSTR("SAST-2")
#define TZ_Africa_Juba PSTR("EAT-3")
#define TZ_Africa_Kampala PSTR("EAT-3")
#define TZ_Africa_Khartoum PSTR("CAT-2")
#define TZ_Africa_Kigali PSTR("CAT-2")
#define TZ_Africa_Kinshasa PSTR("WAT-1")
#define TZ_Africa_Lagos PSTR("WAT-1")
#define TZ_Africa_Libreville PSTR("WAT-1")
#define TZ_Africa_Lome PSTR("GMT0")
#define TZ_Africa_Luanda PSTR("WAT-1")
#define TZ_Africa_Lubumbashi PSTR("CAT-2")
#define TZ_Africa_Lusaka PSTR("CAT-2")
#define TZ_Africa_Malabo PSTR("WAT-1")
#define TZ_Africa_Maputo PSTR("CAT-2")
#define TZ_Africa_Maseru PSTR("SAST-2")
#define TZ_Africa_Mbabane PSTR("SAST-2")
#define TZ_Africa_Mogadishu PSTR("EAT-3")
#define TZ_Africa_Monrovia PSTR("GMT0")
#define TZ_Africa_Nairobi PSTR("EAT-3")
#define TZ_Africa_Ndjamena PSTR("WAT-1")
#define TZ_Africa_Niamey PSTR("WAT-1")
#define TZ_Africa_Nouakchott PSTR("GMT0")
#define TZ_Africa_Ouagadougou PSTR("GMT0")
#define TZ_Africa_PortomNovo PSTR("WAT-1")
#define TZ_Africa_Sao_Tome PSTR("GMT0")
#define TZ_Africa_Tripoli PSTR("EET-2")
#define TZ_Africa_Tunis PSTR("CET-1")
#define TZ_Africa_Windhoek PSTR("CAT-2")
#define TZ_America_Adak PSTR("HST10HDT,M3.2.0,M11.1.0")
#define TZ_America_Anchorage PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Anguilla PSTR("AST4")
#define TZ_America_Antigua PSTR("AST4")
#define TZ_America_Araguaina PSTR("<-03>3")
#define TZ_America_Argentina_Buenos_Aires PSTR("<-03>3")
#define TZ_America_Argentina_Catamarca PSTR("<-03>3")
#define TZ_America_Argentina_Cordoba PSTR("<-03>3")
#define TZ_America_Argentina_Jujuy PSTR("<-03>3")
#define TZ_America_Argentina_La_Rioja PSTR("<-03>3")
#define TZ_America_Argentina_Mendoza PSTR("<-03>3")
#define TZ_America_Argentina_Rio_Gallegos PSTR("<-03>3")
#define TZ_America_Argentina_Salta PSTR("<-03>3")
#define TZ_America_Argentina_San_Juan PSTR("<-03>3")
#define TZ_America_Argentina_San_Luis PSTR("<-03>3")
#define TZ_America_Argentina_Tucuman PSTR("<-03>3")
#define TZ_America_Argentina_Ushuaia PSTR("<-03>3")
#define TZ_America_Aruba PSTR("AST4")
#define TZ_America_Asuncion PSTR("<-04>4<-03>,M10.1.0/0,M3.4.0/0")
#define TZ_America_Atikokan PSTR("EST5")
#define TZ_America_Bahia PSTR("<-03>3")
#define TZ_America_Bahia_Banderas PSTR("CST6CDT,M4.1.0,M10.5.0")
#define TZ_America_Barbados PSTR("AST4")
#define TZ_America_Belem PSTR("<-03>3")
#define TZ_America_Belize PSTR("CST6")
#define TZ_America_BlancmSablon PSTR("AST4")
#define TZ_America_Boa_Vista PSTR("<-04>4")
#define TZ_America_Bogota PSTR("<-05>5")
#define TZ_America_Boise PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Cambridge_Bay PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Campo_Grande PSTR("<-04>4")
#define TZ_America_Cancun PSTR("EST5")
#define TZ_America_Caracas PSTR("<-04>4")
#define TZ_America_Cayenne PSTR("<-03>3")
#define TZ_America_Cayman PSTR("EST5")
#define TZ_America_Chicago PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Chihuahua PSTR("MST7MDT,M4.1.0,M10.5.0")
#define TZ_America_Costa_Rica PSTR("CST6")
#define TZ_America_Creston PSTR("MST7")
#define TZ_America_Cuiaba PSTR("<-04>4")
#define TZ_America_Curacao PSTR("AST4")
#define TZ_America_Danmarkshavn PSTR("GMT0")
#define TZ_America_Dawson PSTR("MST7")
#define TZ_America_Dawson_Creek PSTR("MST7")
#define TZ_America_Denver PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Detroit PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Dominica PSTR("AST4")
#define TZ_America_Edmonton PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Eirunepe PSTR("<-05>5")
#define TZ_America_El_Salvador PSTR("CST6")
#define TZ_America_Fortaleza PSTR("<-03>3")
#define TZ_America_Fort_Nelson PSTR("MST7")
#define TZ_America_Glace_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_America_Godthab PSTR("<-03>3<-02>,M3.5.0/-2,M10.5.0/-1")
#define TZ_America_Goose_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_America_Grand_Turk PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Grenada PSTR("AST4")
#define TZ_America_Guadeloupe PSTR("AST4")
#define TZ_America_Guatemala PSTR("CST6")
#define TZ_America_Guayaquil PSTR("<-05>5")
#define TZ_America_Guyana PSTR("<-04>4")
#define TZ_America_Halifax PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_America_Havana PSTR("CST5CDT,M3.2.0/0,M11.1.0/1")
#define TZ_America_Hermosillo PSTR("MST7")
#define TZ_America_Indiana_Indianapolis PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Knox PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Marengo PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Petersburg PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Tell_City PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Vevay PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Vincennes PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Indiana_Winamac PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Inuvik PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Iqaluit PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Jamaica PSTR("EST5")
#define TZ_America_Juneau PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Kentucky_Louisville PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Kentucky_Monticello PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Kralendijk PSTR("AST4")
#define TZ_America_La_Paz PSTR("<-04>4")
#define TZ_America_Lima PSTR("<-05>5")
#define TZ_America_Los_Angeles PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Lower_Princes PSTR("AST4")
#define TZ_America_Maceio PSTR("<-03>3")
#define TZ_America_Managua PSTR("CST6")
#define TZ_America_Manaus PSTR("<-04>4")
#define TZ_America_Marigot PSTR("AST4")
#define TZ_America_Martinique PSTR("AST4")
#define TZ_America_Matamoros PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Mazatlan PSTR("MST7MDT,M4.1.0,M10.5.0")
#define TZ_America_Menominee PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Merida PSTR("CST6CDT,M4.1.0,M10.5.0")
#define TZ_America_Metlakatla PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Mexico_City PSTR("CST6CDT,M4.1.0,M10.5.0")
#define TZ_America_Miquelon PSTR("<-03>3<-02>,M3.2.0,M11.1.0")
#define TZ_America_Moncton PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_America_Monterrey PSTR("CST6CDT,M4.1.0,M10.5.0")
#define TZ_America_Montevideo PSTR("<-03>3")
#define TZ_America_Montreal PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Montserrat PSTR("AST4")
#define TZ_America_Nassau PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_New_York PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Nipigon PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Nome PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Noronha PSTR("<-02>2")
#define TZ_America_North_Dakota_Beulah PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_North_Dakota_Center PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_North_Dakota_New_Salem PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Ojinaga PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_America_Panama PSTR("EST5")
#define TZ_America_Pangnirtung PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Paramaribo PSTR("<-03>3")
#define TZ_America_Phoenix PSTR("MST7")
#define TZ_America_PortmaumPrince PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Port_of_Spain PSTR("AST4")
#define TZ_America_Porto_Velho PSTR("<-04>4")
#define TZ_America_Puerto_Rico PSTR("AST4")
#define TZ_America_Punta_Arenas PSTR("<-03>3")
#define TZ_America_Rainy_River PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Rankin_Inlet PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Recife PSTR("<-03>3")
#define TZ_America_Regina PSTR("CST6")
#define TZ_America_Resolute PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Rio_Branco PSTR("<-05>5")
#define TZ_America_Santarem PSTR("<-03>3")
#define TZ_America_Santiago PSTR("<-04>4<-03>,M9.1.6/24,M4.1.6/24")
#define TZ_America_Santo_Domingo PSTR("AST4")
#define TZ_America_Sao_Paulo PSTR("<-03>3")
#define TZ_America_Scoresbysund PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
#define TZ_America_Sitka PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_St_Barthelemy PSTR("AST4")
#define TZ_America_St_Johns PSTR("NST3:30NDT,M3.2.0,M11.1.0")
#define TZ_America_St_Kitts PSTR("AST4")
#define TZ_America_St_Lucia PSTR("AST4")
#define TZ_America_St_Thomas PSTR("AST4")
#define TZ_America_St_Vincent PSTR("AST4")
#define TZ_America_Swift_Current PSTR("CST6")
#define TZ_America_Tegucigalpa PSTR("CST6")
#define TZ_America_Thule PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_America_Thunder_Bay PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Tijuana PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Toronto PSTR("EST5EDT,M3.2.0,M11.1.0")
#define TZ_America_Tortola PSTR("AST4")
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
#define TZ_America_Whitehorse PSTR("MST7")
#define TZ_America_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
#define TZ_America_Yakutat PSTR("AKST9AKDT,M3.2.0,M11.1.0")
#define TZ_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
#define TZ_Antarctica_Casey PSTR("<+11>-11")
#define TZ_Antarctica_Davis PSTR("<+07>-7")
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
#define TZ_Antarctica_Palmer PSTR("<-03>3")
#define TZ_Antarctica_Rothera PSTR("<-03>3")
#define TZ_Antarctica_Syowa PSTR("<+03>-3")
#define TZ_Antarctica_Troll PSTR("<+00>0<+02>-2,M3.5.0/1,M10.5.0/3")
#define TZ_Antarctica_Vostok PSTR("<+06>-6")
#define TZ_Arctic_Longyearbyen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Asia_Aden PSTR("<+03>-3")
#define TZ_Asia_Almaty PSTR("<+06>-6")
#define TZ_Asia_Amman PSTR("EET-2EEST,M3.5.4/24,M10.5.5/1")
#define TZ_Asia_Anadyr PSTR("<+12>-12")
#define TZ_Asia_Aqtau PSTR("<+05>-5")
#define TZ_Asia_Aqtobe PSTR("<+05>-5")
#define TZ_Asia_Ashgabat PSTR("<+05>-5")
#define TZ_Asia_Atyrau PSTR("<+05>-5")
#define TZ_Asia_Baghdad PSTR("<+03>-3")
#define TZ_Asia_Bahrain PSTR("<+03>-3")
#define TZ_Asia_Baku PSTR("<+04>-4")
#define TZ_Asia_Bangkok PSTR("<+07>-7")
#define TZ_Asia_Barnaul PSTR("<+07>-7")
#define TZ_Asia_Beirut PSTR("EET-2EEST,M3.5.0/0,M10.5.0/0")
#define TZ_Asia_Bishkek PSTR("<+06>-6")
#define TZ_Asia_Brunei PSTR("<+08>-8")
#define TZ_Asia_Chita PSTR("<+09>-9")
#define TZ_Asia_Choibalsan PSTR("<+08>-8")
#define TZ_Asia_Colombo PSTR("<+0530>-5:30")
#define TZ_Asia_Damascus PSTR("EET-2EEST,M3.5.5/0,M10.5.5/0")
#define TZ_Asia_Dhaka PSTR("<+06>-6")
#define TZ_Asia_Dili PSTR("<+09>-9")
#define TZ_Asia_Dubai PSTR("<+04>-4")
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
#define TZ_Asia_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
#define TZ_Asia_Ho_Chi_Minh PSTR("<+07>-7")
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
#define TZ_Asia_Hovd PSTR("<+07>-7")
#define TZ_Asia_Irkutsk PSTR("<+08>-8")
#define TZ_Asia_Jakarta PSTR("WIB-7")
#define TZ_Asia_Jayapura PSTR("WIT-9")
#define TZ_Asia_Jerusalem PSTR("IST-2IDT,M3.4.4/26,M10.5.0")
#define TZ_Asia_Kabul PSTR("<+0430>-4:30")
#define TZ_Asia_Kamchatka PSTR("<+12>-12")
#define TZ_Asia_Karachi PSTR("PKT-5")
#define TZ_Asia_Kathmandu PSTR("<+0545>-5:45")
#define TZ_Asia_Khandyga PSTR("<+09>-9")
#define TZ_Asia_Kolkata PSTR("IST-5:30")
#define TZ_Asia_Krasnoyarsk PSTR("<+07>-7")
#define TZ_Asia_Kuala_Lumpur PSTR("<+08>-8")
#define TZ_Asia_Kuching PSTR("<+08>-8")
#define TZ_Asia_Kuwait PSTR("<+03>-3")
#define TZ_Asia_Macau PSTR("CST-8")
#define TZ_Asia_Magadan PSTR("<+11>-11")
#define TZ_Asia_Makassar PSTR("WITA-8")
#define TZ_Asia_Manila PSTR("PST-8")
#define TZ_Asia_Muscat PSTR("<+04>-4")
#define TZ_Asia_Nicosia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Asia_Novokuznetsk PSTR("<+07>-7")
#define TZ_Asia_Novosibirsk PSTR("<+07>-7")
#define TZ_Asia_Omsk PSTR("<+06>-6")
#define TZ_Asia_Oral PSTR("<+05>-5")
#define TZ_Asia_Phnom_Penh PSTR("<+07>-7")
#define TZ_Asia_Pontianak PSTR("WIB-7")
#define TZ_Asia_Pyongyang PSTR("KST-9")
#define TZ_Asia_Qatar PSTR("<+03>-3")
#define TZ_Asia_Qyzylorda PSTR("<+05>-5")
#define TZ_Asia_Riyadh PSTR("<+03>-3")
#define TZ_Asia_Sakhalin PSTR("<+11>-11")
#define TZ_Asia_Samarkand PSTR("<+05>-5")
#define TZ_Asia_Seoul PSTR("KST-9")
#define TZ_Asia_Shanghai PSTR("CST-8")
#define TZ_Asia_Singapore PSTR("<+08>-8")
#define TZ_Asia_Srednekolymsk PSTR("<+11>-11")
#define TZ_Asia_Taipei PSTR("CST-8")
#define TZ_Asia_Tashkent PSTR("<+05>-5")
#define TZ_Asia_Tbilisi PSTR("<+04>-4")
#define TZ_Asia_Tehran PSTR("<+0330>-3:30<+0430>,J79/24,J263/24")
#define TZ_Asia_Thimphu PSTR("<+06>-6")
#define TZ_Asia_Tokyo PSTR("JST-9")
#define TZ_Asia_Tomsk PSTR("<+07>-7")
#define TZ_Asia_Ulaanbaatar PSTR("<+08>-8")
#define TZ_Asia_Urumqi PSTR("<+06>-6")
#define TZ_Asia_UstmNera PSTR("<+10>-10")
#define TZ_Asia_Vientiane PSTR("<+07>-7")
#define TZ_Asia_Vladivostok PSTR("<+10>-10")
#define TZ_Asia_Yakutsk PSTR("<+09>-9")
#define TZ_Asia_Yangon PSTR("<+0630>-6:30")
#define TZ_Asia_Yekaterinburg PSTR("<+05>-5")
#define TZ_Asia_Yerevan PSTR("<+04>-4")
#define TZ_Atlantic_Azores PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
#define TZ_Atlantic_Bermuda PSTR("AST4ADT,M3.2.0,M11.1.0")
#define TZ_Atlantic_Canary PSTR("WET0WEST,M3.5.0/1,M10.5.0")
#define TZ_Atlantic_Cape_Verde PSTR("<-01>1")
#define TZ_Atlantic_Faroe PSTR("WET0WEST,M3.5.0/1,M10.5.0")
#define TZ_Atlantic_Madeira PSTR("WET0WEST,M3.5.0/1,M10.5.0")
#define TZ_Atlantic_Reykjavik PSTR("GMT0")
#define TZ_Atlantic_South_Georgia PSTR("<-02>2")
#define TZ_Atlantic_Stanley PSTR("<-03>3")
#define TZ_Atlantic_St_Helena PSTR("GMT0")
#define TZ_Australia_Adelaide PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
#define TZ_Australia_Brisbane PSTR("AEST-10")
#define TZ_Australia_Broken_Hill PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
#define TZ_Australia_Currie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Australia_Darwin PSTR("ACST-9:30")
#define TZ_Australia_Eucla PSTR("<+0845>-8:45")
#define TZ_Australia_Hobart PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Australia_Lindeman PSTR("AEST-10")
#define TZ_Australia_Lord_Howe PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0")
#define TZ_Australia_Melbourne PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Australia_Perth PSTR("AWST-8")
#define TZ_Australia_Sydney PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
#define TZ_Europe_Amsterdam PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Andorra PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Astrakhan PSTR("<+04>-4")
#define TZ_Europe_Athens PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Belgrade PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Berlin PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Bratislava PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Brussels PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Bucharest PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Budapest PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Busingen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Chisinau PSTR("EET-2EEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Copenhagen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Dublin PSTR("IST-1GMT0,M10.5.0,M3.5.0/1")
#define TZ_Europe_Gibraltar PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Guernsey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
#define TZ_Europe_Helsinki PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Isle_of_Man PSTR("GMT0BST,M3.5.0/1,M10.5.0")
#define TZ_Europe_Istanbul PSTR("<+03>-3")
#define TZ_Europe_Jersey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
#define TZ_Europe_Kaliningrad PSTR("EET-2")
#define TZ_Europe_Kiev PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Kirov PSTR("<+03>-3")
#define TZ_Europe_Lisbon PSTR("WET0WEST,M3.5.0/1,M10.5.0")
#define TZ_Europe_Ljubljana PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_London PSTR("GMT0BST,M3.5.0/1,M10.5.0")
#define TZ_Europe_Luxembourg PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Madrid PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Malta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Mariehamn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Minsk PSTR("<+03>-3")
#define TZ_Europe_Monaco PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Moscow PSTR("MSK-3")
#define TZ_Europe_Oslo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Paris PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Podgorica PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Prague PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Riga PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Rome PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Samara PSTR("<+04>-4")
#define TZ_Europe_San_Marino PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Sarajevo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Saratov PSTR("<+04>-4")
#define TZ_Europe_Simferopol PSTR("MSK-3")
#define TZ_Europe_Skopje PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Sofia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Stockholm PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Tallinn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Tirane PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Ulyanovsk PSTR("<+04>-4")
#define TZ_Europe_Uzhgorod PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Vaduz PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Vatican PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Vienna PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Vilnius PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Volgograd PSTR("<+04>-4")
#define TZ_Europe_Warsaw PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Zagreb PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Europe_Zaporozhye PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
#define TZ_Europe_Zurich PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
#define TZ_Indian_Antananarivo PSTR("EAT-3")
#define TZ_Indian_Chagos PSTR("<+06>-6")
#define TZ_Indian_Christmas PSTR("<+07>-7")
#define TZ_Indian_Cocos PSTR("<+0630>-6:30")
#define TZ_Indian_Comoro PSTR("EAT-3")
#define TZ_Indian_Kerguelen PSTR("<+05>-5")
#define TZ_Indian_Mahe PSTR("<+04>-4")
#define TZ_Indian_Maldives PSTR("<+05>-5")
#define TZ_Indian_Mauritius PSTR("<+04>-4")
#define TZ_Indian_Mayotte PSTR("EAT-3")
#define TZ_Indian_Reunion PSTR("<+04>-4")
#define TZ_Pacific_Apia PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4")
#define TZ_Pacific_Auckland PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
#define TZ_Pacific_Bougainville PSTR("<+11>-11")
#define TZ_Pacific_Chatham PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45")
#define TZ_Pacific_Chuuk PSTR("<+10>-10")
#define TZ_Pacific_Easter PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22")
#define TZ_Pacific_Efate PSTR("<+11>-11")
#define TZ_Pacific_Enderbury PSTR("<+13>-13")
#define TZ_Pacific_Fakaofo PSTR("<+13>-13")
#define TZ_Pacific_Fiji PSTR("<+12>-12<+13>,M11.2.0,M1.2.3/99")
#define TZ_Pacific_Funafuti PSTR("<+12>-12")
#define TZ_Pacific_Galapagos PSTR("<-06>6")
#define TZ_Pacific_Gambier PSTR("<-09>9")
#define TZ_Pacific_Guadalcanal PSTR("<+11>-11")
#define TZ_Pacific_Guam PSTR("ChST-10")
#define TZ_Pacific_Honolulu PSTR("HST10")
#define TZ_Pacific_Kiritimati PSTR("<+14>-14")
#define TZ_Pacific_Kosrae PSTR("<+11>-11")
#define TZ_Pacific_Kwajalein PSTR("<+12>-12")
#define TZ_Pacific_Majuro PSTR("<+12>-12")
#define TZ_Pacific_Marquesas PSTR("<-0930>9:30")
#define TZ_Pacific_Midway PSTR("SST11")
#define TZ_Pacific_Nauru PSTR("<+12>-12")
#define TZ_Pacific_Niue PSTR("<-11>11")
#define TZ_Pacific_Norfolk PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3")
#define TZ_Pacific_Noumea PSTR("<+11>-11")
#define TZ_Pacific_Pago_Pago PSTR("SST11")
#define TZ_Pacific_Palau PSTR("<+09>-9")
#define TZ_Pacific_Pitcairn PSTR("<-08>8")
#define TZ_Pacific_Pohnpei PSTR("<+11>-11")
#define TZ_Pacific_Port_Moresby PSTR("<+10>-10")
#define TZ_Pacific_Rarotonga PSTR("<-10>10")
#define TZ_Pacific_Saipan PSTR("ChST-10")
#define TZ_Pacific_Tahiti PSTR("<-10>10")
#define TZ_Pacific_Tarawa PSTR("<+12>-12")
#define TZ_Pacific_Tongatapu PSTR("<+13>-13")
#define TZ_Pacific_Wake PSTR("<+12>-12")
#define TZ_Pacific_Wallis PSTR("<+12>-12")
#define TZ_Etc_GMT PSTR("GMT0")
#define TZ_Etc_GMTm0 PSTR("GMT0")
#define TZ_Etc_GMTm1 PSTR("<+01>-1")
#define TZ_Etc_GMTm2 PSTR("<+02>-2")
#define TZ_Etc_GMTm3 PSTR("<+03>-3")
#define TZ_Etc_GMTm4 PSTR("<+04>-4")
#define TZ_Etc_GMTm5 PSTR("<+05>-5")
#define TZ_Etc_GMTm6 PSTR("<+06>-6")
#define TZ_Etc_GMTm7 PSTR("<+07>-7")
#define TZ_Etc_GMTm8 PSTR("<+08>-8")
#define TZ_Etc_GMTm9 PSTR("<+09>-9")
#define TZ_Etc_GMTm10 PSTR("<+10>-10")
#define TZ_Etc_GMTm11 PSTR("<+11>-11")
#define TZ_Etc_GMTm12 PSTR("<+12>-12")
#define TZ_Etc_GMTm13 PSTR("<+13>-13")
#define TZ_Etc_GMTm14 PSTR("<+14>-14")
#define TZ_Etc_GMT0 PSTR("GMT0")
#define TZ_Etc_GMTp0 PSTR("GMT0")
#define TZ_Etc_GMTp1 PSTR("<-01>1")
#define TZ_Etc_GMTp2 PSTR("<-02>2")
#define TZ_Etc_GMTp3 PSTR("<-03>3")
#define TZ_Etc_GMTp4 PSTR("<-04>4")
#define TZ_Etc_GMTp5 PSTR("<-05>5")
#define TZ_Etc_GMTp6 PSTR("<-06>6")
#define TZ_Etc_GMTp7 PSTR("<-07>7")
#define TZ_Etc_GMTp8 PSTR("<-08>8")
#define TZ_Etc_GMTp9 PSTR("<-09>9")
#define TZ_Etc_GMTp10 PSTR("<-10>10")
#define TZ_Etc_GMTp11 PSTR("<-11>11")
#define TZ_Etc_GMTp12 PSTR("<-12>12")
#define TZ_Etc_UCT PSTR("UTC0")
#define TZ_Etc_UTC PSTR("UTC0")
#define TZ_Etc_Greenwich PSTR("GMT0")
#define TZ_Etc_Universal PSTR("UTC0")
#define TZ_Etc_Zulu PSTR("UTC0")
#endif
////////////////////////////////////////////////////////////
#define TIMEZONE_MAX_LEN 50
static const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
#if 1
const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
{
#if USING_AFRICA
"Africa/Abidjan", //PSTR("GMT0")
@ -1033,7 +1503,7 @@ static const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
////////////////////////////////////////////////////////////
static const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
{
#if USING_AFRICA
TZ_Africa_Abidjan, //PSTR("GMT0")
@ -1529,4 +1999,152 @@ static const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
#endif
};
#else
const char TZ_NAME[][TIMEZONE_MAX_LEN] =
{
"Pacific/Pago_Pago",
"America/Adak",
"Pacific/Honolulu",
"Pacific/Marquesas",
"Pacific/Gambier",
"America/Anchorage",
"America/Los_Angeles",
"Pacific/Pitcairn",
"America/Phoenix",
"America/Denver",
"America/Guatemala",
"America/Chicago",
"Pacific/Easter",
"America/Bogota",
"America/New_York",
"America/Caracas",
"America/Halifax",
"America/Santo_Domingo",
"America/Santiago",
"America/St_Johns",
"America/Godthab",
"America/Argentina/Buenos_Aires",
"America/Montevideo",
"Etc/GMT+2",
"Atlantic/Azores",
"Atlantic/Cape_Verde",
"Etc/UTC",
"Europe/London",
"Europe/Berlin",
"Africa/Lagos",
"Africa/Windhoek",
"Asia/Beirut",
"Africa/Johannesburg",
"Asia/Baghdad",
"Europe/Moscow",
"Asia/Tehran",
"Asia/Dubai",
"Asia/Baku",
"Asia/Kabul",
"Asia/Yekaterinburg",
"Asia/Karachi",
"Asia/Kolkata",
"Asia/Kathmandu",
"Asia/Dhaka",
"Asia/Omsk",
"Asia/Krasnoyarsk",
"Asia/Jakarta",
"Asia/Shanghai",
"Asia/Irkutsk",
"Australia/Eucla",
"Asia/Yakutsk",
"Asia/Tokyo",
"Australia/Darwin",
"Australia/Adelaide",
"Australia/Brisbane",
"Asia/Vladivostok",
"Australia/Sydney",
"Australia/Lord_Howe",
"Asia/Kamchatka",
"Pacific/Noumea",
"Pacific/Norfolk",
"Pacific/Auckland",
"Pacific/Tarawa",
"Pacific/Chatham",
"Pacific/Tongatapu",
"Pacific/Apia",
"Pacific/Kiritimati",
};
////////////////////////////////////////////////////////////
const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] =
{
TZ_Pacific_Pago_Pago, //PSTR("SST11")
TZ_America_Adak, //PSTR("HST10HDT,M3.2.0,M11.1.0")
TZ_Pacific_Honolulu, //PSTR("HST10")
TZ_Pacific_Marquesas, //PSTR("<-0930>9:30")
TZ_Pacific_Gambier, //PSTR("<-09>9")
TZ_America_Anchorage, //PSTR("AKST9AKDT,M3.2.0,M11.1.0")
TZ_America_Los_Angeles, //"PST8PDT,M3.2.0,M11.1.0", //"America/Los_Angeles",
TZ_Pacific_Pitcairn, //PSTR("<-08>8")
TZ_America_Phoenix, //PSTR("MST7")
TZ_America_Denver, //"MST7MDT,M3.2.0,M11.1.0", //"America/Denver",
TZ_America_Guatemala, //PSTR("CST6")
TZ_America_Chicago, //"CST6CDT,M3.2.0,M11.1.0", //"America/Chicago",
TZ_Pacific_Easter, //PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22")
TZ_America_Bogota, //PSTR("<-05>5")
TZ_America_New_York, //"EST5EDT,M3.2.0,M11.1.0", //"America/New_York",
TZ_America_Caracas, //PSTR("<-04>4")
TZ_America_Halifax, //"AST4ADT,M3.2.0,M11.1.0", //"America/Halifax",
TZ_America_Santo_Domingo, //PSTR("AST4")
TZ_America_Santiago, //"<-04>4<-03>,M9.1.6/24,M4.1.6/24", //"America/Santiago",
TZ_America_St_Johns, //PSTR("NST3:30NDT,M3.2.0,M11.1.0")
TZ_America_Godthab, //"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1", //"America/Godthab",
TZ_America_Argentina_Buenos_Aires, //PSTR("<-03>3")
TZ_America_Montevideo, //"<-03>3", //"America/Montevideo",
TZ_Etc_GMTp2, //PSTR("<-02>2")
TZ_Atlantic_Azores, //PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
TZ_Atlantic_Cape_Verde, //PSTR("<-01>1")
TZ_Etc_UTC, //PSTR("UTC0")
TZ_Europe_London, //PSTR("GMT0BST,M3.5.0/1,M10.5.0")
TZ_Europe_Berlin, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
TZ_Africa_Lagos, //PSTR("WAT-1")
TZ_Africa_Windhoek, //PSTR("CAT-2")
TZ_Asia_Beirut, //"EET-2EEST,M3.5.0/0,M10.5.0/0" //"Asia/Beirut",
TZ_Africa_Johannesburg, //"SAST-2", //"Africa/Johannesburg",
TZ_Asia_Baghdad, //"<+03>-3", //"Asia/Baghdad",
TZ_Europe_Moscow, //PSTR("MSK-3")
TZ_Asia_Tehran, //PSTR("<+0330>-3:30<+0430>,J79/24,J263/24")
TZ_Asia_Dubai, //"<+04>-4", //"Asia/Dubai",
TZ_Asia_Baku, //PSTR("<+04>-4")
TZ_Asia_Kabul, //PSTR("<+0430>-4:30")
TZ_Asia_Yekaterinburg, //PSTR("<+05>-5")
TZ_Asia_Karachi, //PSTR("PKT-5")
TZ_Asia_Kolkata, //PSTR("IST-5:30")
TZ_Asia_Kathmandu, //PSTR("<+0545>-5:45")
TZ_Asia_Dhaka, //"<+06>-6", //"Asia/Dhaka",
TZ_Asia_Omsk, //PSTR("<+06>-6")
TZ_Asia_Krasnoyarsk, //PSTR("<+07>-7")
TZ_Asia_Jakarta, //"WIB-7", //"Asia/Jakarta",
TZ_Asia_Shanghai, //"CST-8", //"Asia/Shanghai",
TZ_Asia_Irkutsk, //PSTR("<+08>-8")
TZ_Australia_Eucla, //PSTR("<+0845>-8:45")
TZ_Asia_Yakutsk, //PSTR("<+09>-9")
TZ_Asia_Tokyo, //"JST-9", //"Asia/Tokyo",
TZ_Australia_Darwin, //PSTR("ACST-9:30")
TZ_Australia_Adelaide, //PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
TZ_Australia_Brisbane, //"AEST-10", //"Australia/Brisbane",
TZ_Asia_Vladivostok, //PSTR("<+10>-10")
TZ_Australia_Sydney, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
TZ_Australia_Lord_Howe, //PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0")
TZ_Asia_Kamchatka, //PSTR("<+12>-12")
TZ_Pacific_Noumea, //"<+11>-11", //"Pacific/Noumea",
TZ_Pacific_Norfolk, //PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3")
TZ_Pacific_Auckland, //"NZST-12NZDT,M9.5.0,M4.1.0/3", //"Pacific/Auckland",
TZ_Pacific_Tarawa, //"<+12>-12", //"Pacific/Tarawa",
TZ_Pacific_Chatham, //PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45")
TZ_Pacific_Tongatapu, //PSTR("<+13>-13")
TZ_Pacific_Apia, //PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4")
TZ_Pacific_Kiritimati, //PSTR("<+14>-14")
};
#endif
#endif // TZDB_H

View File

@ -1,588 +0,0 @@
/*
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
The latest version of this library may be found at:
http://www.pjrc.com/teensy/td_libs_OneWire.html
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
January 2010.
DO NOT EMAIL for technical support, especially not for ESP chips!
All project support questions must be posted on public forums
relevant to the board or chips used. If using Arduino, post on
Arduino's forum. If using ESP, post on the ESP community forums.
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
Github's issue tracker for OneWire should be used only to report
specific bugs. DO NOT request project support via Github. All
project and tech support questions must be posted on forums, not
github issues. If you experience a problem and you are not
absolutely sure it's an issue with the library, ask on a forum
first. Only use github to report issues after experts have
confirmed the issue is with OneWire rather than your project.
Back in 2010, OneWire was in need of many bug fixes, but had
been abandoned the original author (Jim Studt). None of the known
contributors were interested in maintaining OneWire. Paul typically
works on OneWire every 6 to 12 months. Patches usually wait that
long. If anyone is interested in more actively maintaining OneWire,
please contact Paul (this is pretty much the only reason to use
private email about OneWire).
OneWire is now very mature code. No changes other than adding
definitions for newer hardware support are anticipated.
Version 2.3:
Unknown chip fallback mode, Roger Clark
Teensy-LC compatibility, Paul Stoffregen
Search bug fix, Love Nystrom
Version 2.2:
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
Fix DS18B20 example negative temperature
Fix DS18B20 example's low res modes, Ken Butcher
Improve reset timing, Mark Tillotson
Add const qualifiers, Bertrik Sikken
Add initial value input to crc16, Bertrik Sikken
Add target_search() function, Scott Roberts
Version 2.1:
Arduino 1.0 compatibility, Paul Stoffregen
Improve temperature example, Paul Stoffregen
DS250x_PROM example, Guillermo Lovato
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
Improvements from Glenn Trewitt:
- crc16() now works
- check_crc16() does all of calculation/checking work.
- Added read_bytes() and write_bytes(), to reduce tedious loops.
- Added ds2408 example.
Delete very old, out-of-date readme file (info is here)
Version 2.0: Modifications by Paul Stoffregen, January 2010:
http://www.pjrc.com/teensy/td_libs_OneWire.html
Search fix from Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Use direct optimized I/O in all cases
Disable interrupts during timing critical sections
(this solves many random communication errors)
Disable interrupts during read-modify-write I/O
Reduce RAM consumption by eliminating unnecessary
variables and trimming many to 8 bits
Optimize both crc8 - table version moved to flash
Modified to work with larger numbers of devices - avoids loop.
Tested in Arduino 11 alpha with 12 sensors.
26 Sept 2008 -- Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Updated to work with arduino-0008 and to include skip() as of
2007/07/06. --RJL20
Modified to calculate the 8-bit CRC directly, avoiding the need for
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
-- Tom Pollard, Jan 23, 2008
Jim Studt's original library was modified by Josh Larios.
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
Much of the code was inspired by Derek Yerger's code, though I don't
think much of that remains. In any event that was..
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
The CRC code was excerpted and inspired by the Dallas Semiconductor
sample code bearing this copyright.
//---------------------------------------------------------------------------
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER 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.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//--------------------------------------------------------------------------
*/
#include <Arduino.h>
#include "OneWire.h"
#include "util/OneWire_direct_gpio.h"
void OneWire::begin(uint8_t pin)
{
pinMode(pin, INPUT);
bitmask = PIN_TO_BITMASK(pin);
baseReg = PIN_TO_BASEREG(pin);
#if ONEWIRE_SEARCH
reset_search();
#endif
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return a 0;
//
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
//
uint8_t OneWire::reset(void)
{
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
uint8_t r;
uint8_t retries = 125;
noInterrupts();
DIRECT_MODE_INPUT(reg, mask);
interrupts();
// wait until the wire is high... just in case
do {
if (--retries == 0) return 0;
delayMicroseconds(2);
} while ( !DIRECT_READ(reg, mask));
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
interrupts();
delayMicroseconds(480);
noInterrupts();
DIRECT_MODE_INPUT(reg, mask); // allow it to float
delayMicroseconds(70);
r = !DIRECT_READ(reg, mask);
interrupts();
delayMicroseconds(410);
return r;
}
//
// Write a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
#if defined(ARDUINO_ARCH_ESP32)
void IRAM_ATTR OneWire::write_bit(uint8_t v)
#else
void OneWire::write_bit(uint8_t v)
#endif
{
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
if (v & 1) {
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
delayMicroseconds(10);
DIRECT_WRITE_HIGH(reg, mask); // drive output high
interrupts();
delayMicroseconds(55);
} else {
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
delayMicroseconds(65);
DIRECT_WRITE_HIGH(reg, mask); // drive output high
interrupts();
delayMicroseconds(5);
}
}
//
// Read a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
#if defined(ARDUINO_ARCH_ESP32)
uint8_t IRAM_ATTR OneWire::read_bit(void)
#else
uint8_t OneWire::read_bit(void)
#endif
{
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
uint8_t r;
noInterrupts();
DIRECT_MODE_OUTPUT(reg, mask);
DIRECT_WRITE_LOW(reg, mask);
delayMicroseconds(3);
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
delayMicroseconds(10);
r = DIRECT_READ(reg, mask);
interrupts();
delayMicroseconds(53);
return r;
}
//
// Write a byte. The writing code uses the active drivers to raise the
// pin high, if you need power after the write (e.g. DS18S20 in
// parasite power mode) then set 'power' to 1, otherwise the pin will
// go tri-state at the end of the write to avoid heating in a short or
// other mishap.
//
void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
uint8_t bitMask;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
OneWire::write_bit( (bitMask & v)?1:0);
}
if ( !power) {
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
DIRECT_WRITE_LOW(baseReg, bitmask);
interrupts();
}
}
void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
for (uint16_t i = 0 ; i < count ; i++)
write(buf[i]);
if (!power) {
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
DIRECT_WRITE_LOW(baseReg, bitmask);
interrupts();
}
}
//
// Read a byte
//
uint8_t OneWire::read() {
uint8_t bitMask;
uint8_t r = 0;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
if ( OneWire::read_bit()) r |= bitMask;
}
return r;
}
void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
for (uint16_t i = 0 ; i < count ; i++)
buf[i] = read();
}
//
// Do a ROM select
//
void OneWire::select(const uint8_t rom[8])
{
uint8_t i;
write(0x55); // Choose ROM
for (i = 0; i < 8; i++) write(rom[i]);
}
//
// Do a ROM skip
//
void OneWire::skip()
{
write(0xCC); // Skip ROM
}
void OneWire::depower()
{
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
interrupts();
}
#if ONEWIRE_SEARCH
//
// You need to use this function to start a search again from the beginning.
// You do not need to do it for the first search, though you could.
//
void OneWire::reset_search()
{
// reset the search state
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
for(int i = 7; ; i--) {
ROM_NO[i] = 0;
if ( i == 0) break;
}
}
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
//
void OneWire::target_search(uint8_t family_code)
{
// set the search state to find SearchFamily type devices
ROM_NO[0] = family_code;
for (uint8_t i = 1; i < 8; i++)
ROM_NO[i] = 0;
LastDiscrepancy = 64;
LastFamilyDiscrepancy = 0;
LastDeviceFlag = false;
}
//
// Perform a search. If this function returns a '1' then it has
// enumerated the next device and you may retrieve the ROM from the
// OneWire::address variable. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then a 0 is returned. If a new device is found then
// its address is copied to newAddr. Use OneWire::reset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return TRUE : device found, ROM number in ROM_NO buffer
// FALSE : device not found, end of search
//
bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
{
uint8_t id_bit_number;
uint8_t last_zero, rom_byte_number;
bool search_result;
uint8_t id_bit, cmp_id_bit;
unsigned char rom_byte_mask, search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = false;
// if the last call was not the last one
if (!LastDeviceFlag) {
// 1-Wire reset
if (!reset()) {
// reset the search
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
return false;
}
// issue the search command
if (search_mode == true) {
write(0xF0); // NORMAL SEARCH
} else {
write(0xEC); // CONDITIONAL SEARCH
}
// loop to do the search
do
{
// read a bit and its complement
id_bit = read_bit();
cmp_id_bit = read_bit();
// check for no devices on 1-wire
if ((id_bit == 1) && (cmp_id_bit == 1)) {
break;
} else {
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit) {
search_direction = id_bit; // bit write value for search
} else {
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < LastDiscrepancy) {
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
} else {
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == LastDiscrepancy);
}
// if 0 was picked then record its position in LastZero
if (search_direction == 0) {
last_zero = id_bit_number;
// check for Last discrepancy in family
if (last_zero < 9)
LastFamilyDiscrepancy = last_zero;
}
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction == 1)
ROM_NO[rom_byte_number] |= rom_byte_mask;
else
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
write_bit(search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0) {
rom_byte_number++;
rom_byte_mask = 1;
}
}
}
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65)) {
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
LastDiscrepancy = last_zero;
// check for last device
if (LastDiscrepancy == 0) {
LastDeviceFlag = true;
}
search_result = true;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !ROM_NO[0]) {
LastDiscrepancy = 0;
LastDeviceFlag = false;
LastFamilyDiscrepancy = 0;
search_result = false;
} else {
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
}
return search_result;
}
#endif
#if ONEWIRE_CRC
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
//
#if ONEWIRE_CRC8_TABLE
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
// Tiny 2x16 entry CRC table created by Arjen Lentz
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
static const uint8_t PROGMEM dscrc2x16_table[] = {
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83,
0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74
};
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
// and the registers. (Use tiny 2x16 entry CRC table)
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
{
uint8_t crc = 0;
while (len--) {
crc = *addr++ ^ crc; // just re-using crc as intermediate
crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^
pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f));
}
return crc;
}
#else
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but a little smaller, than the lookup table.
//
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
{
uint8_t crc = 0;
while (len--) {
#if defined(__AVR__)
crc = _crc_ibutton_update(crc, *addr++);
#else
uint8_t inbyte = *addr++;
for (uint8_t i = 8; i; i--) {
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
inbyte >>= 1;
}
#endif
}
return crc;
}
#endif
#if ONEWIRE_CRC16
bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
{
crc = ~crc16(input, len, crc);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
{
#if defined(__AVR__)
for (uint16_t i = 0 ; i < len ; i++) {
crc = _crc16_update(crc, input[i]);
}
#else
static const uint8_t oddparity[16] =
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
for (uint16_t i = 0 ; i < len ; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
#endif
return crc;
}
#endif
#endif

View File

@ -1,190 +0,0 @@
#ifndef OneWire_h
#define OneWire_h
#ifdef __cplusplus
#include <stdint.h>
#if defined(__AVR__)
#include <util/crc16.h>
#endif
#if ARDUINO >= 100
#include <Arduino.h> // for delayMicroseconds, digitalPinToBitMask, etc
#else
#include "WProgram.h" // for delayMicroseconds
#include "pins_arduino.h" // for digitalPinToBitMask, etc
#endif
// You can exclude certain features from OneWire. In theory, this
// might save some space. In practice, the compiler automatically
// removes unused code (technically, the linker, using -fdata-sections
// and -ffunction-sections when compiling, and Wl,--gc-sections
// when linking), so most of these will not result in any code size
// reduction. Well, unless you try to use the missing features
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
// is the exception, because it selects a fast but large algorithm
// or a small but slow algorithm.
// you can exclude onewire_search by defining that to 0
#ifndef ONEWIRE_SEARCH
#define ONEWIRE_SEARCH 1
#endif
// You can exclude CRC checks altogether by defining this to 0
#ifndef ONEWIRE_CRC
#define ONEWIRE_CRC 1
#endif
// Select the table-lookup method of computing the 8-bit CRC
// by setting this to 1. The lookup table enlarges code size by
// about 250 bytes. It does NOT consume RAM (but did in very
// old versions of OneWire). If you disable this, a slower
// but very compact algorithm is used.
#ifndef ONEWIRE_CRC8_TABLE
#define ONEWIRE_CRC8_TABLE 1
#endif
// You can allow 16-bit CRC checks by defining this to 1
// (Note that ONEWIRE_CRC must also be 1.)
#ifndef ONEWIRE_CRC16
#define ONEWIRE_CRC16 1
#endif
// Board-specific macros for direct GPIO
#include "util/OneWire_direct_regtype.h"
class OneWire
{
private:
IO_REG_TYPE bitmask;
volatile IO_REG_TYPE *baseReg;
#if ONEWIRE_SEARCH
// global search state
unsigned char ROM_NO[8];
uint8_t LastDiscrepancy;
uint8_t LastFamilyDiscrepancy;
bool LastDeviceFlag;
#endif
public:
OneWire() { }
OneWire(uint8_t pin) { begin(pin); }
void begin(uint8_t pin);
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
// with a presence pulse. Returns 0 if there is no device or the
// bus is shorted or otherwise held low for more than 250uS
uint8_t reset(void);
// Issue a 1-Wire rom select command, you do the reset first.
void select(const uint8_t rom[8]);
// Issue a 1-Wire rom skip command, to address all on bus.
void skip(void);
// Write a byte. If 'power' is one then the wire is held high at
// the end for parasitically powered devices. You are responsible
// for eventually depowering it by calling depower() or doing
// another read or write.
void write(uint8_t v, uint8_t power = 0);
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
// Read a byte.
uint8_t read(void);
void read_bytes(uint8_t *buf, uint16_t count);
// Write a bit. The bus is always left powered at the end, see
// note in write() about that.
#if defined (ARDUINO_ARCH_ESP32)
void IRAM_ATTR write_bit(uint8_t v);
#else
void write_bit(uint8_t v);
#endif
// Read a bit.
#if defined (ARDUINO_ARCH_ESP32)
uint8_t IRAM_ATTR read_bit(void);
#else
uint8_t read_bit(void);
#endif
// Stop forcing power onto the bus. You only need to do this if
// you used the 'power' flag to write() or used a write_bit() call
// and aren't about to do another read or write. You would rather
// not leave this powered if you don't have to, just in case
// someone shorts your bus.
void depower(void);
#if ONEWIRE_SEARCH
// Clear the search state so that if will start from the beginning again.
void reset_search();
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
void target_search(uint8_t family_code);
// Look for the next device. Returns 1 if a new address has been
// returned. A zero might mean that the bus is shorted, there are
// no devices, or you have already retrieved all of them. It
// might be a good idea to check the CRC to make sure you didn't
// get garbage. The order is deterministic. You will always get
// the same devices in the same order.
bool search(uint8_t *newAddr, bool search_mode = true);
#endif
#if ONEWIRE_CRC
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
// ROM and scratchpad registers.
static uint8_t crc8(const uint8_t *addr, uint8_t len);
#if ONEWIRE_CRC16
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return True, iff the CRC matches.
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
#endif
#endif
};
// Prevent this name from leaking into Arduino sketches
#ifdef IO_REG_TYPE
#undef IO_REG_TYPE
#endif
#endif // __cplusplus
#endif // OneWire_h

View File

@ -1,463 +0,0 @@
#ifndef OneWire_Direct_GPIO_h
#define OneWire_Direct_GPIO_h
// This header should ONLY be included by OneWire.cpp. These defines are
// meant to be private, used within OneWire.cpp, but not exposed to Arduino
// sketches or other libraries which may include OneWire.h.
#include <stdint.h>
// Platform specific I/O definitions
#if defined(__AVR__)
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_BASE_ATTR asm("r30")
#define IO_REG_MASK_ATTR
#if defined(__AVR_ATmega4809__)
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask))
#else
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
#endif
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin) (1)
#define IO_REG_TYPE uint8_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR __attribute__ ((unused))
#define DIRECT_READ(base, mask) (*((base)+512))
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
#elif defined(__MKL26Z64__)
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask))
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
// If you have trouble with OneWire on Arduino Due, please check the
// status of delayMicroseconds() before reporting a bug in OneWire!
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
#ifndef PROGMEM
#define PROGMEM
#endif
#ifndef pgm_read_byte
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
#endif
#elif defined(__PIC32MX__)
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
#elif defined(ARDUINO_ARCH_ESP8266)
// Special note: I depend on the ESP community to maintain these definitions and
// submit good pull requests. I can not answer any ESP questions or help you
// resolve any problems related to ESP chips. Please do not contact me and please
// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
// on ESP community forums.
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
#define PIN_TO_BITMASK(pin) (1 << pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS
#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS
#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS
#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS
#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS
#elif defined(ARDUINO_ARCH_ESP32)
#include <driver/rtc_io.h>
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
static inline __attribute__((always_inline))
IO_REG_TYPE directRead(IO_REG_TYPE pin)
{
if ( pin < 32 )
return (GPIO.in >> pin) & 0x1;
else if ( pin < 40 )
return (GPIO.in1.val >> (pin - 32)) & 0x1;
return 0;
}
static inline __attribute__((always_inline))
void directWriteLow(IO_REG_TYPE pin)
{
if ( pin < 32 )
GPIO.out_w1tc = ((uint32_t)1 << pin);
else if ( pin < 34 )
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
}
static inline __attribute__((always_inline))
void directWriteHigh(IO_REG_TYPE pin)
{
if ( pin < 32 )
GPIO.out_w1ts = ((uint32_t)1 << pin);
else if ( pin < 34 )
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
}
static inline __attribute__((always_inline))
void directModeInput(IO_REG_TYPE pin)
{
if ( digitalPinIsValid(pin) )
{
#if defined(ESP_ARDUINO_VERSION)
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0)
int pin_io = rtc_io_number_get((gpio_num_t)pin);
uint32_t rtc_reg(rtc_io_desc[pin_io].reg);
if ( rtc_reg ) // RTC pins PULL settings
{
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].mux);
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].pullup | rtc_io_desc[pin_io].pulldown);
}
#endif
#else
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
if ( rtc_reg ) // RTC pins PULL settings
{
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
}
#endif
if ( pin < 32 )
GPIO.enable_w1tc = ((uint32_t)1 << pin);
else
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
pinFunction |= FUN_IE; // input enable but required for output as well?
pinFunction |= ((uint32_t)2 << MCU_SEL_S);
ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
GPIO.pin[pin].val = 0;
}
}
static inline __attribute__((always_inline))
void directModeOutput(IO_REG_TYPE pin)
{
if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs
{
#if defined(ESP_ARDUINO_VERSION)
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0)
int pin_io = rtc_io_number_get((gpio_num_t)pin);
uint32_t rtc_reg(rtc_io_desc[pin_io].reg);
if ( rtc_reg ) // RTC pins PULL settings
{
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].mux);
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].pullup | rtc_io_desc[pin_io].pulldown);
}
#endif
#else
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
if ( rtc_reg ) // RTC pins PULL settings
{
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
}
#endif
if ( pin < 32 )
GPIO.enable_w1ts = ((uint32_t)1 << pin);
else // already validated to pins <= 33
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
pinFunction |= FUN_IE; // input enable but required for output as well?
pinFunction |= ((uint32_t)2 << MCU_SEL_S);
ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
GPIO.pin[pin].val = 0;
}
}
#define DIRECT_READ(base, pin) directRead(pin)
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
// https://github.com/PaulStoffregen/OneWire/pull/47
// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c
#ifdef interrupts
#undef interrupts
#endif
#ifdef noInterrupts
#undef noInterrupts
#endif
#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
#define interrupts() portEXIT_CRITICAL(&mux);}
//#warning "ESP32 OneWire testing"
#elif defined(ARDUINO_ARCH_STM32)
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin)
#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW)
#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH)
#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0))
#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0))
#elif defined(__SAMD21G18A__)
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
#elif defined(__ASR6501__)
#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT)
#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT)
#define PORT_OFFSET(port) (PORT_REG_SHFIT * port)
#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin)))
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin))
#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ)
#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG)
#elif defined(RBL_NRF51822)
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
#include "scss_registers.h"
#include "portable.h"
#include "avr/pgmspace.h"
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
#define DIR_OFFSET_SS 0x01
#define DIR_OFFSET_SOC 0x04
#define EXT_PORT_OFFSET_SS 0x0A
#define EXT_PORT_OFFSET_SOC 0x50
/* GPIO registers base address */
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
#define PIN_TO_BITMASK(pin) pin
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
static inline __attribute__((always_inline))
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
IO_REG_TYPE ret;
if (SS_GPIO == GPIO_TYPE(pin)) {
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
} else {
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
}
return ((ret >> GPIO_ID(pin)) & 0x01);
}
static inline __attribute__((always_inline))
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
} else {
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
} else {
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
} else {
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
} else {
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
}
}
#define DIRECT_READ(base, pin) directRead(base, pin)
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
#elif defined(__riscv)
/*
* Tested on highfive1
*
* Stable results are achieved operating in the
* two high speed modes of the highfive1. It
* seems to be less reliable in slow mode.
*/
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
static inline __attribute__((always_inline))
IO_REG_TYPE directRead(IO_REG_TYPE mask)
{
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
}
static inline __attribute__((always_inline))
void directModeInput(IO_REG_TYPE mask)
{
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
GPIO_REG(GPIO_IOF_EN) &= ~mask;
GPIO_REG(GPIO_INPUT_EN) |= mask;
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
}
static inline __attribute__((always_inline))
void directModeOutput(IO_REG_TYPE mask)
{
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
GPIO_REG(GPIO_IOF_EN) &= ~mask;
GPIO_REG(GPIO_INPUT_EN) &= ~mask;
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
}
static inline __attribute__((always_inline))
void directWriteLow(IO_REG_TYPE mask)
{
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
}
static inline __attribute__((always_inline))
void directWriteHigh(IO_REG_TYPE mask)
{
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
}
#define DIRECT_READ(base, mask) directRead(mask)
#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
#else
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE unsigned int
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, pin) digitalRead(pin)
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
#endif
#endif

View File

@ -1,55 +0,0 @@
#ifndef OneWire_Direct_RegType_h
#define OneWire_Direct_RegType_h
#include <stdint.h>
// Platform specific I/O register type
#if defined(__AVR__)
#define IO_REG_TYPE uint8_t
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
#define IO_REG_TYPE uint8_t
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
#define IO_REG_TYPE uint32_t
#elif defined(__MKL26Z64__)
#define IO_REG_TYPE uint8_t
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
#define IO_REG_TYPE uint32_t
#elif defined(__PIC32MX__)
#define IO_REG_TYPE uint32_t
#elif defined(ARDUINO_ARCH_ESP8266)
#define IO_REG_TYPE uint32_t
#elif defined(ARDUINO_ARCH_ESP32)
#define IO_REG_TYPE uint32_t
#define IO_REG_MASK_ATTR
#elif defined(ARDUINO_ARCH_STM32)
#define IO_REG_TYPE uint32_t
#elif defined(__SAMD21G18A__)
#define IO_REG_TYPE uint32_t
#elif defined(__ASR6501__)
#define IO_REG_TYPE uint32_t
#elif defined(RBL_NRF51822)
#define IO_REG_TYPE uint32_t
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
#define IO_REG_TYPE uint32_t
#elif defined(__riscv)
#define IO_REG_TYPE uint32_t
#else
#define IO_REG_TYPE unsigned int
#endif
#endif

View File

@ -1,13 +0,0 @@
# Name,Type,SubType,Offset,Size,Flags
nvs,data,nvs,0x9000,0x5000
otadata,data,ota,0xe000,0x2000
app0,app,ota_0,0x10000,0x1c0000
app1,app,ota_1,0x1d0000,0x1c0000
spiffs,data,spiffs,0x390000,0x70000
# Name, Type, SubType, Offset, Size, Flags
#nvs, data, nvs, 0x9000, 0x5000,
#otadata, data, ota, 0xe000, 0x2000,
#app0, app, ota_0, 0x10000, 0x140000, # 1310720
#app1, app, ota_1, 0x150000,0x140000, # 1310720
#spiffs, data, spiffs, 0x290000,0x170000, # 1507328
1 # Name,Type,SubType,Offset,Size,Flags
2 nvs,data,nvs,0x9000,0x5000
3 otadata,data,ota,0xe000,0x2000
4 app0,app,ota_0,0x10000,0x1c0000
5 app1,app,ota_1,0x1d0000,0x1c0000
6 spiffs,data,spiffs,0x390000,0x70000
7 # Name, Type, SubType, Offset, Size, Flags
8 #nvs, data, nvs, 0x9000, 0x5000,
9 #otadata, data, ota, 0xe000, 0x2000,
10 #app0, app, ota_0, 0x10000, 0x140000, # 1310720
11 #app1, app, ota_1, 0x150000,0x140000, # 1310720
12 #spiffs, data, spiffs, 0x290000,0x170000, # 1507328

View File

@ -20,32 +20,37 @@ framework = arduino
board = d1_mini
build_unflags =
build_flags =
-Wl,-Map,output.map
-D BAUD=${common_env_data.monitor_speed}
-D ACTIVATE_OTA
-D ACTIVATE_OTA
#-D DEBUG_ESP_HTTP_CLIENT
#-D DEBUG_ESP_HTTP_SERVER
#-D DEBUG_ESP_PORT=Serial
#-D DEBUG_ESP_WIFI
#-D DEBUG_ESP_SSL
#-D DEBUG_ESP_CORE
#-D SKIP_SLEEPMODE
-D CFG_DISABLE_LOGGING # Turn off verbose logging for some of the parts (too much will cause a crash) but also add space
-D GYRO_DISABLE_LOGGING
-D PUSH_DISABLE_LOGGING
-D TSEN_DISABLE_LOGGING
-D WEB_DISABLE_LOGGING
-D MAIN_DISABLE_LOGGING
-D USE_LITTLEFS=true
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
-D USER_SSID=\""\"" # =\""myssid\""
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
-D CFG_APPVER="\"0.9.0\""
!python script/git_rev.py
-D CFG_APPVER="\"0.6.0\""
lib_deps = # Switched to forks for better version control.
# Using local copy of these libraries
# Using local copy of this library
#https://github.com/jrowberg/i2cdevlib.git#<document>
#https://github.com/khoih-prog/ESP_WiFiManager#<document>
#https://github.com/khoih-prog/ESP_DoubleResetDetector#<document>
#https://github.com/PaulStoffregen/OneWire
#https://github.com/milesburton/Arduino-Temperature-Control-Library
https://github.com/mp-se/tinyexpr # https://github.com/codeplea/tinyexpr
https://github.com/mp-se/incbin # https://github.com/graphitemaster/incbin
https://github.com/mp-se/Arduino-Log#1.1.1 # https://github.com/thijse/Arduino-Log
https://github.com/mp-se/ArduinoJson#v6.18.5 # https://github.com/bblanchon/ArduinoJson
https://github.com/mp-se/OneWire#v2.3.6 # https://github.com/PaulStoffregen/OneWire
https://github.com/mp-se/Arduino-Temperature-Control-Library#3.9.1 # https://github.com/milesburton/Arduino-Temperature-Control-Library
https://github.com/mp-se/arduinoCurveFitting#v1.0.6 # https://github.com/Rotario/arduinoCurveFitting
https://github.com/mp-se/arduino-mqtt#v2.5.0 # https://github.com/256dpi/arduino-mqtt
@ -60,27 +65,20 @@ extra_scripts =
script/create_versionjson.py
build_unflags =
${common_env_data.build_unflags}
build_flags =
-Wl,-Map,output.map
${common_env_data.build_flags}
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
#-D SKIP_SLEEPMODE
#-D DOUBLERESETDETECTOR_DEBUG=true
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
-D LOG_LEVEL=6 # Maximum log level for the debug build.
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart.
-D GYRO_DISABLE_LOGGING
-D CALC_DISABLE_LOGGING
-D HELPER_DISABLE_LOGGING
-D PUSH_DISABLE_LOGGING
-D TSEN_DISABLE_LOGGING
-D WIFI_DISABLE_LOGGING
# -D MAIN_DISABLE_LOGGING
-D WEB_DISABLE_LOGGING
-D MAIN_DISABLE_LOGGING
-D PUSH_DISABLE_LOGGING
build_flags =
${common_env_data.build_flags}
-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
#-D SKIP_SLEEPMODE
-D DOUBLERESETDETECTOR_DEBUG=true
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
-D LOG_LEVEL=6 # Maximum log level for the debug build.
lib_deps =
${common_env_data.lib_deps}
board = ${common_env_data.board}
#build_type = debug
#build_type = debug # Using debug type crashes my devkit...
build_type = release
board_build.filesystem = littlefs
monitor_filters = esp8266_exception_decoder
@ -115,96 +113,11 @@ extra_scripts =
script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags}
build_flags =
${common_env_data.build_flags}
-D COLLECT_PERFDATA
${common_env_data.build_flags}
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
-D LOG_LEVEL=5
lib_deps =
${common_env_data.lib_deps}
board = ${common_env_data.board}
build_type = release
board_build.filesystem = littlefs
[env:gravity32-release]
framework = arduino
platform = espressif32
upload_speed = ${common_env_data.upload_speed}
monitor_speed = ${common_env_data.monitor_speed}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags =
${common_env_data.build_unflags}
build_flags =
-Wl,-Map,output.map
${common_env_data.build_flags}
#-D COLLECT_PERFDATA
-D LOG_LEVEL=5
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart (applies to LOG_LEVEL6)
-D GYRO_DISABLE_LOGGING
-D CALC_DISABLE_LOGGING
-D HELPER_DISABLE_LOGGING
-D PUSH_DISABLE_LOGGING
-D TSEN_DISABLE_LOGGING
-D WIFI_DISABLE_LOGGING
-D WEB_DISABLE_LOGGING
-D MAIN_DISABLE_LOGGING
lib_deps =
${common_env_data.lib_deps}
https://github.com/lorol/LITTLEFS#1.0.6
https://github.com/h2zero/NimBLE-Arduino#1.3.7
board = featheresp32
build_type = release
board_build.partitions = part32.csv
board_build.filesystem = littlefs
monitor_filters = esp32_exception_decoder
[env:gravity32-perf]
framework = arduino
platform = espressif32
upload_speed = ${common_env_data.upload_speed}
monitor_speed = ${common_env_data.monitor_speed}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags =
${common_env_data.build_unflags}
build_flags =
-Wl,-Map,output.map
${common_env_data.build_flags}
-D COLLECT_PERFDATA
-D LOG_LEVEL=5
lib_deps =
${common_env_data.lib_deps}
https://github.com/lorol/LITTLEFS#1.0.6
https://github.com/h2zero/NimBLE-Arduino#1.3.7
board = featheresp32
build_type = release
board_build.partitions = part32.csv
board_build.filesystem = littlefs
monitor_filters = esp32_exception_decoder
[env:gravity32-release2]
framework = arduino
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip # build fails
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
upload_speed = ${common_env_data.upload_speed}
monitor_speed = ${common_env_data.monitor_speed}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags =
${common_env_data.build_unflags}
build_flags =
${common_env_data.build_flags}
-D ESPRESSIF32_20 # v2.0 framework
-D LOG_LEVEL=5
lib_deps =
${common_env_data.lib_deps}
https://github.com/h2zero/NimBLE-Arduino#1.3.7
board = featheresp32
build_type = release
board_build.partitions = part32.csv
board_build.filesystem = littlefs

View File

@ -18,12 +18,6 @@ def after_build(source, target, env):
target = dir + "/bin/firmware.bin"
if name == "gravity-perf" :
target = dir + "/bin/firmware-perf.bin"
if name == "gravity32-release" :
target = dir + "/bin/firmware32.bin"
if name == "gravity32-perf" :
target = dir + "/bin/firmware32-perf.bin"
if name == "gravity32-release2" :
target = dir + "/bin/firmware32_2.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )

View File

@ -17,18 +17,12 @@ shutil.copyfile( source + file, target + file )
file = "config.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "device.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "index.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "upload.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "format.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "test.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "firmware.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )

85
script/create_cert.py Normal file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# This script pulls the list of Mozilla trusted certificate authorities
# from the web at the "mozurl" below, parses the file to grab the PEM
# for each cert, and then generates DER files in a new ./data directory
# Upload these to an on-chip filesystem and use the CertManager to parse
# and use them for your outgoing SSL connections.
#
# Script by Earle F. Philhower, III. Released to the public domain.
from __future__ import print_function
import csv
import os
import sys
from shutil import which
# Change the path to the installed files.
arCmd = "C:\\Users\\magnu\\.platformio\\packages\\toolchain-xtensa\\bin\\xtensa-lx106-elf-ar.exe"
opensslCmd = "C:\\Program Files\\Git\\usr\\bin\\openssl.exe"
from subprocess import Popen, PIPE, call
try:
from urllib.request import urlopen
except Exception:
from urllib2 import urlopen
try:
from StringIO import StringIO
except Exception:
from io import StringIO
# check if ar and openssl are available
#if which('ar') is None and not os.path.isfile('./ar') and not os.path.isfile('./ar.exe'):
# raise Exception("You need the program 'ar' from xtensa-lx106-elf found here: (esp8266-arduino-core)/hardware/esp8266com/esp8266/tools/xtensa-lx106-elf/xtensa-lx106-elf/bin/ar")
#if which('openssl') is None and not os.path.isfile('./openssl') and not os.path.isfile('./openssl.exe'):
# raise Exception("You need to have openssl in PATH, installable from https://www.openssl.org/")
# Mozilla's URL for the CSV file with included PEM certs
mozurl = "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV"
# Load the names[] and pems[] array from the URL
names = []
pems = []
response = urlopen(mozurl)
csvData = response.read()
if sys.version_info[0] > 2:
csvData = csvData.decode('utf-8')
csvFile = StringIO(csvData)
csvReader = csv.reader(csvFile)
for row in csvReader:
names.append(row[0]+":"+row[1]+":"+row[2])
for item in row:
if item.startswith("'-----BEGIN CERTIFICATE-----"):
pems.append(item)
del names[0] # Remove headers
del pems[0] # Remove headers
# Try and make ./data, skip if present
try:
os.mkdir("../data")
except Exception:
pass
derFiles = []
idx = 0
# Process the text PEM using openssl into DER files
for i in range(0, len(pems)):
certName = "../data/ca_%03d.der" % (idx);
thisPem = pems[i].replace("'", "")
print(names[i] + " -> " + certName)
ssl = Popen([opensslCmd,'x509','-inform','PEM','-outform','DER','-out', certName], shell = False, stdin = PIPE)
pipe = ssl.stdin
pipe.write(thisPem.encode('utf-8'))
pipe.close()
ssl.wait()
if os.path.exists(certName):
derFiles.append(certName)
idx = idx + 1
if os.path.exists("../data/certs.ar"):
os.unlink("../data/certs.ar");
arCmd = [arCmd, 'q', '../data/certs.ar'] + derFiles;
call( arCmd )
for der in derFiles:
os.unlink(der)

View File

@ -19,43 +19,36 @@ def after_build(source, target, env):
shutil.copyfile( source, target )
# Copy file 2
source = dir + "/data/device.min.htm"
target = dir + "/bin/device.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 3
source = dir + "/data/config.min.htm"
target = dir + "/bin/config.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 3
# Copy file 4
source = dir + "/data/about.min.htm"
target = dir + "/bin/about.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 4
# Copy file 5
source = dir + "/data/calibration.min.htm"
target = dir + "/bin/calibration.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 5
source = dir + "/data/format.min.htm"
target = dir + "/bin/format.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 6
source = dir + "/data/test.min.htm"
target = dir + "/bin/test.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
target = dir + "/bin/version.json"
ver = get_build_flag_value("CFG_APPVER")
print( "Creating version.json" )
f = open( target, "w" )
f.write( "{ \"project\":\"gravmon\", \"version\":" + ver + ", " )
#f.write( " \"html\": [ \"index.min.htm\", \"config.min.htm\", \"calibration.min.htm\", \"test.min.htm\", \"format.min.htm\", \"about.min.htm\" ] }" )
f.write( " \"html\": [ ] }" )
f.write( " \"html\": [ \"index.min.htm\", \"device.min.htm\", \"config.min.htm\", \"calibration.min.htm\", \"about.min.htm\" ] }" )
f.close()

View File

@ -1,9 +0,0 @@
import subprocess
revision = (
subprocess.check_output(["git", "rev-parse", "HEAD"])
.strip()
.decode("utf-8")
)
revision = revision[-6:]
print("-D CFG_GITREV='\"..%s\"'" % revision)

View File

@ -1,102 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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(ESP32)
#include <ble.hpp>
#include <string>
// Tilt UUID variants and data format, based on tilt-sim
//
// https://github.com/spouliot/tilt-sim
//
// Tilt data format is described here. Only SG and Temp is transmitted over BLE.
// https://kvurd.com/blog/tilt-hydrometer-ibeacon-data-format/
//
// Create ble sender
//
BleSender::BleSender(const char* color) {
BLEDevice::init("");
// boost power to maximum, these might be changed once battery life using BLE
// has been tested.
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P9);
_advertising = BLEDevice::getAdvertising();
_color = color;
if (!_color.compareTo("red"))
_uuid = BLEUUID::fromString("A495BB10-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("green"))
_uuid = BLEUUID::fromString("A495BB20-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("black"))
_uuid = BLEUUID::fromString("A495BB30-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("purple"))
_uuid = BLEUUID::fromString("A495BB40-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("orange"))
_uuid = BLEUUID::fromString("A495BB50-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("blue"))
_uuid = BLEUUID::fromString("A495BB60-C5B1-4B44-B512-1370F02D74DE");
else if (!_color.compareTo("yellow"))
_uuid = BLEUUID::fromString("A495BB70-C5B1-4B44-B512-1370F02D74DE");
else // if (_color.compareTo("pink"))
_uuid = BLEUUID::fromString("A495BB80-C5B1-4B44-B512-1370F02D74DE");
}
//
// Send temp and gravity via BLE
//
void BleSender::sendData(float tempF, float gravSG) {
uint16_t gravity = gravSG * 1000; // SG * 1000 or SG * 10000 for Tilt Pro/HD
uint16_t temperature = tempF; // Deg F _or_ Deg F * 10 for Tilt Pro/HD
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setManufacturerId(
0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!)
oBeacon.setProximityUUID(_uuid);
oBeacon.setMajor(temperature);
oBeacon.setMinor(gravity);
std::string strServiceData = "";
strServiceData += static_cast<char>(26); // Len
strServiceData += static_cast<char>(0xFF); // Type
strServiceData += oBeacon.getData();
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04
oAdvertisementData.addData(strServiceData);
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
_advertising->setAdvertisementData(oAdvertisementData);
_advertising->setScanResponseData(oScanResponseData);
_advertising->setAdvertisementType(BLE_GAP_CONN_MODE_NON);
_advertising->start();
delay(100);
_advertising->stop();
delay(100);
}
#endif // ESP32

View File

@ -1,47 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
*/
#ifndef SRC_BLE_HPP_
#define SRC_BLE_HPP_
#if defined(ESP32)
#include <NimBLEBeacon.h>
#include <NimBLEDevice.h>
#include <config.hpp>
#include <main.hpp>
class BleSender {
private:
BLEAdvertising* _advertising;
String _color;
BLEUUID _uuid;
public:
explicit BleSender(const char* color);
void sendData(float tempF, float gravSG);
};
#endif // ESP32
#endif // SRC_BLE_HPP_

View File

@ -25,7 +25,11 @@ SOFTWARE.
#include <tinyexpr.h>
#include <calc.hpp>
#include <main.hpp>
#include <config.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
#define FORMULA_MAX_DEVIATION 1.5
//
// Use values to derive a formula
@ -42,15 +46,14 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0)
noAngles = 3;
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(
F("CALC: Trying to create formula using order = %d, found %d angles" CR),
order, noAngles);
#endif
if (!noAngles) {
ErrorFileLog errLog;
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
Log.error(F("CALC: Not enough values for deriving formula" CR));
return ERR_FORMULA_NOTENOUGHVALUES;
} else {
double coeffs[order + 1];
@ -59,7 +62,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
// Returned value is 0 if no error
if (ret == 0) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Finshied processing data points." CR));
#endif
@ -80,7 +83,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
coeffs[1]);
}
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
#endif
@ -93,21 +96,11 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
// If the deviation is more than 2 degress we mark it as failed.
if (dev * 1000 > myHardwareConfig.getMaxFormulaCreationDeviation()) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
char s[20];
snprintf(&s[0], sizeof(s), "%.8f", dev);
Log.verbose(F("CALC: Deviation is: %s" CR), &s[0]);
#endif
valid = false;
}
if (dev * 1000 > FORMULA_MAX_DEVIATION) valid = false;
}
if (!valid) {
ErrorFileLog errLog;
errLog.addEntry(
F("CALC: Error validating created formula. Deviation to large, "
"formula rejected."));
Log.error(F("CALC: Deviation to large, formula rejected." CR));
return ERR_FORMULA_UNABLETOFFIND;
}
@ -116,8 +109,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
}
}
ErrorFileLog errLog;
errLog.addEntry(F("CALC: Internal error finding formula."));
Log.error(F("CALC: Internal error finding formula." CR));
return ERR_FORMULA_INTERNAL;
}
@ -129,13 +121,13 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
const char *formula = myConfig.getGravityFormula();
if (tempFormula != 0) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Using temporary formula." CR));
#endif
formula = tempFormula;
}
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
@ -154,35 +146,30 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
char s[20];
snprintf(&s[0], sizeof(s), "%.8f", g);
Log.verbose(F("CALC: Calculated gravity is %s." CR), &s[0]);
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
#endif
return g;
}
ErrorFileLog errLog;
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
Log.error(F("CALC: Failed to parse expression %d." CR), err);
return 0;
}
//
// Do a standard gravity temperature correction. This is a simple way to adjust
// for differnt worth temperatures. This function uses C as temperature.
// for differnt worth temperatures
//
// Source: https://homebrewacademy.com/hydrometer-temperature-correction/
//
double gravityTemperatureCorrectionC(double gravity, double tempC,
double calTempC) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
double gravityTemperatureCorrection(double gravity, double temp,
char tempFormat, double calTemp) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, "
"temp %F, calTemp %F." CR),
gravity, tempC, calTempC);
gravity, temp, calTemp);
#endif
// float tempF = convertCtoF(tempC);
// float calTempF = convertCtoF(calTempC);
if (tempFormat == 'C') temp = convertCtoF(temp);
double calTempF = convertCtoF(calTemp); // calTemp is in C
const char *formula =
"gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0."
"00000000232820948*temp^3)/"
@ -191,7 +178,7 @@ double gravityTemperatureCorrectionC(double gravity, double tempC,
// Store variable names and pointers.
te_variable vars[] = {
{"gravity", &gravity}, {"temp", &tempC}, {"cal", &calTempC}};
{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
int err;
// Compile the expression with variables.
@ -201,18 +188,15 @@ double gravityTemperatureCorrectionC(double gravity, double tempC,
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
char s[10];
snprintf(&s[0], sizeof(s), "%.8f", g);
Log.verbose(F("CALC: Corrected gravity is %s." CR), &s[0]);
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Corrected gravity is %F." CR), g);
#endif
return g;
}
ErrorFileLog errLog;
errLog.addEntry(
"CALC: Failed to parse expression for gravity temperature correction " +
String(err));
Log.error(
F("CALC: Failed to parse expression %d, no correction has been made." CR),
err);
return gravity;
}

View File

@ -24,17 +24,18 @@ SOFTWARE.
#ifndef SRC_CALC_HPP_
#define SRC_CALC_HPP_
// Includes
#include <config.hpp>
#include <helper.hpp>
#define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3
double calculateGravity(double angle, double tempC,
const char *tempFormula = 0);
double gravityTemperatureCorrectionC(
double gravity, double tempC,
double calTempC = myHardwareConfig.getDefaultCalibrationTemp());
// Functions
double calculateGravity(double angle, double temp, const char *tempFormula = 0);
double gravityTemperatureCorrection(double gravity, double temp,
char tempFormat, double calTemp = 20);
int createFormula(RawFormulaData &fd, char *formulaBuffer,
int formulaBufferSize, int order);

635
src/certificates.h Normal file

File diff suppressed because one or more lines are too long

View File

@ -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.
*/
#include <LittleFS.h>
#include <config.hpp>
#include <main.hpp>
#include <wifi.hpp>
#include <helper.hpp>
Config myConfig;
HardwareConfig myHardwareConfig;
//
// Create the config class with default settings.
@ -34,23 +34,26 @@ HardwareConfig myHardwareConfig;
Config::Config() {
// Assiging default values
char buf[30];
#if defined(ESP8266)
snprintf(&buf[0], sizeof(buf), "%06x", (unsigned int)ESP.getChipId());
#else // defined (ESP32)
uint32_t chipId = 0;
for (int i = 0; i < 17; i = i + 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
snprintf(&buf[0], sizeof(buf), "%06x", chipId);
#endif
_id = String(&buf[0]);
snprintf(&buf[0], sizeof(buf), "%6x", (unsigned int)ESP.getChipId());
id = String(&buf[0]);
snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID());
_mDNS = String(&buf[0]);
mDNS = String(&buf[0]);
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Created config for %s (%s)." CR), _id.c_str(),
_mDNS.c_str());
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Created config for %s (%s)." CR), id.c_str(),
mDNS.c_str());
#endif
setTempFormat('C');
setGravityFormat('G');
setSleepInterval(900); // 15 minutes
setVoltageFactor(1.59); // Conversion factor for battery
setTempSensorAdj(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;
}
//
@ -58,89 +61,79 @@ Config::Config() {
// web and saving to file)
//
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_BLE] = getColorBLE();
doc[PARAM_TEMPFORMAT] = String(getTempFormat());
doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
doc[PARAM_TOKEN] = getToken();
doc[PARAM_TOKEN2] = getToken2();
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_HTTP3] = getHttp3Url();
doc[PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
doc[PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
doc[PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
doc[PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
doc[PARAM_PUSH_MQTT] = getMqttUrl();
doc[PARAM_PUSH_MQTT_PORT] = getMqttPort();
doc[PARAM_PUSH_MQTT_USER] = getMqttUser();
doc[PARAM_PUSH_MQTT_PASS] = getMqttPass();
doc[PARAM_SLEEP_INTERVAL] = getSleepInterval();
doc[PARAM_VOLTAGEFACTOR] = getVoltageFactor();
doc[PARAM_GRAVITY_FORMULA] = getGravityFormula();
doc[PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
doc[PARAM_TEMP_ADJ] = getTempSensorAdjC();
doc[PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
doc[PARAM_GYRO_TEMP] = isGyroTemp();
doc[CFG_PARAM_MDNS] = getMDNS();
doc[CFG_PARAM_ID] = getID();
doc[CFG_PARAM_OTA] = getOtaURL();
doc[CFG_PARAM_SSID] = getWifiSSID();
doc[CFG_PARAM_PASS] = getWifiPass();
doc[CFG_PARAM_TEMPFORMAT] = String(getTempFormat());
doc[CFG_PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
doc[CFG_PARAM_PUSH_HTTP] = getHttpPushUrl();
doc[CFG_PARAM_PUSH_HTTP2] = getHttpPushUrl2();
doc[CFG_PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
doc[CFG_PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
doc[CFG_PARAM_PUSH_MQTT] = getMqttUrl();
doc[CFG_PARAM_PUSH_MQTT_TOPIC] = getMqttTopic();
doc[CFG_PARAM_PUSH_MQTT_USER] = getMqttUser();
doc[CFG_PARAM_PUSH_MQTT_PASS] = getMqttPass();
doc[CFG_PARAM_SLEEP_INTERVAL] = getSleepInterval();
doc[CFG_PARAM_VOLTAGEFACTOR] = getVoltageFactor();
doc[CFG_PARAM_GRAVITY_FORMULA] = getGravityFormula();
doc[CFG_PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
doc[CFG_PARAM_TEMP_ADJ] = getTempSensorAdj();
doc[CFG_PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
doc[CFG_PARAM_GYRO_TEMP] = isGyroTemp();
JsonObject cal = doc.createNestedObject(PARAM_GYRO_CALIBRATION);
cal["ax"] = _gyroCalibration.ax;
cal["ay"] = _gyroCalibration.ay;
cal["az"] = _gyroCalibration.az;
cal["gx"] = _gyroCalibration.gx;
cal["gy"] = _gyroCalibration.gy;
cal["gz"] = _gyroCalibration.gz;
JsonObject cal = doc.createNestedObject(CFG_PARAM_GYRO_CALIBRATION);
cal["ax"] = gyroCalibration.ax;
cal["ay"] = gyroCalibration.ay;
cal["az"] = gyroCalibration.az;
cal["gx"] = gyroCalibration.gx;
cal["gy"] = gyroCalibration.gy;
cal["gz"] = gyroCalibration.gz;
JsonObject cal2 = doc.createNestedObject(PARAM_FORMULA_DATA);
cal2["a1"] = reduceFloatPrecision(_formulaData.a[0], 2);
cal2["a2"] = reduceFloatPrecision(_formulaData.a[1], 2);
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
JsonObject cal2 = doc.createNestedObject(CFG_PARAM_FORMULA_DATA);
cal2["a1"] = reduceFloatPrecision(formulaData.a[0], 2);
cal2["a2"] = reduceFloatPrecision(formulaData.a[1], 2);
cal2["a3"] = reduceFloatPrecision(formulaData.a[2], 2);
cal2["a4"] = reduceFloatPrecision(formulaData.a[3], 2);
cal2["a5"] = reduceFloatPrecision(formulaData.a[4], 2);
cal2["g1"] = reduceFloatPrecision(_formulaData.g[0], 4);
cal2["g2"] = reduceFloatPrecision(_formulaData.g[1], 4);
cal2["g3"] = reduceFloatPrecision(_formulaData.g[2], 4);
cal2["g4"] = reduceFloatPrecision(_formulaData.g[3], 4);
cal2["g5"] = reduceFloatPrecision(_formulaData.g[4], 4);
cal2["g1"] = reduceFloatPrecision(formulaData.g[0], 4);
cal2["g2"] = reduceFloatPrecision(formulaData.g[1], 4);
cal2["g3"] = reduceFloatPrecision(formulaData.g[2], 4);
cal2["g4"] = reduceFloatPrecision(formulaData.g[3], 4);
cal2["g5"] = reduceFloatPrecision(formulaData.g[4], 4);
}
//
// Save json document to file
//
bool Config::saveFile() {
if (!_saveNeeded) {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
if (!saveNeeded) {
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Skipping save, not needed." CR));
#endif
return true;
}
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Saving configuration to file." CR));
#endif
File configFile = LittleFS.open(CFG_FILENAME, "w");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to save configuration."));
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
return false;
}
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
createJson(doc);
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
serializeJson(doc, Serial);
Serial.print(CR);
#endif
@ -149,7 +142,8 @@ bool Config::saveFile() {
configFile.flush();
configFile.close();
_saveNeeded = false;
saveNeeded = false;
myConfig.debug();
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
return true;
}
@ -158,21 +152,20 @@ bool Config::saveFile() {
// Load config file from disk
//
bool Config::loadFile() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Loading configuration from file." CR));
#endif
if (!LittleFS.exists(CFG_FILENAME)) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Configuration file does not exist."));
Log.error(
F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
return false;
}
File configFile = LittleFS.open(CFG_FILENAME, "r");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to load configuration."));
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
return false;
}
@ -188,120 +181,103 @@ bool Config::loadFile() {
configFile.close();
if (err) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR),
err.c_str(), doc.capacity());
return false;
}
#if LOG_LEVEL == 6
Log.verbose(F("CFG : Parsed configuration file." CR));
#endif
if (!doc[PARAM_OTA].isNull()) setOtaURL(doc[PARAM_OTA]);
if (!doc[PARAM_MDNS].isNull()) setMDNS(doc[PARAM_MDNS]);
if (!doc[PARAM_SSID].isNull()) setWifiSSID(doc[PARAM_SSID]);
if (!doc[PARAM_PASS].isNull()) setWifiPass(doc[PARAM_PASS]);
if (!doc[PARAM_BLE].isNull()) setColorBLE(doc[PARAM_BLE]);
if (!doc[CFG_PARAM_OTA].isNull()) setOtaURL(doc[CFG_PARAM_OTA]);
if (!doc[CFG_PARAM_MDNS].isNull()) setMDNS(doc[CFG_PARAM_MDNS]);
if (!doc[CFG_PARAM_SSID].isNull()) setWifiSSID(doc[CFG_PARAM_SSID]);
if (!doc[CFG_PARAM_PASS].isNull()) setWifiPass(doc[CFG_PARAM_PASS]);
if (!doc[PARAM_TEMPFORMAT].isNull()) {
String s = doc[PARAM_TEMPFORMAT];
if (!doc[CFG_PARAM_TEMPFORMAT].isNull()) {
String s = doc[CFG_PARAM_TEMPFORMAT];
setTempFormat(s.charAt(0));
}
if (!doc[PARAM_PUSH_BREWFATHER].isNull())
setBrewfatherPushUrl(doc[PARAM_PUSH_BREWFATHER]);
if (!doc[CFG_PARAM_PUSH_BREWFATHER].isNull())
setBrewfatherPushUrl(doc[CFG_PARAM_PUSH_BREWFATHER]);
if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]);
if (!doc[PARAM_TOKEN2].isNull()) setToken2(doc[PARAM_TOKEN2]);
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_HTTP3].isNull()) setHttp3Url(doc[PARAM_PUSH_HTTP3]);
if (!doc[CFG_PARAM_PUSH_HTTP].isNull())
setHttpPushUrl(doc[CFG_PARAM_PUSH_HTTP]);
if (!doc[CFG_PARAM_PUSH_HTTP2].isNull())
setHttpPushUrl2(doc[CFG_PARAM_PUSH_HTTP2]);
if (!doc[PARAM_PUSH_INFLUXDB2].isNull())
setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]);
if (!doc[PARAM_PUSH_INFLUXDB2_ORG].isNull())
setInfluxDb2PushOrg(doc[PARAM_PUSH_INFLUXDB2_ORG]);
if (!doc[PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
setInfluxDb2PushBucket(doc[PARAM_PUSH_INFLUXDB2_BUCKET]);
if (!doc[PARAM_PUSH_INFLUXDB2_AUTH].isNull())
setInfluxDb2PushToken(doc[PARAM_PUSH_INFLUXDB2_AUTH]);
if (!doc[CFG_PARAM_PUSH_INFLUXDB2].isNull())
setInfluxDb2PushUrl(doc[CFG_PARAM_PUSH_INFLUXDB2]);
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_ORG].isNull())
setInfluxDb2PushOrg(doc[CFG_PARAM_PUSH_INFLUXDB2_ORG]);
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
setInfluxDb2PushBucket(doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET]);
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH].isNull())
setInfluxDb2PushToken(doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH]);
if (!doc[PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[PARAM_PUSH_MQTT]);
if (!doc[PARAM_PUSH_MQTT_PORT].isNull())
setMqttPort(doc[PARAM_PUSH_MQTT_PORT].as<int>());
if (!doc[PARAM_PUSH_MQTT_USER].isNull())
setMqttUser(doc[PARAM_PUSH_MQTT_USER]);
if (!doc[PARAM_PUSH_MQTT_PASS].isNull())
setMqttPass(doc[PARAM_PUSH_MQTT_PASS]);
if (!doc[CFG_PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[CFG_PARAM_PUSH_MQTT]);
if (!doc[CFG_PARAM_PUSH_MQTT_TOPIC].isNull())
setMqttTopic(doc[CFG_PARAM_PUSH_MQTT_TOPIC]);
if (!doc[CFG_PARAM_PUSH_MQTT_USER].isNull())
setMqttUser(doc[CFG_PARAM_PUSH_MQTT_USER]);
if (!doc[CFG_PARAM_PUSH_MQTT_PASS].isNull())
setMqttPass(doc[CFG_PARAM_PUSH_MQTT_PASS]);
if (!doc[PARAM_SLEEP_INTERVAL].isNull())
setSleepInterval(doc[PARAM_SLEEP_INTERVAL].as<int>());
if (!doc[PARAM_VOLTAGEFACTOR].isNull())
setVoltageFactor(doc[PARAM_VOLTAGEFACTOR].as<float>());
if (!doc[PARAM_GRAVITY_FORMULA].isNull())
setGravityFormula(doc[PARAM_GRAVITY_FORMULA]);
if (!doc[PARAM_GRAVITY_TEMP_ADJ].isNull())
setGravityTempAdj(doc[PARAM_GRAVITY_TEMP_ADJ].as<bool>());
if (!doc[PARAM_GYRO_TEMP].isNull())
setGyroTemp(doc[PARAM_GYRO_TEMP].as<bool>());
if (!doc[PARAM_GRAVITY_FORMAT].isNull()) {
String s = doc[PARAM_GRAVITY_FORMAT];
if (!doc[CFG_PARAM_SLEEP_INTERVAL].isNull())
setSleepInterval(doc[CFG_PARAM_SLEEP_INTERVAL].as<int>());
if (!doc[CFG_PARAM_VOLTAGEFACTOR].isNull())
setVoltageFactor(doc[CFG_PARAM_VOLTAGEFACTOR].as<float>());
if (!doc[CFG_PARAM_GRAVITY_FORMULA].isNull())
setGravityFormula(doc[CFG_PARAM_GRAVITY_FORMULA]);
if (!doc[CFG_PARAM_GRAVITY_TEMP_ADJ].isNull())
setGravityTempAdj(doc[CFG_PARAM_GRAVITY_TEMP_ADJ].as<bool>());
if (!doc[CFG_PARAM_GYRO_TEMP].isNull())
setGyroTemp(doc[CFG_PARAM_GYRO_TEMP].as<bool>());
if (!doc[CFG_PARAM_GRAVITY_FORMAT].isNull()) {
String s = doc[CFG_PARAM_GRAVITY_FORMAT];
setGravityFormat(s.charAt(0));
}
if (!doc[PARAM_TEMP_ADJ].isNull())
setTempSensorAdjC(doc[PARAM_TEMP_ADJ].as<float>());
if (!doc[CFG_PARAM_TEMP_ADJ].isNull())
setTempSensorAdj(doc[CFG_PARAM_TEMP_ADJ].as<float>());
if (!doc[PARAM_GYRO_CALIBRATION]["ax"].isNull())
_gyroCalibration.ax = doc[PARAM_GYRO_CALIBRATION]["ax"];
if (!doc[PARAM_GYRO_CALIBRATION]["ay"].isNull())
_gyroCalibration.ay = doc[PARAM_GYRO_CALIBRATION]["ay"];
if (!doc[PARAM_GYRO_CALIBRATION]["az"].isNull())
_gyroCalibration.az = doc[PARAM_GYRO_CALIBRATION]["az"];
if (!doc[PARAM_GYRO_CALIBRATION]["gx"].isNull())
_gyroCalibration.gx = doc[PARAM_GYRO_CALIBRATION]["gx"];
if (!doc[PARAM_GYRO_CALIBRATION]["gy"].isNull())
_gyroCalibration.gy = doc[PARAM_GYRO_CALIBRATION]["gy"];
if (!doc[PARAM_GYRO_CALIBRATION]["gz"].isNull())
_gyroCalibration.gz = doc[PARAM_GYRO_CALIBRATION]["gz"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ax"].isNull())
gyroCalibration.ax = doc[CFG_PARAM_GYRO_CALIBRATION]["ax"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ay"].isNull())
gyroCalibration.ay = doc[CFG_PARAM_GYRO_CALIBRATION]["ay"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["az"].isNull())
gyroCalibration.az = doc[CFG_PARAM_GYRO_CALIBRATION]["az"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gx"].isNull())
gyroCalibration.gx = doc[CFG_PARAM_GYRO_CALIBRATION]["gx"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gy"].isNull())
gyroCalibration.gy = doc[CFG_PARAM_GYRO_CALIBRATION]["gy"];
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gz"].isNull())
gyroCalibration.gz = doc[CFG_PARAM_GYRO_CALIBRATION]["gz"];
if (!doc[PARAM_FORMULA_DATA]["a1"].isNull())
_formulaData.a[0] = doc[PARAM_FORMULA_DATA]["a1"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a2"].isNull())
_formulaData.a[1] = doc[PARAM_FORMULA_DATA]["a2"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a3"].isNull())
_formulaData.a[2] = doc[PARAM_FORMULA_DATA]["a3"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a4"].isNull())
_formulaData.a[3] = doc[PARAM_FORMULA_DATA]["a4"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a5"].isNull())
_formulaData.a[4] = doc[PARAM_FORMULA_DATA]["a5"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["a1"].isNull())
formulaData.a[0] = doc[CFG_PARAM_FORMULA_DATA]["a1"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["a2"].isNull())
formulaData.a[1] = doc[CFG_PARAM_FORMULA_DATA]["a2"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["a3"].isNull())
formulaData.a[2] = doc[CFG_PARAM_FORMULA_DATA]["a3"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["a4"].isNull())
formulaData.a[3] = doc[CFG_PARAM_FORMULA_DATA]["a4"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["a5"].isNull())
formulaData.a[4] = doc[CFG_PARAM_FORMULA_DATA]["a5"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g2"].isNull())
_formulaData.g[1] = doc[PARAM_FORMULA_DATA]["g2"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g3"].isNull())
_formulaData.g[2] = doc[PARAM_FORMULA_DATA]["g3"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g4"].isNull())
_formulaData.g[3] = doc[PARAM_FORMULA_DATA]["g4"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g5"].isNull())
_formulaData.g[4] = doc[PARAM_FORMULA_DATA]["g5"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["g1"].isNull())
formulaData.g[0] = doc[CFG_PARAM_FORMULA_DATA]["g1"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["g2"].isNull())
formulaData.g[1] = doc[CFG_PARAM_FORMULA_DATA]["g2"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["g3"].isNull())
formulaData.g[2] = doc[CFG_PARAM_FORMULA_DATA]["g3"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["g4"].isNull())
formulaData.g[3] = doc[CFG_PARAM_FORMULA_DATA]["g4"].as<double>();
if (!doc[CFG_PARAM_FORMULA_DATA]["g5"].isNull())
formulaData.g[4] = doc[CFG_PARAM_FORMULA_DATA]["g5"].as<double>();
/*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
myConfig.debug();
saveNeeded = false; // Reset save flag
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
return true;
}
@ -318,7 +294,7 @@ void Config::formatFileSystem() {
// Check if file system can be mounted, if not we format it.
//
void Config::checkFileSystem() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
#endif
@ -331,106 +307,31 @@ void Config::checkFileSystem() {
}
//
// Save json document to file
// Dump the configuration to the serial port
//
bool HardwareConfig::saveFile() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Saving hardware configuration to file." CR));
void Config::debug() {
#if LOG_LEVEL == 6 && !defined(CFG_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), getTempSensorAdj());
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
File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
return false;
}
DynamicJsonDocument doc(512);
doc[PARAM_HW_GYRO_READ_COUNT] = this->getGyroReadCount();
doc[PARAM_HW_GYRO_READ_DELAY] = this->getGyroReadDelay();
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] = this->getGyroSensorMovingThreashold();
doc[PARAM_HW_FORMULA_DEVIATION] = this->getMaxFormulaCreationDeviation();
doc[PARAM_HW_WIFI_PORTALTIMEOUT] = this->getWifiPortalTimeout();
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] = this->getDefaultCalibrationTemp();
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
serializeJson(doc, Serial);
Serial.print(CR);
#endif
serializeJson(doc, configFile);
configFile.flush();
configFile.close();
Log.notice(F("CFG : Configuration saved to " CFG_HW_FILENAME "." CR));
return true;
}
//
// Load config file from disk
//
bool HardwareConfig::loadFile() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Loading hardware configuration from file." CR));
#endif
if (!LittleFS.exists(CFG_HW_FILENAME)) {
Log.warning(
F("CFG : Configuration file does not exist " CFG_HW_FILENAME "." CR));
return false;
}
File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
return false;
}
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
configFile.size());
DynamicJsonDocument doc(512);
DeserializationError err = deserializeJson(doc, configFile);
#if LOG_LEVEL == 6
serializeJson(doc, Serial);
Serial.print(CR);
#endif
configFile.close();
if (err) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
return false;
}
#if LOG_LEVEL == 6
Log.verbose(F("CFG : Parsed hardware configuration file." CR));
#endif
if (!doc[PARAM_HW_GYRO_READ_COUNT].isNull())
this->setGyroReadCount(doc[PARAM_HW_GYRO_READ_COUNT].as<int>());
if (!doc[PARAM_HW_GYRO_READ_DELAY].isNull())
this->setGyroReadDelay(doc[PARAM_HW_GYRO_READ_DELAY].as<int>());
if (!doc[PARAM_HW_GYRO_MOVING_THREASHOLD].isNull())
this->setGyroSensorMovingThreashold(
doc[PARAM_HW_GYRO_MOVING_THREASHOLD].as<int>());
if (!doc[PARAM_HW_FORMULA_DEVIATION].isNull())
this->setMaxFormulaCreationDeviation(
doc[PARAM_HW_FORMULA_DEVIATION].as<float>());
if (!doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].isNull())
this->SetDefaultCalibrationTemp(
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;
}
// EOF

View File

@ -24,14 +24,72 @@ SOFTWARE.
#ifndef SRC_CONFIG_HPP_
#define SRC_CONFIG_HPP_
#include <helper.hpp>
#include <resources.hpp>
// Includes
#include <Arduino.h>
#include <ArduinoJson.h>
#include <stdlib.h>
#include <helper.hpp>
// defintions
#define CFG_JSON_BUFSIZE 3192
#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
#define CFG_APPNAME "GravityMon " // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file
#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
#define WIFI_PORTAL_TIMEOUT \
120 // Number of seconds until the config portal is closed
// These are used in API + Savefile
#define CFG_PARAM_ID "id"
#define CFG_PARAM_MDNS "mdns" // Device name
#define CFG_PARAM_OTA "ota-url"
#define CFG_PARAM_SSID "wifi-ssid"
#define CFG_PARAM_PASS "wifi-pass"
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push"
#define CFG_PARAM_PUSH_HTTP "http-push"
#define CFG_PARAM_PUSH_HTTP2 "http-push2"
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push"
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth"
#define CFG_PARAM_PUSH_MQTT "mqtt-push"
#define CFG_PARAM_PUSH_MQTT_USER "mqtt-user"
#define CFG_PARAM_PUSH_MQTT_PASS "mqtt-pass"
#define CFG_PARAM_PUSH_MQTT_TOPIC "mqtt-topic"
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
#define CFG_PARAM_VOLTAGEFACTOR \
"voltage-factor" // Factor to calculate the battery voltage
#define CFG_PARAM_GRAVITY_FORMULA \
"gravity-formula" // Formula for calculating gravity
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
#define CFG_PARAM_GRAVITY_TEMP_ADJ \
"gravity-temp-adjustment" // True/False. Adjust gravity for temperature
#define CFG_PARAM_TEMP_ADJ \
"temp-adjustment-value" // Correction value for temp sensor
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
#define CFG_PARAM_GYRO_TEMP \
"gyro-temp" // True/False. Use temp sensor in gyro (only in gravity mode)
#define CFG_PARAM_FORMULA_DATA \
"formula-calculation-data" // Raw data for the formula calculation
// These are used in API's
#define CFG_PARAM_APP_NAME "app-name"
#define CFG_PARAM_APP_VER "app-ver"
#define CFG_PARAM_ANGLE "angle"
#define CFG_PARAM_GRAVITY "gravity"
#define CFG_PARAM_TEMP_C "temp-c"
#define CFG_PARAM_TEMP_F "temp-f"
#define CFG_PARAM_BATTERY "battery"
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
#define CFG_PARAM_RSSI "rssi"
#define CFG_PARAM_CERTS "certs"
// Used for holding sensordata or sensoroffsets
struct RawGyroData {
@ -52,330 +110,230 @@ struct RawFormulaData {
double g[5];
};
class HardwareConfig {
private:
int _wifiPortalTimeout = 120;
float _maxFormulaCreationDeviation = 1.6;
float _defaultCalibrationTemp = 20.0;
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; }
void setWifiPortalTimeout(int t) { _wifiPortalTimeout = t; }
float getMaxFormulaCreationDeviation() {
return _maxFormulaCreationDeviation;
}
void setMaxFormulaCreationDeviation(float f) {
_maxFormulaCreationDeviation = f;
}
float getDefaultCalibrationTemp() { return _defaultCalibrationTemp; }
void SetDefaultCalibrationTemp(float t) { _defaultCalibrationTemp = t; }
int getGyroSensorMovingThreashold() { return _gyroSensorMovingThreashold; }
void setGyroSensorMovingThreashold(int t) { _gyroSensorMovingThreashold = t; }
int getGyroReadCount() { return _gyroReadCount; }
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();
};
// Main configuration class
class Config {
private:
bool _saveNeeded = false;
int _configVersion = 2;
bool saveNeeded;
// Device configuration
String _id = "";
String _mDNS = "";
String _otaURL = "";
char _tempFormat = 'C';
float _voltageFactor = 1.59;
float _tempSensorAdjC = 0;
int _sleepInterval = 900;
bool _gyroTemp = false;
String id;
String mDNS;
String otaURL;
char tempFormat; // C, F
float voltageFactor;
float tempSensorAdj; // This value will be added to the read sensor value
int sleepInterval;
bool gyroTemp; // Experimental feature
// Wifi Config
String _wifiSSID = "";
String _wifiPASS = "";
String wifiSSID;
String wifiPASS;
// Push target settings
String _brewfatherPushUrl = "";
String brewfatherPushUrl; // URL For brewfather
String _token = "";
String _token2 = "";
String httpPushUrl; // URL 1 for standard http
String httpPushUrl2; // URL 2 for standard http
String _httpUrl = "";
String _httpHeader[2] = {"Content-Type: application/json", ""};
String _http2Url = "";
String _http2Header[2] = {"Content-Type: application/json", ""};
String _http3Url = "";
String influxDb2Url; // URL for InfluxDB v2
String influxDb2Org; // Organisation for InfluxDB v2
String influxDb2Bucket; // Bucket for InfluxDB v2
String influxDb2Token; // Auth Token for InfluxDB v2
String _influxDb2Url = "";
String _influxDb2Org = "";
String _influxDb2Bucket = "";
String _influxDb2Token = "";
String _mqttUrl = "";
int _mqttPort = 1883;
String _mqttUser = "";
String _mqttPass = "";
String mqttUrl; // Server name
String mqttTopic;
String mqttUser;
String mqttPass;
// Gravity and temperature calculations
String _gravityFormula = "";
bool _gravityTempAdj = false;
char _gravityFormat = 'G';
String gravityFormula;
bool gravityTempAdj; // true, false
char gravityFormat; // G, P
// BLE (ESP32 only)
String _colorBLE;
// Gyro calibration and formula calculation data
RawGyroData _gyroCalibration = {0, 0, 0, 0, 0, 0};
RawFormulaData _formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
// Gyro calibration data
RawGyroData
gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
RawFormulaData formulaData; // Used for creating formula
void debug();
void formatFileSystem();
public:
Config();
const char* getID() { return _id.c_str(); }
const char* getID() { return id.c_str(); }
const char* getMDNS() { return _mDNS.c_str(); }
const char* getMDNS() { return mDNS.c_str(); }
void setMDNS(String s) {
_mDNS = s;
_saveNeeded = true;
mDNS = s;
saveNeeded = true;
}
int getConfigVersion() { return _configVersion; }
const bool isGyroTemp() { return _gyroTemp; }
const bool isGyroTemp() { return gyroTemp; }
void setGyroTemp(bool b) {
_gyroTemp = b;
_saveNeeded = true;
gyroTemp = b;
saveNeeded = true;
}
const char* getOtaURL() { return _otaURL.c_str(); }
const char* getOtaURL() { return otaURL.c_str(); }
void setOtaURL(String s) {
_otaURL = s;
_saveNeeded = true;
otaURL = s;
saveNeeded = true;
}
bool isOtaActive() { return _otaURL.length() ? true : false; }
bool isOtaSSL() { return _otaURL.startsWith("https://"); }
bool isOtaActive() { return otaURL.length() ? true : false; }
bool isOtaSecure() { return otaURL.startsWith("https://"); }
const char* getWifiSSID() { return _wifiSSID.c_str(); }
const char* getWifiSSID() { return wifiSSID.c_str(); }
void setWifiSSID(String s) {
_wifiSSID = s;
_saveNeeded = true;
wifiSSID = s;
saveNeeded = true;
}
const char* getWifiPass() { return _wifiPASS.c_str(); }
const char* getWifiPass() { return wifiPASS.c_str(); }
void setWifiPass(String s) {
_wifiPASS = s;
_saveNeeded = true;
wifiPASS = s;
saveNeeded = true;
}
// Brewfather
const char* getBrewfatherPushUrl() { return _brewfatherPushUrl.c_str(); }
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
void setBrewfatherPushUrl(String s) {
_brewfatherPushUrl = s;
_saveNeeded = true;
brewfatherPushUrl = s;
saveNeeded = true;
}
bool isBrewfatherActive() {
return _brewfatherPushUrl.length() ? true : false;
}
// Token parameter
const char* getToken() { return _token.c_str(); }
void setToken(String s) {
_token = s;
_saveNeeded = true;
}
const char* getToken2() { return _token2.c_str(); }
void setToken2(String s) {
_token2 = s;
_saveNeeded = true;
return brewfatherPushUrl.length() ? true : false;
}
// Standard HTTP
const char* getHttpUrl() { return _httpUrl.c_str(); }
void setHttpUrl(String s) {
_httpUrl = s;
_saveNeeded = true;
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
void setHttpPushUrl(String s) {
httpPushUrl = s;
saveNeeded = true;
}
const char* getHttpHeader(int idx) { return _httpHeader[idx].c_str(); }
void setHttpHeader(String s, int idx) {
_httpHeader[idx] = s;
_saveNeeded = true;
bool isHttpActive() { return httpPushUrl.length() ? true : false; }
bool isHttpSecure() { return httpPushUrl.startsWith("https://"); }
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
void setHttpPushUrl2(String s) {
httpPushUrl2 = s;
saveNeeded = true;
}
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://"); }
const char* getHttp3Url() { return _http3Url.c_str(); }
void setHttp3Url(String s) {
_http3Url = s;
_saveNeeded = true;
}
bool isHttp3Active() { return _http3Url.length() ? true : false; }
bool isHttp3SSL() { return _http3Url.startsWith("https://"); }
bool isHttpActive2() { return httpPushUrl2.length() ? true : false; }
bool isHttpSecure2() { return httpPushUrl2.startsWith("https://"); }
// InfluxDB2
const char* getInfluxDb2PushUrl() { return _influxDb2Url.c_str(); }
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
void setInfluxDb2PushUrl(String s) {
_influxDb2Url = s;
_saveNeeded = true;
influxDb2Url = s;
saveNeeded = true;
}
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
bool isInfluxDb2Active() { return influxDb2Url.length() ? true : false; }
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
void setInfluxDb2PushOrg(String s) {
_influxDb2Org = s;
_saveNeeded = true;
influxDb2Org = s;
saveNeeded = true;
}
const char* getInfluxDb2PushBucket() { return _influxDb2Bucket.c_str(); }
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
void setInfluxDb2PushBucket(String s) {
_influxDb2Bucket = s;
_saveNeeded = true;
influxDb2Bucket = s;
saveNeeded = true;
}
const char* getInfluxDb2PushToken() { return _influxDb2Token.c_str(); }
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
void setInfluxDb2PushToken(String s) {
_influxDb2Token = s;
_saveNeeded = true;
influxDb2Token = s;
saveNeeded = true;
}
// MQTT
const char* getMqttUrl() { return _mqttUrl.c_str(); }
bool isMqttActive() { return mqttUrl.length() ? true : false; }
bool isMqttSecure() { return mqttUrl.endsWith(":8883"); }
const char* getMqttUrl() { return mqttUrl.c_str(); }
void setMqttUrl(String s) {
_mqttUrl = s;
_saveNeeded = true;
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();
_saveNeeded = true;
const char* getMqttTopic() { return mqttTopic.c_str(); }
void setMqttTopic(String s) {
mqttTopic = s;
saveNeeded = true;
}
void setMqttPort(int i) {
_mqttPort = i;
_saveNeeded = true;
}
const char* getMqttUser() { return _mqttUser.c_str(); }
const char* getMqttUser() { return mqttUser.c_str(); }
void setMqttUser(String s) {
_mqttUser = s;
_saveNeeded = true;
mqttUser = s;
saveNeeded = true;
}
const char* getMqttPass() { return _mqttPass.c_str(); }
const char* getMqttPass() { return mqttPass.c_str(); }
void setMqttPass(String s) {
_mqttPass = s;
_saveNeeded = true;
mqttPass = s;
saveNeeded = true;
}
int getSleepInterval() { return _sleepInterval; }
int getSleepInterval() { return sleepInterval; }
void setSleepInterval(int v) {
_sleepInterval = v;
_saveNeeded = true;
sleepInterval = v;
saveNeeded = true;
}
void setSleepInterval(String s) {
_sleepInterval = s.toInt();
_saveNeeded = true;
sleepInterval = s.toInt();
saveNeeded = true;
}
char getTempFormat() { return _tempFormat; }
char getTempFormat() { return tempFormat; }
void setTempFormat(char c) {
if (c == 'C' || c == 'F') {
_tempFormat = c;
_saveNeeded = true;
}
tempFormat = c;
saveNeeded = true;
}
bool isTempC() { return _tempFormat == 'C'; }
bool isTempF() { return _tempFormat == 'F'; }
bool isTempC() { return tempFormat == 'C' ? false : true; }
bool isTempF() { return tempFormat == 'F' ? false : true; }
float getVoltageFactor() { return _voltageFactor; }
float getVoltageFactor() { return voltageFactor; }
void setVoltageFactor(float f) {
_voltageFactor = f;
_saveNeeded = true;
voltageFactor = f;
saveNeeded = true;
}
void setVoltageFactor(String s) {
_voltageFactor = s.toFloat();
_saveNeeded = true;
voltageFactor = s.toFloat();
saveNeeded = true;
}
float getTempSensorAdjC() { return _tempSensorAdjC; }
void setTempSensorAdjC(float f) {
_tempSensorAdjC = f;
_saveNeeded = true;
float getTempSensorAdj() { return tempSensorAdj; }
void setTempSensorAdj(float f) {
tempSensorAdj = f;
saveNeeded = true;
}
void setTempSensorAdjC(String s, float adjustC = 0) {
_tempSensorAdjC = s.toFloat() + adjustC;
_saveNeeded = true;
}
void setTempSensorAdjF(String s, float adjustF = 0) {
_tempSensorAdjC = convertFtoC(s.toFloat() + adjustF);
_saveNeeded = true;
void setTempSensorAdj(String s) {
tempSensorAdj = s.toFloat();
saveNeeded = true;
}
const char* getGravityFormula() { return _gravityFormula.c_str(); }
const char* getGravityFormula() { return gravityFormula.c_str(); }
void setGravityFormula(String s) {
_gravityFormula = s;
_saveNeeded = true;
gravityFormula = s;
saveNeeded = true;
}
bool isGravityTempAdj() { return _gravityTempAdj; }
bool isGravityTempAdj() { return gravityTempAdj; }
void setGravityTempAdj(bool b) {
_gravityTempAdj = b;
_saveNeeded = true;
gravityTempAdj = b;
saveNeeded = true;
}
char getGravityFormat() { return _gravityFormat; }
char getGravityFormat() { return gravityFormat; }
void setGravityFormat(char c) {
if (c == 'G' || c == 'P') {
_gravityFormat = c;
_saveNeeded = true;
}
gravityFormat = c;
saveNeeded = true;
}
bool isGravitySG() { return _gravityFormat == 'G'; }
bool isGravityPlato() { return _gravityFormat == 'P'; }
bool isGravitySG() { return gravityFormat == 'G' ? false : true; }
bool isGravityPlato() { return gravityFormat == 'P' ? false : true; }
const char* getColorBLE() { return _colorBLE.c_str(); }
void setColorBLE(String c) {
_colorBLE = c;
_saveNeeded = true;
}
bool isBLEActive() { return _colorBLE.length() ? true : false; }
bool isWifiPushActive() {
return (isHttpActive() || isHttp2Active() || isHttp3Active() ||
isBrewfatherActive() || isInfluxDb2Active() || isMqttActive())
? true
: false;
}
const RawGyroData& getGyroCalibration() { return _gyroCalibration; }
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
void setGyroCalibration(const RawGyroData& r) {
_gyroCalibration = r;
_saveNeeded = true;
gyroCalibration = r;
saveNeeded = true;
}
const RawFormulaData& getFormulaData() { return _formulaData; }
const RawFormulaData& getFormulaData() { return formulaData; }
void setFormulaData(const RawFormulaData& r) {
_formulaData = r;
_saveNeeded = true;
formulaData = r;
saveNeeded = true;
}
// IO functions
@ -383,12 +341,12 @@ class Config {
bool saveFile();
bool loadFile();
void checkFileSystem();
bool isSaveNeeded() { return _saveNeeded; }
void setSaveNeeded() { _saveNeeded = true; }
bool isSaveNeeded() { return saveNeeded; }
void setSaveNeeded() { saveNeeded = true; }
};
// Global instance created
extern Config myConfig;
extern HardwareConfig myHardwareConfig;
#endif // SRC_CONFIG_HPP_

View File

@ -22,14 +22,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <gyro.hpp>
#include <main.hpp>
#include <helper.hpp>
GyroSensor myGyro;
MPU6050 accelgyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
// calibration
#define SENSOR_MOVING_THREASHOLD 500
#define SENSOR_READ_COUNT 50
#define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
// calibration
// #define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
@ -39,20 +43,19 @@ bool GyroSensor::setup() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
Wire.begin(PIN_SDA, PIN_SCL);
Wire.begin(D3, D4);
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
// compilation difficulties
if (!accelgyro.testConnection()) {
ErrorFileLog errLog;
errLog.addEntry(F("GYRO: Failed to connect to gyro, is it connected?"));
_sensorConnected = false;
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false;
} else {
#if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
#endif
accelgyro.initialize();
_sensorConnected = true;
sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true);
@ -74,10 +77,10 @@ bool GyroSensor::setup() {
// Once we have calibration values stored we just apply them from the
// config.
_calibrationOffset = myConfig.getGyroCalibration();
calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
}
return _sensorConnected;
return sensorConnected;
}
//
@ -196,16 +199,11 @@ float GyroSensor::calculateAngle(RawGyroData &raw) {
az = (static_cast<float>(raw.az)) / 16384;
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
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);
float v = (acos(ay / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
// Log.notice(F("GYRO: angleX= %F." CR), vX);
Log.notice(F("GYRO: angleY= %F." CR), vY);
// Log.notice(F("GYRO: angleZ= %F." CR), vZ);
Log.verbose(F("GYRO: angle = %F." CR), v);
#endif
return vY;
return v;
}
//
@ -217,11 +215,11 @@ bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#endif
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
int threashold = myHardwareConfig.getGyroSensorMovingThreashold();
if (x > threashold || y > threashold || z > threashold) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
y, z);
if (x > SENSOR_MOVING_THREASHOLD || y > SENSOR_MOVING_THREASHOLD ||
z > SENSOR_MOVING_THREASHOLD) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR),
SENSOR_MOVING_THREASHOLD, x, y, z);
return true;
}
@ -236,37 +234,35 @@ bool GyroSensor::read() {
Log.verbose(F("GYRO: Getting new gyro position." CR));
#endif
if (!_sensorConnected) return false;
if (!sensorConnected) return false;
readSensor(
_lastGyroData, myHardwareConfig.getGyroReadCount(),
myHardwareConfig.getGyroReadDelay()); // Last param is unused if
// GYRO_USE_INTERRUPT is defined.
readSensor(lastGyroData, SENSOR_READ_COUNT,
SENSOR_READ_DELAY); // Last param is unused if GYRO_USE_INTERRUPT
// is defined.
// If the sensor is unstable we return false to signal we dont have valid
// value
if (isSensorMoving(_lastGyroData)) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
if (isSensorMoving(lastGyroData)) {
#if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Sensor is moving." CR));
#endif
_validValue = false;
validValue = false;
} else {
_validValue = true;
_angle = calculateAngle(_lastGyroData);
#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);
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);
#endif
}
_sensorTemp = (static_cast<float>(_lastGyroData.temp)) / 340 + 36.53;
sensorTemp = (static_cast<float>(lastGyroData.temp)) / 340 + 36.53;
// The first read value is close to the DS18 value according to my tests, if
// more reads are done then the gyro temp will increase to much
if (_initialSensorTemp == INVALID_TEMPERATURE)
_initialSensorTemp = _sensorTemp;
if (initialSensorTemp == INVALID_TEMPERATURE) initialSensorTemp = sensorTemp;
return _validValue;
return validValue;
}
//
@ -274,10 +270,10 @@ bool GyroSensor::read() {
//
void GyroSensor::dumpCalibration() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), _calibrationOffset.ax,
_calibrationOffset.ay, _calibrationOffset.az);
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), _calibrationOffset.gx,
_calibrationOffset.gy, _calibrationOffset.gz);
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax,
calibrationOffset.ay, calibrationOffset.az);
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx,
calibrationOffset.gy, calibrationOffset.gz);
#endif
}
@ -289,21 +285,19 @@ void GyroSensor::applyCalibration() {
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR));
#endif
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
if ((calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az +
calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz) ==
0) {
ErrorFileLog errLog;
errLog.addEntry(
F("GYRO: No valid calibration values, please calibrate the device."));
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR));
return;
}
accelgyro.setXAccelOffset(_calibrationOffset.ax);
accelgyro.setYAccelOffset(_calibrationOffset.ay);
accelgyro.setZAccelOffset(_calibrationOffset.az);
accelgyro.setXGyroOffset(_calibrationOffset.gx);
accelgyro.setYGyroOffset(_calibrationOffset.gy);
accelgyro.setZGyroOffset(_calibrationOffset.gz);
accelgyro.setXAccelOffset(calibrationOffset.ax);
accelgyro.setYAccelOffset(calibrationOffset.ay);
accelgyro.setZAccelOffset(calibrationOffset.az);
accelgyro.setXGyroOffset(calibrationOffset.gx);
accelgyro.setYGyroOffset(calibrationOffset.gy);
accelgyro.setZGyroOffset(calibrationOffset.gz);
}
//
@ -323,15 +317,15 @@ void GyroSensor::calibrateSensor() {
accelgyro.PrintActiveOffsets();
Serial.print(CR);
_calibrationOffset.ax = accelgyro.getXAccelOffset();
_calibrationOffset.ay = accelgyro.getYAccelOffset();
_calibrationOffset.az = accelgyro.getZAccelOffset();
_calibrationOffset.gx = accelgyro.getXGyroOffset();
_calibrationOffset.gy = accelgyro.getYGyroOffset();
_calibrationOffset.gz = accelgyro.getZGyroOffset();
calibrationOffset.ax = accelgyro.getXAccelOffset();
calibrationOffset.ay = accelgyro.getYAccelOffset();
calibrationOffset.az = accelgyro.getZAccelOffset();
calibrationOffset.gx = accelgyro.getXGyroOffset();
calibrationOffset.gy = accelgyro.getYGyroOffset();
calibrationOffset.gz = accelgyro.getZGyroOffset();
// Save the calibrated values
myConfig.setGyroCalibration(_calibrationOffset);
myConfig.setGyroCalibration(calibrationOffset);
myConfig.saveFile();
}
@ -386,17 +380,17 @@ void GyroSensor::debug() {
}
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR),
accelgyro.getXAccelOffset(), _calibrationOffset.az);
accelgyro.getXAccelOffset(), calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR),
accelgyro.getYAccelOffset(), _calibrationOffset.ay);
accelgyro.getYAccelOffset(), calibrationOffset.ay);
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR),
accelgyro.getZAccelOffset(), _calibrationOffset.az);
accelgyro.getZAccelOffset(), calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR),
accelgyro.getXGyroOffset(), _calibrationOffset.gx);
accelgyro.getXGyroOffset(), calibrationOffset.gx);
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR),
accelgyro.getYGyroOffset(), _calibrationOffset.gy);
accelgyro.getYGyroOffset(), calibrationOffset.gy);
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR),
accelgyro.getZGyroOffset(), _calibrationOffset.gz);
accelgyro.getZGyroOffset(), calibrationOffset.gz);
#endif
}

View File

@ -27,10 +27,13 @@ SOFTWARE.
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
// #define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
// Includes
#include <Arduino.h>
#include <MPU6050.h>
#include <config.hpp>
// Classes
struct RawGyroDataL { // Used for average multiple readings
int32_t ax; // Raw Acceleration
int32_t ay;
@ -47,13 +50,13 @@ struct RawGyroDataL { // Used for average multiple readings
class GyroSensor {
private:
bool _sensorConnected = false;
bool _validValue = false;
float _angle = 0;
float _sensorTemp = 0;
float _initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData _calibrationOffset;
RawGyroData _lastGyroData;
bool sensorConnected = false;
bool validValue = false;
float angle = 0;
float sensorTemp = 0;
float initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData calibrationOffset;
RawGyroData lastGyroData;
void debug();
void applyCalibration();
@ -68,15 +71,16 @@ class GyroSensor {
bool read();
void calibrateSensor();
const RawGyroData &getLastGyroData() { return _lastGyroData; }
float getAngle() { return _angle; }
float getSensorTempC() { return _sensorTemp; }
float getInitialSensorTempC() { return _initialSensorTemp; }
bool isConnected() { return _sensorConnected; }
bool hasValue() { return _validValue; }
const RawGyroData &getLastGyroData() { return lastGyroData; }
float getAngle() { return angle; }
float getSensorTempC() { return sensorTemp; }
float getInitialSensorTempC() { return initialSensorTemp; }
bool isConnected() { return sensorConnected; }
bool hasValue() { return validValue; }
void enterSleep();
};
// Global instance created
extern GyroSensor myGyro;
#endif // SRC_GYRO_HPP_

View File

@ -21,157 +21,25 @@ 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)
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#else // defined (ESP32)
#include <HTTPClient.h>
#include <WiFi.h>
#endif
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <main.hpp>
#include <tempsensor.hpp>
#include <wifi.hpp>
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
//
double convertToPlato(double sg) {
if (sg) return 259 - (259 / sg);
return 0;
}
//
// Convert plato to sg
//
double convertToSG(double plato) { return 259 / (259 - plato); }
//
// Conversion to F
//
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
//
// Conversion to C
//
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;
err += String(CR);
Log.error(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(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);
void printHeap() {
#if LOG_LEVEL == 6
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
ESP.getFreeSketchSpace() / 1024);
#endif
}
@ -179,7 +47,7 @@ void printHeap(String prefix) {
// Enter deep sleep for the defined duration (Argument is seconds)
//
void deepSleep(int t) {
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
#endif
uint32_t wake = t * 1000000;
@ -190,7 +58,7 @@ void deepSleep(int t) {
// Print the build options used
//
void printBuildOptions() {
Log.notice(F("Build options: %s (%s) LOGLEVEL %d "
Log.notice(F("Build options: %s LOGLEVEL %d "
#ifdef SKIP_SLEEPMODE
"SKIP_SLEEP "
#endif
@ -204,7 +72,7 @@ void printBuildOptions() {
"OTA "
#endif
CR),
CFG_APPVER, CFG_GITREV, LOG_LEVEL);
CFG_APPVER, LOG_LEVEL);
}
//
@ -236,20 +104,12 @@ void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce
// the voltage (from max 5V)
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
int v = analogRead(PIN_A0);
// 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)
_batteryLevel = ((3.3 / 1023) * v) * factor;
#else // defined (ESP32)
_batteryLevel = ((3.3 / 4095) * v) * factor;
#endif
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
int v = analogRead(A0);
batteryLevel = ((3.3 / 1023) * v) * factor;
#if LOG_LEVEL == 6
Log.verbose(
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
factor, v, _batteryLevel);
factor, v, batteryLevel);
#endif
}
@ -317,24 +177,23 @@ void PerfLogging::print() {
void PerfLogging::pushInflux() {
if (!myConfig.isInfluxDb2Active()) return;
WiFiClient wifi;
WiFiClient client;
HTTPClient http;
String serverPath =
String(myConfig.getInfluxDb2PushUrl()) +
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
http.begin(wifi, serverPath);
http.begin(client, 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[150];
char buf[100];
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
myConfig.getID());
body += &buf[0];
@ -356,30 +215,17 @@ 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,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()), 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
snprintf(
&buf[0], sizeof(buf),
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f",
myGyro.getAngle(), myGyro.getLastGyroData().ax,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()));
body += &buf[0];
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
// Log.notice(F("PERF: data %s." CR), body.c_str() );
#if LOG_LEVEL == 6
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
Log.verbose(F("PERF: data %s." CR), body.c_str());
#endif
@ -387,23 +233,18 @@ 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) {
#if !defined(HELPER_DISABLE_LOGGING)
Log.notice(
F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
httpResponseCode);
#endif
} else {
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
httpResponseCode);
}
http.end();
wifi.stop();
tcp_cleanup();
}
#endif // COLLECT_PERFDATA
@ -426,80 +267,4 @@ float reduceFloatPrecision(float f, int dec) {
return atof(&buffer[0]);
}
//
// urlencode
//
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
//
String urlencode(String str) {
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++) {
c = str.charAt(i);
if (isalnum(c)) {
encodedString += c;
} else {
code1 = (c & 0xf) + '0';
if ((c & 0xf) > 9) {
code1 = (c & 0xf) - 10 + 'A';
}
c = (c >> 4) & 0xf;
code0 = c + '0';
if (c > 9) {
code0 = c - 10 + 'A';
}
encodedString += '%';
encodedString += code0;
encodedString += code1;
}
}
// 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 >= 'a' && c <= 'f') {
return ((unsigned char)c - 'a' + 10);
}
if (c >= 'A' && c <= 'F') {
return ((unsigned char)c - 'A' + 10);
}
return (0);
}
//
// urlencode string
//
String urldecode(String str) {
String encodedString;
encodedString.reserve(str.length());
encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < static_cast<int>(str.length()); i++) {
c = str.charAt(i);
if (c == '%') {
i++;
code0 = str.charAt(i);
i++;
code1 = str.charAt(i);
c = (h2int(code0) << 4) | h2int(code1);
encodedString += c;
} else {
encodedString += c;
}
}
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
return encodedString;
}
// EOF

View File

@ -25,15 +25,7 @@ SOFTWARE.
#define SRC_HELPER_HPP_
// Includes
#include <main.hpp>
#define ERR_FILENAME "/error.log"
#define ERR_COUNT 15
#define RUNTIME_FILENAME "/runtime.log"
// tcp cleanup
void tcp_cleanup();
#include <ArduinoLog.h>
// Sleep mode
void deepSleep(int t);
@ -41,16 +33,6 @@ void deepSleep(int t);
// Show build options
void printBuildOptions();
// Data conversion
double convertToPlato(double sg);
double convertToSG(double plato);
float convertCtoF(float c);
float convertFtoC(float f);
// url encode/decode
String urldecode(String str);
String urlencode(String str);
// Float to String
char* convertFloatToString(float f, char* buf, int dec = 2);
float reduceFloatPrecision(float f, int dec = 2);
@ -58,7 +40,7 @@ float reduceFloatPrecision(float f, int dec = 2);
// Logging via serial
void printTimestamp(Print* _logOutput, int _logLevel);
void printNewline(Print* _logOutput);
void printHeap(String prefix = "HELP");
void printHeap();
// Classes
class SerialDebug {
@ -67,37 +49,13 @@ 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;
float batteryLevel;
public:
void read();
float getVoltage() { return _batteryLevel; }
float getVoltage() { return batteryLevel; }
};
#if defined(COLLECT_PERFDATA)
@ -170,7 +128,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()

View File

@ -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.
*/
#include <ble.hpp>
#include <LittleFS.h>
#include <calc.hpp>
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <main.hpp>
#include <pushtarget.hpp>
#include <tempsensor.hpp>
#include <webserver.hpp>
@ -41,7 +41,6 @@ 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
@ -64,18 +63,14 @@ void checkSleepMode(float angle, float volt) {
if (!g.ax && !g.ay && !g.az && !g.gx && !g.gy && !g.gz) {
// Will not enter sleep mode if: no calibration data
#if !defined(MAIN_DISABLE_LOGGING)
Log.notice(
F("MAIN: Missing calibration data, so forcing webserver to be "
"active." CR));
#endif
runMode = RunMode::configurationMode;
} else if (sleepModeAlwaysSkip) {
// Check if the flag from the UI has been set, the we force configuration
// mode.
#if !defined(MAIN_DISABLE_LOGGING)
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR));
#endif
runMode = RunMode::configurationMode;
} else if ((volt < 4.15 && (angle > 85 && angle < 95)) || (volt > 4.15)) {
runMode = RunMode::configurationMode;
@ -85,18 +80,14 @@ void checkSleepMode(float angle, float volt) {
switch (runMode) {
case RunMode::configurationMode:
#if !defined(MAIN_DISABLE_LOGGING)
Log.notice(F("MAIN: run mode CONFIG (angle=%F volt=%F)." CR), angle,
volt);
#endif
break;
case RunMode::wifiSetupMode:
break;
case RunMode::gravityMode:
#if !defined(MAIN_DISABLE_LOGGING)
Log.notice(F("MAIN: run mode GRAVITY (angle=%F volt=%F)." CR), angle,
volt);
#endif
break;
}
}
@ -112,41 +103,21 @@ void setup() {
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
// Add a delay so that serial is started.
// delay(3000);
#if defined(ESP8266)
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
#else // defined (ESP32)
#endif
#endif
// Main startup
#if defined(ESP8266)
Log.notice(F("Main: Started setup for %s." CR),
String(ESP.getChipId(), HEX).c_str());
#else // defined (ESP32)
char buf[20];
uint32_t chipId = 0;
for (int i = 0; i < 17; i = i + 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
Log.notice(F("Main: Started setup for %s." CR), &buf[0]);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);
#endif
printBuildOptions();
LOG_PERF_START("main-config-load");
myConfig.checkFileSystem();
myConfig.loadFile();
myWifi.init();
myHardwareConfig.loadFile();
LOG_PERF_STOP("main-config-load");
// Setup watchdog
#if defined(ESP8266)
ESP.wdtDisable();
ESP.wdtEnable(5000); // 5 seconds
#else // defined (ESP32)
#endif
// No stored config, move to portal
if (!myWifi.hasConfig()) {
@ -161,9 +132,6 @@ void setup() {
runMode = RunMode::wifiSetupMode;
}
bool needWifi = true; // Under ESP32 we dont need wifi if only BLE is active
// in gravityMode
// Do this setup for all modes exect wifi setup
switch (runMode) {
case RunMode::wifiSetupMode:
@ -171,10 +139,16 @@ void setup() {
break;
default:
LOG_PERF_START("main-wifi-connect");
myWifi.connect();
LOG_PERF_STOP("main-wifi-connect");
LOG_PERF_START("main-temp-setup");
myTempSensor.setup();
LOG_PERF_STOP("main-temp-setup");
if (!myGyro.setup()) {
ErrorFileLog errLog;
errLog.addEntry(
F("MAIN: Failed to initialize the gyro, is it connected?"));
Log.error(F("Main: Failed to initialize the gyro." CR));
} else {
LOG_PERF_START("main-gyro-read");
myGyro.read();
@ -183,25 +157,6 @@ void setup() {
myBatteryVoltage.read();
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
#if defined(ESP32)
if (!myConfig.isWifiPushActive() && runMode == RunMode::gravityMode) {
Log.notice(
F("Main: Wifi is not needed in gravity mode, skipping "
"connection." CR));
needWifi = false;
}
#endif
if (needWifi) {
LOG_PERF_START("main-wifi-connect");
myWifi.connect();
LOG_PERF_STOP("main-wifi-connect");
}
LOG_PERF_START("main-temp-setup");
myTempSensor.setup();
LOG_PERF_STOP("main-temp-setup");
break;
}
@ -214,7 +169,7 @@ void setup() {
if (myWifi.checkFirmwareVersion()) myWifi.updateFirmware();
LOG_PERF_STOP("main-wifi-ota");
#endif
myWebServerHandler
myWebServer
.setupWebServer(); // Takes less than 4ms, so skip this measurement
}
@ -225,10 +180,19 @@ void setup() {
break;
}
// Check if we need SSL for any of the push targets
if (myConfig.isHttpSecure() || myConfig.isHttpSecure2() || myConfig.isMqttSecure()) {
LOG_PERF_START("main-cert-store");
myWifi.initCertstore();
LOG_PERF_STOP("main-cert-store");
LOG_PERF_START("main-cert-ntp");
myWifi.initNTP();
LOG_PERF_STOP("main-cert-ntp");
}
LOG_PERF_STOP("main-setup");
Log.notice(F("Main: Setup completed." CR));
pushMillis = stableGyroMillis =
millis(); // Dont include time for wifi connection
stableGyroMillis = millis(); // Dont include time for wifi connection
}
//
@ -253,50 +217,28 @@ bool loopReadGravity() {
stableGyroMillis = millis(); // Reset timer
LOG_PERF_START("loop-temp-read");
float tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
float temp = myTempSensor.getTempC(myConfig.isGyroTemp());
LOG_PERF_STOP("loop-temp-read");
float gravitySG = calculateGravity(angle, tempC);
float corrGravitySG = gravityTemperatureCorrectionC(gravitySG, tempC);
float gravity = calculateGravity(angle, temp);
float corrGravity =
gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat());
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%FC, gravity=%F, "
"corr_gravity=%F." CR),
angle, tempC, gravitySG, corrGravitySG);
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, "
"corr=%F." CR),
angle, temp, gravity, corrGravity);
#endif
bool pushExpired = (abs((int32_t)(millis() - pushMillis)) >
(myConfig.getSleepInterval() * 1000));
if (pushExpired || runMode == RunMode::gravityMode) {
pushMillis = millis();
LOG_PERF_START("loop-push");
#if defined(ESP32)
if (myConfig.isBLEActive()) {
BleSender ble(myConfig.getColorBLE());
ble.sendData(convertCtoF(tempC), gravitySG);
Log.notice(F("MAIN: Broadcast data over bluetooth." CR));
}
#endif
if (myWifi.isConnected()) { // no need to try if there is no wifi
// connection.
PushTarget push;
push.sendAll(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();
}
}
LOG_PERF_START("loop-push");
// Force the transmission if we are going to sleep
myPushTarget.send(angle, gravity, corrGravity, temp,
(millis() - runtimeMillis) / 1000,
runMode == RunMode::gravityMode ? true : false);
LOG_PERF_STOP("loop-push");
return true;
} else {
Log.error(F("MAIN: No gyro value found, the device might be moving." CR));
Log.error(F("Main: No gyro value." CR));
}
return false;
}
@ -309,17 +251,20 @@ void loopGravityOnInterval() {
if (abs((int32_t)(millis() - loopMillis)) > interval) {
loopReadGravity();
loopMillis = millis();
// printHeap("MAIN");
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb HeapFrag %d %%." CR),
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024,
ESP.getHeapFragmentation());
#endif
LOG_PERF_START("loop-gyro-read");
myGyro.read();
LOG_PERF_STOP("loop-gyro-read");
myBatteryVoltage.read();
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
LOG_PERF_PUSH();
}
}
bool skipRunTimeLog = false;
//
// Main loop that determines if device should go to sleep
//
@ -327,11 +272,6 @@ 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);
@ -349,20 +289,15 @@ void goToSleep(int sleepInterval) {
void loop() {
switch (runMode) {
case RunMode::configurationMode:
myWebServerHandler.loop();
myWebServer.loop();
myWifi.loop();
loopGravityOnInterval();
// If we switched mode, dont include this in the log.
if (runMode != RunMode::configurationMode) skipRunTimeLog = true;
break;
case RunMode::gravityMode:
// If we didnt get a wifi connection, we enter sleep for a short time to
// conserve battery.
if (!myWifi.isConnected() &&
myConfig.isWifiPushActive()) { // no connection to wifi and we have
// defined push targets.
if (!myWifi.isConnected()) { // no connection to wifi
Log.notice(
F("MAIN: No connection to wifi established, sleeping for 60s." CR));
myWifi.stopDoubleReset();

View File

@ -1,60 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
*/
#ifndef SRC_MAIN_HPP_
#define SRC_MAIN_HPP_
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ArduinoLog.h>
#include <stdlib.h>
#if defined(ESP8266)
#include <LittleFS.h>
#define ESP_RESET ESP.reset
#define PIN_SDA D3
#define PIN_SCL D4
#define PIN_DS D6
#define PIN_LED 2
// #define PIN_A0 A0
#else // defined (ESP32)
#if defined(ESPRESSIF32_20)
#include <LittleFS.h>
#else
#include <LITTLEFS.h>
#define LittleFS LITTLEFS
#endif
#include <FS.h>
#define ESPhttpUpdate httpUpdate
#define ESP_RESET ESP.restart
#define ESP8266WebServer WebServer
#define PIN_SDA 17
#define PIN_SCL 16
#define PIN_DS 19
#define PIN_A0 36
#define PIN_LED 2
#endif
#define PIN_LED 2
#endif // SRC_MAIN_HPP_

View File

@ -21,62 +21,65 @@ 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)
#include <ESP8266mDNS.h>
#else // defined (ESP32)
#endif
#include <MQTT.h>
#include <config.hpp>
#include <helper.hpp>
#include <pushtarget.hpp>
#include <MQTT.h>
#include <config.hpp>
#include <gyro.hpp>
#include <wifi.hpp>
//
// Send the data to targets
//
void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
float tempC, float runTime) {
printHeap("PUSH");
_http.setReuse(false);
_httpSecure.setReuse(false);
PushTarget myPushTarget;
TemplatingEngine engine;
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
//
// Send the pressure value
//
void PushTarget::send(float angle, float gravity, float corrGravity, float temp,
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;
}
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: Sending data." CR));
#endif
ms = millis();
if (myConfig.isBrewfatherActive()) {
LOG_PERF_START("push-brewfather");
sendBrewfather(engine);
sendBrewfather(angle, gravity, corrGravity, temp);
LOG_PERF_STOP("push-brewfather");
}
if (myConfig.isHttpActive()) {
LOG_PERF_START("push-http");
sendHttpPost(engine, myConfig.isHttpSSL(), 0);
sendHttp(myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp,
runTime);
LOG_PERF_STOP("push-http");
}
if (myConfig.isHttp2Active()) {
if (myConfig.isHttpActive2()) {
LOG_PERF_START("push-http2");
sendHttpPost(engine, myConfig.isHttp2SSL(), 1);
sendHttp(myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp,
runTime);
LOG_PERF_STOP("push-http2");
}
if (myConfig.isHttp3Active()) {
LOG_PERF_START("push-http3");
sendHttpGet(engine, myConfig.isHttp3SSL());
LOG_PERF_STOP("push-http3");
}
if (myConfig.isInfluxDb2Active()) {
LOG_PERF_START("push-influxdb2");
sendInfluxDb2(engine);
sendInfluxDb2(angle, gravity, corrGravity, temp, runTime);
LOG_PERF_STOP("push-influxdb2");
}
if (myConfig.isMqttActive()) {
LOG_PERF_START("push-mqtt");
sendMqtt(engine, myConfig.isMqttSSL());
sendMqtt(angle, gravity, corrGravity, temp, runTime);
LOG_PERF_STOP("push-mqtt");
}
}
@ -84,339 +87,256 @@ void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
//
// Send to influx db v2
//
void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
void PushTarget::sendInfluxDb2(float angle, float gravity, float corrGravity,
float temp, float runTime) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to influxdb2." CR));
Log.notice(
F("PUSH: Sending values to influxdb2 angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
_lastCode = 0;
_lastSuccess = false;
WiFiClient client;
HTTPClient http;
String serverPath =
String(myConfig.getInfluxDb2PushUrl()) +
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
String doc = engine.create(TemplatingEngine::TEMPLATE_INFLUX);
_http.begin(_wifi, serverPath);
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
http.begin(client, serverPath);
// Create body for influxdb2
char buf[1024];
snprintf(&buf[0], sizeof(buf),
"measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,"
"rssi=%d\n",
// TODO: Add support for plato format
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
myConfig.isGravityTempAdj() ? corrGravity : gravity, corrGravity,
angle, temp, myBatteryVoltage.getVoltage(), WiFi.RSSI());
#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());
Log.verbose(F("PUSH: data %s." CR), &buf[0]);
#endif
// Send HTTP POST request
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
_http.addHeader(F("Authorization"), auth.c_str());
_lastCode = _http.POST(doc);
http.addHeader(F("Authorization"), auth.c_str());
int httpResponseCode = http.POST(&buf[0]);
if (_lastCode == 204) {
_lastSuccess = true;
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), _lastCode);
if (httpResponseCode == 204) {
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
httpResponseCode);
} else {
ErrorFileLog errLog;
errLog.addEntry("PUSH: Influxdb push failed response=" + String(_lastCode));
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR),
httpResponseCode);
}
_http.end();
_wifi.stop();
tcp_cleanup();
http.end();
}
//
// Send data to brewfather
//
void PushTarget::sendBrewfather(TemplatingEngine& engine) {
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity,
float temp) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to brewfather" CR));
Log.notice(
F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
_lastCode = 0;
_lastSuccess = false;
DynamicJsonDocument doc(300);
//
// {
// "name": "YourDeviceName", // Required field, this will be the ID in
// Brewfather "temp": 20.32, "aux_temp": 15.61, // Fridge Temp
// "ext_temp": 6.51, // Room Temp
// "temp_unit": "C", // C, F, K
// "gravity": 1.042,
// "gravity_unit": "G", // G, P
// "pressure": 10,
// "pressure_unit": "PSI", // PSI, BAR, KPA
// "ph": 4.12,
// "bpm": 123, // Bubbles Per Minute
// "comment": "Hello World",
// "beer": "Pale Ale"
// "battery": 4.98
// }
//
doc["name"] = myConfig.getMDNS();
doc["temp"] = reduceFloatPrecision(temp, 1);
doc["temp_unit"] = String(myConfig.getTempFormat());
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
// TODO: Add support for plato format
doc["gravity"] = reduceFloatPrecision(
myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
doc["gravity_unit"] = myConfig.isGravitySG() ? "G" : "P";
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getBrewfatherPushUrl();
String doc = engine.create(TemplatingEngine::TEMPLATE_BREWFATHER);
_http.begin(_wifi, serverPath);
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
// Your Domain name with URL path or IP address with path
http.begin(client, serverPath);
String json;
serializeJson(doc, json);
#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());
Log.verbose(F("PUSH: json %s." CR), json.c_str());
#endif
_http.addHeader(F("Content-Type"), F("application/json"));
_lastCode = _http.POST(doc);
// Send HTTP POST request
http.addHeader(F("Content-Type"), F("application/json"));
int httpResponseCode = http.POST(json);
if (_lastCode == 200) {
_lastSuccess = true;
if (httpResponseCode == 200) {
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
_lastCode);
httpResponseCode);
} else {
ErrorFileLog errLog;
errLog.addEntry("PUSH: Brewfather push failed response=" +
String(_lastCode));
Log.error(F("PUSH: Brewfather push failed, response=%d" CR),
httpResponseCode);
}
_http.end();
_wifi.stop();
tcp_cleanup();
http.end();
}
//
// Send data to http target
//
//
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
if (!header.length()) return;
void PushTarget::createIspindleFormat(DynamicJsonDocument &doc, float angle,
float gravity, float corrGravity,
float temp, float runTime) {
// Use iSpindle format for compatibility
doc["name"] = myConfig.getMDNS();
doc["ID"] = myConfig.getID();
doc["token"] = "gravmon";
doc["interval"] = myConfig.getSleepInterval();
doc["temperature"] = reduceFloatPrecision(temp, 1);
doc["temp-units"] = String(myConfig.getTempFormat());
// TODO: Add support for plato format
doc["gravity"] = reduceFloatPrecision(
myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
doc["corr-gravity"] = reduceFloatPrecision(corrGravity, 4);
doc["angle"] = reduceFloatPrecision(angle, 2);
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
doc["rssi"] = WiFi.RSSI();
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);
}
// Some additional information
doc["gravity-units"] = "SG";
doc["run-time"] = reduceFloatPrecision(runTime, 2);
}
//
// Send data to http target using POST
// Send data to http target
//
void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
int index) {
void PushTarget::sendHttp(String serverPath, float angle, float gravity,
float corrGravity, float temp, float runTime) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to http (%s)" CR),
index ? "http2" : "http");
Log.notice(
F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
_lastCode = 0;
_lastSuccess = false;
String serverPath, doc;
DynamicJsonDocument doc(256);
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
if (index == 0) {
serverPath = myConfig.getHttpUrl();
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP1);
WiFiClient client;
WiFiClientSecure clientSecure;
HTTPClient http;
if (serverPath.startsWith("https://")) {
/*if (myWifi.getCertCount() > 0) {
// Allow secure channel, with CA validation
clientSecure.setCertStore(myWifi.getCertStore());
Log.notice(F("PUSH: SSL enabled using certificate store." CR));
} else*/ {
// Allow secure channel, but without certificate validation
clientSecure.setInsecure();
Log.notice(F("PUSH: SSL enabled without validation." CR));
}
http.begin(clientSecure, serverPath);
} else {
serverPath = myConfig.getHttp2Url();
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP2);
http.begin(client, serverPath);
}
String json;
serializeJson(doc, json);
#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());
Log.verbose(F("PUSH: json %s." CR), json.c_str());
#endif
if (isSecure) {
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
_wifiSecure.setInsecure();
// Send HTTP POST request
http.addHeader(F("Content-Type"), F("application/json"));
int httpResponseCode = http.POST(json);
#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));
}
_lastCode = _httpSecure.POST(doc);
if (httpResponseCode == 200) {
Log.notice(F("PUSH: HTTP push successful, response=%d" CR),
httpResponseCode);
} 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));
}
_lastCode = _http.POST(doc);
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
}
if (_lastCode == 200) {
_lastSuccess = true;
Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode);
} else {
ErrorFileLog errLog;
errLog.addEntry("PUSH: HTTP post failed response=" + String(_lastCode) +
String(index == 0 ? " (http)" : " (http2)"));
}
if (isSecure) {
_httpSecure.end();
_wifiSecure.stop();
} else {
_http.end();
_wifi.stop();
}
tcp_cleanup();
http.end();
}
//
// Send data to http target using GET
// Send data to http target
//
void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
void PushTarget::sendMqtt(float angle, float gravity, float corrGravity,
float temp, float runTime) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to http3" CR));
#endif
_lastCode = 0;
_lastSuccess = false;
String serverPath;
serverPath = myConfig.getHttp3Url();
serverPath += engine.create(TemplatingEngine::TEMPLATE_HTTP3);
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
Log.notice(
F("PUSH: Sending values to mqtt angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
if (isSecure) {
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
_wifiSecure.setInsecure();
DynamicJsonDocument doc(256);
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
#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);
WiFiClient client;
WiFiClientSecure clientSecure;
MQTTClient mqtt(512); // Maximum message size
if (_wifiSecure.probeMaxFragmentLength(host, 443, 512)) {
Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR));
_wifiSecure.setBufferSizes(512, 512);
if (myConfig.isMqttSecure()) {
if (myWifi.getCertCount() > 0) {
// Allow secure channel, with CA validation
clientSecure.setCertStore(myWifi.getCertStore());
Log.notice(F("PUSH: SSL enabled using certificate store." CR));
} else {
// Allow secure channel, but without certificate validation
clientSecure.setInsecure();
Log.notice(F("PUSH: SSL enabled without validation." CR));
}
#endif
_httpSecure.begin(_wifiSecure, serverPath);
_httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
_lastCode = _httpSecure.GET();
String url = myConfig.getMqttUrl();
url.replace(":8883", "");
mqtt.begin(url.c_str(), 8883, clientSecure);
} else {
_http.begin(_wifi, serverPath);
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
_lastCode = _http.GET();
}
if (_lastCode == 200) {
_lastSuccess = true;
Log.notice(F("PUSH: HTTP get successful, response=%d" CR), _lastCode);
} else {
ErrorFileLog errLog;
errLog.addEntry("PUSH: HTTP get failed response=" + String(_lastCode));
}
if (isSecure) {
_httpSecure.end();
_wifiSecure.stop();
} else {
_http.end();
_wifi.stop();
}
tcp_cleanup();
}
//
// Send data to mqtt target
//
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to mqtt." CR));
#endif
_lastCode = 0;
_lastSuccess = false;
MQTTClient mqtt(512);
String host = myConfig.getMqttUrl();
String doc = engine.create(TemplatingEngine::TEMPLATE_MQTT);
int port = myConfig.getMqttPort();
if (myConfig.isMqttSSL()) {
Log.notice(F("PUSH: MQTT, SSL enabled without validation." CR));
_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(host.c_str(), port, _wifi);
mqtt.begin(myConfig.getMqttUrl(), client);
}
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
myConfig.getMqttPass());
String json;
serializeJson(doc, json);
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: url %s." CR), myConfig.getMqttUrl());
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
Log.verbose(F("PUSH: json %s." CR), json.c_str());
#endif
// Send MQQT message(s)
mqtt.setTimeout(myHardwareConfig.getPushTimeout()); // 10 seconds timeout
int lines = 1;
// Find out how many lines are in the document. Each line is one
// topic/message. | is used as new line.
for (unsigned int i = 0; i < doc.length() - 1; i++) {
if (doc.charAt(i) == '|') lines++;
}
int index = 0;
while (lines) {
int next = doc.indexOf('|', index);
String line = doc.substring(index, next);
// Each line equals one topic post, format is <topic>:<value>
String topic = line.substring(0, line.indexOf(":"));
String value = line.substring(line.indexOf(":") + 1);
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(),
value.c_str());
#endif
if (mqtt.publish(topic, value)) {
_lastSuccess = true;
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
_lastCode = 0;
} else {
_lastCode = mqtt.lastError();
ErrorFileLog errLog;
errLog.addEntry("PUSH: MQTT push on " + topic +
" failed error=" + String(mqtt.lastError()));
}
index = next + 1;
lines--;
// Send MQQT message
mqtt.setTimeout(10); // 10 seconds timeout
if (mqtt.publish(myConfig.getMqttTopic(), json)) {
Log.notice(F("PUSH: MQTT publish successful" CR));
} else {
Log.error(F("PUSH: MQTT publish failed err=%d, ret=%d" CR),
mqtt.lastError(), mqtt.returnCode());
}
mqtt.disconnect();
if (isSecure) {
_wifiSecure.stop();
} else {
_wifi.stop();
}
tcp_cleanup();
}
// EOF

View File

@ -24,48 +24,39 @@ SOFTWARE.
#ifndef SRC_PUSHTARGET_HPP_
#define SRC_PUSHTARGET_HPP_
#include <templating.hpp>
#if defined(ESP8266)
// Includes
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#else // defined (ESP32)
#include <HTTPClient.h>
#endif
#include <ESP8266WiFi.h>
#include <helper.hpp>
// Classes
class PushTarget {
private:
WiFiClient _wifi;
WiFiClientSecure _wifiSecure;
HTTPClient _http;
HTTPClient _httpSecure;
int _lastCode;
bool _lastSuccess;
uint32_t ms; // Used to check that we do not post to often
void sendHttpPost(TemplatingEngine& engine, bool isSecure, int index);
void sendHttpGet(TemplatingEngine& engine, bool isSecure);
void addHttpHeader(HTTPClient& http, String header);
void sendBrewfather(float angle, float gravity, float corrGravity,
float temp);
void sendHttp(String serverPath, float angle, float gravity,
float corrGravity, float temp, float runTime);
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp,
float runTime);
void sendMqtt(float angle, float gravity, float corrGravity, float temp,
float runTime);
void createIspindleFormat(DynamicJsonDocument &doc, float angle,
float gravity, float corrGravity, float temp,
float runTime);
public:
void sendAll(float angle, float gravitySG, float corrGravitySG, float tempC,
float runTime);
void sendBrewfather(TemplatingEngine& engine);
void sendHttp1(TemplatingEngine& engine, bool isSecure) {
sendHttpPost(engine, isSecure, 0);
}
void sendHttp2(TemplatingEngine& engine, bool isSecure) {
sendHttpPost(engine, isSecure, 1);
}
void sendHttp3(TemplatingEngine& engine, bool isSecure) {
sendHttpGet(engine, isSecure);
}
void sendInfluxDb2(TemplatingEngine& engine);
void sendMqtt(TemplatingEngine& engine, bool isSecure);
int getLastCode() { return _lastCode; }
bool getLastSuccess() { return _lastSuccess; }
PushTarget() { ms = millis(); }
void send(float angle, float gravity, float corrGravity, float temp,
float runTime, bool force = false);
};
extern PushTarget myPushTarget;
#endif // SRC_PUSHTARGET_HPP_
// EOF

View File

@ -21,25 +21,20 @@ 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)
#define INCBIN_OUTPUT_SECTION ".irom.text"
#endif
#include <incbin.h>
#include <resources.hpp>
#if defined(EMBED_HTML)
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
INCBIN(IndexHtm, "data/index.min.htm");
INCBIN(DeviceHtm, "data/device.min.htm");
INCBIN(ConfigHtm, "data/config.min.htm");
INCBIN(CalibrationHtm, "data/calibration.min.htm");
INCBIN(FormatHtm, "data/format.min.htm");
INCBIN(TestHtm, "data/test.min.htm");
INCBIN(AboutHtm, "data/about.min.htm");
#else
// Minium web interface for uploading htm files
INCBIN(UploadHtm, "data/upload.min.htm");
#endif
INCBIN(FirmwareHtm, "data/firmware.min.htm");
// Minium web interface for uploading htm files, also used to upload certificate store.
INCBIN(UploadHtm, "data/upload.min.htm");
// EOF

View File

@ -1,96 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
*/
#ifndef SRC_RESOURCES_HPP_
#define SRC_RESOURCES_HPP_
// 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_TOKEN2 "token2"
#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_HTTP3 "http-push3"
#define PARAM_PUSH_INFLUXDB2 "influxdb2-push"
#define PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
#define PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
#define PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth"
#define PARAM_PUSH_MQTT "mqtt-push"
#define PARAM_PUSH_MQTT_USER "mqtt-user"
#define PARAM_PUSH_MQTT_PASS "mqtt-pass"
#define PARAM_PUSH_MQTT_PORT "mqtt-port"
#define PARAM_SLEEP_INTERVAL "sleep-interval"
#define PARAM_TEMPFORMAT "temp-format"
#define PARAM_VOLTAGEFACTOR "voltage-factor"
#define PARAM_GRAVITY_FORMULA "gravity-formula"
#define PARAM_GRAVITY_FORMAT "gravity-format"
#define PARAM_GRAVITY_TEMP_ADJ "gravity-temp-adjustment"
#define PARAM_TEMP_ADJ "temp-adjustment-value"
#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_VER "app-ver"
#define PARAM_APP_BUILD "app-build"
#define PARAM_ANGLE "angle"
#define PARAM_GRAVITY "gravity"
#define PARAM_TEMP_C "temp-c"
#define PARAM_TEMP_F "temp-f"
#define PARAM_BATTERY "battery"
#define PARAM_SLEEP_MODE "sleep-mode"
#define PARAM_RSSI "rssi"
#define PARAM_ERROR "error"
#define PARAM_PLATFORM "platform"
#define PARAM_BLE "ble"
#define PARAM_HW_GYRO_READ_COUNT "gyro-read-count"
#define PARAM_HW_GYRO_READ_DELAY "gyro-read-delay"
#define PARAM_HW_GYRO_MOVING_THREASHOLD "gyro-moving-threashold"
#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_HTTP3 "http-3"
#define PARAM_FORMAT_BREWFATHER "brewfather"
#define PARAM_FORMAT_INFLUXDB "influxdb"
#define PARAM_FORMAT_MQTT "mqtt"
#define PARAM_PUSH_FORMAT "format"
#define PARAM_PUSH_SUCCESS "success"
#define PARAM_PUSH_CODE "code"
#define PARAM_PUSH_ENABLED "enabled"
#endif // SRC_RESOURCES_HPP_

View File

@ -1,216 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
*/
#include <config.hpp>
#include <templating.hpp>
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#else // defined (ESP32)
#include <WiFi.h>
#endif
// Use iSpindle format for compatibility, HTTP POST
const char iSpindleFormat[] PROGMEM =
"{"
"\"name\" : \"${mdns}\", "
"\"ID\": \"${id}\", "
"\"token\" : \"${token}\", "
"\"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} "
"}";
// Format for an HTTP GET
const char iHttpGetFormat[] PROGMEM =
"?name=${mdns}"
"&id=${id}"
"&token=${token2}"
"&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}";
const char brewfatherFormat[] PROGMEM =
"{"
"\"name\": \"${mdns}\","
"\"temp\": ${temp}, "
"\"aux_temp\": 0, "
"\"ext_temp\": 0, "
"\"temp_unit\": \"${temp-unit}\", "
"\"gravity\": ${gravity}, "
"\"gravity_unit\": \"${gravity-unit}\", "
"\"pressure\": 0, "
"\"pressure_unit\": \"PSI\", "
"\"ph\": 0, "
"\"bpm\": 0, "
"\"comment\": \"\", "
"\"beer\": \"\", "
"\"battery\": ${battery}"
"}";
const char influxDbFormat[] PROGMEM =
"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}\n";
const char mqttFormat[] PROGMEM =
"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}|";
//
// Initialize the variables
//
void TemplatingEngine::initialize(float angle, float gravitySG,
float corrGravitySG, float tempC,
float runTime) {
// Names
setVal(TPL_MDNS, myConfig.getMDNS());
setVal(TPL_ID, myConfig.getID());
setVal(TPL_TOKEN, myConfig.getToken());
setVal(TPL_TOKEN2, myConfig.getToken2());
// Temperature
if (myConfig.isTempC()) {
setVal(TPL_TEMP, tempC, 1);
} else {
setVal(TPL_TEMP, convertCtoF(tempC), 1);
}
setVal(TPL_TEMP_C, tempC, 1);
setVal(TPL_TEMP_F, convertCtoF(tempC), 1);
setVal(TPL_TEMP_UNITS, myConfig.getTempFormat());
// Battery & Timer
setVal(TPL_BATTERY, myBatteryVoltage.getVoltage());
setVal(TPL_SLEEP_INTERVAL, myConfig.getSleepInterval());
// Performance metrics
setVal(TPL_RUN_TIME, runTime, 1);
setVal(TPL_RSSI, WiFi.RSSI());
// Angle/Tilt
setVal(TPL_TILT, angle);
setVal(TPL_ANGLE, angle);
// Gravity options
if (myConfig.isGravitySG()) {
setVal(TPL_GRAVITY, gravitySG, 4);
setVal(TPL_GRAVITY_CORR, corrGravitySG, 4);
} else {
setVal(TPL_GRAVITY, convertToPlato(gravitySG), 1);
setVal(TPL_GRAVITY_CORR, convertToPlato(corrGravitySG), 1);
}
setVal(TPL_GRAVITY_G, gravitySG, 4);
setVal(TPL_GRAVITY_P, convertToPlato(gravitySG), 1);
setVal(TPL_GRAVITY_CORR_G, corrGravitySG, 4);
setVal(TPL_GRAVITY_CORR_P, convertToPlato(corrGravitySG), 1);
setVal(TPL_GRAVITY_UNIT, myConfig.getGravityFormat());
#if LOG_LEVEL == 6
// dumpAll();
#endif
}
//
// Create the data using defined template.
//
const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
String fname;
baseTemplate.reserve(600);
// Load templates from memory
switch (idx) {
case TEMPLATE_HTTP1:
baseTemplate = String(iSpindleFormat);
fname = TPL_FNAME_HTTP1;
break;
case TEMPLATE_HTTP2:
baseTemplate = String(iSpindleFormat);
fname = TPL_FNAME_HTTP2;
break;
case TEMPLATE_HTTP3:
baseTemplate = String(iHttpGetFormat);
fname = TPL_FNAME_HTTP3;
break;
case TEMPLATE_BREWFATHER:
baseTemplate = String(brewfatherFormat);
// fname = TPL_FNAME_BREWFATHER;
break;
case TEMPLATE_INFLUX:
baseTemplate = String(influxDbFormat);
fname = TPL_FNAME_INFLUXDB;
break;
case TEMPLATE_MQTT:
baseTemplate = String(mqttFormat);
fname = TPL_FNAME_MQTT;
break;
}
// TODO: Add code to load templates from disk if they exist.
File file = LittleFS.open(fname, "r");
if (file) {
char buf[file.size() + 1];
memset(&buf[0], 0, file.size() + 1);
file.readBytes(&buf[0], file.size());
baseTemplate = String(&buf[0]);
file.close();
Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str());
}
#if LOG_LEVEL == 6
// Log.verbose(F("TPL : Base '%s'." CR), baseTemplate.c_str());
#endif
// Insert data into template.
transform(baseTemplate);
#if LOG_LEVEL == 6
// Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str());
#endif
return baseTemplate;
}
// EOF

View File

@ -1,148 +0,0 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
*/
#ifndef SRC_TEMPLATING_HPP_
#define SRC_TEMPLATING_HPP_
// Includes
#include <Arduino.h>
#include <algorithm>
#include <helper.hpp>
#include <main.hpp>
// Templating variables
#define TPL_MDNS "${mdns}"
#define TPL_ID "${id}"
#define TPL_TOKEN "${token}"
#define TPL_TOKEN2 "${token2}"
#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_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_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_FNAME_HTTP1 "/http-1.tpl"
#define TPL_FNAME_HTTP2 "/http-2.tpl"
#define TPL_FNAME_HTTP3 "/http-3.tpl"
// #define TPL_FNAME_BREWFATHER "/brewfather.tpl"
#define TPL_FNAME_INFLUXDB "/influxdb.tpl"
#define TPL_FNAME_MQTT "/mqtt.tpl"
extern const char iSpindleFormat[] PROGMEM;
extern const char iHttpGetFormat[] PROGMEM;
extern const char brewfatherFormat[] PROGMEM;
extern const char influxDbFormat[] PROGMEM;
extern const char mqttFormat[] PROGMEM;
// Classes
class TemplatingEngine {
private:
struct KeyVal {
String key;
String val;
};
KeyVal items[21] = {{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, ""},
{TPL_TOKEN2, ""}};
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, 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);
for (int i = 0; i < max; i++) {
if (items[i].key.equals(key)) {
items[i].val = val;
return;
}
}
Log.warning(F("TPL : Key not found %s." CR), key.c_str());
}
void transform(String& s) {
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);
}
}
void dumpAll() {
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("\'");
}
}
public:
enum Templates {
TEMPLATE_HTTP1 = 0,
TEMPLATE_HTTP2 = 1,
TEMPLATE_HTTP3 = 2,
TEMPLATE_BREWFATHER = 3,
TEMPLATE_INFLUX = 4,
TEMPLATE_MQTT = 5
};
void initialize(float angle, float gravitySG, float corrGravitySG,
float tempC, float runTime);
const String& create(TemplatingEngine::Templates idx);
};
#endif // SRC_TEMPLATING_HPP_
// EOF

View File

@ -27,10 +27,15 @@ SOFTWARE.
#include <config.hpp>
#include <gyro.hpp>
#include <main.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
OneWire myOneWire(PIN_DS);
//
// Conversion between C and F
//
float convertCtoF(float t) { return (t * 1.8) + 32.0; }
OneWire myOneWire(D6);
DallasTemperature mySensors(&myOneWire);
#define TEMPERATURE_PRECISION 9
@ -58,12 +63,20 @@ void TempSensor::setup() {
mySensors.setResolution(TEMPERATURE_PRECISION);
}
float t = myConfig.getTempSensorAdj();
// Set the temp sensor adjustment values
_tempSensorAdjC = myConfig.getTempSensorAdjC();
if (myConfig.isTempC()) {
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
tempSensorAdjC = t;
} else {
tempSensorAdjF = t;
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
}
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C." CR),
_tempSensorAdjC);
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR),
tempSensorAdjC, tempSensorAdjF);
#endif
}
@ -82,15 +95,13 @@ float TempSensor::getValue(bool useGyro) {
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
#endif
_hasSensor = true;
hasSensor = true;
return c;
}
// If we dont have sensors just return 0
if (!mySensors.getDS18Count()) {
#if !defined(TSEN_DISABLE_LOGGING)
Log.notice(F("TSEN: No temperature sensors found. Skipping read." CR));
#endif
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
return -273;
}
@ -105,7 +116,7 @@ float TempSensor::getValue(bool useGyro) {
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for DS18B20 sensor %F C." CR), c);
#endif
_hasSensor = true;
hasSensor = true;
}
return c;
}

View File

@ -24,20 +24,29 @@ SOFTWARE.
#ifndef SRC_TEMPSENSOR_HPP_
#define SRC_TEMPSENSOR_HPP_
// definitions
float convertCtoF(float t);
// classes
class TempSensor {
private:
bool _hasSensor = false;
float _tempSensorAdjC = 0;
bool hasSensor = false;
float tempSensorAdjF = 0;
float tempSensorAdjC = 0;
float getValue(bool useGyro);
public:
void setup();
bool isSensorAttached() { return _hasSensor; }
bool isSensorAttached() { return hasSensor; }
float getTempC(bool useGyro = false) {
return getValue(useGyro) + _tempSensorAdjC;
return getValue(useGyro) + tempSensorAdjC;
}
float getTempF(bool useGyro = false) {
return convertCtoF(getValue(useGyro)) + tempSensorAdjF;
}
};
// Global instance created
extern TempSensor myTempSensor;
#endif // SRC_TEMPSENSOR_HPP_

File diff suppressed because it is too large Load Diff

View File

@ -24,38 +24,28 @@ SOFTWARE.
#ifndef SRC_WEBSERVER_HPP_
#define SRC_WEBSERVER_HPP_
#if defined(ESP8266)
// Include
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#define MAX_SKETCH_SPACE 1044464
#else // defined (ESP32)
#include <ESPmDNS.h>
#include <WebServer.h>
#include <WiFi.h>
#include <Update.h>
#define MAX_SKETCH_SPACE 1835008
#endif
#include <incbin.h>
// Binary resouces
#if defined(EMBED_HTML)
INCBIN_EXTERN(IndexHtm);
INCBIN_EXTERN(DeviceHtm);
INCBIN_EXTERN(ConfigHtm);
INCBIN_EXTERN(CalibrationHtm);
INCBIN_EXTERN(FormatHtm);
INCBIN_EXTERN(TestHtm);
INCBIN_EXTERN(AboutHtm);
#else
INCBIN_EXTERN(UploadHtm);
#endif
INCBIN_EXTERN(FirmwareHtm);
INCBIN_EXTERN(UploadHtm);
class WebServerHandler {
// classes
class WebServer {
private:
ESP8266WebServer* _server = 0;
File _uploadFile;
int _lastFormulaCreateError = 0;
int _uploadReturn = 200;
ESP8266WebServer* server = 0;
File uploadFile;
int lastFormulaCreateError = 0;
void webHandleConfig();
void webHandleFormulaWrite();
@ -64,69 +54,51 @@ class WebServerHandler {
void webHandleConfigGravity();
void webHandleConfigPush();
void webHandleConfigDevice();
void webHandleConfigFormatRead();
void webHandleConfigFormatWrite();
void webHandleTestPush();
void webHandleStatusSleepmode();
void webHandleClearWIFI();
void webHandleStatus();
void webHandleFactoryDefaults();
void webHandleFactoryReset();
void webHandleCalibrate();
void webHandleUploadFile();
void webHandleUpload();
void webHandleDeviceParam();
void webHandleDevice();
void webHandlePageNotFound();
String readFile(String fname);
bool writeFile(String fname, String data);
String getRequestArguments();
// Inline functions.
void webReturnOK() { _server->send(_uploadReturn); }
void webReturnOK() { server->send(200); }
#if defined(EMBED_HTML)
void webReturnIndexHtm() {
_server->send_P(200, "text/html", (const char*)gIndexHtmData,
gIndexHtmSize);
server->send_P(200, "text/html", (const char*)gIndexHtmData, gIndexHtmSize);
}
void webReturnDeviceHtm() {
server->send_P(200, "text/html", (const char*)gDeviceHtmData,
gDeviceHtmSize);
}
void webReturnConfigHtm() {
_server->send_P(200, "text/html", (const char*)gConfigHtmData,
gConfigHtmSize);
server->send_P(200, "text/html", (const char*)gConfigHtmData,
gConfigHtmSize);
}
void webReturnCalibrationHtm() {
_server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
gCalibrationHtmSize);
}
void webReturnFormatHtm() {
_server->send_P(200, "text/html", (const char*)gFormatHtmData,
gFormatHtmSize);
server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
gCalibrationHtmSize);
}
void webReturnAboutHtm() {
_server->send_P(200, "text/html", (const char*)gAboutHtmData,
gAboutHtmSize);
}
void webReturnTestHtm() {
_server->send_P(200, "text/html", (const char*)gTestHtmData, gTestHtmSize);
}
#else
void webReturnUploadHtm() {
_server->send_P(200, "text/html", (const char*)gUploadHtmData,
gUploadHtmSize);
server->send_P(200, "text/html", (const char*)gAboutHtmData, gAboutHtmSize);
}
#endif
void webReturnFirmwareHtm() {
_server->send_P(200, "text/html", (const char*)gFirmwareHtmData,
gFirmwareHtmSize);
void webReturnUploadHtm() {
server->send_P(200, "text/html", (const char*)gUploadHtmData,
gUploadHtmSize);
}
public:
enum HtmlFile {
HTML_INDEX = 0,
HTML_CONFIG = 1,
HTML_ABOUT = 2,
HTML_CALIBRATION = 3,
HTML_FORMAT = 4,
HTML_TEST = 5
HTML_DEVICE = 1,
HTML_CONFIG = 2,
HTML_ABOUT = 3,
HTML_CALIBRATION = 4,
CA_CERTS = 5
};
bool setupWebServer();
@ -136,7 +108,7 @@ class WebServerHandler {
};
// Global instance created
extern WebServerHandler myWebServerHandler;
extern WebServer myWebServer;
#endif // SRC_WEBSERVER_HPP_

View File

@ -21,20 +21,19 @@ 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)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#else // defined (ESP32)
#include <HTTPUpdate.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#endif
#include <LittleFS.h>
#include <incbin.h>
#include <ArduinoJson.hpp>
#include <calc.hpp>
#include <config.hpp>
#include <main.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
#include <wifi.hpp>
#warning "Implement SSL for OTA"
// Settings for DRD
#define ESP_DRD_USE_LITTLEFS true
@ -50,7 +49,6 @@ SOFTWARE.
#define USING_CORS_FEATURE false
#define NUM_WIFI_CREDENTIALS 1
#define USE_STATIC_IP_CONFIG_IN_CP false
#define _WIFIMGR_LOGLEVEL_ 3
#include <ESP_WiFiManager.h>
// Override the look and feel of the standard ui (hide secondary forms)
const char WM_HTTP_FORM_START[] PROGMEM =
@ -63,18 +61,45 @@ const char WM_HTTP_FORM_START[] PROGMEM =
"placeholder='SSID1'><div></div></div><div "
"hidden><label>Password</label><input id='p1' name='p1' length=64 "
"placeholder='password1'><div></div></div></fieldset>";
#include <ESP_WiFiManager-Impl.h>
ESP_WiFiManager *myWifiManager;
DoubleResetDetector *myDRD;
WifiConnection myWifi;
const char *userSSID = USER_SSID;
const char *userPWD = USER_SSID_PWD;
const int PIN_LED = 2;
//
// Initialize
// Initialize the certificate store
//
void WifiConnection::init() {
void WifiConnection::initCertstore() {
_certCount = _certStore.initCertStore(LittleFS, "/certs.idx", "/certs.ar");
Log.notice(F("WIFI: Number of CA certs read: %d." CR), _certCount);
}
// Set time via NTP, as required for x.509 validation
void WifiConnection::initNTP() {
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
Log.notice(F("WIFI: Waiting for NTP time sync." CR));
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println();
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Log.notice(F("WIFI: Current time %s." CR), asctime(&timeinfo));
}
//
// Constructor
//
WifiConnection::WifiConnection() {
myDRD = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
}
@ -89,11 +114,8 @@ bool WifiConnection::hasConfig() {
String ssid = WiFi.SSID();
if (ssid.length()) {
Log.notice(F("WIFI: Found credentials in EEPORM." CR));
myConfig.setWifiSSID(ssid);
if (WiFi.psk().length())
myConfig.setWifiPass(WiFi.psk());
myConfig.setWifiSSID(WiFi.SSID());
myConfig.setWifiPass(WiFi.psk());
myConfig.saveFile();
return true;
}
@ -114,8 +136,6 @@ String WifiConnection::getIPAddress() { return WiFi.localIP().toString(); }
// Additional method to detect double reset.
//
bool WifiConnection::isDoubleResetDetected() {
if (strlen(userSSID))
return false; // Ignore this if we have hardcoded settings.
return myDRD->detectDoubleReset();
}
@ -136,24 +156,12 @@ void WifiConnection::startPortal() {
myWifiManager = new ESP_WiFiManager(WIFI_MDNS);
myWifiManager->setMinimumSignalQuality(-1);
myWifiManager->setConfigPortalChannel(0);
myWifiManager->setConfigPortalTimeout(
myHardwareConfig.getWifiPortalTimeout());
String mdns("<p>Default mDNS name is: http://");
mdns += myConfig.getMDNS();
mdns += ".local<p>";
ESP_WMParameter deviceName(mdns.c_str());
myWifiManager->addParameter(&deviceName);
myWifiManager->setConfigPortalTimeout(120);
if (myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD)) {
Log.notice(F("WIFI: Exited portal, connected to wifi. Rebooting..." CR));
if (myWifiManager->getSSID().length())
myConfig.setWifiSSID(myWifiManager->getSSID());
if (myWifiManager->getPW().length())
myConfig.setWifiPass(myWifiManager->getPW());
myConfig.setWifiSSID(myWifiManager->getSSID());
myConfig.setWifiPass(myWifiManager->getPW());
myConfig.saveFile();
} else {
Log.notice(
@ -162,7 +170,7 @@ void WifiConnection::startPortal() {
stopDoubleReset();
delay(500);
ESP_RESET();
ESP.reset();
}
//
@ -202,14 +210,14 @@ bool WifiConnection::waitForConnection(int maxTime) {
if (i++ >
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
ErrorFileLog errLog;
errLog.addEntry("WIFI: Failed to connect to wifi " +
String(WiFi.status()));
Log.error(F("WIFI: Failed to connect to wifi %d, aborting %s." CR),
WiFi.status(), getIPAddress().c_str());
WiFi.disconnect();
Serial.print(CR);
return false; // Return to main that we have failed to connect.
}
}
Serial.print(CR);
Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str());
Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS());
@ -240,7 +248,7 @@ bool WifiConnection::disconnect() {
//
//
bool WifiConnection::updateFirmware() {
if (!_newFirmware) {
if (!newFirmware) {
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
return false;
}
@ -248,103 +256,88 @@ bool WifiConnection::updateFirmware() {
Log.verbose(F("WIFI: Updating firmware." CR));
#endif
WiFiClient wifi;
WiFiClientSecure wifiSecure;
HTTPUpdateResult ret;
WiFiClient client;
String serverPath = myConfig.getOtaURL();
#if defined(ESP8266)
serverPath += "firmware.bin";
#else // defined (ESP32)
serverPath += "firmware32.bin";
#endif
if (serverPath.startsWith("https://")) {
wifiSecure.setInsecure();
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
ret = ESPhttpUpdate.update(wifiSecure, serverPath);
} else {
ret = ESPhttpUpdate.update(wifi, serverPath);
}
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
switch (ret) {
case HTTP_UPDATE_FAILED: {
ErrorFileLog errLog;
errLog.addEntry("WIFI: OTA update failed " +
String(ESPhttpUpdate.getLastError()));
} break;
case HTTP_UPDATE_FAILED:
Log.error(F("WIFI: OTA update failed %d, %s." CR),
ESPhttpUpdate.getLastError(),
ESPhttpUpdate.getLastErrorString().c_str());
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();
ESP.reset();
break;
}
}
return false;
}
//
// Download and save file
//
void WifiConnection::downloadFile(HTTPClient &http, String &fname) {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
void WifiConnection::downloadFile(const char *fname) {
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Download file %s." CR), fname);
#endif
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += fname;
// Your Domain name with URL path or IP address with path
http.begin(client, serverPath);
int httpResponseCode = http.GET();
if (httpResponseCode == 200) {
File f = LittleFS.open(fname, "w");
http.writeToStream(&f);
f.close();
Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str());
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
} else {
ErrorFileLog errLog;
errLog.addEntry("WIFI: Failed to download html-file " +
String(httpResponseCode));
Log.error(F("WIFI: Failed to download file, respone=%d" CR),
httpResponseCode);
}
http.end();
}
//
// Check what firmware version is available over OTA
//
bool WifiConnection::checkFirmwareVersion() {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Checking if new version exist." CR));
#endif
WiFiClient wifi;
WiFiClientSecure wifiSecure;
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += "version.json";
// Your Domain name with URL path or IP address with path
if (myConfig.isOtaSSL()) {
wifiSecure.setInsecure();
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
http.begin(wifiSecure, serverPath);
} else {
http.begin(wifi, serverPath);
}
http.begin(client, serverPath);
// Send HTTP GET request
DynamicJsonDocument ver(300);
int httpResponseCode = http.GET();
if (httpResponseCode == 200) {
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
String payload = http.getString();
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
#endif
DynamicJsonDocument ver(300);
DeserializationError err = deserializeJson(ver, payload);
if (err) {
ErrorFileLog errLog;
errLog.addEntry(F("WIFI: Failed to parse version.json"));
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
} else {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Project %s version %s." CR),
(const char *)ver["project"], (const char *)ver["version"]);
#endif
@ -353,34 +346,33 @@ bool WifiConnection::checkFirmwareVersion() {
if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) {
if (parseFirmwareVersionString(curVer, CFG_APPVER)) {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR),
newVer[0], newVer[1], newVer[2], curVer[0], curVer[1],
curVer[2]);
#endif
// Compare major version
if (newVer[0] > curVer[0]) _newFirmware = true;
if (newVer[0] > curVer[0]) newFirmware = true;
// Compare minor version
else if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
_newFirmware = true;
if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
newFirmware = true;
// Compare patch version
else if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
newVer[2] > curVer[2])
_newFirmware = true;
if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
newVer[2] > curVer[2])
newFirmware = true;
}
}
// Download new html files to filesystem if they are present.
if (!ver["html"].isNull() && _newFirmware) {
Log.notice(
F("WIFI: OTA checking if html files should be downloaded." CR));
if (!ver["html"].isNull() && newFirmware) {
Log.notice(F("WIFI: OTA downloading new html files." CR));
JsonArray htmlFiles = ver["html"].as<JsonArray>();
for (JsonVariant v : htmlFiles) {
String s = v;
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str());
#endif
downloadFile(http, s);
downloadFile(s.c_str());
}
}
}
@ -389,13 +381,11 @@ bool WifiConnection::checkFirmwareVersion() {
httpResponseCode);
}
http.end();
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA found new version %s." CR),
_newFirmware ? "true" : "false");
newFirmware ? "true" : "false");
#endif
return _newFirmware;
return newFirmware;
}
//
@ -403,7 +393,7 @@ bool WifiConnection::checkFirmwareVersion() {
//
bool WifiConnection::parseFirmwareVersionString(int (&num)[3],
const char *version) {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Parsing version number string %s." CR), version);
#endif
char temp[80];

View File

@ -24,30 +24,29 @@ SOFTWARE.
#ifndef SRC_WIFI_HPP_
#define SRC_WIFI_HPP_
#if defined(ESP8266)
#include <ESP8266HTTPClient.h>
#else // defined (ESP32)
#include <HTTPClient.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.
// Include
#include <Arduino.h>
#include <CertStoreBearSSL.h>
// classes
class WifiConnection {
private:
// OTA
bool _newFirmware = false;
bool parseFirmwareVersionString(int (&num)[3], const char* version);
void downloadFile(HTTPClient& http, String& fname);
// SSL
BearSSL::CertStore _certStore;
int _certCount = 0;
// WIFI
void connectAsync();
bool waitForConnection(int maxTime = 20);
// OTA
bool newFirmware = false;
bool parseFirmwareVersionString(int (&num)[3], const char *version);
void downloadFile(const char *fname);
public:
// WIFI
void init();
WifiConnection();
bool connect();
bool disconnect();
@ -59,6 +58,12 @@ class WifiConnection {
void startPortal();
void loop();
// SSL
void initCertstore();
BearSSL::CertStore* getCertStore() { return &_certStore; }
int getCertCount() { return _certCount; }
void initNTP();
// OTA
bool updateFirmware();
bool checkFirmwareVersion();

View File

@ -1,98 +0,0 @@
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
* - ${token}
- Token
- any value
* - ${token2}
- Token 2
- any value
* - ${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

View File

@ -1,392 +0,0 @@
.. _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)
* ``ble`` is used to enable ble data transmission (only on esp32) simulating a tilt. Valid color names are; red, green, black, purple, orange, blue, yellow, pink
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",
"ble": "color",
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
"token": "token",
"token2": "token2",
"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",
"http-push3": "http://192.168.1.50/ispindel",
"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
},
"formula-calculation-data": {
"a1":25,
"a2":30,
"a3":35,
"a4":40,
"a5":45,
"g1":1,
"g2":1.01,
"g3":1.02,
"g4":1.03,
"g5":1.04
},
"angle": 90.93,
"gravity": 1.105,
"battery": 0.04,
"platform": "esp8266",
"runtime-average": 3.12
}
GET: /api/device
================
This API has been removed from 0.9 and merged with /api/status
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``
* ``platform`` can be either ``esp8266`` or ``esp32``
Other parameters are the same as in the configuration guide.
.. code-block:: json
{
"id": "ee1bfc",
"angle": 89.86,
"gravity": 1.1052,
"temp-c": 0,
"temp-f": 32,
"battery": 0,
"temp-format": "C",
"sleep-mode": false,
"token": "token",
"token2": "token2",
"rssi": -56,
"app-ver": "0.0.0",
"mdns": "gravmon",
"sleep-interval": 30,
"platform": "esp8266",
"runtime-average": 3.12
}
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/clearwifi
===================
Will reset the wifi settings both in the configuration file and eeprom, leaving the rest of the configuration.
For this to work you will need to supply the device id as a parameter in the request:
::
http://mygravity.local/api/clearwifi?id=<mydeviceid>
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>
GET: /api/test/push
===================
Trigger a push on one of the targets, used to validate the configuration from the UI.
Requires to parameters to function /api/test/push?id=<deviceid>&format=<format>
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, brewfather, influxdb, mqtt
The response is an json message with the following values.
* ``code`` is the return code from the push function, typically http responsecode or error code from mqtt library.
.. code-block:: json
{
"success": false,
"enabled": true,
"code": -3
}
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
token=
token2=
http-push=http://192.168.1.50/ispindel
http-push2=
http-push3=
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
ble=red
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": "",
"token2": "",
"http-push": "http://192.168.1.1/ispindel",
"http-push2": "",
"http-push3": "",
"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)
"ble": "red", # Enable ble on esp32
"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 )

View File

@ -0,0 +1,18 @@
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
Code
----
- Support for plato
- Use pre-commit for validating check-in
- Show indicated battery life based on interval (check if its feasable)
- Use brewflasher for flashing

View File

@ -30,16 +30,12 @@ In the platformio config there are 3 targets defined
* gravity-debug; Maximum logging for trouble shooting, deep sleep is disabled.
* gravity-release; Standard release
* gravity-perf; Standard release but contains code for measuring performance
* gravity32-perf: Experimental version for ESP32.
.. note::
There is an experimental ESP32 target but since platformio only supports SDK 1.0.6 and the WIFI connection is really slow compared to ESP8266,
so the recommendation is to wait for support on 2.0.x branch. With the tested version an wifi connection takes 3-8s on a ESP32 compared
to 0.5s on an ESP8266. There is also a bug in OneWire connected to ESP32 that has not been fixed in the main repository yet.
.. warning::
The debug target can be unstable and crash the device under certain circumstanses. Excessive logging to the serial port can cause corruption and crashes.
So only enable enough debugging to troubleshoot your changes.
The debug target can be unsable and crash the device under certain circumstanses.
Excessive logging to the serial port can cause corruption and crashes. I'm still
trying to figure out what causes these issues in the debug target. Other targets are
stable and works fine.
Source structure
@ -87,9 +83,19 @@ This is a list of C++ defines that is used to enable/disable functions in the co
* - ACTIVATE_OTA
- Enables the OTA functionallity in the code
* - SKIP_SLEEPMODE
- The device never goes into sleep mode, useful when developing.
* - xxx_DISABLE_LOGGING
- Done include verbose logging in the corresponding class. Excessive logging may crash device.
- THe device never goes into sleep mode, useful when developing.
* - CFG_DISABLE_LOGGING
- Done include verbose logging in Config class. Excessive logging may crash device.
* - GYRO_DISABLE_LOGGING
- Done include verbose logging in Gyro class. Excessive logging may crash device.
* - PUSH_DISABLE_LOGGING
- Done include verbose logging in PushTarget class. Excessive logging may crash device.
* - TSEN_DISABLE_LOGGING
- Done include verbose logging in TempSensor class. Excessive logging may crash device.
* - WEB_DISABLE_LOGGING
- Done include verbose logging in WebServer class. Excessive logging may crash device.
* - MAIN_DISABLE_LOGGING
- Done include verbose logging in Main class. Excessive logging may crash device.
* - USE_LITTLEFS
- Use the new filesystem in Ardurino
* - EMBED_HTML

View File

@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
author = 'Magnus Persson'
# The full version, including alpha/beta/rc tags
release = '0.9.0'
release = '0.6.0'
# -- General configuration ---------------------------------------------------

View File

@ -1,7 +1,7 @@
.. _setting-up-device:
Configuration
#############
Setting up device
#################
The device can operate in two modes and must be in ``configuration mode`` in order for the web server to be active.
@ -12,7 +12,6 @@ One of the following conditions will place the device in ``configuration mode``:
- Placed in horizontal mode 85-90 degrees
- Charger connected >4.15V
Status
======
@ -22,23 +21,37 @@ URL: (http://gravmon.local)
:width: 800
:alt: Index page
Configuration is accessed by entering the URL for the device, this will be the mDNS name *device.local* or the IP adress. The following chapter assumes the device name is *gravmon*.
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if
you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
You can also view 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.
.. tip::
If you are connected to the device via a serial console (speed: 115200) you can see the connection sequence and get the Unique ID and IP adress from there.
.. 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`.
Device
======
URL: (http://gravmon.local/device)
.. image:: images/device.png
:width: 800
:alt: Device Settings
* **Version:**
Installed version of the code and html files.
* **Device name:**
This is unique name of the device.
* **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.
Configuration
@ -55,28 +68,23 @@ Device Setting
* **Device name:**
This is unique name for the device. It will be used in pushing data as well as mDNS name on the network (<name>.local)
This is unique name for the device. It will be used in pushing data as well as mDNS name on the network (<name>.local)
* **Temperature format:**
Choose between Celsius and Farenheight when displaying temperature.
Choose between Celsius and Farenheight
* **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).
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.
.. note::
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.
The device will show the estimated lifespan based on the average connection time, if no data exist it will not be shown.
The sleep interval can be set between 10 - 3600 seconds (60 minutes).
* **Calibration values:**
These are calibration data for the gyro. Place the device flat on a table and press the button to save the default orientation values. Without this calibration we cannot calculate the correct angle/tilt.
These are calibration data for the gyro. Place the device flat on a table and press the button to save the default orientation values. Without this calibration we cannot calculate the correct angle/tilt.
.. warning::
@ -89,108 +97,50 @@ Push Settings
:width: 800
:alt: Push Settings
.. note::
* **HTTP URL 1:**
When enabling SSL this will not validate the root CA of the remote service, this is a design decision based on two aspects. Enabling CA validation will take 3-4s extra on each connection which means way less
battery life, so the decision is to prioritize battery life over security. The data transmitted is not really that sensitive anyway so I belive this is a good balance.
Endpoint to send data via http. Format used Format used :ref:`data-formats-ispindle`
* **HTTP 1 (POST):**
* **HTTP URL 2:**
Endpoint to send data via http. Default format used Format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
If you add the prefix `https://` then the device will use SSL when sending data.
* **HTTP 2 (POST):**
Endpoint to send data via http. Default format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
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.
* **HTTP 3 (GET):**
Endpoint to send data via http. This is using an HTTP GET request instead of a post. This means that the values are appended to the URL like; http://endpoint?param=value&param2=value2. You can customize the format using :ref:`format-editor`.
If you add the prefix `https://` then the device will use SSL when sending data.
* **Token 2:**
The token is included in the default format for the HTTP GET url but can be used for any of the formats. For HTTP GET use can use this for an authorization token with for instance ubidots or blynk http api.
Endpoint to send data via http. Format used :ref:`data-formats-ispindle`
* **Brewfather URL:**
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
SSL is not supported for this target.
* **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>`_
Push Settings (2)
+++++++++++++++++
.. image:: images/config2b.png
:width: 800
:alt: Push Settings
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
* **Influx DB v2 URL:**
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`. You can customize the format using :ref:`format-editor`.
SSL is not supported for this target. Raise a issue on github if this is wanted.
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`
* **Influx DB v2 Organisation:**
Name of organisation in Influx.
Name of organisation in Influx.
* **Influx DB v2 Bucket:**
Identifier for bucket.
Identifier for bucket.
* **Influx DB v2 Token:**
Token with write access to bucket.
Token with write access to bucket.
* **MQTT server:**
IP or name of server to send data to. Default format used :ref:`data-formats-mqtt`. You can customize the format using :ref:`format-editor`.
IP or name of server to send data to. Format used :ref:`data-formats-ispindle`
* **MQTT Port:**
* **MQTT Topic:**
Which port should be used for communication, default is 1883 (standard port). For SSL use 8883 (any port over 8000 is treated as SSL).
Name of topic to publish sensor readings to, iSpindle format is used.
* **MQTT user:**
Username or blank if anonymous is accepted
Username or blank if anonymous is accepted
* **MQTT password:**
Password or blank if anonymous is accepted
Password or blank if anonymous is accepted
Gravity Settings
++++++++++++++++
@ -199,28 +149,19 @@ Gravity Settings
:width: 800
:alt: Gravity Settings
* **Gravity format:**
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.
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`
* **Temperature correct gravity:**
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.
Will apply a temperature calibration formula to the gravity as a second step.
.. warning::
This formula assumes that the calibration has been done at 20C.
This formula assumes that the calibration has been done at 20°C / 68°F.
Formula used in temperature correction.
Formula used in temperature correction:
::
@ -237,47 +178,438 @@ Hardware Settings
* **Voltage factor:**
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
* **Temperature correction:**
This value will be added to the temperature reading (negative value will reduce temperature reading). This is applied
when the device starts. So changing this will not take affect until the device is restarted.
This value will be added to the temperature reading (negative value will reduce temperature reading). This is applied
when the device starts. So changing this will not take affect until the device is restarted.
* **Gyro Temperature:**
Enable this feature will use the temp sensor i the gyro instead of the DS18B20, the benefit is shorter run time and
longer battery life (this is an experimental feature). The value used is the first temperature reading from when the
device is activated, since the gyro should be cool this is reflecting the surronding temperature. After it has
been running the value would be totally off.
* **Bluetooth: (Only ESP32)**
If the build is using an ESP32 then you can send data over BLE, simulating a Tilt device. Choose the color that you want the device to simulate.
Enable this feature will use the temp sensor i the gyro instead of the DS18B20, the benefit is shorter run time and
longer battery life (this is an experimental feature). The value used is the first temperature reading from when the
device is activated, since the gyro should be cool this is reflecting the surronding temperature. After it has
been running the value would be totally off.
* **OTA URL:**
Should point to a URL where the firmware.bin file + version.json file are located. For an ESP32 target the firmware should be named firmware32.bin.
Should point to a URL where the .bin file + version.json file is 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.
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.
If you have the previx `https://` then the device will use secure transfer without CA validation.
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
.. code-block::
http://192.168.1.1/firmware/gravmon/
https://192.168.1.1/firmware/gravmon/
.. _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-topic": "Qwerty",
"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
}
* **Upload Firmware**
GET: /api/device
================
This option gives you the possibility to install an new version of the firmware (or any firmware that uses the standard flash layout).
Retrive the current device settings via an HTTP GET command. Payload is in JSON format.
.. image:: images/firmware.png
:width: 600
:alt: Update firmware
.. 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)
.. 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-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`` or ``F``
.. 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-topic": "Qwerty",
"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.
.. 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-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-topic": "Qwerty",
"mqtt-user": "Qwerty",
"mqtt-pass": "Qwerty"
}
set_config( url, json )
url = "http://" + host + "/api/config/gravity"
json = { "id": id,
"gravity-formula": "",
"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.
* ``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,
"corr-gravity": 1.0050,
"angle": 45.34,
"battery": 3.67,
"rssi": -12,
"run-time": 6
}
.. _data-formats-brewfather:
Brewfather format
=================
This is the format for Brewfather
.. 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
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.4.10",
"html": [
"index.min.htm",
"device.min.htm",
"config.min.htm",
"calibration.min.htm",
"about.min.htm"
]
}

View File

@ -1,181 +0,0 @@
.. _data-formats:
Data Formats
############
.. _data-formats-ispindle:
HTTP Post, 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:
HTTP Get
========
This is the format added to the URL when using HTTP get
.. code-block::
?name=<mdns>,id=<id>,token=<token>&interval=300&temperature=20.1&temp-units=<C|F>&
gravity=$1.004&angle=45.5&battery=3.96&rssi=-18&corr-gravity=1.004&gravity-unit=<G|P>&run-time=2.1
This is the format template used to create the data above.
.. code-block::
?name=${mdns}&id=${id}&token=${token2}&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}
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",
"test.min.htm",
"config.min.htm",
"format.min.htm",
"calibration.min.htm",
"about.min.htm"
]
}

View File

@ -1,16 +0,0 @@
.. _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.

View File

@ -3,105 +3,58 @@
Functionallity
==============
The main features
-----------------
The main differences
--------------------
* **Operates in two modes gravity monitoring and configuration mode**
In ``gravity monitoring`` mode it behaves just like the iSpindle, it wakes up at regular intervals, measures
angle/tile, temperature, calculates gravity and pushes the data to defined endpoints.
In ``gravity monitoring`` mode it behaves just like the iSpindle, it wakes up at regular intervals, measures 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, 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).
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).
.. 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
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
sequence can be handled via the web interface without need for additional software tools.
See the :ref:`setting-up-device` section for more information on how to trigger the configuration mode.
* **Can send data to multiple endpoints**
* **Can send data to multiple endpoints at once**
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. Its much
more efficient to have the endpoints on your local network than on the internet.
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 a lot of battery power so use only as many as needed.
Currently the device supports the following endpoints.
Currently the device supports the following endpoints: http (2 different), influxdb2 and Brewfather
* http or https
* influxdb v2
* Brewfather
* MQTT
* Home Assistant
* Brew Spy
* Brewers Friend
* Fermentrack
* Ubidots
* Thingsspeak
If you want additional targets please raise a feature request in the github repo.
* **Build in function to create gravity formulas, so no need for using other tools for this part**
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.
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.
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 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.
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.
* **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.
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.
* **OTA support from webserver**
.. note::
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.
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.
* **DS18B20 temperature adjustments**
You can adjust the temperature reading of the temperature sensor. In normal cases this should not be needed since
the sensors should be calibrated.
You can adjust the temperature reading of the temperature sensor.
* **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 and peaks in the graphs.
This way we should avoid faulty measurements.
* **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 later. This to conserve batter as much as possible.
the device will go into deep sleep for 60 seoncds and then retry.
* **Use gyro temperature sensor**
@ -119,36 +72,21 @@ The main features
:width: 800
:alt: Gyro temp vs DS18B20
* **Celsius or Farenheigt**
Other features
--------------
You can switch between different temperature formats. GravityMon will always use C for it's internal calculations and
convert to F when displayed.
* Support for Celcius and Farenheigt as temperature formats.
* **SG or Plato**
* Support SG (Plato is not yet supported)
You can switch between different gravity formats. GravityMon will always use SG for it's internal calculations and
convert to Plato when displayed.
* Gyro data is read 50 times to ensure good accuracy
* **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.
Experimental features
---------------------
* **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. 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
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.
See the :ref:`compiling-the-software` for more information.
@ -160,11 +98,9 @@ The main features
Battery life
------------
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'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).
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

View File

@ -1,26 +0,0 @@
.. _hardware:
Hardware
########
There are lots of resouces out there on how to build the hardware for an iSpindle so I will not go into details on that part. Here are two of my builds using the iSpindle PCB v4.
.. image:: images/ispindel.jpg
:width: 500
:alt: Builds of iSpindel
It's possible to use this PCB and mount an ESP32 on top of that. It must be an pin compatible ESP32 and the one I used was called *ESP32 d1 mini*. Since this is the same width as the PCB you need to
mount it really close to the PCB in order for it to fit in the PET tube/container. I also had to smooth the edge of the ESP32 in order for it to fit.
I would suggest that you try how it fits into the PET tube before soldering it to the PCB. Make sure that the battery is attached since this will be a really tight fit.
You also need to desolder (remove) the RED ON LED from the ESP32 or the battery power will be reduced a lot.
Final thing is to add a resistor between A0 (Analog PIN) and ground of 470k. The reason is that the esp8266 has a build in resistor which
the esp32 does not have. So in order to get a valid voltage (less than 3.2V) on the A0 pin this is needed. Once the modification is done you might
need to adjust the voltage factor so the battery reading is correct.
.. image:: images/esp32.jpg
:width: 500
:alt: Mounting esp32

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 931 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show More