Compare commits
418 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
446cb61e1c | |||
2f01222582 | |||
fa3d68c321 | |||
0fe9bb146d | |||
d4df0dd272 | |||
c319afbe9f | |||
cf143c0e73 | |||
902d123a68 | |||
b359f3aba8 | |||
fb856dde75 | |||
c0f7cf2823 | |||
d208b11384 | |||
d83ef79165 | |||
c685c18b57 | |||
7a84042781 | |||
14a73b011e | |||
121eeea392 | |||
ed8dc68fc7 | |||
3d4c04333e | |||
5b267210d2 | |||
e9742888f8 | |||
5e1d2a73fa | |||
7eddf35b97 | |||
a648d54a14 | |||
e9229efe56 | |||
635d788ba6 | |||
2e3820ca73 | |||
d52615f8e3 | |||
786b8e9b19 | |||
10fa71d7ca | |||
aeda821396 | |||
3db3968e07 | |||
430f01943a | |||
ca97a586c1 | |||
5c80b780a0 | |||
2e42b86444 | |||
acb53bf611 | |||
5b99304d7f | |||
d94028a7b9 | |||
f4ca86e8ff | |||
2c9f5c72f2 | |||
f5aae4f2ea | |||
51daa23327 | |||
a5fb4f40b9 | |||
17e9b0d51b | |||
2877f91ef8 | |||
8dd509214b | |||
223ab7f81e | |||
b6ba01f6e0 | |||
75e9d178a3 | |||
e8740364cf | |||
53f1373432 | |||
40e7a37aa5 | |||
24b2446521 | |||
82f48604a2 | |||
f5b627616e | |||
dc4eb4f4a1 | |||
52785871b9 | |||
9c92bb9214 | |||
96295f161a | |||
50116e8b45 | |||
29b243f115 | |||
c779a45ea9 | |||
2d67a44ad0 | |||
8d44a5dcea | |||
3d3293138f | |||
cf3e683137 | |||
f2b926cce6 | |||
1494b2b3b2 | |||
474f987e73 | |||
294b7a7fdd | |||
fda5c6ff27 | |||
58144a5187 | |||
877afbc26a | |||
98a4c3650f | |||
dcbedf5899 | |||
4fff5ad185 | |||
d1f1e926e7 | |||
a20baa6b27 | |||
42ca66555a | |||
13d5280d76 | |||
ae4d5eb8a2 | |||
d6227b6dad | |||
07fefe41fb | |||
dab4d0ed22 | |||
faba3d7619 | |||
8637b0f72d | |||
169798e0eb | |||
04b2721c5d | |||
14909b241b | |||
f8e72a50e9 | |||
5d03a33253 | |||
65983e638a | |||
aae29786bb | |||
b28797a79b | |||
d4452e1d59 | |||
3717561466 | |||
4cad6f888f | |||
6e9d562977 | |||
3e6d698a40 | |||
dc70b250d8 | |||
276b311194 | |||
2609bf840c | |||
fe3ca247b9 | |||
37a42aa2a1 | |||
68dfacb07c | |||
a60abdbaa1 | |||
ef3cc5b523 | |||
99a57978fa | |||
a2aaeb3f84 | |||
2f8a324bfc | |||
16e91ec4f5 | |||
3407567568 | |||
83aa1b2202 | |||
6193f422e8 | |||
a657f698b8 | |||
3d497a1acc | |||
4e190af499 | |||
f07f845dfb | |||
f1936b6b0d | |||
aa4e3b5e8d | |||
7cafedd9bf | |||
1e44d9bd01 | |||
1bc3abc9f0 | |||
8d51c5ad12 | |||
d9c467d54f | |||
07e7cbee1c | |||
f5fcf42fbe | |||
036e10cd5d | |||
2d1317af0d | |||
21fba0481c | |||
6dfe5a80fd | |||
c681619be8 | |||
02cb91e918 | |||
8a597162ae | |||
f49f386569 | |||
008ad490a7 | |||
3533ee8dac | |||
2b9abda873 | |||
4bbb558c8b | |||
8ebbc6559f | |||
0af872e743 | |||
c20f9a534a | |||
10ce1fc245 | |||
b43874d151 | |||
1428bec3da | |||
f9791dd349 | |||
1a7f28413c | |||
d22309bb2e | |||
b901a12699 | |||
914b4125d8 | |||
95216ecc54 | |||
4d83bf8fce | |||
e125ca4a10 | |||
5880d3a6ba | |||
cda3a87dd9 | |||
4bcacea9d7 | |||
838d062eea | |||
77cdbf7649 | |||
f33a58cffe | |||
7ca536b216 | |||
f0ec352538 | |||
e336633c38 | |||
a130ebd67d | |||
4c789a8b37 | |||
7ab5f451f5 | |||
545f274a47 | |||
9bea54b703 | |||
35333469c7 | |||
a9e0d9290a | |||
e459ceb2fa | |||
2615debe35 | |||
6113a436b0 | |||
2d5158465f | |||
3af52b5464 | |||
c116d672d1 | |||
e1cc54d188 | |||
761d570d39 | |||
22ade61af8 | |||
928054458a | |||
2e67bd1d57 | |||
83d7aee944 | |||
8bcd27a076 | |||
0912d672fe | |||
01d7c8adb5 | |||
69076d5878 | |||
3d939ad733 | |||
f2112cb344 | |||
f88fb8241b | |||
6eed5f143b | |||
044bfcddad | |||
1fd3b1911d | |||
9727e87e33 | |||
3390ebc5ab | |||
e076de022c | |||
4e0980e814 | |||
b8959ae165 | |||
4d6b4b7fd6 | |||
64e582d0e5 | |||
5d0f02eb18 | |||
fbc1eb4e31 | |||
700f00f48d | |||
9a2f86fed7 | |||
63fd80e750 | |||
b106ebfa20 | |||
dc5979dd28 | |||
22d5e4fad7 | |||
ce361f66c8 | |||
2474306acb | |||
1d3cbbb3c2 | |||
0db586d744 | |||
a01c838b8f | |||
080820f10f | |||
1a9283b719 | |||
1d113e6941 | |||
49f166cc67 | |||
58a9966e6e | |||
357772dbaf | |||
1c0eb11133 | |||
7890f8096f | |||
68018329bb | |||
57bfdc1e87 | |||
d3a71da643 | |||
42c58fdcdf | |||
7b4e95b5ad | |||
767988a7c5 | |||
20771b3244 | |||
35f66e0458 | |||
37a1ca6058 | |||
ddb34e129d |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
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
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
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
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
|
||||
|
53
.github/workflows/pio-build-patch.yaml
vendored
Normal file
53
.github/workflows/pio-build-patch.yaml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: PlatformIO CI Patch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- patch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
git config --global advice.detachedHead false
|
||||
|
||||
- name: Run PlatformIO
|
||||
#run: pio run -e gravity-release -e gravity-perf -e gravity-debug
|
||||
run: pio run -e gravity-release -e gravity-perf
|
||||
#run: pio run -e gravity-release
|
||||
|
||||
- uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: 'bin'
|
||||
author_name: GitHub Action
|
||||
author_email: mp-se@noreply.github.com
|
||||
|
||||
branch: patch
|
||||
|
||||
default_author: github_actor
|
||||
message: 'GitHub Action Build'
|
||||
pathspec_error_handling: ignore
|
3
.github/workflows/pio-build.yaml
vendored
3
.github/workflows/pio-build.yaml
vendored
@ -36,8 +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 -e gravity-perf -e gravity32-release -e gravity32-perf
|
||||
|
||||
- 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
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
|
@ -3,6 +3,11 @@ repos:
|
||||
rev: 9a5aa38207bf557961110d6a4f7e3a9d352911f9
|
||||
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
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!
|
43
README.md
43
README.md
@ -1,5 +1,44 @@
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Gravity Monitor for Beer Brewing
|
||||
|
||||
This software can be used with iSpindle hardware and utilizes the same hardware configuration. No code has been reused from the iSpindle project.
|
||||
GravityMon is a replacement firmware for the iSpindle firmware. It's 100% compatible with the iSpindle hardware design so it does not require any hardware changes.
|
||||
|
||||
The documenation is now moved to https://mp-se.github.io/gravitymon/index.html
|
||||
Now also works with ESP32 (use ESP32 d1 mini which is compatible with ESP8266)
|
||||
|
||||
Installation can be made using https://www.brewflasher.com or https://web.brewflasher.com
|
||||
|
||||
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:
|
||||
---------------------
|
||||
|
||||
* Operates in two modes gravity monitoring and configuration mode
|
||||
* Gravity mode is comparable to how the iSpindle works when collectintg data
|
||||
* Configuration mode has a modern HTML5 based web UI. No need to start the access point to change settings
|
||||
* Offloading some of the functionallity to run in the web browser, this allows for more advanced features.
|
||||
* REST API to enable scripted configuration
|
||||
* Send data to multiple endpoints and services at once (2xHTTP POST, HTTP GET, MQTT, INFLUXDB2)
|
||||
* Directly test all endpoints from user interface with feedback to simplify troubleshooting
|
||||
* Complete format customization for all endpoints using templates (dont really need to change the software to support new services)
|
||||
* Setup guides for how to send data to many popular services. Currently 10+ are documented
|
||||
* Automatic temperature adjustment of gravity (just tick a checkbox)
|
||||
* OTA support from webserver
|
||||
* Firmware update via web interface
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity and let GravityMon creates a formula
|
||||
* Visual graph showing how formula will be interpreted based on entered values
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* SSL support in all endpoints (no certificate validation due to limitations on esp8266).
|
||||
* Built in performance measurements (used to optimise code)
|
||||
* Storage mode when placed on cap (indefinite sleep)
|
||||
* Customize various hardware parameters to opimize device functionallity.
|
||||
*
|
||||
No code has been reused from the iSpindle project.
|
||||
|
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.6.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
Binary file not shown.
BIN
bin/firmware.bin
BIN
bin/firmware.bin
Binary file not shown.
BIN
bin/firmware32-perf.bin
Normal file
BIN
bin/firmware32-perf.bin
Normal file
Binary file not shown.
BIN
bin/firmware32.bin
Normal file
BIN
bin/firmware32.bin
Normal file
Binary file not shown.
11
bin/format.min.htm
Normal file
11
bin/format.min.htm
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
bin/partitions32.bin
Normal file
BIN
bin/partitions32.bin
Normal file
Binary file not shown.
1
bin/test.min.htm
Normal file
1
bin/test.min.htm
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{ "project":"gravmon", "version":"0.6.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "calibration.min.htm", "about.min.htm" ] }
|
||||
{ "project":"gravmon", "version":"1.1.0", "html": [ ] }
|
167
html/about.htm
167
html/about.htm
@ -5,77 +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="/device.htm">Device</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<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="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!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,342 +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 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>
|
||||
<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="/device.htm">Device</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
<!-- START 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="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||
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="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<form action="/api/formula" method="post">
|
||||
<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 (SG). These values
|
||||
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
|
||||
formula and if the deviation is more than 1.5 SG then the forumla will be rejected. On the bottom of the page you can
|
||||
see a graph over the entered values + values calcualated by the formula.
|
||||
</div>
|
||||
<div class="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">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="1" max="2" 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="1" max="2" 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="1" max="2" 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="1" max="2" 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="1" max="2" 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 = setFourNumberDecimal
|
||||
g2.onchange = setFourNumberDecimal
|
||||
g3.onchange = setFourNumberDecimal
|
||||
g4.onchange = setFourNumberDecimal
|
||||
g5.onchange = setFourNumberDecimal
|
||||
<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 = setTwoNumberDecimal
|
||||
a2.onchange = setTwoNumberDecimal
|
||||
a3.onchange = setTwoNumberDecimal
|
||||
a4.onchange = setTwoNumberDecimal
|
||||
a5.onchange = setTwoNumberDecimal
|
||||
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 setTwoNumberDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(2);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function setFourNumberDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(4);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
}
|
||||
|
||||
function populateChartCalc(a, g) {
|
||||
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
}
|
||||
|
||||
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 );
|
||||
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"]);
|
||||
function convertToPlato(sg) {
|
||||
return 259-(259/sg);
|
||||
}
|
||||
|
||||
$("#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 convertToSG(plato) {
|
||||
return 259/(259-plato);
|
||||
}
|
||||
|
||||
$("#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 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();
|
||||
})
|
||||
.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>
|
File diff suppressed because one or more lines are too long
1147
html/config.htm
1147
html/config.htm
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
120
html/device.htm
120
html/device.htm
@ -1,120 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/device.htm">Device</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
|
||||
<div id="alert-msg">...</div>
|
||||
<button type="button" id="alert-btn" class="close" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Current version:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3" id="h-app-ver-new" hidden>
|
||||
<div class="col-md-8 themed-grid-col bg-light">New version:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Host name:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Device ID:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
function getConfig() {
|
||||
var url = "/api/device";
|
||||
//var url = "/test/device.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.6.0)");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function () {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.6.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
211
html/firmware.htm
Normal file
211
html/firmware.htm
Normal file
@ -0,0 +1,211 @@
|
||||
<!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: 1.0em; }
|
||||
</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( 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="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 progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
79
html/firmware.min.htm
Normal file
79
html/firmware.min.htm
Normal file
@ -0,0 +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 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 progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}</script><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
434
html/format.htm
Normal file
434
html/format.htm
Normal file
@ -0,0 +1,434 @@
|
||||
<!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: 1.0em; }
|
||||
</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 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>
|
||||
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<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-warning alert-dismissible hide fade d-none" role="alert" id="warning-ha">
|
||||
<div>Home Assistant device configuration detected in MQTT format. These messages will be posted when format is saved and not during gravity measurement.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning alert-dismissible hide fade d-none" role="alert" id="warning-length">
|
||||
<div>The format template is quite large, its possible the device will not have enough memory to handle it.</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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 );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
|
||||
function showWarningHomeAssistant() {
|
||||
$('#warning-ha').removeClass('d-none').addClass('show').removeClass('hide');
|
||||
}
|
||||
function hideWarningHomeAssistant() {
|
||||
$('#warning-ha').addClass('d-none').removeClass('show').addClass('hide');
|
||||
}
|
||||
|
||||
function showWarningLength() {
|
||||
$('#warning-length').removeClass('d-none').addClass('show').removeClass('hide');
|
||||
}
|
||||
function hideWarningLength() {
|
||||
$('#warning-length').addClass('d-none').removeClass('show').addClass('hide');
|
||||
}
|
||||
</script>
|
||||
|
||||
<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 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="influxdb" id="influxdb" hidden>
|
||||
<input type="text" name="mqtt" id="mqtt" hidden>
|
||||
|
||||
<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" 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="influxdb">Influx DB</option>
|
||||
<option value="mqtt">MQTT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12">
|
||||
<textarea rows="10" class="form-control" name="format" id="format" onchange="updateStatusField()" onkeydown="updateStatusField()">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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');
|
||||
}
|
||||
});
|
||||
|
||||
function updateStatusField() {
|
||||
var t = $("#format").val();
|
||||
$("#status").text( t.length + " characters in template, recommended maximum is " + maxCharsInFormatTemplate );
|
||||
|
||||
if (t.length > maxCharsInFormatTemplate) {
|
||||
showWarningLength();
|
||||
} else {
|
||||
hideWarningLength();
|
||||
}
|
||||
};
|
||||
|
||||
$("#push-target").change(function(e){
|
||||
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>
|
11
html/format.min.htm
Normal file
11
html/format.min.htm
Normal file
File diff suppressed because one or more lines are too long
488
html/index.htm
488
html/index.htm
@ -5,152 +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 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 -->
|
||||
<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="/device.htm">Device</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/calibration.htm">Calibration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div 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="" id="id" hidden></div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Gravity:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="gravity">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Temperature:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="temp">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Angle/Tilt:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="angle">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Battery:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="battery">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
|
||||
<label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
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 );
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
$("#battery").text(cfg["battery"] + " V");
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
//console.log(cfg["sleep-mode"] );
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
$("#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>
|
File diff suppressed because one or more lines are too long
244
html/test.htm
Normal file
244
html/test.htm
Normal file
@ -0,0 +1,244 @@
|
||||
<!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: 1.0em; }
|
||||
</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 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>
|
||||
|
||||
<!-- 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( 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="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">
|
||||
$('#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" );
|
||||
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>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
1
html/test.min.htm
Normal file
1
html/test.min.htm
Normal file
File diff suppressed because one or more lines are too long
268
html/upload.htm
268
html/upload.htm
@ -5,151 +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 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 -->
|
||||
<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')
|
||||
});
|
||||
</script>
|
||||
function getUpload() {
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
<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>
|
||||
if( cfg["index"] )
|
||||
$("#index").text("Completed.");
|
||||
else
|
||||
$("#index").text("File is missing.");
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">index.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">device.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">config.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">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">about.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" class="custom-file-input" name="name" id="name">
|
||||
<label class="custom-file-label" for="name">Choose file</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
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["device"] )
|
||||
$("#device").text("Completed.");
|
||||
else
|
||||
$("#device").text("File is missing.");
|
||||
|
||||
if( cfg["config"] )
|
||||
$("#config").text("Completed.");
|
||||
else
|
||||
$("#config").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.");
|
||||
$("#calibration").text("Completed.");
|
||||
else
|
||||
$("#calibration").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
$("#about").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>
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</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>
|
File diff suppressed because one or more lines are too long
1051
lib/Arduino-Temperature-Control-Library/DallasTemperature.cpp
Normal file
1051
lib/Arduino-Temperature-Control-Library/DallasTemperature.cpp
Normal file
File diff suppressed because it is too large
Load Diff
323
lib/Arduino-Temperature-Control-Library/DallasTemperature.h
Normal file
323
lib/Arduino-Temperature-Control-Library/DallasTemperature.h
Normal file
@ -0,0 +1,323 @@
|
||||
#ifndef DallasTemperature_h
|
||||
#define DallasTemperature_h
|
||||
|
||||
#define DALLASTEMPLIBVERSION "3.8.1" // To be deprecated -> TODO remove in 4.0.0
|
||||
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
// set to true to include code for new and delete operators
|
||||
#ifndef REQUIRESNEW
|
||||
#define REQUIRESNEW false
|
||||
#endif
|
||||
|
||||
// set to true to include code implementing alarm search functions
|
||||
#ifndef REQUIRESALARMS
|
||||
#define REQUIRESALARMS true
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#ifdef __STM32F1__
|
||||
#include <OneWireSTM.h>
|
||||
#else
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
|
||||
// Model IDs
|
||||
#define DS18S20MODEL 0x10 // also DS1820
|
||||
#define DS18B20MODEL 0x28 // also MAX31820
|
||||
#define DS1822MODEL 0x22
|
||||
#define DS1825MODEL 0x3B
|
||||
#define DS28EA00MODEL 0x42
|
||||
|
||||
// Error Codes
|
||||
#define DEVICE_DISCONNECTED_C -127
|
||||
#define DEVICE_DISCONNECTED_F -196.6
|
||||
#define DEVICE_DISCONNECTED_RAW -7040
|
||||
|
||||
// For readPowerSupply on oneWire bus
|
||||
// definition of nullptr for C++ < 11, using official workaround:
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
|
||||
#if __cplusplus < 201103L
|
||||
const class
|
||||
{
|
||||
public:
|
||||
template <class T>
|
||||
operator T *() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template <class C, class T>
|
||||
operator T C::*() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void operator&() const;
|
||||
} nullptr = {};
|
||||
#endif
|
||||
|
||||
typedef uint8_t DeviceAddress[8];
|
||||
|
||||
class DallasTemperature {
|
||||
public:
|
||||
|
||||
DallasTemperature();
|
||||
DallasTemperature(OneWire*);
|
||||
DallasTemperature(OneWire*, uint8_t);
|
||||
|
||||
void setOneWire(OneWire*);
|
||||
|
||||
void setPullupPin(uint8_t);
|
||||
|
||||
// initialise bus
|
||||
void begin(void);
|
||||
|
||||
// returns the number of devices found on the bus
|
||||
uint8_t getDeviceCount(void);
|
||||
|
||||
// returns the number of DS18xxx Family devices on bus
|
||||
uint8_t getDS18Count(void);
|
||||
|
||||
// returns true if address is valid
|
||||
bool validAddress(const uint8_t*);
|
||||
|
||||
// returns true if address is of the family of sensors the lib supports.
|
||||
bool validFamily(const uint8_t* deviceAddress);
|
||||
|
||||
// finds an address at a given index on the bus
|
||||
bool getAddress(uint8_t*, uint8_t);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
bool isConnected(const uint8_t*);
|
||||
|
||||
// attempt to determine if the device at the given address is connected to the bus
|
||||
// also allows for updating the read scratchpad
|
||||
bool isConnected(const uint8_t*, uint8_t*);
|
||||
|
||||
// read device's scratchpad
|
||||
bool readScratchPad(const uint8_t*, uint8_t*);
|
||||
|
||||
// write device's scratchpad
|
||||
void writeScratchPad(const uint8_t*, const uint8_t*);
|
||||
|
||||
// read device's power requirements
|
||||
bool readPowerSupply(const uint8_t* deviceAddress = nullptr);
|
||||
|
||||
// get global resolution
|
||||
uint8_t getResolution();
|
||||
|
||||
// set global resolution to 9, 10, 11, or 12 bits
|
||||
void setResolution(uint8_t);
|
||||
|
||||
// returns the device resolution: 9, 10, 11, or 12 bits
|
||||
uint8_t getResolution(const uint8_t*);
|
||||
|
||||
// set resolution of a device to 9, 10, 11, or 12 bits
|
||||
bool setResolution(const uint8_t*, uint8_t,
|
||||
bool skipGlobalBitResolutionCalculation = false);
|
||||
|
||||
// sets/gets the waitForConversion flag
|
||||
void setWaitForConversion(bool);
|
||||
bool getWaitForConversion(void);
|
||||
|
||||
// sets/gets the checkForConversion flag
|
||||
void setCheckForConversion(bool);
|
||||
bool getCheckForConversion(void);
|
||||
|
||||
// sends command for all devices on the bus to perform a temperature conversion
|
||||
void requestTemperatures(void);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by address
|
||||
bool requestTemperaturesByAddress(const uint8_t*);
|
||||
|
||||
// sends command for one device to perform a temperature conversion by index
|
||||
bool requestTemperaturesByIndex(uint8_t);
|
||||
|
||||
// returns temperature raw value (12 bit integer of 1/128 degrees C)
|
||||
int16_t getTemp(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees C
|
||||
float getTempC(const uint8_t*);
|
||||
|
||||
// returns temperature in degrees F
|
||||
float getTempF(const uint8_t*);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempCByIndex(uint8_t);
|
||||
|
||||
// Get temperature for device index (slow)
|
||||
float getTempFByIndex(uint8_t);
|
||||
|
||||
// returns true if the bus requires parasite power
|
||||
bool isParasitePowerMode(void);
|
||||
|
||||
// Is a conversion complete on the wire? Only applies to the first sensor on the wire.
|
||||
bool isConversionComplete(void);
|
||||
|
||||
static uint16_t millisToWaitForConversion(uint8_t);
|
||||
|
||||
uint16_t millisToWaitForConversion();
|
||||
|
||||
// Sends command to one device to save values from scratchpad to EEPROM by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to save values from scratchpad to EEPROM
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool saveScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sends command to one device to recall values from EEPROM to scratchpad by index
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPadByIndex(uint8_t);
|
||||
|
||||
// Sends command to one or more devices to recall values from EEPROM to scratchpad
|
||||
// Returns true if no errors were encountered, false indicates failure
|
||||
bool recallScratchPad(const uint8_t* = nullptr);
|
||||
|
||||
// Sets the autoSaveScratchPad flag
|
||||
void setAutoSaveScratchPad(bool);
|
||||
|
||||
// Gets the autoSaveScratchPad flag
|
||||
bool getAutoSaveScratchPad(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
typedef void AlarmHandler(const uint8_t*);
|
||||
|
||||
// sets the high alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setHighAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// sets the low alarm temperature for a device
|
||||
// accepts a int8_t. valid range is -55C - 125C
|
||||
void setLowAlarmTemp(const uint8_t*, int8_t);
|
||||
|
||||
// returns a int8_t with the current high alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getHighAlarmTemp(const uint8_t*);
|
||||
|
||||
// returns a int8_t with the current low alarm temperature for a device
|
||||
// in the range -55C - 125C
|
||||
int8_t getLowAlarmTemp(const uint8_t*);
|
||||
|
||||
// resets internal variables used for the alarm search
|
||||
void resetAlarmSearch(void);
|
||||
|
||||
// search the wire for devices with active alarms
|
||||
bool alarmSearch(uint8_t*);
|
||||
|
||||
// returns true if ia specific device has an alarm
|
||||
bool hasAlarm(const uint8_t*);
|
||||
|
||||
// returns true if any device is reporting an alarm on the bus
|
||||
bool hasAlarm(void);
|
||||
|
||||
// runs the alarm handler for all devices returned by alarmSearch()
|
||||
void processAlarms(void);
|
||||
|
||||
// sets the alarm handler
|
||||
void setAlarmHandler(const AlarmHandler *);
|
||||
|
||||
// returns true if an AlarmHandler has been set
|
||||
bool hasAlarmHandler();
|
||||
|
||||
#endif
|
||||
|
||||
// if no alarm handler is used the two bytes can be used as user data
|
||||
// example of such usage is an ID.
|
||||
// note if device is not connected it will fail writing the data.
|
||||
// note if address cannot be found no error will be reported.
|
||||
// in short use carefully
|
||||
void setUserData(const uint8_t*, int16_t);
|
||||
void setUserDataByIndex(uint8_t, int16_t);
|
||||
int16_t getUserData(const uint8_t*);
|
||||
int16_t getUserDataByIndex(uint8_t);
|
||||
|
||||
// convert from Celsius to Fahrenheit
|
||||
static float toFahrenheit(float);
|
||||
|
||||
// convert from Fahrenheit to Celsius
|
||||
static float toCelsius(float);
|
||||
|
||||
// convert from raw to Celsius
|
||||
static float rawToCelsius(int16_t);
|
||||
|
||||
// convert from Celsius to raw
|
||||
static int16_t celsiusToRaw(float);
|
||||
|
||||
// convert from raw to Fahrenheit
|
||||
static float rawToFahrenheit(int16_t);
|
||||
|
||||
#if REQUIRESNEW
|
||||
|
||||
// initialize memory area
|
||||
void* operator new (unsigned int);
|
||||
|
||||
// delete memory reference
|
||||
void operator delete(void*);
|
||||
|
||||
#endif
|
||||
|
||||
void blockTillConversionComplete(uint8_t);
|
||||
|
||||
private:
|
||||
typedef uint8_t ScratchPad[9];
|
||||
|
||||
// parasite power on or off
|
||||
bool parasite;
|
||||
|
||||
// external pullup
|
||||
bool useExternalPullup;
|
||||
uint8_t pullupPin;
|
||||
|
||||
// used to determine the delay amount needed to allow for the
|
||||
// temperature conversion to take place
|
||||
uint8_t bitResolution;
|
||||
|
||||
// used to requestTemperature with or without delay
|
||||
bool waitForConversion;
|
||||
|
||||
// used to requestTemperature to dynamically check if a conversion is complete
|
||||
bool checkForConversion;
|
||||
|
||||
// used to determine if values will be saved from scratchpad to EEPROM on every scratchpad write
|
||||
bool autoSaveScratchPad;
|
||||
|
||||
// count of devices on the bus
|
||||
uint8_t devices;
|
||||
|
||||
// count of DS18xxx Family devices on bus
|
||||
uint8_t ds18Count;
|
||||
|
||||
// Take a pointer to one wire instance
|
||||
OneWire* _wire;
|
||||
|
||||
// reads scratchpad and returns the raw temperature
|
||||
int16_t calculateTemperature(const uint8_t*, uint8_t*);
|
||||
|
||||
|
||||
// Returns true if all bytes of scratchPad are '\0'
|
||||
bool isAllZeros(const uint8_t* const scratchPad, const size_t length = 9);
|
||||
|
||||
// External pullup control
|
||||
void activateExternalPullup(void);
|
||||
void deactivateExternalPullup(void);
|
||||
|
||||
#if REQUIRESALARMS
|
||||
|
||||
// required for alarmSearch
|
||||
uint8_t alarmSearchAddress[8];
|
||||
int8_t alarmSearchJunction;
|
||||
uint8_t alarmSearchExhausted;
|
||||
|
||||
// the alarm handler function pointer
|
||||
AlarmHandler *_AlarmHandler;
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
#endif
|
@ -9,7 +9,7 @@
|
||||
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_DoubleResetDetector
|
||||
Licensed under MIT license
|
||||
Version: 1.2.1
|
||||
Version: 1.3.1
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -22,6 +22,8 @@
|
||||
1.1.2 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
|
||||
1.2.0 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
|
||||
1.2.1 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
|
||||
1.3.0 K Hoang 10/02/2022 Add support to new ESP32-S3
|
||||
1.3.1 K Hoang 04/03/2022 Add waitingForDRD() function to signal in DRD wating period
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
@ -29,13 +31,26 @@
|
||||
#ifndef ESP_DoubleResetDetector_H
|
||||
#define ESP_DoubleResetDetector_H
|
||||
|
||||
#ifndef DOUBLERESETDETECTOR_DEBUG
|
||||
#define DOUBLERESETDETECTOR_DEBUG false
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO) && (ARDUINO >= 100)
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#endif
|
||||
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION "ESP_DoubleResetDetector v1.2.1"
|
||||
#ifndef ESP_DOUBLE_RESET_DETECTOR_VERSION
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION "ESP_DoubleResetDetector v1.3.1"
|
||||
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_MAJOR 1
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_MINOR 3
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_PATCH 1
|
||||
|
||||
#define ESP_DOUBLE_RESET_DETECTOR_VERSION_INT 1003001
|
||||
#endif
|
||||
|
||||
#define ESP_DOUBLERESETDETECTOR_VERSION ESP_DOUBLE_RESET_DETECTOR_VERSION
|
||||
|
||||
//#define ESP_DRD_USE_EEPROM false
|
||||
@ -45,7 +60,11 @@
|
||||
|
||||
#ifdef ESP32
|
||||
#if (!ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
|
||||
#warning Neither EEPROM, SPIFFS nor LittleFS selected. Default to EEPROM
|
||||
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
#warning Neither EEPROM, SPIFFS nor LittleFS selected. Default to EEPROM
|
||||
#endif
|
||||
|
||||
#ifdef ESP_DRD_USE_EEPROM
|
||||
#undef ESP_DRD_USE_EEPROM
|
||||
#define ESP_DRD_USE_EEPROM true
|
||||
@ -55,7 +74,10 @@
|
||||
|
||||
#ifdef ESP8266
|
||||
#if (!ESP8266_DRD_USE_RTC && !ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
|
||||
#warning Neither RTC, EEPROM, LITTLEFS nor SPIFFS selected. Default to EEPROM
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
#warning Neither RTC, EEPROM, LITTLEFS nor SPIFFS selected. Default to EEPROM
|
||||
#endif
|
||||
|
||||
#ifdef ESP_DRD_USE_EEPROM
|
||||
#undef ESP_DRD_USE_EEPROM
|
||||
#define ESP_DRD_USE_EEPROM true
|
||||
@ -87,14 +109,20 @@
|
||||
// Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h
|
||||
//#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2)
|
||||
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) )
|
||||
#warning Using ESP32 Core 1.0.6 or 2.0.0+
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
#warning Using ESP32 Core 1.0.6 or 2.0.0+
|
||||
#endif
|
||||
|
||||
// The library has been merged into esp32 core from release 1.0.6
|
||||
#include <LittleFS.h>
|
||||
|
||||
#define FileFS LittleFS
|
||||
#define FS_Name "LittleFS"
|
||||
#else
|
||||
#warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library
|
||||
#if (DOUBLERESETDETECTOR_DEBUG)
|
||||
#warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library
|
||||
#endif
|
||||
|
||||
// The library has been merged into esp32 core from release 1.0.6
|
||||
#include <LITTLEFS.h> // https://github.com/lorol/LITTLEFS
|
||||
|
||||
@ -125,9 +153,7 @@
|
||||
|
||||
#endif //#if ESP_DRD_USE_EEPROM
|
||||
|
||||
#ifndef DOUBLERESETDETECTOR_DEBUG
|
||||
#define DOUBLERESETDETECTOR_DEBUG false
|
||||
#endif
|
||||
|
||||
|
||||
#define DOUBLERESETDETECTOR_FLAG_SET 0xD0D01234
|
||||
#define DOUBLERESETDETECTOR_FLAG_CLEAR 0xD0D04321
|
||||
@ -194,6 +220,11 @@ class DoubleResetDetector
|
||||
return doubleResetDetected;
|
||||
|
||||
};
|
||||
|
||||
bool waitingForDRD()
|
||||
{
|
||||
return waitingForDoubleReset;
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
|
@ -56,7 +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.8.0 K Hoang 29/12/2021 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
72
lib/ESP_WifiManager/ESP_WiFiManager.h
Normal file
72
lib/ESP_WifiManager/ESP_WiFiManager.h
Normal file
@ -0,0 +1,72 @@
|
||||
/****************************************************************************************************************************
|
||||
ESP_WiFiManager.h
|
||||
For ESP8266 / ESP32 boards
|
||||
|
||||
ESP_WiFiManager is a library for the ESP8266/Arduino platform
|
||||
(https://github.com/esp8266/Arduino) to enable easy
|
||||
configuration and reconfiguration of WiFi credentials using a Captive Portal
|
||||
inspired by:
|
||||
http://www.esp8266.com/viewtopic.php?f=29&t=2520
|
||||
https://github.com/chriscook8/esp-arduino-apboot
|
||||
https://github.com/esp8266/Arduino/blob/master/libraries/DNSServer/examples/CaptivePortalAdvanced/
|
||||
|
||||
Modified from Tzapu https://github.com/tzapu/WiFiManager
|
||||
and from Ken Taylor https://github.com/kentaylor
|
||||
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
1.0.0 K Hoang 07/10/2019 Initial coding
|
||||
1.0.1 K Hoang 13/12/2019 Fix bug. Add features. Add support for ESP32
|
||||
1.0.2 K Hoang 19/12/2019 Fix bug thatkeeps ConfigPortal in endless loop if Portal/Router SSID or Password is NULL.
|
||||
1.0.3 K Hoang 05/01/2020 Option not displaying AvailablePages in Info page. Enhance README.md. Modify examples
|
||||
1.0.4 K Hoang 07/01/2020 Add RFC952 setHostname feature.
|
||||
1.0.5 K Hoang 15/01/2020 Add configurable DNS feature. Thanks to @Amorphous of https://community.blynk.cc
|
||||
1.0.6 K Hoang 03/02/2020 Add support for ArduinoJson version 6.0.0+ ( tested with v6.14.1 )
|
||||
1.0.7 K Hoang 13/04/2020 Reduce start time, fix SPIFFS bug in examples, update README.md
|
||||
1.0.8 K Hoang 10/06/2020 Fix STAstaticIP issue. Restructure code. Add LittleFS support for ESP8266 core 2.7.1+
|
||||
1.0.9 K Hoang 29/07/2020 Fix ESP32 STAstaticIP bug. Permit changing from DHCP <-> static IP using Config Portal.
|
||||
Add, enhance examples (fix MDNS for ESP32)
|
||||
1.0.10 K Hoang 08/08/2020 Add more features to Config Portal. Use random WiFi AP channel to avoid conflict.
|
||||
1.0.11 K Hoang 17/08/2020 Add CORS feature. Fix bug in softAP, autoConnect, resetSettings.
|
||||
1.1.0 K Hoang 28/08/2020 Add MultiWiFi feature to autoconnect to best WiFi at runtime
|
||||
1.1.1 K Hoang 30/08/2020 Add setCORSHeader function to allow flexible CORS. Fix typo and minor improvement.
|
||||
1.1.2 K Hoang 17/08/2020 Fix bug. Add example.
|
||||
1.2.0 K Hoang 09/10/2020 Restore cpp code besides Impl.h code to use if linker error. Fix bug.
|
||||
1.3.0 K Hoang 04/12/2020 Add LittleFS support to ESP32 using LITTLEFS Library
|
||||
1.4.1 K Hoang 22/12/2020 Fix staticIP not saved. Add functions. Add complex examples. Sync with ESPAsync_WiFiManager
|
||||
1.4.2 K Hoang 14/01/2021 Fix examples' bug not using saved WiFi Credentials after losing all WiFi connections.
|
||||
1.4.3 K Hoang 23/01/2021 Fix examples' bug not saving Static IP in certain cases.
|
||||
1.5.0 K Hoang 12/02/2021 Add support to new ESP32-S2
|
||||
1.5.1 K Hoang 26/03/2021 Fix compiler error if setting Compiler Warnings to All. Retest with esp32 core v1.0.6
|
||||
1.5.2 K Hoang 08/04/2021 Fix example misleading messages.
|
||||
1.5.3 K Hoang 13/04/2021 Add dnsServer error message.
|
||||
1.6.0 K Hoang 20/04/2021 Add support to new ESP32-C3 using SPIFFS or EEPROM
|
||||
1.6.1 K Hoang 25/04/2021 Fix MultiWiFi bug. Fix captive-portal bug if CP AP address is not default 192.168.4.1
|
||||
1.7.0 K Hoang 06/05/2021 Set _timezoneName. Add support to new ESP32-S2 (METRO_ESP32S2, FUNHOUSE_ESP32S2, etc.)
|
||||
1.7.1 K Hoang 08/05/2021 Fix Json bug. Fix timezoneName not displayed in Info page.
|
||||
1.7.2 K Hoang 08/05/2021 Fix warnings with ESP8266 core v3.0.0
|
||||
1.7.3 K Hoang 29/07/2021 Fix MultiWiFi connection issue with ESP32 core v2.0.0-rc1+
|
||||
1.7.4 K Hoang 13/08/2021 Add WiFi scanning of hidden SSIDs
|
||||
1.7.5 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
|
||||
1.7.6 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
|
||||
1.7.7 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
|
||||
1.7.8 K Hoang 30/11/2021 Fix bug to permit using HTTP port different from 80. Fix bug
|
||||
1.8.0 K Hoang 29/12/2021 Fix `multiple-definitions` linker error and weird bug related to src_cpp
|
||||
1.9.0 K Hoang 17/01/2022 Enable compatibility with old code to include only ESP_WiFiManager.h
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP_WiFiManager_h
|
||||
#define ESP_WiFiManager_h
|
||||
|
||||
#include <ESP_WiFiManager.hpp> //https://github.com/khoih-prog/ESP_WiFiManager
|
||||
#include <ESP_WiFiManager-Impl.h> //https://github.com/khoih-prog/ESP_WiFiManager
|
||||
|
||||
#endif // ESP_WiFiManager_h
|
||||
|
@ -1,5 +1,5 @@
|
||||
/****************************************************************************************************************************
|
||||
ESP_WiFiManager.h
|
||||
ESP_WiFiManager.hpp
|
||||
For ESP8266 / ESP32 boards
|
||||
|
||||
ESP_WiFiManager is a library for the ESP8266/Arduino platform
|
||||
@ -16,7 +16,7 @@
|
||||
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
|
||||
Licensed under MIT license
|
||||
|
||||
Version: 1.8.0
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -56,13 +56,14 @@
|
||||
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.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
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ESP_WiFiManager_h
|
||||
#define ESP_WiFiManager_h
|
||||
#ifndef ESP_WiFiManager_hpp
|
||||
#define ESP_WiFiManager_hpp
|
||||
|
||||
#if !( defined(ESP8266) || defined(ESP32) )
|
||||
#error This code is intended to run on the ESP8266 or ESP32 platform! Please check your Tools->Board setting.
|
||||
@ -78,13 +79,13 @@
|
||||
#define USING_ESP32_C3 true
|
||||
#endif
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION "ESP_WiFiManager v1.8.0"
|
||||
#define ESP_WIFIMANAGER_VERSION "ESP_WiFiManager v1.9.0"
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION_MAJOR 1
|
||||
#define ESP_WIFIMANAGER_VERSION_MINOR 8
|
||||
#define ESP_WIFIMANAGER_VERSION_MINOR 9
|
||||
#define ESP_WIFIMANAGER_VERSION_PATCH 0
|
||||
|
||||
#define ESP_WIFIMANAGER_VERSION_INT 1008000
|
||||
#define ESP_WIFIMANAGER_VERSION_INT 1009000
|
||||
|
||||
#include "ESP_WiFiManager_Debug.h"
|
||||
|
||||
@ -231,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>";
|
||||
//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>";
|
||||
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.8.0
|
||||
Version: 1.9.0
|
||||
|
||||
Version Modified By Date Comments
|
||||
------- ----------- ---------- -----------
|
||||
@ -56,7 +56,8 @@
|
||||
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.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
|
||||
*****************************************************************************************************************************/
|
||||
|
||||
#pragma once
|
580
lib/OneWire/OneWire.cpp
Normal file
580
lib/OneWire/OneWire.cpp
Normal file
@ -0,0 +1,580 @@
|
||||
/*
|
||||
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
|
||||
|
||||
The latest version of this library may be found at:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
|
||||
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
|
||||
January 2010.
|
||||
|
||||
DO NOT EMAIL for technical support, especially not for ESP chips!
|
||||
All project support questions must be posted on public forums
|
||||
relevant to the board or chips used. If using Arduino, post on
|
||||
Arduino's forum. If using ESP, post on the ESP community forums.
|
||||
There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
|
||||
|
||||
Github's issue tracker for OneWire should be used only to report
|
||||
specific bugs. DO NOT request project support via Github. All
|
||||
project and tech support questions must be posted on forums, not
|
||||
github issues. If you experience a problem and you are not
|
||||
absolutely sure it's an issue with the library, ask on a forum
|
||||
first. Only use github to report issues after experts have
|
||||
confirmed the issue is with OneWire rather than your project.
|
||||
|
||||
Back in 2010, OneWire was in need of many bug fixes, but had
|
||||
been abandoned the original author (Jim Studt). None of the known
|
||||
contributors were interested in maintaining OneWire. Paul typically
|
||||
works on OneWire every 6 to 12 months. Patches usually wait that
|
||||
long. If anyone is interested in more actively maintaining OneWire,
|
||||
please contact Paul (this is pretty much the only reason to use
|
||||
private email about OneWire).
|
||||
|
||||
OneWire is now very mature code. No changes other than adding
|
||||
definitions for newer hardware support are anticipated.
|
||||
|
||||
Version 2.3:
|
||||
Unknown chip fallback mode, Roger Clark
|
||||
Teensy-LC compatibility, Paul Stoffregen
|
||||
Search bug fix, Love Nystrom
|
||||
|
||||
Version 2.2:
|
||||
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
|
||||
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
|
||||
Fix DS18B20 example negative temperature
|
||||
Fix DS18B20 example's low res modes, Ken Butcher
|
||||
Improve reset timing, Mark Tillotson
|
||||
Add const qualifiers, Bertrik Sikken
|
||||
Add initial value input to crc16, Bertrik Sikken
|
||||
Add target_search() function, Scott Roberts
|
||||
|
||||
Version 2.1:
|
||||
Arduino 1.0 compatibility, Paul Stoffregen
|
||||
Improve temperature example, Paul Stoffregen
|
||||
DS250x_PROM example, Guillermo Lovato
|
||||
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
|
||||
Improvements from Glenn Trewitt:
|
||||
- crc16() now works
|
||||
- check_crc16() does all of calculation/checking work.
|
||||
- Added read_bytes() and write_bytes(), to reduce tedious loops.
|
||||
- Added ds2408 example.
|
||||
Delete very old, out-of-date readme file (info is here)
|
||||
|
||||
Version 2.0: Modifications by Paul Stoffregen, January 2010:
|
||||
http://www.pjrc.com/teensy/td_libs_OneWire.html
|
||||
Search fix from Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
Use direct optimized I/O in all cases
|
||||
Disable interrupts during timing critical sections
|
||||
(this solves many random communication errors)
|
||||
Disable interrupts during read-modify-write I/O
|
||||
Reduce RAM consumption by eliminating unnecessary
|
||||
variables and trimming many to 8 bits
|
||||
Optimize both crc8 - table version moved to flash
|
||||
|
||||
Modified to work with larger numbers of devices - avoids loop.
|
||||
Tested in Arduino 11 alpha with 12 sensors.
|
||||
26 Sept 2008 -- Robin James
|
||||
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
|
||||
|
||||
Updated to work with arduino-0008 and to include skip() as of
|
||||
2007/07/06. --RJL20
|
||||
|
||||
Modified to calculate the 8-bit CRC directly, avoiding the need for
|
||||
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
|
||||
-- Tom Pollard, Jan 23, 2008
|
||||
|
||||
Jim Studt's original library was modified by Josh Larios.
|
||||
|
||||
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Much of the code was inspired by Derek Yerger's code, though I don't
|
||||
think much of that remains. In any event that was..
|
||||
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
|
||||
|
||||
The CRC code was excerpted and inspired by the Dallas Semiconductor
|
||||
sample code bearing this copyright.
|
||||
//---------------------------------------------------------------------------
|
||||
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// Except as contained in this notice, the name of Dallas Semiconductor
|
||||
// shall not be used except as stated in the Dallas Semiconductor
|
||||
// Branding Policy.
|
||||
//--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "OneWire.h"
|
||||
#include "util/OneWire_direct_gpio.h"
|
||||
|
||||
|
||||
void OneWire::begin(uint8_t pin)
|
||||
{
|
||||
pinMode(pin, INPUT);
|
||||
bitmask = PIN_TO_BITMASK(pin);
|
||||
baseReg = PIN_TO_BASEREG(pin);
|
||||
#if ONEWIRE_SEARCH
|
||||
reset_search();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Perform the onewire reset function. We will wait up to 250uS for
|
||||
// the bus to come high, if it doesn't then it is broken or shorted
|
||||
// and we return a 0;
|
||||
//
|
||||
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
|
||||
//
|
||||
uint8_t OneWire::reset(void)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
uint8_t retries = 125;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask);
|
||||
interrupts();
|
||||
// wait until the wire is high... just in case
|
||||
do {
|
||||
if (--retries == 0) return 0;
|
||||
delayMicroseconds(2);
|
||||
} while ( !DIRECT_READ(reg, mask));
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
interrupts();
|
||||
delayMicroseconds(480);
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(reg, mask); // allow it to float
|
||||
delayMicroseconds(70);
|
||||
r = !DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
void OneWire::write_bit(uint8_t v)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
|
||||
if (v & 1) {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(10);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(55);
|
||||
} else {
|
||||
noInterrupts();
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
|
||||
delayMicroseconds(65);
|
||||
DIRECT_WRITE_HIGH(reg, mask); // drive output high
|
||||
interrupts();
|
||||
delayMicroseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a bit. Port and bit is used to cut lookup time and provide
|
||||
// more certain timing.
|
||||
//
|
||||
uint8_t OneWire::read_bit(void)
|
||||
{
|
||||
IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
|
||||
volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
|
||||
uint8_t r;
|
||||
|
||||
noInterrupts();
|
||||
DIRECT_MODE_OUTPUT(reg, mask);
|
||||
DIRECT_WRITE_LOW(reg, mask);
|
||||
delayMicroseconds(3);
|
||||
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
|
||||
delayMicroseconds(10);
|
||||
r = DIRECT_READ(reg, mask);
|
||||
interrupts();
|
||||
delayMicroseconds(53);
|
||||
return r;
|
||||
}
|
||||
|
||||
//
|
||||
// Write a byte. The writing code uses the active drivers to raise the
|
||||
// pin high, if you need power after the write (e.g. DS18S20 in
|
||||
// parasite power mode) then set 'power' to 1, otherwise the pin will
|
||||
// go tri-state at the end of the write to avoid heating in a short or
|
||||
// other mishap.
|
||||
//
|
||||
void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
|
||||
uint8_t bitMask;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
OneWire::write_bit( (bitMask & v)?1:0);
|
||||
}
|
||||
if ( !power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
write(buf[i]);
|
||||
if (!power) {
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
DIRECT_WRITE_LOW(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Read a byte
|
||||
//
|
||||
uint8_t OneWire::read() {
|
||||
uint8_t bitMask;
|
||||
uint8_t r = 0;
|
||||
|
||||
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
|
||||
if ( OneWire::read_bit()) r |= bitMask;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
|
||||
for (uint16_t i = 0 ; i < count ; i++)
|
||||
buf[i] = read();
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM select
|
||||
//
|
||||
void OneWire::select(const uint8_t rom[8])
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
write(0x55); // Choose ROM
|
||||
|
||||
for (i = 0; i < 8; i++) write(rom[i]);
|
||||
}
|
||||
|
||||
//
|
||||
// Do a ROM skip
|
||||
//
|
||||
void OneWire::skip()
|
||||
{
|
||||
write(0xCC); // Skip ROM
|
||||
}
|
||||
|
||||
void OneWire::depower()
|
||||
{
|
||||
noInterrupts();
|
||||
DIRECT_MODE_INPUT(baseReg, bitmask);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
|
||||
//
|
||||
// You need to use this function to start a search again from the beginning.
|
||||
// You do not need to do it for the first search, though you could.
|
||||
//
|
||||
void OneWire::reset_search()
|
||||
{
|
||||
// reset the search state
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
for(int i = 7; ; i--) {
|
||||
ROM_NO[i] = 0;
|
||||
if ( i == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
//
|
||||
void OneWire::target_search(uint8_t family_code)
|
||||
{
|
||||
// set the search state to find SearchFamily type devices
|
||||
ROM_NO[0] = family_code;
|
||||
for (uint8_t i = 1; i < 8; i++)
|
||||
ROM_NO[i] = 0;
|
||||
LastDiscrepancy = 64;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Perform a search. If this function returns a '1' then it has
|
||||
// enumerated the next device and you may retrieve the ROM from the
|
||||
// OneWire::address variable. If there are no devices, no further
|
||||
// devices, or something horrible happens in the middle of the
|
||||
// enumeration then a 0 is returned. If a new device is found then
|
||||
// its address is copied to newAddr. Use OneWire::reset_search() to
|
||||
// start over.
|
||||
//
|
||||
// --- Replaced by the one from the Dallas Semiconductor web site ---
|
||||
//--------------------------------------------------------------------------
|
||||
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
|
||||
// search state.
|
||||
// Return TRUE : device found, ROM number in ROM_NO buffer
|
||||
// FALSE : device not found, end of search
|
||||
//
|
||||
bool OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
|
||||
{
|
||||
uint8_t id_bit_number;
|
||||
uint8_t last_zero, rom_byte_number;
|
||||
bool search_result;
|
||||
uint8_t id_bit, cmp_id_bit;
|
||||
|
||||
unsigned char rom_byte_mask, search_direction;
|
||||
|
||||
// initialize for search
|
||||
id_bit_number = 1;
|
||||
last_zero = 0;
|
||||
rom_byte_number = 0;
|
||||
rom_byte_mask = 1;
|
||||
search_result = false;
|
||||
|
||||
// if the last call was not the last one
|
||||
if (!LastDeviceFlag) {
|
||||
// 1-Wire reset
|
||||
if (!reset()) {
|
||||
// reset the search
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// issue the search command
|
||||
if (search_mode == true) {
|
||||
write(0xF0); // NORMAL SEARCH
|
||||
} else {
|
||||
write(0xEC); // CONDITIONAL SEARCH
|
||||
}
|
||||
|
||||
// loop to do the search
|
||||
do
|
||||
{
|
||||
// read a bit and its complement
|
||||
id_bit = read_bit();
|
||||
cmp_id_bit = read_bit();
|
||||
|
||||
// check for no devices on 1-wire
|
||||
if ((id_bit == 1) && (cmp_id_bit == 1)) {
|
||||
break;
|
||||
} else {
|
||||
// all devices coupled have 0 or 1
|
||||
if (id_bit != cmp_id_bit) {
|
||||
search_direction = id_bit; // bit write value for search
|
||||
} else {
|
||||
// if this discrepancy if before the Last Discrepancy
|
||||
// on a previous next then pick the same as last time
|
||||
if (id_bit_number < LastDiscrepancy) {
|
||||
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
|
||||
} else {
|
||||
// if equal to last pick 1, if not then pick 0
|
||||
search_direction = (id_bit_number == LastDiscrepancy);
|
||||
}
|
||||
// if 0 was picked then record its position in LastZero
|
||||
if (search_direction == 0) {
|
||||
last_zero = id_bit_number;
|
||||
|
||||
// check for Last discrepancy in family
|
||||
if (last_zero < 9)
|
||||
LastFamilyDiscrepancy = last_zero;
|
||||
}
|
||||
}
|
||||
|
||||
// set or clear the bit in the ROM byte rom_byte_number
|
||||
// with mask rom_byte_mask
|
||||
if (search_direction == 1)
|
||||
ROM_NO[rom_byte_number] |= rom_byte_mask;
|
||||
else
|
||||
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
|
||||
|
||||
// serial number search direction write bit
|
||||
write_bit(search_direction);
|
||||
|
||||
// increment the byte counter id_bit_number
|
||||
// and shift the mask rom_byte_mask
|
||||
id_bit_number++;
|
||||
rom_byte_mask <<= 1;
|
||||
|
||||
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
|
||||
if (rom_byte_mask == 0) {
|
||||
rom_byte_number++;
|
||||
rom_byte_mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
|
||||
|
||||
// if the search was successful then
|
||||
if (!(id_bit_number < 65)) {
|
||||
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
|
||||
LastDiscrepancy = last_zero;
|
||||
|
||||
// check for last device
|
||||
if (LastDiscrepancy == 0) {
|
||||
LastDeviceFlag = true;
|
||||
}
|
||||
search_result = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no device found then reset counters so next 'search' will be like a first
|
||||
if (!search_result || !ROM_NO[0]) {
|
||||
LastDiscrepancy = 0;
|
||||
LastDeviceFlag = false;
|
||||
LastFamilyDiscrepancy = 0;
|
||||
search_result = false;
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
|
||||
}
|
||||
return search_result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
|
||||
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
|
||||
//
|
||||
|
||||
#if ONEWIRE_CRC8_TABLE
|
||||
// Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0
|
||||
// Tiny 2x16 entry CRC table created by Arjen Lentz
|
||||
// See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table
|
||||
static const uint8_t PROGMEM dscrc2x16_table[] = {
|
||||
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83,
|
||||
0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
|
||||
0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8,
|
||||
0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74
|
||||
};
|
||||
|
||||
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
|
||||
// and the registers. (Use tiny 2x16 entry CRC table)
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
crc = *addr++ ^ crc; // just re-using crc as intermediate
|
||||
crc = pgm_read_byte(dscrc2x16_table + (crc & 0x0f)) ^
|
||||
pgm_read_byte(dscrc2x16_table + 16 + ((crc >> 4) & 0x0f));
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
#else
|
||||
//
|
||||
// Compute a Dallas Semiconductor 8 bit CRC directly.
|
||||
// this is much slower, but a little smaller, than the lookup table.
|
||||
//
|
||||
uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
#if defined(__AVR__)
|
||||
crc = _crc_ibutton_update(crc, *addr++);
|
||||
#else
|
||||
uint8_t inbyte = *addr++;
|
||||
for (uint8_t i = 8; i; i--) {
|
||||
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix) crc ^= 0x8C;
|
||||
inbyte >>= 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
|
||||
{
|
||||
crc = ~crc16(input, len, crc);
|
||||
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
|
||||
}
|
||||
|
||||
uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
|
||||
{
|
||||
#if defined(__AVR__)
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
crc = _crc16_update(crc, input[i]);
|
||||
}
|
||||
#else
|
||||
static const uint8_t oddparity[16] =
|
||||
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
|
||||
|
||||
for (uint16_t i = 0 ; i < len ; i++) {
|
||||
// Even though we're just copying a byte from the input,
|
||||
// we'll be doing 16-bit computation with it.
|
||||
uint16_t cdata = input[i];
|
||||
cdata = (cdata ^ crc) & 0xff;
|
||||
crc >>= 8;
|
||||
|
||||
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
|
||||
crc ^= 0xC001;
|
||||
|
||||
cdata <<= 6;
|
||||
crc ^= cdata;
|
||||
cdata <<= 1;
|
||||
crc ^= cdata;
|
||||
}
|
||||
#endif
|
||||
return crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
182
lib/OneWire/OneWire.h
Normal file
182
lib/OneWire/OneWire.h
Normal file
@ -0,0 +1,182 @@
|
||||
#ifndef OneWire_h
|
||||
#define OneWire_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h> // for delayMicroseconds, digitalPinToBitMask, etc
|
||||
#else
|
||||
#include "WProgram.h" // for delayMicroseconds
|
||||
#include "pins_arduino.h" // for digitalPinToBitMask, etc
|
||||
#endif
|
||||
|
||||
// You can exclude certain features from OneWire. In theory, this
|
||||
// might save some space. In practice, the compiler automatically
|
||||
// removes unused code (technically, the linker, using -fdata-sections
|
||||
// and -ffunction-sections when compiling, and Wl,--gc-sections
|
||||
// when linking), so most of these will not result in any code size
|
||||
// reduction. Well, unless you try to use the missing features
|
||||
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
|
||||
// is the exception, because it selects a fast but large algorithm
|
||||
// or a small but slow algorithm.
|
||||
|
||||
// you can exclude onewire_search by defining that to 0
|
||||
#ifndef ONEWIRE_SEARCH
|
||||
#define ONEWIRE_SEARCH 1
|
||||
#endif
|
||||
|
||||
// You can exclude CRC checks altogether by defining this to 0
|
||||
#ifndef ONEWIRE_CRC
|
||||
#define ONEWIRE_CRC 1
|
||||
#endif
|
||||
|
||||
// Select the table-lookup method of computing the 8-bit CRC
|
||||
// by setting this to 1. The lookup table enlarges code size by
|
||||
// about 250 bytes. It does NOT consume RAM (but did in very
|
||||
// old versions of OneWire). If you disable this, a slower
|
||||
// but very compact algorithm is used.
|
||||
#ifndef ONEWIRE_CRC8_TABLE
|
||||
#define ONEWIRE_CRC8_TABLE 1
|
||||
#endif
|
||||
|
||||
// You can allow 16-bit CRC checks by defining this to 1
|
||||
// (Note that ONEWIRE_CRC must also be 1.)
|
||||
#ifndef ONEWIRE_CRC16
|
||||
#define ONEWIRE_CRC16 1
|
||||
#endif
|
||||
|
||||
// Board-specific macros for direct GPIO
|
||||
#include "util/OneWire_direct_regtype.h"
|
||||
|
||||
class OneWire
|
||||
{
|
||||
private:
|
||||
IO_REG_TYPE bitmask;
|
||||
volatile IO_REG_TYPE *baseReg;
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// global search state
|
||||
unsigned char ROM_NO[8];
|
||||
uint8_t LastDiscrepancy;
|
||||
uint8_t LastFamilyDiscrepancy;
|
||||
bool LastDeviceFlag;
|
||||
#endif
|
||||
|
||||
public:
|
||||
OneWire() { }
|
||||
OneWire(uint8_t pin) { begin(pin); }
|
||||
void begin(uint8_t pin);
|
||||
|
||||
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
|
||||
// with a presence pulse. Returns 0 if there is no device or the
|
||||
// bus is shorted or otherwise held low for more than 250uS
|
||||
uint8_t reset(void);
|
||||
|
||||
// Issue a 1-Wire rom select command, you do the reset first.
|
||||
void select(const uint8_t rom[8]);
|
||||
|
||||
// Issue a 1-Wire rom skip command, to address all on bus.
|
||||
void skip(void);
|
||||
|
||||
// Write a byte. If 'power' is one then the wire is held high at
|
||||
// the end for parasitically powered devices. You are responsible
|
||||
// for eventually depowering it by calling depower() or doing
|
||||
// another read or write.
|
||||
void write(uint8_t v, uint8_t power = 0);
|
||||
|
||||
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
|
||||
|
||||
// Read a byte.
|
||||
uint8_t read(void);
|
||||
|
||||
void read_bytes(uint8_t *buf, uint16_t count);
|
||||
|
||||
// Write a bit. The bus is always left powered at the end, see
|
||||
// note in write() about that.
|
||||
void write_bit(uint8_t v);
|
||||
|
||||
// Read a bit.
|
||||
uint8_t read_bit(void);
|
||||
|
||||
// Stop forcing power onto the bus. You only need to do this if
|
||||
// you used the 'power' flag to write() or used a write_bit() call
|
||||
// and aren't about to do another read or write. You would rather
|
||||
// not leave this powered if you don't have to, just in case
|
||||
// someone shorts your bus.
|
||||
void depower(void);
|
||||
|
||||
#if ONEWIRE_SEARCH
|
||||
// Clear the search state so that if will start from the beginning again.
|
||||
void reset_search();
|
||||
|
||||
// Setup the search to find the device type 'family_code' on the next call
|
||||
// to search(*newAddr) if it is present.
|
||||
void target_search(uint8_t family_code);
|
||||
|
||||
// Look for the next device. Returns 1 if a new address has been
|
||||
// returned. A zero might mean that the bus is shorted, there are
|
||||
// no devices, or you have already retrieved all of them. It
|
||||
// might be a good idea to check the CRC to make sure you didn't
|
||||
// get garbage. The order is deterministic. You will always get
|
||||
// the same devices in the same order.
|
||||
bool search(uint8_t *newAddr, bool search_mode = true);
|
||||
#endif
|
||||
|
||||
#if ONEWIRE_CRC
|
||||
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
|
||||
// ROM and scratchpad registers.
|
||||
static uint8_t crc8(const uint8_t *addr, uint8_t len);
|
||||
|
||||
#if ONEWIRE_CRC16
|
||||
// Compute the 1-Wire CRC16 and compare it against the received CRC.
|
||||
// Example usage (reading a DS2408):
|
||||
// // Put everything in a buffer so we can compute the CRC easily.
|
||||
// uint8_t buf[13];
|
||||
// buf[0] = 0xF0; // Read PIO Registers
|
||||
// buf[1] = 0x88; // LSB address
|
||||
// buf[2] = 0x00; // MSB address
|
||||
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
|
||||
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
|
||||
// if (!CheckCRC16(buf, 11, &buf[11])) {
|
||||
// // Handle error.
|
||||
// }
|
||||
//
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param inverted_crc - The two CRC16 bytes in the received data.
|
||||
// This should just point into the received data,
|
||||
// *not* at a 16-bit integer.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return True, iff the CRC matches.
|
||||
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
|
||||
|
||||
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
|
||||
// the integrity of data received from many 1-Wire devices. Note that the
|
||||
// CRC computed here is *not* what you'll get from the 1-Wire network,
|
||||
// for two reasons:
|
||||
// 1) The CRC is transmitted bitwise inverted.
|
||||
// 2) Depending on the endian-ness of your processor, the binary
|
||||
// representation of the two-byte return value may have a different
|
||||
// byte order than the two bytes you get from 1-Wire.
|
||||
// @param input - Array of bytes to checksum.
|
||||
// @param len - How many bytes to use.
|
||||
// @param crc - The crc starting value (optional)
|
||||
// @return The CRC16, as defined by Dallas Semiconductor.
|
||||
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
// Prevent this name from leaking into Arduino sketches
|
||||
#ifdef IO_REG_TYPE
|
||||
#undef IO_REG_TYPE
|
||||
#endif
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // OneWire_h
|
459
lib/OneWire/util/OneWire_direct_gpio.h
Normal file
459
lib/OneWire/util/OneWire_direct_gpio.h
Normal file
@ -0,0 +1,459 @@
|
||||
#ifndef OneWire_Direct_GPIO_h
|
||||
#define OneWire_Direct_GPIO_h
|
||||
|
||||
// This header should ONLY be included by OneWire.cpp. These defines are
|
||||
// meant to be private, used within OneWire.cpp, but not exposed to Arduino
|
||||
// sketches or other libraries which may include OneWire.h.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O definitions
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR asm("r30")
|
||||
#define IO_REG_MASK_ATTR
|
||||
#if defined(__AVR_ATmega4809__)
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask))
|
||||
#else
|
||||
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
|
||||
#endif
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (1)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR __attribute__ ((unused))
|
||||
#define DIRECT_READ(base, mask) (*((base)+512))
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint8_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask))
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
|
||||
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
|
||||
// If you have trouble with OneWire on Arduino Due, please check the
|
||||
// status of delayMicroseconds() before reporting a bug in OneWire!
|
||||
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#endif
|
||||
#ifndef pgm_read_byte
|
||||
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
|
||||
#endif
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
// Special note: I depend on the ESP community to maintain these definitions and
|
||||
// submit good pull requests. I can not answer any ESP questions or help you
|
||||
// resolve any problems related to ESP chips. Please do not contact me and please
|
||||
// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
|
||||
// on ESP community forums.
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
|
||||
#define PIN_TO_BITMASK(pin) (1 << pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS
|
||||
#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS
|
||||
#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS
|
||||
#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
return (GPIO.in.val >> pin) & 0x1;
|
||||
#else // plain ESP32
|
||||
if ( pin < 32 )
|
||||
return (GPIO.in >> pin) & 0x1;
|
||||
else if ( pin < 46 )
|
||||
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 < 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 < 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 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
|
||||
{
|
||||
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
|
||||
// Input
|
||||
if ( pin < 32 )
|
||||
GPIO.enable_w1tc = ((uint32_t)1 << pin);
|
||||
else
|
||||
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||
}
|
||||
#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 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
|
||||
{
|
||||
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
|
||||
// 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));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
|
||||
// https://github.com/PaulStoffregen/OneWire/pull/47
|
||||
// https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c
|
||||
#ifdef interrupts
|
||||
#undef interrupts
|
||||
#endif
|
||||
#ifdef noInterrupts
|
||||
#undef noInterrupts
|
||||
#endif
|
||||
#define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux)
|
||||
#define interrupts() portEXIT_CRITICAL(&mux);}
|
||||
//#warning "ESP32 OneWire testing"
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalReadFast((PinName)pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0))
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0))
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
|
||||
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
|
||||
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
|
||||
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
|
||||
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT)
|
||||
#define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT)
|
||||
#define PORT_OFFSET(port) (PORT_REG_SHFIT * port)
|
||||
#define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin)))
|
||||
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin))
|
||||
#define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG)
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) (pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
|
||||
#include "scss_registers.h"
|
||||
#include "portable.h"
|
||||
#include "avr/pgmspace.h"
|
||||
|
||||
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
|
||||
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
|
||||
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
|
||||
#define DIR_OFFSET_SS 0x01
|
||||
#define DIR_OFFSET_SOC 0x04
|
||||
#define EXT_PORT_OFFSET_SS 0x0A
|
||||
#define EXT_PORT_OFFSET_SOC 0x50
|
||||
|
||||
/* GPIO registers base address */
|
||||
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
|
||||
#define PIN_TO_BITMASK(pin) pin
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
IO_REG_TYPE ret;
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
||||
} else {
|
||||
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
|
||||
}
|
||||
return ((ret >> GPIO_ID(pin)) & 0x01);
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
|
||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||
} else {
|
||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
||||
{
|
||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
||||
} else {
|
||||
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
|
||||
}
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, pin) directRead(base, pin)
|
||||
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
|
||||
|
||||
#elif defined(__riscv)
|
||||
|
||||
/*
|
||||
* Tested on highfive1
|
||||
*
|
||||
* Stable results are achieved operating in the
|
||||
* two high speed modes of the highfive1. It
|
||||
* seems to be less reliable in slow mode.
|
||||
*/
|
||||
#define PIN_TO_BASEREG(pin) (0)
|
||||
#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
IO_REG_TYPE directRead(IO_REG_TYPE mask)
|
||||
{
|
||||
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeInput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) |= mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directModeOutput(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||
|
||||
GPIO_REG(GPIO_INPUT_EN) &= ~mask;
|
||||
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteLow(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void directWriteHigh(IO_REG_TYPE mask)
|
||||
{
|
||||
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
||||
}
|
||||
|
||||
#define DIRECT_READ(base, mask) directRead(mask)
|
||||
#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
|
||||
#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
|
||||
#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
|
||||
#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
|
||||
|
||||
#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)
|
||||
#define IO_REG_TYPE unsigned int
|
||||
#define IO_REG_BASE_ATTR
|
||||
#define IO_REG_MASK_ATTR
|
||||
#define DIRECT_READ(base, pin) digitalRead(pin)
|
||||
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
|
||||
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
|
||||
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
|
||||
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
|
||||
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
55
lib/OneWire/util/OneWire_direct_regtype.h
Normal file
55
lib/OneWire/util/OneWire_direct_regtype.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef OneWire_Direct_RegType_h
|
||||
#define OneWire_Direct_RegType_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Platform specific I/O register type
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__MKL26Z64__)
|
||||
#define IO_REG_TYPE uint8_t
|
||||
|
||||
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__PIC32MX__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
#define IO_REG_MASK_ATTR
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__ASR6501__)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(RBL_NRF51822)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#elif defined(__riscv)
|
||||
#define IO_REG_TYPE uint32_t
|
||||
|
||||
#else
|
||||
#define IO_REG_TYPE unsigned int
|
||||
|
||||
#endif
|
||||
#endif
|
@ -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_ */
|
13
part32.csv
Normal file
13
part32.csv
Normal file
@ -0,0 +1,13 @@
|
||||
# Name,Type,SubType,Offset,Size,Flags
|
||||
nvs,data,nvs,0x9000,0x5000
|
||||
otadata,data,ota,0xe000,0x2000
|
||||
app0,app,ota_0,0x10000,0x1c0000
|
||||
app1,app,ota_1,0x1d0000,0x1c0000
|
||||
spiffs,data,spiffs,0x390000,0x70000
|
||||
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
#nvs, data, nvs, 0x9000, 0x5000,
|
||||
#otadata, data, ota, 0xe000, 0x2000,
|
||||
#app0, app, ota_0, 0x10000, 0x140000, # 1310720
|
||||
#app1, app, ota_1, 0x150000,0x140000, # 1310720
|
||||
#spiffs, data, spiffs, 0x290000,0x170000, # 1507328
|
|
112
platformio.ini
112
platformio.ini
@ -15,42 +15,39 @@ 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 ACTIVATE_OTA
|
||||
#-D DEBUG_ESP_HTTP_CLIENT
|
||||
#-D DEBUG_ESP_HTTP_SERVER
|
||||
#-D DEBUG_ESP_PORT=Serial
|
||||
#-D DEBUG_ESP_WIFI
|
||||
#-D DEBUG_ESP_SSL
|
||||
#-D DEBUG_ESP_CORE
|
||||
#-D SKIP_SLEEPMODE
|
||||
-D CFG_DISABLE_LOGGING # Turn off verbose logging for some of the parts (too much will cause a crash) but also add space
|
||||
-D GYRO_DISABLE_LOGGING
|
||||
-D PUSH_DISABLE_LOGGING
|
||||
-D TSEN_DISABLE_LOGGING
|
||||
-D WEB_DISABLE_LOGGING
|
||||
-D MAIN_DISABLE_LOGGING
|
||||
-D USE_LITTLEFS=true
|
||||
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
|
||||
-D USER_SSID=\""\"" # =\""myssid\""
|
||||
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
|
||||
-D CFG_APPVER="\"0.6.0\""
|
||||
-D CFG_APPVER="\"1.1.0\""
|
||||
#-D CFG_GITREV=\""beta-4\""
|
||||
!python script/git_rev.py
|
||||
lib_deps = # Switched to forks for better version control.
|
||||
# Using local copy of this library
|
||||
#https://github.com/jrowberg/i2cdevlib.git#<document>
|
||||
#https://github.com/khoih-prog/ESP_WiFiManager#<document>
|
||||
#https://github.com/khoih-prog/ESP_DoubleResetDetector#<document>
|
||||
# Using local copy of these libraries
|
||||
#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
|
||||
https://github.com/mp-se/ArduinoJson#v6.18.5 # https://github.com/bblanchon/ArduinoJson
|
||||
https://github.com/mp-se/OneWire#v2.3.6 # https://github.com/PaulStoffregen/OneWire
|
||||
https://github.com/mp-se/Arduino-Temperature-Control-Library#3.9.1 # https://github.com/milesburton/Arduino-Temperature-Control-Library
|
||||
https://github.com/mp-se/arduinoCurveFitting#v1.0.6 # https://github.com/Rotario/arduinoCurveFitting
|
||||
https://github.com/mp-se/arduino-mqtt#v2.5.0 # https://github.com/256dpi/arduino-mqtt
|
||||
|
||||
@ -65,20 +62,27 @@ extra_scripts =
|
||||
script/create_versionjson.py
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
# -D MAIN_DISABLE_LOGGING
|
||||
-D WEB_DISABLE_LOGGING
|
||||
-D PUSH_DISABLE_LOGGING
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
|
||||
#-D SKIP_SLEEPMODE
|
||||
-D DOUBLERESETDETECTOR_DEBUG=true
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
-D LOG_LEVEL=6 # Maximum log level for the debug build.
|
||||
#-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.
|
||||
-D GYRO_DISABLE_LOGGING
|
||||
-D CALC_DISABLE_LOGGING
|
||||
-D HELPER_DISABLE_LOGGING
|
||||
-D PUSH_DISABLE_LOGGING
|
||||
-D TSEN_DISABLE_LOGGING
|
||||
-D WIFI_DISABLE_LOGGING
|
||||
-D WEB_DISABLE_LOGGING
|
||||
-D MAIN_DISABLE_LOGGING
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
board = ${common_env_data.board}
|
||||
#build_type = debug # Using debug type crashes my devkit...
|
||||
#build_type = debug
|
||||
build_type = release
|
||||
board_build.filesystem = littlefs
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
@ -113,11 +117,71 @@ extra_scripts =
|
||||
script/create_versionjson.py
|
||||
build_unflags = ${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
board = ${common_env_data.board}
|
||||
build_type = release
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
[env:gravity32-release]
|
||||
framework = arduino
|
||||
platform = espressif32 @ 5.0.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
-Wl,-Map,output.map
|
||||
${common_env_data.build_flags}
|
||||
#-D COLLECT_PERFDATA
|
||||
-D LOG_LEVEL=5
|
||||
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart (applies to LOG_LEVEL6)
|
||||
-D GYRO_DISABLE_LOGGING
|
||||
-D CALC_DISABLE_LOGGING
|
||||
-D HELPER_DISABLE_LOGGING
|
||||
-D PUSH_DISABLE_LOGGING
|
||||
-D TSEN_DISABLE_LOGGING
|
||||
-D WIFI_DISABLE_LOGGING
|
||||
-D WEB_DISABLE_LOGGING
|
||||
-D MAIN_DISABLE_LOGGING
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
https://github.com/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
|
||||
board_build.filesystem = littlefs
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:gravity32-perf]
|
||||
framework = arduino
|
||||
platform = espressif32 @ 5.0.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
script/copy_html.py
|
||||
script/copy_firmware.py
|
||||
script/create_versionjson.py
|
||||
build_unflags =
|
||||
${common_env_data.build_unflags}
|
||||
build_flags =
|
||||
-Wl,-Map,output.map
|
||||
${common_env_data.build_flags}
|
||||
-D COLLECT_PERFDATA
|
||||
-D LOG_LEVEL=5
|
||||
lib_deps =
|
||||
${common_env_data.lib_deps}
|
||||
https://github.com/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
|
||||
|
@ -11,15 +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"
|
||||
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 )
|
||||
|
||||
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"
|
||||
source = dir + "/.pio/build/" + name + "/firmware.bin"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
|
||||
print( "Adding custom build step (copy firmware): ")
|
||||
|
@ -17,12 +17,18 @@ shutil.copyfile( source + file, target + file )
|
||||
file = "config.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "device.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "index.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "upload.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "format.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "test.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
file = "firmware.min.htm"
|
||||
#print( "Copy file: " + source + file + "->" + target + file)
|
||||
shutil.copyfile( source + file, target + file )
|
||||
|
@ -19,36 +19,43 @@ def after_build(source, target, env):
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 2
|
||||
source = dir + "/data/device.min.htm"
|
||||
target = dir + "/bin/device.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 3
|
||||
source = dir + "/data/config.min.htm"
|
||||
target = dir + "/bin/config.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 4
|
||||
# Copy file 3
|
||||
source = dir + "/data/about.min.htm"
|
||||
target = dir + "/bin/about.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 5
|
||||
# Copy file 4
|
||||
source = dir + "/data/calibration.min.htm"
|
||||
target = dir + "/bin/calibration.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 5
|
||||
source = dir + "/data/format.min.htm"
|
||||
target = dir + "/bin/format.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
# Copy file 6
|
||||
source = dir + "/data/test.min.htm"
|
||||
target = dir + "/bin/test.min.htm"
|
||||
print( "Copy file : " + source + " -> " + target )
|
||||
shutil.copyfile( source, target )
|
||||
|
||||
target = dir + "/bin/version.json"
|
||||
ver = get_build_flag_value("CFG_APPVER")
|
||||
|
||||
print( "Creating version.json" )
|
||||
f = open( target, "w" )
|
||||
f.write( "{ \"project\":\"gravmon\", \"version\":" + ver + ", " )
|
||||
f.write( " \"html\": [ \"index.min.htm\", \"device.min.htm\", \"config.min.htm\", \"calibration.min.htm\", \"about.min.htm\" ] }" )
|
||||
#f.write( " \"html\": [ \"index.min.htm\", \"config.min.htm\", \"calibration.min.htm\", \"test.min.htm\", \"format.min.htm\", \"about.min.htm\" ] }" )
|
||||
f.write( " \"html\": [ ] }" )
|
||||
f.close()
|
||||
|
||||
|
||||
|
9
script/git_rev.py
Normal file
9
script/git_rev.py
Normal file
@ -0,0 +1,9 @@
|
||||
import subprocess
|
||||
|
||||
revision = (
|
||||
subprocess.check_output(["git", "rev-parse", "HEAD"])
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
revision = revision[-6:]
|
||||
print("-D CFG_GITREV='\"..%s\"'" % revision)
|
102
src/ble.cpp
Normal file
102
src/ble.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP32)
|
||||
|
||||
#include <ble.hpp>
|
||||
#include <string>
|
||||
|
||||
// Tilt UUID variants and data format, based on tilt-sim
|
||||
//
|
||||
// https://github.com/spouliot/tilt-sim
|
||||
//
|
||||
// Tilt data format is described here. Only SG and Temp is transmitted over BLE.
|
||||
// https://kvurd.com/blog/tilt-hydrometer-ibeacon-data-format/
|
||||
|
||||
//
|
||||
// Create ble sender
|
||||
//
|
||||
BleSender::BleSender(const char* color) {
|
||||
BLEDevice::init("");
|
||||
|
||||
// boost power to maximum, these might be changed once battery life using BLE
|
||||
// has been tested.
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9);
|
||||
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN, ESP_PWR_LVL_P9);
|
||||
|
||||
_advertising = BLEDevice::getAdvertising();
|
||||
_color = color;
|
||||
|
||||
if (!_color.compareTo("red"))
|
||||
_uuid = BLEUUID::fromString("A495BB10-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("green"))
|
||||
_uuid = BLEUUID::fromString("A495BB20-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("black"))
|
||||
_uuid = BLEUUID::fromString("A495BB30-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("purple"))
|
||||
_uuid = BLEUUID::fromString("A495BB40-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("orange"))
|
||||
_uuid = BLEUUID::fromString("A495BB50-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("blue"))
|
||||
_uuid = BLEUUID::fromString("A495BB60-C5B1-4B44-B512-1370F02D74DE");
|
||||
else if (!_color.compareTo("yellow"))
|
||||
_uuid = BLEUUID::fromString("A495BB70-C5B1-4B44-B512-1370F02D74DE");
|
||||
else // if (_color.compareTo("pink"))
|
||||
_uuid = BLEUUID::fromString("A495BB80-C5B1-4B44-B512-1370F02D74DE");
|
||||
}
|
||||
|
||||
//
|
||||
// Send temp and gravity via BLE
|
||||
//
|
||||
void BleSender::sendData(float tempF, float gravSG) {
|
||||
uint16_t gravity = gravSG * 1000; // SG * 1000 or SG * 10000 for Tilt Pro/HD
|
||||
uint16_t temperature = tempF; // Deg F _or_ Deg F * 10 for Tilt Pro/HD
|
||||
|
||||
BLEBeacon oBeacon = BLEBeacon();
|
||||
oBeacon.setManufacturerId(
|
||||
0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!)
|
||||
oBeacon.setProximityUUID(_uuid);
|
||||
oBeacon.setMajor(temperature);
|
||||
oBeacon.setMinor(gravity);
|
||||
std::string strServiceData = "";
|
||||
strServiceData += static_cast<char>(26); // Len
|
||||
strServiceData += static_cast<char>(0xFF); // Type
|
||||
strServiceData += oBeacon.getData();
|
||||
|
||||
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
|
||||
oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04
|
||||
oAdvertisementData.addData(strServiceData);
|
||||
|
||||
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
|
||||
_advertising->setAdvertisementData(oAdvertisementData);
|
||||
_advertising->setScanResponseData(oScanResponseData);
|
||||
_advertising->setAdvertisementType(BLE_GAP_CONN_MODE_NON);
|
||||
|
||||
_advertising->start();
|
||||
delay(100);
|
||||
_advertising->stop();
|
||||
delay(100);
|
||||
}
|
||||
|
||||
#endif // ESP32
|
47
src/ble.hpp
Normal file
47
src/ble.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_BLE_HPP_
|
||||
#define SRC_BLE_HPP_
|
||||
|
||||
#if defined(ESP32)
|
||||
|
||||
#include <NimBLEBeacon.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
class BleSender {
|
||||
private:
|
||||
BLEAdvertising* _advertising;
|
||||
String _color;
|
||||
BLEUUID _uuid;
|
||||
|
||||
public:
|
||||
explicit BleSender(const char* color);
|
||||
void sendData(float tempF, float gravSG);
|
||||
};
|
||||
|
||||
#endif // ESP32
|
||||
#endif // SRC_BLE_HPP_
|
93
src/calc.cpp
93
src/calc.cpp
@ -25,11 +25,7 @@ SOFTWARE.
|
||||
#include <tinyexpr.h>
|
||||
|
||||
#include <calc.hpp>
|
||||
#include <config.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
|
||||
#define FORMULA_MAX_DEVIATION 1.5
|
||||
#include <main.hpp>
|
||||
|
||||
//
|
||||
// Use values to derive a formula
|
||||
@ -37,33 +33,37 @@ 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
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
F("CALC: Trying to create formula using order = %d, found %d angles" CR),
|
||||
order, noAngles);
|
||||
#endif
|
||||
|
||||
if (!noAngles) {
|
||||
Log.error(F("CALC: Not enough values for deriving formula" CR));
|
||||
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
|
||||
Log.verbose(F("CALC: Finshied processing data points." CR));
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Finshied processing data points, order = %d." CR),
|
||||
order);
|
||||
#endif
|
||||
|
||||
// Print the formula based on 'order'
|
||||
@ -83,7 +83,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
coeffs[1]);
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
|
||||
#endif
|
||||
|
||||
@ -96,11 +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 > FORMULA_MAX_DEVIATION) valid = false;
|
||||
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) {
|
||||
Log.error(F("CALC: Deviation to large, formula rejected." CR));
|
||||
return ERR_FORMULA_UNABLETOFFIND;
|
||||
}
|
||||
|
||||
@ -109,7 +114,7 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Internal error finding formula." CR));
|
||||
writeErrorLog("CALC: Internal error finding formula.");
|
||||
return ERR_FORMULA_INTERNAL;
|
||||
}
|
||||
|
||||
@ -121,13 +126,13 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
const char *formula = myConfig.getGravityFormula();
|
||||
|
||||
if (tempFormula != 0) {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Using temporary formula." CR));
|
||||
#endif
|
||||
formula = tempFormula;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
|
||||
temp);
|
||||
Log.verbose(F("CALC: Formula %s." CR), formula);
|
||||
@ -146,30 +151,33 @@ double calculateGravity(double angle, double temp, const char *tempFormula) {
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
char s[20];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", g);
|
||||
Log.verbose(F("CALC: Calculated gravity is %s." CR), &s[0]);
|
||||
#endif
|
||||
return g;
|
||||
}
|
||||
|
||||
Log.error(F("CALC: Failed to parse expression %d." CR), err);
|
||||
writeErrorLog("CALC: Failed to parse gravity expression %d", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Do a standard gravity temperature correction. This is a simple way to adjust
|
||||
// for differnt worth temperatures
|
||||
// for differnt worth temperatures. This function uses C as temperature.
|
||||
//
|
||||
double gravityTemperatureCorrection(double gravity, double temp,
|
||||
char tempFormat, double calTemp) {
|
||||
#if LOG_LEVEL == 6
|
||||
// Source: https://homebrewacademy.com/hydrometer-temperature-correction/
|
||||
//
|
||||
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, temp, calTemp);
|
||||
gravitySG, tempC, calTempC);
|
||||
#endif
|
||||
|
||||
if (tempFormat == 'C') temp = convertCtoF(temp);
|
||||
double calTempF = convertCtoF(calTemp); // calTemp is in C
|
||||
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)/"
|
||||
@ -178,7 +186,7 @@ double gravityTemperatureCorrection(double gravity, double temp,
|
||||
|
||||
// Store variable names and pointers.
|
||||
te_variable vars[] = {
|
||||
{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
|
||||
{"gravity", &gravitySG}, {"temp", &tempF}, {"cal", &calTempF}};
|
||||
|
||||
int err;
|
||||
// Compile the expression with variables.
|
||||
@ -188,16 +196,19 @@ double gravityTemperatureCorrection(double gravity, double temp,
|
||||
double g = te_eval(expr);
|
||||
te_free(expr);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CALC: Corrected gravity is %F." CR), g);
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
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;
|
||||
}
|
||||
|
||||
Log.error(
|
||||
F("CALC: Failed to parse expression %d, no correction has been made." CR),
|
||||
writeErrorLog(
|
||||
"CALC: Failed to parse expression for gravity temperature correction %d",
|
||||
err);
|
||||
return gravity;
|
||||
return gravitySG;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
10
src/calc.hpp
10
src/calc.hpp
@ -24,18 +24,16 @@ SOFTWARE.
|
||||
#ifndef SRC_CALC_HPP_
|
||||
#define SRC_CALC_HPP_
|
||||
|
||||
// Includes
|
||||
#include <config.hpp>
|
||||
#include <helper.hpp>
|
||||
|
||||
#define ERR_FORMULA_NOTENOUGHVALUES -1
|
||||
#define ERR_FORMULA_INTERNAL -2
|
||||
#define ERR_FORMULA_UNABLETOFFIND -3
|
||||
|
||||
// Functions
|
||||
double calculateGravity(double angle, double temp, const char *tempFormula = 0);
|
||||
double gravityTemperatureCorrection(double gravity, double temp,
|
||||
char tempFormat, double calTemp = 20);
|
||||
double calculateGravity(double angle, double tempC,
|
||||
const char *tempFormula = 0);
|
||||
double gravityTemperatureCorrectionC(double gravity, double tempC,
|
||||
double calTempC);
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order);
|
||||
|
||||
|
509
src/config.cpp
509
src/config.cpp
@ -21,12 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
Config myConfig;
|
||||
AdvancedConfig myAdvancedConfig;
|
||||
|
||||
//
|
||||
// Create the config class with default settings.
|
||||
@ -34,26 +34,23 @@ Config myConfig;
|
||||
Config::Config() {
|
||||
// Assiging default values
|
||||
char buf[30];
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", (unsigned int)ESP.getChipId());
|
||||
id = String(&buf[0]);
|
||||
snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID());
|
||||
mDNS = String(&buf[0]);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Created config for %s (%s)." CR), id.c_str(),
|
||||
mDNS.c_str());
|
||||
#if defined(ESP8266)
|
||||
snprintf(&buf[0], sizeof(buf), "%06x", (unsigned int)ESP.getChipId());
|
||||
#else // defined (ESP32)
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%06x", chipId);
|
||||
#endif
|
||||
_id = String(&buf[0]);
|
||||
snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID());
|
||||
_mDNS = String(&buf[0]);
|
||||
|
||||
setTempFormat('C');
|
||||
setGravityFormat('G');
|
||||
setSleepInterval(900); // 15 minutes
|
||||
setVoltageFactor(1.59); // Conversion factor for battery
|
||||
setTempSensorAdj(0.0);
|
||||
setGravityTempAdj(false);
|
||||
gyroCalibration = {0, 0, 0, 0, 0, 0};
|
||||
formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
|
||||
gyroTemp = false;
|
||||
saveNeeded = false;
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Created config for %s (%s)." CR), _id.c_str(),
|
||||
_mDNS.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
@ -61,79 +58,101 @@ Config::Config() {
|
||||
// web and saving to file)
|
||||
//
|
||||
void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[CFG_PARAM_MDNS] = getMDNS();
|
||||
doc[CFG_PARAM_ID] = getID();
|
||||
doc[CFG_PARAM_OTA] = getOtaURL();
|
||||
doc[CFG_PARAM_SSID] = getWifiSSID();
|
||||
doc[CFG_PARAM_PASS] = getWifiPass();
|
||||
doc[CFG_PARAM_TEMPFORMAT] = String(getTempFormat());
|
||||
doc[CFG_PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
|
||||
doc[CFG_PARAM_PUSH_HTTP] = getHttpPushUrl();
|
||||
doc[CFG_PARAM_PUSH_HTTP2] = getHttpPushUrl2();
|
||||
doc[CFG_PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
|
||||
doc[CFG_PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
|
||||
doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
|
||||
doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
|
||||
doc[CFG_PARAM_PUSH_MQTT] = getMqttUrl();
|
||||
doc[CFG_PARAM_PUSH_MQTT_TOPIC] = getMqttTopic();
|
||||
doc[CFG_PARAM_PUSH_MQTT_USER] = getMqttUser();
|
||||
doc[CFG_PARAM_PUSH_MQTT_PASS] = getMqttPass();
|
||||
doc[CFG_PARAM_SLEEP_INTERVAL] = getSleepInterval();
|
||||
doc[CFG_PARAM_VOLTAGEFACTOR] = getVoltageFactor();
|
||||
doc[CFG_PARAM_GRAVITY_FORMULA] = getGravityFormula();
|
||||
doc[CFG_PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
|
||||
doc[CFG_PARAM_TEMP_ADJ] = getTempSensorAdj();
|
||||
doc[CFG_PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
|
||||
doc[CFG_PARAM_GYRO_TEMP] = isGyroTemp();
|
||||
doc[PARAM_MDNS] = getMDNS();
|
||||
// doc[PARAM_CONFIG_VER] = getConfigVersion();
|
||||
doc[PARAM_ID] = getID();
|
||||
doc[PARAM_OTA] = getOtaURL();
|
||||
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_TOKEN] = getToken();
|
||||
doc[PARAM_TOKEN2] = getToken2();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpUrl();
|
||||
doc[PARAM_PUSH_HTTP_H1] = getHttpHeader(0);
|
||||
doc[PARAM_PUSH_HTTP_H2] = getHttpHeader(1);
|
||||
doc[PARAM_PUSH_HTTP2] = getHttp2Url();
|
||||
doc[PARAM_PUSH_HTTP2_H1] = getHttp2Header(0);
|
||||
doc[PARAM_PUSH_HTTP2_H2] = getHttp2Header(1);
|
||||
doc[PARAM_PUSH_HTTP3] = getHttp3Url();
|
||||
doc[PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
|
||||
doc[PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
|
||||
doc[PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
|
||||
doc[PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
|
||||
doc[PARAM_PUSH_MQTT] = getMqttUrl();
|
||||
doc[PARAM_PUSH_MQTT_PORT] = getMqttPort();
|
||||
doc[PARAM_PUSH_MQTT_USER] = getMqttUser();
|
||||
doc[PARAM_PUSH_MQTT_PASS] = getMqttPass();
|
||||
doc[PARAM_SLEEP_INTERVAL] = getSleepInterval();
|
||||
doc[PARAM_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(CFG_PARAM_GYRO_CALIBRATION);
|
||||
cal["ax"] = gyroCalibration.ax;
|
||||
cal["ay"] = gyroCalibration.ay;
|
||||
cal["az"] = gyroCalibration.az;
|
||||
cal["gx"] = gyroCalibration.gx;
|
||||
cal["gy"] = gyroCalibration.gy;
|
||||
cal["gz"] = gyroCalibration.gz;
|
||||
JsonObject cal = doc.createNestedObject(PARAM_GYRO_CALIBRATION);
|
||||
cal["ax"] = _gyroCalibration.ax;
|
||||
cal["ay"] = _gyroCalibration.ay;
|
||||
cal["az"] = _gyroCalibration.az;
|
||||
cal["gx"] = _gyroCalibration.gx;
|
||||
cal["gy"] = _gyroCalibration.gy;
|
||||
cal["gz"] = _gyroCalibration.gz;
|
||||
|
||||
JsonObject cal2 = doc.createNestedObject(CFG_PARAM_FORMULA_DATA);
|
||||
cal2["a1"] = reduceFloatPrecision(formulaData.a[0], 2);
|
||||
cal2["a2"] = reduceFloatPrecision(formulaData.a[1], 2);
|
||||
cal2["a3"] = reduceFloatPrecision(formulaData.a[2], 2);
|
||||
cal2["a4"] = reduceFloatPrecision(formulaData.a[3], 2);
|
||||
cal2["a5"] = reduceFloatPrecision(formulaData.a[4], 2);
|
||||
JsonObject cal2 = doc.createNestedObject(PARAM_FORMULA_DATA);
|
||||
cal2["a1"] = reduceFloatPrecision(_formulaData.a[0], 2);
|
||||
cal2["a2"] = reduceFloatPrecision(_formulaData.a[1], 2);
|
||||
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
|
||||
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
|
||||
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
|
||||
cal2["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["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);
|
||||
}
|
||||
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool Config::saveFile() {
|
||||
if (!saveNeeded) {
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
if (!_saveNeeded) {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Skipping save, not needed." CR));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving configuration to file." CR));
|
||||
#endif
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
|
||||
writeErrorLog("CFG : Failed to save configuration.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
|
||||
DynamicJsonDocument doc(3000);
|
||||
createJson(doc);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
@ -142,8 +161,7 @@ bool Config::saveFile() {
|
||||
configFile.flush();
|
||||
configFile.close();
|
||||
|
||||
saveNeeded = false;
|
||||
myConfig.debug();
|
||||
_saveNeeded = false;
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
@ -152,27 +170,26 @@ bool Config::saveFile() {
|
||||
// Load config file from disk
|
||||
//
|
||||
bool Config::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading configuration from file." CR));
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_FILENAME)) {
|
||||
Log.error(
|
||||
F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
|
||||
writeErrorLog("CFG : Configuration file does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
|
||||
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);
|
||||
@ -181,103 +198,142 @@ bool Config::loadFile() {
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR),
|
||||
err.c_str(), doc.capacity());
|
||||
writeErrorLog("CFG : Failed to parse configuration (json)");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CFG : Parsed configuration file." CR));
|
||||
#endif
|
||||
if (!doc[CFG_PARAM_OTA].isNull()) setOtaURL(doc[CFG_PARAM_OTA]);
|
||||
if (!doc[CFG_PARAM_MDNS].isNull()) setMDNS(doc[CFG_PARAM_MDNS]);
|
||||
if (!doc[CFG_PARAM_SSID].isNull()) setWifiSSID(doc[CFG_PARAM_SSID]);
|
||||
if (!doc[CFG_PARAM_PASS].isNull()) setWifiPass(doc[CFG_PARAM_PASS]);
|
||||
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], 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[CFG_PARAM_TEMPFORMAT].isNull()) {
|
||||
String s = doc[CFG_PARAM_TEMPFORMAT];
|
||||
if (!doc[PARAM_TEMPFORMAT].isNull()) {
|
||||
String s = doc[PARAM_TEMPFORMAT];
|
||||
setTempFormat(s.charAt(0));
|
||||
}
|
||||
|
||||
if (!doc[CFG_PARAM_PUSH_BREWFATHER].isNull())
|
||||
setBrewfatherPushUrl(doc[CFG_PARAM_PUSH_BREWFATHER]);
|
||||
if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]);
|
||||
if (!doc[PARAM_TOKEN2].isNull()) setToken2(doc[PARAM_TOKEN2]);
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]);
|
||||
if (!doc[PARAM_PUSH_HTTP_H1].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP_H2].isNull())
|
||||
setHttpHeader(doc[PARAM_PUSH_HTTP_H2], 1);
|
||||
if (!doc[PARAM_PUSH_HTTP2].isNull()) setHttp2Url(doc[PARAM_PUSH_HTTP2]);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H1].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H1], 0);
|
||||
if (!doc[PARAM_PUSH_HTTP2_H2].isNull())
|
||||
setHttp2Header(doc[PARAM_PUSH_HTTP2_H2], 1);
|
||||
if (!doc[PARAM_PUSH_HTTP3].isNull()) setHttp3Url(doc[PARAM_PUSH_HTTP3]);
|
||||
|
||||
if (!doc[CFG_PARAM_PUSH_HTTP].isNull())
|
||||
setHttpPushUrl(doc[CFG_PARAM_PUSH_HTTP]);
|
||||
if (!doc[CFG_PARAM_PUSH_HTTP2].isNull())
|
||||
setHttpPushUrl2(doc[CFG_PARAM_PUSH_HTTP2]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2].isNull())
|
||||
setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_ORG].isNull())
|
||||
setInfluxDb2PushOrg(doc[PARAM_PUSH_INFLUXDB2_ORG]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
|
||||
setInfluxDb2PushBucket(doc[PARAM_PUSH_INFLUXDB2_BUCKET]);
|
||||
if (!doc[PARAM_PUSH_INFLUXDB2_AUTH].isNull())
|
||||
setInfluxDb2PushToken(doc[PARAM_PUSH_INFLUXDB2_AUTH]);
|
||||
|
||||
if (!doc[CFG_PARAM_PUSH_INFLUXDB2].isNull())
|
||||
setInfluxDb2PushUrl(doc[CFG_PARAM_PUSH_INFLUXDB2]);
|
||||
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_ORG].isNull())
|
||||
setInfluxDb2PushOrg(doc[CFG_PARAM_PUSH_INFLUXDB2_ORG]);
|
||||
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
|
||||
setInfluxDb2PushBucket(doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET]);
|
||||
if (!doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH].isNull())
|
||||
setInfluxDb2PushToken(doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH]);
|
||||
if (!doc[PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[PARAM_PUSH_MQTT]);
|
||||
if (!doc[PARAM_PUSH_MQTT_PORT].isNull())
|
||||
setMqttPort(doc[PARAM_PUSH_MQTT_PORT].as<int>());
|
||||
if (!doc[PARAM_PUSH_MQTT_USER].isNull())
|
||||
setMqttUser(doc[PARAM_PUSH_MQTT_USER]);
|
||||
if (!doc[PARAM_PUSH_MQTT_PASS].isNull())
|
||||
setMqttPass(doc[PARAM_PUSH_MQTT_PASS]);
|
||||
|
||||
if (!doc[CFG_PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[CFG_PARAM_PUSH_MQTT]);
|
||||
if (!doc[CFG_PARAM_PUSH_MQTT_TOPIC].isNull())
|
||||
setMqttTopic(doc[CFG_PARAM_PUSH_MQTT_TOPIC]);
|
||||
if (!doc[CFG_PARAM_PUSH_MQTT_USER].isNull())
|
||||
setMqttUser(doc[CFG_PARAM_PUSH_MQTT_USER]);
|
||||
if (!doc[CFG_PARAM_PUSH_MQTT_PASS].isNull())
|
||||
setMqttPass(doc[CFG_PARAM_PUSH_MQTT_PASS]);
|
||||
|
||||
if (!doc[CFG_PARAM_SLEEP_INTERVAL].isNull())
|
||||
setSleepInterval(doc[CFG_PARAM_SLEEP_INTERVAL].as<int>());
|
||||
if (!doc[CFG_PARAM_VOLTAGEFACTOR].isNull())
|
||||
setVoltageFactor(doc[CFG_PARAM_VOLTAGEFACTOR].as<float>());
|
||||
if (!doc[CFG_PARAM_GRAVITY_FORMULA].isNull())
|
||||
setGravityFormula(doc[CFG_PARAM_GRAVITY_FORMULA]);
|
||||
if (!doc[CFG_PARAM_GRAVITY_TEMP_ADJ].isNull())
|
||||
setGravityTempAdj(doc[CFG_PARAM_GRAVITY_TEMP_ADJ].as<bool>());
|
||||
if (!doc[CFG_PARAM_GYRO_TEMP].isNull())
|
||||
setGyroTemp(doc[CFG_PARAM_GYRO_TEMP].as<bool>());
|
||||
if (!doc[CFG_PARAM_GRAVITY_FORMAT].isNull()) {
|
||||
String s = doc[CFG_PARAM_GRAVITY_FORMAT];
|
||||
if (!doc[PARAM_SLEEP_INTERVAL].isNull())
|
||||
setSleepInterval(doc[PARAM_SLEEP_INTERVAL].as<int>());
|
||||
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));
|
||||
}
|
||||
if (!doc[CFG_PARAM_TEMP_ADJ].isNull())
|
||||
setTempSensorAdj(doc[CFG_PARAM_TEMP_ADJ].as<float>());
|
||||
if (!doc[PARAM_TEMP_ADJ].isNull())
|
||||
setTempSensorAdjC(doc[PARAM_TEMP_ADJ].as<float>());
|
||||
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ax"].isNull())
|
||||
gyroCalibration.ax = doc[CFG_PARAM_GYRO_CALIBRATION]["ax"];
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ay"].isNull())
|
||||
gyroCalibration.ay = doc[CFG_PARAM_GYRO_CALIBRATION]["ay"];
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["az"].isNull())
|
||||
gyroCalibration.az = doc[CFG_PARAM_GYRO_CALIBRATION]["az"];
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gx"].isNull())
|
||||
gyroCalibration.gx = doc[CFG_PARAM_GYRO_CALIBRATION]["gx"];
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gy"].isNull())
|
||||
gyroCalibration.gy = doc[CFG_PARAM_GYRO_CALIBRATION]["gy"];
|
||||
if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gz"].isNull())
|
||||
gyroCalibration.gz = doc[CFG_PARAM_GYRO_CALIBRATION]["gz"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["ax"].isNull())
|
||||
_gyroCalibration.ax = doc[PARAM_GYRO_CALIBRATION]["ax"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["ay"].isNull())
|
||||
_gyroCalibration.ay = doc[PARAM_GYRO_CALIBRATION]["ay"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["az"].isNull())
|
||||
_gyroCalibration.az = doc[PARAM_GYRO_CALIBRATION]["az"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gx"].isNull())
|
||||
_gyroCalibration.gx = doc[PARAM_GYRO_CALIBRATION]["gx"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gy"].isNull())
|
||||
_gyroCalibration.gy = doc[PARAM_GYRO_CALIBRATION]["gy"];
|
||||
if (!doc[PARAM_GYRO_CALIBRATION]["gz"].isNull())
|
||||
_gyroCalibration.gz = doc[PARAM_GYRO_CALIBRATION]["gz"];
|
||||
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["a1"].isNull())
|
||||
formulaData.a[0] = doc[CFG_PARAM_FORMULA_DATA]["a1"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["a2"].isNull())
|
||||
formulaData.a[1] = doc[CFG_PARAM_FORMULA_DATA]["a2"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["a3"].isNull())
|
||||
formulaData.a[2] = doc[CFG_PARAM_FORMULA_DATA]["a3"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["a4"].isNull())
|
||||
formulaData.a[3] = doc[CFG_PARAM_FORMULA_DATA]["a4"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["a5"].isNull())
|
||||
formulaData.a[4] = doc[CFG_PARAM_FORMULA_DATA]["a5"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a1"].isNull())
|
||||
_formulaData.a[0] = doc[PARAM_FORMULA_DATA]["a1"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a2"].isNull())
|
||||
_formulaData.a[1] = doc[PARAM_FORMULA_DATA]["a2"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a3"].isNull())
|
||||
_formulaData.a[2] = doc[PARAM_FORMULA_DATA]["a3"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a4"].isNull())
|
||||
_formulaData.a[3] = doc[PARAM_FORMULA_DATA]["a4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a5"].isNull())
|
||||
_formulaData.a[4] = doc[PARAM_FORMULA_DATA]["a5"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["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[CFG_PARAM_FORMULA_DATA]["g1"].isNull())
|
||||
formulaData.g[0] = doc[CFG_PARAM_FORMULA_DATA]["g1"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["g2"].isNull())
|
||||
formulaData.g[1] = doc[CFG_PARAM_FORMULA_DATA]["g2"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["g3"].isNull())
|
||||
formulaData.g[2] = doc[CFG_PARAM_FORMULA_DATA]["g3"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["g4"].isNull())
|
||||
formulaData.g[3] = doc[CFG_PARAM_FORMULA_DATA]["g4"].as<double>();
|
||||
if (!doc[CFG_PARAM_FORMULA_DATA]["g5"].isNull())
|
||||
formulaData.g[4] = doc[CFG_PARAM_FORMULA_DATA]["g5"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
|
||||
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g2"].isNull())
|
||||
_formulaData.g[1] = doc[PARAM_FORMULA_DATA]["g2"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g3"].isNull())
|
||||
_formulaData.g[2] = doc[PARAM_FORMULA_DATA]["g3"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g4"].isNull())
|
||||
_formulaData.g[3] = doc[PARAM_FORMULA_DATA]["g4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g5"].isNull())
|
||||
_formulaData.g[4] = doc[PARAM_FORMULA_DATA]["g5"].as<double>();
|
||||
if (!doc[PARAM_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>();
|
||||
|
||||
myConfig.debug();
|
||||
saveNeeded = false; // Reset save flag
|
||||
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
|
||||
// If this parameter is missing we need to reset the gyrocalibaration due to
|
||||
bug #29 _gyroCalibration.ax = _gyroCalibration.ay = _gyroCalibration.az = 0;
|
||||
_gyroCalibration.gx = _gyroCalibration.gy = _gyroCalibration.gz = 0;
|
||||
Log.warning(F("CFG : Old configuration format, clearing gyro calibration."
|
||||
CR));
|
||||
}*/
|
||||
|
||||
_saveNeeded = false; // Reset save flag
|
||||
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
|
||||
return true;
|
||||
}
|
||||
@ -294,7 +350,7 @@ void Config::formatFileSystem() {
|
||||
// Check if file system can be mounted, if not we format it.
|
||||
//
|
||||
void Config::checkFileSystem() {
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
|
||||
#endif
|
||||
|
||||
@ -307,31 +363,128 @@ void Config::checkFileSystem() {
|
||||
}
|
||||
|
||||
//
|
||||
// Dump the configuration to the serial port
|
||||
// Save json document to file
|
||||
//
|
||||
void Config::debug() {
|
||||
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR));
|
||||
Log.verbose(F("CFG : ID; '%s'." CR), getID());
|
||||
Log.verbose(F("CFG : WIFI; '%s', '%s'." CR), getWifiSSID(), getWifiPass());
|
||||
Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS());
|
||||
Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval());
|
||||
Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL());
|
||||
Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat());
|
||||
Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdj());
|
||||
Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor());
|
||||
Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula());
|
||||
Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat());
|
||||
Log.verbose(F("CFG : Gravity temp adj; %s." CR),
|
||||
isGravityTempAdj() ? "true" : "false");
|
||||
Log.verbose(F("CFG : Gyro temp; %s." CR), isGyroTemp() ? "true" : "false");
|
||||
Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl());
|
||||
Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl());
|
||||
Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2());
|
||||
Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR),
|
||||
getInfluxDb2PushUrl(), getInfluxDb2PushOrg(),
|
||||
getInfluxDb2PushBucket(), getInfluxDb2PushToken());
|
||||
bool AdvancedConfig::saveFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving hardware configuration to file." CR));
|
||||
#endif
|
||||
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "w");
|
||||
|
||||
if (!configFile) {
|
||||
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_MOVING_THREASHOLD] = this->getGyroSensorMovingThreashold();
|
||||
doc[PARAM_HW_FORMULA_DEVIATION] = this->getMaxFormulaCreationDeviation();
|
||||
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);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
|
||||
serializeJson(doc, configFile);
|
||||
configFile.flush();
|
||||
configFile.close();
|
||||
|
||||
Log.notice(F("CFG : Configuration saved to " CFG_HW_FILENAME "." CR));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool AdvancedConfig::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading hardware configuration from file." CR));
|
||||
#endif
|
||||
|
||||
if (!LittleFS.exists(CFG_HW_FILENAME)) {
|
||||
Log.warning(
|
||||
F("CFG : Configuration file does not exist " CFG_HW_FILENAME "." CR));
|
||||
return false;
|
||||
}
|
||||
|
||||
File configFile = LittleFS.open(CFG_HW_FILENAME, "r");
|
||||
|
||||
if (!configFile) {
|
||||
writeErrorLog("CFG : Failed to read hardware configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
|
||||
configFile.size());
|
||||
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, configFile);
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
#endif
|
||||
configFile.close();
|
||||
|
||||
if (err) {
|
||||
writeErrorLog("CFG : Failed to parse hardware configuration (json)");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("CFG : Parsed hardware configuration file." CR));
|
||||
#endif
|
||||
|
||||
if (!doc[PARAM_HW_GYRO_READ_COUNT].isNull())
|
||||
this->setGyroReadCount(doc[PARAM_HW_GYRO_READ_COUNT].as<int>());
|
||||
// if (!doc[PARAM_HW_GYRO_READ_DELAY].isNull())
|
||||
// this->setGyroReadDelay(doc[PARAM_HW_GYRO_READ_DELAY].as<int>());
|
||||
if (!doc[PARAM_HW_GYRO_MOVING_THREASHOLD].isNull())
|
||||
this->setGyroSensorMovingThreashold(
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD].as<int>());
|
||||
if (!doc[PARAM_HW_FORMULA_DEVIATION].isNull())
|
||||
this->setMaxFormulaCreationDeviation(
|
||||
doc[PARAM_HW_FORMULA_DEVIATION].as<float>());
|
||||
if (!doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].isNull())
|
||||
this->SetDefaultCalibrationTemp(
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].as<float>());
|
||||
if (!doc[PARAM_HW_WIFI_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;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
519
src/config.hpp
519
src/config.hpp
@ -24,71 +24,12 @@ SOFTWARE.
|
||||
#ifndef SRC_CONFIG_HPP_
|
||||
#define SRC_CONFIG_HPP_
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <helper.hpp>
|
||||
#include <resources.hpp>
|
||||
|
||||
// defintions
|
||||
#define CFG_JSON_BUFSIZE 3192
|
||||
|
||||
#define CFG_APPNAME "GravityMon " // Name of firmware
|
||||
#define CFG_FILENAME "/gravitymon.json" // Name of config file
|
||||
|
||||
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
|
||||
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
|
||||
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
|
||||
#define WIFI_PORTAL_TIMEOUT \
|
||||
120 // Number of seconds until the config portal is closed
|
||||
|
||||
// These are used in API + Savefile
|
||||
#define CFG_PARAM_ID "id"
|
||||
#define CFG_PARAM_MDNS "mdns" // Device name
|
||||
#define CFG_PARAM_OTA "ota-url"
|
||||
#define CFG_PARAM_SSID "wifi-ssid"
|
||||
#define CFG_PARAM_PASS "wifi-pass"
|
||||
|
||||
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push"
|
||||
#define CFG_PARAM_PUSH_HTTP "http-push"
|
||||
#define CFG_PARAM_PUSH_HTTP2 "http-push2"
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push"
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
|
||||
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth"
|
||||
#define CFG_PARAM_PUSH_MQTT "mqtt-push"
|
||||
#define CFG_PARAM_PUSH_MQTT_USER "mqtt-user"
|
||||
#define CFG_PARAM_PUSH_MQTT_PASS "mqtt-pass"
|
||||
#define CFG_PARAM_PUSH_MQTT_TOPIC "mqtt-topic"
|
||||
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
|
||||
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
|
||||
#define CFG_PARAM_VOLTAGEFACTOR \
|
||||
"voltage-factor" // Factor to calculate the battery voltage
|
||||
#define CFG_PARAM_GRAVITY_FORMULA \
|
||||
"gravity-formula" // Formula for calculating gravity
|
||||
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
|
||||
#define CFG_PARAM_GRAVITY_TEMP_ADJ \
|
||||
"gravity-temp-adjustment" // True/False. Adjust gravity for temperature
|
||||
#define CFG_PARAM_TEMP_ADJ \
|
||||
"temp-adjustment-value" // Correction value for temp sensor
|
||||
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
|
||||
#define CFG_PARAM_GYRO_TEMP \
|
||||
"gyro-temp" // True/False. Use temp sensor in gyro (only in gravity mode)
|
||||
|
||||
#define CFG_PARAM_FORMULA_DATA \
|
||||
"formula-calculation-data" // Raw data for the formula calculation
|
||||
|
||||
// These are used in API's
|
||||
#define CFG_PARAM_APP_NAME "app-name"
|
||||
#define CFG_PARAM_APP_VER "app-ver"
|
||||
#define CFG_PARAM_ANGLE "angle"
|
||||
#define CFG_PARAM_GRAVITY "gravity"
|
||||
#define CFG_PARAM_TEMP_C "temp-c"
|
||||
#define CFG_PARAM_TEMP_F "temp-f"
|
||||
#define CFG_PARAM_BATTERY "battery"
|
||||
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
|
||||
#define CFG_PARAM_RSSI "rssi"
|
||||
#define CFG_APPNAME "GravityMon" // Name of firmware
|
||||
#define CFG_FILENAME "/gravitymon.json" // Name of config file
|
||||
#define CFG_HW_FILENAME "/hardware.json" // Name of config file for hw
|
||||
|
||||
// Used for holding sensordata or sensoroffsets
|
||||
struct RawGyroData {
|
||||
@ -104,231 +45,407 @@ 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 AdvancedConfig {
|
||||
private:
|
||||
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();
|
||||
};
|
||||
|
||||
// Main configuration class
|
||||
class Config {
|
||||
private:
|
||||
bool saveNeeded;
|
||||
bool _saveNeeded = false;
|
||||
int _configVersion = 2;
|
||||
|
||||
// Device configuration
|
||||
String id;
|
||||
String mDNS;
|
||||
String otaURL;
|
||||
char tempFormat; // C, F
|
||||
float voltageFactor;
|
||||
float tempSensorAdj; // This value will be added to the read sensor value
|
||||
int sleepInterval;
|
||||
bool gyroTemp; // Experimental feature
|
||||
String _id = "";
|
||||
String _mDNS = "";
|
||||
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; // URL For brewfather
|
||||
String _token = "";
|
||||
String _token2 = "";
|
||||
|
||||
String httpPushUrl; // URL 1 for standard http
|
||||
String httpPushUrl2; // URL 2 for standard http
|
||||
String _httpUrl = "";
|
||||
String _httpHeader[2] = {"Content-Type: application/json", ""};
|
||||
String _http2Url = "";
|
||||
String _http2Header[2] = {"Content-Type: application/json", ""};
|
||||
String _http3Url = "";
|
||||
|
||||
String influxDb2Url; // URL for InfluxDB v2
|
||||
String influxDb2Org; // Organisation for InfluxDB v2
|
||||
String influxDb2Bucket; // Bucket for InfluxDB v2
|
||||
String influxDb2Token; // Auth Token for InfluxDB v2
|
||||
String _influxDb2Url = "";
|
||||
String _influxDb2Org = "";
|
||||
String _influxDb2Bucket = "";
|
||||
String _influxDb2Token = "";
|
||||
|
||||
String mqttUrl; // Server name
|
||||
String mqttTopic;
|
||||
String mqttUser;
|
||||
String mqttPass;
|
||||
String _mqttUrl = "";
|
||||
int _mqttPort = 1883;
|
||||
String _mqttUser = "";
|
||||
String _mqttPass = "";
|
||||
|
||||
// Gravity and temperature calculations
|
||||
String gravityFormula;
|
||||
bool gravityTempAdj; // true, false
|
||||
char gravityFormat; // G, P
|
||||
String _gravityFormula = "";
|
||||
bool _gravityTempAdj = false;
|
||||
char _gravityFormat = 'G';
|
||||
|
||||
// Gyro calibration data
|
||||
RawGyroData
|
||||
gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
|
||||
RawFormulaData formulaData; // Used for creating formula
|
||||
// BLE (ESP32 only)
|
||||
String _colorBLE;
|
||||
|
||||
// Gyro calibration and formula calculation data
|
||||
RawGyroData _gyroCalibration = {0, 0, 0, 0, 0, 0};
|
||||
RawFormulaData _formulaData = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
||||
|
||||
void debug();
|
||||
void formatFileSystem();
|
||||
|
||||
public:
|
||||
Config();
|
||||
const char* getID() { return id.c_str(); }
|
||||
const char* getID() { return _id.c_str(); }
|
||||
|
||||
const char* getMDNS() { return mDNS.c_str(); }
|
||||
const char* getMDNS() { return _mDNS.c_str(); }
|
||||
void setMDNS(String s) {
|
||||
mDNS = s;
|
||||
saveNeeded = true;
|
||||
_mDNS = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const bool isGyroTemp() { return gyroTemp; }
|
||||
int getConfigVersion() { return _configVersion; }
|
||||
|
||||
const bool isGyroTemp() { return _gyroTemp; }
|
||||
void setGyroTemp(bool b) {
|
||||
gyroTemp = b;
|
||||
saveNeeded = true;
|
||||
_gyroTemp = b;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getOtaURL() { return otaURL.c_str(); }
|
||||
const 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;
|
||||
saveNeeded = true;
|
||||
_otaURL = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isOtaActive() { return otaURL.length() ? true : false; }
|
||||
bool isOtaActive() { return _otaURL.length() ? true : false; }
|
||||
bool isOtaSSL() { return _otaURL.startsWith("https://"); }
|
||||
|
||||
const char* getWifiSSID() { return wifiSSID.c_str(); }
|
||||
void setWifiSSID(String s) {
|
||||
wifiSSID = s;
|
||||
saveNeeded = true;
|
||||
const char* 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;
|
||||
saveNeeded = true;
|
||||
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;
|
||||
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Brewfather
|
||||
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
|
||||
void setBrewfatherPushUrl(String s) {
|
||||
brewfatherPushUrl = s;
|
||||
saveNeeded = true;
|
||||
// Token parameter
|
||||
const char* getToken() { return _token.c_str(); }
|
||||
void setToken(String s) {
|
||||
_token = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isBrewfatherActive() {
|
||||
return brewfatherPushUrl.length() ? true : false;
|
||||
const char* getToken2() { return _token2.c_str(); }
|
||||
void setToken2(String s) {
|
||||
_token2 = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Standard HTTP
|
||||
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
|
||||
void setHttpPushUrl(String s) {
|
||||
httpPushUrl = s;
|
||||
saveNeeded = true;
|
||||
const char* getHttpUrl() { return _httpUrl.c_str(); }
|
||||
void setHttpUrl(String s) {
|
||||
_httpUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive() { return httpPushUrl.length() ? true : false; }
|
||||
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
|
||||
void setHttpPushUrl2(String s) {
|
||||
httpPushUrl2 = s;
|
||||
saveNeeded = true;
|
||||
const char* getHttpHeader(int idx) { return _httpHeader[idx].c_str(); }
|
||||
void setHttpHeader(String s, int idx) {
|
||||
_httpHeader[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive2() { return httpPushUrl2.length() ? true : false; }
|
||||
bool isHttpActive() { return _httpUrl.length() ? true : false; }
|
||||
bool isHttpSSL() { return _httpUrl.startsWith("https://"); }
|
||||
|
||||
const char* getHttp2Url() { return _http2Url.c_str(); }
|
||||
void setHttp2Url(String s) {
|
||||
_http2Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getHttp2Header(int idx) { return _http2Header[idx].c_str(); }
|
||||
void setHttp2Header(String s, int idx) {
|
||||
_http2Header[idx] = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttp2Active() { return _http2Url.length() ? true : false; }
|
||||
bool isHttp2SSL() { return _http2Url.startsWith("https://"); }
|
||||
|
||||
const char* getHttp3Url() { return _http3Url.c_str(); }
|
||||
void setHttp3Url(String s) {
|
||||
_http3Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isHttp3Active() { return _http3Url.length() ? true : false; }
|
||||
bool isHttp3SSL() { return _http3Url.startsWith("https://"); }
|
||||
|
||||
// InfluxDB2
|
||||
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
|
||||
const char* getInfluxDb2PushUrl() { return _influxDb2Url.c_str(); }
|
||||
void setInfluxDb2PushUrl(String s) {
|
||||
influxDb2Url = s;
|
||||
saveNeeded = true;
|
||||
_influxDb2Url = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isInfluxDb2Active() { return influxDb2Url.length() ? true : false; }
|
||||
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
|
||||
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
|
||||
bool isInfluxSSL() { return _influxDb2Url.startsWith("https://"); }
|
||||
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
|
||||
void setInfluxDb2PushOrg(String s) {
|
||||
influxDb2Org = s;
|
||||
saveNeeded = true;
|
||||
_influxDb2Org = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
|
||||
const char* getInfluxDb2PushBucket() { return _influxDb2Bucket.c_str(); }
|
||||
void setInfluxDb2PushBucket(String s) {
|
||||
influxDb2Bucket = s;
|
||||
saveNeeded = true;
|
||||
_influxDb2Bucket = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
|
||||
const char* getInfluxDb2PushToken() { return _influxDb2Token.c_str(); }
|
||||
void setInfluxDb2PushToken(String s) {
|
||||
influxDb2Token = s;
|
||||
saveNeeded = true;
|
||||
_influxDb2Token = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// MQTT
|
||||
bool isMqttActive() { return mqttUrl.length() ? true : false; }
|
||||
const char* getMqttUrl() { return mqttUrl.c_str(); }
|
||||
const char* getMqttUrl() { return _mqttUrl.c_str(); }
|
||||
void setMqttUrl(String s) {
|
||||
mqttUrl = s;
|
||||
saveNeeded = true;
|
||||
_mqttUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttTopic() { return mqttTopic.c_str(); }
|
||||
void setMqttTopic(String s) {
|
||||
mqttTopic = s;
|
||||
saveNeeded = true;
|
||||
bool isMqttActive() { return _mqttUrl.length() ? true : false; }
|
||||
bool isMqttSSL() { return _mqttPort > 8000 ? true : false; }
|
||||
|
||||
int getMqttPort() { return _mqttPort; }
|
||||
void setMqttPort(String s) {
|
||||
_mqttPort = s.toInt();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttUser() { return mqttUser.c_str(); }
|
||||
void setMqttPort(int i) {
|
||||
_mqttPort = i;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttUser() { return _mqttUser.c_str(); }
|
||||
void setMqttUser(String s) {
|
||||
mqttUser = s;
|
||||
saveNeeded = true;
|
||||
_mqttUser = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
const char* getMqttPass() { return mqttPass.c_str(); }
|
||||
const char* getMqttPass() { return _mqttPass.c_str(); }
|
||||
void setMqttPass(String s) {
|
||||
mqttPass = s;
|
||||
saveNeeded = true;
|
||||
_mqttPass = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
int getSleepInterval() { return sleepInterval; }
|
||||
int getSleepInterval() { return _sleepInterval; }
|
||||
void setSleepInterval(int v) {
|
||||
sleepInterval = v;
|
||||
saveNeeded = true;
|
||||
_sleepInterval = v;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setSleepInterval(String s) {
|
||||
sleepInterval = s.toInt();
|
||||
saveNeeded = true;
|
||||
_sleepInterval = s.toInt();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
char getTempFormat() { return tempFormat; }
|
||||
char getTempFormat() { return _tempFormat; }
|
||||
void setTempFormat(char c) {
|
||||
tempFormat = c;
|
||||
saveNeeded = true;
|
||||
if (c == 'C' || c == 'F') {
|
||||
_tempFormat = c;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
}
|
||||
bool isTempC() { return tempFormat == 'C' ? false : true; }
|
||||
bool isTempF() { return tempFormat == 'F' ? false : true; }
|
||||
bool isTempC() { return _tempFormat == 'C'; }
|
||||
bool isTempF() { return _tempFormat == 'F'; }
|
||||
|
||||
float getVoltageFactor() { return voltageFactor; }
|
||||
float getVoltageFactor() { return _voltageFactor; }
|
||||
void setVoltageFactor(float f) {
|
||||
voltageFactor = f;
|
||||
saveNeeded = true;
|
||||
_voltageFactor = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setVoltageFactor(String s) {
|
||||
voltageFactor = s.toFloat();
|
||||
saveNeeded = true;
|
||||
_voltageFactor = s.toFloat();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
float getTempSensorAdj() { return tempSensorAdj; }
|
||||
void setTempSensorAdj(float f) {
|
||||
tempSensorAdj = f;
|
||||
saveNeeded = true;
|
||||
float getVoltageConfig() { return _voltageConfig; }
|
||||
void setVoltageConfig(float f) {
|
||||
_voltageConfig = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setTempSensorAdj(String s) {
|
||||
tempSensorAdj = s.toFloat();
|
||||
saveNeeded = true;
|
||||
void setVoltageConfig(String s) {
|
||||
_voltageConfig = s.toFloat();
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getGravityFormula() { return gravityFormula.c_str(); }
|
||||
float getTempSensorAdjC() { return _tempSensorAdjC; }
|
||||
void setTempSensorAdjC(float f) {
|
||||
_tempSensorAdjC = f;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setTempSensorAdjC(String s, float adjustC = 0) {
|
||||
_tempSensorAdjC = s.toFloat() + adjustC;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
void setTempSensorAdjF(String s, float adjustF = 0) {
|
||||
_tempSensorAdjC = convertFtoC(s.toFloat() + adjustF);
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const char* getGravityFormula() { return _gravityFormula.c_str(); }
|
||||
void setGravityFormula(String s) {
|
||||
gravityFormula = s;
|
||||
saveNeeded = true;
|
||||
_gravityFormula = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
bool isGravityTempAdj() { return gravityTempAdj; }
|
||||
bool isGravityTempAdj() { return _gravityTempAdj; }
|
||||
void setGravityTempAdj(bool b) {
|
||||
gravityTempAdj = b;
|
||||
saveNeeded = true;
|
||||
_gravityTempAdj = b;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
char getGravityFormat() { return gravityFormat; }
|
||||
char getGravityFormat() { return _gravityFormat; }
|
||||
void setGravityFormat(char c) {
|
||||
gravityFormat = c;
|
||||
saveNeeded = true;
|
||||
if (c == 'G' || c == 'P') {
|
||||
_gravityFormat = c;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
}
|
||||
bool isGravitySG() { return gravityFormat == 'G' ? false : true; }
|
||||
bool isGravityPlato() { return gravityFormat == 'P' ? false : true; }
|
||||
bool isGravitySG() { return _gravityFormat == 'G'; }
|
||||
bool isGravityPlato() { return _gravityFormat == 'P'; }
|
||||
|
||||
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
|
||||
const char* getColorBLE() { return _colorBLE.c_str(); }
|
||||
void setColorBLE(String c) {
|
||||
_colorBLE = c;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isBLEActive() { return _colorBLE.length() ? true : false; }
|
||||
bool isWifiPushActive() {
|
||||
return (isHttpActive() || isHttp2Active() || isHttp3Active() ||
|
||||
isInfluxDb2Active() || isMqttActive())
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
const RawGyroData& getGyroCalibration() { return _gyroCalibration; }
|
||||
void setGyroCalibration(const RawGyroData& r) {
|
||||
gyroCalibration = r;
|
||||
saveNeeded = true;
|
||||
_gyroCalibration = r;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
const RawFormulaData& getFormulaData() { return formulaData; }
|
||||
const RawFormulaData& getFormulaData() { return _formulaData; }
|
||||
void setFormulaData(const RawFormulaData& r) {
|
||||
formulaData = r;
|
||||
saveNeeded = true;
|
||||
_formulaData = r;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// IO functions
|
||||
@ -336,12 +453,12 @@ class Config {
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
void checkFileSystem();
|
||||
bool isSaveNeeded() { return saveNeeded; }
|
||||
void setSaveNeeded() { saveNeeded = true; }
|
||||
bool isSaveNeeded() { return _saveNeeded; }
|
||||
void setSaveNeeded() { _saveNeeded = true; }
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern Config myConfig;
|
||||
extern AdvancedConfig myAdvancedConfig;
|
||||
|
||||
#endif // SRC_CONFIG_HPP_
|
||||
|
||||
|
132
src/gyro.cpp
132
src/gyro.cpp
@ -22,18 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
GyroSensor myGyro;
|
||||
MPU6050 accelgyro;
|
||||
|
||||
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
|
||||
#define SENSOR_MOVING_THREASHOLD 500
|
||||
#define SENSOR_READ_COUNT 50
|
||||
#define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz
|
||||
|
||||
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
|
||||
// calibration
|
||||
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
|
||||
// calibration
|
||||
// #define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
|
||||
|
||||
//
|
||||
@ -43,19 +39,21 @@ bool GyroSensor::setup() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Setting up hardware." CR));
|
||||
#endif
|
||||
Wire.begin(D3, D4);
|
||||
Wire.begin(PIN_SDA, PIN_SCL);
|
||||
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
|
||||
// compilation difficulties
|
||||
|
||||
if (!accelgyro.testConnection()) {
|
||||
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
|
||||
sensorConnected = false;
|
||||
|
||||
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)
|
||||
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
|
||||
#endif
|
||||
accelgyro.initialize();
|
||||
sensorConnected = true;
|
||||
_sensorConnected = true;
|
||||
|
||||
// Configure the sensor
|
||||
accelgyro.setTempSensorEnabled(true);
|
||||
@ -77,10 +75,10 @@ bool GyroSensor::setup() {
|
||||
|
||||
// Once we have calibration values stored we just apply them from the
|
||||
// config.
|
||||
calibrationOffset = myConfig.getGyroCalibration();
|
||||
_calibrationOffset = myConfig.getGyroCalibration();
|
||||
applyCalibration();
|
||||
}
|
||||
return sensorConnected;
|
||||
return _sensorConnected;
|
||||
}
|
||||
|
||||
//
|
||||
@ -199,11 +197,16 @@ float GyroSensor::calculateAngle(RawGyroData &raw) {
|
||||
az = (static_cast<float>(raw.az)) / 16384;
|
||||
|
||||
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
|
||||
float v = (acos(ay / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
float vY = (acos(abs(ay) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
|
||||
// float vZ = (acos(abs(az) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 /
|
||||
// PI); float vX = (acos(abs(ax) / sqrt(ax * ax + ay * ay + az * az)) * 180.0
|
||||
// / PI);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: angle = %F." CR), v);
|
||||
// Log.notice(F("GYRO: angleX= %F." CR), vX);
|
||||
Log.notice(F("GYRO: angleY= %F." CR), vY);
|
||||
// Log.notice(F("GYRO: angleZ= %F." CR), vZ);
|
||||
#endif
|
||||
return v;
|
||||
return vY;
|
||||
}
|
||||
|
||||
//
|
||||
@ -215,11 +218,11 @@ bool GyroSensor::isSensorMoving(RawGyroData &raw) {
|
||||
#endif
|
||||
|
||||
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
|
||||
int threashold = myAdvancedConfig.getGyroSensorMovingThreashold();
|
||||
|
||||
if (x > SENSOR_MOVING_THREASHOLD || y > SENSOR_MOVING_THREASHOLD ||
|
||||
z > SENSOR_MOVING_THREASHOLD) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR),
|
||||
SENSOR_MOVING_THREASHOLD, x, y, z);
|
||||
if (x > threashold || y > threashold || z > threashold) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
|
||||
y, z);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -234,35 +237,37 @@ bool GyroSensor::read() {
|
||||
Log.verbose(F("GYRO: Getting new gyro position." CR));
|
||||
#endif
|
||||
|
||||
if (!sensorConnected) return false;
|
||||
if (!_sensorConnected) return false;
|
||||
|
||||
readSensor(lastGyroData, SENSOR_READ_COUNT,
|
||||
SENSOR_READ_DELAY); // Last param is unused if GYRO_USE_INTERRUPT
|
||||
// is defined.
|
||||
readSensor(
|
||||
_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
|
||||
// value
|
||||
if (isSensorMoving(lastGyroData)) {
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
if (isSensorMoving(_lastGyroData)) {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Sensor is moving." CR));
|
||||
#endif
|
||||
validValue = false;
|
||||
_validValue = false;
|
||||
} else {
|
||||
validValue = true;
|
||||
angle = calculateAngle(lastGyroData);
|
||||
#if !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), lastGyroData.ax,
|
||||
lastGyroData.ay, lastGyroData.az, angle);
|
||||
_validValue = true;
|
||||
_angle = calculateAngle(_lastGyroData);
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Sensor values %d,%d,%d\t%F" CR), _lastGyroData.ax,
|
||||
_lastGyroData.ay, _lastGyroData.az, _angle);
|
||||
#endif
|
||||
}
|
||||
|
||||
sensorTemp = (static_cast<float>(lastGyroData.temp)) / 340 + 36.53;
|
||||
_sensorTemp = (static_cast<float>(_lastGyroData.temp)) / 340 + 36.53;
|
||||
|
||||
// The first read value is close to the DS18 value according to my tests, if
|
||||
// more reads are done then the gyro temp will increase to much
|
||||
if (initialSensorTemp == INVALID_TEMPERATURE) initialSensorTemp = sensorTemp;
|
||||
if (_initialSensorTemp == INVALID_TEMPERATURE)
|
||||
_initialSensorTemp = _sensorTemp;
|
||||
|
||||
return validValue;
|
||||
return _validValue;
|
||||
}
|
||||
|
||||
//
|
||||
@ -270,10 +275,10 @@ bool GyroSensor::read() {
|
||||
//
|
||||
void GyroSensor::dumpCalibration() {
|
||||
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
|
||||
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax,
|
||||
calibrationOffset.ay, calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx,
|
||||
calibrationOffset.gy, calibrationOffset.gz);
|
||||
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), _calibrationOffset.ax,
|
||||
_calibrationOffset.ay, _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), _calibrationOffset.gx,
|
||||
_calibrationOffset.gy, _calibrationOffset.gz);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -285,19 +290,20 @@ void GyroSensor::applyCalibration() {
|
||||
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR));
|
||||
#endif
|
||||
|
||||
if ((calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az +
|
||||
calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz) ==
|
||||
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
|
||||
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
|
||||
0) {
|
||||
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR));
|
||||
writeErrorLog(
|
||||
"GYRO: No valid calibration values, please calibrate the device.");
|
||||
return;
|
||||
}
|
||||
|
||||
accelgyro.setXAccelOffset(calibrationOffset.ax);
|
||||
accelgyro.setYAccelOffset(calibrationOffset.ay);
|
||||
accelgyro.setZAccelOffset(calibrationOffset.az);
|
||||
accelgyro.setXGyroOffset(calibrationOffset.gx);
|
||||
accelgyro.setYGyroOffset(calibrationOffset.gy);
|
||||
accelgyro.setZGyroOffset(calibrationOffset.gz);
|
||||
accelgyro.setXAccelOffset(_calibrationOffset.ax);
|
||||
accelgyro.setYAccelOffset(_calibrationOffset.ay);
|
||||
accelgyro.setZAccelOffset(_calibrationOffset.az);
|
||||
accelgyro.setXGyroOffset(_calibrationOffset.gx);
|
||||
accelgyro.setYGyroOffset(_calibrationOffset.gy);
|
||||
accelgyro.setZGyroOffset(_calibrationOffset.gz);
|
||||
}
|
||||
|
||||
//
|
||||
@ -317,15 +323,15 @@ void GyroSensor::calibrateSensor() {
|
||||
accelgyro.PrintActiveOffsets();
|
||||
Serial.print(CR);
|
||||
|
||||
calibrationOffset.ax = accelgyro.getXAccelOffset();
|
||||
calibrationOffset.ay = accelgyro.getYAccelOffset();
|
||||
calibrationOffset.az = accelgyro.getZAccelOffset();
|
||||
calibrationOffset.gx = accelgyro.getXGyroOffset();
|
||||
calibrationOffset.gy = accelgyro.getYGyroOffset();
|
||||
calibrationOffset.gz = accelgyro.getZGyroOffset();
|
||||
_calibrationOffset.ax = accelgyro.getXAccelOffset();
|
||||
_calibrationOffset.ay = accelgyro.getYAccelOffset();
|
||||
_calibrationOffset.az = accelgyro.getZAccelOffset();
|
||||
_calibrationOffset.gx = accelgyro.getXGyroOffset();
|
||||
_calibrationOffset.gy = accelgyro.getYGyroOffset();
|
||||
_calibrationOffset.gz = accelgyro.getZGyroOffset();
|
||||
|
||||
// Save the calibrated values
|
||||
myConfig.setGyroCalibration(calibrationOffset);
|
||||
myConfig.setGyroCalibration(_calibrationOffset);
|
||||
myConfig.saveFile();
|
||||
}
|
||||
|
||||
@ -380,17 +386,17 @@ void GyroSensor::debug() {
|
||||
}
|
||||
|
||||
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR),
|
||||
accelgyro.getXAccelOffset(), calibrationOffset.az);
|
||||
accelgyro.getXAccelOffset(), _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR),
|
||||
accelgyro.getYAccelOffset(), calibrationOffset.ay);
|
||||
accelgyro.getYAccelOffset(), _calibrationOffset.ay);
|
||||
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR),
|
||||
accelgyro.getZAccelOffset(), calibrationOffset.az);
|
||||
accelgyro.getZAccelOffset(), _calibrationOffset.az);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR),
|
||||
accelgyro.getXGyroOffset(), calibrationOffset.gx);
|
||||
accelgyro.getXGyroOffset(), _calibrationOffset.gx);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR),
|
||||
accelgyro.getYGyroOffset(), calibrationOffset.gy);
|
||||
accelgyro.getYGyroOffset(), _calibrationOffset.gy);
|
||||
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR),
|
||||
accelgyro.getZGyroOffset(), calibrationOffset.gz);
|
||||
accelgyro.getZGyroOffset(), _calibrationOffset.gz);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
30
src/gyro.hpp
30
src/gyro.hpp
@ -27,13 +27,10 @@ SOFTWARE.
|
||||
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
|
||||
// #define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
#include <MPU6050.h>
|
||||
|
||||
#include <config.hpp>
|
||||
|
||||
// Classes
|
||||
struct RawGyroDataL { // Used for average multiple readings
|
||||
int32_t ax; // Raw Acceleration
|
||||
int32_t ay;
|
||||
@ -50,13 +47,13 @@ struct RawGyroDataL { // Used for average multiple readings
|
||||
|
||||
class GyroSensor {
|
||||
private:
|
||||
bool sensorConnected = false;
|
||||
bool validValue = false;
|
||||
float angle = 0;
|
||||
float sensorTemp = 0;
|
||||
float initialSensorTemp = INVALID_TEMPERATURE;
|
||||
RawGyroData calibrationOffset;
|
||||
RawGyroData lastGyroData;
|
||||
bool _sensorConnected = false;
|
||||
bool _validValue = false;
|
||||
float _angle = 0;
|
||||
float _sensorTemp = 0;
|
||||
float _initialSensorTemp = INVALID_TEMPERATURE;
|
||||
RawGyroData _calibrationOffset;
|
||||
RawGyroData _lastGyroData;
|
||||
|
||||
void debug();
|
||||
void applyCalibration();
|
||||
@ -71,16 +68,15 @@ class GyroSensor {
|
||||
bool read();
|
||||
void calibrateSensor();
|
||||
|
||||
const RawGyroData &getLastGyroData() { return lastGyroData; }
|
||||
float getAngle() { return angle; }
|
||||
float getSensorTempC() { return sensorTemp; }
|
||||
float getInitialSensorTempC() { return initialSensorTemp; }
|
||||
bool isConnected() { return sensorConnected; }
|
||||
bool hasValue() { return validValue; }
|
||||
const RawGyroData &getLastGyroData() { return _lastGyroData; }
|
||||
float getAngle() { return _angle; }
|
||||
float getSensorTempC() { return _sensorTemp; }
|
||||
float getInitialSensorTempC() { return _initialSensorTemp; }
|
||||
bool isConnected() { return _sensorConnected; }
|
||||
bool hasValue() { return _validValue; }
|
||||
void enterSleep();
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern GyroSensor myGyro;
|
||||
|
||||
#endif // SRC_GYRO_HPP_
|
||||
|
317
src/helper.cpp
317
src/helper.cpp
@ -21,63 +21,164 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
SerialDebug mySerial;
|
||||
BatteryVoltage myBatteryVoltage;
|
||||
|
||||
//
|
||||
// Print the heap information.
|
||||
//
|
||||
void printHeap() {
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
||||
ESP.getFreeSketchSpace() / 1024);
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
struct tcp_pcb;
|
||||
extern struct tcp_pcb* tcp_tw_pcbs;
|
||||
extern "C" void tcp_abort(struct tcp_pcb* pcb);
|
||||
void tcp_cleanup() {
|
||||
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
double convertToSG(double plato) { return 259 / (259 - plato); }
|
||||
|
||||
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
|
||||
|
||||
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
|
||||
|
||||
FloatHistoryLog::FloatHistoryLog(String fName) {
|
||||
_fName = fName;
|
||||
|
||||
File runFile = LittleFS.open(_fName, "r");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
_runTime[i] = runFile.readStringUntil('\n').toFloat();
|
||||
if (_runTime[i]) {
|
||||
_average += _runTime[i];
|
||||
_count++;
|
||||
}
|
||||
}
|
||||
runFile.close();
|
||||
_average = _average / _count;
|
||||
}
|
||||
}
|
||||
|
||||
void FloatHistoryLog::addEntry(float time) {
|
||||
for (int i = (10 - 1); i > 0; i--) {
|
||||
_runTime[i] = _runTime[i - 1];
|
||||
}
|
||||
_runTime[0] = time;
|
||||
save();
|
||||
}
|
||||
|
||||
void FloatHistoryLog::save() {
|
||||
File runFile = LittleFS.open(_fName, "w");
|
||||
if (runFile) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
runFile.println(_runTime[i], 2);
|
||||
}
|
||||
runFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void printHeap(String prefix) {
|
||||
#if defined(ESP8266)
|
||||
Log.notice(
|
||||
F("%s: Free-heap %d kb, Heap-rag %d %%, Max-block %d kb Stack=%d b." CR),
|
||||
prefix.c_str(), ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
||||
ESP.getMaxFreeBlockSize() / 1024, ESP.getFreeContStack());
|
||||
// Log.notice(F("%s: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
||||
// prefix.c_str(), ESP.getFreeHeap() / 1024,
|
||||
// ESP.getHeapFragmentation(), ESP.getFreeSketchSpace() / 1024);
|
||||
#else // defined (ESP32)
|
||||
Log.verbose(F("HELP: Heap %d kb, FreeSketch %d kb." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024);
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Enter deep sleep for the defined duration (Argument is seconds)
|
||||
//
|
||||
void deepSleep(int t) {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
|
||||
#endif
|
||||
uint32_t wake = t * 1000000;
|
||||
ESP.deepSleep(wake);
|
||||
}
|
||||
|
||||
//
|
||||
// Print the build options used
|
||||
//
|
||||
void printBuildOptions() {
|
||||
Log.notice(F("Build options: %s LOGLEVEL %d "
|
||||
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, LOG_LEVEL);
|
||||
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();
|
||||
@ -88,28 +189,30 @@ 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)
|
||||
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
|
||||
int v = analogRead(A0);
|
||||
batteryLevel = ((3.3 / 1023) * v) * factor;
|
||||
#if LOG_LEVEL == 6
|
||||
int v = analogRead(PIN_A0);
|
||||
|
||||
// An ESP8266 has a ADC range of 0-1023 and a maximum voltage of 3.3V
|
||||
// An ESP32 has an ADC range of 0-4095 and a maximum voltage of 3.3V
|
||||
|
||||
#if defined(ESP8266)
|
||||
_batteryLevel = ((3.3 / 1023) * v) * factor;
|
||||
#else // defined (ESP32)
|
||||
_batteryLevel = ((3.3 / 4095) * v) * factor;
|
||||
#endif
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
|
||||
factor, v, batteryLevel);
|
||||
factor, v, _batteryLevel);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -117,9 +220,6 @@ void BatteryVoltage::read() {
|
||||
|
||||
PerfLogging myPerfLogging;
|
||||
|
||||
//
|
||||
// Clear the current cache
|
||||
//
|
||||
void PerfLogging::clear() {
|
||||
// Clear the measurements
|
||||
if (first == 0) return;
|
||||
@ -136,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);
|
||||
|
||||
@ -159,9 +253,6 @@ void PerfLogging::stop(const char* key) {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Print the collected performance data
|
||||
//
|
||||
void PerfLogging::print() {
|
||||
PerfEntry* pe = first;
|
||||
|
||||
@ -171,29 +262,34 @@ void PerfLogging::print() {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Push collected performance data to influx (use influx configuration)
|
||||
//
|
||||
void PerfLogging::pushInflux() {
|
||||
if (!myConfig.isInfluxDb2Active()) return;
|
||||
|
||||
WiFiClient client;
|
||||
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 =
|
||||
String(myConfig.getInfluxDb2PushUrl()) +
|
||||
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin(client, serverPath);
|
||||
http.begin(wifi, serverPath);
|
||||
|
||||
// Create body for influxdb2, format used
|
||||
// key,host=mdns value=0.0
|
||||
String body;
|
||||
body.reserve(500);
|
||||
|
||||
// Create the payload with performance data.
|
||||
// ------------------------------------------------------------------------------------------
|
||||
PerfEntry* pe = first;
|
||||
char buf[100];
|
||||
char buf[150];
|
||||
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
|
||||
myConfig.getID());
|
||||
body += &buf[0];
|
||||
@ -215,17 +311,30 @@ void PerfLogging::pushInflux() {
|
||||
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
|
||||
myConfig.getMDNS(), myConfig.getID());
|
||||
body += &buf[0];
|
||||
snprintf(
|
||||
&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(), myTempSensor.getTempC(myConfig.isGyroTemp()));
|
||||
#if defined(ESP8266)
|
||||
snprintf(&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
|
||||
"%.2f,heap=%d,heap-frag=%d,heap-max=%d,stack=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(),
|
||||
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(),
|
||||
ESP.getHeapFragmentation(), ESP.getMaxFreeBlockSize(),
|
||||
ESP.getFreeContStack());
|
||||
#else // defined (ESP32)
|
||||
snprintf(&buf[0], sizeof(buf),
|
||||
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
|
||||
"%.2f,heap=%d,heap-frag=%d,heap-max=%d",
|
||||
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
||||
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
||||
myGyro.getSensorTempC(),
|
||||
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), 0,
|
||||
ESP.getMaxAllocHeap());
|
||||
#endif
|
||||
|
||||
body += &buf[0];
|
||||
|
||||
// Log.notice(F("PERF: data %s." CR), body.c_str() );
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PERF: data %s." CR), body.c_str());
|
||||
#endif
|
||||
@ -233,38 +342,106 @@ void PerfLogging::pushInflux() {
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
http.setTimeout(myAdvancedConfig.getPushTimeout());
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
#if !defined(HELPER_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
#endif
|
||||
} else {
|
||||
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
}
|
||||
|
||||
http.end();
|
||||
wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
#endif // COLLECT_PERFDATA
|
||||
|
||||
//
|
||||
// Convert float to formatted string with n decimals. Buffer should be at least
|
||||
// 10 chars.
|
||||
//
|
||||
char* convertFloatToString(float f, char* buffer, int dec) {
|
||||
dtostrf(f, 6, dec, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//
|
||||
// Reduce precision to n decimals
|
||||
//
|
||||
float reduceFloatPrecision(float f, int dec) {
|
||||
char buffer[5];
|
||||
dtostrf(f, 6, dec, &buffer[0]);
|
||||
return atof(&buffer[0]);
|
||||
}
|
||||
|
||||
// urlencode
|
||||
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
|
||||
String urlencode(String str) {
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length() * 2);
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (isalnum(c)) {
|
||||
encodedString += c;
|
||||
} else {
|
||||
code1 = (c & 0xf) + '0';
|
||||
if ((c & 0xf) > 9) {
|
||||
code1 = (c & 0xf) - 10 + 'A';
|
||||
}
|
||||
c = (c >> 4) & 0xf;
|
||||
code0 = c + '0';
|
||||
if (c > 9) {
|
||||
code0 = c - 10 + 'A';
|
||||
}
|
||||
encodedString += '%';
|
||||
encodedString += code0;
|
||||
encodedString += code1;
|
||||
}
|
||||
}
|
||||
// Log.verbose(F("HELP: encode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
unsigned char h2int(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return ((unsigned char)c - '0');
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return ((unsigned char)c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return ((unsigned char)c - 'A' + 10);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
String urldecode(String str) {
|
||||
String encodedString;
|
||||
encodedString.reserve(str.length());
|
||||
encodedString = "";
|
||||
char c;
|
||||
char code0;
|
||||
char code1;
|
||||
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
||||
c = str.charAt(i);
|
||||
if (c == '%') {
|
||||
i++;
|
||||
code0 = str.charAt(i);
|
||||
i++;
|
||||
code1 = str.charAt(i);
|
||||
c = (h2int(code0) << 4) | h2int(code1);
|
||||
encodedString += c;
|
||||
} else {
|
||||
encodedString += c;
|
||||
}
|
||||
}
|
||||
|
||||
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
|
||||
return encodedString;
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -25,7 +25,20 @@ SOFTWARE.
|
||||
#define SRC_HELPER_HPP_
|
||||
|
||||
// Includes
|
||||
#include <ArduinoLog.h>
|
||||
#include <main.hpp>
|
||||
|
||||
#define ERR_FILENAME "/error.log"
|
||||
#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);
|
||||
@ -33,6 +46,16 @@ void deepSleep(int t);
|
||||
// Show build options
|
||||
void printBuildOptions();
|
||||
|
||||
// Data conversion
|
||||
double convertToPlato(double sg);
|
||||
double convertToSG(double plato);
|
||||
float convertCtoF(float c);
|
||||
float convertFtoC(float f);
|
||||
|
||||
// url encode/decode
|
||||
String urldecode(String str);
|
||||
String urlencode(String str);
|
||||
|
||||
// Float to String
|
||||
char* convertFloatToString(float f, char* buf, int dec = 2);
|
||||
float reduceFloatPrecision(float f, int dec = 2);
|
||||
@ -40,7 +63,7 @@ float reduceFloatPrecision(float f, int dec = 2);
|
||||
// Logging via serial
|
||||
void printTimestamp(Print* _logOutput, int _logLevel);
|
||||
void printNewline(Print* _logOutput);
|
||||
void printHeap();
|
||||
void printHeap(String prefix = "HELP");
|
||||
|
||||
// Classes
|
||||
class SerialDebug {
|
||||
@ -49,13 +72,27 @@ class SerialDebug {
|
||||
static Logging* getLog() { return &Log; }
|
||||
};
|
||||
|
||||
class FloatHistoryLog {
|
||||
private:
|
||||
String _fName;
|
||||
float _average = 0;
|
||||
float _runTime[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
int _count = 0;
|
||||
void save();
|
||||
|
||||
public:
|
||||
explicit FloatHistoryLog(String fName);
|
||||
void addEntry(float time);
|
||||
float getAverage() { return _average; }
|
||||
};
|
||||
|
||||
class BatteryVoltage {
|
||||
private:
|
||||
float batteryLevel;
|
||||
float _batteryLevel;
|
||||
|
||||
public:
|
||||
void read();
|
||||
float getVoltage() { return batteryLevel; }
|
||||
float getVoltage() { return _batteryLevel; }
|
||||
};
|
||||
|
||||
#if defined(COLLECT_PERFDATA)
|
||||
@ -128,7 +165,7 @@ extern PerfLogging myPerfLogging;
|
||||
// Use these to collect performance data from various parts of the code
|
||||
#define LOG_PERF_START(s) myPerfLogging.start(s)
|
||||
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
|
||||
// #define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
// #define LOG_PERF_PRINT() myPerfLogging.print()
|
||||
#define LOG_PERF_PRINT()
|
||||
#define LOG_PERF_CLEAR() myPerfLogging.clear()
|
||||
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
|
||||
|
213
src/main.cpp
213
src/main.cpp
@ -21,17 +21,19 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include <ble.hpp>
|
||||
#include <calc.hpp>
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <pushtarget.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <webserver.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// #define FORCE_GRAVITY_MODE
|
||||
|
||||
// Define constats for this program
|
||||
#ifdef DEACTIVATE_SLEEPMODE
|
||||
const int interval = 1000; // ms, time to wait between changes to output
|
||||
@ -41,17 +43,13 @@ int interval = 200; // ms, time to wait between changes to output
|
||||
bool sleepModeAlwaysSkip =
|
||||
false; // Flag set in web interface to override normal behaviour
|
||||
uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
|
||||
uint32_t pushMillis = 0; // Used to control how often we will send push data
|
||||
uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
|
||||
uint32_t stableGyroMillis; // Used to calculate the total time since last
|
||||
// stable gyro reading
|
||||
|
||||
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;
|
||||
@ -59,42 +57,77 @@ 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) {
|
||||
// Will not enter sleep mode if: no calibration data
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("MAIN: Missing calibration data, so forcing webserver to be "
|
||||
"active." CR));
|
||||
#endif
|
||||
runMode = RunMode::configurationMode;
|
||||
} else if (sleepModeAlwaysSkip) {
|
||||
// Check if the flag from the UI has been set, the we force configuration
|
||||
// mode.
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR));
|
||||
#endif
|
||||
runMode = RunMode::configurationMode;
|
||||
} else if ((volt < 4.15 && (angle > 85 && angle < 95)) || (volt > 4.15)) {
|
||||
} 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;
|
||||
}
|
||||
|
||||
switch (runMode) {
|
||||
case RunMode::configurationMode:
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: run mode CONFIG (angle=%F volt=%F)." CR), angle,
|
||||
volt);
|
||||
#endif
|
||||
break;
|
||||
case RunMode::wifiSetupMode:
|
||||
break;
|
||||
case RunMode::gravityMode:
|
||||
#if !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.notice(F("MAIN: run mode GRAVITY (angle=%F volt=%F)." CR), angle,
|
||||
volt);
|
||||
#endif
|
||||
break;
|
||||
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");
|
||||
@ -103,21 +136,39 @@ void setup() {
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
// Add a delay so that serial is started.
|
||||
// delay(3000);
|
||||
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
|
||||
#endif
|
||||
// Main startup
|
||||
|
||||
#if defined(ESP8266)
|
||||
Log.notice(F("Main: Started setup for %s." CR),
|
||||
String(ESP.getChipId(), HEX).c_str());
|
||||
#else // defined (ESP32)
|
||||
char buf[20];
|
||||
uint32_t chipId = 0;
|
||||
for (int i = 0; i < 17; i = i + 8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
|
||||
Log.notice(F("Main: Started setup for %s." CR), &buf[0]);
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
digitalWrite(PIN_LED, HIGH);
|
||||
#endif
|
||||
printBuildOptions();
|
||||
|
||||
LOG_PERF_START("main-config-load");
|
||||
myConfig.checkFileSystem();
|
||||
checkResetReason();
|
||||
myConfig.loadFile();
|
||||
myWifi.init();
|
||||
myAdvancedConfig.loadFile();
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
#if defined(ESP8266)
|
||||
ESP.wdtDisable();
|
||||
ESP.wdtEnable(5000); // 5 seconds
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
|
||||
// No stored config, move to portal
|
||||
if (!myWifi.hasConfig()) {
|
||||
@ -132,6 +183,9 @@ void setup() {
|
||||
runMode = RunMode::wifiSetupMode;
|
||||
}
|
||||
|
||||
bool needWifi = true; // Under ESP32 we dont need wifi if only BLE is active
|
||||
// in gravityMode
|
||||
|
||||
// Do this setup for all modes exect wifi setup
|
||||
switch (runMode) {
|
||||
case RunMode::wifiSetupMode:
|
||||
@ -139,24 +193,37 @@ void setup() {
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_PERF_START("main-wifi-connect");
|
||||
myWifi.connect();
|
||||
LOG_PERF_STOP("main-wifi-connect");
|
||||
|
||||
LOG_PERF_START("main-temp-setup");
|
||||
myTempSensor.setup();
|
||||
LOG_PERF_STOP("main-temp-setup");
|
||||
|
||||
if (!myGyro.setup()) {
|
||||
Log.error(F("Main: Failed to initialize the gyro." CR));
|
||||
} 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();
|
||||
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
|
||||
|
||||
#if defined(ESP32)
|
||||
if (!myConfig.isWifiPushActive() && runMode == RunMode::gravityMode) {
|
||||
Log.notice(
|
||||
F("Main: Wifi is not needed in gravity mode, skipping "
|
||||
"connection." CR));
|
||||
needWifi = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (needWifi) {
|
||||
LOG_PERF_START("main-wifi-connect");
|
||||
myWifi.connect();
|
||||
LOG_PERF_STOP("main-wifi-connect");
|
||||
}
|
||||
|
||||
LOG_PERF_START("main-temp-setup");
|
||||
myTempSensor.setup();
|
||||
LOG_PERF_STOP("main-temp-setup");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -164,12 +231,13 @@ 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();
|
||||
LOG_PERF_STOP("main-wifi-ota");
|
||||
#endif
|
||||
myWebServer
|
||||
myWebServerHandler
|
||||
.setupWebServer(); // Takes less than 4ms, so skip this measurement
|
||||
}
|
||||
|
||||
@ -182,7 +250,8 @@ void setup() {
|
||||
|
||||
LOG_PERF_STOP("main-setup");
|
||||
Log.notice(F("Main: Setup completed." CR));
|
||||
stableGyroMillis = millis(); // Dont include time for wifi connection
|
||||
pushMillis = stableGyroMillis =
|
||||
millis(); // Dont include time for wifi connection
|
||||
}
|
||||
|
||||
//
|
||||
@ -207,28 +276,63 @@ bool loopReadGravity() {
|
||||
stableGyroMillis = millis(); // Reset timer
|
||||
|
||||
LOG_PERF_START("loop-temp-read");
|
||||
float temp = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
float tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
|
||||
LOG_PERF_STOP("loop-temp-read");
|
||||
|
||||
float gravity = calculateGravity(angle, temp);
|
||||
float corrGravity =
|
||||
gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat());
|
||||
float gravitySG = calculateGravity(angle, 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=%F, gravity=%F, "
|
||||
"corr=%F." CR),
|
||||
angle, temp, gravity, corrGravity);
|
||||
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%FC, gravity=%F, "
|
||||
"corr_gravity=%F." CR),
|
||||
angle, tempC, gravitySG, corrGravitySG);
|
||||
#endif
|
||||
|
||||
LOG_PERF_START("loop-push");
|
||||
// Force the transmission if we are going to sleep
|
||||
myPushTarget.send(angle, gravity, corrGravity, temp,
|
||||
(millis() - runtimeMillis) / 1000,
|
||||
runMode == RunMode::gravityMode ? true : false);
|
||||
LOG_PERF_STOP("loop-push");
|
||||
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");
|
||||
|
||||
#if defined(ESP32)
|
||||
if (myConfig.isBLEActive()) {
|
||||
BleSender ble(myConfig.getColorBLE());
|
||||
ble.sendData(convertCtoF(tempC), gravitySG);
|
||||
Log.notice(F("MAIN: Broadcast data over bluetooth." CR));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (myWifi.isConnected()) { // no need to try if there is no wifi
|
||||
// connection.
|
||||
PushTarget push;
|
||||
push.sendAll(angle, gravitySG, corrGravitySG, tempC,
|
||||
(millis() - runtimeMillis) / 1000);
|
||||
}
|
||||
|
||||
LOG_PERF_STOP("loop-push");
|
||||
|
||||
// Send stats to influx after each push run.
|
||||
if (runMode == RunMode::configurationMode) {
|
||||
LOG_PERF_PUSH();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Log.error(F("Main: No gyro value." CR));
|
||||
Log.error(F("MAIN: No gyro value found, the device might be moving." CR));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -241,20 +345,17 @@ void loopGravityOnInterval() {
|
||||
if (abs((int32_t)(millis() - loopMillis)) > interval) {
|
||||
loopReadGravity();
|
||||
loopMillis = millis();
|
||||
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
|
||||
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb HeapFrag %d %%." CR),
|
||||
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024,
|
||||
ESP.getHeapFragmentation());
|
||||
#endif
|
||||
// printHeap("MAIN");
|
||||
LOG_PERF_START("loop-gyro-read");
|
||||
myGyro.read();
|
||||
LOG_PERF_STOP("loop-gyro-read");
|
||||
myBatteryVoltage.read();
|
||||
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
|
||||
LOG_PERF_PUSH();
|
||||
}
|
||||
}
|
||||
|
||||
bool skipRunTimeLog = false;
|
||||
|
||||
//
|
||||
// Main loop that determines if device should go to sleep
|
||||
//
|
||||
@ -262,6 +363,11 @@ void goToSleep(int sleepInterval) {
|
||||
float volt = myBatteryVoltage.getVoltage();
|
||||
float runtime = (millis() - runtimeMillis);
|
||||
|
||||
if (!skipRunTimeLog) {
|
||||
FloatHistoryLog runLog(RUNTIME_FILENAME);
|
||||
runLog.addEntry(runtime);
|
||||
}
|
||||
|
||||
Log.notice(F("MAIN: Entering deep sleep for %ds, run time %Fs, "
|
||||
"battery=%FV." CR),
|
||||
sleepInterval, reduceFloatPrecision(runtime / 1000, 2), volt);
|
||||
@ -273,21 +379,28 @@ 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:
|
||||
myWebServer.loop();
|
||||
if (myWifi.isConnected()) myWebServerHandler.loop();
|
||||
|
||||
myWifi.loop();
|
||||
loopGravityOnInterval();
|
||||
|
||||
// If we switched mode, dont include this in the log.
|
||||
if (runMode != RunMode::configurationMode) skipRunTimeLog = true;
|
||||
break;
|
||||
|
||||
case RunMode::gravityMode:
|
||||
// If we didnt get a wifi connection, we enter sleep for a short time to
|
||||
// conserve battery.
|
||||
if (!myWifi.isConnected()) { // no connection to wifi
|
||||
if (!myWifi.isConnected() &&
|
||||
myConfig.isWifiPushActive()) { // no connection to wifi and we have
|
||||
// defined push targets.
|
||||
Log.notice(
|
||||
F("MAIN: No connection to wifi established, sleeping for 60s." CR));
|
||||
myWifi.stopDoubleReset();
|
||||
|
63
src/main.hpp
Normal file
63
src/main.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_MAIN_HPP_
|
||||
#define SRC_MAIN_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum RunMode {
|
||||
gravityMode = 0,
|
||||
configurationMode = 1,
|
||||
wifiSetupMode = 2,
|
||||
storageMode = 3
|
||||
};
|
||||
extern RunMode runMode;
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <LittleFS.h>
|
||||
#define ESP_RESET ESP.reset
|
||||
#define PIN_SDA D3
|
||||
#define PIN_SCL D4
|
||||
#define PIN_DS D6
|
||||
#define PIN_LED 2
|
||||
// #define PIN_A0 A0
|
||||
#else // defined (ESP32)
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
#define ESPhttpUpdate httpUpdate
|
||||
#define ESP_RESET ESP.restart
|
||||
#define ESP8266WebServer WebServer
|
||||
#define PIN_SDA 17
|
||||
#define PIN_SCL 16
|
||||
#define PIN_DS 19
|
||||
#define PIN_A0 36
|
||||
#define PIN_LED 2
|
||||
#endif
|
||||
|
||||
#define PIN_LED 2
|
||||
|
||||
#endif // SRC_MAIN_HPP_
|
@ -21,291 +21,491 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266mDNS.h>
|
||||
#else // defined (ESP32)
|
||||
#endif
|
||||
#include <MQTT.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
PushTarget myPushTarget;
|
||||
#define PUSHINT_FILENAME "/push.dat"
|
||||
|
||||
//
|
||||
// Send the pressure value
|
||||
//
|
||||
void PushTarget::send(float angle, float gravity, float corrGravity, float temp,
|
||||
float runTime, bool force) {
|
||||
uint32_t timePassed = abs((int32_t)(millis() - ms));
|
||||
uint32_t interval = myConfig.getSleepInterval() * 1000;
|
||||
void PushIntervalTracker::update(const int index, const int defaultValue) {
|
||||
if (_counters[index] <= 0)
|
||||
_counters[index] = defaultValue;
|
||||
else
|
||||
_counters[index]--;
|
||||
}
|
||||
|
||||
if ((timePassed < interval) && !force) {
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed,
|
||||
interval);
|
||||
#endif
|
||||
return;
|
||||
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 LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: Sending data." CR));
|
||||
#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
|
||||
}
|
||||
|
||||
ms = millis();
|
||||
//
|
||||
// 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 (myConfig.isBrewfatherActive()) {
|
||||
LOG_PERF_START("push-brewfather");
|
||||
sendBrewfather(angle, gravity, corrGravity, temp);
|
||||
LOG_PERF_STOP("push-brewfather");
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myConfig.isHttpActive()) {
|
||||
//
|
||||
// Send the data to targets
|
||||
//
|
||||
void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime) {
|
||||
printHeap("PUSH");
|
||||
_http.setReuse(true);
|
||||
_httpSecure.setReuse(true);
|
||||
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
|
||||
|
||||
PushIntervalTracker intDelay;
|
||||
intDelay.load();
|
||||
|
||||
if (myConfig.isHttpActive() && intDelay.useHttp1()) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttp(myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp,
|
||||
runTime);
|
||||
sendHttpPost(engine, myConfig.isHttpSSL(), 0);
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if (myConfig.isHttpActive2()) {
|
||||
if (myConfig.isHttp2Active() && intDelay.useHttp2()) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttp(myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp,
|
||||
runTime);
|
||||
sendHttpPost(engine, myConfig.isHttp2SSL(), 1);
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
if (myConfig.isInfluxDb2Active()) {
|
||||
if (myConfig.isHttp3Active() && intDelay.useHttp3()) {
|
||||
LOG_PERF_START("push-http3");
|
||||
sendHttpGet(engine, myConfig.isHttp3SSL());
|
||||
LOG_PERF_STOP("push-http3");
|
||||
}
|
||||
|
||||
if (myConfig.isInfluxDb2Active() && intDelay.useInflux()) {
|
||||
LOG_PERF_START("push-influxdb2");
|
||||
sendInfluxDb2(angle, gravity, corrGravity, temp, runTime);
|
||||
sendInfluxDb2(engine, myConfig.isInfluxSSL());
|
||||
LOG_PERF_STOP("push-influxdb2");
|
||||
}
|
||||
|
||||
if (myConfig.isMqttActive()) {
|
||||
if (myConfig.isMqttActive() && intDelay.useMqtt()) {
|
||||
LOG_PERF_START("push-mqtt");
|
||||
sendMqtt(angle, gravity, corrGravity, temp, runTime);
|
||||
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(float angle, float gravity, float corrGravity,
|
||||
float temp, float runTime) {
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PUSH: Sending values to influxdb2 angle=%F, gravity=%F, temp=%F." CR),
|
||||
angle, gravity, temp);
|
||||
Log.notice(F("PUSH: Sending values to influxdb2." CR));
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath =
|
||||
String(myConfig.getInfluxDb2PushUrl()) +
|
||||
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
|
||||
http.begin(client, serverPath);
|
||||
|
||||
// Create body for influxdb2
|
||||
char buf[1024];
|
||||
snprintf(&buf[0], sizeof(buf),
|
||||
"measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
|
||||
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,"
|
||||
"rssi=%d\n",
|
||||
// TODO: Add support for plato format
|
||||
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
|
||||
myConfig.isGravityTempAdj() ? corrGravity : gravity, corrGravity,
|
||||
angle, temp, myBatteryVoltage.getVoltage(), WiFi.RSSI());
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_INFLUX);
|
||||
|
||||
#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), &buf[0]);
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
int httpResponseCode = http.POST(&buf[0]);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
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 {
|
||||
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _http.POST(doc);
|
||||
}
|
||||
|
||||
http.end();
|
||||
if (_lastCode == 204) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
writeErrorLog("PUSH: Influxdb push failed response=%d", _lastCode);
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
_httpSecure.end();
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to brewfather
|
||||
// Add HTTP header to request
|
||||
//
|
||||
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity,
|
||||
float temp) {
|
||||
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
if (!header.length()) return;
|
||||
|
||||
int i = header.indexOf(":");
|
||||
if (i) {
|
||||
String name = header.substring(0, i);
|
||||
String value = header.substring(i + 1);
|
||||
value.trim();
|
||||
Log.notice(F("PUSH: Adding header '%s': '%s'" CR), name.c_str(),
|
||||
value.c_str());
|
||||
http.addHeader(name, value);
|
||||
} else {
|
||||
writeErrorLog("PUSH: Unable to set header, invalid value %s",
|
||||
header.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target using POST
|
||||
//
|
||||
void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
int index) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR),
|
||||
angle, gravity, temp);
|
||||
Log.notice(F("PUSH: Sending values to http (%s)" CR),
|
||||
index ? "http2" : "http");
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
DynamicJsonDocument doc(300);
|
||||
//
|
||||
// {
|
||||
// "name": "YourDeviceName", // Required field, this will be the ID in
|
||||
// Brewfather "temp": 20.32, "aux_temp": 15.61, // Fridge Temp
|
||||
// "ext_temp": 6.51, // Room Temp
|
||||
// "temp_unit": "C", // C, F, K
|
||||
// "gravity": 1.042,
|
||||
// "gravity_unit": "G", // G, P
|
||||
// "pressure": 10,
|
||||
// "pressure_unit": "PSI", // PSI, BAR, KPA
|
||||
// "ph": 4.12,
|
||||
// "bpm": 123, // Bubbles Per Minute
|
||||
// "comment": "Hello World",
|
||||
// "beer": "Pale Ale"
|
||||
// "battery": 4.98
|
||||
// }
|
||||
//
|
||||
doc["name"] = myConfig.getMDNS();
|
||||
doc["temp"] = reduceFloatPrecision(temp, 1);
|
||||
doc["temp_unit"] = String(myConfig.getTempFormat());
|
||||
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
|
||||
// TODO: Add support for plato format
|
||||
doc["gravity"] = reduceFloatPrecision(
|
||||
myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
|
||||
doc["gravity_unit"] = myConfig.isGravitySG() ? "G" : "P";
|
||||
String serverPath, doc;
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getBrewfatherPushUrl();
|
||||
if (index == 0) {
|
||||
serverPath = myConfig.getHttpUrl();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP1);
|
||||
} else {
|
||||
serverPath = myConfig.getHttp2Url();
|
||||
doc = engine.create(TemplatingEngine::TEMPLATE_HTTP2);
|
||||
}
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin(client, serverPath);
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), json.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json"));
|
||||
int httpResponseCode = http.POST(json);
|
||||
if (isSecure) {
|
||||
#if defined(ESP8266)
|
||||
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
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement(serverPath);
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_httpSecure, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(_httpSecure, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(_httpSecure, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(_httpSecure, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
_lastCode = _httpSecure.POST(doc);
|
||||
} else {
|
||||
Log.error(F("PUSH: Brewfather push failed, response=%d" CR),
|
||||
httpResponseCode);
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_http, myConfig.getHttpHeader(0));
|
||||
addHttpHeader(_http, myConfig.getHttpHeader(1));
|
||||
} else {
|
||||
addHttpHeader(_http, myConfig.getHttp2Header(0));
|
||||
addHttpHeader(_http, myConfig.getHttp2Header(1));
|
||||
}
|
||||
|
||||
_lastCode = _http.POST(doc);
|
||||
}
|
||||
|
||||
http.end();
|
||||
if (_lastCode == 200) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
writeErrorLog("PUSH: HTTP post failed response=%d http%d", _lastCode,
|
||||
index + 1);
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
_httpSecure.end();
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
// Send data to http target using GET
|
||||
//
|
||||
void PushTarget::createIspindleFormat(DynamicJsonDocument &doc, float angle,
|
||||
float gravity, float corrGravity,
|
||||
float temp, float runTime) {
|
||||
// Use iSpindle format for compatibility
|
||||
doc["name"] = myConfig.getMDNS();
|
||||
doc["ID"] = myConfig.getID();
|
||||
doc["token"] = "gravmon";
|
||||
doc["interval"] = myConfig.getSleepInterval();
|
||||
doc["temperature"] = reduceFloatPrecision(temp, 1);
|
||||
doc["temp-units"] = String(myConfig.getTempFormat());
|
||||
// TODO: Add support for plato format
|
||||
doc["gravity"] = reduceFloatPrecision(
|
||||
myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
|
||||
doc["corr-gravity"] = reduceFloatPrecision(corrGravity, 4);
|
||||
doc["angle"] = reduceFloatPrecision(angle, 2);
|
||||
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
|
||||
doc["rssi"] = WiFi.RSSI();
|
||||
|
||||
// Some additional information
|
||||
doc["gravity-units"] = "SG";
|
||||
doc["run-time"] = reduceFloatPrecision(runTime, 2);
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
//
|
||||
void PushTarget::sendHttp(String serverPath, float angle, float gravity,
|
||||
float corrGravity, float temp, float runTime) {
|
||||
void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR),
|
||||
angle, gravity, temp);
|
||||
Log.notice(F("PUSH: Sending values to http3" CR));
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
DynamicJsonDocument doc(256);
|
||||
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
|
||||
String serverPath;
|
||||
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
serverPath = myConfig.getHttp3Url();
|
||||
serverPath += engine.create(TemplatingEngine::TEMPLATE_HTTP3);
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin(client, serverPath);
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), json.c_str());
|
||||
#endif
|
||||
|
||||
// Send HTTP POST request
|
||||
http.addHeader(F("Content-Type"), F("application/json"));
|
||||
int httpResponseCode = http.POST(json);
|
||||
if (isSecure) {
|
||||
#if defined(ESP8266)
|
||||
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
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("PUSH: HTTP push successful, response=%d" CR),
|
||||
httpResponseCode);
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement(serverPath);
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_lastCode = _httpSecure.GET();
|
||||
} else {
|
||||
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_lastCode = _http.GET();
|
||||
}
|
||||
|
||||
http.end();
|
||||
if (_lastCode == 200) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: HTTP get successful, response=%d" CR), _lastCode);
|
||||
} else {
|
||||
writeErrorLog("PUSH: HTTP get failed response=%d", _lastCode);
|
||||
}
|
||||
|
||||
if (isSecure) {
|
||||
_httpSecure.end();
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to http target
|
||||
// Send data to mqtt target
|
||||
//
|
||||
void PushTarget::sendMqtt(float angle, float gravity, float corrGravity,
|
||||
float temp, float runTime) {
|
||||
void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure,
|
||||
bool skipHomeAssistantRegistration) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(
|
||||
F("PUSH: Sending values to mqtt angle=%F, gravity=%F, temp=%F." CR),
|
||||
angle, gravity, temp);
|
||||
Log.notice(F("PUSH: Sending values to mqtt. Skip HA registration=%s" CR),
|
||||
skipHomeAssistantRegistration ? "yes" : "no");
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
String host = myConfig.getMqttUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_MQTT);
|
||||
int port = myConfig.getMqttPort();
|
||||
|
||||
if (myConfig.isMqttSSL()) {
|
||||
#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
|
||||
|
||||
DynamicJsonDocument doc(256);
|
||||
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
|
||||
Log.notice(F("PUSH: MQTT, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
|
||||
WiFiClient client;
|
||||
MQTTClient mqtt(512); // Maximum message size
|
||||
#if defined(ESP8266)
|
||||
if (_wifiSecure.probeMaxFragmentLength(host, port, 512)) {
|
||||
Log.notice(F("PUSH: MQTT server supports smaller SSL buffer." CR));
|
||||
_wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifiSecure);
|
||||
} else {
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifi);
|
||||
}
|
||||
|
||||
mqtt.begin(myConfig.getMqttUrl(), client);
|
||||
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
|
||||
myConfig.getMqttPass());
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), myConfig.getMqttUrl());
|
||||
Log.verbose(F("PUSH: json %s." CR), json.c_str());
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send MQQT message
|
||||
mqtt.setTimeout(10); // 10 seconds timeout
|
||||
if (mqtt.publish(myConfig.getMqttTopic(), json)) {
|
||||
Log.notice(F("PUSH: MQTT publish successful" CR));
|
||||
} else {
|
||||
Log.error(F("PUSH: MQTT publish failed err=%d, ret=%d" CR),
|
||||
mqtt.lastError(), mqtt.returnCode());
|
||||
int lines = 1;
|
||||
// Find out how many lines are in the document. Each line is one
|
||||
// topic/message. | is used as new line.
|
||||
for (unsigned int i = 0; i < doc.length() - 1; i++) {
|
||||
if (doc.charAt(i) == '|') lines++;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while (lines) {
|
||||
int next = doc.indexOf('|', index);
|
||||
String line = doc.substring(index, next);
|
||||
|
||||
// Each line equals one topic post, format is <topic>:<value>
|
||||
String topic = line.substring(0, line.indexOf(":"));
|
||||
String value = line.substring(line.indexOf(":") + 1);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: topic '%s', value '%s'." CR), topic.c_str(),
|
||||
value.c_str());
|
||||
#endif
|
||||
|
||||
if (skipHomeAssistantRegistration &&
|
||||
topic.startsWith("homeassistant/sensor/")) {
|
||||
Log.notice(F("PUSH: Ignoring Home Assistant registration topic %s" CR),
|
||||
topic.c_str());
|
||||
} else {
|
||||
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;
|
||||
lines--;
|
||||
}
|
||||
|
||||
mqtt.disconnect();
|
||||
if (isSecure) {
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
_wifi.stop();
|
||||
}
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
@ -24,38 +24,63 @@ SOFTWARE.
|
||||
#ifndef SRC_PUSHTARGET_HPP_
|
||||
#define SRC_PUSHTARGET_HPP_
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <templating.hpp>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
#include <helper.hpp>
|
||||
|
||||
// Classes
|
||||
class PushTarget {
|
||||
private:
|
||||
uint32_t ms; // Used to check that we do not post to often
|
||||
WiFiClient _wifi;
|
||||
WiFiClientSecure _wifiSecure;
|
||||
HTTPClient _http;
|
||||
HTTPClient _httpSecure;
|
||||
int _lastCode;
|
||||
bool _lastSuccess;
|
||||
|
||||
void sendBrewfather(float angle, float gravity, float corrGravity,
|
||||
float temp);
|
||||
void sendHttp(String serverPath, float angle, float gravity,
|
||||
float corrGravity, float temp, float runTime);
|
||||
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp,
|
||||
float runTime);
|
||||
void sendMqtt(float angle, float gravity, float corrGravity, float temp,
|
||||
float runTime);
|
||||
void createIspindleFormat(DynamicJsonDocument &doc, float angle,
|
||||
float gravity, float corrGravity, float temp,
|
||||
float runTime);
|
||||
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:
|
||||
PushTarget() { ms = millis(); }
|
||||
void send(float angle, float gravity, float corrGravity, float temp,
|
||||
float runTime, bool force = false);
|
||||
void sendAll(float angle, float gravitySG, float corrGravitySG, float tempC,
|
||||
float runTime);
|
||||
|
||||
void sendHttp1(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpPost(engine, isSecure, 0);
|
||||
}
|
||||
void sendHttp2(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpPost(engine, isSecure, 1);
|
||||
}
|
||||
void sendHttp3(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpGet(engine, isSecure);
|
||||
}
|
||||
void sendInfluxDb2(TemplatingEngine& engine, bool isSecure);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure,
|
||||
bool skipHomeAssistantRegistration = true);
|
||||
int getLastCode() { return _lastCode; }
|
||||
bool getLastSuccess() { return _lastSuccess; }
|
||||
};
|
||||
|
||||
extern PushTarget myPushTarget;
|
||||
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_
|
||||
|
||||
|
@ -21,23 +21,25 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
#define INCBIN_OUTPUT_SECTION ".irom.text"
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#if defined(EMBED_HTML)
|
||||
#include <resources.hpp>
|
||||
|
||||
#if defined(EMBED_HTML)
|
||||
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
|
||||
INCBIN(IndexHtm, "data/index.min.htm");
|
||||
INCBIN(DeviceHtm, "data/device.min.htm");
|
||||
INCBIN(ConfigHtm, "data/config.min.htm");
|
||||
INCBIN(CalibrationHtm, "data/calibration.min.htm");
|
||||
INCBIN(FormatHtm, "data/format.min.htm");
|
||||
INCBIN(TestHtm, "data/test.min.htm");
|
||||
INCBIN(AboutHtm, "data/about.min.htm");
|
||||
|
||||
#else
|
||||
|
||||
// Minium web interface for uploading htm files
|
||||
INCBIN(UploadHtm, "data/upload.min.htm");
|
||||
|
||||
#endif
|
||||
INCBIN(FirmwareHtm, "data/firmware.min.htm");
|
||||
|
||||
// EOF
|
||||
|
106
src/resources.hpp
Normal file
106
src/resources.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_RESOURCES_HPP_
|
||||
#define SRC_RESOURCES_HPP_
|
||||
|
||||
// Common strings used in json formats.
|
||||
#define PARAM_ID "id"
|
||||
#define PARAM_MDNS "mdns"
|
||||
#define PARAM_CONFIG_VER "config-version"
|
||||
#define PARAM_OTA "ota-url"
|
||||
#define PARAM_SSID "wifi-ssid"
|
||||
#define PARAM_PASS "wifi-pass"
|
||||
#define PARAM_SSID2 "wifi-ssid2"
|
||||
#define PARAM_PASS2 "wifi-pass2"
|
||||
#define PARAM_RUNTIME_AVERAGE "runtime-average"
|
||||
#define PARAM_TOKEN "token"
|
||||
#define PARAM_TOKEN2 "token2"
|
||||
#define PARAM_PUSH_HTTP "http-push"
|
||||
#define PARAM_PUSH_HTTP_H1 "http-push-h1"
|
||||
#define PARAM_PUSH_HTTP_H2 "http-push-h2"
|
||||
#define PARAM_PUSH_HTTP2 "http-push2"
|
||||
#define PARAM_PUSH_HTTP2_H1 "http-push2-h1"
|
||||
#define PARAM_PUSH_HTTP2_H2 "http-push2-h2"
|
||||
#define PARAM_PUSH_HTTP3 "http-push3"
|
||||
#define PARAM_PUSH_INFLUXDB2 "influxdb2-push"
|
||||
#define PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org"
|
||||
#define PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket"
|
||||
#define PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth"
|
||||
#define PARAM_PUSH_MQTT "mqtt-push"
|
||||
#define PARAM_PUSH_MQTT_USER "mqtt-user"
|
||||
#define PARAM_PUSH_MQTT_PASS "mqtt-pass"
|
||||
#define PARAM_PUSH_MQTT_PORT "mqtt-port"
|
||||
#define PARAM_SLEEP_INTERVAL "sleep-interval"
|
||||
#define PARAM_TEMPFORMAT "temp-format"
|
||||
#define PARAM_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"
|
||||
#define PARAM_FILE_SIZE "file-size"
|
||||
#define PARAM_APP_VER "app-ver"
|
||||
#define PARAM_APP_BUILD "app-build"
|
||||
#define PARAM_ANGLE "angle"
|
||||
#define PARAM_GRAVITY "gravity"
|
||||
#define PARAM_TEMP_C "temp-c"
|
||||
#define PARAM_TEMP_F "temp-f"
|
||||
#define PARAM_BATTERY "battery"
|
||||
#define PARAM_SLEEP_MODE "sleep-mode"
|
||||
#define PARAM_RSSI "rssi"
|
||||
#define PARAM_ERROR "error"
|
||||
#define PARAM_PLATFORM "platform"
|
||||
#define PARAM_BLE "ble"
|
||||
#define PARAM_HW_GYRO_READ_COUNT "gyro-read-count"
|
||||
// #define PARAM_HW_GYRO_READ_DELAY "gyro-read-delay"
|
||||
#define PARAM_HW_GYRO_MOVING_THREASHOLD "gyro-moving-threashold"
|
||||
#define PARAM_HW_FORMULA_DEVIATION "formula-max-deviation"
|
||||
#define PARAM_HW_FORMULA_CALIBRATION_TEMP "formula-calibration-temp"
|
||||
#define PARAM_HW_WIFI_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_INFLUXDB "influxdb"
|
||||
#define PARAM_FORMAT_MQTT "mqtt"
|
||||
#define PARAM_PUSH_FORMAT "format"
|
||||
#define PARAM_PUSH_SUCCESS "success"
|
||||
#define PARAM_PUSH_CODE "code"
|
||||
#define PARAM_PUSH_ENABLED "enabled"
|
||||
|
||||
#endif // SRC_RESOURCES_HPP_
|
200
src/templating.cpp
Normal file
200
src/templating.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <config.hpp>
|
||||
#include <templating.hpp>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // defined (ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
// Use iSpindle format for compatibility, HTTP POST
|
||||
const char iSpindleFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\" : \"${mdns}\", "
|
||||
"\"ID\": \"${id}\", "
|
||||
"\"token\" : \"${token}\", "
|
||||
"\"interval\": ${sleep-interval}, "
|
||||
"\"temperature\": ${temp}, "
|
||||
"\"temp_units\": \"${temp-unit}\", "
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"angle\": ${angle}, "
|
||||
"\"battery\": ${battery}, "
|
||||
"\"RSSI\": ${rssi}, "
|
||||
"\"corr-gravity\": ${corr-gravity}, "
|
||||
"\"gravity-unit\": \"${gravity-unit}\", "
|
||||
"\"run-time\": ${run-time} "
|
||||
"}";
|
||||
|
||||
// Format for an HTTP GET
|
||||
const char iHttpGetFormat[] PROGMEM =
|
||||
"?name=${mdns}"
|
||||
"&id=${id}"
|
||||
"&token=${token2}"
|
||||
"&interval=${sleep-interval}"
|
||||
"&temperature=${temp}"
|
||||
"&temp-units=${temp-unit}"
|
||||
"&gravity=${gravity}"
|
||||
"&angle=${angle}"
|
||||
"&battery=${battery}"
|
||||
"&rssi=${rssi}"
|
||||
"&corr-gravity=${corr-gravity}"
|
||||
"&gravity-unit=${gravity-unit}"
|
||||
"&run-time=${run-time}";
|
||||
|
||||
const char influxDbFormat[] PROGMEM =
|
||||
"measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-"
|
||||
"format=${gravity-unit} "
|
||||
"gravity=${gravity},corr-gravity=${corr-gravity},angle=${angle},temp=${"
|
||||
"temp},battery=${battery},"
|
||||
"rssi=${rssi}\n";
|
||||
|
||||
const char mqttFormat[] PROGMEM =
|
||||
"ispindel/${mdns}/tilt:${angle}|"
|
||||
"ispindel/${mdns}/temperature:${temp}|"
|
||||
"ispindel/${mdns}/temp_units:${temp-unit}|"
|
||||
"ispindel/${mdns}/battery:${battery}|"
|
||||
"ispindel/${mdns}/gravity:${gravity}|"
|
||||
"ispindel/${mdns}/interval:${sleep-interval}|"
|
||||
"ispindel/${mdns}/RSSI:${rssi}|";
|
||||
|
||||
//
|
||||
// Initialize the variables
|
||||
//
|
||||
void TemplatingEngine::initialize(float angle, float gravitySG,
|
||||
float corrGravitySG, float tempC,
|
||||
float runTime) {
|
||||
// Names
|
||||
setVal(TPL_MDNS, myConfig.getMDNS());
|
||||
setVal(TPL_ID, myConfig.getID());
|
||||
setVal(TPL_TOKEN, myConfig.getToken());
|
||||
setVal(TPL_TOKEN2, myConfig.getToken2());
|
||||
|
||||
// Temperature
|
||||
if (myConfig.isTempC()) {
|
||||
setVal(TPL_TEMP, tempC, 1);
|
||||
} else {
|
||||
setVal(TPL_TEMP, convertCtoF(tempC), 1);
|
||||
}
|
||||
|
||||
setVal(TPL_TEMP_C, tempC, 1);
|
||||
setVal(TPL_TEMP_F, convertCtoF(tempC), 1);
|
||||
setVal(TPL_TEMP_UNITS, myConfig.getTempFormat());
|
||||
|
||||
// Battery & Timer
|
||||
setVal(TPL_BATTERY, myBatteryVoltage.getVoltage());
|
||||
setVal(TPL_SLEEP_INTERVAL, myConfig.getSleepInterval());
|
||||
|
||||
// Performance metrics
|
||||
setVal(TPL_RUN_TIME, runTime, 1);
|
||||
setVal(TPL_RSSI, WiFi.RSSI());
|
||||
|
||||
// Angle/Tilt
|
||||
setVal(TPL_TILT, angle);
|
||||
setVal(TPL_ANGLE, angle);
|
||||
|
||||
// Gravity options
|
||||
if (myConfig.isGravitySG()) {
|
||||
setVal(TPL_GRAVITY, gravitySG, 4);
|
||||
setVal(TPL_GRAVITY_CORR, corrGravitySG, 4);
|
||||
} else {
|
||||
setVal(TPL_GRAVITY, convertToPlato(gravitySG), 1);
|
||||
setVal(TPL_GRAVITY_CORR, convertToPlato(corrGravitySG), 1);
|
||||
}
|
||||
|
||||
setVal(TPL_GRAVITY_G, gravitySG, 4);
|
||||
setVal(TPL_GRAVITY_P, convertToPlato(gravitySG), 1);
|
||||
setVal(TPL_GRAVITY_CORR_G, corrGravitySG, 4);
|
||||
setVal(TPL_GRAVITY_CORR_P, convertToPlato(corrGravitySG), 1);
|
||||
setVal(TPL_GRAVITY_UNIT, myConfig.getGravityFormat());
|
||||
|
||||
setVal(TPL_APP_VER, CFG_APPVER);
|
||||
setVal(TPL_APP_BUILD, CFG_GITREV);
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// dumpAll();
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Create the data using defined template.
|
||||
//
|
||||
const char* TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
String fname;
|
||||
_baseTemplate.reserve(600);
|
||||
|
||||
// Load templates from memory
|
||||
switch (idx) {
|
||||
case TEMPLATE_HTTP1:
|
||||
_baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP1;
|
||||
break;
|
||||
case TEMPLATE_HTTP2:
|
||||
_baseTemplate = String(iSpindleFormat);
|
||||
fname = TPL_FNAME_HTTP2;
|
||||
break;
|
||||
case TEMPLATE_HTTP3:
|
||||
_baseTemplate = String(iHttpGetFormat);
|
||||
fname = TPL_FNAME_HTTP3;
|
||||
break;
|
||||
case TEMPLATE_INFLUX:
|
||||
_baseTemplate = String(influxDbFormat);
|
||||
fname = TPL_FNAME_INFLUXDB;
|
||||
break;
|
||||
case TEMPLATE_MQTT:
|
||||
_baseTemplate = String(mqttFormat);
|
||||
fname = TPL_FNAME_MQTT;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Add code to load templates from disk if they exist.
|
||||
File file = LittleFS.open(fname, "r");
|
||||
if (file) {
|
||||
char buf[file.size() + 1];
|
||||
memset(&buf[0], 0, file.size() + 1);
|
||||
file.readBytes(&buf[0], file.size());
|
||||
_baseTemplate = String(&buf[0]);
|
||||
file.close();
|
||||
Log.notice(F("TPL : Template loaded from disk %s." CR), fname.c_str());
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// Log.verbose(F("TPL : Base '%s'." CR), baseTemplate.c_str());
|
||||
#endif
|
||||
|
||||
// Insert data into template.
|
||||
transform();
|
||||
_baseTemplate.clear();
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
// Log.verbose(F("TPL : Transformed '%s'." CR), baseTemplate.c_str());
|
||||
#endif
|
||||
|
||||
if (_output) return _output;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// EOF
|
210
src/templating.hpp
Normal file
210
src/templating.hpp
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-22 Magnus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef SRC_TEMPLATING_HPP_
|
||||
#define SRC_TEMPLATING_HPP_
|
||||
|
||||
// Includes
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
|
||||
// Templating variables
|
||||
#define TPL_MDNS "${mdns}"
|
||||
#define TPL_ID "${id}"
|
||||
#define TPL_TOKEN "${token}"
|
||||
#define TPL_TOKEN2 "${token2}"
|
||||
#define TPL_SLEEP_INTERVAL "${sleep-interval}"
|
||||
#define TPL_TEMP "${temp}"
|
||||
#define TPL_TEMP_C "${temp-c}"
|
||||
#define TPL_TEMP_F "${temp-f}"
|
||||
#define TPL_TEMP_UNITS "${temp-unit}" // C or F
|
||||
#define TPL_BATTERY "${battery}"
|
||||
#define TPL_RSSI "${rssi}"
|
||||
#define TPL_RUN_TIME "${run-time}"
|
||||
#define TPL_ANGLE "${angle}"
|
||||
#define TPL_TILT "${tilt}" // same as angle
|
||||
#define TPL_GRAVITY "${gravity}"
|
||||
#define TPL_GRAVITY_G "${gravity-sg}"
|
||||
#define TPL_GRAVITY_P "${gravity-plato}"
|
||||
#define TPL_GRAVITY_CORR "${corr-gravity}"
|
||||
#define TPL_GRAVITY_CORR_G "${corr-gravity-sg}"
|
||||
#define TPL_GRAVITY_CORR_P "${corr-gravity-plato}"
|
||||
#define TPL_GRAVITY_UNIT "${gravity-unit}" // G or P
|
||||
#define TPL_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_INFLUXDB "/influxdb.tpl"
|
||||
#define TPL_FNAME_MQTT "/mqtt.tpl"
|
||||
|
||||
extern const char iSpindleFormat[] PROGMEM;
|
||||
extern const char iHttpGetFormat[] PROGMEM;
|
||||
extern const char influxDbFormat[] PROGMEM;
|
||||
extern const char mqttFormat[] PROGMEM;
|
||||
|
||||
// Classes
|
||||
class TemplatingEngine {
|
||||
private:
|
||||
struct KeyVal {
|
||||
String key;
|
||||
String val;
|
||||
};
|
||||
|
||||
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 *_output = 0;
|
||||
|
||||
void setVal(String key, float val, int dec = 2) {
|
||||
String s = convertFloatToString(val, &_buffer[0], dec);
|
||||
s.trim();
|
||||
setVal(key, s);
|
||||
}
|
||||
void setVal(String key, int val) { setVal(key, String(val)); }
|
||||
void setVal(String key, char val) { setVal(key, String(val)); }
|
||||
void setVal(String key, String val) {
|
||||
int max = sizeof(_items) / sizeof(KeyVal);
|
||||
for (int i = 0; i < max; i++) {
|
||||
if (_items[i].key.equals(key)) {
|
||||
_items[i].val = val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.warning(F("TPL : Key not found %s." CR), key.c_str());
|
||||
}
|
||||
|
||||
void transform() {
|
||||
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);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Serial.print("Key=\'");
|
||||
Serial.print(_items[i].key.c_str());
|
||||
Serial.print("\', Val=\'");
|
||||
Serial.print(_items[i].val.c_str());
|
||||
Serial.println("\'");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
enum Templates {
|
||||
TEMPLATE_HTTP1 = 0,
|
||||
TEMPLATE_HTTP2 = 1,
|
||||
TEMPLATE_HTTP3 = 2,
|
||||
TEMPLATE_INFLUX = 3,
|
||||
TEMPLATE_MQTT = 4
|
||||
};
|
||||
|
||||
TemplatingEngine() {}
|
||||
~TemplatingEngine() { freeMemory(); }
|
||||
|
||||
void freeMemory() {
|
||||
if (_output) free(_output);
|
||||
|
||||
_output = 0;
|
||||
_baseTemplate.clear();
|
||||
}
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG,
|
||||
float tempC, float runTime);
|
||||
const char *create(TemplatingEngine::Templates idx);
|
||||
};
|
||||
|
||||
#endif // SRC_TEMPLATING_HPP_
|
||||
|
||||
// EOF
|
@ -27,17 +27,11 @@ SOFTWARE.
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <main.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
|
||||
//
|
||||
// Conversion between C and F
|
||||
//
|
||||
float convertCtoF(float t) { return (t * 1.8) + 32.0; }
|
||||
|
||||
OneWire myOneWire(D6);
|
||||
OneWire myOneWire(PIN_DS);
|
||||
DallasTemperature mySensors(&myOneWire);
|
||||
#define TEMPERATURE_PRECISION 9
|
||||
|
||||
TempSensor myTempSensor;
|
||||
|
||||
@ -57,26 +51,18 @@ 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);
|
||||
}
|
||||
|
||||
float t = myConfig.getTempSensorAdj();
|
||||
|
||||
// Set the temp sensor adjustment values
|
||||
if (myConfig.isTempC()) {
|
||||
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
|
||||
tempSensorAdjC = t;
|
||||
} else {
|
||||
tempSensorAdjF = t;
|
||||
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
|
||||
}
|
||||
_tempSensorAdjC = myConfig.getTempSensorAdjC();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR),
|
||||
tempSensorAdjC, tempSensorAdjF);
|
||||
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C." CR),
|
||||
_tempSensorAdjC);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -95,17 +81,20 @@ float TempSensor::getValue(bool useGyro) {
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
|
||||
#endif
|
||||
hasSensor = true;
|
||||
_hasSensor = true;
|
||||
return c;
|
||||
}
|
||||
|
||||
// If we dont have sensors just return 0
|
||||
if (!mySensors.getDS18Count()) {
|
||||
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
|
||||
#if !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.notice(F("TSEN: No temperature sensors found. Skipping read." CR));
|
||||
#endif
|
||||
return -273;
|
||||
}
|
||||
|
||||
// Read the sensors
|
||||
mySensors.setResolution(myAdvancedConfig.getTempSensorResolution());
|
||||
mySensors.requestTemperatures();
|
||||
|
||||
float c = 0;
|
||||
@ -116,7 +105,7 @@ float TempSensor::getValue(bool useGyro) {
|
||||
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
|
||||
Log.verbose(F("TSEN: Reciving temp value for DS18B20 sensor %F C." CR), c);
|
||||
#endif
|
||||
hasSensor = true;
|
||||
_hasSensor = true;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
@ -24,29 +24,20 @@ SOFTWARE.
|
||||
#ifndef SRC_TEMPSENSOR_HPP_
|
||||
#define SRC_TEMPSENSOR_HPP_
|
||||
|
||||
// definitions
|
||||
float convertCtoF(float t);
|
||||
|
||||
// classes
|
||||
class TempSensor {
|
||||
private:
|
||||
bool hasSensor = false;
|
||||
float tempSensorAdjF = 0;
|
||||
float tempSensorAdjC = 0;
|
||||
bool _hasSensor = false;
|
||||
float _tempSensorAdjC = 0;
|
||||
float getValue(bool useGyro);
|
||||
|
||||
public:
|
||||
void setup();
|
||||
bool isSensorAttached() { return hasSensor; }
|
||||
bool isSensorAttached() { return _hasSensor; }
|
||||
float getTempC(bool useGyro = false) {
|
||||
return getValue(useGyro) + tempSensorAdjC;
|
||||
}
|
||||
float getTempF(bool useGyro = false) {
|
||||
return convertCtoF(getValue(useGyro)) + tempSensorAdjF;
|
||||
return getValue(useGyro) + _tempSensorAdjC;
|
||||
}
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern TempSensor myTempSensor;
|
||||
|
||||
#endif // SRC_TEMPSENSOR_HPP_
|
||||
|
1313
src/webserver.cpp
1313
src/webserver.cpp
File diff suppressed because it is too large
Load Diff
@ -24,82 +24,111 @@ SOFTWARE.
|
||||
#ifndef SRC_WEBSERVER_HPP_
|
||||
#define SRC_WEBSERVER_HPP_
|
||||
|
||||
// Include
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#define MAX_SKETCH_SPACE 1044464
|
||||
#else // defined (ESP32)
|
||||
#include <ESPmDNS.h>
|
||||
#include <Update.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#define MAX_SKETCH_SPACE 1835008
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
// Binary resouces
|
||||
#if defined(EMBED_HTML)
|
||||
INCBIN_EXTERN(IndexHtm);
|
||||
INCBIN_EXTERN(DeviceHtm);
|
||||
INCBIN_EXTERN(ConfigHtm);
|
||||
INCBIN_EXTERN(CalibrationHtm);
|
||||
INCBIN_EXTERN(FormatHtm);
|
||||
INCBIN_EXTERN(TestHtm);
|
||||
INCBIN_EXTERN(AboutHtm);
|
||||
#else
|
||||
INCBIN_EXTERN(UploadHtm);
|
||||
#endif
|
||||
INCBIN_EXTERN(FirmwareHtm);
|
||||
|
||||
// classes
|
||||
class WebServer {
|
||||
class WebServerHandler {
|
||||
private:
|
||||
ESP8266WebServer* server = 0;
|
||||
File uploadFile;
|
||||
int lastFormulaCreateError = 0;
|
||||
ESP8266WebServer* _server = 0;
|
||||
File _uploadFile;
|
||||
int _lastFormulaCreateError = 0;
|
||||
int _uploadReturn = 200;
|
||||
|
||||
void webHandleConfig();
|
||||
void webHandleFormulaWrite();
|
||||
void webHandleFormulaRead();
|
||||
void webHandleConfigAdvancedRead();
|
||||
void webHandleConfigAdvancedWrite();
|
||||
void webHandleConfigHardware();
|
||||
void webHandleConfigGravity();
|
||||
void webHandleConfigPush();
|
||||
void webHandleConfigDevice();
|
||||
void webHandleConfigFormatRead();
|
||||
void webHandleConfigFormatWrite();
|
||||
void webHandleTestPush();
|
||||
void webHandleStatusSleepmode();
|
||||
void webHandleClearWIFI();
|
||||
void webHandleStatus();
|
||||
void webHandleFactoryReset();
|
||||
void webHandleFactoryDefaults();
|
||||
void webHandleCalibrate();
|
||||
void webHandleUploadFile();
|
||||
void webHandleUpload();
|
||||
void webHandleDevice();
|
||||
void webHandleLogClear();
|
||||
void webHandlePageNotFound();
|
||||
|
||||
String readFile(String fname);
|
||||
bool writeFile(String fname, String data);
|
||||
|
||||
String getRequestArguments();
|
||||
|
||||
// Inline functions.
|
||||
void webReturnOK() { server->send(200); }
|
||||
void webReturnOK() { _server->send(_uploadReturn); }
|
||||
#if defined(EMBED_HTML)
|
||||
void webReturnIndexHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gIndexHtmData, gIndexHtmSize);
|
||||
}
|
||||
void webReturnDeviceHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gDeviceHtmData,
|
||||
gDeviceHtmSize);
|
||||
_server->send_P(200, "text/html", (const char*)gIndexHtmData,
|
||||
gIndexHtmSize);
|
||||
}
|
||||
void webReturnConfigHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gConfigHtmData,
|
||||
gConfigHtmSize);
|
||||
_server->send_P(200, "text/html", (const char*)gConfigHtmData,
|
||||
gConfigHtmSize);
|
||||
}
|
||||
void webReturnCalibrationHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
|
||||
gCalibrationHtmSize);
|
||||
_server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
|
||||
gCalibrationHtmSize);
|
||||
}
|
||||
void webReturnFormatHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gFormatHtmData,
|
||||
gFormatHtmSize);
|
||||
}
|
||||
void webReturnAboutHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gAboutHtmData, gAboutHtmSize);
|
||||
_server->send_P(200, "text/html", (const char*)gAboutHtmData,
|
||||
gAboutHtmSize);
|
||||
}
|
||||
void webReturnTestHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gTestHtmData, gTestHtmSize);
|
||||
}
|
||||
#else
|
||||
void webReturnUploadHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gUploadHtmData,
|
||||
gUploadHtmSize);
|
||||
_server->send_P(200, "text/html", (const char*)gUploadHtmData,
|
||||
gUploadHtmSize);
|
||||
}
|
||||
#endif
|
||||
void webReturnFirmwareHtm() {
|
||||
_server->send_P(200, "text/html", (const char*)gFirmwareHtmData,
|
||||
gFirmwareHtmSize);
|
||||
}
|
||||
|
||||
public:
|
||||
enum HtmlFile {
|
||||
HTML_INDEX = 0,
|
||||
HTML_DEVICE = 1,
|
||||
HTML_CONFIG = 2,
|
||||
HTML_ABOUT = 3,
|
||||
HTML_CALIBRATION = 4
|
||||
HTML_CONFIG = 1,
|
||||
HTML_ABOUT = 2,
|
||||
HTML_CALIBRATION = 3,
|
||||
HTML_FORMAT = 4,
|
||||
HTML_TEST = 5
|
||||
};
|
||||
|
||||
bool setupWebServer();
|
||||
@ -109,7 +138,7 @@ class WebServer {
|
||||
};
|
||||
|
||||
// Global instance created
|
||||
extern WebServer myWebServer;
|
||||
extern WebServerHandler myWebServerHandler;
|
||||
|
||||
#endif // SRC_WEBSERVER_HPP_
|
||||
|
||||
|
283
src/wifi.cpp
283
src/wifi.cpp
@ -21,17 +21,19 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#include <LittleFS.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPUpdate.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#include <incbin.h>
|
||||
|
||||
#include <ArduinoJson.hpp>
|
||||
#include <calc.hpp>
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <helper.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <main.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
// Settings for DRD
|
||||
@ -48,32 +50,19 @@ SOFTWARE.
|
||||
#define USING_CORS_FEATURE false
|
||||
#define NUM_WIFI_CREDENTIALS 1
|
||||
#define USE_STATIC_IP_CONFIG_IN_CP false
|
||||
#define _WIFIMGR_LOGLEVEL_ 3
|
||||
#include <ESP_WiFiManager.h>
|
||||
// Override the look and feel of the standard ui (hide secondary forms)
|
||||
const char WM_HTTP_FORM_START[] PROGMEM =
|
||||
"<form method='get' "
|
||||
"action='wifisave'><fieldset><div><label>SSID</label><input id='s' "
|
||||
"name='s' length=32 "
|
||||
"placeholder='SSID'><div></div></div><div><label>Password</label><input "
|
||||
"id='p' name='p' length=64 placeholder='password'><div></div></div><div "
|
||||
"hidden><label>SSID1</label><input id='s1' name='s1' length=32 "
|
||||
"placeholder='SSID1'><div></div></div><div "
|
||||
"hidden><label>Password</label><input id='p1' name='p1' length=64 "
|
||||
"placeholder='password1'><div></div></div></fieldset>";
|
||||
#include <ESP_WiFiManager-Impl.h>
|
||||
ESP_WiFiManager *myWifiManager;
|
||||
DoubleResetDetector *myDRD;
|
||||
|
||||
WifiConnection myWifi;
|
||||
|
||||
const char *userSSID = USER_SSID;
|
||||
const char *userPWD = USER_SSID_PWD;
|
||||
|
||||
const int PIN_LED = 2;
|
||||
|
||||
//
|
||||
// Constructor
|
||||
// Initialize
|
||||
//
|
||||
WifiConnection::WifiConnection() {
|
||||
void WifiConnection::init() {
|
||||
myDRD = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
|
||||
}
|
||||
|
||||
@ -81,18 +70,25 @@ WifiConnection::WifiConnection() {
|
||||
// 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(WiFi.SSID());
|
||||
myConfig.setWifiPass(WiFi.psk());
|
||||
myConfig.setWifiSSID(ssid, 0);
|
||||
|
||||
if (WiFi.psk().length()) myConfig.setWifiPass(WiFi.psk(), 0);
|
||||
|
||||
myConfig.saveFile();
|
||||
return true;
|
||||
}
|
||||
#else // defined( ESP32 )
|
||||
#warning "Cant read SSID property on ESP32 until a connection has been made!"
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -110,6 +106,8 @@ String WifiConnection::getIPAddress() { return WiFi.localIP().toString(); }
|
||||
// Additional method to detect double reset.
|
||||
//
|
||||
bool WifiConnection::isDoubleResetDetected() {
|
||||
if (strlen(userSSID))
|
||||
return false; // Ignore this if we have hardcoded settings.
|
||||
return myDRD->detectDoubleReset();
|
||||
}
|
||||
|
||||
@ -130,21 +128,41 @@ void WifiConnection::startPortal() {
|
||||
myWifiManager = new ESP_WiFiManager(WIFI_MDNS);
|
||||
myWifiManager->setMinimumSignalQuality(-1);
|
||||
myWifiManager->setConfigPortalChannel(0);
|
||||
myWifiManager->setConfigPortalTimeout(120);
|
||||
myWifiManager->setConfigPortalTimeout(
|
||||
myAdvancedConfig.getWifiPortalTimeout());
|
||||
|
||||
if (myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD)) {
|
||||
Log.notice(F("WIFI: Exited portal, connected to wifi. Rebooting..." CR));
|
||||
myConfig.setWifiSSID(myWifiManager->getSSID());
|
||||
myConfig.setWifiPass(myWifiManager->getPW());
|
||||
String mdns("<p>Default mDNS name is: http://");
|
||||
mdns += myConfig.getMDNS();
|
||||
mdns += ".local<p>";
|
||||
ESP_WMParameter deviceName(mdns.c_str());
|
||||
myWifiManager->addParameter(&deviceName);
|
||||
|
||||
myWifiManager->startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD);
|
||||
|
||||
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();
|
||||
ESP_RESET();
|
||||
}
|
||||
|
||||
//
|
||||
@ -155,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)) {
|
||||
@ -163,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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,26 +203,101 @@ bool WifiConnection::waitForConnection(int maxTime) {
|
||||
|
||||
if (i++ >
|
||||
(maxTime * 10)) { // Try for maxTime seconds. Since delay is 100ms.
|
||||
Log.error(F("WIFI: Failed to connect to wifi %d, aborting %s." CR),
|
||||
WiFi.status(), getIPAddress().c_str());
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
@ -222,7 +316,7 @@ bool WifiConnection::disconnect() {
|
||||
//
|
||||
//
|
||||
bool WifiConnection::updateFirmware() {
|
||||
if (!newFirmware) {
|
||||
if (!_newFirmware) {
|
||||
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
|
||||
return false;
|
||||
}
|
||||
@ -230,88 +324,98 @@ bool WifiConnection::updateFirmware() {
|
||||
Log.verbose(F("WIFI: Updating firmware." CR));
|
||||
#endif
|
||||
|
||||
WiFiClient client;
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPUpdateResult ret;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
#if defined(ESP8266)
|
||||
serverPath += "firmware.bin";
|
||||
#else // defined (ESP32)
|
||||
serverPath += "firmware32.bin";
|
||||
#endif
|
||||
|
||||
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
|
||||
if (serverPath.startsWith("https://")) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
ret = ESPhttpUpdate.update(wifiSecure, serverPath);
|
||||
} else {
|
||||
ret = ESPhttpUpdate.update(wifi, serverPath);
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
Log.error(F("WIFI: OTA update failed %d, %s." CR),
|
||||
ESPhttpUpdate.getLastError(),
|
||||
ESPhttpUpdate.getLastErrorString().c_str());
|
||||
break;
|
||||
case HTTP_UPDATE_FAILED: {
|
||||
writeErrorLog("WIFI: OTA update failed %d", ESPhttpUpdate.getLastError());
|
||||
} break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
case HTTP_UPDATE_OK: {
|
||||
Log.notice("WIFI: OTA Update sucesfull, rebooting.");
|
||||
delay(100);
|
||||
ESP.reset();
|
||||
ESP_RESET();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Download and save file
|
||||
//
|
||||
void WifiConnection::downloadFile(const char *fname) {
|
||||
#if LOG_LEVEL == 6
|
||||
void WifiConnection::downloadFile(HTTPClient &http, String &fname) {
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Download file %s." CR), fname);
|
||||
#endif
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += fname;
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin(client, serverPath);
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
File f = LittleFS.open(fname, "w");
|
||||
http.writeToStream(&f);
|
||||
f.close();
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
|
||||
Log.notice(F("WIFI: Downloaded file %s." CR), fname.c_str());
|
||||
} else {
|
||||
Log.error(F("WIFI: Failed to download file, respone=%d" CR),
|
||||
httpResponseCode);
|
||||
writeErrorLog("WIFI: Failed to download html-file %d", httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Check what firmware version is available over OTA
|
||||
//
|
||||
bool WifiConnection::checkFirmwareVersion() {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Checking if new version exist." CR));
|
||||
#endif
|
||||
WiFiClient client;
|
||||
WiFiClient wifi;
|
||||
WiFiClientSecure wifiSecure;
|
||||
HTTPClient http;
|
||||
String serverPath = myConfig.getOtaURL();
|
||||
serverPath += "version.json";
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
http.begin(client, serverPath);
|
||||
if (myConfig.isOtaSSL()) {
|
||||
wifiSecure.setInsecure();
|
||||
Log.notice(F("WIFI: OTA, SSL enabled without validation." CR));
|
||||
http.begin(wifiSecure, serverPath);
|
||||
} else {
|
||||
http.begin(wifi, serverPath);
|
||||
}
|
||||
|
||||
// Send HTTP GET request
|
||||
DynamicJsonDocument ver(300);
|
||||
int httpResponseCode = http.GET();
|
||||
|
||||
if (httpResponseCode == 200) {
|
||||
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
|
||||
|
||||
String payload = http.getString();
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
|
||||
#endif
|
||||
DynamicJsonDocument ver(300);
|
||||
DeserializationError err = deserializeJson(ver, payload);
|
||||
if (err) {
|
||||
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
|
||||
writeErrorLog("WIFI: Failed to parse version.json");
|
||||
} else {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Project %s version %s." CR),
|
||||
(const char *)ver["project"], (const char *)ver["version"]);
|
||||
#endif
|
||||
@ -320,33 +424,34 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
|
||||
if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) {
|
||||
if (parseFirmwareVersionString(curVer, CFG_APPVER)) {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR),
|
||||
newVer[0], newVer[1], newVer[2], curVer[0], curVer[1],
|
||||
curVer[2]);
|
||||
#endif
|
||||
// Compare major version
|
||||
if (newVer[0] > curVer[0]) newFirmware = true;
|
||||
if (newVer[0] > curVer[0]) _newFirmware = true;
|
||||
// Compare minor version
|
||||
if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
|
||||
newFirmware = true;
|
||||
else if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
|
||||
_newFirmware = true;
|
||||
// Compare patch version
|
||||
if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
|
||||
newVer[2] > curVer[2])
|
||||
newFirmware = true;
|
||||
else if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
|
||||
newVer[2] > curVer[2])
|
||||
_newFirmware = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Download new html files to filesystem if they are present.
|
||||
if (!ver["html"].isNull() && newFirmware) {
|
||||
Log.notice(F("WIFI: OTA downloading new html files." CR));
|
||||
if (!ver["html"].isNull() && _newFirmware) {
|
||||
Log.notice(
|
||||
F("WIFI: OTA checking if html files should be downloaded." CR));
|
||||
JsonArray htmlFiles = ver["html"].as<JsonArray>();
|
||||
for (JsonVariant v : htmlFiles) {
|
||||
String s = v;
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str());
|
||||
#endif
|
||||
downloadFile(s.c_str());
|
||||
downloadFile(http, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -355,11 +460,13 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
httpResponseCode);
|
||||
}
|
||||
http.end();
|
||||
|
||||
#if LOG_LEVEL == 6
|
||||
Log.verbose(F("WIFI: OTA found new version %s." CR),
|
||||
newFirmware ? "true" : "false");
|
||||
_newFirmware ? "true" : "false");
|
||||
#endif
|
||||
return newFirmware;
|
||||
|
||||
return _newFirmware;
|
||||
}
|
||||
|
||||
//
|
||||
@ -367,7 +474,7 @@ bool WifiConnection::checkFirmwareVersion() {
|
||||
//
|
||||
bool WifiConnection::parseFirmwareVersionString(int (&num)[3],
|
||||
const char *version) {
|
||||
#if LOG_LEVEL == 6
|
||||
#if LOG_LEVEL == 6 && !defined(WIFI_DISABLE_LOGGING)
|
||||
Log.verbose(F("WIFI: Parsing version number string %s." CR), version);
|
||||
#endif
|
||||
char temp[80];
|
||||
|
29
src/wifi.hpp
29
src/wifi.hpp
@ -24,24 +24,33 @@ SOFTWARE.
|
||||
#ifndef SRC_WIFI_HPP_
|
||||
#define SRC_WIFI_HPP_
|
||||
|
||||
// Include
|
||||
#include <Arduino.h>
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#else // defined (ESP32)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
|
||||
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
|
||||
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
|
||||
|
||||
// tcp cleanup, to avoid memory crash.
|
||||
|
||||
// classes
|
||||
class WifiConnection {
|
||||
private:
|
||||
// WIFI
|
||||
|
||||
// OTA
|
||||
bool newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char *version);
|
||||
void downloadFile(const char *fname);
|
||||
void connectAsync();
|
||||
bool _newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char* version);
|
||||
void downloadFile(HTTPClient& http, String& fname);
|
||||
|
||||
// WIFI
|
||||
void connectAsync(int wifiIndex);
|
||||
bool waitForConnection(int maxTime = 20);
|
||||
int findStrongestNetwork();
|
||||
|
||||
public:
|
||||
// WIFI
|
||||
WifiConnection();
|
||||
void init();
|
||||
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
|
108
src_docs/source/advanced.rst
Normal file
108
src_docs/source/advanced.rst
Normal file
@ -0,0 +1,108 @@
|
||||
Advanced Configuration
|
||||
######################
|
||||
|
||||
.. _format-editor:
|
||||
|
||||
Format editor
|
||||
+++++++++++++
|
||||
|
||||
To reduce the need for adding custom endpoints for various services there is an built in format editor that allows the user to customize the format being sent to the push target.
|
||||
|
||||
.. warning::
|
||||
|
||||
Since the format templates can be big this function can be quite slow on a small device such as the esp8266.
|
||||
|
||||
.. image:: images/format.png
|
||||
:width: 800
|
||||
:alt: Format editor
|
||||
|
||||
You enter the format data in the text field and the test button will show an example on what the output would look like. If the data cannot be formatted in json it will just be displayed as a long string.
|
||||
The save button will save the current formla and reload the data from the device.
|
||||
|
||||
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::
|
||||
|
||||
If you save a blank string the default template will be loaded.
|
||||
|
||||
These are the format keys available for use in the format.
|
||||
|
||||
.. list-table:: Directory structure
|
||||
:widths: 30 50 20
|
||||
:header-rows: 1
|
||||
|
||||
* - key
|
||||
- description
|
||||
- example
|
||||
* - ${mdns}
|
||||
- Name of the device
|
||||
- gravmon2
|
||||
* - ${token}
|
||||
- Token
|
||||
- any value
|
||||
* - ${token2}
|
||||
- Token 2
|
||||
- any value
|
||||
* - ${id}
|
||||
- Unique id of the device
|
||||
- e422a3
|
||||
* - ${sleep-interval}
|
||||
- Seconds between data is pushed
|
||||
- 900
|
||||
* - ${temp}
|
||||
- Temperature in format configured on device, one decimal
|
||||
- 21.2
|
||||
* - ${temp-c}
|
||||
- Temperature in C, one decimal
|
||||
- 21.2
|
||||
* - ${temp-f}
|
||||
- Temperature in F, one decimal
|
||||
- 58.0
|
||||
* - ${temp-unit}
|
||||
- Temperature format `C` or `F`
|
||||
- C
|
||||
* - ${battery}
|
||||
- Battery voltage, two decimals
|
||||
- 3.89
|
||||
* - ${rssi}
|
||||
- Wifi signal strength
|
||||
- -75
|
||||
* - ${run-time}
|
||||
- How long the last measurement took, two decimals
|
||||
- 3.87
|
||||
* - ${angle}
|
||||
- Angle of the gyro, two decimals
|
||||
- 28.67
|
||||
* - ${tilt}
|
||||
- Same as angle.
|
||||
- 28.67
|
||||
* - ${gravity}
|
||||
- Calculated gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${gravity-sg}
|
||||
- Calculated gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${gravity-plato}
|
||||
- Calculated gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${corr-gravity}
|
||||
- Temperature corrected gravity, 4 decimals for SG and 1 for Plato.
|
||||
- 1.0456
|
||||
* - ${corr-gravity-sg}
|
||||
- Temperature corrected gravity in SG, 4 decimals
|
||||
- 1.0456
|
||||
* - ${corr-gravity-plato}
|
||||
- Temperature corrected gravity in Plato, 1 decimal
|
||||
- 8.5
|
||||
* - ${gravity-unit}
|
||||
- Gravity format, `G` or `P`
|
||||
- G
|
||||
* - ${app-ver}
|
||||
- Software version
|
||||
- 1.1.0
|
||||
* - ${app-build}
|
||||
- Software revision (git hash)
|
||||
- ..e456743
|
||||
|
455
src_docs/source/api.rst
Normal file
455
src_docs/source/api.rst
Normal file
@ -0,0 +1,455 @@
|
||||
.. _rest-api:
|
||||
|
||||
REST API
|
||||
########
|
||||
|
||||
All the API's use a key called ``ID`` which is the unique device id (chip id). This is used as an API key when sending requests to the device.
|
||||
|
||||
GET: /api/config
|
||||
================
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``gravity-format`` is always ``G`` (plato is not yet supported)
|
||||
* ``ble`` is used to enable ble data transmission (only on esp32) simulating a tilt. Valid color names are; red, green, black, purple, orange, blue, yellow, pink
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"mdns": "gravmon",
|
||||
"id": "ee1bfc",
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"ble": "color",
|
||||
"token": "token",
|
||||
"token2": "token2",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
"http-push-h1": "header: value",
|
||||
"http-push-h2": "header: value",
|
||||
"http-push2": "http://192.168.1.50/ispindel",
|
||||
"http-push2-h1": "header: value",
|
||||
"http-push2-h2": "header: value",
|
||||
"http-push3": "http://192.168.1.50/ispindel",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "org",
|
||||
"influxdb2-bucket": "bucket_id",
|
||||
"influxdb2-auth": "token",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "user",
|
||||
"mqtt-pass": "pass",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "G",
|
||||
"temp-adjustment-value": 0,
|
||||
"gravity-temp-adjustment": false,
|
||||
"gyro-temp": true,
|
||||
"gyro-calibration-data": {
|
||||
"ax": -330,
|
||||
"ay": -2249,
|
||||
"az": 1170,
|
||||
"gx": 99,
|
||||
"gy": -6,
|
||||
"gz": 4
|
||||
},
|
||||
"formula-calculation-data": {
|
||||
"a1":25,
|
||||
"a10":0,
|
||||
"g1":1,
|
||||
"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
|
||||
}
|
||||
|
||||
|
||||
GET: /api/device
|
||||
================
|
||||
|
||||
This API has been removed from 0.9 and merged with /api/status
|
||||
|
||||
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``platform`` can be either ``esp8266`` or ``esp32``
|
||||
* ``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.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"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",
|
||||
"runtime-average": 3.12
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a10`` are the angles/tilt readings (up to 10 are currently supported)
|
||||
* ``g1``-``g10`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"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
|
||||
===================
|
||||
|
||||
Will reset the wifi settings both in the configuration file and eeprom, leaving the rest of the configuration.
|
||||
|
||||
For this to work you will need to supply the device id as a parameter in the request:
|
||||
|
||||
::
|
||||
|
||||
http://mygravity.local/api/clearwifi?id=<mydeviceid>
|
||||
|
||||
|
||||
GET: /api/factory
|
||||
=================
|
||||
|
||||
Will do a reset to factory defaults and delete all data except wifi settings.
|
||||
|
||||
For this to work you will need to supply the device id as a parameter in the request:
|
||||
|
||||
::
|
||||
|
||||
http://mygravity.local/api/factory?id=<mydeviceid>
|
||||
|
||||
|
||||
GET: /api/test/push
|
||||
===================
|
||||
|
||||
Trigger a push on one of the targets, used to validate the configuration from the UI.
|
||||
|
||||
Requires to parameters to function /api/test/push?id=<deviceid>&format=<format>
|
||||
|
||||
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, http-3, influxdb, mqtt
|
||||
|
||||
The response is an json message with the following values.
|
||||
|
||||
* ``code`` is the return code from the push function, typically http responsecode or error code from mqtt library.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"success": false,
|
||||
"enabled": true,
|
||||
"code": -3
|
||||
}
|
||||
|
||||
POST: /api/config/device
|
||||
========================
|
||||
|
||||
Used to update device settings via an HTTP POST command.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
* ``temp-format`` can be either ``C`` (Celcius) or ``F`` (Farenheight)
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
mdns=gravmon
|
||||
temp-format=C
|
||||
sleep-interval=30
|
||||
|
||||
|
||||
POST: /api/config/push
|
||||
======================
|
||||
|
||||
Used to update push settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
token=
|
||||
token2=
|
||||
http-push=http://192.168.1.50/ispindel
|
||||
http-push2=
|
||||
http-push3=
|
||||
http-push-h1=
|
||||
http-push-h2=
|
||||
http-push2-h1=
|
||||
http-push2-h2=
|
||||
influxdb2-push=http://192.168.1.50:8086
|
||||
influxdb2-org=
|
||||
influxdb2-bucket=
|
||||
influxdb2-auth=
|
||||
mqtt-push=192.168.1.50
|
||||
mqtt-port=1883
|
||||
mqtt-user=
|
||||
mqtt-pass=
|
||||
|
||||
|
||||
POST: /api/config/gravity
|
||||
=========================
|
||||
|
||||
Used to update gravity settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``gravity-formula`` keywords ``temp`` and ``tilt`` are supported.
|
||||
* ``gravity-format`` can be either ``G`` (SG) or ``P`` (PLATO)
|
||||
|
||||
.. note::
|
||||
``gravity-temp-adjustment`` is defined as "on" or "off" when posting since this is the output values
|
||||
from a checkbox, when reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
gravity-formula=0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436,
|
||||
gravity-format=P
|
||||
gravity-temp-adjustment=off
|
||||
|
||||
|
||||
POST: /api/config/hardware
|
||||
==========================
|
||||
|
||||
Used to update hardware settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. note::
|
||||
``gyro-temp`` is defined as "on" or "off" when posting since this is the output values from a checkbox, when
|
||||
reading data it's sent as boolean (true,false).
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
voltage-factor=1.59
|
||||
temp-adjustment=0
|
||||
ble=red
|
||||
gyro-temp=off
|
||||
ota-url=http://192.168.1.50/firmware/gravmon/
|
||||
|
||||
|
||||
POST: /api/config/formula
|
||||
=========================
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a10`` are the angles/tilt readings (up to 10 are currently supported)
|
||||
* ``g1``-``g10`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
.. code-block::
|
||||
|
||||
id=ee1bfc
|
||||
a1=22.4
|
||||
a2=54.4
|
||||
a3=58
|
||||
a4=0
|
||||
a5=0
|
||||
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. You only need to include the parameters you want to change.
|
||||
|
||||
The requests package converts the json to standard form post format.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
id = "ee1bfc" # Device ID (shown in serial console during startup or in UI)
|
||||
|
||||
def set_config( url, json ):
|
||||
headers = { "ContentType": "application/json" }
|
||||
print( url )
|
||||
resp = requests.post( url, headers=headers, data=json )
|
||||
if resp.status_code != 200 :
|
||||
print ( "Failed " )
|
||||
else :
|
||||
print ( "Success " )
|
||||
|
||||
url = "http://" + host + "/api/config/device"
|
||||
json = { "id": id,
|
||||
"mdns": "gravmon", # Name of the device
|
||||
"temp-format": "C", # Temperature format C or F
|
||||
"sleep-interval": 30 # Sleep interval in seconds
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"token": "",
|
||||
"token2": "",
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"http-push3": "",
|
||||
"http-push-h1": "",
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": ""
|
||||
"http-push2-h2": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-port": 1883,
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "",
|
||||
"gravity-format": "P",
|
||||
"gravity-temp-adjustment": "off" # Adjust gravity (on/off)
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/hardware"
|
||||
json = { "id": id,
|
||||
"voltage-factor": 1.59, # Default value for voltage calculation
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ble": "red", # Enable ble on esp32
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/formula"
|
||||
json = { "id": id,
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"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
|
||||
}
|
||||
set_config( url, json )
|
@ -1,18 +0,0 @@
|
||||
Backlog of changes
|
||||
##################
|
||||
|
||||
This is a list of potential ideas to implemnt in the software.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Write contribution instructions
|
||||
- Example project for creating integrations and instructions
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
- Support for plato
|
||||
- Use pre-commit for validating check-in
|
||||
- Show indicated battery life based on interval (check if its feasable)
|
||||
- Use brewflasher for flashing
|
@ -29,13 +29,18 @@ 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
|
||||
* 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,
|
||||
so the recommendation is to wait for support on 2.0.x branch. With the tested version an wifi connection takes 3-8s on a ESP32 compared
|
||||
to 0.5s on an ESP8266. There is also a bug in OneWire connected to ESP32 that has not been fixed in the main repository yet.
|
||||
|
||||
.. warning::
|
||||
The debug target can be unsable and crash the device under certain circumstanses.
|
||||
Excessive logging to the serial port can cause corruption and crashes. I'm still
|
||||
trying to figure out what causes these issues in the debug target. Other targets are
|
||||
stable and works fine.
|
||||
The debug target can be unstable and crash the device under certain circumstanses. Excessive logging to the serial port can cause corruption and crashes.
|
||||
So only enable enough debugging to troubleshoot your changes.
|
||||
|
||||
|
||||
Source structure
|
||||
@ -64,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
|
||||
|
||||
@ -83,19 +86,9 @@ This is a list of C++ defines that is used to enable/disable functions in the co
|
||||
* - ACTIVATE_OTA
|
||||
- Enables the OTA functionallity in the code
|
||||
* - SKIP_SLEEPMODE
|
||||
- THe device never goes into sleep mode, useful when developing.
|
||||
* - CFG_DISABLE_LOGGING
|
||||
- Done include verbose logging in Config class. Excessive logging may crash device.
|
||||
* - GYRO_DISABLE_LOGGING
|
||||
- Done include verbose logging in Gyro class. Excessive logging may crash device.
|
||||
* - PUSH_DISABLE_LOGGING
|
||||
- Done include verbose logging in PushTarget class. Excessive logging may crash device.
|
||||
* - TSEN_DISABLE_LOGGING
|
||||
- Done include verbose logging in TempSensor class. Excessive logging may crash device.
|
||||
* - WEB_DISABLE_LOGGING
|
||||
- Done include verbose logging in WebServer class. Excessive logging may crash device.
|
||||
* - MAIN_DISABLE_LOGGING
|
||||
- Done include verbose logging in Main class. Excessive logging may crash device.
|
||||
- The device never goes into sleep mode, useful when developing.
|
||||
* - xxx_DISABLE_LOGGING
|
||||
- Done include verbose logging in the corresponding class. Excessive logging may crash device.
|
||||
* - USE_LITTLEFS
|
||||
- Use the new filesystem in Ardurino
|
||||
* - EMBED_HTML
|
||||
|
@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.6.0'
|
||||
release = '1.1.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -1,7 +1,7 @@
|
||||
.. _setting-up-device:
|
||||
|
||||
Setting up device
|
||||
#################
|
||||
Configuration
|
||||
#############
|
||||
|
||||
The device can operate in two modes and must be in ``configuration mode`` in order for the web server to be active.
|
||||
|
||||
@ -12,6 +12,7 @@ One of the following conditions will place the device in ``configuration mode``:
|
||||
- Placed in horizontal mode 85-90 degrees
|
||||
- Charger connected >4.15V
|
||||
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
@ -21,37 +22,24 @@ URL: (http://gravmon.local)
|
||||
:width: 800
|
||||
:alt: Index page
|
||||
|
||||
|
||||
Configuration is accessed by entering the URL for the device, this will be the mDNS name *device.local* or the IP adress. The following chapter assumes the device name is *gravmon*.
|
||||
|
||||
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if
|
||||
you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
|
||||
|
||||
You can also view the average time a gravity measurement takes. Under optimal setting this should be around 1.5 - 2.0 seconds. If this is higher than 2 seconds this is most likley connected to slow wifi
|
||||
connection. It will show 0 if data has not been collected yet.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you are connected to the device via a serial console (speed: 115200) you can see the connection sequence and get the Unique ID and IP adress from there.
|
||||
|
||||
.. tip::
|
||||
|
||||
Device
|
||||
======
|
||||
|
||||
URL: (http://gravmon.local/device)
|
||||
|
||||
.. image:: images/device.png
|
||||
:width: 800
|
||||
:alt: Device Settings
|
||||
|
||||
|
||||
* **Version:**
|
||||
|
||||
Installed version of the code and html files.
|
||||
|
||||
* **Device name:**
|
||||
|
||||
This is unique name of the device.
|
||||
|
||||
* **Device ID:**
|
||||
|
||||
This is unique identifier for the device (ESP8266 id), this is required when using the API as an API Key to safeguard against faulty requests.
|
||||
The button `view error log` will show the last error messages on the device. This can be useful for checking errors without
|
||||
the need to connect to the serial port or to check what errors has occured while in `gravity mode`. From v1.1 it will also detect
|
||||
any abnormal restarts or crashes and record these in the logfile.
|
||||
|
||||
|
||||
Configuration
|
||||
@ -68,23 +56,29 @@ 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
|
||||
Choose between Celsius and Farenheight when displaying temperature.
|
||||
|
||||
* **Interval:**
|
||||
|
||||
This defines how long the device should be sleeping between the readings when in `gravity monitoring` mode. You will also see the values in minutes/seconds to easier set the interval. 900s is a recommended interval.
|
||||
This defines how long the device should be sleeping between the readings when in `gravity monitoring` mode. You will also see
|
||||
the values in minutes/seconds to easier set the interval. 900s is a recommended interval. The sleep interval can
|
||||
be set between 10 - 3600 seconds (60 minutes).
|
||||
|
||||
.. note::
|
||||
|
||||
The sleep interval can be set between 10 - 3600 seconds (60 minutes).
|
||||
A low value such as 30s will give a lifespan of 1-2 weeks and 300s (5 min) would last for 3+ weeks. This assumes that
|
||||
there is good wifi connection that takes less than 1s to reconnect. Poor wifi connection is the main reason for battery drain.
|
||||
The device will show the estimated lifespan based on the average connection time, if no data exist it will not be shown.
|
||||
|
||||
|
||||
* **Calibration values:**
|
||||
|
||||
These are calibration data for the gyro. Place the device flat on a table and press the button to save the default orientation values. Without this calibration we cannot calculate the correct angle/tilt.
|
||||
These are calibration data for the gyro. Place the device flat on a table and press the button to save the default orientation values. Without this calibration we cannot calculate the correct angle/tilt.
|
||||
|
||||
.. warning::
|
||||
|
||||
@ -97,50 +91,102 @@ Push Settings
|
||||
:width: 800
|
||||
:alt: Push Settings
|
||||
|
||||
* **HTTP URL 1:**
|
||||
.. note::
|
||||
|
||||
Endpoint to send data via http. Format used Format used :ref:`data-formats-ispindle`
|
||||
When enabling SSL this will not validate the root CA of the remote service, this is a design decision based on two aspects. Enabling CA validation will take 3-4s extra on each connection which means way less
|
||||
battery life, so the decision is to prioritize battery life over security. The data transmitted is not really that sensitive anyway so I belive this is a good balance.
|
||||
|
||||
* **HTTP URL 2:**
|
||||
* **HTTP 1 (POST):**
|
||||
|
||||
Endpoint to send data via http. Format used :ref:`data-formats-ispindle`
|
||||
Endpoint to send data via http. Default format used Format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
* **Brewfather URL:**
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
* **HTTP 2 (POST):**
|
||||
|
||||
Endpoint to send data via http. Default format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
* **Token:**
|
||||
|
||||
The token is included in the iSpindle JSON format and will be used for both HTTP targets. If you
|
||||
need to have 2 different tokens please use the :ref:`format-editor` to customize the data format.
|
||||
|
||||
* **HTTP 3 (GET):**
|
||||
|
||||
Endpoint to send data via http. This is using an HTTP GET request instead of a post. This means that the values are appended to the URL like; http://endpoint?param=value¶m2=value2. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
* **Token 2:**
|
||||
|
||||
The token is included in the default format for the HTTP GET url but can be used for any of the formats. For HTTP GET use can use this for an authorization token with for instance ubidots or blynk http api.
|
||||
|
||||
* **HTTP Headers**
|
||||
|
||||
.. image:: images/config-popup1.png
|
||||
:width: 300
|
||||
:alt: HTTP Headers
|
||||
|
||||
You can define 2 http headers per push target. This is available via a pop-up window but dont forget
|
||||
to press the save buttons on the post section to save the values. One common header is content type which is the
|
||||
default setting for http targets.
|
||||
|
||||
The input must have the format **'<header>: <value>'** for it to work. The UI will accept any value so errors
|
||||
will not show until the device tries to push data.
|
||||
|
||||
|
||||
::
|
||||
|
||||
Content-Type: application/json
|
||||
X-Auth-Token: <api-token>
|
||||
|
||||
|
||||
Mozilla has a good guide on what headers are valid; `HTTP Headers <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers>`_
|
||||
|
||||
|
||||
Push Settings (2)
|
||||
+++++++++++++++++
|
||||
|
||||
.. image:: images/config2b.png
|
||||
:width: 800
|
||||
:alt: Push Settings
|
||||
|
||||
* **Influx DB v2 URL:**
|
||||
|
||||
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`
|
||||
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
SSL is not supported for this target. Raise a issue on github if this is wanted.
|
||||
|
||||
* **Influx DB v2 Organisation:**
|
||||
|
||||
Name of organisation in Influx.
|
||||
Name of organisation in Influx.
|
||||
|
||||
* **Influx DB v2 Bucket:**
|
||||
|
||||
Identifier for bucket.
|
||||
Identifier for bucket.
|
||||
|
||||
* **Influx DB v2 Token:**
|
||||
|
||||
Token with write access to bucket.
|
||||
Token with write access to bucket.
|
||||
|
||||
* **MQTT server:**
|
||||
|
||||
IP or name of server to send data to. Format used :ref:`data-formats-ispindle`
|
||||
IP or name of server to send data to. Default format used :ref:`data-formats-mqtt`. You can customize the format using :ref:`format-editor`.
|
||||
|
||||
* **MQTT Topic:**
|
||||
* **MQTT Port:**
|
||||
|
||||
Name of topic to publish sensor readings to, iSpindle format is used.
|
||||
Which port should be used for communication, default is 1883 (standard port). For SSL use 8883 (any port over 8000 is treated as SSL).
|
||||
|
||||
* **MQTT user:**
|
||||
|
||||
Username or blank if anonymous is accepted
|
||||
Username or blank if anonymous is accepted
|
||||
|
||||
* **MQTT password:**
|
||||
|
||||
Password or blank if anonymous is accepted
|
||||
|
||||
Password or blank if anonymous is accepted
|
||||
|
||||
|
||||
Gravity Settings
|
||||
++++++++++++++++
|
||||
@ -149,19 +195,28 @@ Gravity Settings
|
||||
:width: 800
|
||||
:alt: Gravity Settings
|
||||
|
||||
* **Gravity format:**
|
||||
|
||||
Gravity format can be eihter `SG` or `Plato`. The device will use SG Internally and convert to Plato when displaying or sending data.
|
||||
|
||||
* **Gravity formula:**
|
||||
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. You can also use
|
||||
the feature to create the formula by supplying the raw data. See :ref:`create-formula`
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. You can also use
|
||||
the feature to create the formula by supplying the raw data. See :ref:`create-formula`
|
||||
|
||||
The gravity formula accepts to paramaters, **tilt** for the angle or **temp** for temperature (temperature inserted into the formula
|
||||
will be in celsius). I would recommend to use the formula calculation feature instead since this is much easier.
|
||||
|
||||
* **Temperature correct gravity:**
|
||||
|
||||
Will apply a temperature calibration formula to the gravity as a second step.
|
||||
Will apply a temperature calibration formula to the gravity as a second step after gravity has been calculated. It's also possible to
|
||||
build this into the gravity formula.
|
||||
|
||||
.. warning::
|
||||
This formula assumes that the calibration has been done at 20C.
|
||||
|
||||
Formula used in temperature correction:
|
||||
This formula assumes that the calibration has been done at 20°C / 68°F.
|
||||
|
||||
Formula used in temperature correction. The calibration temperature can be changed under advanced settings.
|
||||
|
||||
::
|
||||
|
||||
@ -178,438 +233,113 @@ Hardware Settings
|
||||
|
||||
* **Voltage factor:**
|
||||
|
||||
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
|
||||
Factor used to calcualate the battery voltage. If you get a too low/high voltage you can adjust this value.
|
||||
|
||||
* **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 dont what the device to go into configuration mode when charging, set this to 6V. This was added since differnt batteries have different
|
||||
voltages when fully charged.
|
||||
|
||||
* **Temperature correction:**
|
||||
|
||||
This value will be added to the temperature reading (negative value will reduce temperature reading). This is applied
|
||||
when the device starts. So changing this will not take affect until the device is restarted.
|
||||
This value will be added to the temperature reading (negative value will reduce temperature reading). This is applied
|
||||
when the device starts. So changing this will not take affect until the device is restarted.
|
||||
|
||||
* **Gyro Temperature:**
|
||||
|
||||
Enable this feature will use the temp sensor i the gyro instead of the DS18B20, the benefit is shorter run time and
|
||||
longer battery life (this is an experimental feature). The value used is the first temperature reading from when the
|
||||
device is activated, since the gyro should be cool this is reflecting the surronding temperature. After it has
|
||||
been running the value would be totally off.
|
||||
Enable this feature will use the temp sensor i the gyro instead of the DS18B20, the benefit is shorter run time and
|
||||
longer battery life (this is an experimental feature). The value used is the first temperature reading from when the
|
||||
device is activated, since the gyro should be cool this is reflecting the surronding temperature. After it has
|
||||
been running the value would be totally off.
|
||||
|
||||
* **Enable storage mode when placed on cap**
|
||||
|
||||
When place on the cap (<5 degres 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 incapsulated 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.
|
||||
|
||||
* **OTA URL:**
|
||||
|
||||
Should point to a URL where the .bin file + version.json file is located.
|
||||
Should point to a URL where the firmware.bin file + version.json file are located. For an ESP32 target the firmware should be named firmware32.bin.
|
||||
|
||||
For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the
|
||||
code the update will be done during startup.
|
||||
For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the
|
||||
code the update will be done during startup.
|
||||
|
||||
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
|
||||
If you have the previx `https://` then the device will use secure transfer without CA validation.
|
||||
|
||||
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://192.168.1.1/firmware/gravmon/
|
||||
|
||||
https://192.168.1.1/firmware/gravmon/
|
||||
|
||||
.. _create-formula:
|
||||
|
||||
Create formula
|
||||
##############
|
||||
* **Upload Firmware**
|
||||
|
||||
.. image:: images/formula1.png
|
||||
This option gives you the possibility to install an new version of the firmware (or any firmware that uses the standard flash layout).
|
||||
|
||||
.. image:: images/firmware.png
|
||||
:width: 600
|
||||
:alt: Update firmware
|
||||
|
||||
|
||||
Advanded Settings
|
||||
+++++++++++++++++
|
||||
|
||||
.. image:: images/config5.png
|
||||
:width: 800
|
||||
:alt: Formula data
|
||||
:alt: Advanced Settings
|
||||
|
||||
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.
|
||||
.. warning::
|
||||
|
||||
.. image:: images/formula2.png
|
||||
:width: 800
|
||||
:alt: Formula graph
|
||||
Changeing these parameters with caution. The wrong values might cause the device to become unresponsive.
|
||||
|
||||
Once the formula is created a graph over the entered values and a simulation of the formula will give you a nice overview on how the formula will work.
|
||||
|
||||
.. _rest-api:
|
||||
* **Gyro reads:**
|
||||
|
||||
REST API
|
||||
########
|
||||
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 affecte batterylife. 50 takes about 800 ms to execute.
|
||||
|
||||
All the API's use a key called ``ID`` which is the unique device id (chip id). This is used as an API key when sending requests to the device.
|
||||
* **Gyro moving threashold:**
|
||||
|
||||
GET: /api/config
|
||||
================
|
||||
This is the max amount of deviation allowed for a stable reading.
|
||||
|
||||
Retrive the current configuation of the device via an HTTP GET command. Payload is in JSON format.
|
||||
* **Formula deviation:**
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
* ``gravity-format`` is always ``G`` (plato is not yet supported)
|
||||
This is the maximum devation on the formlula 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.
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
* **Ignore angles below water:**
|
||||
|
||||
.. code-block:: json
|
||||
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.
|
||||
|
||||
{
|
||||
"mdns": "gravmon",
|
||||
"id": "ee1bfc",
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
"http-push2": "http://192.168.1.50/ispindel",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-format": "G",
|
||||
"temp-adjustment-value": 0,
|
||||
"gravity-temp-adjustment": false,
|
||||
"gyro-temp": true,
|
||||
"gyro-calibration-data": {
|
||||
"ax": -330,
|
||||
"ay": -2249,
|
||||
"az": 1170,
|
||||
"gx": 99,
|
||||
"gy": -6,
|
||||
"gz": 4
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
"battery": 0.04
|
||||
}
|
||||
* **Gravity calibration temp**
|
||||
|
||||
This option allows you to set the correction temperature used in the automatic temperature gravity adjustment formula. Standard is 20C.
|
||||
|
||||
GET: /api/device
|
||||
================
|
||||
* **DS18B20 Resolution:**
|
||||
|
||||
Retrive the current device settings via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"app-name": "GravityMon ",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon"
|
||||
}
|
||||
|
||||
|
||||
GET: /api/status
|
||||
================
|
||||
|
||||
Retrive the current device status via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
|
||||
Other parameters are the same as in the configuration guide.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"angle": 89.86,
|
||||
"gravity": 1.1052,
|
||||
"gravity-tempcorr": 1.1031,
|
||||
"temp-c": 0,
|
||||
"temp-f": 32,
|
||||
"battery": 0,
|
||||
"temp-format": "C",
|
||||
"sleep-mode": false,
|
||||
"rssi": -56
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/formula
|
||||
========================
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/device
|
||||
========================
|
||||
|
||||
Used to update device settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``temp-format`` can be either ``C`` or ``F``
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"mdns": "gravmon",
|
||||
"temp-format": "C",
|
||||
"sleep-interval": 30
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/push
|
||||
======================
|
||||
|
||||
Used to update push settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"http-push": "http://192.168.1.50/ispindel",
|
||||
"http-push2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "http://192.168.1.50:8086",
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty"
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/gravity
|
||||
=========================
|
||||
|
||||
Used to update gravity settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``gravity-formula`` keywords ``temp`` and ``tilt`` are supported.
|
||||
|
||||
.. note::
|
||||
``gravity-temp-adjustment`` is defined as "on" or "off" when posting since this is the output values
|
||||
from a checkbox, when reading data it's sent as boolean (true,false).
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
"gravity-temp-adjustment": "off"
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/hardware
|
||||
==========================
|
||||
|
||||
Used to update hardware settings via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
.. note::
|
||||
``gyro-temp`` is defined as "on" or "off" when posting since this is the output values from a checkbox, when
|
||||
reading data it's sent as boolean (true,false).
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"voltage-factor": 1.59,
|
||||
"temp-adjustment": 0,
|
||||
"gyro-temp": "off",
|
||||
"ota-url": "http://192.168.1.50/firmware/gravmon/"
|
||||
}
|
||||
|
||||
|
||||
POST: /api/config/formula
|
||||
=========================
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": "ee1bfc",
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
|
||||
|
||||
Calling the API's from Python
|
||||
=============================
|
||||
|
||||
Here is some example code for how to access the API's from a python script. Keys should always be
|
||||
present or the API call will fail.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
host = "192.168.1.1" # IP adress (or name) of the device to send these settings to
|
||||
id = "ee1bfc" # Device ID (shown in serial console during startup or in UI)
|
||||
|
||||
def set_config( url, json ):
|
||||
headers = { "ContentType": "application/json" }
|
||||
print( url )
|
||||
resp = requests.post( url, headers=headers, data=json )
|
||||
if resp.status_code != 200 :
|
||||
print ( "Failed " )
|
||||
else :
|
||||
print ( "Success " )
|
||||
|
||||
url = "http://" + host + "/api/config/device"
|
||||
json = { "id": id,
|
||||
"mdns": "gravmon", # Name of the device
|
||||
"temp-format": "C", # Temperature format C or F
|
||||
"sleep-interval": 30 # Sleep interval in seconds
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/push"
|
||||
json = { "id": id,
|
||||
"http-push": "http://192.168.1.1/ispindel",
|
||||
"http-push2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/gravity"
|
||||
json = { "id": id,
|
||||
"gravity-formula": "",
|
||||
"gravity-temp-adjustment": "off" # Adjust gravity (on/off)
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/config/hardware"
|
||||
json = { "id": id,
|
||||
"voltage-factor": 1.59, # Default value for voltage calculation
|
||||
"temp-adjustment": 0, # If temp sensor needs to be corrected
|
||||
"gyro-temp": "on", # Use the temp sensor in the gyro instead (on/off)
|
||||
"ota-url": "" # if the device should seach for a new update when active
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
url = "http://" + host + "/api/formula"
|
||||
json = { "id": id,
|
||||
"a1": 22.4,
|
||||
"a2": 54.4,
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
|
||||
.. _data-formats:
|
||||
|
||||
Data Formats
|
||||
############
|
||||
|
||||
.. _data-formats-ispindle:
|
||||
|
||||
iSpindle format
|
||||
===============
|
||||
|
||||
This is the format used for standard http posts.
|
||||
|
||||
* ``corr-gravity`` is an extended parameter containing a temperature corrected gravity reading.
|
||||
* ``run-time`` is an extended parameter containing the number of seconds the execution took.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"ID": "2E6753",
|
||||
"token" : "gravmon",
|
||||
"interval": 900,
|
||||
"temperature": 20.5,
|
||||
"temp-units": "C",
|
||||
"gravity": 1.0050,
|
||||
"corr-gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
"run-time": 6
|
||||
}
|
||||
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:**
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
This is the format for Brewfather
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"temp": 20.5,
|
||||
"temp-unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G",
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
Influx DB v2
|
||||
============
|
||||
|
||||
This is the format for InfluxDB v2
|
||||
|
||||
.. code-block::
|
||||
This is the amount of time allowed for a wifi connect.
|
||||
|
||||
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
|
||||
* **Wifi portal timeout:**
|
||||
|
||||
If the wifi portal is triggered (can be triggerd by reset) then this is the amount of time allowed before it exists again.
|
||||
|
||||
* **Skip Interval (...):**
|
||||
|
||||
version.json
|
||||
============
|
||||
These options allow the user to have variable push intervals for the diffrent 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.
|
||||
|
||||
Contents version.json. The version is used by the device to check if the this version is newer. The html files will also be downloaded if the are present on the server. This way it's easy to
|
||||
upgrade to a version that serve the html files from the file system. If they dont exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
they can be uploaded manually afterwards.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"project":"gravmon",
|
||||
"version":"0.4.10",
|
||||
"html": [
|
||||
"index.min.htm",
|
||||
"device.min.htm",
|
||||
"config.min.htm",
|
||||
"calibration.min.htm",
|
||||
"about.min.htm"
|
||||
]
|
||||
}
|
||||
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 Subssytem 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 inte 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
|
||||
|
146
src_docs/source/data.rst
Normal file
146
src_docs/source/data.rst
Normal file
@ -0,0 +1,146 @@
|
||||
.. _data-formats:
|
||||
|
||||
Data Formats
|
||||
############
|
||||
|
||||
.. _data-formats-ispindle:
|
||||
|
||||
HTTP Post, iSpindle format
|
||||
==========================
|
||||
|
||||
This is the format used for standard http posts.
|
||||
|
||||
* ``corr-gravity`` is an extended parameter containing a temperature corrected gravity reading.
|
||||
* ``gravity-format`` is an extended parameter containing the gravity format (G or P).
|
||||
* ``run-time`` is an extended parameter containing the number of seconds the execution took.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"ID": "2E6753",
|
||||
"token" : "gravmon",
|
||||
"interval": 900,
|
||||
"temperature": 20.5,
|
||||
"temp_units": "C",
|
||||
"gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"RSSI": -12,
|
||||
|
||||
"corr-gravity": 1.0050,
|
||||
"gravity-unit": "G",
|
||||
"run-time": 6
|
||||
}
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{
|
||||
"name" : "${mdns}",
|
||||
"ID": "${id}",
|
||||
"token" : "${token}",
|
||||
"interval": ${sleep-interval},
|
||||
"temperature": ${temp},
|
||||
"temp_units": "${temp-unit}",
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"battery": ${battery},
|
||||
"RSSI": ${rssi},
|
||||
"corr-gravity": ${corr-gravity},
|
||||
"gravity-unit": "${gravity-unit}",
|
||||
"run-time": ${run-time}
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
HTTP Get
|
||||
========
|
||||
|
||||
This is the format added to the URL when using HTTP get
|
||||
|
||||
.. code-block::
|
||||
|
||||
?name=<mdns>,id=<id>,token=<token>&interval=300&temperature=20.1&temp-units=<C|F>&
|
||||
gravity=$1.004&angle=45.5&battery=3.96&rssi=-18&corr-gravity=1.004&gravity-unit=<G|P>&run-time=2.1
|
||||
|
||||
This is the format template used to create the data above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
?name=${mdns}&id=${id}&token=${token2}&interval=${sleep-interval}&temperature=${temp}&
|
||||
temp-units=${temp-unit}&gravity=${gravity}&angle=${angle}&battery=${battery}&rssi=${rssi}&
|
||||
corr-gravity=${corr-gravity}&gravity-unit=${gravity-unit}&run-time=${run-time}
|
||||
|
||||
|
||||
Influx DB v2
|
||||
============
|
||||
|
||||
This is the format for InfluxDB v2
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,
|
||||
gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. code-block::
|
||||
|
||||
measurement,host=${mdns},device=${id},temp-format=${temp-unit},
|
||||
gravity-format=${gravity-unit} gravity=${gravity},corr-gravity=${corr-gravity},
|
||||
angle=${angle},temp=${temp},battery=${battery},rssi=${rssi}
|
||||
|
||||
|
||||
.. _data-formats-mqtt:
|
||||
|
||||
MQTT
|
||||
====
|
||||
|
||||
This is the format used to send data to MQTT. Each of the lines are specific topics
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/device_name/tilt 89.96796
|
||||
ispindel/device_name/temperature 21.375
|
||||
ispindel/device_name/temp_units C
|
||||
ispindel/device_name/battery 0.04171
|
||||
ispindel/device_name/gravity 33.54894
|
||||
ispindel/device_name/interval 1
|
||||
ispindel/device_name/RSSI -58
|
||||
|
||||
|
||||
This is the format template used to create the json above.
|
||||
|
||||
.. tip::
|
||||
|
||||
Each line in the format is treated as one topic. The `|` is used as separator between lines and the first `:` is used as separator between topic and value. Each line is formatted as `<topic>:<value>`
|
||||
|
||||
.. code-block::
|
||||
|
||||
ispindel/${mdns}/tilt:${angle}|
|
||||
ispindel/${mdns}/temperature:${temp}|
|
||||
ispindel/${mdns}/temp_units:${temp-unit}|
|
||||
ispindel/${mdns}/battery:${battery}|
|
||||
ispindel/${mdns}/gravity:${gravity}|
|
||||
ispindel/${mdns}/interval:${sleep-interval}|
|
||||
ispindel/${mdns}/RSSI:${rssi}|
|
||||
|
||||
|
||||
version.json
|
||||
============
|
||||
|
||||
Contents version.json. The version is used by the device to check if the this version is newer. The html files will also be downloaded if the are present on the server. This way it's easy to
|
||||
upgrade to a version that serve the html files from the file system. If they dont exist nothing will happen, the OTA flashing will still work. If the html files are missing from the file system
|
||||
they can be uploaded manually afterwards.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"project":"gravmon",
|
||||
"version":"1.0.0",
|
||||
"html": [ ]
|
||||
}
|
39
src_docs/source/formula.rst
Normal file
39
src_docs/source/formula.rst
Normal file
@ -0,0 +1,39 @@
|
||||
.. _create-formula:
|
||||
|
||||
Create formula
|
||||
##############
|
||||
|
||||
.. image:: images/formula1.png
|
||||
:width: 800
|
||||
:alt: Formula data
|
||||
|
||||
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 forumla 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 threashold 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 threashold 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.
|
||||
|
@ -3,58 +3,105 @@
|
||||
Functionallity
|
||||
==============
|
||||
|
||||
The main differences
|
||||
--------------------
|
||||
The main features
|
||||
-----------------
|
||||
|
||||
* **Operates in two modes gravity monitoring and configuration mode**
|
||||
|
||||
In ``gravity monitoring`` mode it behaves just like the iSpindle, it wakes up at regular intervals, measures angle/tile, temperature, calculates gravity and pushes the data to defined endpoints.
|
||||
In ``gravity monitoring`` mode it behaves just like the iSpindle, it wakes up at regular intervals, measures
|
||||
angle/tile, temperature, calculates gravity and pushes the data to defined endpoints.
|
||||
|
||||
In ``configuration mode`` the device is always active and the webserver is active. Here you can view the angle/tilt values, change configuration options and more. When in this mode you can also interact with the device
|
||||
via an REST API so data can be pushed to the device via scripts (see API section for more information).
|
||||
In ``configuration mode`` the device is always active and the webserver is active. Here you can view the
|
||||
angle/tilt values, change configuration, update the gravity formula. When in this mode you can also interact
|
||||
with the device via an REST API so data can be pushed to the device via scripts (see API section for more information).
|
||||
|
||||
You can force the device into ``configuration mode`` while measuring gravity. This is useful when calibrating the device so you don't needs to wait for the device to wake up and push the data. The entire calibration
|
||||
.. image:: images/index.png
|
||||
:width: 700
|
||||
:alt: UI example
|
||||
|
||||
You can force the device into ``configuration mode`` while measuring gravity. This is useful when calibrating
|
||||
the device so you don't needs to wait for the device to wake up and push the data. The entire calibration
|
||||
sequence can be handled via the web interface without need for additional software tools.
|
||||
|
||||
See the :ref:`setting-up-device` section for more information on how to trigger the configuration mode.
|
||||
|
||||
* **Can send data to multiple endpoints at once**
|
||||
* **Can send data to multiple endpoints**
|
||||
|
||||
The original iSpindle can only have one destination, this software will push data to all defined endpoints so in theory you can use them all. However this will consume a lot of battery power so use only as many as needed.
|
||||
The original iSpindle can only have one destination, this software will push data to all defined endpoints so
|
||||
in theory you can use them all. However this will consume more battery power so use only as many as needed. Its much
|
||||
more efficient to have the endpoints on your local network than on the internet.
|
||||
|
||||
Currently the device supports the following endpoints: http (2 different), influxdb2 and Brewfather
|
||||
Currently the device supports the following endpoints.
|
||||
|
||||
If you want additional targets please raise a feature request in the github repo.
|
||||
* http (ssl optional)
|
||||
* influxdb v2 (ssl optional)
|
||||
* MQTT (ssl optional)
|
||||
* Brewfather
|
||||
* Home Assistant
|
||||
* Brew Spy
|
||||
* Brewers Friend
|
||||
* Fermentrack
|
||||
* Ubidots
|
||||
* Thingsspeak
|
||||
|
||||
* **Build in function to create gravity formulas, so no need for using other tools for this part**
|
||||
|
||||
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.
|
||||
Under the :ref:`services` section you can find guides for how to connect GravityMon to these services. For a
|
||||
description of what data is transmitted you can see :ref:`data-formats`.
|
||||
|
||||
The software support SSL endpoints but currently without CA validation, this means that the data is encrypted
|
||||
but it does not validate if the remote endpoint is who it claims to be.
|
||||
|
||||
* **Automatic temperature adjustment of gravity reading**
|
||||
|
||||
If you want to correct gravity based on beer temperature you can do this in the formula but here is a nice feature that can correct the gravity as a second step making this independant of the formula.
|
||||
if you require CA validation please leave a comment on GitHub and I will make that a priority. Adding this function
|
||||
will dramatically reduce the battery life of the device.
|
||||
|
||||
.. note::
|
||||
|
||||
This feature needs more testing to be validated.
|
||||
Using SSL on a small device such as the esp8266 can be unstable since it requires a lot of RAM to work. And running out
|
||||
of RAM will cause the device to crash. So enable SSL with caution and only when you really need it. GravityMon will try
|
||||
to minimize the needed RAM but the remote service might not support that feature.
|
||||
|
||||
* **OTA support from local webserver**
|
||||
* **Create gravity formulas on the device**
|
||||
|
||||
When starting up in configuration mode the device will check for a software update from a local webserver.
|
||||
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 10 data points which should be enough to get a accurate formula. At least 3 data points
|
||||
is needed to get an accurate formula.
|
||||
|
||||
* **Customize the data format beeing sent to push targets**
|
||||
|
||||
In order to make it easier to support more targets there is a built in format editor that can be used to
|
||||
customize the data that is to be sent. This way you can easily adapt the software to new targets without coding.
|
||||
If you have a good template please share it on the github repository and I will add it to the documentation
|
||||
for other users to enjoy. See the :ref:`format-editor` for more information. See :ref:`services` for a list of
|
||||
services currently validated.
|
||||
|
||||
* **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.
|
||||
|
||||
* **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. OTA can also be
|
||||
done over a SSL connection.
|
||||
|
||||
* **DS18B20 temperature adjustments**
|
||||
|
||||
You can adjust the temperature reading of the temperature sensor.
|
||||
You can adjust the temperature reading of the temperature sensor. In normal cases this should not be needed since
|
||||
the sensors should be calibrated.
|
||||
|
||||
* **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.
|
||||
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.
|
||||
the device will try the seconday wifi configuration, and that also failes 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**
|
||||
|
||||
@ -72,21 +119,36 @@ The main differences
|
||||
:width: 800
|
||||
:alt: Gyro temp vs DS18B20
|
||||
|
||||
Other features
|
||||
--------------
|
||||
* **Celsius or Farenheigt**
|
||||
|
||||
* Support for Celcius and Farenheigt as temperature formats.
|
||||
You can switch between different temperature formats. GravityMon will always use C for it's internal calculations and
|
||||
convert to F when displayed.
|
||||
|
||||
* Support SG (Plato is not yet supported)
|
||||
* **SG or Plato**
|
||||
|
||||
* Gyro data is read 50 times to ensure good accuracy
|
||||
You can switch between different gravity formats. GravityMon will always use SG for it's internal calculations and
|
||||
convert to Plato when displayed.
|
||||
|
||||
Experimental 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
|
||||
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.
|
||||
I've also create a small library to measure execution code in some areas of the code that i know is time consuming. This
|
||||
way I can find a good balance between performace and quality. This is a lot of help trying to figure out where bottlenecks
|
||||
are in the code and where to put optimization efforts. Examples of real measurements:
|
||||
|
||||
* Reading the gyro: 885 ms
|
||||
* Reading DS18B20 temperature sensor: 546 ms
|
||||
* Connect to WIFI: 408 ms
|
||||
* Send data to local influxdb v2: 25 ms
|
||||
* Send data to local mqtt server: 35 ms
|
||||
* Send data to local http server: 40 ms
|
||||
* Send data to http server on internet: 0.2 - 5 seconds
|
||||
|
||||
See the :ref:`compiling-the-software` for more information.
|
||||
|
||||
@ -98,9 +160,11 @@ Experimental features
|
||||
Battery life
|
||||
------------
|
||||
|
||||
I'm currently measuring battery life of v0.5 but previous versions have been able to measure gravity for a 2-3 weeks without issues (Using 900 seconds as interval).
|
||||
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.
|
||||
|
||||
*More on this topics once my tests are done*
|
||||
In another test I had a device running with an sleep interval of only 30s with ok wifi connection. The device lasted 12 days which i think is excellent considering the short sleep interval.
|
||||
|
||||
From what I have discovered it's the WIFI connection or latency to internet hosted that has the most impact on the battery life. The typical runtime in the tests above was around 2 seconds.
|
||||
|
||||
|
||||
Performance
|
||||
|
66
src_docs/source/hardware.rst
Normal file
66
src_docs/source/hardware.rst
Normal file
@ -0,0 +1,66 @@
|
||||
.. _hardware:
|
||||
|
||||
Hardware
|
||||
########
|
||||
|
||||
There are lots of resouces out there on how to build the hardware for an iSpindle so I will not go into details on that part. Here are two of my builds using the iSpindle PCB v4.
|
||||
|
||||
.. image:: images/ispindel.jpg
|
||||
:width: 500
|
||||
:alt: Builds of iSpindel
|
||||
|
||||
It's possible to use this PCB and mount an ESP32 on top of that. It must be an pin compatible ESP32 and the one I used was called *ESP32 d1 mini*. Since this is the same width as the PCB you need to
|
||||
mount it really close to the PCB in order for it to fit in the PET tube/container. I also had to smooth the edge of the ESP32 in order for it to fit.
|
||||
|
||||
I would suggest that you try how it fits into the PET tube before soldering it to the PCB. Make sure that the battery is attached since this will be a really tight fit.
|
||||
|
||||
You also need to desolder (remove) the RED ON LED from the ESP32 or the battery power will be reduced a lot.
|
||||
|
||||
Final thing is to add a resistor between A0 (Analog PIN) and ground of 470k. The reason is that the esp8266 has a build in resistor which
|
||||
the esp32 does not have. So in order to get a valid voltage (less than 3.2V) on the A0 pin this is needed. Once the modification is done you might
|
||||
need to adjust the voltage factor so the battery reading is correct.
|
||||
|
||||
.. image:: images/esp32.jpg
|
||||
:width: 500
|
||||
:alt: Mounting esp32
|
||||
|
||||
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
|
||||
|
||||
|
BIN
src_docs/source/images/config-popup1.png
Normal file
BIN
src_docs/source/images/config-popup1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 32 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user