42 Commits

Author SHA1 Message Date
059865f271 GitHub Action Build 2022-08-14 12:27:26 +00:00
a9deb588aa Fixed error printout for calibration errors 2022-08-14 14:23:23 +02:00
0de8971255 GitHub Action Build 2022-08-14 11:59:10 +00:00
08a102159b Increased file eror log to 4k 2022-08-14 13:54:29 +02:00
8b13b4769c GitHub Action Build 2022-08-14 08:59:57 +00:00
c3044f9878 Updated 1.1 docs 2022-08-14 10:54:43 +02:00
0a4e7f37b2 Gen .map always 2022-08-09 20:28:52 +02:00
69f4b8ec3d Doc updates 2022-08-09 20:25:17 +02:00
d4c88c1200 Updated docs 2022-08-06 09:17:10 +02:00
4bbfb77361 Fix pre-commit 2022-08-06 08:55:52 +02:00
655235fc4e Added log message to webserver 2022-08-06 08:52:54 +02:00
4b0e5b393a Storage mode deepSleep forever 2022-08-05 22:50:03 +02:00
db590f8f5f Updated docs 2022-08-05 22:49:44 +02:00
4c805fe235 Added crash detector 2022-08-05 18:17:10 +02:00
34a1d7d3c3 GitHub Action Build 2022-08-05 09:21:15 +00:00
bcee0e21da Corrected error code from mqtt push 2022-08-05 11:16:58 +02:00
9fc5ab147d GitHub Action Build 2022-08-05 08:17:37 +00:00
b3a89a1fcc Fix javascript error in format 2022-08-05 10:13:20 +02:00
03b58eb112 GitHub Action Build 2022-08-05 08:00:11 +00:00
63ddcfbb57 Fixed compiler error on esp32 + release notes 2022-08-05 09:55:47 +02:00
68d44a5846 Fixes for new tpl engine, optimize memory. 2022-08-05 09:38:10 +02:00
45294f6b07 Using strncat instead 2022-08-04 16:17:24 +02:00
09d8226af1 Bump beta4 2022-08-04 16:12:28 +02:00
6c94c6b204 Precommit fixes 2022-08-04 16:12:14 +02:00
9e437d1e0d Refactored error logger to reduce memory footprint 2022-08-04 08:31:10 +02:00
a486f1be30 Updated code for gyro error to UI 2022-08-03 17:22:26 +02:00
0536b14280 Updated docs for beta3 2022-08-02 13:42:39 +02:00
a6bff7287e Added "not found" messages for gyro/temp sensor. 2022-08-02 13:02:01 +02:00
414d7d51c2 GitHub Action Build 2022-08-02 06:28:23 +00:00
640733f143 Added drop down menu, fixed bus in ha workflow 2022-08-02 08:24:08 +02:00
f6196e6dbf GitHub Action Build 2022-08-01 20:33:24 +00:00
f857dab3c0 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-08-01 22:28:48 +02:00
787c5c09bb Updated workflow for HA registration 2022-08-01 22:28:17 +02:00
afe3ec2412 GitHub Action Build 2022-08-01 19:30:38 +00:00
980099a5e5 Updated clang format 2022-08-01 21:26:20 +02:00
99d577d4a0 Updated release notes and error message 2022-08-01 21:25:21 +02:00
6db6e96d90 Fixed crash after firmware update 2022-08-01 16:16:24 +02:00
92df08baa2 Updated format template 2022-08-01 11:26:10 +02:00
5466550f68 Updated format templates 2022-08-01 11:25:55 +02:00
94f703b087 Bump to beta3 2022-08-01 11:06:14 +02:00
57e078986b Upd release notes 2022-08-01 11:05:53 +02:00
e445e7649c Refactor format read to handle large payload 2022-08-01 11:03:51 +02:00
53 changed files with 776 additions and 488 deletions

View File

@ -21,18 +21,24 @@ Note! If Brewflasher being flagged as malware by your antivirus software, try th
The main differences: The main differences:
--------------------- ---------------------
* Modern web based user interface for configuration when connected to WIFI * Operates in two modes gravity monitoring and configuration mode
* Efficient software, long lifespan (+45 days with 5min update frequencey) * Gravity mode is comparable to how the iSpindle works when collectintg data
* Send data to multiple endpoints (http-post, http-get, influxdb v2, mqtt) * Configuration mode has a modern HTML5 based web UI. No need to start the access point to change settings
* Instructions for service such as; Brewfather, Fermentrack, Ubidots, Home Assistant, Brewers Friend, Brewspy, Thingspeak, Blynk. * Offloading some of the functionallity to run in the web browser, this allows for more advanced features.
* SSL support for all remote endpoints * REST API to enable scripted configuration
* ESP32 support with Bluetooth push * Send data to multiple endpoints and services at once (2xHTTP POST, HTTP GET, MQTT, INFLUXDB2)
* Customize data format to be pushed * Directly test all endpoints from user interface with feedback to simplify troubleshooting
* Automatic temperature adjustment of gravity when enabled * Complete format customization for all endpoints using templates (dont really need to change the software to support new services)
* Use the temperature sensor in gyro instead of DS18B20 * Setup guides for how to send data to many popular services. Currently 10+ are documented
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity. * Automatic temperature adjustment of gravity (just tick a checkbox)
* Visual graph showing how gravity formula will be interpreted * OTA support from webserver
* OTA support or firmware upload via web interface * Firmware update via web interface
* REST API for scripting * Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and let GravityMon creates a formula
* Visual graph showing how formula will be interpreted based on entered values
* Using the temperature sensor in gyro instead of DS18B20 (faster)
* SSL support in all endpoints (no certificate validation due to limitations on esp8266).
* Built in performance measurements (used to optimise code)
* Storage mode when placed on cap (indefinite sleep)
* Customize various hardware parameters to opimize device functionallity.
*
No code has been reused from the iSpindle project. No code has been reused from the iSpindle project.

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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</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="#"><b>About</b></a></li></ul></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">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 h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">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><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li></ul></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link active" href="#">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">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 h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">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><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,17 +28,26 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a> <a class="nav-link" href="/index.htm">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="/config.htm">Configuration</a> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/calibration.htm">Calibration</a> <a class="nav-link" href="/calibration.htm">Calibration</a>
</li> </li>
<li class="nav-item active"> <li class="nav-item">
<a class="nav-link" href="#"><b>About</b></a> <a class="nav-link active" href="#">About</a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div> </div>
</nav> </nav>

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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</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="#"><b>About</b></a></li></ul></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">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 h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">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><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li></ul></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link active" href="#">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">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 h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">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><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>

View File

@ -16,8 +16,6 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <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/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
@ -29,11 +27,19 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a> <a class="nav-link" href="/index.htm">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="/config.htm">Configuration</a> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</li> </li>
<li class="nav-item active"> <li class="nav-item">
<a class="nav-link" href="#"><b>Calibration</b></a> <a class="nav-link active" href="#">Calibration</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/about.htm">About</a> <a class="nav-link" href="/about.htm">About</a>

File diff suppressed because one or more lines are too long

View File

