485 Commits

Author SHA1 Message Date
a5f7f0f8a4 Updated release notes for beta 2 2022-07-29 08:37:34 +02:00
7826e56d8a GitHub Action Build 2022-07-27 22:03:51 +00:00
353d6d77e1 Minor fixes for new features 2022-07-27 23:58:25 +02:00
437873489e Added ver and build into templating 2022-07-27 13:11:17 +02:00
e34e73fae2 GitHub Action Build 2022-07-27 06:49:27 +00:00
69a4c5607c Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-27 08:44:48 +02:00
d980fdae1a Adding sw ver to template engine 2022-07-27 08:44:32 +02:00
272e349375 GitHub Action Build 2022-07-27 06:18:09 +00:00
34d46ef768 Updated error message 2022-07-27 08:12:44 +02:00
588ff2d1e0 GitHub Action Build 2022-07-27 06:07:51 +00:00
5d1811d240 Updated config view 2022-07-27 08:02:19 +02:00
85e602d29b GitHub Action Build 2022-07-27 05:56:41 +00:00
8ee882c522 Adjusted error handling and formula deviation 2022-07-27 07:52:03 +02:00
51e7ee6867 GitHub Action Build 2022-07-27 05:48:54 +00:00
70858ef841 Updated error msg 2022-07-27 07:45:02 +02:00
83bd80ba28 GitHub Action Build 2022-07-27 05:43:50 +00:00
d33576b2df Increase size of buffer 2022-07-27 07:39:31 +02:00
5c9525c0c5 GitHub Action Build 2022-07-26 19:28:17 +00:00
ad8704fc20 Changed error message in formula validation 2022-07-26 21:23:50 +02:00
f1c1958f88 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-26 21:11:24 +02:00
e5394113e4 Updated HA integration 2022-07-26 21:11:16 +02:00
6d4e713da8 GitHub Action Build 2022-07-23 07:08:20 +00:00
36a858af2d Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-23 09:00:46 +02:00
77d2c15e39 Updated docs for v1.1 beta 2 2022-07-23 08:58:48 +02:00
9df072cc78 GitHub Action Build 2022-07-07 10:25:06 +00:00
bedbda4662 Made cal temp adjustable in UI 2022-07-07 11:11:45 +02:00
76702dfc95 Fix #90 2022-07-07 10:13:17 +02:00
789eb32aa8 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-06 09:44:11 +02:00
c0db4dd8da Precommit format 2022-07-06 09:43:48 +02:00
cb677e10ae GitHub Action Build 2022-07-06 07:14:25 +00:00
b4c7566a08 Bump docs version 2022-07-06 09:10:23 +02:00
70f391fa1a Moved code for storage mode to apply in loop 2022-07-06 09:10:10 +02:00
542beffe4c Added brewblox format template 2022-07-06 09:09:18 +02:00
1a42bd332f Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-05 18:11:22 +02:00
c16108352c GitHub Action Build 2022-07-05 16:07:32 +00:00
4b5df951de Precommit fix 2022-07-05 18:02:47 +02:00
6bbb904427 Fixed header case 2022-07-05 18:00:45 +02:00
66c6c44a38 precommit fixes 2022-07-05 17:58:14 +02:00
e2fee1fb35 Updated esp32 fwk 2022-07-05 17:55:05 +02:00
ae595ff50c Updated one wire 2022-07-05 17:50:51 +02:00
50257e2805 Updated mpu libraries 2022-07-05 17:50:33 +02:00
c503ad88a9 Removed serial debug 2022-07-05 17:43:59 +02:00
5b7290c991 Remove warning 2022-07-04 09:51:49 +02:00
f7c43dad55 Checked in wifi libs to remove unwanted deps. 2022-07-04 09:47:49 +02:00
95654bac66 Updated docs 2022-07-04 09:46:31 +02:00
ffa7ac294d Set limits on voltage config 2022-07-04 09:14:10 +02:00
79a3274286 Made config voltage configurable 2022-07-03 23:46:44 +02:00
0c936cfb88 merge master 2022-07-03 19:50:22 +02:00
f366b78cb3 merge master 2022-07-03 19:49:28 +02:00
702eba515d Updated history 2022-07-03 19:44:56 +02:00
a992e90bc3 Fix #84 2022-07-03 13:20:24 +02:00
8b4b89ba20 Fixed storage deep sleep for esp32 2022-07-02 10:45:10 +02:00
361d287a22 Updated brewblox integration 2022-07-02 09:52:14 +02:00
8e36453b13 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-07-01 21:11:42 +02:00
ded06d15a1 Added storage mode 2022-07-01 21:11:10 +02:00
67f60b817e Attempting to fix broken docs build 2022-07-01 09:42:25 +02:00
6373923506 Doc build pip upgrade 2022-07-01 09:30:16 +02:00
bf3a3de207 Added brewblox 2022-07-01 09:22:16 +02:00
e6fd027d51 GitHub Action Build 2022-06-11 14:03:00 +00:00
0cba58a0dd Bump library versions 2022-06-11 15:56:38 +02:00
fd71a2d428 Create stale.yaml 2022-06-04 14:40:20 +02:00
907e33ce2d Updated ubidots docs 2022-05-26 08:55:25 +02:00
18ba0b225f Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-05-26 08:38:37 +02:00
2a9472b453 GitHub Action Build 2022-05-25 20:42:49 +00:00
0b23a0f69e Updated rel notes 2022-05-25 22:39:46 +02:00
9cba1db4e5 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-05-25 22:38:23 +02:00
9d5d6a5a58 Added brewfather ispindle copy format 2022-05-25 22:38:11 +02:00
58fe408803 GitHub Action Build 2022-05-25 18:54:46 +00:00
c2ae70bb1a #64 fix format, bump ver 2022-05-25 20:50:22 +02:00
7a3b048d80 Fixed errors in docs 2022-05-18 07:12:37 +02:00
ad3fbb7270 Merge branch 'master' into dev 2022-05-16 19:20:14 +02:00
02df656343 Added qa on formula creation 2022-05-16 19:19:00 +02:00
f73f63fec3 GitHub Action Build 2022-05-16 06:09:36 +00:00
bb09072520 Upd pre-commit 2022-05-16 08:04:26 +02:00
5875ee83d8 GitHub Action Build 2022-05-15 08:31:09 +00:00
6185d67d12 Fix pre-commit fail 2022-05-15 10:26:21 +02:00
00f82f5c37 Added pre-commit action 2022-05-15 10:24:02 +02:00
59ad285bb8 Updated pre-commit hooks 2022-05-15 10:23:01 +02:00
bc09d617fc Force g0 to water 2022-05-15 10:22:49 +02:00
a71f54b99f Brewpiless docs 2022-05-15 10:05:06 +02:00
152ff89bf6 Set rfd grav to 1 2022-05-15 09:55:01 +02:00
d9fb8291f4 Merge branch 'master' of https://github.com/mp-se/gravitymon 2022-05-10 18:43:22 +02:00
030746e982 Updated readme 2022-05-10 18:42:19 +02:00
d4260c6380 GitHub Action Build 2022-05-10 10:11:48 +00:00
50725407ea Bump v1.0 2022-05-10 12:06:05 +02:00
b4bac17114 Update issue templates 2022-05-09 20:44:42 +02:00
cb433a4a91 GitHub Action Build 2022-05-09 17:23:12 +00:00
095c1dc6a7 Adding http to keep-alive 2022-05-09 19:18:44 +02:00
43e2d165f5 Fix link error 2022-05-08 09:59:39 +02:00
fca7294b61 Updated docs 2022-05-08 09:58:26 +02:00
390c0882d1 Applied pre-commit on v1.0 source 2022-05-08 09:54:25 +02:00
5e9e705b96 #6 contribution 2022-05-08 09:50:45 +02:00
d67f72f123 #6 added contribution 2022-05-08 09:50:27 +02:00
f09aadaf66 #69 added schema for esp32 2022-05-08 09:22:52 +02:00
c68f67a558 GitHub Action Build 2022-05-06 16:11:31 +00:00
299e915bd2 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-05-06 18:06:25 +02:00
06a1541090 Updated docs for 1.0 2022-05-06 18:05:52 +02:00
a8b87140b2 #76 formula creation 2022-05-06 17:39:54 +02:00
1d738a14dd GitHub Action Build 2022-05-06 14:56:02 +00:00
4e37b9329c Updated esp32 code 2022-05-06 16:48:42 +02:00
9712e13c78 Updated release notes 2022-05-05 19:12:56 +02:00
1b11e49883 GitHub Action Build 2022-05-05 16:49:10 +00:00
cc6fecbdf8 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-05-05 18:44:35 +02:00
971b210438 Fixed error message 2022-05-05 18:44:16 +02:00
7b2a99c8a3 #73 add support for MPU6500 2022-05-05 17:52:26 +02:00
d6f8ff67a3 GitHub Action Build 2022-05-04 14:42:28 +00:00
f173b205ae Fix compiler error for esp32 2022-05-04 16:38:39 +02:00
e0312ab3c5 Update web pages 2022-05-04 16:31:49 +02:00
7678bb1f43 Remove zero length files at startup 2022-05-04 16:31:20 +02:00
403ed1d350 #72 lost wifi config 2022-05-04 16:31:02 +02:00
ced4c9f8fc GitHub Action Build 2022-05-01 16:11:44 +00:00
7e1862390e Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-05-01 18:07:22 +02:00
a3cd3217ab #63 tilt validation 2022-05-01 18:07:16 +02:00
7ede9a6d19 Reserved point for water 2022-04-30 19:37:30 +02:00
f27e8ac79f Added function to collect support cfg 2022-04-30 19:30:09 +02:00
7874f1bcf2 Bump to beta2 2022-04-30 17:46:55 +02:00
9f87bf5432 Adjust advanced settings 2022-04-30 17:46:32 +02:00
5bbb7ebfaa Hide wifi pass2 2022-04-30 17:46:09 +02:00
328e7e71ae Added mqtt errors to docs 2022-04-30 07:09:08 +02:00
2912749f19 GitHub Action Build 2022-04-29 18:14:13 +00:00
43b6881477 Upd release notes 2022-04-29 20:08:58 +02:00
3a4cfb1ca5 Pre update of docs for v1.0 2022-04-29 20:08:02 +02:00
a40c3528d4 Update release notes 2022-04-29 19:10:49 +02:00
87036d56e5 Moved sensor bits to loop 2022-04-29 19:07:48 +02:00
e6508b639d GitHub Action Build 2022-04-29 14:46:43 +00:00
1fb8541be6 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-29 16:42:32 +02:00
911b08cd73 Tag build as beta1 2022-04-29 16:42:04 +02:00
7d9228f9d8 #67 2022-04-29 16:41:44 +02:00
891794af7c Added error code -100 2022-04-29 08:59:37 +02:00
56cd730ad4 Removed gyro delay from api 2022-04-29 06:56:38 +02:00
5a9169be64 GitHub Action Build 2022-04-29 04:52:45 +00:00
c45a5174ae Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-29 06:48:40 +02:00
396608bd7e #65 #58 2022-04-29 06:48:17 +02:00
31dc2bae5f Pushing perf-data to influx ssl not supported 2022-04-28 21:28:26 +02:00
dfd5cea53d Disable adv config by default 2022-04-28 20:14:16 +02:00
41507f2bd4 GitHub Action Build 2022-04-28 18:05:57 +00:00
304903564d #62 2022-04-28 20:01:45 +02:00
8dcadf4240 GitHub Action Build 2022-04-27 20:25:31 +00:00
b1474d19c4 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-27 22:21:04 +02:00
d8cb4fe622 Merge branch 'bs5' into dev 2022-04-27 22:19:24 +02:00
e30e3b2cb5 Merge branch 'master' into dev 2022-04-27 22:19:14 +02:00
e91c8af1a5 moved maxfrag to own function 2022-04-27 22:15:35 +02:00
8342341cb9 Specified esp32 fwk ver 2022-04-27 22:14:07 +02:00
149124fc34 bump doc ver 2022-04-26 20:52:25 +02:00
1761ed47ba Updated release notes 2022-04-26 20:13:35 +02:00
1dd4c541b0 Merge branch 'master' into bs5 2022-04-26 20:05:57 +02:00
446cb61e1c Updated BF docs to clarify SG 2022-04-26 20:00:26 +02:00
2f01222582 Merge branch 'dev' into bs5 2022-04-26 19:55:16 +02:00
fa3d68c321 Added wifi advanced config 2022-04-26 19:54:48 +02:00
0fe9bb146d Merge branch 'master' into bs5 2022-04-26 19:52:44 +02:00
d4df0dd272 Fixed bug with rssi key in json 2022-04-26 16:42:13 +02:00
c319afbe9f Added tooltips 2022-04-26 16:24:33 +02:00
cf143c0e73 Added advanced settings 2022-04-25 21:41:00 +02:00
902d123a68 Added SSL support for influxdb 2022-04-24 22:35:10 +02:00
b359f3aba8 Bump ver 2022-04-24 22:06:47 +02:00
fb856dde75 Added used repos to about 2022-04-24 22:05:55 +02:00
c0f7cf2823 Formula fixes 2022-04-24 22:05:40 +02:00
d208b11384 Updated release notes 2022-04-24 18:55:50 +02:00
d83ef79165 Added copy function for standard formats 2022-04-24 18:54:16 +02:00
c685c18b57 Increasing calibration points to 10 2022-04-24 18:15:37 +02:00
7a84042781 Added gif animation of ui 2022-04-23 16:28:33 +02:00
14a73b011e Updated install instructions 2022-04-23 16:13:18 +02:00
121eeea392 Saving partitions.bin for esp32 2022-04-21 07:35:32 +02:00
ed8dc68fc7 GitHub Action Build 2022-04-18 15:14:29 +00:00
3d4c04333e Added perf32 as target 2022-04-18 17:09:02 +02:00
5b267210d2 Added save for additional formula points 2022-04-11 09:27:40 +02:00
e9742888f8 Updated html 2022-04-11 08:16:57 +02:00
5e1d2a73fa Started release notes for 1.0 2022-04-10 19:19:57 +02:00
7eddf35b97 Added 3 more points for formula calculation 2022-04-10 19:13:50 +02:00
a648d54a14 Removed brewfather option 2022-04-10 15:47:44 +02:00
e9229efe56 Removed dev code 2022-04-10 15:43:15 +02:00
635d788ba6 Rough update to bootstrap 5 done 2022-04-10 15:33:00 +02:00
2e3820ca73 GitHub Action Build 2022-04-09 12:09:24 +00:00
d52615f8e3 Update screendumps for 0.9 2022-04-09 14:05:18 +02:00
786b8e9b19 Update docs 2022-04-09 10:49:29 +02:00
10fa71d7ca GitHub Action Build 2022-04-09 07:09:20 +00:00
aeda821396 Fixed bad link to firmware upload 2022-04-09 09:05:28 +02:00
3db3968e07 GitHub Action Build 2022-04-09 07:00:17 +00:00
430f01943a Disable firmware upgrade if no file is selected 2022-04-09 08:56:59 +02:00
ca97a586c1 GitHub Action Build 2022-04-08 18:19:45 +00:00
5c80b780a0 Added check for valid SSID 2022-04-08 20:15:25 +02:00
2e42b86444 Updated hw docs for esp32 2022-04-08 20:14:57 +02:00
acb53bf611 Updated voltage factor for esp32 2022-04-08 20:07:23 +02:00
5b99304d7f GitHub Action Build 2022-04-07 18:40:31 +00:00
d94028a7b9 Updated docs 2022-04-07 20:37:35 +02:00
f4ca86e8ff Added link to firmware updata 2022-04-07 20:36:39 +02:00
2c9f5c72f2 GitHub Action Build 2022-04-07 14:56:20 +00:00
f5aae4f2ea Fixing temp adj in F correcly 2022-04-07 16:52:12 +02:00
51daa23327 GitHub Action Build 2022-04-06 18:21:08 +00:00
a5fb4f40b9 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-06 20:17:46 +02:00
17e9b0d51b added upgrade instructions 2022-04-06 20:17:26 +02:00
2877f91ef8 GitHub Action Build 2022-04-06 17:56:29 +00:00
8dd509214b Added git revision on UI 2022-04-06 19:52:56 +02:00
223ab7f81e GitHub Action Build 2022-04-06 13:50:10 +00:00
b6ba01f6e0 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-06 15:46:32 +02:00
75e9d178a3 Added bug to release notes 2022-04-06 15:44:26 +02:00
e8740364cf Adding leading zero to id 2022-04-06 15:43:01 +02:00
53f1373432 GitHub Action Build 2022-04-05 15:51:46 +00:00
40e7a37aa5 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-05 17:47:32 +02:00
24b2446521 Added perf target for esp32 2022-04-05 17:47:06 +02:00
82f48604a2 GitHub Action Build 2022-04-05 07:10:29 +00:00
f5b627616e Updated docs with ispindel web interface 2022-04-05 09:06:38 +02:00
dc4eb4f4a1 GitHub Action Build 2022-04-05 06:38:20 +00:00
52785871b9 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-05 08:35:06 +02:00
9c92bb9214 Update firmware htm 2022-04-05 08:35:00 +02:00
96295f161a GitHub Action Build 2022-04-05 06:32:51 +00:00
50116e8b45 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-04-05 08:29:27 +02:00
29b243f115 Esp32 support for firmware upload 2022-04-04 23:32:41 +02:00
c779a45ea9 pre-commit updates 2022-04-04 23:16:24 +02:00
2d67a44ad0 Added firmware upload page 2022-04-04 23:03:36 +02:00
8d44a5dcea Moved temp sensor code to correct api 2022-04-04 09:47:20 +02:00
3d3293138f Showing mDNS on wifi config page 2022-04-04 08:32:13 +02:00
cf3e683137 Fix rounding error for temp sensor adj 2022-04-04 08:08:14 +02:00
f2b926cce6 Temp sensor adj when in F 2022-04-03 20:28:59 +02:00
1494b2b3b2 iSpindle temp format error 2022-04-03 20:01:12 +02:00
474f987e73 GitHub Action Build 2022-04-01 06:45:57 +00:00
294b7a7fdd Added remove led on esp32 2022-04-01 08:41:31 +02:00
fda5c6ff27 Enable blue LED when running on ESP32 2022-03-31 16:51:27 +02:00
58144a5187 GitHub Action Build 2022-03-29 05:59:46 +00:00
877afbc26a Merge branch 'master' into dev 2022-03-29 07:56:23 +02:00
98a4c3650f GitHub Action Build 2022-03-27 20:37:11 +00:00
dcbedf5899 Fixed bug in upload api 2022-03-27 22:33:29 +02:00
4fff5ad185 Merge pull request #43 from jinjorge/doc_fix
Minor edits to docs
2022-03-27 11:51:41 +02:00
d1f1e926e7 Minor edits to docs 2022-03-26 23:08:32 -07:00
a20baa6b27 GitHub Action Build 2022-03-24 07:55:57 +00:00
42ca66555a Fixed test setup in htm 2022-03-24 08:52:37 +01:00
13d5280d76 GitHub Action Build 2022-03-21 21:08:33 +00:00
ae4d5eb8a2 Showing only last 6 chars of git rev 2022-03-21 22:05:13 +01:00
d6227b6dad GitHub Action Build 2022-03-21 20:46:07 +00:00
07fefe41fb Added git rev to build info 2022-03-21 21:42:44 +01:00
dab4d0ed22 GitHub Action Build 2022-03-20 14:39:41 +00:00
faba3d7619 Build skip html files in version.json 2022-03-20 15:36:13 +01:00
8637b0f72d Fix crashbug in ota 2022-03-20 15:35:37 +01:00
169798e0eb Remove unused file 2022-03-19 21:26:23 +01:00
04b2721c5d GitHub Action Build 2022-03-19 20:24:00 +00:00
14909b241b Bump to v0.9 2022-03-19 21:20:24 +01:00
f8e72a50e9 GitHub Action Build 2022-03-18 18:29:43 +00:00
5d03a33253 Bump version 2022-03-18 19:25:38 +01:00
65983e638a Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-03-18 19:25:04 +01:00
aae29786bb Added estimated runtime on config 2022-03-18 19:24:28 +01:00
b28797a79b GitHub Action Build 2022-03-16 18:08:46 +00:00
d4452e1d59 Added btn on test page 2022-03-16 19:05:11 +01:00
3717561466 GitHub Action Build 2022-03-14 17:59:26 +00:00
4cad6f888f Added http get option to push 2022-03-14 18:55:12 +01:00
6e9d562977 GitHub Action Build 2022-03-13 18:11:32 +00:00
3e6d698a40 Increased json buffer 2022-03-13 19:07:23 +01:00
dc70b250d8 Updated screenshots for 0.9 2022-03-13 19:07:12 +01:00
276b311194 GitHub Action Build 2022-03-13 16:41:38 +00:00
2609bf840c Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-03-13 17:38:26 +01:00
fe3ca247b9 Split push config into 2 sections 2022-03-13 17:37:53 +01:00
37a42aa2a1 GitHub Action Build 2022-03-13 10:00:37 +00:00
68dfacb07c Fixed bug in build script 2022-03-13 10:57:15 +01:00
a60abdbaa1 Merge branch 'master' into dev 2022-03-13 10:39:52 +01:00
ef3cc5b523 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-03-13 10:39:47 +01:00
99a57978fa Bump version 2022-03-13 10:38:08 +01:00
a2aaeb3f84 Format test using real data 2022-03-13 10:37:21 +01:00
2f8a324bfc Fixed issue with converstion when SG was 0 2022-03-13 10:37:01 +01:00
16e91ec4f5 Merged device and index pages in UI 2022-03-13 10:07:03 +01:00
3407567568 GitHub Action Build 2022-03-12 18:52:27 +00:00
83aa1b2202 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-03-12 19:49:26 +01:00
6193f422e8 Added target with esp32 fwk 2.0 2022-03-12 19:49:04 +01:00
a657f698b8 GitHub Action Build 2022-03-12 17:31:44 +00:00
3d497a1acc Dont start wifi on esp32 is not needed 2022-03-12 18:28:02 +01:00
4e190af499 Adjusted voltage factor on esp32 2022-03-12 17:28:41 +01:00
f07f845dfb GitHub Action Build 2022-03-12 12:57:17 +00:00
f1936b6b0d Fix compile error on linux 2022-03-12 13:53:40 +01:00
aa4e3b5e8d Updated docs to 0.9 2022-03-12 10:37:02 +01:00
7cafedd9bf Added CR on errors in serial console. 2022-03-12 08:25:24 +01:00
1e44d9bd01 Fixed back link in format editor 2022-03-12 08:20:17 +01:00
1bc3abc9f0 Integrated test function 2022-03-12 08:20:02 +01:00
8d51c5ad12 Updated q&a 2022-03-11 22:53:58 +01:00
d9c467d54f Added test option for push targets 2022-03-11 22:50:12 +01:00
07e7cbee1c Update partition info 2022-03-11 15:11:22 +01:00
f5fcf42fbe Fix littlefs header name (case sensitive) 2022-03-11 10:36:26 +01:00
036e10cd5d Updated esp32 target, updated github actions 2022-03-11 08:14:46 +01:00
2d1317af0d Migrated to littlefs on esp32 + fix A0 pin 2022-03-10 11:11:39 +01:00
21fba0481c Added BLE on ESP32 target. 2022-03-10 11:10:54 +01:00
6dfe5a80fd Update wifimgr 2022-03-05 19:55:16 +01:00
c681619be8 Update wifimgr 2022-03-05 17:30:49 +01:00
02cb91e918 Update dbl resetdetector 2022-03-05 17:30:32 +01:00
8a597162ae GitHub Action Build 2022-03-05 14:27:39 +00:00
f49f386569 Updated html for calibration 2022-03-05 15:25:48 +01:00
008ad490a7 GitHub Action Build 2022-03-05 14:17:23 +00:00
3533ee8dac Show tdevice movement in ui 2022-03-05 15:14:46 +01:00
2b9abda873 Bump html version 2022-03-05 14:56:46 +01:00
4bbb558c8b GitHub Action Build 2022-03-05 13:38:39 +00:00
8ebbc6559f Bump version 2022-03-05 14:36:15 +01:00
0af872e743 Updated docs for 0.8 2022-03-05 14:36:04 +01:00
c20f9a534a Remove sled 2022-02-14 21:49:07 +01:00
10ce1fc245 Adding moving info on html 2022-02-14 21:48:34 +01:00
b43874d151 Sorting graph data 2022-02-14 21:47:59 +01:00
1428bec3da GitHub Action Build 2022-02-08 18:07:39 +00:00
f9791dd349 Bump vuild version 2022-02-08 19:05:29 +01:00
1a7f28413c Revert gyro change 2022-02-08 19:05:13 +01:00
d22309bb2e GitHub Action Build 2022-02-06 20:32:15 +00:00
b901a12699 Removed trailing LF from error log 2022-02-06 21:29:58 +01:00
914b4125d8 Fixed tilt calulation error #29 2022-02-06 21:22:46 +01:00
95216ecc54 Fixed ESP32 build 2022-02-06 21:21:55 +01:00
4d83bf8fce GitHub Action Build 2022-02-06 10:21:33 +00:00
e125ca4a10 Added data to upload api 2022-02-06 10:04:53 +01:00
5880d3a6ba GitHub Action Build 2022-02-04 15:35:08 +00:00
cda3a87dd9 Docs updated 2022-02-04 16:22:27 +01:00
4bcacea9d7 Fixed hostname for min ssl buffer 2022-02-04 16:22:14 +01:00
838d062eea validation on header form 2022-02-03 21:48:11 +01:00
77cdbf7649 Updated bootstrap to 4.6.1 2022-02-03 20:33:20 +01:00
f33a58cffe GitHub Action Build 2022-02-03 15:05:54 +00:00
7ca536b216 Changed from s to ms in post timeout 2022-02-03 16:03:39 +01:00
f0ec352538 GitHub Action Build 2022-02-03 08:35:02 +00:00
e336633c38 Mem debug, Variable http timeout and min heapfrag 2022-02-03 09:32:09 +01:00
a130ebd67d GitHub Action Build 2022-02-02 12:32:48 +00:00
4c789a8b37 Minor update 2022-02-02 13:30:48 +01:00
7ab5f451f5 GitHub Action Build 2022-02-02 12:26:05 +00:00
545f274a47 Refactored to free up heap for SSL 2022-02-02 13:23:16 +01:00
9bea54b703 GitHub Action Build 2022-02-01 15:46:41 +00:00
35333469c7 Added thingsspeak setup docs 2022-02-01 16:44:07 +01:00
a9e0d9290a Update release notes 2022-02-01 11:28:28 +01:00
e459ceb2fa Updated docs 2022-02-01 11:27:46 +01:00
2615debe35 Added prel calculation for estimated total runtime 2022-02-01 10:37:17 +01:00
6113a436b0 Added battery percentage to index page 2022-01-31 22:42:41 +01:00
2d5158465f Removed batt from device page 2022-01-31 22:32:52 +01:00
3af52b5464 GitHub Action Build 2022-01-31 17:57:57 +00:00
c116d672d1 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-01-31 18:51:06 +01:00
e1cc54d188 Added token as option in UI #32 2022-01-31 18:50:47 +01:00
761d570d39 Added average runtime + voltage to device page 2022-01-31 18:29:27 +01:00
22ade61af8 Added runtime time logger 2022-01-30 22:54:48 +01:00
928054458a GitHub Action Build 2022-01-30 18:23:44 +00:00
2e67bd1d57 Checking missing params in API #16 2022-01-30 19:21:32 +01:00
83d7aee944 Merge branch 'master' into dev 2022-01-30 17:29:36 +01:00
8bcd27a076 Fix bad link in docs 2022-01-30 17:29:12 +01:00
0912d672fe Merge branch 'master' into dev 2022-01-30 17:27:36 +01:00
01d7c8adb5 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-01-30 17:27:29 +01:00
69076d5878 Fixed bad doc_ref 2022-01-30 17:26:13 +01:00
3d939ad733 Merge branch 'master' into dev 2022-01-30 17:23:59 +01:00
f2112cb344 Added intro section to docs 2022-01-30 17:22:38 +01:00
f88fb8241b GitHub Action Build 2022-01-30 13:57:05 +00:00
6eed5f143b Merged patch 0.7.1 into dev 2022-01-30 14:55:00 +01:00
044bfcddad GitHub Action Build 2022-01-30 13:32:41 +00:00
1fd3b1911d Bump version of html 2022-01-30 14:30:52 +01:00
9727e87e33 GitHub Action Build 2022-01-30 13:27:18 +00:00
3390ebc5ab Updated html 2022-01-30 14:25:29 +01:00
e076de022c GitHub Action Build 2022-01-30 11:16:18 +00:00
4e0980e814 New attempt to fix action 2022-01-30 12:13:59 +01:00
b8959ae165 Fixing workflow 2022-01-30 12:09:47 +01:00
4d6b4b7fd6 Fixing 3 bugs 2022-01-30 11:54:55 +01:00
64e582d0e5 GitHub Action Build 2022-01-30 10:52:11 +00:00
5d0f02eb18 Update docs 2022-01-30 11:50:09 +01:00
fbc1eb4e31 Updated docs 2022-01-28 17:15:12 +01:00
700f00f48d GitHub Action Build 2022-01-27 20:33:34 +00:00
9a2f86fed7 Updated docs for 0.8 2022-01-27 21:31:35 +01:00
63fd80e750 Added errlog, custom http headers 2022-01-27 14:00:12 +01:00
b106ebfa20 Extended length of http url 2022-01-25 21:57:22 +01:00
dc5979dd28 Corrected docs 2022-01-25 21:19:02 +01:00
22d5e4fad7 Fix link to release in docs 2022-01-25 20:12:25 +01:00
ce361f66c8 Update release notes 2022-01-25 18:08:07 +01:00
2474306acb Updated docs to 0.7 2022-01-25 16:21:24 +01:00
1d3cbbb3c2 GitHub Action Build 2022-01-24 21:23:43 +00:00
0db586d744 Updated release notes 2022-01-24 22:20:57 +01:00
a01c838b8f GitHub Action Build 2022-01-24 06:57:35 +00:00
080820f10f Fix esp32 build 2022-01-24 07:54:59 +01:00
1a9283b719 Change github action 2022-01-24 07:41:40 +01:00
1d113e6941 Dont run push when low on mem, avoid crash 2022-01-24 07:34:34 +01:00
49f166cc67 GitHub Action Build 2022-01-23 16:07:19 +00:00
58a9966e6e Bump v0.7 2022-01-23 17:05:25 +01:00
357772dbaf Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-01-23 15:55:33 +01:00
1c0eb11133 Updated docs 2022-01-23 15:55:25 +01:00
7890f8096f GitHub Action Build 2022-01-23 14:09:29 +00:00
68018329bb Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-01-23 15:07:05 +01:00
57bfdc1e87 Change mqtt behaviour to simulate ispindel 2022-01-23 15:06:41 +01:00
d3a71da643 Added format editor and template engine 2022-01-23 11:11:41 +01:00
42c58fdcdf GitHub Action Build 2022-01-22 08:57:59 +00:00
7b4e95b5ad Updates for ESP32 2022-01-22 09:53:08 +01:00
767988a7c5 Added ESP32 as new target (still on SDK 1.0.6) 2022-01-21 17:15:27 +01:00
20771b3244 Added option to change hw params. 2022-01-20 16:59:24 +01:00
35f66e0458 Refactored includes 2022-01-19 19:08:16 +01:00
37a1ca6058 Refactored class members 2022-01-18 23:33:34 +01:00
ddb34e129d Added ssl and plato 2022-01-18 23:01:10 +01:00
10163f3aa7 New install method via brewflasher 2022-01-15 16:49:36 +01:00
1adef20edd Updated docs 2022-01-14 16:39:59 +01:00
617e77a9d8 Updated formats with mqtt 2022-01-13 21:15:48 +01:00
c8d48a3236 Updated link to v0.6 2022-01-13 18:21:57 +01:00
f3d2e88b31 GitHub Action Build 2022-01-13 17:12:15 +00:00
a9dc3be329 Bump html to 0.6 2022-01-13 18:10:06 +01:00
043963df91 Added reference to mqtt 2022-01-13 12:38:29 +01:00
f318dea9b7 Moved some images 2022-01-13 09:34:01 +01:00
06b5b949d0 GitHub Action Build 2022-01-13 08:21:35 +00:00
5c400e4e47 Bump version 2022-01-13 09:18:51 +01:00
e1484ca2fd GitHub Action Build 2022-01-13 08:12:04 +00:00
4d300908a6 Updated release notes / doc 2022-01-13 09:09:09 +01:00
5b6ce7d672 Fixed crash bug in gyro.cpp 2022-01-13 09:08:53 +01:00
adc21b5527 Added new theames to doc build 2022-01-13 09:08:24 +01:00
8919c842fb GitHub Action Build 2022-01-12 17:57:58 +00:00
a270faa480 Merged mqtt 2022-01-12 18:55:54 +01:00
aad35e20bd Fixed mqtt issues 2022-01-12 17:39:54 +01:00
a83a74b5a4 Initial mqtt version 2022-01-11 23:23:43 +01:00
a703add227 GitHub Action Build 2022-01-11 16:22:54 +00:00
534a739891 Updated docs 2022-01-11 17:20:38 +01:00
01fe6bbf19 Updated influx format (corr gyrotemp) 2022-01-11 11:32:04 +01:00
19d1f7b1ca GitHub Action Build 2022-01-11 08:08:00 +00:00
2a2aecdee5 Merge branch 'dev' of https://github.com/mp-se/gravitymon into dev 2022-01-11 09:06:03 +01:00
325c7ee2ca Merge 2022-01-11 09:05:27 +01:00
57f5816f63 Enabled gyro temp 2022-01-11 09:04:09 +01:00
46b17177f2 GitHub Action Build 2022-01-10 19:42:49 +00:00
df1981e3dd Updated workflow 2022-01-10 20:40:44 +01:00
2f391c95c7 Updated code to 0.6 test 2022-01-10 20:14:58 +01:00
ea62d9e752 Added warning on short sleep interval 2022-01-10 20:14:23 +01:00
6364e251b7 Moved doc publich to master branch 2022-01-10 20:14:03 +01:00
93d9effcef Integrated changed for new wifimgr 2022-01-10 10:27:24 +01:00
4ff114642e Added wifmanager 2022-01-09 23:12:40 +01:00
7f775d78eb Refactor main to prepare for wifimgr lite 2022-01-09 14:49:20 +01:00
17bc23bef8 Updated release notes for 0.5 2022-01-09 09:50:28 +01:00
e234d6f908 Updates to docs 2022-01-09 09:23:40 +01:00
2662f38bf7 GitHub Action Build 2022-01-08 22:45:35 +00:00
3b3ee5bbf9 Removed unused code 2022-01-08 23:42:44 +01:00
4731569b75 GitHub Action Build 2022-01-08 20:11:54 +00:00
a255239c02 Added licence 2022-01-08 21:09:02 +01:00
c72ba40884 GitHub Action Build 2022-01-08 19:25:17 +00:00
7dde662aa8 merge with origin 2022-01-08 20:23:32 +01:00
c08ac7c4e4 Build of v0.5 updated install instructions 2022-01-08 20:21:56 +01:00
4e32bfbec6 GitHub Action Build 2022-01-08 16:54:58 +00:00
e4fd33fe87 Building v0.5 2022-01-08 17:52:58 +01:00
33748aa1c2 Minor updates to docs 2022-01-08 16:54:26 +01:00
c72a249bd4 Testing 2022-01-08 16:39:10 +01:00
b1ccdfad6a Test 2022-01-08 16:37:19 +01:00
b277274eab Testing 2022-01-08 16:31:38 +01:00
e321eadf0f Testing 2022-01-08 16:21:37 +01:00
604761dbe0 Test 2022-01-08 16:19:25 +01:00
1d5e0ffb63 Testing 2022-01-08 16:10:09 +01:00
dfe6bbf61d Testing 2022-01-08 16:08:12 +01:00
1fe49d5fad Testing doc build 2022-01-08 16:04:48 +01:00
3f74228a12 Switching checkin handler 2022-01-08 12:31:26 +01:00
788e0e783d Testing 2022-01-08 12:20:32 +01:00
825d2ba6f3 Testing 2022-01-08 12:10:41 +01:00
4d65e0d3e0 Testing push to docs 2022-01-08 11:38:06 +01:00
95985eab0e Published github actions for pio and sphinx 2022-01-08 11:23:33 +01:00
5119ffcbe5 Added checking of built docs 2022-01-08 11:20:08 +01:00
0a53a5efab Added theme 2022-01-08 11:16:01 +01:00
2a61658088 Fixed naming in gha 2022-01-08 11:11:51 +01:00
33081ee290 Updated action for docs 2022-01-08 10:27:44 +01:00
d21d5f7965 Updated doc build 2022-01-08 10:20:45 +01:00
7e264b29ff Merge branch 'ghactions' of https://github.com/mp-se/gravitymon into ghactions 2022-01-08 10:14:23 +01:00
503de20e42 Added gh action for docs 2022-01-08 10:13:36 +01:00
9ea4d302d6 GitHub Action Build 2022-01-08 09:05:21 +00:00
d7b30c484d Added checin of binaries 2022-01-08 10:03:11 +01:00
3c1bf123b4 Updated to linux path 2022-01-08 09:48:09 +01:00
42205a94c9 Updated action w linux path 2022-01-08 09:38:25 +01:00
1894bc4959 Updated scripts for actions 2022-01-08 09:30:19 +01:00
1928dbb749 Acttion debug 2022-01-08 09:19:39 +01:00
6c26bbeacb Action test 2022-01-08 00:45:58 +01:00
c67585c7e4 Action test 2022-01-08 00:44:22 +01:00
11d106da4f Testing actions 2022-01-08 00:42:10 +01:00
fdd04bc19a Merge branch 'docs' into ghactions 2022-01-08 00:37:34 +01:00
5f2c7eb2d8 Merge branch 'dev' into ghactions 2022-01-08 00:37:14 +01:00
864f04c707 Remove action 2022-01-08 00:33:09 +01:00
8f95cfaea5 Create codeql-analysis.yml 2022-01-07 23:28:59 +01:00
19f3ba1aae Added performance section 2022-01-07 19:48:40 +01:00
3e947346b4 Corrected install instructions 2022-01-07 17:39:39 +01:00
31a8500405 Bump v0.5 (beta) 2022-01-07 17:22:34 +01:00
5573e06155 Updated docs for v0.5 2022-01-07 17:08:57 +01:00
298ea1de61 0.5 release candidate for testing. 2022-01-07 14:48:26 +01:00
1478430f03 Merged precommit branch 2022-01-07 13:38:37 +01:00
5612c0ce64 Merged pre-commit (pre testing) 2022-01-07 10:26:35 +01:00
e3ac920fc4 Added precommit config 2022-01-07 10:13:47 +01:00
ed53182c29 Applied precommit cpp checks 2022-01-07 10:13:08 +01:00
88bd971b73 Lint for config 2022-01-06 22:20:23 +01:00
5d9115137f Lint checked 2022-01-06 21:54:34 +01:00
299d2dd0fe Updated readme 2022-01-06 21:05:08 +01:00
2fb86c594a Prel docs for v0.5 2022-01-06 21:01:06 +01:00
51a2d30824 Disable jekyll 2022-01-06 16:56:23 +01:00
4de40366bb Moved directories to make github pages work 2022-01-06 16:49:58 +01:00
27b7b70998 Merge branch 'docs' of https://github.com/mp-se/gravitymon into docs 2022-01-06 16:30:04 +01:00
4f0b6d11e7 Merged changes from dev 2022-01-06 16:29:48 +01:00
20d5f50c19 Merge branch 'dev' into docs 2022-01-06 16:14:53 +01:00
93df4b60e0 Updated remove build folder for docs 2022-01-06 16:13:32 +01:00
0c0e9067f9 First draft of new documentation 2022-01-06 16:12:44 +01:00
c1db431490 Set theme jekyll-theme-architect 2022-01-05 22:03:10 +01:00
791f5fedd2 Creating /docs 2022-01-05 22:00:47 +01:00
f8e5957045 Moving defines to .ini 2022-01-05 21:57:37 +01:00
088a37eaf3 Reduced logging messages 2022-01-04 21:13:34 +01:00
5a7858ecaf Refactor webserver, changes to formulaCreation 2022-01-04 19:46:37 +01:00
0783a206b2 Added graph + updated formula creation 2022-01-04 11:22:11 +01:00
d796794602 Simplified wifmanager code, due to crashes 2022-01-03 21:47:42 +01:00
d075fe2117 Prel code for formula creation 2022-01-03 18:30:57 +01:00
48f71bd59a Dont check in html in /data folder 2022-01-02 13:24:35 +01:00
bdd4b7117b Moved html files to separate folder 2022-01-02 13:24:00 +01:00
29174bf1f9 Swiched to own forks of libs for better version control 2022-01-01 17:42:50 +01:00
173 changed files with 21981 additions and 5131 deletions

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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
View 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

