gravitymon/docs/functionality.html
2023-02-04 11:01:25 +00:00

462 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html class="no-js">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting started" href="intro.html" /><link rel="prev" title="Welcome to GravityMon" href="index.html" />
<meta name="generator" content="sphinx-4.3.2, furo 2022.01.02"/>
<title>Functionality - GravityMon v1.2.0</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=df49af52631e7917044a9c21a57f7b83170a6dd0" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=fade93df149f7c5fedb3ff897f799dc7d283b420" />
<style>
body {
--color-code-background: #f8f8f8;
--color-code-foreground: black;
}
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" />
<line x1="4" y1="6" x2="20" y2="6" />
<line x1="10" y1="12" x2="20" y2="12" />
<line x1="6" y1="18" x2="20" y2="18" />
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-half" viewBox="0 0 24 24">
<title>Auto light/dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<path d="M13 12h5" />
<path d="M13 15h4" />
<path d="M13 18h1" />
<path d="M13 9h4" />
<path d="M13 6h1" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">GravityMon v1.2.0</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/gravitymon_logo.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">GravityMon v1.2.0</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Functionality</a></li>
<li class="toctree-l1"><a class="reference internal" href="intro.html">Getting started</a></li>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="releases.html">Releases</a></li>
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="formula.html">Create formula</a></li>
<li class="toctree-l1"><a class="reference internal" href="services.html">Service Integration</a></li>
<li class="toctree-l1"><a class="reference internal" href="advanced.html">Advanced Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">REST API</a></li>
<li class="toctree-l1"><a class="reference internal" href="data.html">Data Formats</a></li>
<li class="toctree-l1"><a class="reference internal" href="compiling.html">Compiling the software</a></li>
<li class="toctree-l1"><a class="reference internal" href="contributing.html">Contributing</a></li>
<li class="toctree-l1"><a class="reference internal" href="license.html">Licence</a></li>
<li class="toctree-l1"><a class="reference internal" href="troubleshooting.html">Troubleshooting</a></li>
<li class="toctree-l1"><a class="reference internal" href="q_and_a.html">Q &amp; A</a></li>
</ul>
</div>
</div>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<div class="content-icon-container">
<div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
<article role="main">
<div class="section" id="functionality">
<span id="id1"></span><h1>Functionality<a class="headerlink" href="#functionality" title="Permalink to this headline"></a></h1>
<div class="section" id="the-main-features">
<h2>The main features<a class="headerlink" href="#the-main-features" title="Permalink to this headline"></a></h2>
<ul>
<li><p><strong>Operates in two modes gravity monitoring and configuration mode</strong></p>
<p>In <code class="docutils literal notranslate"><span class="pre">gravity</span> <span class="pre">monitoring</span></code> mode it behaves just like the iSpindle, it wakes up at regular intervals, measures
angle/tile, temperature, calculates gravity and pushes the data to defined endpoints.</p>
<p>In <code class="docutils literal notranslate"><span class="pre">configuration</span> <span class="pre">mode</span></code> the device is always active and the webserver is active. Here you can view the
angle/tilt values, change configuration, update the gravity formula. When in this mode you can also interact
with the device via an REST API so data can be pushed to the device via scripts (see API section for more information).</p>
<a class="reference internal image-reference" href="_images/index.png"><img alt="UI example" src="_images/index.png" style="width: 700px;"/></a>
<p>You can force the device into <code class="docutils literal notranslate"><span class="pre">configuration</span> <span class="pre">mode</span></code> while measuring gravity. This is useful when calibrating
the device so you dont needs to wait for the device to wake up and push the data. The entire calibration
sequence can be handled via the web interface without need for additional software tools.</p>
<p>See the <a class="reference internal" href="configuration.html#setting-up-device"><span class="std std-ref">Configuration</span></a> section for more information on how to trigger the configuration mode.</p>
</li>
<li><p><strong>Can send data to multiple endpoints</strong></p>
<p>The original iSpindle can only have one destination, this software will push data to all defined endpoints so
in theory you can use them all. However this will consume more battery power so use only as many as needed. Its much
more efficient to have the endpoints on your local network than on the internet.</p>
<p>Currently the device supports the following endpoints.</p>
<ul class="simple">
<li><p>http (ssl optional)</p></li>
<li><p>influxdb v2 (ssl optional)</p></li>
<li><p>MQTT (ssl optional)</p></li>
<li><p>Brewfather</p></li>
<li><p>Home Assistant</p></li>
<li><p>Brew Spy</p></li>
<li><p>Brewers Friend</p></li>
<li><p>Fermentrack</p></li>
<li><p>Ubidots</p></li>
<li><p>Thingsspeak</p></li>
</ul>
<p>Under the <a class="reference internal" href="services.html#services"><span class="std std-ref">Service Integration</span></a> section you can find guides for how to connect GravityMon to these services. For a
description of what data is transmitted you can see <a class="reference internal" href="data.html#data-formats"><span class="std std-ref">Data Formats</span></a>.</p>
<p>The software support SSL endpoints but currently without CA validation, this means that the data is encrypted
but it does not validate if the remote endpoint is who it claims to be.</p>
<p>if you require CA validation please leave a comment on GitHub and I will make that a priority. Adding this function
will dramatically reduce the battery life of the device.</p>
</li>
</ul>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Using SSL on a small device such as the esp8266 can be unstable since it requires a lot of RAM to work. And running out
of RAM will cause the device to crash. So enable SSL with caution and only when you really need it. GravityMon will try
to minimize the needed RAM but the remote service might not support that feature.</p>
</div>
<ul>
<li><p><strong>Create gravity formulas on the device</strong></p>
<p>Another big difference is that this software can create the gravity formula in the device, just enter the
angle/gravity data that you have collected. You will also see a graph simulating how the formula would work.</p>
<p>Currently the device can handle 10 data points which should be enough to get a accurate formula. At least 3 data points
is needed to get an accurate formula.</p>
</li>
<li><p><strong>Customize the data format being sent to push targets</strong></p>
<p>In order to make it easier to support more targets there is a built in format editor that can be used to
customize the data that is to be sent. This way you can easily adapt the software to new targets without coding.
If you have a good template please share it on the github repository and I will add it to the documentation
for other users to enjoy. See the <a class="reference internal" href="advanced.html#format-editor"><span class="std std-ref">Format editor</span></a> for more information. See <a class="reference internal" href="services.html#services"><span class="std std-ref">Service Integration</span></a> for a list of
services currently validated.</p>
</li>
<li><p><strong>Automatic temperature adjustment of gravity reading</strong></p>
<p>If you want to correct gravity based on beer temperature you can do this in the formula but here is a nice
feature that can correct the gravity as a second step making this independent of the formula.</p>
</li>
<li><p><strong>OTA support from webserver</strong></p>
<p>When starting up in configuration mode the device will check for a software update from a webserver. This is an easily
way to keep the software up to date. In the future I might add a hosted endpoint for providing updates. OTA can also be
done over a SSL connection.</p>
</li>
<li><p><strong>DS18B20 temperature adjustments</strong></p>
<p>You can adjust the temperature reading of the temperature sensor. In normal cases this should not be needed since
the sensors should be calibrated.</p>
</li>
<li><p><strong>Gyro Movement</strong></p>
<p>The software will detect if the gyro is moving and if this is the case it will go back to sleep for 60 seconds.
This way we should avoid faulty measurements and peaks in the graphs.</p>
</li>
<li><p><strong>WIFI connection issues</strong></p>
<p>The software will not wait indefinitely for a wifi connection. If it takes longer than 20 seconds to connect then
the device will try the secondary wifi configuration, and that also fails it will go into deep sleep for 60 seconds and then
retry later. This to conserve batter as much as possible.</p>
</li>
<li><p><strong>Use gyro temperature sensor</strong></p>
<p>This works fine when the device has time to cool down between measurements and it saves up to 400 ms.
My testing shows that this is quite accurate with a deviation of less than 0.3C. This
reduces the run time by 20% (with optimal wifi connection).</p>
<p>The graph below compares from the temp from two different devices in the same bucket of water. One with
gyro temp enabled and one with the DS18B20 sensor. The blue line is the gyro temperature and this clear
that the temperature will be higher after it has been running but cools down when in sleep mode. The interval
has been set to 300s. A low delay of 30s will not allow the gyro to cool down and the temperature will
be 0.5-1.0C higher.</p>
</li>
</ul>
<a class="reference internal image-reference" href="_images/temp1.png"><img alt="Gyro temp vs DS18B20" src="_images/temp1.png" style="width: 800px;"/></a>
<ul>
<li><p><strong>Celsius or Fahrenheit</strong></p>
<p>You can switch between different temperature formats. GravityMon will always use C for its internal calculations and
convert to F when displayed.</p>
</li>
<li><p><strong>SG or Plato</strong></p>
<p>You can switch between different gravity formats. GravityMon will always use SG for its internal calculations and
convert to Plato when displayed.</p>
</li>
<li><p><strong>Stable gyro data</strong></p>
<p>The device will read the gyro 50 times to get an accurate reading. If the standard deviation is to high it will not
use the data since this is inaccurate and the device is probably moving, probably do to active fermentation or movement of
fermentation vessel. This sequence takes 900 ms seconds to execute and besides wifi connection this is what consumes the most
battery. With more testing this might be changes to either speed up or provide more stable readings.</p>
</li>
<li><p><strong>Crash detection and Error Logging</strong></p>
<p>There is a build in logging function so that errors that occurs can be detected and logged to a file. On the ESP8266 crashes will also
be logged so that these problems can be detected and fixed. Crash logging is not available on the ESP32 variants.</p>
</li>
<li><p><strong>Performance measurements</strong></p>
<p>Ive also create a small library to measure execution code in some areas of the code that i know is time consuming. This
way I can find a good balance between performance and quality. This is a lot of help trying to figure out where bottlenecks
are in the code and where to put optimization efforts. Examples of real measurements:</p>
<ul class="simple">
<li><p>Reading the gyro: 885 ms</p></li>
<li><p>Reading DS18B20 temperature sensor: 546 ms</p></li>
<li><p>Connect to WIFI: 408 ms</p></li>
<li><p>Send data to local influxdb v2: 25 ms</p></li>
<li><p>Send data to local mqtt server: 35 ms</p></li>
<li><p>Send data to local http server: 40 ms</p></li>
<li><p>Send data to http server on internet: 0.2 - 5 seconds</p></li>
</ul>
<p>See the <a class="reference internal" href="compiling.html#compiling-the-software"><span class="std std-ref">Compiling the software</span></a> for more information.</p>
</li>
</ul>
</div>
<div class="section" id="battery-life">
<h2>Battery life<a class="headerlink" href="#battery-life" title="Permalink to this headline"></a></h2>
<p>For the 1.2 version I have been running some long term battery tests on a few of the boards and also comparing wifi vs Bluetooth. I was using a standard 2200 mA battery
that was fully charged at the start of the tests. All devices started with factory settings with only a change in push destination and sleep interval.</p>
<p>For the wifi tests, I was pushing data every 30 seconds to a local influxdb2 server to reduce errors connected to slow response on the server side. The devices
was placed 2 meters from the wifi AP to ensure a good and stable wifi connection (so ideal conditions).</p>
<p>For the Bluetooth tests I was pusing data every 10 seconds to a linux server.</p>
<p>To make this comparable I measured how many times the device was able to wake up and push data before the battery was dead. I theory the power consumption when in
deep sleep is to low it can almost be ignored for the calculations. So the impact on battery is mainly caused by how long the device is awake. In the most optimal case
this can be as low as 1.5-2.0 seconds but in reality its probably around 3-4 seconds. Wifi consumes a lot of power so Bluetooth is a better option for long battery life.</p>
<div class="table-wrapper"><table class="colwidths-given docutils align-default" id="id2">
<caption><span class="caption-text">Battery power</span><a class="headerlink" href="#id2" title="Permalink to this table"></a></caption>
<colgroup>
<col style="width: 33%"/>
<col style="width: 22%"/>
<col style="width: 22%"/>
<col style="width: 22%"/>
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Device</p></th>
<th class="head"><p>Transmissions</p></th>
<th class="head"><p>30s</p></th>
<th class="head"><p>300s / 15min</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>ESP 8266 (wifi)</p></td>
<td><p>26,000</p></td>
<td><p>9 days</p></td>
<td><p>90 days</p></td>
</tr>
<tr class="row-odd"><td><p>ESP32 c3 (wifi)</p></td>
<td><p>12,000</p></td>
<td><p>4 days</p></td>
<td><p>43 days</p></td>
</tr>
<tr class="row-even"><td><p>ESP32 d1 (ble)</p></td>
<td><p>56,000</p></td>
<td><p>20 days</p></td>
<td><p>196 days</p></td>
</tr>
</tbody>
</table></div>
<p>As you can see from the table above there is quite some differences between the boards and connection methods.</p>
</div>
<div class="section" id="performance">
<h2>Performance<a class="headerlink" href="#performance" title="Permalink to this headline"></a></h2>
<p>Since I have the possibility to measure the performance of different function in the code this is what I have been able to gather.</p>
<p>The typical runtime in a measurement cycle is approx 2 seconds and in some cases it can take up to 6-8 seconds but this is mainly related to establishing the WIFI connection. So stable wifi is
essential for long battery life. Out of the 2 seconds of run-time the major time is spent on gyro readings (1.3s) and temperature measurements of (0.6s) so using the gyro sensor for measuring
temperature would reduce the total runtime with 25%. Sending data over http takes less than 100ms (on my local network) so this is not drawing much power.</p>
<p>The image below shows how the run-time varies over time. The pink line is the wifi connection time and this is why the time varies. The orange is the total runtime for the awake period.</p>
<a class="reference internal image-reference" href="_images/perf1.png"><img alt="Performance view" src="_images/perf1.png" style="width: 800px;"/></a>
</div>
</div>
</article>
</div>
<footer>
<div class="related-pages">
<a class="next-page" href="intro.html">
<div class="page-info">
<div class="context">
<span>Next</span>
</div>
<div class="title">Getting started</div>
</div>
<svg><use href="#svg-arrow-right"></use></svg>
</a>
<a class="prev-page" href="index.html">
<svg><use href="#svg-arrow-right"></use></svg>
<div class="page-info">
<div class="context">
<span>Previous</span>
</div>
<div class="title">Home</div>
</div>
</a>
</div>
<div class="related-information">
Copyright &#169; 2021-2023, Magnus Persson |
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> and <a class="muted-link" href="https://pradyunsg.me">@pradyunsg</a>'s
<a href="https://github.com/pradyunsg/furo">Furo theme</a>.
</div>
</footer>
</div>
<aside class="toc-drawer">
<div class="toc-sticky toc-scroll">
<div class="toc-title-container">
<span class="toc-title">
Contents
</span>
</div>
<div class="toc-tree-container">
<div class="toc-tree">
<ul>
<li><a class="reference internal" href="#">Functionality</a><ul>
<li><a class="reference internal" href="#the-main-features">The main features</a></li>
<li><a class="reference internal" href="#battery-life">Battery life</a></li>
<li><a class="reference internal" href="#performance">Performance</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</aside>
</div>
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/scripts/furo.js"></script>
<script src="_static/clipboard.min.js"></script>
<script src="_static/copybutton.js"></script>
</body>
</html>