@ -28,10 +28,18 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a> <a class="nav-link" href="/index.htm">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link active" href="#"><b>Configuration</b></a> <a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/calibration.htm">Calibration</a> <a class="nav-link" href="/calibration.htm">Calibration</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
@ -24,9 +22,26 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a> <a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="#">Upload firmware</a></li>
</ul>
</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> </li>
</ul> </ul>
</div> </div>
@ -34,6 +49,7 @@
</div> </div>
</nav> </nav>
<!-- START MAIN INDEX --> <!-- START MAIN INDEX -->
<div class="container row-margin-10"> <div class="container row-margin-10">
@ -135,7 +151,7 @@
showError("Upload failed"); showError("Upload failed");
}, },
success: function(resp) { success: function(resp) {
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser."); showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser. If you don't get any gyro readings after update, please press the reset button!");
setTimeout(() => { setTimeout(() => {
window.location = "/"; window.location = "/";
}, 10000); }, 10000);

View File

@ -1,4 +1,4 @@
<!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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><!-- START MENU --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><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></div></nav><!-- START MAIN INDEX --><div class="container row-margin-10"><div class="alert alert-success alert-dismissible hide fade d-none" role="alert"><div id="alert"></div><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("hide").removeClass("show").addClass("d-none")})</script><div class="accordion" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingFirmware"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware"><b>Upload firmware</b></button></h2><div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion"><div class="accordion-body"><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-2 themed-grid-col bg-light">Current version:</div><div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Platform:</div><div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div></div><form id="uploadForm" enctype="multipart/form-data"><div class="row mb-3"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload"></div></div><div class="row mb-3"><div class="col-md-4"><button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button></div></div></form><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><script type="text/javascript">window.onload = getStatus; <!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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="#">Upload firmware</a></li></ul></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></div></nav><!-- START MAIN INDEX --><div class="container row-margin-10"><div class="alert alert-success alert-dismissible hide fade d-none" role="alert"><div id="alert"></div><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("hide").removeClass("show").addClass("d-none")})</script><div class="accordion" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingFirmware"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware"><b>Upload firmware</b></button></h2><div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion"><div class="accordion-body"><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-2 themed-grid-col bg-light">Current version:</div><div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Platform:</div><div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div></div><form id="uploadForm" enctype="multipart/form-data"><div class="row mb-3"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload"></div></div><div class="row mb-3"><div class="col-md-4"><button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button></div></div></form><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><script type="text/javascript">window.onload = getStatus;
$(document).ready(function() { $(document).ready(function() {
$("#uploadForm").on('submit', function(e) { $("#uploadForm").on('submit', function(e) {
@ -26,7 +26,7 @@
showError("Upload failed"); showError("Upload failed");
}, },
success: function(resp) { success: function(resp) {
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser."); showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser. If you don't get any gyro readings after update, please press the reset button!");
setTimeout(() => { setTimeout(() => {
window.location = "/"; window.location = "/";
}, 10000); }, 10000);

View File

@ -15,8 +15,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
@ -24,9 +22,26 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a> <a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="#">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</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> </li>
</ul> </ul>
</div> </div>
@ -34,29 +49,54 @@
</div> </div>
</nav> </nav>
<!-- START MAIN INDEX --> <!-- START MAIN INDEX -->
<div class="container row-margin-10"> <div class="container row-margin-10">
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert"> <div class="alert alert-success alert-dismissible hide fade d-none" role="alert" id="alert">
<div id="alert"></div> <div id="alert-msg"></div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="alert alert-warning alert-dismissible hide fade d-none" role="alert" id="warning-ha">
<div>Home Assistant device configuration detected in MQTT format. These messages will be posted when format is saved and not during gravity measurement.</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="alert alert-warning alert-dismissible hide fade d-none" role="alert" id="warning-length">
<div>The format template is quite large, its possible the device will not have enough memory to handle it.</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
function showError( msg ) { function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none'); $('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
$('#alert').text( msg ); $('#alert-msg').text( msg );
} }
function showSuccess( msg ) { function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none'); $('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
$('#alert').text( msg ); $('#alert-msg').text( msg );
} }
$("#alert-btn").click(function(e){ $("#alert-btn").click(function(e){
$('.alert').addClass('hide').removeClass('show').addClass('d-none'); $('#alert').addClass('hide').removeClass('show').addClass('d-none');
}); });
function showWarningHomeAssistant() {
$('#warning-ha').removeClass('d-none').addClass('show').removeClass('hide');
}
function hideWarningHomeAssistant() {
$('#warning-ha').addClass('d-none').removeClass('show').addClass('hide');
}
function showWarningLength() {
$('#warning-length').removeClass('d-none').addClass('show').removeClass('hide');
}
function hideWarningLength() {
$('#warning-length').addClass('d-none').removeClass('show').addClass('hide');
}
</script> </script>
<div class="accordion" id="accordion"> <div class="accordion" id="accordion">
@ -89,7 +129,7 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-sm-12"> <div class="col-sm-12">
<textarea rows="10" class="form-control" name="format" id="format"> <textarea rows="10" class="form-control" name="format" id="format" onchange="updateStatusField()" onkeydown="updateStatusField()">
</textarea> </textarea>
</div> </div>
</div> </div>
@ -102,7 +142,7 @@
{ "id": "BrewFatherCustom-Post", "format": "%7B%20%20%20%22name%22%3A%20%22%24%7Bmdns%7D%22%2C%20%20%20%22temp%22%3A%20%24%7Btemp%7D%2C%20%20%20%22aux_temp%22%3A%200%2C%20%20%20%22ext_temp%22%3A%200%2C%20%20%20%22temp_unit%22%3A%20%22%24%7Btemp-unit%7D%22%2C%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%20%20%22gravity_unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%20%20%20%22pressure%22%3A%200%2C%20%20%20%22pressure_unit%22%3A%20%22PSI%22%2C%20%20%20%22ph%22%3A%200%2C%20%20%20%22bpm%22%3A%200%2C%20%20%20%22comment%22%3A%20%22%22%2C%20%20%20%22beer%22%3A%20%22%22%2C%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%7D" }, { "id": "BrewFatherCustom-Post", "format": "%7B%20%20%20%22name%22%3A%20%22%24%7Bmdns%7D%22%2C%20%20%20%22temp%22%3A%20%24%7Btemp%7D%2C%20%20%20%22aux_temp%22%3A%200%2C%20%20%20%22ext_temp%22%3A%200%2C%20%20%20%22temp_unit%22%3A%20%22%24%7Btemp-unit%7D%22%2C%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%20%20%22gravity_unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%20%20%20%22pressure%22%3A%200%2C%20%20%20%22pressure_unit%22%3A%20%22PSI%22%2C%20%20%20%22ph%22%3A%200%2C%20%20%20%22bpm%22%3A%200%2C%20%20%20%22comment%22%3A%20%22%22%2C%20%20%20%22beer%22%3A%20%22%22%2C%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%7D" },
{ "id": "iSpindle-Mqtt", "format": "ispindel%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemp_units%3A%24%7Btemp-unit%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Finterval%3A%24%7Bsleep-interval%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2FRSSI%3A%24%7Brssi%7D%7C" }, { "id": "iSpindle-Mqtt", "format": "ispindel%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemp_units%3A%24%7Btemp-unit%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Finterval%3A%24%7Bsleep-interval%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2FRSSI%3A%24%7Brssi%7D%7C" },
{ "id": "HomeAssistant-Mqtt", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0A" }, { "id": "HomeAssistant-Mqtt", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0A" },
{ "id": "HomeAssistant-Mqtt2", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftemperature%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_temp%22%2C%22name%22%3A%22temperature%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%24%7Btemp-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftemperature%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fgravity%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_grav%22%2C%22name%22%3A%22gravity%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%20%24%7Bgravity-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fgravity%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Frssi%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_rssi%22%2C%22name%22%3A%22rssi%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22dBm%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Frssi%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftilt%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_tilt%22%2C%22name%22%3A%22tilt%22%2C%22dev_cla%22%3A%22temperature%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftilt%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fbattery%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_batt%22%2C%22name%22%3A%22battery%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22V%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fbattery%22%7D%7C%0A" }, { "id": "HomeAssistant-Mqtt2", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftemperature%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_temp%22%2C%22name%22%3A%22temperature%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%24%7Btemp-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftemperature%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fgravity%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_grav%22%2C%22name%22%3A%22gravity%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%20%24%7Bgravity-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fgravity%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Frssi%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_rssi%22%2C%22name%22%3A%22rssi%22%2C%22dev_cla%22%3A%22signal_strength%22%2C%22unit_of_meas%22%3A%22dBm%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Frssi%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftilt%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_tilt%22%2C%22name%22%3A%22tilt%22%2C%22dev_cla%22%3A%22temperature%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftilt%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fbattery%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_batt%22%2C%22name%22%3A%22battery%22%2C%22dev_cla%22%3A%22voltage%22%2C%22unit_of_meas%22%3A%22V%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fbattery%22%7D%7C%0A" },
{ "id": "Brewblox-Mqtt", "format": "brewcast%2Fhistory%3A%7B%22key%22%3A%22%24%7Bmdns%7D%22%2C%22data%22%3A%7B%22Temperature%5BdegC%5D%22%3A%20%24%7Btemp-c%7D%2C%22Temperature%5BdegF%5D%22%3A%20%24%7Btemp-f%7D%2C%22Battery%5BV%5D%22%3A%24%7Bbattery%7D%2C%22Tilt%5Bdeg%5D%22%3A%24%7Bangle%7D%2C%22Rssi%5BdBm%5D%22%3A%24%7Brssi%7D%2C%22SG%22%3A%24%7Bgravity-sg%7D%2C%22Plato%22%3A%24%7Bgravity-plato%7D%7D%7D%7C" }, { "id": "Brewblox-Mqtt", "format": "brewcast%2Fhistory%3A%7B%22key%22%3A%22%24%7Bmdns%7D%22%2C%22data%22%3A%7B%22Temperature%5BdegC%5D%22%3A%20%24%7Btemp-c%7D%2C%22Temperature%5BdegF%5D%22%3A%20%24%7Btemp-f%7D%2C%22Battery%5BV%5D%22%3A%24%7Bbattery%7D%2C%22Tilt%5Bdeg%5D%22%3A%24%7Bangle%7D%2C%22Rssi%5BdBm%5D%22%3A%24%7Brssi%7D%2C%22SG%22%3A%24%7Bgravity-sg%7D%2C%22Plato%22%3A%24%7Bgravity-plato%7D%7D%7D%7C" },
{ "id": "UBIDots-Post", "format": "%7B%0A%20%20%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%20%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%20%20%22rssi%22%3A%20%24%7Brssi%7D%0A%7D" } ]; { "id": "UBIDots-Post", "format": "%7B%0A%20%20%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%20%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%20%20%22rssi%22%3A%20%24%7Brssi%7D%0A%7D" } ];
</script> </script>
@ -130,6 +170,9 @@
</div> </div>
</div> </div>
<div class="col-sm-8" id="status">
</div>
<hr class="my-2"> <hr class="my-2">
<pre class="card-preview" id="preview" name="preview"></pre> <pre class="card-preview" id="preview" name="preview"></pre>
@ -143,6 +186,8 @@
<script type="text/javascript"> <script type="text/javascript">
window.onload = getConfig; window.onload = getConfig;
var maxCharsInFormatTemplate = 1500;
setButtonDisabled( true ); setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseOne to #collapseFour) // Opens the targetet according (if URL has #collapseOne to #collapseFour)
@ -153,6 +198,17 @@
} }
}); });
function updateStatusField() {
var t = $("#format").val();
$("#status").text( t.length + " characters in template, recommended maximum is " + maxCharsInFormatTemplate );
if (t.length > maxCharsInFormatTemplate) {
showWarningLength();
} else {
hideWarningLength();
}
};
$("#push-target").change(function(e){ $("#push-target").change(function(e){
console.log(e) console.log(e)
selectFormat(); selectFormat();
@ -169,29 +225,88 @@
$("#format").val( decodeURIComponent(item.format) ); $("#format").val( decodeURIComponent(item.format) );
} }
}); });
updateStatusField();
}); });
// Clear the selected template // Clear the selected template
$("#clear-btn").click(function(e) { $("#clear-btn").click(function(e) {
$("#format").val( "" ); $("#format").val( "" );
updateStatusField();
}); });
// Store the format // Store the format
$("#format-btn").click(function(e) { $("#format-btn").click(function(e) {
var s = $("#format").val(); var s = $("#format").val();
/*if (s.length > maxCharsInFormatTemplate) {
showError("Format template is too large for the device to handle, unable to save.")
return;
}*/
$('#spinner').show();
setButtonDisabled( true );
var ha = false;
hideWarningHomeAssistant();
if ($("#push-target").val() == "mqtt") {
if (s.search("homeassistant/sensor/") != -1) {
console.log("Current format is mqtt, it contains topics for Home Assistant device registration.")
showWarningHomeAssistant();
ha = true;
}
}
s = s.replaceAll("\n", ""); s = s.replaceAll("\n", "");
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s); var obj = 'id=' + $("#id").val() + "&" + $("#push-target").val() + '=' + encodeURIComponent(s);
console.log(obj); console.log(obj);
$.ajax( { $.ajax( {
type: "POST", type: "POST",
url: "/api/config/format", url: "/api/config/format",
data: obj, data: obj,
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); }, success: function(result) { showSuccess('Format stored successfully.'); postHomeAssistant(ha); },
error: function(result) { showError('Unable to store format.'); } error: function(result) { showError('Unable to store format.'); $('#spinner').hide(); },
always: function() { $('#spinner').hide(); setButtonDisabled( false ); }
} ); } );
}); });
function postHomeAssistant(active) {
if (!active) {
getConfig();
return;
}
console.log("Current format is mqtt, running post tests to register device.")
$('#spinner').show();
setButtonDisabled( true );
var url = "/api/test/push";
url += "?id=" + $("#id").val() + "&format=mqtt";
//var url = "/test/push.json";
$.getJSON(url, function (cfg) {
console.log(cfg);
var code = cfg["code"];
var success = cfg["success"];
var enabled = cfg["enabled"];
if(success) {
showSuccess( "Format stored successfully. Home Assistant Device Registration Successful." );
} else {
showError( "Format stored successfully. Home Assistant Device Registration Failed!" );
}
})
.fail(function() {
showError( "Format stored successfully. Home Assistant Device Registration Failed!" );
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
getConfig();
});
}
// Test the calibration // Test the calibration
$("#test-btn").click(function(e) { $("#test-btn").click(function(e) {
var url = "/api/status"; var url = "/api/status";
@ -265,6 +380,9 @@
function setButtonDisabled( b ) { function setButtonDisabled( b ) {
$("#format-btn").prop("disabled", b); $("#format-btn").prop("disabled", b);
$("#test-btn").prop("disabled", b); $("#test-btn").prop("disabled", b);
$("#copy-btn").prop("disabled", b);
$("#clear-btn").prop("disabled", b);
$("#push-target").prop("disabled", b);
} }
function selectFormat() { function selectFormat() {
@ -276,6 +394,7 @@
console.log(s); console.log(s);
$("#format").val(s); $("#format").val(s);
$("#preview").text(""); $("#preview").text("");
updateStatusField();
} }
// Get the configuration values from the API // Get the configuration values from the API
@ -283,7 +402,7 @@
setButtonDisabled( true ); setButtonDisabled( true );
var url = "/api/config/format"; var url = "/api/config/format";
//var url = "/test/format.json"; // var url = "/test/format.json";
$('#spinner').show(); $('#spinner').show();
$.getJSON(url, function (cfg) { $.getJSON(url, function (cfg) {
console.log( cfg ); console.log( cfg );
@ -303,6 +422,8 @@
$('#spinner').hide(); $('#spinner').hide();
setButtonDisabled( false ); setButtonDisabled( false );
}); });
updateStatusField();
} }
</script> </script>

File diff suppressed because one or more lines are too long

View File

@ -16,8 +16,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
@ -27,12 +25,20 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="#"><b>Home</b></a> <a class="nav-link active" href="#">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="/config.htm">Configuration</a> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/calibration.htm">Calibration</a> <a class="nav-link" href="/calibration.htm">Calibration</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -44,6 +50,7 @@
</div> </div>
</nav> </nav>
<!-- START MAIN INDEX --> <!-- START MAIN INDEX -->
<div class="container row-margin-10"> <div class="container row-margin-10">
@ -153,11 +160,33 @@
}); });
setInterval(function() { setInterval(function() {
loadLog(); loadLog();
}, 5000); }, 30000);
function loadLog() { function loadLog() {
$("#logContent").load("/log"); var url2 = "/log2";
//$("#logContent").load("/test/log"); var url1 = "/log";
//var url2 = "/test/log2";
//var url1 = "/test/log1";
var log = "";
$.get(url2, function(data) {
console.log(data);
var list = data.split("\n");
list.forEach(function (item, index) {
log = item + "\n" + log;
});
}).always( function() {
$.get(url1, function(data) {
console.log(data);
var list = data.split("\n");
list.forEach(function (item, index) {
log = item + "\n" + log;
});
}).always( function() {
console.log(log);
$("#logContent").text(log);
});
});
}; };
</script> </script>
@ -175,13 +204,13 @@
<div class="collapse row-margin-10" id="collapseLog"> <div class="collapse row-margin-10" id="collapseLog">
<div class="card card-body"> <div class="card card-body">
<pre><code class="card-text" id="logContent"></code></pre> <pre><code class="card-text" id="logContent" data-bs-toggle="tooltip" title="Shows the last errors on the device, newest on top.">Loading log data, please wait...</code></pre>
</div> </div>
</div> </div>
<div class="collapse row-margin-10" id="collapseSupport"> <div class="collapse row-margin-10" id="collapseSupport">
<div class="card card-body"> <div class="card card-body">
<pre><code class="card-text" id="supportContent"></code></pre> <pre><code class="card-text" id="supportContent">Collecting support data, please wait...</code></pre>
</div> </div>
</div> </div>
@ -293,7 +322,11 @@
var angle = cfg["angle"]; var angle = cfg["angle"];
if(angle==0) { if(angle==-1) {
showError("Unable to connect to gyro, try a reset or power off/on")
$("#angle").text("No gyro");
$("#gravity").text("No gyro");
} else if(angle==0) {
$("#angle").text("Gyro moving"); $("#angle").text("Gyro moving");
$("#gravity").text("Gyro moving"); $("#gravity").text("Gyro moving");
} else { } else {
@ -322,10 +355,14 @@
$("#battery").text(batt + " V (" + charge + "%)" ); $("#battery").text(batt + " V (" + charge + "%)" );
if( cfg["temp-format"] == "C") if( cfg["temp-c"] == -273) {
$("#temp").text(cfg["temp-c"] + " C"); $("#temp").text("No temp sensor");
else } else {
$("#temp").text(cfg["temp-f"] + " F"); if( cfg["temp-format"] == "C")
$("#temp").text(cfg["temp-c"] + " C");
else
$("#temp").text(cfg["temp-f"] + " F");
}
if( cfg["sleep-mode"] ) if( cfg["sleep-mode"] )
$("#sleep-mode").attr("checked", true ); $("#sleep-mode").attr("checked", true );
@ -342,7 +379,7 @@
} }
function start() { function start() {
setInterval(getStatus, 3000); setInterval(getStatus, 5000);
} }
</script> </script>

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,6 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
@ -24,9 +22,26 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a> <a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
<li><a class="dropdown-item" href="#">Test push</a></li>
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
</ul>
</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> </li>
</ul> </ul>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,7 @@ framework = arduino
board = d1_mini board = d1_mini
build_unflags = build_unflags =
build_flags = build_flags =
-Wl,-Map,output.map
-D BAUD=${common_env_data.monitor_speed} -D BAUD=${common_env_data.monitor_speed}
-D ACTIVATE_OTA -D ACTIVATE_OTA
#-D DEBUG_ESP_HTTP_CLIENT #-D DEBUG_ESP_HTTP_CLIENT
@ -34,8 +35,8 @@ build_flags =
-D USER_SSID=\""\"" # =\""myssid\"" -D USER_SSID=\""\"" # =\""myssid\""
-D USER_SSID_PWD=\""\"" # =\""mypwd\"" -D USER_SSID_PWD=\""\"" # =\""mypwd\""
-D CFG_APPVER="\"1.1.0\"" -D CFG_APPVER="\"1.1.0\""
-D CFG_GITREV=\""beta-2\"" #-D CFG_GITREV=\""beta-4\""
#!python script/git_rev.py !python script/git_rev.py
lib_deps = # Switched to forks for better version control. lib_deps = # Switched to forks for better version control.
# Using local copy of these libraries # Using local copy of these libraries
#https://github.com/mp-se/i2cdevlib.git#<document> #https://github.com/mp-se/i2cdevlib.git#<document>
@ -62,9 +63,8 @@ extra_scripts =
build_unflags = build_unflags =
${common_env_data.build_unflags} ${common_env_data.build_unflags}
build_flags = build_flags =
-Wl,-Map,output.map
${common_env_data.build_flags} ${common_env_data.build_flags}
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS -D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
#-D SKIP_SLEEPMODE #-D SKIP_SLEEPMODE
#-D DOUBLERESETDETECTOR_DEBUG=true #-D DOUBLERESETDETECTOR_DEBUG=true
#-D FORCE_GRAVITY_MODE # used to debug gravity mode #-D FORCE_GRAVITY_MODE # used to debug gravity mode