49
.github/workflows/doc-build.yaml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Sphinx Build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ammaraskar/sphinx-action@master
with:
docs-folder: "src_docs/"
pre-build-command: |
pip install --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
run: |
pwd
ls -al
ls -al docs
git clone https://github.com/mp-se/gravitymon.git --branch ghpages --single-branch ghpages
mkdir -p ghpages/docs
cp -r docs/* ghpages/docs
cd ghpages
touch docs/.nojekyll
git config --local user.email "action@noreply.github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "Update documentation" -a || true
# git push https://${{secrets.token}}@github.com/mp-se/gravitymon.git
# The above command will fail if no changes were present, so we ignore
# the return code.
- name: Push changes
uses: ad-m/github-push-action@master
with:
branch: ghpages
directory: ghpages
github_token: ${{ secrets.GITHUB_TOKEN }}

53
.github/workflows/pio-build-patch.yaml vendored Normal file
View 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

51
.github/workflows/pio-build.yaml vendored Normal file
View File

@ -0,0 +1,51 @@
name: PlatformIO CI
on:
push:
branches:
- dev
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v2
with:
path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: Set up Python
uses: actions/setup-python@v2
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
git config --global advice.detachedHead false
- name: Run PlatformIO
run: pio run -e gravity-release -e gravity-perf -e 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:
add: 'bin'
author_name: GitHub Action
author_email: mp-se@noreply.github.com
branch: dev
default_author: github_actor
message: 'GitHub Action Build'
pathspec_error_handling: ignore

19
.github/workflows/pre-commit.yaml vendored Normal file
View 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

5
.gitignore vendored
View File

@ -1,8 +1,9 @@
.pio/*
.vscode/*
*.map
docs/*
test/*.md
test/env/*
test/*.py
test/configure_*.py
TODO.md
src_docs/_build/*
data/*.min.htm

13
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,13 @@
repos:
- repo: https://github.com/bmorcos/pre-commit-hooks-cpp
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
View 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!

View File

@ -1,7 +1,6 @@
/*
MIT License
Copyright (c) 2021 Magnus
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -20,17 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _CALC_H
#define _CALC_H
// Includes
#include "helper.h"
// Functions
double calculateGravity( double angle, double temp );
double gravityTemperatureCorrection( double gravity, double temp, char tempFormat, double calTemp = 20 );
#endif // _CALC_H
// EOF

176
README.md
View File

@ -1,156 +1,38 @@
![download](https://img.shields.io/github/downloads/mp-se/gravitymon/total)
![release](https://img.shields.io/github/v/release/mp-se/gravitymon?label=latest%20release)
![issues](https://img.shields.io/github/issues/mp-se/gravitymon)
![pr](https://img.shields.io/github/issues-pr/mp-se/gravitymon)
![dev_build](https://img.shields.io/github/workflow/status/mp-se/gravitymon/PlatformIO%20CI/dev?label=dev%20build)
![doc_build](https://img.shields.io/github/workflow/status/mp-se/gravitymon/Sphinx%20Build/dev?label=doc%20build)
# Gravity Monitor for Beer Brewing
This software can be used with iSpindle hardware and utilizes the same hardware configuration. No code has been reused from the iSpindle project.
GravityMon is a replacement firmware for the iSpindle firmware. It's 100% compatible with the iSpindle hardware design so it does not require any hardware changes.
I started this project out of curiosity for how a motion sensor is working and since I like to brew beer this was the result.
Now also works with ESP32 (use ESP32 d1 mini which is compatible with ESP8266)
After 6 months of testing I believe this is working as planned. It give accurate readings same as the orginal iSpindel software.
Installation can be made using https://www.brewflasher.com or https://web.brewflasher.com
Version history
* v0.4 First official version with 5+ brews on record.
The documenation can be found here: https://mp-se.github.io/gravitymon/index.html
Lower priority
* Add support for Plato in device (today it assumes that formula is in SG).
* Add support for converting between SG/Plato in device.
* Add support for Blynk as endpoint
* Add support for https connections (push) - [need to reduce memory usage for this to work, gets out of memory error]
* Add support for https web server (will require certificates to be created as part of build process)
* Add support for WifiManager Secure access, depends on support in library.
Note! If Brewflasher being flagged as malware by your antivirus software, try the web version.
# Functionallity
The main differences:
---------------------
I have made a few different design decision compared to the standard iSpindle software.
* Modern web based user interface for configuration when connected to WIFI
* Efficient software, long lifespan (+45 days with 5min update frequencey)
* Send data to multiple endpoints (http-post, http-get, influxdb v2, mqtt)
* Instructions for service such as; Brewfather, Fermentrack, Ubidots, Home Assistant, Brewers Friend, Brewspy, Thingspeak, Blynk.
* SSL support for all remote endpoints
* ESP32 support with Bluetooth push
* Customize data format to be pushed
* Automatic temperature adjustment of gravity when enabled
* Use the temperature sensor in gyro instead of DS18B20
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
* Visual graph showing how gravity formula will be interpreted
* OTA support or firmware upload via web interface
* REST API for scripting
* The device operate in two modes, __always-on__ or __deep-sleep__. Always on can be triggered in two ways: Connected to charger and the power is over 4.1V or the device is lying flat (angle is approx 90 degrees).
* Configuration options have been moved to a web gui and is accesible when the device is in __always-on__ mode. The Wifi portal only need to be used for changing WIFI network.
* The software also has built in OTA support so new versions can be downloaded from a local webserver and checks are done during startup and the device is in __always-on__ mode.
* Temperature calibration has been added for the DS18B20 sensor so you can adjust the temperature sensor readings if there is a need. When the device is in __always-on__ mode the temperature will rise in the container so the value will increase and not reflect the temperature for the surronding the container.
* There is an option to automatically correct the gravity calculation based on the temperature. Useful if you are fermenting at lower temperatures. It's possible to build this into the normal gravity formula but this is an easier option. Just make sure that the calibration is done at 20°C.
* The software will read the motion sensor 50 times and use the average to calculate the angle. If the readings show that the device is moving it will wait a few seconds and retry the operation again to make sure that no invalid angles should be reported. If the device is unsuccesful to get a valid angle within 10s it will go to sleep for 60s and then retry again (TODO: This will be adjusted after more testing).
## Configuration
Configuration is accessed by entering the URL for the device, this will be the mDNS name __device.local__ or the IP adress. The following chapter assumes the device name is __gravmon__.
### Index page
http://gravmon.local/
The main page shows the device readings; gravity, angle, temperature and battery charge. If the checkbox is active then the device will never go into sleep mode. This is useful if you are collecting angle/tilt for calibration. If this is unchecked the device will change mode as explained before.
![Status](img/index.png)
### Device page
http://gravmon.local/device.htm
The device page shows the device settings and software versions.
![Device](img/device.png)
### Config page
http://gravmon.local/config.htm
* This page is divided into several categories of settings. The first one contains device settings, mDNS name, temperature format, sleep interval and gyro calibration data. The interval setting is the amount of time the device will be in sleep mode between readings (interval is in seconds). To simplify this you can also see the conversion to minutes / seconds next to the input field.
* The sleep interval can be set between 10 - 3600 seconds (60 minutes).
* Calibration needs to be done or the device will not work correctly. Place the device flat on a surface with gyro up and press the calibrate button when it's stable. If no calibration data exist the device will not enter sleep-mode.
![Config - Device](img/config1.png)
* The second section contains the push settings, two URL's for http posts, Brewfather and settings for InfluxDB v2.
### This is the format used for standard http posts.
```
{
"name" : "gravmon", // mDNS name
"ID": "2E6753", // esp device id
"token" : "gravmon",
"interval": 900,
"temperature": 20.5, // C or F based on setting, adjusted value.
"temp-units": "C", // C or F based on setting
"gravity": 1.0050, // Raw or temperature corrected gravity (based on setting)
"corr-gravity": 1.0050, // Temperature corrected gravity
"angle": 45.34,
"battery": 3.67,
"rssi": -12,
"run-time": 230, // ms, Runtime for this reading, this is an additional field not part of the standard format
}
```
### This is the format for Brewfather
```
{
"name" : "gravmon", // mDNS name
"temp": 20.5,
"temp-unit": "C",
"battery": 3.67,
"gravity": 1.0050,
"gravity_unit": "G", // G = SG, Plato is not yet supported
}
```
### This is the format for InfluxDB v2
```
measurement,host=<mdns>,device=<id>,temp-format=<C|F>,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18
```
![Config - Push](img/config2.png)
* The third section contains the gravity formula and also option for doing temperature compensation. The calibration formula uses two keywords, temp and tilt. Temperature is in the selected device format.
* Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. I use the tool fermentrack for controlling my fermentation and I use this tool for calculating gravity. The formula can handle two keywords, __tilt__ and __temp__. This is an example of a formula; __0.00145*tilt^3+0.1445*tilt^2+0.00179*tilt+0.9436__
* This is the formula used for temperature calibration (temp is in F). Cal = 20C.
```
gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))
```
![Config - gravity](img/config3.png)
* Hardware settings contain settings for temperature sensor adjustment, voltage factor (to calulate the battery level) and OTA URL.
* For the OTA to work, place the following files (version.json + firmware.bin) at the location that you pointed out in OTA URL. If the version number in the json file is newer than in the code the update will be done during startup.
Example; OTA URL (don't forget trailing dash), the name of the file should be firmware.bin
```
http://192.168.1.1/firmware/gravmon/
```
Contents version.json
```
{ "project":"gravmon", "version":"0.3.0" }
```
![Config - Hardware](img/config4.png)
# Building a device
See the iSpindle documentation for building a device.
I've included my 3d sled that I use for my builds that allows for easy adjustment of the default angle. The stl files can be found under the stl directory.
![Spindle sled](img/spindle.png)
![Spindle sled with weight](img/spindle-weight.png)
# Compiling the software
I recommend that VSCODE with PlatformIO and Minfy extensions are used. Minify is used to reduce the size of the HTML files which are embedded into the firmware or uploaded to the file system. When using minify on a file, for example index.htm the output will be called index.min.htm. This is the file that will be used when buildning the image.
By default the html files are embedded but there are options to upload them to the file system to reduce the size of the image if the size becomes to large for OTA.
You can set the SSID and PWD as presets through platformio.ini by adding the settings to the following definitions:
```
-D USER_SSID=\""\"" // =\""myssid\""
-D USER_SSID_PWD=\""\"" // =\""mypwd\""
```
There are more options in teh platform.ini file that enable/disable additional functions for logging level, pushing performance data to InfluxDB and more. If i get the time I will add some documentation around these.
No code has been reused from the iSpindle project.

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="#"><b>About</b></a></li></ul></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>

52
bin/calibration.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

View File

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.4.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bin/firmware32-perf.bin Normal file

Binary file not shown.

BIN
bin/firmware32.bin Normal file

Binary file not shown.

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

Binary file not shown.

1
bin/test.min.htm Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{ "project":"gravmon", "version":"0.4.0", "html": [ "index.min.htm", "device.min.htm", "config.min.htm", "about.min.htm" ] }
{ "project":"gravmon", "version":"1.1.0", "html": [ ] }

View File

@ -1,78 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/about.htm">About</a>
</li>
</ul>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-4">
<div class="row mb-3">
<h3>Beer Gravity Monitor</h3>
This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.
</div>
<div class="row mb-3">
<h3>MIT License</h3>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</div>
<hr class="my-4">
</div>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><hr class="my-4"></div><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

View File

@ -1,383 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about.htm">About</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-2">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<script type="text/javascript">
function showError( msg ) {
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
function showSuccess( msg ) {
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
$('#alert-msg').text( msg );
}
$("#alert-btn").click(function(e){
$('.alert').addClass('d-none').removeClass('show')
});
</script>
<div class="accordion" id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Device settings
</button>
</h2>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/device" method="post">
<input type="text" name="id" id="id1" hidden>
<div class="form-group row">
<label for="mdns" class="col-sm-4 col-form-label">Device name:</label>
<div class="col-sm-8">
<input type="text" maxlength="12" class="form-control" name="mdns" id="mdns">
</div>
</div>
<fieldset class="form-group row">
<legend class="col-form-label col-sm-4 float-sm-left pt-0">Temperature Format:</legend>
<div class="col-sm-8">
<div class="form-check">
<input class="form-check-input" type="radio" name="temp-format" id="temp-format-c" value="C" checked>
<label class="form-check-label" for="temp-format-c">
Celsius
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="temp-format" id="temp-format-f" value="F">
<label class="form-check-label" for="temp-format-f">
Farenheight
</label>
</div>
</div>
</fieldset>
<div class="form-group row">
<label for="sleep-interval" class="col-sm-4 col-form-label">Interval (seconds):</label>
<div class="col-sm-4">
<input type="number" min="10" max="3600" class="form-control" name="sleep-interval" id="sleep-interval">
</div>
<label for="sleep-interval" class="col-sm-4 col-form-label" id="sleep-interval-info"></label>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-4">
<button type="submit" class="btn btn-primary" id="device-btn">Save</button>
</div>
</div>
</form>
<hr class="my-2">
<div class="form-group row">
<label for="calibrate-btn" class="col-sm-4 col-form-label">Current calibration values:</label>
<label for="calibrate-btn" class="col-sm-4 col-form-label" id="gyro-calibration-data">Loading...</label>
<label for="gyro-calibration-data" class="col-sm-4 col-form-label" id="angle">Loading...</label>
<div class="col-sm-8 offset-sm-4">
<button type="button" class="btn btn-warning" id="calibrate-btn">Calibrate device</button>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Push settings
</button>
</h2>
</div>
<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/push" method="post">
<input type="text" name="id" id="id2" hidden>
<div class="form-group row">
<label for="http-push" class="col-sm-4 col-form-label">Http URL 1:</label>
<div class="col-sm-8">
<input type="url" maxlength="100" class="form-control" name="http-push" id="http-push">
</div>
</div>
<div class="form-group row">
<label for="http-push2" class="col-sm-4 col-form-label">Http URL 2:</label>
<div class="col-sm-8">
<input type="url" maxlength="100" class="form-control" name="http-push2" id="http-push2">
</div>
</div>
<hr class="my-2">
<div class="form-group row">
<label for="inputBrewfatherPush" class="col-sm-4 col-form-label">Brewfather URL:</label>
<div class="col-sm-8">
<input type="url" maxlength="100" class="form-control" name="brewfather-push" id="brewfather-push">
</div>
</div>
<hr class="my-2">
<div class="form-group row">
<label for="influxdb2-push" class="col-sm-4 col-form-label">InfluxDB v2 URL:</label>
<div class="col-sm-8">
<input type="url" maxlength="100" class="form-control" name="influxdb2-push" id="influxdb2-push">
</div>
</div>
<div class="form-group row">
<label for="influxdb2-org" class="col-sm-4 col-form-label">InfluxDB v2 Organisation:</label>
<div class="col-sm-8">
<input type="text" maxlength="50" class="form-control" name="influxdb2-org" id="influxdb2-org">
</div>
</div>
<div class="form-group row">
<label for="influxdb2-bucket" class="col-sm-4 col-form-label">InfluxDB v2 Bucket:</label>
<div class="col-sm-8">
<input type="text" maxlength="50" class="form-control" name="influxdb2-bucket" id="influxdb2-bucket">
</div>
</div>
<div class="form-group row">
<label for="influxdb2-auth" class="col-sm-4 col-form-label">InfluxDB v2 Auth. Token:</label>
<div class="col-sm-8">
<input type="text" maxlength="100" class="form-control" name="influxdb2-auth" id="influxdb2-auth">
</div>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-4">
<button type="submit" class="btn btn-primary" id="push-btn">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingThree">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Gravity
</button>
</h2>
</div>
<div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/gravity" method="post">
<input type="text" name="id" id="id3" hidden>
<div class="form-group row">
<label for="gravity-formula" class="col-sm-4 col-form-label">Formula</label>
<div class="col-sm-8">
<input type="text" maxlength="200" class="form-control" name="gravity-formula" id="gravity-formula">
</div>
</div>
<label for="gravity-formula"" class="col-sm-8 offset-sm-4 col-form-label" id="gravity">Loading...</label>
<div class="form-group row">
<div class="col-sm-8 offset-sm-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="gravity-temp-adjustment" id="gravity-temp-adjustment">
<label class="form-check-label" for="gravity-temp-adjustment">
Temperature adjust gravity
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-4">
<button type="submit" class="btn btn-primary" id="gravity-btn">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingFour">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
Hardware settings
</button>
</h2>
</div>
<div id="collapseFour" class="collapse" aria-labelledby="headingFour" data-parent="#accordion">
<div class="card-body">
<form action="/api/config/hardware" method="post">
<input type="text" name="id" id="id4" hidden>
<div class="form-group row">
<label for="voltage-factor" class="col-sm-4 col-form-label">Voltage factor:</label>
<div class="col-sm-4">
<input type="number" step=".01" class="form-control" name="voltage-factor" id="voltage-factor">
</div>
<label for="voltage-factor" class="col-sm-4 col-form-label" id="battery">Loading...</label>
</div>
<div class="form-group row">
<label for="temp-adjustment-value" class="col-sm-4 col-form-label">Temp Sensor Adj:</label>
<div class="col-sm-8">
<input type="number" step=".1" class="form-control" name="temp-adjustment-value" id="temp-adjustment-value">
</div>
</div>
<div class="form-group row">
<label for="ota-url" class="col-sm-4 col-form-label">OTA base URL:</label>
<div class="col-sm-8">
<input type="url" maxlength="90" class="form-control" name="ota-url" id="ota-url">
</div>
</div>
<div class="form-group row">
<div class="col-sm-8 offset-sm-4">
<button type="submit" class="btn btn-primary" id="hardware-btn">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
window.onload = getConfig;
setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
$(location.hash + '.collapse').collapse('show');
}
});
// Trigger the calibration
$("#calibrate-btn").click(function(e){
console.log( "Calibrating..." );
$.ajax( {
type: "POST",
url: "/api/calibrate",
data: { id: $("#id1").val() },
success: function(result) { showSuccess('Calibration of device was completed successfully.'); getConfig(); },
error: function(result) { showError('Unable to calibrate device.'); }
} );
});
function updateSleepInfo() {
var i = $("#sleep-interval").val()
$("#sleep-interval-info").text( Math.floor(i/60) + " m " + (i%60) + " s" )
}
// Trigger the calibration
$("#sleep-interval").keyup(updateSleepInfo);
function setButtonDisabled( b ) {
$("#device-btn").prop("disabled", b);
$("#calibrate-btn").prop("disabled", b);
$("#push-btn").prop("disabled", b);
$("#gravity-btn").prop("disabled", b);
$("#hardware-btn").prop("disabled", b);
}
// Get the configuration values from the API
function getConfig() {
setButtonDisabled( true );
var url = "/api/config";
//var url = "/test/config.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#id1").val(cfg["id"]);
$("#id2").val(cfg["id"]);
$("#id3").val(cfg["id"]);
$("#id4").val(cfg["id"]);
$("#mdns").val(cfg["mdns"]);
if( cfg["temp-format"] == "C" )
$("#temp-format-c").click();
else
$("#temp-format-f").click();
$("#ota-url").val(cfg["ota-url"]);
$("#http-push").val(cfg["http-push"]);
$("#http-push2").val(cfg["http-push2"]);
$("#brewfather-push").val(cfg["brewfather-push"]);
$("#influxdb2-push").val(cfg["influxdb2-push"]);
$("#influxdb2-org").val(cfg["influxdb2-org"]);
$("#influxdb2-bucket").val(cfg["influxdb2-bucket"]);
$("#influxdb2-auth").val(cfg["influxdb2-auth"]);
$("#sleep-interval").val(cfg["sleep-interval"]);
$("#voltage-factor").val(cfg["voltage-factor"]);
$("#gravity-formula").val(cfg["gravity-formula"]);
$("#temp-adjustment-value").val(cfg["temp-adjustment-value"]);
$("#gravity-temp-adjustment").prop( "checked", cfg["gravity-temp-adjustment"] );
$("#gyro-calibration-data").text( cfg["gyro-calibration-data"]["ax"] + "," + cfg["gyro-calibration-data"]["ay"] + "," + cfg["gyro-calibration-data"]["az"] + "," + cfg["gyro-calibration-data"]["gx"] + "," + cfg["gyro-calibration-data"]["gy"] + "," + cfg["gyro-calibration-data"]["gz"] );
$("#battery").text(cfg["battery"] + " V");
$("#angle").text(cfg["angle"]);
$("#gravity").text(cfg["gravity"] + " SG");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
updateSleepInfo();
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,117 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about.htm">About</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-4">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</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.4.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 Magnus Persson</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.4.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

View File

@ -1,153 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/device.htm">Device</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about.htm">About</a>
</li>
</ul>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-4">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</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="" id="id" hidden></div>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">Gravity:</div>
<div class="col-md-4 themed-grid-col bg-light" id="gravity">Loading...</div>
</div>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">Temperature:</div>
<div class="col-md-4 themed-grid-col bg-light" id="temp">Loading...</div>
</div>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">Angle/Tilt:</div>
<div class="col-md-4 themed-grid-col bg-light" id="angle">Loading...</div>
</div>
<div class="row mb-3">
<div class="col-md-8 themed-grid-col bg-light">Battery:</div>
<div class="col-md-4 themed-grid-col bg-light" id="battery">Loading...</div>
</div>
<div class="row mb-3">
<div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
<label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label>
</div>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
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 Magnus Persson</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item active"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="" id="id" hidden></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Gravity:</div><div class="col-md-4 themed-grid-col bg-light" id="gravity">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Temperature:</div><div class="col-md-4 themed-grid-col bg-light" id="temp">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Angle/Tilt:</div><div class="col-md-4 themed-grid-col bg-light" id="angle">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Battery:</div><div class="col-md-4 themed-grid-col bg-light" id="battery">Loading...</div></div><div class="row mb-3"><div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox"><input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled> <label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label></div></div><hr class="my-4"></div><script type="text/javascript">function getStatus(){var e="/api/status";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),$("#id").text(e.id),$("#angle").text(e.angle),$("#gravity").text(e.gravity+" SG"),$("#battery").text(e.battery+" V"),"C"==e["temp-format"]?$("#temp").text(e["temp-c"]+" C"):$("#temp").text(e["temp-f"]+" F"),e["sleep-mode"]?$("#sleep-mode").attr("checked",!0):$("#sleep-mode").attr("checked",!1),$("#sleep-mode").removeAttr("disabled")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}function start(){setInterval(getStatus,3e3)}window.onload=start,$("#sleep-mode").click(function(e){console.log("Blocking sleep mode = "+$("#sleep-mode").is(":checked")),$.ajax({type:"POST",url:"/api/status/sleepmode",data:{id:$("#id").text(),"sleep-mode":$("#sleep-mode").is(":checked")},success:function(e){},error:function(e){showError("Could not update sleep mode for device.")}})})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

View File

@ -1,146 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Beer Gravity Monitor</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script>
</head>
<body class="py-4">
<!-- START MENU -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container">
<hr class="my-4">
<div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert">
<div id="alert-msg">...</div>
<button type="button" id="alert-btn" class="close" aria-label="Close">
<span aria-hidden="true">&times;</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">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work.
You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download
the files if they are found in the same location as the firmware.bin. This page is a fallback option.
</div>
<div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">index.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">device.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">config.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div>
</div>
<div class="row mb-3">
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
</div>
<div class="row mb-3">
<form action="/api/upload" method="post" enctype="multipart/form-data">
<div class="col-md-8 custom-file">
<input type="file" class="custom-file-input" name="name" id="name">
<label class="custom-file-label" for="name">Choose file</label>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button>
</form>
</div>
<hr class="my-4">
</div>
<script type="text/javascript">
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["about"] )
$("#about").text("Completed.");
else
$("#about").text("File is missing.");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
</script>
<!-- START FOOTER -->
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work. You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download the files if they are found in the same location as the firmware.bin. This page is a fallback option.</div><div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">index.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">device.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">config.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">about.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div></div><div class="row mb-3"><form action="/api/upload" method="post" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" class="custom-file-input" name="name" id="name"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button></form></div><hr class="my-4"></div><script type="text/javascript">function getUpload(){var e="/api/upload";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),e.index?$("#index").text("Completed."):$("#index").text("File is missing."),e.device?$("#device").text("Completed."):$("#device").text("File is missing."),e.config?$("#config").text("Completed."):$("#config").text("File is missing."),e.about?$("#about").text("Completed."):$("#about").text("File is missing.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getUpload,$(".custom-file-input").on("change",function(){var e=$(this).val().split("\\").pop();$(this).siblings(".custom-file-label").addClass("selected").html(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021 Magnus Persson</div></body></html>

121
html/about.htm Normal file
View File

@ -0,0 +1,121 @@
<!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">
<!-- START MENU -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/calibration.htm">Calibration</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#"><b>About</b></a>
</li>
</ul>
</div>
</div>
</nav>
<!-- START BODY -->
<div class="container row-margin-10">
<div class="accordion row-margin-10" id="accordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingAbout">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout">
<b>About</b>
</button>
</h2>
<div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion">
<div class="accordion-body">
<div class="row h3 col-sm-8">
Beer Gravity Monitor
</div>
<div class="row col-sm-8 mb-3">
This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.
</div>
<div class="row h3 col-sm-8 mb-3">
MIT License
</div>
<div class="row col-sm-8 mb-3">
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</div>
<div class="row h3 col-sm-8 mb-3">
Credits to
</div>
<div class="row col-sm-8 mb-3">
This software uses
the following libraries and without these this software would have been much more difficult to acheive:<br><br>
<ul>
<li>https://github.com/jrowberg/i2cdevlib</li>
<li>https://github.com/codeplea/tinyexpr</li>
<li>https://github.com/graphitemaster/incbin</li>
<li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li>
<li>https://github.com/khoih-prog/ESP_WiFiManager</li>
<li>https://github.com/thijse/Arduino-Log</li>
<li>https://github.com/bblanchon/ArduinoJson</li>
<li>https://github.com/PaulStoffregen/OneWire</li>
<li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li>
<li>https://github.com/Rotario</li>
<li>https://github.com/256dpi/arduino-mqtt</li>
<li>https://graphjs.com</li>
<li>https://getbootstrap.com</li>
<li>https://github.com/lorol/LITTLEFS</li>
<li>https://github.com/h2zero/NimBLE-Arduino</li>
<li>https://github.com/spouliot/tilt-sim</li>
</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>

1
html/about.min.htm Normal file
View File

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item active"><a class="nav-link" href="#"><b>About</b></a></li></ul></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</div><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>

429
html/calibration.htm Normal file
View File

@ -0,0 +1,429 @@
<!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>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config.htm">Configuration</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#"><b>Calibration</b></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 BODY -->
<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="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 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 class="row mb-3">
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity. These values
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
formula and if the deviation is more than 1.5SG / 0.38P on any of the provided points then the forumla will be
rejected. On the bottom of the page you can see a graph over the entered values + values calcualated by the formula.
</div>
<div class="row">
<label class="col-sm-1 col-form-label">#</label>
<label class="col-sm-4 col-form-label">Angle/Tilt</label>
<label class="col-sm-4 col-form-label" id="gravity-header">Gravity</label>
</div>
<div class="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="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="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="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="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="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="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 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>
</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>
<script type="text/javascript">
var chartDataForm = [];
var chartDataCalc = [];
const dataSetChart = {
datasets: [{
label: 'Raw data',
borderColor: 'blue',
backgroundColor: 'blue',
data: chartDataForm
}, {
label: 'Calculated',
borderColor: 'green',
backgroundColor: 'green',
data: chartDataCalc
}]
}
const configChart = {
type: 'line',
data: dataSetChart,
options: {
responsive: true,
interaction: {
intersect: false,
},
scales: {
x: {
display: true,
type: 'linear',
grace: '5%',
title: {
display: true,
text: 'Angle/Tilt'
},
ticks: {
crossAlign: 'far'
},
suggestedMin: 25
},
y: {
display: true,
title: {
display: true,
text: 'Gravity'
},
suggestedMin: 1.000
}
}
}
};
var myChart = 0;
</script>
<script type="text/javascript">
g1.onchange = setGravityDecimal
g2.onchange = setGravityDecimal
g3.onchange = setGravityDecimal
g4.onchange = setGravityDecimal
g5.onchange = setGravityDecimal
g6.onchange = setGravityDecimal
g7.onchange = setGravityDecimal
g8.onchange = setGravityDecimal
g9.onchange = setGravityDecimal
g10.onchange = setGravityDecimal
a1.onchange = setAngleDecimal
a2.onchange = setAngleDecimal
a3.onchange = setAngleDecimal
a4.onchange = setAngleDecimal
a5.onchange = setAngleDecimal
a6.onchange = setAngleDecimal
a7.onchange = setAngleDecimal
a8.onchange = setAngleDecimal
a9.onchange = setAngleDecimal
a10.onchange = setAngleDecimal
window.onload = getConfig;
setButtonDisabled( true );
function convertToPlato(sg) {
return 259-(259/sg);
}
function convertToSG(plato) {
return 259/(259-plato);
}
function setAngleDecimal(event) {
this.value = parseFloat(this.value).toFixed(2);
populateChart();
}
function setGravityDecimal(event) {
if(isPlato())
this.value = parseFloat(this.value).toFixed(1);
else
this.value = parseFloat(this.value).toFixed(4);
populateChart();
}
function populateChartForm(a, g) {
if( a != 0)
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
chartDataForm.sort(function (a, b) {
return a.x - b.x;
});
}
function populateChartCalc(a, g) {
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
}
function isPlato() {
return $("#gravity-format").text() == "P";
}
function populateChart() {
chartDataCalc.length = 0
for( i = 25.0; i<80.0; i+=5.0) {
var formula = $("#formula").text();
var angle = i.toString();
formula=formula.replaceAll( "tilt^3", angle+"*"+angle+"*"+angle );
formula=formula.replaceAll( "tilt^2", angle+"*"+angle );
formula=formula.replaceAll( "tilt", angle );
var g = eval( formula );
if(isPlato())
g = convertToPlato(g);
populateChartCalc( i, g );
}
chartDataForm.length = 0
populateChartForm( $("#a1").val(), $("#g1").val() );
populateChartForm( $("#a2").val(), $("#g2").val() );
populateChartForm( $("#a3").val(), $("#g3").val() );
populateChartForm( $("#a4").val(), $("#g4").val() );
populateChartForm( $("#a5").val(), $("#g5").val() );
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>

52
html/calibration.min.htm Normal file

File diff suppressed because one or more lines are too long

914
html/config.htm Normal file
View File

@ -0,0 +1,914 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/index.htm">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="#"><b>Configuration</b></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>
</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-sleep">
<div>A sleep-interval of less than 300s will reduce battery life, consider using 900s</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-gyro">
<div>When using the gyro temperature use a sleep-interval that is greater than 300s for accurate readings</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 showWarningSleep() {
$('#warning-sleep').removeClass('d-none').addClass('show').removeClass('hide');
}
function hideWarningSleep() {
$('#warning-sleep').addClass('d-none').removeClass('show').addClass('hide');
}
function showWarningGyro() {
$('#warning-gyro').removeClass('d-none').addClass('show').removeClass('hide');
}
function hideWarningGyro() {
$('#warning-gyro').addClass('d-none').removeClass('show').addClass('hide');
}
</script>
<div class="accordion" id="accordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingDevice">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDevice" aria-expanded="true" aria-controls="collapseDevice">
<b>Device settings</b>
</button>
</h2>
<div id="collapseDevice" class="accordion-collapse collapse show" aria-labelledby="headingDevice" data-bs-parent="#accordion">
<div class="accordion-body">
<form action="/api/config/device" method="post">
<input type="text" name="id" id="id1" hidden>
<div class="row mb-3">
<label for="mdns" class="col-sm-2 col-form-label">Device name</label>
<div class="col-sm-3">
<input type="text" maxlength="63" class="form-control" name="mdns" id="mdns" placeholder="gravmon" data-bs-toggle="tooltip" title="Name of the device. Will be used for identifying the device when pushing data and on the local network.">
</div>
</div>
<div class="row mb-3">
<fieldset class="form-group row">
<legend class="col-form-label col-sm-2 float-sm-left pt-0">Temperature Format</legend>
<div class="col-sm-4">
<div class="form-check">
<input class="form-check-input" type="radio" name="temp-format" id="temp-format-c" value="C" checked data-bs-toggle="tooltip" title="Temperature format used with displaying data">
<label class="form-check-label" for="temp-format-c">Celsius</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="temp-format" id="temp-format-f" value="F" data-bs-toggle="tooltip" title="Temperature format used with displaying data">
<label class="form-check-label" for="temp-format-f">Fahrenheit</label>
</div>
</div>
</fieldset>
</div>
<div class="row mb-3">
<label for="sleep-interval" class="col-sm-2 col-form-label">Interval (seconds)</label>
<div class="col-sm-2">
<input type="number" min="10" max="3600" class="form-control" name="sleep-interval" id="sleep-interval" placeholder="300" data-bs-toggle="tooltip" title="The number of seconds that the device will sleep between gravity readings. Recommended value is 300s">
</div>
<label for="sleep-interval" class="col-sm-4 col-form-label" id="sleep-interval-info"></label>
</div>
<div class="row mb-3">
<div class="col-sm-4 offset-sm-2">
<button type="submit" class="btn btn-primary" id="device-btn" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
<hr class="my-2">
<div class="row mb-3">
<label for="calibrate-btn" class="col-sm-2 col-form-label">Current calibration</label>
<label for="calibrate-btn" class="col-sm-2 col-form-label" id="gyro-calibration-data">Loading...</label>
<label for="gyro-calibration-data" class="col-sm-2 col-form-label" id="angle">Loading...</label>
<div class="col-sm-8 offset-sm-2">
<button type="button" class="btn btn-warning" id="calibrate-btn" data-bs-toggle="tooltip" title="Perform a calibration. The device should be flat on a surface (90 degrees) with the battery down">Calibrate device</button>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingPush">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePush" aria-expanded="false" aria-controls="collapsePush">
<b>Push settings</b>
</button>
</h2>
<div id="collapsePush" class="accordion-collapse collapse" aria-labelledby="headingPush" data-bs-parent="#accordion">
<div class="accordion-body">
<form action="/api/config/push" method="post">
<input type="text" name="id" id="id2" hidden>
<input type="text" name="section" value="collapsePush" hidden>
<input type="text" name="http-push-h1" id="http-push-h1" hidden>
<input type="text" name="http-push-h2" id="http-push-h2" hidden>
<input type="text" name="http-push2-h1" id="http-push2-h1" hidden>
<input type="text" name="http-push2-h2" id="http-push2-h2" hidden>
<div class="row mb-3">
<label for="http-push" class="col-sm-2 col-form-label">HTTP 1 (POST)</label>
<div class="col-sm-8">
<input type="url" maxlength="120" class="form-control" name="http-push" id="http-push" placeholder="http://www.internet.com/path" data-bs-toggle="tooltip" title="URL to push target, use format http://servername.com/resource (Supports http and https)">
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-secondary" data-field1="#http-push-h1" data-field2="#http-push-h2" data-bs-toggle="modal" data-bs-target="#modal-http" data-bs-toggle="tooltip" title="Edit the http headers for data format and authentication">Headers</button>
</div>
</div>
<div class="row mb-3">
<label for="http-push2" class="col-sm-2 col-form-label">HTTP 2 (POST)</label>
<div class="col-sm-8">
<input type="url" maxlength="120" class="form-control" name="http-push2" id="http-push2" placeholder="http://www.internet.com/path" data-bs-toggle="tooltip" title="URL to push target, use format http://servername.com/resource (Supports http and https)">
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-secondary" data-field1="#http-push2-h1" data-field2="#http-push2-h2" data-bs-toggle="modal" data-bs-target="#modal-http" data-bs-toggle="tooltip" title="Edit the http headers for data format and authentication">Headers</button>
</div>
</div>
<div class="row mb-3">
<label for="token" class="col-sm-2 col-form-label">Token</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="token" id="token" data-bs-toggle="tooltip" title="Token can be used in the format template as a variable, some services use this for authentication">
</div>
</div>
<div class="row mb-3">
<label for="http-push3" class="col-sm-2 col-form-label">HTTP 3 (GET)</label>
<div class="col-sm-8">
<input type="url" maxlength="120" class="form-control" name="http-push3" id="http-push3" placeholder="http://www.internet.com/path" data-bs-toggle="tooltip" title="URL to push target, use format http://servername.com/resource (Supports http and https). Do not add the query string, that will be added via the format template">
</div>
</div>
<div class="row mb-3">
<label for="token2" class="col-sm-2 col-form-label">Token 2</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="token2" id="token2" data-bs-toggle="tooltip" title="Token can be used in the format template as a variable, some services use this for authentication">
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button type="submit" class="btn btn-primary" id="push-btn" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-secondary" id="format-btn" data-bs-toggle="tooltip" title="Open up the format editor to change data format posted">Format editor</button>
<button class="btn btn-secondary" id="test-btn" data-bs-toggle="tooltip" title="Test posting data to defined push targets">Test Push</button>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingPush2">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePush2" aria-expanded="false" aria-controls="collapsePush2">
<b>Push settings (2)</b>
</button>
</h2>
<div id="collapsePush2" class="accordion-collapse collapse" aria-labelledby="headingPush2" data-bs-parent="#accordion">
<div class="accordion-body">
<form action="/api/config/push" method="post">
<input type="text" name="id" id="id5" hidden>
<input type="text" name="section" value="collapsePush2" hidden>
<div class="row mb-3">
<label for="influxdb2-push" class="col-sm-2 col-form-label">InfluxDB v2 URL</label>
<div class="col-sm-8">
<input type="url" maxlength="40" class="form-control" name="influxdb2-push" placeholder="http://www.internet.com" id="influxdb2-push" data-bs-toggle="tooltip" title="URL to push target, use format http://servername.com (Supports http and https)">
</div>
</div>
<div class="row mb-3">
<label for="influxdb2-org" class="col-sm-2 col-form-label">InfluxDB v2 Org</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="influxdb2-org" id="influxdb2-org" data-bs-toggle="tooltip" title="Identifier to what organisation to use">
</div>
</div>
<div class="row mb-3">
<label for="influxdb2-bucket" class="col-sm-2 col-form-label">InfluxDB v2 Bucket</label>
<div class="col-sm-4">
<input type="text" maxlength="50" class="form-control" name="influxdb2-bucket" id="influxdb2-bucket" data-bs-toggle="tooltip" title="Identifier for the data bucket to use">
</div>
</div>
<div class="row mb-3">
<label for="influxdb2-auth" class="col-sm-2 col-form-label">InfluxDB v2 Auth</label>
<div class="col-sm-4">
<input type="text" maxlength="100" class="form-control" name="influxdb2-auth" id="influxdb2-auth" data-bs-toggle="tooltip" title="Authentication token for accessing data bucket">
</div>
</div>
<hr class="my-2">
<div class="row mb-3">
<label for="mqtt-push" class="col-sm-2 col-form-label">MQTT Server</label>
<div class="col-sm-4">
<input type="text" maxlength="40" class="form-control" name="mqtt-push" id="mqtt-push" placeholder="www.internet.com" data-bs-toggle="tooltip" title="Name of server to connect to, use format servername.com">
</div>
</div>
<div class="row mb-3">
<label for="mqtt-topic" class="col-sm-2 col-form-label">MQTT Port</label>
<div class="col-sm-4">
<input type="number" min="1" max="65535" class="form-control" name="mqtt-port" id="mqtt-port" placeholder="1883" data-bs-toggle="tooltip" title="Port number to use, 1883 is standard. Ports higher than 8000 will assume to use SSL">
</div>
</div>
<div class="row mb-3">
<label for="mqtt-user" class="col-sm-2 col-form-label">MQTT User</label>
<div class="col-sm-4">
<input type="text" maxlength="20" class="form-control" name="mqtt-user" id="mqtt-user" data-bs-toggle="tooltip" title="Username to use. Leave blank if authentication is disabled">
</div>
</div>
<div class="row mb-3">
<label for="mqtt-pass" class="col-sm-2 col-form-label">MQTT Password</label>
<div class="col-sm-4">
<input type="text" maxlength="20" class="form-control" name="mqtt-pass" id="mqtt-pass" data-bs-toggle="tooltip" title="Password to use. Leave blank if authentication is disabled">
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button type="submit" class="btn btn-primary" id="push-btn2" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-secondary" id="format-btn2" data-bs-toggle="tooltip" title="Open up the format editor to change data format posted">Format editor</button>
<button class="btn btn-secondary" id="test-btn2" data-bs-toggle="tooltip" title="Test posting data to defined push targets">Test Push</button>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingGravity">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseGravity" aria-expanded="false" aria-controls="collapseGravity">
<b>Gravity Settings</b>
</button>
</h2>
<div id="collapseGravity" class="accordion-collapse collapse" aria-labelledby="headingGravity" data-bs-parent="#accordion">
<div class="accordion-body">
<form action="/api/config/gravity" method="post">
<input type="text" name="id" id="id3" hidden>
<div class="row mb-3">
<fieldset class="form-group row">
<label for="gravity-format" class="col-sm-2 col-form-label">Gravity Format</label>
<div class="col-sm-4">
<div class="form-check">
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-g" value="G" checked data-bs-toggle="tooltip" title="Present gravity in SG format">
<label class="form-check-label" for="gravity-format-g">SG</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gravity-format" id="gravity-format-p" value="P" checked data-bs-toggle="tooltip" title="Present gravity in Plato format">
<label class="form-check-label" for="gravity-format-p">Plato</label>
</div>
</div>
</fieldset>
</div>
<div class="row mb-3">
<label for="gravity-formula" class="col-sm-2 col-form-label">Formula</label>
<div class="col-sm-10">
<input type="text" maxlength="200" class="form-control" name="gravity-formula" id="gravity-formula" placeholder="0.0*tilt^2+0.01*tilt+0.2" checked data-bs-toggle="tooltip" title="Formula used to convert angle to gravity">
</div>
</div>
<div class="row mb-3">
<div class="col-sm-3 offset-sm-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="gravity-temp-adjustment" id="gravity-temp-adjustment" checked data-bs-toggle="tooltip" title="Adjust the calculated gravity based on the current temperature. Assumes that calibration is done using 20C / 68F">
<label class="form-check-label" for="gravity-temp-adjustment">Temperature adjust gravity</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4 offset-sm-2">
<button type="submit" class="btn btn-primary" id="gravity-btn" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingHardware">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseHardware" aria-expanded="false" aria-controls="collapseHardware">
<b>Hardware Settings</b>
</button>
</h2>
<div id="collapseHardware" class="accordion-collapse collapse" aria-labelledby="headingHardware" data-bs-parent="#accordion">
<div class="accordion-body">
<form action="/api/config/hardware" method="post">
<input type="text" name="id" id="id4" hidden>
<div class="row mb-3">
<label for="voltage-factor" class="col-sm-2 col-form-label">Voltage factor</label>
<div class="col-sm-2">
<input type="number" step=".01" class="form-control" name="voltage-factor" id="voltage-factor" placeholder="1.59" data-bs-toggle="tooltip" title="Factor used to calculate the battery voltage. Can vary depending on the R2 value">
</div>
<label for="voltage-factor" class="col-sm-3 col-form-label" id="battery">Loading...</label>
</div>
<div class="row mb-3">
<label for="voltage-config" class="col-sm-2 col-form-label">Config voltage</label>
<div class="col-sm-2">
<input type="number" step=".01" min="3.00" max="6.00" class="form-control" name="voltage-config" id="voltage-config" placeholder="4.16" data-bs-toggle="tooltip" title="Over this level the device will always go into configuration mode, some batteries might have a higher voltage when fully charged">
</div>
</div>
<div class="row mb-3">
<label for="temp-adjustment-value" class="col-sm-2 col-form-label">Temp Sensor Adj</label>
<div class="col-sm-2">
<input type="number" step=".1" class="form-control" name="temp-adjustment-value" id="temp-adjustment-value" placeholder="0" data-bs-toggle="tooltip" title="This value will be added to the sensor value in case the sensor dont show the correct temperature">
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4 offset-sm-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="gyro-temp" id="gyro-temp" data-bs-toggle="tooltip" title="Use the temperature sensor in the gyro instead of DS18B20, require 300s update interval to be accurate">
<label class="form-check-label" for="gyro-temp">Use gyro temperature</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4 offset-sm-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="storage-sleep" id="storage-sleep" data-bs-toggle="tooltip" title="If enabled and the device is placed on its cap (less than 5 degress) it will go into sleep for 2000 minutes">
<label class="form-check-label" for="storage-sleep">Enable storage mode when placed on cap</label>
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 col-form-label" for="ble">Bluetooth tilt color:</label>
<div class="col-sm-2">
<select class="form-select" id="ble" name="ble" disabled data-bs-toggle="tooltip" title="Select the BLE token to be used when sending data over bluetooth">
<option value="">-not active-</option>
<option value="red">red</option>
<option value="green">green</option>
<option value="black">black</option>
<option value="purple">purple</option>
<option value="orange">orange</option>
<option value="blue">blue</option>
<option value="yellow">yellow</option>
<option value="pink">pink</option>
</select>
</div>
</div>
<div class="row mb-3">
<label for="ota-url" class="col-sm-2 col-form-label">OTA base URL</label>
<div class="col-sm-8">
<input type="url" maxlength="90" class="form-control" name="ota-url" id="ota-url" placeholder="http://www.local.com/path/" data-bs-toggle="tooltip" title="Base URL to where firmware and version.json file can be found. Needs to end with '/', example: http://www.mysite.com/firmware/">
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button type="submit" class="btn btn-primary" id="hardware-btn" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-2">
<button class="btn btn-secondary" id="firmware-btn" checked data-bs-toggle="tooltip" title="Manually upload a new firmware version to the device">Upload firmware</button>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingAdvanced">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAdvanced" aria-expanded="false" aria-controls="collapseAdvanced">
<b>Advanced Software Settings (use with caution)</b>
</button>
</h2>
<div id="collapseAdvanced" class="accordion-collapse collapse" aria-labelledby="headingAdvanced" data-bs-parent="#accordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-sm-4">
<div class="form-check">
<input checked class="form-check-input" type="checkbox" name="adv-config" id="adv-config" data-bs-toggle="tooltip" title="Enable the advanced software settings.">
<label class="form-check-label" for="adv-config">Disable advanced software settings</label>
</div>
</div>
</div>
<form action="/api/config/advanced" method="post">
<input type="text" name="id" id="id6" hidden>
<div class="row mb-3">
<label for="gyro-read-count" class="col-sm-3 col-form-label">Gyro reads:</label>
<div class="col-sm-3">
<input disabled type="number" min="10" max="100" class="form-control" name="gyro-read-count" id="gyro-read-count" placeholder="50" checked data-bs-toggle="tooltip" title="How many times should we read the gyro to get an accurate angle. More reads = better accuracy but higher battery drain">
</div>
<div class="col-sm-5">(10-100) - default 50</div>
</div>
<div class="row mb-3">
<label for="gyro-moving-threashold" class="col-sm-3 col-form-label">Gyro moving theashold</label>
<div class="col-sm-3">
<input disabled type="number" min="50" max="1000" class="form-control" name="gyro-moving-threashold" id="gyro-moving-threashold" placeholder="500" checked data-bs-toggle="tooltip" title="How much deviation between gyro reads are acceptable in order to regard this as a valid angle">
</div>
<div class="col-sm-5">(50-1000) - default 500</div>
</div>
<div class="row mb-3">
<label for="formula-max-deviation" class="col-sm-3 col-form-label">Formula max deviation (SG)</label>
<div class="col-sm-3">
<input disabled type="number" step=".1" min="1" max="10" class="form-control" name="formula-max-deviation" id="formula-max-deviation" placeholder="3" checked data-bs-toggle="tooltip" title="When validating the derived formula this is the maximum accepted deviation for the supplied values">
</div>
<div class="col-sm-5">(1 - 10) - default 3 SG</div>
</div>
<div class="row mb-3">
<div class="col-sm-3 offset-sm-3">
<div class="form-check">
<input disabled class="form-check-input" type="checkbox" class="form-control" name="ignore-low-angles" id="ignore-low-angles" data-bs-toggle="tooltip" title="When active, angles below water will be ignored. Note! Angle must be defined under calibration, first field.">
<label class="form-check-label" for="ignore-low-angles">Ignore angles below water</label>
</div>
</div>
<div class="col-sm-5" name="water-angle" id="water-angle"></div>
</div>
<div class="row mb-3">
<label for="formula-calibration-temp" class="col-sm-3 col-form-label">Gravity calibration temp</label>
<div class="col-sm-3">
<input disabled type="number" step=".01" min="0" max="100" class="form-control" name="formula-calibration-temp" id="formula-calibration-temp" placeholder="20" checked data-bs-toggle="tooltip" title="Calibration temperature, used in temperatur correction formula, default 20C/68F">
</div>
<div class="col-sm-5">(0 - 100) - default 20C/68F</div>
</div>
<hr>
<div class="row mb-3">
<label for="tempsensor-resolution" class="col-sm-3 col-form-label">DS18B20 resolution (bits)</label>
<div class="col-sm-3">
<input disabled type="number" min="9" max="12" class="form-control" name="tempsensor-resolution" id="tempsensor-resolution" placeholder="9" checked data-bs-toggle="tooltip" title="Resolution when reading the DS18B20 temperature sensor, higher resolution give better accuracy but takes longer">
</div>
<div class="col-sm-5">(9 - 12) - default 9 bits</div>
</div>
<hr>
<div class="row mb-3">
<label for="wifi-connect-timeout" class="col-sm-3 col-form-label">Wifi connect timeout (s)</label>
<div class="col-sm-3">
<input disabled type="number" min="1" max="60" class="form-control" name="wifi-connect-timeout" id="wifi-connect-timeout" placeholder="20" checked data-bs-toggle="tooltip" title="Max time waiting for a wifi connection before going back to sleep">
</div>
<div class="col-sm-5">(1 - 60) - default 20 s</div>
</div>
<div class="row mb-3">
<label for="wifi-portal-timeout" class="col-sm-3 col-form-label">Wifi portal timeout (s)</label>
<div class="col-sm-3">
<input disabled type="number" min="10" max="240" class="form-control" name="wifi-portal-timeout" id="wifi-portal-timeout" placeholder="120" checked data-bs-toggle="tooltip" title="Max time the wifi portal is active before existing">
</div>
<div class="col-sm-5">(10 - 240) - default 120 s</div>
</div>
<hr>
<div class="row mb-3">
<label for="int-http1" class="col-sm-3 col-form-label">Skip interval - HTTP 1 (POST):</label>
<div class="col-sm-3">
<input disabled type="number" min="0" max="5" class="form-control" name="int-http1" id="int-http1" placeholder="0" checked data-bs-toggle="tooltip" title="Defines how many sleep cycles to skip between pushing data to this target, 1 = every second cycle">
</div>
<div class="col-sm-5">(0 - 5) - default 0</div>
</div>
<div class="row mb-3">
<label for="int-http2" class="col-sm-3 col-form-label">Skip interval - HTTP 2 (POST)</label>
<div class="col-sm-3">
<input disabled type="number" min="0" max="5" class="form-control" name="int-http2" id="int-http2" placeholder="0" data-bs-toggle="tooltip" title="Defines how many sleep cycles to skip between pushing data to this target, 1 = every second cycle">
</div>
<div class="col-sm-5">(0 - 5) - default 0</div>
</div>
<div class="row mb-3">
<label for="int-http3" class="col-sm-3 col-form-label">Skip interval - HTTP 3 (GET)</label>
<div class="col-sm-3">
<input disabled type="number" min="0" max="5" class="form-control" name="int-http3" id="int-http3" placeholder="0" data-bs-toggle="tooltip" title="Defines how many sleep cycles to skip between pushing data to this target, 1 = every second cycle">
</div>
<div class="col-sm-5">(0 - 5) - default 0</div>
</div>
<div class="row mb-3">
<label for="int-influx" class="col-sm-3 col-form-label">Skip interval - InfluxDB v2</label>
<div class="col-sm-3">
<input disabled type="number" min="0" max="5" class="form-control" name="int-influx" id="int-influx" placeholder="0" data-bs-toggle="tooltip" title="Defines how many sleep cycles to skip between pushing data to this target, 1 = every second cycle">
</div>
<div class="col-sm-5">(0 - 5) - default 0</div>
</div>
<div class="row mb-3">
<label for="int-mqtt" class="col-sm-3 col-form-label">Skip interval - MQTT</label>
<div class="col-sm-3">
<input disabled type="number" min="0" max="5" class="form-control" name="int-mqtt" id="int-mqtt" placeholder="0" data-bs-toggle="tooltip" title="Defines how many sleep cycles to skip between pushing data to this target, 1 = every second cycle">
</div>
<div class="col-sm-5">(0 - 5) - default 0</div>
</div>
<div class="row mb-3">
<div class="col-sm-8 offset-sm-3">
<button type="submit" class="btn btn-primary" id="advanced-btn" data-bs-toggle="tooltip" title="Save changes in this section">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modal-http" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="modal-header" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modal-header">Define HTTP headers</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<label for="http-header" class="col-form-label">Header 1 (Header: value)</label>
<input type="text" maxlength="100" class="form-control" id="header1" oninput="checkHeader(this)" placeholder="Content-Type: application/json" data-bs-toggle="tooltip" title="Set a http headers, empty string is skipped">
<label for="http-header" class="col-form-label">Header 2 (Header: value)</label>
<input type="text" maxlength="100" class="form-control" id="header2" oninput="checkHeader(this)" data-bs-toggle="tooltip" title="Set a http headers, empty string is skipped">
<input type="text" id="field1" hidden>
<input type="text" id="field2" hidden>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" data-bs-toggle="tooltip" title="Close dialog, press the save button in the section to save data">Close</button>
</div>
</div>
</div>
</div>
<script>
$('#modal-http').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget)
var field1 = button.data('field1')
var field2 = button.data('field2')
var modal = $(this)
modal.find('.modal-body #header1').val($(field1).val())
modal.find('.modal-body #header2').val($(field2).val())
modal.find('.modal-body #field1').val(field1)
modal.find('.modal-body #field2').val(field2)
})
$('#modal-http').on('hide.bs.modal', function (event) {
console.log("2")
var modal = $(this)
field1 = modal.find('.modal-body #field1').val()
field2 = modal.find('.modal-body #field2').val()
$(field1).val(modal.find('.modal-body #header1').val())
$(field2).val(modal.find('.modal-body #header2').val())
})
function checkHeader(input) {
console.log( input.value );
if (input.value != "" && input.value.indexOf(":") == -1) {
$("#btn-close").prop("disabled", true);
$(input).removeClass("is-valid").addClass("is-invalid");
} else {
$("#btn-close").prop("disabled", false);
$(input).removeClass("is-invalid").addClass("is-valid");
}
}
</script>
<script type="text/javascript">
window.onload = getConfig;
setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseXXX)
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
$(location.hash + '.collapse').collapse('show');
}
});
// Trigger the calibration
$("#calibrate-btn").click(function(e){
console.log( "Calibrating..." );
$.ajax( {
type: "POST",
url: "/api/calibrate",
data: { id: $("#id1").val() },
success: function(result) { showSuccess('Calibration of device was completed successfully.'); getConfig(); },
error: function(result) { showError('Unable to calibrate device.'); }
} );
});
$("#firmware-btn").click(function(e){
window.location.href = "/firmware.htm";
});
// Open the format editor
$("#format-btn").click(function(e){
window.location.href = "/format.htm";
});
// Open the format editor
$("#format-btn2").click(function(e){
window.location.href = "/format.htm";
});
$("#test-btn").click(function(e){
window.location.href = "/test.htm";
});
$("#test-btn2").click(function(e){
window.location.href = "/test.htm";
});
function estimateBatteryLife(interval, rt) {
// ESP8266 consumes between 140-170mA when WIFI is on. Deep sleep is 20uA.
// MPU-6050 consumes 4mA
// DS18B20 consumes 1mA
// For this estimation we use an average of 160mA
var pwrActive = 170; // mA per hour
var pwrSleep = 15; // mA per day
var batt = 2000; // mA
if(rt<1) rt = 2;
// The deep sleep will consume approx 1mA per day.
var powerPerDay = (24*3600)/(interval+rt)*(rt/3600)*pwrActive + pwrSleep;
return batt/powerPerDay;
}
function updateSleepInfo() {
var i = parseInt($("#sleep-interval").val());
var rt = parseInt($("#runtime-average").val());
var j = 0;
if( rt>0 )
j = estimateBatteryLife(i, rt);
var t1 = Math.floor(i/60) + " m " + (i%60) + " s";
var t2 = Math.floor(j/7) + " weeks " + Math.floor(j%7) + " days";
if( j )
$("#sleep-interval-info").text( t1 + " - Estimated runtime: " + t2);
else
$("#sleep-interval-info").text( t1 );
hideWarningGyro();
if(i>0 && i<300) {
if( $("#gyro-temp").is(":checked") )
showWarningGyro();
showWarningSleep();
} else {
hideWarningSleep();
}
}
// Trigger the calibration and show warnings if needed
$("#sleep-interval").keyup(updateSleepInfo);
$("#gyro-temp").change(updateSleepInfo);
function setButtonDisabled( b ) {
$("#device-btn").prop("disabled", b);
$("#calibrate-btn").prop("disabled", b);
$("#push-btn").prop("disabled", b);
$("#format-btn").prop("disabled", b);
$("#test-btn").prop("disabled", b);
$("#gravity-btn").prop("disabled", b);
$("#hardware-btn").prop("disabled", b);
$("#push-btn2").prop("disabled", b);
$("#format-btn2").prop("disabled", b);
$("#test-btn2").prop("disabled", b);
$("#advanced-btn").prop("disabled", b);
checkAdvancedSection( b );
}
$("#adv-config").click(function(e){
checkAdvancedSection();
});
function checkAdvancedSection( b ) {
var b = $("#adv-config").is(":checked");
$("#advanced-btn").prop("disabled", b);
$("#gyro-read-count").prop("disabled", b);
$("#gyro-moving-threashold").prop("disabled", b);
$("#formula-max-deviation").prop("disabled", b);
$("#wifi-portal-timeout").prop("disabled", b);
$("#wifi-connect-timeout").prop("disabled", b);
$("#formula-calibration-temp").prop("disabled", b);
$("#int-http1").prop("disabled", b);
$("#int-http2").prop("disabled", b);
$("#int-http3").prop("disabled", b);
$("#int-influx").prop("disabled", b);
$("#int-mqtt").prop("disabled", b);
$("#tempsensor-resolution").prop("disabled", b);
$("#ignore-low-angles").prop("disabled", b);
}
// Get the advanced values from the API
function getAdvancedConfig() {
setButtonDisabled( true );
var url = "/api/config/advanced";
//var url = "/test/adv.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#gyro-read-count").val(cfg["gyro-read-count"]);
$("#gyro-moving-threashold").val(cfg["gyro-moving-threashold"]);
$("#formula-max-deviation").val(cfg["formula-max-deviation"]);
$("#wifi-portal-timeout").val(cfg["wifi-portal-timeout"]);
$("#wifi-connect-timeout").val(cfg["wifi-connect-timeout"]);
$("#tempsensor-resolution").val(cfg["tempsensor-resolution"]);
$("#ignore-low-angles").prop( "checked", cfg["ignore-low-angles"] );
$("#formula-calibration-temp").val(cfg["formula-calibration-temp"]);
$("#int-http1").val(cfg["int-http1"]);
$("#int-http2").val(cfg["int-http2"]);
$("#int-http3").val(cfg["int-http3"]);
$("#int-influx").val(cfg["int-influx"]);
$("#int-mqtt").val(cfg["int-mqtt"]);
if ( cfg["gyro-read-count"] != 50 || cfg["gyro-moving-threashold"] != 500 || cfg["formula-max-deviation"] != 3 || cfg["wifi-portal-timeout"] != 120 || cfg["wifi-connect-timeout"] != 20 || cfg["tempsensor-resolution"] != 9 ||
cfg["int-http1"] != 0 || cfg["int-http2"] != 0 || cfg["int-http3"] != 0 || cfg["int-influx"] != 0 || cfg["int-mqtt"] != 0 || cfg["ignore-low-angles"] != false || (cfg["formula-calibration-temp"] != 20 && cfg["formula-calibration-temp"] != 68)) {
$("#adv-config").attr("checked", false );
}
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
});
}
// Get the configuration values from the API
function getConfig() {
setButtonDisabled( true );
var url = "/api/config";
//var url = "/test/config.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
if(cfg["platform"]=="esp32") {
$('#ble').prop('disabled', false);
$("#ble").val(cfg["ble"]);
}
$("#id1").val(cfg["id"]);
$("#id2").val(cfg["id"]);
$("#id3").val(cfg["id"]);
$("#id4").val(cfg["id"]);
$("#id5").val(cfg["id"]);
$("#id6").val(cfg["id"]);
$("#mdns").val(cfg["mdns"]);
if( cfg["temp-format"] == "C" ) $("#temp-format-c").click();
else $("#temp-format-f").click();
if( cfg["gravity-format"] == "G" ) $("#gravity-format-g").click();
else $("#gravity-format-p").click();
$("#ota-url").val(cfg["ota-url"]);
$("#token").val(cfg["token"]);
$("#token2").val(cfg["token2"]);
$("#http-push").val(cfg["http-push"]);
$("#http-push-h1").val(cfg["http-push-h1"]);
$("#http-push-h2").val(cfg["http-push-h2"]);
$("#http-push2").val(cfg["http-push2"]);
$("#http-push2-h1").val(cfg["http-push2-h1"]);
$("#http-push2-h2").val(cfg["http-push2-h2"]);
$("#http-push3").val(cfg["http-push3"]);
$("#influxdb2-push").val(cfg["influxdb2-push"]);
$("#influxdb2-org").val(cfg["influxdb2-org"]);
$("#influxdb2-bucket").val(cfg["influxdb2-bucket"]);
$("#influxdb2-auth").val(cfg["influxdb2-auth"]);
$("#mqtt-push").val(cfg["mqtt-push"]);
$("#mqtt-port").val(cfg["mqtt-port"]);
$("#mqtt-user").val(cfg["mqtt-user"]);
$("#mqtt-pass").val(cfg["mqtt-pass"]);
$("#sleep-interval").val(cfg["sleep-interval"]);
$("#voltage-factor").val(cfg["voltage-factor"]);
$("#voltage-config").val(cfg["voltage-config"]);
$("#gravity-formula").val(cfg["gravity-formula"]);
$("#temp-adjustment-value").val(cfg["temp-adjustment-value"]);
$("#gravity-temp-adjustment").prop( "checked", cfg["gravity-temp-adjustment"] );
$("#gyro-temp").prop( "checked", cfg["gyro-temp"] );
$("#storage-sleep").prop( "checked", cfg["storage-sleep"] );
$("#gyro-calibration-data").text( cfg["gyro-calibration-data"]["ax"] + "," + cfg["gyro-calibration-data"]["ay"] + "," + cfg["gyro-calibration-data"]["az"] + "," + cfg["gyro-calibration-data"]["gx"] + "," + cfg["gyro-calibration-data"]["gy"] + "," + cfg["gyro-calibration-data"]["gz"] );
$("#battery").text(cfg["battery"] + " V");
$("#angle").text(cfg["angle"]);
$("#runtime-average").val(cfg["runtime-average"]);
$("#water-angle").text( "(Water angle: " + cfg["formula-calculation-data"]["a1"] + ") - default off");
//$("#gravity").text(cfg["gravity"] + " SG");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
//$('#spinner').hide();
//setButtonDisabled( false );
updateSleepInfo();
getAdvancedConfig();
});
}
</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/config.min.htm Normal file

File diff suppressed because one or more lines are too long

195
html/firmware.htm Normal file
View File

@ -0,0 +1,195 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container row-margin-10">
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
<div id="alert"></div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<script type="text/javascript">
function showError( 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.");
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
View 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><!-- START MENU --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="javascript:history.back()">Back to configuration</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container row-margin-10"><div class="alert alert-success alert-dismissible hide fade d-none" role="alert"><div id="alert"></div><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("hide").removeClass("show").addClass("d-none")})</script><div class="accordion" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingFirmware"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware"><b>Upload firmware</b></button></h2><div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion"><div class="accordion-body"><div class="row mb-3"><div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Current version:</div><div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Platform:</div><div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div></div><form id="uploadForm" enctype="multipart/form-data"><div class="row mb-3"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload"></div></div><div class="row mb-3"><div class="col-md-4"><button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button></div></div></form><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><script type="text/javascript">window.onload = getStatus;
$(document).ready(function() {
$("#uploadForm").on('submit', function(e) {
e.preventDefault();
$.ajax( {
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
progressHandler(evt);
}
}, false);
return xhr;
},
type: 'POST',
url: '/api/upload',
data: new FormData(this),
contentType: false,
cache: false,
processData:false,
beforeSend: function() {
setProgress(0);
},
error:function() {
showError("Upload failed");
},
success: function(resp) {
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
setTimeout(() => {
window.location = "/";
}, 10000);
}
});
});
});
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>

313
html/format.htm Normal file
View File

@ -0,0 +1,313 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container row-margin-10">
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
<div id="alert"></div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<script type="text/javascript">
function showError( 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="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">
</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%22temperature%22%2C%22unit_of_meas%22%3A%22dBm%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Frssi%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Ftilt%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_tilt%22%2C%22name%22%3A%22tilt%22%2C%22dev_cla%22%3A%22temperature%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Ftilt%22%7D%7C%0Ahomeassistant%2Fsensor%2Fgravmon_%24%7Bid%7D%2Fbattery%2Fconfig%3A%7B%22dev%22%3A%7B%22name%22%3A%22%24%7Bmdns%7D%22%2C%22mdl%22%3A%22gravmon%22%2C%22sw%22%3A%22%24%7Bapp-ver%7D%22%2C%22ids%22%3A%22%24%7Bid%7D%22%7D%2C%22uniq_id%22%3A%22%24%7Bid%7D_batt%22%2C%22name%22%3A%22battery%22%2C%22dev_cla%22%3A%22temperature%22%2C%22unit_of_meas%22%3A%22V%22%2C%22stat_t%22%3A%22gravmon%2F%24%7Bmdns%7D%2Fbattery%22%7D%7C%0A" },
{ "id": "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>
<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;
setButtonDisabled( true );
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
$(location.hash + '.collapse').collapse('show');
}
});
$("#push-target").change(function(e){
console.log(e)
selectFormat();
});
// 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) );
}
});
});
// Clear the selected template
$("#clear-btn").click(function(e) {
$("#format").val( "" );
});
// Store the format
$("#format-btn").click(function(e) {
var s = $("#format").val();
s = s.replaceAll("\n", "");
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
console.log(obj);
$.ajax( {
type: "POST",
url: "/api/config/format",
data: obj,
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
error: function(result) { showError('Unable to store format.'); }
} );
});
// Test the calibration
$("#test-btn").click(function(e) {
var url = "/api/status";
//var url = "/test/status.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
var doc = $("#format").val();
if (cfg["temp-format"]=="C")
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
else
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
if (cfg["gravity-format"]=="G") {
var sg = cfg["gravity"];
doc = doc.replaceAll("${gravity-sg}", sg);
doc = doc.replaceAll("${corr-gravity-sg}", sg);
var plato = 259 - (259 - sg);
doc = doc.replaceAll("${gravity-plato}", plato);
doc = doc.replaceAll("${corr-gravity-plato}", plato);
}
else {
var plato = cfg["gravity"];
doc = doc.replaceAll("${gravity-plato}", plato);
doc = doc.replaceAll("${corr-gravity-plato}", plato);
var sg = 259 / (259 - plato);
doc = doc.replaceAll("${gravity-sg}", sg);
doc = doc.replaceAll("${corr-gravity-sg}", sg);
}
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
doc = doc.replaceAll("${id}", cfg["id"]);
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
doc = doc.replaceAll("${token}", cfg["token"]);
doc = doc.replaceAll("${token2}", cfg["token2"]);
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
doc = doc.replaceAll("${battery}", cfg["battery"]);
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
doc = doc.replaceAll("${angle}", cfg["angle"]);
doc = doc.replaceAll("${tilt}", cfg["angle"]);
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);
}
function selectFormat() {
var s = "#" + $("#push-target").val()
console.log(s);
s = decodeURIComponent($(s).val());
console.log(s);
s = s.replaceAll("|", "|\n");
console.log(s);
$("#format").val(s);
$("#preview").text("");
}
// Get the configuration values from the API
function getConfig() {
setButtonDisabled( true );
var url = "/api/config/format";
//var url = "/test/format.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
$("#id").val(cfg["id"]);
$("#http-1").val(cfg["http-1"]);
$("#http-2").val(cfg["http-2"]);
$("#http-3").val(cfg["http-3"]);
$("#influxdb").val(cfg["influxdb"]);
$("#mqtt").val(cfg["mqtt"]);
selectFormat();
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
setButtonDisabled( false );
});
}
</script>
<!-- START FOOTER -->
<div class="container 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

File diff suppressed because one or more lines are too long

353
html/index.htm Normal file
View File

@ -0,0 +1,353 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="#"><b>Home</b></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>
</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 ) {
console.log("Error:" + msg);
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
$('#alert').text( msg );
}
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();
}, 5000);
function loadLog() {
$("#logContent").load("/log");
//$("#logContent").load("/test/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"></code></pre>
</div>
</div>
<div class="collapse row-margin-10" id="collapseSupport">
<div class="card card-body">
<pre><code class="card-text" id="supportContent"></code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
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==0) {
$("#angle").text("Gyro moving");
$("#gravity").text("Gyro moving");
} else {
$("#angle").text(cfg["angle"]);
if( cfg["gravity-format"] == "G")
$("#gravity").text(cfg["gravity"] + " SG");
else
$("#gravity").text(cfg["gravity"] + " °P");
}
var batt = cfg["battery"];
var charge = 0;
if(batt>4.15) charge = 100;
else if(batt>4.05) charge = 90;
else if(batt>3.97) charge = 80;
else if(batt>3.91) charge = 70;
else if(batt>3.86) charge = 60;
else if(batt>3.81) charge = 50;
else if(batt>3.78) charge = 40;
else if(batt>3.76) charge = 30;
else if(batt>3.73) charge = 20;
else if(batt>3.67) charge = 10;
else if(batt>3.44) charge = 5;
$("#battery").text(batt + " V (" + charge + "%)" );
if( cfg["temp-format"] == "C")
$("#temp").text(cfg["temp-c"] + " C");
else
$("#temp").text(cfg["temp-f"] + " F");
if( cfg["sleep-mode"] )
$("#sleep-mode").attr("checked", true );
else
$("#sleep-mode").attr("checked", false );
$("#sleep-mode").removeAttr("disabled");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
function start() {
setInterval(getStatus, 3000);
}
</script>
<!-- START FOOTER -->
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

1
html/index.min.htm Normal file

File diff suppressed because one or more lines are too long

229
html/test.htm Normal file
View File

@ -0,0 +1,229 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
</li>
</ul>
</div>
<div class="spinner-border text-light" id="spinner" role="status"></div>
</div>
</nav>
<!-- START MAIN INDEX -->
<div class="container row-margin-10">
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
<div id="alert"></div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<script type="text/javascript">
function showError( 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

File diff suppressed because one or more lines are too long

179
html/upload.htm Normal file
View File

@ -0,0 +1,179 @@
<!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>
<!-- START MENU -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="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="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">
window.onload = getUpload;
// Add the following code if you want the name of the file appear on select
$(".custom-file-input").on("change", function() {
var fileName = $(this).val().split("\\").pop();
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
});
function getUpload() {
var url = "/api/upload";
//var url = "/test/upload.json";
$('#spinner').show();
$.getJSON(url, function (cfg) {
console.log( cfg );
if( cfg["index"] )
$("#index").text("Completed.");
else
$("#index").text("File is missing.");
if( cfg["config"] )
$("#config").text("Completed.");
else
$("#config").text("File is missing.");
if( cfg["calibration"] )
$("#calibration").text("Completed.");
else
$("#calibration").text("File is missing.");
if( cfg["test"] )
$("#test").text("Completed.");
else
$("#test").text("File is missing.");
if( cfg["format"] )
$("#format").text("Completed.");
else
$("#format").text("File is missing.");
if( cfg["about"] )
$("#about").text("Completed.");
else
$("#about").text("File is missing.");
})
.fail(function () {
showError('Unable to get data from the device.');
})
.always(function() {
$('#spinner').hide();
});
}
</script>
<!-- START FOOTER -->
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
</body>
</html>

1
html/upload.min.htm Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

File diff suppressed because it is too large Load Diff

View 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

View File

@ -0,0 +1,399 @@
/****************************************************************************************************************************
ESP_DoubleResetDetector.h
For ESP8266 / ESP32 boards
ESP_DoubleResetDetector is a library for the ESP8266/Arduino platform
to enable trigger configure mode by resetting ESP32 / ESP8266 twice.
Forked from DataCute https://github.com/datacute/DoubleResetDetector
Built by Khoi Hoang https://github.com/khoih-prog/ESP_DoubleResetDetector
Licensed under MIT license
Version: 1.3.1
Version Modified By Date Comments
------- ----------- ---------- -----------
1.0.0 K Hoang 15/12/2019 Initial coding
1.0.1 K Hoang 30/12/2019 Now can use EEPROM or SPIFFS for both ESP8266 and ESP32. RTC still OK for ESP8266
1.0.2 K Hoang 10/04/2020 Fix bug by left-over cpp file and in example.
1.0.3 K Hoang 13/05/2020 Update to use LittleFS for ESP8266 core 2.7.1+
1.1.0 K Hoang 04/12/2020 Add support to LittleFS for ESP32 using LITTLEFS Library
1.1.1 K Hoang 28/12/2020 Suppress all possible compiler warnings
1.1.2 K Hoang 10/10/2021 Update `platform.ini` and `library.json`
1.2.0 K Hoang 26/11/2021 Auto detect ESP32 core and use either built-in LittleFS or LITTLEFS library
1.2.1 K Hoang 26/11/2021 Fix compile error for ESP32 core v1.0.5-
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
#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
#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
//#define ESP_DRD_USE_LITTLEFS false
//#define ESP_DRD_USE_SPIFFS false
//#define ESP8266_DRD_USE_RTC false //true
#ifdef ESP32
#if (!ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
#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
#endif
#endif
#endif
#ifdef ESP8266
#if (!ESP8266_DRD_USE_RTC && !ESP_DRD_USE_EEPROM && !ESP_DRD_USE_SPIFFS && !ESP_DRD_USE_LITTLEFS)
#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
#endif
#endif
#endif
//default to use EEPROM, otherwise, use LITTLEFS (higher priority), then SPIFFS
#if ESP_DRD_USE_EEPROM
#include <EEPROM.h>
#define FLAG_DATA_SIZE 4
#ifndef EEPROM_SIZE
#define EEPROM_SIZE 512
#endif
#ifndef EEPROM_START
#define EEPROM_START 256
#endif
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
#include <FS.h>
#ifdef ESP32
#if ESP_DRD_USE_LITTLEFS
// Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h
//#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2)
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) )
#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
#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
#define FileFS LITTLEFS
#define FS_Name "LittleFS"
#endif
#else
#include "SPIFFS.h"
// ESP32 core 1.0.4 still uses SPIFFS
#define FileFS SPIFFS
#endif
#else
// From ESP8266 core 2.7.1
#include <LittleFS.h>
#if ESP_DRD_USE_LITTLEFS
#define FileFS LittleFS
#else
#define FileFS SPIFFS
#endif
#endif // #if ESP_DRD_USE_EEPROM
#define DRD_FILENAME "/drd.dat"
#endif //#if ESP_DRD_USE_EEPROM
#define DOUBLERESETDETECTOR_FLAG_SET 0xD0D01234
#define DOUBLERESETDETECTOR_FLAG_CLEAR 0xD0D04321
class DoubleResetDetector
{
public:
DoubleResetDetector(int timeout, int address)
{
#if ESP_DRD_USE_EEPROM
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.printf("EEPROM size = %d, start = %d\n", EEPROM_SIZE, EEPROM_START);
#endif
EEPROM.begin(EEPROM_SIZE);
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
// LittleFS / SPIFFS code
if (!FileFS.begin())
{
#if (DOUBLERESETDETECTOR_DEBUG)
#if ESP_DRD_USE_LITTLEFS
Serial.println("LittleFS failed!. Please use SPIFFS or EEPROM.");
#else
Serial.println("SPIFFS failed!. Please use LittleFS or EEPROM.");
#endif
#endif
}
#else
#ifdef ESP8266
//RTC only for ESP8266
#endif
#endif
this->timeout = timeout * 1000;
this->address = address;
doubleResetDetected = false;
waitingForDoubleReset = false;
};
bool detectDoubleReset()
{
doubleResetDetected = detectRecentlyResetFlag();
if (doubleResetDetected)
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("doubleResetDetected");
#endif
clearRecentlyResetFlag();
}
else
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("No doubleResetDetected");
#endif
setRecentlyResetFlag();
waitingForDoubleReset = true;
}
return doubleResetDetected;
};
bool waitingForDRD()
{
return waitingForDoubleReset;
}
void loop()
{
if (waitingForDoubleReset && millis() > timeout)
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Stop doubleResetDetecting");
#endif
stop();
}
};
void stop()
{
clearRecentlyResetFlag();
waitingForDoubleReset = false;
};
bool doubleResetDetected;
private:
uint32_t DOUBLERESETDETECTOR_FLAG;
unsigned long timeout;
int address;
bool waitingForDoubleReset;
bool detectRecentlyResetFlag()
{
#if (ESP_DRD_USE_EEPROM)
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG;
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.printf("EEPROM Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
#endif
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
// LittleFS / SPIFFS code
if (FileFS.exists(DRD_FILENAME))
{
// if config file exists, load
File file = FileFS.open(DRD_FILENAME, "r");
if (!file)
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Loading config file failed");
#endif
}
file.readBytes((char *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG;
#if (DOUBLERESETDETECTOR_DEBUG)
#if ESP_DRD_USE_LITTLEFS
Serial.printf("LittleFS Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
#else
Serial.printf("SPIFFS Flag read = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
#endif
#endif
file.close();
}
#else
#ifdef ESP8266
//RTC only for ESP8266
ESP.rtcUserMemoryRead(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
#endif
#endif
doubleResetDetected = (doubleResetDetectorFlag == DOUBLERESETDETECTOR_FLAG_SET);
return doubleResetDetected;
};
void setRecentlyResetFlag()
{
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG_SET;
DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_SET;
#if (ESP_DRD_USE_EEPROM)
EEPROM.put(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
EEPROM.commit();
#if (DOUBLERESETDETECTOR_DEBUG)
delay(1000);
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
Serial.printf("SetFlag write = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
#endif
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
// LittleFS / SPIFFS code
File file = FileFS.open(DRD_FILENAME, "w");
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file...");
#endif
if (file)
{
file.write((uint8_t *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
file.close();
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file OK");
#endif
}
else
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file failed");
#endif
}
#else
#ifdef ESP8266
//RTC only for ESP8266
ESP.rtcUserMemoryWrite(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
#endif
#endif
};
void clearRecentlyResetFlag()
{
doubleResetDetectorFlag = DOUBLERESETDETECTOR_FLAG_CLEAR;
DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_CLEAR;
#if (ESP_DRD_USE_EEPROM)
//DOUBLERESETDETECTOR_FLAG = DOUBLERESETDETECTOR_FLAG_CLEAR;
EEPROM.put(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
EEPROM.commit();
#if (DOUBLERESETDETECTOR_DEBUG)
delay(1000);
EEPROM.get(EEPROM_START, DOUBLERESETDETECTOR_FLAG);
Serial.printf("ClearFlag write = 0x%X\n", DOUBLERESETDETECTOR_FLAG);
#endif
#elif ( ESP_DRD_USE_LITTLEFS || ESP_DRD_USE_SPIFFS )
// LittleFS / SPIFFS code
File file = FileFS.open(DRD_FILENAME, "w");
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file...");
#endif
if (file)
{
file.write((uint8_t *) &DOUBLERESETDETECTOR_FLAG, sizeof(DOUBLERESETDETECTOR_FLAG));
file.close();
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file OK");
#endif
}
else
{
#if (DOUBLERESETDETECTOR_DEBUG)
Serial.println("Saving config file failed");
#endif
}
#else
#ifdef ESP8266
//RTC only for ESP8266
ESP.rtcUserMemoryWrite(address, &doubleResetDetectorFlag, sizeof(doubleResetDetectorFlag));
#endif
#endif
};
uint32_t doubleResetDetectorFlag;
};
#endif // ESP_DoubleResetDetector_H

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,128 @@
/****************************************************************************************************************************
ESP_WiFiManager_Debug.h
For ESP8266 / ESP32 boards
ESP_WiFiManager is a library for the ESP8266/Arduino platform
(https://github.com/esp8266/Arduino) to enable easy
configuration and reconfiguration of WiFi credentials using a Captive Portal
inspired by:
http://www.esp8266.com/viewtopic.php?f=29&t=2520
https://github.com/chriscook8/esp-arduino-apboot
https://github.com/esp8266/Arduino/blob/master/libraries/DNSServer/examples/CaptivePortalAdvanced/
Modified from Tzapu https://github.com/tzapu/WiFiManager
and from Ken Taylor https://github.com/kentaylor
Built by Khoi Hoang https://github.com/khoih-prog/ESP_WiFiManager
Licensed under MIT license
Version: 1.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_Debug_H
#define ESP_WiFiManager_Debug_H
#ifdef WIFIMGR_DEBUG_PORT
#define WM_DBG_PORT WIFIMGR_DEBUG_PORT
#else
#define WM_DBG_PORT Serial
#endif
// Change _WIFIMGR_LOGLEVEL_ to set tracing and logging verbosity
// 0: DISABLED: no logging
// 1: ERROR: errors
// 2: WARN: errors and warnings
// 3: INFO: errors, warnings and informational (default)
// 4: DEBUG: errors, warnings, informational and debug
#ifndef _WIFIMGR_LOGLEVEL_
#define _WIFIMGR_LOGLEVEL_ 0
#endif
const char WM_MARK[] = "[WM] ";
const char WM_SP[] = " ";
#define WM_PRINT WM_DBG_PORT.print
#define WM_PRINTLN WM_DBG_PORT.println
#define WM_PRINT_MARK WM_PRINT(WM_MARK)
#define WM_PRINT_SP WM_PRINT(WM_SP)
////////////////////////////////////////////////////
#define LOGERROR(x) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINTLN(x); }
#define LOGERROR0(x) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT(x); }
#define LOGERROR1(x,y) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
#define LOGERROR2(x,y,z) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
#define LOGERROR3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>0) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
////////////////////////////////////////////////////
#define LOGWARN(x) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINTLN(x); }
#define LOGWARN0(x) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT(x); }
#define LOGWARN1(x,y) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
#define LOGWARN2(x,y,z) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
#define LOGWARN3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>1) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
////////////////////////////////////////////////////
#define LOGINFO(x) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINTLN(x); }
#define LOGINFO0(x) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT(x); }
#define LOGINFO1(x,y) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
#define LOGINFO2(x,y,z) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
#define LOGINFO3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>2) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
////////////////////////////////////////////////////
#define LOGDEBUG(x) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINTLN(x); }
#define LOGDEBUG0(x) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT(x); }
#define LOGDEBUG1(x,y) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINTLN(y); }
#define LOGDEBUG2(x,y,z) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINTLN(z); }
#define LOGDEBUG3(x,y,z,w) if(_WIFIMGR_LOGLEVEL_>3) { WM_PRINT_MARK; WM_PRINT(x); WM_PRINT_SP; WM_PRINT(y); WM_PRINT_SP; WM_PRINT(z); WM_PRINT_SP; WM_PRINTLN(w); }
////////////////////////////////////////////////////
#endif //ESP_WiFiManager_Debug_H

File diff suppressed because it is too large Load Diff

580
lib/OneWire/OneWire.cpp Normal file
View 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
View 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

View 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

View 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

View File

@ -3,6 +3,9 @@
// 2013-06-05 by Jeff Rowberg <jeff@rowberg.net>
//
// Changelog:
// 2021-09-28 - allow custom Wire object as transaction function argument
// 2020-01-20 - hardija : complete support for Teensy 3.x
// 2015-10-30 - simondlevy : support i2c_t3 for Teensy3.1
// 2013-05-06 - add Francesco Ferrara's Fastwire v0.24 implementation with small modifications
// 2013-05-05 - fix issue with writing bit values to words (Sasquatch/Farzanegan)
// 2012-06-09 - fix major issue with reading > 32 bytes at a time with Arduino Wire
@ -87,11 +90,6 @@ THE SOFTWARE.
#endif
#ifndef BUFFER_LENGTH
// band-aid fix for platforms without Wire-defined BUFFER_LENGTH (removed from some official implementations)
#define BUFFER_LENGTH 32
#endif
/** Default constructor.
*/
I2Cdev::I2Cdev() {
@ -105,9 +103,9 @@ I2Cdev::I2Cdev() {
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (true = success)
*/
int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout) {
int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout, void *wireObj) {
uint8_t b;
uint8_t count = readByte(devAddr, regAddr, &b, timeout);
uint8_t count = readByte(devAddr, regAddr, &b, timeout, wireObj);
*data = b & (1 << bitNum);
return count;
}
@ -120,9 +118,9 @@ int8_t I2Cdev::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (true = success)
*/
int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout) {
int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout, void *wireObj) {
uint16_t b;
uint8_t count = readWord(devAddr, regAddr, &b, timeout);
uint8_t count = readWord(devAddr, regAddr, &b, timeout, wireObj);
*data = b & (1 << bitNum);
return count;
}
@ -136,14 +134,14 @@ int8_t I2Cdev::readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (true = success)
*/
int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout) {
int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout, void *wireObj) {
// 01101001 read byte
// 76543210 bit numbers
// xxx args: bitStart=4, length=3
// 010 masked
// -> 010 shifted
uint8_t count, b;
if ((count = readByte(devAddr, regAddr, &b, timeout)) != 0) {
if ((count = readByte(devAddr, regAddr, &b, timeout, wireObj)) != 0) {
uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
b &= mask;
b >>= (bitStart - length + 1);
@ -161,7 +159,7 @@ int8_t I2Cdev::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (1 = success, 0 = failure, -1 = timeout)
*/
int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout) {
int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout, void *wireObj) {
// 1101011001101001 read byte
// fedcba9876543210 bit numbers
// xxx args: bitStart=12, length=3
@ -169,7 +167,7 @@ int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uin
// -> 010 shifted
uint8_t count;
uint16_t w;
if ((count = readWord(devAddr, regAddr, &w, timeout)) != 0) {
if ((count = readWord(devAddr, regAddr, &w, timeout, wireObj)) != 0) {
uint16_t mask = ((1 << length) - 1) << (bitStart - length + 1);
w &= mask;
w >>= (bitStart - length + 1);
@ -185,8 +183,8 @@ int8_t I2Cdev::readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uin
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (true = success)
*/
int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout) {
return readBytes(devAddr, regAddr, 1, data, timeout);
int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout, void *wireObj) {
return readBytes(devAddr, regAddr, 1, data, timeout, wireObj);
}
/** Read single word from a 16-bit device register.
@ -196,8 +194,8 @@ int8_t I2Cdev::readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Status of read operation (true = success)
*/
int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout) {
return readWords(devAddr, regAddr, 1, data, timeout);
int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout, void *wireObj) {
return readWords(devAddr, regAddr, 1, data, timeout, wireObj);
}
/** Read multiple bytes from an 8-bit device register.
@ -208,7 +206,7 @@ int8_t I2Cdev::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Number of bytes read (-1 indicates failure)
*/
int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout) {
int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout, void *wireObj) {
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print("I2C (0x");
Serial.print(devAddr, HEX);
@ -223,70 +221,73 @@ int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8
uint32_t t1 = millis();
#if (I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
TwoWire *useWire = &Wire;
if (wireObj) useWire = (TwoWire *)wireObj;
#if (ARDUINO < 100)
// Arduino v00xx (before v1.0), Wire library
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.send(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->send(regAddr);
useWire->endTransmission();
useWire->beginTransmission(devAddr);
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = Wire.receive();
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = useWire->receive();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
#endif
}
Wire.endTransmission();
useWire->endTransmission();
}
#elif (ARDUINO == 100)
// Arduino v1.0.0, Wire library
// Adds standardized write() and read() stream methods instead of send() and receive()
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->write(regAddr);
useWire->endTransmission();
useWire->beginTransmission(devAddr);
useWire->requestFrom((uint8_t)devAddr, (uint8_t)min((int)length - k, I2CDEVLIB_WIRE_BUFFER_LENGTH));
for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = Wire.read();
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = useWire->read();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
#endif
}
Wire.endTransmission();
useWire->endTransmission();
}
#elif (ARDUINO > 100)
// Arduino v1.0.1+, Wire library
// Adds official support for repeated start condition, yay!
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length; k += min((int)length, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)min(length - k, BUFFER_LENGTH));
for (int k = 0; k < length; k += min((int)length, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->write(regAddr);
useWire->endTransmission();
// 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 (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = Wire.read();
for (; useWire->available() && (timeout == 0 || millis() - t1 < timeout); count++) {
data[count] = useWire->read();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
@ -328,7 +329,7 @@ int8_t I2Cdev::readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8
* @param timeout Optional read timeout in milliseconds (0 to disable, leave off to use default class value in I2Cdev::readTimeout)
* @return Number of words read (-1 indicates failure)
*/
int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout) {
int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout, void *wireObj) {
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print("I2C (0x");
Serial.print(devAddr, HEX);
@ -343,28 +344,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
uint32_t t1 = millis();
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
TwoWire *useWire = &Wire;
if (wireObj) useWire = (TwoWire *)wireObj;
#if (ARDUINO < 100)
// Arduino v00xx (before v1.0), Wire library
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.send(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->send(regAddr);
useWire->endTransmission();
useWire->beginTransmission(devAddr);
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
bool msb = true; // starts with MSB, then LSB
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
if (msb) {
// first byte is bits 15-8 (MSb=15)
data[count] = Wire.receive() << 8;
data[count] = useWire->receive() << 8;
} else {
// second byte is bits 7-0 (LSb=0)
data[count] |= Wire.receive();
data[count] |= useWire->receive();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
@ -374,30 +377,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
msb = !msb;
}
Wire.endTransmission();
useWire->endTransmission();
}
#elif (ARDUINO == 100)
// Arduino v1.0.0, Wire library
// Adds standardized write() and read() stream methods instead of send() and receive()
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->write(regAddr);
useWire->endTransmission();
useWire->beginTransmission(devAddr);
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
bool msb = true; // starts with MSB, then LSB
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
if (msb) {
// first byte is bits 15-8 (MSb=15)
data[count] = Wire.read() << 8;
data[count] = useWire->read() << 8;
} else {
// second byte is bits 7-0 (LSb=0)
data[count] |= Wire.read();
data[count] |= useWire->read();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
@ -407,30 +410,30 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
msb = !msb;
}
Wire.endTransmission();
useWire->endTransmission();
}
#elif (ARDUINO > 100)
// Arduino v1.0.1+, Wire library
// Adds official support for repeated start condition, yay!
// I2C/TWI subsystem uses internal buffer that breaks with large data requests
// so if user requests more than BUFFER_LENGTH bytes, we have to do it in
// so if user requests more than I2CDEVLIB_WIRE_BUFFER_LENGTH bytes, we have to do it in
// smaller chunks instead of all at once
for (uint8_t k = 0; k < length * 2; k += min(length * 2, BUFFER_LENGTH)) {
Wire.beginTransmission(devAddr);
Wire.write(regAddr);
Wire.endTransmission();
Wire.beginTransmission(devAddr);
Wire.requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
for (uint8_t k = 0; k < length * 2; k += min(length * 2, I2CDEVLIB_WIRE_BUFFER_LENGTH)) {
useWire->beginTransmission(devAddr);
useWire->write(regAddr);
useWire->endTransmission();
useWire->beginTransmission(devAddr);
useWire->requestFrom(devAddr, (uint8_t)(length * 2)); // length=words, this wants bytes
bool msb = true; // starts with MSB, then LSB
for (; Wire.available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
for (; useWire->available() && count < length && (timeout == 0 || millis() - t1 < timeout);) {
if (msb) {
// first byte is bits 15-8 (MSb=15)
data[count] = Wire.read() << 8;
data[count] = useWire->read() << 8;
} else {
// second byte is bits 7-0 (LSb=0)
data[count] |= Wire.read();
data[count] |= useWire->read();
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print(data[count], HEX);
if (count + 1 < length) Serial.print(" ");
@ -440,7 +443,7 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
msb = !msb;
}
Wire.endTransmission();
useWire->endTransmission();
}
#endif
@ -479,11 +482,11 @@ int8_t I2Cdev::readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint1
* @param value New bit value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data) {
bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data, void *wireObj) {
uint8_t b;
readByte(devAddr, regAddr, &b);
readByte(devAddr, regAddr, &b, I2Cdev::readTimeout, wireObj);
b = (data != 0) ? (b | (1 << bitNum)) : (b & ~(1 << bitNum));
return writeByte(devAddr, regAddr, b);
return writeByte(devAddr, regAddr, b, wireObj);
}
/** write a single bit in a 16-bit device register.
@ -493,11 +496,11 @@ bool I2Cdev::writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t
* @param value New bit value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data) {
bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data, void *wireObj) {
uint16_t w;
readWord(devAddr, regAddr, &w);
readWord(devAddr, regAddr, &w, I2Cdev::readTimeout, wireObj);
w = (data != 0) ? (w | (1 << bitNum)) : (w & ~(1 << bitNum));
return writeWord(devAddr, regAddr, w);
return writeWord(devAddr, regAddr, w, wireObj);
}
/** Write multiple bits in an 8-bit device register.
@ -508,7 +511,7 @@ bool I2Cdev::writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_
* @param data Right-aligned value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data) {
bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data, void *wireObj) {
// 010 value to write
// 76543210 bit numbers
// xxx args: bitStart=4, length=3
@ -517,13 +520,13 @@ bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8
// 10100011 original & ~mask
// 10101011 masked | value
uint8_t b;
if (readByte(devAddr, regAddr, &b) != 0) {
if (readByte(devAddr, regAddr, &b, I2Cdev::readTimeout, wireObj) != 0) {
uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1);
data <<= (bitStart - length + 1); // shift data into correct position
data &= mask; // zero all non-important bits in data
b &= ~(mask); // zero all important bits in existing byte
b |= data; // combine data with existing byte
return writeByte(devAddr, regAddr, b);
return writeByte(devAddr, regAddr, b, wireObj);
} else {
return false;
}
@ -537,7 +540,7 @@ bool I2Cdev::writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8
* @param data Right-aligned value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data) {
bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data, void *wireObj) {
// 010 value to write
// fedcba9876543210 bit numbers
// xxx args: bitStart=12, length=3
@ -546,13 +549,13 @@ bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
// 1010001110010110 original & ~mask
// 1010101110010110 masked | value
uint16_t w;
if (readWord(devAddr, regAddr, &w) != 0) {
if (readWord(devAddr, regAddr, &w, I2Cdev::readTimeout, wireObj) != 0) {
uint16_t mask = ((1 << length) - 1) << (bitStart - length + 1);
data <<= (bitStart - length + 1); // shift data into correct position
data &= mask; // zero all non-important bits in data
w &= ~(mask); // zero all important bits in existing word
w |= data; // combine data with existing word
return writeWord(devAddr, regAddr, w);
return writeWord(devAddr, regAddr, w, wireObj);
} else {
return false;
}
@ -564,8 +567,8 @@ bool I2Cdev::writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint
* @param data New byte value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
return writeBytes(devAddr, regAddr, 1, &data);
bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data, void *wireObj) {
return writeBytes(devAddr, regAddr, 1, &data, wireObj);
}
/** Write single word to a 16-bit device register.
@ -574,8 +577,8 @@ bool I2Cdev::writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
* @param data New word value to write
* @return Status of operation (true = success)
*/
bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data) {
return writeWords(devAddr, regAddr, 1, &data);
bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data, void *wireObj) {
return writeWords(devAddr, regAddr, 1, &data, wireObj);
}
/** Write multiple bytes to an 8-bit device register.
@ -585,7 +588,7 @@ bool I2Cdev::writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data) {
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t* data) {
bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t* data, void *wireObj) {
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print("I2C (0x");
Serial.print(devAddr, HEX);
@ -596,14 +599,20 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
Serial.print("...");
#endif
uint8_t status = 0;
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
TwoWire *useWire = &Wire;
if (wireObj) useWire = (TwoWire *)wireObj;
#endif
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.beginTransmission(devAddr);
Wire.send((uint8_t) regAddr); // send address
useWire->beginTransmission(devAddr);
useWire->send((uint8_t) regAddr); // send address
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
Wire.beginTransmission(devAddr);
Wire.write((uint8_t) regAddr); // send address
useWire->beginTransmission(devAddr);
useWire->write((uint8_t) regAddr); // send address
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::beginTransmission(devAddr);
Fastwire::write(regAddr);
@ -614,21 +623,21 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
if (i + 1 < length) Serial.print(" ");
#endif
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.send((uint8_t) data[i]);
useWire->send((uint8_t) data[i]);
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
Wire.write((uint8_t) data[i]);
useWire->write((uint8_t) data[i]);
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::write((uint8_t) data[i]);
#endif
}
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.endTransmission();
useWire->endTransmission();
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
status = Wire.endTransmission();
status = useWire->endTransmission();
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::stop();
//status = Fastwire::endTransmission();
@ -646,7 +655,7 @@ bool I2Cdev::writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t* data) {
bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t* data, void *wireObj) {
#ifdef I2CDEV_SERIAL_DEBUG
Serial.print("I2C (0x");
Serial.print(devAddr, HEX);
@ -657,14 +666,20 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
Serial.print("...");
#endif
uint8_t status = 0;
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE || I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE
TwoWire *useWire = &Wire;
if (wireObj) useWire = (TwoWire *)wireObj;
#endif
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.beginTransmission(devAddr);
Wire.send(regAddr); // send address
useWire->beginTransmission(devAddr);
useWire->send(regAddr); // send address
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
Wire.beginTransmission(devAddr);
Wire.write(regAddr); // send address
useWire->beginTransmission(devAddr);
useWire->write(regAddr); // send address
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::beginTransmission(devAddr);
Fastwire::write(regAddr);
@ -675,13 +690,13 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
if (i + 1 < length) Serial.print(" ");
#endif
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.send((uint8_t)(data[i] >> 8)); // send MSB
Wire.send((uint8_t)data[i]); // send LSB
useWire->send((uint8_t)(data[i] >> 8)); // send MSB
useWire->send((uint8_t)data[i]); // send LSB
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
Wire.write((uint8_t)(data[i] >> 8)); // send MSB
Wire.write((uint8_t)data[i]); // send LSB
useWire->write((uint8_t)(data[i] >> 8)); // send MSB
useWire->write((uint8_t)data[i]); // send LSB
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::write((uint8_t)(data[i] >> 8)); // send MSB
status = Fastwire::write((uint8_t)data[i]); // send LSB
@ -689,11 +704,11 @@ bool I2Cdev::writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16
#endif
}
#if ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO < 100) || I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE)
Wire.endTransmission();
useWire->endTransmission();
#elif ((I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE && ARDUINO >= 100) \
|| (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_SBWIRE && ARDUINO >= 100) \
|| I2CDEV_IMPLEMENTATION == I2CDEV_TEENSY_3X_WIRE)
status = Wire.endTransmission();
status = useWire->endTransmission();
#elif (I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE)
Fastwire::stop();
//status = Fastwire::endTransmission();
@ -753,7 +768,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
#endif
TWSR = 0; // no prescaler => prescaler = 1
TWBR = ((16000L / khz) - 16) / 2; // change the I2C clock rate
TWBR = F_CPU / 2000 / khz - 8; // change the I2C clock rate
TWCR = 1 << TWEN; // enable twi module, no interrupt
}
@ -993,7 +1008,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2; // bitrate register
// enable twi module, acks, and twi interrupt
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA);
/* TWEN - TWI Enable Bit
TWIE - TWI Interrupt Enable
@ -1062,7 +1077,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
}
void twii_SetStart() {
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT) | (1 << TWSTA);
}
void twi_write01() {
@ -1145,19 +1160,19 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
void twi_reply(uint8_t ack) {
// transmit master read ready signal, with or without ack
if (ack){
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWINT) | (1 << TWEA);
} else {
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWINT);
}
}
void twi_stop(void) {
// send stop condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT) | (1 << TWSTO);
// wait for stop condition to be exectued on bus
// TWINT is not set after a stop condition!
while (TWCR & _BV(TWSTO)) {
while (TWCR & (1 << TWSTO)) {
continue;
}
@ -1167,7 +1182,7 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
void twi_releaseBus(void) {
// release bus
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
TWCR = (1 << TWEN) | (1 << TWIE) | (1 << TWEA) | (1 << TWINT);
// update twi state
twi_state = TWI_READY;
@ -1471,4 +1486,4 @@ uint16_t I2Cdev::readTimeout = I2CDEV_DEFAULT_READ_TIMEOUT;
return rxBufferLength - rxBufferIndex;
}
#endif
#endif

View File

@ -3,6 +3,7 @@
// 2013-06-05 by Jeff Rowberg <jeff@rowberg.net>
//
// Changelog:
// 2021-09-28 - allow custom Wire object as transaction function argument
// 2020-01-20 - hardija : complete support for Teensy 3.x
// 2015-10-30 - simondlevy : support i2c_t3 for Teensy3.1
// 2013-05-06 - add Francesco Ferrara's Fastwire v0.24 implementation with small modifications
@ -48,6 +49,11 @@ THE SOFTWARE.
#ifndef _I2CDEV_H_
#define _I2CDEV_H_
// -----------------------------------------------------------------------------
// Enable deprecated pgmspace typedefs in avr-libc
// -----------------------------------------------------------------------------
#define __PROG_TYPES_COMPAT__
// -----------------------------------------------------------------------------
// I2C interface implementation setting
// -----------------------------------------------------------------------------
@ -99,10 +105,26 @@ THE SOFTWARE.
#endif
#ifdef SPARK
#include <spark_wiring_i2c.h>
#include "application.h"
#define ARDUINO 101
#define BUFFER_LENGTH 32
#endif
#ifndef I2CDEVLIB_WIRE_BUFFER_LENGTH
#if defined(I2C_BUFFER_LENGTH)
// Arduino ESP32 core Wire uses this
#define I2CDEVLIB_WIRE_BUFFER_LENGTH I2C_BUFFER_LENGTH
#elif defined(BUFFER_LENGTH)
// Arduino AVR core Wire and many others use this
#define I2CDEVLIB_WIRE_BUFFER_LENGTH BUFFER_LENGTH
#elif defined(SERIAL_BUFFER_SIZE)
// Arduino SAMD core Wire uses this
#define I2CDEVLIB_WIRE_BUFFER_LENGTH SERIAL_BUFFER_SIZE
#else
// should be a safe fallback, though possibly inefficient
#define I2CDEVLIB_WIRE_BUFFER_LENGTH 32
#endif
#endif
// 1000ms default read timeout (modify with "I2Cdev::readTimeout = [ms];")
#define I2CDEV_DEFAULT_READ_TIMEOUT 1000
@ -111,23 +133,23 @@ class I2Cdev {
public:
I2Cdev();
static int8_t readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout);
static int8_t readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readByte(uint8_t devAddr, uint8_t regAddr, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static int8_t readWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, uint16_t timeout=I2Cdev::readTimeout, void *wireObj=0);
static bool writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data);
static bool writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data);
static bool writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data);
static bool writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data);
static bool writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data);
static bool writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data);
static bool writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data);
static bool writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data);
static bool writeBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint8_t data, void *wireObj=0);
static bool writeBitW(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t data, void *wireObj=0);
static bool writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data, void *wireObj=0);
static bool writeBitsW(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t data, void *wireObj=0);
static bool writeByte(uint8_t devAddr, uint8_t regAddr, uint8_t data, void *wireObj=0);
static bool writeWord(uint8_t devAddr, uint8_t regAddr, uint16_t data, void *wireObj=0);
static bool writeBytes(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint8_t *data, void *wireObj=0);
static bool writeWords(uint8_t devAddr, uint8_t regAddr, uint8_t length, uint16_t *data, void *wireObj=0);
static uint16_t readTimeout;
};
@ -240,7 +262,7 @@ class I2Cdev {
/* TWI Status is in TWSR, in the top 5 bits: TWS7 - TWS3 */
#define TW_STATUS_MASK (_BV(TWS7)|_BV(TWS6)|_BV(TWS5)|_BV(TWS4)|_BV(TWS3))
#define TW_STATUS_MASK ((1 << TWS7)|(1 << TWS6)|(1 << TWS5)|(1 << TWS4)|(1 << TWS3))
#define TW_STATUS (TWSR & TW_STATUS_MASK)
#define TW_START 0x08
#define TW_REP_START 0x10
@ -275,15 +297,15 @@ class I2Cdev {
//#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))
#ifndef sbi // set bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= (1 << bit))
#endif // sbi
#ifndef cbi // clear bit
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~(1 << bit))
#endif // cbi
extern TwoWire Wire;
#endif // I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_NBWIRE
#endif /* _I2CDEV_H_ */
#endif /* _I2CDEV_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
// 2021/09/27 - split implementations out of header files, finally
// ... - ongoing debug release
// NOTE: THIS IS ONLY A PARIAL RELEASE. THIS DEVICE CLASS IS CURRENTLY UNDERGOING ACTIVE
@ -38,12 +39,15 @@ THE SOFTWARE.
#define _MPU6050_H_
#include "I2Cdev.h"
#include "helper_3dmath.h"
// supporting link: http://forum.arduino.cc/index.php?&topic=143444.msg1079517#msg1079517
// also: http://forum.arduino.cc/index.php?&topic=141571.msg1062899#msg1062899s
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP32)
#include <pgmspace.h>
#else
//#define PROGMEM /* empty */
//#define pgm_read_byte(x) (*(x))
@ -431,11 +435,11 @@ THE SOFTWARE.
#define MPU6050_DMP_MEMORY_BANK_SIZE 256
#define MPU6050_DMP_MEMORY_CHUNK_SIZE 16
// note: DMP code memory blocks defined at end of header file
#define MPU6050_FIFO_DEFAULT_TIMEOUT 11000
class MPU6050 {
class MPU6050_Base {
public:
MPU6050(uint8_t address=MPU6050_DEFAULT_ADDRESS);
MPU6050_Base(uint8_t address=MPU6050_DEFAULT_ADDRESS, void *wireObj=0);
void initialize();
bool testConnection();
@ -717,6 +721,8 @@ class MPU6050 {
int8_t GetCurrentFIFOPacket(uint8_t *data, uint8_t length);
void setFIFOByte(uint8_t data);
void getFIFOBytes(uint8_t *data, uint8_t length);
void setFIFOTimeout(uint32_t fifoTimeout);
uint32_t getFIFOTimeout();
// WHO_AM_I register
uint8_t getDeviceID();
@ -826,216 +832,21 @@ class MPU6050 {
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();
// special methods for MotionApps 2.0 implementation
#ifdef MPU6050_INCLUDE_DMP_MOTIONAPPS20
uint8_t dmpInitialize();
bool dmpPacketAvailable();
uint8_t dmpSetFIFORate(uint8_t fifoRate);
uint8_t dmpGetFIFORate();
uint8_t dmpGetSampleStepSizeMS();
uint8_t dmpGetSampleFrequency();
int32_t dmpDecodeTemperature(int8_t tempReg);
// Register callbacks after a packet of FIFO data is processed
//uint8_t dmpRegisterFIFORateProcess(inv_obj_func func, int16_t priority);
//uint8_t dmpUnregisterFIFORateProcess(inv_obj_func func);
uint8_t dmpRunFIFORateProcesses();
// Setup FIFO for various output
uint8_t dmpSendQuaternion(uint_fast16_t accuracy);
uint8_t dmpSendGyro(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendLinearAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendLinearAccelInWorld(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendControlData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendExternalSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendGravity(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendPacketNumber(uint_fast16_t accuracy);
uint8_t dmpSendQuantizedAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendEIS(uint_fast16_t elements, uint_fast16_t accuracy);
// Get Fixed Point data from FIFO
uint8_t dmpGetAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(Quaternion *q, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(Quaternion *q, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(Quaternion *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpSetLinearAccelFilterCoefficient(float coef);
uint8_t dmpGetLinearAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(VectorInt16 *v, VectorInt16 *vRaw, VectorFloat *gravity);
uint8_t dmpGetLinearAccelInWorld(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, VectorInt16 *vReal, Quaternion *q);
uint8_t dmpGetGyroAndAccelSensor(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroAndAccelSensor(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroAndAccelSensor(VectorInt16 *g, VectorInt16 *a, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetControlData(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetTemperature(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetGravity(VectorFloat *v, Quaternion *q);
uint8_t dmpGetUnquantizedAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetUnquantizedAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetUnquantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetExternalSensorData(int32_t *data, uint16_t size, const uint8_t* packet=0);
uint8_t dmpGetEIS(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetEuler(float *data, Quaternion *q);
uint8_t dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity);
// Get Floating Point data from FIFO
uint8_t dmpGetAccelFloat(float *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternionFloat(float *data, const uint8_t* packet=0);
uint8_t dmpProcessFIFOPacket(const unsigned char *dmpData);
uint8_t dmpReadAndProcessFIFOPacket(uint8_t numPackets, uint8_t *processed=NULL);
uint8_t dmpSetFIFOProcessedCallback(void (*func) (void));
uint8_t dmpInitFIFOParam();
uint8_t dmpCloseFIFO();
uint8_t dmpSetGyroDataSource(uint8_t source);
uint8_t dmpDecodeQuantizedAccel();
uint32_t dmpGetGyroSumOfSquare();
uint32_t dmpGetAccelSumOfSquare();
void dmpOverrideQuaternion(long *q);
uint16_t dmpGetFIFOPacketSize();
uint8_t dmpGetCurrentFIFOPacket(uint8_t *data); // overflow proof
#endif
// special methods for MotionApps 4.1 implementation
#ifdef MPU6050_INCLUDE_DMP_MOTIONAPPS41
uint8_t dmpInitialize();
bool dmpPacketAvailable();
uint8_t dmpSetFIFORate(uint8_t fifoRate);
uint8_t dmpGetFIFORate();
uint8_t dmpGetSampleStepSizeMS();
uint8_t dmpGetSampleFrequency();
int32_t dmpDecodeTemperature(int8_t tempReg);
// Register callbacks after a packet of FIFO data is processed
//uint8_t dmpRegisterFIFORateProcess(inv_obj_func func, int16_t priority);
//uint8_t dmpUnregisterFIFORateProcess(inv_obj_func func);
uint8_t dmpRunFIFORateProcesses();
// Setup FIFO for various output
uint8_t dmpSendQuaternion(uint_fast16_t accuracy);
uint8_t dmpSendGyro(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendLinearAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendLinearAccelInWorld(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendControlData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendExternalSensorData(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendGravity(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendPacketNumber(uint_fast16_t accuracy);
uint8_t dmpSendQuantizedAccel(uint_fast16_t elements, uint_fast16_t accuracy);
uint8_t dmpSendEIS(uint_fast16_t elements, uint_fast16_t accuracy);
// Get Fixed Point data from FIFO
uint8_t dmpGetAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternion(Quaternion *q, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGet6AxisQuaternion(Quaternion *q, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetRelativeQuaternion(Quaternion *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyro(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetMag(int16_t *data, const uint8_t* packet=0);
uint8_t dmpSetLinearAccelFilterCoefficient(float coef);
uint8_t dmpGetLinearAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetLinearAccel(VectorInt16 *v, VectorInt16 *vRaw, VectorFloat *gravity);
uint8_t dmpGetLinearAccelInWorld(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetLinearAccelInWorld(VectorInt16 *v, VectorInt16 *vReal, Quaternion *q);
uint8_t dmpGetGyroAndAccelSensor(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroAndAccelSensor(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroAndAccelSensor(VectorInt16 *g, VectorInt16 *a, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGyroSensor(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetControlData(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetTemperature(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetGravity(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetGravity(VectorFloat *v, Quaternion *q);
uint8_t dmpGetUnquantizedAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetUnquantizedAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetUnquantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(int16_t *data, const uint8_t* packet=0);
uint8_t dmpGetQuantizedAccel(VectorInt16 *v, const uint8_t* packet=0);
uint8_t dmpGetExternalSensorData(int32_t *data, uint16_t size, const uint8_t* packet=0);
uint8_t dmpGetEIS(int32_t *data, const uint8_t* packet=0);
uint8_t dmpGetEuler(float *data, Quaternion *q);
uint8_t dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity);
// Get Floating Point data from FIFO
uint8_t dmpGetAccelFloat(float *data, const uint8_t* packet=0);
uint8_t dmpGetQuaternionFloat(float *data, const uint8_t* packet=0);
uint8_t dmpProcessFIFOPacket(const unsigned char *dmpData);
uint8_t dmpReadAndProcessFIFOPacket(uint8_t numPackets, uint8_t *processed=NULL);
uint8_t dmpSetFIFOProcessedCallback(void (*func) (void));
uint8_t dmpInitFIFOParam();
uint8_t dmpCloseFIFO();
uint8_t dmpSetGyroDataSource(uint8_t source);
uint8_t dmpDecodeQuantizedAccel();
uint32_t dmpGetGyroSumOfSquare();
uint32_t dmpGetAccelSumOfSquare();
void dmpOverrideQuaternion(long *q);
uint16_t dmpGetFIFOPacketSize();
#endif
private:
protected:
uint8_t devAddr;
void *wireObj;
uint8_t buffer[14];
#if defined(MPU6050_INCLUDE_DMP_MOTIONAPPS20) or defined(MPU6050_INCLUDE_DMP_MOTIONAPPS41)
uint8_t *dmpPacketBuffer;
uint16_t dmpPacketSize;
#endif
uint32_t fifoTimeout = MPU6050_FIFO_DEFAULT_TIMEOUT;
private:
int16_t offsets[6];
};
#endif /* _MPU6050_H_ */
#ifndef I2CDEVLIB_MPU6050_TYPEDEF
#define I2CDEVLIB_MPU6050_TYPEDEF
typedef MPU6050_Base MPU6050;
#endif
#endif /* _MPU6050_H_ */

216
lib/mpu6050/helper_3dmath.h Normal file
View File

@ -0,0 +1,216 @@
// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class, 3D math helper
// 6/5/2012 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
// 2012-06-05 - add 3D math helper file to DMP6 example sketch
/* ============================================
I2Cdev device library code is placed under the MIT license
Copyright (c) 2012 Jeff Rowberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================
*/
#ifndef _HELPER_3DMATH_H_
#define _HELPER_3DMATH_H_
class Quaternion {
public:
float w;
float x;
float y;
float z;
Quaternion() {
w = 1.0f;
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
Quaternion(float nw, float nx, float ny, float nz) {
w = nw;
x = nx;
y = ny;
z = nz;
}
Quaternion getProduct(Quaternion q) {
// Quaternion multiplication is defined by:
// (Q1 * Q2).w = (w1w2 - x1x2 - y1y2 - z1z2)
// (Q1 * Q2).x = (w1x2 + x1w2 + y1z2 - z1y2)
// (Q1 * Q2).y = (w1y2 - x1z2 + y1w2 + z1x2)
// (Q1 * Q2).z = (w1z2 + x1y2 - y1x2 + z1w2
return Quaternion(
w*q.w - x*q.x - y*q.y - z*q.z, // new w
w*q.x + x*q.w + y*q.z - z*q.y, // new x
w*q.y - x*q.z + y*q.w + z*q.x, // new y
w*q.z + x*q.y - y*q.x + z*q.w); // new z
}
Quaternion getConjugate() {
return Quaternion(w, -x, -y, -z);
}
float getMagnitude() {
return sqrt(w*w + x*x + y*y + z*z);
}
void normalize() {
float m = getMagnitude();
w /= m;
x /= m;
y /= m;
z /= m;
}
Quaternion getNormalized() {
Quaternion r(w, x, y, z);
r.normalize();
return r;
}
};
class VectorInt16 {
public:
int16_t x;
int16_t y;
int16_t z;
VectorInt16() {
x = 0;
y = 0;
z = 0;
}
VectorInt16(int16_t nx, int16_t ny, int16_t nz) {
x = nx;
y = ny;
z = nz;
}
float getMagnitude() {
return sqrt(x*x + y*y + z*z);
}
void normalize() {
float m = getMagnitude();
x /= m;
y /= m;
z /= m;
}
VectorInt16 getNormalized() {
VectorInt16 r(x, y, z);
r.normalize();
return r;
}
void rotate(Quaternion *q) {
// http://www.cprogramming.com/tutorial/3d/quaternions.html
// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/transforms/index.htm
// http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation
// ^ or: http://webcache.googleusercontent.com/search?q=cache:xgJAp3bDNhQJ:content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation&hl=en&gl=us&strip=1
// P_out = q * P_in * conj(q)
// - P_out is the output vector
// - q is the orientation quaternion
// - P_in is the input vector (a*aReal)
// - conj(q) is the conjugate of the orientation quaternion (q=[w,x,y,z], q*=[w,-x,-y,-z])
Quaternion p(0, x, y, z);
// quaternion multiplication: q * p, stored back in p
p = q -> getProduct(p);
// quaternion multiplication: p * conj(q), stored back in p
p = p.getProduct(q -> getConjugate());
// p quaternion is now [0, x', y', z']
x = p.x;
y = p.y;
z = p.z;
}
VectorInt16 getRotated(Quaternion *q) {
VectorInt16 r(x, y, z);
r.rotate(q);
return r;
}
};
class VectorFloat {
public:
float x;
float y;
float z;
VectorFloat() {
x = 0;
y = 0;
z = 0;
}
VectorFloat(float nx, float ny, float nz) {
x = nx;
y = ny;
z = nz;
}
float getMagnitude() {
return sqrt(x*x + y*y + z*z);
}
void normalize() {
float m = getMagnitude();
x /= m;
y /= m;
z /= m;
}
VectorFloat getNormalized() {
VectorFloat r(x, y, z);
r.normalize();
return r;
}
void rotate(Quaternion *q) {
Quaternion p(0, x, y, z);
// quaternion multiplication: q * p, stored back in p
p = q -> getProduct(p);
// quaternion multiplication: p * conj(q), stored back in p
p = p.getProduct(q -> getConjugate());
// p quaternion is now [0, x', y', z']
x = p.x;
y = p.y;
z = p.z;
}
VectorFloat getRotated(Quaternion *q) {
VectorFloat r(x, y, z);
r.rotate(q);
return r;
}
};
#endif /* _HELPER_3DMATH_H_ */

13
part32.csv Normal file
View 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
1 # Name,Type,SubType,Offset,Size,Flags
2 nvs,data,nvs,0x9000,0x5000
3 otadata,data,ota,0xe000,0x2000
4 app0,app,ota_0,0x10000,0x1c0000
5 app1,app,ota_1,0x1d0000,0x1c0000
6 spiffs,data,spiffs,0x390000,0x70000
7 # Name, Type, SubType, Offset, Size, Flags
8 #nvs, data, nvs, 0x9000, 0x5000,
9 #otadata, data, ota, 0xe000, 0x2000,
10 #app0, app, ota_0, 0x10000, 0x140000, # 1310720
11 #app1, app, ota_1, 0x150000,0x140000, # 1310720
12 #spiffs, data, spiffs, 0x290000,0x170000, # 1507328

View File

@ -15,34 +15,40 @@ include_dir = lib
[common_env_data]
upload_speed = 921600
monitor_speed = 115200
platform = espressif8266
platform = espressif8266 @ 4.0.1
framework = arduino
board = d1_mini
build_unflags =
build_flags = #-O0 -Wl,-Map,output.map
build_flags =
-D BAUD=${common_env_data.monitor_speed}
-D ACTIVATE_OTA
#-D USE_GYRO_TEMP # If this is enabled the DS18 will not be used, temp is read from the gyro.
-D 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 DOUBLERESETDETECTOR_DEBUG true
-D USE_LITTLEFS=true
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
-D USER_SSID=\""\"" # =\""myssid\""
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
-D CFG_APPVER="\"0.4.0\""
lib_deps =
# https://github.com/jrowberg/i2cdevlib.git # Using local copy of this library
https://github.com/codeplea/tinyexpr
https://github.com/graphitemaster/incbin
https://github.com/khoih-prog/ESP_DoubleResetDetector
https://github.com/tzapu/WiFiManager
https://github.com/thijse/Arduino-Log
https://github.com/bblanchon/ArduinoJson
https://github.com/PaulStoffregen/OneWire
https://github.com/milesburton/Arduino-Temperature-Control-Library
-D CFG_APPVER="\"1.1.0\""
-D CFG_GITREV=\""beta-2\""
#!python script/git_rev.py
lib_deps = # Switched to forks for better version control.
# 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/arduinoCurveFitting#v1.0.6 # https://github.com/Rotario/arduinoCurveFitting
https://github.com/mp-se/arduino-mqtt#v2.5.0 # https://github.com/256dpi/arduino-mqtt
[env:gravity-debug]
upload_speed = ${common_env_data.upload_speed}
@ -50,18 +56,36 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework}
platform = ${common_env_data.platform}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags}
build_unflags =
${common_env_data.build_unflags}
build_flags =
${common_env_data.build_flags}
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
-D LOG_LEVEL=6 # Maximum log level for the debug build.
-Wl,-Map,output.map
${common_env_data.build_flags}
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
#-D SKIP_SLEEPMODE
#-D DOUBLERESETDETECTOR_DEBUG=true
#-D FORCE_GRAVITY_MODE # used to debug gravity mode
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
-D LOG_LEVEL=6 # Maximum log level for the debug build.
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart.
-D GYRO_DISABLE_LOGGING
-D CALC_DISABLE_LOGGING
-D HELPER_DISABLE_LOGGING
-D PUSH_DISABLE_LOGGING
-D TSEN_DISABLE_LOGGING
-D WIFI_DISABLE_LOGGING
-D WEB_DISABLE_LOGGING
-D MAIN_DISABLE_LOGGING
lib_deps =
${common_env_data.lib_deps}
board = ${common_env_data.board}
build_type = debug
#build_type = debug
build_type = release
board_build.filesystem = littlefs
monitor_filters = esp8266_exception_decoder
[env:gravity-release]
upload_speed = ${common_env_data.upload_speed}
@ -69,6 +93,7 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework}
platform = ${common_env_data.platform}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags}
@ -87,15 +112,76 @@ monitor_speed = ${common_env_data.monitor_speed}
framework = ${common_env_data.framework}
platform = ${common_env_data.platform}
extra_scripts =
script/copy_html.py
script/copy_firmware.py
script/create_versionjson.py
build_unflags = ${common_env_data.build_unflags}
build_flags =
${common_env_data.build_flags}
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
${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

View File

@ -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"
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"
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 )
target = dir + "/bin/firmware-perf.bin"
source = dir + "/.pio/build/" + name + "/firmware.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
if name == "gravity32-release" :
target = dir + "/bin/firmware32.bin"
source = dir + "/.pio/build/" + name + "/firmware.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
target = dir + "/bin/partitions32.bin"
source = dir + "/.pio/build/" + name + "/partitions.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
if name == "gravity32-perf" :
target = dir + "/bin/firmware32-perf.bin"
source = dir + "/.pio/build/" + name + "/firmware.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
if name == "gravity32-release2" :
target = dir + "/bin/firmware32_2.bin"
source = dir + "/.pio/build/" + name + "/firmware.bin"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
print( "Adding custom build step (copy firmware): ")

34
script/copy_html.py Normal file
View File

@ -0,0 +1,34 @@
Import("env")
import shutil, os
print( "Executing custom step " )
dir = env.GetLaunchDir()
source = dir + "/html/"
target = dir + "/data/"
print( "Copy html-files from " + source + " -> " + target )
os.makedirs(os.path.dirname( target ), exist_ok=True)
file = "about.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "calibration.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "config.min.htm"
#print( "Copy file: " + source + file + "->" + target + file)
shutil.copyfile( source + file, target + file )
file = "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 )

View File

@ -13,38 +13,51 @@ def after_build(source, target, env):
#name = env.get( "PIOENV" )
# Copy file 1
source = dir + "\\data\\index.min.htm"
target = dir + "\\bin\\index.min.htm"
source = dir + "/data/index.min.htm"
target = dir + "/bin/index.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 2
source = dir + "\\data\\device.min.htm"
target = dir + "\\bin\\device.min.htm"
source = dir + "/data/config.min.htm"
target = dir + "/bin/config.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 3
source = dir + "\\data\\config.min.htm"
target = dir + "\\bin\\config.min.htm"
source = dir + "/data/about.min.htm"
target = dir + "/bin/about.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
# Copy file 4
source = dir + "\\data\\about.min.htm"
target = dir + "\\bin\\about.min.htm"
source = dir + "/data/calibration.min.htm"
target = dir + "/bin/calibration.min.htm"
print( "Copy file : " + source + " -> " + target )
shutil.copyfile( source, target )
target = dir + "\\bin\\version.json"
# 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\", \"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()
print( "Adding custom build step (create json):")
env.AddPreAction("buildprog", after_build)
env.AddPostAction("buildprog", after_build)

9
script/git_rev.py Normal file
View 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
View 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

View File

@ -1,7 +1,7 @@
/*
MIT License
Copyright (c) 2021 Magnus
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -21,30 +21,27 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _WEBSERVER_H
#define _WEBSERVER_H
#ifndef SRC_BLE_HPP_
#define SRC_BLE_HPP_
// Include
#if defined(ESP32)
// classes
class WebServer {
public:
enum HtmlFile {
HTML_INDEX = 0,
HTML_DEVICE = 1,
HTML_CONFIG = 2,
HTML_ABOUT = 3
};
#include <NimBLEBeacon.h>
#include <NimBLEDevice.h>
bool setupWebServer();
void loop();
bool checkHtmlFile( HtmlFile item );
const char* getHtmlFileName( HtmlFile item );
#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);
};
// Global instance created
extern WebServer myWebServer;
#endif // _WEBSERVER_H
// EOF
#endif // ESP32
#endif // SRC_BLE_HPP_

View File

@ -1,98 +1,221 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "calc.h"
#include "helper.h"
#include "config.h"
#include "tinyexpr.h"
#include "tempsensor.h"
//
// Calculates gravity according to supplied formula, compatible with iSpindle/Fermentrack formula
//
double calculateGravity( double angle, double temp ) {
const char* formula = myConfig.getGravityFormula();
#if LOG_LEVEL==6
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
#endif
if( strlen(formula) == 0 )
return 0.0;
// Store variable names and pointers.
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 2, &err);
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL==6
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
#endif
return g;
}
Log.error(F("CALC: Failed to parse expression %d." CR), err);
return 0;
}
//
// Do a standard gravity temperature correction. This is a simple way to adjust for differnt worth temperatures
//
double gravityTemperatureCorrection( double gravity, double temp, char tempFormat, double calTemp) {
#if LOG_LEVEL==6
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, temp %F, calTemp %F." CR), gravity, temp, calTemp);
#endif
if( tempFormat == 'C')
temp = convertCtoF( temp );
double calTempF = convertCtoF(calTemp); // calTemp is in C
const char* formula = "gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))";
// Store variable names and pointers.
te_variable vars[] = {{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 3, &err);
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL==6
Log.verbose(F("CALC: Corrected gravity is %F." CR), g);
#endif
return g;
}
Log.error(F("CALC: Failed to parse expression %d, no correction has been made." CR), err);
return gravity;
}
// EOF
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <curveFitting.h>
#include <tinyexpr.h>
#include <calc.hpp>
#include <main.hpp>
//
// Use values to derive a formula
//
int createFormula(RawFormulaData &fd, char *formulaBuffer,
int formulaBufferSize, int order) {
int noAngles = 0;
RawFormulaData fd2;
// Check how many valid values we have got and make sure we have a full
// series.
for (int i = 0; i < FORMULA_DATA_SIZE; i++) {
if (fd.a[i]) {
fd2.a[noAngles] = fd.a[i];
fd2.g[noAngles] = fd.g[i];
noAngles++;
}
}
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
Log.verbose(
F("CALC: Trying to create formula using order = %d, found %d angles" CR),
order, noAngles);
#endif
if (noAngles < 3) {
ErrorFileLog errLog;
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
return ERR_FORMULA_NOTENOUGHVALUES;
} else {
double coeffs[order + 1];
int ret = fitCurve(order, noAngles, fd2.a, fd2.g,
sizeof(coeffs) / sizeof(double), coeffs);
// Returned value is 0 if no error
if (ret == 0) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
Log.verbose(F("CALC: Finshied processing data points, order = %d." CR),
order);
#endif
// Print the formula based on 'order'
if (order == 4) {
snprintf(formulaBuffer, formulaBufferSize,
"%.8f*tilt^4+%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f",
coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]);
} else if (order == 3) {
snprintf(formulaBuffer, formulaBufferSize,
"%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1],
coeffs[2], coeffs[3]);
} else if (order == 2) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f",
coeffs[0], coeffs[1], coeffs[2]);
} else { // order == 1
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0],
coeffs[1]);
}
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
#endif
bool valid = true;
for (int i = 0; i < 5; i++) {
if (fd.a[i] == 0 && valid) break;
double g = calculateGravity(fd.a[i], 0, formulaBuffer);
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
// If the deviation is more than 2 degress we mark it as failed.
if (dev * 1000 > myAdvancedConfig.getMaxFormulaCreationDeviation()) {
char s[120];
snprintf(&s[0], sizeof(s),
"CALC: Validation failed on angle %f, deviation too large "
"%.2f SG, formula order %d",
fd.a[i], dev * 1000, order);
ErrorFileLog errLog;
errLog.addEntry(&s[0]);
valid = false;
}
}
if (!valid) {
return ERR_FORMULA_UNABLETOFFIND;
}
Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
return 0;
}
}
ErrorFileLog errLog;
errLog.addEntry(F("CALC: Internal error finding formula."));
return ERR_FORMULA_INTERNAL;
}
//
// Calculates gravity according to supplied formula, compatible with
// iSpindle/Fermentrack formula
//
double calculateGravity(double angle, double temp, const char *tempFormula) {
const char *formula = myConfig.getGravityFormula();
if (tempFormula != 0) {
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
Log.verbose(F("CALC: Using temporary formula." CR));
#endif
formula = tempFormula;
}
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
#endif
if (strlen(formula) == 0) return 0.0;
// Store variable names and pointers.
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 2, &err);
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
char s[20];
snprintf(&s[0], sizeof(s), "%.8f", g);
Log.verbose(F("CALC: Calculated gravity is %s." CR), &s[0]);
#endif
return g;
}
ErrorFileLog errLog;
errLog.addEntry("CALC: Failed to parse gravity expression " + String(err));
return 0;
}
//
// Do a standard gravity temperature correction. This is a simple way to adjust
// for differnt worth temperatures. This function uses C as temperature.
//
// Source: https://homebrewacademy.com/hydrometer-temperature-correction/
//
double gravityTemperatureCorrectionC(double 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),
gravitySG, tempC, calTempC);
#endif
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)/"
"(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0."
"00000000232820948*cal^3))";
// Store variable names and pointers.
te_variable vars[] = {
{"gravity", &gravitySG}, {"temp", &tempF}, {"cal", &calTempF}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 3, &err);
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6 && !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;
}
ErrorFileLog errLog;
errLog.addEntry(
"CALC: Failed to parse expression for gravity temperature correction " +
String(err));
return gravitySG;
}
// EOF

42
src/calc.hpp Normal file
View File

@ -0,0 +1,42 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SRC_CALC_HPP_
#define SRC_CALC_HPP_
#include <config.hpp>
#define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3
double calculateGravity(double angle, double tempC,
const char *tempFormula = 0);
double gravityTemperatureCorrectionC(double gravity, double tempC,
double calTempC);
int createFormula(RawFormulaData &fd, char *formulaBuffer,
int formulaBufferSize, int order);
#endif // SRC_CALC_HPP_
// EOF

View File

@ -1,268 +1,497 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "config.h"
#include "helper.h"
#include <LittleFS.h>
Config myConfig;
//
// Create the config class with default settings.
//
Config::Config() {
// Assiging default values
char buf[20];
sprintf(&buf[0], "%6x", (unsigned int) ESP.getChipId() );
id = &buf[0];
sprintf(&buf[0], "" WIFI_MDNS "%s", getID() );
mDNS = &buf[0];
setTempFormat('C');
setGravityFormat('G');
setSleepInterval(900); // 15 minutes
setVoltageFactor(1.59); // Conversion factor for battery
setTempSensorAdj(0.0);
setGravityTempAdj(false);
gyroCalibration = { 0, 0, 0, 0, 0 ,0 };
saveNeeded = false;
}
//
// Populate the json document with all configuration parameters (used in both web and saving to file)
//
void Config::createJson(DynamicJsonDocument& doc) {
doc[ CFG_PARAM_MDNS ] = getMDNS();
doc[ CFG_PARAM_ID ] = getID();
doc[ CFG_PARAM_OTA ] = getOtaURL();
doc[ CFG_PARAM_TEMPFORMAT ] = String( getTempFormat() );
doc[ CFG_PARAM_PUSH_BREWFATHER ] = getBrewfatherPushUrl();
doc[ CFG_PARAM_PUSH_HTTP ] = getHttpPushUrl();
doc[ CFG_PARAM_PUSH_HTTP2 ] = getHttpPushUrl2();
doc[ CFG_PARAM_PUSH_INFLUXDB2 ] = getInfluxDb2PushUrl();
doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ] = getInfluxDb2PushOrg();
doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ] = getInfluxDb2PushBucket();
doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ] = getInfluxDb2PushToken();
doc[ CFG_PARAM_SLEEP_INTERVAL ] = getSleepInterval();
// doc[ CFG_PARAM_PUSH_INTERVAL ] = getSleepInterval(); // TODO: @deprecated
doc[ CFG_PARAM_VOLTAGEFACTOR ] = getVoltageFactor();
doc[ CFG_PARAM_GRAVITY_FORMULA ] = getGravityFormula();
doc[ CFG_PARAM_GRAVITY_FORMAT ] = String(getGravityFormat());
doc[ CFG_PARAM_TEMP_ADJ ] = getTempSensorAdj();
doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ] = isGravityTempAdj();
JsonObject cal = doc.createNestedObject( CFG_PARAM_GYRO_CALIBRATION );
cal["ax"] = gyroCalibration.ax;
cal["ay"] = gyroCalibration.ay;
cal["az"] = gyroCalibration.az;
cal["gx"] = gyroCalibration.gx;
cal["gy"] = gyroCalibration.gy;
cal["gz"] = gyroCalibration.gz;
}
//
// Save json document to file
//
bool Config::saveFile() {
if( !saveNeeded ) {
#if LOG_LEVEL==6
Log.verbose(F("CFG : Skipping save, not needed." CR));
#endif
return true;
}
#if LOG_LEVEL==6
Log.verbose(F("CFG : Saving configuration to file." CR));
#endif
File configFile = LittleFS.open(CFG_FILENAME, "w");
if (!configFile) {
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
return false;
}
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
createJson( doc );
#if LOG_LEVEL==6
serializeJson(doc, Serial);
Serial.print( CR );
#endif
serializeJson(doc, configFile);
configFile.flush();
configFile.close();
saveNeeded = false;
myConfig.debug();
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
return true;
}
//
// Load config file from disk
//
bool Config::loadFile() {
#if LOG_LEVEL==6
Log.verbose(F("CFG : Loading configuration from file." CR));
#endif
if (!LittleFS.exists(CFG_FILENAME)) {
Log.error(F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
return false;
}
File configFile = LittleFS.open(CFG_FILENAME, "r");
if (!configFile) {
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
return false;
}
Log.notice(F("CFG : Size of configuration file=%d bytes." CR), configFile.size() );
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
DeserializationError err = deserializeJson(doc, configFile);
#if LOG_LEVEL==6
serializeJson(doc, Serial);
Serial.print( CR );
#endif
configFile.close();
if( err ) {
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR), err.c_str(), doc.capacity());
return false;
}
#if LOG_LEVEL==6
Log.verbose(F("CFG : Parsed configuration file." CR));
#endif
if( !doc[ CFG_PARAM_OTA ].isNull() )
setOtaURL( doc[ CFG_PARAM_OTA ] );
if( !doc[ CFG_PARAM_MDNS ].isNull() )
setMDNS( doc[ CFG_PARAM_MDNS ] );
if( !doc[ CFG_PARAM_TEMPFORMAT ].isNull() ) {
String s = doc[ CFG_PARAM_TEMPFORMAT ];
setTempFormat( s.charAt(0) );
}
if( !doc[ CFG_PARAM_PUSH_BREWFATHER ].isNull() )
setBrewfatherPushUrl( doc[ CFG_PARAM_PUSH_BREWFATHER ] );
if( !doc[ CFG_PARAM_PUSH_HTTP ].isNull() )
setHttpPushUrl( doc[ CFG_PARAM_PUSH_HTTP ] );
if( !doc[ CFG_PARAM_PUSH_HTTP2 ].isNull() )
setHttpPushUrl2( doc[ CFG_PARAM_PUSH_HTTP2 ] );
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2 ].isNull() )
setInfluxDb2PushUrl( doc[ CFG_PARAM_PUSH_INFLUXDB2 ] );
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ].isNull() )
setInfluxDb2PushOrg( doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ] );
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ].isNull() )
setInfluxDb2PushBucket( doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ] );
if( !doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ].isNull() )
setInfluxDb2PushToken( doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ] );
if( !doc[ CFG_PARAM_SLEEP_INTERVAL ].isNull() )
setSleepInterval( doc[ CFG_PARAM_SLEEP_INTERVAL ].as<int>() );
if( !doc[ CFG_PARAM_PUSH_INTERVAL ].isNull() ) // TODO: @deprecated
setSleepInterval( doc[ CFG_PARAM_PUSH_INTERVAL ].as<int>() ); // TODO: @deprecated
if( !doc[ CFG_PARAM_VOLTAGEFACTOR ].isNull() )
setVoltageFactor( doc[ CFG_PARAM_VOLTAGEFACTOR ].as<float>() );
if( !doc[ CFG_PARAM_GRAVITY_FORMULA ].isNull() )
setGravityFormula( doc[ CFG_PARAM_GRAVITY_FORMULA ] );
if( !doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].isNull() )
setGravityTempAdj( doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].as<bool>() );
if( !doc[ CFG_PARAM_GRAVITY_FORMAT ].isNull() ) {
String s = doc[ CFG_PARAM_GRAVITY_FORMAT ];
setGravityFormat( s.charAt(0) );
}
if( !doc[ CFG_PARAM_TEMP_ADJ ].isNull() )
setTempSensorAdj( doc[ CFG_PARAM_TEMP_ADJ ].as<float>() );
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"].isNull() )
gyroCalibration.ax = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"];
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"].isNull() )
gyroCalibration.ay = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"];
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"].isNull() )
gyroCalibration.az = doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"];
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"].isNull() )
gyroCalibration.gx = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"];
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"].isNull() )
gyroCalibration.gy = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"];
if( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"].isNull() )
gyroCalibration.gz = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"];
myConfig.debug();
saveNeeded = false; // Reset save flag
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
return true;
}
//
// Check if file system can be mounted, if not we format it.
//
void Config::formatFileSystem() {
#if LOG_LEVEL==6
Log.verbose(F("CFG : Formating filesystem." CR));
#endif
LittleFS.format();
}
//
// Check if file system can be mounted, if not we format it.
//
void Config::checkFileSystem() {
#if LOG_LEVEL==6
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
#endif
if (LittleFS.begin()) {
Log.notice(F("CFG : Filesystem mounted." CR));
} else {
Log.error(F("CFG : Unable to mount file system, formatting..." CR));
LittleFS.format();
}
}
//
// Dump the configuration to the serial port
//
void Config::debug() {
#if LOG_LEVEL==6
Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR));
Log.verbose(F("CFG : ID; '%s'." CR), getID());
Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS() );
Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval() );
Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL() );
Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat() );
Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdj() );
Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor() );
Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula() );
Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat() );
Log.verbose(F("CFG : Gravity temp adj; %s." CR), isGravityTempAdj()?"true":"false" );
Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl() );
Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl() );
Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2() );
Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR), getInfluxDb2PushUrl(), getInfluxDb2PushOrg(),
getInfluxDb2PushBucket(), getInfluxDb2PushToken() );
// Log.verbose(F("CFG : Accel offset\t%d\t%d\t%d" CR), gyroCalibration.ax, gyroCalibration.ay, gyroCalibration.az );
// Log.verbose(F("CFG : Gyro offset \t%d\t%d\t%d" CR), gyroCalibration.gx, gyroCalibration.gy, gyroCalibration.gz );
#endif
}
// EOF
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <config.hpp>
#include <main.hpp>
#include <wifi.hpp>
Config myConfig;
AdvancedConfig myAdvancedConfig;
//
// Create the config class with default settings.
//
Config::Config() {
// Assiging default values
char buf[30];
#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]);
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Created config for %s (%s)." CR), _id.c_str(),
_mDNS.c_str());
#endif
}
//
// Populate the json document with all configuration parameters (used in both
// web and saving to file)
//
void Config::createJson(DynamicJsonDocument& doc) {
doc[PARAM_MDNS] = getMDNS();
// doc[PARAM_CONFIG_VER] = getConfigVersion();
doc[PARAM_ID] = getID();
doc[PARAM_OTA] = getOtaURL();
doc[PARAM_SSID] = getWifiSSID(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(PARAM_GYRO_CALIBRATION);
cal["ax"] = _gyroCalibration.ax;
cal["ay"] = _gyroCalibration.ay;
cal["az"] = _gyroCalibration.az;
cal["gx"] = _gyroCalibration.gx;
cal["gy"] = _gyroCalibration.gy;
cal["gz"] = _gyroCalibration.gz;
JsonObject cal2 = doc.createNestedObject(PARAM_FORMULA_DATA);
cal2["a1"] = reduceFloatPrecision(_formulaData.a[0], 2);
cal2["a2"] = reduceFloatPrecision(_formulaData.a[1], 2);
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
cal2["a6"] = reduceFloatPrecision(_formulaData.a[5], 2);
cal2["a7"] = reduceFloatPrecision(_formulaData.a[6], 2);
cal2["a8"] = reduceFloatPrecision(_formulaData.a[7], 2);
cal2["a9"] = reduceFloatPrecision(_formulaData.a[8], 2);
cal2["a10"] = reduceFloatPrecision(_formulaData.a[9], 2);
cal2["g1"] = reduceFloatPrecision(_formulaData.g[0], 4);
cal2["g2"] = reduceFloatPrecision(_formulaData.g[1], 4);
cal2["g3"] = reduceFloatPrecision(_formulaData.g[2], 4);
cal2["g4"] = reduceFloatPrecision(_formulaData.g[3], 4);
cal2["g5"] = reduceFloatPrecision(_formulaData.g[4], 4);
cal2["g6"] = reduceFloatPrecision(_formulaData.g[5], 4);
cal2["g7"] = reduceFloatPrecision(_formulaData.g[6], 4);
cal2["g8"] = reduceFloatPrecision(_formulaData.g[7], 4);
cal2["g9"] = reduceFloatPrecision(_formulaData.g[8], 4);
cal2["g10"] = reduceFloatPrecision(_formulaData.g[9], 4);
}
//
// Save json document to file
//
bool Config::saveFile() {
if (!_saveNeeded) {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Skipping save, not needed." CR));
#endif
return true;
}
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Saving configuration to file." CR));
#endif
File configFile = LittleFS.open(CFG_FILENAME, "w");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to save configuration."));
return false;
}
DynamicJsonDocument doc(3000);
createJson(doc);
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
serializeJson(doc, Serial);
Serial.print(CR);
#endif
serializeJson(doc, configFile);
configFile.flush();
configFile.close();
_saveNeeded = false;
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
return true;
}
//
// Load config file from disk
//
bool Config::loadFile() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Loading configuration from file." CR));
#endif
if (!LittleFS.exists(CFG_FILENAME)) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Configuration file does not exist."));
return false;
}
File configFile = LittleFS.open(CFG_FILENAME, "r");
if (!configFile) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to load configuration."));
return false;
}
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
configFile.size());
DynamicJsonDocument doc(3000);
DeserializationError err = deserializeJson(doc, configFile);
#if LOG_LEVEL == 6
serializeJson(doc, Serial);
Serial.print(CR);
#endif
configFile.close();
if (err) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to parse configuration (json)"));
return false;
}
#if LOG_LEVEL == 6
Log.verbose(F("CFG : Parsed configuration file." CR));
#endif
if (!doc[PARAM_OTA].isNull()) setOtaURL(doc[PARAM_OTA]);
if (!doc[PARAM_MDNS].isNull()) setMDNS(doc[PARAM_MDNS]);
if (!doc[PARAM_SSID].isNull()) setWifiSSID(doc[PARAM_SSID], 0);
if (!doc[PARAM_PASS].isNull()) setWifiPass(doc[PARAM_PASS], 0);
if (!doc[PARAM_SSID2].isNull()) setWifiSSID(doc[PARAM_SSID2], 1);
if (!doc[PARAM_PASS2].isNull()) setWifiPass(doc[PARAM_PASS2], 1);
if (!doc[PARAM_BLE].isNull()) setColorBLE(doc[PARAM_BLE]);
if (!doc[PARAM_TEMPFORMAT].isNull()) {
String s = doc[PARAM_TEMPFORMAT];
setTempFormat(s.charAt(0));
}
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[PARAM_PUSH_INFLUXDB2].isNull())
setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]);
if (!doc[PARAM_PUSH_INFLUXDB2_ORG].isNull())
setInfluxDb2PushOrg(doc[PARAM_PUSH_INFLUXDB2_ORG]);
if (!doc[PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
setInfluxDb2PushBucket(doc[PARAM_PUSH_INFLUXDB2_BUCKET]);
if (!doc[PARAM_PUSH_INFLUXDB2_AUTH].isNull())
setInfluxDb2PushToken(doc[PARAM_PUSH_INFLUXDB2_AUTH]);
if (!doc[PARAM_PUSH_MQTT].isNull()) setMqttUrl(doc[PARAM_PUSH_MQTT]);
if (!doc[PARAM_PUSH_MQTT_PORT].isNull())
setMqttPort(doc[PARAM_PUSH_MQTT_PORT].as<int>());
if (!doc[PARAM_PUSH_MQTT_USER].isNull())
setMqttUser(doc[PARAM_PUSH_MQTT_USER]);
if (!doc[PARAM_PUSH_MQTT_PASS].isNull())
setMqttPass(doc[PARAM_PUSH_MQTT_PASS]);
if (!doc[PARAM_SLEEP_INTERVAL].isNull())
setSleepInterval(doc[PARAM_SLEEP_INTERVAL].as<int>());
if (!doc[PARAM_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[PARAM_TEMP_ADJ].isNull())
setTempSensorAdjC(doc[PARAM_TEMP_ADJ].as<float>());
if (!doc[PARAM_GYRO_CALIBRATION]["ax"].isNull())
_gyroCalibration.ax = doc[PARAM_GYRO_CALIBRATION]["ax"];
if (!doc[PARAM_GYRO_CALIBRATION]["ay"].isNull())
_gyroCalibration.ay = doc[PARAM_GYRO_CALIBRATION]["ay"];
if (!doc[PARAM_GYRO_CALIBRATION]["az"].isNull())
_gyroCalibration.az = doc[PARAM_GYRO_CALIBRATION]["az"];
if (!doc[PARAM_GYRO_CALIBRATION]["gx"].isNull())
_gyroCalibration.gx = doc[PARAM_GYRO_CALIBRATION]["gx"];
if (!doc[PARAM_GYRO_CALIBRATION]["gy"].isNull())
_gyroCalibration.gy = doc[PARAM_GYRO_CALIBRATION]["gy"];
if (!doc[PARAM_GYRO_CALIBRATION]["gz"].isNull())
_gyroCalibration.gz = doc[PARAM_GYRO_CALIBRATION]["gz"];
if (!doc[PARAM_FORMULA_DATA]["a1"].isNull())
_formulaData.a[0] = doc[PARAM_FORMULA_DATA]["a1"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a2"].isNull())
_formulaData.a[1] = doc[PARAM_FORMULA_DATA]["a2"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a3"].isNull())
_formulaData.a[2] = doc[PARAM_FORMULA_DATA]["a3"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a4"].isNull())
_formulaData.a[3] = doc[PARAM_FORMULA_DATA]["a4"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a5"].isNull())
_formulaData.a[4] = doc[PARAM_FORMULA_DATA]["a5"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a6"].isNull())
_formulaData.a[5] = doc[PARAM_FORMULA_DATA]["a6"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a7"].isNull())
_formulaData.a[6] = doc[PARAM_FORMULA_DATA]["a7"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a8"].isNull())
_formulaData.a[7] = doc[PARAM_FORMULA_DATA]["a8"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a9"].isNull())
_formulaData.a[8] = doc[PARAM_FORMULA_DATA]["a9"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["a10"].isNull())
_formulaData.a[9] = doc[PARAM_FORMULA_DATA]["a10"].as<double>();
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
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>();
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
// If this parameter is missing we need to reset the gyrocalibaration due to
bug #29 _gyroCalibration.ax = _gyroCalibration.ay = _gyroCalibration.az = 0;
_gyroCalibration.gx = _gyroCalibration.gy = _gyroCalibration.gz = 0;
Log.warning(F("CFG : Old configuration format, clearing gyro calibration."
CR));
}*/
_saveNeeded = false; // Reset save flag
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
return true;
}
//
// Check if file system can be mounted, if not we format it.
//
void Config::formatFileSystem() {
Log.notice(F("CFG : Formating filesystem." CR));
LittleFS.format();
}
//
// Check if file system can be mounted, if not we format it.
//
void Config::checkFileSystem() {
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
Log.verbose(F("CFG : Checking if filesystem is valid." CR));
#endif
if (LittleFS.begin()) {
Log.notice(F("CFG : Filesystem mounted." CR));
} else {
Log.error(F("CFG : Unable to mount file system, formatting..." CR));
LittleFS.format();
}
}
//
// Save json document to file
//
bool 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) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to write hardware configuration "));
return false;
}
DynamicJsonDocument doc(512);
doc[PARAM_HW_GYRO_READ_COUNT] = this->getGyroReadCount();
// doc[PARAM_HW_GYRO_READ_DELAY] = this->getGyroReadDelay();
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] = this->getGyroSensorMovingThreashold();
doc[PARAM_HW_FORMULA_DEVIATION] = this->getMaxFormulaCreationDeviation();
doc[PARAM_HW_WIFI_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) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to read hardware configuration "));
return false;
}
Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
configFile.size());
DynamicJsonDocument doc(512);
DeserializationError err = deserializeJson(doc, configFile);
#if LOG_LEVEL == 6
serializeJson(doc, Serial);
Serial.print(CR);
#endif
configFile.close();
if (err) {
ErrorFileLog errLog;
errLog.addEntry(F("CFG : Failed to parse hardware configuration (json)"));
return false;
}
#if LOG_LEVEL == 6
Log.verbose(F("CFG : Parsed hardware configuration file." CR));
#endif
if (!doc[PARAM_HW_GYRO_READ_COUNT].isNull())
this->setGyroReadCount(doc[PARAM_HW_GYRO_READ_COUNT].as<int>());
// if (!doc[PARAM_HW_GYRO_READ_DELAY].isNull())
// this->setGyroReadDelay(doc[PARAM_HW_GYRO_READ_DELAY].as<int>());
if (!doc[PARAM_HW_GYRO_MOVING_THREASHOLD].isNull())
this->setGyroSensorMovingThreashold(
doc[PARAM_HW_GYRO_MOVING_THREASHOLD].as<int>());
if (!doc[PARAM_HW_FORMULA_DEVIATION].isNull())
this->setMaxFormulaCreationDeviation(
doc[PARAM_HW_FORMULA_DEVIATION].as<float>());
if (!doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].isNull())
this->SetDefaultCalibrationTemp(
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].as<float>());
if (!doc[PARAM_HW_WIFI_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

View File

@ -1,206 +0,0 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _CONFIG_H
#define _CONFIG_H
// Includes
#include "helper.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <stdlib.h>
// defintions
#define CFG_JSON_BUFSIZE 2000
#define CFG_APPNAME "GravityMon " // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
#define WIFI_PORTAL_TIMEOUT 120 // Number of seconds until the config portal is closed
// These are used in API + Savefile
#define CFG_PARAM_ID "id"
#define CFG_PARAM_MDNS "mdns" // Device name
#define CFG_PARAM_OTA "ota-url" // Base URL for OTA
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push" // URL (brewfather format)
#define CFG_PARAM_PUSH_HTTP "http-push" // URL (iSpindle format)
#define CFG_PARAM_PUSH_HTTP2 "http-push2" // URL (iSpindle format)
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth" // URL
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
// TODO: @deprecated setting
#define CFG_PARAM_PUSH_INTERVAL "push-interval" // Time between push
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
#define CFG_PARAM_VOLTAGEFACTOR "voltage-factor" // Factor to calculate the battery voltage
#define CFG_PARAM_GRAVITY_FORMULA "gravity-formula" // Formula for calculating gravity
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
#define CFG_PARAM_GRAVITY_TEMP_ADJ "gravity-temp-adjustment" // True/False. Adjust gravity for temperature
#define CFG_PARAM_TEMP_ADJ "temp-adjustment-value" // Correction value for temp sensor
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
// These are used in API's
#define CFG_PARAM_APP_NAME "app-name"
#define CFG_PARAM_APP_VER "app-ver"
#define CFG_PARAM_ANGLE "angle"
#define CFG_PARAM_GRAVITY "gravity"
#define CFG_PARAM_TEMP_C "temp-c"
#define CFG_PARAM_TEMP_F "temp-f"
#define CFG_PARAM_BATTERY "battery"
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
#define CFG_PARAM_RSSI "rssi"
// Used for holding sensordata or sensoroffsets
struct RawGyroData {
int16_t ax; // Raw Acceleration
int16_t ay;
int16_t az;
int16_t gx; // Raw Position
int16_t gy;
int16_t gz;
int16_t temp; // Only for information (temperature of chip)
};
// Main configuration class
class Config {
private:
bool saveNeeded;
// Device configuration
String id;
String mDNS;
String otaURL;
char tempFormat; // C, F
float voltageFactor;
float tempSensorAdj; // This value will be added to the read sensor value
int sleepInterval;
// Push target settings
String brewfatherPushUrl; // URL For brewfather
String httpPushUrl; // URL 1 for standard http
String httpPushUrl2; // URL 2 for standard http
String influxDb2Url; // URL for InfluxDB v2
String influxDb2Org; // Organisation for InfluxDB v2
String influxDb2Bucket; // Bucket for InfluxDB v2
String influxDb2Token; // Auth Token for InfluxDB v2
// Gravity and temperature calculations
String gravityFormula;
bool gravityTempAdj; // true, false
char gravityFormat; // G, P
// Gyro calibration data
RawGyroData gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
void debug();
void formatFileSystem();
public:
Config();
const char* getID() { return id.c_str(); };
const char* getMDNS() { return mDNS.c_str(); }
void setMDNS( String s ) { mDNS = s; saveNeeded = true; }
const char* getOtaURL() { return otaURL.c_str(); }
void setOtaURL( String s ) { otaURL = s; saveNeeded = true; }
bool isOtaActive() { return otaURL.length()?true:false; }
// Brewfather
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
void setBrewfatherPushUrl( String s ) { brewfatherPushUrl = s; saveNeeded = true; }
bool isBrewfatherActive() { return brewfatherPushUrl.length()?true:false; }
// Standard HTTP
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
void setHttpPushUrl( String s ) { httpPushUrl = s; saveNeeded = true; }
bool isHttpActive() { return httpPushUrl.length()?true:false; }
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
void setHttpPushUrl2( String s ) { httpPushUrl2 = s; saveNeeded = true; }
bool isHttpActive2() { return httpPushUrl2.length()?true:false; }
// InfluxDB2
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
void setInfluxDb2PushUrl( String s ) { influxDb2Url = s; saveNeeded = true; }
bool isInfluxDb2Active() { return influxDb2Url.length()?true:false; }
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
void setInfluxDb2PushOrg( String s ) { influxDb2Org = s; saveNeeded = true; }
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
void setInfluxDb2PushBucket( String s ) { influxDb2Bucket = s; saveNeeded = true; }
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
void setInfluxDb2PushToken( String s ) { influxDb2Token = s; saveNeeded = true; }
int getSleepInterval() { return sleepInterval; }
void setSleepInterval( int v ) { sleepInterval = v; saveNeeded = true; }
void setSleepInterval( String s ) { sleepInterval = s.toInt(); saveNeeded = true; }
char getTempFormat() { return tempFormat; }
void setTempFormat( char c ) { tempFormat = c; saveNeeded = true; }
bool isTempC() { return tempFormat=='C'?false:true; };
bool isTempF() { return tempFormat=='F'?false:true; };
float getVoltageFactor() { return voltageFactor; }
void setVoltageFactor( float f ) { voltageFactor = f; saveNeeded = true; }
void setVoltageFactor( String s ) { voltageFactor = s.toFloat(); saveNeeded = true; }
float getTempSensorAdj() { return tempSensorAdj; }
void setTempSensorAdj( float f ) { tempSensorAdj = f; saveNeeded = true; }
void setTempSensorAdj( String s ) { tempSensorAdj = s.toFloat(); saveNeeded = true; }
const char* getGravityFormula() { return gravityFormula.c_str(); }
void setGravityFormula( String s ) { gravityFormula = s; saveNeeded = true; }
bool isGravityTempAdj() { return gravityTempAdj; }
void setGravityTempAdj( bool b ) { gravityTempAdj = b; saveNeeded = true; }
char getGravityFormat() { return gravityFormat; }
void setGravityFormat( char c ) { gravityFormat = c; saveNeeded = true; }
bool isGravitySG() { return gravityFormat=='G'?false:true; };
bool isGravityPlato() { return gravityFormat=='P'?false:true; };
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
void setGyroCalibration( const RawGyroData &r ) { gyroCalibration = r; saveNeeded = true; }
// IO functions
void createJson(DynamicJsonDocument& doc);
bool saveFile();
bool loadFile();
void checkFileSystem();
bool isSaveNeeded() { return saveNeeded; };
void setSaveNeeded() { saveNeeded = true; };
};
// Global instance created
extern Config myConfig;
#endif // _CONFIG_H
// EOF

465
src/config.hpp Normal file
View File

@ -0,0 +1,465 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SRC_CONFIG_HPP_
#define SRC_CONFIG_HPP_
#include <helper.hpp>
#include <resources.hpp>
#define CFG_APPNAME "GravityMon" // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file
#define CFG_HW_FILENAME "/hardware.json" // Name of config file for hw
// Used for holding sensordata or sensoroffsets
struct RawGyroData {
int16_t ax; // Raw Acceleration
int16_t ay;
int16_t az;
int16_t gx; // Raw Position
int16_t gy;
int16_t gz;
int16_t temp; // Only for information (temperature of chip)
};
// Used for holding formulaData (used for calculating formula on device)
#define FORMULA_DATA_SIZE 10
struct RawFormulaData {
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();
};
class Config {
private:
bool _saveNeeded = false;
int _configVersion = 2;
// Device configuration
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[2] = {"", ""};
String _wifiPASS[2] = {"", ""};
// Push target settings
String _token = "";
String _token2 = "";
String _httpUrl = "";
String _httpHeader[2] = {"Content-Type: application/json", ""};
String _http2Url = "";
String _http2Header[2] = {"Content-Type: application/json", ""};
String _http3Url = "";
String _influxDb2Url = "";
String _influxDb2Org = "";
String _influxDb2Bucket = "";
String _influxDb2Token = "";
String _mqttUrl = "";
int _mqttPort = 1883;
String _mqttUser = "";
String _mqttPass = "";
// Gravity and temperature calculations
String _gravityFormula = "";
bool _gravityTempAdj = false;
char _gravityFormat = 'G';
// 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 formatFileSystem();
public:
Config();
const char* getID() { return _id.c_str(); }
const char* getMDNS() { return _mDNS.c_str(); }
void setMDNS(String s) {
_mDNS = s;
_saveNeeded = true;
}
int getConfigVersion() { return _configVersion; }
const bool isGyroTemp() { return _gyroTemp; }
void setGyroTemp(bool b) {
_gyroTemp = b;
_saveNeeded = true;
}
const 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;
}
bool isOtaActive() { return _otaURL.length() ? true : false; }
bool isOtaSSL() { return _otaURL.startsWith("https://"); }
const char* getWifiSSID(int idx) { return _wifiSSID[idx].c_str(); }
void setWifiSSID(String s, int idx) {
_wifiSSID[idx] = 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;
}
// Token parameter
const char* getToken() { return _token.c_str(); }
void setToken(String s) {
_token = s;
_saveNeeded = true;
}
const char* getToken2() { return _token2.c_str(); }
void setToken2(String s) {
_token2 = s;
_saveNeeded = true;
}
// Standard HTTP
const char* getHttpUrl() { return _httpUrl.c_str(); }
void setHttpUrl(String s) {
_httpUrl = s;
_saveNeeded = true;
}
const char* getHttpHeader(int idx) { return _httpHeader[idx].c_str(); }
void setHttpHeader(String s, int idx) {
_httpHeader[idx] = s;
_saveNeeded = true;
}
bool isHttpActive() { return _httpUrl.length() ? true : false; }
bool isHttpSSL() { return _httpUrl.startsWith("https://"); }
const char* getHttp2Url() { return _http2Url.c_str(); }
void setHttp2Url(String s) {
_http2Url = s;
_saveNeeded = true;
}
const char* getHttp2Header(int idx) { return _http2Header[idx].c_str(); }
void setHttp2Header(String s, int idx) {
_http2Header[idx] = s;
_saveNeeded = true;
}
bool isHttp2Active() { return _http2Url.length() ? true : false; }
bool isHttp2SSL() { return _http2Url.startsWith("https://"); }
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(); }
void setInfluxDb2PushUrl(String s) {
_influxDb2Url = s;
_saveNeeded = true;
}
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
bool isInfluxSSL() { return _influxDb2Url.startsWith("https://"); }
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
void setInfluxDb2PushOrg(String s) {
_influxDb2Org = s;
_saveNeeded = true;
}
const char* getInfluxDb2PushBucket() { return _influxDb2Bucket.c_str(); }
void setInfluxDb2PushBucket(String s) {
_influxDb2Bucket = s;
_saveNeeded = true;
}
const char* getInfluxDb2PushToken() { return _influxDb2Token.c_str(); }
void setInfluxDb2PushToken(String s) {
_influxDb2Token = s;
_saveNeeded = true;
}
// MQTT
const char* getMqttUrl() { return _mqttUrl.c_str(); }
void setMqttUrl(String s) {
_mqttUrl = s;
_saveNeeded = true;
}
bool isMqttActive() { return _mqttUrl.length() ? true : false; }
bool isMqttSSL() { return _mqttPort > 8000 ? true : false; }
int getMqttPort() { return _mqttPort; }
void setMqttPort(String s) {
_mqttPort = s.toInt();
_saveNeeded = true;
}
void setMqttPort(int i) {
_mqttPort = i;
_saveNeeded = true;
}
const char* getMqttUser() { return _mqttUser.c_str(); }
void setMqttUser(String s) {
_mqttUser = s;
_saveNeeded = true;
}
const char* getMqttPass() { return _mqttPass.c_str(); }
void setMqttPass(String s) {
_mqttPass = s;
_saveNeeded = true;
}
int getSleepInterval() { return _sleepInterval; }
void setSleepInterval(int v) {
_sleepInterval = v;
_saveNeeded = true;
}
void setSleepInterval(String s) {
_sleepInterval = s.toInt();
_saveNeeded = true;
}
char getTempFormat() { return _tempFormat; }
void setTempFormat(char c) {
if (c == 'C' || c == 'F') {
_tempFormat = c;
_saveNeeded = true;
}
}
bool isTempC() { return _tempFormat == 'C'; }
bool isTempF() { return _tempFormat == 'F'; }
float getVoltageFactor() { return _voltageFactor; }
void setVoltageFactor(float f) {
_voltageFactor = f;
_saveNeeded = true;
}
void setVoltageFactor(String s) {
_voltageFactor = s.toFloat();
_saveNeeded = true;
}
float getVoltageConfig() { return _voltageConfig; }
void setVoltageConfig(float f) {
_voltageConfig = f;
_saveNeeded = true;
}
void setVoltageConfig(String s) {
_voltageConfig = s.toFloat();
_saveNeeded = true;
}
float getTempSensorAdjC() { return _tempSensorAdjC; }
void setTempSensorAdjC(float f) {
_tempSensorAdjC = f;
_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;
}
bool isGravityTempAdj() { return _gravityTempAdj; }
void setGravityTempAdj(bool b) {
_gravityTempAdj = b;
_saveNeeded = true;
}
char getGravityFormat() { return _gravityFormat; }
void setGravityFormat(char c) {
if (c == 'G' || c == 'P') {
_gravityFormat = c;
_saveNeeded = true;
}
}
bool isGravitySG() { return _gravityFormat == 'G'; }
bool isGravityPlato() { return _gravityFormat == 'P'; }
const 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;
}
const RawFormulaData& getFormulaData() { return _formulaData; }
void setFormulaData(const RawFormulaData& r) {
_formulaData = r;
_saveNeeded = true;
}
// IO functions
void createJson(DynamicJsonDocument& doc);
bool saveFile();
bool loadFile();
void checkFileSystem();
bool isSaveNeeded() { return _saveNeeded; }
void setSaveNeeded() { _saveNeeded = true; }
};
extern Config myConfig;
extern AdvancedConfig myAdvancedConfig;
#endif // SRC_CONFIG_HPP_
// EOF

View File

@ -1,359 +1,405 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "gyro.h"
#include "helper.h"
GyroSensor myGyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define SENSOR_MOVING_THREASHOLD 500
#define SENSOR_READ_COUNT 50
#define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing calibration
//#define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
// Initialize the sensor chip.
//
bool GyroSensor::setup() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
Wire.begin(D3, D4);
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
accelgyro.initialize();
if( !accelgyro.testConnection() ) {
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false;
} else {
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true);
//accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in .initalize()
//accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); // Set in .initalize()
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
#if defined( GYRO_USE_INTERRUPT )
// Alternative method to read data, let the MPU signal when sampling is done.
accelgyro.setRate(17);
accelgyro.setInterruptDrive(1);
accelgyro.setInterruptMode(1);
accelgyro.setInterruptLatch(0);
accelgyro.setIntDataReadyEnabled(true);
#endif
#if defined ( GYRO_CALIBRATE_STARTUP )
// Run the calibration at start, useful for testing.
calibrateSensor();
#endif
// Once we have calibration values stored we just apply them from the config.
calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
}
return sensorConnected;
}
//
// Set sensor in sleep mode to conserve battery
//
void GyroSensor::enterSleep() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
accelgyro.setSleepEnabled( true );
}
//
// Do a number of reads to get a more stable value.
//
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations, const int delayTime) {
RawGyroDataL average = { 0, 0, 0, 0, 0, 0 };
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR), noIterations, delayTime );
#endif
// Set some initial values
#if defined( GYRO_SHOW_MINMAX )
RawGyroData min, max;
//accelgyro.getRotation( &min.gx, &min.gy, &min.gz );
accelgyro.getAcceleration( &min.ax, &min.ay, &min.az );
min.temp = accelgyro.getTemperature();
max = min;
#endif
for(int cnt = 0; cnt < noIterations ; cnt ++) {
#if defined( GYRO_USE_INTERRUPT )
while( accelgyro.getIntDataReadyStatus() == 0) {
delayMicroseconds( 1 );
}
#endif
//accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz );
//accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az );
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
raw.temp = accelgyro.getTemperature();
average.ax += raw.ax;
average.ay += raw.ay;
average.az += raw.az;
average.gx += raw.gx;
average.gy += raw.gy;
average.gz += raw.gz;
average.temp += raw.temp;
// Log what the minium value is
#if defined( GYRO_SHOW_MINMAX )
if( raw.ax < min.ax ) min.ax = raw.ax;
if( raw.ay < min.ay ) min.ay = raw.ay;
if( raw.az < min.az ) min.az = raw.az;
if( raw.gx < min.gx ) min.gx = raw.gx;
if( raw.gy < min.gy ) min.gy = raw.gy;
if( raw.gz < min.gz ) min.gz = raw.gz;
if( raw.temp < min.temp ) min.temp = raw.temp;
// Log what the maximum value is
if( raw.ax > max.ax ) max.ax = raw.ax;
if( raw.ay > max.ay ) max.ay = raw.ay;
if( raw.az > max.az ) max.az = raw.az;
if( raw.gx > max.gx ) max.gx = raw.gx;
if( raw.gy > max.gy ) max.gy = raw.gy;
if( raw.gz > max.gz ) max.gz = raw.gz;
if( raw.temp > max.temp ) max.temp = raw.temp;
#endif
#if !defined( GYRO_USE_INTERRUPT )
delayMicroseconds( delayTime );
#endif
}
raw.ax = average.ax/noIterations;
raw.ay = average.ay/noIterations;
raw.az = average.az/noIterations;
raw.gx = average.gx/noIterations;
raw.gy = average.gy/noIterations;
raw.gz = average.gz/noIterations;
raw.temp = average.temp/noIterations;
#if LOG_LEVEL==6
#if defined( GYRO_SHOW_MINMAX )
Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax, min.ay, min.az, min.gx, min.gy, min.gz, min.temp );
Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax, max.ay, max.az, max.gx, max.gy, max.gz, max.temp );
#endif
Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax, raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp );
//Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR), average.ax/noIterations, average.ay/noIterations, average.az/noIterations,
// average.gx/noIterations, average.gy/noIterations, average.gz/noIterations );
#endif
}
//
// Calcuate the angles (tilt)
//
float GyroSensor::calculateAngle(RawGyroData &raw) {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Calculating the angle." CR) );
#endif
// Smooth out the readings to we can have a more stable angle/tilt.
// ------------------------------------------------------------------------------------------------------------
// Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of 16,384 LSB(Count)/g.
// Gyroscope full scale range of +/- 250 °/s with Sensitivity Scale Factor of 131 LSB (Count)/°/s.
float ax = ((float) raw.ax)/16384,
ay = ((float) raw.ay)/16384,
az = ((float) raw.az)/16384;
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
float v = (acos( ay / sqrt( ax*ax + ay*ay + az*az ) ) *180.0 / PI);
//Log.notice(F("GYRO: angle = %F." CR), v );
//double v = (acos( raw.az / sqrt( raw.ax*raw.ax + raw.ay*raw.ay + raw.az*raw.az ) ) *180.0 / PI);
//Log.notice(F("GYRO: angle = %F." CR), v );
#if LOG_LEVEL==6
Log.verbose(F("GYRO: angle = %F." CR), v );
#endif
return v;
}
//
// Check if the values are high that indicate that the sensor is moving.
//
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Checking for sensor movement." CR) );
#endif
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
if( x>SENSOR_MOVING_THREASHOLD || y>SENSOR_MOVING_THREASHOLD || z>SENSOR_MOVING_THREASHOLD ) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), SENSOR_MOVING_THREASHOLD, x, y, z);
return true;
}
return false;
}
//
// Read the tilt angle from the gyro.
//
bool GyroSensor::read() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Getting new gyro position." CR) );
#endif
readSensor( lastGyroData, SENSOR_READ_COUNT, SENSOR_READ_DELAY ); // Last param is unused if GYRO_USE_INTERRUPT is defined.
// If the sensor is unstable we return false to signal we dont have valid value
if( isSensorMoving(lastGyroData) ) {
Log.notice(F("GYRO: Sensor is moving." CR) );
validValue = false;
} else {
validValue = true;
angle = calculateAngle( lastGyroData );
//Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), raw.ax, raw.ay, raw.az, angle );
}
sensorTemp = ((float) lastGyroData.temp) / 340 + 36.53;
// The first read value is close to the DS18 value according to my tests, if more reads are
// done then the gyro temp will increase to much
if( initialSensorTemp == INVALID_TEMPERATURE )
initialSensorTemp = sensorTemp;
return validValue;
}
//
// Dump the stored calibration values.
//
void GyroSensor::dumpCalibration() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax, calibrationOffset.ay, calibrationOffset.az );
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx, calibrationOffset.gy, calibrationOffset.gz );
#endif
}
//
// Update the sensor with out calculated offsets.
//
void GyroSensor::applyCalibration() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR) );
#endif
if( ( calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az + calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz ) == 0 ) {
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR) );
return;
}
accelgyro.setXAccelOffset( calibrationOffset.ax );
accelgyro.setYAccelOffset( calibrationOffset.ay );
accelgyro.setZAccelOffset( calibrationOffset.az );
accelgyro.setXGyroOffset( calibrationOffset.gx );
accelgyro.setYGyroOffset( calibrationOffset.gy );
accelgyro.setZGyroOffset( calibrationOffset.gz );
}
//
// Calculate the offsets for calibration.
//
void GyroSensor::calibrateSensor() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Calibrating sensor" CR) );
#endif
//accelgyro.PrintActiveOffsets();
//Serial.print( CR );
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
accelgyro.CalibrateAccel(6); // 6 = 600 readings
accelgyro.CalibrateGyro(6);
accelgyro.PrintActiveOffsets();
Serial.print( CR );
calibrationOffset.ax = accelgyro.getXAccelOffset();
calibrationOffset.ay = accelgyro.getYAccelOffset();
calibrationOffset.az = accelgyro.getZAccelOffset();
calibrationOffset.gx = accelgyro.getXGyroOffset();
calibrationOffset.gy = accelgyro.getYGyroOffset();
calibrationOffset.gz = accelgyro.getZGyroOffset();
// Save the calibrated values
myConfig.setGyroCalibration( calibrationOffset );
myConfig.saveFile();
}
//
// Calibrate the device.
//
void GyroSensor::debug() {
#if LOG_LEVEL==6
Log.verbose(F("GYRO: Debug - Clock src %d." CR), accelgyro.getClockSource() );
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID() );
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode() );
Log.verbose(F("GYRO: Debug - DMP on %s." CR), accelgyro.getDMPEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Acc range %d." CR), accelgyro.getFullScaleAccelRange() );
Log.verbose(F("GYRO: Debug - Gyr range %d." CR), accelgyro.getFullScaleGyroRange() );
Log.verbose(F("GYRO: Debug - Int %s." CR), accelgyro.getIntEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Clock %d." CR), accelgyro.getMasterClockSpeed() );
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate() );
Log.verbose(F("GYRO: Debug - Gyro range %d." CR), accelgyro.getFullScaleGyroRange() );
// Log.verbose(F("GYRO: Debug - I2C bypass %s." CR), accelgyro.getI2CBypassEnabled()?"on":"off" );
// Log.verbose(F("GYRO: Debug - I2C master %s." CR), accelgyro.getI2CMasterModeEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR), accelgyro.getAccelXSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Acc FactY %d." CR), accelgyro.getAccelYSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR), accelgyro.getAccelZSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR), accelgyro.getGyroXSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR), accelgyro.getGyroYSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR), accelgyro.getGyroZSelfTestFactoryTrim() );
switch( accelgyro.getFullScaleAccelRange() ) {
case 0:
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR));
break;
case 1:
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR));
break;
case 2:
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR));
break;
case 3:
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR));
break;
}
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR), accelgyro.getXAccelOffset(), calibrationOffset.az );
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR), accelgyro.getYAccelOffset(), calibrationOffset.ay );
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR), accelgyro.getZAccelOffset(), calibrationOffset.az );
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR), accelgyro.getXGyroOffset(), calibrationOffset.gx );
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR), accelgyro.getYGyroOffset(), calibrationOffset.gy );
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR), accelgyro.getZGyroOffset(), calibrationOffset.gz );
#endif
}
// EOF
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <gyro.hpp>
#include <main.hpp>
GyroSensor myGyro;
MPU6050 accelgyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
// calibration
// #define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
// Initialize the sensor chip.
//
bool GyroSensor::setup() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
Wire.begin(PIN_SDA, PIN_SCL);
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
// compilation difficulties
uint8_t id = accelgyro.getDeviceID();
if (id != 0x34 && id != 0x38) { // Allow both MPU6050 and MPU6000
ErrorFileLog errLog;
errLog.addEntry(F("GYRO: Failed to connect to gyro, is it connected?"));
_sensorConnected = false;
} else {
#if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
#endif
accelgyro.initialize();
_sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true);
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
#if defined(GYRO_USE_INTERRUPT)
// Alternative method to read data, let the MPU signal when sampling is
// done.
accelgyro.setRate(17);
accelgyro.setInterruptDrive(1);
accelgyro.setInterruptMode(1);
accelgyro.setInterruptLatch(0);
accelgyro.setIntDataReadyEnabled(true);
#endif
#if defined(GYRO_CALIBRATE_STARTUP)
// Run the calibration at start, useful for testing.
calibrateSensor();
#endif
// Once we have calibration values stored we just apply them from the
// config.
_calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
}
return _sensorConnected;
}
//
// Set sensor in sleep mode to conserve battery
//
void GyroSensor::enterSleep() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
accelgyro.setSleepEnabled(true);
}
//
// Do a number of reads to get a more stable value.
//
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations,
const int delayTime) {
RawGyroDataL average = {0, 0, 0, 0, 0, 0};
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR),
noIterations, delayTime);
#endif
// Set some initial values
#if defined(GYRO_SHOW_MINMAX)
RawGyroData min, max;
accelgyro.getAcceleration(&min.ax, &min.ay, &min.az);
min.temp = accelgyro.getTemperature();
max = min;
#endif
for (int cnt = 0; cnt < noIterations; cnt++) {
#if defined(GYRO_USE_INTERRUPT)
while (accelgyro.getIntDataReadyStatus() == 0) {
delayMicroseconds(1);
}
#endif
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
raw.temp = accelgyro.getTemperature();
average.ax += raw.ax;
average.ay += raw.ay;
average.az += raw.az;
average.gx += raw.gx;
average.gy += raw.gy;
average.gz += raw.gz;
average.temp += raw.temp;
// Log what the minium value is
#if defined(GYRO_SHOW_MINMAX)
if (raw.ax < min.ax) min.ax = raw.ax;
if (raw.ay < min.ay) min.ay = raw.ay;
if (raw.az < min.az) min.az = raw.az;
if (raw.gx < min.gx) min.gx = raw.gx;
if (raw.gy < min.gy) min.gy = raw.gy;
if (raw.gz < min.gz) min.gz = raw.gz;
if (raw.temp < min.temp) min.temp = raw.temp;
// Log what the maximum value is
if (raw.ax > max.ax) max.ax = raw.ax;
if (raw.ay > max.ay) max.ay = raw.ay;
if (raw.az > max.az) max.az = raw.az;
if (raw.gx > max.gx) max.gx = raw.gx;
if (raw.gy > max.gy) max.gy = raw.gy;
if (raw.gz > max.gz) max.gz = raw.gz;
if (raw.temp > max.temp) max.temp = raw.temp;
#endif
#if !defined(GYRO_USE_INTERRUPT)
delayMicroseconds(delayTime);
#endif
}
raw.ax = average.ax / noIterations;
raw.ay = average.ay / noIterations;
raw.az = average.az / noIterations;
raw.gx = average.gx / noIterations;
raw.gy = average.gy / noIterations;
raw.gz = average.gz / noIterations;
raw.temp = average.temp / noIterations;
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
#if defined(GYRO_SHOW_MINMAX)
Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax,
min.ay, min.az, min.gx, min.gy, min.gz, min.temp);
Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax,
max.ay, max.az, max.gx, max.gy, max.gz, max.temp);
#endif
Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax,
raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp);
// Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR),
// average.ax/noIterations, average.ay/noIterations, average.az/noIterations,
// average.gx/noIterations,
// average.gy/noIterations,
// average.gz/noIterations
// );
#endif
}
//
// Calcuate the angles (tilt)
//
float GyroSensor::calculateAngle(RawGyroData &raw) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Calculating the angle." CR));
#endif
// Smooth out the readings to we can have a more stable angle/tilt.
// ------------------------------------------------------------------------------------------------------------
// Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of
// 16,384 LSB(Count)/g. Gyroscope full scale range of +/- 250 °/s with
// Sensitivity Scale Factor of 131 LSB (Count)/°/s.
float ax = (static_cast<float>(raw.ax)) / 16384,
ay = (static_cast<float>(raw.ay)) / 16384,
az = (static_cast<float>(raw.az)) / 16384;
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
float vY = (acos(abs(ay) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
// float vZ = (acos(abs(az) / sqrt(ax * ax + ay * ay + az * az)) * 180.0 /
// PI); float vX = (acos(abs(ax) / sqrt(ax * ax + ay * ay + az * az)) * 180.0
// / PI);
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
// Log.notice(F("GYRO: angleX= %F." CR), vX);
Log.notice(F("GYRO: angleY= %F." CR), vY);
// Log.notice(F("GYRO: angleZ= %F." CR), vZ);
#endif
return vY;
}
//
// Check if the values are high that indicate that the sensor is moving.
//
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Checking for sensor movement." CR));
#endif
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
int threashold = myAdvancedConfig.getGyroSensorMovingThreashold();
if (x > threashold || y > threashold || z > threashold) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
y, z);
return true;
}
return false;
}
//
// Read the tilt angle from the gyro.
//
bool GyroSensor::read() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Getting new gyro position." CR));
#endif
if (!_sensorConnected) return false;
readSensor(
_lastGyroData, 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 LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Sensor is moving." CR));
#endif
_validValue = false;
} else {
_validValue = true;
_angle = calculateAngle(_lastGyroData);
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Sensor values %d,%d,%d\t%F" CR), _lastGyroData.ax,
_lastGyroData.ay, _lastGyroData.az, _angle);
#endif
}
_sensorTemp = (static_cast<float>(_lastGyroData.temp)) / 340 + 36.53;
// The first read value is close to the DS18 value according to my tests, if
// more reads are done then the gyro temp will increase to much
if (_initialSensorTemp == INVALID_TEMPERATURE)
_initialSensorTemp = _sensorTemp;
return _validValue;
}
//
// Dump the stored calibration values.
//
void GyroSensor::dumpCalibration() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), _calibrationOffset.ax,
_calibrationOffset.ay, _calibrationOffset.az);
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), _calibrationOffset.gx,
_calibrationOffset.gy, _calibrationOffset.gz);
#endif
}
//
// Update the sensor with out calculated offsets.
//
void GyroSensor::applyCalibration() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR));
#endif
if ((_calibrationOffset.ax + _calibrationOffset.ay + _calibrationOffset.az +
_calibrationOffset.gx + _calibrationOffset.gy + _calibrationOffset.gz) ==
0) {
ErrorFileLog errLog;
errLog.addEntry(
F("GYRO: No valid calibration values, please calibrate the device."));
return;
}
accelgyro.setXAccelOffset(_calibrationOffset.ax);
accelgyro.setYAccelOffset(_calibrationOffset.ay);
accelgyro.setZAccelOffset(_calibrationOffset.az);
accelgyro.setXGyroOffset(_calibrationOffset.gx);
accelgyro.setYGyroOffset(_calibrationOffset.gy);
accelgyro.setZGyroOffset(_calibrationOffset.gz);
}
//
// Calculate the offsets for calibration.
//
void GyroSensor::calibrateSensor() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Calibrating sensor" CR));
#endif
// accelgyro.PrintActiveOffsets();
// Serial.print( CR );
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
accelgyro.CalibrateAccel(6); // 6 = 600 readings
accelgyro.CalibrateGyro(6);
accelgyro.PrintActiveOffsets();
Serial.print(CR);
_calibrationOffset.ax = accelgyro.getXAccelOffset();
_calibrationOffset.ay = accelgyro.getYAccelOffset();
_calibrationOffset.az = accelgyro.getZAccelOffset();
_calibrationOffset.gx = accelgyro.getXGyroOffset();
_calibrationOffset.gy = accelgyro.getYGyroOffset();
_calibrationOffset.gz = accelgyro.getZGyroOffset();
// Save the calibrated values
myConfig.setGyroCalibration(_calibrationOffset);
myConfig.saveFile();
}
//
// Calibrate the device.
//
void GyroSensor::debug() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Debug - Clock src %d." CR),
accelgyro.getClockSource());
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID());
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode());
Log.verbose(F("GYRO: Debug - DMP on %s." CR),
accelgyro.getDMPEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Acc range %d." CR),
accelgyro.getFullScaleAccelRange());
Log.verbose(F("GYRO: Debug - Gyr range %d." CR),
accelgyro.getFullScaleGyroRange());
Log.verbose(F("GYRO: Debug - Int %s." CR),
accelgyro.getIntEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Clock %d." CR),
accelgyro.getMasterClockSpeed());
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate());
Log.verbose(F("GYRO: Debug - Gyro range %d." CR),
accelgyro.getFullScaleGyroRange());
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR),
accelgyro.getAccelXSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Acc FactY %d." CR),
accelgyro.getAccelYSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR),
accelgyro.getAccelZSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR),
accelgyro.getGyroXSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR),
accelgyro.getGyroYSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR),
accelgyro.getGyroZSelfTestFactoryTrim());
switch (accelgyro.getFullScaleAccelRange()) {
case 0:
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR));
break;
case 1:
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR));
break;
case 2:
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR));
break;
case 3:
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR));
break;
}
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR),
accelgyro.getXAccelOffset(), _calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR),
accelgyro.getYAccelOffset(), _calibrationOffset.ay);
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR),
accelgyro.getZAccelOffset(), _calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR),
accelgyro.getXGyroOffset(), _calibrationOffset.gx);
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR),
accelgyro.getYGyroOffset(), _calibrationOffset.gy);
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR),
accelgyro.getZGyroOffset(), _calibrationOffset.gz);
#endif
}
// EOF

