Compare commits
205 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a597162ae | |||
f49f386569 | |||
008ad490a7 | |||
3533ee8dac | |||
2b9abda873 | |||
4bbb558c8b | |||
8ebbc6559f | |||
0af872e743 | |||
c20f9a534a | |||
10ce1fc245 | |||
b43874d151 | |||
1428bec3da | |||
f9791dd349 | |||
1a7f28413c | |||
d22309bb2e | |||
b901a12699 | |||
914b4125d8 | |||
95216ecc54 | |||
4d83bf8fce | |||
e125ca4a10 | |||
5880d3a6ba | |||
cda3a87dd9 | |||
4bcacea9d7 | |||
838d062eea | |||
77cdbf7649 | |||
f33a58cffe | |||
7ca536b216 | |||
f0ec352538 | |||
e336633c38 | |||
a130ebd67d | |||
4c789a8b37 | |||
7ab5f451f5 | |||
545f274a47 | |||
9bea54b703 | |||
35333469c7 | |||
a9e0d9290a | |||
e459ceb2fa | |||
2615debe35 | |||
6113a436b0 | |||
2d5158465f | |||
3af52b5464 | |||
c116d672d1 | |||
e1cc54d188 | |||
761d570d39 | |||
22ade61af8 | |||
928054458a | |||
2e67bd1d57 | |||
83d7aee944 | |||
8bcd27a076 | |||
0912d672fe | |||
01d7c8adb5 | |||
69076d5878 | |||
3d939ad733 | |||
f2112cb344 | |||
f88fb8241b | |||
6eed5f143b | |||
044bfcddad | |||
1fd3b1911d | |||
9727e87e33 | |||
3390ebc5ab | |||
e076de022c | |||
4e0980e814 | |||
b8959ae165 | |||
4d6b4b7fd6 | |||
64e582d0e5 | |||
5d0f02eb18 | |||
fbc1eb4e31 | |||
700f00f48d | |||
9a2f86fed7 | |||
63fd80e750 | |||
b106ebfa20 | |||
dc5979dd28 | |||
22d5e4fad7 | |||
ce361f66c8 | |||
2474306acb | |||
1d3cbbb3c2 | |||
0db586d744 | |||
a01c838b8f | |||
080820f10f | |||
1a9283b719 | |||
1d113e6941 | |||
49f166cc67 | |||
58a9966e6e | |||
357772dbaf | |||
1c0eb11133 | |||
7890f8096f | |||
68018329bb | |||
57bfdc1e87 | |||
d3a71da643 | |||
42c58fdcdf | |||
7b4e95b5ad | |||
767988a7c5 | |||
20771b3244 | |||
35f66e0458 | |||
37a1ca6058 | |||
ddb34e129d | |||
10163f3aa7 | |||
1adef20edd | |||
617e77a9d8 | |||
c8d48a3236 | |||
f3d2e88b31 | |||
a9dc3be329 | |||
043963df91 | |||
f318dea9b7 | |||
06b5b949d0 | |||
5c400e4e47 | |||
e1484ca2fd | |||
4d300908a6 | |||
5b6ce7d672 | |||
adc21b5527 | |||
8919c842fb | |||
a270faa480 | |||
aad35e20bd | |||
a83a74b5a4 | |||
a703add227 | |||
534a739891 | |||
01fe6bbf19 | |||
19d1f7b1ca | |||
2a2aecdee5 | |||
325c7ee2ca | |||
57f5816f63 | |||
46b17177f2 | |||
df1981e3dd | |||
2f391c95c7 | |||
ea62d9e752 | |||
6364e251b7 | |||
93d9effcef | |||
4ff114642e | |||
7f775d78eb | |||
17bc23bef8 | |||
e234d6f908 | |||
2662f38bf7 | |||
3b3ee5bbf9 | |||
4731569b75 | |||
a255239c02 | |||
c72ba40884 | |||
7dde662aa8 | |||
c08ac7c4e4 | |||
4e32bfbec6 | |||
e4fd33fe87 | |||
33748aa1c2 | |||
c72a249bd4 | |||
b1ccdfad6a | |||
b277274eab | |||
e321eadf0f | |||
604761dbe0 | |||
1d5e0ffb63 | |||
dfe6bbf61d | |||
1fe49d5fad | |||
3f74228a12 | |||
788e0e783d | |||
825d2ba6f3 | |||
4d65e0d3e0 | |||
95985eab0e | |||
5119ffcbe5 | |||
0a53a5efab | |||
2a61658088 | |||
33081ee290 | |||
d21d5f7965 | |||
7e264b29ff | |||
503de20e42 | |||
9ea4d302d6 | |||
d7b30c484d | |||
3c1bf123b4 | |||
42205a94c9 | |||
1894bc4959 | |||
1928dbb749 | |||
6c26bbeacb | |||
c67585c7e4 | |||
11d106da4f | |||
fdd04bc19a | |||
5f2c7eb2d8 | |||
864f04c707 | |||
8f95cfaea5 | |||
19f3ba1aae | |||
3e947346b4 | |||
31a8500405 | |||
5573e06155 | |||
298ea1de61 | |||
1478430f03 | |||
5612c0ce64 | |||
e3ac920fc4 | |||
ed53182c29 | |||
88bd971b73 | |||
5d9115137f | |||
299d2dd0fe | |||
2fb86c594a | |||
51a2d30824 | |||
4de40366bb | |||
27b7b70998 | |||
4f0b6d11e7 | |||
20d5f50c19 | |||
93df4b60e0 | |||
0c0e9067f9 | |||
c1db431490 | |||
791f5fedd2 | |||
f8e5957045 | |||
088a37eaf3 | |||
5a7858ecaf | |||
0783a206b2 | |||
d796794602 | |||
d075fe2117 | |||
48f71bd59a | |||
bdd4b7117b | |||
29174bf1f9 |
45
.github/workflows/doc-build.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Sphinx Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: ammaraskar/sphinx-action@master
|
||||
with:
|
||||
docs-folder: "src_docs/"
|
||||
pre-build-command: |
|
||||
pip install sphinx_rtd_theme
|
||||
pip install furo
|
||||
build-command: "sphinx-build -b html ./source ../docs"
|
||||
|
||||
- name: Commit documentation changes
|
||||
run: |
|
||||
pwd
|
||||
ls -al
|
||||
ls -al docs
|
||||
git clone https://github.com/mp-se/gravitymon.git --branch ghpages --single-branch ghpages
|
||||
mkdir -p ghpages/docs
|
||||
cp -r docs/* ghpages/docs
|
||||
cd ghpages
|
||||
touch docs/.nojekyll
|
||||
git config --local user.email "action@noreply.github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add .
|
||||
git commit -m "Update documentation" -a || true
|
||||
# git push https://${{secrets.token}}@github.com/mp-se/gravitymon.git
|
||||
# The above command will fail if no changes were present, so we ignore
|
||||
# the return code.
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
branch: ghpages
|
||||
directory: ghpages
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
53
.github/workflows/pio-build-patch.yaml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: PlatformIO CI Patch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- patch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
git config --global advice.detachedHead false
|
||||
|
||||
- name: Run PlatformIO
|
||||
#run: pio run -e gravity-release -e gravity-perf -e gravity-debug
|
||||
run: pio run -e gravity-release -e gravity-perf
|
||||
#run: pio run -e gravity-release
|
||||
|
||||
- uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: 'bin'
|
||||
author_name: GitHub Action
|
||||
author_email: mp-se@noreply.github.com
|
||||
|
||||
branch: patch
|
||||
|
||||
default_author: github_actor
|
||||
message: 'GitHub Action Build'
|
||||
pathspec_error_handling: ignore
|
53
.github/workflows/pio-build.yaml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: PlatformIO CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
git config --global advice.detachedHead false
|
||||
|
||||
- name: Run PlatformIO
|
||||
#run: pio run -e gravity-release -e gravity-perf -e gravity-debug
|
||||
run: pio run -e gravity-release -e gravity-perf
|
||||
#run: pio run -e gravity-release
|
||||
|
||||
- uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: 'bin'
|
||||
author_name: GitHub Action
|
||||
author_email: mp-se@noreply.github.com
|
||||
|
||||
branch: dev
|
||||
|
||||
default_author: github_actor
|
||||
message: 'GitHub Action Build'
|
||||
pathspec_error_handling: ignore
|
5
.gitignore
vendored
@ -1,8 +1,9 @@
|
||||
.pio/*
|
||||
.vscode/*
|
||||
*.map
|
||||
docs/*
|
||||
test/*.md
|
||||
test/env/*
|
||||
test/*.py
|
||||
test/configure_*.py
|
||||
TODO.md
|
||||
src_docs/_build/*
|
||||
data/*.min.htm
|
||||
|
10
.pre-commit-config.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
repos:
|
||||
- repo: https://github.com/bmorcos/pre-commit-hooks-cpp
|
||||
rev: 9a5aa38207bf557961110d6a4f7e3a9d352911f9
|
||||
hooks:
|
||||
- id: clang-format
|
||||
files: ^src/
|
||||
- id: cpplint
|
||||
files: ^src/
|
||||
- id: cppcheck
|
||||
files: ^src/
|
@ -1,7 +1,6 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -20,17 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _CALC_H
|
||||
#define _CALC_H
|
||||
|
||||
// Includes
|
||||
#include "helper.h"
|
||||
|
||||
// Functions
|
||||
double calculateGravity( double angle, double temp );
|
||||
double gravityTemperatureCorrection( double gravity, double temp, char tempFormat, double calTemp = 20 );
|
||||
|
||||
#endif // _CALC_H
|
||||
|
||||
// EOF
|
174
README.md
@ -1,156 +1,32 @@
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Gravity Monitor for Beer Brewing
|
||||
|
||||
This software can be used with iSpindle hardware and utilizes the same hardware configuration. No code has been reused from the iSpindle project.
|
||||
GravityMon is a replacement firmware for the iSpindle firmware. It's 100% compatible with the iSpindle hardware design so it does not require any hardware changes.
|
||||
|
||||
I started this project out of curiosity for how a motion sensor is working and since I like to brew beer this was the result.
|
||||
Installation can be made using https://www.brewflasher.com
|
||||
|
||||
After 6 months of testing I believe this is working as planned. It give accurate readings same as the orginal iSpindel software.
|
||||
The main differences:
|
||||
---------------------
|
||||
|
||||
Version history
|
||||
* v0.4 First official version with 5+ brews on record.
|
||||
* Operates in two modes gravity monitoring and configuration mode (simplify calibration)
|
||||
* Modern web based UI for configuration (in config mode)
|
||||
* REST API
|
||||
* Send data to multiple endpoints when pushing data (2xhttp, brewfather, influxdb v2, mqtt supported)
|
||||
* Automatic temperature adjustment of gravity reading
|
||||
* OTA support from local webserver
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
|
||||
* Visual graph showing how formula will be interpreted
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* Built in performance measurements (used to optimise code)
|
||||
* SSL support in standard HTTP and MQTT connections.
|
||||
|
||||
Lower priority
|
||||
* Add support for Plato in device (today it assumes that formula is in SG).
|
||||
* Add support for converting between SG/Plato in device.
|
||||
* Add support for Blynk as endpoint
|
||||
* Add support for https connections (push) - [need to reduce memory usage for this to work, gets out of memory error]
|
||||
* Add support for https web server (will require certificates to be created as part of build process)
|
||||
* Add support for WifiManager Secure access, depends on support in library.
|
||||
No code has been reused from the iSpindle project.
|
||||
|
||||
# Functionallity
|
||||
|
||||
I have made a few different design decision compared to the standard iSpindle software.
|
||||
|
||||
* The device operate in two modes, __always-on__ or __deep-sleep__. Always on can be triggered in two ways: Connected to charger and the power is over 4.1V or the device is lying flat (angle is approx 90 degrees).
|
||||
|
||||
* Configuration options have been moved to a web gui and is accesible when the device is in __always-on__ mode. The Wifi portal only need to be used for changing WIFI network.
|
||||
|
||||
* The software also has built in OTA support so new versions can be downloaded from a local webserver and checks are done during startup and the device is in __always-on__ mode.
|
||||
|
||||
* Temperature calibration has been added for the DS18B20 sensor so you can adjust the temperature sensor readings if there is a need. When the device is in __always-on__ mode the temperature will rise in the container so the value will increase and not reflect the temperature for the surronding the container.
|
||||
|
||||
* There is an option to automatically correct the gravity calculation based on the temperature. Useful if you are fermenting at lower temperatures. It's possible to build this into the normal gravity formula but this is an easier option. Just make sure that the calibration is done at 20°C.
|
||||
|
||||
* The software will read the motion sensor 50 times and use the average to calculate the angle. If the readings show that the device is moving it will wait a few seconds and retry the operation again to make sure that no invalid angles should be reported. If the device is unsuccesful to get a valid angle within 10s it will go to sleep for 60s and then retry again (TODO: This will be adjusted after more testing).
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is accessed by entering the URL for the device, this will be the mDNS name __device.local__ or the IP adress. The following chapter assumes the device name is __gravmon__.
|
||||
|
||||
### Index page
|
||||
|
||||
http://gravmon.local/
|
||||
|
||||
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
|
||||
|
||||

|
||||
|
||||
### Device page
|
||||
|
||||
http://gravmon.local/device.htm
|
||||
|
||||
The device page shows the device settings and software versions.
|
||||
|
||||

|
||||
|
||||
### Config page
|
||||
|
||||
http://gravmon.local/config.htm
|
||||
|
||||
* This page is divided into several categories of settings. The first one contains device settings, mDNS name, temperature format, sleep interval and gyro calibration data. The interval setting is the amount of time the device will be in sleep mode between readings (interval is in seconds). To simplify this you can also see the conversion to minutes / seconds next to the input field.
|
||||
|
||||
* The sleep interval can be set between 10 - 3600 seconds (60 minutes).
|
||||
|
||||
* Calibration needs to be done or the device will not work correctly. Place the device flat on a surface with gyro up and press the calibrate button when it's stable. If no calibration data exist the device will not enter sleep-mode.
|
||||
|
||||

|
||||
|
||||
* The second section contains the push settings, two URL's for http posts, Brewfather and settings for InfluxDB v2.
|
||||
|
||||
### This is the format used for standard http posts.
|
||||
```
|
||||
{
|
||||
"name" : "gravmon", // mDNS name
|
||||
"ID": "2E6753", // esp device id
|
||||
"token" : "gravmon",
|
||||
"interval": 900,
|
||||
"temperature": 20.5, // C or F based on setting, adjusted value.
|
||||
"temp-units": "C", // C or F based on setting
|
||||
"gravity": 1.0050, // Raw or temperature corrected gravity (based on setting)
|
||||
"corr-gravity": 1.0050, // Temperature corrected gravity
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
"run-time": 230, // ms, Runtime for this reading, this is an additional field not part of the standard format
|
||||
}
|
||||
```
|
||||
|
||||
### This is the format for Brewfather
|
||||
|
||||
```
|
||||
{
|
||||
"name" : "gravmon", // mDNS name
|
||||
"temp": 20.5,
|
||||
"temp-unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G", // G = SG, Plato is not yet supported
|
||||
}
|
||||
```
|
||||
|
||||
### This is the format for InfluxDB v2
|
||||
|
||||
```
|
||||
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
|
||||
```
|
||||
|
||||

|
||||
|
||||
* The third section contains the gravity formula and also option for doing temperature compensation. The calibration formula uses two keywords, temp and tilt. Temperature is in the selected device format.
|
||||
|
||||
* Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. I use the tool fermentrack for controlling my fermentation and I use this tool for calculating gravity. The formula can handle two keywords, __tilt__ and __temp__. This is an example of a formula; __0.00145*tilt^3+0.1445*tilt^2+0.00179*tilt+0.9436__
|
||||
|
||||
* This is the formula used for temperature calibration (temp is in F). Cal = 20C.
|
||||
```
|
||||
gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))
|
||||
```
|
||||

|
||||
|
||||
* Hardware settings contain settings for temperature sensor adjustment, voltage factor (to calulate the battery level) and OTA URL.
|
||||
|
||||
* For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the code the update will be done during startup.
|
||||
|
||||
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
|
||||
```
|
||||
http://192.168.1.1/firmware/gravmon/
|
||||
```
|
||||
|
||||
Contents version.json
|
||||
```
|
||||
{ "project":"gravmon", "version":"0.3.0" }
|
||||
```
|
||||
|
||||

|
||||
|
||||
# Building a device
|
||||
|
||||
See the iSpindle documentation for building a device.
|
||||
|
||||
I've included my 3d sled that I use for my builds that allows for easy adjustment of the default angle. The stl files can be found under the stl directory.
|
||||
|
||||

|
||||

|
||||
|
||||
# Compiling the software
|
||||
|
||||
I recommend that VSCODE with PlatformIO and Minfy extensions are used. Minify is used to reduce the size of the HTML files which are embedded into the firmware or uploaded to the file system. When using minify on a file, for example index.htm the output will be called index.min.htm. This is the file that will be used when buildning the image.
|
||||
|
||||
By default the html files are embedded but there are options to upload them to the file system to reduce the size of the image if the size becomes to large for OTA.
|
||||
|
||||
You can set the SSID and PWD as presets through platformio.ini by adding the settings to the following definitions:
|
||||
```
|
||||
-D USER_SSID=\""\"" // =\""myssid\""
|
||||
-D USER_SSID_PWD=\""\"" // =\""mypwd\""
|
||||
```
|
||||
|
||||
There are more options in teh platform.ini file that enable/disable additional functions for logging level, pushing performance data to InfluxDB and more. If i get the time I will add some documentation around these.
|
||||
The documenation can be found here: https://mp-se.github.io/gravitymon/index.html
|
||||
|
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
52
bin/calibration.min.htm
Normal file
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.4.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Average runtime:</div><div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div></div><div class="row mb-3"><a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">View error log</a></div><script>function loadLog(){$("#logContent").load("/log")}$("#log-btn").click(function(o){loadLog()}),setInterval(function(){loadLog()},3e3)</script><div class="collapse" id="collapseLog"><div class="card card-body"><pre><code id="logContent"></code></pre></div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var e="/api/device";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#app-ver").text(e["app-ver"]+" (html 0.8.0)"),$("#mdns").text(e.mdns),$("#id").text(e.id),$("#runtime").text(e["runtime-average"]+" seconds")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
BIN
bin/firmware.bin
2
bin/format.min.htm
Normal file
@ -0,0 +1,2 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/config.htm">Back to configuration</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-2"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#alert").addClass("d-none").removeClass("show")})</script><div class="accordion" id="accordion"><div class="card"><div class="card-header" id="headingOne"><h2 class="mb-0"><button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","testing"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${token}","a-token"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
@ -1 +1 @@
|
||||
{ "project":"gravmon", "version":"0.4.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "about.min.htm" ] }
|
||||
{ "project":"gravmon", "version":"0.8.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "calibration.min.htm", "format.min.htm", "about.min.htm" ] }
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.4.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item active"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="" id="id" hidden></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Gravity:</div><div class="col-md-4 themed-grid-col bg-light" id="gravity">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Temperature:</div><div class="col-md-4 themed-grid-col bg-light" id="temp">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Angle/Tilt:</div><div class="col-md-4 themed-grid-col bg-light" id="angle">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Battery:</div><div class="col-md-4 themed-grid-col bg-light" id="battery">Loading...</div></div><div class="row mb-3"><div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox"><input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled> <label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label></div></div><hr class="my-4"></div><script type="text/javascript">function getStatus(){var e="/api/status";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#id").text(e.id),$("#angle").text(e.angle),$("#gravity").text(e.gravity+" SG"),$("#battery").text(e.battery+" V"),"C"==e["temp-format"]?$("#temp").text(e["temp-c"]+" C"):$("#temp").text(e["temp-f"]+" F"),e["sleep-mode"]?$("#sleep-mode").attr("checked",!0):$("#sleep-mode").attr("checked",!1),$("#sleep-mode").removeAttr("disabled")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}function start(){setInterval(getStatus,3e3)}window.onload=start,$("#sleep-mode").click(function(e){console.log("Blocking sleep mode = "+$("#sleep-mode").is(":checked")),$.ajax({type:"POST",url:"/api/status/sleepmode",data:{id:$("#id").text(),"sleep-mode":$("#sleep-mode").is(":checked")},success:function(e){},error:function(e){showError("Could not update sleep mode for device.")}})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work. You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download the files if they are found in the same location as the firmware.bin. This page is a fallback option.</div><div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">index.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">device.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">config.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">about.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div></div><div class="row mb-3"><form action="/api/upload" method="post" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" class="custom-file-input" name="name" id="name"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button></form></div><hr class="my-4"></div><script type="text/javascript">function getUpload(){var e="/api/upload";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),e.index?$("#index").text("Completed."):$("#index").text("File is missing."),e.device?$("#device").text("Completed."):$("#device").text("File is missing."),e.config?$("#config").text("Completed."):$("#config").text("File is missing."),e.about?$("#about").text("Completed."):$("#about").text("File is missing.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getUpload,$(".custom-file-input").on("change",function(){var e=$(this).val().split("\\").pop();$(this).siblings(".custom-file-label").addClass("selected").html(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
|
@ -31,6 +31,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
@ -73,6 +76,6 @@
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/about.min.htm
Normal file
@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
386
html/calibration.htm
Normal file
@ -0,0 +1,386 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/device.htm">Device</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
|
||||
<div id="alert-msg">...</div>
|
||||
<button type="button" id="alert-btn" class="close" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingOne">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||
Formula calculation
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<form action="/api/formula" method="post">
|
||||
<input type="text" name="gravity-format" id="gravity-format" hidden>
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
|
||||
<div class="row mb-3">
|
||||
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity. These values
|
||||
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
|
||||
formula and if the deviation is more than 1.5SG / 0.38P on any of the provided points then the forumla will be
|
||||
rejected. On the bottom of the page you can see a graph over the entered values + values calcualated by the formula.
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">#:</label>
|
||||
<label class="col-sm-4 col-form-label">Angle/Tilt:</label>
|
||||
<label class="col-sm-4 col-form-label" id="gravity-header">Gravity (SG):</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle1" class="col-sm-2 col-form-label">1.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a1" id="a1">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g1" id="g1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle2" class="col-sm-2 col-form-label">2.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a2" id="a2">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g2" id="g2">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle3" class="col-sm-2 col-form-label">3.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a3" id="a3">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g3" id="g3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle4" class="col-sm-2 col-form-label">4.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a4" id="a4">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g4" id="g4">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle5" class="col-sm-2 col-form-label">5.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a5" id="a5">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g5" id="g5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-0">
|
||||
<button type="submit" class="btn btn-primary" id="calculate-btn">Save & Calculate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label">Current angle: </label>
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label" id="angle"></label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label">Formula: </label>
|
||||
<label for="calculate-btn" class="col-sm-8 col-form-label" id="formula">Loading...</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div>
|
||||
<canvas id="gravityChart"></canvas>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var chartDataForm = [];
|
||||
var chartDataCalc = [];
|
||||
|
||||
const dataSetChart = {
|
||||
datasets: [{
|
||||
label: 'Raw data',
|
||||
borderColor: 'blue',
|
||||
backgroundColor: 'blue',
|
||||
data: chartDataForm
|
||||
}, {
|
||||
label: 'Calculated',
|
||||
borderColor: 'green',
|
||||
backgroundColor: 'green',
|
||||
data: chartDataCalc
|
||||
}]
|
||||
}
|
||||
|
||||
const configChart = {
|
||||
type: 'line',
|
||||
data: dataSetChart,
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
type: 'linear',
|
||||
grace: '5%',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle/Tilt'
|
||||
},
|
||||
ticks: {
|
||||
crossAlign: 'far'
|
||||
},
|
||||
suggestedMin: 25
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Gravity'
|
||||
},
|
||||
suggestedMin: 1.000
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var myChart = 0;
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
g1.onchange = setGravityDecimal
|
||||
g2.onchange = setGravityDecimal
|
||||
g3.onchange = setGravityDecimal
|
||||
g4.onchange = setGravityDecimal
|
||||
g5.onchange = setGravityDecimal
|
||||
|
||||
a1.onchange = setAngleDecimal
|
||||
a2.onchange = setAngleDecimal
|
||||
a3.onchange = setAngleDecimal
|
||||
a4.onchange = setAngleDecimal
|
||||
a5.onchange = setAngleDecimal
|
||||
|
||||
window.onload = getConfig;
|
||||
setButtonDisabled( true );
|
||||
|
||||
function convertToPlato(sg) {
|
||||
return 259-(259/sg);
|
||||
}
|
||||
|
||||
function convertToSG(plato) {
|
||||
return 259/(259-plato);
|
||||
}
|
||||
|
||||
function setAngleDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(2);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function setGravityDecimal(event) {
|
||||
if(isPlato())
|
||||
this.value = parseFloat(this.value).toFixed(1);
|
||||
else
|
||||
this.value = parseFloat(this.value).toFixed(4);
|
||||
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
|
||||
chartDataForm.sort(function (a, b) {
|
||||
return a.x - b.x;
|
||||
});
|
||||
}
|
||||
|
||||
function populateChartCalc(a, g) {
|
||||
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
}
|
||||
|
||||
function isPlato() {
|
||||
return $("#gravity-format").text() == "P";
|
||||
}
|
||||
|
||||
function populateChart() {
|
||||
|
||||
chartDataCalc.length = 0
|
||||
|
||||
for( i = 25.0; i<80.0; i+=5.0) {
|
||||
var formula = $("#formula").text();
|
||||
var angle = i.toString();
|
||||
formula=formula.replaceAll( "tilt^3", angle+"*"+angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt^2", angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt", angle );
|
||||
var g = eval( formula );
|
||||
|
||||
if(isPlato())
|
||||
g = convertToPlato(g);
|
||||
|
||||
populateChartCalc( i, g );
|
||||
}
|
||||
|
||||
chartDataForm.length = 0
|
||||
populateChartForm( $("#a1").val(), $("#g1").val() );
|
||||
populateChartForm( $("#a2").val(), $("#g2").val() );
|
||||
populateChartForm( $("#a3").val(), $("#g3").val() );
|
||||
populateChartForm( $("#a4").val(), $("#g4").val() );
|
||||
populateChartForm( $("#a5").val(), $("#g5").val() );
|
||||
|
||||
if( myChart )
|
||||
myChart.destroy();
|
||||
|
||||
myChart = new Chart(
|
||||
document.getElementById('gravityChart'),
|
||||
configChart
|
||||
);
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#calculate-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/formula";
|
||||
//var url = "/test/formula.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#id").val(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#formula").text(cfg["gravity-formula"]);
|
||||
$("#gravity-format").text(cfg["gravity-format"]); // Sets the variable used by isPlato()
|
||||
|
||||
if(isPlato()) {
|
||||
$("#gravity-header").text("Gravity (Plato):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(1) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(1) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(1) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(1) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(1) );
|
||||
} else {
|
||||
$("#gravity-header").text("Gravity (SG):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
|
||||
}
|
||||
|
||||
$("#a1").val( parseFloat(cfg["a1"]).toFixed(2) );
|
||||
$("#a2").val( parseFloat(cfg["a2"]).toFixed(2) );
|
||||
$("#a3").val( parseFloat(cfg["a3"]).toFixed(2) );
|
||||
$("#a4").val( parseFloat(cfg["a4"]).toFixed(2) );
|
||||
$("#a5").val( parseFloat(cfg["a5"]).toFixed(2) );
|
||||
|
||||
if( cfg["error"]!="" ) {
|
||||
showError(cfg["error"]);
|
||||
}
|
||||
|
||||
populateChart();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
52
html/calibration.min.htm
Normal file
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
@ -31,6 +31,9 @@
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
@ -52,23 +55,45 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning fade hide show d-none" role="alert" id="warning-sleep">
|
||||
<div>A sleep-interval of less than 300s will reduce battery life, consider using 900s</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning fade hide show d-none" role="alert" id="warning-gyro">
|
||||
<div>When using the gyro temperature use a sleep-interval that is greater than 300s for accurate readings</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
|
||||
function showWarningSleep() {
|
||||
$('#warning-sleep').removeClass('d-none').addClass('show');
|
||||
}
|
||||
function hideWarningSleep() {
|
||||
$('#warning-sleep').addClass('d-none').removeClass('show');
|
||||
}
|
||||
function showWarningGyro() {
|
||||
$('#warning-gyro').removeClass('d-none').addClass('show');
|
||||
}
|
||||
function hideWarningGyro() {
|
||||
$('#warning-gyro').addClass('d-none').removeClass('show');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<input type="text" name="runtime-average" id="runtime-average" hidden>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingOne">
|
||||
@ -83,14 +108,14 @@
|
||||
<form action="/api/config/device" method="post">
|
||||
<input type="text" name="id" id="id1" hidden>
|
||||
<div class="form-group row">
|
||||
<label for="mdns" class="col-sm-4 col-form-label">Device name:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="mdns" class="col-sm-3 col-form-label">Device name:</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" maxlength="12" class="form-control" name="mdns" id="mdns">
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="form-group row">
|
||||
<legend class="col-form-label col-sm-4 float-sm-left pt-0">Temperature Format:</legend>
|
||||
<div class="col-sm-8">
|
||||
<legend class="col-form-label col-sm-3 float-sm-left pt-0">Temperature Format:</legend>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="temp-format" id="temp-format-c" value="C" checked>
|
||||
<label class="form-check-label" for="temp-format-c">
|
||||
@ -106,14 +131,14 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group row">
|
||||
<label for="sleep-interval" class="col-sm-4 col-form-label">Interval (seconds):</label>
|
||||
<div class="col-sm-4">
|
||||
<label for="sleep-interval" class="col-sm-3 col-form-label">Interval (seconds):</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" min="10" max="3600" class="form-control" name="sleep-interval" id="sleep-interval">
|
||||
</div>
|
||||
<label for="sleep-interval" class="col-sm-4 col-form-label" id="sleep-interval-info"></label>
|
||||
<label for="sleep-interval" class="col-sm-7 col-form-label" id="sleep-interval-info"></label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<div class="col-sm-8 offset-sm-3">
|
||||
<button type="submit" class="btn btn-primary" id="device-btn">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -122,10 +147,10 @@
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="calibrate-btn" class="col-sm-4 col-form-label">Current calibration values:</label>
|
||||
<label for="calibrate-btn" class="col-sm-4 col-form-label" id="gyro-calibration-data">Loading...</label>
|
||||
<label for="gyro-calibration-data" class="col-sm-4 col-form-label" id="angle">Loading...</label>
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<label for="calibrate-btn" class="col-sm-3 col-form-label">Current calibration values:</label>
|
||||
<label for="calibrate-btn" class="col-sm-3 col-form-label" id="gyro-calibration-data">Loading...</label>
|
||||
<label for="gyro-calibration-data" class="col-sm-3 col-form-label" id="angle">Loading...</label>
|
||||
<div class="col-sm-8 offset-sm-3">
|
||||
<button type="button" class="btn btn-warning" id="calibrate-btn">Calibrate device</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -146,24 +171,44 @@
|
||||
<div class="card-body">
|
||||
<form action="/api/config/push" method="post">
|
||||
<input type="text" name="id" id="id2" hidden>
|
||||
<input type="text" name="http-push-h1" id="http-push-h1" hidden>
|
||||
<input type="text" name="http-push-h2" id="http-push-h2" hidden>
|
||||
<input type="text" name="http-push2-h1" id="http-push2-h1" hidden>
|
||||
<input type="text" name="http-push2-h2" id="http-push2-h2" hidden>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="http-push" class="col-sm-4 col-form-label">Http URL 1:</label>
|
||||
<label for="http-push" class="col-sm-2 col-form-label">Http URL 1:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="url" maxlength="100" class="form-control" name="http-push" id="http-push">
|
||||
<input type="url" maxlength="120" class="form-control" name="http-push" id="http-push">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-info" data-field1="#http-push-h1" data-field2="#http-push-h2" data-toggle="modal" data-target="#modal-http">Headers</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="http-push2" class="col-sm-4 col-form-label">Http URL 2:</label>
|
||||
<label for="http-push2" class="col-sm-2 col-form-label">Http URL 2:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="url" maxlength="100" class="form-control" name="http-push2" id="http-push2">
|
||||
<input type="url" maxlength="120" class="form-control" name="http-push2" id="http-push2">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="button" class="btn btn-info" data-field1="#http-push2-h1" data-field2="#http-push2-h2" data-toggle="modal" data-target="#modal-http">Headers</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="inputBrewfatherPush" class="col-sm-4 col-form-label">Brewfather URL:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="token" class="col-sm-2 col-form-label">Token:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="50" class="form-control" name="token" id="token">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="inputBrewfatherPush" class="col-sm-2 col-form-label">Brewfather URL:</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="url" maxlength="100" class="form-control" name="brewfather-push" id="brewfather-push">
|
||||
</div>
|
||||
</div>
|
||||
@ -171,35 +216,70 @@
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="influxdb2-push" class="col-sm-4 col-form-label">InfluxDB v2 URL:</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="url" maxlength="100" class="form-control" name="influxdb2-push" id="influxdb2-push">
|
||||
<label for="influxdb2-push" class="col-sm-2 col-form-label">InfluxDB v2 URL:</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="url" maxlength="40" class="form-control" name="influxdb2-push" id="influxdb2-push">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="influxdb2-org" class="col-sm-4 col-form-label">InfluxDB v2 Organisation:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="influxdb2-org" class="col-sm-2 col-form-label">InfluxDB v2 Org:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="50" class="form-control" name="influxdb2-org" id="influxdb2-org">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="influxdb2-bucket" class="col-sm-4 col-form-label">InfluxDB v2 Bucket:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="influxdb2-bucket" class="col-sm-2 col-form-label">InfluxDB v2 Bucket:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="50" class="form-control" name="influxdb2-bucket" id="influxdb2-bucket">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="influxdb2-auth" class="col-sm-4 col-form-label">InfluxDB v2 Auth. Token:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="influxdb2-auth" class="col-sm-2 col-form-label">InfluxDB v2 Auth:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="100" class="form-control" name="influxdb2-auth" id="influxdb2-auth">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<label for="mqtt-push" class="col-sm-2 col-form-label">MQTT Server:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="40" class="form-control" name="mqtt-push" id="mqtt-push">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="mqtt-topic" class="col-sm-2 col-form-label">MQTT Port:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="1" max="65535" class="form-control" name="mqtt-port" id="mqtt-port">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="mqtt-user" class="col-sm-2 col-form-label">MQTT User:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="20" class="form-control" name="mqtt-user" id="mqtt-user">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="mqtt-pass" class="col-sm-2 col-form-label">MQTT Password:</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" maxlength="20" class="form-control" name="mqtt-pass" id="mqtt-pass">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button type="submit" class="btn btn-primary" id="push-btn">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button class="btn btn-info" id="format-btn">Format editor</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -216,15 +296,31 @@
|
||||
<div class="card-body">
|
||||
<form action="/api/config/gravity" method="post">
|
||||
<input type="text" name="id" id="id3" hidden>
|
||||
<fieldset class="form-group row">
|
||||
<legend class="col-form-label col-sm-2 float-sm-left pt-0">Gravity Format:</legend>
|
||||
<div class="col-sm-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-g" value="G" checked>
|
||||
<label class="form-check-label" for="gravity-format-g">
|
||||
SG
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-p" value="P">
|
||||
<label class="form-check-label" for="gravity-format-p">
|
||||
Plato
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group row">
|
||||
<label for="gravity-formula" class="col-sm-4 col-form-label">Formula</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="gravity-formula" class="col-sm-2 col-form-label">Formula (SG)</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" maxlength="200" class="form-control" name="gravity-formula" id="gravity-formula">
|
||||
</div>
|
||||
</div>
|
||||
<label for="gravity-formula"" class="col-sm-8 offset-sm-4 col-form-label" id="gravity">Loading...</label>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<div class="col-sm-4 offset-sm-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="gravity-temp-adjustment" id="gravity-temp-adjustment">
|
||||
<label class="form-check-label" for="gravity-temp-adjustment">
|
||||
@ -234,7 +330,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<div class="col-sm-4 offset-sm-2">
|
||||
<button type="submit" class="btn btn-primary" id="gravity-btn">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -256,26 +352,36 @@
|
||||
<form action="/api/config/hardware" method="post">
|
||||
<input type="text" name="id" id="id4" hidden>
|
||||
<div class="form-group row">
|
||||
<label for="voltage-factor" class="col-sm-4 col-form-label">Voltage factor:</label>
|
||||
<div class="col-sm-4">
|
||||
<label for="voltage-factor" class="col-sm-2 col-form-label">Voltage factor:</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" step=".01" class="form-control" name="voltage-factor" id="voltage-factor">
|
||||
</div>
|
||||
<label for="voltage-factor" class="col-sm-4 col-form-label" id="battery">Loading...</label>
|
||||
<label for="voltage-factor" class="col-sm-3 col-form-label" id="battery">Loading...</label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="temp-adjustment-value" class="col-sm-4 col-form-label">Temp Sensor Adj:</label>
|
||||
<div class="col-sm-8">
|
||||
<label for="temp-adjustment-value" class="col-sm-2 col-form-label">Temp Sensor Adj:</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" step=".1" class="form-control" name="temp-adjustment-value" id="temp-adjustment-value">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="ota-url" class="col-sm-4 col-form-label">OTA base URL:</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-3 offset-sm-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="gyro-temp" id="gyro-temp">
|
||||
<label class="form-check-label" for="gyro-temp">
|
||||
Use gyro temperature
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="ota-url" class="col-sm-2 col-form-label">OTA base URL:</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="url" maxlength="90" class="form-control" name="ota-url" id="ota-url">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-4">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button type="submit" class="btn btn-primary" id="hardware-btn">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -288,6 +394,57 @@
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-http" tabindex="-1" role="dialog" aria-labelledby="modal-header" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modal-header">Define HTTP headers</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label for="http-header" class="col-form-label">Header 1 (Header: value)</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="header1" oninput="checkHeader(this)">
|
||||
<label for="http-header" class="col-form-label">Header 2 (Header: value)</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="header2" oninput="checkHeader(this)">
|
||||
<input type="text" id="field1" hidden>
|
||||
<input type="text" id="field2" hidden>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" id="btn-close" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#modal-http').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget)
|
||||
var field1 = button.data('field1')
|
||||
var field2 = button.data('field2')
|
||||
var modal = $(this)
|
||||
modal.find('.modal-body #header1').val($(field1).val())
|
||||
modal.find('.modal-body #header2').val($(field2).val())
|
||||
modal.find('.modal-body #field1').val(field1)
|
||||
modal.find('.modal-body #field2').val(field2)
|
||||
})
|
||||
$('#modal-http').on('hide.bs.modal', function (event) {
|
||||
var modal = $(this)
|
||||
field1 = modal.find('.modal-body #field1').val()
|
||||
field2 = modal.find('.modal-body #field2').val()
|
||||
$(field1).val(modal.find('.modal-body #header1').val())
|
||||
$(field2).val(modal.find('.modal-body #header2').val())
|
||||
})
|
||||
function checkHeader(input) {
|
||||
console.log( input.value );
|
||||
if (input.value != "" && input.value.indexOf(":") == -1) {
|
||||
$("#btn-close").prop("disabled", true);
|
||||
$(input).removeClass("is-valid").addClass("is-invalid");
|
||||
} else {
|
||||
$("#btn-close").prop("disabled", false);
|
||||
$(input).removeClass("is-invalid").addClass("is-valid");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
@ -313,13 +470,54 @@
|
||||
} );
|
||||
});
|
||||
|
||||
function updateSleepInfo() {
|
||||
var i = $("#sleep-interval").val()
|
||||
$("#sleep-interval-info").text( Math.floor(i/60) + " m " + (i%60) + " s" )
|
||||
// Open the format editor
|
||||
$("#format-btn").click(function(e){
|
||||
window.location.href = "/format.htm";
|
||||
});
|
||||
|
||||
function estimateBatteryLife(interval) {
|
||||
// ESP8266 consumes between 140-170mA when WIFI is on. Deep sleep is 20uA.
|
||||
// MPU-6050 consumes 4mA
|
||||
// DS18B20 consumes 1mA
|
||||
// For this estimation we use an average of 160mA
|
||||
|
||||
var pwrActive = 160; // mA per hour
|
||||
var pwrSleep = 5; // mA per day
|
||||
var batt = 2200; // mA
|
||||
var rt = parseInt($("#runtime-average").val());
|
||||
|
||||
if(rt<1) rt = 2;
|
||||
|
||||
// The deep sleep will consume approx 1mA per day.
|
||||
var powerPerDay = (24*3600)/(interval+rt)*(rt/3600)*pwrActive + pwrSleep;
|
||||
return batt/powerPerDay;
|
||||
}
|
||||
|
||||
// Trigger the calibration
|
||||
function updateSleepInfo() {
|
||||
var i = parseInt($("#sleep-interval").val());
|
||||
|
||||
var j = estimateBatteryLife(i);
|
||||
|
||||
var t1 = Math.floor(i/60) + " m " + (i%60) + " s";
|
||||
var t2 = Math.floor(j/7) + " weeks " + (i%7) + " days";
|
||||
|
||||
$("#sleep-interval-info").text(t1);
|
||||
//$("#sleep-interval-info").text( t1 + " - Estimated life: " + t2);
|
||||
console.log( "Estimated life: " + t2);
|
||||
|
||||
hideWarningGyro();
|
||||
if(i>0 && i<300) {
|
||||
if( $("#gyro-temp").is(":checked") )
|
||||
showWarningGyro();
|
||||
showWarningSleep();
|
||||
} else {
|
||||
hideWarningSleep();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the calibration and show warnings if needed
|
||||
$("#sleep-interval").keyup(updateSleepInfo);
|
||||
$("#gyro-temp").change(updateSleepInfo);
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#device-btn").prop("disabled", b);
|
||||
@ -327,6 +525,7 @@
|
||||
$("#push-btn").prop("disabled", b);
|
||||
$("#gravity-btn").prop("disabled", b);
|
||||
$("#hardware-btn").prop("disabled", b);
|
||||
$("#format-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
@ -343,27 +542,38 @@
|
||||
$("#id3").val(cfg["id"]);
|
||||
$("#id4").val(cfg["id"]);
|
||||
$("#mdns").val(cfg["mdns"]);
|
||||
if( cfg["temp-format"] == "C" )
|
||||
$("#temp-format-c").click();
|
||||
else
|
||||
$("#temp-format-f").click();
|
||||
if( cfg["temp-format"] == "C" ) $("#temp-format-c").click();
|
||||
else $("#temp-format-f").click();
|
||||
if( cfg["gravity-format"] == "G" ) $("#gravity-format-g").click();
|
||||
else $("#gravity-format-p").click();
|
||||
$("#ota-url").val(cfg["ota-url"]);
|
||||
$("#token").val(cfg["token"]);
|
||||
$("#http-push").val(cfg["http-push"]);
|
||||
$("#http-push-h1").val(cfg["http-push-h1"]);
|
||||
$("#http-push-h2").val(cfg["http-push-h2"]);
|
||||
$("#http-push2").val(cfg["http-push2"]);
|
||||
$("#http-push2-h1").val(cfg["http-push2-h1"]);
|
||||
$("#http-push2-h2").val(cfg["http-push2-h2"]);
|
||||
$("#brewfather-push").val(cfg["brewfather-push"]);
|
||||
$("#influxdb2-push").val(cfg["influxdb2-push"]);
|
||||
$("#influxdb2-org").val(cfg["influxdb2-org"]);
|
||||
$("#influxdb2-bucket").val(cfg["influxdb2-bucket"]);
|
||||
$("#influxdb2-auth").val(cfg["influxdb2-auth"]);
|
||||
$("#mqtt-push").val(cfg["mqtt-push"]);
|
||||
$("#mqtt-port").val(cfg["mqtt-port"]);
|
||||
$("#mqtt-user").val(cfg["mqtt-user"]);
|
||||
$("#mqtt-pass").val(cfg["mqtt-pass"]);
|
||||
$("#sleep-interval").val(cfg["sleep-interval"]);
|
||||
$("#voltage-factor").val(cfg["voltage-factor"]);
|
||||
$("#gravity-formula").val(cfg["gravity-formula"]);
|
||||
$("#temp-adjustment-value").val(cfg["temp-adjustment-value"]);
|
||||
$("#gravity-temp-adjustment").prop( "checked", cfg["gravity-temp-adjustment"] );
|
||||
$("#gyro-temp").prop( "checked", cfg["gyro-temp"] );
|
||||
$("#gyro-calibration-data").text( cfg["gyro-calibration-data"]["ax"] + "," + cfg["gyro-calibration-data"]["ay"] + "," + cfg["gyro-calibration-data"]["az"] + "," + cfg["gyro-calibration-data"]["gx"] + "," + cfg["gyro-calibration-data"]["gy"] + "," + cfg["gyro-calibration-data"]["gz"] );
|
||||
$("#battery").text(cfg["battery"] + " V");
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
$("#runtime-average").val(cfg["runtime-average"]);
|
||||
//$("#gravity").text(cfg["gravity"] + " SG");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
@ -378,6 +588,6 @@
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/config.min.htm
Normal file
@ -6,9 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
@ -32,6 +32,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
@ -85,7 +88,39 @@
|
||||
<div class="col-md-8 themed-grid-col bg-light">Device ID:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">
|
||||
View error log
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 3000); //5 seconds
|
||||
|
||||
function loadLog() {
|
||||
$("#logContent").load("/log");
|
||||
//$("#logContent").load("/test/log");
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="collapse" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code id="logContent"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -97,9 +132,10 @@
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.4.0)");
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.8.0)");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
@ -112,6 +148,6 @@
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/device.min.htm
Normal file
@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Average runtime:</div><div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div></div><div class="row mb-3"><a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">View error log</a></div><script>function loadLog(){$("#logContent").load("/log")}$("#log-btn").click(function(o){loadLog()}),setInterval(function(){loadLog()},3e3)</script><div class="collapse" id="collapseLog"><div class="card card-body"><pre><code id="logContent"></code></pre></div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var e="/api/device";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#app-ver").text(e["app-ver"]+" (html 0.8.0)"),$("#mdns").text(e.mdns),$("#id").text(e.id),$("#runtime").text(e["runtime-average"]+" seconds")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
231
html/format.htm
Normal file
@ -0,0 +1,231 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
|
||||
<div id="alert-msg">...</div>
|
||||
<button type="button" id="alert-btn" class="close" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingOne">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||
Push Format Templates
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
<input type="text" name="http-1" id="http-1" hidden>
|
||||
<input type="text" name="http-2" id="http-2" hidden>
|
||||
<!--<input type="text" name="brewfather" id="brewfather" hidden>-->
|
||||
<input type="text" name="influxdb" id="influxdb" hidden>
|
||||
<input type="text" name="mqtt" id="mqtt" hidden>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="push-target" class="col-sm-2 col-form-label">Push target:</label>
|
||||
<select class="custom-select col-sm-4" required name="push-target" id="push-target">
|
||||
<option value="http-1">HTTP option 1</option>
|
||||
<option value="http-2">HTTP option 2</option>
|
||||
<!--<option value="brewfather">Brewfather</option>-->
|
||||
<option value="influxdb">Influx DB</option>
|
||||
<option value="mqtt">MQTT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button class="btn btn-primary" id="format-btn">Save</button>
|
||||
<button class="btn btn-secondary" id="test-btn">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre class="card-preview" id="preview" name="preview"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
setButtonDisabled( true );
|
||||
|
||||
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
|
||||
$(document).ready(function () {
|
||||
if(location.hash != null && location.hash != ""){
|
||||
$('.collapse').removeClass('in');
|
||||
$(location.hash + '.collapse').collapse('show');
|
||||
}
|
||||
});
|
||||
|
||||
$("#push-target").change(function(e){
|
||||
console.log(e)
|
||||
selectFormat();
|
||||
});
|
||||
|
||||
// Store the format
|
||||
$("#format-btn").click(function(e) {
|
||||
var s = $("#format").val();
|
||||
s = s.replaceAll("\n", "");
|
||||
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
|
||||
console.log(obj);
|
||||
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/config/format",
|
||||
data: obj,
|
||||
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
|
||||
error: function(result) { showError('Unable to store format.'); }
|
||||
} );
|
||||
});
|
||||
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var doc = $("#format").val();
|
||||
doc = doc.replaceAll("${mdns}", "testing");
|
||||
doc = doc.replaceAll("${id}", "e4a344");
|
||||
doc = doc.replaceAll("${sleep-interval}", "300");
|
||||
doc = doc.replaceAll("${temp}", "21.1");
|
||||
doc = doc.replaceAll("${token}", "a-token");
|
||||
doc = doc.replaceAll("${temp-c}", "21.1");
|
||||
doc = doc.replaceAll("${temp-f}", "51.3");
|
||||
doc = doc.replaceAll("${temp-unit}", "C");
|
||||
doc = doc.replaceAll("${battery}", "3.86");
|
||||
doc = doc.replaceAll("${rssi}", "-76");
|
||||
doc = doc.replaceAll("${run-time}", "4.32");
|
||||
doc = doc.replaceAll("${gravity}", "1.044");
|
||||
doc = doc.replaceAll("${gravity-sg}", "1.044");
|
||||
doc = doc.replaceAll("${gravity-plato}", "9.5");
|
||||
doc = doc.replaceAll("${gravity-unit}", "G");
|
||||
doc = doc.replaceAll("${corr-gravity}", "1.044");
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", "1.044");
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", "9.5");
|
||||
doc = doc.replaceAll("${angle}", "54.5");
|
||||
doc = doc.replaceAll("${tilt}", "54.5");
|
||||
|
||||
// Format in a readable json string.
|
||||
try {
|
||||
var json = JSON.parse(doc);
|
||||
doc = JSON.stringify(json, null, 2);
|
||||
} catch(e) {
|
||||
console.log("Not a javascript object!")
|
||||
}
|
||||
|
||||
$("#preview").text(doc);
|
||||
});
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#format-btn").prop("disabled", b);
|
||||
$("#test-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function selectFormat() {
|
||||
var s = "#" + $("#push-target").val()
|
||||
console.log(s);
|
||||
s = decodeURIComponent($(s).val());
|
||||
console.log(s);
|
||||
s = s.replaceAll("|", "|\n");
|
||||
console.log(s);
|
||||
$("#format").val(s);
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/config/format";
|
||||
//var url = "/test/format.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").val(cfg["id"]);
|
||||
|
||||
$("#http-1").val(cfg["http-1"]);
|
||||
$("#http-2").val(cfg["http-2"]);
|
||||
//$("#brewfather").val(cfg["brewfather"]);
|
||||
$("#influxdb").val(cfg["influxdb"]);
|
||||
$("#mqtt").val(cfg["mqtt"]);
|
||||
selectFormat();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
2
html/format.min.htm
Normal file
@ -0,0 +1,2 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/config.htm">Back to configuration</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-2"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$("#alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$("#alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$("#alert").addClass("d-none").removeClass("show")})</script><div class="accordion" id="accordion"><div class="card"><div class="card-header" id="headingOne"><h2 class="mb-0"><button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">Push Format Templates</button></h2></div><div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion"><div class="card-body"><input type="text" name="id" id="id" hidden> <input type="text" name="http-1" id="http-1" hidden> <input type="text" name="http-2" id="http-2" hidden><!--<input type="text" name="brewfather" id="brewfather" hidden>--> <input type="text" name="influxdb" id="influxdb" hidden> <input type="text" name="mqtt" id="mqtt" hidden><div class="form-group row"><label for="push-target" class="col-sm-2 col-form-label">Push target:</label> <select class="custom-select col-sm-4" required name="push-target" id="push-target"><option value="http-1">HTTP option 1</option><option value="http-2">HTTP option 2</option><!--<option value="brewfather">Brewfather</option>--><option value="influxdb">Influx DB</option><option value="mqtt">MQTT</option></select></div><div class="form-group row"><div class="col-sm-12"><textarea rows="5" class="form-control" name="format" id="format">
|
||||
</textarea></div></div><div class="form-group row"><div class="col-sm-8 offset-sm-2"><button class="btn btn-primary" id="format-btn">Save</button> <button class="btn btn-secondary" id="test-btn">Test</button></div></div><pre class="card-preview" id="preview" name="preview"></pre></div></div></div><hr class="my-4"></div><script type="text/javascript">function setButtonDisabled(l){$("#format-btn").prop("disabled",l),$("#test-btn").prop("disabled",l)}function selectFormat(){var l="#"+$("#push-target").val();console.log(l),l=decodeURIComponent($(l).val()),console.log(l),l=l.replaceAll("|","|\n"),console.log(l),$("#format").val(l),$("#preview").text("")}function getConfig(){setButtonDisabled(!0);var l="/api/config/format";$("#spinner").show(),$.getJSON(l,function(l){console.log(l),$("#id").val(l.id),$("#http-1").val(l["http-1"]),$("#http-2").val(l["http-2"]),$("#influxdb").val(l.influxdb),$("#mqtt").val(l.mqtt),selectFormat()}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide(),setButtonDisabled(!1)})}window.onload=getConfig,setButtonDisabled(!0),$(document).ready(function(){null!=location.hash&&""!=location.hash&&($(".collapse").removeClass("in"),$(location.hash+".collapse").collapse("show"))}),$("#push-target").change(function(l){console.log(l),selectFormat()}),$("#format-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("\n","");var t="id="+$("#id").val()+"&"+$("#push-target").val()+"="+encodeURIComponent(e);console.log(t),$.ajax({type:"POST",url:"/api/config/format",data:t,success:function(l){showSuccess("Format stored successfully."),getConfig()},error:function(l){showError("Unable to store format.")}})}),$("#test-btn").click(function(l){var e=$("#format").val();e=e.replaceAll("${mdns}","testing"),e=e.replaceAll("${id}","e4a344"),e=e.replaceAll("${sleep-interval}","300"),e=e.replaceAll("${temp}","21.1"),e=e.replaceAll("${token}","a-token"),e=e.replaceAll("${temp-c}","21.1"),e=e.replaceAll("${temp-f}","51.3"),e=e.replaceAll("${temp-unit}","C"),e=e.replaceAll("${battery}","3.86"),e=e.replaceAll("${rssi}","-76"),e=e.replaceAll("${run-time}","4.32"),e=e.replaceAll("${gravity}","1.044"),e=e.replaceAll("${gravity-sg}","1.044"),e=e.replaceAll("${gravity-plato}","9.5"),e=e.replaceAll("${gravity-unit}","G"),e=e.replaceAll("${corr-gravity}","1.044"),e=e.replaceAll("${corr-gravity-sg}","1.044"),e=e.replaceAll("${corr-gravity-plato}","9.5"),e=e.replaceAll("${angle}","54.5"),e=e.replaceAll("${tilt}","54.5");try{var t=JSON.parse(e);e=JSON.stringify(t,null,2)}catch(l){console.log("Not a javascript object!")}$("#preview").text(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
@ -31,6 +31,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
@ -119,14 +122,43 @@
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
$("#battery").text(cfg["battery"] + " V");
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#gravity").text("Gyro moving");
|
||||
} else {
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
}
|
||||
|
||||
var batt = cfg["battery"];
|
||||
var charge = 0;
|
||||
|
||||
if(batt>4.15) charge = 100;
|
||||
else if(batt>4.05) charge = 90;
|
||||
else if(batt>3.97) charge = 80;
|
||||
else if(batt>3.91) charge = 70;
|
||||
else if(batt>3.86) charge = 60;
|
||||
else if(batt>3.81) charge = 50;
|
||||
else if(batt>3.78) charge = 40;
|
||||
else if(batt>3.76) charge = 30;
|
||||
else if(batt>3.73) charge = 20;
|
||||
else if(batt>3.67) charge = 10;
|
||||
else if(batt>3.44) charge = 5;
|
||||
|
||||
$("#battery").text(batt + " V (" + charge + "%)" );
|
||||
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
//console.log(cfg["sleep-mode"] );
|
||||
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
@ -148,6 +180,6 @@
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/index.min.htm
Normal file
@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
@ -74,6 +74,14 @@
|
||||
<div class="col-md-2 themed-grid-col bg-light">config.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">calibration.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="calibration">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">format.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="format">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
|
||||
@ -118,11 +126,21 @@ function getUpload() {
|
||||
else
|
||||
$("#device").text("File is missing.");
|
||||
|
||||
if( cfg["config"] )
|
||||
if( cfg["config"] )
|
||||
$("#config").text("Completed.");
|
||||
else
|
||||
$("#config").text("File is missing.");
|
||||
|
||||
if( cfg["calibration"] )
|
||||
$("#calibration").text("Completed.");
|
||||
else
|
||||
$("#calibration").text("File is missing.");
|
||||
|
||||
if( cfg["format"] )
|
||||
$("#format").text("Completed.");
|
||||
else
|
||||
$("#format").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
@ -141,6 +159,6 @@ function getUpload() {
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/upload.min.htm
Normal file
BIN
img/build-1.png
Before Width: | Height: | Size: 112 KiB |
BIN
img/build-2.png
Before Width: | Height: | Size: 110 KiB |
BIN
img/config1.png
Before Width: | Height: | Size: 13 KiB |
BIN
img/config2.png
Before Width: | Height: | Size: 19 KiB |
BIN
img/config3.png
Before Width: | Height: | Size: 7.7 KiB |
BIN
img/config4.png
Before Width: | Height: | Size: 11 KiB |
BIN
img/device.png
Before Width: | Height: | Size: 16 KiB |
BIN
img/index.png
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 115 KiB |
BIN
img/spindle.png
Before Width: | Height: | Size: 114 KiB |
1051
lib/Arduino-Temperature-Control-Library/DallasTemperature.cpp
Normal file
323
lib/Arduino-Temperature-Control-Library/DallasTemperature.h
Normal file
@ -0,0 +1,323 @@
|
||||
#ifndef DallasTemperature_h
|
||||
#define DallasTemperature_h
|
||||
|
||||
#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0
|
||||
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
// set to true to include code for new and delete operators
|
||||
#ifndef REQUIRESNEW
|
||||
#define REQUIRESNEW false
|
||||
#endif
|
||||
|
||||
// set to true to include code implementing alarm search functions
|
||||
#ifndef REQUIRESALARMS
|
||||
#define REQUIRESALARMS true
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#ifdef __STM32F1__
|
||||
#include <OneWireSTM.h>
|
||||
#else
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
|
||||
// Model IDs
|
||||
#define DS18S20MODEL 0x10 // also DS1820
|
||||
#define DS18B20MODEL 0x28 // also MAX31820
|
||||
#define DS1822MODEL 0x22
|
||||
#define DS1825MODEL 0x3B
|
||||
#define DS28EA00MODEL 0x42
|
||||
|
||||
// Error Codes
|
||||
#define DEVICE_DISCONNECTED_C -127
|
||||
#define DEVICE_DISCONNECTED_F -196.6
|
||||
#define DEVICE_DISCONNECTED_RAW -7040
|
||||
|
||||
// For readPowerSupply on oneWire bus
|
||||
// definition of nullptr for C++ < 11, using official workaround:
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
|
||||
#if __cplusplus < 201103L
|
||||
const class
|
||||
{
|
||||
public:
|
||||
template <class T>
|
||||
operator T *() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template <class C, class T>
|
||||
operator T C::*() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void operator&() const;
|
||||
} nullptr = {};
|
||||
#endif
|
||||
|
||||
typedef uint8_t DeviceAddress[8];
|
||||
|
||||
class DallasTemperature {
|
||||
public:
|
||||
|
||||
DallasTemperature();
|
||||
DallasTemperature(OneWire*);
|
||||
DallasTemperature(OneWire*, uint8_t);
|
||||
|
||||
void setOneWire(OneWire*);
|
||||
|
||||
void setPullupPin(uint8_t);
|
||||
|
||||
// initialise bus
|
||||
void begin(void);
|
||||
|
||||
// returns the number of devices found on the bus
|
||||
uint8_t getDeviceCount(void);
|
||||
|
||||
// returns the number of DS18xxx Family devices on bus
|
||||
uint8_t getDS18Count(void);
|
||||
|
||||
// returns true if address is valid
|
||||
bool validAddress(const uint8_t*);
|
||||
|
||||
// returns true if address is of the family of sensors the lib supports.
|
||||
bool validFamily(const uint8_t* deviceAddress);
|
||||
|
||||
// finds an address at a given index on the bus
|
||||
bool getAddress(uint8_t*, uint8_t);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
bool isConnected(const uint8_t*);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
// also allows for updating the read scratchpad
|
||||
bool isConnected(const uint8_t*, uint8_t*);
|
||||
|
||||
// read device's scratchpad
|
||||
bool readScratchPad(const uint8_t*, uint8_t*);
|
||||
|
||||
// write device's scratchpad
|
||||
void writeScratchPad(const uint8_t*, const uint8_t*);
|
||||
|
||||
// read device's power requirements
|
||||
bool readPowerSupply(const uint8_t* deviceAddress = nullptr);
|
||||
|
||||
// get global resolution
|
||||
uint8_t getResolution();
|
||||
|
||||
// set global resolution to 9, 10, 11, or 12 bits
|
||||
void setResolution(uint8_t);
|
||||
|
||||
// returns the device resolution: 9, 10, 11, or 12 bits
|
||||
uint8_t getResolution(const uint8_t*);
|
||||
|
||||
// set resolution of a device to 9, 10, 11, or 12 bits
|
||||
bool setResolution(const uint8_t*, uint8_t,
|
||||
bool skipGlobalBitResolutionCalculation = false);
|
||||
|
||||
// sets/gets the waitForConversion flag
|
||||
void setWaitForConversion(bool);
|
||||
bool getWaitForConversion(void);
|
||||
|
||||
// sets/gets the checkForConversion flag
|
||||
void setCheckForConversion(bool);
|
||||
bool getCheckForConversion(void);
|
||||
|
||||
// sends command for all devices on the bus to perform a temperature conversion
|
||||
void requestTemperatures(void);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by address
|
||||
bool requestTemperaturesByAddress(const uint8_t*);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by index
|
||||
bool requestTemperaturesByIndex(uint8_t);
|
||||
|
||||
// returns temperature raw value (12 bit integer of 1/128 degrees C)
|
||||
int16_t getTemp(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees C
|
||||
float getTempC(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees F
|
||||
float getTempF(const uint8_t*);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempCByIndex(uint8_t);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempFByIndex(uint8_t);
|
||||
|
||||
// returns true if the bus requires parasite power
|
||||
bool isParasitePowerMode(void);
|
||||
|
||||
// Is a conversion complete on the wire? Only applies to the first sensor on the wire.
|
||||
bool isConversionComplete(void);
|
||||
|
||||
static uint16_t millisToWaitForConversion(uint8_t);
|
||||
|
||||
uint16_t millisToWaitForConversion();
|
||||
|
||||
// Sends command to one device to save values from scratchpad to EEPROM by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to save values from scratchpad to EEPROM
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sends command to one device to recall values from EEPROM to scratchpad by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to recall values from EEPROM to scratchpad
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sets the autoSaveScratchPad flag
|
||||
void setAutoSaveScratchPad(bool);
|
||||
|
||||
// Gets the autoSaveScratchPad flag
|
||||
bool getAutoSaveScratchPad(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
typedef void AlarmHandler(const uint8_t*);
|
||||
|
||||
// sets the high alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setHighAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// sets the low alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setLowAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// returns a int8_t with the current high alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getHighAlarmTemp(const uint8_t*);
|
||||
|
||||
// returns a int8_t with the current low alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getLowAlarmTemp(const uint8_t*);
|
||||
|
||||
// resets internal variables used for the alarm search
|
||||
void resetAlarmSearch(void);
|
||||
|
||||
// search the wire for devices with active alarms
|
||||
bool alarmSearch(uint8_t*);
|
||||
|
||||
// returns true if ia specific device has an alarm
|
||||
bool hasAlarm(const uint8_t*);
|
||||
|
||||
// returns true if any device is reporting an alarm on the bus
|
||||
bool hasAlarm(void);
|
||||
|
||||
// runs the alarm handler for all devices returned by alarmSearch()
|
||||
void processAlarms(void);
|
||||
|
||||
// sets the alarm handler
|
||||
void setAlarmHandler(const AlarmHandler *);
|
||||
|
||||
// returns true if an AlarmHandler has been set
|
||||
bool hasAlarmHandler();
|
||||
|
||||
#endif
|
||||
|
||||
// if no alarm handler is used the two bytes can be used as user data
|
||||
// example of such usage is an ID.
|
||||
// note if device is not connected it will fail writing the data.
|
||||
// note if address cannot be found no error will be reported.
|
||||
// in short use carefully
|
||||
void setUserData(const uint8_t*, int16_t);
|
||||
void setUserDataByIndex(uint8_t, int16_t);
|
||||
int16_t getUserData(const uint8_t*);
|
||||
int16_t getUserDataByIndex(uint8_t);
|
||||
|
||||
// convert from Celsius to Fahrenheit
|
||||
static float toFahrenheit(float);
|
||||
|
||||
// convert from Fahrenheit to Celsius
|
||||
static float toCelsius(float);
|
||||
|
||||
// convert from raw to Celsius
|
||||
static float rawToCelsius(int16_t);
|
||||
|
||||
// convert from Celsius to raw
|
||||
static int16_t celsiusToRaw(float);
|
||||
|
||||
// convert from raw to Fahrenheit
|
||||
static float rawToFahrenheit(int16_t);
|
||||
|
||||
#if REQUIRESNEW
|
||||
|
||||
// initialize memory area
|
||||
void* operator new (unsigned int);
|
||||
|
||||
// delete memory reference
|
||||
void operator delete(void*);
|
||||
|
||||
#endif
|
||||
|
||||
void blockTillConversionComplete(uint8_t);
|
||||
|
||||
private:
|
||||
typedef uint8_t ScratchPad[9];
|
||||
|
||||
// parasite power on or off
|
||||
bool parasite;
|
||||
|
||||
// external pullup
|
||||
bool useExternalPullup;
|
||||
uint8_t pullupPin;
|
||||
|
||||
// used to determine the delay amount needed to allow for the
|
||||
// temperature conversion to take place
|
||||
uint8_t bitResolution;
|
||||
|
||||
// used to requestTemperature with or without delay
|
||||
bool waitForConversion;
|
||||
|
||||
// used to requestTemperature to dynamically check if a conversion is complete
|
||||
bool checkForConversion;
|
||||
|
||||
// used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write
|
||||
bool autoSaveScratchPad;
|
||||
|
||||
// count of devices on the bus
|
||||
uint8_t devices;
|
||||
|
||||
// count of DS18xxx Family devices on bus
|
||||
uint8_t ds18Count;
|
||||
|
||||
// Take a pointer to one wire instance
|
||||
OneWire* _wire;
|
||||
|
||||
// reads scratchpad and returns the raw temperature
|
||||
int16_t calculateTemperature(const uint8_t*, uint8_t*);
|
||||
|
||||
|
||||
// Returns true if all bytes of scratchPad are '\0'
|
||||
bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9);
|
||||
|
||||
// External pullup control
|
||||
void activateExternalPullup(void);
|
||||
void deactivateExternalPullup(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
// required for alarmSearch
|
||||
uint8_t alarmSearchAddress[8];
|
||||
int8_t alarmSearchJunction;
|
||||
uint8_t alarmSearchExhausted;
|
||||
|
||||
// the alarm handler function pointer
|
||||
AlarmHandler *_AlarmHandler;
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
#endif
|
368
lib/ESP_DoubleResetDetector/ESP_DoubleResetDetector.h
Normal file
@ -0,0 +1,368 @@
|
||||
/****************************************************************************************************************************
|
||||
ESP_DoubleResetDetector.h
|
||||
For ESP8266 / ESP32 boards
|
||||
|
||||
ESP_DoubleResetDetector is a library for the ESP8266/Arduino platform
|
||||
to enable trigger configure mode by resetting ESP32 / ESP8266 twice.
|
||||
|
||||
Forked from DataCute https://github.com/datacute/DoubleResetDetector
|
||||
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_DoubleResetDetector
|
||||
Licensed under MIT license
|
||||
Version: 1.2.1
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
1.0.0 K Hoang 15/12/2019 Initial coding
|
||||
1.0.1 K Hoang 30/12/2019 Now can use EEPROM or SPIFFS for both ESP8266 and ESP32. RTC still OK for ESP8266
|
||||
1.0.2 K Hoang 10/04/2020 Fix bug by left-over cpp file and in example.
|
||||
1.0.3 K Hoang 13/05/2020 Update to use LittleFS for ESP8266 core 2.7.1+
|
||||
1.1.0 K Hoang 04/12/2020 Add support to LittleFS for ESP32 using LITTLEFS Library
|
||||
1.1.1 K Hoang 28/12/2020 Suppress all possible compiler warnings
|
||||
1.1.2 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
|
||||
1.2.0 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
|
||||
1.2.1 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP_DoubleResetDetector_H
|
||||
#define ESP_DoubleResetDetector_H
|
||||
|
||||
#if defined(ARDUINO) && (ARDUINO >= 100)
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#endif
|
||||
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION "ESP_DoubleResetDetector v1.2.1"
|
||||
#define ESP_DOUBLERESETDETECTOR_VERSION ESP_DOUBLE_RESET_DETECTOR_VERSION
|
||||
|
||||
//#define ESP_DRD_USE_EEPROM false
|
||||
//#define ESP_DRD_USE_LITTLEFS false
|
||||
//#define ESP_DRD_USE_SPIFFS false
|
||||
//#define ESP8266_DRD_USE_RTC false //true
|
||||
|
||||
#ifdef ESP32
|
||||
#if (!ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
|
||||
#warning Neither EEPROM, SPIFFS nor LittleFS selected. Default to EEPROM
|
||||
#ifdef ESP_DRD_USE_EEPROM
|
||||
#undef ESP_DRD_USE_EEPROM
|
||||
#define ESP_DRD_USE_EEPROM true
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ESP8266
|
||||
#if (!ESP8266_DRD_USE_RTC && !ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
|
||||
#warning Neither RTC, EEPROM, LITTLEFS nor SPIFFS selected. Default to EEPROM
|
||||
#ifdef ESP_DRD_USE_EEPROM
|
||||
#undef ESP_DRD_USE_EEPROM
|
||||
#define ESP_DRD_USE_EEPROM true
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//default to use EEPROM, otherwise, use LITTLEFS (higher priority), then SPIFFS
|
||||
#if ESP_DRD_USE_EEPROM
|
||||
#include <EEPROM.h>
|
||||
|
||||
#define FLAG_DATA_SIZE 4
|
||||
|
||||
#ifndef EEPROM_SIZE
|
||||
#define EEPROM_SIZE 512
|
||||
#endif
|
||||
|
||||
#ifndef EEPROM_START
|
||||
#define EEPROM_START 256
|
||||
#endif
|
||||
|
||||
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
|
||||
|
||||
#include <FS.h>
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#if ESP_DRD_USE_LITTLEFS
|
||||
// Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h
|
||||
//#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2)
|
||||
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) )
|
||||
#warning Using ESP32 Core 1.0.6 or 2.0.0+
|
||||
// The library has been merged into esp32 core from release 1.0.6
|
||||
#include <LittleFS.h>
|
||||
|
||||
#define FileFS LittleFS
|
||||
#define FS_Name "LittleFS"
|
||||
#else
|
||||
#warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library
|
||||
// The library has been merged into esp32 core from release 1.0.6
|
||||
#include <LITTLEFS.h> // https://github.com/lorol/LITTLEFS
|
||||
|
||||
#define FileFS LITTLEFS
|
||||
#define FS_Name "LittleFS"
|
||||
#endif
|
||||
#else
|
||||
#include "SPIFFS.h"
|
||||
// ESP32 core 1.0.4 still uses SPIFFS
|
||||
#define FileFS SPIFFS
|
||||
#endif
|
||||
|
||||
#else
|
||||
// From ESP8266 core 2.7.1
|
||||
#include <LittleFS.h>
|
||||
|
||||
#if ESP_DRD_USE_LITTLEFS
|
||||
#define FileFS LittleFS
|
||||
#else
|
||||
#define FileFS SPIFFS
|
||||
#endif
|
||||
|
||||
#endif // #if ESP_DRD_USE_EEPROM
|
||||
|
||||
|
||||
|
||||
#define DRD_FILENAME "/drd.dat"
|
||||
|
||||
#endif //#if ESP_DRD_USE_EEPROM
|
||||
|
||||
#ifndef DOUBLERESETDETECTOR_DEBUG
|
||||
#define DOUBLERESETDETECTOR_DEBUG false
|
||||
#endif
|
||||
|
||||
#define DOUBLERESETDETECTOR_FLAG_SET 0xD0D01234
|
||||
#define DOUBLERESETDETECTOR_FLAG_CLEAR 0xD0D04321
|
||||
|
||||
class DoubleResetDetector
|
||||
{
|
||||
public:
|
||||
DoubleResetDetector(int timeout, int address)
|
||||
{
|
||||
#if ESP_DRD_USE_EEPROM
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.printf("EEPROM size = %d, start = %d\n", EEPROM_SIZE, EEPROM_START);
|
||||
#endif
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
|
||||
// LittleFS / SPIFFS code
|
||||
if (!FileFS.begin())
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
|
||||
#if ESP_DRD_USE_LITTLEFS
|
||||
Serial.println("LittleFS failed!. Please use SPIFFS or EEPROM.");
|
||||
#else
|
||||
Serial.println("SPIFFS failed!. Please use LittleFS or EEPROM.");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#ifdef ESP8266
|
||||
//RTC only for ESP8266
|
||||
#endif
|
||||
#endif
|
||||
|
||||
this->timeout = timeout * 1000;
|
||||
this->address = address;
|
||||
doubleResetDetected = false;
|
||||
waitingForDoubleReset = false;
|
||||
};
|
||||
|
||||
bool detectDoubleReset()
|
||||
{
|
||||
doubleResetDetected = detectRecentlyResetFlag();
|
||||
|
||||
if (doubleResetDetected)
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("doubleResetDetected");
|
||||
#endif
|
||||
|
||||
clearRecentlyResetFlag();
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("No doubleResetDetected");
|
||||
#endif
|
||||
|
||||
setRecentlyResetFlag();
|
||||
waitingForDoubleReset = true;
|
||||
}
|
||||
|
||||
return doubleResetDetected;
|
||||
|
||||
};
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (waitingForDoubleReset && millis() > timeout)
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Stop doubleResetDetecting");
|
||||
#endif
|
||||
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
void stop()
|
||||
{
|
||||
clearRecentlyResetFlag();
|
||||
waitingForDoubleReset = false;
|
||||
};
|
||||
|
||||
bool doubleResetDetected;
|
||||
|
||||
|
||||
private:
|
||||
uint32_t DOUBLERESETDETECTOR_FLAG;
|
||||
unsigned long timeout;
|
||||
int address;
|
||||
bool waitingForDoubleReset;
|
||||
|
||||
bool detectRecentlyResetFlag()
|
||||
{
|
||||
#if (ESP_DRD_USE_EEPROM)
|
||||
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
|
||||
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG;
|
||||
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.printf("EEPROM Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
|
||||
#endif
|
||||
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
|
||||
// LittleFS / SPIFFS code
|
||||
if (FileFS.exists(DRD_FILENAME))
|
||||
{
|
||||
// if config file exists, load
|
||||
File file = FileFS.open(DRD_FILENAME, "r");
|
||||
|
||||
if (!file)
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Loading config file failed");
|
||||
#endif
|
||||
}
|
||||
|
||||
file.readBytes((char *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
|
||||
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG;
|
||||
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
|
||||
#if ESP_DRD_USE_LITTLEFS
|
||||
Serial.printf("LittleFS Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
|
||||
#else
|
||||
Serial.printf("SPIFFS Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
file.close();
|
||||
}
|
||||
#else
|
||||
#ifdef ESP8266
|
||||
//RTC only for ESP8266
|
||||
ESP.rtcUserMemoryRead(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
doubleResetDetected = (doubleResetDetectorFlag == DOUBLERESETDETECTOR_FLAG_SET);
|
||||
return doubleResetDetected;
|
||||
};
|
||||
|
||||
void setRecentlyResetFlag()
|
||||
{
|
||||
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG_SET;
|
||||
|
||||
DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_SET;
|
||||
|
||||
#if (ESP_DRD_USE_EEPROM)
|
||||
EEPROM.put(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
|
||||
EEPROM.commit();
|
||||
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
delay(1000);
|
||||
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
|
||||
|
||||
Serial.printf("SetFlag write = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
|
||||
#endif
|
||||
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
|
||||
// LittleFS / SPIFFS code
|
||||
File file = FileFS.open(DRD_FILENAME, "w");
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file...");
|
||||
#endif
|
||||
|
||||
if (file)
|
||||
{
|
||||
file.write((uint8_t *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
|
||||
file.close();
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file OK");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file failed");
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#ifdef ESP8266
|
||||
//RTC only for ESP8266
|
||||
ESP.rtcUserMemoryWrite(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
void clearRecentlyResetFlag()
|
||||
{
|
||||
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG_CLEAR;
|
||||
DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_CLEAR;
|
||||
|
||||
#if (ESP_DRD_USE_EEPROM)
|
||||
//DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_CLEAR;
|
||||
EEPROM.put(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
|
||||
EEPROM.commit();
|
||||
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
delay(1000);
|
||||
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
|
||||
|
||||
Serial.printf("ClearFlag write = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
|
||||
#endif
|
||||
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
|
||||
// LittleFS / SPIFFS code
|
||||
File file = FileFS.open(DRD_FILENAME, "w");
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file...");
|
||||
#endif
|
||||
|
||||
if (file)
|
||||
{
|
||||
file.write((uint8_t *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
|
||||
file.close();
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file OK");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
Serial.println("Saving config file failed");
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
#ifdef ESP8266
|
||||
//RTC only for ESP8266
|
||||
ESP.rtcUserMemoryWrite(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
uint32_t doubleResetDetectorFlag;
|
||||
};
|
||||
#endif // ESP_DoubleResetDetector_H
|
2122
lib/ESP_WiFiManager/ESP_WiFiManager-Impl.h
Normal file
728
lib/ESP_WiFiManager/ESP_WiFiManager.h
Normal file
127
lib/ESP_WiFiManager/ESP_WiFiManager_Debug.h
Normal file
@ -0,0 +1,127 @@
|
||||
/****************************************************************************************************************************
|
||||
ESP_WiFiManager_Debug.h
|
||||
For ESP8266 / ESP32 boards
|
||||
|
||||
ESP_WiFiManager is a library for the ESP8266/Arduino platform
|
||||
(https://github.com/esp8266/Arduino) to enable easy
|
||||
configuration and reconfiguration of WiFi credentials using a Captive Portal
|
||||
inspired by:
|
||||
http://www.esp8266.com/viewtopic.php?f=29&t=2520
|
||||
https://github.com/chriscook8/esp-arduino-apboot
|
||||
https://github.com/esp8266/Arduino/blob/master/libraries/DNSServer/examples/CaptivePortalAdvanced/
|
||||
|
||||
Modified from Tzapu https://github.com/tzapu/WiFiManager
|
||||
and from Ken Taylor https://github.com/kentaylor
|
||||
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.8.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
1.0.0 K Hoang 07/10/2019 Initial coding
|
||||
1.0.1 K Hoang 13/12/2019 Fix bug. Add features. Add support for ESP32
|
||||
1.0.2 K Hoang 19/12/2019 Fix bug thatkeeps ConfigPortal in endless loop if Portal/Router SSID or Password is NULL.
|
||||
1.0.3 K Hoang 05/01/2020 Option not displaying AvailablePages in Info page. Enhance README.md. Modify examples
|
||||
1.0.4 K Hoang 07/01/2020 Add RFC952 setHostname feature.
|
||||
1.0.5 K Hoang 15/01/2020 Add configurable DNS feature. Thanks to @Amorphous of https://community.blynk.cc
|
||||
1.0.6 K Hoang 03/02/2020 Add support for ArduinoJson version 6.0.0+ ( tested with v6.14.1 )
|
||||
1.0.7 K Hoang 13/04/2020 Reduce start time, fix SPIFFS bug in examples, update README.md
|
||||
1.0.8 K Hoang 10/06/2020 Fix STAstaticIP issue. Restructure code. Add LittleFS support for ESP8266 core 2.7.1+
|
||||
1.0.9 K Hoang 29/07/2020 Fix ESP32 STAstaticIP bug. Permit changing from DHCP <-> static IP using Config Portal.
|
||||
Add, enhance examples (fix MDNS for ESP32)
|
||||
1.0.10 K Hoang 08/08/2020 Add more features to Config Portal. Use random WiFi AP channel to avoid conflict.
|
||||
1.0.11 K Hoang 17/08/2020 Add CORS feature. Fix bug in softAP, autoConnect, resetSettings.
|
||||
1.1.0 K Hoang 28/08/2020 Add MultiWiFi feature to autoconnect to best WiFi at runtime
|
||||
1.1.1 K Hoang 30/08/2020 Add setCORSHeader function to allow flexible CORS. Fix typo and minor improvement.
|
||||
1.1.2 K Hoang 17/08/2020 Fix bug. Add example.
|
||||
1.2.0 K Hoang 09/10/2020 Restore cpp code besides Impl.h code to use if linker error. Fix bug.
|
||||
1.3.0 K Hoang 04/12/2020 Add LittleFS support to ESP32 using LITTLEFS Library
|
||||
1.4.1 K Hoang 22/12/2020 Fix staticIP not saved. Add functions. Add complex examples. Sync with ESPAsync_WiFiManager
|
||||
1.4.2 K Hoang 14/01/2021 Fix examples' bug not using saved WiFi Credentials after losing all WiFi connections.
|
||||
1.4.3 K Hoang 23/01/2021 Fix examples' bug not saving Static IP in certain cases.
|
||||
1.5.0 K Hoang 12/02/2021 Add support to new ESP32-S2
|
||||
1.5.1 K Hoang 26/03/2021 Fix compiler error if setting Compiler Warnings to All. Retest with esp32 core v1.0.6
|
||||
1.5.2 K Hoang 08/04/2021 Fix example misleading messages.
|
||||
1.5.3 K Hoang 13/04/2021 Add dnsServer error message.
|
||||
1.6.0 K Hoang 20/04/2021 Add support to new ESP32-C3 using SPIFFS or EEPROM
|
||||
1.6.1 K Hoang 25/04/2021 Fix MultiWiFi bug. Fix captive-portal bug if CP AP address is not default 192.168.4.1
|
||||
1.7.0 K Hoang 06/05/2021 Set _timezoneName. Add support to new ESP32-S2 (METRO_ESP32S2, FUNHOUSE_ESP32S2, etc.)
|
||||
1.7.1 K Hoang 08/05/2021 Fix Json bug. Fix timezoneName not displayed in Info page.
|
||||
1.7.2 K Hoang 08/05/2021 Fix warnings with ESP8266 core v3.0.0
|
||||
1.7.3 K Hoang 29/07/2021 Fix MultiWiFi connection issue with ESP32 core v2.0.0-rc1+
|
||||
1.7.4 K Hoang 13/08/2021 Add WiFi scanning of hidden SSIDs
|
||||
1.7.5 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
|
||||
1.7.6 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
|
||||
1.7.7 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP_WiFiManager_Debug_H
|
||||
#define ESP_WiFiManager_Debug_H
|
||||
|
||||
#ifdef WIFIMGR_DEBUG_PORT
|
||||
#define WM_DBG_PORT WIFIMGR_DEBUG_PORT
|
||||
#else
|
||||
#define WM_DBG_PORT Serial
|
||||
#endif
|
||||
|
||||
// Change _WIFIMGR_LOGLEVEL_ to set tracing and logging verbosity
|
||||
// 0: DISABLED: no logging
|
||||
// 1: ERROR: errors
|
||||
// 2: WARN: errors and warnings
|
||||
// 3: INFO: errors, warnings and informational (default)
|
||||
// 4: DEBUG: errors, warnings, informational and debug
|
||||
|
||||
#ifndef _WIFIMGR_LOGLEVEL_
|
||||
#define _WIFIMGR_LOGLEVEL_ 0
|
||||
#endif
|
||||
|
||||
const char WM_MARK[] = "[WM] ";
|
||||
const char WM_SP[] = " ";
|
||||
|
||||
#define WM_PRINT WM_DBG_PORT.print
|
||||
#define WM_PRINTLN WM_DBG_PORT.println
|
||||
|
||||
#define WM_PRINT_MARK WM_PRINT(WM_MARK)
|
||||
#define WM_PRINT_SP WM_PRINT(WM_SP)
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#define LOGERROR(x) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINTLN(x); }
|
||||
#define LOGERROR0(x) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT(x); }
|
||||
#define LOGERROR1(x,y) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
|
||||
#define LOGERROR2(x,y,z) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
|
||||
#define LOGERROR3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#define LOGWARN(x) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINTLN(x); }
|
||||
#define LOGWARN0(x) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT(x); }
|
||||
#define LOGWARN1(x,y) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
|
||||
#define LOGWARN2(x,y,z) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
|
||||
#define LOGWARN3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#define LOGINFO(x) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINTLN(x); }
|
||||
#define LOGINFO0(x) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT(x); }
|
||||
#define LOGINFO1(x,y) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
|
||||
#define LOGINFO2(x,y,z) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
|
||||
#define LOGINFO3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#define LOGDEBUG(x) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINTLN(x); }
|
||||
#define LOGDEBUG0(x) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT(x); }
|
||||
#define LOGDEBUG1(x,y) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
|
||||
#define LOGDEBUG2(x,y,z) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
|
||||
#define LOGDEBUG3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#endif //ESP_WiFiManager_Debug_H
|
2150
lib/ESP_WiFiManager/utils/TZ.h
Normal file
588
lib/OneWire/OneWire.cpp
Normal file
@ -0,0 +1,588 @@
|
||||
/*
|
||||
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
|
||||
|
||||
The latest version of this library may be found at:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
|
||||
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
|
||||
January 2010.
|
||||
|
||||
DO NOT EMAIL for technical support, especially not for ESP chips!
|
||||
All project support questions must be posted on public forums
|
||||
relevant to the board or chips used. If using Arduino, post on
|
||||
Arduino's forum. If using ESP, post on the ESP community forums.
|
||||
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
|
||||
|
||||
Github's issue tracker for OneWire should be used only to report
|
||||
specific bugs. DO NOT request project support via Github. All
|
||||
project and tech support questions must be posted on forums, not
|
||||
github issues. If you experience a problem and you are not
|
||||
absolutely sure it's an issue with the library, ask on a forum
|
||||
first. Only use github to report issues after experts have
|
||||
confirmed the issue is with OneWire rather than your project.
|
||||
|
||||
Back in 2010, OneWire was in need of many bug fixes, but had
|
||||
been abandoned the original author (Jim Studt). None of the known
|
||||
contributors were interested in maintaining OneWire. Paul typically
|
||||
works on OneWire every 6 to 12 months. Patches usually wait that
|
||||
long. If anyone is interested in more actively maintaining OneWire,
|
||||
please contact Paul (this is pretty much the only reason to use
|
||||
private email about OneWire).
|
||||
|
||||
OneWire is now very mature code. No changes other than adding
|
||||
definitions for newer hardware support are anticipated.
|
||||
|
||||
Version 2.3:
|
||||
Unknown chip fallback mode, Roger Clark
|
||||
Teensy-LC compatibility, Paul Stoffregen
|
||||
Search bug fix, Love Nystrom
|
||||
|
||||
Version 2.2:
|
||||
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
|
||||
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
|
||||
Fix DS18B20 example negative temperature
|
||||
Fix DS18B20 example's low res modes, Ken Butcher
|
||||
Improve reset timing, Mark Tillotson
|
||||
Add const qualifiers, Bertrik Sikken
|
||||
Add initial value input to crc16, Bertrik Sikken
|
||||
Add target_search() function, Scott Roberts
|
||||
|
||||
Version 2.1:
|
||||
Arduino 1.0 compatibility, Paul Stoffregen
|
||||
Improve temperature example, Paul Stoffregen
|
||||
DS250x_PROM example, Guillermo Lovato
|
||||
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
|
||||
Improvements from Glenn Trewitt:
|
||||
- crc16() now works
|
||||
- check_crc16() does all of calculation/checking work.
|
||||
- Added read_bytes() and write_bytes(), to reduce tedious loops.
|
||||
- Added ds2408 example.
|
||||
Delete very old, out-of-date readme file (info is here)
|
||||
|
||||
Version 2.0: Modifications by Paul Stoffregen, January 2010:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
Search fix from Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
Use direct optimized I/O in all cases
|
||||
Disable interrupts during timing critical sections
|
||||
(this solves many random communication errors)
|
||||
Disable interrupts during read-modify-write I/O
|
||||
Reduce RAM consumption by eliminating unnecessary
|
||||
variables and trimming many to 8 bits
|
||||
Optimize both crc8 - table version moved to flash
|
||||
|
||||
Modified to work with larger numbers of devices - avoids loop.
|
||||
Tested in Arduino 11 alpha with 12 sensors.
|
||||
26 Sept 2008 -- Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
|
||||
Updated to work with arduino-0008 and to include skip() as of
|
||||
2007/07/06. --RJL20
|
||||
|
||||
Modified to calculate the 8-bit CRC directly, avoiding the need for
|
||||
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
|
||||
-- Tom Pollard, Jan 23, 2008
|
||||
|
||||
Jim Studt's original library was modified by Josh Larios.
|
||||
|
||||
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Much of the code was inspired by Derek Yerger's code, though I don't
|
||||
think much of that remains. In any event that was..
|
||||
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
|
||||
|
||||
The CRC code was excerpted and inspired by the Dallas Semiconductor
|
||||
sample code bearing this copyright.
|
||||
//---------------------------------------------------------------------------
|
||||
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// Except as contained in this notice, the name of Dallas Semiconductor
|
||||
// shall not be used except as stated in the Dallas Semiconductor
|
||||
// Branding Policy.
|
||||
//--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "OneWire.h"
|
||||
#include "util/OneWire_direct_gpio.h"
|
||||
|
||||
|
||||
void OneWire::begin(uint8_t pin)
|
||||
{
|
||||
pinMode(pin, INPUT);
|
||||
bitmask = PIN_TO_BITMASK(pin);
|
||||
baseReg = PIN_TO_BASEREG(pin);
|
||||
#if ONEWIRE_SEARCH
|
||||
reset_search();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Perform the onewire reset function. We will wait up to 250uS for
|
||||
// the bus to come high, if it doesn't then it is broken or shorted
|
||||
// and we return a 0;
|
||||
//
|
||||
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
|
||||
//
|
||||
uint8_t OneWire::reset(void)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
uint8_t retries = 125;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask);
|
||||
interrupts();
|
||||
// wait until the wire is high... just in case
|
||||
do {
|
||||
if (--retries == 0) return 0;
|
||||
delayMicroseconds(2);
|
||||
} while ( !DIRECT_READ(reg, mask));
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
interrupts();
|
||||
delayMicroseconds(480);
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask); // allow it to float
|
||||
delayMicroseconds(70);
|
||||
r = !DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
void IRAM_ATTR OneWire::write_bit(uint8_t v)
|
||||
#else
|
||||
void OneWire::write_bit(uint8_t v)
|
||||
#endif
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
|
||||
if (v & 1) {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(10);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(55);
|
||||
} else {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(65);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
uint8_t IRAM_ATTR OneWire::read_bit(void)
|
||||
#else
|
||||
uint8_t OneWire::read_bit(void)
|
||||
#endif
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_OUTPUT(reg, mask);
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
delayMicroseconds(3);
|
||||
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
|
||||
delayMicroseconds(10);
|
||||
r = DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(53);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a byte. The writing code uses the active drivers to raise the
|
||||
// pin high, if you need power after the write (e.g. DS18S20 in
|
||||
// parasite power mode) then set 'power' to 1, otherwise the pin will
|
||||
// go tri-state at the end of the write to avoid heating in a short or
|
||||
// other mishap.
|
||||
//
|
||||
void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
|
||||
uint8_t bitMask;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
OneWire::write_bit( (bitMask & v)?1:0);
|
||||
}
|
||||
if ( !power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
write(buf[i]);
|
||||
if (!power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a byte
|
||||
//
|
||||
uint8_t OneWire::read() {
|
||||
uint8_t bitMask;
|
||||
uint8_t r = 0;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
if ( OneWire::read_bit()) r |= bitMask;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
buf[i] = read();
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM select
|
||||
//
|
||||
void OneWire::select(const uint8_t rom[8])
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
write(0x55); // Choose ROM
|
||||
|
||||
for (i = 0; i < 8; i++) write(rom[i]);
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM skip
|
||||
//
|
||||
void OneWire::skip()
|
||||
{
|
||||
write(0xCC); // Skip ROM
|
||||
}
|
||||
|
||||
void OneWire::depower()
|
||||
{
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
|
||||
//
|
||||
// You need to use this function to start a search again from the beginning.
|
||||
// You do not need to do it for the first search, though you could.
|
||||
//
|
||||
void OneWire::reset_search()
|
||||
{
|
||||
// reset the search state
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
for(int i = 7; ; i--) {
|
||||
ROM_NO[i] = 0;
|
||||
if ( i == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
//
|
||||
void OneWire::target_search(uint8_t family_code)
|
||||
{
|
||||
// set the search state to find SearchFamily type devices
|
||||
ROM_NO[0] = family_code;
|
||||
for (uint8_t i = 1; i < 8; i++)
|
||||
ROM_NO[i] = 0;
|
||||
LastDiscrepancy = 64;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Perform a search. If this function returns a '1' then it has
|
||||
// enumerated the next device and you may retrieve the ROM from the
|
||||
// OneWire::address variable. If there are no devices, no further
|
||||
// devices, or something horrible happens in the middle of the
|
||||
// enumeration then a 0 is returned. If a new device is found then
|
||||
// its address is copied to newAddr. Use OneWire::reset_search() to
|
||||
// start over.
|
||||
//
|
||||
// --- Replaced by the one from the Dallas Semiconductor web site ---
|
||||
//--------------------------------------------------------------------------
|
||||
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
|
||||
// search state.
|
||||
// Return TRUE : device found, ROM number in ROM_NO buffer
|
||||
// FALSE : device not found, end of search
|
||||
//
|
||||
bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
|
||||
{
|
||||
uint8_t id_bit_number;
|
||||
uint8_t last_zero, rom_byte_number;
|
||||
bool search_result;
|
||||
uint8_t id_bit, cmp_id_bit;
|
||||
|
||||
unsigned char rom_byte_mask, search_direction;
|
||||
|
||||
// initialize for search
|
||||
id_bit_number = 1;
|
||||
last_zero = 0;
|
||||
rom_byte_number = 0;
|
||||
rom_byte_mask = 1;
|
||||
search_result = false;
|
||||
|
||||
// if the last call was not the last one
|
||||
if (!LastDeviceFlag) {
|
||||
// 1-Wire reset
|
||||
if (!reset()) {
|
||||
// reset the search
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// issue the search command
|
||||
if (search_mode == true) {
|
||||
write(0xF0); // NORMAL SEARCH
|
||||
} else {
|
||||
write(0xEC); // CONDITIONAL SEARCH
|
||||
}
|
||||
|
||||
// loop to do the search
|
||||
do
|
||||
{
|
||||
// read a bit and its complement
|
||||
id_bit = read_bit();
|
||||
cmp_id_bit = read_bit();
|
||||
|
||||
// check for no devices on 1-wire
|
||||
if ((id_bit == 1) && (cmp_id_bit == 1)) {
|
||||
break;
|
||||
} else {
|
||||
// all devices coupled have 0 or 1
|
||||
if (id_bit != cmp_id_bit) {
|
||||
search_direction = id_bit; // bit write value for search
|
||||
} else {
|
||||
// if this discrepancy if before the Last Discrepancy
|
||||
// on a previous next then pick the same as last time
|
||||
if (id_bit_number < LastDiscrepancy) {
|
||||
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
|
||||
} else {
|
||||
// if equal to last pick 1, if not then pick 0
|
||||
search_direction = (id_bit_number == LastDiscrepancy);
|
||||
}
|
||||
// if 0 was picked then record its position in LastZero
|
||||
if (search_direction == 0) {
|
||||
last_zero = id_bit_number;
|
||||
|
||||
// check for Last discrepancy in family
|
||||
if (last_zero < 9)
|
||||
LastFamilyDiscrepancy = last_zero;
|
||||
}
|
||||
}
|
||||
|
||||
// set or clear the bit in the ROM byte rom_byte_number
|
||||
// with mask rom_byte_mask
|
||||
if (search_direction == 1)
|
||||
ROM_NO[rom_byte_number] |= rom_byte_mask;
|
||||
else
|
||||
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
|
||||
|
||||
// serial number search direction write bit
|
||||
write_bit(search_direction);
|
||||
|
||||
// increment the byte counter id_bit_number
|
||||
// and shift the mask rom_byte_mask
|
||||
id_bit_number++;
|
||||
rom_byte_mask <<= 1;
|
||||
|
||||
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
|
||||
if (rom_byte_mask == 0) {
|
||||
rom_byte_number++;
|
||||
rom_byte_mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
|
||||
|
||||
// if the search was successful then
|
||||
if (!(id_bit_number < 65)) {
|
||||
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
|
||||
LastDiscrepancy = last_zero;
|
||||
|
||||
// check for last device
|
||||
if (LastDiscrepancy == 0) {
|
||||
LastDeviceFlag = true;
|
||||
}
|
||||
search_result = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no device found then reset counters so next 'search' will be like a first
|
||||
if (!search_result || !ROM_NO[0]) {
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
search_result = false;
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
|
||||
}
|
||||
return search_result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
|
||||
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
|
||||
//
|
||||
|
||||
#if ONEWIRE_CRC8_TABLE
|
||||
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||
static const uint8_t PROGMEM dscrc2x16_table[] = {
|
||||
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83,
|
||||
0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
|
||||
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74
|
||||
};
|
||||
|
||||
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
|
||||
// and the registers. (Use tiny 2x16 entry CRC table)
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
crc = *addr++ ^ crc; // just re-using crc as intermediate
|
||||
crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^
|
||||
pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f));
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
#else
|
||||
//
|
||||
// Compute a Dallas Semiconductor 8 bit CRC directly.
|
||||
// this is much slower, but a little smaller, than the lookup table.
|
||||
//
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
#if defined(__AVR__)
|
||||
crc = _crc_ibutton_update(crc, *addr++);
|
||||
#else
|
||||
uint8_t inbyte = *addr++;
|
||||
for (uint8_t i = 8; i; i--) {
|
||||
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix) crc ^= 0x8C;
|
||||
inbyte >>= 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
|
||||
{
|
||||
crc = ~crc16(input, len, crc);
|
||||
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
|
||||
}
|
||||
|
||||
uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||
{
|
||||
#if defined(__AVR__)
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
crc = _crc16_update(crc, input[i]);
|
||||
}
|
||||
#else
|
||||
static const uint8_t oddparity[16] =
|
||||
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
|
||||
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
// Even though we're just copying a byte from the input,
|
||||
// we'll be doing 16-bit computation with it.
|
||||
uint16_t cdata = input[i];
|
||||
cdata = (cdata ^ crc) & 0xff;
|
||||
crc >>= 8;
|
||||
|
||||
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
|
||||
crc ^= 0xC001;
|
||||
|
||||
cdata <<= 6;
|
||||
crc ^= cdata;
|
||||
cdata <<= 1;
|
||||
crc ^= cdata;
|
||||
}
|
||||
#endif
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
190
lib/OneWire/OneWire.h
Normal file
@ -0,0 +1,190 @@
|
||||
#ifndef OneWire_h
|
||||
#define OneWire_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h> // for delayMicroseconds, digitalPinToBitMask, etc
|
||||
#else
|
||||
#include "WProgram.h" // for delayMicroseconds
|
||||
#include "pins_arduino.h" // for digitalPinToBitMask, etc
|
||||
#endif
|
||||
|
||||
// You can exclude certain features from OneWire. In theory, this
|
||||
// might save some space. In practice, the compiler automatically
|
||||
// removes unused code (technically, the linker, using -fdata-sections
|
||||
// and -ffunction-sections when compiling, and Wl,--gc-sections
|
||||
// when linking), so most of these will not result in any code size
|
||||
// reduction. Well, unless you try to use the missing features
|
||||
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
|
||||
// is the exception, because it selects a fast but large algorithm
|
||||
// or a small but slow algorithm.
|
||||
|
||||
// you can exclude onewire_search by defining that to 0
|
||||
#ifndef ONEWIRE_SEARCH
|
||||
#define ONEWIRE_SEARCH 1
|
||||
#endif
|
||||
|
||||
// You can exclude CRC checks altogether by defining this to 0
|
||||
#ifndef ONEWIRE_CRC
|
||||
#define ONEWIRE_CRC 1
|
||||
#endif
|
||||
|
||||
// Select the table-lookup method of computing the 8-bit CRC
|
||||
// by setting this to 1. The lookup table enlarges code size by
|
||||
// about 250 bytes. It does NOT consume RAM (but did in very
|
||||
// old versions of OneWire). If you disable this, a slower
|
||||
// but very compact algorithm is used.
|
||||
#ifndef ONEWIRE_CRC8_TABLE
|
||||
#define ONEWIRE_CRC8_TABLE 1
|
||||
#endif
|
||||
|
||||
// You can allow 16-bit CRC checks by defining this to 1
|
||||
// (Note that ONEWIRE_CRC must also be 1.)
|
||||
#ifndef ONEWIRE_CRC16
|
||||
#define ONEWIRE_CRC16 1
|
||||
#endif
|
||||
|
||||
// Board-specific macros for direct GPIO
|
||||
#include "util/OneWire_direct_regtype.h"
|
||||
|
||||
class OneWire
|
||||
{
|
||||
private:
|
||||
IO_REG_TYPE bitmask;
|
||||
volatile IO_REG_TYPE *baseReg;
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// global search state
|
||||
unsigned char ROM_NO[8];
|
||||
uint8_t LastDiscrepancy;
|
||||
uint8_t LastFamilyDiscrepancy;
|
||||
bool LastDeviceFlag;
|
||||
#endif
|
||||
|
||||
public:
|
||||
OneWire() { }
|
||||
OneWire(uint8_t pin) { begin(pin); }
|
||||
void begin(uint8_t pin);
|
||||
|
||||
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
|
||||
// with a presence pulse. Returns 0 if there is no device or the
|
||||
// bus is shorted or otherwise held low for more than 250uS
|
||||
uint8_t reset(void);
|
||||
|
||||
// Issue a 1-Wire rom select command, you do the reset first.
|
||||
void select(const uint8_t rom[8]);
|
||||
|
||||
// Issue a 1-Wire rom skip command, to address all on bus.
|
||||
void skip(void);
|
||||
|
||||
// Write a byte. If 'power' is one then the wire is held high at
|
||||
// the end for parasitically powered devices. You are responsible
|
||||
// for eventually depowering it by calling depower() or doing
|
||||
// another read or write.
|
||||
void write(uint8_t v, uint8_t power = 0);
|
||||
|
||||
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
|
||||
|
||||
// Read a byte.
|
||||
uint8_t read(void);
|
||||
|
||||
void read_bytes(uint8_t *buf, uint16_t count);
|
||||
|
||||
// Write a bit. The bus is always left powered at the end, see
|
||||
// note in write() about that.
|
||||
#if defined (ARDUINO_ARCH_ESP32)
|
||||
void IRAM_ATTR write_bit(uint8_t v);
|
||||
#else
|
||||
void write_bit(uint8_t v);
|
||||
#endif
|
||||
|
||||
// Read a bit.
|
||||
#if defined (ARDUINO_ARCH_ESP32)
|
||||
uint8_t IRAM_ATTR read_bit(void);
|
||||
#else
|
||||
uint8_t read_bit(void);
|
||||
#endif
|
||||
|
||||
// Stop forcing power onto the bus. You only need to do this if
|
||||
// you used the 'power' flag to write() or used a write_bit() call
|
||||
// and aren't about to do another read or write. You would rather
|
||||
// not leave this powered if you don't have to, just in case
|
||||
// someone shorts your bus.
|
||||
void depower(void);
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// Clear the search state so that if will start from the beginning again.
|
||||
void reset_search();
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
void target_search(uint8_t family_code);
|
||||
|
||||
// Look for the next device. Returns 1 if a new address has been
|
||||
// returned. A zero might mean that the bus is shorted, there are
|
||||
// no devices, or you have already retrieved all of them. It
|
||||
// might be a good idea to check the CRC to make sure you didn't
|
||||
// get garbage. The order is deterministic. You will always get
|
||||
// the same devices in the same order.
|
||||
bool search(uint8_t *newAddr, bool search_mode = true);
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
|
||||
// ROM and scratchpad registers.
|
||||
static uint8_t crc8(const uint8_t *addr, uint8_t len);
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
// Compute the 1-Wire CRC16 and compare it against the received CRC.
|
||||
// Example usage (reading a DS2408):
|
||||
// // Put everything in a buffer so we can compute the CRC easily.
|
||||
// uint8_t buf[13];
|
||||
// buf[0] = 0xF0; // Read PIO Registers
|
||||
// buf[1] = 0x88; // LSB address
|
||||
// buf[2] = 0x00; // MSB address
|
||||
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
|
||||
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
|
||||
// if (!CheckCRC16(buf, 11, &buf[11])) {
|
||||
// // Handle error.
|
||||
// }
|
||||
//
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param inverted_crc - The two CRC16 bytes in the received data.
|
||||
// This should just point into the received data,
|
||||
// *not* at a 16-bit integer.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return True, iff the CRC matches.
|
||||
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
|
||||
|
||||
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
|
||||
// the integrity of data received from many 1-Wire devices. Note that the
|
||||
// CRC computed here is *not* what you'll get from the 1-Wire network,
|
||||
// for two reasons:
|
||||
// 1) The CRC is transmitted bitwise inverted.
|
||||
// 2) Depending on the endian-ness of your processor, the binary
|
||||
// representation of the two-byte return value may have a different
|
||||
// byte order than the two bytes you get from 1-Wire.
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return The CRC16, as defined by Dallas Semiconductor.
|
||||
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
// Prevent this name from leaking into Arduino sketches
|
||||
#ifdef IO_REG_TYPE
|
||||
#undef IO_REG_TYPE
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // OneWire_h
|
463
lib/OneWire/util/OneWire_direct_gpio.h
Normal file
@ -0,0 +1,463 @@
|
||||
#ifndef OneWire_Direct_GPIO_h
|
||||
#define OneWire_Direct_GPIO_h
|
||||
|
||||
// This header should ONLY be included by OneWire.cpp. These defines are
|
||||
// meant to be private, used within OneWire.cpp, but not exposed to Arduino
|
||||
// sketches or other libraries which may include OneWire.h.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O definitions
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR asm("r30")
|
||||
#define IO_REG_MASK_ATTR
|
||||
#if defined(__AVR_ATmega4809__)
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask))
|
||||
#else
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
|
||||
#endif
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (1)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR __attribute__ ((unused))
|
||||
#define DIRECT_READ(base, mask) (*((base)+512))
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask))
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
|
||||
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
|
||||
// If you have trouble with OneWire on Arduino Due, please check the
|
||||
// status of delayMicroseconds() before reporting a bug in OneWire!
|
||||
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
#ifndef pgm_read_byte
|
||||
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
|
||||
#endif
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
// Special note: I depend on the ESP community to maintain these definitions and
|
||||
// submit good pull requests. I can not answer any ESP questions or help you
|
||||
// resolve any problems related to ESP chips. Please do not contact me and please
|
||||
// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
|
||||
// on ESP community forums.
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
|
||||
#define PIN_TO_BITMASK(pin) (1 << pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS
|
||||
#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS
|
||||
#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||
{
|
||||
if ( pin < 32 )
|
||||
return (GPIO.in >> pin) & 0x1;
|
||||
else if ( pin < 40 )
|
||||
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE pin)
|
||||
{
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1tc = ((uint32_t)1 << pin);
|
||||
else if ( pin < 34 )
|
||||
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE pin)
|
||||
{
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1ts = ((uint32_t)1 << pin);
|
||||
else if ( pin < 34 )
|
||||
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE pin)
|
||||
{
|
||||
if ( digitalPinIsValid(pin) )
|
||||
{
|
||||
#if defined(ESP_ARDUINO_VERSION)
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0)
|
||||
int pin_io = rtc_io_number_get((gpio_num_t)pin);
|
||||
uint32_t rtc_reg(rtc_io_desc[pin_io].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].pullup | rtc_io_desc[pin_io].pulldown);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( pin < 32 )
|
||||
GPIO.enable_w1tc = ((uint32_t)1 << pin);
|
||||
else
|
||||
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
|
||||
uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
|
||||
pinFunction |= FUN_IE; // input enable but required for output as well?
|
||||
pinFunction |= ((uint32_t)2 << MCU_SEL_S);
|
||||
|
||||
ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
|
||||
|
||||
GPIO.pin[pin].val = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE pin)
|
||||
{
|
||||
if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs
|
||||
{
|
||||
#if defined(ESP_ARDUINO_VERSION)
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0)
|
||||
int pin_io = rtc_io_number_get((gpio_num_t)pin);
|
||||
uint32_t rtc_reg(rtc_io_desc[pin_io].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_io_desc[pin_io].pullup | rtc_io_desc[pin_io].pulldown);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
{
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
|
||||
ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( pin < 32 )
|
||||
GPIO.enable_w1ts = ((uint32_t)1 << pin);
|
||||
else // already validated to pins <= 33
|
||||
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||
|
||||
uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
|
||||
pinFunction |= FUN_IE; // input enable but required for output as well?
|
||||
pinFunction |= ((uint32_t)2 << MCU_SEL_S);
|
||||
|
||||
ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
|
||||
|
||||
GPIO.pin[pin].val = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
|
||||
// https://github.com/PaulStoffregen/OneWire/pull/47
|
||||
// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c
|
||||
#ifdef interrupts
|
||||
#undef interrupts
|
||||
#endif
|
||||
#ifdef noInterrupts
|
||||
#undef noInterrupts
|
||||
#endif
|
||||
#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||
#define interrupts() portEXIT_CRITICAL(&mux);}
|
||||
//#warning "ESP32 OneWire testing"
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0))
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0))
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT)
|
||||
#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT)
|
||||
#define PORT_OFFSET(port) (PORT_REG_SHFIT * port)
|
||||
#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin)))
|
||||
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG)
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
|
||||
#include "scss_registers.h"
|
||||
#include "portable.h"
|
||||
#include "avr/pgmspace.h"
|
||||
|
||||
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
|
||||
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
|
||||
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
|
||||
#define DIR_OFFSET_SS 0x01
|
||||
#define DIR_OFFSET_SOC 0x04
|
||||
#define EXT_PORT_OFFSET_SS 0x0A
|
||||
#define EXT_PORT_OFFSET_SOC 0x50
|
||||
|
||||
/* GPIO registers base address */
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
|
||||
#define PIN_TO_BITMASK(pin) pin
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
IO_REG_TYPE ret;
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
||||
} else {
|
||||
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
|
||||
}
|
||||
return ((ret >> GPIO_ID(pin)) & 0x01);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(base, pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
|
||||
|
||||
#elif defined(__riscv)
|
||||
|
||||
/*
|
||||
* Tested on highfive1
|
||||
*
|
||||
* Stable results are achieved operating in the
|
||||
* two high speed modes of the highfive1. It
|
||||
* seems to be less reliable in slow mode.
|
||||
*/
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE mask)
|
||||
{
|
||||
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) |= mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) &= ~mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, mask) directRead(mask)
|
||||
#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
|
||||
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||
|
||||
#else
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE unsigned int
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
55
lib/OneWire/util/OneWire_direct_regtype.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef OneWire_Direct_RegType_h
|
||||
#define OneWire_Direct_RegType_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O register type
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__riscv)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#else
|
||||
#define IO_REG_TYPE unsigned int
|
||||
|
||||
#endif
|
||||
#endif
|
@ -3,6 +3,9 @@
|
||||
// 2013-06-05 by Jeff Rowberg <jeff@rowberg.net>
|
||||
//
|
||||
// Changelog:
|
||||
// 2021-09-28 - allow custom Wire object as transaction function argument
|
||||
// 2020-01-20 - hardija : complete support for Teensy 3.x
|
||||
// 2015-10-30 - simondlevy : support i2c_t3 for Teensy3.1
|
||||
// 2013-05-06 - add Francesco Ferrara's Fastwire v0.24 implementation with small modifications
|
||||
// 2013-05-05 - fix issue with writing bit values to words (Sasquatch/Farzanegan)
|
||||
// 2012-06-09 - fix major issue with reading > 32 bytes at a time with Arduino Wire
|
||||
@ -87,11 +90,6 @@ THE SOFTWARE.
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef BUFFER_LENGTH
|
||||
// band-aid fix for platforms without Wire-defined BUFFER_LENGTH (removed from some official implementations)
|
||||
#define BUFFER_LENGTH 32
|
||||
#endif
|
||||
|
||||
/** Default constructor.
|
||||
*/
|
||||
I2Cdev::I2Cdev() {
|
||||
@ -105,9 +103,9 @@ I2Cdev::I2Cdev() {
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (true = success)
|
||||
*/
|
||||
int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout, void *wireObj) {
|
||||
uint8_t b;
|
||||
uint8_t count = readByte(devAddr, regAddr, &b, timeout);
|
||||
uint8_t count = readByte(devAddr, regAddr, &b, timeout, wireObj);
|
||||
*data = b & (1 << bitNum);
|
||||
return count;
|
||||
}
|
||||
@ -120,9 +118,9 @@ int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (true = success)
|
||||
*/
|
||||
int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout, void *wireObj) {
|
||||
uint16_t b;
|
||||
uint8_t count = readWord(devAddr, regAddr, &b, timeout);
|
||||
uint8_t count = readWord(devAddr, regAddr, &b, timeout, wireObj);
|
||||
*data = b & (1 << bitNum);
|
||||
return count;
|
||||
}
|
||||
@ -136,14 +134,14 @@ int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (true = success)
|
||||
*/
|
||||
int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout, void *wireObj) {
|
||||
// 01101001 read byte
|
||||
// 76543210 bit numbers
|
||||
// xxx args: bitStart=4, length=3
|
||||
// 010 masked
|
||||
// -> 010 shifted
|
||||
uint8_t count, b;
|
||||
if ((count = readByte(devAddr, regAddr, &b, timeout)) != 0) {
|
||||
if ((count = readByte(devAddr, regAddr, &b, timeout, wireObj)) != 0) {
|
||||
uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
|
||||
b &= mask;
|
||||
b >>= (bitStart - length + 1);
|
||||
@ -161,7 +159,7 @@ int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (1 = success, 0 = failure, -1 = timeout)
|
||||
*/
|
||||
int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout, void *wireObj) {
|
||||
// 1101011001101001 read byte
|
||||
// fedcba9876543210 bit numbers
|
||||
// xxx args: bitStart=12, length=3
|
||||
@ -169,7 +167,7 @@ int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uin
|
||||
// -> 010 shifted
|
||||
uint8_t count;
|
||||
uint16_t w;
|
||||
if ((count = readWord(devAddr, regAddr, &w, timeout)) != 0) {
|
||||
if ((count = readWord(devAddr, regAddr, &w, timeout, wireObj)) != 0) {
|
||||
uint16_t mask = ((1 << length) - 1) << (bitStart - length + 1);
|
||||
w &= mask;
|
||||
w >>= (bitStart - length + 1);
|
||||
@ -185,8 +183,8 @@ int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uin
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (true = success)
|
||||
*/
|
||||
int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout) {
|
||||
return readBytes(devAddr, regAddr, 1, data, timeout);
|
||||
int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout, void *wireObj) {
|
||||
return readBytes(devAddr, regAddr, 1, data, timeout, wireObj);
|
||||
}
|
||||
|
||||
/** Read single word from a 16-bit device register.
|
||||
@ -196,8 +194,8 @@ int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Status of read operation (true = success)
|
||||
*/
|
||||
int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout) {
|
||||
return readWords(devAddr, regAddr, 1, data, timeout);
|
||||
int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout, void *wireObj) {
|
||||
return readWords(devAddr, regAddr, 1, data, timeout, wireObj);
|
||||
}
|
||||
|
||||
/** Read multiple bytes from an 8-bit device register.
|
||||
@ -208,7 +206,7 @@ int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Number of bytes read (-1 indicates failure)
|
||||
*/
|
||||
int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout, void *wireObj) {
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print("I2C (0x");
|
||||
Serial.print(devAddr, HEX);
|
||||
@ -223,70 +221,72 @@ int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8
|
||||
uint32_t t1 = millis();
|
||||
|
||||
#if (I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
TwoWire *useWire = &Wire;
|
||||
if (wireObj) useWire = (TwoWire *)wireObj;
|
||||
|
||||
#if (ARDUINO < 100)
|
||||
// Arduino v00xx (before v1.0), Wire library
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.send(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
|
||||
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->send(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
|
||||
|
||||
for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = Wire.receive();
|
||||
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = useWire->receive();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
#endif
|
||||
}
|
||||
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
}
|
||||
#elif (ARDUINO == 100)
|
||||
// Arduino v1.0.0, Wire library
|
||||
// Adds standardized write() and read() stream methods instead of send() and receive()
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
|
||||
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
|
||||
|
||||
for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = Wire.read();
|
||||
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = useWire->read();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
#endif
|
||||
}
|
||||
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
}
|
||||
#elif (ARDUINO > 100)
|
||||
// Arduino v1.0.1+, Wire library
|
||||
// Adds official support for repeated start condition, yay!
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
|
||||
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
|
||||
|
||||
for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = Wire.read();
|
||||
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
data[count] = useWire->read();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
@ -328,7 +328,7 @@ int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8
|
||||
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
|
||||
* @return Number of words read (-1 indicates failure)
|
||||
*/
|
||||
int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout) {
|
||||
int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout, void *wireObj) {
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print("I2C (0x");
|
||||
Serial.print(devAddr, HEX);
|
||||
@ -343,28 +343,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
|
||||
uint32_t t1 = millis();
|
||||
|
||||
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
|
||||
TwoWire *useWire = &Wire;
|
||||
if (wireObj) useWire = (TwoWire *)wireObj;
|
||||
|
||||
#if (ARDUINO < 100)
|
||||
// Arduino v00xx (before v1.0), Wire library
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.send(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->send(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
|
||||
bool msb = true; // starts with MSB, then LSB
|
||||
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
if (msb) {
|
||||
// first byte is bits 15-8 (MSb=15)
|
||||
data[count] = Wire.receive() << 8;
|
||||
data[count] = useWire->receive() << 8;
|
||||
} else {
|
||||
// second byte is bits 7-0 (LSb=0)
|
||||
data[count] |= Wire.receive();
|
||||
data[count] |= useWire->receive();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
@ -374,30 +376,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
|
||||
msb = !msb;
|
||||
}
|
||||
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
}
|
||||
#elif (ARDUINO == 100)
|
||||
// Arduino v1.0.0, Wire library
|
||||
// Adds standardized write() and read() stream methods instead of send() and receive()
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
|
||||
bool msb = true; // starts with MSB, then LSB
|
||||
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
if (msb) {
|
||||
// first byte is bits 15-8 (MSb=15)
|
||||
data[count] = Wire.read() << 8;
|
||||
data[count] = useWire->read() << 8;
|
||||
} else {
|
||||
// second byte is bits 7-0 (LSb=0)
|
||||
data[count] |= Wire.read();
|
||||
data[count] |= useWire->read();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
@ -407,30 +409,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
|
||||
msb = !msb;
|
||||
}
|
||||
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
}
|
||||
#elif (ARDUINO > 100)
|
||||
// Arduino v1.0.1+, Wire library
|
||||
// Adds official support for repeated start condition, yay!
|
||||
|
||||
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
|
||||
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
|
||||
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
|
||||
// smaller chunks instead of all at once
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write(regAddr);
|
||||
Wire.endTransmission();
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
|
||||
|
||||
bool msb = true; // starts with MSB, then LSB
|
||||
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
|
||||
if (msb) {
|
||||
// first byte is bits 15-8 (MSb=15)
|
||||
data[count] = Wire.read() << 8;
|
||||
data[count] = useWire->read() << 8;
|
||||
} else {
|
||||
// second byte is bits 7-0 (LSb=0)
|
||||
data[count] |= Wire.read();
|
||||
data[count] |= useWire->read();
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print(data[count], HEX);
|
||||
if (count + 1 < length) Serial.print(" ");
|
||||
@ -440,7 +442,7 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
|
||||
msb = !msb;
|
||||
}
|
||||
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -479,11 +481,11 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
|
||||
* @param value New bit value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data) {
|
||||
bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data, void *wireObj) {
|
||||
uint8_t b;
|
||||
readByte(devAddr, regAddr, &b);
|
||||
readByte(devAddr, regAddr, &b, I2Cdev::readTimeout, wireObj);
|
||||
b = (data != 0) ? (b | (1 << bitNum)) : (b & ~(1 << bitNum));
|
||||
return writeByte(devAddr, regAddr, b);
|
||||
return writeByte(devAddr, regAddr, b, wireObj);
|
||||
}
|
||||
|
||||
/** write a single bit in a 16-bit device register.
|
||||
@ -493,11 +495,11 @@ bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t
|
||||
* @param value New bit value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data) {
|
||||
bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data, void *wireObj) {
|
||||
uint16_t w;
|
||||
readWord(devAddr, regAddr, &w);
|
||||
readWord(devAddr, regAddr, &w, I2Cdev::readTimeout, wireObj);
|
||||
w = (data != 0) ? (w | (1 << bitNum)) : (w & ~(1 << bitNum));
|
||||
return writeWord(devAddr, regAddr, w);
|
||||
return writeWord(devAddr, regAddr, w, wireObj);
|
||||
}
|
||||
|
||||
/** Write multiple bits in an 8-bit device register.
|
||||
@ -508,7 +510,7 @@ bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_
|
||||
* @param data Right-aligned value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data) {
|
||||
bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data, void *wireObj) {
|
||||
// 010 value to write
|
||||
// 76543210 bit numbers
|
||||
// xxx args: bitStart=4, length=3
|
||||
@ -517,13 +519,13 @@ bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8
|
||||
// 10100011 original & ~mask
|
||||
// 10101011 masked | value
|
||||
uint8_t b;
|
||||
if (readByte(devAddr, regAddr, &b) != 0) {
|
||||
if (readByte(devAddr, regAddr, &b, I2Cdev::readTimeout, wireObj) != 0) {
|
||||
uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
|
||||
data <<= (bitStart - length + 1); // shift data into correct position
|
||||
data &= mask; // zero all non-important bits in data
|
||||
b &= ~(mask); // zero all important bits in existing byte
|
||||
b |= data; // combine data with existing byte
|
||||
return writeByte(devAddr, regAddr, b);
|
||||
return writeByte(devAddr, regAddr, b, wireObj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -537,7 +539,7 @@ bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8
|
||||
* @param data Right-aligned value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data) {
|
||||
bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data, void *wireObj) {
|
||||
// 010 value to write
|
||||
// fedcba9876543210 bit numbers
|
||||
// xxx args: bitStart=12, length=3
|
||||
@ -546,13 +548,13 @@ bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
|
||||
// 1010001110010110 original & ~mask
|
||||
// 1010101110010110 masked | value
|
||||
uint16_t w;
|
||||
if (readWord(devAddr, regAddr, &w) != 0) {
|
||||
if (readWord(devAddr, regAddr, &w, I2Cdev::readTimeout, wireObj) != 0) {
|
||||
uint16_t mask = ((1 << length) - 1) << (bitStart - length + 1);
|
||||
data <<= (bitStart - length + 1); // shift data into correct position
|
||||
data &= mask; // zero all non-important bits in data
|
||||
w &= ~(mask); // zero all important bits in existing word
|
||||
w |= data; // combine data with existing word
|
||||
return writeWord(devAddr, regAddr, w);
|
||||
return writeWord(devAddr, regAddr, w, wireObj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -564,8 +566,8 @@ bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
|
||||
* @param data New byte value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
|
||||
return writeBytes(devAddr, regAddr, 1, &data);
|
||||
bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data, void *wireObj) {
|
||||
return writeBytes(devAddr, regAddr, 1, &data, wireObj);
|
||||
}
|
||||
|
||||
/** Write single word to a 16-bit device register.
|
||||
@ -574,8 +576,8 @@ bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
|
||||
* @param data New word value to write
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data) {
|
||||
return writeWords(devAddr, regAddr, 1, &data);
|
||||
bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data, void *wireObj) {
|
||||
return writeWords(devAddr, regAddr, 1, &data, wireObj);
|
||||
}
|
||||
|
||||
/** Write multiple bytes to an 8-bit device register.
|
||||
@ -585,7 +587,7 @@ bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data) {
|
||||
* @param data Buffer to copy new data from
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t* data) {
|
||||
bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t* data, void *wireObj) {
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print("I2C (0x");
|
||||
Serial.print(devAddr, HEX);
|
||||
@ -596,14 +598,20 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
|
||||
Serial.print("...");
|
||||
#endif
|
||||
uint8_t status = 0;
|
||||
|
||||
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
|
||||
TwoWire *useWire = &Wire;
|
||||
if (wireObj) useWire = (TwoWire *)wireObj;
|
||||
#endif
|
||||
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.send((uint8_t) regAddr); // send address
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->send((uint8_t) regAddr); // send address
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write((uint8_t) regAddr); // send address
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write((uint8_t) regAddr); // send address
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::beginTransmission(devAddr);
|
||||
Fastwire::write(regAddr);
|
||||
@ -614,21 +622,21 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
|
||||
if (i + 1 < length) Serial.print(" ");
|
||||
#endif
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.send((uint8_t) data[i]);
|
||||
useWire->send((uint8_t) data[i]);
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
Wire.write((uint8_t) data[i]);
|
||||
useWire->write((uint8_t) data[i]);
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::write((uint8_t) data[i]);
|
||||
#endif
|
||||
}
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
status = Wire.endTransmission();
|
||||
status = useWire->endTransmission();
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::stop();
|
||||
//status = Fastwire::endTransmission();
|
||||
@ -646,7 +654,7 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
|
||||
* @param data Buffer to copy new data from
|
||||
* @return Status of operation (true = success)
|
||||
*/
|
||||
bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t* data) {
|
||||
bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t* data, void *wireObj) {
|
||||
#ifdef I2CDEV_SERIAL_DEBUG
|
||||
Serial.print("I2C (0x");
|
||||
Serial.print(devAddr, HEX);
|
||||
@ -657,14 +665,20 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
|
||||
Serial.print("...");
|
||||
#endif
|
||||
uint8_t status = 0;
|
||||
|
||||
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
|
||||
TwoWire *useWire = &Wire;
|
||||
if (wireObj) useWire = (TwoWire *)wireObj;
|
||||
#endif
|
||||
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.send(regAddr); // send address
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->send(regAddr); // send address
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
Wire.beginTransmission(devAddr);
|
||||
Wire.write(regAddr); // send address
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr); // send address
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::beginTransmission(devAddr);
|
||||
Fastwire::write(regAddr);
|
||||
@ -675,13 +689,13 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
|
||||
if (i + 1 < length) Serial.print(" ");
|
||||
#endif
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.send((uint8_t)(data[i] >> 8)); // send MSB
|
||||
Wire.send((uint8_t)data[i]); // send LSB
|
||||
useWire->send((uint8_t)(data[i] >> 8)); // send MSB
|
||||
useWire->send((uint8_t)data[i]); // send LSB
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
Wire.write((uint8_t)(data[i] >> 8)); // send MSB
|
||||
Wire.write((uint8_t)data[i]); // send LSB
|
||||
useWire->write((uint8_t)(data[i] >> 8)); // send MSB
|
||||
useWire->write((uint8_t)data[i]); // send LSB
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::write((uint8_t)(data[i] >> 8)); // send MSB
|
||||
status = Fastwire::write((uint8_t)data[i]); // send LSB
|
||||
@ -689,11 +703,11 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
|
||||
#endif
|
||||
}
|
||||
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
|
||||
Wire.endTransmission();
|
||||
useWire->endTransmission();
|
||||
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|
||||
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|
||||
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
|
||||
status = Wire.endTransmission();
|
||||
status = useWire->endTransmission();
|
||||
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
|
||||
Fastwire::stop();
|
||||
//status = Fastwire::endTransmission();
|
||||
@ -753,7 +767,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
#endif
|
||||
|
||||
TWSR = 0; // no prescaler => prescaler = 1
|
||||
TWBR = ((16000L / khz) - 16) / 2; // change the I2C clock rate
|
||||
TWBR = F_CPU / 2000 / khz - 8; // change the I2C clock rate
|
||||
TWCR = 1 << TWEN; // enable twi module, no interrupt
|
||||
}
|
||||
|
||||
@ -993,7 +1007,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2; // bitrate register
|
||||
// enable twi module, acks, and twi interrupt
|
||||
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA);
|
||||
|
||||
/* TWEN - TWI Enable Bit
|
||||
TWIE - TWI Interrupt Enable
|
||||
@ -1062,7 +1076,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
}
|
||||
|
||||
void twii_SetStart() {
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT) | (1 << TWSTA);
|
||||
}
|
||||
|
||||
void twi_write01() {
|
||||
@ -1145,19 +1159,19 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
void twi_reply(uint8_t ack) {
|
||||
// transmit master read ready signal, with or without ack
|
||||
if (ack){
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWINT) | (1 << TWEA);
|
||||
} else {
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWINT);
|
||||
}
|
||||
}
|
||||
|
||||
void twi_stop(void) {
|
||||
// send stop condition
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT) | (1 << TWSTO);
|
||||
|
||||
// wait for stop condition to be exectued on bus
|
||||
// TWINT is not set after a stop condition!
|
||||
while (TWCR & _BV(TWSTO)) {
|
||||
while (TWCR & (1 << TWSTO)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1167,7 +1181,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
|
||||
void twi_releaseBus(void) {
|
||||
// release bus
|
||||
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
|
||||
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT);
|
||||
|
||||
// update twi state
|
||||
twi_state = TWI_READY;
|
||||
|
@ -3,6 +3,7 @@
|
||||
// 2013-06-05 by Jeff Rowberg <jeff@rowberg.net>
|
||||
//
|
||||
// Changelog:
|
||||
// 2021-09-28 - allow custom Wire object as transaction function argument
|
||||
// 2020-01-20 - hardija : complete support for Teensy 3.x
|
||||
// 2015-10-30 - simondlevy : support i2c_t3 for Teensy3.1
|
||||
// 2013-05-06 - add Francesco Ferrara's Fastwire v0.24 implementation with small modifications
|
||||
@ -48,6 +49,11 @@ THE SOFTWARE.
|
||||
#ifndef _I2CDEV_H_
|
||||
#define _I2CDEV_H_
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Enable deprecated pgmspace typedefs in avr-libc
|
||||
// -----------------------------------------------------------------------------
|
||||
#define __PROG_TYPES_COMPAT__
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// I2C interface implementation setting
|
||||
// -----------------------------------------------------------------------------
|
||||
@ -99,10 +105,26 @@ THE SOFTWARE.
|
||||
#endif
|
||||
|
||||
#ifdef SPARK
|
||||
#include <spark_wiring_i2c.h>
|
||||
#include "application.h"
|
||||
#define ARDUINO 101
|
||||
#define BUFFER_LENGTH 32
|
||||
#endif
|
||||
|
||||
#ifndef I2CDEVLIB_WIRE_BUFFER_LENGTH
|
||||
#if defined(I2C_BUFFER_LENGTH)
|
||||
// Arduino ESP32 core Wire uses this
|
||||
#define I2CDEVLIB_WIRE_BUFFER_LENGTH I2C_BUFFER_LENGTH
|
||||
#elif defined(BUFFER_LENGTH)
|
||||
// Arduino AVR core Wire and many others use this
|
||||
#define I2CDEVLIB_WIRE_BUFFER_LENGTH BUFFER_LENGTH
|
||||
#elif defined(SERIAL_BUFFER_SIZE)
|
||||
// Arduino SAMD core Wire uses this
|
||||
#define I2CDEVLIB_WIRE_BUFFER_LENGTH SERIAL_BUFFER_SIZE
|
||||
#else
|
||||
// should be a safe fallback, though possibly inefficient
|
||||
#define I2CDEVLIB_WIRE_BUFFER_LENGTH 32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 1000ms default read timeout (modify with "I2Cdev::readTimeout = [ms];")
|
||||
#define I2CDEV_DEFAULT_READ_TIMEOUT 1000
|
||||
@ -111,23 +133,23 @@ class I2Cdev {
|
||||
public:
|
||||
I2Cdev();
|
||||
|
||||
static int8_t readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
|
||||
static int8_t readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
static int8_t readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
|
||||
|
||||
static bool writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data);
|
||||
static bool writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data);
|
||||
static bool writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data);
|
||||
static bool writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data);
|
||||
static bool writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data);
|
||||
static bool writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data);
|
||||
static bool writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data);
|
||||
static bool writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data);
|
||||
static bool writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data, void *wireObj=0);
|
||||
static bool writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data, void *wireObj=0);
|
||||
static bool writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data, void *wireObj=0);
|
||||
static bool writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data, void *wireObj=0);
|
||||
static bool writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data, void *wireObj=0);
|
||||
static bool writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data, void *wireObj=0);
|
||||
static bool writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, void *wireObj=0);
|
||||
static bool writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, void *wireObj=0);
|
||||
|
||||
static uint16_t readTimeout;
|
||||
};
|
||||
@ -240,7 +262,7 @@ class I2Cdev {
|
||||
|
||||
/* TWI Status is in TWSR, in the top 5 bits: TWS7 - TWS3 */
|
||||
|
||||
#define TW_STATUS_MASK (_BV(TWS7)|_BV(TWS6)|_BV(TWS5)|_BV(TWS4)|_BV(TWS3))
|
||||
#define TW_STATUS_MASK ((1 << TWS7)|(1 << TWS6)|(1 << TWS5)|(1 << TWS4)|(1 << TWS3))
|
||||
#define TW_STATUS (TWSR & TW_STATUS_MASK)
|
||||
#define TW_START 0x08
|
||||
#define TW_REP_START 0x10
|
||||
@ -275,11 +297,11 @@ class I2Cdev {
|
||||
//#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))
|
||||
|
||||
#ifndef sbi // set bit
|
||||
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||||
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= (1 << bit))
|
||||
#endif // sbi
|
||||
|
||||
#ifndef cbi // clear bit
|
||||
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||||
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~(1 << bit))
|
||||
#endif // cbi
|
||||
|
||||
extern TwoWire Wire;
|
||||
|
@ -4,6 +4,7 @@
|
||||
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
|
||||
//
|
||||
// Changelog:
|
||||
// 2021/09/27 - split implementations out of header files, finally
|
||||
// ... - ongoing debug release
|
||||
|
||||
// NOTE: THIS IS ONLY A PARIAL RELEASE. THIS DEVICE CLASS IS CURRENTLY UNDERGOING ACTIVE
|
||||
@ -38,12 +39,15 @@ THE SOFTWARE.
|
||||
#define _MPU6050_H_
|
||||
|
||||
#include "I2Cdev.h"
|
||||
#include "helper_3dmath.h"
|
||||
|
||||
// supporting link: http://forum.arduino.cc/index.php?&topic=143444.msg1079517#msg1079517
|
||||
// also: http://forum.arduino.cc/index.php?&topic=141571.msg1062899#msg1062899s
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#elif defined(ESP32)
|
||||
#include <pgmspace.h>
|
||||
#else
|
||||
//#define PROGMEM /* empty */
|
||||
//#define pgm_read_byte(x) (*(x))
|
||||
@ -431,11 +435,11 @@ THE SOFTWARE.
|
||||
#define MPU6050_DMP_MEMORY_BANK_SIZE 256
|
||||
#define MPU6050_DMP_MEMORY_CHUNK_SIZE 16
|
||||
|
||||
// note: DMP code memory blocks defined at end of header file
|
||||
#define MPU6050_FIFO_DEFAULT_TIMEOUT 11000
|
||||
|
||||
class MPU6050 {
|
||||
class MPU6050_Base {
|
||||
public:
|
||||
MPU6050(uint8_t address=MPU6050_DEFAULT_ADDRESS);
|
||||
MPU6050_Base(uint8_t address=MPU6050_DEFAULT_ADDRESS, void *wireObj=0);
|
||||
|
||||
void initialize();
|
||||
bool testConnection();
|
||||
@ -717,6 +721,8 @@ class MPU6050 {
|
||||
int8_t GetCurrentFIFOPacket(uint8_t *data, uint8_t length);
|
||||
void setFIFOByte(uint8_t data);
|
||||
void getFIFOBytes(uint8_t *data, uint8_t length);
|
||||
void setFIFOTimeout(uint32_t fifoTimeout);
|
||||
uint32_t getFIFOTimeout();
|
||||
|
||||
// WHO_AM_I register
|
||||
uint8_t getDeviceID();
|
||||
@ -827,215 +833,16 @@ class MPU6050 {
|
||||
void PID(uint8_t ReadAddress, float kP,float kI, uint8_t Loops); // Does the math
|
||||
void PrintActiveOffsets(); // See the results of the Calibration
|
||||
|
||||
|
||||
|
||||
// special methods for MotionApps 2.0 implementation
|
||||
#ifdef MPU6050_INCLUDE_DMP_MOTIONAPPS20
|
||||
|
||||
uint8_t dmpInitialize();
|
||||
bool dmpPacketAvailable();
|
||||
|
||||
uint8_t dmpSetFIFORate(uint8_t fifoRate);
|
||||
uint8_t dmpGetFIFORate();
|
||||
uint8_t dmpGetSampleStepSizeMS();
|
||||
uint8_t dmpGetSampleFrequency();
|
||||
int32_t dmpDecodeTemperature(int8_t tempReg);
|
||||
|
||||
// Register callbacks after a packet of FIFO data is processed
|
||||
//uint8_t dmpRegisterFIFORateProcess(inv_obj_func func, int16_t priority);
|
||||
//uint8_t dmpUnregisterFIFORateProcess(inv_obj_func func);
|
||||
uint8_t dmpRunFIFORateProcesses();
|
||||
|
||||
// Setup FIFO for various output
|
||||
uint8_t dmpSendQuaternion(uint_fast16_t accuracy);
|
||||
uint8_t dmpSendGyro(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendLinearAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendLinearAccelInWorld(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendControlData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendExternalSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendGravity(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendPacketNumber(uint_fast16_t accuracy);
|
||||
uint8_t dmpSendQuantizedAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendEIS(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
|
||||
// Get Fixed Point data from FIFO
|
||||
uint8_t dmpGetAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(Quaternion *q, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(Quaternion *q, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(Quaternion *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpSetLinearAccelFilterCoefficient(float coef);
|
||||
uint8_t dmpGetLinearAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(VectorInt16 *v, VectorInt16 *vRaw, VectorFloat *gravity);
|
||||
uint8_t dmpGetLinearAccelInWorld(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, VectorInt16 *vReal, Quaternion *q);
|
||||
uint8_t dmpGetGyroAndAccelSensor(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroAndAccelSensor(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroAndAccelSensor(VectorInt16 *g, VectorInt16 *a, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetControlData(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetTemperature(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(VectorFloat *v, Quaternion *q);
|
||||
uint8_t dmpGetUnquantizedAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetUnquantizedAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetUnquantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetExternalSensorData(int32_t *data, uint16_t size, const uint8_t* packet=0);
|
||||
uint8_t dmpGetEIS(int32_t *data, const uint8_t* packet=0);
|
||||
|
||||
uint8_t dmpGetEuler(float *data, Quaternion *q);
|
||||
uint8_t dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity);
|
||||
|
||||
// Get Floating Point data from FIFO
|
||||
uint8_t dmpGetAccelFloat(float *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternionFloat(float *data, const uint8_t* packet=0);
|
||||
|
||||
uint8_t dmpProcessFIFOPacket(const unsigned char *dmpData);
|
||||
uint8_t dmpReadAndProcessFIFOPacket(uint8_t numPackets, uint8_t *processed=NULL);
|
||||
|
||||
uint8_t dmpSetFIFOProcessedCallback(void (*func) (void));
|
||||
|
||||
uint8_t dmpInitFIFOParam();
|
||||
uint8_t dmpCloseFIFO();
|
||||
uint8_t dmpSetGyroDataSource(uint8_t source);
|
||||
uint8_t dmpDecodeQuantizedAccel();
|
||||
uint32_t dmpGetGyroSumOfSquare();
|
||||
uint32_t dmpGetAccelSumOfSquare();
|
||||
void dmpOverrideQuaternion(long *q);
|
||||
uint16_t dmpGetFIFOPacketSize();
|
||||
uint8_t dmpGetCurrentFIFOPacket(uint8_t *data); // overflow proof
|
||||
#endif
|
||||
|
||||
// special methods for MotionApps 4.1 implementation
|
||||
#ifdef MPU6050_INCLUDE_DMP_MOTIONAPPS41
|
||||
|
||||
uint8_t dmpInitialize();
|
||||
bool dmpPacketAvailable();
|
||||
|
||||
uint8_t dmpSetFIFORate(uint8_t fifoRate);
|
||||
uint8_t dmpGetFIFORate();
|
||||
uint8_t dmpGetSampleStepSizeMS();
|
||||
uint8_t dmpGetSampleFrequency();
|
||||
int32_t dmpDecodeTemperature(int8_t tempReg);
|
||||
|
||||
// Register callbacks after a packet of FIFO data is processed
|
||||
//uint8_t dmpRegisterFIFORateProcess(inv_obj_func func, int16_t priority);
|
||||
//uint8_t dmpUnregisterFIFORateProcess(inv_obj_func func);
|
||||
uint8_t dmpRunFIFORateProcesses();
|
||||
|
||||
// Setup FIFO for various output
|
||||
uint8_t dmpSendQuaternion(uint_fast16_t accuracy);
|
||||
uint8_t dmpSendGyro(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendLinearAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendLinearAccelInWorld(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendControlData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendExternalSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendGravity(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendPacketNumber(uint_fast16_t accuracy);
|
||||
uint8_t dmpSendQuantizedAccel(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
uint8_t dmpSendEIS(uint_fast16_t elements, uint_fast16_t accuracy);
|
||||
|
||||
// Get Fixed Point data from FIFO
|
||||
uint8_t dmpGetAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternion(Quaternion *q, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGet6AxisQuaternion(Quaternion *q, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetRelativeQuaternion(Quaternion *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyro(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetMag(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpSetLinearAccelFilterCoefficient(float coef);
|
||||
uint8_t dmpGetLinearAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccel(VectorInt16 *v, VectorInt16 *vRaw, VectorFloat *gravity);
|
||||
uint8_t dmpGetLinearAccelInWorld(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, VectorInt16 *vReal, Quaternion *q);
|
||||
uint8_t dmpGetGyroAndAccelSensor(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroAndAccelSensor(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroAndAccelSensor(VectorInt16 *g, VectorInt16 *a, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGyroSensor(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetControlData(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetTemperature(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetGravity(VectorFloat *v, Quaternion *q);
|
||||
uint8_t dmpGetUnquantizedAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetUnquantizedAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetUnquantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(int32_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(int16_t *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
|
||||
uint8_t dmpGetExternalSensorData(int32_t *data, uint16_t size, const uint8_t* packet=0);
|
||||
uint8_t dmpGetEIS(int32_t *data, const uint8_t* packet=0);
|
||||
|
||||
uint8_t dmpGetEuler(float *data, Quaternion *q);
|
||||
uint8_t dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity);
|
||||
|
||||
// Get Floating Point data from FIFO
|
||||
uint8_t dmpGetAccelFloat(float *data, const uint8_t* packet=0);
|
||||
uint8_t dmpGetQuaternionFloat(float *data, const uint8_t* packet=0);
|
||||
|
||||
uint8_t dmpProcessFIFOPacket(const unsigned char *dmpData);
|
||||
uint8_t dmpReadAndProcessFIFOPacket(uint8_t numPackets, uint8_t *processed=NULL);
|
||||
|
||||
uint8_t dmpSetFIFOProcessedCallback(void (*func) (void));
|
||||
|
||||
uint8_t dmpInitFIFOParam();
|
||||
uint8_t dmpCloseFIFO();
|
||||
uint8_t dmpSetGyroDataSource(uint8_t source);
|
||||
uint8_t dmpDecodeQuantizedAccel();
|
||||
uint32_t dmpGetGyroSumOfSquare();
|
||||
uint32_t dmpGetAccelSumOfSquare();
|
||||
void dmpOverrideQuaternion(long *q);
|
||||
uint16_t dmpGetFIFOPacketSize();
|
||||
#endif
|
||||
|
||||
private:
|
||||
protected:
|
||||
uint8_t devAddr;
|
||||
void *wireObj;
|
||||
uint8_t buffer[14];
|
||||
#if defined(MPU6050_INCLUDE_DMP_MOTIONAPPS20) or defined(MPU6050_INCLUDE_DMP_MOTIONAPPS41)
|
||||
uint8_t *dmpPacketBuffer;
|
||||
uint16_t dmpPacketSize;
|
||||
#endif
|
||||
uint32_t fifoTimeout = MPU6050_FIFO_DEFAULT_TIMEOUT;
|
||||
};
|
||||
|
||||
#ifndef I2CDEVLIB_MPU6050_TYPEDEF
|
||||
#define I2CDEVLIB_MPU6050_TYPEDEF
|
||||
typedef MPU6050_Base MPU6050;
|
||||
#endif
|
||||
|
||||
#endif /* _MPU6050_H_ */
|
||||
|
216
lib/mpu6050/helper_3dmath.h
Normal file
@ -0,0 +1,216 @@
|
||||
// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class, 3D math helper
|
||||
// 6/5/2012 by Jeff Rowberg <jeff@rowberg.net>
|
||||
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
|
||||
//
|
||||
// Changelog:
|
||||
// 2012-06-05 - add 3D math helper file to DMP6 example sketch
|
||||
|
||||
/* ============================================
|
||||
I2Cdev device library code is placed under the MIT license
|
||||
Copyright (c) 2012 Jeff Rowberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
===============================================
|
||||
*/
|
||||
|
||||
#ifndef _HELPER_3DMATH_H_
|
||||
#define _HELPER_3DMATH_H_
|
||||
|
||||
class Quaternion {
|
||||
public:
|
||||
float w;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
Quaternion() {
|
||||
w = 1.0f;
|
||||
x = 0.0f;
|
||||
y = 0.0f;
|
||||
z = 0.0f;
|
||||
}
|
||||
|
||||
Quaternion(float nw, float nx, float ny, float nz) {
|
||||
w = nw;
|
||||
x = nx;
|
||||
y = ny;
|
||||
z = nz;
|
||||
}
|
||||
|
||||
Quaternion getProduct(Quaternion q) {
|
||||
// Quaternion multiplication is defined by:
|
||||
// (Q1 * Q2).w = (w1w2 - x1x2 - y1y2 - z1z2)
|
||||
// (Q1 * Q2).x = (w1x2 + x1w2 + y1z2 - z1y2)
|
||||
// (Q1 * Q2).y = (w1y2 - x1z2 + y1w2 + z1x2)
|
||||
// (Q1 * Q2).z = (w1z2 + x1y2 - y1x2 + z1w2
|
||||
return Quaternion(
|
||||
w*q.w - x*q.x - y*q.y - z*q.z, // new w
|
||||
w*q.x + x*q.w + y*q.z - z*q.y, // new x
|
||||
w*q.y - x*q.z + y*q.w + z*q.x, // new y
|
||||
w*q.z + x*q.y - y*q.x + z*q.w); // new z
|
||||
}
|
||||
|
||||
Quaternion getConjugate() {
|
||||
return Quaternion(w, -x, -y, -z);
|
||||
}
|
||||
|
||||
float getMagnitude() {
|
||||
return sqrt(w*w + x*x + y*y + z*z);
|
||||
}
|
||||
|
||||
void normalize() {
|
||||
float m = getMagnitude();
|
||||
w /= m;
|
||||
x /= m;
|
||||
y /= m;
|
||||
z /= m;
|
||||
}
|
||||
|
||||
Quaternion getNormalized() {
|
||||
Quaternion r(w, x, y, z);
|
||||
r.normalize();
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
class VectorInt16 {
|
||||
public:
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
|
||||
VectorInt16() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
VectorInt16(int16_t nx, int16_t ny, int16_t nz) {
|
||||
x = nx;
|
||||
y = ny;
|
||||
z = nz;
|
||||
}
|
||||
|
||||
float getMagnitude() {
|
||||
return sqrt(x*x + y*y + z*z);
|
||||
}
|
||||
|
||||
void normalize() {
|
||||
float m = getMagnitude();
|
||||
x /= m;
|
||||
y /= m;
|
||||
z /= m;
|
||||
}
|
||||
|
||||
VectorInt16 getNormalized() {
|
||||
VectorInt16 r(x, y, z);
|
||||
r.normalize();
|
||||
return r;
|
||||
}
|
||||
|
||||
void rotate(Quaternion *q) {
|
||||
// http://www.cprogramming.com/tutorial/3d/quaternions.html
|
||||
// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/index.htm
|
||||
// http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation
|
||||
// ^ or: http://webcache.googleusercontent.com/search?q=cache:xgJAp3bDNhQJ:content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation&hl=en&gl=us&strip=1
|
||||
|
||||
// P_out = q * P_in * conj(q)
|
||||
// - P_out is the output vector
|
||||
// - q is the orientation quaternion
|
||||
// - P_in is the input vector (a*aReal)
|
||||
// - conj(q) is the conjugate of the orientation quaternion (q=[w,x,y,z], q*=[w,-x,-y,-z])
|
||||
Quaternion p(0, x, y, z);
|
||||
|
||||
// quaternion multiplication: q * p, stored back in p
|
||||
p = q -> getProduct(p);
|
||||
|
||||
// quaternion multiplication: p * conj(q), stored back in p
|
||||
p = p.getProduct(q -> getConjugate());
|
||||
|
||||
// p quaternion is now [0, x', y', z']
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
z = p.z;
|
||||
}
|
||||
|
||||
VectorInt16 getRotated(Quaternion *q) {
|
||||
VectorInt16 r(x, y, z);
|
||||
r.rotate(q);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
class VectorFloat {
|
||||
public:
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
VectorFloat() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
VectorFloat(float nx, float ny, float nz) {
|
||||
x = nx;
|
||||
y = ny;
|
||||
z = nz;
|
||||
}
|
||||
|
||||
float getMagnitude() {
|
||||
return sqrt(x*x + y*y + z*z);
|
||||
}
|
||||
|
||||
void normalize() {
|
||||
float m = getMagnitude();
|
||||
x /= m;
|
||||
y /= m;
|
||||
z /= m;
|
||||
}
|
||||
|
||||
VectorFloat getNormalized() {
|
||||
VectorFloat r(x, y, z);
|
||||
r.normalize();
|
||||
return r;
|
||||
}
|
||||
|
||||
void rotate(Quaternion *q) {
|
||||
Quaternion p(0, x, y, z);
|
||||
|
||||
// quaternion multiplication: q * p, stored back in p
|
||||
p = q -> getProduct(p);
|
||||
|
||||
// quaternion multiplication: p * conj(q), stored back in p
|
||||
p = p.getProduct(q -> getConjugate());
|
||||
|
||||
// p quaternion is now [0, x', y', z']
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
z = p.z;
|
||||
}
|
||||
|
||||
VectorFloat getRotated(Quaternion *q) {
|
||||
VectorFloat r(x, y, z);
|
||||
r.rotate(q);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _HELPER_3DMATH_H_ */
|
@ -15,34 +15,39 @@ include_dir = lib
|
||||
[common_env_data]
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
platform = espressif8266
|
||||
platform = espressif8266 @ 3.2.0
|
||||
framework = arduino
|
||||
board = d1_mini
|
||||
build_unflags =
|
||||
build_flags = #-O0 -Wl,-Map,output.map
|
||||
build_flags =
|
||||
-Wl,-Map,output.map
|
||||
-D BAUD=${common_env_data.monitor_speed}
|
||||
-D ACTIVATE_OTA
|
||||
#-D USE_GYRO_TEMP # If this is enabled the DS18 will not be used, temp is read from the gyro.
|
||||
#-D DEBUG_ESP_HTTP_CLIENT
|
||||
#-D DEBUG_ESP_HTTP_SERVER
|
||||
#-D DEBUG_ESP_PORT=Serial
|
||||
#-D DEBUG_ESP_WIFI
|
||||
#-D DEBUG_ESP_SSL
|
||||
#-D DEBUG_ESP_CORE
|
||||
#-D SKIP_SLEEPMODE
|
||||
#-D DOUBLERESETDETECTOR_DEBUG true
|
||||
-D USE_LITTLEFS=true
|
||||
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
|
||||
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
|
||||
-D USER_SSID=\""\"" # =\""myssid\""
|
||||
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
|
||||
-D CFG_APPVER="\"0.4.0\""
|
||||
lib_deps =
|
||||
# https://github.com/jrowberg/i2cdevlib.git # Using local copy of this library
|
||||
https://github.com/codeplea/tinyexpr
|
||||
https://github.com/graphitemaster/incbin
|
||||
https://github.com/khoih-prog/ESP_DoubleResetDetector
|
||||
https://github.com/tzapu/WiFiManager
|
||||
https://github.com/thijse/Arduino-Log
|
||||
https://github.com/bblanchon/ArduinoJson
|
||||
https://github.com/PaulStoffregen/OneWire
|
||||
https://github.com/milesburton/Arduino-Temperature-Control-Library
|
||||
-D CFG_APPVER="\"0.8.0\""
|
||||
lib_deps = # Switched to forks for better version control.
|
||||
# Using local copy of these libraries
|
||||
#https://github.com/jrowberg/i2cdevlib.git#<document>
|
||||
#https://github.com/khoih-prog/ESP_WiFiManager#<document>
|
||||
#https://github.com/khoih-prog/ESP_DoubleResetDetector#<document>
|
||||
#https://github.com/PaulStoffregen/OneWire
|
||||
#https://github.com/milesburton/Arduino-Temperature-Control-Library
|
||||
https://github.com/mp-se/tinyexpr # https://github.com/codeplea/tinyexpr
|
||||
https://github.com/mp-se/incbin # https://github.com/graphitemaster/incbin
|
||||
https://github.com/mp-se/Arduino-Log#1.1.1 # https://github.com/thijse/Arduino-Log
|
||||
https://github.com/mp-se/ArduinoJson#v6.18.5 # https://github.com/bblanchon/ArduinoJson
|
||||
https://github.com/mp-se/arduinoCurveFitting#v1.0.6 # https://github.com/Rotario/arduinoCurveFitting
|
||||
https://github.com/mp-se/arduino-mqtt#v2.5.0 # https://github.com/256dpi/arduino-mqtt
|
||||
|
||||
[env:gravity-debug]
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
@ -50,18 +55,34 @@ monitor_speed = ${common_env_data.monitor_speed}
|
||||
framework = ${common_env_data.framework}
|
||||
platform = ${common_env_data.platform}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags = ${common_env_data.build_unflags}
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
-D LOG_LEVEL=6 # Maximum log level for the debug build.
|
||||
${common_env_data.build_flags}
|
||||
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
|
||||
#-D SKIP_SLEEPMODE
|
||||
#-D DOUBLERESETDETECTOR_DEBUG=true
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
-D LOG_LEVEL=6 # Maximum log level for the debug build.
|
||||
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart.
|
||||
-D GYRO_DISABLE_LOGGING
|
||||
-D CALC_DISABLE_LOGGING
|
||||
-D HELPER_DISABLE_LOGGING
|
||||
-D PUSH_DISABLE_LOGGING
|
||||
-D TSEN_DISABLE_LOGGING
|
||||
-D WIFI_DISABLE_LOGGING
|
||||
-D WEB_DISABLE_LOGGING
|
||||
-D MAIN_DISABLE_LOGGING
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
board = ${common_env_data.board}
|
||||
build_type = debug
|
||||
#build_type = debug
|
||||
build_type = release
|
||||
board_build.filesystem = littlefs
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
|
||||
[env:gravity-release]
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
@ -69,6 +90,7 @@ monitor_speed = ${common_env_data.monitor_speed}
|
||||
framework = ${common_env_data.framework}
|
||||
platform = ${common_env_data.platform}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags = ${common_env_data.build_unflags}
|
||||
@ -87,15 +109,43 @@ monitor_speed = ${common_env_data.monitor_speed}
|
||||
framework = ${common_env_data.framework}
|
||||
platform = ${common_env_data.platform}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags = ${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
-D COLLECT_PERFDATA
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
board = ${common_env_data.board}
|
||||
build_type = release
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
[env:gravity32-perf]
|
||||
framework = arduino
|
||||
# platformio only supports v1.0.6 of the esp32 libs.
|
||||
platform = espressif32
|
||||
# tasmota port of v2.0.2
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.1/platform-tasmota-espressif32-2.0.2.1.zip
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
board = nodemcu-32s
|
||||
build_type = release
|
||||
#build_type = debug
|
||||
#board_build.filesystem = littlefs
|
||||
board_build.filesystem = spiffs
|
||||
monitor_filters = esp32_exception_decoder
|
@ -11,13 +11,15 @@ def after_build(source, target, env):
|
||||
print( "Executing custom step " )
|
||||
dir = env.GetLaunchDir()
|
||||
name = env.get( "PIOENV" )
|
||||
source = dir + "\\.pio\\build\\" + name + "\\firmware.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
if name == "gravity-debug" :
|
||||
target = dir + "\\bin\\firmware-debug.bin"
|
||||
target = dir + "/bin/firmware-debug.bin"
|
||||
if name == "gravity-release" :
|
||||
target = dir + "\\bin\\firmware.bin"
|
||||
target = dir + "/bin/firmware.bin"
|
||||
if name == "gravity-perf" :
|
||||
target = dir + "\\bin\\firmware-perf.bin"
|
||||
target = dir + "/bin/firmware-perf.bin"
|
||||
if name == "gravity32-perf" :
|
||||
target = dir + "/bin/firmware32-perf.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
|
31
script/copy_html.py
Normal file
@ -0,0 +1,31 @@
|
||||
Import("env")
|
||||
import shutil, os
|
||||
|
||||
print( "Executing custom step " )
|
||||
dir = env.GetLaunchDir()
|
||||
source = dir + "/html/"
|
||||
target = dir + "/data/"
|
||||
print( "Copy html-files from " + source + " -> " + target )
|
||||
|
||||
os.makedirs(os.path.dirname( target ), exist_ok=True)
|
||||
file = "about.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "calibration.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "config.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "device.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "index.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "upload.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "format.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
@ -13,38 +13,50 @@ def after_build(source, target, env):
|
||||
#name = env.get( "PIOENV" )
|
||||
|
||||
# Copy file 1
|
||||
source = dir + "\\data\\index.min.htm"
|
||||
target = dir + "\\bin\\index.min.htm"
|
||||
source = dir + "/data/index.min.htm"
|
||||
target = dir + "/bin/index.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 2
|
||||
source = dir + "\\data\\device.min.htm"
|
||||
target = dir + "\\bin\\device.min.htm"
|
||||
source = dir + "/data/device.min.htm"
|
||||
target = dir + "/bin/device.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 3
|
||||
source = dir + "\\data\\config.min.htm"
|
||||
target = dir + "\\bin\\config.min.htm"
|
||||
source = dir + "/data/config.min.htm"
|
||||
target = dir + "/bin/config.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 4
|
||||
source = dir + "\\data\\about.min.htm"
|
||||
target = dir + "\\bin\\about.min.htm"
|
||||
source = dir + "/data/about.min.htm"
|
||||
target = dir + "/bin/about.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
target = dir + "\\bin\\version.json"
|
||||
# Copy file 5
|
||||
source = dir + "/data/calibration.min.htm"
|
||||
target = dir + "/bin/calibration.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 6
|
||||
source = dir + "/data/format.min.htm"
|
||||
target = dir + "/bin/format.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
target = dir + "/bin/version.json"
|
||||
ver = get_build_flag_value("CFG_APPVER")
|
||||
|
||||
print( "Creating version.json" )
|
||||
f = open( target, "w" )
|
||||
f.write( "{ \"project\":\"gravmon\", \"version\":" + ver + ", " )
|
||||
f.write( " \"html\": [ \"index.min.htm\", \"device.min.htm\", \"config.min.htm\", \"about.min.htm\" ] }" )
|
||||
f.write( " \"html\": [ \"index.min.htm\", \"device.min.htm\", \"config.min.htm\", \"calibration.min.htm\", \"format.min.htm\", \"about.min.htm\" ] }" )
|
||||
f.close()
|
||||
|
||||
|
||||
print( "Adding custom build step (create json):")
|
||||
env.AddPreAction("buildprog", after_build)
|
||||
env.AddPostAction("buildprog", after_build)
|
||||
|
317
src/calc.cpp
@ -1,98 +1,219 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "calc.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "tinyexpr.h"
|
||||
#include "tempsensor.h"
|
||||
|
||||
//
|
||||
// Calculates gravity according to supplied formula, compatible with iSpindle/Fermentrack formula
|
||||
//
|
||||
double calculateGravity( double angle, double temp ) {
|
||||
const char* formula = myConfig.getGravityFormula();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp);
|
||||
Log.verbose(F("CALC: Formula %s." CR), formula);
|
||||
#endif
|
||||
|
||||
if( strlen(formula) == 0 )
|
||||
return 0.0;
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
te_expr *expr = te_compile(formula, vars, 2, &err);
|
||||
|
||||
if (expr) {
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Failed to parse expression %d." CR), err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Do a standard gravity temperature correction. This is a simple way to adjust for differnt worth temperatures
|
||||
//
|
||||
double gravityTemperatureCorrection( double gravity, double temp, char tempFormat, double calTemp) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, temp %F, calTemp %F." CR), gravity, temp, calTemp);
|
||||
#endif
|
||||
|
||||
if( tempFormat == 'C')
|
||||
temp = convertCtoF( temp );
|
||||
double calTempF = convertCtoF(calTemp); // calTemp is in C
|
||||
const char* formula = "gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))";
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
te_expr *expr = te_compile(formula, vars, 3, &err);
|
||||
|
||||
if (expr) {
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CALC: Corrected gravity is %F." CR), g);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Failed to parse expression %d, no correction has been made." CR), err);
|
||||
return gravity;
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <curveFitting.h>
|
||||
#include <tinyexpr.h>
|
||||
|
||||
#include <calc.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
//
|
||||
// Use values to derive a formula
|
||||
//
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order) {
|
||||
int noAngles = 0;
|
||||
|
||||
// Check how many valid values we have got
|
||||
if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 && fd.a[4] > 0)
|
||||
noAngles = 5;
|
||||
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0)
|
||||
noAngles = 4;
|
||||
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0)
|
||||
noAngles = 3;
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
F("CALC: Trying to create formula using order = %d, found %d angles" CR),
|
||||
order, noAngles);
|
||||
#endif
|
||||
|
||||
if (!noAngles) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
|
||||
return ERR_FORMULA_NOTENOUGHVALUES;
|
||||
} else {
|
||||
double coeffs[order + 1];
|
||||
int ret = fitCurve(order, noAngles, fd.a, fd.g,
|
||||
sizeof(coeffs) / sizeof(double), coeffs);
|
||||
|
||||
// Returned value is 0 if no error
|
||||
if (ret == 0) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Finshied processing data points." CR));
|
||||
#endif
|
||||
|
||||
// Print the formula based on 'order'
|
||||
if (order == 4) {
|
||||
snprintf(formulaBuffer, formulaBufferSize,
|
||||
"%.8f*tilt^4+%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f",
|
||||
coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]);
|
||||
} else if (order == 3) {
|
||||
snprintf(formulaBuffer, formulaBufferSize,
|
||||
"%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1],
|
||||
coeffs[2], coeffs[3]);
|
||||
} else if (order == 2) {
|
||||
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f",
|
||||
coeffs[0], coeffs[1], coeffs[2]);
|
||||
} else { // order == 1
|
||||
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0],
|
||||
coeffs[1]);
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
|
||||
#endif
|
||||
|
||||
bool valid = true;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (fd.a[i] == 0 && valid) break;
|
||||
|
||||
double g = calculateGravity(fd.a[i], 0, formulaBuffer);
|
||||
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
|
||||
|
||||
// If the deviation is more than 2 degress we mark it as failed.
|
||||
if (dev * 1000 > myHardwareConfig.getMaxFormulaCreationDeviation()) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
char s[20];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", dev);
|
||||
Log.verbose(F("CALC: Deviation is: %s" CR), &s[0]);
|
||||
#endif
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("CALC: Error validating created formula. Deviation to large, "
|
||||
"formula rejected."));
|
||||
return ERR_FORMULA_UNABLETOFFIND;
|
||||
}
|
||||
|
||||
Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Internal error finding formula."));
|
||||
return ERR_FORMULA_INTERNAL;
|
||||
}
|
||||
|
||||
//
|
||||
// Calculates gravity according to supplied formula, compatible with
|
||||
// iSpindle/Fermentrack formula
|
||||
//
|
||||
double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
const char *formula = myConfig.getGravityFormula();
|
||||
|
||||
if (tempFormula != 0) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Using temporary formula." CR));
|
||||
#endif
|
||||
formula = tempFormula;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
|
||||
temp);
|
||||
Log.verbose(F("CALC: Formula %s." CR), formula);
|
||||
#endif
|
||||
|
||||
if (strlen(formula) == 0) return 0.0;
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
te_expr *expr = te_compile(formula, vars, 2, &err);
|
||||
|
||||
if (expr) {
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
char s[20];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", g);
|
||||
Log.verbose(F("CALC: Calculated gravity is %s." CR), &s[0]);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Do a standard gravity temperature correction. This is a simple way to adjust
|
||||
// for differnt worth temperatures. This function uses C as temperature.
|
||||
//
|
||||
// Source: https://homebrewacademy.com/hydrometer-temperature-correction/
|
||||
//
|
||||
double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
double calTempC) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, "
|
||||
"temp %F, calTemp %F." CR),
|
||||
gravity, tempC, calTempC);
|
||||
#endif
|
||||
// float tempF = convertCtoF(tempC);
|
||||
// float calTempF = convertCtoF(calTempC);
|
||||
|
||||
const char *formula =
|
||||
"gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0."
|
||||
"00000000232820948*temp^3)/"
|
||||
"(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0."
|
||||
"00000000232820948*cal^3))";
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {
|
||||
{"gravity", &gravity}, {"temp", &tempC}, {"cal", &calTempC}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
te_expr *expr = te_compile(formula, vars, 3, &err);
|
||||
|
||||
if (expr) {
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
char s[10];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", g);
|
||||
Log.verbose(F("CALC: Corrected gravity is %s." CR), &s[0]);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
"CALC: Failed to parse expression for gravity temperature correction " +
|
||||
String(err));
|
||||
return gravity;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -1,58 +1,43 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _WIFI_H
|
||||
#define _WIFI_H
|
||||
|
||||
// Include
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
// classes
|
||||
class Wifi {
|
||||
private:
|
||||
// WIFI
|
||||
bool connectedFlag = false;
|
||||
|
||||
// OTA
|
||||
bool newFirmware = false;
|
||||
bool parseFirmwareVersionString( int (&num)[3], const char *version );
|
||||
void downloadFile(const char *fname);
|
||||
|
||||
public:
|
||||
// WIFI
|
||||
bool connect( bool showPortal = false );
|
||||
bool disconnect();
|
||||
bool isConnected() { return connectedFlag; };
|
||||
String getIPAddress() { return WiFi.localIP().toString(); };
|
||||
|
||||
// OTA
|
||||
bool updateFirmware();
|
||||
bool checkFirmwareVersion();
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern Wifi myWifi;
|
||||
|
||||
#endif // _WIFI_H
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_CALC_HPP_
|
||||
#define SRC_CALC_HPP_
|
||||
|
||||
#include <config.hpp>
|
||||
|
||||
#define ERR_FORMULA_NOTENOUGHVALUES -1
|
||||
#define ERR_FORMULA_INTERNAL -2
|
||||
#define ERR_FORMULA_UNABLETOFFIND -3
|
||||
|
||||
double calculateGravity(double angle, double tempC,
|
||||
const char *tempFormula = 0);
|
||||
double gravityTemperatureCorrectionC(
|
||||
double gravity, double tempC,
|
||||
double calTempC = myHardwareConfig.getDefaultCalibrationTemp());
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order);
|
||||
|
||||
#endif // SRC_CALC_HPP_
|
||||
|
||||
// EOF
|
703
src/config.cpp
@ -1,268 +1,435 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "helper.h"
|
||||
#include <LittleFS.h>
|
||||
|
||||
Config myConfig;
|
||||
|
||||
//
|
||||
// Create the config class with default settings.
|
||||
//
|
||||
Config::Config() {
|
||||
// Assiging default values
|
||||
char buf[20];
|
||||
sprintf(&buf[0], "%6x", (unsigned int) ESP.getChipId() );
|
||||
id = &buf[0];
|
||||
sprintf(&buf[0], "" WIFI_MDNS "%s", getID() );
|
||||
mDNS = &buf[0];
|
||||
setTempFormat('C');
|
||||
setGravityFormat('G');
|
||||
setSleepInterval(900); // 15 minutes
|
||||
setVoltageFactor(1.59); // Conversion factor for battery
|
||||
setTempSensorAdj(0.0);
|
||||
setGravityTempAdj(false);
|
||||
gyroCalibration = { 0, 0, 0, 0, 0 ,0 };
|
||||
saveNeeded = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Populate the json document with all configuration parameters (used in both web and saving to file)
|
||||
//
|
||||
void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[ CFG_PARAM_MDNS ] = getMDNS();
|
||||
doc[ CFG_PARAM_ID ] = getID();
|
||||
doc[ CFG_PARAM_OTA ] = getOtaURL();
|
||||
doc[ CFG_PARAM_TEMPFORMAT ] = String( getTempFormat() );
|
||||
doc[ CFG_PARAM_PUSH_BREWFATHER ] = getBrewfatherPushUrl();
|
||||
doc[ CFG_PARAM_PUSH_HTTP ] = getHttpPushUrl();
|
||||
doc[ CFG_PARAM_PUSH_HTTP2 ] = getHttpPushUrl2();
|
||||
doc[ CFG_PARAM_PUSH_INFLUXDB2 ] = getInfluxDb2PushUrl();
|
||||
doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ] = getInfluxDb2PushOrg();
|
||||
doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ] = getInfluxDb2PushBucket();
|
||||
doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ] = getInfluxDb2PushToken();
|
||||
doc[ CFG_PARAM_SLEEP_INTERVAL ] = getSleepInterval();
|
||||
// doc[ CFG_PARAM_PUSH_INTERVAL ] = getSleepInterval(); // TODO: @deprecated
|
||||
doc[ CFG_PARAM_VOLTAGEFACTOR ] = getVoltageFactor();
|
||||
doc[ CFG_PARAM_GRAVITY_FORMULA ] = getGravityFormula();
|
||||
doc[ CFG_PARAM_GRAVITY_FORMAT ] = String(getGravityFormat());
|
||||
doc[ CFG_PARAM_TEMP_ADJ ] = getTempSensorAdj();
|
||||
doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ] = isGravityTempAdj();
|
||||
|
||||
JsonObject cal = doc.createNestedObject( CFG_PARAM_GYRO_CALIBRATION );
|
||||
cal["ax"] = gyroCalibration.ax;
|
||||
cal["ay"] = gyroCalibration.ay;
|
||||
cal["az"] = gyroCalibration.az;
|
||||
cal["gx"] = gyroCalibration.gx;
|
||||
cal["gy"] = gyroCalibration.gy;
|
||||
cal["gz"] = gyroCalibration.gz;
|
||||
}
|
||||
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool Config::saveFile() {
|
||||
if( !saveNeeded ) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Skipping save, not needed." CR));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Saving configuration to file." CR));
|
||||
#endif
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
createJson( doc );
|
||||
#if LOG_LEVEL==6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print( CR );
|
||||
#endif
|
||||
serializeJson(doc, configFile);
|
||||
configFile.flush();
|
||||
configFile.close();
|
||||
|
||||
saveNeeded = false;
|
||||
myConfig.debug();
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool Config::loadFile() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Loading configuration from file." CR));
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_FILENAME)) {
|
||||
Log.error(F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.notice(F("CFG : Size of configuration file=%d bytes." CR), configFile.size() );
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
DeserializationError err = deserializeJson(doc, configFile);
|
||||
#if LOG_LEVEL==6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print( CR );
|
||||
#endif
|
||||
configFile.close();
|
||||
|
||||
if( err ) {
|
||||
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR), err.c_str(), doc.capacity());
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Parsed configuration file." CR));
|
||||
#endif
|
||||
if( !doc[ CFG_PARAM_OTA ].isNull() )
|
||||
setOtaURL( doc[ CFG_PARAM_OTA ] );
|
||||
if( !doc[ CFG_PARAM_MDNS ].isNull() )
|
||||
setMDNS( doc[ CFG_PARAM_MDNS ] );
|
||||
if( !doc[ CFG_PARAM_TEMPFORMAT ].isNull() ) {
|
||||
String s = doc[ CFG_PARAM_TEMPFORMAT ];
|
||||
setTempFormat( s.charAt(0) );
|
||||
}
|
||||
if( !doc[ CFG_PARAM_PUSH_BREWFATHER ].isNull() )
|
||||
setBrewfatherPushUrl( doc[ CFG_PARAM_PUSH_BREWFATHER ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_HTTP ].isNull() )
|
||||
setHttpPushUrl( doc[ CFG_PARAM_PUSH_HTTP ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_HTTP2 ].isNull() )
|
||||
setHttpPushUrl2( doc[ CFG_PARAM_PUSH_HTTP2 ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2 ].isNull() )
|
||||
setInfluxDb2PushUrl( doc[ CFG_PARAM_PUSH_INFLUXDB2 ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ].isNull() )
|
||||
setInfluxDb2PushOrg( doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ].isNull() )
|
||||
setInfluxDb2PushBucket( doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ] );
|
||||
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ].isNull() )
|
||||
setInfluxDb2PushToken( doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ] );
|
||||
if( !doc[ CFG_PARAM_SLEEP_INTERVAL ].isNull() )
|
||||
setSleepInterval( doc[ CFG_PARAM_SLEEP_INTERVAL ].as<int>() );
|
||||
if( !doc[ CFG_PARAM_PUSH_INTERVAL ].isNull() ) // TODO: @deprecated
|
||||
setSleepInterval( doc[ CFG_PARAM_PUSH_INTERVAL ].as<int>() ); // TODO: @deprecated
|
||||
if( !doc[ CFG_PARAM_VOLTAGEFACTOR ].isNull() )
|
||||
setVoltageFactor( doc[ CFG_PARAM_VOLTAGEFACTOR ].as<float>() );
|
||||
if( !doc[ CFG_PARAM_GRAVITY_FORMULA ].isNull() )
|
||||
setGravityFormula( doc[ CFG_PARAM_GRAVITY_FORMULA ] );
|
||||
if( !doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].isNull() )
|
||||
setGravityTempAdj( doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].as<bool>() );
|
||||
if( !doc[ CFG_PARAM_GRAVITY_FORMAT ].isNull() ) {
|
||||
String s = doc[ CFG_PARAM_GRAVITY_FORMAT ];
|
||||
setGravityFormat( s.charAt(0) );
|
||||
}
|
||||
if( !doc[ CFG_PARAM_TEMP_ADJ ].isNull() )
|
||||
setTempSensorAdj( doc[ CFG_PARAM_TEMP_ADJ ].as<float>() );
|
||||
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"].isNull() )
|
||||
gyroCalibration.ax = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"];
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"].isNull() )
|
||||
gyroCalibration.ay = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"];
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"].isNull() )
|
||||
gyroCalibration.az = doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"];
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"].isNull() )
|
||||
gyroCalibration.gx = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"];
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"].isNull() )
|
||||
gyroCalibration.gy = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"];
|
||||
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"].isNull() )
|
||||
gyroCalibration.gz = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"];
|
||||
|
||||
myConfig.debug();
|
||||
saveNeeded = false; // Reset save flag
|
||||
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if file system can be mounted, if not we format it.
|
||||
//
|
||||
void Config::formatFileSystem() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Formating filesystem." CR));
|
||||
#endif
|
||||
LittleFS.format();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if file system can be mounted, if not we format it.
|
||||
//
|
||||
void Config::checkFileSystem() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
|
||||
#endif
|
||||
|
||||
if (LittleFS.begin()) {
|
||||
Log.notice(F("CFG : Filesystem mounted." CR));
|
||||
} else {
|
||||
Log.error(F("CFG : Unable to mount file system, formatting..." CR));
|
||||
LittleFS.format();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Dump the configuration to the serial port
|
||||
//
|
||||
void Config::debug() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR));
|
||||
Log.verbose(F("CFG : ID; '%s'." CR), getID());
|
||||
Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS() );
|
||||
Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval() );
|
||||
Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL() );
|
||||
Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat() );
|
||||
Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdj() );
|
||||
Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor() );
|
||||
Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula() );
|
||||
Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat() );
|
||||
Log.verbose(F("CFG : Gravity temp adj; %s." CR), isGravityTempAdj()?"true":"false" );
|
||||
Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl() );
|
||||
Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl() );
|
||||
Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2() );
|
||||
Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR), getInfluxDb2PushUrl(), getInfluxDb2PushOrg(),
|
||||
getInfluxDb2PushBucket(), getInfluxDb2PushToken() );
|
||||
// Log.verbose(F("CFG : Accel offset\t%d\t%d\t%d" CR), gyroCalibration.ax, gyroCalibration.ay, gyroCalibration.az );
|
||||
// Log.verbose(F("CFG : Gyro offset \t%d\t%d\t%d" CR), gyroCalibration.gx, gyroCalibration.gy, gyroCalibration.gz );
|
||||
#endif
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <config.hpp>
|
||||
#include <main.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
Config myConfig;
|
||||
HardwareConfig myHardwareConfig;
|
||||
|
||||
//
|
||||
// Create the config class with default settings.
|
||||
//
|
||||
Config::Config() {
|
||||
// Assiging default values
|
||||
char buf[30];
|
||||
#if defined(ESP8266)
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", (unsigned int)ESP.getChipId());
|
||||
#else // defined (ESP32)
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
|
||||
#endif
|
||||
_id = String(&buf[0]);
|
||||
snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID());
|
||||
_mDNS = String(&buf[0]);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Created config for %s (%s)." CR), _id.c_str(),
|
||||
_mDNS.c_str());
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
setVoltageFactor(1.59); // Conversion factor for battery on ESP8266
|
||||
#else // defined (ESP32)
|
||||
setVoltageFactor(1.43); // Conversion factor for battery on ESP32
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Populate the json document with all configuration parameters (used in both
|
||||
// web and saving to file)
|
||||
//
|
||||
void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[PARAM_MDNS] = getMDNS();
|
||||
//doc[PARAM_CONFIG_VER] = getConfigVersion();
|
||||
doc[PARAM_ID] = getID();
|
||||
doc[PARAM_OTA] = getOtaURL();
|
||||
doc[PARAM_SSID] = getWifiSSID();
|
||||
doc[PARAM_PASS] = getWifiPass();
|
||||
doc[PARAM_TEMPFORMAT] = String(getTempFormat());
|
||||
doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
|
||||
doc[PARAM_TOKEN] = getToken();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpUrl();
|
||||
doc[PARAM_PUSH_HTTP_H1] = getHttpHeader(0);
|
||||
doc[PARAM_PUSH_HTTP_H2] = getHttpHeader(1);
|
||||
doc[PARAM_PUSH_HTTP2] = getHttp2Url();
|
||||
doc[PARAM_PUSH_HTTP2_H1] = getHttp2Header(0);
|
||||
doc[PARAM_PUSH_HTTP2_H2] = getHttp2Header(1);
|
||||
doc[PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
|
||||
doc[PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
|
||||
doc[PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
|
||||
doc[PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
|
||||
doc[PARAM_PUSH_MQTT] = getMqttUrl();
|
||||
doc[PARAM_PUSH_MQTT_PORT] = getMqttPort();
|
||||
doc[PARAM_PUSH_MQTT_USER] = getMqttUser();
|
||||
doc[PARAM_PUSH_MQTT_PASS] = getMqttPass();
|
||||
doc[PARAM_SLEEP_INTERVAL] = getSleepInterval();
|
||||
doc[PARAM_VOLTAGEFACTOR] = getVoltageFactor();
|
||||
doc[PARAM_GRAVITY_FORMULA] = getGravityFormula();
|
||||
doc[PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
|
||||
doc[PARAM_TEMP_ADJ] = getTempSensorAdjC();
|
||||
doc[PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
|
||||
doc[PARAM_GYRO_TEMP] = isGyroTemp();
|
||||
|
||||
JsonObject cal = doc.createNestedObject(PARAM_GYRO_CALIBRATION);
|
||||
cal["ax"] = _gyroCalibration.ax;
|
||||
cal["ay"] = _gyroCalibration.ay;
|
||||
cal["az"] = _gyroCalibration.az;
|
||||
cal["gx"] = _gyroCalibration.gx;
|
||||
cal["gy"] = _gyroCalibration.gy;
|
||||
cal["gz"] = _gyroCalibration.gz;
|
||||
|
||||
JsonObject cal2 = doc.createNestedObject(PARAM_FORMULA_DATA);
|
||||
cal2["a1"] = reduceFloatPrecision(_formulaData.a[0], 2);
|
||||
cal2["a2"] = reduceFloatPrecision(_formulaData.a[1], 2);
|
||||
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
|
||||
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
|
||||
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
|
||||
|
||||
cal2["g1"] = reduceFloatPrecision(_formulaData.g[0], 4);
|
||||
cal2["g2"] = reduceFloatPrecision(_formulaData.g[1], 4);
|
||||
cal2["g3"] = reduceFloatPrecision(_formulaData.g[2], 4);
|
||||
cal2["g4"] = reduceFloatPrecision(_formulaData.g[3], 4);
|
||||
cal2["g5"] = reduceFloatPrecision(_formulaData.g[4], 4);
|
||||
}
|
||||
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool Config::saveFile() {
|
||||
if (!_saveNeeded) {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Skipping save, not needed." CR));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving configuration to file." CR));
|
||||
#endif
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to save configuration."));
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
createJson(doc);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
serializeJson(doc, configFile);
|
||||
configFile.flush();
|
||||
configFile.close();
|
||||
|
||||
_saveNeeded = false;
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool Config::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading configuration from file." CR));
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_FILENAME)) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Configuration file does not exist."));
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to load configuration."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
|
||||
configFile.size());
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
DeserializationError err = deserializeJson(doc, configFile);
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CFG : Parsed configuration file." CR));
|
||||
#endif
|
||||
if (!doc[PARAM_OTA].isNull()) setOtaURL(doc[PARAM_OTA]);
|
||||
if (!doc[PARAM_MDNS].isNull()) setMDNS(doc[PARAM_MDNS]);
|
||||
if (!doc[PARAM_SSID].isNull()) setWifiSSID(doc[PARAM_SSID]);
|
||||
if (!doc[PARAM_PASS].isNull()) setWifiPass(doc[PARAM_PASS]);
|
||||
|
||||
if (!doc[PARAM_TEMPFORMAT].isNull()) {
|
||||
String s = doc[PARAM_TEMPFORMAT];
|
||||
setTempFormat(s.charAt(0));
|
||||
}
|
||||
|
||||
if (!doc[PARAM_PUSH_BREWFATHER].isNull())
|
||||
setBrewfatherPushUrl(doc[PARAM_PUSH_BREWFATHER]);
|
||||
|
||||
if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]);
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]);
|
||||
if (!doc[PARAM_PUSH_HTTP_H1].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP_H2].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H2], 1);
|
||||
if (!doc[PARAM_PUSH_HTTP2].isNull()) setHttp2Url(doc[PARAM_PUSH_HTTP2]);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H1].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H2].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H2], 1);
|
||||
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2].isNull())
|
||||
setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_ORG].isNull())
|
||||
setInfluxDb2PushOrg(doc[PARAM_PUSH_INFLUXDB2_ORG]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
|
||||
setInfluxDb2PushBucket(doc[PARAM_PUSH_INFLUXDB2_BUCKET]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_AUTH].isNull())
|
||||
setInfluxDb2PushToken(doc[PARAM_PUSH_INFLUXDB2_AUTH]);
|
||||
|
||||
if (!doc[PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[PARAM_PUSH_MQTT]);
|
||||
if (!doc[PARAM_PUSH_MQTT_PORT].isNull())
|
||||
setMqttPort(doc[PARAM_PUSH_MQTT_PORT].as<int>());
|
||||
if (!doc[PARAM_PUSH_MQTT_USER].isNull())
|
||||
setMqttUser(doc[PARAM_PUSH_MQTT_USER]);
|
||||
if (!doc[PARAM_PUSH_MQTT_PASS].isNull())
|
||||
setMqttPass(doc[PARAM_PUSH_MQTT_PASS]);
|
||||
|
||||
if (!doc[PARAM_SLEEP_INTERVAL].isNull())
|
||||
setSleepInterval(doc[PARAM_SLEEP_INTERVAL].as<int>());
|
||||
if (!doc[PARAM_VOLTAGEFACTOR].isNull())
|
||||
setVoltageFactor(doc[PARAM_VOLTAGEFACTOR].as<float>());
|
||||
if (!doc[PARAM_GRAVITY_FORMULA].isNull())
|
||||
setGravityFormula(doc[PARAM_GRAVITY_FORMULA]);
|
||||
if (!doc[PARAM_GRAVITY_TEMP_ADJ].isNull())
|
||||
setGravityTempAdj(doc[PARAM_GRAVITY_TEMP_ADJ].as<bool>());
|
||||
if (!doc[PARAM_GYRO_TEMP].isNull())
|
||||
setGyroTemp(doc[PARAM_GYRO_TEMP].as<bool>());
|
||||
if (!doc[PARAM_GRAVITY_FORMAT].isNull()) {
|
||||
String s = doc[PARAM_GRAVITY_FORMAT];
|
||||
setGravityFormat(s.charAt(0));
|
||||
}
|
||||
if (!doc[PARAM_TEMP_ADJ].isNull())
|
||||
setTempSensorAdjC(doc[PARAM_TEMP_ADJ].as<float>());
|
||||
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["ax"].isNull())
|
||||
_gyroCalibration.ax = doc[PARAM_GYRO_CALIBRATION]["ax"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["ay"].isNull())
|
||||
_gyroCalibration.ay = doc[PARAM_GYRO_CALIBRATION]["ay"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["az"].isNull())
|
||||
_gyroCalibration.az = doc[PARAM_GYRO_CALIBRATION]["az"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gx"].isNull())
|
||||
_gyroCalibration.gx = doc[PARAM_GYRO_CALIBRATION]["gx"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gy"].isNull())
|
||||
_gyroCalibration.gy = doc[PARAM_GYRO_CALIBRATION]["gy"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gz"].isNull())
|
||||
_gyroCalibration.gz = doc[PARAM_GYRO_CALIBRATION]["gz"];
|
||||
|
||||
if (!doc[PARAM_FORMULA_DATA]["a1"].isNull())
|
||||
_formulaData.a[0] = doc[PARAM_FORMULA_DATA]["a1"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a2"].isNull())
|
||||
_formulaData.a[1] = doc[PARAM_FORMULA_DATA]["a2"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a3"].isNull())
|
||||
_formulaData.a[2] = doc[PARAM_FORMULA_DATA]["a3"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a4"].isNull())
|
||||
_formulaData.a[3] = doc[PARAM_FORMULA_DATA]["a4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a5"].isNull())
|
||||
_formulaData.a[4] = doc[PARAM_FORMULA_DATA]["a5"].as<double>();
|
||||
|
||||
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
|
||||
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g2"].isNull())
|
||||
_formulaData.g[1] = doc[PARAM_FORMULA_DATA]["g2"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g3"].isNull())
|
||||
_formulaData.g[2] = doc[PARAM_FORMULA_DATA]["g3"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g4"].isNull())
|
||||
_formulaData.g[3] = doc[PARAM_FORMULA_DATA]["g4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g5"].isNull())
|
||||
_formulaData.g[4] = doc[PARAM_FORMULA_DATA]["g5"].as<double>();
|
||||
|
||||
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
|
||||
// If this parameter is missing we need to reset the gyrocalibaration due to bug #29
|
||||
_gyroCalibration.ax = _gyroCalibration.ay = _gyroCalibration.az = 0;
|
||||
_gyroCalibration.gx = _gyroCalibration.gy = _gyroCalibration.gz = 0;
|
||||
Log.warning(F("CFG : Old configuration format, clearing gyro calibration." CR));
|
||||
}*/
|
||||
|
||||
_saveNeeded = false; // Reset save flag
|
||||
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if file system can be mounted, if not we format it.
|
||||
//
|
||||
void Config::formatFileSystem() {
|
||||
Log.notice(F("CFG : Formating filesystem." CR));
|
||||
LittleFS.format();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if file system can be mounted, if not we format it.
|
||||
//
|
||||
void Config::checkFileSystem() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
|
||||
#endif
|
||||
|
||||
if (LittleFS.begin()) {
|
||||
Log.notice(F("CFG : Filesystem mounted." CR));
|
||||
} else {
|
||||
Log.error(F("CFG : Unable to mount file system, formatting..." CR));
|
||||
LittleFS.format();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool HardwareConfig::saveFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving hardware configuration to file." CR));
|
||||
#endif
|
||||
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(512);
|
||||
|
||||
doc[PARAM_HW_GYRO_READ_COUNT] = this->getGyroReadCount();
|
||||
doc[PARAM_HW_GYRO_READ_DELAY] = this->getGyroReadDelay();
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] = this->getGyroSensorMovingThreashold();
|
||||
doc[PARAM_HW_FORMULA_DEVIATION] = this->getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTALTIMEOUT] = this->getWifiPortalTimeout();
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] = this->getDefaultCalibrationTemp();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
serializeJson(doc, configFile);
|
||||
configFile.flush();
|
||||
configFile.close();
|
||||
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_HW_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool HardwareConfig::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading hardware configuration from file." CR));
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_HW_FILENAME)) {
|
||||
Log.warning(
|
||||
F("CFG : Configuration file does not exist " CFG_HW_FILENAME "." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
|
||||
configFile.size());
|
||||
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, configFile);
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CFG : Parsed hardware configuration file." CR));
|
||||
#endif
|
||||
|
||||
if (!doc[PARAM_HW_GYRO_READ_COUNT].isNull())
|
||||
this->setGyroReadCount(doc[PARAM_HW_GYRO_READ_COUNT].as<int>());
|
||||
if (!doc[PARAM_HW_GYRO_READ_DELAY].isNull())
|
||||
this->setGyroReadDelay(doc[PARAM_HW_GYRO_READ_DELAY].as<int>());
|
||||
if (!doc[PARAM_HW_GYRO_MOVING_THREASHOLD].isNull())
|
||||
this->setGyroSensorMovingThreashold(
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD].as<int>());
|
||||
if (!doc[PARAM_HW_FORMULA_DEVIATION].isNull())
|
||||
this->setMaxFormulaCreationDeviation(
|
||||
doc[PARAM_HW_FORMULA_DEVIATION].as<float>());
|
||||
if (!doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].isNull())
|
||||
this->SetDefaultCalibrationTemp(
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].as<float>());
|
||||
if (!doc[PARAM_HW_WIFI_PORTALTIMEOUT].isNull())
|
||||
this->setWifiPortalTimeout(doc[PARAM_HW_WIFI_PORTALTIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_TIMEOUT].isNull())
|
||||
this->setPushTimeout(doc[PARAM_HW_PUSH_TIMEOUT].as<int>());
|
||||
|
||||
Log.notice(F("CFG : Configuration file " CFG_HW_FILENAME " loaded." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
206
src/config.h
@ -1,206 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _CONFIG_H
|
||||
#define _CONFIG_H
|
||||
|
||||
// Includes
|
||||
#include "helper.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// defintions
|
||||
#define CFG_JSON_BUFSIZE 2000
|
||||
|
||||
#define CFG_APPNAME "GravityMon " // Name of firmware
|
||||
#define CFG_FILENAME "/gravitymon.json" // Name of config file
|
||||
|
||||
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
|
||||
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
|
||||
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
|
||||
#define WIFI_PORTAL_TIMEOUT 120 // Number of seconds until the config portal is closed
|
||||
|
||||
// These are used in API + Savefile
|
||||
#define CFG_PARAM_ID "id"
|
||||
#define CFG_PARAM_MDNS "mdns" // Device name
|
||||
#define CFG_PARAM_OTA "ota-url" // Base URL for OTA
|
||||
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push" // URL (brewfather format)
|
||||
#define CFG_PARAM_PUSH_HTTP "http-push" // URL (iSpindle format)
|
||||
#define CFG_PARAM_PUSH_HTTP2 "http-push2" // URL (iSpindle format)
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push" // URL
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org" // URL
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket" // URL
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth" // URL
|
||||
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
|
||||
// TODO: @deprecated setting
|
||||
#define CFG_PARAM_PUSH_INTERVAL "push-interval" // Time between push
|
||||
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
|
||||
#define CFG_PARAM_VOLTAGEFACTOR "voltage-factor" // Factor to calculate the battery voltage
|
||||
#define CFG_PARAM_GRAVITY_FORMULA "gravity-formula" // Formula for calculating gravity
|
||||
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
|
||||
#define CFG_PARAM_GRAVITY_TEMP_ADJ "gravity-temp-adjustment" // True/False. Adjust gravity for temperature
|
||||
#define CFG_PARAM_TEMP_ADJ "temp-adjustment-value" // Correction value for temp sensor
|
||||
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
|
||||
|
||||
// These are used in API's
|
||||
#define CFG_PARAM_APP_NAME "app-name"
|
||||
#define CFG_PARAM_APP_VER "app-ver"
|
||||
#define CFG_PARAM_ANGLE "angle"
|
||||
#define CFG_PARAM_GRAVITY "gravity"
|
||||
#define CFG_PARAM_TEMP_C "temp-c"
|
||||
#define CFG_PARAM_TEMP_F "temp-f"
|
||||
#define CFG_PARAM_BATTERY "battery"
|
||||
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
|
||||
#define CFG_PARAM_RSSI "rssi"
|
||||
|
||||
// Used for holding sensordata or sensoroffsets
|
||||
struct RawGyroData {
|
||||
int16_t ax; // Raw Acceleration
|
||||
int16_t ay;
|
||||
int16_t az;
|
||||
|
||||
int16_t gx; // Raw Position
|
||||
int16_t gy;
|
||||
int16_t gz;
|
||||
|
||||
int16_t temp; // Only for information (temperature of chip)
|
||||
};
|
||||
|
||||
// Main configuration class
|
||||
class Config {
|
||||
private:
|
||||
bool saveNeeded;
|
||||
|
||||
// Device configuration
|
||||
String id;
|
||||
String mDNS;
|
||||
String otaURL;
|
||||
char tempFormat; // C, F
|
||||
float voltageFactor;
|
||||
float tempSensorAdj; // This value will be added to the read sensor value
|
||||
int sleepInterval;
|
||||
|
||||
// Push target settings
|
||||
String brewfatherPushUrl; // URL For brewfather
|
||||
|
||||
String httpPushUrl; // URL 1 for standard http
|
||||
String httpPushUrl2; // URL 2 for standard http
|
||||
|
||||
String influxDb2Url; // URL for InfluxDB v2
|
||||
String influxDb2Org; // Organisation for InfluxDB v2
|
||||
String influxDb2Bucket; // Bucket for InfluxDB v2
|
||||
String influxDb2Token; // Auth Token for InfluxDB v2
|
||||
|
||||
// Gravity and temperature calculations
|
||||
String gravityFormula;
|
||||
bool gravityTempAdj; // true, false
|
||||
char gravityFormat; // G, P
|
||||
|
||||
// Gyro calibration data
|
||||
RawGyroData gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
|
||||
|
||||
void debug();
|
||||
void formatFileSystem();
|
||||
|
||||
public:
|
||||
Config();
|
||||
const char* getID() { return id.c_str(); };
|
||||
|
||||
const char* getMDNS() { return mDNS.c_str(); }
|
||||
void setMDNS( String s ) { mDNS = s; saveNeeded = true; }
|
||||
|
||||
const char* getOtaURL() { return otaURL.c_str(); }
|
||||
void setOtaURL( String s ) { otaURL = s; saveNeeded = true; }
|
||||
bool isOtaActive() { return otaURL.length()?true:false; }
|
||||
|
||||
// Brewfather
|
||||
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
|
||||
void setBrewfatherPushUrl( String s ) { brewfatherPushUrl = s; saveNeeded = true; }
|
||||
bool isBrewfatherActive() { return brewfatherPushUrl.length()?true:false; }
|
||||
|
||||
// Standard HTTP
|
||||
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
|
||||
void setHttpPushUrl( String s ) { httpPushUrl = s; saveNeeded = true; }
|
||||
bool isHttpActive() { return httpPushUrl.length()?true:false; }
|
||||
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
|
||||
void setHttpPushUrl2( String s ) { httpPushUrl2 = s; saveNeeded = true; }
|
||||
bool isHttpActive2() { return httpPushUrl2.length()?true:false; }
|
||||
|
||||
// InfluxDB2
|
||||
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
|
||||
void setInfluxDb2PushUrl( String s ) { influxDb2Url = s; saveNeeded = true; }
|
||||
bool isInfluxDb2Active() { return influxDb2Url.length()?true:false; }
|
||||
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
|
||||
void setInfluxDb2PushOrg( String s ) { influxDb2Org = s; saveNeeded = true; }
|
||||
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
|
||||
void setInfluxDb2PushBucket( String s ) { influxDb2Bucket = s; saveNeeded = true; }
|
||||
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
|
||||
void setInfluxDb2PushToken( String s ) { influxDb2Token = s; saveNeeded = true; }
|
||||
|
||||
int getSleepInterval() { return sleepInterval; }
|
||||
void setSleepInterval( int v ) { sleepInterval = v; saveNeeded = true; }
|
||||
void setSleepInterval( String s ) { sleepInterval = s.toInt(); saveNeeded = true; }
|
||||
|
||||
char getTempFormat() { return tempFormat; }
|
||||
void setTempFormat( char c ) { tempFormat = c; saveNeeded = true; }
|
||||
bool isTempC() { return tempFormat=='C'?false:true; };
|
||||
bool isTempF() { return tempFormat=='F'?false:true; };
|
||||
|
||||
float getVoltageFactor() { return voltageFactor; }
|
||||
void setVoltageFactor( float f ) { voltageFactor = f; saveNeeded = true; }
|
||||
void setVoltageFactor( String s ) { voltageFactor = s.toFloat(); saveNeeded = true; }
|
||||
|
||||
float getTempSensorAdj() { return tempSensorAdj; }
|
||||
void setTempSensorAdj( float f ) { tempSensorAdj = f; saveNeeded = true; }
|
||||
void setTempSensorAdj( String s ) { tempSensorAdj = s.toFloat(); saveNeeded = true; }
|
||||
|
||||
const char* getGravityFormula() { return gravityFormula.c_str(); }
|
||||
void setGravityFormula( String s ) { gravityFormula = s; saveNeeded = true; }
|
||||
|
||||
bool isGravityTempAdj() { return gravityTempAdj; }
|
||||
void setGravityTempAdj( bool b ) { gravityTempAdj = b; saveNeeded = true; }
|
||||
|
||||
char getGravityFormat() { return gravityFormat; }
|
||||
void setGravityFormat( char c ) { gravityFormat = c; saveNeeded = true; }
|
||||
bool isGravitySG() { return gravityFormat=='G'?false:true; };
|
||||
bool isGravityPlato() { return gravityFormat=='P'?false:true; };
|
||||
|
||||
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
|
||||
void setGyroCalibration( const RawGyroData &r ) { gyroCalibration = r; saveNeeded = true; }
|
||||
|
||||
// IO functions
|
||||
void createJson(DynamicJsonDocument& doc);
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
void checkFileSystem();
|
||||
bool isSaveNeeded() { return saveNeeded; };
|
||||
void setSaveNeeded() { saveNeeded = true; };
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern Config myConfig;
|
||||
|
||||
#endif // _CONFIG_H
|
||||
|
||||
// EOF
|
364
src/config.hpp
Normal file
@ -0,0 +1,364 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_CONFIG_HPP_
|
||||
#define SRC_CONFIG_HPP_
|
||||
|
||||
#include <helper.hpp>
|
||||
#include <resources.hpp>
|
||||
|
||||
#define CFG_JSON_BUFSIZE 3192
|
||||
|
||||
#define CFG_APPNAME "GravityMon" // Name of firmware
|
||||
#define CFG_FILENAME "/gravitymon.json" // Name of config file
|
||||
#define CFG_HW_FILENAME "/hardware.json" // Name of config file for hw
|
||||
|
||||
// Used for holding sensordata or sensoroffsets
|
||||
struct RawGyroData {
|
||||
int16_t ax; // Raw Acceleration
|
||||
int16_t ay;
|
||||
int16_t az;
|
||||
|
||||
int16_t gx; // Raw Position
|
||||
int16_t gy;
|
||||
int16_t gz;
|
||||
|
||||
int16_t temp; // Only for information (temperature of chip)
|
||||
};
|
||||
|
||||
// Used for holding formulaData (used for calculating formula on device)
|
||||
struct RawFormulaData {
|
||||
double a[5];
|
||||
double g[5];
|
||||
};
|
||||
|
||||
class HardwareConfig {
|
||||
private:
|
||||
int _wifiPortalTimeout = 120;
|
||||
float _maxFormulaCreationDeviation = 1.6;
|
||||
float _defaultCalibrationTemp = 20.0;
|
||||
int _gyroSensorMovingThreashold = 500;
|
||||
int _gyroReadCount = 50;
|
||||
int _gyroReadDelay = 3150; // us, empirical, to hold sampling to 200 Hz
|
||||
int _pushTimeout = 10; // seconds
|
||||
|
||||
public:
|
||||
int getWifiPortalTimeout() { return _wifiPortalTimeout; }
|
||||
void setWifiPortalTimeout(int t) { _wifiPortalTimeout = t; }
|
||||
float getMaxFormulaCreationDeviation() {
|
||||
return _maxFormulaCreationDeviation;
|
||||
}
|
||||
void setMaxFormulaCreationDeviation(float f) {
|
||||
_maxFormulaCreationDeviation = f;
|
||||
}
|
||||
float getDefaultCalibrationTemp() { return _defaultCalibrationTemp; }
|
||||
void SetDefaultCalibrationTemp(float t) { _defaultCalibrationTemp = t; }
|
||||
int getGyroSensorMovingThreashold() { return _gyroSensorMovingThreashold; }
|
||||
void setGyroSensorMovingThreashold(int t) { _gyroSensorMovingThreashold = t; }
|
||||
int getGyroReadCount() { return _gyroReadCount; }
|
||||
void setGyroReadCount(int c) { _gyroReadCount = c; }
|
||||
int getGyroReadDelay() { return _gyroReadDelay; }
|
||||
void setGyroReadDelay(int d) { _gyroReadDelay = d; }
|
||||
int getPushTimeout() { return _pushTimeout; }
|
||||
void setPushTimeout(int t) { _pushTimeout = t; }
|
||||
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
};
|
||||
|
||||
class Config {
|
||||
private:
|
||||
bool _saveNeeded = false;
|
||||
int _configVersion = 2;
|
||||
|
||||
// Device configuration
|
||||
String _id = "";
|
||||
String _mDNS = "";
|
||||
String _otaURL = "";
|
||||
char _tempFormat = 'C';
|
||||
float _voltageFactor = 0;
|
||||
float _tempSensorAdjC = 0;
|
||||
int _sleepInterval = 900;
|
||||
bool _gyroTemp = false;
|
||||
|
||||
// Wifi Config
|
||||
String _wifiSSID = "";
|
||||
String _wifiPASS = "";
|
||||
|
||||
// Push target settings
|
||||
String _brewfatherPushUrl = "";
|
||||
|
||||
String _token = "";
|
||||
|
||||
String _httpUrl = "";
|
||||
String _httpHeader[2] = {"Content-Type: application/json", ""};
|
||||
String _http2Url = "";
|
||||
String _http2Header[2] = {"Content-Type: application/json", ""};
|
||||
|
||||
String _influxDb2Url = "";
|
||||
String _influxDb2Org = "";
|
||||
String _influxDb2Bucket = "";
|
||||
String _influxDb2Token = "";
|
||||
|
||||
String _mqttUrl = "";
|
||||
int _mqttPort = 1883;
|
||||
String _mqttUser = "";
|
||||
String _mqttPass = "";
|
||||
|
||||
// Gravity and temperature calculations
|
||||
String _gravityFormula = "";
|
||||
bool _gravityTempAdj = false;
|
||||
char _gravityFormat = 'G';
|
||||
|
||||
// Gyro calibration and formula calculation data
|
||||
RawGyroData _gyroCalibration = {0, 0, 0, 0, 0, 0};
|
||||
RawFormulaData _formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
|
||||
|
||||
void formatFileSystem();
|
||||
|
||||
public:
|
||||
Config();
|
||||
const char* getID() { return _id.c_str(); }
|
||||
|
||||
const char* getMDNS() { return _mDNS.c_str(); }
|
||||
void setMDNS(String s) {
|
||||
_mDNS = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
int getConfigVersion() { return _configVersion; }
|
||||
|
||||
const bool isGyroTemp() { return _gyroTemp; }
|
||||
void setGyroTemp(bool b) {
|
||||
_gyroTemp = b;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getOtaURL() { return _otaURL.c_str(); }
|
||||
void setOtaURL(String s) {
|
||||
_otaURL = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isOtaActive() { return _otaURL.length() ? true : false; }
|
||||
bool isOtaSSL() { return _otaURL.startsWith("https://"); }
|
||||
|
||||
const char* getWifiSSID() { return _wifiSSID.c_str(); }
|
||||
void setWifiSSID(String s) {
|
||||
_wifiSSID = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getWifiPass() { return _wifiPASS.c_str(); }
|
||||
void setWifiPass(String s) {
|
||||
_wifiPASS = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Brewfather
|
||||
const char* getBrewfatherPushUrl() { return _brewfatherPushUrl.c_str(); }
|
||||
void setBrewfatherPushUrl(String s) {
|
||||
_brewfatherPushUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isBrewfatherActive() {
|
||||
return _brewfatherPushUrl.length() ? true : false;
|
||||
}
|
||||
|
||||
// Token parameter
|
||||
const char* getToken() { return _token.c_str(); }
|
||||
void setToken(String s) {
|
||||
_token = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Standard HTTP
|
||||
const char* getHttpUrl() { return _httpUrl.c_str(); }
|
||||
void setHttpUrl(String s) {
|
||||
_httpUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getHttpHeader(int idx) { return _httpHeader[idx].c_str(); }
|
||||
void setHttpHeader(String s, int idx) {
|
||||
_httpHeader[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive() { return _httpUrl.length() ? true : false; }
|
||||
bool isHttpSSL() { return _httpUrl.startsWith("https://"); }
|
||||
|
||||
const char* getHttp2Url() { return _http2Url.c_str(); }
|
||||
void setHttp2Url(String s) {
|
||||
_http2Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getHttp2Header(int idx) { return _http2Header[idx].c_str(); }
|
||||
void setHttp2Header(String s, int idx) {
|
||||
_http2Header[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttp2Active() { return _http2Url.length() ? true : false; }
|
||||
bool isHttp2SSL() { return _http2Url.startsWith("https://"); }
|
||||
|
||||
// InfluxDB2
|
||||
const char* getInfluxDb2PushUrl() { return _influxDb2Url.c_str(); }
|
||||
void setInfluxDb2PushUrl(String s) {
|
||||
_influxDb2Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
|
||||
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
|
||||
void setInfluxDb2PushOrg(String s) {
|
||||
_influxDb2Org = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getInfluxDb2PushBucket() { return _influxDb2Bucket.c_str(); }
|
||||
void setInfluxDb2PushBucket(String s) {
|
||||
_influxDb2Bucket = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getInfluxDb2PushToken() { return _influxDb2Token.c_str(); }
|
||||
void setInfluxDb2PushToken(String s) {
|
||||
_influxDb2Token = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// MQTT
|
||||
const char* getMqttUrl() { return _mqttUrl.c_str(); }
|
||||
void setMqttUrl(String s) {
|
||||
_mqttUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isMqttActive() { return _mqttUrl.length() ? true : false; }
|
||||
bool isMqttSSL() { return _mqttPort > 8000 ? true : false; }
|
||||
|
||||
int getMqttPort() { return _mqttPort; }
|
||||
void setMqttPort(String s) {
|
||||
_mqttPort = s.toInt();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setMqttPort(int i) {
|
||||
_mqttPort = i;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttUser() { return _mqttUser.c_str(); }
|
||||
void setMqttUser(String s) {
|
||||
_mqttUser = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttPass() { return _mqttPass.c_str(); }
|
||||
void setMqttPass(String s) {
|
||||
_mqttPass = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
int getSleepInterval() { return _sleepInterval; }
|
||||
void setSleepInterval(int v) {
|
||||
_sleepInterval = v;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setSleepInterval(String s) {
|
||||
_sleepInterval = s.toInt();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
char getTempFormat() { return _tempFormat; }
|
||||
void setTempFormat(char c) {
|
||||
if (c == 'C' || c == 'F') {
|
||||
_tempFormat = c;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
}
|
||||
bool isTempC() { return _tempFormat == 'C'; }
|
||||
bool isTempF() { return _tempFormat == 'F'; }
|
||||
|
||||
float getVoltageFactor() { return _voltageFactor; }
|
||||
void setVoltageFactor(float f) {
|
||||
_voltageFactor = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setVoltageFactor(String s) {
|
||||
_voltageFactor = s.toFloat();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
float getTempSensorAdjC() { return _tempSensorAdjC; }
|
||||
void setTempSensorAdjC(float f) {
|
||||
_tempSensorAdjC = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setTempSensorAdjC(String s) {
|
||||
_tempSensorAdjC = s.toFloat();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setTempSensorAdjF(String s) {
|
||||
_tempSensorAdjC = convertFtoC(s.toFloat());
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getGravityFormula() { return _gravityFormula.c_str(); }
|
||||
void setGravityFormula(String s) {
|
||||
_gravityFormula = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
bool isGravityTempAdj() { return _gravityTempAdj; }
|
||||
void setGravityTempAdj(bool b) {
|
||||
_gravityTempAdj = b;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
char getGravityFormat() { return _gravityFormat; }
|
||||
void setGravityFormat(char c) {
|
||||
if (c == 'G' || c == 'P') {
|
||||
_gravityFormat = c;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
}
|
||||
bool isGravitySG() { return _gravityFormat == 'G'; }
|
||||
bool isGravityPlato() { return _gravityFormat == 'P'; }
|
||||
|
||||
const RawGyroData& getGyroCalibration() { return _gyroCalibration; }
|
||||
void setGyroCalibration(const RawGyroData& r) {
|
||||
_gyroCalibration = r;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const RawFormulaData& getFormulaData() { return _formulaData; }
|
||||
void setFormulaData(const RawFormulaData& r) {
|
||||
_formulaData = r;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// IO functions
|
||||
void createJson(DynamicJsonDocument& doc);
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
void checkFileSystem();
|
||||
bool isSaveNeeded() { return _saveNeeded; }
|
||||
void setSaveNeeded() { _saveNeeded = true; }
|
||||
};
|
||||
|
||||
extern Config myConfig;
|
||||
extern HardwareConfig myHardwareConfig;
|
||||
|
||||
#endif // SRC_CONFIG_HPP_
|
||||
|
||||
// EOF
|
761
src/gyro.cpp
@ -1,359 +1,402 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "gyro.h"
|
||||
#include "helper.h"
|
||||
|
||||
GyroSensor myGyro;
|
||||
|
||||
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
|
||||
#define SENSOR_MOVING_THREASHOLD 500
|
||||
#define SENSOR_READ_COUNT 50
|
||||
#define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz
|
||||
|
||||
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing calibration
|
||||
//#define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
|
||||
|
||||
//
|
||||
// Initialize the sensor chip.
|
||||
//
|
||||
bool GyroSensor::setup() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Setting up hardware." CR));
|
||||
#endif
|
||||
Wire.begin(D3, D4);
|
||||
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
|
||||
accelgyro.initialize();
|
||||
|
||||
if( !accelgyro.testConnection() ) {
|
||||
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
|
||||
sensorConnected = false;
|
||||
} else {
|
||||
|
||||
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
|
||||
sensorConnected = true;
|
||||
|
||||
// Configure the sensor
|
||||
accelgyro.setTempSensorEnabled(true);
|
||||
//accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in .initalize()
|
||||
//accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); // Set in .initalize()
|
||||
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
|
||||
#if defined( GYRO_USE_INTERRUPT )
|
||||
// Alternative method to read data, let the MPU signal when sampling is done.
|
||||
accelgyro.setRate(17);
|
||||
accelgyro.setInterruptDrive(1);
|
||||
accelgyro.setInterruptMode(1);
|
||||
accelgyro.setInterruptLatch(0);
|
||||
accelgyro.setIntDataReadyEnabled(true);
|
||||
#endif
|
||||
|
||||
#if defined ( GYRO_CALIBRATE_STARTUP )
|
||||
// Run the calibration at start, useful for testing.
|
||||
calibrateSensor();
|
||||
#endif
|
||||
|
||||
// Once we have calibration values stored we just apply them from the config.
|
||||
calibrationOffset = myConfig.getGyroCalibration();
|
||||
applyCalibration();
|
||||
}
|
||||
|
||||
return sensorConnected;
|
||||
}
|
||||
|
||||
//
|
||||
// Set sensor in sleep mode to conserve battery
|
||||
//
|
||||
void GyroSensor::enterSleep() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Setting up hardware." CR));
|
||||
#endif
|
||||
accelgyro.setSleepEnabled( true );
|
||||
}
|
||||
|
||||
//
|
||||
// Do a number of reads to get a more stable value.
|
||||
//
|
||||
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations, const int delayTime) {
|
||||
RawGyroDataL average = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR), noIterations, delayTime );
|
||||
#endif
|
||||
|
||||
// Set some initial values
|
||||
#if defined( GYRO_SHOW_MINMAX )
|
||||
RawGyroData min, max;
|
||||
//accelgyro.getRotation( &min.gx, &min.gy, &min.gz );
|
||||
accelgyro.getAcceleration( &min.ax, &min.ay, &min.az );
|
||||
min.temp = accelgyro.getTemperature();
|
||||
max = min;
|
||||
#endif
|
||||
for(int cnt = 0; cnt < noIterations ; cnt ++) {
|
||||
|
||||
#if defined( GYRO_USE_INTERRUPT )
|
||||
while( accelgyro.getIntDataReadyStatus() == 0) {
|
||||
delayMicroseconds( 1 );
|
||||
}
|
||||
#endif
|
||||
|
||||
//accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz );
|
||||
//accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az );
|
||||
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
|
||||
raw.temp = accelgyro.getTemperature();
|
||||
|
||||
average.ax += raw.ax;
|
||||
average.ay += raw.ay;
|
||||
average.az += raw.az;
|
||||
average.gx += raw.gx;
|
||||
average.gy += raw.gy;
|
||||
average.gz += raw.gz;
|
||||
average.temp += raw.temp;
|
||||
|
||||
// Log what the minium value is
|
||||
#if defined( GYRO_SHOW_MINMAX )
|
||||
if( raw.ax < min.ax ) min.ax = raw.ax;
|
||||
if( raw.ay < min.ay ) min.ay = raw.ay;
|
||||
if( raw.az < min.az ) min.az = raw.az;
|
||||
if( raw.gx < min.gx ) min.gx = raw.gx;
|
||||
if( raw.gy < min.gy ) min.gy = raw.gy;
|
||||
if( raw.gz < min.gz ) min.gz = raw.gz;
|
||||
if( raw.temp < min.temp ) min.temp = raw.temp;
|
||||
|
||||
// Log what the maximum value is
|
||||
if( raw.ax > max.ax ) max.ax = raw.ax;
|
||||
if( raw.ay > max.ay ) max.ay = raw.ay;
|
||||
if( raw.az > max.az ) max.az = raw.az;
|
||||
if( raw.gx > max.gx ) max.gx = raw.gx;
|
||||
if( raw.gy > max.gy ) max.gy = raw.gy;
|
||||
if( raw.gz > max.gz ) max.gz = raw.gz;
|
||||
if( raw.temp > max.temp ) max.temp = raw.temp;
|
||||
#endif
|
||||
|
||||
#if !defined( GYRO_USE_INTERRUPT )
|
||||
delayMicroseconds( delayTime );
|
||||
#endif
|
||||
}
|
||||
|
||||
raw.ax = average.ax/noIterations;
|
||||
raw.ay = average.ay/noIterations;
|
||||
raw.az = average.az/noIterations;
|
||||
raw.gx = average.gx/noIterations;
|
||||
raw.gy = average.gy/noIterations;
|
||||
raw.gz = average.gz/noIterations;
|
||||
raw.temp = average.temp/noIterations;
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
#if defined( GYRO_SHOW_MINMAX )
|
||||
Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax, min.ay, min.az, min.gx, min.gy, min.gz, min.temp );
|
||||
Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax, max.ay, max.az, max.gx, max.gy, max.gz, max.temp );
|
||||
#endif
|
||||
Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax, raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp );
|
||||
//Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR), average.ax/noIterations, average.ay/noIterations, average.az/noIterations,
|
||||
// average.gx/noIterations, average.gy/noIterations, average.gz/noIterations );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Calcuate the angles (tilt)
|
||||
//
|
||||
float GyroSensor::calculateAngle(RawGyroData &raw) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Calculating the angle." CR) );
|
||||
#endif
|
||||
|
||||
// Smooth out the readings to we can have a more stable angle/tilt.
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of 16,384 LSB(Count)/g.
|
||||
// Gyroscope full scale range of +/- 250 °/s with Sensitivity Scale Factor of 131 LSB (Count)/°/s.
|
||||
float ax = ((float) raw.ax)/16384,
|
||||
ay = ((float) raw.ay)/16384,
|
||||
az = ((float) raw.az)/16384;
|
||||
|
||||
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
|
||||
float v = (acos( ay / sqrt( ax*ax + ay*ay + az*az ) ) *180.0 / PI);
|
||||
//Log.notice(F("GYRO: angle = %F." CR), v );
|
||||
//double v = (acos( raw.az / sqrt( raw.ax*raw.ax + raw.ay*raw.ay + raw.az*raw.az ) ) *180.0 / PI);
|
||||
//Log.notice(F("GYRO: angle = %F." CR), v );
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: angle = %F." CR), v );
|
||||
#endif
|
||||
return v;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the values are high that indicate that the sensor is moving.
|
||||
//
|
||||
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Checking for sensor movement." CR) );
|
||||
#endif
|
||||
|
||||
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
|
||||
|
||||
if( x>SENSOR_MOVING_THREASHOLD || y>SENSOR_MOVING_THREASHOLD || z>SENSOR_MOVING_THREASHOLD ) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), SENSOR_MOVING_THREASHOLD, x, y, z);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Read the tilt angle from the gyro.
|
||||
//
|
||||
bool GyroSensor::read() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Getting new gyro position." CR) );
|
||||
#endif
|
||||
|
||||
readSensor( lastGyroData, SENSOR_READ_COUNT, SENSOR_READ_DELAY ); // Last param is unused if GYRO_USE_INTERRUPT is defined.
|
||||
|
||||
// If the sensor is unstable we return false to signal we dont have valid value
|
||||
if( isSensorMoving(lastGyroData) ) {
|
||||
Log.notice(F("GYRO: Sensor is moving." CR) );
|
||||
validValue = false;
|
||||
} else {
|
||||
validValue = true;
|
||||
angle = calculateAngle( lastGyroData );
|
||||
//Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), raw.ax, raw.ay, raw.az, angle );
|
||||
}
|
||||
|
||||
sensorTemp = ((float) lastGyroData.temp) / 340 + 36.53;
|
||||
|
||||
// The first read value is close to the DS18 value according to my tests, if more reads are
|
||||
// done then the gyro temp will increase to much
|
||||
if( initialSensorTemp == INVALID_TEMPERATURE )
|
||||
initialSensorTemp = sensorTemp;
|
||||
|
||||
return validValue;
|
||||
}
|
||||
|
||||
//
|
||||
// Dump the stored calibration values.
|
||||
//
|
||||
void GyroSensor::dumpCalibration() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax, calibrationOffset.ay, calibrationOffset.az );
|
||||
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx, calibrationOffset.gy, calibrationOffset.gz );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Update the sensor with out calculated offsets.
|
||||
//
|
||||
void GyroSensor::applyCalibration() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR) );
|
||||
#endif
|
||||
if( ( calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az + calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz ) == 0 ) {
|
||||
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR) );
|
||||
return;
|
||||
}
|
||||
|
||||
accelgyro.setXAccelOffset( calibrationOffset.ax );
|
||||
accelgyro.setYAccelOffset( calibrationOffset.ay );
|
||||
accelgyro.setZAccelOffset( calibrationOffset.az );
|
||||
accelgyro.setXGyroOffset( calibrationOffset.gx );
|
||||
accelgyro.setYGyroOffset( calibrationOffset.gy );
|
||||
accelgyro.setZGyroOffset( calibrationOffset.gz );
|
||||
}
|
||||
|
||||
//
|
||||
// Calculate the offsets for calibration.
|
||||
//
|
||||
void GyroSensor::calibrateSensor() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Calibrating sensor" CR) );
|
||||
#endif
|
||||
//accelgyro.PrintActiveOffsets();
|
||||
//Serial.print( CR );
|
||||
|
||||
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
|
||||
accelgyro.CalibrateAccel(6); // 6 = 600 readings
|
||||
accelgyro.CalibrateGyro(6);
|
||||
|
||||
accelgyro.PrintActiveOffsets();
|
||||
Serial.print( CR );
|
||||
|
||||
calibrationOffset.ax = accelgyro.getXAccelOffset();
|
||||
calibrationOffset.ay = accelgyro.getYAccelOffset();
|
||||
calibrationOffset.az = accelgyro.getZAccelOffset();
|
||||
calibrationOffset.gx = accelgyro.getXGyroOffset();
|
||||
calibrationOffset.gy = accelgyro.getYGyroOffset();
|
||||
calibrationOffset.gz = accelgyro.getZGyroOffset();
|
||||
|
||||
// Save the calibrated values
|
||||
myConfig.setGyroCalibration( calibrationOffset );
|
||||
myConfig.saveFile();
|
||||
}
|
||||
|
||||
//
|
||||
// Calibrate the device.
|
||||
//
|
||||
void GyroSensor::debug() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("GYRO: Debug - Clock src %d." CR), accelgyro.getClockSource() );
|
||||
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID() );
|
||||
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode() );
|
||||
Log.verbose(F("GYRO: Debug - DMP on %s." CR), accelgyro.getDMPEnabled()?"on":"off" );
|
||||
Log.verbose(F("GYRO: Debug - Acc range %d." CR), accelgyro.getFullScaleAccelRange() );
|
||||
Log.verbose(F("GYRO: Debug - Gyr range %d." CR), accelgyro.getFullScaleGyroRange() );
|
||||
Log.verbose(F("GYRO: Debug - Int %s." CR), accelgyro.getIntEnabled()?"on":"off" );
|
||||
Log.verbose(F("GYRO: Debug - Clock %d." CR), accelgyro.getMasterClockSpeed() );
|
||||
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate() );
|
||||
Log.verbose(F("GYRO: Debug - Gyro range %d." CR), accelgyro.getFullScaleGyroRange() );
|
||||
// Log.verbose(F("GYRO: Debug - I2C bypass %s." CR), accelgyro.getI2CBypassEnabled()?"on":"off" );
|
||||
// Log.verbose(F("GYRO: Debug - I2C master %s." CR), accelgyro.getI2CMasterModeEnabled()?"on":"off" );
|
||||
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR), accelgyro.getAccelXSelfTestFactoryTrim() );
|
||||
Log.verbose(F("GYRO: Debug - Acc FactY %d." CR), accelgyro.getAccelYSelfTestFactoryTrim() );
|
||||
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR), accelgyro.getAccelZSelfTestFactoryTrim() );
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR), accelgyro.getGyroXSelfTestFactoryTrim() );
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR), accelgyro.getGyroYSelfTestFactoryTrim() );
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR), accelgyro.getGyroZSelfTestFactoryTrim() );
|
||||
|
||||
switch( accelgyro.getFullScaleAccelRange() ) {
|
||||
case 0:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR));
|
||||
break;
|
||||
case 1:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR));
|
||||
break;
|
||||
case 2:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR));
|
||||
break;
|
||||
case 3:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR));
|
||||
break;
|
||||
}
|
||||
|
||||
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR), accelgyro.getXAccelOffset(), calibrationOffset.az );
|
||||
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR), accelgyro.getYAccelOffset(), calibrationOffset.ay );
|
||||
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR), accelgyro.getZAccelOffset(), calibrationOffset.az );
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR), accelgyro.getXGyroOffset(), calibrationOffset.gx );
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR), accelgyro.getYGyroOffset(), calibrationOffset.gy );
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR), accelgyro.getZGyroOffset(), calibrationOffset.gz );
|
||||
#endif
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <gyro.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
GyroSensor myGyro;
|
||||
MPU6050 accelgyro;
|
||||
|
||||
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
|
||||
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
|
||||
// calibration
|
||||
// #define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
|
||||
|
||||
//
|
||||
// Initialize the sensor chip.
|
||||
//
|
||||
bool GyroSensor::setup() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Setting up hardware." CR));
|
||||
#endif
|
||||
Wire.begin(PIN_SDA, PIN_SCL);
|
||||
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
|
||||
// compilation difficulties
|
||||
|
||||
if (!accelgyro.testConnection()) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("GYRO: Failed to connect to gyro, is it connected?"));
|
||||
_sensorConnected = false;
|
||||
} else {
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
|
||||
#endif
|
||||
accelgyro.initialize();
|
||||
_sensorConnected = true;
|
||||
|
||||
// Configure the sensor
|
||||
accelgyro.setTempSensorEnabled(true);
|
||||
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
|
||||
#if defined(GYRO_USE_INTERRUPT)
|
||||
// Alternative method to read data, let the MPU signal when sampling is
|
||||
// done.
|
||||
accelgyro.setRate(17);
|
||||
accelgyro.setInterruptDrive(1);
|
||||
accelgyro.setInterruptMode(1);
|
||||
accelgyro.setInterruptLatch(0);
|
||||
accelgyro.setIntDataReadyEnabled(true);
|
||||
#endif
|
||||
|
||||
#if defined(GYRO_CALIBRATE_STARTUP)
|
||||
// Run the calibration at start, useful for testing.
|
||||
calibrateSensor();
|
||||
#endif
|
||||
|
||||
// Once we have calibration values stored we just apply them from the
|
||||
// config.
|
||||
_calibrationOffset = myConfig.getGyroCalibration();
|
||||
applyCalibration();
|
||||
}
|
||||
return _sensorConnected;
|
||||
}
|
||||
|
||||
//
|
||||
// Set sensor in sleep mode to conserve battery
|
||||
//
|
||||
void GyroSensor::enterSleep() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Setting up hardware." CR));
|
||||
#endif
|
||||
accelgyro.setSleepEnabled(true);
|
||||
}
|
||||
|
||||
//
|
||||
// Do a number of reads to get a more stable value.
|
||||
//
|
||||
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations,
|
||||
const int delayTime) {
|
||||
RawGyroDataL average = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR),
|
||||
noIterations, delayTime);
|
||||
#endif
|
||||
|
||||
// Set some initial values
|
||||
#if defined(GYRO_SHOW_MINMAX)
|
||||
RawGyroData min, max;
|
||||
accelgyro.getAcceleration(&min.ax, &min.ay, &min.az);
|
||||
min.temp = accelgyro.getTemperature();
|
||||
max = min;
|
||||
#endif
|
||||
for (int cnt = 0; cnt < noIterations; cnt++) {
|
||||
#if defined(GYRO_USE_INTERRUPT)
|
||||
while (accelgyro.getIntDataReadyStatus() == 0) {
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
|
||||
raw.temp = accelgyro.getTemperature();
|
||||
|
||||
average.ax += raw.ax;
|
||||
average.ay += raw.ay;
|
||||
average.az += raw.az;
|
||||
average.gx += raw.gx;
|
||||
average.gy += raw.gy;
|
||||
average.gz += raw.gz;
|
||||
average.temp += raw.temp;
|
||||
|
||||
// Log what the minium value is
|
||||
#if defined(GYRO_SHOW_MINMAX)
|
||||
if (raw.ax < min.ax) min.ax = raw.ax;
|
||||
if (raw.ay < min.ay) min.ay = raw.ay;
|
||||
if (raw.az < min.az) min.az = raw.az;
|
||||
if (raw.gx < min.gx) min.gx = raw.gx;
|
||||
if (raw.gy < min.gy) min.gy = raw.gy;
|
||||
if (raw.gz < min.gz) min.gz = raw.gz;
|
||||
if (raw.temp < min.temp) min.temp = raw.temp;
|
||||
|
||||
// Log what the maximum value is
|
||||
if (raw.ax > max.ax) max.ax = raw.ax;
|
||||
if (raw.ay > max.ay) max.ay = raw.ay;
|
||||
if (raw.az > max.az) max.az = raw.az;
|
||||
if (raw.gx > max.gx) max.gx = raw.gx;
|
||||
if (raw.gy > max.gy) max.gy = raw.gy;
|
||||
if (raw.gz > max.gz) max.gz = raw.gz;
|
||||
if (raw.temp > max.temp) max.temp = raw.temp;
|
||||
#endif
|
||||
|
||||
#if !defined(GYRO_USE_INTERRUPT)
|
||||
delayMicroseconds(delayTime);
|
||||
#endif
|
||||
}
|
||||
|
||||
raw.ax = average.ax / noIterations;
|
||||
raw.ay = average.ay / noIterations;
|
||||
raw.az = average.az / noIterations;
|
||||
raw.gx = average.gx / noIterations;
|
||||
raw.gy = average.gy / noIterations;
|
||||
raw.gz = average.gz / noIterations;
|
||||
raw.temp = average.temp / noIterations;
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
#if defined(GYRO_SHOW_MINMAX)
|
||||
Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax,
|
||||
min.ay, min.az, min.gx, min.gy, min.gz, min.temp);
|
||||
Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax,
|
||||
max.ay, max.az, max.gx, max.gy, max.gz, max.temp);
|
||||
#endif
|
||||
Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax,
|
||||
raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp);
|
||||
// Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR),
|
||||
// average.ax/noIterations, average.ay/noIterations, average.az/noIterations,
|
||||
// average.gx/noIterations,
|
||||
// average.gy/noIterations,
|
||||
// average.gz/noIterations
|
||||
// );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Calcuate the angles (tilt)
|
||||
//
|
||||
float GyroSensor::calculateAngle(RawGyroData &raw) {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Calculating the angle." CR));
|
||||
#endif
|
||||
|
||||
// Smooth out the readings to we can have a more stable angle/tilt.
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of
|
||||
// 16,384 LSB(Count)/g. Gyroscope full scale range of +/- 250 °/s with
|
||||
// Sensitivity Scale Factor of 131 LSB (Count)/°/s.
|
||||
float ax = (static_cast<float>(raw.ax)) / 16384,
|
||||
ay = (static_cast<float>(raw.ay)) / 16384,
|
||||
az = (static_cast<float>(raw.az)) / 16384;
|
||||
|
||||
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
|
||||
float vY = (acos(abs(ay) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
//float vZ = (acos(abs(az) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
//float vX = (acos(abs(ax) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
//Log.notice(F("GYRO: angleX= %F." CR), vX);
|
||||
Log.notice(F("GYRO: angleY= %F." CR), vY);
|
||||
//Log.notice(F("GYRO: angleZ= %F." CR), vZ);
|
||||
#endif
|
||||
return vY;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the values are high that indicate that the sensor is moving.
|
||||
//
|
||||
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Checking for sensor movement." CR));
|
||||
#endif
|
||||
|
||||
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
|
||||
int threashold = myHardwareConfig.getGyroSensorMovingThreashold();
|
||||
|
||||
if (x > threashold || y > threashold || z > threashold) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
|
||||
y, z);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Read the tilt angle from the gyro.
|
||||
//
|
||||
bool GyroSensor::read() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Getting new gyro position." CR));
|
||||
#endif
|
||||
|
||||
if (!_sensorConnected) return false;
|
||||
|
||||
readSensor(
|
||||
_lastGyroData, myHardwareConfig.getGyroReadCount(),
|
||||
myHardwareConfig.getGyroReadDelay()); // Last param is unused if
|
||||
// GYRO_USE_INTERRUPT is defined.
|
||||
|
||||
// If the sensor is unstable we return false to signal we dont have valid
|
||||
// value
|
||||
if (isSensorMoving(_lastGyroData)) {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Sensor is moving." CR));
|
||||
#endif
|
||||
_validValue = false;
|
||||
} else {
|
||||
_validValue = true;
|
||||
_angle = calculateAngle(_lastGyroData);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Sensor values %d,%d,%d\t%F" CR), _lastGyroData.ax,
|
||||
_lastGyroData.ay, _lastGyroData.az, _angle);
|
||||
#endif
|
||||
}
|
||||
|
||||
_sensorTemp = (static_cast<float>(_lastGyroData.temp)) / 340 + 36.53;
|
||||
|
||||
// The first read value is close to the DS18 value according to my tests, if
|
||||
// more reads are done then the gyro temp will increase to much
|
||||
if (_initialSensorTemp == INVALID_TEMPERATURE)
|
||||
_initialSensorTemp = _sensorTemp;
|
||||
|
||||
return _validValue;
|
||||
}
|
||||
|
||||
//
|
||||
// Dump the stored calibration values.
|
||||
//
|
||||
void GyroSensor::dumpCalibration() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), _calibrationOffset.ax,
|
||||
_calibrationOffset.ay, _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), _calibrationOffset.gx,
|
||||
_calibrationOffset.gy, _calibrationOffset.gz);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Update the sensor with out calculated offsets.
|
||||
//
|
||||
void GyroSensor::applyCalibration() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR));
|
||||
#endif
|
||||
|
||||
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
|
||||
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
|
||||
0) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("GYRO: No valid calibration values, please calibrate the device."));
|
||||
return;
|
||||
}
|
||||
|
||||
accelgyro.setXAccelOffset(_calibrationOffset.ax);
|
||||
accelgyro.setYAccelOffset(_calibrationOffset.ay);
|
||||
accelgyro.setZAccelOffset(_calibrationOffset.az);
|
||||
accelgyro.setXGyroOffset(_calibrationOffset.gx);
|
||||
accelgyro.setYGyroOffset(_calibrationOffset.gy);
|
||||
accelgyro.setZGyroOffset(_calibrationOffset.gz);
|
||||
}
|
||||
|
||||
//
|
||||
// Calculate the offsets for calibration.
|
||||
//
|
||||
void GyroSensor::calibrateSensor() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Calibrating sensor" CR));
|
||||
#endif
|
||||
// accelgyro.PrintActiveOffsets();
|
||||
// Serial.print( CR );
|
||||
|
||||
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
|
||||
accelgyro.CalibrateAccel(6); // 6 = 600 readings
|
||||
accelgyro.CalibrateGyro(6);
|
||||
|
||||
accelgyro.PrintActiveOffsets();
|
||||
Serial.print(CR);
|
||||
|
||||
_calibrationOffset.ax = accelgyro.getXAccelOffset();
|
||||
_calibrationOffset.ay = accelgyro.getYAccelOffset();
|
||||
_calibrationOffset.az = accelgyro.getZAccelOffset();
|
||||
_calibrationOffset.gx = accelgyro.getXGyroOffset();
|
||||
_calibrationOffset.gy = accelgyro.getYGyroOffset();
|
||||
_calibrationOffset.gz = accelgyro.getZGyroOffset();
|
||||
|
||||
// Save the calibrated values
|
||||
myConfig.setGyroCalibration(_calibrationOffset);
|
||||
myConfig.saveFile();
|
||||
}
|
||||
|
||||
//
|
||||
// Calibrate the device.
|
||||
//
|
||||
void GyroSensor::debug() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Debug - Clock src %d." CR),
|
||||
accelgyro.getClockSource());
|
||||
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID());
|
||||
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode());
|
||||
Log.verbose(F("GYRO: Debug - DMP on %s." CR),
|
||||
accelgyro.getDMPEnabled() ? "on" : "off");
|
||||
Log.verbose(F("GYRO: Debug - Acc range %d." CR),
|
||||
accelgyro.getFullScaleAccelRange());
|
||||
Log.verbose(F("GYRO: Debug - Gyr range %d." CR),
|
||||
accelgyro.getFullScaleGyroRange());
|
||||
Log.verbose(F("GYRO: Debug - Int %s." CR),
|
||||
accelgyro.getIntEnabled() ? "on" : "off");
|
||||
Log.verbose(F("GYRO: Debug - Clock %d." CR),
|
||||
accelgyro.getMasterClockSpeed());
|
||||
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate());
|
||||
Log.verbose(F("GYRO: Debug - Gyro range %d." CR),
|
||||
accelgyro.getFullScaleGyroRange());
|
||||
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR),
|
||||
accelgyro.getAccelXSelfTestFactoryTrim());
|
||||
Log.verbose(F("GYRO: Debug - Acc FactY %d." CR),
|
||||
accelgyro.getAccelYSelfTestFactoryTrim());
|
||||
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR),
|
||||
accelgyro.getAccelZSelfTestFactoryTrim());
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR),
|
||||
accelgyro.getGyroXSelfTestFactoryTrim());
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR),
|
||||
accelgyro.getGyroYSelfTestFactoryTrim());
|
||||
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR),
|
||||
accelgyro.getGyroZSelfTestFactoryTrim());
|
||||
|
||||
switch (accelgyro.getFullScaleAccelRange()) {
|
||||
case 0:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR));
|
||||
break;
|
||||
case 1:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR));
|
||||
break;
|
||||
case 2:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR));
|
||||
break;
|
||||
case 3:
|
||||
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR));
|
||||
break;
|
||||
}
|
||||
|
||||
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR),
|
||||
accelgyro.getXAccelOffset(), _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR),
|
||||
accelgyro.getYAccelOffset(), _calibrationOffset.ay);
|
||||
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR),
|
||||
accelgyro.getZAccelOffset(), _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR),
|
||||
accelgyro.getXGyroOffset(), _calibrationOffset.gx);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR),
|
||||
accelgyro.getYGyroOffset(), _calibrationOffset.gy);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR),
|
||||
accelgyro.getZGyroOffset(), _calibrationOffset.gz);
|
||||
#endif
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
87
src/gyro.h
@ -1,87 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _GYRO_H
|
||||
#define _GYRO_H
|
||||
|
||||
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
|
||||
//#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
|
||||
|
||||
// Includes
|
||||
#include <arduino.h>
|
||||
#include "MPU6050.h"
|
||||
#include "config.h"
|
||||
|
||||
// Classes
|
||||
struct RawGyroDataL { // Used for average multiple readings
|
||||
long ax; // Raw Acceleration
|
||||
long ay;
|
||||
long az;
|
||||
|
||||
long gx; // Raw Position
|
||||
long gy;
|
||||
long gz;
|
||||
|
||||
long temp; // Only for information (temperature of chip)
|
||||
};
|
||||
|
||||
#define INVALID_TEMPERATURE -273
|
||||
|
||||
class GyroSensor {
|
||||
private:
|
||||
MPU6050 accelgyro;
|
||||
bool sensorConnected = false;
|
||||
bool validValue = false;
|
||||
float angle = 0;
|
||||
float sensorTemp = 0;
|
||||
float initialSensorTemp = INVALID_TEMPERATURE;
|
||||
RawGyroData calibrationOffset;
|
||||
RawGyroData lastGyroData;
|
||||
|
||||
void debug();
|
||||
void applyCalibration();
|
||||
void dumpCalibration();
|
||||
void readSensor(RawGyroData &raw, const int noIterations = 100, const int delayTime = 1);
|
||||
bool isSensorMoving(RawGyroData &raw);
|
||||
float calculateAngle(RawGyroData &raw);
|
||||
|
||||
public:
|
||||
bool setup();
|
||||
bool read();
|
||||
void calibrateSensor();
|
||||
|
||||
const RawGyroData& getLastGyroData() { return lastGyroData; }
|
||||
float getAngle() { return angle; };
|
||||
float getSensorTempC() { return sensorTemp; };
|
||||
float getInitialSensorTempC() { return initialSensorTemp; };
|
||||
bool isConnected() { return sensorConnected; };
|
||||
bool hasValue() { return validValue; };
|
||||
void enterSleep();
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern GyroSensor myGyro;
|
||||
|
||||
#endif // _GYRO_H
|
||||
|
||||
// EOF
|
84
src/gyro.hpp
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_GYRO_HPP_
|
||||
#define SRC_GYRO_HPP_
|
||||
|
||||
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
|
||||
// #define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
|
||||
|
||||
#include <MPU6050.h>
|
||||
|
||||
#include <config.hpp>
|
||||
|
||||
struct RawGyroDataL { // Used for average multiple readings
|
||||
int32_t ax; // Raw Acceleration
|
||||
int32_t ay;
|
||||
int32_t az;
|
||||
|
||||
int32_t gx; // Raw Position
|
||||
int32_t gy;
|
||||
int32_t gz;
|
||||
|
||||
int32_t temp; // Only for information (temperature of chip)
|
||||
};
|
||||
|
||||
#define INVALID_TEMPERATURE -273
|
||||
|
||||
class GyroSensor {
|
||||
private:
|
||||
bool _sensorConnected = false;
|
||||
bool _validValue = false;
|
||||
float _angle = 0;
|
||||
float _sensorTemp = 0;
|
||||
float _initialSensorTemp = INVALID_TEMPERATURE;
|
||||
RawGyroData _calibrationOffset;
|
||||
RawGyroData _lastGyroData;
|
||||
|
||||
void debug();
|
||||
void applyCalibration();
|
||||
void dumpCalibration();
|
||||
void readSensor(RawGyroData &raw, const int noIterations = 100,
|
||||
const int delayTime = 1);
|
||||
bool isSensorMoving(RawGyroData &raw);
|
||||
float calculateAngle(RawGyroData &raw);
|
||||
|
||||
public:
|
||||
bool setup();
|
||||
bool read();
|
||||
void calibrateSensor();
|
||||
|
||||
const RawGyroData &getLastGyroData() { return _lastGyroData; }
|
||||
float getAngle() { return _angle; }
|
||||
float getSensorTempC() { return _sensorTemp; }
|
||||
float getInitialSensorTempC() { return _initialSensorTemp; }
|
||||
bool isConnected() { return _sensorConnected; }
|
||||
bool hasValue() { return _validValue; }
|
||||
void enterSleep();
|
||||
};
|
||||
|
||||
extern GyroSensor myGyro;
|
||||
|
||||
#endif // SRC_GYRO_HPP_
|
||||
|
||||
// EOF
|
752
src/helper.cpp
@ -1,256 +1,496 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "gyro.h"
|
||||
#include "tempsensor.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
|
||||
SerialDebug mySerial;
|
||||
BatteryVoltage myBatteryVoltage;
|
||||
|
||||
//
|
||||
// Print the heap information.
|
||||
//
|
||||
void printHeap() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getHeapFragmentation(), ESP.getFreeSketchSpace()/1024 );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Enter deep sleep for the defined duration (Argument is seconds)
|
||||
//
|
||||
void deepSleep(int t) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t );
|
||||
#endif
|
||||
uint64_t wake = t * 1000000;
|
||||
ESP.deepSleep( wake );
|
||||
}
|
||||
|
||||
//
|
||||
// Print the build options used
|
||||
//
|
||||
void printBuildOptions() {
|
||||
Log.notice( F("Build options: %s LOGLEVEL %d "
|
||||
#ifdef SKIP_SLEEPMODE
|
||||
"SKIP_SLEEP "
|
||||
#endif
|
||||
#ifdef EMBED_HTML
|
||||
"EMBED_HTML "
|
||||
#endif
|
||||
#ifdef COLLECT_PERFDATA
|
||||
"PERFDATA "
|
||||
#endif
|
||||
#ifdef ACTIVATE_OTA
|
||||
"OTA "
|
||||
#endif
|
||||
CR), CFG_APPVER, LOG_LEVEL );
|
||||
}
|
||||
|
||||
//
|
||||
// Configure serial debug output
|
||||
//
|
||||
SerialDebug::SerialDebug(const long serialSpeed) {
|
||||
// Start serial with auto-detected rate (default to defined BAUD)
|
||||
Serial.flush();
|
||||
Serial.begin(serialSpeed);
|
||||
|
||||
getLog()->begin(LOG_LEVEL, &Serial, true);
|
||||
getLog()->setPrefix(printTimestamp);
|
||||
getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed);
|
||||
}
|
||||
|
||||
//
|
||||
// Print the timestamp (ms since start of device)
|
||||
//
|
||||
void printTimestamp(Print* _logOutput, int _logLevel) {
|
||||
char c[12];
|
||||
sprintf(c, "%10lu ", millis());
|
||||
_logOutput->print(c);
|
||||
}
|
||||
|
||||
//
|
||||
// Read and calculate the battery voltage
|
||||
//
|
||||
void BatteryVoltage::read() {
|
||||
// The analog pin can only handle 3.3V maximum voltage so we need to reduce the voltage (from max 5V)
|
||||
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
|
||||
int v = analogRead( A0 );
|
||||
batteryLevel = ((3.3/1023)*v)*factor;
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR), factor, v, batteryLevel );
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined( COLLECT_PERFDATA )
|
||||
|
||||
PerfLogging myPerfLogging;
|
||||
|
||||
//
|
||||
// Clear the current cache
|
||||
//
|
||||
void PerfLogging::clear() {
|
||||
// Clear the measurements
|
||||
if( first == 0 )
|
||||
return;
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
do {
|
||||
pe->max = 0;
|
||||
pe->start = 0;
|
||||
pe->end = 0;
|
||||
pe->mA = 0;
|
||||
pe->V = 0;
|
||||
pe = pe->next;
|
||||
} while( pe != 0 );
|
||||
}
|
||||
|
||||
//
|
||||
// Start measuring this performance point
|
||||
//
|
||||
void PerfLogging::start( const char* key ) {
|
||||
PerfEntry* pe = add( key );
|
||||
pe->start = millis();
|
||||
}
|
||||
|
||||
//
|
||||
// Finalize measuring of this performance point
|
||||
//
|
||||
void PerfLogging::stop( const char* key ) {
|
||||
PerfEntry* pe = find( key );
|
||||
|
||||
if( pe != 0 ) {
|
||||
pe->end = millis();
|
||||
|
||||
unsigned long t = pe->end - pe->start;
|
||||
|
||||
if( t > pe->max )
|
||||
pe->max = t;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the collected performance data
|
||||
//
|
||||
void PerfLogging::print() {
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while( pe != 0 ) {
|
||||
//Log.notice( F("PERF: %s=%l ms (%l, %l)" CR), pe->key, (pe->end - pe->start), pe->start, pe->end );
|
||||
Log.notice( F("PERF: %s %lms" CR), pe->key, pe->max );
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Push collected performance data to influx (use influx configuration)
|
||||
//
|
||||
void PerfLogging::pushInflux() {
|
||||
if( !myConfig.isInfluxDb2Active() )
|
||||
return;
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" +
|
||||
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" +
|
||||
String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin( client, serverPath);
|
||||
|
||||
// Create body for influxdb2, format used
|
||||
// key,host=mdns value=0.0
|
||||
String body;
|
||||
|
||||
// Create the payload with performance data.
|
||||
// ------------------------------------------------------------------------------------------
|
||||
PerfEntry* pe = first;
|
||||
char buf[100];
|
||||
sprintf( &buf[0], "perf,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
|
||||
body += &buf[0];
|
||||
|
||||
while( pe != 0 ) {
|
||||
if( pe->max ) {
|
||||
if( pe->next )
|
||||
sprintf( &buf[0], "%s=%ld,", pe->key, pe->max);
|
||||
else
|
||||
sprintf( &buf[0], "%s=%ld", pe->key, pe->max);
|
||||
|
||||
body += &buf[0];
|
||||
}
|
||||
pe = pe->next;
|
||||
}
|
||||
|
||||
// Create the payload with debug data for validating sensor stability
|
||||
// ------------------------------------------------------------------------------------------
|
||||
sprintf( &buf[0], "\ndebug,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
|
||||
body += &buf[0];
|
||||
sprintf( &buf[0], "angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f", myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az, myGyro.getSensorTempC(), myTempSensor.getTempC() );
|
||||
body += &buf[0];
|
||||
|
||||
// Log.notice(F("PERF: data %s." CR), body.c_str() );
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PERF: data %s." CR), body.c_str() );
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() );
|
||||
http.addHeader(F("Authorization"), auth.c_str() );
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode==204) {
|
||||
Log.notice(F("PERF: InfluxDB2 push performance data successful, response=%d" CR), httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR), httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
}
|
||||
|
||||
#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 ) {
|
||||
dtostrf(f, 6, dec, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//
|
||||
// Reduce precision to n decimals
|
||||
//
|
||||
float reduceFloatPrecision( float f, int dec ) {
|
||||
char buffer[5];
|
||||
dtostrf(f, 6, dec, &buffer[0]);
|
||||
return atof(&buffer[0]);
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
SerialDebug mySerial;
|
||||
BatteryVoltage myBatteryVoltage;
|
||||
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
struct tcp_pcb;
|
||||
extern struct tcp_pcb* tcp_tw_pcbs;
|
||||
extern "C" void tcp_abort(struct tcp_pcb* pcb);
|
||||
void tcp_cleanup() {
|
||||
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
//
|
||||
// Convert sg to plato
|
||||
//
|
||||
double convertToPlato(double sg) { return 259 - (259 / sg); }
|
||||
|
||||
//
|
||||
// Convert plato to sg
|
||||
//
|
||||
double convertToSG(double plato) { return 259 / (259 - plato); }
|
||||
|
||||
//
|
||||
// Conversion to F
|
||||
//
|
||||
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
|
||||
|
||||
//
|
||||
// Conversion to C
|
||||
//
|
||||
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
|
||||
|
||||
//
|
||||
// Load error log from disk
|
||||
//
|
||||
ErrorFileLog::ErrorFileLog() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "r");
|
||||
int i = 0;
|
||||
|
||||
if (errFile) {
|
||||
do {
|
||||
_errors[i] = errFile.readStringUntil('\n');
|
||||
_errors[i].replace("\r", "");
|
||||
_errors[i].replace("\n", "");
|
||||
} while (_errors[i++].length());
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add new entry to top of error log
|
||||
//
|
||||
void ErrorFileLog::addEntry(String err) {
|
||||
for (int i = (ERR_COUNT - 1); i > 0; i--) {
|
||||
_errors[i] = _errors[i - 1];
|
||||
}
|
||||
_errors[0] = err;
|
||||
Log.errorln(err.c_str());
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save error log
|
||||
//
|
||||
void ErrorFileLog::save() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "w");
|
||||
if (errFile) {
|
||||
for (int i = 0; i < ERR_COUNT; i++) {
|
||||
errFile.println(_errors[i]);
|
||||
}
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Load history log of floats
|
||||
//
|
||||
FloatHistoryLog::FloatHistoryLog(String fName) {
|
||||
_fName = fName;
|
||||
|
||||
File runFile = LittleFS.open(_fName, "r");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
_runTime[i] = runFile.readStringUntil('\n').toFloat();
|
||||
if (_runTime[i]) {
|
||||
_average += _runTime[i];
|
||||
_count++;
|
||||
}
|
||||
}
|
||||
runFile.close();
|
||||
_average = _average / _count;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add entry to top of log
|
||||
//
|
||||
void FloatHistoryLog::addEntry(float time) {
|
||||
for (int i = (10 - 1); i > 0; i--) {
|
||||
_runTime[i] = _runTime[i - 1];
|
||||
}
|
||||
_runTime[0] = time;
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save log
|
||||
//
|
||||
void FloatHistoryLog::save() {
|
||||
File runFile = LittleFS.open(_fName, "w");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
runFile.println(_runTime[i], 2);
|
||||
}
|
||||
runFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the heap information.
|
||||
//
|
||||
void printHeap(String prefix) {
|
||||
#if defined(ESP8266)
|
||||
Log.notice(
|
||||
F("%s: Free-heap %d kb, Heap-rag %d %%, Max-block %d kb Stack=%d b." CR),
|
||||
prefix.c_str(), ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
||||
ESP.getMaxFreeBlockSize() / 1024, ESP.getFreeContStack());
|
||||
// Log.notice(F("%s: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
||||
// prefix.c_str(), ESP.getFreeHeap() / 1024,
|
||||
// ESP.getHeapFragmentation(), ESP.getFreeSketchSpace() / 1024);
|
||||
#else // defined (ESP32)
|
||||
Log.verbose(F("HELP: Heap %d kb, FreeSketch %d kb." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Enter deep sleep for the defined duration (Argument is seconds)
|
||||
//
|
||||
void deepSleep(int t) {
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
|
||||
#endif
|
||||
uint32_t wake = t * 1000000;
|
||||
ESP.deepSleep(wake);
|
||||
}
|
||||
|
||||
//
|
||||
// Print the build options used
|
||||
//
|
||||
void printBuildOptions() {
|
||||
Log.notice(F("Build options: %s LOGLEVEL %d "
|
||||
#ifdef SKIP_SLEEPMODE
|
||||
"SKIP_SLEEP "
|
||||
#endif
|
||||
#ifdef EMBED_HTML
|
||||
"EMBED_HTML "
|
||||
#endif
|
||||
#ifdef COLLECT_PERFDATA
|
||||
"PERFDATA "
|
||||
#endif
|
||||
#ifdef ACTIVATE_OTA
|
||||
"OTA "
|
||||
#endif
|
||||
CR),
|
||||
CFG_APPVER, LOG_LEVEL);
|
||||
}
|
||||
|
||||
//
|
||||
// Configure serial debug output
|
||||
//
|
||||
SerialDebug::SerialDebug(const uint32_t serialSpeed) {
|
||||
// Start serial with auto-detected rate (default to defined BAUD)
|
||||
Serial.flush();
|
||||
Serial.begin(serialSpeed);
|
||||
|
||||
getLog()->begin(LOG_LEVEL, &Serial, true);
|
||||
getLog()->setPrefix(printTimestamp);
|
||||
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) {
|
||||
char c[12];
|
||||
snprintf(c, sizeof(c), "%10lu ", millis());
|
||||
_logOutput->print(c);
|
||||
}
|
||||
|
||||
//
|
||||
// Read and calculate the battery voltage
|
||||
//
|
||||
void BatteryVoltage::read() {
|
||||
// The analog pin can only handle 3.3V maximum voltage so we need to reduce
|
||||
// the voltage (from max 5V)
|
||||
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
|
||||
int v = analogRead(A0);
|
||||
|
||||
// An ESP8266 has a ADC range of 0-1023 and a maximum voltage of 3.3V
|
||||
// An ESP32 has an ADC range of 0-4095 and a maximum voltage of 3.3V
|
||||
|
||||
#if defined(ESP8266)
|
||||
_batteryLevel = ((3.3 / 1023) * v) * factor;
|
||||
#else // defined (ESP32)
|
||||
_batteryLevel = ((3.3 / 4095) * v) * factor;
|
||||
#endif
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
|
||||
factor, v, _batteryLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(COLLECT_PERFDATA)
|
||||
|
||||
PerfLogging myPerfLogging;
|
||||
|
||||
//
|
||||
// Clear the current cache
|
||||
//
|
||||
void PerfLogging::clear() {
|
||||
// Clear the measurements
|
||||
if (first == 0) return;
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
do {
|
||||
pe->max = 0;
|
||||
pe->start = 0;
|
||||
pe->end = 0;
|
||||
pe->mA = 0;
|
||||
pe->V = 0;
|
||||
pe = pe->next;
|
||||
} while (pe != 0);
|
||||
}
|
||||
|
||||
//
|
||||
// Start measuring this performance point
|
||||
//
|
||||
void PerfLogging::start(const char* key) {
|
||||
PerfEntry* pe = add(key);
|
||||
pe->start = millis();
|
||||
}
|
||||
|
||||
//
|
||||
// Finalize measuring of this performance point
|
||||
//
|
||||
void PerfLogging::stop(const char* key) {
|
||||
PerfEntry* pe = find(key);
|
||||
|
||||
if (pe != 0) {
|
||||
pe->end = millis();
|
||||
|
||||
uint32_t t = pe->end - pe->start;
|
||||
|
||||
if (t > pe->max) pe->max = t;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the collected performance data
|
||||
//
|
||||
void PerfLogging::print() {
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while (pe != 0) {
|
||||
Log.notice(F("PERF: %s %ums" CR), pe->key, pe->max);
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Push collected performance data to influx (use influx configuration)
|
||||
//
|
||||
void PerfLogging::pushInflux() {
|
||||
if (!myConfig.isInfluxDb2Active()) return;
|
||||
|
||||
WiFiClient wifi;
|
||||
HTTPClient http;
|
||||
String serverPath =
|
||||
String(myConfig.getInfluxDb2PushUrl()) +
|
||||
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin(wifi, serverPath);
|
||||
|
||||
// Create body for influxdb2, format used
|
||||
// key,host=mdns value=0.0
|
||||
String body;
|
||||
body.reserve(500);
|
||||
|
||||
// Create the payload with performance data.
|
||||
// ------------------------------------------------------------------------------------------
|
||||
PerfEntry* pe = first;
|
||||
char buf[150];
|
||||
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
|
||||
myConfig.getID());
|
||||
body += &buf[0];
|
||||
|
||||
while (pe != 0) {
|
||||
if (pe->max) {
|
||||
if (pe->next)
|
||||
snprintf(&buf[0], sizeof(buf), "%s=%u,", pe->key, pe->max);
|
||||
else
|
||||
snprintf(&buf[0], sizeof(buf), "%s=%u", pe->key, pe->max);
|
||||
|
||||
body += &buf[0];
|
||||
}
|
||||
pe = pe->next;
|
||||
}
|
||||
|
||||
// Create the payload with debug data for validating sensor stability
|
||||
// ------------------------------------------------------------------------------------------
|
||||
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
|
||||
myConfig.getMDNS(), myConfig.getID());
|
||||
body += &buf[0];
|
||||
#if defined (ESP8266)
|
||||
snprintf(
|
||||
&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f,heap=%d,heap-frag=%d,heap-max=%d,stack=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), ESP.getHeapFragmentation(), ESP.getMaxFreeBlockSize(), ESP.getFreeContStack());
|
||||
#else // defined (ESP32)
|
||||
snprintf(
|
||||
&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f,heap=%d,heap-frag=%d,heap-max=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), 0, ESP.getMaxAllocHeap());
|
||||
#endif
|
||||
|
||||
body += &buf[0];
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PERF: data %s." CR), body.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout());
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
#if !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
#endif
|
||||
} else {
|
||||
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
#endif // COLLECT_PERFDATA
|
||||
|
||||
//
|
||||
// Convert float to formatted string with n decimals. Buffer should be at least
|
||||
// 10 chars.
|
||||
//
|
||||
char* convertFloatToString(float f, char* buffer, int dec) {
|
||||
dtostrf(f, 6, dec, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//
|
||||
// Reduce precision to n decimals
|
||||
//
|
||||
float reduceFloatPrecision(float f, int dec) {
|
||||
char buffer[5];
|
||||
dtostrf(f, 6, dec, &buffer[0]);
|
||||
return atof(&buffer[0]);
|
||||
}
|
||||
|
||||
//
|
||||
// urlencode
|
||||
//
|
||||
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
|
||||
//
|
||||
String urlencode(String str) {
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length()*2);
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (isalnum(c)) {
|
||||
encodedString += c;
|
||||
} else {
|
||||
code1 = (c & 0xf) + '0';
|
||||
if ((c & 0xf) > 9) {
|
||||
code1 = (c & 0xf) - 10 + 'A';
|
||||
}
|
||||
c = (c >> 4) & 0xf;
|
||||
code0 = c + '0';
|
||||
if (c > 9) {
|
||||
code0 = c - 10 + 'A';
|
||||
}
|
||||
encodedString += '%';
|
||||
encodedString += code0;
|
||||
encodedString += code1;
|
||||
}
|
||||
}
|
||||
// Log.verbose(F("HELP: encode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
unsigned char h2int(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return ((unsigned char)c - '0');
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return ((unsigned char)c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return ((unsigned char)c - 'A' + 10);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
//
|
||||
// urlencode string
|
||||
//
|
||||
String urldecode(String str) {
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length());
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (c == '%') {
|
||||
i++;
|
||||
code0 = str.charAt(i);
|
||||
i++;
|
||||
code1 = str.charAt(i);
|
||||
c = (h2int(code0) << 4) | h2int(code1);
|
||||
encodedString += c;
|
||||
} else {
|
||||
encodedString += c;
|
||||
}
|
||||
}
|
||||
|
||||
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
156
src/helper.h
@ -1,156 +0,0 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _HELPER_H
|
||||
#define _HELPER_H
|
||||
|
||||
// Includes
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
// Sleep mode
|
||||
void deepSleep(int t);
|
||||
|
||||
// Show build options
|
||||
void printBuildOptions();
|
||||
|
||||
// Float to String
|
||||
char* convertFloatToString( float f, char* buf, int dec = 2);
|
||||
float reduceFloatPrecision( float f, int dec = 2 );
|
||||
|
||||
// Logging via serial
|
||||
void printTimestamp(Print* _logOutput, int _logLevel);
|
||||
void printNewline(Print* _logOutput);
|
||||
void printHeap();
|
||||
|
||||
// Classes
|
||||
class SerialDebug {
|
||||
public:
|
||||
SerialDebug(const long serialSpeed = 115200L);
|
||||
static Logging* getLog() { return &Log; };
|
||||
};
|
||||
|
||||
class BatteryVoltage {
|
||||
private:
|
||||
float batteryLevel;
|
||||
|
||||
public:
|
||||
void read();
|
||||
float getVoltage() { return batteryLevel; };
|
||||
};
|
||||
|
||||
#if defined( COLLECT_PERFDATA )
|
||||
|
||||
class PerfLogging {
|
||||
private:
|
||||
struct PerfEntry {
|
||||
unsigned long start; // millis()
|
||||
unsigned long end; // millis()
|
||||
unsigned long max; // max time in ms
|
||||
const char* key; // measurement
|
||||
|
||||
PerfEntry* next; // Next in the linked list
|
||||
|
||||
float mA; // Power consumption
|
||||
float V; // Power consumption
|
||||
};
|
||||
|
||||
PerfEntry* first = 0;
|
||||
bool measurePower = false;
|
||||
|
||||
PerfEntry* find( const char* k ) {
|
||||
if( first == 0 )
|
||||
return 0;
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while( strcmp( k, pe->key ) != 0 ) {
|
||||
if( pe->next == 0 )
|
||||
return 0;
|
||||
|
||||
pe = pe->next;
|
||||
}
|
||||
return pe;
|
||||
};
|
||||
|
||||
PerfEntry* add( const char* k ) {
|
||||
if( first == 0 ) {
|
||||
first = new PerfEntry();
|
||||
first->key = k;
|
||||
first->next = 0;
|
||||
first->max = 0;
|
||||
return first;
|
||||
}
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while( strcmp( k, pe->key ) != 0 ) {
|
||||
if( pe->next == 0 ) {
|
||||
pe->next = new PerfEntry();
|
||||
pe->next->key = k;
|
||||
pe->next->max = 0;
|
||||
pe->next->next = 0;
|
||||
return pe->next;
|
||||
}
|
||||
|
||||
pe = pe->next;
|
||||
}
|
||||
|
||||
return pe;
|
||||
};
|
||||
|
||||
public:
|
||||
void clear();
|
||||
void start( const char* key );
|
||||
void stop( const char* key );
|
||||
void print();
|
||||
void pushInflux();
|
||||
};
|
||||
|
||||
extern PerfLogging myPerfLogging;
|
||||
|
||||
// Use these to collect performance data from various parts of the code
|
||||
#define LOG_PERF_START(s) myPerfLogging.start(s)
|
||||
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
|
||||
//#define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR() myPerfLogging.clear()
|
||||
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
|
||||
|
||||
#else
|
||||
|
||||
// These will disable the performance collection function
|
||||
#define LOG_PERF_START(s)
|
||||
#define LOG_PERF_STOP(s)
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR()
|
||||
#define LOG_PERF_PUSH()
|
||||
|
||||
#endif // COLLECT_PERFDATA
|
||||
|
||||
// Global instance created
|
||||
extern SerialDebug mySerial;
|
||||
extern BatteryVoltage myBatteryVoltage;
|
||||
|
||||
#endif // _HELPER_H
|
||||
|
||||
// EOF
|
195
src/helper.hpp
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_HELPER_HPP_
|
||||
#define SRC_HELPER_HPP_
|
||||
|
||||
// Includes
|
||||
#include <main.hpp>
|
||||
|
||||
#define ERR_FILENAME "/error.log"
|
||||
#define ERR_COUNT 15
|
||||
|
||||
#define RUNTIME_FILENAME "/runtime.log"
|
||||
|
||||
// tcp cleanup
|
||||
void tcp_cleanup();
|
||||
|
||||
// Sleep mode
|
||||
void deepSleep(int t);
|
||||
|
||||
// Show build options
|
||||
void printBuildOptions();
|
||||
|
||||
// Data conversion
|
||||
double convertToPlato(double sg);
|
||||
double convertToSG(double plato);
|
||||
float convertCtoF(float c);
|
||||
float convertFtoC(float f);
|
||||
|
||||
// url encode/decode
|
||||
String urldecode(String str);
|
||||
String urlencode(String str);
|
||||
|
||||
// Float to String
|
||||
char* convertFloatToString(float f, char* buf, int dec = 2);
|
||||
float reduceFloatPrecision(float f, int dec = 2);
|
||||
|
||||
// Logging via serial
|
||||
void printTimestamp(Print* _logOutput, int _logLevel);
|
||||
void printNewline(Print* _logOutput);
|
||||
void printHeap(String prefix = "HELP");
|
||||
|
||||
// Classes
|
||||
class SerialDebug {
|
||||
public:
|
||||
explicit SerialDebug(const uint32_t serialSpeed = 115200L);
|
||||
static Logging* getLog() { return &Log; }
|
||||
};
|
||||
|
||||
class ErrorFileLog {
|
||||
private:
|
||||
String _errors[ERR_COUNT];
|
||||
|
||||
public:
|
||||
ErrorFileLog();
|
||||
void addEntry(String error);
|
||||
void save();
|
||||
};
|
||||
|
||||
class FloatHistoryLog {
|
||||
private:
|
||||
String _fName;
|
||||
float _average = 0;
|
||||
float _runTime[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
int _count = 0;
|
||||
void save();
|
||||
|
||||
public:
|
||||
explicit FloatHistoryLog(String fName);
|
||||
void addEntry(float time);
|
||||
float getAverage() { return _average; }
|
||||
};
|
||||
|
||||
class BatteryVoltage {
|
||||
private:
|
||||
float _batteryLevel;
|
||||
|
||||
public:
|
||||
void read();
|
||||
float getVoltage() { return _batteryLevel; }
|
||||
};
|
||||
|
||||
#if defined(COLLECT_PERFDATA)
|
||||
|
||||
class PerfLogging {
|
||||
private:
|
||||
struct PerfEntry {
|
||||
uint32_t start; // millis()
|
||||
uint32_t end; // millis()
|
||||
uint32_t max; // max time in ms
|
||||
const char* key; // measurement
|
||||
|
||||
PerfEntry* next; // Next in the linked list
|
||||
|
||||
float mA; // Power consumption
|
||||
float V; // Power consumption
|
||||
};
|
||||
|
||||
PerfEntry* first = 0;
|
||||
bool measurePower = false;
|
||||
|
||||
PerfEntry* find(const char* k) {
|
||||
if (first == 0) return 0;
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while (strcmp(k, pe->key) != 0) {
|
||||
if (pe->next == 0) return 0;
|
||||
pe = pe->next;
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
PerfEntry* add(const char* k) {
|
||||
if (first == 0) {
|
||||
first = new PerfEntry();
|
||||
first->key = k;
|
||||
first->next = 0;
|
||||
first->max = 0;
|
||||
return first;
|
||||
}
|
||||
|
||||
PerfEntry* pe = first;
|
||||
|
||||
while (strcmp(k, pe->key) != 0) {
|
||||
if (pe->next == 0) {
|
||||
pe->next = new PerfEntry();
|
||||
pe->next->key = k;
|
||||
pe->next->max = 0;
|
||||
pe->next->next = 0;
|
||||
return pe->next;
|
||||
}
|
||||
|
||||
pe = pe->next;
|
||||
}
|
||||
|
||||
return pe;
|
||||
}
|
||||
|
||||
public:
|
||||
void clear();
|
||||
void start(const char* key);
|
||||
void stop(const char* key);
|
||||
void print();
|
||||
void pushInflux();
|
||||
};
|
||||
|
||||
extern PerfLogging myPerfLogging;
|
||||
|
||||
// Use these to collect performance data from various parts of the code
|
||||
#define LOG_PERF_START(s) myPerfLogging.start(s)
|
||||
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
|
||||
// #define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR() myPerfLogging.clear()
|
||||
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
|
||||
|
||||
#else
|
||||
|
||||
// These will disable the performance collection function
|
||||
#define LOG_PERF_START(s)
|
||||
#define LOG_PERF_STOP(s)
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR()
|
||||
#define LOG_PERF_PUSH()
|
||||
|
||||
#endif // COLLECT_PERFDATA
|
||||
|
||||
// Global instance created
|
||||
extern SerialDebug mySerial;
|
||||
extern BatteryVoltage myBatteryVoltage;
|
||||
|
||||
#endif // SRC_HELPER_HPP_
|
||||
|
||||
// EOF
|
646
src/main.cpp
@ -1,279 +1,367 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "helper.h"
|
||||
#include "gyro.h"
|
||||
#include "config.h"
|
||||
#include "wifi.h"
|
||||
#include "webserver.h"
|
||||
#include "calc.h"
|
||||
#include "tempsensor.h"
|
||||
#include "pushtarget.h"
|
||||
#include <LittleFS.h>
|
||||
|
||||
// Settings for double reset detector.
|
||||
#define ESP8266_DRD_USE_RTC true
|
||||
#define DRD_TIMEOUT 2
|
||||
#define DRD_ADDRESS 0
|
||||
#include <ESP_DoubleResetDetector.h>
|
||||
DoubleResetDetector *drd;
|
||||
|
||||
// Define constats for this program
|
||||
#if LOG_LEVEL==6
|
||||
const int interval = 1000; // ms, time to wait between changes to output
|
||||
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
|
||||
#else
|
||||
const int interval = 200; // ms, time to wait between changes to output
|
||||
bool sleepModeAlwaysSkip = false; // Web interface can override normal behaviour
|
||||
#endif
|
||||
unsigned long loopMillis = 0; // Used for main loop to run the code every _interval_
|
||||
unsigned long runtimeMillis; // Used to calculate the total time since start/wakeup
|
||||
unsigned long stableGyroMillis; // Used to calculate the total time since last stable gyro reading
|
||||
bool sleepModeActive = false;
|
||||
bool goToSleep = false;
|
||||
int loopCounter = 0;
|
||||
|
||||
//
|
||||
// Check if we should be in sleep mode
|
||||
//
|
||||
void checkSleepMode( float angle, float volt ) {
|
||||
|
||||
#if defined( SKIP_SLEEPMODE )
|
||||
sleepModeActive = false;
|
||||
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR) );
|
||||
return;
|
||||
#endif
|
||||
|
||||
const RawGyroData &g = myConfig.getGyroCalibration();
|
||||
|
||||
// Will not enter sleep mode if: no calibration data
|
||||
if( g.ax==0 && g.ay==0 && g.az==0 && g.gx==0 && g.gy==0 && g.gz==0 ) {
|
||||
Log.notice(F("MAIN: Missing calibration data, so forcing webserver to be active." CR) );
|
||||
sleepModeAlwaysSkip = true;
|
||||
}
|
||||
|
||||
if( sleepModeAlwaysSkip ) {
|
||||
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR) );
|
||||
sleepModeActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Will not enter sleep mode if: charger is connected
|
||||
sleepModeActive = (volt<4.15 && (angle>85 && angle<95)) || (volt>4.15) ? false : true;
|
||||
|
||||
// sleep mode active when flat
|
||||
//sleepModeActive = ( angle<85 && angle>5 ) ? true : false;
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("MAIN: Deep sleep mode %s (angle=%F volt=%F)." CR), sleepModeActive ? "true":"false", angle, volt );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Setup
|
||||
//
|
||||
void setup() {
|
||||
LOG_PERF_START("run-time");
|
||||
LOG_PERF_START("main-setup");
|
||||
runtimeMillis = millis();
|
||||
|
||||
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
|
||||
bool dt = drd->detectDoubleReset();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str() );
|
||||
#endif
|
||||
// Main startup
|
||||
Log.notice(F("Main: Started setup for %s." CR), String( ESP.getChipId(), HEX).c_str() );
|
||||
printBuildOptions();
|
||||
|
||||
LOG_PERF_START("main-config-load");
|
||||
myConfig.checkFileSystem();
|
||||
myConfig.loadFile();
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
ESP.wdtDisable();
|
||||
ESP.wdtEnable( interval*2 );
|
||||
|
||||
LOG_PERF_START("main-temp-setup");
|
||||
myTempSensor.setup();
|
||||
LOG_PERF_STOP("main-temp-setup");
|
||||
|
||||
// Setup Gyro
|
||||
//LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this measurment
|
||||
if( !myGyro.setup() )
|
||||
Log.error(F("Main: Failed to initialize the gyro." CR));
|
||||
//LOG_PERF_STOP("main-gyro-setup");
|
||||
|
||||
if( dt )
|
||||
Log.notice(F("Main: Detected doubletap on reset." CR));
|
||||
|
||||
LOG_PERF_START("main-wifi-connect");
|
||||
myWifi.connect( dt ); // This will return false if unable to connect to wifi, will be handled in loop()
|
||||
LOG_PERF_STOP("main-wifi-connect");
|
||||
|
||||
LOG_PERF_START("main-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("main-gyro-read");
|
||||
|
||||
LOG_PERF_START("main-batt-read");
|
||||
myBatteryVoltage.read();
|
||||
LOG_PERF_STOP("main-batt-read");
|
||||
checkSleepMode( myGyro.getAngle(), myBatteryVoltage.getVoltage() );
|
||||
|
||||
if( myWifi.isConnected() ) {
|
||||
#if defined( ACTIVATE_OTA )
|
||||
LOG_PERF_START("main-wifi-ota");
|
||||
if( !sleepModeActive && myWifi.checkFirmwareVersion() ) {
|
||||
myWifi.updateFirmware();
|
||||
}
|
||||
LOG_PERF_STOP("main-wifi-ota");
|
||||
#endif
|
||||
if( !sleepModeActive ) {
|
||||
//LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so skip this measurment
|
||||
myWebServer.setupWebServer();
|
||||
//LOG_PERF_STOP("main-webserver-setup");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_PERF_STOP("main-setup");
|
||||
Log.notice(F("Main: Setup completed." CR));
|
||||
stableGyroMillis = millis(); // Put it here so we dont include time for wifi connection
|
||||
}
|
||||
|
||||
//
|
||||
// Main loops
|
||||
//
|
||||
void loop() {
|
||||
drd->loop();
|
||||
|
||||
if( sleepModeActive || abs( (long) (millis() - loopMillis)) > interval ) {
|
||||
float angle = 0;
|
||||
float volt = myBatteryVoltage.getVoltage();
|
||||
//float sensorTemp = 0;
|
||||
loopCounter++;
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("Main: Entering main loop." CR) );
|
||||
#endif
|
||||
// Process the sensor values and push data to targets.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// If we dont get any readings we just skip this and try again the next interval.
|
||||
//
|
||||
if( myGyro.hasValue() ) {
|
||||
angle = myGyro.getAngle(); // Gyro angle
|
||||
|
||||
stableGyroMillis = millis(); // Reset timer
|
||||
|
||||
LOG_PERF_START("loop-temp-read");
|
||||
float temp = myTempSensor.getTempC();
|
||||
LOG_PERF_STOP("loop-temp-read");
|
||||
|
||||
//LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip this measurment
|
||||
float gravity = calculateGravity( angle, temp );
|
||||
//LOG_PERF_STOP("loop-gravity-calc");
|
||||
|
||||
//LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip this measurment
|
||||
// Use default correction temperature of 20C
|
||||
float corrGravity = gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() );
|
||||
//LOG_PERF_STOP("loop-gravity-corr");
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, corr=%F." CR), angle, temp, gravity, corrGravity );
|
||||
#endif
|
||||
// Limit the printout when sleep mode is not active.
|
||||
if( loopCounter%10 == 0 || sleepModeActive ) {
|
||||
Log.notice(F("Main: angle=%F, temp=%F, gravity=%F, corrGravity=%F, batt=%F." CR), angle, temp, gravity, corrGravity ,volt );
|
||||
}
|
||||
|
||||
LOG_PERF_START("loop-push");
|
||||
myPushTarget.send( angle, gravity, corrGravity, temp, (millis()-runtimeMillis)/1000, sleepModeActive ); // Force the transmission if we are going to sleep
|
||||
LOG_PERF_STOP("loop-push");
|
||||
|
||||
// If we have completed the update lets go to sleep
|
||||
if( sleepModeActive )
|
||||
goToSleep = true;
|
||||
} else {
|
||||
Log.error(F("Main: No gyro value." CR) );
|
||||
}
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("Main: Sleep mode not active." CR) );
|
||||
#endif
|
||||
int sleepInterval = myConfig.getSleepInterval();
|
||||
|
||||
// If we didnt get a wifi connection, we enter sleep for a short time to conserve battery.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
//
|
||||
if( !myWifi.isConnected() ) { // no connection to wifi
|
||||
Log.notice(F("MAIN: No connection to wifi established, sleeping for 60s." CR) );
|
||||
sleepInterval = 60; // 60s
|
||||
goToSleep = true;
|
||||
}
|
||||
|
||||
// If the sensor is moving and we are not getting a clear reading, we enter sleep for a short time to conserve battery.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
//
|
||||
if( sleepModeActive && ((millis()-stableGyroMillis)>10000L) ) { // 10s since last stable gyro reading
|
||||
Log.notice(F("MAIN: Unable to get a stable reading for 10s, sleeping for 60s." CR) );
|
||||
sleepInterval = 60; // 60s
|
||||
goToSleep = true;
|
||||
}
|
||||
|
||||
// Enter sleep mode if the conditions are right
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
//
|
||||
if( goToSleep ) {
|
||||
Log.notice(F("MAIN: Entering deep sleep for %d s, run time %l s, battery=%F V." CR), sleepInterval, (millis()-runtimeMillis)/1000, volt );
|
||||
LittleFS.end();
|
||||
myGyro.enterSleep();
|
||||
drd->stop();
|
||||
LOG_PERF_STOP("run-time");
|
||||
LOG_PERF_PUSH();
|
||||
delay(100);
|
||||
deepSleep( sleepInterval );
|
||||
}
|
||||
|
||||
// If we are running in normal mode we just continue
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Do these checks if we are running in normal mode (not sleep mode)
|
||||
//
|
||||
checkSleepMode( angle, volt );
|
||||
|
||||
LOG_PERF_START("loop-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("loop-gyro-read");
|
||||
|
||||
//LOG_PERF_START("loop-batt-read"); // Takes less than 2ms , so skip this measurment
|
||||
myBatteryVoltage.read();
|
||||
//LOG_PERF_STOP("loop-batt-read");
|
||||
|
||||
loopMillis = millis();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getFreeSketchSpace()/1024 );
|
||||
Log.verbose(F("Main: HeapFrag %d %%." CR), ESP.getHeapFragmentation() );
|
||||
#endif
|
||||
}
|
||||
|
||||
myWebServer.loop();
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <calc.hpp>
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <pushtarget.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <webserver.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// Define constats for this program
|
||||
#ifdef DEACTIVATE_SLEEPMODE
|
||||
const int interval = 1000; // ms, time to wait between changes to output
|
||||
#else
|
||||
int interval = 200; // ms, time to wait between changes to output
|
||||
#endif
|
||||
bool sleepModeAlwaysSkip =
|
||||
false; // Flag set in web interface to override normal behaviour
|
||||
uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
|
||||
uint32_t pushMillis = 0; // Used to control how often we will send push data
|
||||
uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
|
||||
uint32_t stableGyroMillis; // Used to calculate the total time since last
|
||||
// stable gyro reading
|
||||
|
||||
enum RunMode { gravityMode = 0, configurationMode = 1, wifiSetupMode = 2 };
|
||||
|
||||
RunMode runMode = RunMode::gravityMode;
|
||||
|
||||
//
|
||||
// Check if we should be in sleep mode
|
||||
//
|
||||
void checkSleepMode(float angle, float volt) {
|
||||
#if defined(SKIP_SLEEPMODE)
|
||||
runMode = RunMode::configurationMode;
|
||||
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR));
|
||||
return;
|
||||
#endif
|
||||
|
||||
const RawGyroData &g = myConfig.getGyroCalibration();
|
||||
|
||||
if (!g.ax && !g.ay && !g.az && !g.gx && !g.gy && !g.gz) {
|
||||
// Will not enter sleep mode if: no calibration data
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("MAIN: Missing calibration data, so forcing webserver to be "
|
||||
"active." CR));
|
||||
#endif
|
||||
runMode = RunMode::configurationMode;
|
||||
} else if (sleepModeAlwaysSkip) {
|
||||
// Check if the flag from the UI has been set, the we force configuration
|
||||
// mode.
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR));
|
||||
#endif
|
||||
runMode = RunMode::configurationMode;
|
||||
} else if ((volt < 4.15 && (angle > 85 && angle < 95)) || (volt > 4.15)) {
|
||||
runMode = RunMode::configurationMode;
|
||||
} else {
|
||||
runMode = RunMode::gravityMode;
|
||||
}
|
||||
|
||||
switch (runMode) {
|
||||
case RunMode::configurationMode:
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: run mode CONFIG (angle=%F volt=%F)." CR), angle,
|
||||
volt);
|
||||
#endif
|
||||
break;
|
||||
case RunMode::wifiSetupMode:
|
||||
break;
|
||||
case RunMode::gravityMode:
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: run mode GRAVITY (angle=%F volt=%F)." CR), angle,
|
||||
volt);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Setup
|
||||
//
|
||||
void setup() {
|
||||
LOG_PERF_START("run-time");
|
||||
LOG_PERF_START("main-setup");
|
||||
runtimeMillis = millis();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
// Add a delay so that serial is started.
|
||||
// delay(3000);
|
||||
#if defined(ESP8266)
|
||||
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
#endif
|
||||
// Main startup
|
||||
#if defined(ESP8266)
|
||||
Log.notice(F("Main: Started setup for %s." CR),
|
||||
String(ESP.getChipId(), HEX).c_str());
|
||||
#else // defined (ESP32)
|
||||
char buf[20];
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
|
||||
Log.notice(F("Main: Started setup for %s." CR), &buf[0]);
|
||||
#endif
|
||||
printBuildOptions();
|
||||
|
||||
LOG_PERF_START("main-config-load");
|
||||
myConfig.checkFileSystem();
|
||||
myConfig.loadFile();
|
||||
myWifi.init();
|
||||
myHardwareConfig.loadFile();
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
#if defined(ESP8266)
|
||||
ESP.wdtDisable();
|
||||
ESP.wdtEnable(5000); // 5 seconds
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
|
||||
// No stored config, move to portal
|
||||
if (!myWifi.hasConfig()) {
|
||||
Log.notice(
|
||||
F("Main: No wifi configuration detected, entering wifi setup." CR));
|
||||
runMode = RunMode::wifiSetupMode;
|
||||
}
|
||||
|
||||
// Double reset, go to portal.
|
||||
if (myWifi.isDoubleResetDetected()) {
|
||||
Log.notice(F("Main: Double reset detected, entering wifi setup." CR));
|
||||
runMode = RunMode::wifiSetupMode;
|
||||
}
|
||||
|
||||
// Do this setup for all modes exect wifi setup
|
||||
switch (runMode) {
|
||||
case RunMode::wifiSetupMode:
|
||||
myWifi.startPortal();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_PERF_START("main-wifi-connect");
|
||||
myWifi.connect();
|
||||
LOG_PERF_STOP("main-wifi-connect");
|
||||
|
||||
LOG_PERF_START("main-temp-setup");
|
||||
myTempSensor.setup();
|
||||
LOG_PERF_STOP("main-temp-setup");
|
||||
|
||||
if (!myGyro.setup()) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("MAIN: Failed to initialize the gyro, is it connected?"));
|
||||
} else {
|
||||
LOG_PERF_START("main-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("main-gyro-read");
|
||||
}
|
||||
|
||||
myBatteryVoltage.read();
|
||||
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
|
||||
break;
|
||||
}
|
||||
|
||||
// Do this setup for configuration mode
|
||||
switch (runMode) {
|
||||
case RunMode::configurationMode:
|
||||
if (myWifi.isConnected()) {
|
||||
#if defined(ACTIVATE_OTA)
|
||||
LOG_PERF_START("main-wifi-ota");
|
||||
if (myWifi.checkFirmwareVersion()) myWifi.updateFirmware();
|
||||
LOG_PERF_STOP("main-wifi-ota");
|
||||
#endif
|
||||
myWebServerHandler
|
||||
.setupWebServer(); // Takes less than 4ms, so skip this measurement
|
||||
}
|
||||
|
||||
interval = 1000; // Change interval from 200ms to 1s
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_PERF_STOP("main-setup");
|
||||
Log.notice(F("Main: Setup completed." CR));
|
||||
pushMillis = stableGyroMillis =
|
||||
millis(); // Dont include time for wifi connection
|
||||
}
|
||||
|
||||
//
|
||||
// Main loop that does gravity readings and push data to targets
|
||||
//
|
||||
// Return true if gravity reading was successful
|
||||
//
|
||||
bool loopReadGravity() {
|
||||
float angle = 0;
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.verbose(F("Main: Entering main loopGravity." CR));
|
||||
#endif
|
||||
|
||||
// Process the sensor values and push data to targets.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// If we dont get any readings we just skip this and try again the next
|
||||
// interval.
|
||||
//
|
||||
if (myGyro.hasValue()) {
|
||||
angle = myGyro.getAngle(); // Gyro angle
|
||||
stableGyroMillis = millis(); // Reset timer
|
||||
|
||||
LOG_PERF_START("loop-temp-read");
|
||||
float tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
LOG_PERF_STOP("loop-temp-read");
|
||||
|
||||
float gravitySG = calculateGravity(angle, tempC);
|
||||
float corrGravitySG = gravityTemperatureCorrectionC(gravitySG, tempC);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%FC, gravity=%F, "
|
||||
"corr_gravity=%F." CR),
|
||||
angle, tempC, gravity, corrGravity);
|
||||
#endif
|
||||
|
||||
bool pushExpired = (abs((int32_t)(millis() - pushMillis)) >
|
||||
(myConfig.getSleepInterval() * 1000));
|
||||
|
||||
if (pushExpired || runMode == RunMode::gravityMode) {
|
||||
pushMillis = millis();
|
||||
LOG_PERF_START("loop-push");
|
||||
PushTarget push;
|
||||
push.send(angle, gravitySG, corrGravitySG, tempC,
|
||||
(millis() - runtimeMillis) / 1000);
|
||||
LOG_PERF_STOP("loop-push");
|
||||
// Send stats to influx after each push run.
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
LOG_PERF_PUSH();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Log.error(F("MAIN: No gyro value found, the device might be moving."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Wrapper for loopGravity that only calls every 200ms so that we dont overload
|
||||
// this.
|
||||
//
|
||||
void loopGravityOnInterval() {
|
||||
if (abs((int32_t)(millis() - loopMillis)) > interval) {
|
||||
loopReadGravity();
|
||||
loopMillis = millis();
|
||||
// printHeap("MAIN");
|
||||
LOG_PERF_START("loop-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("loop-gyro-read");
|
||||
myBatteryVoltage.read();
|
||||
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
|
||||
}
|
||||
}
|
||||
|
||||
bool skipRunTimeLog = false;
|
||||
|
||||
//
|
||||
// Main loop that determines if device should go to sleep
|
||||
//
|
||||
void goToSleep(int sleepInterval) {
|
||||
float volt = myBatteryVoltage.getVoltage();
|
||||
float runtime = (millis() - runtimeMillis);
|
||||
|
||||
if (!skipRunTimeLog) {
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
runLog.addEntry(runtime);
|
||||
}
|
||||
|
||||
Log.notice(F("MAIN: Entering deep sleep for %ds, run time %Fs, "
|
||||
"battery=%FV." CR),
|
||||
sleepInterval, reduceFloatPrecision(runtime / 1000, 2), volt);
|
||||
LittleFS.end();
|
||||
myGyro.enterSleep();
|
||||
LOG_PERF_STOP("run-time");
|
||||
LOG_PERF_PUSH();
|
||||
delay(100);
|
||||
deepSleep(sleepInterval);
|
||||
}
|
||||
|
||||
//
|
||||
// Main loops
|
||||
//
|
||||
void loop() {
|
||||
switch (runMode) {
|
||||
case RunMode::configurationMode:
|
||||
myWebServerHandler.loop();
|
||||
myWifi.loop();
|
||||
loopGravityOnInterval();
|
||||
|
||||
// If we switched mode, dont include this in the log.
|
||||
if (runMode != RunMode::configurationMode) skipRunTimeLog = true;
|
||||
break;
|
||||
|
||||
case RunMode::gravityMode:
|
||||
// If we didnt get a wifi connection, we enter sleep for a short time to
|
||||
// conserve battery.
|
||||
if (!myWifi.isConnected()) { // no connection to wifi
|
||||
Log.notice(
|
||||
F("MAIN: No connection to wifi established, sleeping for 60s." CR));
|
||||
myWifi.stopDoubleReset();
|
||||
goToSleep(60);
|
||||
}
|
||||
|
||||
if (loopReadGravity()) {
|
||||
myWifi.stopDoubleReset();
|
||||
goToSleep(myConfig.getSleepInterval());
|
||||
}
|
||||
|
||||
// If the sensor is moving and we are not getting a clear reading, we
|
||||
// enter sleep for a short time to conserve battery.
|
||||
if (((millis() - stableGyroMillis) >
|
||||
10000L)) { // 10s since last stable gyro reading
|
||||
Log.notice(
|
||||
F("MAIN: Unable to get a stable reading for 10s, sleeping for "
|
||||
"60s." CR));
|
||||
myWifi.stopDoubleReset();
|
||||
goToSleep(60);
|
||||
}
|
||||
|
||||
LOG_PERF_START("loop-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("loop-gyro-read");
|
||||
myWifi.loop();
|
||||
break;
|
||||
|
||||
case RunMode::wifiSetupMode:
|
||||
myWifi.loop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -21,30 +21,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _TEMPSENSOR_H
|
||||
#define _TEMPSENSOR_H
|
||||
#ifndef SRC_MAIN_HPP_
|
||||
#define SRC_MAIN_HPP_
|
||||
|
||||
// definitions
|
||||
float convertCtoF( float t );
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// classes
|
||||
class TempSensor {
|
||||
private:
|
||||
bool hasSensor = false;
|
||||
float tempSensorAdjF = 0;
|
||||
float tempSensorAdjC = 0;
|
||||
float getValue();
|
||||
#if defined (ESP8266)
|
||||
#include <LittleFS.h>
|
||||
#define ESP_RESET ESP.reset
|
||||
#define PIN_SDA D3
|
||||
#define PIN_SCL D4
|
||||
#define PIN_DS D6
|
||||
#else // defined (ESP32)
|
||||
#define LittleFS SPIFFS
|
||||
#define ESPhttpUpdate httpUpdate
|
||||
#define ESP_RESET ESP.restart
|
||||
#define ESP8266WebServer WebServer
|
||||
#include <spiffs.h>
|
||||
#define PIN_SDA 17
|
||||
#define PIN_SCL 16
|
||||
#define PIN_DS 19
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup();
|
||||
bool isSensorAttached() { return hasSensor; };
|
||||
float getTempC() { return getValue() + tempSensorAdjC; }
|
||||
float getTempF() { return convertCtoF(getValue()) + tempSensorAdjF; };
|
||||
};
|
||||
#define PIN_LED 2
|
||||
|
||||
// Global instance created
|
||||
extern TempSensor myTempSensor;
|
||||
|
||||
#endif // _TEMPSENSOR_H
|
||||
|
||||
// EOF
|
||||
#endif // SRC_MAIN_HPP_
|
@ -1,226 +1,342 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "pushtarget.h"
|
||||
#include "config.h"
|
||||
#include "gyro.h" // For testing the tempsensor in the gyro
|
||||
|
||||
PushTarget myPushTarget;
|
||||
|
||||
//
|
||||
// Send the pressure value
|
||||
//
|
||||
void PushTarget::send(float angle, float gravity, float corrGravity, float temp, float runTime, bool force ) {
|
||||
unsigned long timePassed = abs( (long) (millis() - ms) );
|
||||
unsigned long interval = myConfig.getSleepInterval()*1000;
|
||||
|
||||
if( ( timePassed < interval ) && !force) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed, interval );
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PUSH: Sending data." CR) );
|
||||
#endif
|
||||
ms = millis();
|
||||
|
||||
if( myConfig.isBrewfatherActive() ) {
|
||||
LOG_PERF_START("push-brewfather");
|
||||
sendBrewfather( angle, gravity, corrGravity, temp );
|
||||
LOG_PERF_STOP("push-brewfather");
|
||||
}
|
||||
|
||||
if( myConfig.isHttpActive() ) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttp( myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp, runTime );
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if( myConfig.isHttpActive2() ) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttp( myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp, runTime );
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
if( myConfig.isInfluxDb2Active() ) {
|
||||
LOG_PERF_START("push-influxdb2");
|
||||
sendInfluxDb2( angle, gravity, corrGravity, temp, runTime );
|
||||
LOG_PERF_STOP("push-influxdb2");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send to influx db v2
|
||||
//
|
||||
void PushTarget::sendInfluxDb2(float angle, float gravity, float corrGravity, float temp, float runTime) {
|
||||
Log.notice(F("PUSH: Sending values to influxdb2 angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp );
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" +
|
||||
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" +
|
||||
String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin( client, serverPath);
|
||||
|
||||
// Create body for influxdb2
|
||||
char buf[1024];
|
||||
sprintf( &buf[0], "measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
|
||||
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,rssi=%d,temp2=%.2f\n",
|
||||
// TODO: Add support for plato format
|
||||
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
|
||||
myConfig.isGravityTempAdj() ? corrGravity : gravity,
|
||||
corrGravity, angle, temp, myBatteryVoltage.getVoltage(), WiFi.RSSI(), myGyro.getSensorTempC() ); // For comparing gyro tempsensor vs DSB1820
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: data %s." CR), &buf[0] );
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() );
|
||||
http.addHeader(F("Authorization"), auth.c_str() );
|
||||
int httpResponseCode = http.POST(&buf[0]);
|
||||
|
||||
if (httpResponseCode==204) {
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR), httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to brewfather
|
||||
//
|
||||
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity, float temp ) {
|
||||
Log.notice(F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp );
|
||||
|
||||
DynamicJsonDocument doc(300);
|
||||
//
|
||||
// {
|
||||
// "name": "YourDeviceName", // Required field, this will be the ID in Brewfather
|
||||
// "temp": 20.32,
|
||||
// "aux_temp": 15.61, // Fridge Temp
|
||||
// "ext_temp": 6.51, // Room Temp
|
||||
// "temp_unit": "C", // C, F, K
|
||||
// "gravity": 1.042,
|
||||
// "gravity_unit": "G", // G, P
|
||||
// "pressure": 10,
|
||||
// "pressure_unit": "PSI", // PSI, BAR, KPA
|
||||
// "ph": 4.12,
|
||||
// "bpm": 123, // Bubbles Per Minute
|
||||
// "comment": "Hello World",
|
||||
// "beer": "Pale Ale"
|
||||
// "battery": 4.98
|
||||
// }
|
||||
//
|
||||
doc["name"] = myConfig.getMDNS();
|
||||
doc["temp"] = reduceFloatPrecision( temp, 1);
|
||||
doc["temp_unit"] = String( myConfig.getTempFormat() );
|
||||
doc["battery"] = reduceFloatPrecision( myBatteryVoltage.getVoltage(), 2 );
|
||||
// TODO: Add support for plato format
|
||||
doc["gravity"] = reduceFloatPrecision( myConfig.isGravityTempAdj() ? corrGravity : gravity, 4 );
|
||||
doc["gravity_unit"] = myConfig.isGravitySG()?"G":"P";
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getBrewfatherPushUrl();
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin( client, serverPath);
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), json.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json") );
|
||||
int httpResponseCode = http.POST(json);
|
||||
|
||||
if (httpResponseCode==200) {
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR), httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: Brewfather push failed, response=%d" CR), httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendHttp( String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime ) {
|
||||
Log.notice(F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp );
|
||||
|
||||
DynamicJsonDocument doc(256);
|
||||
|
||||
// Use iSpindle format for compatibility
|
||||
doc["name"] = myConfig.getMDNS();
|
||||
doc["ID"] = myConfig.getID();
|
||||
doc["token"] = "gravmon";
|
||||
doc["interval"] = myConfig.getSleepInterval();
|
||||
doc["temperature"] = reduceFloatPrecision( temp, 1 );
|
||||
doc["temp-units"] = String( myConfig.getTempFormat() );
|
||||
// TODO: Add support for plato format
|
||||
doc["gravity"] = reduceFloatPrecision( myConfig.isGravityTempAdj() ? corrGravity : gravity, 4 );
|
||||
doc["corr-gravity"] = reduceFloatPrecision( corrGravity, 4 );
|
||||
doc["angle"] = reduceFloatPrecision( angle, 2);
|
||||
doc["battery"] = reduceFloatPrecision( myBatteryVoltage.getVoltage(), 2 );
|
||||
doc["rssi"] = WiFi.RSSI();
|
||||
|
||||
// Some additional information
|
||||
doc["gravity-units"] = "SG";
|
||||
doc["run-time"] = reduceFloatPrecision( runTime, 2 );
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin( client, serverPath);
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), json.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json") );
|
||||
int httpResponseCode = http.POST(json);
|
||||
|
||||
if (httpResponseCode==200) {
|
||||
Log.notice(F("PUSH: HTTP push successful, response=%d" CR), httpResponseCode);
|
||||
} else {
|
||||
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266mDNS.h>
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
#include <MQTT.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
//
|
||||
// Send the data to targets
|
||||
//
|
||||
void PushTarget::send(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime) {
|
||||
printHeap("PUSH");
|
||||
http.setReuse(false);
|
||||
httpSecure.setReuse(false);
|
||||
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
|
||||
|
||||
if (myConfig.isBrewfatherActive()) {
|
||||
LOG_PERF_START("push-brewfather");
|
||||
sendBrewfather(engine);
|
||||
LOG_PERF_STOP("push-brewfather");
|
||||
}
|
||||
|
||||
if (myConfig.isHttpActive()) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttp(engine, myConfig.isHttpSSL(), 0);
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if (myConfig.isHttp2Active()) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttp(engine, myConfig.isHttp2SSL(), 1);
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
if (myConfig.isInfluxDb2Active()) {
|
||||
LOG_PERF_START("push-influxdb2");
|
||||
sendInfluxDb2(engine);
|
||||
LOG_PERF_STOP("push-influxdb2");
|
||||
}
|
||||
|
||||
if (myConfig.isMqttActive()) {
|
||||
LOG_PERF_START("push-mqtt");
|
||||
sendMqtt(engine, myConfig.isMqttSSL());
|
||||
LOG_PERF_STOP("push-mqtt");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send to influx db v2
|
||||
//
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to influxdb2." CR));
|
||||
#endif
|
||||
|
||||
String serverPath =
|
||||
String(myConfig.getInfluxDb2PushUrl()) +
|
||||
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_INFLUX);
|
||||
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
int httpResponseCode = http.POST(doc);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Influxdb push failed response=" +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
|
||||
http.end();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to brewfather
|
||||
//
|
||||
void PushTarget::sendBrewfather(TemplatingEngine& engine) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to brewfather" CR));
|
||||
#endif
|
||||
|
||||
String serverPath = myConfig.getBrewfatherPushUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_BREWFATHER);
|
||||
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
http.addHeader(F("Content-Type"), F("application/json"));
|
||||
int httpResponseCode = http.POST(doc);
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Brewfather push failed response=" +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
|
||||
http.end();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
if (!header.length()) return;
|
||||
|
||||
int i = header.indexOf(":");
|
||||
if (i) {
|
||||
String name = header.substring(0, i);
|
||||
String value = header.substring(i + 1);
|
||||
value.trim();
|
||||
Log.notice(F("PUSH: Adding header '%s': '%s'" CR), name.c_str(),
|
||||
value.c_str());
|
||||
http.addHeader(name, value);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Unable to set header, invalid value " + header);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendHttp(TemplatingEngine& engine, bool isSecure, int index) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to http (%s)" CR),
|
||||
index ? "http2" : "http");
|
||||
#endif
|
||||
String serverPath, doc;
|
||||
|
||||
if (index == 0) {
|
||||
serverPath = myConfig.getHttpUrl();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP1);
|
||||
} else {
|
||||
serverPath = myConfig.getHttp2Url();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP2);
|
||||
}
|
||||
|
||||
int httpResponseCode;
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
if (isSecure) {
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
wifiSecure.setInsecure();
|
||||
|
||||
#if defined (ESP8266)
|
||||
String host = serverPath.substring(8); // remove the prefix or the probe will fail, it needs a pure host name.
|
||||
int idx = host.indexOf("/");
|
||||
if (idx!=-1)
|
||||
host = host.substring(0, idx);
|
||||
|
||||
if (wifiSecure.probeMaxFragmentLength(host, 443, 512)) {
|
||||
Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR));
|
||||
wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
httpSecure.begin(wifiSecure, serverPath);
|
||||
httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(httpSecure, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(httpSecure, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(httpSecure, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(httpSecure, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
httpResponseCode = httpSecure.POST(doc);
|
||||
} else {
|
||||
http.begin(wifi, serverPath);
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(http, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(http, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(http, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(http, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
httpResponseCode = http.POST(doc);
|
||||
}
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("PUSH: HTTP push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
"PUSH: HTTP push failed response=" + String(httpResponseCode) +
|
||||
String(index == 0 ? " (http)" : " (http2)"));
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
httpSecure.end();
|
||||
wifiSecure.stop();
|
||||
} else {
|
||||
http.end();
|
||||
wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to mqtt." CR));
|
||||
#endif
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
String host = myConfig.getMqttUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_MQTT);
|
||||
int port = myConfig.getMqttPort();
|
||||
|
||||
if (myConfig.isMqttSSL()) {
|
||||
Log.notice(F("PUSH: MQTT, SSL enabled without validation." CR));
|
||||
wifiSecure.setInsecure();
|
||||
|
||||
#if defined (ESP8266)
|
||||
if (wifiSecure.probeMaxFragmentLength(host, port, 512)) {
|
||||
Log.notice(F("PUSH: MQTT server supports smaller SSL buffer." CR));
|
||||
wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
mqtt.begin(host.c_str(), port, wifiSecure);
|
||||
} else {
|
||||
mqtt.begin(host.c_str(), port, wifi);
|
||||
}
|
||||
|
||||
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
|
||||
myConfig.getMqttPass());
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), myConfig.getMqttUrl());
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send MQQT message(s)
|
||||
mqtt.setTimeout(myHardwareConfig.getPushTimeout()); // 10 seconds timeout
|
||||
|
||||
int lines = 1;
|
||||
// Find out how many lines are in the document. Each line is one
|
||||
// topic/message. | is used as new line.
|
||||
for (unsigned int i = 0; i < doc.length() - 1; i++) {
|
||||
if (doc.charAt(i) == '|') lines++;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while (lines) {
|
||||
int next = doc.indexOf('|', index);
|
||||
String line = doc.substring(index, next);
|
||||
|
||||
// Each line equals one topic post, format is <topic>:<value>
|
||||
String topic = line.substring(0, line.indexOf(":"));
|
||||
String value = line.substring(line.indexOf(":") + 1);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(),
|
||||
value.c_str());
|
||||
#endif
|
||||
if (mqtt.publish(topic, value)) {
|
||||
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: MQTT push on " + topic +
|
||||
" failed error=" + String(mqtt.lastError()));
|
||||
}
|
||||
|
||||
index = next + 1;
|
||||
lines--;
|
||||
}
|
||||
|
||||
mqtt.disconnect();
|
||||
if (isSecure) {
|
||||
wifiSecure.stop();
|
||||
} else {
|
||||
wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -1,53 +1,56 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _PUSHTARGET_H
|
||||
#define _PUSHTARGET_H
|
||||
|
||||
// Includes
|
||||
#include "helper.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
|
||||
// Classes
|
||||
class PushTarget {
|
||||
private:
|
||||
unsigned long ms; // Used to check that we do not post to often
|
||||
|
||||
void sendBrewfather(float angle, float gravity, float corrGravity, float temp );
|
||||
void sendHttp(String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime);
|
||||
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp, float runTime);
|
||||
|
||||
public:
|
||||
PushTarget() { ms = millis(); }
|
||||
void send(float angle, float gravity, float corrGravity, float temp, float runTime, bool force = false );
|
||||
};
|
||||
|
||||
extern PushTarget myPushTarget;
|
||||
|
||||
#endif // _PUSHTARGET_H
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_PUSHTARGET_HPP_
|
||||
#define SRC_PUSHTARGET_HPP_
|
||||
|
||||
#include <templating.hpp>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
class PushTarget {
|
||||
private:
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
HTTPClient httpSecure;
|
||||
|
||||
void sendBrewfather(TemplatingEngine& engine);
|
||||
void sendHttp(TemplatingEngine& engine, bool isSecure, int index);
|
||||
void sendInfluxDb2(TemplatingEngine& engine);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure);
|
||||
void addHttpHeader(HTTPClient& http, String header);
|
||||
|
||||
public:
|
||||
void send(float angle, float gravitySG, float corrGravitySG, float tempC,
|
||||
float runTime);
|
||||
};
|
||||
|
||||
#endif // SRC_PUSHTARGET_HPP_
|
||||
|
||||
// EOF
|
@ -1,41 +1,44 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <incbin.h>
|
||||
|
||||
#if defined( EMBED_HTML )
|
||||
|
||||
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
|
||||
INCBIN(IndexHtm, "data/index.min.htm" );
|
||||
INCBIN(DeviceHtm, "data/device.min.htm" );
|
||||
INCBIN(ConfigHtm, "data/config.min.htm" );
|
||||
INCBIN(AboutHtm, "data/about.min.htm" );
|
||||
|
||||
#else
|
||||
|
||||
// Minium web interface for uploading htm files
|
||||
INCBIN(UploadHtm, "data/upload.min.htm" );
|
||||
|
||||
#endif
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined (ESP8266)
|
||||
#define INCBIN_OUTPUT_SECTION ".irom.text"
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#include <resources.hpp>
|
||||
|
||||
#if defined(EMBED_HTML)
|
||||
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
|
||||
INCBIN(IndexHtm, "data/index.min.htm");
|
||||
INCBIN(DeviceHtm, "data/device.min.htm");
|
||||
INCBIN(ConfigHtm, "data/config.min.htm");
|
||||
INCBIN(CalibrationHtm, "data/calibration.min.htm");
|
||||
INCBIN(FormatHtm, "data/format.min.htm");
|
||||
INCBIN(AboutHtm, "data/about.min.htm");
|
||||
#else
|
||||
// Minium web interface for uploading htm files
|
||||
INCBIN(UploadHtm, "data/upload.min.htm");
|
||||
#endif
|
||||
|
||||
// EOF
|
||||
|
87
src/resources.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_RESOURCES_HPP_
|
||||
#define SRC_RESOURCES_HPP_
|
||||
|
||||
// Common strings used in json formats.
|
||||
#define PARAM_ID "id"
|
||||
#define PARAM_MDNS "mdns"
|
||||
#define PARAM_CONFIG_VER "config-version"
|
||||
#define PARAM_OTA "ota-url"
|
||||
#define PARAM_SSID "wifi-ssid"
|
||||
#define PARAM_PASS "wifi-pass"
|
||||
#define PARAM_RUNTIME_AVERAGE "runtime-average"
|
||||
#define PARAM_PUSH_BREWFATHER "brewfather-push"
|
||||
#define PARAM_TOKEN "token"
|
||||
#define PARAM_PUSH_HTTP "http-push"
|
||||
#define PARAM_PUSH_HTTP_H1 "http-push-h1"
|
||||
#define PARAM_PUSH_HTTP_H2 "http-push-h2"
|
||||
#define PARAM_PUSH_HTTP2 "http-push2"
|
||||
#define PARAM_PUSH_HTTP2_H1 "http-push2-h1"
|
||||
#define PARAM_PUSH_HTTP2_H2 "http-push2-h2"
|
||||
#define PARAM_PUSH_INFLUXDB2 "influxdb2-push"
|
||||
#define PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
|
||||
#define PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
|
||||
#define PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth"
|
||||
#define PARAM_PUSH_MQTT "mqtt-push"
|
||||
#define PARAM_PUSH_MQTT_USER "mqtt-user"
|
||||
#define PARAM_PUSH_MQTT_PASS "mqtt-pass"
|
||||
#define PARAM_PUSH_MQTT_PORT "mqtt-port"
|
||||
#define PARAM_SLEEP_INTERVAL "sleep-interval"
|
||||
#define PARAM_TEMPFORMAT "temp-format"
|
||||
#define PARAM_VOLTAGEFACTOR "voltage-factor"
|
||||
#define PARAM_GRAVITY_FORMULA "gravity-formula"
|
||||
#define PARAM_GRAVITY_FORMAT "gravity-format"
|
||||
#define PARAM_GRAVITY_TEMP_ADJ "gravity-temp-adjustment"
|
||||
#define PARAM_TEMP_ADJ "temp-adjustment-value"
|
||||
#define PARAM_GYRO_CALIBRATION "gyro-calibration-data"
|
||||
#define PARAM_GYRO_TEMP "gyro-temp"
|
||||
#define PARAM_FORMULA_DATA "formula-calculation-data"
|
||||
#define PARAM_FILES "files"
|
||||
#define PARAM_FILE_NAME "file-name"
|
||||
#define PARAM_FILE_SIZE "file-size"
|
||||
#define PARAM_APP_NAME "app-name"
|
||||
#define PARAM_APP_VER "app-ver"
|
||||
#define PARAM_ANGLE "angle"
|
||||
#define PARAM_GRAVITY "gravity"
|
||||
#define PARAM_TEMP_C "temp-c"
|
||||
#define PARAM_TEMP_F "temp-f"
|
||||
#define PARAM_BATTERY "battery"
|
||||
#define PARAM_SLEEP_MODE "sleep-mode"
|
||||
#define PARAM_RSSI "rssi"
|
||||
#define PARAM_ERROR "error"
|
||||
#define PARAM_HW_GYRO_READ_COUNT "gyro-read-count"
|
||||
#define PARAM_HW_GYRO_READ_DELAY "gyro-read-delay"
|
||||
#define PARAM_HW_GYRO_MOVING_THREASHOLD "gyro-moving-threashold"
|
||||
#define PARAM_HW_FORMULA_DEVIATION "formula-max-deviation"
|
||||
#define PARAM_HW_FORMULA_CALIBRATION_TEMP "formula-calibration-temp"
|
||||
#define PARAM_HW_WIFI_PORTALTIMEOUT "wifi-portaltimeout"
|
||||
#define PARAM_HW_PUSH_TIMEOUT "push-timeout"
|
||||
#define PARAM_FORMAT_HTTP1 "http-1"
|
||||
#define PARAM_FORMAT_HTTP2 "http-2"
|
||||
#define PARAM_FORMAT_BREWFATHER "brewfather"
|
||||
#define PARAM_FORMAT_INFLUXDB "influxdb"
|
||||
#define PARAM_FORMAT_MQTT "mqtt"
|
||||
|
||||
#endif // SRC_RESOURCES_HPP_
|
193
src/templating.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <templating.hpp>
|
||||
#include <config.hpp>
|
||||
|
||||
#if defined (ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
// Use iSpindle format for compatibility
|
||||
const char iSpindleFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\" : \"${mdns}\", "
|
||||
"\"ID\": \"${id}\", "
|
||||
"\"token\" : \"${token}\", "
|
||||
"\"interval\": ${sleep-interval}, "
|
||||
"\"temperature\": ${temp}, "
|
||||
"\"temp-units\": \"${temp-unit}\", "
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"angle\": ${angle}, "
|
||||
"\"battery\": ${battery}, "
|
||||
"\"rssi\": ${rssi}, "
|
||||
"\"corr-gravity\": ${corr-gravity}, "
|
||||
"\"gravity-unit\": \"${gravity-unit}\", "
|
||||
"\"run-time\": ${run-time} "
|
||||
"}";
|
||||
|
||||
const char brewfatherFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\": \"${mdns}\","
|
||||
"\"temp\": ${temp}, "
|
||||
"\"aux_temp\": 0, "
|
||||
"\"ext_temp\": 0, "
|
||||
"\"temp_unit\": \"${temp-unit}\", "
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"gravity_unit\": \"${gravity-unit}\", "
|
||||
"\"pressure\": 0, "
|
||||
"\"pressure_unit\": \"PSI\", "
|
||||
"\"ph\": 0, "
|
||||
"\"bpm\": 0, "
|
||||
"\"comment\": \"\", "
|
||||
"\"beer\": \"\", "
|
||||
"\"battery\": ${battery}"
|
||||
"}";
|
||||
|
||||
const char influxDbFormat[] PROGMEM =
|
||||
"measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-format=${gravity-unit} "
|
||||
"gravity=${gravity},corr-gravity=${corr-gravity},angle=${angle},temp=${temp},battery=${battery},"
|
||||
"rssi=${rssi}\n";
|
||||
|
||||
const char mqttFormat[] PROGMEM =
|
||||
"ispindel/${mdns}/tilt:${angle}|"
|
||||
"ispindel/${mdns}/temperature:${temp}|"
|
||||
"ispindel/${mdns}/temp_units:${temp-unit}|"
|
||||
"ispindel/${mdns}/battery:${battery}|"
|
||||
"ispindel/${mdns}/gravity:${gravity}|"
|
||||
"ispindel/${mdns}/interval:${sleep-interval}|"
|
||||
"ispindel/${mdns}/RSSI:${rssi}|";
|
||||
|
||||
//
|
||||
// Initialize the variables
|
||||
//
|
||||
void TemplatingEngine::initialize(float angle, float gravitySG, float corrGravitySG, float tempC, float runTime) {
|
||||
|
||||
// Names
|
||||
setVal(TPL_MDNS, myConfig.getMDNS());
|
||||
setVal(TPL_ID, myConfig.getID());
|
||||
setVal(TPL_TOKEN, myConfig.getToken());
|
||||
|
||||
// Temperature
|
||||
if (myConfig.isTempC()) {
|
||||
setVal(TPL_TEMP, tempC, 1);
|
||||
} else {
|
||||
setVal(TPL_TEMP, convertCtoF(tempC), 1);
|
||||
}
|
||||
|
||||
setVal(TPL_TEMP_C, tempC, 1);
|
||||
setVal(TPL_TEMP_F, convertCtoF(tempC), 1);
|
||||
setVal(TPL_TEMP_UNITS, myConfig.getTempFormat());
|
||||
|
||||
// Battery & Timer
|
||||
setVal(TPL_BATTERY, myBatteryVoltage.getVoltage());
|
||||
setVal(TPL_SLEEP_INTERVAL, myConfig.getSleepInterval());
|
||||
|
||||
// Performance metrics
|
||||
setVal(TPL_RUN_TIME, runTime, 1);
|
||||
setVal(TPL_RSSI, WiFi.RSSI());
|
||||
|
||||
// Angle/Tilt
|
||||
setVal(TPL_TILT, angle);
|
||||
setVal(TPL_ANGLE, angle);
|
||||
|
||||
// Gravity options
|
||||
if (myConfig.isGravitySG()) {
|
||||
setVal(TPL_GRAVITY, gravitySG, 4);
|
||||
setVal(TPL_GRAVITY_CORR, corrGravitySG, 4);
|
||||
}
|
||||
else {
|
||||
setVal(TPL_GRAVITY, convertToPlato(gravitySG), 1);
|
||||
setVal(TPL_GRAVITY_CORR, convertToPlato(corrGravitySG), 1);
|
||||
}
|
||||
|
||||
setVal(TPL_GRAVITY_G, gravitySG, 4);
|
||||
setVal(TPL_GRAVITY_P, convertToPlato(gravitySG), 1);
|
||||
setVal(TPL_GRAVITY_CORR_G, corrGravitySG, 4);
|
||||
setVal(TPL_GRAVITY_CORR_P, convertToPlato(corrGravitySG), 1);
|
||||
setVal(TPL_GRAVITY_UNIT, myConfig.getGravityFormat());
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// dumpAll();
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Create the data using defined template.
|
||||
//
|
||||
const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
String fname;
|
||||
baseTemplate.reserve(600);
|
||||
|
||||
// Load templates from memory
|
||||
switch (idx) {
|
||||
case TEMPLATE_HTTP1:
|
||||
baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP1;
|
||||
break;
|
||||
case TEMPLATE_HTTP2:
|
||||
baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP2;
|
||||
break;
|
||||
case TEMPLATE_BREWFATHER:
|
||||
baseTemplate = String(brewfatherFormat);
|
||||
//fname = TPL_FNAME_BREWFATHER;
|
||||
break;
|
||||
case TEMPLATE_INFLUX:
|
||||
baseTemplate = String(influxDbFormat);
|
||||
fname = TPL_FNAME_INFLUXDB;
|
||||
break;
|
||||
case TEMPLATE_MQTT:
|
||||
baseTemplate = String(mqttFormat);
|
||||
fname = TPL_FNAME_MQTT;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Add code to load templates from disk if they exist.
|
||||
File file = LittleFS.open(fname, "r");
|
||||
if (file) {
|
||||
char buf[file.size()+1];
|
||||
memset(&buf[0], 0, file.size()+1);
|
||||
file.readBytes(&buf[0], file.size());
|
||||
baseTemplate = String(&buf[0]);
|
||||
file.close();
|
||||
Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str());
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
//Log.verbose(F("TPL : Base '%s'." CR), baseTemplate.c_str());
|
||||
#endif
|
||||
|
||||
// Insert data into template.
|
||||
transform(baseTemplate);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
//Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str());
|
||||
#endif
|
||||
|
||||
return baseTemplate;
|
||||
}
|
||||
|
||||
// EOF
|
143
src/templating.hpp
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_TEMPLATING_HPP_
|
||||
#define SRC_TEMPLATING_HPP_
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
// Templating variables
|
||||
#define TPL_MDNS "${mdns}"
|
||||
#define TPL_ID "${id}"
|
||||
#define TPL_TOKEN "${token}"
|
||||
#define TPL_SLEEP_INTERVAL "${sleep-interval}"
|
||||
#define TPL_TEMP "${temp}"
|
||||
#define TPL_TEMP_C "${temp-c}"
|
||||
#define TPL_TEMP_F "${temp-f}"
|
||||
#define TPL_TEMP_UNITS "${temp-unit}" // C or F
|
||||
#define TPL_BATTERY "${battery}"
|
||||
#define TPL_RSSI "${rssi}"
|
||||
#define TPL_RUN_TIME "${run-time}"
|
||||
#define TPL_ANGLE "${angle}"
|
||||
#define TPL_TILT "${tilt}" // same as angle
|
||||
#define TPL_GRAVITY "${gravity}"
|
||||
#define TPL_GRAVITY_G "${gravity-sg}"
|
||||
#define TPL_GRAVITY_P "${gravity-plato}"
|
||||
#define TPL_GRAVITY_CORR "${corr-gravity}"
|
||||
#define TPL_GRAVITY_CORR_G "${corr-gravity-sg}"
|
||||
#define TPL_GRAVITY_CORR_P "${corr-gravity-plato}"
|
||||
#define TPL_GRAVITY_UNIT "${gravity-unit}" // G or P
|
||||
|
||||
#define TPL_FNAME_HTTP1 "/http-1.tpl"
|
||||
#define TPL_FNAME_HTTP2 "/http-2.tpl"
|
||||
// #define TPL_FNAME_BREWFATHER "/brewfather.tpl"
|
||||
#define TPL_FNAME_INFLUXDB "/influxdb.tpl"
|
||||
#define TPL_FNAME_MQTT "/mqtt.tpl"
|
||||
|
||||
extern const char iSpindleFormat[] PROGMEM;
|
||||
extern const char brewfatherFormat[] PROGMEM;
|
||||
extern const char influxDbFormat[] PROGMEM;
|
||||
extern const char mqttFormat[] PROGMEM;
|
||||
|
||||
// Classes
|
||||
class TemplatingEngine {
|
||||
private:
|
||||
struct KeyVal {
|
||||
String key;
|
||||
String val;
|
||||
};
|
||||
|
||||
KeyVal items[20] = {{TPL_MDNS, ""}, {TPL_ID, ""},
|
||||
{TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""},
|
||||
{TPL_TEMP_C, ""}, {TPL_TEMP_F, ""},
|
||||
{TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""},
|
||||
{TPL_RSSI, ""}, {TPL_RUN_TIME, ""},
|
||||
{TPL_ANGLE, ""}, {TPL_TILT, ""},
|
||||
{TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""},
|
||||
{TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""},
|
||||
{TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""},
|
||||
{TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""}};
|
||||
|
||||
char buffer[20];
|
||||
String baseTemplate;
|
||||
|
||||
void setVal(String key, float val, int dec = 2) {
|
||||
String s = convertFloatToString(val, &buffer[0], dec);
|
||||
s.trim();
|
||||
setVal(key, s);
|
||||
}
|
||||
void setVal(String key, int val) { setVal(key, String(val)); }
|
||||
void setVal(String key, char val) { setVal(key, String(val)); }
|
||||
void setVal(String key, String val) {
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (items[i].key.equals(key)) {
|
||||
items[i].val = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.warning(F("TPL : Key not found %s." CR), key.c_str());
|
||||
}
|
||||
|
||||
void transform(String& s) {
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
while (s.indexOf(items[i].key) != -1)
|
||||
s.replace(items[i].key, items[i].val);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpAll() {
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Serial.print("Key=\'");
|
||||
Serial.print(items[i].key.c_str());
|
||||
Serial.print("\', Val=\'");
|
||||
Serial.print(items[i].val.c_str());
|
||||
Serial.println("\'");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
enum Templates {
|
||||
TEMPLATE_HTTP1 = 0,
|
||||
TEMPLATE_HTTP2 = 1,
|
||||
TEMPLATE_BREWFATHER = 2,
|
||||
TEMPLATE_INFLUX = 3,
|
||||
TEMPLATE_MQTT = 4
|
||||
};
|
||||
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime);
|
||||
const String& create(TemplatingEngine::Templates idx);
|
||||
};
|
||||
|
||||
#endif // SRC_TEMPLATING_HPP_
|
||||
|
||||
// EOF
|
@ -1,132 +1,113 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "tempsensor.h"
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
#include "gyro.h"
|
||||
#include <onewire.h>
|
||||
#include <DallasTemperature.h>
|
||||
#include <Wire.h>
|
||||
|
||||
//
|
||||
// Conversion between C and F
|
||||
//
|
||||
float convertCtoF( float t ) {
|
||||
return (t * 1.8 ) + 32.0;
|
||||
}
|
||||
|
||||
#if !defined( USE_GYRO_TEMP )
|
||||
OneWire myOneWire(D6);
|
||||
DallasTemperature mySensors(&myOneWire);
|
||||
#define TEMPERATURE_PRECISION 9
|
||||
#endif
|
||||
|
||||
TempSensor myTempSensor;
|
||||
|
||||
//
|
||||
// Setup temp sensors
|
||||
//
|
||||
void TempSensor::setup() {
|
||||
|
||||
#if defined( SIMULATE_TEMP )
|
||||
hasSensors = true;
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if defined( USE_GYRO_TEMP )
|
||||
Log.notice(F("TSEN: Using temperature from gyro." CR));
|
||||
#else
|
||||
// This code is used to read the DS18 temp sensor
|
||||
if( mySensors.getDS18Count() )
|
||||
return;
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("TSEN: Looking for temp sensors." CR));
|
||||
#endif
|
||||
mySensors.begin();
|
||||
|
||||
if( mySensors.getDS18Count() ) {
|
||||
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR), mySensors.getDS18Count());
|
||||
mySensors.setResolution(TEMPERATURE_PRECISION);
|
||||
}
|
||||
#endif
|
||||
|
||||
float t = myConfig.getTempSensorAdj();
|
||||
|
||||
// Set the temp sensor adjustment values
|
||||
if( myConfig.isTempC() ) {
|
||||
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
|
||||
tempSensorAdjC = t;
|
||||
} else {
|
||||
tempSensorAdjF = t;
|
||||
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
|
||||
}
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR), tempSensorAdjC, tempSensorAdjF );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieving value from sensor, value is in Celcius
|
||||
//
|
||||
float TempSensor::getValue() {
|
||||
#if defined( SIMULATE_TEMP )
|
||||
return 21;
|
||||
#endif
|
||||
|
||||
#if defined( USE_GYRO_TEMP )
|
||||
// When using the gyro temperature only the first read value will be accurate so we will use this for processing.
|
||||
//LOG_PERF_START("temp-get");
|
||||
float c = myGyro.getInitialSensorTempC();
|
||||
//LOG_PERF_STOP("temp-get");
|
||||
hasSensor = true;
|
||||
return c;
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
|
||||
#endif
|
||||
#else
|
||||
// Read the sensors
|
||||
//LOG_PERF_START("temp-request");
|
||||
mySensors.requestTemperatures();
|
||||
//LOG_PERF_STOP("temp-request");
|
||||
|
||||
float c = 0;
|
||||
|
||||
if( mySensors.getDS18Count() >= 1) {
|
||||
//LOG_PERF_START("temp-get");
|
||||
c = mySensors.getTempCByIndex(0);
|
||||
//LOG_PERF_STOP("temp-get");
|
||||
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("TSEN: Reciving temp value for sensor %F C." CR), c);
|
||||
#endif
|
||||
hasSensor = true;
|
||||
}
|
||||
|
||||
return c;
|
||||
#endif
|
||||
}
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <DallasTemperature.h>
|
||||
#include <OneWire.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <main.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
|
||||
OneWire myOneWire(PIN_DS);
|
||||
DallasTemperature mySensors(&myOneWire);
|
||||
#define TEMPERATURE_PRECISION 9
|
||||
|
||||
TempSensor myTempSensor;
|
||||
|
||||
//
|
||||
// Setup DS18B20 temp sensor. Doing setup is not that time consuming.
|
||||
//
|
||||
void TempSensor::setup() {
|
||||
#if defined(SIMULATE_TEMP)
|
||||
hasSensors = true;
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Looking for temp sensors." CR));
|
||||
#endif
|
||||
mySensors.begin();
|
||||
|
||||
if (mySensors.getDS18Count()) {
|
||||
#if !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR),
|
||||
mySensors.getDS18Count());
|
||||
#endif
|
||||
mySensors.setResolution(TEMPERATURE_PRECISION);
|
||||
}
|
||||
|
||||
// Set the temp sensor adjustment values
|
||||
_tempSensorAdjC = myConfig.getTempSensorAdjC();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C." CR),
|
||||
_tempSensorAdjC);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieving value from sensor, value is in Celcius
|
||||
//
|
||||
float TempSensor::getValue(bool useGyro) {
|
||||
#if defined(SIMULATE_TEMP)
|
||||
return 21;
|
||||
#endif
|
||||
|
||||
if (useGyro) {
|
||||
// When using the gyro temperature only the first read value will be
|
||||
// accurate so we will use this for processing.
|
||||
float c = myGyro.getInitialSensorTempC();
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
|
||||
#endif
|
||||
_hasSensor = true;
|
||||
return c;
|
||||
}
|
||||
|
||||
// If we dont have sensors just return 0
|
||||
if (!mySensors.getDS18Count()) {
|
||||
#if !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.notice(F("TSEN: No temperature sensors found. Skipping read." CR));
|
||||
#endif
|
||||
return -273;
|
||||
}
|
||||
|
||||
// Read the sensors
|
||||
mySensors.requestTemperatures();
|
||||
|
||||
float c = 0;
|
||||
|
||||
if (mySensors.getDS18Count() >= 1) {
|
||||
c = mySensors.getTempCByIndex(0);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Reciving temp value for DS18B20 sensor %F C." CR), c);
|
||||
#endif
|
||||
_hasSensor = true;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -1,50 +1,45 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef _WEBSERVER_H
|
||||
#define _WEBSERVER_H
|
||||
|
||||
// Include
|
||||
|
||||
// classes
|
||||
class WebServer {
|
||||
public:
|
||||
enum HtmlFile {
|
||||
HTML_INDEX = 0,
|
||||
HTML_DEVICE = 1,
|
||||
HTML_CONFIG = 2,
|
||||
HTML_ABOUT = 3
|
||||
};
|
||||
|
||||
bool setupWebServer();
|
||||
void loop();
|
||||
bool checkHtmlFile( HtmlFile item );
|
||||
const char* getHtmlFileName( HtmlFile item );
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern WebServer myWebServer;
|
||||
|
||||
#endif // _WEBSERVER_H
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_TEMPSENSOR_HPP_
|
||||
#define SRC_TEMPSENSOR_HPP_
|
||||
|
||||
class TempSensor {
|
||||
private:
|
||||
bool _hasSensor = false;
|
||||
float _tempSensorAdjC = 0;
|
||||
float getValue(bool useGyro);
|
||||
|
||||
public:
|
||||
void setup();
|
||||
bool isSensorAttached() { return _hasSensor; }
|
||||
float getTempC(bool useGyro = false) {
|
||||
return getValue(useGyro) + _tempSensorAdjC;
|
||||
}
|
||||
};
|
||||
|
||||
extern TempSensor myTempSensor;
|
||||
|
||||
#endif // SRC_TEMPSENSOR_HPP_
|
||||
|
||||
// EOF
|
1627
src/webserver.cpp
135
src/webserver.hpp
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_WEBSERVER_HPP_
|
||||
#define SRC_WEBSERVER_HPP_
|
||||
|
||||
#if defined (ESP8266)
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#if defined(EMBED_HTML)
|
||||
INCBIN_EXTERN(IndexHtm);
|
||||
INCBIN_EXTERN(DeviceHtm);
|
||||
INCBIN_EXTERN(ConfigHtm);
|
||||
INCBIN_EXTERN(CalibrationHtm);
|
||||
INCBIN_EXTERN(FormatHtm);
|
||||
INCBIN_EXTERN(AboutHtm);
|
||||
#else
|
||||
INCBIN_EXTERN(UploadHtm);
|
||||
#endif
|
||||
|
||||
class WebServerHandler {
|
||||
private:
|
||||
ESP8266WebServer* _server = 0;
|
||||
File _uploadFile;
|
||||
int _lastFormulaCreateError = 0;
|
||||
|
||||
void webHandleConfig();
|
||||
void webHandleFormulaWrite();
|
||||
void webHandleFormulaRead();
|
||||
void webHandleConfigHardware();
|
||||
void webHandleConfigGravity();
|
||||
void webHandleConfigPush();
|
||||
void webHandleConfigDevice();
|
||||
void webHandleConfigFormatRead();
|
||||
void webHandleConfigFormatWrite();
|
||||
void webHandleStatusSleepmode();
|
||||
void webHandleClearWIFI();
|
||||
void webHandleStatus();
|
||||
void webHandleFactoryDefaults();
|
||||
void webHandleCalibrate();
|
||||
void webHandleUploadFile();
|
||||
void webHandleUpload();
|
||||
void webHandleDevice();
|
||||
void webHandleDeviceParam();
|
||||
void webHandlePageNotFound();
|
||||
|
||||
String readFile(String fname);
|
||||
bool writeFile(String fname, String data);
|
||||
|
||||
String getRequestArguments();
|
||||
|
||||
// Inline functions.
|
||||
void webReturnOK() { _server->send(200); }
|
||||
#if defined(EMBED_HTML)
|
||||
void webReturnIndexHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gIndexHtmData,
|
||||
gIndexHtmSize);
|
||||
}
|
||||
void webReturnDeviceHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gDeviceHtmData,
|
||||
gDeviceHtmSize);
|
||||
}
|
||||
void webReturnConfigHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gConfigHtmData,
|
||||
gConfigHtmSize);
|
||||
}
|
||||
void webReturnCalibrationHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
|
||||
gCalibrationHtmSize);
|
||||
}
|
||||
void webReturnFormatHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gFormatHtmData,
|
||||
gFormatHtmSize);
|
||||
}
|
||||
void webReturnAboutHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gAboutHtmData,
|
||||
gAboutHtmSize);
|
||||
}
|
||||
#else
|
||||
void webReturnUploadHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gUploadHtmData,
|
||||
gUploadHtmSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
enum HtmlFile {
|
||||
HTML_INDEX = 0,
|
||||
HTML_DEVICE = 1,
|
||||
HTML_CONFIG = 2,
|
||||
HTML_ABOUT = 3,
|
||||
HTML_CALIBRATION = 4,
|
||||
HTML_FORMAT = 5
|
||||
};
|
||||
|
||||
bool setupWebServer();
|
||||
void loop();
|
||||
bool checkHtmlFile(HtmlFile item);
|
||||
const char* getHtmlFileName(HtmlFile item);
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern WebServerHandler myWebServerHandler;
|
||||
|
||||
#endif // SRC_WEBSERVER_HPP_
|
||||
|
||||
// EOF
|
712
src/wifi.cpp
@ -1,283 +1,429 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "wifi.h"
|
||||
#include "config.h"
|
||||
#include "helper.h"
|
||||
#include "gyro.h"
|
||||
#include "calc.h"
|
||||
#include "tempsensor.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <LittleFS.h>
|
||||
#include <incbin.h>
|
||||
|
||||
Wifi myWifi;
|
||||
WiFiManager myWifiManager;
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
const char* userSSID= USER_SSID;
|
||||
const char* userPWD = USER_SSID_PWD;
|
||||
|
||||
//
|
||||
// Callback notifying us of the need to save config
|
||||
//
|
||||
void saveConfigCallback () {
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Connect to last known access point or create one if connection is not working.
|
||||
//
|
||||
bool Wifi::connect( bool showPortal ) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false");
|
||||
myWifiManager.setDebugOutput(true);
|
||||
#endif
|
||||
if( strlen(userSSID)==0 && showPortal ) {
|
||||
Log.notice(F("WIFI: Starting wifi portal." CR));
|
||||
|
||||
myWifiManager.setBreakAfterConfig( true );
|
||||
myWifiManager.setSaveConfigCallback(saveConfigCallback);
|
||||
myWifiManager.setMinimumSignalQuality(10);
|
||||
myWifiManager.setClass("invert");
|
||||
myWifiManager.setHostname( myConfig.getMDNS() );
|
||||
myWifiManager.setConfigPortalTimeout( 120 ); // Keep it open for 120 seconds
|
||||
|
||||
WiFiManagerParameter mdnsParam("mDNS", "hostname", myConfig.getMDNS(), 20);
|
||||
myWifiManager.addParameter( &mdnsParam );
|
||||
|
||||
myWifiManager.startConfigPortal( WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD );
|
||||
|
||||
if( shouldSaveConfig ) {
|
||||
myConfig.setMDNS( mdnsParam.getValue() );
|
||||
myConfig.saveFile();
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to wifi
|
||||
int i = 0;
|
||||
|
||||
Log.notice(F("WIFI: Connecting to WIFI." CR));
|
||||
WiFi.mode(WIFI_STA);
|
||||
if( strlen(userSSID) ) {
|
||||
Log.notice(F("WIFI: Connecting to wifi using predefined settings %s." CR), userSSID);
|
||||
WiFi.begin( userSSID, userPWD );
|
||||
} else {
|
||||
WiFi.begin();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Using SSID=%s, KEY=%s." CR), WiFi.SSID().c_str(), WiFi.psk().c_str() );
|
||||
#endif
|
||||
}
|
||||
|
||||
while( WiFi.status() != WL_CONNECTED ) {
|
||||
delay(100);
|
||||
Serial.print( "." );
|
||||
|
||||
// if( i++ > 60 ) { // Try for 6 seconds.
|
||||
if( i++ > 200 ) { // Try for 20 seconds.
|
||||
Log.error(F("WIFI: Failed to connect to wifi, aborting." CR));
|
||||
return connectedFlag; // Return to main that we have failed to connect.
|
||||
}
|
||||
}
|
||||
Serial.print( CR );
|
||||
connectedFlag = true;
|
||||
Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str() );
|
||||
Log.notice(F("WIFI: Using mDNS name %s%s." CR), myConfig.getMDNS() );
|
||||
return connectedFlag;
|
||||
}
|
||||
|
||||
//
|
||||
// This will erase the stored credentials and forcing the WIFI manager to AP mode.
|
||||
//
|
||||
bool Wifi::disconnect() {
|
||||
Log.notice(F("WIFI: Erasing stored WIFI credentials." CR));
|
||||
// Erase WIFI credentials
|
||||
return WiFi.disconnect(true);
|
||||
}
|
||||
|
||||
#if defined( ACTIVATE_OTA )
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
bool Wifi::updateFirmware() {
|
||||
if( !newFirmware ) {
|
||||
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
|
||||
return false;
|
||||
}
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Updating firmware." CR));
|
||||
#endif
|
||||
|
||||
WiFiClient client;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "firmware.bin";
|
||||
|
||||
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
|
||||
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
Log.error(F("WIFI: OTA update failed %d, %s." CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
Log.notice("WIFI: OTA Update sucesfull, rebooting." );
|
||||
delay(100);
|
||||
ESP.reset();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Download and save file
|
||||
//
|
||||
void Wifi::downloadFile(const char *fname) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Download file %s." CR), fname);
|
||||
#endif
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += fname;
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin( client, serverPath);
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode==200) {
|
||||
File f = LittleFS.open( fname, "w" );
|
||||
http.writeToStream( &f );
|
||||
f.close();
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
|
||||
} else {
|
||||
Log.error(F("WIFI: Failed to download file, respone=%d" CR), httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Check what firmware version is available over OTA
|
||||
//
|
||||
bool Wifi::checkFirmwareVersion() {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Checking if new version exist." CR));
|
||||
#endif
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "version.json";
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin( client, serverPath);
|
||||
|
||||
// Send HTTP GET request
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode==200) {
|
||||
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
|
||||
|
||||
String payload = http.getString();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
|
||||
#endif
|
||||
DynamicJsonDocument ver(300);
|
||||
DeserializationError err = deserializeJson(ver, payload);
|
||||
if( err ) {
|
||||
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
|
||||
} else {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Project %s version %s." CR), (const char*) ver["project"], (const char*) ver["version"]);
|
||||
#endif
|
||||
int newVer[3];
|
||||
int curVer[3];
|
||||
|
||||
if( parseFirmwareVersionString( newVer, (const char*) ver["version"] ) ) {
|
||||
if( parseFirmwareVersionString( curVer, CFG_APPVER) ) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR), newVer[0], newVer[1], newVer[2], curVer[0], curVer[1], curVer[2] );
|
||||
#endif
|
||||
// Compare major version
|
||||
if( newVer[0] > curVer[0] )
|
||||
newFirmware = true;
|
||||
// Compare minor version
|
||||
if( newVer[0] == curVer[0] && newVer[1] > curVer[1] )
|
||||
newFirmware = true;
|
||||
// Compare patch version
|
||||
if( newVer[0] == curVer[0] && newVer[1] == curVer[1] && newVer[2] > curVer[2] )
|
||||
newFirmware = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Download new html files to filesystem if they are present.
|
||||
if( !ver["html"].isNull() && newFirmware ) {
|
||||
Log.notice(F("WIFI: OTA downloading new html files." CR));
|
||||
JsonArray htmlFiles = ver["html"].as<JsonArray>();
|
||||
for(JsonVariant v : htmlFiles) {
|
||||
String s = v;
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str() );
|
||||
#endif
|
||||
downloadFile( s.c_str() );
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.error(F("WIFI: OTA error checking version.json, response=%d" CR), httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: OTA found new version %s." CR), newFirmware?"true":"false");
|
||||
#endif
|
||||
return newFirmware;
|
||||
}
|
||||
|
||||
//
|
||||
// Parse a version string in the format M.m.p (eg. 1.2.10)
|
||||
//
|
||||
bool Wifi::parseFirmwareVersionString( int (&num)[3], const char *version ) {
|
||||
#if LOG_LEVEL==6
|
||||
Log.verbose(F("WIFI: Parsing version number string %s." CR), version);
|
||||
#endif
|
||||
char temp[80];
|
||||
char *s;
|
||||
char *p = &temp[0];
|
||||
int i = 0;
|
||||
|
||||
strcpy( &temp[0], version );
|
||||
|
||||
while ((s = strtok_r(p, ".", &p)) != NULL) {
|
||||
num[i++] = atoi( s );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // ACTIVATE_OTA
|
||||
|
||||
// EOF
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <main.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// Settings for DRD
|
||||
#if defined(ESP8266)
|
||||
#define ESP_DRD_USE_LITTLEFS true
|
||||
#define ESP_DRD_USE_SPIFFS false
|
||||
#else // defined (ESP32)
|
||||
#define ESP_DRD_USE_LITTLEFS false
|
||||
#define ESP_DRD_USE_SPIFFS true
|
||||
#endif
|
||||
#define ESP_DRD_USE_EEPROM false
|
||||
#include <ESP_DoubleResetDetector.h>
|
||||
#define DRD_TIMEOUT 3
|
||||
#define DRD_ADDRESS 0
|
||||
|
||||
// Settings for WIFI Manager
|
||||
#define USE_ESP_WIFIMANAGER_NTP false
|
||||
#define USE_CLOUDFLARE_NTP false
|
||||
#define USING_CORS_FEATURE false
|
||||
#define NUM_WIFI_CREDENTIALS 1
|
||||
#define USE_STATIC_IP_CONFIG_IN_CP false
|
||||
#include <ESP_WiFiManager.h>
|
||||
// Override the look and feel of the standard ui (hide secondary forms)
|
||||
const char WM_HTTP_FORM_START[] PROGMEM =
|
||||
"<form method='get' "
|
||||
"action='wifisave'><fieldset><div><label>SSID</label><input id='s' "
|
||||
"name='s' length=32 "
|
||||
"placeholder='SSID'><div></div></div><div><label>Password</label><input "
|
||||
"id='p' name='p' length=64 placeholder='password'><div></div></div><div "
|
||||
"hidden><label>SSID1</label><input id='s1' name='s1' length=32 "
|
||||
"placeholder='SSID1'><div></div></div><div "
|
||||
"hidden><label>Password</label><input id='p1' name='p1' length=64 "
|
||||
"placeholder='password1'><div></div></div></fieldset>";
|
||||
#include <ESP_WiFiManager-Impl.h>
|
||||
ESP_WiFiManager *myWifiManager;
|
||||
DoubleResetDetector *myDRD;
|
||||
|
||||
WifiConnection myWifi;
|
||||
|
||||
const char *userSSID = USER_SSID;
|
||||
const char *userPWD = USER_SSID_PWD;
|
||||
|
||||
//
|
||||
// Initialize
|
||||
//
|
||||
void WifiConnection::init() {
|
||||
myDRD = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
|
||||
}
|
||||
|
||||
//
|
||||
// Check if we have a valid wifi configuration
|
||||
//
|
||||
bool WifiConnection::hasConfig() {
|
||||
if (strlen(myConfig.getWifiSSID())) return true;
|
||||
if (strlen(userSSID)) return true;
|
||||
|
||||
// Check if there are stored WIFI Settings we can use.
|
||||
String ssid = WiFi.SSID();
|
||||
if (ssid.length()) {
|
||||
Log.notice(F("WIFI: Found credentials in EEPORM." CR));
|
||||
myConfig.setWifiSSID(WiFi.SSID());
|
||||
myConfig.setWifiPass(WiFi.psk());
|
||||
myConfig.saveFile();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the wifi is connected
|
||||
//
|
||||
bool WifiConnection::isConnected() { return WiFi.status() == WL_CONNECTED; }
|
||||
|
||||
//
|
||||
// Get the IP adress
|
||||
//
|
||||
String WifiConnection::getIPAddress() { return WiFi.localIP().toString(); }
|
||||
|
||||
//
|
||||
// Additional method to detect double reset.
|
||||
//
|
||||
bool WifiConnection::isDoubleResetDetected() {
|
||||
if (strlen(userSSID))
|
||||
return false; // Ignore this if we have hardcoded settings.
|
||||
return myDRD->detectDoubleReset();
|
||||
}
|
||||
|
||||
//
|
||||
// Stop double reset detection
|
||||
//
|
||||
void WifiConnection::stopDoubleReset() { myDRD->stop(); }
|
||||
|
||||
//
|
||||
// Start the wifi manager
|
||||
//
|
||||
void WifiConnection::startPortal() {
|
||||
Log.notice(F("WIFI: Starting Wifi config portal." CR));
|
||||
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
|
||||
myWifiManager = new ESP_WiFiManager(WIFI_MDNS);
|
||||
myWifiManager->setMinimumSignalQuality(-1);
|
||||
myWifiManager->setConfigPortalChannel(0);
|
||||
myWifiManager->setConfigPortalTimeout(
|
||||
myHardwareConfig.getWifiPortalTimeout());
|
||||
|
||||
if (myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD)) {
|
||||
Log.notice(F("WIFI: Exited portal, connected to wifi. Rebooting..." CR));
|
||||
myConfig.setWifiSSID(myWifiManager->getSSID());
|
||||
myConfig.setWifiPass(myWifiManager->getPW());
|
||||
myConfig.saveFile();
|
||||
} else {
|
||||
Log.notice(
|
||||
F("WIFI: Exited portal, no connection to wifi. Rebooting..." CR));
|
||||
}
|
||||
|
||||
stopDoubleReset();
|
||||
delay(500);
|
||||
ESP_RESET();
|
||||
}
|
||||
|
||||
//
|
||||
// Call the wifi manager in loop
|
||||
//
|
||||
void WifiConnection::loop() { myDRD->loop(); }
|
||||
|
||||
//
|
||||
// Connect to last known access point, non blocking mode.
|
||||
//
|
||||
void WifiConnection::connectAsync() {
|
||||
WiFi.persistent(true);
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (strlen(userSSID)) {
|
||||
Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR),
|
||||
userSSID);
|
||||
WiFi.begin(userSSID, userPWD);
|
||||
} else {
|
||||
Log.notice(F("WIFI: Connecting to wifi using stored settings %s." CR),
|
||||
myConfig.getWifiSSID());
|
||||
WiFi.begin(myConfig.getWifiSSID(), myConfig.getWifiPass());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Blocks until wifi connection has been found
|
||||
//
|
||||
bool WifiConnection::waitForConnection(int maxTime) {
|
||||
#if DEBUG_LEVEL == 6
|
||||
WiFi.printDiag(Serial);
|
||||
#endif
|
||||
int i = 0;
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
|
||||
if (i % 10) Serial.print(".");
|
||||
|
||||
if (i++ >
|
||||
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to connect to wifi " +
|
||||
String(WiFi.status()));
|
||||
WiFi.disconnect();
|
||||
Serial.print(CR);
|
||||
return false; // Return to main that we have failed to connect.
|
||||
}
|
||||
}
|
||||
Serial.print(CR);
|
||||
Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str());
|
||||
Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS());
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Connect to last known access point, blocking mode.
|
||||
//
|
||||
bool WifiConnection::connect() {
|
||||
connectAsync();
|
||||
return waitForConnection(20); // 20 seconds.
|
||||
}
|
||||
|
||||
//
|
||||
// This will erase the stored credentials and forcing the WIFI manager to AP
|
||||
// mode.
|
||||
//
|
||||
bool WifiConnection::disconnect() {
|
||||
Log.notice(F("WIFI: Erasing stored WIFI credentials." CR));
|
||||
// Erase WIFI credentials
|
||||
return WiFi.disconnect(true);
|
||||
}
|
||||
|
||||
#if defined(ACTIVATE_OTA)
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
bool WifiConnection::updateFirmware() {
|
||||
if (!_newFirmware) {
|
||||
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
|
||||
return false;
|
||||
}
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: Updating firmware." CR));
|
||||
#endif
|
||||
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPUpdateResult ret;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "firmware.bin";
|
||||
|
||||
if (serverPath.startsWith("https://")) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
ret = ESPhttpUpdate.update(wifiSecure, serverPath);
|
||||
} else {
|
||||
ret = ESPhttpUpdate.update(wifi, serverPath);
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case HTTP_UPDATE_FAILED: {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: OTA update failed " +
|
||||
String(ESPhttpUpdate.getLastError()));
|
||||
} break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
break;
|
||||
case HTTP_UPDATE_OK: {
|
||||
Log.notice("WIFI: OTA Update sucesfull, rebooting.");
|
||||
delay(100);
|
||||
ESP_RESET();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Download and save file
|
||||
//
|
||||
void WifiConnection::downloadFile(const char *fname) {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Download file %s." CR), fname);
|
||||
#endif
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += fname;
|
||||
|
||||
if (myConfig.isOtaSSL()) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
http.begin(wifiSecure, serverPath);
|
||||
} else {
|
||||
http.begin(wifi, serverPath);
|
||||
}
|
||||
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
File f = LittleFS.open(fname, "w");
|
||||
http.writeToStream(&f);
|
||||
f.close();
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to download html-file " +
|
||||
String(httpResponseCode));
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Check what firmware version is available over OTA
|
||||
//
|
||||
bool WifiConnection::checkFirmwareVersion() {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Checking if new version exist." CR));
|
||||
#endif
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "version.json";
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
if (myConfig.isOtaSSL()) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
http.begin(wifiSecure, serverPath);
|
||||
} else {
|
||||
http.begin(wifi, serverPath);
|
||||
}
|
||||
|
||||
// Send HTTP GET request
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
|
||||
|
||||
String payload = http.getString();
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
|
||||
#endif
|
||||
DynamicJsonDocument ver(300);
|
||||
DeserializationError err = deserializeJson(ver, payload);
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("WIFI: Failed to parse version.json"));
|
||||
} else {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Project %s version %s." CR),
|
||||
(const char *)ver["project"], (const char *)ver["version"]);
|
||||
#endif
|
||||
int newVer[3];
|
||||
int curVer[3];
|
||||
|
||||
if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) {
|
||||
if (parseFirmwareVersionString(curVer, CFG_APPVER)) {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR),
|
||||
newVer[0], newVer[1], newVer[2], curVer[0], curVer[1],
|
||||
curVer[2]);
|
||||
#endif
|
||||
// Compare major version
|
||||
if (newVer[0] > curVer[0]) _newFirmware = true;
|
||||
// Compare minor version
|
||||
if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
|
||||
_newFirmware = true;
|
||||
// Compare patch version
|
||||
if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
|
||||
newVer[2] > curVer[2])
|
||||
_newFirmware = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Download new html files to filesystem if they are present.
|
||||
if (!ver["html"].isNull() && _newFirmware) {
|
||||
Log.notice(F("WIFI: OTA downloading new html files." CR));
|
||||
JsonArray htmlFiles = ver["html"].as<JsonArray>();
|
||||
for (JsonVariant v : htmlFiles) {
|
||||
String s = v;
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str());
|
||||
#endif
|
||||
downloadFile(s.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.error(F("WIFI: OTA error checking version.json, response=%d" CR),
|
||||
httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: OTA found new version %s." CR),
|
||||
_newFirmware ? "true" : "false");
|
||||
#endif
|
||||
|
||||
return _newFirmware;
|
||||
}
|
||||
|
||||
//
|
||||
// Parse a version string in the format M.m.p (eg. 1.2.10)
|
||||
//
|
||||
bool WifiConnection::parseFirmwareVersionString(int (&num)[3],
|
||||
const char *version) {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Parsing version number string %s." CR), version);
|
||||
#endif
|
||||
char temp[80];
|
||||
char *s;
|
||||
char *p = &temp[0];
|
||||
int i = 0;
|
||||
|
||||
// strcpy(&temp[0], version);
|
||||
snprintf(&temp[0], sizeof(temp), "%s", version);
|
||||
|
||||
while ((s = strtok_r(p, ".", &p)) != NULL) {
|
||||
num[i++] = atoi(s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // ACTIVATE_OTA
|
||||
|
||||
// EOF
|
||||
|
66
src/wifi.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_WIFI_HPP_
|
||||
#define SRC_WIFI_HPP_
|
||||
|
||||
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
|
||||
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
|
||||
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
|
||||
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
|
||||
class WifiConnection {
|
||||
private:
|
||||
// OTA
|
||||
bool _newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char* version);
|
||||
void downloadFile(const char* fname);
|
||||
void connectAsync();
|
||||
bool waitForConnection(int maxTime = 20);
|
||||
|
||||
public:
|
||||
// WIFI
|
||||
void init();
|
||||
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
bool isConnected();
|
||||
bool isDoubleResetDetected();
|
||||
void stopDoubleReset();
|
||||
bool hasConfig();
|
||||
String getIPAddress();
|
||||
void startPortal();
|
||||
void loop();
|
||||
|
||||
// OTA
|
||||
bool updateFirmware();
|
||||
bool checkFirmwareVersion();
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern WifiConnection myWifi;
|
||||
|
||||
#endif // SRC_WIFI_HPP_
|
||||
|
||||
// EOF
|
21
src_docs/Makefile
Normal file
@ -0,0 +1,21 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
35
src_docs/make.bat
Normal file
@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
92
src_docs/source/advanced.rst
Normal file
@ -0,0 +1,92 @@
|
||||
Advanced Configuration
|
||||
######################
|
||||
|
||||
.. _format-editor:
|
||||
|
||||
Format editor
|
||||
+++++++++++++
|
||||
|
||||
To reduce the need for adding custom endpoints for various services there is an built in format editor that allows the user to customize the format being sent to the push target.
|
||||
|
||||
.. warning::
|
||||
|
||||
Since the format templates can be big this function can be quite slow on a small device such as the esp8266.
|
||||
|
||||
.. image:: images/format.png
|
||||
:width: 800
|
||||
:alt: Format editor
|
||||
|
||||
You enter the format data in the text field and the test button will show an example on what the output would look like. If the data cannot be formatted in json it will just be displayed as a long string.
|
||||
The save button will save the current formla and reload the data from the device.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you save a blank string the default template will be loaded.
|
||||
|
||||
These are the format keys available for use in the format.
|
||||
|
||||
.. list-table:: Directory structure
|
||||
:widths: 30 50 20
|
||||
:header-rows: 1
|
||||
|
||||
* - key
|
||||
- description
|
||||
- example
|
||||
* - ${mdns}
|
||||
- Name of the device
|
||||
- gravmon2
|
||||
* - ${id}
|
||||
- Unique id of the device
|
||||
- e422a3
|
||||
* - ${sleep-interval}
|
||||
- Seconds between data is pushed
|
||||
- 900
|
||||
* - ${temp}
|
||||
- Temperature in format configured on device, one decimal
|
||||
- 21.2
|
||||
* - ${temp-c}
|
||||
- Temperature in C, one decimal
|
||||
- 21.2
|
||||
* - ${temp-f}
|
||||
- Temperature in F, one decimal
|
||||
- 58.0
|
||||
* - ${temp-unit}
|
||||
- Temperature format `C` or `F`
|
||||
- C
|
||||
* - ${battery}
|
||||
- Battery voltage, two decimals
|
||||
- 3.89
|
||||
* - ${rssi}
|
||||
- Wifi signal strength
|
||||
- -75
|
||||
* - ${run-time}
|
||||
- How long the last measurement took, two decimals
|
||||
- 3.87
|
||||
* - ${angle}
|
||||
- Angle of the gyro, two decimals
|
||||
- 28.67
|
||||
* - ${tilt}
|
||||
- Same as angle.
|
||||
- 28.67
|
||||
* - ${gravity}
|
||||
- Calculated gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${gravity-sg}
|
||||
- Calculated gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${gravity-plato}
|
||||
- Calculated gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${corr-gravity}
|
||||
- Temperature corrected gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${corr-gravity-sg}
|
||||
- Temperature corrected gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${corr-gravity-plato}
|
||||
- Temperature corrected gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${gravity-unit}
|
||||
- Gravity format, `G` or `P`
|
||||
- G
|
||||
|
338
src_docs/source/api.rst
Normal file
@ -0,0 +1,338 @@
|
||||
.. _rest-api:
|
||||
|
||||
REST API
|
||||
########
|
||||
|
||||
All the API's use a key called ``ID`` which is the unique device id (chip id). This is used as an API key when sending requests to the device.
|
||||
|
||||
GET: /api/config
|
||||
================
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``gravity-format`` is always ``G`` (plato is not yet supported)
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"mdns": "gravmon",
|
||||
"id": "ee1bfc",
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"token": "token",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
"http-push-h1": "header: value",
|
||||
"http-push-h2": "header: value",
|
||||
"http-push2": "http://192.168.1.50/ispindel",
|
||||
"http-push2-h1": "header: value",
|
||||
"http-push2-h2": "header: value",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "org",
|
||||
"influxdb2-bucket": "bucket_id",
|
||||
"influxdb2-auth": "token",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "user",
|
||||
"mqtt-pass": "pass",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "G",
|
||||
"temp-adjustment-value": 0,
|
||||
"gravity-temp-adjustment": false,
|
||||
"gyro-temp": true,
|
||||
"gyro-calibration-data": {
|
||||
"ax": -330,
|
||||
"ay": -2249,
|
||||
"az": 1170,
|
||||
"gx": 99,
|
||||
"gy": -6,
|
||||
"gz": 4
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04,
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
GET: /api/device
|
||||
================
|
||||
|
||||
Retrive the current device settings via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"app-name": "GravityMon",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon",
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"gravity-tempcorr": 1.1031,
|
||||
"temp-c": 0,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"rssi": -56
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1,
|
||||
"gravity-format": "G",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436"
|
||||
}
|
||||
|
||||
|
||||
GET: /api/factory
|
||||
================
|
||||
|
||||
Will do a reset to factory defaults and delete all data except wifi settings.
|
||||
|
||||
For this to work you will need to supply the device id as a parameter in the request:
|
||||
|
||||
::
|
||||
|
||||
http://mygravity.local/api/factory?id=<mydeviceid>
|
||||
|
||||
|
||||
POST: /api/config/device
|
||||
========================
|
||||
|
||||
Used to update device settings via an HTTP POST command.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
* ``temp-format`` can be either ``C`` (Celcius) or ``F`` (Farenheight)
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
mdns=gravmon
|
||||
temp-format=C
|
||||
sleep-interval=30
|
||||
|
||||
|
||||
POST: /api/config/push
|
||||
======================
|
||||
|
||||
Used to update push settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
http-push=http://192.168.1.50/ispindel
|
||||
http-push2=
|
||||
http-push-h1=
|
||||
http-push-h2=
|
||||
http-push2-h1=
|
||||
http-push2-h2=
|
||||
brewfather-push=
|
||||
influxdb2-push=http://192.168.1.50:8086
|
||||
influxdb2-org=
|
||||
influxdb2-bucket=
|
||||
influxdb2-auth=
|
||||
mqtt-push=192.168.1.50
|
||||
mqtt-port=1883
|
||||
mqtt-user=
|
||||
mqtt-pass=
|
||||
|
||||
|
||||
POST: /api/config/gravity
|
||||
=========================
|
||||
|
||||
Used to update gravity settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``gravity-formula`` keywords ``temp`` and ``tilt`` are supported.
|
||||
* ``gravity-format`` can be either ``G`` (SG) or ``P`` (PLATO)
|
||||
|
||||
.. note::
|
||||
``gravity-temp-adjustment`` is defined as "on" or "off" when posting since this is the output values
|
||||
from a checkbox, when reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
gravity-formula=0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436,
|
||||
gravity-format=P
|
||||
gravity-temp-adjustment=off
|
||||
|
||||
|
||||
POST: /api/config/hardware
|
||||
==========================
|
||||
|
||||
Used to update hardware settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. note::
|
||||
``gyro-temp`` is defined as "on" or "off" when posting since this is the output values from a checkbox, when
|
||||
reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
voltage-factor=1.59
|
||||
temp-adjustment=0
|
||||
gyro-temp=off
|
||||
ota-url=http://192.168.1.50/firmware/gravmon/
|
||||
|
||||
|
||||
POST: /api/config/formula
|
||||
=========================
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
a1=22.4
|
||||
a2=54.4
|
||||
a3=58
|
||||
a4=0
|
||||
a5=0
|
||||
g1=1.000
|
||||
g2=1.053
|
||||
g3=1.062
|
||||
g4=1
|
||||
g5=1
|
||||
|
||||
|
||||
Calling the API's from Python
|
||||
=============================
|
||||
|
||||
Here is some example code for how to access the API's from a python script. Keys should always be
|
||||
present or the API call will fail.
|
||||
|
||||
The requests package converts the json to standard form post format.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
id = "ee1bfc" # Device ID (shown in serial console during startup or in UI)
|
||||
|
||||
def set_config( url, json ):
|
||||
headers = { "ContentType": "application/json" }
|
||||
print( url )
|
||||
resp = requests.post( url, headers=headers, data=json )
|
||||
if resp.status_code != 200 :
|
||||
print ( "Failed " )
|
||||
else :
|
||||
print ( "Success " )
|
||||
|
||||
url = "http://" + host + "/api/config/device"
|
||||
json = { "id": id,
|
||||
"mdns": "gravmon", # Name of the device
|
||||
"temp-format": "C", # Temperature format C or F
|
||||
"sleep-interval": 30 # Sleep interval in seconds
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"token": "",
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"http-push-h1": "",
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": "",
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "",
|
||||
"gravity-format": "P",
|
||||
"gravity-temp-adjustment": "off" # Adjust gravity (on/off)
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/hardware"
|
||||
json = { "id": id,
|
||||
"voltage-factor": 1.59, # Default value for voltage calculation
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/formula"
|
||||
json = { "id": id,
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
set_config( url, json )
|
103
src_docs/source/compiling.rst
Normal file
@ -0,0 +1,103 @@
|
||||
.. _compiling-the-software:
|
||||
|
||||
Compiling the software
|
||||
######################
|
||||
|
||||
Tools
|
||||
=====
|
||||
I use the following tools in order to build and manage the software:
|
||||
|
||||
* Visual Studio Code
|
||||
* PlatformIO
|
||||
* Git for Windows
|
||||
* VSCode plugin: Minify (used to minimise the html files)
|
||||
|
||||
Code Formatting
|
||||
===============
|
||||
I use pre-commit and their cpp style checks to validate the code. Plugin defintions are found in **.pre-commit-config.yaml**
|
||||
|
||||
`Pre-Commit <https://www.pre-commit.com>`_
|
||||
|
||||
.. note::
|
||||
|
||||
There is not yet any automatic checks since this does not work on Windows. It works if running under WSL2 with Ubuntu.
|
||||
|
||||
|
||||
Targets
|
||||
=======
|
||||
In the platformio config there are 3 targets defined
|
||||
|
||||
* gravity-debug; Maximum logging for trouble shooting, deep sleep is disabled.
|
||||
* gravity-release; Standard release
|
||||
* gravity-perf; Standard release but contains code for measuring performance
|
||||
* gravity32-perf: Experimental version for ESP32.
|
||||
|
||||
.. note::
|
||||
There is an experimental ESP32 target but since platformio only supports SDK 1.0.6 and the WIFI connection is really slow compared to ESP8266,
|
||||
so the recommendation is to wait for support on 2.0.x branch. With the tested version an wifi connection takes 3-8s on a ESP32 compared
|
||||
to 0.5s on an ESP8266. There is also a bug in OneWire connected to ESP32 that has not been fixed in the main repository yet.
|
||||
|
||||
.. warning::
|
||||
The debug target can be unstable and crash the device under certain circumstanses. Excessive logging to the serial port can cause corruption and crashes.
|
||||
So only enable enough debugging to troubleshoot your changes.
|
||||
|
||||
|
||||
Source structure
|
||||
================
|
||||
.. list-table:: Directory structure
|
||||
:widths: 40 60
|
||||
:header-rows: 1
|
||||
|
||||
* - path
|
||||
- content
|
||||
* - /bin
|
||||
- Contains compiled binaries
|
||||
* - /data
|
||||
- Directory for flashing device filesystem
|
||||
* - /doc
|
||||
- Various external documents used as input
|
||||
* - /html
|
||||
- Source for html files
|
||||
* - /img
|
||||
- Images uses in README.md
|
||||
* - /lib
|
||||
- External libraries used when compiling
|
||||
* - /script
|
||||
- Scripts used in build process
|
||||
* - /src
|
||||
- Source code for software
|
||||
* - /src_docs
|
||||
- Source code for documentation
|
||||
* - /stl
|
||||
- 3d models
|
||||
* - /test
|
||||
- Test data for developing html files
|
||||
|
||||
|
||||
Options
|
||||
=======
|
||||
This is a list of C++ defines that is used to enable/disable functions in the code.
|
||||
|
||||
.. list-table:: Defines
|
||||
:widths: 40 60
|
||||
:header-rows: 1
|
||||
|
||||
* - define
|
||||
- description
|
||||
* - ACTIVATE_OTA
|
||||
- Enables the OTA functionallity in the code
|
||||
* - SKIP_SLEEPMODE
|
||||
- The device never goes into sleep mode, useful when developing.
|
||||
* - xxx_DISABLE_LOGGING
|
||||
- Done include verbose logging in the corresponding class. Excessive logging may crash device.
|
||||
* - USE_LITTLEFS
|
||||
- Use the new filesystem in Ardurino
|
||||
* - EMBED_HTML
|
||||
- Html files are included in code, if not defined they are served from the file system.
|
||||
* - USER_SSID
|
||||
- If defined the device will always use this SSID
|
||||
* - USER_SSID_PWD
|
||||
- Password to the SSID
|
||||
* - CFG_APPVER
|
||||
- Defines the version of the compiled software
|
||||
|
57
src_docs/source/conf.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'GravityMon'
|
||||
copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.8.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'furo'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_show_sourcelink = False
|
||||
html_show_sphinx = True
|
276
src_docs/source/configuration.rst
Normal file
@ -0,0 +1,276 @@
|
||||
.. _setting-up-device:
|
||||
|
||||
Configuration
|
||||
#############
|
||||
|
||||
The device can operate in two modes and must be in ``configuration mode`` in order for the web server to be active.
|
||||
|
||||
One of the following conditions will place the device in ``configuration mode``:
|
||||
|
||||
- Gyro has not been calibrated
|
||||
- Sleep mode has been disabled in the web interface
|
||||
- Placed in horizontal mode 85-90 degrees
|
||||
- Charger connected >4.15V
|
||||
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
URL: (http://gravmon.local)
|
||||
|
||||
.. image:: images/index.png
|
||||
:width: 800
|
||||
:alt: Index page
|
||||
|
||||
Configuration is accessed by entering the URL for the device, this will be the mDNS name *device.local* or the IP adress. The following chapter assumes the device name is *gravmon*.
|
||||
|
||||
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if
|
||||
you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you are connected to the device via a serial console (speed: 115200) you can see the connection sequence and get the Unique ID and IP adress from there.
|
||||
|
||||
|
||||
Device
|
||||
======
|
||||
|
||||
URL: (http://gravmon.local/device)
|
||||
|
||||
.. image:: images/device.png
|
||||
:width: 800
|
||||
:alt: Device Settings
|
||||
|
||||
.. tip::
|
||||
|
||||
The button `view error log` will show the last 15 errors on the device. This can be useful for checking errors without
|
||||
the need to connect to the serial port or to check what errors has occured while in `gravity mode`.
|
||||
|
||||
* **Version:**
|
||||
|
||||
Installed version of the code and html files.
|
||||
|
||||
* **Device name:**
|
||||
|
||||
This is unique name of the device which is set in the configuration, also known as MDNS name.
|
||||
|
||||
* **Device ID:**
|
||||
|
||||
This is unique identifier for the device (ESP8266 id), this is required when using the API as an API Key to safeguard
|
||||
against faulty requests. This is the ESP8266 chip ID, so it should be unique.
|
||||
|
||||
* **Average runtime:**
|
||||
|
||||
This shows the average time a gravity measurement takes. Under optimal setting this should be
|
||||
around 1.5 - 2.0 seconds. If this is higher than 2 seconds this is most likley connected to slow wifi
|
||||
connection. It will show 0 if data has not been collected yet.
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
URL: (http://gravmon.local/config)
|
||||
|
||||
Device Setting
|
||||
++++++++++++++
|
||||
|
||||
.. image:: images/config1.png
|
||||
:width: 800
|
||||
:alt: Device Settings
|
||||
|
||||
* **Device name:**
|
||||
|
||||
This is unique name for the device. It will be used in pushing data as well as mDNS name on the network (<name>.local)
|
||||
|
||||
* **Temperature format:**
|
||||
|
||||
Choose between Celsius and Farenheight when displaying temperature.
|
||||
|
||||
* **Interval:**
|
||||
|
||||
This defines how long the device should be sleeping between the readings when in `gravity monitoring` mode. You will also see
|
||||
the values in minutes/seconds to easier set the interval. 900s is a recommended interval. The sleep interval can
|
||||
be set between 10 - 3600 seconds (60 minutes).
|
||||
|
||||
.. note::
|
||||
|
||||
A low value such as 30s will give a lifespan of 1-2 weeks and 300s (5 min) would last for 3+ weeks. This assumes that
|
||||
there is good wifi connection that takes less than 1s to reconnect. Poor wifi connection is the main reason for battery drain.
|
||||
|
||||
|
||||
* **Calibration values:**
|
||||
|
||||
These are calibration data for the gyro. Place the device flat on a table and press the button to save the default orientation values. Without this calibration we cannot calculate the correct angle/tilt.
|
||||
|
||||
.. warning::
|
||||
|
||||
The device will **not** go into `gravity monitoring` mode unless calibrated
|
||||
|
||||
Push Settings
|
||||
+++++++++++++
|
||||
|
||||
.. image:: images/config2.png
|
||||
:width: 800
|
||||
:alt: Push Settings
|
||||
|
||||
.. note::
|
||||
|
||||
When enabling SSL this will not validate the root CA of the remote service, this is a design decision based on two aspects. Enabling CA validation will take 3-4s extra on each connection which means way less
|
||||
battery life, so the decision is to prioritize battery life over security. The data transmitted is not really that sensitive anyway so I belive this is a good balance.
|
||||
|
||||
|
||||
* **HTTP URL 1:**
|
||||
|
||||
Endpoint to send data via http. Default format used Format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
* **HTTP URL 2:**
|
||||
|
||||
Endpoint to send data via http. Default format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
* **Token:**
|
||||
|
||||
The token is included in the iSpindle JSON format and will be used for both HTTP targets. If you
|
||||
need to have 2 different tokens please use the :ref:`format-editor` to customize the data format.
|
||||
|
||||
* **Brewfather URL:**
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
|
||||
SSL is not supported for this target.
|
||||
|
||||
* **Influx DB v2 URL:**
|
||||
|
||||
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
SSL is not supported for this target. Raise a issue on github if this is wanted.
|
||||
|
||||
* **Influx DB v2 Organisation:**
|
||||
|
||||
Name of organisation in Influx.
|
||||
|
||||
* **Influx DB v2 Bucket:**
|
||||
|
||||
Identifier for bucket.
|
||||
|
||||
* **Influx DB v2 Token:**
|
||||
|
||||
Token with write access to bucket.
|
||||
|
||||
* **MQTT server:**
|
||||
|
||||
IP or name of server to send data to. Default format used :ref:`data-formats-mqtt`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
* **MQTT Port:**
|
||||
|
||||
Which port should be used for communication, default is 1883 (standard port). For SSL use 8883 (any port over 8000 is treated as SSL).
|
||||
|
||||
* **MQTT user:**
|
||||
|
||||
Username or blank if anonymous is accepted
|
||||
|
||||
* **MQTT password:**
|
||||
|
||||
Password or blank if anonymous is accepted
|
||||
|
||||
* **HTTP Headers**
|
||||
|
||||
.. image:: images/config-popup1.png
|
||||
:width: 300
|
||||
:alt: HTTP Headers
|
||||
|
||||
You can define 2 http headers per push target. This is available via a pop-up window but dont forget
|
||||
to press the save buttons on the post section to save the values. One common header is content type which is the
|
||||
default setting for http targets.
|
||||
|
||||
The input must have the format **'<header>: <value>'** for it to work. The UI will accept any value so errors
|
||||
will not show until the device tries to push data.
|
||||
|
||||
|
||||
::
|
||||
|
||||
Content-Type: application/json
|
||||
X-Auth-Token: <api-token>
|
||||
|
||||
|
||||
Mozilla has a good guide on what headers are valid; `HTTP Headers <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers>`_
|
||||
|
||||
|
||||
Gravity Settings
|
||||
++++++++++++++++
|
||||
|
||||
.. image:: images/config3.png
|
||||
:width: 800
|
||||
:alt: Gravity Settings
|
||||
|
||||
* **Gravity format:**
|
||||
|
||||
Gravity format can be eihter `SG` or `Plato`. The device will use SG Internally and convert to Plato when displaying or sending data.
|
||||
|
||||
* **Gravity formula:**
|
||||
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. You can also use
|
||||
the feature to create the formula by supplying the raw data. See :ref:`create-formula`
|
||||
|
||||
The gravity formula accepts to paramaters, **tilt** for the angle or **temp** for temperature (temperature inserted into the formula
|
||||
will be in celsius). I would recommend to use the formula calculation feature instead since this is much easier.
|
||||
|
||||
* **Temperature correct gravity:**
|
||||
|
||||
Will apply a temperature calibration formula to the gravity as a second step after gravity has been calculated. It's also possible to
|
||||
build this into the gravity formula.
|
||||
|
||||
.. warning::
|
||||
This formula assumes that the calibration has been done at 20°C / 68°F.
|
||||
|
||||
Formula used in temperature correction.
|
||||
|
||||
::
|
||||
|
||||
gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/
|
||||
(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))
|
||||
|
||||
|
||||
Hardware Settings
|
||||
+++++++++++++++++
|
||||
|
||||
.. image:: images/config4.png
|
||||
:width: 800
|
||||
:alt: Hardware Settings
|
||||
|
||||
* **Voltage factor:**
|
||||
|
||||
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
|
||||
|
||||
* **Temperature correction:**
|
||||
|
||||
This value will be added to the temperature reading (negative value will reduce temperature reading). This is applied
|
||||
when the device starts. So changing this will not take affect until the device is restarted.
|
||||
|
||||
* **Gyro Temperature:**
|
||||
|
||||
Enable this feature will use the temp sensor i the gyro instead of the DS18B20, the benefit is shorter run time and
|
||||
longer battery life (this is an experimental feature). The value used is the first temperature reading from when the
|
||||
device is activated, since the gyro should be cool this is reflecting the surronding temperature. After it has
|
||||
been running the value would be totally off.
|
||||
|
||||
* **OTA URL:**
|
||||
|
||||
Should point to a URL where the firmware.bin file + version.json file are located.
|
||||
|
||||
For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the
|
||||
code the update will be done during startup.
|
||||
|
||||
If you have the previx `https://` then the device will use secure transfer without CA validation.
|
||||
|
||||
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://192.168.1.1/firmware/gravmon/
|
||||
https://192.168.1.1/firmware/gravmon/
|
||||
|
||||
|
5
src_docs/source/contributing.rst
Normal file
@ -0,0 +1,5 @@
|
||||
Contributing
|
||||
------------
|
||||
|
||||
This section is under construction.
|
||||
|