View File

@ -52,8 +52,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
#endif #endif
if (noAngles < 3) { if (noAngles < 3) {
ErrorFileLog errLog; writeErrorLog("CALC: Not enough values for deriving formula");
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
return ERR_FORMULA_NOTENOUGHVALUES; return ERR_FORMULA_NOTENOUGHVALUES;
} else { } else {
double coeffs[order + 1]; double coeffs[order + 1];
@ -98,13 +97,10 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
// If the deviation is more than 2 degress we mark it as failed. // If the deviation is more than 2 degress we mark it as failed.
if (dev * 1000 > myAdvancedConfig.getMaxFormulaCreationDeviation()) { if (dev * 1000 > myAdvancedConfig.getMaxFormulaCreationDeviation()) {
char s[120]; writeErrorLog(
snprintf(&s[0], sizeof(s), "CALC: Validation failed on angle %.2f, deviation too large %.4f "
"CALC: Validation failed on angle %f, deviation too large " "SG, formula order %d",
"%.2f SG, formula order %d", fd.a[i], dev * 1000, order);
fd.a[i], dev * 1000, order);
ErrorFileLog errLog;
errLog.addEntry(&s[0]);
valid = false; valid = false;
} }
} }
@ -118,8 +114,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
} }
} }
ErrorFileLog errLog; writeErrorLog("CALC: Internal error finding formula.");
errLog.addEntry(F("CALC: Internal error finding formula."));
return ERR_FORMULA_INTERNAL; return ERR_FORMULA_INTERNAL;
} }
@ -164,8 +159,7 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
return g; return g;
} }
ErrorFileLog errLog; writeErrorLog("CALC: Failed to parse gravity expression %d", err);
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
return 0; return 0;
} }
@ -211,10 +205,9 @@ double gravityTemperatureCorrectionC(double gravitySG, double tempC,
return g; return g;
} }
ErrorFileLog errLog; writeErrorLog(
errLog.addEntry( "CALC: Failed to parse expression for gravity temperature correction %d",
"CALC: Failed to parse expression for gravity temperature correction " + err);
String(err));
return gravitySG; return gravitySG;
} }

View File