View File

@ -1,87 +0,0 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _GYRO_H
#define _GYRO_H
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
//#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
// Includes
#include <arduino.h>
#include "MPU6050.h"
#include "config.h"
// Classes
struct RawGyroDataL { // Used for average multiple readings
long ax; // Raw Acceleration
long ay;
long az;
long gx; // Raw Position
long gy;
long gz;
long temp; // Only for information (temperature of chip)
};
#define INVALID_TEMPERATURE -273
class GyroSensor {
private:
MPU6050 accelgyro;
bool sensorConnected = false;
bool validValue = false;
float angle = 0;
float sensorTemp = 0;
float initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData calibrationOffset;
RawGyroData lastGyroData;
void debug();
void applyCalibration();
void dumpCalibration();
void readSensor(RawGyroData &raw, const int noIterations = 100, const int delayTime = 1);
bool isSensorMoving(RawGyroData &raw);
float calculateAngle(RawGyroData &raw);
public:
bool setup();
bool read();
void calibrateSensor();
const RawGyroData& getLastGyroData() { return lastGyroData; }
float getAngle() { return angle; };
float getSensorTempC() { return sensorTemp; };
float getInitialSensorTempC() { return initialSensorTemp; };
bool isConnected() { return sensorConnected; };
bool hasValue() { return validValue; };
void enterSleep();
};
// Global instance created
extern GyroSensor myGyro;
#endif // _GYRO_H
// EOF

