Compare commits
246 Commits
v1.0.0-bet
...
v1.1.1
Author | SHA1 | Date | |
---|---|---|---|
4f28ea9975 | |||
c1eade04af | |||
2fd0c881f3 | |||
8113275402 | |||
f4401ea526 | |||
535367d943 | |||
8db58c76dc | |||
9441e4bc54 | |||
b7d2183564 | |||
18f7ba8911 | |||
af0d6f21ca | |||
e3fcd53a36 | |||
5149760d22 | |||
403719073b | |||
dd3a4e5742 | |||
2e27ec78d6 | |||
d5dae6a40d | |||
3b716e5499 | |||
2aebae59b3 | |||
eb4b975407 | |||
1f25e3ac50 | |||
f1a608d060 | |||
c33326e6a4 | |||
1afb5ed604 | |||
823d8ca39e | |||
5a11ab84db | |||
5378d4c700 | |||
c10abe2087 | |||
ec3ef06826 | |||
b9273b7b79 | |||
6b649070e1 | |||
291229255c | |||
2747484775 | |||
ecf0c08f47 | |||
eb82bf526d | |||
b45ef627a0 | |||
059865f271 | |||
a9deb588aa | |||
0de8971255 | |||
08a102159b | |||
8b13b4769c | |||
c3044f9878 | |||
0a4e7f37b2 | |||
69f4b8ec3d | |||
d4c88c1200 | |||
4bbfb77361 | |||
655235fc4e | |||
4b0e5b393a | |||
db590f8f5f | |||
4c805fe235 | |||
34a1d7d3c3 | |||
bcee0e21da | |||
9fc5ab147d | |||
b3a89a1fcc | |||
03b58eb112 | |||
63ddcfbb57 | |||
68d44a5846 | |||
45294f6b07 | |||
09d8226af1 | |||
6c94c6b204 | |||
9e437d1e0d | |||
a486f1be30 | |||
0536b14280 | |||
a6bff7287e | |||
414d7d51c2 | |||
640733f143 | |||
f6196e6dbf | |||
f857dab3c0 | |||
787c5c09bb | |||
afe3ec2412 | |||
980099a5e5 | |||
99d577d4a0 | |||
6db6e96d90 | |||
92df08baa2 | |||
5466550f68 | |||
94f703b087 | |||
57e078986b | |||
e445e7649c | |||
a5f7f0f8a4 | |||
7826e56d8a | |||
353d6d77e1 | |||
437873489e | |||
e34e73fae2 | |||
69a4c5607c | |||
d980fdae1a | |||
272e349375 | |||
34d46ef768 | |||
588ff2d1e0 | |||
5d1811d240 | |||
85e602d29b | |||
8ee882c522 | |||
51e7ee6867 | |||
70858ef841 | |||
83bd80ba28 | |||
d33576b2df | |||
5c9525c0c5 | |||
ad8704fc20 | |||
f1c1958f88 | |||
e5394113e4 | |||
6d4e713da8 | |||
36a858af2d | |||
77d2c15e39 | |||
9df072cc78 | |||
bedbda4662 | |||
76702dfc95 | |||
789eb32aa8 | |||
c0db4dd8da | |||
cb677e10ae | |||
b4c7566a08 | |||
70f391fa1a | |||
542beffe4c | |||
1a42bd332f | |||
c16108352c | |||
4b5df951de | |||
6bbb904427 | |||
66c6c44a38 | |||
e2fee1fb35 | |||
ae595ff50c | |||
50257e2805 | |||
c503ad88a9 | |||
5b7290c991 | |||
f7c43dad55 | |||
95654bac66 | |||
ffa7ac294d | |||
79a3274286 | |||
0c936cfb88 | |||
f366b78cb3 | |||
702eba515d | |||
a992e90bc3 | |||
8b4b89ba20 | |||
361d287a22 | |||
8e36453b13 | |||
ded06d15a1 | |||
67f60b817e | |||
6373923506 | |||
bf3a3de207 | |||
e6fd027d51 | |||
0cba58a0dd | |||
fd71a2d428 | |||
907e33ce2d | |||
18ba0b225f | |||
2a9472b453 | |||
0b23a0f69e | |||
9cba1db4e5 | |||
9d5d6a5a58 | |||
58fe408803 | |||
c2ae70bb1a | |||
7a3b048d80 | |||
ad3fbb7270 | |||
02df656343 | |||
f73f63fec3 | |||
bb09072520 | |||
5875ee83d8 | |||
6185d67d12 | |||
00f82f5c37 | |||
59ad285bb8 | |||
bc09d617fc | |||
a71f54b99f | |||
152ff89bf6 | |||
d9fb8291f4 | |||
030746e982 | |||
d4260c6380 | |||
50725407ea | |||
b4bac17114 | |||
cb433a4a91 | |||
095c1dc6a7 | |||
43e2d165f5 | |||
fca7294b61 | |||
390c0882d1 | |||
5e9e705b96 | |||
d67f72f123 | |||
f09aadaf66 | |||
c68f67a558 | |||
299e915bd2 | |||
06a1541090 | |||
a8b87140b2 | |||
1d738a14dd | |||
4e37b9329c | |||
9712e13c78 | |||
1b11e49883 | |||
cc6fecbdf8 | |||
971b210438 | |||
7b2a99c8a3 | |||
d6f8ff67a3 | |||
f173b205ae | |||
e0312ab3c5 | |||
7678bb1f43 | |||
403ed1d350 | |||
ced4c9f8fc | |||
7e1862390e | |||
a3cd3217ab | |||
7ede9a6d19 | |||
f27e8ac79f | |||
7874f1bcf2 | |||
9f87bf5432 | |||
5bbb7ebfaa | |||
328e7e71ae | |||
2912749f19 | |||
43b6881477 | |||
3a4cfb1ca5 | |||
a40c3528d4 | |||
87036d56e5 | |||
e6508b639d | |||
1fb8541be6 | |||
911b08cd73 | |||
7d9228f9d8 | |||
891794af7c | |||
56cd730ad4 | |||
5a9169be64 | |||
c45a5174ae | |||
396608bd7e | |||
31dc2bae5f | |||
dfd5cea53d | |||
41507f2bd4 | |||
304903564d | |||
8dcadf4240 | |||
b1474d19c4 | |||
d8cb4fe622 | |||
e30e3b2cb5 | |||
e91c8af1a5 | |||
8342341cb9 | |||
149124fc34 | |||
1761ed47ba | |||
1dd4c541b0 | |||
2f01222582 | |||
fa3d68c321 | |||
0fe9bb146d | |||
d4df0dd272 | |||
c319afbe9f | |||
cf143c0e73 | |||
902d123a68 | |||
b359f3aba8 | |||
fb856dde75 | |||
c0f7cf2823 | |||
d208b11384 | |||
d83ef79165 | |||
c685c18b57 | |||
121eeea392 | |||
ed8dc68fc7 | |||
5b267210d2 | |||
e9742888f8 | |||
5e1d2a73fa | |||
7eddf35b97 | |||
a648d54a14 | |||
e9229efe56 | |||
635d788ba6 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. ...
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Software:**
|
||||
- Platform (esp8266, esp32)
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
17
.github/stale.yaml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 14
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- on-hold
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
8
.github/workflows/doc-build.yaml
vendored
@ -16,8 +16,12 @@ jobs:
|
||||
with:
|
||||
docs-folder: "src_docs/"
|
||||
pre-build-command: |
|
||||
pip install sphinx_rtd_theme
|
||||
pip install furo
|
||||
pip install --upgrade pip
|
||||
pip install sphinx==4.3.2
|
||||
pip install docutils==0.16
|
||||
pip install pygments==2.11.1
|
||||
pip install furo==2022.1.2
|
||||
pip list
|
||||
build-command: "sphinx-build -b html ./source ../docs"
|
||||
|
||||
- name: Commit documentation changes
|
||||
|
4
.github/workflows/pio-build-patch.yaml
vendored
@ -36,9 +36,7 @@ jobs:
|
||||
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
|
||||
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:
|
||||
|
19
.github/workflows/pre-commit.yaml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- name: clang format support
|
||||
run: |
|
||||
sudo apt install clang-format cppcheck
|
||||
- uses: pre-commit/action@v2.0.3
|
@ -4,7 +4,10 @@ repos:
|
||||
hooks:
|
||||
- id: clang-format
|
||||
files: ^src/
|
||||
exclude: 'lib/'
|
||||
- id: cpplint
|
||||
files: ^src/
|
||||
exclude: 'lib/'
|
||||
- id: cppcheck
|
||||
files: ^src/
|
||||
exclude: 'lib/'
|
||||
|
35
CONTRIBUTING.md
Normal file
@ -0,0 +1,35 @@
|
||||
## How to contribute to GravityMon
|
||||
|
||||
#### **Did you find a bug?**
|
||||
|
||||
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/mp-se/gravitymon/issues). Dont forget to look under closed issues. There might be a fix but not yet included in the released version.
|
||||
|
||||
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/mp-se/gravitymon/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, Use the function on the device to extract configuration and device information (does not contain any sensitive data). This can help to pinpoint the issue.
|
||||
|
||||
#### **Did you write a patch that fixes a bug?**
|
||||
|
||||
* Open a new GitHub pull request with the patch.
|
||||
|
||||
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
* Before submitting, please use `pre-commit` to validate that your code contribution complies with the formatting standards for C++ and C.
|
||||
|
||||
* Check the contribution section under the documentation for additional information.
|
||||
|
||||
#### **Do you intend to add a new feature or change an existing one?**
|
||||
|
||||
* Suggest your change in the [Discussion forums](https://github.com/mp-se/gravitymon/discussions) and start writing code.
|
||||
|
||||
* Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes.
|
||||
|
||||
#### **Do you have questions about the source software?**
|
||||
|
||||
* Start a discussion in the [Discussion forums](https://github.com/mp-se/gravitymon/discussions) and start writing code.
|
||||
|
||||
#### **Do you want to contribute to the documentation?**
|
||||
|
||||
* Open a new GitHub pull request with the updated documentation changes.
|
||||
|
||||
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
Thanks!
|
41
README.md
@ -12,28 +12,33 @@ GravityMon is a replacement firmware for the iSpindle firmware. It's 100% compat
|
||||
|
||||
Now also works with ESP32 (use ESP32 d1 mini which is compatible with ESP8266)
|
||||
|
||||
Installation can be made using https://www.brewflasher.com
|
||||
Installation can be made using https://www.brewflasher.com or https://web.brewflasher.com
|
||||
|
||||
Note! If its being flagged as malware, try the older version.
|
||||
The documenation can be found here: https://mp-se.github.io/gravitymon/index.html
|
||||
|
||||
Note! If Brewflasher being flagged as malware by your antivirus software, try the web version.
|
||||
|
||||
The main differences:
|
||||
---------------------
|
||||
|
||||
* Modern web based user interface for configuration when connected to WIFI
|
||||
* Efficient software, long lifespan (+45 days with 5min update frequencey)
|
||||
* Send data to multiple endpoints (http-post, http-get, influxdb v2, mqtt)
|
||||
* Instructions for service such as; Brewfather, Fermentrack, Ubidots, Home Assistant, Brewers Friend, Brewspy, Thingspeak, Blynk.
|
||||
* SSL support in standard HTTP and MQTT connections.
|
||||
* ESP32 support with Bluetooth push
|
||||
* Customize data format to be pushed
|
||||
* Automatic temperature adjustment of gravity when enabled
|
||||
* Use the temperature sensor in gyro instead of DS18B20
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
|
||||
* Visual graph showing how gravity formula will be interpreted
|
||||
* OTA support
|
||||
* Operates in two modes gravity monitoring and configuration mode
|
||||
* Gravity mode is comparable to how the iSpindle works when collectintg data
|
||||
* Configuration mode has a modern HTML5 based web UI. No need to start the access point to change settings
|
||||
* Offloading some of the functionallity to run in the web browser, this allows for more advanced features.
|
||||
* REST API to enable scripted configuration
|
||||
* Send data to multiple endpoints and services at once (2xHTTP POST, HTTP GET, MQTT, INFLUXDB2)
|
||||
* Directly test all endpoints from user interface with feedback to simplify troubleshooting
|
||||
* Complete format customization for all endpoints using templates (dont really need to change the software to support new services)
|
||||
* Setup guides for how to send data to many popular services. Currently 10+ are documented
|
||||
* Automatic temperature adjustment of gravity (just tick a checkbox)
|
||||
* OTA support from webserver
|
||||
* Firmware update via web interface
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and let GravityMon creates a formula
|
||||
* Visual graph showing how formula will be interpreted based on entered values
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* SSL support in all endpoints (no certificate validation due to limitations on esp8266).
|
||||
* Built in performance measurements (used to optimise code)
|
||||
* REST API for scripting
|
||||
|
||||
* Storage mode when placed on cap (indefinite sleep)
|
||||
* Customize various hardware parameters to opimize device functionallity.
|
||||
*
|
||||
No code has been reused from the iSpindle project.
|
||||
|
||||
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="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li></ul></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link active" href="#">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
BIN
bin/firmware.bin
BIN
bin/firmware32-perf.bin
Normal file
BIN
bin/partitions32.bin
Normal file
@ -1 +1 @@
|
||||
{ "project":"gravmon", "version":"0.9.0", "html": [ ] }
|
||||
{ "project":"gravmon", "version":"1.1.0", "html": [ ] }
|
164
html/about.htm
@ -5,74 +5,126 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START BODY -->
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<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>
|
||||
<div class="accordion row-margin-10" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingAbout">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout">
|
||||
<b>About</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row h3 col-sm-8">
|
||||
Beer Gravity Monitor
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.
|
||||
</div>
|
||||
<div class="row h3 col-sm-8 mb-3">
|
||||
MIT License
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</div>
|
||||
|
||||
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 class="row h3 col-sm-8 mb-3">
|
||||
Credits to
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
This software uses
|
||||
the following libraries and without these this software would have been much more difficult to acheive:<br><br>
|
||||
<ul>
|
||||
<li>https://github.com/jrowberg/i2cdevlib</li>
|
||||
<li>https://github.com/codeplea/tinyexpr</li>
|
||||
<li>https://github.com/graphitemaster/incbin</li>
|
||||
<li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li>
|
||||
<li>https://github.com/khoih-prog/ESP_WiFiManager</li>
|
||||
<li>https://github.com/thijse/Arduino-Log</li>
|
||||
<li>https://github.com/bblanchon/ArduinoJson</li>
|
||||
<li>https://github.com/PaulStoffregen/OneWire</li>
|
||||
<li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li>
|
||||
<li>https://github.com/Rotario</li>
|
||||
<li>https://github.com/256dpi/arduino-mqtt</li>
|
||||
<li>https://graphjs.com</li>
|
||||
<li>https://getbootstrap.com</li>
|
||||
<li>https://github.com/lorol/LITTLEFS</li>
|
||||
<li>https://github.com/h2zero/NimBLE-Arduino</li>
|
||||
<li>https://github.com/spouliot/tilt-sim</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</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="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li></ul></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link active" href="#">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
@ -5,379 +5,431 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item 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 BODY -->
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<div class="container row-margin-10">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<hr class="my-2">
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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>
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
<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="#collapseCalibration" aria-expanded="true" aria-controls="collapseCalibration">
|
||||
Formula calculation
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFormula">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFormula" aria-expanded="true" aria-controls="collapseFormula">
|
||||
<b>Formula calculation</b>
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseFormula" class="accordion-collapse collapse show" aria-labelledby="headingFormula" data-bs-parent="#accordion">
|
||||
<div class="accordion-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 id="collapseCalibration" 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="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="row">
|
||||
<label class="col-sm-1 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</label>
|
||||
</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="row mb-3">
|
||||
<label for="angle1" class="col-sm-1 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" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<input hidden type="number" name="g1" id="g1">
|
||||
<div class="col-sm-4"><input disabled type="number" class="form-control" name="g1a" id="g1a" data-bs-toggle="tooltip" title="The first value is reserved for water gravity."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle2" class="col-sm-1 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" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g2" id="g2" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle3" class="col-sm-1 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" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g3" id="g3" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle4" class="col-sm-1 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" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g4" id="g4" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle5" class="col-sm-1 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" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g5" id="g5" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle6" class="col-sm-1 col-form-label">6.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a6" id="a6" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g6" id="g6" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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 class="row mb-3">
|
||||
<label for="angle7" class="col-sm-1 col-form-label">7.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a7" id="a7" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g7" id="g7" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</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">
|
||||
<div class="row mb-3">
|
||||
<label for="angle8" class="col-sm-1 col-form-label">8.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a8" id="a8" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g8" id="g8" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="angle9" class="col-sm-1 col-form-label">9.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a9" id="a9" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g9" id="g9" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="angle10" class="col-sm-1 col-form-label">10.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a10" id="a10" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g10" id="g10" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-8 offset-sm-1"><button type="submit" class="btn btn-primary" id="calculate-btn" data-bs-toggle="tooltip" title="Save the values and try to create a formula">Save & Create</button></div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="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="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>
|
||||
<label for="calculate-btn" class="col-sm-8 col-form-label" id="formula">Loading...</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingGraph">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseGraph" aria-expanded="false" aria-controls="collapseGraph">
|
||||
<b>Formula graph</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseGraph" class="accordion-collapse collapse" aria-labelledby="headingGraph" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<canvas id="gravityChart"></canvas>
|
||||
</div>
|
||||
</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 = [];
|
||||
<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 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
|
||||
const configChart = {
|
||||
type: 'line',
|
||||
data: dataSetChart,
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
text: 'Gravity'
|
||||
type: 'linear',
|
||||
grace: '5%',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle/Tilt'
|
||||
},
|
||||
ticks: {
|
||||
crossAlign: 'far'
|
||||
},
|
||||
suggestedMin: 25
|
||||
},
|
||||
suggestedMin: 1.000
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Gravity'
|
||||
},
|
||||
suggestedMin: 1.000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var myChart = 0;
|
||||
</script>
|
||||
var myChart = 0;
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
g1.onchange = setGravityDecimal
|
||||
g2.onchange = setGravityDecimal
|
||||
g3.onchange = setGravityDecimal
|
||||
g4.onchange = setGravityDecimal
|
||||
g5.onchange = setGravityDecimal
|
||||
<script type="text/javascript">
|
||||
g1.onchange = setGravityDecimal
|
||||
g2.onchange = setGravityDecimal
|
||||
g3.onchange = setGravityDecimal
|
||||
g4.onchange = setGravityDecimal
|
||||
g5.onchange = setGravityDecimal
|
||||
g6.onchange = setGravityDecimal
|
||||
g7.onchange = setGravityDecimal
|
||||
g8.onchange = setGravityDecimal
|
||||
g9.onchange = setGravityDecimal
|
||||
g10.onchange = setGravityDecimal
|
||||
|
||||
a1.onchange = setAngleDecimal
|
||||
a2.onchange = setAngleDecimal
|
||||
a3.onchange = setAngleDecimal
|
||||
a4.onchange = setAngleDecimal
|
||||
a5.onchange = setAngleDecimal
|
||||
a1.onchange = setAngleDecimal
|
||||
a2.onchange = setAngleDecimal
|
||||
a3.onchange = setAngleDecimal
|
||||
a4.onchange = setAngleDecimal
|
||||
a5.onchange = setAngleDecimal
|
||||
a6.onchange = setAngleDecimal
|
||||
a7.onchange = setAngleDecimal
|
||||
a8.onchange = setAngleDecimal
|
||||
a9.onchange = setAngleDecimal
|
||||
a10.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() {
|
||||
window.onload = 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()
|
||||
function convertToPlato(sg) {
|
||||
return 259-(259/sg);
|
||||
}
|
||||
|
||||
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) );
|
||||
}
|
||||
function convertToSG(plato) {
|
||||
return 259/(259-plato);
|
||||
}
|
||||
|
||||
$("#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) );
|
||||
function setAngleDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(2);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
if( cfg["error"]!="" ) {
|
||||
showError(cfg["error"]);
|
||||
}
|
||||
function setGravityDecimal(event) {
|
||||
if(isPlato())
|
||||
this.value = parseFloat(this.value).toFixed(1);
|
||||
else
|
||||
this.value = parseFloat(this.value).toFixed(4);
|
||||
|
||||
populateChart();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<!-- START FOOTER -->
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
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() );
|
||||
populateChartForm( $("#a6").val(), $("#g6").val() );
|
||||
populateChartForm( $("#a7").val(), $("#g7").val() );
|
||||
populateChartForm( $("#a8").val(), $("#g8").val() );
|
||||
populateChartForm( $("#a9").val(), $("#g9").val() );
|
||||
populateChartForm( $("#a10").val(), $("#g10").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) );
|
||||
$("#g1a").val( "0.0" );
|
||||
$("#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) );
|
||||
$("#g6").val( parseFloat(cfg["g6"]).toFixed(1) );
|
||||
$("#g7").val( parseFloat(cfg["g7"]).toFixed(1) );
|
||||
$("#g8").val( parseFloat(cfg["g8"]).toFixed(1) );
|
||||
$("#g9").val( parseFloat(cfg["g9"]).toFixed(1) );
|
||||
$("#g10").val( parseFloat(cfg["g10"]).toFixed(1) );
|
||||
} else {
|
||||
$("#gravity-header").text("Gravity (SG):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
|
||||
$("#g1a").val( "1.0000" );
|
||||
$("#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) );
|
||||
$("#g6").val( parseFloat(cfg["g6"]).toFixed(4) );
|
||||
$("#g7").val( parseFloat(cfg["g7"]).toFixed(4) );
|
||||
$("#g8").val( parseFloat(cfg["g8"]).toFixed(4) );
|
||||
$("#g9").val( parseFloat(cfg["g9"]).toFixed(4) );
|
||||
$("#g10").val( parseFloat(cfg["g10"]).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) );
|
||||
$("#a6").val( parseFloat(cfg["a6"]).toFixed(2) );
|
||||
$("#a7").val( parseFloat(cfg["a7"]).toFixed(2) );
|
||||
$("#a8").val( parseFloat(cfg["a8"]).toFixed(2) );
|
||||
$("#a9").val( parseFloat(cfg["a9"]).toFixed(2) );
|
||||
$("#a10").val( parseFloat(cfg["a10"]).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 themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1220
html/config.htm
@ -5,184 +5,207 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<a class="navbar-brand" href="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="#">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-4">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').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('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFirmware">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware">
|
||||
<b>Upload firmware</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">Current version:</div>
|
||||
<div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">Platform:</div>
|
||||
<div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Current version:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Platform:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<!--
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name">
|
||||
<label class="custom-file-label" for="name">Choose file</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
|
||||
</form>
|
||||
-->
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()">
|
||||
<label class="custom-file-label" for="name">Choose file</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
<script type="text/javascript">
|
||||
window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser. If you don't get any gyro readings after update, please press the reset button!");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
@ -1,87 +1,79 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="javascript:history.back()">Back to configuration</a></li></ul><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Platform:</div><div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div></div><div class="row mb-3"><!--
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name">
|
||||
<label class="custom-file-label" for="name">Choose file</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
|
||||
</form>
|
||||
--><form id="uploadForm" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button></form></div><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div><hr class="my-4"></div><script type="text/javascript">window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Configuration</a><ul class="dropdown-menu"><li><a class="dropdown-item" href="/config.htm">Configuration</a></li><li><a class="dropdown-item" href="/format.htm">Format editor</a></li><li><a class="dropdown-item" href="/test.htm">Test push</a></li><li><a class="dropdown-item" href="#">Upload firmware</a></li></ul></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container row-margin-10"><div class="alert alert-success alert-dismissible hide fade d-none" role="alert"><div id="alert"></div><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("hide").removeClass("show").addClass("d-none")})</script><div class="accordion" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingFirmware"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware"><b>Upload firmware</b></button></h2><div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion"><div class="accordion-body"><div class="row mb-3"><div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Current version:</div><div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Platform:</div><div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div></div><form id="uploadForm" enctype="multipart/form-data"><div class="row mb-3"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload"></div></div><div class="row mb-3"><div class="col-md-4"><button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button></div></div></form><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><script type="text/javascript">window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser. If you don't get any gyro readings after update, please press the reset button!");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}</script><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
582
html/format.htm
@ -5,264 +5,430 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="#">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert" id="alert">
|
||||
<div id="alert-msg"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="alert alert-warning alert-dismissible hide fade d-none" role="alert" id="warning-ha">
|
||||
<div>Home Assistant device configuration detected in MQTT format. These messages will be posted when format is saved and not during gravity measurement.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
<div class="alert alert-warning alert-dismissible hide fade d-none" role="alert" id="warning-length">
|
||||
<div>The format template is quite large, its possible the device will not have enough memory to handle it.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
</script>
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingFormat">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">
|
||||
Push Format Templates
|
||||
function showWarningHomeAssistant() {
|
||||
$('#warning-ha').removeClass('d-none').addClass('show').removeClass('hide');
|
||||
}
|
||||
function hideWarningHomeAssistant() {
|
||||
$('#warning-ha').addClass('d-none').removeClass('show').addClass('hide');
|
||||
}
|
||||
|
||||
function showWarningLength() {
|
||||
$('#warning-length').removeClass('d-none').addClass('show').removeClass('hide');
|
||||
}
|
||||
function hideWarningLength() {
|
||||
$('#warning-length').addClass('d-none').removeClass('show').addClass('hide');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFormat">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">
|
||||
<b>Push Format Templates</b>
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseFormat" class="collapse show" aria-labelledby="headingFormat" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<div id="collapseFormat" class="accordion-collapse collapse show" aria-labelledby="headingFormat" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
<input type="text" name="http-1" id="http-1" hidden>
|
||||
<input type="text" name="http-2" id="http-2" hidden>
|
||||
<input type="text" name="http-3" id="http-3" hidden>
|
||||
<!--<input type="text" name="brewfather" id="brewfather" hidden>-->
|
||||
<input type="text" name="influxdb" id="influxdb" hidden>
|
||||
<input type="text" name="mqtt" id="mqtt" hidden>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="row mb-3">
|
||||
<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">
|
||||
<select class="custom-select col-sm-4" required name="push-target" id="push-target" data-bs-toggle="tooltip" title="Select the push target to edit format template for">
|
||||
<option value="http-1">HTTP option 1 (post)</option>
|
||||
<option value="http-2">HTTP option 2 (post)</option>
|
||||
<option value="http-3">HTTP option 3 (get)</option>
|
||||
<!--<option value="brewfather">Brewfather</option>-->
|
||||
<option value="influxdb">Influx DB</option>
|
||||
<option value="mqtt">MQTT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12">
|
||||
<textarea rows="5" class="form-control" name="format" id="format">
|
||||
<textarea rows="10" class="form-control" name="format" id="format" onchange="updateStatusField()" onkeydown="updateStatusField()">
|
||||
</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>
|
||||
<script>
|
||||
let formatTemplates = [
|
||||
{ "id": "GravityMon-Post", "format": "%7B%20%22name%22%20%3A%20%22%24%7Bmdns%7D%22%2C%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%20%22token%22%20%3A%20%22%24%7Btoken%7D%22%2C%20%22interval%22%3A%20%24%7Bsleep-interval%7D%2C%20%22temperature%22%3A%20%24%7Btemp%7D%2C%20%22temp_units%22%3A%20%22%24%7Btemp-unit%7D%22%2C%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%22angle%22%3A%20%24%7Bangle%7D%2C%20%22battery%22%3A%20%24%7Bbattery%7D%2C%20%22RSSI%22%3A%20%24%7Brssi%7D%2C%20%22corr-gravity%22%3A%20%24%7Bcorr-gravity%7D%2C%20%22gravity-unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%20%22run-time%22%3A%20%24%7Brun-time%7D%7D" },
|
||||
{ "id": "GravityMon-Get", "format": "%3Fname%3D%24%7Bmdns%7D%26id%3D%24%7Bid%7D%26token%3D%24%7Btoken2%7D%26interval%3D%24%7Bsleep-interval%7D%26temperature%3D%24%7Btemp%7D%26%0Atemp-units%3D%24%7Btemp-unit%7D%26gravity%3D%24%7Bgravity%7D%26angle%3D%24%7Bangle%7D%26battery%3D%24%7Bbattery%7D%26rssi%3D%24%7Brssi%7D%26%0Acorr-gravity%3D%24%7Bcorr-gravity%7D%26gravity-unit%3D%24%7Bgravity-unit%7D%26run-time%3D%24%7Brun-time%7D" },
|
||||
{ "id": "iSpindle-Post", "format": "%7B%20%22name%22%20%3A%20%22%24%7Bmdns%7D%22%2C%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%20%22token%22%20%3A%20%22%24%7Btoken%7D%22%2C%20%22interval%22%3A%20%24%7Bsleep-interval%7D%2C%20%22temperature%22%3A%20%24%7Btemp%7D%2C%20%22temp_units%22%3A%20%22%24%7Btemp-unit%7D%22%2C%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%22angle%22%3A%20%24%7Bangle%7D%2C%20%22battery%22%3A%20%24%7Bbattery%7D%2C%20%22RSSI%22%3A%20%24%7Brssi%7D%7D" },
|
||||
{ "id": "BrewFatherCustom-Post", "format": "%7B%20%20%20%22name%22%3A%20%22%24%7Bmdns%7D%22%2C%20%20%20%22temp%22%3A%20%24%7Btemp%7D%2C%20%20%20%22aux_temp%22%3A%200%2C%20%20%20%22ext_temp%22%3A%200%2C%20%20%20%22temp_unit%22%3A%20%22%24%7Btemp-unit%7D%22%2C%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%20%20%22gravity_unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%20%20%20%22pressure%22%3A%200%2C%20%20%20%22pressure_unit%22%3A%20%22PSI%22%2C%20%20%20%22ph%22%3A%200%2C%20%20%20%22bpm%22%3A%200%2C%20%20%20%22comment%22%3A%20%22%22%2C%20%20%20%22beer%22%3A%20%22%22%2C%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%7D" },
|
||||
{ "id": "iSpindle-Mqtt", "format": "ispindel%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemp_units%3A%24%7Btemp-unit%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Finterval%3A%24%7Bsleep-interval%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2FRSSI%3A%24%7Brssi%7D%7C" },
|
||||
{ "id": "HomeAssistant-Mqtt", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0A" },
|
||||
{ "id": "HomeAssistant-Mqtt2", "format": "gravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Frssi%3A%24%7Brssi%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Btilt%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftemperature%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_temp%22%2C%22name%22%3A%22temperature%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%24%7Btemp-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftemperature%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fgravity%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_grav%22%2C%22name%22%3A%22gravity%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22%20%24%7Bgravity-unit%7D%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fgravity%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Frssi%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_rssi%22%2C%22name%22%3A%22rssi%22%2C%22dev_cla%22%3A%22signal_strength%22%2C%22unit_of_meas%22%3A%22dBm%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Frssi%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftilt%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_tilt%22%2C%22name%22%3A%22tilt%22%2C%22dev_cla%22%3A%22temperature%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftilt%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fbattery%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_batt%22%2C%22name%22%3A%22battery%22%2C%22dev_cla%22%3A%22voltage%22%2C%22unit_of_meas%22%3A%22V%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fbattery%22%7D%7C%0A" },
|
||||
{ "id": "Brewblox-Mqtt", "format": "brewcast%2Fhistory%3A%7B%22key%22%3A%22%24%7Bmdns%7D%22%2C%22data%22%3A%7B%22Temperature%5BdegC%5D%22%3A%20%24%7Btemp-c%7D%2C%22Temperature%5BdegF%5D%22%3A%20%24%7Btemp-f%7D%2C%22Battery%5BV%5D%22%3A%24%7Bbattery%7D%2C%22Tilt%5Bdeg%5D%22%3A%24%7Bangle%7D%2C%22Rssi%5BdBm%5D%22%3A%24%7Brssi%7D%2C%22SG%22%3A%24%7Bgravity-sg%7D%2C%22Plato%22%3A%24%7Bgravity-plato%7D%7D%7D%7C" },
|
||||
{ "id": "UBIDots-Post", "format": "%7B%0A%20%20%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%20%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%20%20%22rssi%22%3A%20%24%7Brssi%7D%0A%7D" } ];
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary" id="format-btn" data-bs-toggle="tooltip" title="Save the format template, saving an empty form will reset the template to default">Save</button>
|
||||
<button class="btn btn-secondary" id="test-btn" data-bs-toggle="tooltip" title="Apply device data to template to see how it works">Test</button>
|
||||
</div>
|
||||
<select class="custom-select col-sm-4" required name="predefined" id="predefined" data-bs-toggle="tooltip" title="Select a pre-defined format template">
|
||||
<option value="iSpindle-Post">iSpindle (POST)</option>
|
||||
<option value="GravityMon-Post">GravityMon (POST)</option>
|
||||
<option value="iSpindle-Mqtt">iSpindle (MQTT)</option>
|
||||
<option value="HomeAssistant-Mqtt">Home Assistant (MQTT)</option>
|
||||
<option value="HomeAssistant-Mqtt2">Home Assistant - Auto register sensor (MQTT)</option>
|
||||
<option value="UBIDots-Post">UBIdots (POST)</option>
|
||||
<option value="BrewFatherCustom-Post">Brewfather - Custom Endpoint (POST)</option>
|
||||
<option value="iSpindle-Post">Brewfather - iSpindle Endpoint (POST)</option>
|
||||
<option value="GravityMon-Get">GravityMon (GET)</option>
|
||||
<option value="Brewblox-Mqtt">BrewBlox (MQTT)</option>
|
||||
</select>
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-secondary" id="copy-btn" data-bs-toggle="tooltip" title="Copy the selected format template to the selected push target">Copy format</button>
|
||||
<button class="btn btn-secondary" id="clear-btn" data-bs-toggle="tooltip" title="Clear the current format template">Clear format</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8" id="status">
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<pre class="card-preview" id="preview" name="preview"></pre>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
var maxCharsInFormatTemplate = 1500;
|
||||
|
||||
setButtonDisabled( true );
|
||||
|
||||
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
|
||||
$(document).ready(function () {
|
||||
if(location.hash != null && location.hash != ""){
|
||||
$('.collapse').removeClass('in');
|
||||
$(location.hash + '.collapse').collapse('show');
|
||||
}
|
||||
});
|
||||
|
||||
$("#push-target").change(function(e){
|
||||
console.log(e)
|
||||
selectFormat();
|
||||
});
|
||||
|
||||
// Store the format
|
||||
$("#format-btn").click(function(e) {
|
||||
var s = $("#format").val();
|
||||
s = s.replaceAll("\n", "");
|
||||
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
|
||||
console.log(obj);
|
||||
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/config/format",
|
||||
data: obj,
|
||||
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
|
||||
error: function(result) { showError('Unable to store format.'); }
|
||||
} );
|
||||
});
|
||||
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
var doc = $("#format").val();
|
||||
|
||||
if (cfg["temp-format"]=="C")
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
|
||||
else
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
|
||||
|
||||
if (cfg["gravity-format"]=="G") {
|
||||
var sg = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
var plato = 259 - (259 - sg);
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
}
|
||||
else {
|
||||
var plato = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
var sg = 259 / (259 - plato);
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
}
|
||||
|
||||
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
|
||||
doc = doc.replaceAll("${id}", cfg["id"]);
|
||||
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
|
||||
doc = doc.replaceAll("${token}", cfg["token"]);
|
||||
doc = doc.replaceAll("${token2}", cfg["token2"]);
|
||||
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
|
||||
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
|
||||
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
|
||||
doc = doc.replaceAll("${battery}", cfg["battery"]);
|
||||
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
|
||||
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
|
||||
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
|
||||
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${angle}", cfg["angle"]);
|
||||
doc = doc.replaceAll("${tilt}", cfg["angle"]);
|
||||
|
||||
// Format in a readable json string.
|
||||
try {
|
||||
var json = JSON.parse(doc);
|
||||
doc = JSON.stringify(json, null, 2);
|
||||
} catch(e) {
|
||||
console.log("Not a javascript object!")
|
||||
}
|
||||
|
||||
$("#preview").text(doc);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#format-btn").prop("disabled", b);
|
||||
$("#test-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function selectFormat() {
|
||||
var s = "#" + $("#push-target").val()
|
||||
console.log(s);
|
||||
s = decodeURIComponent($(s).val());
|
||||
console.log(s);
|
||||
s = s.replaceAll("|", "|\n");
|
||||
console.log(s);
|
||||
$("#format").val(s);
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/config/format";
|
||||
//var url = "/test/format.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").val(cfg["id"]);
|
||||
|
||||
$("#http-1").val(cfg["http-1"]);
|
||||
$("#http-2").val(cfg["http-2"]);
|
||||
$("#http-3").val(cfg["http-3"]);
|
||||
//$("#brewfather").val(cfg["brewfather"]);
|
||||
$("#influxdb").val(cfg["influxdb"]);
|
||||
$("#mqtt").val(cfg["mqtt"]);
|
||||
selectFormat();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
function updateStatusField() {
|
||||
var t = $("#format").val();
|
||||
$("#status").text( t.length + " characters in template, recommended maximum is " + maxCharsInFormatTemplate );
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
if (t.length > maxCharsInFormatTemplate) {
|
||||
showWarningLength();
|
||||
} else {
|
||||
hideWarningLength();
|
||||
}
|
||||
};
|
||||
|
||||
$("#push-target").change(function(e){
|
||||
console.log(e)
|
||||
selectFormat();
|
||||
});
|
||||
|
||||
// Copy the selected template
|
||||
$("#copy-btn").click(function(e) {
|
||||
var id = $("#predefined").val();
|
||||
|
||||
//console.log( encodeURIComponent( $("#format").val() ) );
|
||||
|
||||
formatTemplates.forEach(function (item, index) {
|
||||
if( item.id == id ) {
|
||||
$("#format").val( decodeURIComponent(item.format) );
|
||||
}
|
||||
});
|
||||
|
||||
updateStatusField();
|
||||
});
|
||||
|
||||
// Clear the selected template
|
||||
$("#clear-btn").click(function(e) {
|
||||
$("#format").val( "" );
|
||||
updateStatusField();
|
||||
});
|
||||
|
||||
// Store the format
|
||||
$("#format-btn").click(function(e) {
|
||||
var s = $("#format").val();
|
||||
|
||||
/*if (s.length > maxCharsInFormatTemplate) {
|
||||
showError("Format template is too large for the device to handle, unable to save.")
|
||||
return;
|
||||
}*/
|
||||
|
||||
$('#spinner').show();
|
||||
setButtonDisabled( true );
|
||||
var ha = false;
|
||||
|
||||
hideWarningHomeAssistant();
|
||||
if ($("#push-target").val() == "mqtt") {
|
||||
if (s.search("homeassistant/sensor/") != -1) {
|
||||
console.log("Current format is mqtt, it contains topics for Home Assistant device registration.")
|
||||
showWarningHomeAssistant();
|
||||
ha = true;
|
||||
}
|
||||
}
|
||||
|
||||
s = s.replaceAll("\n", "");
|
||||
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.'); postHomeAssistant(ha); },
|
||||
error: function(result) { showError('Unable to store format.'); $('#spinner').hide(); },
|
||||
always: function() { $('#spinner').hide(); setButtonDisabled( false ); }
|
||||
} );
|
||||
});
|
||||
|
||||
function postHomeAssistant(active) {
|
||||
if (!active) {
|
||||
getConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Current format is mqtt, running post tests to register device.")
|
||||
$('#spinner').show();
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + $("#id").val() + "&format=mqtt";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log(cfg);
|
||||
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(success) {
|
||||
showSuccess( "Format stored successfully. Home Assistant Device Registration Successful." );
|
||||
} else {
|
||||
showError( "Format stored successfully. Home Assistant Device Registration Failed!" );
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
showError( "Format stored successfully. Home Assistant Device Registration Failed!" );
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
getConfig();
|
||||
});
|
||||
}
|
||||
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
var doc = $("#format").val();
|
||||
|
||||
if (cfg["temp-format"]=="C")
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
|
||||
else
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
|
||||
|
||||
if (cfg["gravity-format"]=="G") {
|
||||
var sg = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
var plato = 259 - (259 - sg);
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
}
|
||||
else {
|
||||
var plato = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
var sg = 259 / (259 - plato);
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
}
|
||||
|
||||
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
|
||||
doc = doc.replaceAll("${id}", cfg["id"]);
|
||||
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
|
||||
doc = doc.replaceAll("${token}", cfg["token"]);
|
||||
doc = doc.replaceAll("${token2}", cfg["token2"]);
|
||||
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
|
||||
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
|
||||
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
|
||||
doc = doc.replaceAll("${battery}", cfg["battery"]);
|
||||
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
|
||||
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
|
||||
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
|
||||
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${angle}", cfg["angle"]);
|
||||
doc = doc.replaceAll("${tilt}", cfg["angle"]);
|
||||
doc = doc.replaceAll("${app-ver}", cfg["app-ver"]);
|
||||
doc = doc.replaceAll("${app-build}", cfg["app-build"]);
|
||||
|
||||
// Format in a readable json string.
|
||||
try {
|
||||
var json = JSON.parse(doc);
|
||||
doc = JSON.stringify(json, null, 2);
|
||||
} catch(e) {
|
||||
console.log("Not a JSON object!")
|
||||
}
|
||||
|
||||
$("#preview").text(doc);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
});
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#format-btn").prop("disabled", b);
|
||||
$("#test-btn").prop("disabled", b);
|
||||
$("#copy-btn").prop("disabled", b);
|
||||
$("#clear-btn").prop("disabled", b);
|
||||
$("#push-target").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("");
|
||||
updateStatusField();
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/config/format";
|
||||
// var url = "/test/format.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").val(cfg["id"]);
|
||||
|
||||
$("#http-1").val(cfg["http-1"]);
|
||||
$("#http-2").val(cfg["http-2"]);
|
||||
$("#http-3").val(cfg["http-3"]);
|
||||
$("#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 );
|
||||
});
|
||||
|
||||
updateStatusField();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
573
html/index.htm
@ -5,237 +5,386 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-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>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="/test.htm">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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="/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 class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<div class="container">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
console.log("Error:" + msg);
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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>
|
||||
function showSuccess( msg ) {
|
||||
console.log("Success:" + msg);
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
console.log("Disable");
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
|
||||
<h2 class="accordion-header" id="headingData">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseData" aria-expanded="true" aria-controls="collapseData">
|
||||
<b>Measurement</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseData" class="accordion-collapse collapse show" aria-labelledby="headingData" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Gravity:</div>
|
||||
<div class="col-md-4 bg-light" id="gravity">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Temperature:</div>
|
||||
<div class="col-md-4 bg-light" id="temp">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Angle/Tilt:</div>
|
||||
<div class="col-md-4 bg-light" id="angle">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Battery:</div>
|
||||
<div class="col-md-4 bg-light" id="battery">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSoftware">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSoftware" aria-expanded="true" aria-controls="collapseSoftware">
|
||||
<b>Device</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseSoftware" class="accordion-collapse collapse show" aria-labelledby="headingSoftware" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Current version:</div>
|
||||
<div class="col-md-4 bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3" id="h-app-ver-new" hidden>
|
||||
<div class="col-md-4 bg-light">New version:</div>
|
||||
<div class="col-md-4 bg-light" id="app-ver-new">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Host name:</div>
|
||||
<div class="col-md-4 bg-light" id="mdns">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Device ID:</div>
|
||||
<div class="col-md-4 bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Platform:</div>
|
||||
<div class="col-md-4 bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">SSID:</div>
|
||||
<div class="col-md-4 bg-light" id="wifi-ssid">Loading...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 30000);
|
||||
|
||||
function loadLog() {
|
||||
var url2 = "/log2";
|
||||
var url1 = "/log";
|
||||
//var url2 = "/test/log2";
|
||||
//var url1 = "/test/log1";
|
||||
var log = "";
|
||||
|
||||
$.get(url2, function(data) {
|
||||
console.log(data);
|
||||
var list = data.split("\n");
|
||||
list.forEach(function (item, index) {
|
||||
log = item + "\n" + log;
|
||||
});
|
||||
}).always( function() {
|
||||
$.get(url1, function(data) {
|
||||
console.log(data);
|
||||
var list = data.split("\n");
|
||||
list.forEach(function (item, index) {
|
||||
log = item + "\n" + log;
|
||||
});
|
||||
}).always( function() {
|
||||
console.log(log);
|
||||
$("#logContent").text(log);
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLog" aria-expanded="false" aria-controls="collapseLog" data-bs-toggle="tooltip" title="Load and show the last 10 errors that has occured on the device">
|
||||
View error log
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="tooltip" name="github-btn" id="github-btn" title="Go to github and place a issue">
|
||||
Submit a issue on github
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSupport" aria-expanded="false" aria-controls="collapseSupport" data-bs-toggle="tooltip" name="support-btn" id="support-btn" title="Collect data relevant for a support case">
|
||||
Gather support information
|
||||
</button>
|
||||
|
||||
<div class="collapse row-margin-10" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code class="card-text" id="logContent" data-bs-toggle="tooltip" title="Shows the last errors on the device, newest on top.">Loading log data, please wait...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapse row-margin-10" id="collapseSupport">
|
||||
<div class="card card-body">
|
||||
<pre><code class="card-text" id="supportContent">Collecting support data, please wait...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
var debug = {};
|
||||
|
||||
window.onload = start;
|
||||
|
||||
$("#github-btn").click(function(e){
|
||||
window.location.href = "https://github.com/mp-se/gravitymon/issues";
|
||||
});
|
||||
|
||||
$("#support-btn").click(function(e){
|
||||
$('#spinner').show();
|
||||
|
||||
var url = "/api/config";
|
||||
//var url = "/test/config.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
debug = {};
|
||||
debug["mdns"] = cfg["mdns"];
|
||||
debug["id"] = cfg["id"];
|
||||
|
||||
debug["sleep-interval"] = cfg["sleep-interval"];
|
||||
debug["temp-format"] = cfg["temp-format"];
|
||||
|
||||
debug["gravity-format"] = cfg["gravity-format"];
|
||||
debug["gravity-temp-adjustment"] = cfg["gravity-temp-adjustment"];
|
||||
|
||||
debug["voltage-factor"] = cfg["voltage-factor"];
|
||||
debug["platform"] = cfg["platform"];
|
||||
debug["ble"] = cfg["ble"];
|
||||
debug["gyro-temp"] = cfg["gyro-temp"];
|
||||
debug["gyro-calibration-data"] = cfg["gyro-calibration-data"];
|
||||
debug["temp-adjustment-value"] = cfg["temp-adjustment-value"];
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
debug["runtime-average"] = cfg["runtime-average"];
|
||||
debug["rssi"] = cfg["rssi"];
|
||||
debug["app-ver"] = cfg["app-ver"];
|
||||
debug["app-build"] = cfg["app-build"];
|
||||
});
|
||||
|
||||
var url = "/api/config/format";
|
||||
//var url = "/test/format.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
debug["formats"] = cfg;
|
||||
|
||||
var url = "/api/config/advanced";
|
||||
//var url = "/test/adv.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
debug["advanced"] = cfg;
|
||||
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
debug["files"] = cfg;
|
||||
|
||||
var url = "/log";
|
||||
//var url = "/test/log";
|
||||
$.ajax({url: url, method: 'get', success: function (data) {
|
||||
debug["log"] = data.split("\n");
|
||||
var s = JSON.stringify(debug, null, 2);
|
||||
$("#supportContent").text( s );
|
||||
$('#spinner').hide();
|
||||
//navigator.clipboard.writeText(s);
|
||||
//alert("Support information copied to clipboard");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$("#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(result) { },
|
||||
error: function(result) { showError('Could not update sleep mode for device.'); },
|
||||
} );
|
||||
});
|
||||
|
||||
function getStatus() {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"] + " (" + cfg["app-build"] + ")");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
$("#wifi-ssid").text(cfg["wifi-ssid"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if(angle==-1) {
|
||||
showError("Unable to connect to gyro, try a reset or power off/on")
|
||||
$("#angle").text("No gyro");
|
||||
$("#gravity").text("No gyro");
|
||||
} else if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#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-c"] == -273) {
|
||||
$("#temp").text("No temp sensor");
|
||||
} else {
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
}
|
||||
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
$("#sleep-mode").attr("checked", false );
|
||||
$("#sleep-mode").removeAttr("disabled");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
function start() {
|
||||
setInterval(getStatus, 5000);
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Current version:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3" id="h-app-ver-new" hidden>
|
||||
<div class="col-md-8 themed-grid-col bg-light">New version:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Host name:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Device ID:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Platform:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 3000); //5 seconds
|
||||
|
||||
function loadLog() {
|
||||
$("#logContent").load("/log");
|
||||
//$("#logContent").load("/test/status.json");
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">
|
||||
View error log
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code id="logContent"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="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-8 themed-grid-col bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
|
||||
<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">
|
||||
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(result) { },
|
||||
error: function(result) { showError('Could not update sleep mode for device.'); },
|
||||
} );
|
||||
});
|
||||
|
||||
function getStatus() {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
//$("#app-ver").text(cfg["app-ver"] + " (html 0.8.0)");
|
||||
$("#app-ver").text(cfg["app-ver"] + " (" + cfg["app-build"] + ")");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#gravity").text("Gyro moving");
|
||||
} else {
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
}
|
||||
|
||||
var batt = cfg["battery"];
|
||||
var charge = 0;
|
||||
|
||||
if(batt>4.15) charge = 100;
|
||||
else if(batt>4.05) charge = 90;
|
||||
else if(batt>3.97) charge = 80;
|
||||
else if(batt>3.91) charge = 70;
|
||||
else if(batt>3.86) charge = 60;
|
||||
else if(batt>3.81) charge = 50;
|
||||
else if(batt>3.78) charge = 40;
|
||||
else if(batt>3.76) charge = 30;
|
||||
else if(batt>3.73) charge = 20;
|
||||
else if(batt>3.67) charge = 10;
|
||||
else if(batt>3.44) charge = 5;
|
||||
|
||||
$("#battery").text(batt + " V (" + charge + "%)" );
|
||||
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
$("#sleep-mode").attr("checked", false );
|
||||
$("#sleep-mode").removeAttr("disabled");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
405
html/test.htm
@ -5,213 +5,240 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Configuration
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/config.htm">Configuration</a></li>
|
||||
<li><a class="dropdown-item" href="/format.htm">Format editor</a></li>
|
||||
<li><a class="dropdown-item" href="#">Test push</a></li>
|
||||
<li><a class="dropdown-item" href="/firmware.htm">Upload firmware</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<div class="container">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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>
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTest">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest" aria-expanded="true" aria-controls="collapseTest">
|
||||
<b>Testing push targets</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseTest" class="accordion-collapse collapse show" aria-labelledby="headingTest" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row mb-3">
|
||||
<pre class="card-preview" id="preview" name="preview">Press test button to start testing all defined push targets.</pre>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-8">
|
||||
<button class="btn btn-primary" id="test-btn" data-bs-toggle="tooltip" title="Test all push targets">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
$('#spinner').hide();
|
||||
|
||||
function clearLog() {
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
function appendLog(log) {
|
||||
doc = $("#preview").text();
|
||||
doc += log + "\n";
|
||||
$("#preview").text(doc);
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
// Get the configuration values from the API
|
||||
$("#test-btn").click(function(e) {
|
||||
clearLog();
|
||||
appendLog( "Starting test of push targets" );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$("#test-btn").prop("disabled", true);
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
var id = cfg["id"];
|
||||
console.log( id );
|
||||
|
||||
testHttp( id, "http-1" );
|
||||
testHttp( id, "http-2" );
|
||||
testHttp( id, "http-3" );
|
||||
testInfluxdb( id );
|
||||
testMqtt( id );
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.always(function() {
|
||||
});
|
||||
});
|
||||
|
||||
function testMqtt(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=mqtt";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'mqtt' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'mqtt' successful" );
|
||||
} else{
|
||||
if(code==-3)
|
||||
appendLog( "Push target 'mqtt' failed to connect" );
|
||||
else if(code==-4)
|
||||
appendLog( "Push target 'mqtt' failed with error timeout" );
|
||||
else if(code==-10)
|
||||
appendLog( "Push target 'mqtt' failed with error denied" );
|
||||
else if(code==-100)
|
||||
appendLog( "Push target 'mqtt' skipped since it's using SSL" );
|
||||
else
|
||||
appendLog( "Push target 'mqtt' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'mqtt'");
|
||||
})
|
||||
}
|
||||
|
||||
function testInfluxdb(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=influxdb";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'influxdb' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'influxdb' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target 'influxdb' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target 'influxdb' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target 'influxdb' failed with error code 404, url not found" );
|
||||
else if(code==-100)
|
||||
appendLog( "Push target 'influxdb' skipped since it's using SSL" );
|
||||
else
|
||||
appendLog( "Push target 'influxdb' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testHttp(id, target) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=" + target;
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target '" + target + "' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target '" + target + "' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target '" + target + "' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target '" + target + "' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target '" + target + "' failed with error code 404, url not found" );
|
||||
else if(code==-100)
|
||||
appendLog( "Push target '" + target + "' skipped since it's using SSL" );
|
||||
else
|
||||
appendLog( "Push target '" + target + "' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target '" + target + "'");
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<pre class="card-preview" id="preview" name="preview"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="form-group row">
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8">
|
||||
<button class="btn btn-primary" id="test-btn">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$('#spinner').hide();
|
||||
|
||||
function clearLog() {
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
function appendLog(log) {
|
||||
doc = $("#preview").text();
|
||||
doc += log + "\n";
|
||||
$("#preview").text(doc);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
$("#test-btn").click(function(e) {
|
||||
clearLog();
|
||||
appendLog( "Starting test of push targets" );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$("#test-btn").prop("disabled", true);
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
var id = cfg["id"];
|
||||
console.log( id );
|
||||
|
||||
testHttp( id, "http-1" );
|
||||
testHttp( id, "http-2" );
|
||||
testHttp( id, "http-3" );
|
||||
testHttp( id, "brewfather" );
|
||||
testInfluxdb( id );
|
||||
testMqtt( id );
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.always(function() {
|
||||
});
|
||||
});
|
||||
|
||||
function testMqtt(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=mqtt";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'mqtt' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'mqtt' successful" );
|
||||
} else{
|
||||
if(code==-3)
|
||||
appendLog( "Push target 'mqtt' failed to connect" );
|
||||
else if(code==-4)
|
||||
appendLog( "Push target 'mqtt' failed with error timeout" );
|
||||
else if(code==-10)
|
||||
appendLog( "Push target 'mqtt' failed with error denied" );
|
||||
else
|
||||
appendLog( "Push target 'mqtt' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testInfluxdb(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=influxdb";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'influxdb' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'influxdb' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target 'influxdb' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target 'influxdb' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target 'influxdb' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target 'influxdb' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testHttp(id, target) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=" + target;
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target '" + target + "' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target '" + target + "' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target '" + target + "' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target '" + target + "' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target '" + target + "' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target '" + target + "' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target '" + target + "'");
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
289
html/upload.htm
@ -5,160 +5,175 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-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>
|
||||
<!-- START MENU -->
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-4">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingUpload">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseUpload" aria-expanded="true" aria-controls="collapseUpload">
|
||||
<b>Upload missing html files</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseUpload" class="accordion-collapse collapse show" aria-labelledby="headingUpload" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 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 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 bg-light">index.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="index">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">config.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="config">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">calibration.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="calibration">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">format.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="format">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">test.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="test">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">about.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="about">Checking...</div>
|
||||
</div>
|
||||
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".min.htm" class="custom-file-input" name="name" id="name" data-bs-toggle="tooltip" title="Select a file that matches the name in the list above to upload it to the device">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Upload the selected file to the device">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
window.onload = getUpload;
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
function getUpload() {
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
if( cfg["index"] )
|
||||
$("#index").text("Completed.");
|
||||
else
|
||||
$("#index").text("File is missing.");
|
||||
|
||||
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["test"] )
|
||||
$("#test").text("Completed.");
|
||||
else
|
||||
$("#test").text("File is missing.");
|
||||
|
||||
if( cfg["format"] )
|
||||
$("#format").text("Completed.");
|
||||
else
|
||||
$("#format").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
$("#about").text("File is missing.");
|
||||
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</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>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<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">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">test.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="test">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".min.htm" class="custom-file-input" name="name" id="name">
|
||||
<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">
|
||||
window.onload = getUpload;
|
||||
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
|
||||
function getUpload() {
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
if( cfg["index"] )
|
||||
$("#index").text("Completed.");
|
||||
else
|
||||
$("#index").text("File is missing.");
|
||||
|
||||
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["test"] )
|
||||
$("#test").text("Completed.");
|
||||
else
|
||||
$("#test").text("File is missing.");
|
||||
|
||||
if( cfg["format"] )
|
||||
$("#format").text("Completed.");
|
||||
else
|
||||
$("#format").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
$("#about").text("File is missing.");
|
||||
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
@ -16,7 +16,7 @@
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.10.1
|
||||
Version: 1.8.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -56,10 +56,7 @@
|
||||
1.7.6 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
|
||||
1.7.7 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
|
||||
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
|
||||
1.8.0 K Hoang 29/12/2021 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
@ -16,7 +16,7 @@
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.10.1
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -58,8 +58,6 @@
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
|
||||
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
@ -16,7 +16,7 @@
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.10.1
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -58,8 +58,6 @@
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
|
||||
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
@ -76,27 +74,18 @@
|
||||
#warning You have to select HUGE APP or 1.9-2.0 MB APP to be able to run Config Portal. Must use PSRAM
|
||||
#define USING_ESP32_S2 true
|
||||
#elif ( ARDUINO_ESP32C3_DEV )
|
||||
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) )
|
||||
#warning Using ESP32_C3 using core v2.0.0+. Either LittleFS, SPIFFS or EEPROM OK.
|
||||
#else
|
||||
#warning Using ESP32_C3 using core v1.0.6-. To follow library instructions to install esp32-c3 core. Only SPIFFS and EEPROM OK.
|
||||
#endif
|
||||
|
||||
#warning Using ESP32_C3. To follow library instructions to install esp32-c3 core. Only SPIFFS and EEPROM OK.
|
||||
#warning You have to select Flash size 2MB and Minimal APP (1.3MB + 700KB) for some boards
|
||||
#define USING_ESP32_C3 true
|
||||
#elif ( defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32_S3_BOX) || defined(ARDUINO_TINYS3) || \
|
||||
defined(ARDUINO_PROS3) || defined(ARDUINO_FEATHERS3) )
|
||||
#warning Using ESP32_S3. To install esp32-s3-support branch if using core v2.0.2-.
|
||||
#define USING_ESP32_S3 true
|
||||
#endif
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION "ESP_WiFiManager v1.10.1"
|
||||
#define ESP_WIFIMANAGER_VERSION "ESP_WiFiManager v1.9.0"
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION_MAJOR 1
|
||||
#define ESP_WIFIMANAGER_VERSION_MINOR 10
|
||||
#define ESP_WIFIMANAGER_VERSION_PATCH 1
|
||||
#define ESP_WIFIMANAGER_VERSION_MINOR 9
|
||||
#define ESP_WIFIMANAGER_VERSION_PATCH 0
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION_INT 1010001
|
||||
#define ESP_WIFIMANAGER_VERSION_INT 1009000
|
||||
|
||||
#include "ESP_WiFiManager_Debug.h"
|
||||
|
||||
@ -243,8 +232,7 @@ const char WM_HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a>
|
||||
const char JSON_ITEM[] PROGMEM = "{\"SSID\":\"{v}\", \"Encryption\":{i}, \"Quality\":\"{r}\"}";
|
||||
|
||||
// KH, update from v1.1.0
|
||||
//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><label>SSID1</label><input id='s1' name='s1' length=32 placeholder='SSID1'><div></div></div><div><label>Password</label><input id='p1' name='p1' length=64 placeholder='password1'><div></div></div></fieldset>";
|
||||
extern const char WM_HTTP_FORM_START[] PROGMEM;
|
||||
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><label>SSID1</label><input id='s1' name='s1' length=32 placeholder='SSID1'><div></div></div><div><label>Password</label><input id='p1' name='p1' length=64 placeholder='password1'><div></div></div></fieldset>";
|
||||
//////
|
||||
|
||||
// KH, add from v1.0.10
|
@ -16,7 +16,7 @@
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.10.1
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -58,8 +58,6 @@
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
1.10.0 K Hoang 10/02/2022 Add support to new ESP32-S3
|
||||
1.10.1 K Hoang 11/02/2022 Add LittleFS support to ESP32-C3. Use core LittleFS instead of Lorol's LITTLEFS for v2.0.0+
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
@ -71,6 +71,8 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
#if 1
|
||||
|
||||
#define TZ_Africa_Abidjan ("GMT0")
|
||||
#define TZ_Africa_Accra ("GMT0")
|
||||
#define TZ_Africa_Addis_Ababa ("EAT-3")
|
||||
@ -532,11 +534,479 @@
|
||||
#define TZ_Etc_Universal ("UTC0")
|
||||
#define TZ_Etc_Zulu ("UTC0")
|
||||
|
||||
|
||||
#else
|
||||
|
||||
|
||||
#define TZ_Africa_Abidjan PSTR("GMT0")
|
||||
#define TZ_Africa_Accra PSTR("GMT0")
|
||||
#define TZ_Africa_Addis_Ababa PSTR("EAT-3")
|
||||
#define TZ_Africa_Algiers PSTR("CET-1")
|
||||
#define TZ_Africa_Asmara PSTR("EAT-3")
|
||||
#define TZ_Africa_Bamako PSTR("GMT0")
|
||||
#define TZ_Africa_Bangui PSTR("WAT-1")
|
||||
#define TZ_Africa_Banjul PSTR("GMT0")
|
||||
#define TZ_Africa_Bissau PSTR("GMT0")
|
||||
#define TZ_Africa_Blantyre PSTR("CAT-2")
|
||||
#define TZ_Africa_Brazzaville PSTR("WAT-1")
|
||||
#define TZ_Africa_Bujumbura PSTR("CAT-2")
|
||||
#define TZ_Africa_Cairo PSTR("EET-2")
|
||||
#define TZ_Africa_Casablanca PSTR("<+01>-1")
|
||||
#define TZ_Africa_Ceuta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Africa_Conakry PSTR("GMT0")
|
||||
#define TZ_Africa_Dakar PSTR("GMT0")
|
||||
#define TZ_Africa_Dar_es_Salaam PSTR("EAT-3")
|
||||
#define TZ_Africa_Djibouti PSTR("EAT-3")
|
||||
#define TZ_Africa_Douala PSTR("WAT-1")
|
||||
#define TZ_Africa_El_Aaiun PSTR("<+01>-1")
|
||||
#define TZ_Africa_Freetown PSTR("GMT0")
|
||||
#define TZ_Africa_Gaborone PSTR("CAT-2")
|
||||
#define TZ_Africa_Harare PSTR("CAT-2")
|
||||
#define TZ_Africa_Johannesburg PSTR("SAST-2")
|
||||
#define TZ_Africa_Juba PSTR("EAT-3")
|
||||
#define TZ_Africa_Kampala PSTR("EAT-3")
|
||||
#define TZ_Africa_Khartoum PSTR("CAT-2")
|
||||
#define TZ_Africa_Kigali PSTR("CAT-2")
|
||||
#define TZ_Africa_Kinshasa PSTR("WAT-1")
|
||||
#define TZ_Africa_Lagos PSTR("WAT-1")
|
||||
#define TZ_Africa_Libreville PSTR("WAT-1")
|
||||
#define TZ_Africa_Lome PSTR("GMT0")
|
||||
#define TZ_Africa_Luanda PSTR("WAT-1")
|
||||
#define TZ_Africa_Lubumbashi PSTR("CAT-2")
|
||||
#define TZ_Africa_Lusaka PSTR("CAT-2")
|
||||
#define TZ_Africa_Malabo PSTR("WAT-1")
|
||||
#define TZ_Africa_Maputo PSTR("CAT-2")
|
||||
#define TZ_Africa_Maseru PSTR("SAST-2")
|
||||
#define TZ_Africa_Mbabane PSTR("SAST-2")
|
||||
#define TZ_Africa_Mogadishu PSTR("EAT-3")
|
||||
#define TZ_Africa_Monrovia PSTR("GMT0")
|
||||
#define TZ_Africa_Nairobi PSTR("EAT-3")
|
||||
#define TZ_Africa_Ndjamena PSTR("WAT-1")
|
||||
#define TZ_Africa_Niamey PSTR("WAT-1")
|
||||
#define TZ_Africa_Nouakchott PSTR("GMT0")
|
||||
#define TZ_Africa_Ouagadougou PSTR("GMT0")
|
||||
#define TZ_Africa_PortomNovo PSTR("WAT-1")
|
||||
#define TZ_Africa_Sao_Tome PSTR("GMT0")
|
||||
#define TZ_Africa_Tripoli PSTR("EET-2")
|
||||
#define TZ_Africa_Tunis PSTR("CET-1")
|
||||
#define TZ_Africa_Windhoek PSTR("CAT-2")
|
||||
#define TZ_America_Adak PSTR("HST10HDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Anchorage PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Anguilla PSTR("AST4")
|
||||
#define TZ_America_Antigua PSTR("AST4")
|
||||
#define TZ_America_Araguaina PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Buenos_Aires PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Catamarca PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Cordoba PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Jujuy PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_La_Rioja PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Mendoza PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Rio_Gallegos PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Salta PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_San_Juan PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_San_Luis PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Tucuman PSTR("<-03>3")
|
||||
#define TZ_America_Argentina_Ushuaia PSTR("<-03>3")
|
||||
#define TZ_America_Aruba PSTR("AST4")
|
||||
#define TZ_America_Asuncion PSTR("<-04>4<-03>,M10.1.0/0,M3.4.0/0")
|
||||
#define TZ_America_Atikokan PSTR("EST5")
|
||||
#define TZ_America_Bahia PSTR("<-03>3")
|
||||
#define TZ_America_Bahia_Banderas PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Barbados PSTR("AST4")
|
||||
#define TZ_America_Belem PSTR("<-03>3")
|
||||
#define TZ_America_Belize PSTR("CST6")
|
||||
#define TZ_America_BlancmSablon PSTR("AST4")
|
||||
#define TZ_America_Boa_Vista PSTR("<-04>4")
|
||||
#define TZ_America_Bogota PSTR("<-05>5")
|
||||
#define TZ_America_Boise PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Cambridge_Bay PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Campo_Grande PSTR("<-04>4")
|
||||
#define TZ_America_Cancun PSTR("EST5")
|
||||
#define TZ_America_Caracas PSTR("<-04>4")
|
||||
#define TZ_America_Cayenne PSTR("<-03>3")
|
||||
#define TZ_America_Cayman PSTR("EST5")
|
||||
#define TZ_America_Chicago PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Chihuahua PSTR("MST7MDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Costa_Rica PSTR("CST6")
|
||||
#define TZ_America_Creston PSTR("MST7")
|
||||
#define TZ_America_Cuiaba PSTR("<-04>4")
|
||||
#define TZ_America_Curacao PSTR("AST4")
|
||||
#define TZ_America_Danmarkshavn PSTR("GMT0")
|
||||
#define TZ_America_Dawson PSTR("MST7")
|
||||
#define TZ_America_Dawson_Creek PSTR("MST7")
|
||||
#define TZ_America_Denver PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Detroit PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Dominica PSTR("AST4")
|
||||
#define TZ_America_Edmonton PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Eirunepe PSTR("<-05>5")
|
||||
#define TZ_America_El_Salvador PSTR("CST6")
|
||||
#define TZ_America_Fortaleza PSTR("<-03>3")
|
||||
#define TZ_America_Fort_Nelson PSTR("MST7")
|
||||
#define TZ_America_Glace_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Godthab PSTR("<-03>3<-02>,M3.5.0/-2,M10.5.0/-1")
|
||||
#define TZ_America_Goose_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Grand_Turk PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Grenada PSTR("AST4")
|
||||
#define TZ_America_Guadeloupe PSTR("AST4")
|
||||
#define TZ_America_Guatemala PSTR("CST6")
|
||||
#define TZ_America_Guayaquil PSTR("<-05>5")
|
||||
#define TZ_America_Guyana PSTR("<-04>4")
|
||||
#define TZ_America_Halifax PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Havana PSTR("CST5CDT,M3.2.0/0,M11.1.0/1")
|
||||
#define TZ_America_Hermosillo PSTR("MST7")
|
||||
#define TZ_America_Indiana_Indianapolis PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Knox PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Marengo PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Petersburg PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Tell_City PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Vevay PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Vincennes PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Indiana_Winamac PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Inuvik PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Iqaluit PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Jamaica PSTR("EST5")
|
||||
#define TZ_America_Juneau PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Kentucky_Louisville PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Kentucky_Monticello PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Kralendijk PSTR("AST4")
|
||||
#define TZ_America_La_Paz PSTR("<-04>4")
|
||||
#define TZ_America_Lima PSTR("<-05>5")
|
||||
#define TZ_America_Los_Angeles PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Lower_Princes PSTR("AST4")
|
||||
#define TZ_America_Maceio PSTR("<-03>3")
|
||||
#define TZ_America_Managua PSTR("CST6")
|
||||
#define TZ_America_Manaus PSTR("<-04>4")
|
||||
#define TZ_America_Marigot PSTR("AST4")
|
||||
#define TZ_America_Martinique PSTR("AST4")
|
||||
#define TZ_America_Matamoros PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Mazatlan PSTR("MST7MDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Menominee PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Merida PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Metlakatla PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Mexico_City PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Miquelon PSTR("<-03>3<-02>,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Moncton PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Monterrey PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||
#define TZ_America_Montevideo PSTR("<-03>3")
|
||||
#define TZ_America_Montreal PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Montserrat PSTR("AST4")
|
||||
#define TZ_America_Nassau PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_New_York PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Nipigon PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Nome PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Noronha PSTR("<-02>2")
|
||||
#define TZ_America_North_Dakota_Beulah PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_North_Dakota_Center PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_North_Dakota_New_Salem PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Ojinaga PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Panama PSTR("EST5")
|
||||
#define TZ_America_Pangnirtung PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Paramaribo PSTR("<-03>3")
|
||||
#define TZ_America_Phoenix PSTR("MST7")
|
||||
#define TZ_America_PortmaumPrince PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Port_of_Spain PSTR("AST4")
|
||||
#define TZ_America_Porto_Velho PSTR("<-04>4")
|
||||
#define TZ_America_Puerto_Rico PSTR("AST4")
|
||||
#define TZ_America_Punta_Arenas PSTR("<-03>3")
|
||||
#define TZ_America_Rainy_River PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Rankin_Inlet PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Recife PSTR("<-03>3")
|
||||
#define TZ_America_Regina PSTR("CST6")
|
||||
#define TZ_America_Resolute PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Rio_Branco PSTR("<-05>5")
|
||||
#define TZ_America_Santarem PSTR("<-03>3")
|
||||
#define TZ_America_Santiago PSTR("<-04>4<-03>,M9.1.6/24,M4.1.6/24")
|
||||
#define TZ_America_Santo_Domingo PSTR("AST4")
|
||||
#define TZ_America_Sao_Paulo PSTR("<-03>3")
|
||||
#define TZ_America_Scoresbysund PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
|
||||
#define TZ_America_Sitka PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_St_Barthelemy PSTR("AST4")
|
||||
#define TZ_America_St_Johns PSTR("NST3:30NDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_St_Kitts PSTR("AST4")
|
||||
#define TZ_America_St_Lucia PSTR("AST4")
|
||||
#define TZ_America_St_Thomas PSTR("AST4")
|
||||
#define TZ_America_St_Vincent PSTR("AST4")
|
||||
#define TZ_America_Swift_Current PSTR("CST6")
|
||||
#define TZ_America_Tegucigalpa PSTR("CST6")
|
||||
#define TZ_America_Thule PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Thunder_Bay PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Tijuana PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Toronto PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Tortola PSTR("AST4")
|
||||
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Whitehorse PSTR("MST7")
|
||||
#define TZ_America_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Yakutat PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
#define TZ_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||
#define TZ_Antarctica_Casey PSTR("<+11>-11")
|
||||
#define TZ_Antarctica_Davis PSTR("<+07>-7")
|
||||
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
|
||||
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
|
||||
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||
#define TZ_Antarctica_Palmer PSTR("<-03>3")
|
||||
#define TZ_Antarctica_Rothera PSTR("<-03>3")
|
||||
#define TZ_Antarctica_Syowa PSTR("<+03>-3")
|
||||
#define TZ_Antarctica_Troll PSTR("<+00>0<+02>-2,M3.5.0/1,M10.5.0/3")
|
||||
#define TZ_Antarctica_Vostok PSTR("<+06>-6")
|
||||
#define TZ_Arctic_Longyearbyen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Asia_Aden PSTR("<+03>-3")
|
||||
#define TZ_Asia_Almaty PSTR("<+06>-6")
|
||||
#define TZ_Asia_Amman PSTR("EET-2EEST,M3.5.4/24,M10.5.5/1")
|
||||
#define TZ_Asia_Anadyr PSTR("<+12>-12")
|
||||
#define TZ_Asia_Aqtau PSTR("<+05>-5")
|
||||
#define TZ_Asia_Aqtobe PSTR("<+05>-5")
|
||||
#define TZ_Asia_Ashgabat PSTR("<+05>-5")
|
||||
#define TZ_Asia_Atyrau PSTR("<+05>-5")
|
||||
#define TZ_Asia_Baghdad PSTR("<+03>-3")
|
||||
#define TZ_Asia_Bahrain PSTR("<+03>-3")
|
||||
#define TZ_Asia_Baku PSTR("<+04>-4")
|
||||
#define TZ_Asia_Bangkok PSTR("<+07>-7")
|
||||
#define TZ_Asia_Barnaul PSTR("<+07>-7")
|
||||
#define TZ_Asia_Beirut PSTR("EET-2EEST,M3.5.0/0,M10.5.0/0")
|
||||
#define TZ_Asia_Bishkek PSTR("<+06>-6")
|
||||
#define TZ_Asia_Brunei PSTR("<+08>-8")
|
||||
#define TZ_Asia_Chita PSTR("<+09>-9")
|
||||
#define TZ_Asia_Choibalsan PSTR("<+08>-8")
|
||||
#define TZ_Asia_Colombo PSTR("<+0530>-5:30")
|
||||
#define TZ_Asia_Damascus PSTR("EET-2EEST,M3.5.5/0,M10.5.5/0")
|
||||
#define TZ_Asia_Dhaka PSTR("<+06>-6")
|
||||
#define TZ_Asia_Dili PSTR("<+09>-9")
|
||||
#define TZ_Asia_Dubai PSTR("<+04>-4")
|
||||
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
|
||||
#define TZ_Asia_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||
#define TZ_Asia_Ho_Chi_Minh PSTR("<+07>-7")
|
||||
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
|
||||
#define TZ_Asia_Hovd PSTR("<+07>-7")
|
||||
#define TZ_Asia_Irkutsk PSTR("<+08>-8")
|
||||
#define TZ_Asia_Jakarta PSTR("WIB-7")
|
||||
#define TZ_Asia_Jayapura PSTR("WIT-9")
|
||||
#define TZ_Asia_Jerusalem PSTR("IST-2IDT,M3.4.4/26,M10.5.0")
|
||||
#define TZ_Asia_Kabul PSTR("<+0430>-4:30")
|
||||
#define TZ_Asia_Kamchatka PSTR("<+12>-12")
|
||||
#define TZ_Asia_Karachi PSTR("PKT-5")
|
||||
#define TZ_Asia_Kathmandu PSTR("<+0545>-5:45")
|
||||
#define TZ_Asia_Khandyga PSTR("<+09>-9")
|
||||
#define TZ_Asia_Kolkata PSTR("IST-5:30")
|
||||
#define TZ_Asia_Krasnoyarsk PSTR("<+07>-7")
|
||||
#define TZ_Asia_Kuala_Lumpur PSTR("<+08>-8")
|
||||
#define TZ_Asia_Kuching PSTR("<+08>-8")
|
||||
#define TZ_Asia_Kuwait PSTR("<+03>-3")
|
||||
#define TZ_Asia_Macau PSTR("CST-8")
|
||||
#define TZ_Asia_Magadan PSTR("<+11>-11")
|
||||
#define TZ_Asia_Makassar PSTR("WITA-8")
|
||||
#define TZ_Asia_Manila PSTR("PST-8")
|
||||
#define TZ_Asia_Muscat PSTR("<+04>-4")
|
||||
#define TZ_Asia_Nicosia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Asia_Novokuznetsk PSTR("<+07>-7")
|
||||
#define TZ_Asia_Novosibirsk PSTR("<+07>-7")
|
||||
#define TZ_Asia_Omsk PSTR("<+06>-6")
|
||||
#define TZ_Asia_Oral PSTR("<+05>-5")
|
||||
#define TZ_Asia_Phnom_Penh PSTR("<+07>-7")
|
||||
#define TZ_Asia_Pontianak PSTR("WIB-7")
|
||||
#define TZ_Asia_Pyongyang PSTR("KST-9")
|
||||
#define TZ_Asia_Qatar PSTR("<+03>-3")
|
||||
#define TZ_Asia_Qyzylorda PSTR("<+05>-5")
|
||||
#define TZ_Asia_Riyadh PSTR("<+03>-3")
|
||||
#define TZ_Asia_Sakhalin PSTR("<+11>-11")
|
||||
#define TZ_Asia_Samarkand PSTR("<+05>-5")
|
||||
#define TZ_Asia_Seoul PSTR("KST-9")
|
||||
#define TZ_Asia_Shanghai PSTR("CST-8")
|
||||
#define TZ_Asia_Singapore PSTR("<+08>-8")
|
||||
#define TZ_Asia_Srednekolymsk PSTR("<+11>-11")
|
||||
#define TZ_Asia_Taipei PSTR("CST-8")
|
||||
#define TZ_Asia_Tashkent PSTR("<+05>-5")
|
||||
#define TZ_Asia_Tbilisi PSTR("<+04>-4")
|
||||
#define TZ_Asia_Tehran PSTR("<+0330>-3:30<+0430>,J79/24,J263/24")
|
||||
#define TZ_Asia_Thimphu PSTR("<+06>-6")
|
||||
#define TZ_Asia_Tokyo PSTR("JST-9")
|
||||
#define TZ_Asia_Tomsk PSTR("<+07>-7")
|
||||
#define TZ_Asia_Ulaanbaatar PSTR("<+08>-8")
|
||||
#define TZ_Asia_Urumqi PSTR("<+06>-6")
|
||||
#define TZ_Asia_UstmNera PSTR("<+10>-10")
|
||||
#define TZ_Asia_Vientiane PSTR("<+07>-7")
|
||||
#define TZ_Asia_Vladivostok PSTR("<+10>-10")
|
||||
#define TZ_Asia_Yakutsk PSTR("<+09>-9")
|
||||
#define TZ_Asia_Yangon PSTR("<+0630>-6:30")
|
||||
#define TZ_Asia_Yekaterinburg PSTR("<+05>-5")
|
||||
#define TZ_Asia_Yerevan PSTR("<+04>-4")
|
||||
#define TZ_Atlantic_Azores PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
|
||||
#define TZ_Atlantic_Bermuda PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||
#define TZ_Atlantic_Canary PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Atlantic_Cape_Verde PSTR("<-01>1")
|
||||
#define TZ_Atlantic_Faroe PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Atlantic_Madeira PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Atlantic_Reykjavik PSTR("GMT0")
|
||||
#define TZ_Atlantic_South_Georgia PSTR("<-02>2")
|
||||
#define TZ_Atlantic_Stanley PSTR("<-03>3")
|
||||
#define TZ_Atlantic_St_Helena PSTR("GMT0")
|
||||
#define TZ_Australia_Adelaide PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Australia_Brisbane PSTR("AEST-10")
|
||||
#define TZ_Australia_Broken_Hill PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Australia_Currie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Australia_Darwin PSTR("ACST-9:30")
|
||||
#define TZ_Australia_Eucla PSTR("<+0845>-8:45")
|
||||
#define TZ_Australia_Hobart PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Australia_Lindeman PSTR("AEST-10")
|
||||
#define TZ_Australia_Lord_Howe PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0")
|
||||
#define TZ_Australia_Melbourne PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Australia_Perth PSTR("AWST-8")
|
||||
#define TZ_Australia_Sydney PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Europe_Amsterdam PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Andorra PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Astrakhan PSTR("<+04>-4")
|
||||
#define TZ_Europe_Athens PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Belgrade PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Berlin PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Bratislava PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Brussels PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Bucharest PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Budapest PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Busingen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Chisinau PSTR("EET-2EEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Copenhagen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Dublin PSTR("IST-1GMT0,M10.5.0,M3.5.0/1")
|
||||
#define TZ_Europe_Gibraltar PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Guernsey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Europe_Helsinki PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Isle_of_Man PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Europe_Istanbul PSTR("<+03>-3")
|
||||
#define TZ_Europe_Jersey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Europe_Kaliningrad PSTR("EET-2")
|
||||
#define TZ_Europe_Kiev PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Kirov PSTR("<+03>-3")
|
||||
#define TZ_Europe_Lisbon PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Europe_Ljubljana PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_London PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||
#define TZ_Europe_Luxembourg PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Madrid PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Malta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Mariehamn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Minsk PSTR("<+03>-3")
|
||||
#define TZ_Europe_Monaco PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Moscow PSTR("MSK-3")
|
||||
#define TZ_Europe_Oslo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Paris PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Podgorica PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Prague PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Riga PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Rome PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Samara PSTR("<+04>-4")
|
||||
#define TZ_Europe_San_Marino PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Sarajevo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Saratov PSTR("<+04>-4")
|
||||
#define TZ_Europe_Simferopol PSTR("MSK-3")
|
||||
#define TZ_Europe_Skopje PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Sofia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Stockholm PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Tallinn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Tirane PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Ulyanovsk PSTR("<+04>-4")
|
||||
#define TZ_Europe_Uzhgorod PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Vaduz PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Vatican PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Vienna PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Vilnius PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Volgograd PSTR("<+04>-4")
|
||||
#define TZ_Europe_Warsaw PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Zagreb PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Europe_Zaporozhye PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||
#define TZ_Europe_Zurich PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
#define TZ_Indian_Antananarivo PSTR("EAT-3")
|
||||
#define TZ_Indian_Chagos PSTR("<+06>-6")
|
||||
#define TZ_Indian_Christmas PSTR("<+07>-7")
|
||||
#define TZ_Indian_Cocos PSTR("<+0630>-6:30")
|
||||
#define TZ_Indian_Comoro PSTR("EAT-3")
|
||||
#define TZ_Indian_Kerguelen PSTR("<+05>-5")
|
||||
#define TZ_Indian_Mahe PSTR("<+04>-4")
|
||||
#define TZ_Indian_Maldives PSTR("<+05>-5")
|
||||
#define TZ_Indian_Mauritius PSTR("<+04>-4")
|
||||
#define TZ_Indian_Mayotte PSTR("EAT-3")
|
||||
#define TZ_Indian_Reunion PSTR("<+04>-4")
|
||||
#define TZ_Pacific_Apia PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4")
|
||||
#define TZ_Pacific_Auckland PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||
#define TZ_Pacific_Bougainville PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Chatham PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45")
|
||||
#define TZ_Pacific_Chuuk PSTR("<+10>-10")
|
||||
#define TZ_Pacific_Easter PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22")
|
||||
#define TZ_Pacific_Efate PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Enderbury PSTR("<+13>-13")
|
||||
#define TZ_Pacific_Fakaofo PSTR("<+13>-13")
|
||||
#define TZ_Pacific_Fiji PSTR("<+12>-12<+13>,M11.2.0,M1.2.3/99")
|
||||
#define TZ_Pacific_Funafuti PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Galapagos PSTR("<-06>6")
|
||||
#define TZ_Pacific_Gambier PSTR("<-09>9")
|
||||
#define TZ_Pacific_Guadalcanal PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Guam PSTR("ChST-10")
|
||||
#define TZ_Pacific_Honolulu PSTR("HST10")
|
||||
#define TZ_Pacific_Kiritimati PSTR("<+14>-14")
|
||||
#define TZ_Pacific_Kosrae PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Kwajalein PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Majuro PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Marquesas PSTR("<-0930>9:30")
|
||||
#define TZ_Pacific_Midway PSTR("SST11")
|
||||
#define TZ_Pacific_Nauru PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Niue PSTR("<-11>11")
|
||||
#define TZ_Pacific_Norfolk PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3")
|
||||
#define TZ_Pacific_Noumea PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Pago_Pago PSTR("SST11")
|
||||
#define TZ_Pacific_Palau PSTR("<+09>-9")
|
||||
#define TZ_Pacific_Pitcairn PSTR("<-08>8")
|
||||
#define TZ_Pacific_Pohnpei PSTR("<+11>-11")
|
||||
#define TZ_Pacific_Port_Moresby PSTR("<+10>-10")
|
||||
#define TZ_Pacific_Rarotonga PSTR("<-10>10")
|
||||
#define TZ_Pacific_Saipan PSTR("ChST-10")
|
||||
#define TZ_Pacific_Tahiti PSTR("<-10>10")
|
||||
#define TZ_Pacific_Tarawa PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Tongatapu PSTR("<+13>-13")
|
||||
#define TZ_Pacific_Wake PSTR("<+12>-12")
|
||||
#define TZ_Pacific_Wallis PSTR("<+12>-12")
|
||||
#define TZ_Etc_GMT PSTR("GMT0")
|
||||
#define TZ_Etc_GMTm0 PSTR("GMT0")
|
||||
#define TZ_Etc_GMTm1 PSTR("<+01>-1")
|
||||
#define TZ_Etc_GMTm2 PSTR("<+02>-2")
|
||||
#define TZ_Etc_GMTm3 PSTR("<+03>-3")
|
||||
#define TZ_Etc_GMTm4 PSTR("<+04>-4")
|
||||
#define TZ_Etc_GMTm5 PSTR("<+05>-5")
|
||||
#define TZ_Etc_GMTm6 PSTR("<+06>-6")
|
||||
#define TZ_Etc_GMTm7 PSTR("<+07>-7")
|
||||
#define TZ_Etc_GMTm8 PSTR("<+08>-8")
|
||||
#define TZ_Etc_GMTm9 PSTR("<+09>-9")
|
||||
#define TZ_Etc_GMTm10 PSTR("<+10>-10")
|
||||
#define TZ_Etc_GMTm11 PSTR("<+11>-11")
|
||||
#define TZ_Etc_GMTm12 PSTR("<+12>-12")
|
||||
#define TZ_Etc_GMTm13 PSTR("<+13>-13")
|
||||
#define TZ_Etc_GMTm14 PSTR("<+14>-14")
|
||||
#define TZ_Etc_GMT0 PSTR("GMT0")
|
||||
#define TZ_Etc_GMTp0 PSTR("GMT0")
|
||||
#define TZ_Etc_GMTp1 PSTR("<-01>1")
|
||||
#define TZ_Etc_GMTp2 PSTR("<-02>2")
|
||||
#define TZ_Etc_GMTp3 PSTR("<-03>3")
|
||||
#define TZ_Etc_GMTp4 PSTR("<-04>4")
|
||||
#define TZ_Etc_GMTp5 PSTR("<-05>5")
|
||||
#define TZ_Etc_GMTp6 PSTR("<-06>6")
|
||||
#define TZ_Etc_GMTp7 PSTR("<-07>7")
|
||||
#define TZ_Etc_GMTp8 PSTR("<-08>8")
|
||||
#define TZ_Etc_GMTp9 PSTR("<-09>9")
|
||||
#define TZ_Etc_GMTp10 PSTR("<-10>10")
|
||||
#define TZ_Etc_GMTp11 PSTR("<-11>11")
|
||||
#define TZ_Etc_GMTp12 PSTR("<-12>12")
|
||||
#define TZ_Etc_UCT PSTR("UTC0")
|
||||
#define TZ_Etc_UTC PSTR("UTC0")
|
||||
#define TZ_Etc_Greenwich PSTR("GMT0")
|
||||
#define TZ_Etc_Universal PSTR("UTC0")
|
||||
#define TZ_Etc_Zulu PSTR("UTC0")
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#define TIMEZONE_MAX_LEN 50
|
||||
|
||||
static const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
#if 1
|
||||
|
||||
const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
{
|
||||
#if USING_AFRICA
|
||||
"Africa/Abidjan", //PSTR("GMT0")
|
||||
@ -1033,7 +1503,7 @@ static const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
static const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
{
|
||||
#if USING_AFRICA
|
||||
TZ_Africa_Abidjan, //PSTR("GMT0")
|
||||
@ -1529,4 +1999,152 @@ static const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ =
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
#else
|
||||
const char TZ_NAME[][TIMEZONE_MAX_LEN] =
|
||||
{
|
||||
"Pacific/Pago_Pago",
|
||||
"America/Adak",
|
||||
"Pacific/Honolulu",
|
||||
"Pacific/Marquesas",
|
||||
"Pacific/Gambier",
|
||||
"America/Anchorage",
|
||||
"America/Los_Angeles",
|
||||
"Pacific/Pitcairn",
|
||||
"America/Phoenix",
|
||||
"America/Denver",
|
||||
"America/Guatemala",
|
||||
"America/Chicago",
|
||||
"Pacific/Easter",
|
||||
"America/Bogota",
|
||||
"America/New_York",
|
||||
"America/Caracas",
|
||||
"America/Halifax",
|
||||
"America/Santo_Domingo",
|
||||
"America/Santiago",
|
||||
"America/St_Johns",
|
||||
"America/Godthab",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Montevideo",
|
||||
"Etc/GMT+2",
|
||||
"Atlantic/Azores",
|
||||
"Atlantic/Cape_Verde",
|
||||
"Etc/UTC",
|
||||
"Europe/London",
|
||||
"Europe/Berlin",
|
||||
"Africa/Lagos",
|
||||
"Africa/Windhoek",
|
||||
"Asia/Beirut",
|
||||
"Africa/Johannesburg",
|
||||
"Asia/Baghdad",
|
||||
"Europe/Moscow",
|
||||
"Asia/Tehran",
|
||||
"Asia/Dubai",
|
||||
"Asia/Baku",
|
||||
"Asia/Kabul",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Karachi",
|
||||
"Asia/Kolkata",
|
||||
"Asia/Kathmandu",
|
||||
"Asia/Dhaka",
|
||||
"Asia/Omsk",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Irkutsk",
|
||||
"Australia/Eucla",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Tokyo",
|
||||
"Australia/Darwin",
|
||||
"Australia/Adelaide",
|
||||
"Australia/Brisbane",
|
||||
"Asia/Vladivostok",
|
||||
"Australia/Sydney",
|
||||
"Australia/Lord_Howe",
|
||||
"Asia/Kamchatka",
|
||||
"Pacific/Noumea",
|
||||
"Pacific/Norfolk",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Tarawa",
|
||||
"Pacific/Chatham",
|
||||
"Pacific/Tongatapu",
|
||||
"Pacific/Apia",
|
||||
"Pacific/Kiritimati",
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] =
|
||||
{
|
||||
TZ_Pacific_Pago_Pago, //PSTR("SST11")
|
||||
TZ_America_Adak, //PSTR("HST10HDT,M3.2.0,M11.1.0")
|
||||
TZ_Pacific_Honolulu, //PSTR("HST10")
|
||||
TZ_Pacific_Marquesas, //PSTR("<-0930>9:30")
|
||||
TZ_Pacific_Gambier, //PSTR("<-09>9")
|
||||
TZ_America_Anchorage, //PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||
TZ_America_Los_Angeles, //"PST8PDT,M3.2.0,M11.1.0", //"America/Los_Angeles",
|
||||
TZ_Pacific_Pitcairn, //PSTR("<-08>8")
|
||||
TZ_America_Phoenix, //PSTR("MST7")
|
||||
TZ_America_Denver, //"MST7MDT,M3.2.0,M11.1.0", //"America/Denver",
|
||||
TZ_America_Guatemala, //PSTR("CST6")
|
||||
TZ_America_Chicago, //"CST6CDT,M3.2.0,M11.1.0", //"America/Chicago",
|
||||
TZ_Pacific_Easter, //PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22")
|
||||
TZ_America_Bogota, //PSTR("<-05>5")
|
||||
TZ_America_New_York, //"EST5EDT,M3.2.0,M11.1.0", //"America/New_York",
|
||||
TZ_America_Caracas, //PSTR("<-04>4")
|
||||
TZ_America_Halifax, //"AST4ADT,M3.2.0,M11.1.0", //"America/Halifax",
|
||||
TZ_America_Santo_Domingo, //PSTR("AST4")
|
||||
TZ_America_Santiago, //"<-04>4<-03>,M9.1.6/24,M4.1.6/24", //"America/Santiago",
|
||||
TZ_America_St_Johns, //PSTR("NST3:30NDT,M3.2.0,M11.1.0")
|
||||
TZ_America_Godthab, //"<-03>3<-02>,M3.5.0/-2,M10.5.0/-1", //"America/Godthab",
|
||||
TZ_America_Argentina_Buenos_Aires, //PSTR("<-03>3")
|
||||
TZ_America_Montevideo, //"<-03>3", //"America/Montevideo",
|
||||
TZ_Etc_GMTp2, //PSTR("<-02>2")
|
||||
TZ_Atlantic_Azores, //PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
|
||||
TZ_Atlantic_Cape_Verde, //PSTR("<-01>1")
|
||||
TZ_Etc_UTC, //PSTR("UTC0")
|
||||
TZ_Europe_London, //PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||
TZ_Europe_Berlin, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||
TZ_Africa_Lagos, //PSTR("WAT-1")
|
||||
TZ_Africa_Windhoek, //PSTR("CAT-2")
|
||||
TZ_Asia_Beirut, //"EET-2EEST,M3.5.0/0,M10.5.0/0" //"Asia/Beirut",
|
||||
TZ_Africa_Johannesburg, //"SAST-2", //"Africa/Johannesburg",
|
||||
TZ_Asia_Baghdad, //"<+03>-3", //"Asia/Baghdad",
|
||||
TZ_Europe_Moscow, //PSTR("MSK-3")
|
||||
TZ_Asia_Tehran, //PSTR("<+0330>-3:30<+0430>,J79/24,J263/24")
|
||||
TZ_Asia_Dubai, //"<+04>-4", //"Asia/Dubai",
|
||||
TZ_Asia_Baku, //PSTR("<+04>-4")
|
||||
TZ_Asia_Kabul, //PSTR("<+0430>-4:30")
|
||||
TZ_Asia_Yekaterinburg, //PSTR("<+05>-5")
|
||||
TZ_Asia_Karachi, //PSTR("PKT-5")
|
||||
TZ_Asia_Kolkata, //PSTR("IST-5:30")
|
||||
TZ_Asia_Kathmandu, //PSTR("<+0545>-5:45")
|
||||
TZ_Asia_Dhaka, //"<+06>-6", //"Asia/Dhaka",
|
||||
TZ_Asia_Omsk, //PSTR("<+06>-6")
|
||||
TZ_Asia_Krasnoyarsk, //PSTR("<+07>-7")
|
||||
TZ_Asia_Jakarta, //"WIB-7", //"Asia/Jakarta",
|
||||
TZ_Asia_Shanghai, //"CST-8", //"Asia/Shanghai",
|
||||
TZ_Asia_Irkutsk, //PSTR("<+08>-8")
|
||||
TZ_Australia_Eucla, //PSTR("<+0845>-8:45")
|
||||
TZ_Asia_Yakutsk, //PSTR("<+09>-9")
|
||||
TZ_Asia_Tokyo, //"JST-9", //"Asia/Tokyo",
|
||||
TZ_Australia_Darwin, //PSTR("ACST-9:30")
|
||||
TZ_Australia_Adelaide, //PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
|
||||
TZ_Australia_Brisbane, //"AEST-10", //"Australia/Brisbane",
|
||||
TZ_Asia_Vladivostok, //PSTR("<+10>-10")
|
||||
TZ_Australia_Sydney, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||
TZ_Australia_Lord_Howe, //PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0")
|
||||
TZ_Asia_Kamchatka, //PSTR("<+12>-12")
|
||||
TZ_Pacific_Noumea, //"<+11>-11", //"Pacific/Noumea",
|
||||
TZ_Pacific_Norfolk, //PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3")
|
||||
TZ_Pacific_Auckland, //"NZST-12NZDT,M9.5.0,M4.1.0/3", //"Pacific/Auckland",
|
||||
TZ_Pacific_Tarawa, //"<+12>-12", //"Pacific/Tarawa",
|
||||
TZ_Pacific_Chatham, //PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45")
|
||||
TZ_Pacific_Tongatapu, //PSTR("<+13>-13")
|
||||
TZ_Pacific_Apia, //PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4")
|
||||
TZ_Pacific_Kiritimati, //PSTR("<+14>-14")
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // TZDB_H
|
@ -195,11 +195,7 @@ uint8_t OneWire::reset(void)
|
||||
// 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;
|
||||
@ -227,11 +223,7 @@ void OneWire::write_bit(uint8_t v)
|
||||
// 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;
|
||||
@ -585,4 +577,4 @@ uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
@ -99,18 +99,10 @@ class OneWire
|
||||
|
||||
// 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
|
||||
@ -187,4 +179,4 @@ class OneWire
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // OneWire_h
|
||||
#endif // OneWire_h
|
@ -127,10 +127,14 @@
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
return (GPIO.in.val >> pin) & 0x1;
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
return (GPIO.in >> pin) & 0x1;
|
||||
else if ( pin < 40 )
|
||||
else if ( pin < 46 )
|
||||
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -138,38 +142,38 @@ IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.out_w1tc.val = ((uint32_t)1 << pin);
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1tc = ((uint32_t)1 << pin);
|
||||
else if ( pin < 34 )
|
||||
else if ( pin < 46 )
|
||||
GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.out_w1ts.val = ((uint32_t)1 << pin);
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
GPIO.out_w1ts = ((uint32_t)1 << pin);
|
||||
else if ( pin < 34 )
|
||||
else if ( pin < 46 )
|
||||
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.enable_w1tc.val = ((uint32_t)1 << (pin));
|
||||
#else
|
||||
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
|
||||
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
@ -177,40 +181,25 @@ void directModeInput(IO_REG_TYPE pin)
|
||||
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
|
||||
|
||||
#endif
|
||||
// Input
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.enable_w1ts.val = ((uint32_t)1 << (pin));
|
||||
#else
|
||||
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
|
||||
#if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4
|
||||
uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
|
||||
|
||||
if ( rtc_reg ) // RTC pins PULL settings
|
||||
@ -218,21 +207,14 @@ void directModeOutput(IO_REG_TYPE pin)
|
||||
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
|
||||
|
||||
#endif
|
||||
// Output
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(pin)
|
||||
@ -445,6 +427,20 @@ void directWriteHigh(IO_REG_TYPE mask)
|
||||
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||
|
||||
#elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040)
|
||||
#define delayMicroseconds(time) busy_wait_us(time)
|
||||
#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. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite."
|
||||
|
||||
#else
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
@ -460,4 +456,4 @@ void directWriteHigh(IO_REG_TYPE mask)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
@ -282,7 +282,8 @@ int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8
|
||||
useWire->beginTransmission(devAddr);
|
||||
useWire->write(regAddr);
|
||||
useWire->endTransmission();
|
||||
useWire->beginTransmission(devAddr);
|
||||
// See: https://github.com/espressif/arduino-esp32/issues/6674
|
||||
// useWire->beginTransmission(devAddr);
|
||||
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
|
||||
|
||||
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
|
||||
@ -1485,4 +1486,4 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
|
||||
return rxBufferLength - rxBufferIndex;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
@ -308,4 +308,4 @@ class I2Cdev {
|
||||
|
||||
#endif // I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE
|
||||
|
||||
#endif /* _I2CDEV_H_ */
|
||||
#endif /* _I2CDEV_H_ */
|
@ -2556,8 +2556,8 @@ void MPU6050_Base::setClockSource(uint8_t source) {
|
||||
* -------------+------------------
|
||||
* 0 | 1.25 Hz
|
||||
* 1 | 2.5 Hz
|
||||
* 2 | 5 Hz
|
||||
* 3 | 10 Hz
|
||||
* 2 | 20 Hz
|
||||
* 3 | 40 Hz
|
||||
* </pre>
|
||||
*
|
||||
* For further information regarding the MPU-60X0's power modes, please refer to
|
||||
@ -3371,25 +3371,27 @@ void MPU6050_Base::PID(uint8_t ReadAddress, float kP,float kI, uint8_t Loops){
|
||||
resetDMP();
|
||||
}
|
||||
|
||||
void MPU6050_Base::PrintActiveOffsets() {
|
||||
uint8_t AOffsetRegister = (getDeviceID() < 0x38 )? MPU6050_RA_XA_OFFS_H:0x77;
|
||||
int16_t Data[3];
|
||||
//Serial.print(F("Offset Register 0x"));
|
||||
//Serial.print(AOffsetRegister>>4,HEX);Serial.print(AOffsetRegister&0x0F,HEX);
|
||||
Serial.print(F("\n// X Accel Y Accel Z Accel X Gyro Y Gyro Z Gyro\n// OFFSETS "));
|
||||
if(AOffsetRegister == 0x06) I2Cdev::readWords(devAddr, AOffsetRegister, 3, (uint16_t *)Data, I2Cdev::readTimeout, wireObj);
|
||||
else {
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister, 1, (uint16_t *)Data, I2Cdev::readTimeout, wireObj);
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister+3, 1, (uint16_t *)Data+1, I2Cdev::readTimeout, wireObj);
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister+6, 1, (uint16_t *)Data+2, I2Cdev::readTimeout, wireObj);
|
||||
}
|
||||
// A_OFFSET_H_READ_A_OFFS(Data);
|
||||
Serial.print((float)Data[0], 5); Serial.print(", ");
|
||||
Serial.print((float)Data[1], 5); Serial.print(", ");
|
||||
Serial.print((float)Data[2], 5); Serial.print(", ");
|
||||
I2Cdev::readWords(devAddr, 0x13, 3, (uint16_t *)Data, I2Cdev::readTimeout, wireObj);
|
||||
// XG_OFFSET_H_READ_OFFS_USR(Data);
|
||||
Serial.print((float)Data[0], 5); Serial.print(", ");
|
||||
Serial.print((float)Data[1], 5); Serial.print(", ");
|
||||
Serial.print((float)Data[2], 5); Serial.print("\n");
|
||||
int16_t * MPU6050_Base::GetActiveOffsets() {
|
||||
uint8_t AOffsetRegister = (getDeviceID() < 0x38 )? MPU6050_RA_XA_OFFS_H:0x77;
|
||||
if(AOffsetRegister == 0x06) I2Cdev::readWords(devAddr, AOffsetRegister, 3, (uint16_t *)offsets, I2Cdev::readTimeout, wireObj);
|
||||
else {
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister, 1, (uint16_t *)offsets, I2Cdev::readTimeout, wireObj);
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister+3, 1, (uint16_t *)(offsets+1), I2Cdev::readTimeout, wireObj);
|
||||
I2Cdev::readWords(devAddr, AOffsetRegister+6, 1, (uint16_t *)(offsets+2), I2Cdev::readTimeout, wireObj);
|
||||
}
|
||||
I2Cdev::readWords(devAddr, 0x13, 3, (uint16_t *)(offsets+3), I2Cdev::readTimeout, wireObj);
|
||||
return offsets;
|
||||
}
|
||||
|
||||
void MPU6050_Base::PrintActiveOffsets() {
|
||||
GetActiveOffsets();
|
||||
// A_OFFSET_H_READ_A_OFFS(Data);
|
||||
Serial.print((float)offsets[0], 5); Serial.print(",\t");
|
||||
Serial.print((float)offsets[1], 5); Serial.print(",\t");
|
||||
Serial.print((float)offsets[2], 5); Serial.print(",\t");
|
||||
|
||||
// XG_OFFSET_H_READ_OFFS_USR(Data);
|
||||
Serial.print((float)offsets[3], 5); Serial.print(",\t");
|
||||
Serial.print((float)offsets[4], 5); Serial.print(",\t");
|
||||
Serial.print((float)offsets[5], 5); Serial.print("\n\n");
|
||||
}
|
@ -832,12 +832,16 @@ class MPU6050_Base {
|
||||
void CalibrateAccel(uint8_t Loops = 15);// Fine tune after setting offsets with less Loops.
|
||||
void PID(uint8_t ReadAddress, float kP,float kI, uint8_t Loops); // Does the math
|
||||
void PrintActiveOffsets(); // See the results of the Calibration
|
||||
int16_t * GetActiveOffsets();
|
||||
|
||||
protected:
|
||||
uint8_t devAddr;
|
||||
void *wireObj;
|
||||
uint8_t buffer[14];
|
||||
uint32_t fifoTimeout = MPU6050_FIFO_DEFAULT_TIMEOUT;
|
||||
|
||||
private:
|
||||
int16_t offsets[6];
|
||||
};
|
||||
|
||||
#ifndef I2CDEVLIB_MPU6050_TYPEDEF
|
||||
@ -845,4 +849,4 @@ class MPU6050_Base {
|
||||
typedef MPU6050_Base MPU6050;
|
||||
#endif
|
||||
|
||||
#endif /* _MPU6050_H_ */
|
||||
#endif /* _MPU6050_H_ */
|
@ -15,11 +15,12 @@ include_dir = lib
|
||||
[common_env_data]
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
platform = espressif8266 @ 3.2.0
|
||||
platform = espressif8266 @ 4.0.1
|
||||
framework = arduino
|
||||
board = d1_mini
|
||||
build_unflags =
|
||||
build_flags =
|
||||
-Wl,-Map,output.map
|
||||
-D BAUD=${common_env_data.monitor_speed}
|
||||
-D ACTIVATE_OTA
|
||||
#-D DEBUG_ESP_HTTP_CLIENT
|
||||
@ -33,15 +34,16 @@ build_flags =
|
||||
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
|
||||
-D USER_SSID=\""\"" # =\""myssid\""
|
||||
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
|
||||
-D CFG_APPVER="\"0.9.0\""
|
||||
-D CFG_APPVER="\"1.1.1\""
|
||||
#-D CFG_GITREV=\""beta-4\""
|
||||
!python script/git_rev.py
|
||||
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/i2cdevlib.git#<document>
|
||||
#https://github.com/mp-se/OneWire # Using this version; https://github.com/arendst/Tasmota/tree/development/lib/lib_basic/OneWire-Stickbreaker
|
||||
#https://github.com/mp-se/Arduino-Temperature-Control-Library
|
||||
#https://github.com/khoih-prog/ESP_WiFiManager
|
||||
#https://github.com/khoih-prog/ESP_DoubleResetDetector
|
||||
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
|
||||
@ -61,11 +63,11 @@ extra_scripts =
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
-Wl,-Map,output.map
|
||||
${common_env_data.build_flags}
|
||||
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
|
||||
-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
|
||||
#-D SKIP_SLEEPMODE
|
||||
#-D DOUBLERESETDETECTOR_DEBUG=true
|
||||
#-D FORCE_GRAVITY_MODE # used to debug gravity mode
|
||||
-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.
|
||||
@ -126,7 +128,7 @@ board_build.filesystem = littlefs
|
||||
|
||||
[env:gravity32-release]
|
||||
framework = arduino
|
||||
platform = espressif32
|
||||
platform = espressif32 @ 5.0.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
@ -151,8 +153,8 @@ build_flags =
|
||||
-D MAIN_DISABLE_LOGGING
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
https://github.com/lorol/LITTLEFS#1.0.6
|
||||
https://github.com/h2zero/NimBLE-Arduino#1.3.7
|
||||
https://github.com/mp-se/NimBLE-Arduino#1.3.8 # https://github.com/h2zero/NimBLE-Arduino
|
||||
lib_ignore =
|
||||
board = featheresp32
|
||||
build_type = release
|
||||
board_build.partitions = part32.csv
|
||||
@ -161,7 +163,7 @@ monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:gravity32-perf]
|
||||
framework = arduino
|
||||
platform = espressif32
|
||||
platform = espressif32 @ 5.0.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
@ -177,34 +179,9 @@ build_flags =
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
https://github.com/lorol/LITTLEFS#1.0.6
|
||||
https://github.com/h2zero/NimBLE-Arduino#1.3.7
|
||||
https://github.com/mp-se/NimBLE-Arduino#1.3.8 # https://github.com/h2zero/NimBLE-Arduino
|
||||
board = featheresp32
|
||||
build_type = release
|
||||
board_build.partitions = part32.csv
|
||||
board_build.filesystem = littlefs
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:gravity32-release2]
|
||||
framework = arduino
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip # build fails
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D ESPRESSIF32_20 # v2.0 framework
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
https://github.com/h2zero/NimBLE-Arduino#1.3.7
|
||||
board = featheresp32
|
||||
build_type = release
|
||||
board_build.partitions = part32.csv
|
||||
board_build.filesystem = littlefs
|
@ -11,21 +11,46 @@ def after_build(source, target, env):
|
||||
print( "Executing custom step " )
|
||||
dir = env.GetLaunchDir()
|
||||
name = env.get( "PIOENV" )
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
if name == "gravity-debug" :
|
||||
target = dir + "/bin/firmware-debug.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
if name == "gravity-release" :
|
||||
target = dir + "/bin/firmware.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
if name == "gravity-perf" :
|
||||
target = dir + "/bin/firmware-perf.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
if name == "gravity32-release" :
|
||||
target = dir + "/bin/firmware32.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
target = dir + "/bin/partitions32.bin"
|
||||
source = dir + "/.pio/build/" + name + "/partitions.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
if name == "gravity32-perf" :
|
||||
target = dir + "/bin/firmware32-perf.bin"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
if name == "gravity32-release2" :
|
||||
target = dir + "/bin/firmware32_2.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
|
||||
print( "Adding custom build step (copy firmware): ")
|
||||
|
75
src/calc.cpp
@ -33,14 +33,17 @@ SOFTWARE.
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order) {
|
||||
int noAngles = 0;
|
||||
RawFormulaData fd2;
|
||||
|
||||
// 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;
|
||||
// Check how many valid values we have got and make sure we have a full
|
||||
// series.
|
||||
for (int i = 0; i < FORMULA_DATA_SIZE; i++) {
|
||||
if (fd.a[i]) {
|
||||
fd2.a[noAngles] = fd.a[i];
|
||||
fd2.g[noAngles] = fd.g[i];
|
||||
noAngles++;
|
||||
}
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
@ -48,19 +51,19 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
order, noAngles);
|
||||
#endif
|
||||
|
||||
if (!noAngles) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
|
||||
if (noAngles < 3) {
|
||||
writeErrorLog("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,
|
||||
int ret = fitCurve(order, noAngles, fd2.a, fd2.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));
|
||||
Log.verbose(F("CALC: Finshied processing data points, order = %d." CR),
|
||||
order);
|
||||
#endif
|
||||
|
||||
// Print the formula based on 'order'
|
||||
@ -93,21 +96,16 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
|
||||
|
||||
// If the deviation is more than 2 degress we mark it as failed.
|
||||
if (dev * 1000 > myHardwareConfig.getMaxFormulaCreationDeviation()) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
char s[20];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", dev);
|
||||
Log.verbose(F("CALC: Deviation is: %s" CR), &s[0]);
|
||||
#endif
|
||||
if (dev * 1000 > myAdvancedConfig.getMaxFormulaCreationDeviation()) {
|
||||
writeErrorLog(
|
||||
"CALC: Validation failed on angle %.2f, deviation too large %.4f "
|
||||
"SG, formula order %d",
|
||||
fd.a[i], dev * 1000, order);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("CALC: Error validating created formula. Deviation to large, "
|
||||
"formula rejected."));
|
||||
return ERR_FORMULA_UNABLETOFFIND;
|
||||
}
|
||||
|
||||
@ -116,8 +114,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Internal error finding formula."));
|
||||
writeErrorLog("CALC: Internal error finding formula.");
|
||||
return ERR_FORMULA_INTERNAL;
|
||||
}
|
||||
|
||||
@ -162,8 +159,7 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
return g;
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
|
||||
writeErrorLog("CALC: Failed to parse gravity expression %d", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -173,16 +169,15 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
//
|
||||
// Source: https://homebrewacademy.com/hydrometer-temperature-correction/
|
||||
//
|
||||
double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
double gravityTemperatureCorrectionC(double gravitySG, 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);
|
||||
gravitySG, tempC, calTempC);
|
||||
#endif
|
||||
// float tempF = convertCtoF(tempC);
|
||||
// float calTempF = convertCtoF(calTempC);
|
||||
|
||||
double tempF = convertCtoF(tempC);
|
||||
double calTempF = convertCtoF(calTempC);
|
||||
const char *formula =
|
||||
"gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0."
|
||||
"00000000232820948*temp^3)/"
|
||||
@ -191,7 +186,7 @@ double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {
|
||||
{"gravity", &gravity}, {"temp", &tempC}, {"cal", &calTempC}};
|
||||
{"gravity", &gravitySG}, {"temp", &tempF}, {"cal", &calTempF}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
@ -202,18 +197,18 @@ double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
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]);
|
||||
char s[80];
|
||||
snprintf(&s[0], sizeof(s), "Corrected gravity=%.8f, input gravity=%.8f", g,
|
||||
gravitySG);
|
||||
Log.verbose(F("CALC: %s." CR), &s[0]);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
"CALC: Failed to parse expression for gravity temperature correction " +
|
||||
String(err));
|
||||
return gravity;
|
||||
writeErrorLog(
|
||||
"CALC: Failed to parse expression for gravity temperature correction %d",
|
||||
err);
|
||||
return gravitySG;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -32,9 +32,8 @@ SOFTWARE.
|
||||
|
||||
double calculateGravity(double angle, double tempC,
|
||||
const char *tempFormula = 0);
|
||||
double gravityTemperatureCorrectionC(
|
||||
double gravity, double tempC,
|
||||
double calTempC = myHardwareConfig.getDefaultCalibrationTemp());
|
||||
double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
double calTempC);
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order);
|
||||
|
||||
|
126
src/config.cpp
@ -26,7 +26,7 @@ SOFTWARE.
|
||||
#include <wifi.hpp>
|
||||
|
||||
Config myConfig;
|
||||
HardwareConfig myHardwareConfig;
|
||||
AdvancedConfig myAdvancedConfig;
|
||||
|
||||
//
|
||||
// Create the config class with default settings.
|
||||
@ -62,11 +62,12 @@ void Config::createJson(DynamicJsonDocument& doc) {
|
||||
// doc[PARAM_CONFIG_VER] = getConfigVersion();
|
||||
doc[PARAM_ID] = getID();
|
||||
doc[PARAM_OTA] = getOtaURL();
|
||||
doc[PARAM_SSID] = getWifiSSID();
|
||||
doc[PARAM_PASS] = getWifiPass();
|
||||
doc[PARAM_SSID] = getWifiSSID(0);
|
||||
doc[PARAM_PASS] = getWifiPass(0);
|
||||
doc[PARAM_SSID2] = getWifiSSID(1);
|
||||
doc[PARAM_PASS2] = getWifiPass(1);
|
||||
doc[PARAM_BLE] = getColorBLE();
|
||||
doc[PARAM_TEMPFORMAT] = String(getTempFormat());
|
||||
doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
|
||||
doc[PARAM_TOKEN] = getToken();
|
||||
doc[PARAM_TOKEN2] = getToken2();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpUrl();
|
||||
@ -85,12 +86,14 @@ void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[PARAM_PUSH_MQTT_USER] = getMqttUser();
|
||||
doc[PARAM_PUSH_MQTT_PASS] = getMqttPass();
|
||||
doc[PARAM_SLEEP_INTERVAL] = getSleepInterval();
|
||||
doc[PARAM_VOLTAGEFACTOR] = getVoltageFactor();
|
||||
doc[PARAM_VOLTAGE_FACTOR] = getVoltageFactor();
|
||||
doc[PARAM_VOLTAGE_CONFIG] = getVoltageConfig();
|
||||
doc[PARAM_GRAVITY_FORMULA] = getGravityFormula();
|
||||
doc[PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
|
||||
doc[PARAM_TEMP_ADJ] = getTempSensorAdjC();
|
||||
doc[PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
|
||||
doc[PARAM_GYRO_TEMP] = isGyroTemp();
|
||||
doc[PARAM_STORAGE_SLEEP] = isStorageSleep();
|
||||
|
||||
JsonObject cal = doc.createNestedObject(PARAM_GYRO_CALIBRATION);
|
||||
cal["ax"] = _gyroCalibration.ax;
|
||||
@ -106,12 +109,22 @@ void Config::createJson(DynamicJsonDocument& doc) {
|
||||
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
|
||||
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
|
||||
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
|
||||
cal2["a6"] = reduceFloatPrecision(_formulaData.a[5], 2);
|
||||
cal2["a7"] = reduceFloatPrecision(_formulaData.a[6], 2);
|
||||
cal2["a8"] = reduceFloatPrecision(_formulaData.a[7], 2);
|
||||
cal2["a9"] = reduceFloatPrecision(_formulaData.a[8], 2);
|
||||
cal2["a10"] = reduceFloatPrecision(_formulaData.a[9], 2);
|
||||
|
||||
cal2["g1"] = reduceFloatPrecision(_formulaData.g[0], 4);
|
||||
cal2["g2"] = reduceFloatPrecision(_formulaData.g[1], 4);
|
||||
cal2["g3"] = reduceFloatPrecision(_formulaData.g[2], 4);
|
||||
cal2["g4"] = reduceFloatPrecision(_formulaData.g[3], 4);
|
||||
cal2["g5"] = reduceFloatPrecision(_formulaData.g[4], 4);
|
||||
cal2["g6"] = reduceFloatPrecision(_formulaData.g[5], 4);
|
||||
cal2["g7"] = reduceFloatPrecision(_formulaData.g[6], 4);
|
||||
cal2["g8"] = reduceFloatPrecision(_formulaData.g[7], 4);
|
||||
cal2["g9"] = reduceFloatPrecision(_formulaData.g[8], 4);
|
||||
cal2["g10"] = reduceFloatPrecision(_formulaData.g[9], 4);
|
||||
}
|
||||
|
||||
//
|
||||
@ -132,12 +145,11 @@ bool Config::saveFile() {
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to save configuration."));
|
||||
writeErrorLog("CFG : Failed to save configuration.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
DynamicJsonDocument doc(3000);
|
||||
createJson(doc);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
@ -163,23 +175,21 @@ bool Config::loadFile() {
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_FILENAME)) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Configuration file does not exist."));
|
||||
writeErrorLog("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."));
|
||||
writeErrorLog("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);
|
||||
DynamicJsonDocument doc(3000);
|
||||
DeserializationError err = deserializeJson(doc, configFile);
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
@ -188,8 +198,7 @@ bool Config::loadFile() {
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
|
||||
writeErrorLog("CFG : Failed to parse configuration (json)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -198,8 +207,10 @@ bool Config::loadFile() {
|
||||
#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_SSID].isNull()) setWifiSSID(doc[PARAM_SSID], 0);
|
||||
if (!doc[PARAM_PASS].isNull()) setWifiPass(doc[PARAM_PASS], 0);
|
||||
if (!doc[PARAM_SSID2].isNull()) setWifiSSID(doc[PARAM_SSID2], 1);
|
||||
if (!doc[PARAM_PASS2].isNull()) setWifiPass(doc[PARAM_PASS2], 1);
|
||||
if (!doc[PARAM_BLE].isNull()) setColorBLE(doc[PARAM_BLE]);
|
||||
|
||||
if (!doc[PARAM_TEMPFORMAT].isNull()) {
|
||||
@ -207,9 +218,6 @@ bool Config::loadFile() {
|
||||
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_TOKEN2].isNull()) setToken2(doc[PARAM_TOKEN2]);
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]);
|
||||
@ -243,14 +251,18 @@ bool Config::loadFile() {
|
||||
|
||||
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_VOLTAGE_FACTOR].isNull())
|
||||
setVoltageFactor(doc[PARAM_VOLTAGE_FACTOR].as<float>());
|
||||
if (!doc[PARAM_VOLTAGE_CONFIG].isNull())
|
||||
setVoltageConfig(doc[PARAM_VOLTAGE_CONFIG].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_STORAGE_SLEEP].isNull())
|
||||
setStorageSleep(doc[PARAM_STORAGE_SLEEP].as<bool>());
|
||||
if (!doc[PARAM_GRAVITY_FORMAT].isNull()) {
|
||||
String s = doc[PARAM_GRAVITY_FORMAT];
|
||||
setGravityFormat(s.charAt(0));
|
||||
@ -281,6 +293,16 @@ bool Config::loadFile() {
|
||||
_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]["a6"].isNull())
|
||||
_formulaData.a[5] = doc[PARAM_FORMULA_DATA]["a6"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a7"].isNull())
|
||||
_formulaData.a[6] = doc[PARAM_FORMULA_DATA]["a7"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a8"].isNull())
|
||||
_formulaData.a[7] = doc[PARAM_FORMULA_DATA]["a8"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a9"].isNull())
|
||||
_formulaData.a[8] = doc[PARAM_FORMULA_DATA]["a9"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a10"].isNull())
|
||||
_formulaData.a[9] = doc[PARAM_FORMULA_DATA]["a10"].as<double>();
|
||||
|
||||
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
|
||||
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
|
||||
@ -292,6 +314,16 @@ bool Config::loadFile() {
|
||||
_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_FORMULA_DATA]["g6"].isNull())
|
||||
_formulaData.g[5] = doc[PARAM_FORMULA_DATA]["g6"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g7"].isNull())
|
||||
_formulaData.g[6] = doc[PARAM_FORMULA_DATA]["g7"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g8"].isNull())
|
||||
_formulaData.g[7] = doc[PARAM_FORMULA_DATA]["g8"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g9"].isNull())
|
||||
_formulaData.g[8] = doc[PARAM_FORMULA_DATA]["g9"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g10"].isNull())
|
||||
_formulaData.g[9] = doc[PARAM_FORMULA_DATA]["g10"].as<double>();
|
||||
|
||||
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
|
||||
// If this parameter is missing we need to reset the gyrocalibaration due to
|
||||
@ -333,7 +365,7 @@ void Config::checkFileSystem() {
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool HardwareConfig::saveFile() {
|
||||
bool AdvancedConfig::saveFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving hardware configuration to file." CR));
|
||||
#endif
|
||||
@ -341,19 +373,26 @@ bool HardwareConfig::saveFile() {
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
|
||||
writeErrorLog("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_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_WIFI_PORTAL_TIMEOUT] = this->getWifiPortalTimeout();
|
||||
doc[PARAM_HW_WIFI_CONNECT_TIMEOUT] = this->getWifiConnectTimeout();
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] = this->getDefaultCalibrationTemp();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP1] = this->getPushIntervalHttp1();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP2] = this->getPushIntervalHttp2();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP3] = this->getPushIntervalHttp3();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_INFLUX] = this->getPushIntervalInflux();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_MQTT] = this->getPushIntervalMqtt();
|
||||
doc[PARAM_HW_TEMPSENSOR_RESOLUTION] = this->getTempSensorResolution();
|
||||
doc[PARAM_HW_IGNORE_LOW_ANGLES] = this->isIgnoreLowAnges();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -371,7 +410,7 @@ bool HardwareConfig::saveFile() {
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool HardwareConfig::loadFile() {
|
||||
bool AdvancedConfig::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading hardware configuration from file." CR));
|
||||
#endif
|
||||
@ -385,8 +424,7 @@ bool HardwareConfig::loadFile() {
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
|
||||
writeErrorLog("CFG : Failed to read hardware configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -402,8 +440,7 @@ bool HardwareConfig::loadFile() {
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
|
||||
writeErrorLog("CFG : Failed to parse hardware configuration (json)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -413,8 +450,8 @@ bool HardwareConfig::loadFile() {
|
||||
|
||||
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_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>());
|
||||
@ -424,10 +461,27 @@ bool HardwareConfig::loadFile() {
|
||||
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_WIFI_PORTAL_TIMEOUT].isNull())
|
||||
this->setWifiPortalTimeout(doc[PARAM_HW_WIFI_PORTAL_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_WIFI_CONNECT_TIMEOUT].isNull())
|
||||
this->setWifiConnectTimeout(doc[PARAM_HW_WIFI_CONNECT_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_TIMEOUT].isNull())
|
||||
this->setPushTimeout(doc[PARAM_HW_PUSH_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP1].isNull())
|
||||
this->setPushIntervalHttp1(doc[PARAM_HW_PUSH_INTERVAL_HTTP1].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP2].isNull())
|
||||
this->setPushIntervalHttp2(doc[PARAM_HW_PUSH_INTERVAL_HTTP2].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP3].isNull())
|
||||
this->setPushIntervalHttp3(doc[PARAM_HW_PUSH_INTERVAL_HTTP3].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_INFLUX].isNull())
|
||||
this->setPushIntervalInflux(doc[PARAM_HW_PUSH_INTERVAL_INFLUX].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_MQTT].isNull())
|
||||
this->setPushIntervalMqtt(doc[PARAM_HW_PUSH_INTERVAL_MQTT].as<int>());
|
||||
if (!doc[PARAM_HW_TEMPSENSOR_RESOLUTION].isNull())
|
||||
this->setTempSensorResolution(
|
||||
doc[PARAM_HW_TEMPSENSOR_RESOLUTION].as<int>());
|
||||
if (!doc[PARAM_HW_IGNORE_LOW_ANGLES].isNull())
|
||||
setIgnoreLowAnges(doc[PARAM_HW_IGNORE_LOW_ANGLES].as<bool>());
|
||||
|
||||
Log.notice(F("CFG : Configuration file " CFG_HW_FILENAME " loaded." CR));
|
||||
return true;
|
||||
|
126
src/config.hpp
@ -27,8 +27,6 @@ SOFTWARE.
|
||||
#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
|
||||
@ -47,41 +45,90 @@ struct RawGyroData {
|
||||
};
|
||||
|
||||
// Used for holding formulaData (used for calculating formula on device)
|
||||
#define FORMULA_DATA_SIZE 10
|
||||
|
||||
struct RawFormulaData {
|
||||
double a[5];
|
||||
double g[5];
|
||||
double a[FORMULA_DATA_SIZE];
|
||||
double g[FORMULA_DATA_SIZE];
|
||||
};
|
||||
|
||||
class HardwareConfig {
|
||||
class AdvancedConfig {
|
||||
private:
|
||||
int _wifiPortalTimeout = 120;
|
||||
float _maxFormulaCreationDeviation = 1.6;
|
||||
float _defaultCalibrationTemp = 20.0;
|
||||
int _wifiPortalTimeout = 120; // seconds
|
||||
int _wifiConnectTimeout = 20; // seconds
|
||||
float _maxFormulaCreationDeviation = 3; // SG
|
||||
float _defaultCalibrationTemp = 20.0; // C
|
||||
int _gyroSensorMovingThreashold = 500;
|
||||
int _tempSensorResolution = 9; // bits
|
||||
int _gyroReadCount = 50;
|
||||
int _gyroReadDelay = 3150; // us, empirical, to hold sampling to 200 Hz
|
||||
int _pushTimeout = 10; // seconds
|
||||
int _pushIntervalHttp1 = 0;
|
||||
int _pushIntervalHttp2 = 0;
|
||||
int _pushIntervalHttp3 = 0;
|
||||
int _pushIntervalInflux = 0;
|
||||
int _pushIntervalMqtt = 0;
|
||||
bool _IgnoreLowAnges = false;
|
||||
|
||||
public:
|
||||
int getWifiPortalTimeout() { return _wifiPortalTimeout; }
|
||||
void setWifiPortalTimeout(int t) { _wifiPortalTimeout = t; }
|
||||
|
||||
int getWifiConnectTimeout() { return _wifiConnectTimeout; }
|
||||
void setWifiConnectTimeout(int t) { _wifiConnectTimeout = t; }
|
||||
|
||||
float getMaxFormulaCreationDeviation() {
|
||||
return _maxFormulaCreationDeviation;
|
||||
}
|
||||
void setMaxFormulaCreationDeviation(float f) {
|
||||
_maxFormulaCreationDeviation = f;
|
||||
}
|
||||
|
||||
int getTempSensorResolution() { return _tempSensorResolution; }
|
||||
void setTempSensorResolution(int t) {
|
||||
if (t >= 9 && t <= 12) _tempSensorResolution = t;
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
int getPushIntervalHttp1() { return _pushIntervalHttp1; }
|
||||
void setPushIntervalHttp1(int t) { _pushIntervalHttp1 = t; }
|
||||
|
||||
int getPushIntervalHttp2() { return _pushIntervalHttp2; }
|
||||
void setPushIntervalHttp2(int t) { _pushIntervalHttp2 = t; }
|
||||
|
||||
int getPushIntervalHttp3() { return _pushIntervalHttp3; }
|
||||
void setPushIntervalHttp3(int t) { _pushIntervalHttp3 = t; }
|
||||
|
||||
int getPushIntervalInflux() { return _pushIntervalInflux; }
|
||||
void setPushIntervalInflux(int t) { _pushIntervalInflux = t; }
|
||||
|
||||
int getPushIntervalMqtt() { return _pushIntervalMqtt; }
|
||||
void setPushIntervalMqtt(int t) { _pushIntervalMqtt = t; }
|
||||
|
||||
bool isPushIntervalActive() {
|
||||
return (_pushIntervalHttp1 + _pushIntervalHttp2 + _pushIntervalHttp3 +
|
||||
_pushIntervalInflux + _pushIntervalMqtt) == 0
|
||||
? false
|
||||
: true;
|
||||
}
|
||||
|
||||
const bool isIgnoreLowAnges() { return _IgnoreLowAnges; }
|
||||
void setIgnoreLowAnges(bool b) { _IgnoreLowAnges = b; }
|
||||
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
};
|
||||
@ -97,17 +144,17 @@ class Config {
|
||||
String _otaURL = "";
|
||||
char _tempFormat = 'C';
|
||||
float _voltageFactor = 1.59;
|
||||
float _voltageConfig = 4.15;
|
||||
float _tempSensorAdjC = 0;
|
||||
int _sleepInterval = 900;
|
||||
bool _gyroTemp = false;
|
||||
bool _storageSleep = false;
|
||||
|
||||
// Wifi Config
|
||||
String _wifiSSID = "";
|
||||
String _wifiPASS = "";
|
||||
String _wifiSSID[2] = {"", ""};
|
||||
String _wifiPASS[2] = {"", ""};
|
||||
|
||||
// Push target settings
|
||||
String _brewfatherPushUrl = "";
|
||||
|
||||
String _token = "";
|
||||
String _token2 = "";
|
||||
|
||||
@ -137,7 +184,8 @@ class Config {
|
||||
|
||||
// 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}};
|
||||
RawFormulaData _formulaData = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
||||
|
||||
void formatFileSystem();
|
||||
|
||||
@ -159,6 +207,12 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const bool isStorageSleep() { return _storageSleep; }
|
||||
void setStorageSleep(bool b) {
|
||||
_storageSleep = b;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getOtaURL() { return _otaURL.c_str(); }
|
||||
void setOtaURL(String s) {
|
||||
_otaURL = s;
|
||||
@ -167,26 +221,31 @@ class Config {
|
||||
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;
|
||||
const char* getWifiSSID(int idx) { return _wifiSSID[idx].c_str(); }
|
||||
void setWifiSSID(String s, int idx) {
|
||||
_wifiSSID[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getWifiPass() { return _wifiPASS.c_str(); }
|
||||
void setWifiPass(String s) {
|
||||
_wifiPASS = s;
|
||||
const char* getWifiPass(int idx) { return _wifiPASS[idx].c_str(); }
|
||||
void setWifiPass(String s, int idx) {
|
||||
_wifiPASS[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool dualWifiConfigured() {
|
||||
return _wifiSSID[0].length() > 0 && _wifiSSID[1].length() > 0 ? true
|
||||
: false;
|
||||
}
|
||||
void swapPrimaryWifi() {
|
||||
String s = _wifiSSID[0];
|
||||
_wifiSSID[0] = _wifiSSID[1];
|
||||
_wifiSSID[1] = s;
|
||||
|
||||
String p = _wifiPASS[0];
|
||||
_wifiPASS[0] = _wifiPASS[1];
|
||||
_wifiPASS[1] = p;
|
||||
|
||||
// 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(); }
|
||||
@ -242,6 +301,7 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
|
||||
bool isInfluxSSL() { return _influxDb2Url.startsWith("https://"); }
|
||||
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
|
||||
void setInfluxDb2PushOrg(String s) {
|
||||
_influxDb2Org = s;
|
||||
@ -317,6 +377,16 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
float getVoltageConfig() { return _voltageConfig; }
|
||||
void setVoltageConfig(float f) {
|
||||
_voltageConfig = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setVoltageConfig(String s) {
|
||||
_voltageConfig = s.toFloat();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
float getTempSensorAdjC() { return _tempSensorAdjC; }
|
||||
void setTempSensorAdjC(float f) {
|
||||
_tempSensorAdjC = f;
|
||||
@ -361,7 +431,7 @@ class Config {
|
||||
bool isBLEActive() { return _colorBLE.length() ? true : false; }
|
||||
bool isWifiPushActive() {
|
||||
return (isHttpActive() || isHttp2Active() || isHttp3Active() ||
|
||||
isBrewfatherActive() || isInfluxDb2Active() || isMqttActive())
|
||||
isInfluxDb2Active() || isMqttActive())
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
@ -388,7 +458,7 @@ class Config {
|
||||
};
|
||||
|
||||
extern Config myConfig;
|
||||
extern HardwareConfig myHardwareConfig;
|
||||
extern AdvancedConfig myAdvancedConfig;
|
||||
|
||||
#endif // SRC_CONFIG_HPP_
|
||||
|
||||
|
18
src/gyro.cpp
@ -43,9 +43,10 @@ bool GyroSensor::setup() {
|
||||
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?"));
|
||||
uint8_t id = accelgyro.getDeviceID();
|
||||
|
||||
if (id != 0x34 && id != 0x38) { // Allow both MPU6050 and MPU6000
|
||||
writeErrorLog("GYRO: Failed to connect to gyro, is it connected?");
|
||||
_sensorConnected = false;
|
||||
} else {
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
@ -217,7 +218,7 @@ bool GyroSensor::isSensorMoving(RawGyroData &raw) {
|
||||
#endif
|
||||
|
||||
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
|
||||
int threashold = myHardwareConfig.getGyroSensorMovingThreashold();
|
||||
int threashold = myAdvancedConfig.getGyroSensorMovingThreashold();
|
||||
|
||||
if (x > threashold || y > threashold || z > threashold) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
|
||||
@ -239,8 +240,8 @@ bool GyroSensor::read() {
|
||||
if (!_sensorConnected) return false;
|
||||
|
||||
readSensor(
|
||||
_lastGyroData, myHardwareConfig.getGyroReadCount(),
|
||||
myHardwareConfig.getGyroReadDelay()); // Last param is unused if
|
||||
_lastGyroData, myAdvancedConfig.getGyroReadCount(),
|
||||
myAdvancedConfig.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
|
||||
@ -292,9 +293,8 @@ void GyroSensor::applyCalibration() {
|
||||
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."));
|
||||
writeErrorLog(
|
||||
"GYRO: No valid calibration values, please calibrate the device.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
176
src/helper.cpp
@ -47,75 +47,68 @@ void tcp_cleanup() {
|
||||
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
//
|
||||
// Convert sg to plato
|
||||
//
|
||||
void checkResetReason() {
|
||||
#if defined(ESP8266)
|
||||
rst_info* _rinfo;
|
||||
_rinfo = ESP.getResetInfoPtr();
|
||||
|
||||
Log.notice(F("HELP: Last reset cause %d" CR), _rinfo->exccause);
|
||||
|
||||
if (_rinfo->exccause > 0) {
|
||||
char s[120];
|
||||
snprintf(&s[0], sizeof(s),
|
||||
"HELP: Exception (%d) reason=%d epc1=0x%08x epc2=0x%08x "
|
||||
"epc3=0x%08x execvaddr=0x%08x depc=0x%08x",
|
||||
_rinfo->exccause, _rinfo->reason, _rinfo->epc1, _rinfo->epc2,
|
||||
_rinfo->epc3, _rinfo->excvaddr, _rinfo->depc);
|
||||
writeErrorLog(&s[0]);
|
||||
}
|
||||
#else // defined (ESP32)
|
||||
#warning "Todo: Handle reset reasons on ESP32"
|
||||
#endif
|
||||
}
|
||||
|
||||
void writeErrorLog(const char* format, ...) {
|
||||
File f = LittleFS.open(ERR_FILENAME, "a");
|
||||
|
||||
if (f && f.size() > ERR_FILEMAXSIZE) {
|
||||
f.close();
|
||||
LittleFS.remove(ERR_FILENAME2);
|
||||
LittleFS.rename(ERR_FILENAME, ERR_FILENAME2);
|
||||
f = LittleFS.open(ERR_FILENAME, "a");
|
||||
}
|
||||
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
char buf[120];
|
||||
vsnprintf(&buf[0], sizeof(buf), format, arg);
|
||||
va_end(arg);
|
||||
Log.errorln(&buf[0]);
|
||||
|
||||
if (f) {
|
||||
#if defined(ESP8266)
|
||||
f.write(&buf[0], strlen(&buf[0]));
|
||||
#else // ESP32
|
||||
f.write((unsigned char*)&buf[0], strlen(&buf[0]));
|
||||
#endif
|
||||
f.println();
|
||||
f.close();
|
||||
} else {
|
||||
Log.warning(F("HELP: Failed to open error log." CR));
|
||||
}
|
||||
}
|
||||
|
||||
double convertToPlato(double sg) {
|
||||
if (sg) return 259 - (259 / sg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Convert plato to sg
|
||||
//
|
||||
double convertToSG(double plato) { return 259 / (259 - plato); }
|
||||
|
||||
//
|
||||
// Conversion to F
|
||||
//
|
||||
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
|
||||
|
||||
//
|
||||
// Conversion to C
|
||||
//
|
||||
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
|
||||
|
||||
//
|
||||
// Load error log from disk
|
||||
//
|
||||
ErrorFileLog::ErrorFileLog() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "r");
|
||||
int i = 0;
|
||||
|
||||
if (errFile) {
|
||||
do {
|
||||
_errors[i] = errFile.readStringUntil('\n');
|
||||
_errors[i].replace("\r", "");
|
||||
_errors[i].replace("\n", "");
|
||||
} while (_errors[i++].length());
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add new entry to top of error log
|
||||
//
|
||||
void ErrorFileLog::addEntry(String err) {
|
||||
for (int i = (ERR_COUNT - 1); i > 0; i--) {
|
||||
_errors[i] = _errors[i - 1];
|
||||
}
|
||||
_errors[0] = err;
|
||||
err += String(CR);
|
||||
Log.error(err.c_str());
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save error log
|
||||
//
|
||||
void ErrorFileLog::save() {
|
||||
File errFile = LittleFS.open(ERR_FILENAME, "w");
|
||||
if (errFile) {
|
||||
for (int i = 0; i < ERR_COUNT; i++) {
|
||||
errFile.println(_errors[i]);
|
||||
}
|
||||
errFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Load history log of floats
|
||||
//
|
||||
FloatHistoryLog::FloatHistoryLog(String fName) {
|
||||
_fName = fName;
|
||||
|
||||
@ -133,9 +126,6 @@ FloatHistoryLog::FloatHistoryLog(String fName) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add entry to top of log
|
||||
//
|
||||
void FloatHistoryLog::addEntry(float time) {
|
||||
for (int i = (10 - 1); i > 0; i--) {
|
||||
_runTime[i] = _runTime[i - 1];
|
||||
@ -144,9 +134,6 @@ void FloatHistoryLog::addEntry(float time) {
|
||||
save();
|
||||
}
|
||||
|
||||
//
|
||||
// Save log
|
||||
//
|
||||
void FloatHistoryLog::save() {
|
||||
File runFile = LittleFS.open(_fName, "w");
|
||||
if (runFile) {
|
||||
@ -157,9 +144,6 @@ void FloatHistoryLog::save() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the heap information.
|
||||
//
|
||||
void printHeap(String prefix) {
|
||||
#if defined(ESP8266)
|
||||
Log.notice(
|
||||
@ -175,9 +159,6 @@ void printHeap(String prefix) {
|
||||
#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);
|
||||
@ -186,30 +167,18 @@ void deepSleep(int t) {
|
||||
ESP.deepSleep(wake);
|
||||
}
|
||||
|
||||
//
|
||||
// Print the build options used
|
||||
//
|
||||
void printBuildOptions() {
|
||||
Log.notice(F("Build options: %s (%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, CFG_GITREV, LOG_LEVEL);
|
||||
}
|
||||
|
||||
//
|
||||
// Configure serial debug output
|
||||
//
|
||||
SerialDebug::SerialDebug(const uint32_t serialSpeed) {
|
||||
// Start serial with auto-detected rate (default to defined BAUD)
|
||||
Serial.flush();
|
||||
@ -220,18 +189,12 @@ SerialDebug::SerialDebug(const uint32_t serialSpeed) {
|
||||
getLog()->notice(F("SDBG: Serial logging started at %u." CR), serialSpeed);
|
||||
}
|
||||
|
||||
//
|
||||
// Print the timestamp (ms since start of device)
|
||||
//
|
||||
void printTimestamp(Print* _logOutput, int _logLevel) {
|
||||
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)
|
||||
@ -257,9 +220,6 @@ void BatteryVoltage::read() {
|
||||
|
||||
PerfLogging myPerfLogging;
|
||||
|
||||
//
|
||||
// Clear the current cache
|
||||
//
|
||||
void PerfLogging::clear() {
|
||||
// Clear the measurements
|
||||
if (first == 0) return;
|
||||
@ -276,17 +236,11 @@ void PerfLogging::clear() {
|
||||
} 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);
|
||||
|
||||
@ -299,9 +253,6 @@ void PerfLogging::stop(const char* key) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the collected performance data
|
||||
//
|
||||
void PerfLogging::print() {
|
||||
PerfEntry* pe = first;
|
||||
|
||||
@ -311,12 +262,16 @@ void PerfLogging::print() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Push collected performance data to influx (use influx configuration)
|
||||
//
|
||||
void PerfLogging::pushInflux() {
|
||||
if (!myConfig.isInfluxDb2Active()) return;
|
||||
|
||||
if (myConfig.isInfluxSSL()) {
|
||||
Log.warning(
|
||||
F("PERF: InfluxDB2 with SSL is not supported when pushing performance "
|
||||
"data, skipping" CR));
|
||||
return;
|
||||
}
|
||||
|
||||
WiFiClient wifi;
|
||||
HTTPClient http;
|
||||
String serverPath =
|
||||
@ -387,7 +342,7 @@ void PerfLogging::pushInflux() {
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout());
|
||||
http.setTimeout(myAdvancedConfig.getPushTimeout());
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
@ -408,29 +363,19 @@ void PerfLogging::pushInflux() {
|
||||
|
||||
#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);
|
||||
@ -474,9 +419,6 @@ unsigned char h2int(char c) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
//
|
||||
// urlencode string
|
||||
//
|
||||
String urldecode(String str) {
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length());
|
||||
|
@ -28,13 +28,18 @@ SOFTWARE.
|
||||
#include <main.hpp>
|
||||
|
||||
#define ERR_FILENAME "/error.log"
|
||||
#define ERR_COUNT 15
|
||||
#define ERR_FILENAME2 "/error2.log"
|
||||
#define ERR_FILEMAXSIZE 4000
|
||||
|
||||
#define RUNTIME_FILENAME "/runtime.log"
|
||||
|
||||
// tcp cleanup
|
||||
void tcp_cleanup();
|
||||
|
||||
// Error logging
|
||||
void writeErrorLog(const char* format, ...);
|
||||
void checkResetReason();
|
||||
|
||||
// Sleep mode
|
||||
void deepSleep(int t);
|
||||
|
||||
@ -67,16 +72,6 @@ class SerialDebug {
|
||||
static Logging* getLog() { return &Log; }
|
||||
};
|
||||
|
||||
class ErrorFileLog {
|
||||
private:
|
||||
String _errors[ERR_COUNT];
|
||||
|
||||
public:
|
||||
ErrorFileLog();
|
||||
void addEntry(String error);
|
||||
void save();
|
||||
};
|
||||
|
||||
class FloatHistoryLog {
|
||||
private:
|
||||
String _fName;
|
||||
|
86
src/main.cpp
@ -32,6 +32,8 @@ SOFTWARE.
|
||||
#include <webserver.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// #define FORCE_GRAVITY_MODE
|
||||
|
||||
// Define constats for this program
|
||||
#ifdef DEACTIVATE_SLEEPMODE
|
||||
const int interval = 1000; // ms, time to wait between changes to output
|
||||
@ -46,13 +48,8 @@ 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;
|
||||
@ -60,6 +57,12 @@ void checkSleepMode(float angle, float volt) {
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if defined(FORCE_GRAVITY_MODE)
|
||||
Log.notice(F("MAIN: Forcing device into gravity mode for debugging" CR));
|
||||
runMode = RunMode::gravityMode;
|
||||
return;
|
||||
#endif
|
||||
|
||||
const RawGyroData &g = myConfig.getGyroCalibration();
|
||||
|
||||
if (!g.ax && !g.ay && !g.az && !g.gx && !g.gy && !g.gz) {
|
||||
@ -77,8 +80,12 @@ void checkSleepMode(float angle, float volt) {
|
||||
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)) {
|
||||
} else if ((volt < myConfig.getVoltageConfig() &&
|
||||
(angle > 85 && angle < 95)) ||
|
||||
(volt > myConfig.getVoltageConfig())) {
|
||||
runMode = RunMode::configurationMode;
|
||||
} else if (angle < 5 && myConfig.isStorageSleep()) {
|
||||
runMode = RunMode::storageMode;
|
||||
} else {
|
||||
runMode = RunMode::gravityMode;
|
||||
}
|
||||
@ -98,12 +105,29 @@ void checkSleepMode(float angle, float volt) {
|
||||
volt);
|
||||
#endif
|
||||
break;
|
||||
case RunMode::storageMode:
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: run mode STORAGE (angle=%F)." CR), angle);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are in storage mode, just go back to sleep
|
||||
if (runMode == RunMode::storageMode) {
|
||||
Log.notice(
|
||||
F("Main: Storage mode entered, going to sleep for maximum time." CR));
|
||||
#if defined(ESP8266)
|
||||
// ESP.deepSleep(ESP.deepSleepMax());
|
||||
ESP.deepSleep(0); // indefinite sleep
|
||||
#else
|
||||
#warning "Check and test the max deep sleep for esp32"
|
||||
// deepSleep(70 * 60); // quick search on internet suggest max time is 70
|
||||
// min
|
||||
ESP.deepSleep(0); // indefinite sleep
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Setup
|
||||
//
|
||||
void setup() {
|
||||
LOG_PERF_START("run-time");
|
||||
LOG_PERF_START("main-setup");
|
||||
@ -112,12 +136,9 @@ void setup() {
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
// Add a delay so that serial is started.
|
||||
// delay(3000);
|
||||
#if defined(ESP8266)
|
||||
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
#endif
|
||||
// Main startup
|
||||
|
||||
#if defined(ESP8266)
|
||||
Log.notice(F("Main: Started setup for %s." CR),
|
||||
String(ESP.getChipId(), HEX).c_str());
|
||||
@ -136,9 +157,10 @@ void setup() {
|
||||
|
||||
LOG_PERF_START("main-config-load");
|
||||
myConfig.checkFileSystem();
|
||||
checkResetReason();
|
||||
myConfig.loadFile();
|
||||
myWifi.init();
|
||||
myHardwareConfig.loadFile();
|
||||
myAdvancedConfig.loadFile();
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
@ -171,14 +193,14 @@ void setup() {
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!myGyro.setup()) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("MAIN: Failed to initialize the gyro, is it connected?"));
|
||||
} else {
|
||||
if (myGyro.setup()) {
|
||||
LOG_PERF_START("main-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("main-gyro-read");
|
||||
} else {
|
||||
Log.notice(
|
||||
F("Main: Failed to connect to the gyro, software will not be able "
|
||||
"to detect angles." CR));
|
||||
}
|
||||
|
||||
myBatteryVoltage.read();
|
||||
@ -209,6 +231,7 @@ void setup() {
|
||||
switch (runMode) {
|
||||
case RunMode::configurationMode:
|
||||
if (myWifi.isConnected()) {
|
||||
Log.notice(F("Main: Activating web server." CR));
|
||||
#if defined(ACTIVATE_OTA)
|
||||
LOG_PERF_START("main-wifi-ota");
|
||||
if (myWifi.checkFirmwareVersion()) myWifi.updateFirmware();
|
||||
@ -257,7 +280,12 @@ bool loopReadGravity() {
|
||||
LOG_PERF_STOP("loop-temp-read");
|
||||
|
||||
float gravitySG = calculateGravity(angle, tempC);
|
||||
float corrGravitySG = gravityTemperatureCorrectionC(gravitySG, tempC);
|
||||
float corrGravitySG = gravityTemperatureCorrectionC(
|
||||
gravitySG, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
|
||||
|
||||
if (myConfig.isGravityTempAdj()) {
|
||||
gravitySG = corrGravitySG;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%FC, gravity=%F, "
|
||||
@ -268,6 +296,14 @@ bool loopReadGravity() {
|
||||
bool pushExpired = (abs((int32_t)(millis() - pushMillis)) >
|
||||
(myConfig.getSleepInterval() * 1000));
|
||||
|
||||
if (myAdvancedConfig.isIgnoreLowAnges() &&
|
||||
(angle < myConfig.getFormulaData().a[0])) {
|
||||
Log.warning(
|
||||
F("Main: Angle is lower than water, so we regard this as faulty and "
|
||||
"dont send any data." CR));
|
||||
pushExpired = false;
|
||||
}
|
||||
|
||||
if (pushExpired || runMode == RunMode::gravityMode) {
|
||||
pushMillis = millis();
|
||||
LOG_PERF_START("loop-push");
|
||||
@ -343,13 +379,15 @@ void goToSleep(int sleepInterval) {
|
||||
deepSleep(sleepInterval);
|
||||
}
|
||||
|
||||
//
|
||||
// Main loops
|
||||
//
|
||||
void loop() {
|
||||
switch (runMode) {
|
||||
case RunMode::storageMode:
|
||||
// This point is never reached, just here to remove warning.
|
||||
break;
|
||||
|
||||
case RunMode::configurationMode:
|
||||
myWebServerHandler.loop();
|
||||
if (myWifi.isConnected()) myWebServerHandler.loop();
|
||||
|
||||
myWifi.loop();
|
||||
loopGravityOnInterval();
|
||||
|
||||
|
15
src/main.hpp
@ -29,6 +29,14 @@ SOFTWARE.
|
||||
#include <ArduinoLog.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum RunMode {
|
||||
gravityMode = 0,
|
||||
configurationMode = 1,
|
||||
wifiSetupMode = 2,
|
||||
storageMode = 3
|
||||
};
|
||||
extern RunMode runMode;
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <LittleFS.h>
|
||||
#define ESP_RESET ESP.reset
|
||||
@ -38,13 +46,8 @@ SOFTWARE.
|
||||
#define PIN_LED 2
|
||||
// #define PIN_A0 A0
|
||||
#else // defined (ESP32)
|
||||
#if defined(ESPRESSIF32_20)
|
||||
#include <LittleFS.h>
|
||||
#else
|
||||
#include <LITTLEFS.h>
|
||||
#define LittleFS LITTLEFS
|
||||
#endif
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
#define ESPhttpUpdate httpUpdate
|
||||
#define ESP_RESET ESP.restart
|
||||
#define ESP8266WebServer WebServer
|
||||
|
@ -32,59 +32,153 @@ SOFTWARE.
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
#define PUSHINT_FILENAME "/push.dat"
|
||||
|
||||
void PushIntervalTracker::update(const int index, const int defaultValue) {
|
||||
if (_counters[index] <= 0)
|
||||
_counters[index] = defaultValue;
|
||||
else
|
||||
_counters[index]--;
|
||||
}
|
||||
|
||||
void PushIntervalTracker::load() {
|
||||
File intFile = LittleFS.open(PUSHINT_FILENAME, "r");
|
||||
|
||||
if (intFile) {
|
||||
String line = intFile.readStringUntil('\n');
|
||||
Log.notice(F("PUSH: Read interval tracker %s." CR), line.c_str());
|
||||
|
||||
char temp[80];
|
||||
char *s, *p = &temp[0];
|
||||
int i = 0;
|
||||
|
||||
snprintf(&temp[0], sizeof(temp), "%s", line.c_str());
|
||||
while ((s = strtok_r(p, ":", &p)) != NULL) {
|
||||
_counters[i++] = atoi(s);
|
||||
}
|
||||
|
||||
intFile.close();
|
||||
}
|
||||
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: Parsed trackers: %d:%d:%d:%d:%d." CR), _counters[0],
|
||||
_counters[1], _counters[2], _counters[3], _counters[4]);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Update and save counters
|
||||
//
|
||||
void PushIntervalTracker::save() {
|
||||
update(0, myAdvancedConfig.getPushIntervalHttp1());
|
||||
update(1, myAdvancedConfig.getPushIntervalHttp2());
|
||||
update(2, myAdvancedConfig.getPushIntervalHttp3());
|
||||
update(3, myAdvancedConfig.getPushIntervalInflux());
|
||||
update(4, myAdvancedConfig.getPushIntervalMqtt());
|
||||
|
||||
// If this feature is disabled we skip saving the file
|
||||
if (!myAdvancedConfig.isPushIntervalActive()) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Variabled push interval disabled." CR));
|
||||
#endif
|
||||
LittleFS.remove(PUSHINT_FILENAME);
|
||||
} else {
|
||||
Log.notice(
|
||||
F("PUSH: Variabled push interval enabled, updating counters." CR));
|
||||
File intFile = LittleFS.open(PUSHINT_FILENAME, "w");
|
||||
|
||||
if (intFile) {
|
||||
// Format=http1:http2:http3:influx:mqtt
|
||||
intFile.printf("%d:%d:%d:%d:%d\n", _counters[0], _counters[1],
|
||||
_counters[2], _counters[3], _counters[4]);
|
||||
intFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send the data to targets
|
||||
//
|
||||
void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime) {
|
||||
printHeap("PUSH");
|
||||
_http.setReuse(false);
|
||||
_httpSecure.setReuse(false);
|
||||
_http.setReuse(true);
|
||||
_httpSecure.setReuse(true);
|
||||
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
|
||||
|
||||
if (myConfig.isBrewfatherActive()) {
|
||||
LOG_PERF_START("push-brewfather");
|
||||
sendBrewfather(engine);
|
||||
LOG_PERF_STOP("push-brewfather");
|
||||
}
|
||||
PushIntervalTracker intDelay;
|
||||
intDelay.load();
|
||||
|
||||
if (myConfig.isHttpActive()) {
|
||||
if (myConfig.isHttpActive() && intDelay.useHttp1()) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttpPost(engine, myConfig.isHttpSSL(), 0);
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if (myConfig.isHttp2Active()) {
|
||||
if (myConfig.isHttp2Active() && intDelay.useHttp2()) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttpPost(engine, myConfig.isHttp2SSL(), 1);
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
if (myConfig.isHttp3Active()) {
|
||||
if (myConfig.isHttp3Active() && intDelay.useHttp3()) {
|
||||
LOG_PERF_START("push-http3");
|
||||
sendHttpGet(engine, myConfig.isHttp3SSL());
|
||||
LOG_PERF_STOP("push-http3");
|
||||
}
|
||||
|
||||
if (myConfig.isInfluxDb2Active()) {
|
||||
if (myConfig.isInfluxDb2Active() && intDelay.useInflux()) {
|
||||
LOG_PERF_START("push-influxdb2");
|
||||
sendInfluxDb2(engine);
|
||||
sendInfluxDb2(engine, myConfig.isInfluxSSL());
|
||||
LOG_PERF_STOP("push-influxdb2");
|
||||
}
|
||||
|
||||
if (myConfig.isMqttActive()) {
|
||||
if (myConfig.isMqttActive() && intDelay.useMqtt()) {
|
||||
LOG_PERF_START("push-mqtt");
|
||||
sendMqtt(engine, myConfig.isMqttSSL());
|
||||
sendMqtt(engine, myConfig.isMqttSSL(), true);
|
||||
LOG_PERF_STOP("push-mqtt");
|
||||
}
|
||||
|
||||
engine.freeMemory();
|
||||
intDelay.save();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the server can reduce the buffer size to save memory (ESP8266 only)
|
||||
//
|
||||
void PushTarget::probeMaxFragement(String& serverPath) {
|
||||
#if defined(ESP8266) // Looks like this is feature is not supported by influxdb
|
||||
// Format: http:://servername:port/path
|
||||
int port = 443;
|
||||
String host = serverPath.substring(8); // remove the prefix or the probe will
|
||||
// fail, it needs a pure host name.
|
||||
// Remove the path if it exist
|
||||
int idx = host.indexOf("/");
|
||||
if (idx != -1) host = host.substring(0, idx);
|
||||
|
||||
// If a server port is defined, lets extract that part
|
||||
idx = host.indexOf(":");
|
||||
if (idx != -1) {
|
||||
String p = host.substring(idx + 1);
|
||||
port = p.toInt();
|
||||
host = host.substring(0, idx);
|
||||
}
|
||||
|
||||
Log.notice(F("PUSH: Probing server to max fragment %s:%d" CR), host.c_str(),
|
||||
port);
|
||||
if (_wifiSecure.probeMaxFragmentLength(host, port, 512)) {
|
||||
Log.notice(F("PUSH: Server supports smaller SSL buffer." CR));
|
||||
_wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Send to influx db v2
|
||||
//
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to influxdb2." CR));
|
||||
#endif
|
||||
@ -97,72 +191,57 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
"&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());
|
||||
_lastCode = _http.POST(doc);
|
||||
|
||||
if (isSecure) {
|
||||
#if defined(ESP8266)
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
Log.notice(
|
||||
F("PUSH: Skipping InfluxDB since SSL is enabled and we are in config "
|
||||
"mode." CR));
|
||||
_lastCode = -100;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log.notice(F("PUSH: InfluxDB, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement(serverPath);
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _httpSecure.POST(doc);
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _http.POST(doc);
|
||||
}
|
||||
|
||||
if (_lastCode == 204) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Influxdb push failed response=" + String(_lastCode));
|
||||
writeErrorLog("PUSH: Influxdb push failed response=%d", _lastCode);
|
||||
}
|
||||
|
||||
_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
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
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"));
|
||||
_lastCode = _http.POST(doc);
|
||||
|
||||
if (_lastCode == 200) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
|
||||
_lastCode);
|
||||
if (isSecure) {
|
||||
_httpSecure.end();
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Brewfather push failed response=" +
|
||||
String(_lastCode));
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
}
|
||||
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Add HTTP header to request
|
||||
//
|
||||
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
if (!header.length()) return;
|
||||
@ -176,8 +255,8 @@ void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
value.c_str());
|
||||
http.addHeader(name, value);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Unable to set header, invalid value " + header);
|
||||
writeErrorLog("PUSH: Unable to set header, invalid value %s",
|
||||
header.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,24 +288,21 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
#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);
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
Log.notice(
|
||||
F("PUSH: Skipping HTTP since SSL is enabled and we are in config "
|
||||
"mode." CR));
|
||||
_lastCode = -100;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement(serverPath);
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_httpSecure, myConfig.getHttpHeader(0));
|
||||
@ -238,8 +314,8 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
|
||||
_lastCode = _httpSecure.POST(doc);
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_http, myConfig.getHttpHeader(0));
|
||||
@ -256,9 +332,8 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: HTTP post failed response=" + String(_lastCode) +
|
||||
String(index == 0 ? " (http)" : " (http2)"));
|
||||
writeErrorLog("PUSH: HTTP post failed response=%d http%d", _lastCode,
|
||||
index + 1);
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
@ -291,28 +366,25 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
|
||||
#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);
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
Log.notice(
|
||||
F("PUSH: Skipping HTTP since SSL is enabled and we are in config "
|
||||
"mode." CR));
|
||||
_lastCode = -100;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement(serverPath);
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
_lastCode = _httpSecure.GET();
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
_lastCode = _http.GET();
|
||||
}
|
||||
|
||||
@ -320,8 +392,7 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: HTTP get successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: HTTP get failed response=" + String(_lastCode));
|
||||
writeErrorLog("PUSH: HTTP get failed response=%d", _lastCode);
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
@ -337,9 +408,11 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
|
||||
//
|
||||
// Send data to mqtt target
|
||||
//
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure,
|
||||
bool skipHomeAssistantRegistration) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to mqtt." CR));
|
||||
Log.notice(F("PUSH: Sending values to mqtt. Skip HA registration=%s" CR),
|
||||
skipHomeAssistantRegistration ? "yes" : "no");
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
@ -350,6 +423,16 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
int port = myConfig.getMqttPort();
|
||||
|
||||
if (myConfig.isMqttSSL()) {
|
||||
#if defined(ESP8266)
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
Log.notice(
|
||||
F("PUSH: Skipping MQTT since SSL is enabled and we are in config "
|
||||
"mode." CR));
|
||||
_lastCode = -100;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Log.notice(F("PUSH: MQTT, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
|
||||
@ -360,8 +443,10 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
}
|
||||
#endif
|
||||
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifiSecure);
|
||||
} else {
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifi);
|
||||
}
|
||||
|
||||
@ -373,9 +458,6 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
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.
|
||||
@ -395,15 +477,22 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(),
|
||||
value.c_str());
|
||||
#endif
|
||||
if (mqtt.publish(topic, value)) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
|
||||
_lastCode = 0;
|
||||
|
||||
if (skipHomeAssistantRegistration &&
|
||||
topic.startsWith("homeassistant/sensor/")) {
|
||||
Log.notice(F("PUSH: Ignoring Home Assistant registration topic %s" CR),
|
||||
topic.c_str());
|
||||
} else {
|
||||
_lastCode = mqtt.lastError();
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: MQTT push on " + topic +
|
||||
" failed error=" + String(mqtt.lastError()));
|
||||
if (mqtt.publish(topic, value)) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: MQTT publish successful on %s" CR), topic.c_str());
|
||||
_lastCode = 0;
|
||||
} else {
|
||||
_lastSuccess = false;
|
||||
_lastCode = mqtt.lastError();
|
||||
writeErrorLog("PUSH: MQTT push on %s failed error=%d", topic.c_str(),
|
||||
_lastCode);
|
||||
}
|
||||
}
|
||||
|
||||
index = next + 1;
|
||||
|
@ -45,12 +45,12 @@ class PushTarget {
|
||||
void sendHttpPost(TemplatingEngine& engine, bool isSecure, int index);
|
||||
void sendHttpGet(TemplatingEngine& engine, bool isSecure);
|
||||
void addHttpHeader(HTTPClient& http, String header);
|
||||
void probeMaxFragement(String& serverPath);
|
||||
|
||||
public:
|
||||
void sendAll(float angle, float gravitySG, float corrGravitySG, float tempC,
|
||||
float runTime);
|
||||
|
||||
void sendBrewfather(TemplatingEngine& engine);
|
||||
void sendHttp1(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpPost(engine, isSecure, 0);
|
||||
}
|
||||
@ -60,12 +60,28 @@ class PushTarget {
|
||||
void sendHttp3(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpGet(engine, isSecure);
|
||||
}
|
||||
void sendInfluxDb2(TemplatingEngine& engine);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure);
|
||||
void sendInfluxDb2(TemplatingEngine& engine, bool isSecure);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure,
|
||||
bool skipHomeAssistantRegistration = true);
|
||||
int getLastCode() { return _lastCode; }
|
||||
bool getLastSuccess() { return _lastSuccess; }
|
||||
};
|
||||
|
||||
class PushIntervalTracker {
|
||||
private:
|
||||
int _counters[5] = {0, 0, 0, 0, 0};
|
||||
void update(const int index, const int defaultValue);
|
||||
|
||||
public:
|
||||
bool useHttp1() { return _counters[0] == 0 ? true : false; }
|
||||
bool useHttp2() { return _counters[1] == 0 ? true : false; }
|
||||
bool useHttp3() { return _counters[2] == 0 ? true : false; }
|
||||
bool useInflux() { return _counters[3] == 0 ? true : false; }
|
||||
bool useMqtt() { return _counters[4] == 0 ? true : false; }
|
||||
void load();
|
||||
void save();
|
||||
};
|
||||
|
||||
#endif // SRC_PUSHTARGET_HPP_
|
||||
|
||||
// EOF
|
||||
|
@ -31,8 +31,9 @@ SOFTWARE.
|
||||
#define PARAM_OTA "ota-url"
|
||||
#define PARAM_SSID "wifi-ssid"
|
||||
#define PARAM_PASS "wifi-pass"
|
||||
#define PARAM_SSID2 "wifi-ssid2"
|
||||
#define PARAM_PASS2 "wifi-pass2"
|
||||
#define PARAM_RUNTIME_AVERAGE "runtime-average"
|
||||
#define PARAM_PUSH_BREWFATHER "brewfather-push"
|
||||
#define PARAM_TOKEN "token"
|
||||
#define PARAM_TOKEN2 "token2"
|
||||
#define PARAM_PUSH_HTTP "http-push"
|
||||
@ -52,13 +53,15 @@ SOFTWARE.
|
||||
#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_VOLTAGE_FACTOR "voltage-factor"
|
||||
#define PARAM_VOLTAGE_CONFIG "voltage-config"
|
||||
#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_STORAGE_SLEEP "storage-sleep"
|
||||
#define PARAM_FORMULA_DATA "formula-calculation-data"
|
||||
#define PARAM_FILES "files"
|
||||
#define PARAM_FILE_NAME "file-name"
|
||||
@ -76,16 +79,23 @@ SOFTWARE.
|
||||
#define PARAM_PLATFORM "platform"
|
||||
#define PARAM_BLE "ble"
|
||||
#define PARAM_HW_GYRO_READ_COUNT "gyro-read-count"
|
||||
#define PARAM_HW_GYRO_READ_DELAY "gyro-read-delay"
|
||||
// #define PARAM_HW_GYRO_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_WIFI_PORTAL_TIMEOUT "wifi-portal-timeout"
|
||||
#define PARAM_HW_WIFI_CONNECT_TIMEOUT "wifi-connect-timeout"
|
||||
#define PARAM_HW_TEMPSENSOR_RESOLUTION "tempsensor-resolution"
|
||||
#define PARAM_HW_PUSH_TIMEOUT "push-timeout"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP1 "int-http1"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP2 "int-http2"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP3 "int-http3"
|
||||
#define PARAM_HW_PUSH_INTERVAL_INFLUX "int-influx"
|
||||
#define PARAM_HW_PUSH_INTERVAL_MQTT "int-mqtt"
|
||||
#define PARAM_HW_IGNORE_LOW_ANGLES "ignore-low-angles"
|
||||
#define PARAM_FORMAT_HTTP1 "http-1"
|
||||
#define PARAM_FORMAT_HTTP2 "http-2"
|
||||
#define PARAM_FORMAT_HTTP3 "http-3"
|
||||
#define PARAM_FORMAT_BREWFATHER "brewfather"
|
||||
#define PARAM_FORMAT_INFLUXDB "influxdb"
|
||||
#define PARAM_FORMAT_MQTT "mqtt"
|
||||
#define PARAM_PUSH_FORMAT "format"
|
||||
|
@ -42,7 +42,7 @@ const char iSpindleFormat[] PROGMEM =
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"angle\": ${angle}, "
|
||||
"\"battery\": ${battery}, "
|
||||
"\"rssi\": ${rssi}, "
|
||||
"\"RSSI\": ${rssi}, "
|
||||
"\"corr-gravity\": ${corr-gravity}, "
|
||||
"\"gravity-unit\": \"${gravity-unit}\", "
|
||||
"\"run-time\": ${run-time} "
|
||||
@ -64,24 +64,6 @@ const char iHttpGetFormat[] PROGMEM =
|
||||
"&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} "
|
||||
@ -148,6 +130,9 @@ void TemplatingEngine::initialize(float angle, float gravitySG,
|
||||
setVal(TPL_GRAVITY_CORR_P, convertToPlato(corrGravitySG), 1);
|
||||
setVal(TPL_GRAVITY_UNIT, myConfig.getGravityFormat());
|
||||
|
||||
setVal(TPL_APP_VER, CFG_APPVER);
|
||||
setVal(TPL_APP_BUILD, CFG_GITREV);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// dumpAll();
|
||||
#endif
|
||||
@ -156,34 +141,30 @@ void TemplatingEngine::initialize(float angle, float gravitySG,
|
||||
//
|
||||
// Create the data using defined template.
|
||||
//
|
||||
const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
const char* TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
String fname;
|
||||
baseTemplate.reserve(600);
|
||||
_baseTemplate.reserve(600);
|
||||
|
||||
// Load templates from memory
|
||||
switch (idx) {
|
||||
case TEMPLATE_HTTP1:
|
||||
baseTemplate = String(iSpindleFormat);
|
||||
_baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP1;
|
||||
break;
|
||||
case TEMPLATE_HTTP2:
|
||||
baseTemplate = String(iSpindleFormat);
|
||||
_baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP2;
|
||||
break;
|
||||
case TEMPLATE_HTTP3:
|
||||
baseTemplate = String(iHttpGetFormat);
|
||||
_baseTemplate = String(iHttpGetFormat);
|
||||
fname = TPL_FNAME_HTTP3;
|
||||
break;
|
||||
case TEMPLATE_BREWFATHER:
|
||||
baseTemplate = String(brewfatherFormat);
|
||||
// fname = TPL_FNAME_BREWFATHER;
|
||||
break;
|
||||
case TEMPLATE_INFLUX:
|
||||
baseTemplate = String(influxDbFormat);
|
||||
_baseTemplate = String(influxDbFormat);
|
||||
fname = TPL_FNAME_INFLUXDB;
|
||||
break;
|
||||
case TEMPLATE_MQTT:
|
||||
baseTemplate = String(mqttFormat);
|
||||
_baseTemplate = String(mqttFormat);
|
||||
fname = TPL_FNAME_MQTT;
|
||||
break;
|
||||
}
|
||||
@ -194,7 +175,7 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
char buf[file.size() + 1];
|
||||
memset(&buf[0], 0, file.size() + 1);
|
||||
file.readBytes(&buf[0], file.size());
|
||||
baseTemplate = String(&buf[0]);
|
||||
_baseTemplate = String(&buf[0]);
|
||||
file.close();
|
||||
Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str());
|
||||
}
|
||||
@ -204,13 +185,16 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
#endif
|
||||
|
||||
// Insert data into template.
|
||||
transform(baseTemplate);
|
||||
transform();
|
||||
_baseTemplate.clear();
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str());
|
||||
#endif
|
||||
|
||||
return baseTemplate;
|
||||
if (_output) return _output;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -53,17 +53,17 @@ SOFTWARE.
|
||||
#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_APP_VER "${app-ver}"
|
||||
#define TPL_APP_BUILD "${app-build}"
|
||||
|
||||
#define TPL_FNAME_HTTP1 "/http-1.tpl"
|
||||
#define TPL_FNAME_HTTP2 "/http-2.tpl"
|
||||
#define TPL_FNAME_HTTP3 "/http-3.tpl"
|
||||
// #define TPL_FNAME_BREWFATHER "/brewfather.tpl"
|
||||
#define TPL_FNAME_INFLUXDB "/influxdb.tpl"
|
||||
#define TPL_FNAME_MQTT "/mqtt.tpl"
|
||||
|
||||
extern const char iSpindleFormat[] PROGMEM;
|
||||
extern const char iHttpGetFormat[] PROGMEM;
|
||||
extern const char brewfatherFormat[] PROGMEM;
|
||||
extern const char influxDbFormat[] PROGMEM;
|
||||
extern const char mqttFormat[] PROGMEM;
|
||||
|
||||
@ -75,33 +75,35 @@ class TemplatingEngine {
|
||||
String val;
|
||||
};
|
||||
|
||||
KeyVal items[21] = {{TPL_MDNS, ""}, {TPL_ID, ""},
|
||||
{TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""},
|
||||
{TPL_TEMP_C, ""}, {TPL_TEMP_F, ""},
|
||||
{TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""},
|
||||
{TPL_RSSI, ""}, {TPL_RUN_TIME, ""},
|
||||
{TPL_ANGLE, ""}, {TPL_TILT, ""},
|
||||
{TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""},
|
||||
{TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""},
|
||||
{TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""},
|
||||
{TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""},
|
||||
{TPL_TOKEN2, ""}};
|
||||
KeyVal _items[23] = {{TPL_MDNS, ""}, {TPL_ID, ""},
|
||||
{TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""},
|
||||
{TPL_TEMP_C, ""}, {TPL_TEMP_F, ""},
|
||||
{TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""},
|
||||
{TPL_RSSI, ""}, {TPL_RUN_TIME, ""},
|
||||
{TPL_ANGLE, ""}, {TPL_TILT, ""},
|
||||
{TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""},
|
||||
{TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""},
|
||||
{TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""},
|
||||
{TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""},
|
||||
{TPL_TOKEN2, ""}, {TPL_APP_VER, ""},
|
||||
{TPL_APP_BUILD, ""}};
|
||||
|
||||
char buffer[20];
|
||||
String baseTemplate;
|
||||
char _buffer[20] = "";
|
||||
String _baseTemplate;
|
||||
char *_output = 0;
|
||||
|
||||
void setVal(String key, float val, int dec = 2) {
|
||||
String s = convertFloatToString(val, &buffer[0], dec);
|
||||
String s = convertFloatToString(val, &_buffer[0], dec);
|
||||
s.trim();
|
||||
setVal(key, s);
|
||||
}
|
||||
void setVal(String key, int val) { setVal(key, String(val)); }
|
||||
void setVal(String key, char val) { setVal(key, String(val)); }
|
||||
void setVal(String key, String val) {
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
int max = sizeof(_items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (items[i].key.equals(key)) {
|
||||
items[i].val = val;
|
||||
if (_items[i].key.equals(key)) {
|
||||
_items[i].val = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -109,21 +111,73 @@ class TemplatingEngine {
|
||||
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 transform() {
|
||||
const char *format = _baseTemplate.c_str();
|
||||
int len = _baseTemplate.length();
|
||||
int size = len;
|
||||
|
||||
// Lets check how much memory will be needed to transform the template
|
||||
for (int j = 0; j < len - 2; j++) {
|
||||
if (*(format + j) == '$' && *(format + j + 1) == '{') {
|
||||
// Start of format tag found
|
||||
int max = sizeof(_items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (strncmp(format + j, _items[i].key.c_str(),
|
||||
_items[i].key.length()) == 0) {
|
||||
// Found key
|
||||
size = size - _items[i].key.length() + _items[i].val.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeMemory(); // In case this is reused
|
||||
_output = static_cast<char *>(malloc(size + 20));
|
||||
|
||||
if (!_output) {
|
||||
Log.error(F("TPL : Unable to allocate memory for transforming template, "
|
||||
"needed %d." CR),
|
||||
size);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(_output, 0, size + 20);
|
||||
|
||||
// Lets do the transformation
|
||||
int k = 0;
|
||||
for (int j = 0; j < len - 2; j++) {
|
||||
if (*(format + j) == '$' && *(format + j + 1) == '{') {
|
||||
// Start of format tag found
|
||||
int max = sizeof(_items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (strncmp(format + j, _items[i].key.c_str(),
|
||||
_items[i].key.length()) == 0) {
|
||||
// Found key
|
||||
strncat(_output, format + k, j - k);
|
||||
strncat(_output, _items[i].val.c_str(), _items[i].val.length());
|
||||
k = j + _items[i].key.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
strncat(_output, format + k, size - k);
|
||||
Log.notice(F("TPL : Transformed template %d chars to %d chars" CR),
|
||||
strlen(format), strlen(_output));
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
printHeap("TPL ");
|
||||
Log.verboseln(format);
|
||||
Log.verboseln(_output);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dumpAll() {
|
||||
int max = sizeof(items) / sizeof(KeyVal);
|
||||
int max = sizeof(_items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Serial.print("Key=\'");
|
||||
Serial.print(items[i].key.c_str());
|
||||
Serial.print(_items[i].key.c_str());
|
||||
Serial.print("\', Val=\'");
|
||||
Serial.print(items[i].val.c_str());
|
||||
Serial.print(_items[i].val.c_str());
|
||||
Serial.println("\'");
|
||||
}
|
||||
}
|
||||
@ -133,14 +187,21 @@ class TemplatingEngine {
|
||||
TEMPLATE_HTTP1 = 0,
|
||||
TEMPLATE_HTTP2 = 1,
|
||||
TEMPLATE_HTTP3 = 2,
|
||||
TEMPLATE_BREWFATHER = 3,
|
||||
TEMPLATE_INFLUX = 4,
|
||||
TEMPLATE_MQTT = 5
|
||||
TEMPLATE_INFLUX = 3,
|
||||
TEMPLATE_MQTT = 4
|
||||
};
|
||||
|
||||
TemplatingEngine() {}
|
||||
~TemplatingEngine() { freeMemory(); }
|
||||
|
||||
void freeMemory() {
|
||||
if (_output) free(_output);
|
||||
|
||||
_output = 0;
|
||||
}
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime);
|
||||
const String& create(TemplatingEngine::Templates idx);
|
||||
const char *create(TemplatingEngine::Templates idx);
|
||||
};
|
||||
|
||||
#endif // SRC_TEMPLATING_HPP_
|
||||
|
@ -32,7 +32,6 @@ SOFTWARE.
|
||||
|
||||
OneWire myOneWire(PIN_DS);
|
||||
DallasTemperature mySensors(&myOneWire);
|
||||
#define TEMPERATURE_PRECISION 9
|
||||
|
||||
TempSensor myTempSensor;
|
||||
|
||||
@ -52,10 +51,10 @@ void TempSensor::setup() {
|
||||
|
||||
if (mySensors.getDS18Count()) {
|
||||
#if !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR),
|
||||
mySensors.getDS18Count());
|
||||
Log.notice(
|
||||
F("TSEN: Found %d temperature sensor(s). Using %d resolution" CR),
|
||||
mySensors.getDS18Count(), myAdvancedConfig.getTempSensorResolution());
|
||||
#endif
|
||||
mySensors.setResolution(TEMPERATURE_PRECISION);
|
||||
}
|
||||
|
||||
// Set the temp sensor adjustment values
|
||||
@ -95,6 +94,7 @@ float TempSensor::getValue(bool useGyro) {
|
||||
}
|
||||
|
||||
// Read the sensors
|
||||
mySensors.setResolution(myAdvancedConfig.getTempSensorResolution());
|
||||
mySensors.requestTemperatures();
|
||||
|
||||
float c = 0;
|
||||
|
@ -44,10 +44,14 @@ void WebServerHandler::webHandleConfig() {
|
||||
LOG_PERF_START("webserver-api-config");
|
||||
Log.notice(F("WEB : webServer callback for /api/config(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
DynamicJsonDocument doc(2000);
|
||||
myConfig.createJson(doc);
|
||||
|
||||
doc[PARAM_PASS] = ""; // dont show the wifi password
|
||||
doc[PARAM_PASS2] = "";
|
||||
|
||||
doc[PARAM_APP_VER] = String(CFG_APPVER);
|
||||
doc[PARAM_APP_BUILD] = String(CFG_GITREV);
|
||||
|
||||
double angle = 0;
|
||||
|
||||
@ -63,13 +67,13 @@ void WebServerHandler::webHandleConfig() {
|
||||
if (myConfig.isTempF())
|
||||
// We want the delta value (32F = 0C).
|
||||
doc[PARAM_TEMP_ADJ] =
|
||||
reduceFloatPrecision(convertCtoF(myConfig.getTempSensorAdjC())-32, 1);
|
||||
reduceFloatPrecision(convertCtoF(myConfig.getTempSensorAdjC()) - 32, 1);
|
||||
else
|
||||
doc[PARAM_TEMP_ADJ] = reduceFloatPrecision(myConfig.getTempSensorAdjC(), 1);
|
||||
|
||||
if (myConfig.isGravityTempAdj()) {
|
||||
gravity =
|
||||
gravityTemperatureCorrectionC(gravity, tempC, myConfig.getTempFormat());
|
||||
gravity = gravityTemperatureCorrectionC(
|
||||
gravity, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
|
||||
}
|
||||
|
||||
if (myConfig.isGravityPlato()) {
|
||||
@ -96,8 +100,9 @@ void WebServerHandler::webHandleConfig() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(CFG_JSON_BUFSIZE);
|
||||
out.reserve(2000);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-config");
|
||||
}
|
||||
@ -159,13 +164,11 @@ void WebServerHandler::webHandleUpload() {
|
||||
String out;
|
||||
out.reserve(300);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-upload");
|
||||
}
|
||||
|
||||
//
|
||||
// Callback from webServer when / has been accessed.
|
||||
//
|
||||
void WebServerHandler::webHandleUploadFile() {
|
||||
LOG_PERF_START("webserver-api-upload-file");
|
||||
Log.verbose(F("WEB : webServer callback for /api/upload(post)." CR));
|
||||
@ -207,9 +210,7 @@ void WebServerHandler::webHandleUploadFile() {
|
||||
maxSketchSpace / 1024);
|
||||
|
||||
if (!Update.begin(maxSketchSpace, U_FLASH, PIN_LED)) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(
|
||||
F("WEB : Not enough space to store for this firmware."));
|
||||
writeErrorLog("WEB : Not enough space to store for this firmware.");
|
||||
_uploadReturn = 500;
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
@ -231,9 +232,8 @@ void WebServerHandler::webHandleUploadFile() {
|
||||
delay(500);
|
||||
ESP_RESET();
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WEB : Failed to finish firmware flashing error=" +
|
||||
String(Update.getError()));
|
||||
writeErrorLog("WEB : Failed to finish firmware flashing error=%d",
|
||||
Update.getError());
|
||||
_uploadReturn = 500;
|
||||
}
|
||||
} else {
|
||||
@ -291,9 +291,6 @@ void WebServerHandler::webHandleCalibrate() {
|
||||
LOG_PERF_STOP("webserver-api-calibrate");
|
||||
}
|
||||
|
||||
//
|
||||
// Callback from webServer when / has been accessed.
|
||||
//
|
||||
void WebServerHandler::webHandleFactoryDefaults() {
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/factory." CR));
|
||||
@ -317,16 +314,27 @@ void WebServerHandler::webHandleFactoryDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Callback from webServer when / has been accessed.
|
||||
//
|
||||
void WebServerHandler::webHandleLogClear() {
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/clearlog." CR));
|
||||
|
||||
if (!id.compareTo(myConfig.getID())) {
|
||||
_server->send(200, "text/plain", "Removing logfiles...");
|
||||
LittleFS.remove(ERR_FILENAME);
|
||||
LittleFS.remove(ERR_FILENAME2);
|
||||
_server->send(200, "text/plain", "Logfiles cleared.");
|
||||
} else {
|
||||
_server->send(400, "text/plain", "Unknown ID.");
|
||||
}
|
||||
}
|
||||
|
||||
void WebServerHandler::webHandleStatus() {
|
||||
LOG_PERF_START("webserver-api-status");
|
||||
Log.notice(F("WEB : webServer callback for /api/status(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(300);
|
||||
DynamicJsonDocument doc(500);
|
||||
|
||||
double angle = 0;
|
||||
double angle = 0; // Indicate we have no valid gyro value
|
||||
|
||||
if (myGyro.hasValue()) angle = myGyro.getAngle();
|
||||
|
||||
@ -334,9 +342,13 @@ void WebServerHandler::webHandleStatus() {
|
||||
double gravity = calculateGravity(angle, tempC);
|
||||
|
||||
doc[PARAM_ID] = myConfig.getID();
|
||||
doc[PARAM_ANGLE] = reduceFloatPrecision(angle);
|
||||
doc[PARAM_ANGLE] = myGyro.isConnected()
|
||||
? reduceFloatPrecision(angle)
|
||||
: -1; // Indicate that we have no connection to gyro
|
||||
|
||||
if (myConfig.isGravityTempAdj()) {
|
||||
gravity = gravityTemperatureCorrectionC(gravity, tempC);
|
||||
gravity = gravityTemperatureCorrectionC(
|
||||
gravity, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
|
||||
}
|
||||
if (myConfig.isGravityPlato()) {
|
||||
doc[PARAM_GRAVITY] = reduceFloatPrecision(convertToPlato(gravity), 1);
|
||||
@ -357,6 +369,7 @@ void WebServerHandler::webHandleStatus() {
|
||||
doc[PARAM_APP_VER] = CFG_APPVER;
|
||||
doc[PARAM_APP_BUILD] = CFG_GITREV;
|
||||
doc[PARAM_MDNS] = myConfig.getMDNS();
|
||||
doc[PARAM_SSID] = WiFi.SSID();
|
||||
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
doc[PARAM_RUNTIME_AVERAGE] = reduceFloatPrecision(
|
||||
@ -374,15 +387,13 @@ void WebServerHandler::webHandleStatus() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(300);
|
||||
out.reserve(500);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-status");
|
||||
}
|
||||
|
||||
//
|
||||
// Callback from webServer when / has been accessed.
|
||||
//
|
||||
void WebServerHandler::webHandleClearWIFI() {
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/clearwifi." CR));
|
||||
@ -390,8 +401,10 @@ void WebServerHandler::webHandleClearWIFI() {
|
||||
if (!id.compareTo(myConfig.getID())) {
|
||||
_server->send(200, "text/plain",
|
||||
"Clearing WIFI credentials and doing reset...");
|
||||
myConfig.setWifiPass("");
|
||||
myConfig.setWifiSSID("");
|
||||
myConfig.setWifiPass("", 0);
|
||||
myConfig.setWifiSSID("", 0);
|
||||
myConfig.setWifiPass("", 1);
|
||||
myConfig.setWifiSSID("", 1);
|
||||
myConfig.saveFile();
|
||||
delay(1000);
|
||||
WiFi.disconnect(); // Clear credentials
|
||||
@ -401,9 +414,6 @@ void WebServerHandler::webHandleClearWIFI() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Used to force the device to never sleep.
|
||||
//
|
||||
void WebServerHandler::webHandleStatusSleepmode() {
|
||||
LOG_PERF_START("webserver-api-sleepmode");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
@ -498,8 +508,6 @@ void WebServerHandler::webHandleConfigPush() {
|
||||
myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H2).c_str(), 1);
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP3))
|
||||
myConfig.setHttp3Url(_server->arg(PARAM_PUSH_HTTP3).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_BREWFATHER))
|
||||
myConfig.setBrewfatherPushUrl(_server->arg(PARAM_PUSH_BREWFATHER).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2))
|
||||
myConfig.setInfluxDb2PushUrl(_server->arg(PARAM_PUSH_INFLUXDB2).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2_ORG))
|
||||
@ -574,6 +582,9 @@ void WebServerHandler::webHandleConfigGravity() {
|
||||
myConfig.setGravityTempAdj(
|
||||
_server->arg(PARAM_GRAVITY_TEMP_ADJ).equalsIgnoreCase("on") ? true
|
||||
: false);
|
||||
else
|
||||
myConfig.setGravityTempAdj(false);
|
||||
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseGravity", true);
|
||||
_server->send(302, "text/plain", "Gravity config updated");
|
||||
@ -600,8 +611,10 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
if (_server->hasArg(PARAM_VOLTAGEFACTOR))
|
||||
myConfig.setVoltageFactor(_server->arg(PARAM_VOLTAGEFACTOR).toFloat());
|
||||
if (_server->hasArg(PARAM_VOLTAGE_FACTOR))
|
||||
myConfig.setVoltageFactor(_server->arg(PARAM_VOLTAGE_FACTOR).toFloat());
|
||||
if (_server->hasArg(PARAM_VOLTAGE_CONFIG))
|
||||
myConfig.setVoltageConfig(_server->arg(PARAM_VOLTAGE_CONFIG).toFloat());
|
||||
if (_server->hasArg(PARAM_TEMP_ADJ)) {
|
||||
if (myConfig.isTempC()) {
|
||||
myConfig.setTempSensorAdjC(_server->arg(PARAM_TEMP_ADJ));
|
||||
@ -617,6 +630,15 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
if (_server->hasArg(PARAM_GYRO_TEMP))
|
||||
myConfig.setGyroTemp(
|
||||
_server->arg(PARAM_GYRO_TEMP).equalsIgnoreCase("on") ? true : false);
|
||||
else
|
||||
myConfig.setGyroTemp(false);
|
||||
if (_server->hasArg(PARAM_STORAGE_SLEEP))
|
||||
myConfig.setStorageSleep(
|
||||
_server->arg(PARAM_STORAGE_SLEEP).equalsIgnoreCase("on") ? true
|
||||
: false);
|
||||
else
|
||||
myConfig.setStorageSleep(false);
|
||||
|
||||
myConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseHardware", true);
|
||||
_server->send(302, "text/plain", "Hardware config updated");
|
||||
@ -624,18 +646,18 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
}
|
||||
|
||||
//
|
||||
// Update device parameters.
|
||||
// Update advanced settings.
|
||||
//
|
||||
void WebServerHandler::webHandleDeviceParam() {
|
||||
LOG_PERF_START("webserver-api-device-param");
|
||||
void WebServerHandler::webHandleConfigAdvancedWrite() {
|
||||
LOG_PERF_START("webserver-api-config-advanced");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/device/param(post)." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/advaced(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
myConfig.getID());
|
||||
_server->send(400, "text/plain", "Invalid ID.");
|
||||
LOG_PERF_STOP("webserver-api-device-param");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -643,41 +665,93 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < _server->args(); i++) {
|
||||
String s = _server->arg(i);
|
||||
|
||||
if (_server->argName(i).equalsIgnoreCase(PARAM_HW_GYRO_READ_COUNT))
|
||||
myHardwareConfig.setGyroReadCount(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_GYRO_READ_DELAY))
|
||||
myHardwareConfig.setGyroReadDelay(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(
|
||||
PARAM_HW_GYRO_MOVING_THREASHOLD))
|
||||
myHardwareConfig.setGyroSensorMovingThreashold(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_FORMULA_DEVIATION))
|
||||
myHardwareConfig.setMaxFormulaCreationDeviation(s.toFloat());
|
||||
else if (_server->argName(i).equalsIgnoreCase(
|
||||
PARAM_HW_FORMULA_CALIBRATION_TEMP))
|
||||
myHardwareConfig.SetDefaultCalibrationTemp(s.toFloat());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_WIFI_PORTALTIMEOUT))
|
||||
myHardwareConfig.setWifiPortalTimeout(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_PUSH_TIMEOUT))
|
||||
myHardwareConfig.setPushTimeout(s.toInt());
|
||||
if (_server->hasArg(PARAM_HW_GYRO_READ_COUNT))
|
||||
myAdvancedConfig.setGyroReadCount(
|
||||
_server->arg(PARAM_HW_GYRO_READ_COUNT).toInt());
|
||||
// if (_server->hasArg(PARAM_HW_GYRO_READ_DELAY))
|
||||
// myAdvancedConfig.setGyroReadDelay(
|
||||
// _server->arg(PARAM_HW_GYRO_READ_DELAY).toInt());
|
||||
if (_server->hasArg(PARAM_HW_GYRO_MOVING_THREASHOLD))
|
||||
myAdvancedConfig.setGyroSensorMovingThreashold(
|
||||
_server->arg(PARAM_HW_GYRO_MOVING_THREASHOLD).toInt());
|
||||
if (_server->hasArg(PARAM_HW_FORMULA_DEVIATION))
|
||||
myAdvancedConfig.setMaxFormulaCreationDeviation(
|
||||
_server->arg(PARAM_HW_FORMULA_DEVIATION).toFloat());
|
||||
if (_server->hasArg(PARAM_HW_FORMULA_CALIBRATION_TEMP)) {
|
||||
float t = _server->arg(PARAM_HW_FORMULA_CALIBRATION_TEMP).toFloat();
|
||||
if (myConfig.isTempF()) t = convertFtoC(t);
|
||||
myAdvancedConfig.SetDefaultCalibrationTemp(t);
|
||||
}
|
||||
if (_server->hasArg(PARAM_HW_WIFI_PORTAL_TIMEOUT))
|
||||
myAdvancedConfig.setWifiPortalTimeout(
|
||||
_server->arg(PARAM_HW_WIFI_PORTAL_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_WIFI_CONNECT_TIMEOUT))
|
||||
myAdvancedConfig.setWifiConnectTimeout(
|
||||
_server->arg(PARAM_HW_WIFI_CONNECT_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_TIMEOUT))
|
||||
myAdvancedConfig.setPushTimeout(
|
||||
_server->arg(PARAM_HW_PUSH_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP1))
|
||||
myAdvancedConfig.setPushIntervalHttp1(
|
||||
_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP1).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP2))
|
||||
myAdvancedConfig.setPushIntervalHttp2(
|
||||
_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP2).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP3))
|
||||
myAdvancedConfig.setPushIntervalHttp3(
|
||||
_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP3).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_INFLUX))
|
||||
myAdvancedConfig.setPushIntervalInflux(
|
||||
_server->arg(PARAM_HW_PUSH_INTERVAL_INFLUX).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_MQTT))
|
||||
myAdvancedConfig.setPushIntervalMqtt(
|
||||
_server->arg(PARAM_HW_PUSH_INTERVAL_MQTT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_TEMPSENSOR_RESOLUTION))
|
||||
myAdvancedConfig.setTempSensorResolution(
|
||||
_server->arg(PARAM_HW_TEMPSENSOR_RESOLUTION).toInt());
|
||||
if (_server->hasArg(PARAM_HW_IGNORE_LOW_ANGLES))
|
||||
myAdvancedConfig.setIgnoreLowAnges(
|
||||
_server->arg(PARAM_HW_IGNORE_LOW_ANGLES).equalsIgnoreCase("on")
|
||||
? true
|
||||
: false);
|
||||
else
|
||||
myAdvancedConfig.setIgnoreLowAnges(false);
|
||||
|
||||
myHardwareConfig.saveFile();
|
||||
myAdvancedConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseAdvanced", true);
|
||||
_server->send(302, "text/plain", "Advanced config updated");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
}
|
||||
|
||||
// Return the current configuration.
|
||||
DynamicJsonDocument doc(512);
|
||||
//
|
||||
// Read advanced settings
|
||||
//
|
||||
void WebServerHandler::webHandleConfigAdvancedRead() {
|
||||
LOG_PERF_START("webserver-api-config-advanced");
|
||||
Log.notice(F("WEB : webServer callback for /api/config/advanced(get)." CR));
|
||||
|
||||
doc[PARAM_HW_GYRO_READ_COUNT] = myHardwareConfig.getGyroReadCount();
|
||||
doc[PARAM_HW_GYRO_READ_DELAY] = myHardwareConfig.getGyroReadDelay();
|
||||
DynamicJsonDocument doc(500);
|
||||
|
||||
doc[PARAM_HW_GYRO_READ_COUNT] = myAdvancedConfig.getGyroReadCount();
|
||||
// doc[PARAM_HW_GYRO_READ_DELAY] = myAdvancedConfig.getGyroReadDelay();
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] =
|
||||
myHardwareConfig.getGyroSensorMovingThreashold();
|
||||
myAdvancedConfig.getGyroSensorMovingThreashold();
|
||||
doc[PARAM_HW_FORMULA_DEVIATION] =
|
||||
myHardwareConfig.getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTALTIMEOUT] = myHardwareConfig.getWifiPortalTimeout();
|
||||
myAdvancedConfig.getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTAL_TIMEOUT] = myAdvancedConfig.getWifiPortalTimeout();
|
||||
doc[PARAM_HW_WIFI_CONNECT_TIMEOUT] = myAdvancedConfig.getWifiConnectTimeout();
|
||||
doc[PARAM_HW_PUSH_TIMEOUT] = myAdvancedConfig.getPushTimeout();
|
||||
float t = myAdvancedConfig.getDefaultCalibrationTemp();
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] =
|
||||
myHardwareConfig.getDefaultCalibrationTemp();
|
||||
myConfig.isTempC() ? t : reduceFloatPrecision(convertCtoF(t), 1);
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP1] = myAdvancedConfig.getPushIntervalHttp1();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP2] = myAdvancedConfig.getPushIntervalHttp2();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP3] = myAdvancedConfig.getPushIntervalHttp3();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_INFLUX] = myAdvancedConfig.getPushIntervalInflux();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_MQTT] = myAdvancedConfig.getPushIntervalMqtt();
|
||||
doc[PARAM_HW_TEMPSENSOR_RESOLUTION] =
|
||||
myAdvancedConfig.getTempSensorResolution();
|
||||
doc[PARAM_HW_IGNORE_LOW_ANGLES] = myAdvancedConfig.isIgnoreLowAnges();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -685,10 +759,11 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(512);
|
||||
out.reserve(500);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-device-param");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
}
|
||||
|
||||
//
|
||||
@ -698,7 +773,7 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
LOG_PERF_START("webserver-api-formula-read");
|
||||
Log.notice(F("WEB : webServer callback for /api/formula(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(250);
|
||||
DynamicJsonDocument doc(500);
|
||||
const RawFormulaData& fd = myConfig.getFormulaData();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
@ -716,10 +791,13 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
doc[PARAM_ERROR] = "Internal error creating formula.";
|
||||
break;
|
||||
case ERR_FORMULA_NOTENOUGHVALUES:
|
||||
doc[PARAM_ERROR] = "Not enough values to create formula.";
|
||||
doc[PARAM_ERROR] =
|
||||
"Not enough values to create formula, need at least 3 angles.";
|
||||
break;
|
||||
case ERR_FORMULA_UNABLETOFFIND:
|
||||
doc[PARAM_ERROR] = "Unable to find an accurate formula based on input.";
|
||||
doc[PARAM_ERROR] =
|
||||
"Unable to find an accurate formula based on input, check error log "
|
||||
"and graph below.";
|
||||
break;
|
||||
default:
|
||||
doc[PARAM_GRAVITY_FORMULA] = myConfig.getGravityFormula();
|
||||
@ -731,6 +809,11 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
doc["a3"] = reduceFloatPrecision(fd.a[2], 2);
|
||||
doc["a4"] = reduceFloatPrecision(fd.a[3], 2);
|
||||
doc["a5"] = reduceFloatPrecision(fd.a[4], 2);
|
||||
doc["a6"] = reduceFloatPrecision(fd.a[5], 2);
|
||||
doc["a7"] = reduceFloatPrecision(fd.a[6], 2);
|
||||
doc["a8"] = reduceFloatPrecision(fd.a[7], 2);
|
||||
doc["a9"] = reduceFloatPrecision(fd.a[8], 2);
|
||||
doc["a10"] = reduceFloatPrecision(fd.a[9], 2);
|
||||
|
||||
if (myConfig.isGravityPlato()) {
|
||||
doc["g1"] = reduceFloatPrecision(convertToPlato(fd.g[0]), 1);
|
||||
@ -738,12 +821,22 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
doc["g3"] = reduceFloatPrecision(convertToPlato(fd.g[2]), 1);
|
||||
doc["g4"] = reduceFloatPrecision(convertToPlato(fd.g[3]), 1);
|
||||
doc["g5"] = reduceFloatPrecision(convertToPlato(fd.g[4]), 1);
|
||||
doc["g6"] = reduceFloatPrecision(convertToPlato(fd.g[5]), 1);
|
||||
doc["g7"] = reduceFloatPrecision(convertToPlato(fd.g[6]), 1);
|
||||
doc["g8"] = reduceFloatPrecision(convertToPlato(fd.g[7]), 1);
|
||||
doc["g9"] = reduceFloatPrecision(convertToPlato(fd.g[8]), 1);
|
||||
doc["g10"] = reduceFloatPrecision(convertToPlato(fd.g[9]), 1);
|
||||
} else {
|
||||
doc["g1"] = reduceFloatPrecision(fd.g[0], 4);
|
||||
doc["g2"] = reduceFloatPrecision(fd.g[1], 4);
|
||||
doc["g3"] = reduceFloatPrecision(fd.g[2], 4);
|
||||
doc["g4"] = reduceFloatPrecision(fd.g[3], 4);
|
||||
doc["g5"] = reduceFloatPrecision(fd.g[4], 4);
|
||||
doc["g6"] = reduceFloatPrecision(fd.g[5], 4);
|
||||
doc["g7"] = reduceFloatPrecision(fd.g[6], 4);
|
||||
doc["g8"] = reduceFloatPrecision(fd.g[7], 4);
|
||||
doc["g9"] = reduceFloatPrecision(fd.g[8], 4);
|
||||
doc["g10"] = reduceFloatPrecision(fd.g[9], 4);
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
@ -752,8 +845,9 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(256);
|
||||
out.reserve(500);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-formula-read");
|
||||
}
|
||||
@ -792,17 +886,12 @@ void WebServerHandler::webHandleConfigFormatWrite() {
|
||||
} else if (_server->hasArg(PARAM_FORMAT_MQTT)) {
|
||||
success = writeFile(TPL_FNAME_MQTT, _server->arg(PARAM_FORMAT_MQTT));
|
||||
}
|
||||
/*else if (_server->hasArg(PARAM_FORMAT_BREWFATHER)) {
|
||||
success = writeFile(TPL_FNAME_BREWFATHER,
|
||||
_server->arg(PARAM_FORMAT_BREWFATHER));
|
||||
}*/
|
||||
|
||||
if (success) {
|
||||
_server->sendHeader("Location", "/format.htm", true);
|
||||
_server->send(302, "text/plain", "Format updated");
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("WEB : Unable to store format file"));
|
||||
writeErrorLog("WEB : Unable to store format file");
|
||||
_server->send(400, "text/plain", "Unable to store format in file.");
|
||||
}
|
||||
|
||||
@ -832,7 +921,8 @@ void WebServerHandler::webHandleTestPush() {
|
||||
float angle = myGyro.getAngle();
|
||||
float tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
float gravitySG = calculateGravity(angle, tempC);
|
||||
float corrGravitySG = gravityTemperatureCorrectionC(gravitySG, tempC);
|
||||
float corrGravitySG = gravityTemperatureCorrectionC(
|
||||
gravitySG, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
|
||||
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, 2.1);
|
||||
@ -841,11 +931,7 @@ void WebServerHandler::webHandleTestPush() {
|
||||
PushTarget push;
|
||||
bool enabled = false;
|
||||
|
||||
if (!type.compareTo(PARAM_FORMAT_BREWFATHER) &&
|
||||
myConfig.isBrewfatherActive()) {
|
||||
push.sendBrewfather(engine);
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_HTTP1) && myConfig.isHttpActive()) {
|
||||
if (!type.compareTo(PARAM_FORMAT_HTTP1) && myConfig.isHttpActive()) {
|
||||
push.sendHttp1(engine, myConfig.isHttpSSL());
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_HTTP2) && myConfig.isHttp2Active()) {
|
||||
@ -856,13 +942,14 @@ void WebServerHandler::webHandleTestPush() {
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_INFLUXDB) &&
|
||||
myConfig.isInfluxDb2Active()) {
|
||||
push.sendInfluxDb2(engine);
|
||||
push.sendInfluxDb2(engine, myConfig.isInfluxSSL());
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_MQTT) && myConfig.isMqttActive()) {
|
||||
push.sendMqtt(engine, myConfig.isMqttSSL());
|
||||
push.sendMqtt(engine, myConfig.isMqttSSL(), false);
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
engine.freeMemory();
|
||||
DynamicJsonDocument doc(100);
|
||||
doc[PARAM_PUSH_ENABLED] = enabled;
|
||||
doc[PARAM_PUSH_SUCCESS] = push.getLastSuccess();
|
||||
@ -871,6 +958,7 @@ void WebServerHandler::webHandleTestPush() {
|
||||
String out;
|
||||
out.reserve(100);
|
||||
serializeJson(doc, out);
|
||||
doc.clear();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -933,54 +1021,52 @@ void WebServerHandler::webHandleConfigFormatRead() {
|
||||
LOG_PERF_START("webserver-api-config-format-read");
|
||||
Log.notice(F("WEB : webServer callback for /api/config/formula(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(2048);
|
||||
|
||||
doc[PARAM_ID] = myConfig.getID();
|
||||
String out;
|
||||
out.reserve(7000);
|
||||
out += "{\"id\":\"" + String(myConfig.getID()) + "\",";
|
||||
|
||||
String s = readFile(TPL_FNAME_HTTP1);
|
||||
out += "\"" + String(PARAM_FORMAT_HTTP1) + "\":\"";
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_HTTP1] = urlencode(s);
|
||||
out += urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_HTTP1] = urlencode(String(&iSpindleFormat[0]));
|
||||
out += urlencode(String(&iSpindleFormat[0]));
|
||||
|
||||
s = readFile(TPL_FNAME_HTTP2);
|
||||
out += "\",\"" + String(PARAM_FORMAT_HTTP2) + "\":\"";
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_HTTP2] = urlencode(s);
|
||||
out += urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_HTTP2] = urlencode(String(&iSpindleFormat[0]));
|
||||
out += urlencode(String(&iSpindleFormat[0]));
|
||||
|
||||
s = readFile(TPL_FNAME_HTTP3);
|
||||
out += "\",\"" + String(PARAM_FORMAT_HTTP3) + "\":\"";
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_HTTP3] = urlencode(s);
|
||||
out += urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_HTTP3] = urlencode(String(&iHttpGetFormat[0]));
|
||||
|
||||
/*s = readFile(TPL_FNAME_BREWFATHER);
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_BREWFATHER] = urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_BREWFATHER] = urlencode(&brewfatherFormat[0]);*/
|
||||
out += urlencode(String(&iHttpGetFormat[0]));
|
||||
|
||||
s = readFile(TPL_FNAME_INFLUXDB);
|
||||
out += "\",\"" + String(PARAM_FORMAT_INFLUXDB) + "\":\"";
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_INFLUXDB] = urlencode(s);
|
||||
out += urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_INFLUXDB] = urlencode(String(&influxDbFormat[0]));
|
||||
out += urlencode(String(&influxDbFormat[0]));
|
||||
|
||||
s = readFile(TPL_FNAME_MQTT);
|
||||
out += "\",\"" + String(PARAM_FORMAT_MQTT) + "\":\"";
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_MQTT] = urlencode(s);
|
||||
out += urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_MQTT] = urlencode(String(&mqttFormat[0]));
|
||||
out += urlencode(String(&mqttFormat[0]));
|
||||
|
||||
out += "\"}";
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(out.c_str());
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
String out;
|
||||
out.reserve(2048);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-config-format-read");
|
||||
}
|
||||
@ -1011,6 +1097,11 @@ void WebServerHandler::webHandleFormulaWrite() {
|
||||
fd.a[2] = _server->arg("a3").toDouble();
|
||||
fd.a[3] = _server->arg("a4").toDouble();
|
||||
fd.a[4] = _server->arg("a5").toDouble();
|
||||
fd.a[5] = _server->arg("a6").toDouble();
|
||||
fd.a[6] = _server->arg("a7").toDouble();
|
||||
fd.a[7] = _server->arg("a8").toDouble();
|
||||
fd.a[8] = _server->arg("a9").toDouble();
|
||||
fd.a[9] = _server->arg("a10").toDouble();
|
||||
|
||||
if (myConfig.isGravityPlato()) {
|
||||
fd.g[0] = convertToSG(_server->arg("g1").toDouble());
|
||||
@ -1018,14 +1109,25 @@ void WebServerHandler::webHandleFormulaWrite() {
|
||||
fd.g[2] = convertToSG(_server->arg("g3").toDouble());
|
||||
fd.g[3] = convertToSG(_server->arg("g4").toDouble());
|
||||
fd.g[4] = convertToSG(_server->arg("g5").toDouble());
|
||||
fd.g[5] = convertToSG(_server->arg("g6").toDouble());
|
||||
fd.g[6] = convertToSG(_server->arg("g7").toDouble());
|
||||
fd.g[7] = convertToSG(_server->arg("g8").toDouble());
|
||||
fd.g[8] = convertToSG(_server->arg("g9").toDouble());
|
||||
fd.g[9] = convertToSG(_server->arg("g10").toDouble());
|
||||
} else {
|
||||
fd.g[0] = _server->arg("g1").toDouble();
|
||||
fd.g[1] = _server->arg("g2").toDouble();
|
||||
fd.g[2] = _server->arg("g3").toDouble();
|
||||
fd.g[3] = _server->arg("g4").toDouble();
|
||||
fd.g[4] = _server->arg("g5").toDouble();
|
||||
fd.g[5] = _server->arg("g6").toDouble();
|
||||
fd.g[6] = _server->arg("g7").toDouble();
|
||||
fd.g[7] = _server->arg("g8").toDouble();
|
||||
fd.g[8] = _server->arg("g9").toDouble();
|
||||
fd.g[9] = _server->arg("g10").toDouble();
|
||||
}
|
||||
|
||||
fd.g[0] = 1; // force first point to SG gravity of water
|
||||
myConfig.setFormulaData(fd);
|
||||
|
||||
int e;
|
||||
@ -1071,17 +1173,17 @@ const char* WebServerHandler::getHtmlFileName(HtmlFile item) {
|
||||
Log.notice(F("WEB : Looking up filename for %d." CR), item);
|
||||
|
||||
switch (item) {
|
||||
case HTML_INDEX:
|
||||
case HtmlFile::HTML_INDEX:
|
||||
return "index.min.htm";
|
||||
case HTML_CONFIG:
|
||||
case HtmlFile::HTML_CONFIG:
|
||||
return "config.min.htm";
|
||||
case HTML_CALIBRATION:
|
||||
case HtmlFile::HTML_CALIBRATION:
|
||||
return "calibration.min.htm";
|
||||
case HTML_FORMAT:
|
||||
case HtmlFile::HTML_FORMAT:
|
||||
return "format.min.htm";
|
||||
case HTML_ABOUT:
|
||||
case HtmlFile::HTML_ABOUT:
|
||||
return "about.min.htm";
|
||||
case HTML_TEST:
|
||||
case HtmlFile::HTML_TEST:
|
||||
return "test.min.htm";
|
||||
}
|
||||
|
||||
@ -1123,7 +1225,39 @@ bool WebServerHandler::setupWebServer() {
|
||||
MDNS.begin(myConfig.getMDNS());
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
// Show files in the filessytem at startup
|
||||
#if defined(ESP8266)
|
||||
FSInfo fs;
|
||||
LittleFS.info(fs);
|
||||
Log.notice(F("WEB : File system Total=%d, Used=%d." CR), fs.totalBytes,
|
||||
fs.usedBytes);
|
||||
Dir dir = LittleFS.openDir("/");
|
||||
while (dir.next()) {
|
||||
Log.notice(F("WEB : File=%s, %d bytes" CR), dir.fileName().c_str(),
|
||||
dir.fileSize());
|
||||
if (!dir.fileSize()) {
|
||||
Log.notice(F("WEB : Empty file detected, removing file." CR));
|
||||
LittleFS.remove(dir.fileName().c_str());
|
||||
}
|
||||
}
|
||||
#else // defined( ESP32 )
|
||||
File root = LittleFS.open("/");
|
||||
File f = root.openNextFile();
|
||||
while (f) {
|
||||
Log.notice(F("WEB : File=%s, %d bytes" CR), f.name(), f.size());
|
||||
if (!f.size()) {
|
||||
Log.notice(F("WEB : Empty file detected, removing file." CR));
|
||||
LittleFS.remove(f.name());
|
||||
}
|
||||
|
||||
f = root.openNextFile();
|
||||
}
|
||||
f.close();
|
||||
root.close();
|
||||
#endif
|
||||
|
||||
// Static content
|
||||
Log.notice(F("WEB : Setting up handlers for web server." CR));
|
||||
#if defined(EMBED_HTML)
|
||||
_server->on("/", std::bind(&WebServerHandler::webReturnIndexHtm, this));
|
||||
_server->on("/index.htm",
|
||||
@ -1139,18 +1273,6 @@ bool WebServerHandler::setupWebServer() {
|
||||
_server->on("/test.htm",
|
||||
std::bind(&WebServerHandler::webReturnTestHtm, this));
|
||||
#else
|
||||
// Show files in the filessytem at startup
|
||||
|
||||
FSInfo fs;
|
||||
LittleFS.info(fs);
|
||||
Log.notice(F("WEB : File system Total=%d, Used=%d." CR), fs.totalBytes,
|
||||
fs.usedBytes);
|
||||
Dir dir = LittleFS.openDir("/");
|
||||
while (dir.next()) {
|
||||
Log.notice(F("WEB : File=%s, %d bytes" CR), dir.fileName().c_str(),
|
||||
dir.fileSize());
|
||||
}
|
||||
|
||||
// Check if the html files exist, if so serve them, else show the static
|
||||
// upload page.
|
||||
if (checkHtmlFile(HTML_INDEX) && checkHtmlFile(HTML_CONFIG) &&
|
||||
@ -1178,30 +1300,26 @@ bool WebServerHandler::setupWebServer() {
|
||||
_server->on("/firmware.htm",
|
||||
std::bind(&WebServerHandler::webReturnFirmwareHtm, this));
|
||||
_server->serveStatic("/log", LittleFS, ERR_FILENAME);
|
||||
_server->serveStatic("/log2", LittleFS, ERR_FILENAME2);
|
||||
_server->serveStatic("/runtime", LittleFS, RUNTIME_FILENAME);
|
||||
|
||||
// Dynamic content
|
||||
_server->on(
|
||||
"/api/config", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleConfig, this)); // Get config.json
|
||||
_server->on("/api/clearlog", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleLogClear, this));
|
||||
_server->on("/api/config", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleConfig, this));
|
||||
_server->on("/api/formula", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleFormulaRead,
|
||||
this)); // Get formula.json (calibration page)
|
||||
std::bind(&WebServerHandler::webHandleFormulaRead, this));
|
||||
_server->on("/api/formula", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleFormulaWrite,
|
||||
this)); // Get formula.json (calibration page)
|
||||
std::bind(&WebServerHandler::webHandleFormulaWrite, this));
|
||||
_server->on("/api/calibrate", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleCalibrate,
|
||||
this)); // Run calibration routine (param id)
|
||||
std::bind(&WebServerHandler::webHandleCalibrate, this));
|
||||
_server->on("/api/factory", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleFactoryDefaults,
|
||||
this)); // Reset the device
|
||||
std::bind(&WebServerHandler::webHandleFactoryDefaults, this));
|
||||
_server->on("/api/status", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleStatus,
|
||||
this)); // Get the status.json
|
||||
std::bind(&WebServerHandler::webHandleStatus, this));
|
||||
_server->on("/api/clearwifi", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleClearWIFI,
|
||||
this)); // Clear wifi settings
|
||||
std::bind(&WebServerHandler::webHandleClearWIFI, this));
|
||||
_server->on(
|
||||
"/api/upload", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleUpload, this)); // Get upload.json
|
||||
@ -1231,9 +1349,12 @@ bool WebServerHandler::setupWebServer() {
|
||||
_server->on("/api/config/format", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleConfigFormatWrite,
|
||||
this)); // Change template formats
|
||||
_server->on("/api/device/param", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleDeviceParam,
|
||||
this)); // Change device params
|
||||
_server->on("/api/config/advanced", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleConfigAdvancedRead,
|
||||
this)); // Read advanced settings
|
||||
_server->on("/api/config/advanced", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleConfigAdvancedWrite,
|
||||
this)); // Change advanced params
|
||||
_server->on("/api/test/push", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleTestPush,
|
||||
this)); //
|
||||
@ -1245,9 +1366,6 @@ bool WebServerHandler::setupWebServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// called from main loop
|
||||
//
|
||||
void WebServerHandler::loop() {
|
||||
#if defined(ESP8266)
|
||||
MDNS.update();
|
||||
|
@ -28,12 +28,12 @@ SOFTWARE.
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#define MAX_SKETCH_SPACE 1044464
|
||||
#define MAX_SKETCH_SPACE 1044464
|
||||
#else // defined (ESP32)
|
||||
#include <ESPmDNS.h>
|
||||
#include <Update.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#include <Update.h>
|
||||
#define MAX_SKETCH_SPACE 1835008
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
@ -60,6 +60,8 @@ class WebServerHandler {
|
||||
void webHandleConfig();
|
||||
void webHandleFormulaWrite();
|
||||
void webHandleFormulaRead();
|
||||
void webHandleConfigAdvancedRead();
|
||||
void webHandleConfigAdvancedWrite();
|
||||
void webHandleConfigHardware();
|
||||
void webHandleConfigGravity();
|
||||
void webHandleConfigPush();
|
||||
@ -74,7 +76,7 @@ class WebServerHandler {
|
||||
void webHandleCalibrate();
|
||||
void webHandleUploadFile();
|
||||
void webHandleUpload();
|
||||
void webHandleDeviceParam();
|
||||
void webHandleLogClear();
|
||||
void webHandlePageNotFound();
|
||||
|
||||
String readFile(String fname);
|
||||
|
161
src/wifi.cpp
@ -52,20 +52,8 @@ SOFTWARE.
|
||||
#define USE_STATIC_IP_CONFIG_IN_CP false
|
||||
#define _WIFIMGR_LOGLEVEL_ 3
|
||||
#include <ESP_WiFiManager.h>
|
||||
// Override the look and feel of the standard ui (hide secondary forms)
|
||||
const char WM_HTTP_FORM_START[] PROGMEM =
|
||||
"<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>";
|
||||
ESP_WiFiManager *myWifiManager;
|
||||
DoubleResetDetector *myDRD;
|
||||
|
||||
WifiConnection myWifi;
|
||||
|
||||
const char *userSSID = USER_SSID;
|
||||
@ -82,21 +70,25 @@ void WifiConnection::init() {
|
||||
// Check if we have a valid wifi configuration
|
||||
//
|
||||
bool WifiConnection::hasConfig() {
|
||||
if (strlen(myConfig.getWifiSSID())) return true;
|
||||
if (strlen(myConfig.getWifiSSID(0))) return true;
|
||||
if (strlen(userSSID)) return true;
|
||||
|
||||
// Check if there are stored WIFI Settings we can use.
|
||||
// Check if there are stored WIFI Settings we can use.
|
||||
#if defined(ESP8266)
|
||||
String ssid = WiFi.SSID();
|
||||
if (ssid.length()) {
|
||||
Log.notice(F("WIFI: Found credentials in EEPORM." CR));
|
||||
myConfig.setWifiSSID(ssid);
|
||||
myConfig.setWifiSSID(ssid, 0);
|
||||
|
||||
if (WiFi.psk().length()) myConfig.setWifiPass(WiFi.psk(), 0);
|
||||
|
||||
if (WiFi.psk().length())
|
||||
myConfig.setWifiPass(WiFi.psk());
|
||||
|
||||
myConfig.saveFile();
|
||||
return true;
|
||||
}
|
||||
#else // defined( ESP32 )
|
||||
#warning "Cant read SSID property on ESP32 until a connection has been made!"
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -137,7 +129,7 @@ void WifiConnection::startPortal() {
|
||||
myWifiManager->setMinimumSignalQuality(-1);
|
||||
myWifiManager->setConfigPortalChannel(0);
|
||||
myWifiManager->setConfigPortalTimeout(
|
||||
myHardwareConfig.getWifiPortalTimeout());
|
||||
myAdvancedConfig.getWifiPortalTimeout());
|
||||
|
||||
String mdns("<p>Default mDNS name is: http://");
|
||||
mdns += myConfig.getMDNS();
|
||||
@ -145,21 +137,29 @@ void WifiConnection::startPortal() {
|
||||
ESP_WMParameter deviceName(mdns.c_str());
|
||||
myWifiManager->addParameter(&deviceName);
|
||||
|
||||
if (myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD)) {
|
||||
Log.notice(F("WIFI: Exited portal, connected to wifi. Rebooting..." CR));
|
||||
myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD);
|
||||
|
||||
if (myWifiManager->getSSID().length())
|
||||
myConfig.setWifiSSID(myWifiManager->getSSID());
|
||||
|
||||
if (myWifiManager->getPW().length())
|
||||
myConfig.setWifiPass(myWifiManager->getPW());
|
||||
if (myWifiManager->getSSID(0).length()) {
|
||||
myConfig.setWifiSSID(myWifiManager->getSSID(0), 0);
|
||||
myConfig.setWifiPass(myWifiManager->getPW(0), 0);
|
||||
myConfig.setWifiSSID(myWifiManager->getSSID(1), 1);
|
||||
myConfig.setWifiPass(myWifiManager->getPW(1), 1);
|
||||
|
||||
// If the same SSID has been used, lets delete the second
|
||||
if (!strcmp(myConfig.getWifiSSID(0), myConfig.getWifiSSID(1))) {
|
||||
myConfig.setWifiSSID("", 1);
|
||||
myConfig.setWifiPass("", 1);
|
||||
}
|
||||
|
||||
Log.notice(F("WIFI: Stored SSID1:'%s' SSID2:'%s'" CR),
|
||||
myConfig.getWifiSSID(0), myConfig.getWifiSSID(1));
|
||||
myConfig.saveFile();
|
||||
} else {
|
||||
Log.notice(
|
||||
F("WIFI: Exited portal, no connection to wifi. Rebooting..." CR));
|
||||
F("WIFI: Could not find first SSID so assuming we got a timeout." CR));
|
||||
}
|
||||
|
||||
Log.notice(F("WIFI: Exited wifi config portal. Rebooting..." CR));
|
||||
stopDoubleReset();
|
||||
delay(500);
|
||||
ESP_RESET();
|
||||
@ -173,7 +173,7 @@ void WifiConnection::loop() { myDRD->loop(); }
|
||||
//
|
||||
// Connect to last known access point, non blocking mode.
|
||||
//
|
||||
void WifiConnection::connectAsync() {
|
||||
void WifiConnection::connectAsync(int wifiIndex) {
|
||||
WiFi.persistent(true);
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (strlen(userSSID)) {
|
||||
@ -181,9 +181,10 @@ void WifiConnection::connectAsync() {
|
||||
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());
|
||||
Log.notice(F("WIFI: Connecting to wifi (%d) using stored settings %s." CR),
|
||||
wifiIndex, myConfig.getWifiSSID(wifiIndex));
|
||||
WiFi.begin(myConfig.getWifiSSID(wifiIndex),
|
||||
myConfig.getWifiPass(wifiIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,26 +203,101 @@ bool WifiConnection::waitForConnection(int maxTime) {
|
||||
|
||||
if (i++ >
|
||||
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to connect to wifi " +
|
||||
String(WiFi.status()));
|
||||
writeErrorLog("WIFI: Failed to connect to wifi %d", 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: Connected to wifi %s ip=%s." CR), WiFi.SSID().c_str(),
|
||||
getIPAddress().c_str());
|
||||
Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS());
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Check what network is the strongest.
|
||||
//
|
||||
int WifiConnection::findStrongestNetwork() {
|
||||
if (!myConfig.dualWifiConfigured()) {
|
||||
Log.notice(F("WIFI: Only one wifi SSID is configured, skipping scan." CR));
|
||||
return -1;
|
||||
}
|
||||
|
||||
Log.notice(F("WIFI: Scanning for wifi." CR));
|
||||
int noNetwork = WiFi.scanNetworks(false, false);
|
||||
int strenght[2] = {-100, -100};
|
||||
|
||||
for (int i = 0; i < noNetwork; i++) {
|
||||
int rssi = WiFi.RSSI(i);
|
||||
String ssid = WiFi.SSID(i);
|
||||
|
||||
if (ssid.compareTo(myConfig.getWifiSSID(0)))
|
||||
strenght[0] = rssi;
|
||||
else if (ssid.compareTo(myConfig.getWifiSSID(1)))
|
||||
strenght[1] = rssi;
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: Found %s %d." CR), ssid.c_str(), rssi);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (strenght[0] == -100 && strenght[1] == -100)
|
||||
return -1; // None of the stored networks can be seen
|
||||
|
||||
if (strenght[0] >= strenght[1])
|
||||
return 0; // First network is strongest or they have equal strength
|
||||
|
||||
return 1; // Second network is the strongest
|
||||
}
|
||||
|
||||
//
|
||||
// Connect to last known access point, blocking mode.
|
||||
//
|
||||
bool WifiConnection::connect() {
|
||||
connectAsync();
|
||||
return waitForConnection(20); // 20 seconds.
|
||||
/*
|
||||
// Alternative code for connecting to strongest wifi.
|
||||
// Takes approximatly 2 seconds to scan for available network
|
||||
int i = findStrongestNetwork();
|
||||
|
||||
if (i != -1) {
|
||||
Log.notice(F("WIFI: Wifi %d:'%s' is strongest." CR), i,
|
||||
myConfig.getWifiSSID(i)); } else { i = 0; // Use first SSID as default.
|
||||
}
|
||||
|
||||
connectAsync(i);
|
||||
return waitForConnection(myAdvancedConfig.getWifiConnectTimeout());
|
||||
*/
|
||||
|
||||
// Alternative code for using seconday wifi settings as fallback.
|
||||
// If success to seconday is successful this is used as standard
|
||||
int timeout = myAdvancedConfig.getWifiConnectTimeout();
|
||||
|
||||
connectAsync(0);
|
||||
if (!waitForConnection(timeout)) {
|
||||
Log.warning(F("WIFI: Failed to connect to first SSID %s." CR),
|
||||
myConfig.getWifiSSID(0));
|
||||
|
||||
if (strlen(myConfig.getWifiSSID(1))) {
|
||||
connectAsync(1);
|
||||
|
||||
if (waitForConnection(timeout)) {
|
||||
Log.notice(F("WIFI: Connected to second SSID %s, making secondary "
|
||||
"default." CR),
|
||||
myConfig.getWifiSSID(1));
|
||||
|
||||
myConfig.swapPrimaryWifi();
|
||||
myConfig.saveFile();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Log.warning(F("WIFI: Failed to connect to any SSID." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
@ -268,9 +344,7 @@ bool WifiConnection::updateFirmware() {
|
||||
|
||||
switch (ret) {
|
||||
case HTTP_UPDATE_FAILED: {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: OTA update failed " +
|
||||
String(ESPhttpUpdate.getLastError()));
|
||||
writeErrorLog("WIFI: OTA update failed %d", ESPhttpUpdate.getLastError());
|
||||
} break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
break;
|
||||
@ -300,9 +374,7 @@ void WifiConnection::downloadFile(HTTPClient &http, String &fname) {
|
||||
f.close();
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str());
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("WIFI: Failed to download html-file " +
|
||||
String(httpResponseCode));
|
||||
writeErrorLog("WIFI: Failed to download html-file %d", httpResponseCode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,8 +413,7 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
#endif
|
||||
DeserializationError err = deserializeJson(ver, payload);
|
||||
if (err) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("WIFI: Failed to parse version.json"));
|
||||
writeErrorLog("WIFI: Failed to parse version.json");
|
||||
} else {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Project %s version %s." CR),
|
||||
|
@ -42,8 +42,11 @@ class WifiConnection {
|
||||
bool _newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char* version);
|
||||
void downloadFile(HTTPClient& http, String& fname);
|
||||
void connectAsync();
|
||||
|
||||
// WIFI
|
||||
void connectAsync(int wifiIndex);
|
||||
bool waitForConnection(int maxTime = 20);
|
||||
int findStrongestNetwork();
|
||||
|
||||
public:
|
||||
// WIFI
|
||||
|
@ -17,7 +17,11 @@ To reduce the need for adding custom endpoints for various services there is an
|
||||
: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.
|
||||
The save button will save the current formula and reload the data from the device.
|
||||
|
||||
You can also select a template from the list and copy that to the current endpoint.
|
||||
|
||||
Saving an empty formula will reset it to the default value.
|
||||
|
||||
.. tip::
|
||||
|
||||
@ -95,4 +99,10 @@ These are the format keys available for use in the format.
|
||||
* - ${gravity-unit}
|
||||
- Gravity format, `G` or `P`
|
||||
- G
|
||||
* - ${app-ver}
|
||||
- Software version
|
||||
- 1.1.0
|
||||
* - ${app-build}
|
||||
- Software revision (git hash)
|
||||
- ..e456743
|
||||
|
||||
|
@ -8,7 +8,7 @@ All the API's use a key called ``ID`` which is the unique device id (chip id). T
|
||||
GET: /api/config
|
||||
================
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
Retrieve the current configuration 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)
|
||||
@ -24,7 +24,6 @@ Other parameters are the same as in the configuration guide.
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"ble": "color",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"token": "token",
|
||||
"token2": "token2",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
@ -59,19 +58,15 @@ Other parameters are the same as in the configuration guide.
|
||||
},
|
||||
"formula-calculation-data": {
|
||||
"a1":25,
|
||||
"a2":30,
|
||||
"a3":35,
|
||||
"a4":40,
|
||||
"a5":45,
|
||||
"a10":0,
|
||||
"g1":1,
|
||||
"g2":1.01,
|
||||
"g3":1.02,
|
||||
"g4":1.03,
|
||||
"g5":1.04
|
||||
"g10":1
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04,
|
||||
"app-ver": "0.1.0",
|
||||
"app-build": "build",
|
||||
"platform": "esp8266",
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
@ -86,10 +81,12 @@ This API has been removed from 0.9 and merged with /api/status
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
Retrieve the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``platform`` can be either ``esp8266`` or ``esp32``
|
||||
* ``temp-c`` will be set to -273 C if there is no temp sensor
|
||||
* ``angle`` will be set to 0 if no valid angle is found and -1 if there is no gyro
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
@ -102,12 +99,14 @@ Other parameters are the same as in the configuration guide.
|
||||
"temp-c": 0,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"wifi-ssid": "connected SSID",
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"token": "token",
|
||||
"token2": "token2",
|
||||
"rssi": -56,
|
||||
"app-ver": "0.0.0",
|
||||
"app-build": "gitrev",
|
||||
"mdns": "gravmon",
|
||||
"sleep-interval": 30,
|
||||
"platform": "esp8266",
|
||||
@ -118,10 +117,10 @@ Other parameters are the same as in the configuration guide.
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
Retrieve 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.
|
||||
* ``a1``-``a10`` are the angles/tilt readings (up to 10 are currently supported)
|
||||
* ``g1``-``g10`` are the corresponding gravity readings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@ -132,16 +131,62 @@ Retrive the data used for formula calculation data via an HTTP GET command. Payl
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"a6": 0,
|
||||
"a7": 0,
|
||||
"a8": 0,
|
||||
"a9": 0,
|
||||
"a10": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1,
|
||||
"g6": 1,
|
||||
"g7": 1,
|
||||
"g8": 1,
|
||||
"g9": 1,
|
||||
"g10": 1,
|
||||
"error": "Potential error message",
|
||||
"gravity-format": "G",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436"
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/advanced
|
||||
=========================
|
||||
|
||||
Used for adjusting some internal constants and other advanced settings. Should be used with caution.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"gyro-read-count": 50,
|
||||
"gyro-moving-threashold": 500,
|
||||
"formula-max-deviation": 3.0,
|
||||
"wifi-portal-timeout": 120,
|
||||
"wifi-connect-timeout": 20,
|
||||
"push-timeout": 10,
|
||||
"formula-calibration-temp": 20,
|
||||
"int-http1": 0,
|
||||
"int-http2": 0,
|
||||
"int-http3": 0,
|
||||
"int-influx": 0,
|
||||
"int-mqtt": 0,
|
||||
"tempsensor-resolution": 9,
|
||||
"ignore-low-angles": false
|
||||
}
|
||||
|
||||
POST: /api/config/advanced
|
||||
==========================
|
||||
|
||||
Same parameters as above.
|
||||
|
||||
Payload should be in standard format used for posting a form.
|
||||
|
||||
.. note::
|
||||
``ignore-low-angles`` 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).
|
||||
|
||||
GET: /api/clearwifi
|
||||
===================
|
||||
|
||||
@ -173,7 +218,7 @@ Trigger a push on one of the targets, used to validate the configuration from th
|
||||
|
||||
Requires to parameters to function /api/test/push?id=<deviceid>&format=<format>
|
||||
|
||||
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, brewfather, influxdb, mqtt
|
||||
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, http-3, influxdb, mqtt
|
||||
|
||||
The response is an json message with the following values.
|
||||
|
||||
@ -194,7 +239,7 @@ 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)
|
||||
* ``temp-format`` can be either ``C`` (Celsius) or ``F`` (Fahrenheit)
|
||||
|
||||
.. code-block::
|
||||
|
||||
@ -223,7 +268,6 @@ Payload should be in standard format used for posting a form. Such as as: `id=va
|
||||
http-push-h2=
|
||||
http-push2-h1=
|
||||
http-push2-h2=
|
||||
brewfather-push=
|
||||
influxdb2-push=http://192.168.1.50:8086
|
||||
influxdb2-org=
|
||||
influxdb2-bucket=
|
||||
@ -282,8 +326,8 @@ 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)
|
||||
* ``a1``-``a10`` are the angles/tilt readings (up to 10 are currently supported)
|
||||
* ``g1``-``g10`` are the corresponding gravity readings (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.
|
||||
|
||||
@ -295,18 +339,28 @@ Payload should be in standard format used for posting a form. Such as as: `id=va
|
||||
a3=58
|
||||
a4=0
|
||||
a5=0
|
||||
a6=0
|
||||
a7=0
|
||||
a8=0
|
||||
a9=0
|
||||
a19=0
|
||||
g1=1.000
|
||||
g2=1.053
|
||||
g3=1.062
|
||||
g4=1
|
||||
g5=1
|
||||
g6=1
|
||||
g7=1
|
||||
g8=1
|
||||
g9=1
|
||||
g10=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.
|
||||
present or the API call will fail. You only need to include the parameters you want to change.
|
||||
|
||||
The requests package converts the json to standard form post format.
|
||||
|
||||
@ -315,7 +369,7 @@ The requests package converts the json to standard form post format.
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
host = "192.168.1.1" # IP address (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 ):
|
||||
@ -346,7 +400,6 @@ The requests package converts the json to standard form post format.
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": ""
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
@ -372,7 +425,7 @@ The requests package converts the json to standard form post format.
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ble": "red", # Enable ble on esp32
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
"ota-url": "" # if the device should search for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
@ -383,10 +436,20 @@ The requests package converts the json to standard form post format.
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"a6": 0,
|
||||
"a7": 0,
|
||||
"a8": 0,
|
||||
"a9": 0,
|
||||
"a10": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
"g5": 1,
|
||||
"g6": 1,
|
||||
"g7": 1,
|
||||
"g8": 1,
|
||||
"g9": 1,
|
||||
"g10": 1
|
||||
}
|
||||
set_config( url, json )
|
||||
|
@ -29,8 +29,9 @@ 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.
|
||||
* gravity-perf; Standard release but contains code for measuring performance.
|
||||
* gravity32-release: Version for ESP32.
|
||||
* gravity32-perf: Version for ESP32 but contains code for measuring performance.
|
||||
|
||||
.. 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,
|
||||
@ -68,8 +69,6 @@ Source structure
|
||||
- Source code for software
|
||||
* - /src_docs
|
||||
- Source code for documentation
|
||||
* - /stl
|
||||
- 3d models
|
||||
* - /test
|
||||
- Test data for developing html files
|
||||
|
||||
|
@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.9.0'
|
||||
release = '1.1.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -23,22 +23,23 @@ URL: (http://gravmon.local)
|
||||
: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*.
|
||||
Configuration is accessed by entering the URL for the device, this will be the mDNS name *device.local* or the IP address. The following chapter assumes the device name is *gravmon*.
|
||||
|
||||
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if
|
||||
you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
|
||||
|
||||
You can also view the average time a gravity measurement takes. Under optimal setting this should be around 1.5 - 2.0 seconds. If this is higher than 2 seconds this is most likley connected to slow wifi
|
||||
You can also view the average time a gravity measurement takes. Under optimal setting this should be around 1.5 - 2.0 seconds. If this is higher than 2 seconds this is most likely connected to slow wifi
|
||||
connection. It will show 0 if data has not been collected yet.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you are connected to the device via a serial console (speed: 115200) you can see the connection sequence and get the Unique ID and IP adress from there.
|
||||
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 address from there.
|
||||
|
||||
.. tip::
|
||||
|
||||
The button `view error log` will show the last 15 errors on the device. This can be useful for checking errors without
|
||||
the need to connect to the serial port or to check what errors has occured while in `gravity mode`.
|
||||
The button `view error log` will show the last error messages on the device. This can be useful for checking errors without
|
||||
the need to connect to the serial port or to check what errors has occurred while in `gravity mode`. From v1.1 it will also detect
|
||||
any abnormal restarts or crashes and record these in the logfile.
|
||||
|
||||
|
||||
Configuration
|
||||
@ -55,11 +56,12 @@ Device Setting
|
||||
|
||||
* **Device name:**
|
||||
|
||||
This is unique name for the device. It will be used in pushing data as well as mDNS name on the network (<name>.local)
|
||||
This is unique name for the device. It will be used in pushing data as well as mDNS name on the network (<name>.local).
|
||||
The limitation is 63 chars but using long names might break endpoints that data is sent to if they have other limitations.
|
||||
|
||||
* **Temperature format:**
|
||||
|
||||
Choose between Celsius and Farenheight when displaying temperature.
|
||||
Choose between Celsius and Fahrenheit when displaying temperature.
|
||||
|
||||
* **Interval:**
|
||||
|
||||
@ -92,7 +94,7 @@ 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.
|
||||
battery life, so the decision is to prioritize battery life over security. The data transmitted is not really that sensitive anyway so I believe this is a good balance.
|
||||
|
||||
* **HTTP 1 (POST):**
|
||||
|
||||
@ -121,19 +123,13 @@ If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
The token is included in the default format for the HTTP GET url but can be used for any of the formats. For HTTP GET use can use this for an authorization token with for instance ubidots or blynk http api.
|
||||
|
||||
* **Brewfather URL:**
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
|
||||
SSL is not supported for this target.
|
||||
|
||||
* **HTTP Headers**
|
||||
|
||||
.. image:: images/config-popup1.png
|
||||
:width: 300
|
||||
:alt: HTTP Headers
|
||||
|
||||
You can define 2 http headers per push target. This is available via a pop-up window but dont forget
|
||||
You can define 2 http headers per push target. This is available via a pop-up window but don't 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.
|
||||
|
||||
@ -201,14 +197,14 @@ 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 format can be either `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
|
||||
The gravity formula accepts to parameters, **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:**
|
||||
@ -220,7 +216,7 @@ build this into the gravity formula.
|
||||
|
||||
This formula assumes that the calibration has been done at 20°C / 68°F.
|
||||
|
||||
Formula used in temperature correction.
|
||||
Formula used in temperature correction. The calibration temperature can be changed under advanced settings.
|
||||
|
||||
::
|
||||
|
||||
@ -237,7 +233,13 @@ Hardware Settings
|
||||
|
||||
* **Voltage factor:**
|
||||
|
||||
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
|
||||
Factor used to calculate the battery voltage. If you get a too low/high voltage you can adjust this value.
|
||||
|
||||
* **Config voltage:**
|
||||
|
||||
Defines the level of voltage when the device should enter config mode due to charging. This might vary between different battery manufacturers.
|
||||
If you don't what the device to go into configuration mode when charging, set this to 6V. This was added since different batteries have different
|
||||
voltages when fully charged.
|
||||
|
||||
* **Temperature correction:**
|
||||
|
||||
@ -248,9 +250,16 @@ when the device starts. So changing this will not take affect until the device i
|
||||
|
||||
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
|
||||
device is activated, since the gyro should be cool this is reflecting the surrounding temperature. After it has
|
||||
been running the value would be totally off.
|
||||
|
||||
* **Enable storage mode when placed on cap**
|
||||
|
||||
When place on the cap (<5 degree tilt) the device will go into deep sleep forever (until reset). In order to wake it
|
||||
up you need to do a reset. One option is to attach a magnetic reed switch (default open) to the reset pin and use a
|
||||
magnet to force a reset without opening the tube. The reed switch is typically an electronic component of 14 mm
|
||||
long encapsulated in a small glass tube. See hardware section for more information, :ref:`hardware`.
|
||||
|
||||
* **Bluetooth: (Only ESP32)**
|
||||
|
||||
If the build is using an ESP32 then you can send data over BLE, simulating a Tilt device. Choose the color that you want the device to simulate.
|
||||
@ -281,3 +290,56 @@ This option gives you the possibility to install an new version of the firmware
|
||||
:alt: Update firmware
|
||||
|
||||
|
||||
Advanced Settings
|
||||
+++++++++++++++++
|
||||
|
||||
.. image:: images/config5.png
|
||||
:width: 800
|
||||
:alt: Advanced Settings
|
||||
|
||||
.. warning::
|
||||
|
||||
Change these parameters with caution. The wrong values might cause the device to become unresponsive.
|
||||
|
||||
|
||||
* **Gyro reads:**
|
||||
|
||||
This defines how many gyro reads will be done before an angle is calculated. More reads will give better accuracy and also allow detection of
|
||||
movement. Too many reads will take time and affect battery life. 50 takes about 800 ms to execute.
|
||||
|
||||
* **Gyro moving threshold:**
|
||||
|
||||
This is the max amount of deviation allowed for a stable reading.
|
||||
|
||||
* **Formula deviation:**
|
||||
|
||||
This is the maximum deviation on the formula allowed for it to be accepted. Once the formula has been derived it will be validated against the supplied
|
||||
data and of the deviation on any point is bigger the formula will be rejected.
|
||||
|
||||
* **Ignore angles below water:**
|
||||
|
||||
If this option is checked any angles below that of SG 1 will be discarded as invalid and never sent to any server. Default = off.
|
||||
|
||||
* **Gravity calibration temp**
|
||||
|
||||
This option allows you to set the correction temperature used in the automatic temperature gravity adjustment formula. Standard is 20C.
|
||||
|
||||
* **DS18B20 Resolution:**
|
||||
|
||||
Define the resolution used on the temp sensor. 9 bits is default and will give an accuracy of 0.5C, 12 bits will give an accuracy of 0.0625C but will also
|
||||
take longer time to measure..
|
||||
|
||||
* **Wifi connect timeout:**
|
||||
|
||||
This is the amount of time allowed for a wifi connect.
|
||||
|
||||
* **Wifi portal timeout:**
|
||||
|
||||
If the wifi portal is triggered (can be triggered by reset) then this is the amount of time allowed before it exists again.
|
||||
|
||||
* **Skip Interval (...):**
|
||||
|
||||
These options allow the user to have variable push intervals for the different endpoints. 0 means that every wakeup will send data to that endpoint. If you enter another number then that defines how many sleep cycles will be skipped.
|
||||
|
||||
If the sleep interval is 300s and MQTT is set to 0 and HTTP1 is set to 2 then MQTT will be sent every 300s while HTTP1 would be sent 900s. This is great if you want to send data to a local mqtt server often but brewfather will only
|
||||
accept data every 15 min.
|
||||
|
@ -1,5 +1,33 @@
|
||||
Contributing
|
||||
############
|
||||
|
||||
Anyone is welcome to contribute to this project or create their own variant of it. I would appreciate a PR if your feature would be of benefit other users.
|
||||
|
||||
In order to keep the source code in good condition I use `pre-commit <https://pre-commit.com/>`_ to validate and format the code using their standards for C++/C.
|
||||
|
||||
.. note::
|
||||
|
||||
If you are using Windows as a base platform I would suggest that you install pre-commit under wsl (Windows Subsystem for Windows) and run it from there, I have found
|
||||
that this approach works fine.
|
||||
|
||||
|
||||
The following command will run pre-commit on all the source files. Assuming you are in the project directory.
|
||||
|
||||
.. code-block::
|
||||
|
||||
pre-commit run --files src/*
|
||||
|
||||
|
||||
Design goals
|
||||
------------
|
||||
|
||||
This section is under construction.
|
||||
My goals with this software has been the following:
|
||||
|
||||
* Create an open software for the excellent iSpindle hardware platform that is open and available for anyone.
|
||||
* Add user requested features that have not made it into the iSpindle project.
|
||||
* Focus on long battery life and stability.
|
||||
* Explore new technologies and create the next generation gravity monitoring for home brewers.
|
||||
|
||||
I will do my best to assist users and respond to new features, pr and suggestions. But keep in mind I'm doing this on my spare time.
|
||||
|
||||
Regards, Magnus
|
||||
|
@ -26,7 +26,7 @@ This is the format used for standard http posts.
|
||||
"gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
"RSSI": -12,
|
||||
|
||||
"corr-gravity": 1.0050,
|
||||
"gravity-unit": "G",
|
||||
@ -40,39 +40,20 @@ This is the format template used to create the json above.
|
||||
{
|
||||
"name" : "${mdns}",
|
||||
"ID": "${id}",
|
||||
"token" : "gravmon",
|
||||
"token" : "${token}",
|
||||
"interval": ${sleep-interval},
|
||||
"temperature": ${temp},
|
||||
"temp_units": "${temp-unit}",
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"battery": ${battery},
|
||||
"rssi": ${rssi},
|
||||
"RSSI": ${rssi},
|
||||
"corr-gravity": ${corr-gravity},
|
||||
"gravity-unit": "${gravity-unit}",
|
||||
"run-time": ${run-time}
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
This is the format for Brewfather. See: `Brewfather API docs <https://docs.brewfather.app/integrations/custom-stream>`_
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"temp": 20.5,
|
||||
"temp_unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G",
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
HTTP Get
|
||||
@ -148,34 +129,18 @@ This is the format template used to create the json above.
|
||||
ispindel/${mdns}/interval:${sleep-interval}|
|
||||
ispindel/${mdns}/RSSI:${rssi}|
|
||||
|
||||
This is a format template that is compatible with v0.6. Just replace the `topic` with the topic you want to post data to.
|
||||
|
||||
.. code-block::
|
||||
|
||||
topic:{"name":"gravmon", "ID":"${id}", "token":"gravmon", "interval": ${sleep-interval},
|
||||
"temperature": ${temp}, "temp_units": "${temp-unit}", "gravity":${gravity},
|
||||
"angle": ${angle}, "battery":${battery}, "rssi": ${rssi}, "corr-gravity":${corr-gravity},
|
||||
"gravity-unit": "${gravity-unit}", "run-time": ${run-time}}|
|
||||
|
||||
|
||||
version.json
|
||||
============
|
||||
|
||||
Contents version.json. The version is used by the device to check if the this version is newer. The html files will also be downloaded if the are present on the server. This way it's easy to
|
||||
upgrade to a version that serve the html files from the file system. If they dont exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
upgrade to a version that serve the html files from the file system. If they don't exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
they can be uploaded manually afterwards.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"project":"gravmon",
|
||||
"version":"0.7.0",
|
||||
"html": [
|
||||
"index.min.htm",
|
||||
"test.min.htm",
|
||||
"config.min.htm",
|
||||
"format.min.htm",
|
||||
"calibration.min.htm",
|
||||
"about.min.htm"
|
||||
]
|
||||
"version":"1.0.0",
|
||||
"html": [ ]
|
||||
}
|
||||
|
@ -7,10 +7,33 @@ Create formula
|
||||
:width: 800
|
||||
:alt: Formula data
|
||||
|
||||
Here you can enter up to 5 values (angles + gravity) that is then used to create the formula. Angles equal to zero will be regarded as empty even if there is a gravity reading.
|
||||
Here you can enter up to 10 values (angles + gravity) that is then used to create the formula. Angles equal to zero will be regarded as empty even if there is a gravity reading.
|
||||
|
||||
When you submit the values the device will try create a formula with increasing level of complexity. It will start
|
||||
with a order 2 formula and then try 3 and 4.
|
||||
|
||||
Once the formula has been created it will validate the formula against the supplied angles/gravity and if there is a too
|
||||
high difference, it will fail. You can adjust the sensitivity under advanced settings if you have issues.
|
||||
|
||||
Under the Error Log you will also find hints to what problem the formula creator encountered. Here is an example:
|
||||
|
||||
`CALC: Validation failed on angle 33.430000, deviation too large 5.86, formula order 4`
|
||||
|
||||
`CALC: Validation failed on angle 33.430000, deviation too large 3.14, formula order 2`
|
||||
|
||||
This means that the angle 33.43 had a deviation of 5.8 SG and since the default threshold is 3, it will fail. You
|
||||
can also see that it has failed on that point in both a order 2 and 4 formula.
|
||||
|
||||
.. image:: images/qa_1.png
|
||||
:width: 400
|
||||
:alt: Example of deviating value
|
||||
|
||||
So in this case you can either increase the threshold or remove the angle that has an issue. You can also
|
||||
use the graph on the calibration page to identify angles that is probably not correct.
|
||||
|
||||
.. image:: images/formula2.png
|
||||
:width: 800
|
||||
:alt: Formula graph
|
||||
|
||||
Once the formula is created a graph over the entered values and a simulation of the formula will give you a nice overview on how the formula will work.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
.. _functionallity:
|
||||
.. _functionality:
|
||||
|
||||
Functionallity
|
||||
Functionality
|
||||
==============
|
||||
|
||||
The main features
|
||||
@ -33,10 +33,10 @@ The main features
|
||||
|
||||
Currently the device supports the following endpoints.
|
||||
|
||||
* http or https
|
||||
* influxdb v2
|
||||
* http (ssl optional)
|
||||
* influxdb v2 (ssl optional)
|
||||
* MQTT (ssl optional)
|
||||
* Brewfather
|
||||
* MQTT
|
||||
* Home Assistant
|
||||
* Brew Spy
|
||||
* Brewers Friend
|
||||
@ -65,12 +65,10 @@ The main features
|
||||
Another big difference is that this software can create the gravity formula in the device, just enter the
|
||||
angle/gravity data that you have collected. You will also see a graph simulating how the formula would work.
|
||||
|
||||
Currently the device can handle 5 data points which should be enough to get a accurate formula. At least 3 data points
|
||||
Currently the device can handle 10 data points which should be enough to get a accurate formula. At least 3 data points
|
||||
is needed to get an accurate formula.
|
||||
|
||||
If there is a need for more data points, raise a comment on github.
|
||||
|
||||
* **Customize the data format beeing sent to push targets**
|
||||
* **Customize the data format being sent to push targets**
|
||||
|
||||
In order to make it easier to support more targets there is a built in format editor that can be used to
|
||||
customize the data that is to be sent. This way you can easily adapt the software to new targets without coding.
|
||||
@ -81,12 +79,13 @@ The main features
|
||||
* **Automatic temperature adjustment of gravity reading**
|
||||
|
||||
If you want to correct gravity based on beer temperature you can do this in the formula but here is a nice
|
||||
feature that can correct the gravity as a second step making this independant of the formula.
|
||||
feature that can correct the gravity as a second step making this independent of the formula.
|
||||
|
||||
* **OTA support from webserver**
|
||||
|
||||
When starting up in configuration mode the device will check for a software update from a webserver. This is an easily
|
||||
way to keep the software up to date. In the future I might add a hosted endpoint for providing updates.
|
||||
way to keep the software up to date. In the future I might add a hosted endpoint for providing updates. OTA can also be
|
||||
done over a SSL connection.
|
||||
|
||||
* **DS18B20 temperature adjustments**
|
||||
|
||||
@ -95,13 +94,14 @@ The main features
|
||||
|
||||
* **Gyro Movement**
|
||||
|
||||
The software will detect if the gyro is moving and if this is the case it will go back to sleep for 60seconds.
|
||||
This way we should avoid faulty measurements and peaks in the graphs.
|
||||
The software will detect if the gyro is moving and if this is the case it will go back to sleep for 60 seconds.
|
||||
This way we should avoid faulty measurements and peaks in the graphs.
|
||||
|
||||
* **WIFI connection issues**
|
||||
|
||||
The software will not wait indefiently for a wifi connection. If it takes longer than 20 seconds to connect then
|
||||
the device will go into deep sleep for 60 seoncds and then retry later. This to conserve batter as much as possible.
|
||||
The software will not wait indefinitely for a wifi connection. If it takes longer than 20 seconds to connect then
|
||||
the device will try the secondary wifi configuration, and that also fails it will go into deep sleep for 60 seconds and then
|
||||
retry later. This to conserve batter as much as possible.
|
||||
|
||||
* **Use gyro temperature sensor**
|
||||
|
||||
@ -119,7 +119,7 @@ The main features
|
||||
:width: 800
|
||||
:alt: Gyro temp vs DS18B20
|
||||
|
||||
* **Celsius or Farenheigt**
|
||||
* **Celsius or Fahrenheit**
|
||||
|
||||
You can switch between different temperature formats. GravityMon will always use C for it's internal calculations and
|
||||
convert to F when displayed.
|
||||
@ -131,15 +131,15 @@ The main features
|
||||
|
||||
* **Stable gyro data**
|
||||
|
||||
The device will read the gyro 50 times to get an accurate reading. If the standad deviation is to high it will not
|
||||
use the data since this is inacurate and the device is probably moving, probably do to active fermentation or movement of
|
||||
The device will read the gyro 50 times to get an accurate reading. If the standard deviation is to high it will not
|
||||
use the data since this is inaccurate and the device is probably moving, probably do to active fermentation or movement of
|
||||
fermentation vessel. This sequence takes 900 ms seconds to execute and besides wifi connection this is what consumes the most
|
||||
battery. With more testing this might be changes to either speed up or provide more stable readings.
|
||||
|
||||
* **Performance measurements**
|
||||
|
||||
I've also create a small library to measure execution code in some areas of the code that i know is time consuming. This
|
||||
way I can find a good balance between performace and quality. This is a lot of help trying to figure out where bottlenecks
|
||||
way I can find a good balance between performance and quality. This is a lot of help trying to figure out where bottlenecks
|
||||
are in the code and where to put optimization efforts. Examples of real measurements:
|
||||
|
||||
* Reading the gyro: 885 ms
|
||||
@ -160,7 +160,7 @@ The main features
|
||||
Battery life
|
||||
------------
|
||||
|
||||
The long term battery test has now been completed. Using a 2200 mA battery and sending data every 5 minutes to a local server on my network. The battery lasted 47 days which is excellet battery life.
|
||||
The long term battery test has now been completed. Using a 2200 mA battery and sending data every 5 minutes to a local server on my network. The battery lasted 47 days which is excellent battery life.
|
||||
|
||||
In another test I had a device running with an sleep interval of only 30s with ok wifi connection. The device lasted 12 days which i think is excellent considering the short sleep interval.
|
||||
|
||||
@ -173,7 +173,7 @@ Performance
|
||||
Since I have the possibility to measure the performance of different function in the code this is what I have been able to gather.
|
||||
|
||||
The typical runtime in a measurement cycle is approx 2 seconds and in some cases it can take up to 6-8 seconds but this is mainly related to establishing the WIFI connection. So stable wifi is
|
||||
essential for long batterylife. Out of the 2 seconds of run-time the major time is spent on gyro readings (1.3s) and temperature measurements of (0.6s) so using the gyro sensor for measureing
|
||||
essential for long battery life. Out of the 2 seconds of run-time the major time is spent on gyro readings (1.3s) and temperature measurements of (0.6s) so using the gyro sensor for measuring
|
||||
temperature would reduce the total runtime with 25%. Sending data over http takes less than 100ms (on my local network) so this is not drawing much power.
|
||||
|
||||
The image below shows how the run-time varies over time. The pink line is the wifi connection time and this is why the time varies. The orange is the total runtime for the awake period.
|
@ -3,7 +3,7 @@
|
||||
Hardware
|
||||
########
|
||||
|
||||
There are lots of resouces out there on how to build the hardware for an iSpindle so I will not go into details on that part. Here are two of my builds using the iSpindle PCB v4.
|
||||
There are lots of resources out there on how to build the hardware for an iSpindle so I will not go into details on that part. Here are two of my builds using the iSpindle PCB v4.
|
||||
|
||||
.. image:: images/ispindel.jpg
|
||||
:width: 500
|
||||
@ -24,3 +24,43 @@ need to adjust the voltage factor so the battery reading is correct.
|
||||
:width: 500
|
||||
:alt: Mounting esp32
|
||||
|
||||
Schema for esp8266 build
|
||||
------------------------
|
||||
|
||||
.. image:: images/schema_esp8266.png
|
||||
:width: 700
|
||||
:alt: Schema esp8266
|
||||
|
||||
Schema for esp32 build
|
||||
----------------------
|
||||
|
||||
.. note::
|
||||
This schema assumes that an ESP32 D1 Mini (pin compatible with ESP8266 D1 Mini is used)
|
||||
The ESP32 has two rows of pins but only the inner row is used. The main difference is the added resistor R3 so we
|
||||
get a voltage divider for measuring battery. The ESP8266 has a built in resistor thats not visible on the schema.
|
||||
|
||||
|
||||
.. image:: images/schema_esp32.png
|
||||
:width: 700
|
||||
:alt: Schema esp32
|
||||
|
||||
Modifying with reed switch
|
||||
--------------------------
|
||||
|
||||
A reed switch is a switch that reacts to magnetic fields. The ones I have tested are normally open and close in proximity to
|
||||
a magnet.
|
||||
|
||||
.. image:: images/reed.jpg
|
||||
:width: 400
|
||||
:alt: Reed switch
|
||||
|
||||
If this is connected to the reset button a magnet can be used to trigger a reset of the device. The image below shows how
|
||||
I mounted the iSPINDLE PCB v4.0 just under the cap. The lower red circle shows the reset connection point for the reed switch.
|
||||
|
||||
The reed switch is the glass tube visible under the esp8266.
|
||||
|
||||
.. image:: images/reed_build.jpg
|
||||
:width: 400
|
||||
:alt: Reed build
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 20 KiB |
BIN
src_docs/source/images/config5.png
Normal file
After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 4.5 MiB After Width: | Height: | Size: 7.4 MiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 71 KiB |
BIN
src_docs/source/images/qa_1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src_docs/source/images/reed.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src_docs/source/images/reed_build.jpg
Normal file
After Width: | Height: | Size: 574 KiB |
BIN
src_docs/source/images/schema_esp32.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
src_docs/source/images/schema_esp8266.png
Normal file
After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 73 KiB |
@ -7,63 +7,90 @@ Welcome to GravityMon's documentation!
|
||||
######################################
|
||||
|
||||
.. note::
|
||||
This documentation reflects **v0.9**. Last updated 2022-04-23
|
||||
This documentation reflects **v1.1**. Last updated 2022-08-14
|
||||
|
||||
User interface overview
|
||||
-----------------------
|
||||
|
||||
This animation shows how the user interface is structured, it reflects an older version but the structure is the same.
|
||||
|
||||
.. image:: images/gravitymon.gif
|
||||
:width: 800
|
||||
:alt: User Inteface Walkthrough
|
||||
|
||||
.. _main_features:
|
||||
|
||||
Main features
|
||||
-------------
|
||||
|
||||
* Operates in two modes gravity monitoring and configuration mode
|
||||
* Gravity mode is comparable to how the iSpindle works when collecting data
|
||||
* Configuration mode has a modern HTML5 based web UI. No need to start the access point to change settings
|
||||
* Offloading some of the functionality to run in the web browser, this allows for more advanced features.
|
||||
* REST API to enable scripted configuration
|
||||
* Send data to multiple endpoints and services at once (2xHTTP POST, HTTP GET, MQTT, INFLUXDB2)
|
||||
* Directly test all endpoints from user interface with feedback to simplify troubleshooting
|
||||
* Complete format customization for all endpoints using templates (don't really need to change the software to support new services)
|
||||
* Setup guides for how to send data to many popular services. Currently 10+ are documented
|
||||
* Automatic temperature adjustment of gravity (just tick a checkbox)
|
||||
* OTA support from webserver
|
||||
* Firmware update via web interface
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and let GravityMon creates a formula
|
||||
* Visual graph showing how formula will be interpreted based on entered values
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* SSL support in all endpoints (no certificate validation due to limitations on esp8266).
|
||||
* Built in performance measurements (used to optimize code)
|
||||
* Storage mode when placed on cap (indefinite sleep)
|
||||
* Customize various hardware parameters to optimize device functionality.
|
||||
|
||||
For a complete breakdown see the :ref:`functionality`
|
||||
|
||||
.. note::
|
||||
If you are missing some feature, please reach out on `Github <https://github.com/mp-se/gravitymon/discussions>`_ or `homebrewtalk.com <https://www.homebrewtalk.com/threads/replacement-firmware-for-ispindel-gravitymon.698058/>`_
|
||||
|
||||
What is GravityMon?
|
||||
--------------------
|
||||
|
||||
GravityMon is used to measure gravity and temperature during fermentation of beer and report the progress. The graph below is
|
||||
an example on how the fermentation process can be tracked. This is from my last brew that was over on a few days. The graph is rendered using
|
||||
Fermentrack.
|
||||
an example on how the fermentation process can be tracked. The graph has been rendered using Fermentrack.
|
||||
|
||||
.. image:: images/fermentation.png
|
||||
:width: 500
|
||||
:alt: Example fermentation
|
||||
|
||||
GravityMon is a replacement firmware for the iSpindle and uses the same hardware configuration and is 100% compatible. It
|
||||
implements a lot of the features that has been requested in the orginal iSpindle project but has been rejected for
|
||||
implements a lot of the features that has been requested in the original iSpindle project but never implemented for
|
||||
various reasons. Here is a list of :ref:`main_features`.
|
||||
|
||||
From v0.9 the firmware now supports a iSpindle build based on an ESP32 d1 mini (pin compatible with esp8266). See :ref:`hardware`.
|
||||
From v0.9 the firmware also supports a iSpindle built using an ESP32 d1 mini (pin compatible with esp8266). Currently this is an experimental
|
||||
version but since it has more memory and processing capacity it could support more functions. See :ref:`hardware`.
|
||||
|
||||
I started GravityMon because I like to create software and wanted to do some low level programming. I had done a few
|
||||
I started GravityMon because I like to create software and wanted to do some microcontroller programming. I had done a few
|
||||
projects based on esp8266 and also started to brew beer so this combination was quite natural.
|
||||
|
||||
The hardware design comes from the fantastic iSpindle project so that is not covered in this documentation. For more
|
||||
information on this topic and function please visit `iSpindel Homepage <https://www.ispindel.de>`_ .
|
||||
|
||||
My approach to this software is a little different from that the original iSpindle firmware. The github repository
|
||||
can be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
My approach to this software is a little different from that the original iSpindle firmware as can be seen in the list of features.
|
||||
|
||||
The github repository can be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
|
||||
.. note::
|
||||
I dont take responsibility for any errors or issues caused by the software. The software is provided as-is. I will however
|
||||
I don't take responsibility for any errors or issues caused by the software. The software is provided as-is. I will however
|
||||
try my best to fix issues that might occur.
|
||||
|
||||
I have tested this software over the last year on 20+ brews with good results.
|
||||
I have tested this software on 40+ brews with good results.
|
||||
|
||||
.. _main_features:
|
||||
|
||||
Main features:
|
||||
--------------
|
||||
Documentation for older versions
|
||||
--------------------------------
|
||||
|
||||
* Operates in two modes gravity monitoring and configuration mode (simplify calibration). Gravity mode
|
||||
is comparable to how the iSpindle works.
|
||||
* Modern web based UI when in configuration mode. No need to start the access point changing settings.
|
||||
* REST API to enable scripted configuration
|
||||
* Send data to multiple endpoints and services at once
|
||||
* Setup guides for how to send data to many popular services. Currently 8+ are documented.
|
||||
* Automatic temperature adjustment of gravity reading
|
||||
* OTA support from webserver
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and
|
||||
let GravityMon create the formula.
|
||||
* Visual graph showing how formula will be interpreted based on entered values
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* SSL support in standard HTTP and MQTT connections.
|
||||
* Option to customize data posted to endpoints using template from the UI.
|
||||
* Built in performance measurements (used to optimise code)
|
||||
* Docs for: `v1.0 <https://mp-se.github.io/gravitymon/v1.0/index.html>`_
|
||||
* Docs for: `v0.9 <https://mp-se.github.io/gravitymon/v0.9/index.html>`_
|
||||
|
||||
For a complete breakdown see the :ref:`functionallity`
|
||||
|
||||
Software architecture
|
||||
---------------------
|
||||
|
||||
This is a simple overview of the different components that the software contains. The green ones are only active during `configuration mode` in
|
||||
order to save battery.
|
||||
@ -75,8 +102,8 @@ order to save battery.
|
||||
|
||||
Credits to
|
||||
----------
|
||||
Ideas to some of these functions have been picked up from disucssions in the iSpindle forums. This software uses
|
||||
the following libraries and without these this would have been much more difficult to acheive:
|
||||
Ideas to some of these functions have been picked up from discussions in the iSpindle forums. This software uses
|
||||
the following libraries and without these this would have been much more difficult to achieve:
|
||||
|
||||
* https://github.com/jrowberg/i2cdevlib
|
||||
|
||||
@ -84,7 +111,7 @@ the following libraries and without these this would have been much more difficu
|
||||
|
||||
* https://github.com/codeplea/tinyexpr
|
||||
|
||||
Proccess the gravity formula and calculate the gravity and various corrections.
|
||||
Process the gravity formula and calculate the gravity and various corrections.
|
||||
|
||||
* https://github.com/graphitemaster/incbin
|
||||
|
||||
@ -120,7 +147,7 @@ the following libraries and without these this would have been much more difficu
|
||||
|
||||
* https://github.com/256dpi/arduino-mqtt
|
||||
|
||||
Library for sending data to mqtt based on lightweight mqtt implemenentation.
|
||||
Library for sending data to mqtt based on lightweight mqtt implementation.
|
||||
|
||||
* https://graphjs.com/
|
||||
|
||||
@ -148,7 +175,7 @@ the following libraries and without these this would have been much more difficu
|
||||
|
||||
intro
|
||||
releases
|
||||
functionallity
|
||||
functionality
|
||||
installation
|
||||
configuration
|
||||
troubleshooting
|
||||
|
@ -12,8 +12,9 @@ You have these 3 options for flashing this firmware.
|
||||
Brewflasher
|
||||
===========
|
||||
|
||||
The prefered option for flashing GravityMon is using BrewFlasher, its a tools that support many brewing related firmwares for ESP8266 and ESP32. This works
|
||||
on both Windows and Mac. You can download the latest version from here: `Brewflasher <https://www.brewflasher.com/>`_
|
||||
The preferred option for flashing GravityMon is using BrewFlasher, its a tools that support many brewing related firmwares for ESP8266 and ESP32. This works
|
||||
on both Windows and Mac. You can download the latest version from here: `Brewflasher <https://www.brewflasher.com/>`_ there is also a web based version
|
||||
available here `Brewflasher WEB <https://web.brewflasher.com/>`_.
|
||||
|
||||
.. image:: images/brewflasher.png
|
||||
:width: 600
|
||||
@ -26,7 +27,7 @@ In the /bin directory you will find 3 different firmware builds;
|
||||
|
||||
* **firmware.bin**
|
||||
|
||||
This is the standard release build (prefered version)
|
||||
This is the standard release build (preferred version)
|
||||
|
||||
* **firmware-perf.bin**
|
||||
|
||||
@ -34,7 +35,8 @@ In the /bin directory you will find 3 different firmware builds;
|
||||
|
||||
* **firmware32.bin**
|
||||
|
||||
This is the standard release build for an ESP32 variant
|
||||
This is the standard release build for an ESP32 variant. When flashing an ESP32 you also need the **partition32.bin** file that outlines the flash memory structure. Due to
|
||||
the size of the firmware we are using a custom partition setup.
|
||||
|
||||
In these versions all the html files are embedded in the binaries. The file system is currently only used for storing
|
||||
the configuration file.
|
||||
@ -63,7 +65,7 @@ iSpindel
|
||||
========
|
||||
|
||||
If you already have the device flashed with iSpindel firmware you can go into the configuration mode where you will find
|
||||
an option for updating firmware. The option is under the maintence meny.
|
||||
an option for updating firmware. The option is under the maintenance menu.
|
||||
|
||||
Select the esp8266 version of the firmware called firmware.bin and press upload.
|
||||
|
||||
@ -90,7 +92,6 @@ browser and select the firmware.bin file that corresponds to the version you wan
|
||||
``http://<device_name>/firmware.htm``
|
||||
|
||||
|
||||
|
||||
.. _serial_monitoring:
|
||||
|
||||
Serial Monitoring
|
||||
@ -112,14 +113,22 @@ Configuring WIFI
|
||||
When the device is flashed it will need to have WIFI configuration in order to work. If you have used other software on
|
||||
the device its possible that wifi settings already exist.
|
||||
|
||||
If this is not configured in the device it will create an wirless access point called `GravMon`. The default password is `password`.
|
||||
If this is not configured in the device it will create an wireless access point called `GravMon`. The default password is `password`.
|
||||
|
||||
Connect to this AP and enter the SSID and password you want to use. If the web page dont open automatically you can enter the following adress
|
||||
Connect to this AP and enter the SSID and password you want to use. If the web page don't open automatically you can enter the following address
|
||||
in the browser: **http://192.168.4.1**
|
||||
|
||||
Before pressing save on the network infomration, make a note of the devicename that is shown on the screen, this will be the name that is used
|
||||
Before pressing save on the network information, make a note of the devicename that is shown on the screen, this will be the name that is used
|
||||
in the next step to access the configuration pages. The link would look like this: **http://gravitymon56EA34.local**
|
||||
|
||||
.. note::
|
||||
When selecting a SSID in the list this will be populated in both wifi fields. This is the behaviour of the wifi manager library that I'm using,
|
||||
in the future this is planned to be moved to the normal UI.
|
||||
|
||||
Under wifi settings you can define a primary and secondary wifi SSID. The secondary will be used in case the primary fails. If the secondary is
|
||||
successful then it will be used as primary. *The second wifi setting is optional and not needed.*
|
||||
|
||||
|
||||
.. image:: images/wifi.png
|
||||
:width: 200
|
||||
:alt: Wifi page
|
||||
@ -132,10 +141,10 @@ Finding the device adress
|
||||
|
||||
Once the wifi network settings have been added then the device will reboot and connect to your network. If the blue led is flashing then it's up and running and is ready to be configured.
|
||||
|
||||
If your computer supports mDNS the adress you saw before can be used in your web browser to connect to the device. Windows does not have the best support for mDNS so if you are having issues
|
||||
If your computer supports mDNS the address you saw before can be used in your web browser to connect to the device. Windows does not have the best support for mDNS so if you are having issues
|
||||
with finding the network name you can try the following:
|
||||
|
||||
* Check your wireless router for the IP adress and use that to connect instead, for example; http://192.168.1.56
|
||||
* Check your wireless router for the IP address and use that to connect instead, for example; http://192.168.1.56
|
||||
* Download an IP scanner / Port Scanner on your Windows computer or mobile device and use that to find what devices are listening on port 80.
|
||||
|
||||
Once you can access the user interface then proceed to the next step.
|
||||
|
@ -3,7 +3,7 @@
|
||||
Getting started
|
||||
===============
|
||||
|
||||
First you need a completed iSpindle hardware, there are several resouces around that topic so it
|
||||
First you need a completed iSpindle hardware, there are several resources around that topic so it
|
||||
will not be covered in this documentation. Please visit `iSpindel Homepage <https://www.ispindel.de>`_ for
|
||||
more information.
|
||||
|
||||
@ -19,11 +19,17 @@ Step 2 - Setup WIFI
|
||||
When the device starts up the first time it will first start an WIFI access point so that the WIFI Settings
|
||||
can be configured. The instructions for that can be found here :ref:`setup_wifi`
|
||||
|
||||
.. note::
|
||||
Since the user interface is built using modern frameworks the device requires access to the internet
|
||||
for the UI to render and data to be populated. The sites that are needed are; https://cdn.jsdelivr.net/npm/bootstrap
|
||||
and https://code.jquery.com
|
||||
|
||||
|
||||
Step 3 - Configuration
|
||||
----------------------
|
||||
|
||||
Once the device can connect to WIFI it will go into `configuration mode` and start a web server for
|
||||
doing the initial configuration. In order to access the device you will need to find its name or ip adress.
|
||||
doing the initial configuration. In order to access the device you will need to find its name or ip address.
|
||||
|
||||
It will broadcast a name like gravitymonXXXXXX.local over mDNS. Where the XXXXXX is the unique device id. You can
|
||||
find the name via an mDNS browser, check your router or connect the device to a serial monitor. On windows mDNS
|
||||
@ -44,7 +50,7 @@ Configuration - Device Settings - Gyro Calibration
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
You need to place the device on a flat surface and then press the
|
||||
calibrate button. It will take a few seconds for this to complete and the angle should be close to 90 degress. Without
|
||||
calibrate button. It will take a few seconds for this to complete and the angle should be close to 90 degrees. Without
|
||||
calibration the device will not go into gravity mode.
|
||||
|
||||
Configuration - Push Settings
|
||||
@ -59,7 +65,7 @@ previous calibration then you can add them here, if not follow the calibration g
|
||||
|
||||
There are several guides for how to calibrate the device (`iSpindle Calibration <https://www.ispindel.de/docs/Calibration_en.html>`_)
|
||||
|
||||
This will get the data points needed to create the formula, and the datapoints will be stored on the device so you can
|
||||
This will get the data points needed to create the formula, and the data points will be stored on the device so you can
|
||||
adjust them when needed.
|
||||
|
||||
Step 4 - Completed
|
||||
|