@ -145,8 +145,7 @@ bool Config::saveFile() {
File configFile = LittleFS.open(CFG_FILENAME, "w"); File configFile = LittleFS.open(CFG_FILENAME, "w");
if (!configFile) { if (!configFile) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to save configuration.");
errLog.addEntry(F("CFG : Failed to save configuration."));
return false; return false;
} }
@ -176,16 +175,14 @@ bool Config::loadFile() {
#endif #endif
if (!LittleFS.exists(CFG_FILENAME)) { if (!LittleFS.exists(CFG_FILENAME)) {
ErrorFileLog errLog; writeErrorLog("CFG : Configuration file does not exist.");
errLog.addEntry(F("CFG : Configuration file does not exist."));
return false; return false;
} }
File configFile = LittleFS.open(CFG_FILENAME, "r"); File configFile = LittleFS.open(CFG_FILENAME, "r");
if (!configFile) { if (!configFile) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to load configuration.");
errLog.addEntry(F("CFG : Failed to load configuration."));
return false; return false;
} }
@ -201,8 +198,7 @@ bool Config::loadFile() {
configFile.close(); configFile.close();
if (err) { if (err) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to parse configuration (json)");
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
return false; return false;
} }
@ -377,8 +373,7 @@ bool AdvancedConfig::saveFile() {
File configFile = LittleFS.open(CFG_HW_FILENAME, "w"); File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
if (!configFile) { if (!configFile) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to write hardware configuration ");
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
return false; return false;
} }
@ -429,8 +424,7 @@ bool AdvancedConfig::loadFile() {
File configFile = LittleFS.open(CFG_HW_FILENAME, "r"); File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
if (!configFile) { if (!configFile) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to read hardware configuration");
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
return false; return false;
} }
@ -446,8 +440,7 @@ bool AdvancedConfig::loadFile() {
configFile.close(); configFile.close();
if (err) { if (err) {
ErrorFileLog errLog; writeErrorLog("CFG : Failed to parse hardware configuration (json)");
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
return false; return false;
} }

View File

@ -46,8 +46,7 @@ bool GyroSensor::setup() {
uint8_t id = accelgyro.getDeviceID(); uint8_t id = accelgyro.getDeviceID();
if (id != 0x34 && id != 0x38) { // Allow both MPU6050 and MPU6000 if (id != 0x34 && id != 0x38) { // Allow both MPU6050 and MPU6000
ErrorFileLog errLog; writeErrorLog("GYRO: Failed to connect to gyro, is it connected?");
errLog.addEntry(F("GYRO: Failed to connect to gyro, is it connected?"));
_sensorConnected = false; _sensorConnected = false;
} else { } else {
#if !defined(GYRO_DISABLE_LOGGING) #if !defined(GYRO_DISABLE_LOGGING)
@ -294,9 +293,8 @@ void GyroSensor::applyCalibration() {
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az + if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) == _calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
0) { 0) {
ErrorFileLog errLog; writeErrorLog(
errLog.addEntry( "GYRO: No valid calibration values, please calibrate the device.");
F("GYRO: No valid calibration values, please calibrate the device."));
return; return;
} }

View File

@ -47,75 +47,68 @@ void tcp_cleanup() {
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs); while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
} }
// void checkResetReason() {
// Convert sg to plato #if defined(ESP8266)
// rst_info* _rinfo;
_rinfo = ESP.getResetInfoPtr();
Log.notice(F("HELP: Last reset cause %d" CR), _rinfo->exccause);
if (_rinfo->exccause > 0) {
char s[120];
snprintf(&s[0], sizeof(s),
"HELP: Exception (%d) reason=%d epc1=0x%08x epc2=0x%08x "
"epc3=0x%08x execvaddr=0x%08x depc=0x%08x",
_rinfo->exccause, _rinfo->reason, _rinfo->epc1, _rinfo->epc2,
_rinfo->epc3, _rinfo->excvaddr, _rinfo->depc);
writeErrorLog(&s[0]);
}
#else // defined (ESP32)
#warning "Todo: Handle reset reasons on ESP32"
#endif
}
void writeErrorLog(const char* format, ...) {
File f = LittleFS.open(ERR_FILENAME, "a");
if (f && f.size() > ERR_FILEMAXSIZE) {
f.close();
LittleFS.remove(ERR_FILENAME2);
LittleFS.rename(ERR_FILENAME, ERR_FILENAME2);
f = LittleFS.open(ERR_FILENAME, "a");
}
va_list arg;
va_start(arg, format);
char buf[120];
vsnprintf(&buf[0], sizeof(buf), format, arg);
va_end(arg);
Log.errorln(&buf[0]);
if (f) {
#if defined(ESP8266)
f.write(&buf[0], strlen(&buf[0]));
#else // ESP32
f.write((unsigned char*)&buf[0], strlen(&buf[0]));
#endif
f.println();
f.close();
} else {
Log.warning(F("HELP: Failed to open error log." CR));
}
}
double convertToPlato(double sg) { double convertToPlato(double sg) {
if (sg) return 259 - (259 / sg); if (sg) return 259 - (259 / sg);
return 0; return 0;
} }
//
// Convert plato to sg
//
double convertToSG(double plato) { return 259 / (259 - plato); } double convertToSG(double plato) { return 259 / (259 - plato); }
//
// Conversion to F
//
float convertCtoF(float c) { return (c * 1.8) + 32.0; } float convertCtoF(float c) { return (c * 1.8) + 32.0; }
//
// Conversion to C
//
float convertFtoC(float f) { return (f - 32.0) / 1.8; } 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) { FloatHistoryLog::FloatHistoryLog(String fName) {
_fName = fName; _fName = fName;
@ -133,9 +126,6 @@ FloatHistoryLog::FloatHistoryLog(String fName) {
} }
} }
//
// Add entry to top of log
//
void FloatHistoryLog::addEntry(float time) { void FloatHistoryLog::addEntry(float time) {
for (int i = (10 - 1); i > 0; i--) { for (int i = (10 - 1); i > 0; i--) {
_runTime[i] = _runTime[i - 1]; _runTime[i] = _runTime[i - 1];
@ -144,9 +134,6 @@ void FloatHistoryLog::addEntry(float time) {
save(); save();
} }
//
// Save log
//
void FloatHistoryLog::save() { void FloatHistoryLog::save() {
File runFile = LittleFS.open(_fName, "w"); File runFile = LittleFS.open(_fName, "w");
if (runFile) { if (runFile) {
@ -157,9 +144,6 @@ void FloatHistoryLog::save() {
} }
} }
//
// Print the heap information.
//
void printHeap(String prefix) { void printHeap(String prefix) {
#if defined(ESP8266) #if defined(ESP8266)
Log.notice( Log.notice(
@ -175,9 +159,6 @@ void printHeap(String prefix) {
#endif #endif
} }
//
// Enter deep sleep for the defined duration (Argument is seconds)
//
void deepSleep(int t) { void deepSleep(int t) {
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING) #if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t); Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
@ -186,9 +167,6 @@ void deepSleep(int t) {
ESP.deepSleep(wake); ESP.deepSleep(wake);
} }
//
// Print the build options used
//
void printBuildOptions() { void printBuildOptions() {
Log.notice(F("Build options: %s (%s) LOGLEVEL %d " Log.notice(F("Build options: %s (%s) LOGLEVEL %d "
#ifdef SKIP_SLEEPMODE #ifdef SKIP_SLEEPMODE
@ -201,9 +179,6 @@ void printBuildOptions() {
CFG_APPVER, CFG_GITREV, LOG_LEVEL); CFG_APPVER, CFG_GITREV, LOG_LEVEL);
} }
//
// Configure serial debug output
//
SerialDebug::SerialDebug(const uint32_t serialSpeed) { SerialDebug::SerialDebug(const uint32_t serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD) // Start serial with auto-detected rate (default to defined BAUD)
Serial.flush(); Serial.flush();
@ -214,18 +189,12 @@ SerialDebug::SerialDebug(const uint32_t serialSpeed) {
getLog()->notice(F("SDBG: Serial logging started at %u." CR), serialSpeed); getLog()->notice(F("SDBG: Serial logging started at %u." CR), serialSpeed);
} }
//
// Print the timestamp (ms since start of device)
//
void printTimestamp(Print* _logOutput, int _logLevel) { void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12]; char c[12];
snprintf(c, sizeof(c), "%10lu ", millis()); snprintf(c, sizeof(c), "%10lu ", millis());
_logOutput->print(c); _logOutput->print(c);
} }
//
// Read and calculate the battery voltage
//
void BatteryVoltage::read() { void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce // The analog pin can only handle 3.3V maximum voltage so we need to reduce
// the voltage (from max 5V) // the voltage (from max 5V)
@ -251,9 +220,6 @@ void BatteryVoltage::read() {
PerfLogging myPerfLogging; PerfLogging myPerfLogging;
//
// Clear the current cache
//
void PerfLogging::clear() { void PerfLogging::clear() {
// Clear the measurements // Clear the measurements
if (first == 0) return; if (first == 0) return;
@ -270,17 +236,11 @@ void PerfLogging::clear() {
} while (pe != 0); } while (pe != 0);
} }
//
// Start measuring this performance point
//
void PerfLogging::start(const char* key) { void PerfLogging::start(const char* key) {
PerfEntry* pe = add(key); PerfEntry* pe = add(key);
pe->start = millis(); pe->start = millis();
} }
//
// Finalize measuring of this performance point
//
void PerfLogging::stop(const char* key) { void PerfLogging::stop(const char* key) {
PerfEntry* pe = find(key); PerfEntry* pe = find(key);
@ -293,9 +253,6 @@ void PerfLogging::stop(const char* key) {
} }
} }
//
// Print the collected performance data
//
void PerfLogging::print() { void PerfLogging::print() {
PerfEntry* pe = first; PerfEntry* pe = first;
@ -305,9 +262,6 @@ void PerfLogging::print() {
} }
} }
//
// Push collected performance data to influx (use influx configuration)
//
void PerfLogging::pushInflux() { void PerfLogging::pushInflux() {
if (!myConfig.isInfluxDb2Active()) return; if (!myConfig.isInfluxDb2Active()) return;
@ -409,29 +363,19 @@ void PerfLogging::pushInflux() {
#endif // COLLECT_PERFDATA #endif // COLLECT_PERFDATA
//
// Convert float to formatted string with n decimals. Buffer should be at least
// 10 chars.
//
char* convertFloatToString(float f, char* buffer, int dec) { char* convertFloatToString(float f, char* buffer, int dec) {
dtostrf(f, 6, dec, buffer); dtostrf(f, 6, dec, buffer);
return buffer; return buffer;
} }
//
// Reduce precision to n decimals
//
float reduceFloatPrecision(float f, int dec) { float reduceFloatPrecision(float f, int dec) {
char buffer[5]; char buffer[5];
dtostrf(f, 6, dec, &buffer[0]); dtostrf(f, 6, dec, &buffer[0]);
return atof(&buffer[0]); return atof(&buffer[0]);
} }
//
// urlencode // urlencode
//
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/ // https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
//
String urlencode(String str) { String urlencode(String str) {
String encodedString; String encodedString;
encodedString.reserve(str.length() * 2); encodedString.reserve(str.length() * 2);
@ -475,9 +419,6 @@ unsigned char h2int(char c) {
return (0); return (0);
} }
//
// urlencode string
//
String urldecode(String str) { String urldecode(String str) {
String encodedString; String encodedString;
encodedString.reserve(str.length()); encodedString.reserve(str.length());

View File

@ -28,13 +28,18 @@ SOFTWARE.
#include <main.hpp> #include <main.hpp>
#define ERR_FILENAME "/error.log" #define ERR_FILENAME "/error.log"
#define ERR_COUNT 15 #define ERR_FILENAME2 "/error2.log"
#define ERR_FILEMAXSIZE 4000
#define RUNTIME_FILENAME "/runtime.log" #define RUNTIME_FILENAME "/runtime.log"
// tcp cleanup // tcp cleanup
void tcp_cleanup(); void tcp_cleanup();
// Error logging
void writeErrorLog(const char* format, ...);
void checkResetReason();
// Sleep mode // Sleep mode
void deepSleep(int t); void deepSleep(int t);
@ -67,16 +72,6 @@ class SerialDebug {
static Logging* getLog() { return &Log; } static Logging* getLog() { return &Log; }
}; };
class ErrorFileLog {
private:
String _errors[ERR_COUNT];
public:
ErrorFileLog();
void addEntry(String error);
void save();
};
class FloatHistoryLog { class FloatHistoryLog {
private: private:
String _fName; String _fName;

View File

@ -38,7 +38,7 @@ SOFTWARE.
#ifdef DEACTIVATE_SLEEPMODE #ifdef DEACTIVATE_SLEEPMODE
const int interval = 1000; // ms, time to wait between changes to output const int interval = 1000; // ms, time to wait between changes to output
#else #else
int interval = 200; // ms, time to wait between changes to output int interval = 200; // ms, time to wait between changes to output
#endif #endif
bool sleepModeAlwaysSkip = bool sleepModeAlwaysSkip =
false; // Flag set in web interface to override normal behaviour false; // Flag set in web interface to override normal behaviour
@ -117,10 +117,13 @@ void checkSleepMode(float angle, float volt) {
Log.notice( Log.notice(
F("Main: Storage mode entered, going to sleep for maximum time." CR)); F("Main: Storage mode entered, going to sleep for maximum time." CR));
#if defined(ESP8266) #if defined(ESP8266)
ESP.deepSleep(ESP.deepSleepMax()); // ESP.deepSleep(ESP.deepSleepMax());
ESP.deepSleep(0); // indefinite sleep
#else #else
#warning "Check and test the max deep sleep for esp32" #warning "Check and test the max deep sleep for esp32"
deepSleep(70 * 60); // quick search on internet suggest max time is 70 min // deepSleep(70 * 60); // quick search on internet suggest max time is 70
// min
ESP.deepSleep(0); // indefinite sleep
#endif #endif
} }
} }
@ -133,12 +136,9 @@ void setup() {
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING) #if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
// Add a delay so that serial is started. // Add a delay so that serial is started.
// delay(3000); // delay(3000);
#if defined(ESP8266)
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
#else // defined (ESP32)
#endif
#endif #endif
// Main startup // Main startup
#if defined(ESP8266) #if defined(ESP8266)
Log.notice(F("Main: Started setup for %s." CR), Log.notice(F("Main: Started setup for %s." CR),
String(ESP.getChipId(), HEX).c_str()); String(ESP.getChipId(), HEX).c_str());
@ -157,6 +157,7 @@ void setup() {
LOG_PERF_START("main-config-load"); LOG_PERF_START("main-config-load");
myConfig.checkFileSystem(); myConfig.checkFileSystem();
checkResetReason();
myConfig.loadFile(); myConfig.loadFile();
myWifi.init(); myWifi.init();
myAdvancedConfig.loadFile(); myAdvancedConfig.loadFile();
@ -192,14 +193,14 @@ void setup() {
break; break;
default: default:
if (!myGyro.setup()) { if (myGyro.setup()) {
ErrorFileLog errLog;
errLog.addEntry(
F("MAIN: Failed to initialize the gyro, is it connected?"));
} else {
LOG_PERF_START("main-gyro-read"); LOG_PERF_START("main-gyro-read");
myGyro.read(); myGyro.read();
LOG_PERF_STOP("main-gyro-read"); LOG_PERF_STOP("main-gyro-read");
} else {
Log.notice(
F("Main: Failed to connect to the gyro, software will not be able "
"to detect angles." CR));
} }
myBatteryVoltage.read(); myBatteryVoltage.read();

View File

@ -34,9 +34,6 @@ SOFTWARE.
#define PUSHINT_FILENAME "/push.dat" #define PUSHINT_FILENAME "/push.dat"
//
// Decrease counters
//
void PushIntervalTracker::update(const int index, const int defaultValue) { void PushIntervalTracker::update(const int index, const int defaultValue) {
if (_counters[index] <= 0) if (_counters[index] <= 0)
_counters[index] = defaultValue; _counters[index] = defaultValue;
@ -44,9 +41,6 @@ void PushIntervalTracker::update(const int index, const int defaultValue) {
_counters[index]--; _counters[index]--;
} }
//
// Load data from file
//
void PushIntervalTracker::load() { void PushIntervalTracker::load() {
File intFile = LittleFS.open(PUSHINT_FILENAME, "r"); File intFile = LittleFS.open(PUSHINT_FILENAME, "r");
@ -143,10 +137,11 @@ void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
if (myConfig.isMqttActive() && intDelay.useMqtt()) { if (myConfig.isMqttActive() && intDelay.useMqtt()) {
LOG_PERF_START("push-mqtt"); LOG_PERF_START("push-mqtt");
sendMqtt(engine, myConfig.isMqttSSL()); sendMqtt(engine, myConfig.isMqttSSL(), true);
LOG_PERF_STOP("push-mqtt"); LOG_PERF_STOP("push-mqtt");
} }
engine.freeMemory();
intDelay.save(); intDelay.save();
} }
@ -232,8 +227,7 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine, bool isSecure) {
_lastSuccess = true; _lastSuccess = true;
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), _lastCode); Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), _lastCode);
} else { } else {
ErrorFileLog errLog; writeErrorLog("PUSH: Influxdb push failed response=%d", _lastCode);
errLog.addEntry("PUSH: Influxdb push failed response=" + String(_lastCode));
} }
if (isSecure) { if (isSecure) {
@ -261,8 +255,8 @@ void PushTarget::addHttpHeader(HTTPClient& http, String header) {
value.c_str()); value.c_str());
http.addHeader(name, value); http.addHeader(name, value);
} else { } else {
ErrorFileLog errLog; writeErrorLog("PUSH: Unable to set header, invalid value %s",
errLog.addEntry("PUSH: Unable to set header, invalid value " + header); header.c_str());
} }
} }
@ -338,9 +332,8 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
_lastSuccess = true; _lastSuccess = true;
Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode); Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode);
} else { } else {
ErrorFileLog errLog; writeErrorLog("PUSH: HTTP post failed response=%d http%d", _lastCode,
errLog.addEntry("PUSH: HTTP post failed response=" + String(_lastCode) + index + 1);
String(index == 0 ? " (http)" : " (http2)"));
} }
if (isSecure) { if (isSecure) {
@ -399,8 +392,7 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
_lastSuccess = true; _lastSuccess = true;
Log.notice(F("PUSH: HTTP get successful, response=%d" CR), _lastCode); Log.notice(F("PUSH: HTTP get successful, response=%d" CR), _lastCode);
} else { } else {
ErrorFileLog errLog; writeErrorLog("PUSH: HTTP get failed response=%d", _lastCode);
errLog.addEntry("PUSH: HTTP get failed response=" + String(_lastCode));
} }
if (isSecure) { if (isSecure) {
@ -416,9 +408,11 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
// //
// Send data to mqtt target // Send data to mqtt target
// //
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) { void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure,
bool skipHomeAssistantRegistration) {
#if !defined(PUSH_DISABLE_LOGGING) #if !defined(PUSH_DISABLE_LOGGING)
Log.notice(F("PUSH: Sending values to mqtt." CR)); Log.notice(F("PUSH: Sending values to mqtt. Skip HA registration=%s" CR),
skipHomeAssistantRegistration ? "yes" : "no");
#endif #endif
_lastCode = 0; _lastCode = 0;
_lastSuccess = false; _lastSuccess = false;
@ -483,15 +477,22 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(), Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(),
value.c_str()); value.c_str());
#endif #endif
if (mqtt.publish(topic, value)) {
_lastSuccess = true; if (skipHomeAssistantRegistration &&
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str()); topic.startsWith("homeassistant/sensor/")) {
_lastCode = 0; Log.notice(F("PUSH: Ignoring Home Assistant registration topic %s" CR),
topic.c_str());
} else { } else {
_lastCode = mqtt.lastError(); if (mqtt.publish(topic, value)) {
ErrorFileLog errLog; _lastSuccess = true;
errLog.addEntry("PUSH: MQTT push on " + topic + Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
" failed error=" + String(mqtt.lastError())); _lastCode = 0;
} else {
_lastSuccess = false;
_lastCode = mqtt.lastError();
writeErrorLog("PUSH: MQTT push on %s failed error=%d", topic.c_str(),
_lastCode);
}
} }
index = next + 1; index = next + 1;