84
src/gyro.hpp Normal file
View File

@ -0,0 +1,84 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SRC_GYRO_HPP_
#define SRC_GYRO_HPP_
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
// #define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
#include <MPU6050.h>
#include <config.hpp>
struct RawGyroDataL { // Used for average multiple readings
int32_t ax; // Raw Acceleration
int32_t ay;
int32_t az;
int32_t gx; // Raw Position
int32_t gy;
int32_t gz;
int32_t temp; // Only for information (temperature of chip)
};
#define INVALID_TEMPERATURE -273
class GyroSensor {
private:
bool _sensorConnected = false;
bool _validValue = false;
float _angle = 0;
float _sensorTemp = 0;
float _initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData _calibrationOffset;
RawGyroData _lastGyroData;
void debug();
void applyCalibration();
void dumpCalibration();
void readSensor(RawGyroData &raw, const int noIterations = 100,
const int delayTime = 1);
bool isSensorMoving(RawGyroData &raw);
float calculateAngle(RawGyroData &raw);
public:
bool setup();
bool read();
void calibrateSensor();
const RawGyroData &getLastGyroData() { return _lastGyroData; }
float getAngle() { return _angle; }
float getSensorTempC() { return _sensorTemp; }
float getInitialSensorTempC() { return _initialSensorTemp; }
bool isConnected() { return _sensorConnected; }
bool hasValue() { return _validValue; }
void enterSleep();
};
extern GyroSensor myGyro;
#endif // SRC_GYRO_HPP_
// EOF

View File

@ -1,256 +1,506 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "helper.h"
#include "config.h"
#include "gyro.h"
#include "tempsensor.h"
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
SerialDebug mySerial;
BatteryVoltage myBatteryVoltage;
//
// Print the heap information.
//
void printHeap() {
#if LOG_LEVEL==6
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getHeapFragmentation(), ESP.getFreeSketchSpace()/1024 );
#endif
}
//
// Enter deep sleep for the defined duration (Argument is seconds)
//
void deepSleep(int t) {
#if LOG_LEVEL==6
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t );
#endif
uint64_t wake = t * 1000000;
ESP.deepSleep( wake );
}
//
// Print the build options used
//
void printBuildOptions() {
Log.notice( F("Build options: %s LOGLEVEL %d "
#ifdef SKIP_SLEEPMODE
"SKIP_SLEEP "
#endif
#ifdef EMBED_HTML
"EMBED_HTML "
#endif
#ifdef COLLECT_PERFDATA
"PERFDATA "
#endif
#ifdef ACTIVATE_OTA
"OTA "
#endif
CR), CFG_APPVER, LOG_LEVEL );
}
//
// Configure serial debug output
//
SerialDebug::SerialDebug(const long serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD)
Serial.flush();
Serial.begin(serialSpeed);
getLog()->begin(LOG_LEVEL, &Serial, true);
getLog()->setPrefix(printTimestamp);
getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed);
}
//
// Print the timestamp (ms since start of device)
//
void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12];
sprintf(c, "%10lu ", millis());
_logOutput->print(c);
}
//
// Read and calculate the battery voltage
//
void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce the voltage (from max 5V)
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
int v = analogRead( A0 );
batteryLevel = ((3.3/1023)*v)*factor;
#if LOG_LEVEL==6
Log.verbose(F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR), factor, v, batteryLevel );
#endif
}
#if defined( COLLECT_PERFDATA )
PerfLogging myPerfLogging;
//
// Clear the current cache
//
void PerfLogging::clear() {
// Clear the measurements
if( first == 0 )
return;
PerfEntry* pe = first;
do {
pe->max = 0;
pe->start = 0;
pe->end = 0;
pe->mA = 0;
pe->V = 0;
pe = pe->next;
} while( pe != 0 );
}
//
// Start measuring this performance point
//
void PerfLogging::start( const char* key ) {
PerfEntry* pe = add( key );
pe->start = millis();
}
//
// Finalize measuring of this performance point
//
void PerfLogging::stop( const char* key ) {
PerfEntry* pe = find( key );
if( pe != 0 ) {
pe->end = millis();
unsigned long t = pe->end - pe->start;
if( t > pe->max )
pe->max = t;
}
}
//
// Print the collected performance data
//
void PerfLogging::print() {
PerfEntry* pe = first;
while( pe != 0 ) {
//Log.notice( F("PERF: %s=%l ms (%l, %l)" CR), pe->key, (pe->end - pe->start), pe->start, pe->end );
Log.notice( F("PERF: %s %lms" CR), pe->key, pe->max );
pe = pe->next;
}
}
//
// Push collected performance data to influx (use influx configuration)
//
void PerfLogging::pushInflux() {
if( !myConfig.isInfluxDb2Active() )
return;
WiFiClient client;
HTTPClient http;
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" +
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" +
String(myConfig.getInfluxDb2PushBucket());
http.begin( client, serverPath);
// Create body for influxdb2, format used
// key,host=mdns value=0.0
String body;
// Create the payload with performance data.
// ------------------------------------------------------------------------------------------
PerfEntry* pe = first;
char buf[100];
sprintf( &buf[0], "perf,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
body += &buf[0];
while( pe != 0 ) {
if( pe->max ) {
if( pe->next )
sprintf( &buf[0], "%s=%ld,", pe->key, pe->max);
else
sprintf( &buf[0], "%s=%ld", pe->key, pe->max);
body += &buf[0];
}
pe = pe->next;
}
// Create the payload with debug data for validating sensor stability
// ------------------------------------------------------------------------------------------
sprintf( &buf[0], "\ndebug,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
body += &buf[0];
sprintf( &buf[0], "angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f", myGyro.getAngle(), myGyro.getLastGyroData().ax,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az, myGyro.getSensorTempC(), myTempSensor.getTempC() );
body += &buf[0];
// Log.notice(F("PERF: data %s." CR), body.c_str() );
#if LOG_LEVEL==6
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
Log.verbose(F("PERF: data %s." CR), body.c_str() );
#endif
// Send HTTP POST request
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() );
http.addHeader(F("Authorization"), auth.c_str() );
int httpResponseCode = http.POST(body);
if (httpResponseCode==204) {
Log.notice(F("PERF: InfluxDB2 push performance data successful, response=%d" CR), httpResponseCode);
} else {
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR), httpResponseCode);
}
http.end();
}
#endif // COLLECT_PERFDATA
//
// Convert float to formatted string with n decimals. Buffer should be at least 10 chars.
//
char* convertFloatToString( float f, char *buffer, int dec ) {
dtostrf(f, 6, dec, buffer);
return buffer;
}
//
// Reduce precision to n decimals
//
float reduceFloatPrecision( float f, int dec ) {
char buffer[5];
dtostrf(f, 6, dec, &buffer[0]);
return atof(&buffer[0]);
}
// EOF
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if defined(ESP8266)
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#else // defined (ESP32)
#include <HTTPClient.h>
#include <WiFi.h>
#endif
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <main.hpp>
#include <tempsensor.hpp>
#include <wifi.hpp>
SerialDebug mySerial;
BatteryVoltage myBatteryVoltage;
// tcp cleanup, to avoid memory crash.
struct tcp_pcb;
extern struct tcp_pcb* tcp_tw_pcbs;
extern "C" void tcp_abort(struct tcp_pcb* pcb);
void tcp_cleanup() {
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
}
//
// Convert sg to plato
//
double convertToPlato(double sg) {
if (sg) return 259 - (259 / sg);
return 0;
}
//
// Convert plato to sg
//
double convertToSG(double plato) { return 259 / (259 - plato); }
//
// Conversion to F
//
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
//
// Conversion to C
//
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
//
// Load error log from disk
//
ErrorFileLog::ErrorFileLog() {
File errFile = LittleFS.open(ERR_FILENAME, "r");
int i = 0;
if (errFile) {
do {
_errors[i] = errFile.readStringUntil('\n');
_errors[i].replace("\r", "");
_errors[i].replace("\n", "");
} while (_errors[i++].length());
errFile.close();
}
}
//
// Add new entry to top of error log
//
void ErrorFileLog::addEntry(String err) {
for (int i = (ERR_COUNT - 1); i > 0; i--) {
_errors[i] = _errors[i - 1];
}
_errors[0] = err;
err += String(CR);
Log.error(err.c_str());
save();
}
//
// Save error log
//
void ErrorFileLog::save() {
File errFile = LittleFS.open(ERR_FILENAME, "w");
if (errFile) {
for (int i = 0; i < ERR_COUNT; i++) {
errFile.println(_errors[i]);
}
errFile.close();
}
}
//
// Load history log of floats
//
FloatHistoryLog::FloatHistoryLog(String fName) {
_fName = fName;
File runFile = LittleFS.open(_fName, "r");
if (runFile) {
for (int i = 0; i < 10; i++) {
_runTime[i] = runFile.readStringUntil('\n').toFloat();
if (_runTime[i]) {
_average += _runTime[i];
_count++;
}
}
runFile.close();
_average = _average / _count;
}
}
//
// Add entry to top of log
//
void FloatHistoryLog::addEntry(float time) {
for (int i = (10 - 1); i > 0; i--) {
_runTime[i] = _runTime[i - 1];
}
_runTime[0] = time;
save();
}
//
// Save log
//
void FloatHistoryLog::save() {
File runFile = LittleFS.open(_fName, "w");
if (runFile) {
for (int i = 0; i < 10; i++) {
runFile.println(_runTime[i], 2);
}
runFile.close();
}
}
//
// Print the heap information.
//
void printHeap(String prefix) {
#if defined(ESP8266)
Log.notice(
F("%s: Free-heap %d kb, Heap-rag %d %%, Max-block %d kb Stack=%d b." CR),
prefix.c_str(), ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
ESP.getMaxFreeBlockSize() / 1024, ESP.getFreeContStack());
// Log.notice(F("%s: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
// prefix.c_str(), ESP.getFreeHeap() / 1024,
// ESP.getHeapFragmentation(), ESP.getFreeSketchSpace() / 1024);
#else // defined (ESP32)
Log.verbose(F("HELP: Heap %d kb, FreeSketch %d kb." CR),
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024);
#endif
}
//
// Enter deep sleep for the defined duration (Argument is seconds)
//
void deepSleep(int t) {
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
#endif
uint32_t wake = t * 1000000;
ESP.deepSleep(wake);
}
//
// Print the build options used
//
void printBuildOptions() {
Log.notice(F("Build options: %s (%s) LOGLEVEL %d "
#ifdef SKIP_SLEEPMODE
"SKIP_SLEEP "
#endif
#ifdef COLLECT_PERFDATA
"PERFDATA "
#endif
CR),
CFG_APPVER, CFG_GITREV, LOG_LEVEL);
}
//
// Configure serial debug output
//
SerialDebug::SerialDebug(const uint32_t serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD)
Serial.flush();
Serial.begin(serialSpeed);
getLog()->begin(LOG_LEVEL, &Serial, true);
getLog()->setPrefix(printTimestamp);
getLog()->notice(F("SDBG: Serial logging started at %u." CR), serialSpeed);
}
//
// Print the timestamp (ms since start of device)
//
void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12];
snprintf(c, sizeof(c), "%10lu ", millis());
_logOutput->print(c);
}
//
// Read and calculate the battery voltage
//
void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce
// the voltage (from max 5V)
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
int v = analogRead(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);
#endif
}
#if defined(COLLECT_PERFDATA)
PerfLogging myPerfLogging;
//
// Clear the current cache
//
void PerfLogging::clear() {
// Clear the measurements
if (first == 0) return;
PerfEntry* pe = first;
do {
pe->max = 0;
pe->start = 0;
pe->end = 0;
pe->mA = 0;
pe->V = 0;
pe = pe->next;
} while (pe != 0);
}
//
// Start measuring this performance point
//
void PerfLogging::start(const char* key) {
PerfEntry* pe = add(key);
pe->start = millis();
}
//
// Finalize measuring of this performance point
//
void PerfLogging::stop(const char* key) {
PerfEntry* pe = find(key);
if (pe != 0) {
pe->end = millis();
uint32_t t = pe->end - pe->start;
if (t > pe->max) pe->max = t;
}
}
//
// Print the collected performance data
//
void PerfLogging::print() {
PerfEntry* pe = first;
while (pe != 0) {
Log.notice(F("PERF: %s %ums" CR), pe->key, pe->max);
pe = pe->next;
}
}
//
// Push collected performance data to influx (use influx configuration)
//
void PerfLogging::pushInflux() {
if (!myConfig.isInfluxDb2Active()) return;
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(wifi, serverPath);
// Create body for influxdb2, format used
// key,host=mdns value=0.0
String body;
body.reserve(500);
// Create the payload with performance data.
// ------------------------------------------------------------------------------------------
PerfEntry* pe = first;
char buf[150];
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
myConfig.getID());
body += &buf[0];
while (pe != 0) {
if (pe->max) {
if (pe->next)
snprintf(&buf[0], sizeof(buf), "%s=%u,", pe->key, pe->max);
else
snprintf(&buf[0], sizeof(buf), "%s=%u", pe->key, pe->max);
body += &buf[0];
}
pe = pe->next;
}
// Create the payload with debug data for validating sensor stability
// ------------------------------------------------------------------------------------------
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
myConfig.getMDNS(), myConfig.getID());
body += &buf[0];
#if defined(ESP8266)
snprintf(&buf[0], sizeof(buf),
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
"%.2f,heap=%d,heap-frag=%d,heap-max=%d,stack=%d",
myGyro.getAngle(), myGyro.getLastGyroData().ax,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
myGyro.getSensorTempC(),
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(),
ESP.getHeapFragmentation(), ESP.getMaxFreeBlockSize(),
ESP.getFreeContStack());
#else // defined (ESP32)
snprintf(&buf[0], sizeof(buf),
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
"%.2f,heap=%d,heap-frag=%d,heap-max=%d",
myGyro.getAngle(), myGyro.getLastGyroData().ax,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
myGyro.getSensorTempC(),
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), 0,
ESP.getMaxAllocHeap());
#endif
body += &buf[0];
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
Log.verbose(F("PERF: data %s." CR), body.c_str());
#endif
// Send HTTP POST request
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
http.addHeader(F("Authorization"), auth.c_str());
http.setTimeout(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);
}
//
// urlencode string
//
String urldecode(String str) {
String encodedString;
encodedString.reserve(str.length());
encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < static_cast<int>(str.length()); i++) {
c = str.charAt(i);
if (c == '%') {
i++;
code0 = str.charAt(i);
i++;
code1 = str.charAt(i);
c = (h2int(code0) << 4) | h2int(code1);
encodedString += c;
} else {
encodedString += c;
}
}
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
return encodedString;
}
// EOF