View File

@ -61,7 +61,8 @@ class PushTarget {
sendHttpGet(engine, isSecure); sendHttpGet(engine, isSecure);
} }
void sendInfluxDb2(TemplatingEngine& engine, bool isSecure); void sendInfluxDb2(TemplatingEngine& engine, bool isSecure);
void sendMqtt(TemplatingEngine& engine, bool isSecure); void sendMqtt(TemplatingEngine& engine, bool isSecure,
bool skipHomeAssistantRegistration = true);
int getLastCode() { return _lastCode; } int getLastCode() { return _lastCode; }
bool getLastSuccess() { return _lastSuccess; } bool getLastSuccess() { return _lastSuccess; }
}; };

View File

@ -141,30 +141,30 @@ void TemplatingEngine::initialize(float angle, float gravitySG,
// //
// Create the data using defined template. // Create the data using defined template.
// //
const String& TemplatingEngine::create(TemplatingEngine::Templates idx) { const char* TemplatingEngine::create(TemplatingEngine::Templates idx) {
String fname; String fname;
baseTemplate.reserve(600); _baseTemplate.reserve(600);
// Load templates from memory // Load templates from memory
switch (idx) { switch (idx) {
case TEMPLATE_HTTP1: case TEMPLATE_HTTP1:
baseTemplate = String(iSpindleFormat); _baseTemplate = String(iSpindleFormat);
fname = TPL_FNAME_HTTP1; fname = TPL_FNAME_HTTP1;
break; break;
case TEMPLATE_HTTP2: case TEMPLATE_HTTP2:
baseTemplate = String(iSpindleFormat); _baseTemplate = String(iSpindleFormat);
fname = TPL_FNAME_HTTP2; fname = TPL_FNAME_HTTP2;
break; break;
case TEMPLATE_HTTP3: case TEMPLATE_HTTP3:
baseTemplate = String(iHttpGetFormat); _baseTemplate = String(iHttpGetFormat);
fname = TPL_FNAME_HTTP3; fname = TPL_FNAME_HTTP3;
break; break;
case TEMPLATE_INFLUX: case TEMPLATE_INFLUX:
baseTemplate = String(influxDbFormat); _baseTemplate = String(influxDbFormat);
fname = TPL_FNAME_INFLUXDB; fname = TPL_FNAME_INFLUXDB;
break; break;
case TEMPLATE_MQTT: case TEMPLATE_MQTT:
baseTemplate = String(mqttFormat); _baseTemplate = String(mqttFormat);
fname = TPL_FNAME_MQTT; fname = TPL_FNAME_MQTT;
break; break;
} }
@ -175,7 +175,7 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
char buf[file.size() + 1]; char buf[file.size() + 1];
memset(&buf[0], 0, file.size() + 1); memset(&buf[0], 0, file.size() + 1);
file.readBytes(&buf[0], file.size()); file.readBytes(&buf[0], file.size());
baseTemplate = String(&buf[0]); _baseTemplate = String(&buf[0]);
file.close(); file.close();
Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str()); Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str());
} }
@ -185,13 +185,16 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
#endif #endif
// Insert data into template. // Insert data into template.
transform(baseTemplate); transform();
_baseTemplate.clear();
#if LOG_LEVEL == 6 #if LOG_LEVEL == 6
// Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str()); // Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str());
#endif #endif
return baseTemplate; if (_output) return _output;
return "";
} }
// EOF // EOF

View File

@ -75,34 +75,35 @@ class TemplatingEngine {
String val; String val;
}; };
KeyVal items[23] = {{TPL_MDNS, ""}, {TPL_ID, ""}, KeyVal _items[23] = {{TPL_MDNS, ""}, {TPL_ID, ""},
{TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""}, {TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""},
{TPL_TEMP_C, ""}, {TPL_TEMP_F, ""}, {TPL_TEMP_C, ""}, {TPL_TEMP_F, ""},
{TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""}, {TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""},
{TPL_RSSI, ""}, {TPL_RUN_TIME, ""}, {TPL_RSSI, ""}, {TPL_RUN_TIME, ""},
{TPL_ANGLE, ""}, {TPL_TILT, ""}, {TPL_ANGLE, ""}, {TPL_TILT, ""},
{TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""}, {TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""},
{TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""}, {TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""},
{TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""}, {TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""},
{TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""}, {TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""},
{TPL_TOKEN2, ""}, {TPL_APP_VER, ""}, {TPL_TOKEN2, ""}, {TPL_APP_VER, ""},
{TPL_APP_BUILD, ""}}; {TPL_APP_BUILD, ""}};
char buffer[20]; char _buffer[20] = "";
String baseTemplate; String _baseTemplate;
char *_output = 0;
void setVal(String key, float val, int dec = 2) { void setVal(String key, float val, int dec = 2) {
String s = convertFloatToString(val, &buffer[0], dec); String s = convertFloatToString(val, &_buffer[0], dec);
s.trim(); s.trim();
setVal(key, s); setVal(key, s);
} }
void setVal(String key, int val) { setVal(key, String(val)); } void setVal(String key, int val) { setVal(key, String(val)); }
void setVal(String key, char val) { setVal(key, String(val)); } void setVal(String key, char val) { setVal(key, String(val)); }
void setVal(String key, String val) { void setVal(String key, String val) {
int max = sizeof(items) / sizeof(KeyVal); int max = sizeof(_items) / sizeof(KeyVal);
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
if (items[i].key.equals(key)) { if (_items[i].key.equals(key)) {
items[i].val = val; _items[i].val = val;
return; return;
} }
} }
@ -110,21 +111,73 @@ class TemplatingEngine {
Log.warning(F("TPL : Key not found %s." CR), key.c_str()); Log.warning(F("TPL : Key not found %s." CR), key.c_str());
} }
void transform(String& s) { void transform() {
int max = sizeof(items) / sizeof(KeyVal); const char *format = _baseTemplate.c_str();
for (int i = 0; i < max; i++) { int len = _baseTemplate.length();
while (s.indexOf(items[i].key) != -1) int size = len;
s.replace(items[i].key, items[i].val);
// Lets check how much memory will be needed to transform the template
for (int j = 0; j < len - 2; j++) {
if (*(format + j) == '$' && *(format + j + 1) == '{') {
// Start of format tag found
int max = sizeof(_items) / sizeof(KeyVal);
for (int i = 0; i < max; i++) {
if (strncmp(format + j, _items[i].key.c_str(),
_items[i].key.length()) == 0) {
// Found key
size = size - _items[i].key.length() + _items[i].val.length();
}
}
}
} }
freeMemory(); // In case this is reused
_output = static_cast<char *>(malloc(size + 20));
if (!_output) {
Log.error(F("TPL : Unable to allocate memory for transforming template, "
"needed %d." CR),
size);
return;
}
memset(_output, 0, size + 20);
// Lets do the transformation
int k = 0;
for (int j = 0; j < len - 2; j++) {
if (*(format + j) == '$' && *(format + j + 1) == '{') {
// Start of format tag found
int max = sizeof(_items) / sizeof(KeyVal);
for (int i = 0; i < max; i++) {
if (strncmp(format + j, _items[i].key.c_str(),
_items[i].key.length()) == 0) {
// Found key
strncat(_output, format + k, j - k);
strncat(_output, _items[i].val.c_str(), _items[i].val.length());
k = j + _items[i].key.length();
}
}
}
}
strncat(_output, format + k, size - k);
Log.notice(F("TPL : Transformed template %d chars to %d chars" CR),
strlen(format), strlen(_output));
#if LOG_LEVEL == 6
printHeap("TPL ");
Log.verboseln(format);
Log.verboseln(_output);
#endif
} }
void dumpAll() { void dumpAll() {
int max = sizeof(items) / sizeof(KeyVal); int max = sizeof(_items) / sizeof(KeyVal);
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
Serial.print("Key=\'"); Serial.print("Key=\'");
Serial.print(items[i].key.c_str()); Serial.print(_items[i].key.c_str());
Serial.print("\', Val=\'"); Serial.print("\', Val=\'");
Serial.print(items[i].val.c_str()); Serial.print(_items[i].val.c_str());
Serial.println("\'"); Serial.println("\'");
} }
} }
@ -138,9 +191,18 @@ class TemplatingEngine {
TEMPLATE_MQTT = 4 TEMPLATE_MQTT = 4
}; };
TemplatingEngine() {}
~TemplatingEngine() { freeMemory(); }
void freeMemory() {
if (_output) free(_output);
_output = 0;
_baseTemplate.clear();
}
void initialize(float angle, float gravitySG, float corrGravitySG, void initialize(float angle, float gravitySG, float corrGravitySG,
float tempC, float runTime); float tempC, float runTime);
const String& create(TemplatingEngine::Templates idx); const char *create(TemplatingEngine::Templates idx);
}; };
#endif // SRC_TEMPLATING_HPP_ #endif // SRC_TEMPLATING_HPP_

View File