View File

@ -1,156 +0,0 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _HELPER_H
#define _HELPER_H
// Includes
#include <ArduinoLog.h>
// Sleep mode
void deepSleep(int t);
// Show build options
void printBuildOptions();
// Float to String
char* convertFloatToString( float f, char* buf, int dec = 2);
float reduceFloatPrecision( float f, int dec = 2 );
// Logging via serial
void printTimestamp(Print* _logOutput, int _logLevel);
void printNewline(Print* _logOutput);
void printHeap();
// Classes
class SerialDebug {
public:
SerialDebug(const long serialSpeed = 115200L);
static Logging* getLog() { return &Log; };
};
class BatteryVoltage {
private:
float batteryLevel;
public:
void read();
float getVoltage() { return batteryLevel; };
};
#if defined( COLLECT_PERFDATA )
class PerfLogging {
private:
struct PerfEntry {
unsigned long start; // millis()
unsigned long end; // millis()
unsigned long max; // max time in ms
const char* key; // measurement
PerfEntry* next; // Next in the linked list
float mA; // Power consumption
float V; // Power consumption
};
PerfEntry* first = 0;
bool measurePower = false;
PerfEntry* find( const char* k ) {
if( first == 0 )
return 0;
PerfEntry* pe = first;
while( strcmp( k, pe->key ) != 0 ) {
if( pe->next == 0 )
return 0;
pe = pe->next;
}
return pe;
};
PerfEntry* add( const char* k ) {
if( first == 0 ) {
first = new PerfEntry();
first->key = k;
first->next = 0;
first->max = 0;
return first;
}
PerfEntry* pe = first;
while( strcmp( k, pe->key ) != 0 ) {
if( pe->next == 0 ) {
pe->next = new PerfEntry();
pe->next->key = k;
pe->next->max = 0;
pe->next->next = 0;
return pe->next;
}
pe = pe->next;
}
return pe;
};
public:
void clear();
void start( const char* key );
void stop( const char* key );
void print();
void pushInflux();
};
extern PerfLogging myPerfLogging;
// Use these to collect performance data from various parts of the code
#define LOG_PERF_START(s) myPerfLogging.start(s)
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
//#define LOG_PERF_PRINT() myPerfLogging.print()
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR() myPerfLogging.clear()
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
#else
// These will disable the performance collection function
#define LOG_PERF_START(s)
#define LOG_PERF_STOP(s)
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR()
#define LOG_PERF_PUSH()
#endif // COLLECT_PERFDATA
// Global instance created
extern SerialDebug mySerial;
extern BatteryVoltage myBatteryVoltage;
#endif // _HELPER_H
// EOF

195
src/helper.hpp Normal file
View File

@ -0,0 +1,195 @@
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SRC_HELPER_HPP_
#define SRC_HELPER_HPP_
// Includes
#include <main.hpp>
#define ERR_FILENAME "/error.log"
#define ERR_COUNT 15
#define RUNTIME_FILENAME "/runtime.log"
// tcp cleanup
void tcp_cleanup();
// Sleep mode
void deepSleep(int t);
// Show build options
void printBuildOptions();
// Data conversion
double convertToPlato(double sg);
double convertToSG(double plato);
float convertCtoF(float c);
float convertFtoC(float f);
// url encode/decode
String urldecode(String str);
String urlencode(String str);
// Float to String
char* convertFloatToString(float f, char* buf, int dec = 2);
float reduceFloatPrecision(float f, int dec = 2);
// Logging via serial
void printTimestamp(Print* _logOutput, int _logLevel);
void printNewline(Print* _logOutput);
void printHeap(String prefix = "HELP");
// Classes
class SerialDebug {
public:
explicit SerialDebug(const uint32_t serialSpeed = 115200L);
static Logging* getLog() { return &Log; }
};
class ErrorFileLog {
private:
String _errors[ERR_COUNT];
public:
ErrorFileLog();
void addEntry(String error);
void save();
};
class FloatHistoryLog {
private:
String _fName;
float _average = 0;
float _runTime[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int _count = 0;
void save();
public:
explicit FloatHistoryLog(String fName);
void addEntry(float time);
float getAverage() { return _average; }
};
class BatteryVoltage {
private:
float _batteryLevel;
public:
void read();
float getVoltage() { return _batteryLevel; }
};
#if defined(COLLECT_PERFDATA)
class PerfLogging {
private:
struct PerfEntry {
uint32_t start; // millis()
uint32_t end; // millis()
uint32_t max; // max time in ms
const char* key; // measurement
PerfEntry* next; // Next in the linked list
float mA; // Power consumption
float V; // Power consumption
};
PerfEntry* first = 0;
bool measurePower = false;
PerfEntry* find(const char* k) {
if (first == 0) return 0;
PerfEntry* pe = first;
while (strcmp(k, pe->key) != 0) {
if (pe->next == 0) return 0;
pe = pe->next;
}
return pe;
}
PerfEntry* add(const char* k) {
if (first == 0) {
first = new PerfEntry();
first->key = k;
first->next = 0;
first->max = 0;
return first;
}
PerfEntry* pe = first;
while (strcmp(k, pe->key) != 0) {
if (pe->next == 0) {
pe->next = new PerfEntry();
pe->next->key = k;
pe->next->max = 0;
pe->next->next = 0;
return pe->next;
}
pe = pe->next;
}
return pe;
}
public:
void clear();
void start(const char* key);
void stop(const char* key);
void print();
void pushInflux();
};
extern PerfLogging myPerfLogging;
// Use these to collect performance data from various parts of the code
#define LOG_PERF_START(s) myPerfLogging.start(s)
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
// #define LOG_PERF_PRINT() myPerfLogging.print()
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR() myPerfLogging.clear()
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
#else
// These will disable the performance collection function
#define LOG_PERF_START(s)
#define LOG_PERF_STOP(s)
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR()
#define LOG_PERF_PUSH()
#endif // COLLECT_PERFDATA
// Global instance created
extern SerialDebug mySerial;
extern BatteryVoltage myBatteryVoltage;
#endif // SRC_HELPER_HPP_
// EOF

View File

@ -1,279 +1,437 @@
/*
MIT License
Copyright (c) 2021 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "helper.h"
#include "gyro.h"
#include "config.h"
#include "wifi.h"
#include "webserver.h"
#include "calc.h"
#include "tempsensor.h"
#include "pushtarget.h"
#include <LittleFS.h>
// Settings for double reset detector.
#define ESP8266_DRD_USE_RTC true
#define DRD_TIMEOUT 2
#define DRD_ADDRESS 0
#include <ESP_DoubleResetDetector.h>
DoubleResetDetector *drd;
// Define constats for this program
#if LOG_LEVEL==6
const int interval = 1000; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
#else
const int interval = 200; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = false; // Web interface can override normal behaviour
#endif
unsigned long loopMillis = 0; // Used for main loop to run the code every _interval_
unsigned long runtimeMillis; // Used to calculate the total time since start/wakeup
unsigned long stableGyroMillis; // Used to calculate the total time since last stable gyro reading
bool sleepModeActive = false;
bool goToSleep = false;
int loopCounter = 0;
//
// Check if we should be in sleep mode
//
void checkSleepMode( float angle, float volt ) {
#if defined( SKIP_SLEEPMODE )
sleepModeActive = false;
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR) );
return;
#endif
const RawGyroData &g = myConfig.getGyroCalibration();
// Will not enter sleep mode if: no calibration data
if( g.ax==0 && g.ay==0 && g.az==0 && g.gx==0 && g.gy==0 && g.gz==0 ) {
Log.notice(F("MAIN: Missing calibration data, so forcing webserver to be active." CR) );
sleepModeAlwaysSkip = true;
}
if( sleepModeAlwaysSkip ) {
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR) );
sleepModeActive = false;
return;
}
// Will not enter sleep mode if: charger is connected
sleepModeActive = (volt<4.15 && (angle>85 && angle<95)) || (volt>4.15) ? false : true;
// sleep mode active when flat
//sleepModeActive = ( angle<85 && angle>5 ) ? true : false;
#if LOG_LEVEL==6
Log.verbose(F("MAIN: Deep sleep mode %s (angle=%F volt=%F)." CR), sleepModeActive ? "true":"false", angle, volt );
#endif
}
//
// Setup
//
void setup() {
LOG_PERF_START("run-time");
LOG_PERF_START("main-setup");
runtimeMillis = millis();
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
bool dt = drd->detectDoubleReset();
#if LOG_LEVEL==6
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str() );
#endif
// Main startup
Log.notice(F("Main: Started setup for %s." CR), String( ESP.getChipId(), HEX).c_str() );
printBuildOptions();
LOG_PERF_START("main-config-load");
myConfig.checkFileSystem();
myConfig.loadFile();
LOG_PERF_STOP("main-config-load");
// Setup watchdog
ESP.wdtDisable();
ESP.wdtEnable( interval*2 );
LOG_PERF_START("main-temp-setup");
myTempSensor.setup();
LOG_PERF_STOP("main-temp-setup");
// Setup Gyro
//LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this measurment
if( !myGyro.setup() )
Log.error(F("Main: Failed to initialize the gyro." CR));
//LOG_PERF_STOP("main-gyro-setup");
if( dt )
Log.notice(F("Main: Detected doubletap on reset." CR));
LOG_PERF_START("main-wifi-connect");
myWifi.connect( dt ); // This will return false if unable to connect to wifi, will be handled in loop()
LOG_PERF_STOP("main-wifi-connect");
LOG_PERF_START("main-gyro-read");
myGyro.read();
LOG_PERF_STOP("main-gyro-read");
LOG_PERF_START("main-batt-read");
myBatteryVoltage.read();
LOG_PERF_STOP("main-batt-read");
checkSleepMode( myGyro.getAngle(), myBatteryVoltage.getVoltage() );
if( myWifi.isConnected() ) {
#if defined( ACTIVATE_OTA )
LOG_PERF_START("main-wifi-ota");
if( !sleepModeActive && myWifi.checkFirmwareVersion() ) {
myWifi.updateFirmware();
}
LOG_PERF_STOP("main-wifi-ota");
#endif
if( !sleepModeActive ) {
//LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so skip this measurment
myWebServer.setupWebServer();
//LOG_PERF_STOP("main-webserver-setup");
}
}
LOG_PERF_STOP("main-setup");
Log.notice(F("Main: Setup completed." CR));
stableGyroMillis = millis(); // Put it here so we dont include time for wifi connection
}
//
// Main loops
//
void loop() {
drd->loop();
if( sleepModeActive || abs( (long) (millis() - loopMillis)) > interval ) {
float angle = 0;
float volt = myBatteryVoltage.getVoltage();
//float sensorTemp = 0;
loopCounter++;
#if LOG_LEVEL==6
Log.verbose(F("Main: Entering main loop." CR) );
#endif
// Process the sensor values and push data to targets.
// ------------------------------------------------------------------------------------------------
// If we dont get any readings we just skip this and try again the next interval.
//
if( myGyro.hasValue() ) {
angle = myGyro.getAngle(); // Gyro angle
stableGyroMillis = millis(); // Reset timer
LOG_PERF_START("loop-temp-read");
float temp = myTempSensor.getTempC();
LOG_PERF_STOP("loop-temp-read");
//LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip this measurment
float gravity = calculateGravity( angle, temp );
//LOG_PERF_STOP("loop-gravity-calc");
//LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip this measurment
// Use default correction temperature of 20C
float corrGravity = gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() );
//LOG_PERF_STOP("loop-gravity-corr");
#if LOG_LEVEL==6
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, corr=%F." CR), angle, temp, gravity, corrGravity );
#endif
// Limit the printout when sleep mode is not active.
if( loopCounter%10 == 0 || sleepModeActive ) {
Log.notice(F("Main: angle=%F, temp=%F, gravity=%F, corrGravity=%F, batt=%F." CR), angle, temp, gravity, corrGravity ,volt );
}
LOG_PERF_START("loop-push");
myPushTarget.send( angle, gravity, corrGravity, temp, (millis()-runtimeMillis)/1000, sleepModeActive ); // Force the transmission if we are going to sleep
LOG_PERF_STOP("loop-push");
// If we have completed the update lets go to sleep
if( sleepModeActive )
goToSleep = true;
} else {
Log.error(F("Main: No gyro value." CR) );
}
#if LOG_LEVEL==6
Log.verbose(F("Main: Sleep mode not active." CR) );
#endif
int sleepInterval = myConfig.getSleepInterval();
// If we didnt get a wifi connection, we enter sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------
//
if( !myWifi.isConnected() ) { // no connection to wifi
Log.notice(F("MAIN: No connection to wifi established, sleeping for 60s." CR) );
sleepInterval = 60; // 60s
goToSleep = true;
}
// If the sensor is moving and we are not getting a clear reading, we enter sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------
//
if( sleepModeActive && ((millis()-stableGyroMillis)>10000L) ) { // 10s since last stable gyro reading
Log.notice(F("MAIN: Unable to get a stable reading for 10s, sleeping for 60s." CR) );
sleepInterval = 60; // 60s
goToSleep = true;
}
// Enter sleep mode if the conditions are right
// ------------------------------------------------------------------------------------------------
//
if( goToSleep ) {
Log.notice(F("MAIN: Entering deep sleep for %d s, run time %l s, battery=%F V." CR), sleepInterval, (millis()-runtimeMillis)/1000, volt );
LittleFS.end();
myGyro.enterSleep();
drd->stop();
LOG_PERF_STOP("run-time");
LOG_PERF_PUSH();
delay(100);
deepSleep( sleepInterval );
}
// If we are running in normal mode we just continue
// ------------------------------------------------------------------------------------------------
// Do these checks if we are running in normal mode (not sleep mode)
//
checkSleepMode( angle, volt );
LOG_PERF_START("loop-gyro-read");
myGyro.read();
LOG_PERF_STOP("loop-gyro-read");
//LOG_PERF_START("loop-batt-read"); // Takes less than 2ms , so skip this measurment
myBatteryVoltage.read();
//LOG_PERF_STOP("loop-batt-read");
loopMillis = millis();
#if LOG_LEVEL==6
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getFreeSketchSpace()/1024 );
Log.verbose(F("Main: HeapFrag %d %%." CR), ESP.getHeapFragmentation() );
#endif
}
myWebServer.loop();
}
// EOF
/*
MIT License
Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <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
#else
int interval = 200; // ms, time to wait between changes to output
#endif
bool sleepModeAlwaysSkip =
false; // Flag set in web interface to override normal behaviour
uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
uint32_t pushMillis = 0; // Used to control how often we will send push data
uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
uint32_t stableGyroMillis; // Used to calculate the total time since last
// stable gyro reading
RunMode runMode = RunMode::gravityMode;
void checkSleepMode(float angle, float volt) {
#if defined(SKIP_SLEEPMODE)
runMode = RunMode::configurationMode;
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR));
return;
#endif
#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 < 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());
#else
#warning "Check and test the max deep sleep for esp32"
deepSleep(70 * 60); // quick search on internet suggest max time is 70 min
#endif
}
}
void setup() {
LOG_PERF_START("run-time");
LOG_PERF_START("main-setup");
runtimeMillis = millis();
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
// Add a delay so that serial is started.
// delay(3000);
#if defined(ESP8266)
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
#else // defined (ESP32)
#endif
#endif
// Main startup
#if defined(ESP8266)
Log.notice(F("Main: Started setup for %s." CR),
String(ESP.getChipId(), HEX).c_str());
#else // defined (ESP32)
char buf[20];
uint32_t chipId = 0;
for (int i = 0; i < 17; i = i + 8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
snprintf(&buf[0], sizeof(buf), "%6x", chipId);
Log.notice(F("Main: Started setup for %s." CR), &buf[0]);
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);
#endif
printBuildOptions();
LOG_PERF_START("main-config-load");
myConfig.checkFileSystem();
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()) {
Log.notice(
F("Main: No wifi configuration detected, entering wifi setup." CR));
runMode = RunMode::wifiSetupMode;
}
// Double reset, go to portal.
if (myWifi.isDoubleResetDetected()) {
Log.notice(F("Main: Double reset detected, entering wifi setup." CR));
runMode = RunMode::wifiSetupMode;
}
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:
myWifi.startPortal();
break;
default:
if (!myGyro.setup()) {
ErrorFileLog errLog;
errLog.addEntry(
F("MAIN: Failed to initialize the gyro, is it connected?"));
} else {
LOG_PERF_START("main-gyro-read");
myGyro.read();
LOG_PERF_STOP("main-gyro-read");
}
myBatteryVoltage.read();
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
#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;
}
// Do this setup for configuration mode
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
myWebServerHandler
.setupWebServer(); // Takes less than 4ms, so skip this measurement
}
interval = 1000; // Change interval from 200ms to 1s
break;
default:
break;
}
LOG_PERF_STOP("main-setup");
Log.notice(F("Main: Setup completed." CR));
pushMillis = stableGyroMillis =
millis(); // Dont include time for wifi connection
}
//
// Main loop that does gravity readings and push data to targets
//
// Return true if gravity reading was successful
//
bool loopReadGravity() {
float angle = 0;
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Entering main loopGravity." CR));
#endif
// Process the sensor values and push data to targets.
// ------------------------------------------------------------------------------------------------
// If we dont get any readings we just skip this and try again the next
// interval.
//
if (myGyro.hasValue()) {
angle = myGyro.getAngle(); // Gyro angle
stableGyroMillis = millis(); // Reset timer
LOG_PERF_START("loop-temp-read");
float tempC = myTempSensor.getTempC(myConfig.isGyroTemp());
LOG_PERF_STOP("loop-temp-read");
float gravitySG = calculateGravity(angle, tempC);
float corrGravitySG = gravityTemperatureCorrectionC(
gravitySG, tempC, myAdvancedConfig.getDefaultCalibrationTemp());
if (myConfig.isGravityTempAdj()) {
gravitySG = corrGravitySG;
}
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%FC, gravity=%F, "
"corr_gravity=%F." CR),
angle, tempC, gravitySG, corrGravitySG);
#endif
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 found, the device might be moving." CR));
}
return false;
}
//
// Wrapper for loopGravity that only calls every 200ms so that we dont overload
// this.
//
void loopGravityOnInterval() {
if (abs((int32_t)(millis() - loopMillis)) > interval) {
loopReadGravity();
loopMillis = millis();
// printHeap("MAIN");
LOG_PERF_START("loop-gyro-read");
myGyro.read();
LOG_PERF_STOP("loop-gyro-read");
myBatteryVoltage.read();
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
}
}
bool skipRunTimeLog = false;
//
// Main loop that determines if device should go to sleep
//
void goToSleep(int sleepInterval) {
float volt = myBatteryVoltage.getVoltage();
float runtime = (millis() - runtimeMillis);
if (!skipRunTimeLog) {
FloatHistoryLog runLog(RUNTIME_FILENAME);
runLog.addEntry(runtime);
}
Log.notice(F("MAIN: Entering deep sleep for %ds, run time %Fs, "
"battery=%FV." CR),
sleepInterval, reduceFloatPrecision(runtime / 1000, 2), volt);
LittleFS.end();
myGyro.enterSleep();
LOG_PERF_STOP("run-time");
LOG_PERF_PUSH();
delay(100);
deepSleep(sleepInterval);
}
void loop() {
switch (runMode) {
case RunMode::storageMode:
// This point is never reached, just here to remove warning.
break;
case RunMode::configurationMode:
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() &&
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();
goToSleep(60);
}
if (loopReadGravity()) {
myWifi.stopDoubleReset();
goToSleep(myConfig.getSleepInterval());
}
// If the sensor is moving and we are not getting a clear reading, we
// enter sleep for a short time to conserve battery.
if (((millis() - stableGyroMillis) >
10000L)) { // 10s since last stable gyro reading
Log.notice(
F("MAIN: Unable to get a stable reading for 10s, sleeping for "
"60s." CR));
myWifi.stopDoubleReset();
goToSleep(60);
}
LOG_PERF_START("loop-gyro-read");
myGyro.read();
LOG_PERF_STOP("loop-gyro-read");
myWifi.loop();
break;
case RunMode::wifiSetupMode:
myWifi.loop();
break;
}
}
// EOF

Some files were not shown because too many files have changed in this diff Show More