@ -169,9 +169,6 @@ void WebServerHandler::webHandleUpload() {
LOG_PERF_STOP("webserver-api-upload"); LOG_PERF_STOP("webserver-api-upload");
} }
//
// Callback from webServer when / has been accessed.
//
void WebServerHandler::webHandleUploadFile() { void WebServerHandler::webHandleUploadFile() {
LOG_PERF_START("webserver-api-upload-file"); LOG_PERF_START("webserver-api-upload-file");
Log.verbose(F("WEB : webServer callback for /api/upload(post)." CR)); Log.verbose(F("WEB : webServer callback for /api/upload(post)." CR));
@ -213,9 +210,7 @@ void WebServerHandler::webHandleUploadFile() {
maxSketchSpace / 1024); maxSketchSpace / 1024);
if (!Update.begin(maxSketchSpace, U_FLASH, PIN_LED)) { if (!Update.begin(maxSketchSpace, U_FLASH, PIN_LED)) {
ErrorFileLog errLog; writeErrorLog("WEB : Not enough space to store for this firmware.");
errLog.addEntry(
F("WEB : Not enough space to store for this firmware."));
_uploadReturn = 500; _uploadReturn = 500;
} }
} else if (upload.status == UPLOAD_FILE_WRITE) { } else if (upload.status == UPLOAD_FILE_WRITE) {
@ -237,9 +232,8 @@ void WebServerHandler::webHandleUploadFile() {
delay(500); delay(500);
ESP_RESET(); ESP_RESET();
} else { } else {
ErrorFileLog errLog; writeErrorLog("WEB : Failed to finish firmware flashing error=%d",
errLog.addEntry("WEB : Failed to finish firmware flashing error=" + Update.getError());
String(Update.getError()));
_uploadReturn = 500; _uploadReturn = 500;
} }
} else { } else {
@ -297,9 +291,6 @@ void WebServerHandler::webHandleCalibrate() {
LOG_PERF_STOP("webserver-api-calibrate"); LOG_PERF_STOP("webserver-api-calibrate");
} }
//
// Callback from webServer when / has been accessed.
//
void WebServerHandler::webHandleFactoryDefaults() { void WebServerHandler::webHandleFactoryDefaults() {
String id = _server->arg(PARAM_ID); String id = _server->arg(PARAM_ID);
Log.notice(F("WEB : webServer callback for /api/factory." CR)); Log.notice(F("WEB : webServer callback for /api/factory." CR));
@ -323,16 +314,27 @@ void WebServerHandler::webHandleFactoryDefaults() {
} }
} }
// void WebServerHandler::webHandleLogClear() {
// Callback from webServer when / has been accessed. String id = _server->arg(PARAM_ID);
// Log.notice(F("WEB : webServer callback for /api/clearlog." CR));
if (!id.compareTo(myConfig.getID())) {
_server->send(200, "text/plain", "Removing logfiles...");
LittleFS.remove(ERR_FILENAME);
LittleFS.remove(ERR_FILENAME2);
_server->send(200, "text/plain", "Logfiles cleared.");
} else {
_server->send(400, "text/plain", "Unknown ID.");
}
}
void WebServerHandler::webHandleStatus() { void WebServerHandler::webHandleStatus() {
LOG_PERF_START("webserver-api-status"); LOG_PERF_START("webserver-api-status");
Log.notice(F("WEB : webServer callback for /api/status(get)." CR)); Log.notice(F("WEB : webServer callback for /api/status(get)." CR));
DynamicJsonDocument doc(500); DynamicJsonDocument doc(500);
double angle = 0; double angle = 0; // Indicate we have no valid gyro value
if (myGyro.hasValue()) angle = myGyro.getAngle(); if (myGyro.hasValue()) angle = myGyro.getAngle();
@ -340,7 +342,10 @@ void WebServerHandler::webHandleStatus() {
double gravity = calculateGravity(angle, tempC); double gravity = calculateGravity(angle, tempC);
doc[PARAM_ID] = myConfig.getID(); doc[PARAM_ID] = myConfig.getID();
doc[PARAM_ANGLE] = reduceFloatPrecision(angle); doc[PARAM_ANGLE] = myGyro.isConnected()
? reduceFloatPrecision(angle)
: -1; // Indicate that we have no connection to gyro
if (myConfig.isGravityTempAdj()) { if (myConfig.isGravityTempAdj()) {
gravity = gravityTemperatureCorrectionC( gravity = gravityTemperatureCorrectionC(
gravity, tempC, myAdvancedConfig.getDefaultCalibrationTemp()); gravity, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
@ -389,9 +394,6 @@ void WebServerHandler::webHandleStatus() {
LOG_PERF_STOP("webserver-api-status"); LOG_PERF_STOP("webserver-api-status");
} }
//
// Callback from webServer when / has been accessed.
//
void WebServerHandler::webHandleClearWIFI() { void WebServerHandler::webHandleClearWIFI() {
String id = _server->arg(PARAM_ID); String id = _server->arg(PARAM_ID);
Log.notice(F("WEB : webServer callback for /api/clearwifi." CR)); Log.notice(F("WEB : webServer callback for /api/clearwifi." CR));
@ -412,9 +414,6 @@ void WebServerHandler::webHandleClearWIFI() {
} }
} }
//
// Used to force the device to never sleep.
//
void WebServerHandler::webHandleStatusSleepmode() { void WebServerHandler::webHandleStatusSleepmode() {
LOG_PERF_START("webserver-api-sleepmode"); LOG_PERF_START("webserver-api-sleepmode");
String id = _server->arg(PARAM_ID); String id = _server->arg(PARAM_ID);
@ -892,8 +891,7 @@ void WebServerHandler::webHandleConfigFormatWrite() {
_server->sendHeader("Location", "/format.htm", true); _server->sendHeader("Location", "/format.htm", true);
_server->send(302, "text/plain", "Format updated"); _server->send(302, "text/plain", "Format updated");
} else { } else {
ErrorFileLog errLog; writeErrorLog("WEB : Unable to store format file");
errLog.addEntry(F("WEB : Unable to store format file"));
_server->send(400, "text/plain", "Unable to store format in file."); _server->send(400, "text/plain", "Unable to store format in file.");
} }
@ -947,10 +945,11 @@ void WebServerHandler::webHandleTestPush() {
push.sendInfluxDb2(engine, myConfig.isInfluxSSL()); push.sendInfluxDb2(engine, myConfig.isInfluxSSL());
enabled = true; enabled = true;
} else if (!type.compareTo(PARAM_FORMAT_MQTT) && myConfig.isMqttActive()) { } else if (!type.compareTo(PARAM_FORMAT_MQTT) && myConfig.isMqttActive()) {
push.sendMqtt(engine, myConfig.isMqttSSL()); push.sendMqtt(engine, myConfig.isMqttSSL(), false);
enabled = true; enabled = true;
} }
engine.freeMemory();
DynamicJsonDocument doc(100); DynamicJsonDocument doc(100);
doc[PARAM_PUSH_ENABLED] = enabled; doc[PARAM_PUSH_ENABLED] = enabled;
doc[PARAM_PUSH_SUCCESS] = push.getLastSuccess(); doc[PARAM_PUSH_SUCCESS] = push.getLastSuccess();
@ -1022,49 +1021,52 @@ void WebServerHandler::webHandleConfigFormatRead() {
LOG_PERF_START("webserver-api-config-format-read"); LOG_PERF_START("webserver-api-config-format-read");
Log.notice(F("WEB : webServer callback for /api/config/formula(get)." CR)); Log.notice(F("WEB : webServer callback for /api/config/formula(get)." CR));
DynamicJsonDocument doc(5000); String out;
out.reserve(7000);
doc[PARAM_ID] = myConfig.getID(); out += "{\"id\":\"" + String(myConfig.getID()) + "\",";
String s = readFile(TPL_FNAME_HTTP1); String s = readFile(TPL_FNAME_HTTP1);
out += "\"" + String(PARAM_FORMAT_HTTP1) + "\":\"";
if (s.length()) if (s.length())
doc[PARAM_FORMAT_HTTP1] = urlencode(s); out += urlencode(s);
else else
doc[PARAM_FORMAT_HTTP1] = urlencode(String(&iSpindleFormat[0])); out += urlencode(String(&iSpindleFormat[0]));
s = readFile(TPL_FNAME_HTTP2); s = readFile(TPL_FNAME_HTTP2);
out += "\",\"" + String(PARAM_FORMAT_HTTP2) + "\":\"";
if (s.length()) if (s.length())
doc[PARAM_FORMAT_HTTP2] = urlencode(s); out += urlencode(s);
else else
doc[PARAM_FORMAT_HTTP2] = urlencode(String(&iSpindleFormat[0])); out += urlencode(String(&iSpindleFormat[0]));
s = readFile(TPL_FNAME_HTTP3); s = readFile(TPL_FNAME_HTTP3);
out += "\",\"" + String(PARAM_FORMAT_HTTP3) + "\":\"";
if (s.length()) if (s.length())
doc[PARAM_FORMAT_HTTP3] = urlencode(s); out += urlencode(s);
else else
doc[PARAM_FORMAT_HTTP3] = urlencode(String(&iHttpGetFormat[0])); out += urlencode(String(&iHttpGetFormat[0]));
s = readFile(TPL_FNAME_INFLUXDB); s = readFile(TPL_FNAME_INFLUXDB);
out += "\",\"" + String(PARAM_FORMAT_INFLUXDB) + "\":\"";
if (s.length()) if (s.length())
doc[PARAM_FORMAT_INFLUXDB] = urlencode(s); out += urlencode(s);
else else
doc[PARAM_FORMAT_INFLUXDB] = urlencode(String(&influxDbFormat[0])); out += urlencode(String(&influxDbFormat[0]));
s = readFile(TPL_FNAME_MQTT); s = readFile(TPL_FNAME_MQTT);
out += "\",\"" + String(PARAM_FORMAT_MQTT) + "\":\"";
if (s.length()) if (s.length())
doc[PARAM_FORMAT_MQTT] = urlencode(s); out += urlencode(s);
else else
doc[PARAM_FORMAT_MQTT] = urlencode(String(&mqttFormat[0])); out += urlencode(String(&mqttFormat[0]));
out += "\"}";
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) #if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
serializeJson(doc, Serial); Serial.print(out.c_str());
Serial.print(CR); Serial.print(CR);
#endif #endif
String out;
out.reserve(3000);
serializeJson(doc, out);
doc.clear();
_server->send(200, "application/json", out.c_str()); _server->send(200, "application/json", out.c_str());
LOG_PERF_STOP("webserver-api-config-format-read"); LOG_PERF_STOP("webserver-api-config-format-read");
} }
@ -1255,6 +1257,7 @@ bool WebServerHandler::setupWebServer() {
#endif #endif
// Static content // Static content
Log.notice(F("WEB : Setting up handlers for web server." CR));
#if defined(EMBED_HTML) #if defined(EMBED_HTML)
_server->on("/", std::bind(&WebServerHandler::webReturnIndexHtm, this)); _server->on("/", std::bind(&WebServerHandler::webReturnIndexHtm, this));
_server->on("/index.htm", _server->on("/index.htm",
@ -1297,30 +1300,26 @@ bool WebServerHandler::setupWebServer() {
_server->on("/firmware.htm", _server->on("/firmware.htm",
std::bind(&WebServerHandler::webReturnFirmwareHtm, this)); std::bind(&WebServerHandler::webReturnFirmwareHtm, this));
_server->serveStatic("/log", LittleFS, ERR_FILENAME); _server->serveStatic("/log", LittleFS, ERR_FILENAME);
_server->serveStatic("/log2", LittleFS, ERR_FILENAME2);
_server->serveStatic("/runtime", LittleFS, RUNTIME_FILENAME); _server->serveStatic("/runtime", LittleFS, RUNTIME_FILENAME);
// Dynamic content // Dynamic content
_server->on( _server->on("/api/clearlog", HTTP_GET,
"/api/config", HTTP_GET, std::bind(&WebServerHandler::webHandleLogClear, this));
std::bind(&WebServerHandler::webHandleConfig, this)); // Get config.json _server->on("/api/config", HTTP_GET,
std::bind(&WebServerHandler::webHandleConfig, this));
_server->on("/api/formula", HTTP_GET, _server->on("/api/formula", HTTP_GET,
std::bind(&WebServerHandler::webHandleFormulaRead, std::bind(&WebServerHandler::webHandleFormulaRead, this));
this)); // Get formula.json (calibration page)
_server->on("/api/formula", HTTP_POST, _server->on("/api/formula", HTTP_POST,
std::bind(&WebServerHandler::webHandleFormulaWrite, std::bind(&WebServerHandler::webHandleFormulaWrite, this));
this)); // Get formula.json (calibration page)
_server->on("/api/calibrate", HTTP_POST, _server->on("/api/calibrate", HTTP_POST,
std::bind(&WebServerHandler::webHandleCalibrate, std::bind(&WebServerHandler::webHandleCalibrate, this));
this)); // Run calibration routine (param id)
_server->on("/api/factory", HTTP_GET, _server->on("/api/factory", HTTP_GET,
std::bind(&WebServerHandler::webHandleFactoryDefaults, std::bind(&WebServerHandler::webHandleFactoryDefaults, this));
this)); // Reset the device
_server->on("/api/status", HTTP_GET, _server->on("/api/status", HTTP_GET,
std::bind(&WebServerHandler::webHandleStatus, std::bind(&WebServerHandler::webHandleStatus, this));
this)); // Get the status.json
_server->on("/api/clearwifi", HTTP_GET, _server->on("/api/clearwifi", HTTP_GET,
std::bind(&WebServerHandler::webHandleClearWIFI, std::bind(&WebServerHandler::webHandleClearWIFI, this));
this)); // Clear wifi settings
_server->on( _server->on(
"/api/upload", HTTP_GET, "/api/upload", HTTP_GET,
std::bind(&WebServerHandler::webHandleUpload, this)); // Get upload.json std::bind(&WebServerHandler::webHandleUpload, this)); // Get upload.json
@ -1367,9 +1366,6 @@ bool WebServerHandler::setupWebServer() {
return true; return true;
} }
//
// called from main loop
//
void WebServerHandler::loop() { void WebServerHandler::loop() {
#if defined(ESP8266) #if defined(ESP8266)
MDNS.update(); MDNS.update();

View File

@ -76,6 +76,7 @@ class WebServerHandler {
void webHandleCalibrate(); void webHandleCalibrate();
void webHandleUploadFile(); void webHandleUploadFile();
void webHandleUpload(); void webHandleUpload();
void webHandleLogClear();
void webHandlePageNotFound(); void webHandlePageNotFound();
String readFile(String fname); String readFile(String fname);

View File

@ -203,9 +203,7 @@ bool WifiConnection::waitForConnection(int maxTime) {
if (i++ > if (i++ >
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms. (maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
ErrorFileLog errLog; writeErrorLog("WIFI: Failed to connect to wifi %d", WiFi.status());
errLog.addEntry("WIFI: Failed to connect to wifi " +
String(WiFi.status()));
WiFi.disconnect(); WiFi.disconnect();
Serial.print(CR); Serial.print(CR);
return false; // Return to main that we have failed to connect. return false; // Return to main that we have failed to connect.
@ -346,9 +344,7 @@ bool WifiConnection::updateFirmware() {
switch (ret) { switch (ret) {
case HTTP_UPDATE_FAILED: { case HTTP_UPDATE_FAILED: {
ErrorFileLog errLog; writeErrorLog("WIFI: OTA update failed %d", ESPhttpUpdate.getLastError());
errLog.addEntry("WIFI: OTA update failed " +
String(ESPhttpUpdate.getLastError()));
} break; } break;
case HTTP_UPDATE_NO_UPDATES: case HTTP_UPDATE_NO_UPDATES:
break; break;
@ -378,9 +374,7 @@ void WifiConnection::downloadFile(HTTPClient &http, String &fname) {
f.close(); f.close();
Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str()); Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str());
} else { } else {
ErrorFileLog errLog; writeErrorLog("WIFI: Failed to download html-file %d", httpResponseCode);
errLog.addEntry("WIFI: Failed to download html-file " +
String(httpResponseCode));
} }
} }
@ -419,8 +413,7 @@ bool WifiConnection::checkFirmwareVersion() {
#endif #endif
DeserializationError err = deserializeJson(ver, payload); DeserializationError err = deserializeJson(ver, payload);
if (err) { if (err) {
ErrorFileLog errLog; writeErrorLog("WIFI: Failed to parse version.json");
errLog.addEntry(F("WIFI: Failed to parse version.json"));
} else { } else {
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING) #if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
Log.verbose(F("WIFI: Project %s version %s." CR), Log.verbose(F("WIFI: Project %s version %s." CR),

View File

@ -65,6 +65,8 @@ Other parameters are the same as in the configuration guide.
"angle": 90.93, "angle": 90.93,
"gravity": 1.105, "gravity": 1.105,
"battery": 0.04, "battery": 0.04,
"app-ver": "0.1.0",
"app-build": "build",
"platform": "esp8266", "platform": "esp8266",
"runtime-average": 3.12 "runtime-average": 3.12
} }
@ -83,6 +85,8 @@ Retrive the current device status via an HTTP GET command. Payload is in JSON fo
* ``temp-format`` can be either ``C`` or ``F`` * ``temp-format`` can be either ``C`` or ``F``
* ``platform`` can be either ``esp8266`` or ``esp32`` * ``platform`` can be either ``esp8266`` or ``esp32``
* ``temp-c`` will be set to -273 C if there is no temp sensor
* ``angle`` will be set to 0 if no valid angle is found and -1 if there is no gyro
Other parameters are the same as in the configuration guide. Other parameters are the same as in the configuration guide.
@ -102,6 +106,7 @@ Other parameters are the same as in the configuration guide.
"token2": "token2", "token2": "token2",
"rssi": -56, "rssi": -56,
"app-ver": "0.0.0", "app-ver": "0.0.0",
"app-build": "gitrev",
"mdns": "gravmon", "mdns": "gravmon",
"sleep-interval": 30, "sleep-interval": 30,
"platform": "esp8266", "platform": "esp8266",
@ -156,18 +161,19 @@ Used for adjusting some internal constants and other advanced settings. Should b
{ {
"gyro-read-count": 50, "gyro-read-count": 50,
"tempsensor-resolution": 9,
"gyro-moving-threashold": 500, "gyro-moving-threashold": 500,
"formula-max-deviation": 1.6, "formula-max-deviation": 3.0,
"wifi-portal-timeout": 120, "wifi-portal-timeout": 120,
"wifi-connect-timeout": 20, "wifi-connect-timeout": 20,
"push-timeout": 10,
"formula-calibration-temp": 20, "formula-calibration-temp": 20,
"ignore-low-angles": false,
"int-http1": 0, "int-http1": 0,
"int-http2": 0, "int-http2": 0,
"int-http3": 0, "int-http3": 0,
"int-influx": 0, "int-influx": 0,
"int-mqtt": 0 "int-mqtt": 0,
"tempsensor-resolution": 9,
"ignore-low-angles": false
} }
POST: /api/config/advanced POST: /api/config/advanced
@ -354,7 +360,7 @@ 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 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. present or the API call will fail. You only need to include the parameters you want to change.
The requests package converts the json to standard form post format. The requests package converts the json to standard form post format.

View File

@ -37,8 +37,9 @@ connection. It will show 0 if data has not been collected yet.
.. tip:: .. tip::
The button `view error log` will show the last 15 errors on the device. This can be useful for checking errors without The button `view error log` will show the last error messages 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`. the need to connect to the serial port or to check what errors has occured while in `gravity mode`. From v1.1 it will also detect
any abnormal restarts or crashes and record these in the logfile.
Configuration Configuration
@ -215,7 +216,7 @@ build this into the gravity formula.
This formula assumes that the calibration has been done at 20°C / 68°F. This formula assumes that the calibration has been done at 20°C / 68°F.
Formula used in temperature correction. Formula used in temperature correction. The calibration temperature can be changed under advanced settings.
:: ::
@ -237,7 +238,8 @@ Factor used to calcualate the battery voltage. If you get a too low/high voltage
* **Config voltage:** * **Config voltage:**
Defines the level of voltage when the device should enter config mode due to charging. This might vary between different battery manufacturers. Defines the level of voltage when the device should enter config mode due to charging. This might vary between different battery manufacturers.
If you dont what the device to go into configuration mode when charging, set this to 6V. If you dont what the device to go into configuration mode when charging, set this to 6V. This was added since differnt batteries have different
voltages when fully charged.
* **Temperature correction:** * **Temperature correction:**
@ -253,9 +255,10 @@ been running the value would be totally off.
* **Enable storage mode when placed on cap** * **Enable storage mode when placed on cap**
When place on the cap (<5 degres tilt) the device will go into max sleep. In order to wake it up you need to do a reset (or wait for the device When place on the cap (<5 degres tilt) the device will go into deep sleep forever (until reset). In order to wake it
to wake up). One option is to attach a magnetic reed switch (default open) to the reset pin and use a magnet to force a reset without opening up you need to do a reset. One option is to attach a magnetic reed switch (default open) to the reset pin and use a
the tube. The reed switch is typically an electronic component of 14 mm long incapsulated in a small glass tube. magnet to force a reset without opening the tube. The reed switch is typically an electronic component of 14 mm
long incapsulated in a small glass tube. See hardware section for more information, :ref:`hardware`.
* **Bluetooth: (Only ESP32)** * **Bluetooth: (Only ESP32)**
@ -317,6 +320,10 @@ data and of the deviation on any point is bigger the formula will be rejected.
If this option is checked any angles below that of SG 1 will be discarded as invalid and never sent to any server. Default = off. If this option is checked any angles below that of SG 1 will be discarded as invalid and never sent to any server. Default = off.
* **Gravity calibration temp**
This option allows you to set the correction temperature used in the automatic temperature gravity adjustment formula. Standard is 20C.
* **DS18B20 Resolution:** * **DS18B20 Resolution:**
Define the resolution used on the temp sensor. 9 bits is default and will give an accuracy of 0.5C, 12 bits will give an accuracy of 0.0625C but will also Define the resolution used on the temp sensor. 9 bits is default and will give an accuracy of 0.5C, 12 bits will give an accuracy of 0.0625C but will also

View File

@ -43,3 +43,24 @@ Schema for esp32 build
.. image:: images/schema_esp32.png .. image:: images/schema_esp32.png
:width: 700 :width: 700
:alt: Schema esp32 :alt: Schema esp32
Modifying with reed switch
--------------------------
A reed switch is a switch that reacts to magnetic fields. The ones I have tested are normally open and close in proximity to
a magnet.
.. image:: images/reed.jpg
:width: 400
:alt: Reed switch
If this is connected to the reset button a magnet can be used to trigger a reset of the device. The image below shows how
I mounted the iSPINDLE PCB v4.0 just under the cap. The lower red circle shows the reset connection point for the reed switch.
The reed switch is the glass tube visible under the esp8266.
.. image:: images/reed_build.jpg
:width: 400
:alt: Reed build

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

View File

@ -7,66 +7,90 @@ Welcome to GravityMon's documentation!
###################################### ######################################
.. note:: .. note::
This documentation reflects **v1.1 BETA 2**. Last updated 2022-07-23 This documentation reflects **v1.1**. Last updated 2022-08-14
* Docs for: `v1.0 <https://mp-se.github.io/gravitymon/v1.0/index.html>`_ User interface overview
* Docs for: `v0.9 <https://mp-se.github.io/gravitymon/v0.9/index.html>`_ -----------------------
This animation shows how the user interface is structured, it reflects an older version but the structure is the same.
.. image:: images/gravitymon.gif .. image:: images/gravitymon.gif
:width: 800 :width: 800
:alt: User Inteface Walkthrough :alt: User Inteface Walkthrough
.. _main_features:
Main features
-------------
* Operates in two modes gravity monitoring and configuration mode
* Gravity mode is comparable to how the iSpindle works when collectintg data
* Configuration mode has a modern HTML5 based web UI. No need to start the access point to change settings
* Offloading some of the functionallity to run in the web browser, this allows for more advanced features.
* REST API to enable scripted configuration
* Send data to multiple endpoints and services at once (2xHTTP POST, HTTP GET, MQTT, INFLUXDB2)
* Directly test all endpoints from user interface with feedback to simplify troubleshooting
* Complete format customization for all endpoints using templates (dont really need to change the software to support new services)
* Setup guides for how to send data to many popular services. Currently 10+ are documented
* Automatic temperature adjustment of gravity (just tick a checkbox)
* OTA support from webserver
* Firmware update via web interface
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and let GravityMon creates a formula
* Visual graph showing how formula will be interpreted based on entered values
* Using the temperature sensor in gyro instead of DS18B20 (faster)
* SSL support in all endpoints (no certificate validation due to limitations on esp8266).
* Built in performance measurements (used to optimise code)
* Storage mode when placed on cap (indefinite sleep)
* Customize various hardware parameters to opimize device functionallity.
For a complete breakdown see the :ref:`functionallity`
.. note::
If you are missing some feature, please reach out on `Github <https://github.com/mp-se/gravitymon/discussions>`_ or `homebrewtalk.com <https://www.homebrewtalk.com/threads/replacement-firmware-for-ispindel-gravitymon.698058/>`_
What is GravityMon?
--------------------
GravityMon is used to measure gravity and temperature during fermentation of beer and report the progress. The graph below is GravityMon is used to measure gravity and temperature during fermentation of beer and report the progress. The graph below is
an example on how the fermentation process can be tracked. This is from my last brew that was over on a few days. The graph is rendered using an example on how the fermentation process can be tracked. The graph has been rendered using Fermentrack.
Fermentrack.
.. image:: images/fermentation.png .. image:: images/fermentation.png
:width: 500 :width: 500
:alt: Example fermentation :alt: Example fermentation
GravityMon is a replacement firmware for the iSpindle and uses the same hardware configuration and is 100% compatible. It GravityMon is a replacement firmware for the iSpindle and uses the same hardware configuration and is 100% compatible. It
implements a lot of the features that has been requested in the orginal iSpindle project but has been rejected for implements a lot of the features that has been requested in the orginal iSpindle project but never implemented for
various reasons. Here is a list of :ref:`main_features`. various reasons. Here is a list of :ref:`main_features`.
From v0.9 the firmware now supports a iSpindle build based on an ESP32 d1 mini (pin compatible with esp8266). See :ref:`hardware`. From v0.9 the firmware also supports a iSpindle built using an ESP32 d1 mini (pin compatible with esp8266). Currently this is an experimental
version but since it has more memory and processing capacity it could support more functions. See :ref:`hardware`.
I started GravityMon because I like to create software and wanted to do some low level programming. I had done a few I started GravityMon because I like to create software and wanted to do some microcontroller programming. I had done a few
projects based on esp8266 and also started to brew beer so this combination was quite natural. projects based on esp8266 and also started to brew beer so this combination was quite natural.
The hardware design comes from the fantastic iSpindle project so that is not covered in this documentation. For more The hardware design comes from the fantastic iSpindle project so that is not covered in this documentation. For more
information on this topic and function please visit `iSpindel Homepage <https://www.ispindel.de>`_ . information on this topic and function please visit `iSpindel Homepage <https://www.ispindel.de>`_ .
My approach to this software is a little different from that the original iSpindle firmware. The github repository My approach to this software is a little different from that the original iSpindle firmware as can be seen in the list of features.
can be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
The github repository can be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
.. note:: .. note::
I dont take responsibility for any errors or issues caused by the software. The software is provided as-is. I will however I dont take responsibility for any errors or issues caused by the software. The software is provided as-is. I will however
try my best to fix issues that might occur. try my best to fix issues that might occur.
I have tested this software over the last year on 20+ brews with good results. I have tested this software on 40+ brews with good results.
.. _main_features:
Main features: Documentation for older versions
-------------- --------------------------------
* Operates in two modes gravity monitoring and configuration mode (simplify calibration). Gravity mode * Docs for: `v1.0 <https://mp-se.github.io/gravitymon/v1.0/index.html>`_
is comparable to how the iSpindle works. * Docs for: `v0.9 <https://mp-se.github.io/gravitymon/v0.9/index.html>`_
* Modern web based UI when in configuration mode. No need to start the access point changing settings.
* REST API to enable scripted configuration
* Send data to multiple endpoints and services at once
* Setup guides for how to send data to many popular services. Currently 8+ are documented.
* Automatic temperature adjustment of gravity reading
* OTA support from webserver
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and
let GravityMon create the formula.
* Visual graph showing how formula will be interpreted based on entered values
* Using the temperature sensor in gyro instead of DS18B20 (faster)
* SSL support in standard HTTP and MQTT connections.
* Option to customize data posted to endpoints using template from the UI.
* Built in performance measurements (used to optimise code)
For a complete breakdown see the :ref:`functionallity`
Software architecture
---------------------
This is a simple overview of the different components that the software contains. The green ones are only active during `configuration mode` in This is a simple overview of the different components that the software contains. The green ones are only active during `configuration mode` in
order to save battery. order to save battery.

View File

@ -3,35 +3,62 @@
Releases Releases
######## ########
v1.1.0 - beta 2 v1.1.0
=============== ======
Documentation
+++++++++++++
* Fixed errors in data format section
* Added q&a on formula creation and value deviation
* Added documentation for Brewpiless as target
* Updated docs for ubidots service integration.
* Added brewblox as new service
* (beta2) Updated docs for HA integration since method was depricated
User interface
++++++++++++++
* Under format options its now possible to select brewfather ispindle format to avoid mixing endpoints.
* Added brewblox as format under format options.
* User can now edit the voltage level that forces the device into config mode (charging)
* (beta2) Calibration temperature (for temp adjustment) can now be set under advanced settings.
* (beta2) Changes length of device name from 12 to 63 chars. 63 is the max limit according to mdns.
Features Features
++++++++ ++++++++
* Added storage mode which is activated under hardware setting. When place on the cap (<5 degres tilt) the device will go into storage mode and sleep for the max allowed time. * Added information to error log about abnormal resets (for instance crashes) to detect and fix those
* Added ${app-ver} and ${app-build} to format template as new variables. * Changed storage mode so that the device will go into deep sleep until reset (sleep forever)
* Added format templates for HA auto registration * Updated sensor types in home assistant for auto registration of device
* (beta2) Improved error messages when creating formula so the meaasurement points can be identified. * Added ${app-ver} and ${app-build} to format template as new variables
* (beta2) Changed defaule validation threashold from 1.6 SG to 3.0 SG, this should allow for some more variance when creating formula. * Improved error messages when creating formula so the troublesome measurement points can be identified
* (beta2) Updated format template for Home Assistant for using manual configuration (Aligned with new mqtt configuration format) * Changed defaule validation threashold from 1.6 SG to 3.0 SG, this should allow for some more variance when creating formula
* (beta2) Added format template for Home Assistant with automatic device registration * Updated format template for Home Assistant, aligned with new mqtt configuration format
* Added format template for Home Assistant with automatic device registration
* Added storage mode which is activated under hardware setting. When place on the cap (<5 degres tilt) the device will go into storage mode and deep sleep.
Known issues, not yet fixed
+++++++++++++++++++++++++++
* When updating firmware and the feature `deep sleep` is active the device will activate deep sleep if the gyro is not responding. FIX: Reboot device
Issues adressed
++++++++++++++++
* Refactored error logging function to reduce memory usage and crashes. Max size of error log is 2 x 4 kb
* Refactored format template engine to reduce memory usage and crashes, can how handle slightly larger payloads than before. Increase from around 1100 chars to 1600 chars
* BUG: Refactored format api to handle larger payloads
* BUG: After manual firmware upload the device would crash and go into wifi setup mode.
* BUG: After manual firmware upload the device will in some cases not be able to connect with the gyro, the symptom is that it will say, "Gyro moving" in the web UI. In this case the device needs to be reset (or powered on/off). I havent found a way to fix this from the code. The message after firmware update has been updated with this information
* BUG: Temp corrected gravity was not used when pushing data to removed
* BUG: Low memory in format api which resulted in mqtt template to be set to null
* BUG: Large format templates could be saved but when loading it's only blank
* BUG: Copy format templates used an old format for iSpindle and Gravmon where the token was not used
* BUG: Gravity correction formula not calculating correctly
User interface
++++++++++++++
* Updated format template with information on size and warning message if the template is too large
* Added error message if gyro connection/initialization fails (before the message was Gyro Moving only)
* Added error message if no temp sensor can be found
* Added drop down menus in user interface to simplify navigation to sub pages (format, test and upload)
* Added Assistant Device registration, this is only done when format template is saved, during normal operation only data values are posted on MQTT. If HA is restarted then the device will disappear
* Calibration temperature (for temp adjustment) can now be set under advanced settings, default is 20C
* Changed length of device name from 12 to 63 chars. 63 is the max limit according to mdns.
* Under format options its now possible to select brewfather ispindle format to avoid errors connected to using the wrong format template with the various brewfather endpoints
* Added brewblox as format under format options
* Added home assistant (with device registration) as format under format options
* User can now edit the voltage level that forces the device into config mode (device detects charging)
Documentation
+++++++++++++
* Added documentation for Brewpiless as target
* Added documentation for BVrewblox as target
* Updated documentation for HA integration since described method was deprecated
* Updated documentation for ubidots service integration
* Updated documentation in data format section
* Updated hardware section with documentation on installing reed switch
* Updated configuration section with documentation on new settings
* Added q&a on formula creation and value deviation
Other Other
+++++ +++++
@ -40,13 +67,6 @@ Other
* Updated OneWire library to be complaint with new ESP32 SDK * Updated OneWire library to be complaint with new ESP32 SDK
* Fixed issue in i2cdev connected to wrong usage of TwoWire on ESP32 (Gyro initialization hang). * Fixed issue in i2cdev connected to wrong usage of TwoWire on ESP32 (Gyro initialization hang).
Issues adressed
++++++++++++++++
* BUG: Copy format templates used an old format for iSpindle and Gravmon where the token was not used.
* BUG: Gravity correction formula not calculating correctly.
* (beta2) BUG: Temp corrected gravity was not used when pushing data to removed
* (beta2) BUG: Low memory in format api which resulted in mqtt template to be set to null
* (beta2) BUG: Large format templates could be saved but when loading it's only blank. Increased total memory from 3kb to 5kb
v1.0.0 v1.0.0
====== ======

View File

@ -164,7 +164,8 @@ username / password to be able to publish messages on a topic.
It's also possible to allow home assistant to do autodisovery and automatically create the sensor. This format It's also possible to allow home assistant to do autodisovery and automatically create the sensor. This format
template will create two sensors and update the values for them. template will create two sensors and update the values for them. The registration will occur when you save the format template. If Home Assistant
is restarted then the device will dissapear. The first method is the most persistant one.
.. warning:: .. warning::
This will only work on 1.1+ since the the memory allocation on previous versions are not enough to handle this large payload. This will only work on 1.1+ since the the memory allocation on previous versions are not enough to handle this large payload.
@ -182,7 +183,7 @@ template will create two sensors and update the values for them.
homeassistant/sensor/gravmon_${id}/gravity/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_grav","name":"gravity","dev_cla":"temperature","unit_of_meas":" ${gravity-unit}","stat_t":"gravmon/${mdns}/gravity"}| homeassistant/sensor/gravmon_${id}/gravity/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_grav","name":"gravity","dev_cla":"temperature","unit_of_meas":" ${gravity-unit}","stat_t":"gravmon/${mdns}/gravity"}|
homeassistant/sensor/gravmon_${id}/rssi/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_rssi","name":"rssi","dev_cla":"temperature","unit_of_meas":"dBm","stat_t":"gravmon/${mdns}/rssi"}| homeassistant/sensor/gravmon_${id}/rssi/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_rssi","name":"rssi","dev_cla":"temperature","unit_of_meas":"dBm","stat_t":"gravmon/${mdns}/rssi"}|
homeassistant/sensor/gravmon_${id}/tilt/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_tilt","name":"tilt","dev_cla":"temperature","stat_t":"gravmon/${mdns}/tilt"}| homeassistant/sensor/gravmon_${id}/tilt/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_tilt","name":"tilt","dev_cla":"temperature","stat_t":"gravmon/${mdns}/tilt"}|
homeassistant/sensor/gravmon_${id}/battery/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_batt","name":"battery","dev_cla":"temperature","unit_of_meas":"V","stat_t":"gravmon/${mdns}/battery"}| homeassistant/sensor/gravmon_${id}/battery/config:{"dev":{"name":"${mdns}","mdl":"gravmon","sw":"${app-ver}","ids":"${id}"},"uniq_id":"${id}_batt","name":"battery","dev_cla":"voltage","unit_of_meas":"V","stat_t":"gravmon/${mdns}/battery"}|
Brewer's Friend Brewer's Friend

View File

@ -1,7 +1,7 @@
{ {
"gyro-read-count": 50, "gyro-read-count": 50,
"gyro-moving-threashold": 500, "gyro-moving-threashold": 500,
"formula-max-deviation": 1.6, "formula-max-deviation": 3,
"wifi-portal-timeout": 120, "wifi-portal-timeout": 120,
"wifi-connect-timeout": 20, "wifi-connect-timeout": 20,
"push-timeout": 10, "push-timeout": 10,

View File

@ -1,4 +0,0 @@
Line 1
Line 2
Line 3
Line 4

7
test/log1 Normal file
View File

@ -0,0 +1,7 @@
Log Entry 11
Log Entry 12
Log Entry 13
Log Entry 14
Log Entry 15
Log Entry 16
Log Entry 17

10
test/log2 Normal file
View File

@ -0,0 +1,10 @@
Log Entry 1
Log Entry 2
Log Entry 3
Log Entry 4
Log Entry 5
Log Entry 6
Log Entry 7
Log Entry 8
Log Entry 9
Log Entry 10