Compare commits
1452 Commits
v0.13.0-b7
...
v0.14.0-b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e236f9d88 | ||
|
|
72eb61951b | ||
|
|
d7b5719dfd | ||
|
|
284a9999b3 | ||
|
|
b241872a00 | ||
|
|
b7034d3213 | ||
|
|
e7449b4d56 | ||
|
|
fab34c9e49 | ||
|
|
7df08c2120 | ||
|
|
b94e0ef797 | ||
|
|
00fed4f995 | ||
|
|
e8a7802e94 | ||
|
|
bfbc1ebb13 | ||
|
|
a802bb2736 | ||
|
|
3c5838cafd | ||
|
|
9217e8336d | ||
|
|
13cfc2d7bc | ||
|
|
171cebed1c | ||
|
|
1dcef87e1c | ||
|
|
23fb602a33 | ||
|
|
a8a549e8fc | ||
|
|
44790e99ea | ||
|
|
a3f6717c59 | ||
|
|
9587480e29 | ||
|
|
8619e8fc0b | ||
|
|
10dace6de6 | ||
|
|
f6e843b5e2 | ||
|
|
a7bad5df61 | ||
|
|
f50c9e855c | ||
|
|
3653666ffe | ||
|
|
2123e43490 | ||
|
|
19146d8012 | ||
|
|
987dd36401 | ||
|
|
6bb158786b | ||
|
|
f66d091717 | ||
|
|
0a3d911602 | ||
|
|
e8edb99bb0 | ||
|
|
917cd96a3d | ||
|
|
20ad6d239d | ||
|
|
eea3968bfb | ||
|
|
7ac8f6dd19 | ||
|
|
cafa78c3f3 | ||
|
|
e808f7655c | ||
|
|
4f28bf7ab4 | ||
|
|
9380b2b4e8 | ||
|
|
8caeddde15 | ||
|
|
b637398a9c | ||
|
|
2e5b19575f | ||
|
|
102a69996a | ||
|
|
37af48f3fb | ||
|
|
88d05578a8 | ||
|
|
b24c8b3410 | ||
|
|
2c8dbb94fc | ||
|
|
3da2ec5112 | ||
|
|
c3545ef060 | ||
|
|
4308a7cf79 | ||
|
|
e629c90a71 | ||
|
|
e7f07f5bfc | ||
|
|
da7972c119 | ||
|
|
efc476e50b | ||
|
|
33f4e8cf73 | ||
|
|
643f300792 | ||
|
|
bd601ad2da | ||
|
|
9f1a7a1c20 | ||
|
|
98138a02e3 | ||
|
|
f7004e7a7c | ||
|
|
1db25d4b20 | ||
|
|
9a44f0c869 | ||
|
|
713bf66a12 | ||
|
|
78e9f5bd1a | ||
|
|
f03abf2600 | ||
|
|
d5eee5b56c | ||
|
|
8899684092 | ||
|
|
8e30e4925c | ||
|
|
906c7a8ea1 | ||
|
|
324fc149b3 | ||
|
|
29b1f2afae | ||
|
|
b6db86da50 | ||
|
|
713509527a | ||
|
|
0a1bd748d7 | ||
|
|
1b351b7743 | ||
|
|
e409bd298a | ||
|
|
caef289b9b | ||
|
|
43582b6319 | ||
|
|
c14c4425a4 | ||
|
|
c47d6cffa8 | ||
|
|
f104fb0586 | ||
|
|
75e410e4b4 | ||
|
|
740316ae2b | ||
|
|
b141ec7ea7 | ||
|
|
50875d5759 | ||
|
|
61b5e5ad7e | ||
|
|
6403d18d15 | ||
|
|
77f04d913a | ||
|
|
c0a783198e | ||
|
|
d370f67f60 | ||
|
|
5f4199183c | ||
|
|
d097f8bf1e | ||
|
|
1e104bdd9e | ||
|
|
8143be29d9 | ||
|
|
1d8c9ac020 | ||
|
|
7fcc8be73c | ||
|
|
e5f9cfd5b6 | ||
|
|
edd487c1f4 | ||
|
|
71d84fecba | ||
|
|
d30e219d7b | ||
|
|
0cfda55b3a | ||
|
|
17d1ca82a6 | ||
|
|
81d2a67948 | ||
|
|
3bae3aa9aa | ||
|
|
7211e6b929 | ||
|
|
82af52a0bc | ||
|
|
e88d34ea19 | ||
|
|
e1d7d9511f | ||
|
|
22d7d2c1f6 | ||
|
|
2b10a0c513 | ||
|
|
6e59cfc66c | ||
|
|
51d8344515 | ||
|
|
535f285287 | ||
|
|
779fd78091 | ||
|
|
69a111ee35 | ||
|
|
7288e5a8fd | ||
|
|
ac57da8713 | ||
|
|
2000d02768 | ||
|
|
5ba1ebd525 | ||
|
|
92329a8dd0 | ||
|
|
21de073784 | ||
|
|
3d502a41c5 | ||
|
|
8570f9256d | ||
|
|
fe09c417ff | ||
|
|
ca891b0e70 | ||
|
|
af3ee35c50 | ||
|
|
ba0bc31525 | ||
|
|
3905cad68d | ||
|
|
30a029c19f | ||
|
|
7cac609c06 | ||
|
|
e1365f185c | ||
|
|
c2ac215d43 | ||
|
|
c47972d500 | ||
|
|
3c36030977 | ||
|
|
7b2836c63c | ||
|
|
38e2fc6812 | ||
|
|
b0037c75a3 | ||
|
|
d7f6cd944c | ||
|
|
0de928a674 | ||
|
|
5c542d60e5 | ||
|
|
b3a29188a2 | ||
|
|
8417589789 | ||
|
|
c982e022fe | ||
|
|
a4c3fd4493 | ||
|
|
7de35b4830 | ||
|
|
3841780fe6 | ||
|
|
2440d8e3d1 | ||
|
|
5012418ed2 | ||
|
|
a6ab4feca5 | ||
|
|
cd0471386d | ||
|
|
d6749d30ab | ||
|
|
37a4a4dcdf | ||
|
|
cebceb3ec3 | ||
|
|
426635871b | ||
|
|
701c90d18d | ||
|
|
d00a708177 | ||
|
|
1f32f96487 | ||
|
|
7642f8d702 | ||
|
|
e78bf240ca | ||
|
|
4fb44d98db | ||
|
|
113ee73609 | ||
|
|
64441b39ac | ||
|
|
b4e3cccf4b | ||
|
|
6a3ef2a2e4 | ||
|
|
1dd00c2ea9 | ||
|
|
5038e4396e | ||
|
|
bd025309fb | ||
|
|
1ae0dd574d | ||
|
|
7d5ce994ab | ||
|
|
78e0c3dcca | ||
|
|
edbb96bcd9 | ||
|
|
bdb1e839ed | ||
|
|
1880740561 | ||
|
|
503835d47e | ||
|
|
8d372bee67 | ||
|
|
f385af595a | ||
|
|
4cd6bafc15 | ||
|
|
dc700c41fb | ||
|
|
7cd9e8860d | ||
|
|
b6adbc926f | ||
|
|
c253464b2a | ||
|
|
6306cfff96 | ||
|
|
d86d88c7b7 | ||
|
|
5f606bb0b7 | ||
|
|
1daa97545b | ||
|
|
d0189b0719 | ||
|
|
222b92807e | ||
|
|
de90e5b753 | ||
|
|
ad4870a59b | ||
|
|
9a6c387b8e | ||
|
|
81b4a35076 | ||
|
|
411b3d0888 | ||
|
|
fc0dc4472b | ||
|
|
7824f9ee63 | ||
|
|
fb00bef05f | ||
|
|
d7bc6b1be7 | ||
|
|
79776ae8bc | ||
|
|
90b3f009af | ||
|
|
6f8deb83e3 | ||
|
|
da02a68e60 | ||
|
|
85fc8710dd | ||
|
|
a01f3e4efd | ||
|
|
cce54f3bb7 | ||
|
|
92037a480d | ||
|
|
cf6005ce2f | ||
|
|
a766ddbebc | ||
|
|
01acb08c83 | ||
|
|
023c259034 | ||
|
|
dfe4d70198 | ||
|
|
4d939cb778 | ||
|
|
660a809ec8 | ||
|
|
26825ed60f | ||
|
|
8bd8975e0a | ||
|
|
799d4f9465 | ||
|
|
47797bcf4a | ||
|
|
2847921e5a | ||
|
|
8402de601f | ||
|
|
4dec75c589 | ||
|
|
ac15b227da | ||
|
|
a1dc7a52e9 | ||
|
|
6ba3e25d33 | ||
|
|
51a2fa47c9 | ||
|
|
09bd6dba84 | ||
|
|
925bc3b3dd | ||
|
|
382bf1e94c | ||
|
|
575a7531c5 | ||
|
|
dce0c0b47e | ||
|
|
dc8230bf2e | ||
|
|
17428d58d9 | ||
|
|
6771bd84c6 | ||
|
|
ac10b3a5c7 | ||
|
|
56f2513aba | ||
|
|
9858a009da | ||
|
|
73b818cf78 | ||
|
|
4f8ffaee5b | ||
|
|
2cd40c7552 | ||
|
|
c9fd69ceb7 | ||
|
|
e4fbf70568 | ||
|
|
cf93d6bb65 | ||
|
|
228890aa19 | ||
|
|
ad8512e246 | ||
|
|
4480abc646 | ||
|
|
94243ac605 | ||
|
|
e8942c2968 | ||
|
|
8e03395b53 | ||
|
|
4f3de8646a | ||
|
|
6802f5a802 | ||
|
|
3a1ddce13f | ||
|
|
6c18857109 | ||
|
|
508b1e76c2 | ||
|
|
51d3268eed | ||
|
|
a0c90d4ba3 | ||
|
|
2c27240da6 | ||
|
|
787f5f06df | ||
|
|
7894389f1d | ||
|
|
da5f6315be | ||
|
|
42d1ab8a87 | ||
|
|
cf51892782 | ||
|
|
cdd4319991 | ||
|
|
3287eef0f1 | ||
|
|
c4a261f2d2 | ||
|
|
1867db3c4b | ||
|
|
9e23d52193 | ||
|
|
e29be737f7 | ||
|
|
3ac4122ec8 | ||
|
|
d56a79e016 | ||
|
|
77ace76e32 | ||
|
|
3270605b4f | ||
|
|
36e10539e0 | ||
|
|
37ba649930 | ||
|
|
586e72e797 | ||
|
|
38bd0d6bbb | ||
|
|
da0da4c75e | ||
|
|
bfe16bb254 | ||
|
|
2ada88a266 | ||
|
|
5b51ce9840 | ||
|
|
26793c8428 | ||
|
|
8719adef1e | ||
|
|
cd7bcb79e5 | ||
|
|
005419ab9a | ||
|
|
d28d2c57e4 | ||
|
|
64970772c7 | ||
|
|
6019b7bda4 | ||
|
|
5c792eb869 | ||
|
|
c6126db2a2 | ||
|
|
030833f942 | ||
|
|
daf67d9cf7 | ||
|
|
301ed25019 | ||
|
|
a7dbfc4954 | ||
|
|
af5e38e5ee | ||
|
|
102a28aef4 | ||
|
|
d3d8fdff13 | ||
|
|
9cb6f95420 | ||
|
|
89f334e67b | ||
|
|
b8b3d17570 | ||
|
|
053083f600 | ||
|
|
cf46564c67 | ||
|
|
59b038b8c4 | ||
|
|
fb6dfcd3fc | ||
|
|
1711ac9a88 | ||
|
|
e14c5bbd25 | ||
|
|
cf0f0d77be | ||
|
|
be7e7ac274 | ||
|
|
844bef9fda | ||
|
|
d56d41a8c2 | ||
|
|
b722c618bd | ||
|
|
720fae8720 | ||
|
|
8744b40dc5 | ||
|
|
d053bc562f | ||
|
|
cade1800f4 | ||
|
|
450a0180f8 | ||
|
|
ea363a8764 | ||
|
|
bbc8049832 | ||
|
|
66acd60406 | ||
|
|
6fd8a5a084 | ||
|
|
5927332a5f | ||
|
|
44a4b11d36 | ||
|
|
b8db47e528 | ||
|
|
f7652bd2ef | ||
|
|
e9f6509cb0 | ||
|
|
3c57e2e2b9 | ||
|
|
753ae51dd5 | ||
|
|
638178556f | ||
|
|
b44ed70112 | ||
|
|
3e494cc551 | ||
|
|
fa55896722 | ||
|
|
67a51be9ee | ||
|
|
2149bbb8ea | ||
|
|
d92a93f1d5 | ||
|
|
991fad02d7 | ||
|
|
7497e43fb9 | ||
|
|
1336de12a0 | ||
|
|
0f78bd3785 | ||
|
|
90b567c721 | ||
|
|
8176f1141e | ||
|
|
515827c745 | ||
|
|
1a2701561b | ||
|
|
c7d3ee0612 | ||
|
|
4be3cb4b0d | ||
|
|
db759bef46 | ||
|
|
3d47a8e9c0 | ||
|
|
91fe80334b | ||
|
|
7125d19af1 | ||
|
|
873e41dcfb | ||
|
|
420f858d9b | ||
|
|
e6f74751d4 | ||
|
|
5a4713950c | ||
|
|
c6691564a5 | ||
|
|
8acb44b202 | ||
|
|
72770e5809 | ||
|
|
968721a515 | ||
|
|
1de009a80d | ||
|
|
74b6a78a9b | ||
|
|
32fc6d4b7f | ||
|
|
d05b49496c | ||
|
|
52e5f467b0 | ||
|
|
4e0cf380be | ||
|
|
ebe9499e97 | ||
|
|
1cb3ab82c2 | ||
|
|
e0a954caa2 | ||
|
|
ecce3243de | ||
|
|
5e6532959b | ||
|
|
d8b7cfb36b | ||
|
|
b2837563c4 | ||
|
|
957948f906 | ||
|
|
7befafe7b7 | ||
|
|
436ce63e30 | ||
|
|
0268beb9c2 | ||
|
|
6a42e477aa | ||
|
|
22bc3dac2d | ||
|
|
4db4329ce3 | ||
|
|
924073424f | ||
|
|
9e828eccf6 | ||
|
|
3a8c99d43c | ||
|
|
acb17dc575 | ||
|
|
58987989da | ||
|
|
86e8ee334f | ||
|
|
998f2f9421 | ||
|
|
8694e7a6bf | ||
|
|
b46a6ed094 | ||
|
|
d0f53cb14a | ||
|
|
96d497a5cd | ||
|
|
a8785570df | ||
|
|
aa36e04250 | ||
|
|
821b7ed9af | ||
|
|
9270f80af2 | ||
|
|
eb8710df81 | ||
|
|
095099a085 | ||
|
|
ad424cac18 | ||
|
|
4e11806d00 | ||
|
|
f45082b764 | ||
|
|
cdca715afc | ||
|
|
d0a08a55d1 | ||
|
|
c5f3e76b21 | ||
|
|
f58ff68f3c | ||
|
|
a098aa0a89 | ||
|
|
7b3fc206f7 | ||
|
|
8f5d2a7f00 | ||
|
|
44c585e8c8 | ||
|
|
640f45f57d | ||
|
|
0ba8bace0d | ||
|
|
4202fb8cdc | ||
|
|
5f8b8835e1 | ||
|
|
78edcfe5cf | ||
|
|
2ca5e0c8b8 | ||
|
|
7ca1d99412 | ||
|
|
118bcbd6a6 | ||
|
|
987b442796 | ||
|
|
8ea77ccd04 | ||
|
|
004c2920f5 | ||
|
|
f02616acd1 | ||
|
|
69f9a484ca | ||
|
|
dde5367560 | ||
|
|
66da57f375 | ||
|
|
d328db543e | ||
|
|
c2c46f2843 | ||
|
|
96da48ae82 | ||
|
|
eb9eda1f6d | ||
|
|
f16558c126 | ||
|
|
1abf0fc134 | ||
|
|
b0ba1b2ecc | ||
|
|
a70717f2f7 | ||
|
|
5dec73f27c | ||
|
|
92ac87fa3f | ||
|
|
267239e3f2 | ||
|
|
52b863fe36 | ||
|
|
79337a4568 | ||
|
|
84750e2605 | ||
|
|
dfa1a3ad90 | ||
|
|
c1f9445e9d | ||
|
|
191db46c4f | ||
|
|
bc67bf6826 | ||
|
|
df534d30bf | ||
|
|
ce99dbe40c | ||
|
|
affcca8034 | ||
|
|
a6f31a577a | ||
|
|
9b814f4ed8 | ||
|
|
3091440162 | ||
|
|
3b2573afed | ||
|
|
1b64747c2b | ||
|
|
863212915c | ||
|
|
8ef82ebdd7 | ||
|
|
78aad924c5 | ||
|
|
24fda89665 | ||
|
|
59cb9ba344 | ||
|
|
d511eb19ef | ||
|
|
d3e9f51d6b | ||
|
|
e3499e5a70 | ||
|
|
026425407e | ||
|
|
18884111a6 | ||
|
|
3e5b152718 | ||
|
|
8e9637f6d4 | ||
|
|
d11ad39048 | ||
|
|
cb44d45eeb | ||
|
|
c15ffca48c | ||
|
|
902c11d074 | ||
|
|
35250677b9 | ||
|
|
1f3a1a0a95 | ||
|
|
38330b735c | ||
|
|
bda3c4ab7a | ||
|
|
d8d01ac353 | ||
|
|
51d935f419 | ||
|
|
c96f83b076 | ||
|
|
2fe4edb6df | ||
|
|
7308f5993c | ||
|
|
1e4f8be74b | ||
|
|
22ac12dc36 | ||
|
|
866296fefd | ||
|
|
9d574397bc | ||
|
|
b6e53b1a0c | ||
|
|
a46894f395 | ||
|
|
bee48dae7e | ||
|
|
e12f7b67e5 | ||
|
|
c7ceeb1833 | ||
|
|
12f6ed621e | ||
|
|
d7daada42e | ||
|
|
6c6849d8d7 | ||
|
|
e82336f355 | ||
|
|
1151b615a0 | ||
|
|
0cf891b9d9 | ||
|
|
aeb8fd6fda | ||
|
|
615f807909 | ||
|
|
985255afed | ||
|
|
9bec394d7f | ||
|
|
8f72e0ab83 | ||
|
|
6799a3bd35 | ||
|
|
588c7a81fc | ||
|
|
ce32ac19dd | ||
|
|
16aa0e4dba | ||
|
|
81cb765b7a | ||
|
|
d5523615ef | ||
|
|
ff5d899a92 | ||
|
|
d9f2c2b968 | ||
|
|
2f6adbd07c | ||
|
|
f0992d56c1 | ||
|
|
67bcf42125 | ||
|
|
935ddd12ec | ||
|
|
5a772f5410 | ||
|
|
698a32f364 | ||
|
|
bdea2acf67 | ||
|
|
377a11b160 | ||
|
|
9519c8edbd | ||
|
|
64fd207533 | ||
|
|
96e04f1c54 | ||
|
|
348c4b4431 | ||
|
|
bfbff723ac | ||
|
|
03dba4d7d0 | ||
|
|
febd7cbca8 | ||
|
|
e2b7b228c5 | ||
|
|
8b58d96aea | ||
|
|
0a2e01a616 | ||
|
|
a8908238d5 | ||
|
|
569138ac80 | ||
|
|
cf3faa1170 | ||
|
|
ae50374d55 | ||
|
|
d2705f033d | ||
|
|
5d12e2291c | ||
|
|
cd46d84dcb | ||
|
|
a3b0b8b3d0 | ||
|
|
4c60a70c6f | ||
|
|
a75b3a53aa | ||
|
|
94a79b57e9 | ||
|
|
dbe90eb3f5 | ||
|
|
3891348c26 | ||
|
|
84106d6282 | ||
|
|
37395931bf | ||
|
|
88e487be8e | ||
|
|
fd4c0e795a | ||
|
|
c79eb43347 | ||
|
|
860e74bffa | ||
|
|
4c759083be | ||
|
|
d3bb079be4 | ||
|
|
ed374684a6 | ||
|
|
169a46c38c | ||
|
|
1dbea434a3 | ||
|
|
a5b4d7a244 | ||
|
|
7ebb58b1fa | ||
|
|
ac5b3110f2 | ||
|
|
48259b4ffe | ||
|
|
041426fecb | ||
|
|
2caf7efdc6 | ||
|
|
b00e038b33 | ||
|
|
36503f0417 | ||
|
|
0daddf9896 | ||
|
|
f3364e1327 | ||
|
|
cf54115077 | ||
|
|
12a94c50b8 | ||
|
|
0dd12cf0a6 | ||
|
|
f92c336ae4 | ||
|
|
477c9ef577 | ||
|
|
e146a476bd | ||
|
|
45e74126da | ||
|
|
f32a39e79f | ||
|
|
489b144085 | ||
|
|
e7d311d23c | ||
|
|
4cc2dea2fe | ||
|
|
1e2cae7087 | ||
|
|
2b259f3704 | ||
|
|
0df5221784 | ||
|
|
4c78d35680 | ||
|
|
cdef8472e3 | ||
|
|
f9c933bf3b | ||
|
|
cc995ecef8 | ||
|
|
922a3631ae | ||
|
|
0903078618 | ||
|
|
8c759cb65a | ||
|
|
bd45c67528 | ||
|
|
562a206508 | ||
|
|
dd584e929f | ||
|
|
1828a2a81c | ||
|
|
a6746f77f0 | ||
|
|
184ff7a3b3 | ||
|
|
9db872db56 | ||
|
|
f1a1b89d13 | ||
|
|
14e0e96596 | ||
|
|
793c878c66 | ||
|
|
14887d5e88 | ||
|
|
366006273d | ||
|
|
6a69a726f2 | ||
|
|
e9cd4d95a7 | ||
|
|
d7e1dc1f95 | ||
|
|
19c8b4fe2d | ||
|
|
8b73a7375a | ||
|
|
974798f79c | ||
|
|
43c5de074f | ||
|
|
461cc1d5a8 | ||
|
|
3d6df07335 | ||
|
|
7689587879 | ||
|
|
e248b989e1 | ||
|
|
9920424a31 | ||
|
|
17be0a2c12 | ||
|
|
26fa38d052 | ||
|
|
34a4382920 | ||
|
|
e7c9b5a4f0 | ||
|
|
8c31904838 | ||
|
|
d522b608d3 | ||
|
|
3e7303c15c | ||
|
|
6e342983f9 | ||
|
|
a6d7ed3824 | ||
|
|
e003ec39fb | ||
|
|
62abc63f7a | ||
|
|
0955480f5b | ||
|
|
bf76affd06 | ||
|
|
b4d6525899 | ||
|
|
ba3555a66f | ||
|
|
d8be286831 | ||
|
|
65f5bc531c | ||
|
|
3ba4702181 | ||
|
|
ad9c42e832 | ||
|
|
0368d3be59 | ||
|
|
598549b5fb | ||
|
|
49086a3ae0 | ||
|
|
4676a7aa62 | ||
|
|
7d25b234d5 | ||
|
|
c6578870f0 | ||
|
|
4e8030bd81 | ||
|
|
094e130544 | ||
|
|
879fd5a13d | ||
|
|
c2bb49aca0 | ||
|
|
9ed14b6e8c | ||
|
|
4484721a9f | ||
|
|
4963a5797b | ||
|
|
13f5798ed4 | ||
|
|
db8e1dec3e | ||
|
|
c9bdecdb69 | ||
|
|
adb7726974 | ||
|
|
d6883d0c1c | ||
|
|
b2409ac708 | ||
|
|
cf189663a7 | ||
|
|
f0d36fd769 | ||
|
|
213e3e998a | ||
|
|
bef9c68f81 | ||
|
|
099d2fd03d | ||
|
|
9f71a6ab18 | ||
|
|
e088f7a552 | ||
|
|
2e6ce0481c | ||
|
|
23d39e5366 | ||
|
|
279664a578 | ||
|
|
2a3d128f3c | ||
|
|
9667365d9e | ||
|
|
d4ef26e0f3 | ||
|
|
a053e81797 | ||
|
|
1494bfe855 | ||
|
|
f1a4ba4e76 | ||
|
|
385c526414 | ||
|
|
5e95e02429 | ||
|
|
3081802b1c | ||
|
|
1663601dcb | ||
|
|
1a513c7bbf | ||
|
|
92cbdde429 | ||
|
|
0f6b1e4ae1 | ||
|
|
f915201a27 | ||
|
|
d1f76042e1 | ||
|
|
845aa733b7 | ||
|
|
a0e318827d | ||
|
|
39720a11dc | ||
|
|
da33bf3438 | ||
|
|
eda6f134a9 | ||
|
|
46e1c8ef73 | ||
|
|
f247139f1c | ||
|
|
566985cf72 | ||
|
|
70b4cdf520 | ||
|
|
081f211231 | ||
|
|
9cd8acab43 | ||
|
|
8b79a9708b | ||
|
|
969acbd47f | ||
|
|
31012671c5 | ||
|
|
e362b3b6aa | ||
|
|
d2ced93e58 | ||
|
|
958cd35e21 | ||
|
|
73e898773b | ||
|
|
46eae410c3 | ||
|
|
73a9e1c316 | ||
|
|
03862d4b6c | ||
|
|
557a2f08f7 | ||
|
|
ae90aa4ccc | ||
|
|
6eff2e6e74 | ||
|
|
955bb51f11 | ||
|
|
b583def913 | ||
|
|
dd85da053f | ||
|
|
ba6a01408d | ||
|
|
81d880fb4e | ||
|
|
bdbce67473 | ||
|
|
6079effae3 | ||
|
|
41aa1ee318 | ||
|
|
8d2fe315db | ||
|
|
22c3ac5be3 | ||
|
|
a517f0df1d | ||
|
|
c14f16bdf1 | ||
|
|
9c9854b6bf | ||
|
|
d280e16723 | ||
|
|
b93a9cb8bc | ||
|
|
8601052179 | ||
|
|
eaa20ff4bf | ||
|
|
e4c6e4bc48 | ||
|
|
c52597205e | ||
|
|
eee9de8271 | ||
|
|
c73033c0b4 | ||
|
|
aecfa3ff0f | ||
|
|
18f575bee4 | ||
|
|
522e752582 | ||
|
|
854ed8cfa9 | ||
|
|
4642205768 | ||
|
|
40dbfbe092 | ||
|
|
7882519c0e | ||
|
|
0234017ca1 | ||
|
|
6c315e5a9c | ||
|
|
ef0f91d8d0 | ||
|
|
9552784e72 | ||
|
|
ac20d7f302 | ||
|
|
f068327307 | ||
|
|
1bc698ae78 | ||
|
|
3f6691dcd5 | ||
|
|
31981b9080 | ||
|
|
167d29c39f | ||
|
|
28b78c2b27 | ||
|
|
b3d691fff6 | ||
|
|
8e5f2d91e8 | ||
|
|
cfa7f60e5f | ||
|
|
e0fcaa6103 | ||
|
|
1b2134d7a8 | ||
|
|
f922268af7 | ||
|
|
841a9f8082 | ||
|
|
4865ddb377 | ||
|
|
a556732e4f | ||
|
|
0ea31cb088 | ||
|
|
06fe7323eb | ||
|
|
d8d9259c36 | ||
|
|
261260b232 | ||
|
|
5b88894638 | ||
|
|
ddadaa828a | ||
|
|
a3cd10d83b | ||
|
|
b626c7620e | ||
|
|
5d90d8930e | ||
|
|
b01309c3bf | ||
|
|
2d1511b5dd | ||
|
|
e561304645 | ||
|
|
961d5591bd | ||
|
|
eca3f12fed | ||
|
|
a2c8796e04 | ||
|
|
ad301fd087 | ||
|
|
02b08939cd | ||
|
|
9b0d583f1b | ||
|
|
4a0a07f158 | ||
|
|
9c864c9759 | ||
|
|
f1dd1bd6bd | ||
|
|
85b1c309d1 | ||
|
|
6fe43b7b5c | ||
|
|
62fc986d96 | ||
|
|
03710f1fd2 | ||
|
|
2283c7a926 | ||
|
|
fb19ca8bf4 | ||
|
|
4ebfc1516c | ||
|
|
cc713e6c89 | ||
|
|
1be65adf02 | ||
|
|
25427ee60d | ||
|
|
c8eefbaa2e | ||
|
|
b339f426f2 | ||
|
|
9fd26fa574 | ||
|
|
be90bf0188 | ||
|
|
adcdaba199 | ||
|
|
17907589cc | ||
|
|
f333df181f | ||
|
|
61a01cb163 | ||
|
|
4ce557a829 | ||
|
|
4d10c9de95 | ||
|
|
5b84acebbc | ||
|
|
fc845dc936 | ||
|
|
7beae93441 | ||
|
|
c8f1297adb | ||
|
|
053a1d34e5 | ||
|
|
110a75ba0b | ||
|
|
826a31e57d | ||
|
|
23d7c3d0fe | ||
|
|
c5252e06a7 | ||
|
|
8af445e72b | ||
|
|
b382dd6fc0 | ||
|
|
2e84f82ed6 | ||
|
|
05f92b74e7 | ||
|
|
41b6f3ffa7 | ||
|
|
c895b76864 | ||
|
|
d7dac57a07 | ||
|
|
59ce88f044 | ||
|
|
7ebb184c8a | ||
|
|
bd44205b4e | ||
|
|
68087cdea5 | ||
|
|
5151aa677f | ||
|
|
a5ff34d423 | ||
|
|
03cfae45f8 | ||
|
|
4f83325e3c | ||
|
|
cd1765a0f3 | ||
|
|
5c744ad9aa | ||
|
|
f8eece362f | ||
|
|
d4ea30e081 | ||
|
|
25915b5521 | ||
|
|
ed0dcb5c3d | ||
|
|
a5b19bbc83 | ||
|
|
f6b44e03ac | ||
|
|
93bccb96b6 | ||
|
|
e2db37d28a | ||
|
|
5988c2ac78 | ||
|
|
e35ad7551b | ||
|
|
6c943a7158 | ||
|
|
2da14e5b2a | ||
|
|
84e38f765d | ||
|
|
81012e60ff | ||
|
|
52f0ae9350 | ||
|
|
fa0936da3c | ||
|
|
8a30c6347e | ||
|
|
09bcf34050 | ||
|
|
d31271fee3 | ||
|
|
afaa001738 | ||
|
|
026a53f3ff | ||
|
|
599c7919ce | ||
|
|
778b601cd5 | ||
|
|
4518f089cc | ||
|
|
91dd03ba67 | ||
|
|
86092541ed | ||
|
|
70a59daa6f | ||
|
|
34865f797f | ||
|
|
24a5f7a38e | ||
|
|
554949102b | ||
|
|
0f3b95802d | ||
|
|
868910fddf | ||
|
|
436542eff8 | ||
|
|
f4c05c67a2 | ||
|
|
6f7f67df5a | ||
|
|
50b6163e73 | ||
|
|
beb539abaf | ||
|
|
6dd1d45a02 | ||
|
|
90ea01aa46 | ||
|
|
6c52105ac7 | ||
|
|
3332375d7c | ||
|
|
ed2e083d13 | ||
|
|
80f7c5ed9d | ||
|
|
8d33cbeaca | ||
|
|
b0b8bc7385 | ||
|
|
edbc8b28d5 | ||
|
|
da9bab16e1 | ||
|
|
75098d8b3e | ||
|
|
65bb7fd533 | ||
|
|
0259e78b2f | ||
|
|
99b8888a1b | ||
|
|
689f4ef606 | ||
|
|
b0c40e1e37 | ||
|
|
3da70c3e8b | ||
|
|
72a6681ac1 | ||
|
|
1ab555b590 | ||
|
|
4602ec7688 | ||
|
|
5c39d8d12e | ||
|
|
97284fcf87 | ||
|
|
c8b1654e0a | ||
|
|
0400d0e0f0 | ||
|
|
4040f6bec6 | ||
|
|
a4a3aa045b | ||
|
|
a5a18903c7 | ||
|
|
6990986d9f | ||
|
|
06ded0098c | ||
|
|
f1fbea30f1 | ||
|
|
d29283ff21 | ||
|
|
35779dad8b | ||
|
|
ee0d6420a0 | ||
|
|
f66d847032 | ||
|
|
99d0c5e2c6 | ||
|
|
fa55b94528 | ||
|
|
523752b263 | ||
|
|
7df4b8e8cc | ||
|
|
7a4ed5a337 | ||
|
|
ec37085e39 | ||
|
|
61e1e2729d | ||
|
|
2efa68dd60 | ||
|
|
d2c92781c8 | ||
|
|
1906523489 | ||
|
|
24beaa0d18 | ||
|
|
e1deb48121 | ||
|
|
b3891bacaa | ||
|
|
7939904ee3 | ||
|
|
7dcd69a2df | ||
|
|
002b2d0134 | ||
|
|
8b10ec93c2 | ||
|
|
cf9ecd1564 | ||
|
|
f31147248b | ||
|
|
2f9c540660 | ||
|
|
09489feff9 | ||
|
|
0d17a69ee4 | ||
|
|
c5755a4ec4 | ||
|
|
20bc3719a4 | ||
|
|
7dc41ab205 | ||
|
|
acc21e3b43 | ||
|
|
41a6726beb | ||
|
|
2f8ba75970 | ||
|
|
eb08129226 | ||
|
|
816823b115 | ||
|
|
b6059939b4 | ||
|
|
b8e23b2d7e | ||
|
|
a06846fa74 | ||
|
|
e8b2d80037 | ||
|
|
853463b7cd | ||
|
|
518e1a6405 | ||
|
|
afd376ddbc | ||
|
|
1ce325ee8a | ||
|
|
1e947a34a8 | ||
|
|
fe1e5aeebf | ||
|
|
8386b9a0b4 | ||
|
|
40323e3afe | ||
|
|
603dee7661 | ||
|
|
2a27cc46e2 | ||
|
|
a9a06a1e4e | ||
|
|
5f73725b8f | ||
|
|
324983024a | ||
|
|
7cb63b1da0 | ||
|
|
31603380c1 | ||
|
|
5a3d3b0324 | ||
|
|
78006855ee | ||
|
|
71520f6709 | ||
|
|
3f5a09229d | ||
|
|
5609771993 | ||
|
|
79b01cdc3c | ||
|
|
73f99b5e22 | ||
|
|
7d6f8eb495 | ||
|
|
eac5340b03 | ||
|
|
36b196dd93 | ||
|
|
549dcd32ca | ||
|
|
a59f56d852 | ||
|
|
c2b9e06a4a | ||
|
|
964978d45b | ||
|
|
599490bb6e | ||
|
|
a841e1fb2e | ||
|
|
cb4c736fab | ||
|
|
42474e551f | ||
|
|
061e055d1b | ||
|
|
12f2caa8d6 | ||
|
|
b890b5f0dc | ||
|
|
c3df9e6270 | ||
|
|
6463fbee32 | ||
|
|
e43cdc6674 | ||
|
|
59216a9ae1 | ||
|
|
47a620bd36 | ||
|
|
37dbf4d8ec | ||
|
|
80a067806d | ||
|
|
f20065f546 | ||
|
|
43d50e270a | ||
|
|
9c84f13425 | ||
|
|
b2cf7a16f2 | ||
|
|
c2b2fafc9c | ||
|
|
5b88ba6d1d | ||
|
|
d036021a78 | ||
|
|
5462d1e9f8 | ||
|
|
2f411dfc9c | ||
|
|
d6cff870e5 | ||
|
|
e1cd45c57e | ||
|
|
7572b6f66b | ||
|
|
e489b08bc1 | ||
|
|
712e05479b | ||
|
|
7c6c9eed36 | ||
|
|
63eee28a82 | ||
|
|
0777eaad10 | ||
|
|
974dd8ce57 | ||
|
|
957d08f4c6 | ||
|
|
156b499f93 | ||
|
|
f31100326b | ||
|
|
9b7b1d6a61 | ||
|
|
409c1b7584 | ||
|
|
f45b5da71a | ||
|
|
f57b606f72 | ||
|
|
44f9bc5f0e | ||
|
|
5c7e3c6bab | ||
|
|
737151ba09 | ||
|
|
2eef39d64a | ||
|
|
821f76bcd6 | ||
|
|
37ec058572 | ||
|
|
aa34df7a4c | ||
|
|
0f8d464706 | ||
|
|
d6be7b4cae | ||
|
|
70aa01b261 | ||
|
|
fcfb73c755 | ||
|
|
12ce3282ff | ||
|
|
49f79a331c | ||
|
|
ac0a853030 | ||
|
|
e8ae4e76a3 | ||
|
|
c73b098f30 | ||
|
|
4292e26135 | ||
|
|
d09d7521d6 | ||
|
|
20d03cb817 | ||
|
|
b07bbab069 | ||
|
|
295663f6a6 | ||
|
|
47caa2c3a0 | ||
|
|
4ddc422cfa | ||
|
|
856c8aa611 | ||
|
|
6f600f74af | ||
|
|
be8db602b8 | ||
|
|
7871e868a9 | ||
|
|
44467971f2 | ||
|
|
108fc4a0d8 | ||
|
|
65ac8d4b2b | ||
|
|
dd59e3a831 | ||
|
|
1e98d56bb6 | ||
|
|
bee93f4743 | ||
|
|
9888510158 | ||
|
|
5fd4ed91d8 | ||
|
|
2a949cd8f1 | ||
|
|
312cbc86e9 | ||
|
|
85ded6e500 | ||
|
|
ce5a81d83c | ||
|
|
5453ae81a6 | ||
|
|
ff84f2b2f7 | ||
|
|
e9ac20a149 | ||
|
|
7a228cac43 | ||
|
|
442741d246 | ||
|
|
89ff8b3fcb | ||
|
|
5773f141b8 | ||
|
|
f8da8f6929 | ||
|
|
f7de055f67 | ||
|
|
07d09aea85 | ||
|
|
f63ceed1ae | ||
|
|
b93d72296c | ||
|
|
1bc15a8507 | ||
|
|
5cf26af9f0 | ||
|
|
04c9646e05 | ||
|
|
67bf1a93e4 | ||
|
|
c263f11170 | ||
|
|
bb4c646714 | ||
|
|
bd521f858e | ||
|
|
f66fcfbe6d | ||
|
|
10fc9fe268 | ||
|
|
73c75635b1 | ||
|
|
cc7d745ce6 | ||
|
|
044d830b64 | ||
|
|
38c84bb1f6 | ||
|
|
b50e066dee | ||
|
|
757e8eb57c | ||
|
|
a696afaeb8 | ||
|
|
cde497c94e | ||
|
|
94cf6424f5 | ||
|
|
b8013a57e2 | ||
|
|
1b23210902 | ||
|
|
4bb30deca6 | ||
|
|
31bf615fe8 | ||
|
|
bbf46358fa | ||
|
|
1cf793233f | ||
|
|
7acc537c7a | ||
|
|
97c1a2245b | ||
|
|
8e9269fdf9 | ||
|
|
d9b2037b50 | ||
|
|
95827c3ada | ||
|
|
4e2bbc04fa | ||
|
|
61eff6e7e8 | ||
|
|
1d4d5f0c93 | ||
|
|
939de6b177 | ||
|
|
ad4bc206ab | ||
|
|
8cfa5ba39e | ||
|
|
c2e6d1c6bf | ||
|
|
5a658b7080 | ||
|
|
a6adb314ec | ||
|
|
5714578783 | ||
|
|
539125ff47 | ||
|
|
53237c297f | ||
|
|
2d8885cb0c | ||
|
|
4fdf85bbdb | ||
|
|
728d57d955 | ||
|
|
e53a2e7b43 | ||
|
|
e682fd07cb | ||
|
|
bddd22cfab | ||
|
|
19310470b6 | ||
|
|
02fcccc8c7 | ||
|
|
9f230ae339 | ||
|
|
7b21c1bcbe | ||
|
|
9c295d1884 | ||
|
|
aef53a8753 | ||
|
|
5149078f6a | ||
|
|
4d95248f16 | ||
|
|
368f52ade6 | ||
|
|
8cc2ba4770 | ||
|
|
f40398bf42 | ||
|
|
783a21d88d | ||
|
|
62e7c861bd | ||
|
|
9d5b6eac55 | ||
|
|
72c5de6eae | ||
|
|
1d833419aa | ||
|
|
e17e2a636b | ||
|
|
45e0cbdb25 | ||
|
|
dc9d48850f | ||
|
|
9092549f07 | ||
|
|
a94b5ba0c0 | ||
|
|
5d1efd84a4 | ||
|
|
8af953e20d | ||
|
|
66132a912a | ||
|
|
b852cbdc80 | ||
|
|
350caee808 | ||
|
|
7d05236514 | ||
|
|
465b43be6a | ||
|
|
006edacd55 | ||
|
|
a9666a9f6e | ||
|
|
7de492caa7 | ||
|
|
93ee4716cc | ||
|
|
8a60d4cf2f | ||
|
|
6489444158 | ||
|
|
59a66a3ea5 | ||
|
|
e17550e23e | ||
|
|
0f8d6daf99 | ||
|
|
08925a72c6 | ||
|
|
1101299168 | ||
|
|
22be8e2e9d | ||
|
|
6db2240f8a | ||
|
|
54eac18eea | ||
|
|
284e748449 | ||
|
|
3111718eb1 | ||
|
|
fd8d17c5e5 | ||
|
|
109bb62209 | ||
|
|
cc661b26fa | ||
|
|
ea69957ed1 | ||
|
|
700f641e29 | ||
|
|
83f4eeb3b5 | ||
|
|
d1f4cdebf3 | ||
|
|
d95ba43fd1 | ||
|
|
7dc07f6d21 | ||
|
|
6472d35d91 | ||
|
|
95c87919a8 | ||
|
|
077b4d5c89 | ||
|
|
a2105de402 | ||
|
|
d049e0a149 | ||
|
|
7463be862f | ||
|
|
bc6652f443 | ||
|
|
56167f84ad | ||
|
|
db4ecce20b | ||
|
|
95518f1948 | ||
|
|
d4beb2f79b | ||
|
|
9be995bb08 | ||
|
|
c1d47290b1 | ||
|
|
4d6116ed40 | ||
|
|
18ba394901 | ||
|
|
3a83753611 | ||
|
|
c436b586d2 | ||
|
|
f84e2c2ac7 | ||
|
|
4e8c94fd2d | ||
|
|
8f3fd37d47 | ||
|
|
7960e9b309 | ||
|
|
4875544888 | ||
|
|
849bdc52f6 | ||
|
|
63bb05b2d4 | ||
|
|
916ad0a58e | ||
|
|
15055fa509 | ||
|
|
8ee704e123 | ||
|
|
0d552cd880 | ||
|
|
60ce5c67de | ||
|
|
421f932053 | ||
|
|
542d6361f2 | ||
|
|
81bafa00ac | ||
|
|
84d0c17c4b | ||
|
|
be7e2bed6f | ||
|
|
a7bfd0af41 | ||
|
|
55ef547a85 | ||
|
|
19129c8786 | ||
|
|
b5737ce9c1 | ||
|
|
f921d9a1f8 | ||
|
|
9609b48f2f | ||
|
|
5926d396f9 | ||
|
|
7cf16766c4 | ||
|
|
0e200e0c34 | ||
|
|
771a544d0f | ||
|
|
fba707f6a8 | ||
|
|
4e28e2cb59 | ||
|
|
5c3c8b7b8a | ||
|
|
6a52a1dc63 | ||
|
|
445825df44 | ||
|
|
b93c47fa60 | ||
|
|
90b831600f | ||
|
|
b3dd368920 | ||
|
|
c8db90b644 | ||
|
|
7ed65529df | ||
|
|
a8b59f5f59 | ||
|
|
eb966ec041 | ||
|
|
588789cb77 | ||
|
|
3640f977c8 | ||
|
|
28f12a4874 | ||
|
|
93378406c3 | ||
|
|
602d04af82 | ||
|
|
dcfbf2b154 | ||
|
|
5c6d755750 | ||
|
|
3c545d488d | ||
|
|
bd0f84605f | ||
|
|
6e9a69be5c | ||
|
|
392df6ba72 | ||
|
|
c66cffd6a6 | ||
|
|
b3b3e3ea20 | ||
|
|
067a88b3e7 | ||
|
|
251d5f4135 | ||
|
|
44e574f440 | ||
|
|
9e583f9ff0 | ||
|
|
611816c8e3 | ||
|
|
0511a62ca1 | ||
|
|
31e34d6f19 | ||
|
|
5360fe49d4 | ||
|
|
bf94febb11 | ||
|
|
dbd3bd50df | ||
|
|
c1a1fb8d87 | ||
|
|
b10568e917 | ||
|
|
0e1ad39ede | ||
|
|
c8ce06d110 | ||
|
|
b0cfcb1999 | ||
|
|
944b857825 | ||
|
|
34c8f33c3c | ||
|
|
07d74ee692 | ||
|
|
d28158bc74 | ||
|
|
8c9fb956ff | ||
|
|
db632ae847 | ||
|
|
3bef4284e3 | ||
|
|
5229b2959e | ||
|
|
8769234e28 | ||
|
|
bbdd1915eb | ||
|
|
de8a244500 | ||
|
|
3066a142b8 | ||
|
|
ccf047b1ab | ||
|
|
ce725252cc | ||
|
|
73f07b2939 | ||
|
|
b58efa3df0 | ||
|
|
9f885407f5 | ||
|
|
ef59fd4b6f | ||
|
|
36e7c2467e | ||
|
|
f7ce83ea34 | ||
|
|
81182bb125 | ||
|
|
2424df0d18 | ||
|
|
e9d07eadaa | ||
|
|
6f0e944c7e | ||
|
|
cd8d2c141e | ||
|
|
14ac66ff4e | ||
|
|
75bf758042 | ||
|
|
3acc521741 | ||
|
|
b7fb9e182b | ||
|
|
8574bf9d98 | ||
|
|
1a80439825 | ||
|
|
28bc07da2f | ||
|
|
8517cc8211 | ||
|
|
8aa8ae239a | ||
|
|
024ec86dc5 | ||
|
|
f632ef0de8 | ||
|
|
c58ad64a28 | ||
|
|
cbf3ae4db4 | ||
|
|
9ac7acf8b3 | ||
|
|
9dadb6da4c | ||
|
|
b6bbbeb9d3 | ||
|
|
517e9f92ba | ||
|
|
7ec09c80a5 | ||
|
|
b0bfe341df | ||
|
|
70cf6546ca | ||
|
|
0cdab52418 | ||
|
|
777a95d23c | ||
|
|
06caace827 | ||
|
|
0a0a766c0d | ||
|
|
2f9eacdf66 | ||
|
|
79ac85e048 | ||
|
|
23c5ddce83 | ||
|
|
3bb9d220bb | ||
|
|
62cb8358cc | ||
|
|
ada5fd53e7 | ||
|
|
2f30451067 | ||
|
|
6b5c2be701 | ||
|
|
1ba70706c2 | ||
|
|
6760744249 | ||
|
|
11c7d586d9 | ||
|
|
39cd83b171 | ||
|
|
2fdbc88d8c | ||
|
|
cc0f1be5d2 | ||
|
|
1952505e52 | ||
|
|
c4086b9127 | ||
|
|
0ada09891c | ||
|
|
651b4d2461 | ||
|
|
01b2468fea | ||
|
|
28e714db1e | ||
|
|
a0fd02e0c0 | ||
|
|
c925b3d218 | ||
|
|
5da4386f31 | ||
|
|
4104dec87f | ||
|
|
d40a555531 | ||
|
|
95df91a03b | ||
|
|
3a8caa15b9 | ||
|
|
a5a25f02e3 | ||
|
|
bfd7be543a | ||
|
|
778e790e82 | ||
|
|
17eeb22971 | ||
|
|
991fe31569 | ||
|
|
4a5a9b73b1 | ||
|
|
7652336c84 | ||
|
|
94113827a7 | ||
|
|
cc10ee0134 | ||
|
|
5ad0fdf39c | ||
|
|
7201a8d634 | ||
|
|
3fde7365f9 | ||
|
|
f6a5bc9b40 | ||
|
|
9a5917a331 | ||
|
|
94c0324e8a | ||
|
|
d3b5594092 | ||
|
|
742d580eae | ||
|
|
d4a3cadd09 | ||
|
|
ec0feb68f4 | ||
|
|
38c032b79e | ||
|
|
2b7f2d4744 | ||
|
|
05b86a71fd | ||
|
|
e0c0f29fc6 | ||
|
|
3e3dc3a6ab | ||
|
|
8a6945ff3b | ||
|
|
e6111c0d48 | ||
|
|
b2f5bee20d | ||
|
|
04c4451f7d | ||
|
|
ccab1844ce | ||
|
|
5a6be54970 | ||
|
|
ce6b5105c6 | ||
|
|
de7da8b26e | ||
|
|
24932d6ba3 | ||
|
|
d5fd5954d1 | ||
|
|
977075763d | ||
|
|
757172934e | ||
|
|
1a520f8782 | ||
|
|
b56c1b956c | ||
|
|
1a279d14c4 | ||
|
|
0d8e763a5f | ||
|
|
bf6d3649a4 | ||
|
|
65fd705d9a | ||
|
|
311e54451b | ||
|
|
cb6607a169 | ||
|
|
8caa4e9cb6 | ||
|
|
d6338d7b11 | ||
|
|
f96a5ec774 | ||
|
|
1be8e7e216 | ||
|
|
cf9cf9d7bb | ||
|
|
6ba1795ded | ||
|
|
8608c45309 | ||
|
|
90516217e0 | ||
|
|
04aa22b510 | ||
|
|
1cd56decab | ||
|
|
585f8f4683 | ||
|
|
1070d8d3fa | ||
|
|
dcc7ba8f93 | ||
|
|
7d5b20314c | ||
|
|
29e048af7b | ||
|
|
c38f0d751b | ||
|
|
3623afa721 | ||
|
|
6a096fbb27 | ||
|
|
6404071a01 | ||
|
|
735205492e | ||
|
|
a6feb77e52 | ||
|
|
13b3b2fd23 | ||
|
|
9a6d709082 | ||
|
|
6eafab8286 | ||
|
|
c901512db0 | ||
|
|
31ea032054 | ||
|
|
89543e927a | ||
|
|
a52386e6ad | ||
|
|
70546cd2ec | ||
|
|
a7c99cbbd2 | ||
|
|
40780ccec7 | ||
|
|
7078c91f7d | ||
|
|
0e2168392c | ||
|
|
380006c9d8 | ||
|
|
4127882e5f | ||
|
|
5f17d30973 | ||
|
|
6ace46eece | ||
|
|
37cab07295 | ||
|
|
cfeb88f649 | ||
|
|
7ce197e0c8 | ||
|
|
f93b1167f1 | ||
|
|
152ca63529 | ||
|
|
2e8d5311a5 | ||
|
|
23b5fd1c12 | ||
|
|
7a8ba7d47d | ||
|
|
1a509cf3e0 | ||
|
|
9848f9613c | ||
|
|
93cefb88f5 | ||
|
|
7132e1fee1 | ||
|
|
e70e1b8ad7 | ||
|
|
77d8a8e43d | ||
|
|
0b75a7d0d3 | ||
|
|
f6772eaf59 | ||
|
|
7ac5abe7f8 | ||
|
|
157e6b2a33 | ||
|
|
a385ea7c52 | ||
|
|
c58a3c41d8 | ||
|
|
0a7df86f3f | ||
|
|
f8df7ebb7c | ||
|
|
60503c31fb | ||
|
|
77220e24dd | ||
|
|
3c25e11c5f | ||
|
|
be2ffc31b2 | ||
|
|
adfb24ce02 | ||
|
|
e6d50f94ee | ||
|
|
520798bfa6 | ||
|
|
e539a36ae7 | ||
|
|
bfab2d405b | ||
|
|
df38f00cf2 | ||
|
|
a30ce1c44d | ||
|
|
baf4a241a2 | ||
|
|
c59e792178 | ||
|
|
e6a99c1d33 | ||
|
|
3548628c2c | ||
|
|
bb84157a21 | ||
|
|
eb10aa8c97 | ||
|
|
07428922c3 | ||
|
|
0f7e22d8b7 | ||
|
|
caae57d960 | ||
|
|
f91384596c | ||
|
|
cb38976162 | ||
|
|
4c5c4d1700 | ||
|
|
e3fabe92bd | ||
|
|
142740f080 | ||
|
|
09e51c2399 | ||
|
|
0892eb271d | ||
|
|
536be76ecb | ||
|
|
6bfdf0eb4d | ||
|
|
8320ed5a92 | ||
|
|
43677685bb | ||
|
|
61e0aa9845 | ||
|
|
34eee005a8 | ||
|
|
6fa136da0c | ||
|
|
b5abc6c724 | ||
|
|
f74a45a33e | ||
|
|
0a1d04495d | ||
|
|
49dee560fd | ||
|
|
dfb3dfb74d | ||
|
|
c4689c3bcc | ||
|
|
83452d73bc | ||
|
|
e5417d12ca | ||
|
|
6e19e6f0a0 | ||
|
|
071281c13a | ||
|
|
61101987f9 | ||
|
|
a057e50684 | ||
|
|
5f3dc660c8 | ||
|
|
51ead2f6bd | ||
|
|
d433b25627 | ||
|
|
bd7671c07e | ||
|
|
847178b7be | ||
|
|
a899ea8b4d | ||
|
|
7b83b99ac9 | ||
|
|
c11acb6308 | ||
|
|
d76103eb76 | ||
|
|
779f984a30 | ||
|
|
9c55017191 | ||
|
|
c6f575d8d3 | ||
|
|
2c0c22dbf3 | ||
|
|
73a99a7dea | ||
|
|
3c81337630 | ||
|
|
3f41ba6bdf | ||
|
|
2812f61957 | ||
|
|
f23cee17eb | ||
|
|
f24fcfca69 | ||
|
|
d60506a75d | ||
|
|
77dee439e6 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
||||
github: [Aircoookie]
|
||||
custom: ['https://paypal.me/Aircoookie']
|
||||
github: [Aircoookie,blazoncek]
|
||||
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/bug.yml
vendored
7
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -45,7 +45,7 @@ body:
|
||||
attributes:
|
||||
label: What version of WLED?
|
||||
description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message"
|
||||
placeholder: "e.g. WLED 0.13.0-b7 (build 2202222)"
|
||||
placeholder: "e.g. WLED 0.13.1 (build 2203150)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -56,6 +56,9 @@ body:
|
||||
options:
|
||||
- ESP8266
|
||||
- ESP32
|
||||
- ESP32-S3
|
||||
- ESP32-S2
|
||||
- ESP32-C3
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
@@ -80,4 +83,4 @@ body:
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
required: true
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: WLED Discord community
|
||||
url: https://discord.gg/KuqP7NE
|
||||
about: Please ask and answer questions and discuss setup issues here!
|
||||
- name: WLED community forum
|
||||
url: https://wled.discourse.group/
|
||||
about: For issues and ideas that might need longer discussion.
|
||||
- name: kno.wled.ge base
|
||||
url: https://kno.wled.ge/basics/faq/
|
||||
about: Take a look at the frequently asked questions and documentation, perhaps your question is already answered!
|
||||
19
.github/ISSUE_TEMPLATE/question.md
vendored
19
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Have a question about using WLED?
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Take a look at the wiki and FAQ, perhaps your question is already answered!**
|
||||
[FAQ](https://github.com/Aircoookie/WLED/wiki/FAQ)
|
||||
|
||||
**Please consider asking your question on the WLED forum or Discord**
|
||||
[Forum](https://wled.discourse.group/)
|
||||
[Discord](https://discord.gg/KuqP7NE)
|
||||
[What to post where?](https://github.com/Aircoookie/WLED/issues/658)
|
||||
|
||||
**If you do not like to use these platforms, delete this template and ask away!**
|
||||
Please keep in mind though that the issue section is generally not the preferred place for general questions.
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,7 +3,6 @@
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.vscode
|
||||
!.vscode/extensions.json
|
||||
/wled00/Release
|
||||
/wled00/extLibs
|
||||
/platformio_override.ini
|
||||
@@ -15,3 +14,7 @@
|
||||
node_modules
|
||||
.idea
|
||||
.direnv
|
||||
wled-update.sh
|
||||
esp01-update.sh
|
||||
/wled00/LittleFS
|
||||
replace_fs.py
|
||||
4
.gitpod.Dockerfile
vendored
4
.gitpod.Dockerfile
vendored
@@ -1,5 +1,3 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN pip3 install -U platformio
|
||||
USER gitpod
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
tasks:
|
||||
- command: platformio run
|
||||
- command: pip3 install -U platformio && platformio run
|
||||
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- ms-vscode.cpptools@0.26.3:u3GsZ5PK12Ddr79vh4TWgQ==
|
||||
- eamodio.gitlens@10.2.1:e0IYyp0efFqVsrZwsIe8CA==
|
||||
- Atishay-Jain.All-Autocomplete@0.0.23:fbZNfSpnd8XkAHGfAPS2rA==
|
||||
- 2gua.rainbow-brackets@0.0.6:Tbu8dTz0i+/bgcKQTQ5b8g==
|
||||
- Atishay-Jain.All-Autocomplete
|
||||
- esbenp.prettier-vscode
|
||||
- shardulm94.trailing-spaces
|
||||
|
||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -1,6 +1,94 @@
|
||||
## WLED changelog
|
||||
|
||||
### Builds after release 0.12.0
|
||||
### WLED release 0.14.0-b1
|
||||
|
||||
#### Build 2212222
|
||||
|
||||
- Version bump to v0.14.0-b1 "Hoshi"
|
||||
- Full changelog TBD
|
||||
|
||||
### WLED release 0.13.3
|
||||
|
||||
- Version bump to v0.13.3 "Toki"
|
||||
- Disable ESP watchdog by default (fixes flickering and boot issues on a fresh install)
|
||||
- Added support for LPD6803
|
||||
|
||||
### WLED release 0.13.2
|
||||
|
||||
#### Build 2208140
|
||||
|
||||
- Version bump to v0.13.2 "Toki"
|
||||
- Added option to receive live data on the main segment only (PR #2601)
|
||||
- Enable ESP watchdog by default (PR #2657)
|
||||
- Fixed race condition when saving bus config
|
||||
- Better potentiometer filtering (PR #2693)
|
||||
- More suitable DMX libraries (PR #2652)
|
||||
- Fixed outgoing serial TPM2 message length (PR #2628)
|
||||
- Fixed next universe overflow and Art-Net DMX start address (PR #2607)
|
||||
- Fixed relative segment brightness (PR #2665)
|
||||
|
||||
### Builds between releases 0.13.1 and 0.13.2
|
||||
|
||||
#### Build 2203191
|
||||
|
||||
- Fixed sunrise/set calculation (once again)
|
||||
|
||||
#### Build 2203190
|
||||
|
||||
- Fixed `/json/cfg` unable to set busses (#2589)
|
||||
- Fixed Peek with odd LED counts > 255 (#2586)
|
||||
|
||||
#### Build 2203160
|
||||
|
||||
- Version bump to v0.13.2-a0 "Toki"
|
||||
- Add ability to skip up to 255 LEDs
|
||||
- Dependency version bumps
|
||||
|
||||
### WLED release 0.13.1
|
||||
|
||||
#### Build 2203150
|
||||
|
||||
- Version bump to v0.13.1 "Toki"
|
||||
- Fix persistent preset bug, preventing save of new presets
|
||||
|
||||
### WLED release 0.13.0
|
||||
|
||||
#### Build 2203142
|
||||
|
||||
- Release of WLED v0.13.0 "Toki"
|
||||
- Reduce APA102 hardware SPI frequency to 5Mhz
|
||||
- Remove `persistent` parameter in `savePreset()`
|
||||
|
||||
### Builds between releases 0.12.0 and 0.13.0
|
||||
|
||||
#### Build 2203140
|
||||
|
||||
- Added factory reset by pressing button 0 for >10 seconds
|
||||
- Added ability to set presets from DMX Effect mode
|
||||
- Simplified label hiding JS in user interface
|
||||
- Fixed JSON `{"live":true}` indefinite realtime mode
|
||||
|
||||
#### Build 2203080
|
||||
|
||||
- Disabled auto white mode in segments with no RGB bus
|
||||
- Fixed hostname string not 0-terminated
|
||||
- Fixed Popcorn mode not lighting first LED on pop
|
||||
|
||||
#### Build 2203060
|
||||
|
||||
- Dynamic hiding of unused color controls in UI (PR #2567)
|
||||
- Removed native Cronixie support and added Cronixie usermod
|
||||
- Fixed disabled timed preset expanding calendar
|
||||
- Fixed Color Order setting shown for analog busses
|
||||
- Fixed incorrect operator (#2566)
|
||||
|
||||
#### Build 2203011
|
||||
|
||||
- IR rewrite (PR #2561), supports CCT
|
||||
- Added locate button to Time settings
|
||||
- CSS fixes and adjustments
|
||||
- Consistent Tab indentation in index JS and CSS
|
||||
- Added initial contribution style guideline
|
||||
|
||||
#### Build 2202222
|
||||
|
||||
|
||||
78
CONTRIBUTING.md
Normal file
78
CONTRIBUTING.md
Normal file
@@ -0,0 +1,78 @@
|
||||
## Thank you for making WLED better!
|
||||
|
||||
Here are a few suggestions to make it easier for you to contribute!
|
||||
|
||||
### Code style
|
||||
|
||||
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
|
||||
Below are the guidelines we use in the WLED repository.
|
||||
|
||||
#### Indentation
|
||||
|
||||
We use tabs for Indentation in Web files (.html/.css/.js) and spaces (2 per indentation level) for all other files.
|
||||
You are all set if you have enabled `Editor: Detect Indentation` in VS Code.
|
||||
|
||||
#### Blocks
|
||||
|
||||
Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable.
|
||||
|
||||
Good:
|
||||
```cpp
|
||||
if (a == b) {
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
if (a == b)
|
||||
{
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
if (a == b) doStuff(a);
|
||||
```
|
||||
|
||||
There should always be a space between a keyword and its condition and between the condition and brace.
|
||||
Within the condition, no space should be between the paranthesis and variables.
|
||||
Spaces between variables and operators are up to the authors discretion.
|
||||
There should be no space between function names and their argument parenthesis.
|
||||
|
||||
Good:
|
||||
```cpp
|
||||
if (a == b) {
|
||||
doStuff(a);
|
||||
}
|
||||
```
|
||||
|
||||
Not good:
|
||||
```cpp
|
||||
if( a==b ){
|
||||
doStuff ( a);
|
||||
}
|
||||
```
|
||||
|
||||
#### Comments
|
||||
|
||||
Comments should have a space between the delimiting characters (e.g. `//`) and the comment text.
|
||||
Note: This is a recent change, the majority of the codebase still has comments without spaces.
|
||||
|
||||
Good:
|
||||
```
|
||||
// This is a comment.
|
||||
|
||||
/* This is a CSS inline comment */
|
||||
|
||||
/*
|
||||
* This is a comment
|
||||
* wrapping over multiple lines,
|
||||
* used in WLED for file headers and function explanations
|
||||
*/
|
||||
|
||||
<!-- This is an HTML comment -->
|
||||
```
|
||||
|
||||
There is no set character limit for a comment within a line,
|
||||
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
|
||||
Inline comments are OK if they describe that line only and are not exceedingly wide.
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.0-b7",
|
||||
"version": "0.14.0-b1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1339,9 +1339,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
@@ -1742,9 +1742,9 @@
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
@@ -2067,9 +2067,9 @@
|
||||
"integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz",
|
||||
"integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==",
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.13.0-b7",
|
||||
"version": "0.14.0-b1",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
204
platformio.ini
204
platformio.ini
@@ -6,16 +6,14 @@
|
||||
# ENVIRONMENTS
|
||||
#
|
||||
# Please uncomment one of the lines below to select your board(s)
|
||||
# (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Travis CI binaries (use `platformio_override.ini` when building for your own board; see `platformio_override.ini.sample` for an example)
|
||||
; default_envs = travis_esp8266, travis_esp32
|
||||
|
||||
# Release binaries
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32_eth_ota1mapp, esp32s2_saola, esp32c3
|
||||
# Release / CI binaries
|
||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3, esp32s3dev_8MB
|
||||
|
||||
# Build everything
|
||||
; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
|
||||
; default_envs = esp32dev, esp8285_4CH_MagicHome, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips
|
||||
|
||||
# Single binaries (uncomment your board)
|
||||
; default_envs = elekstube_ips
|
||||
@@ -35,6 +33,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32_
|
||||
; default_envs = wemos_shield_esp32
|
||||
; default_envs = m5atom
|
||||
; default_envs = esp32_eth
|
||||
; default_envs = esp32dev_qio80
|
||||
; default_envs = esp32_eth_ota1mapp
|
||||
; default_envs = esp32s2_saola
|
||||
|
||||
@@ -55,14 +54,14 @@ extra_configs =
|
||||
arduino_core_2_6_3 = espressif8266@2.3.3
|
||||
arduino_core_2_7_4 = espressif8266@2.6.2
|
||||
arduino_core_3_0_0 = espressif8266@3.0.0
|
||||
arduino_core_3_0_2 = espressif8266@3.2.0
|
||||
arduino_core_3_2_0 = espressif8266@3.2.0
|
||||
|
||||
# Development platforms
|
||||
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
|
||||
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
|
||||
|
||||
# Platform to use for ESP8266
|
||||
platform_wled_default = ${common.arduino_core_2_7_4}
|
||||
platform_wled_default = ${common.arduino_core_3_2_0}
|
||||
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
|
||||
platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
|
||||
platformio/toolchain-xtensa @ ~2.40802.200502
|
||||
@@ -112,18 +111,14 @@ build_flags =
|
||||
-D DECODE_SONY=true
|
||||
-D DECODE_SAMSUNG=true
|
||||
-D DECODE_LG=true
|
||||
; -Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library
|
||||
-DWLED_USE_MY_CONFIG
|
||||
; -D USERMOD_SENSORSTOMQTT
|
||||
#For ADS1115 sensor uncomment following
|
||||
; -D USERMOD_ADS1115
|
||||
|
||||
build_unflags =
|
||||
|
||||
# enables all features for travis CI
|
||||
build_flags_all_features =
|
||||
-D WLED_ENABLE_ADALIGHT
|
||||
-D WLED_ENABLE_DMX
|
||||
-D WLED_ENABLE_MQTT
|
||||
-D WLED_ENABLE_WEBSOCKETS
|
||||
|
||||
build_flags_esp8266 = ${common.build_flags} ${esp8266.build_flags}
|
||||
build_flags_esp32 = ${common.build_flags} ${esp32.build_flags}
|
||||
|
||||
@@ -161,12 +156,13 @@ upload_speed = 115200
|
||||
lib_compat_mode = strict
|
||||
lib_deps =
|
||||
fastled/FastLED @ 3.5.0
|
||||
IRremoteESP8266 @ 2.8.1
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
|
||||
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
|
||||
#TFT_eSPI
|
||||
#For use SSD1306 OLED display uncomment following
|
||||
#U8g2@~2.27.2
|
||||
#U8g2@~2.28.8
|
||||
#U8g2@~2.32.10
|
||||
#For Dallas sensor uncomment following 2 lines
|
||||
#OneWire@~2.3.5
|
||||
#milesburton/DallasTemperature@^3.9.0
|
||||
@@ -175,6 +171,9 @@ lib_deps =
|
||||
; adafruit/Adafruit BMP280 Library @ 2.1.0
|
||||
; adafruit/Adafruit CCS811 Library @ 1.0.4
|
||||
; adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||
#For ADS1115 sensor uncomment following
|
||||
; adafruit/Adafruit BusIO @ 1.13.2
|
||||
; adafruit/Adafruit ADS1X15 @ 2.4.0
|
||||
|
||||
extra_scripts = ${scripts_defaults.extra_scripts}
|
||||
|
||||
@@ -183,8 +182,8 @@ build_flags =
|
||||
-DESP8266
|
||||
-DFP_IN_IROM
|
||||
;-Wno-deprecated-declarations
|
||||
-Wno-register
|
||||
-Wno-misleading-indentation
|
||||
;-Wno-register
|
||||
;-Wno-misleading-indentation
|
||||
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
|
||||
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
|
||||
; lwIP 2 - Higher Bandwidth no Features
|
||||
@@ -199,27 +198,30 @@ build_flags =
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
#https://github.com/lorol/LITTLEFS.git
|
||||
# ESPAsyncTCP @ 1.2.0
|
||||
ESPAsyncTCP @ 1.2.2
|
||||
ESPAsyncUDP
|
||||
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
|
||||
[esp32]
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
|
||||
platform = espressif32@3.5.0
|
||||
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
|
||||
|
||||
build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
||||
-D LOROL_LITTLEFS
|
||||
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when builing with arduino-esp32 >=2.0.3
|
||||
|
||||
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
https://github.com/lorol/LITTLEFS.git
|
||||
makuna/NeoPixelBus @ 2.6.7
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
[esp32s2]
|
||||
@@ -229,10 +231,13 @@ build_flags = -g
|
||||
-DCONFIG_IDF_TARGET_ESP32S2
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-DCO
|
||||
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT, ARDUINO_USB_MSC_ON_BOOT, ARDUINO_USB_DFU_ON_BOOT
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
makuna/NeoPixelBus @ 2.6.7
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
[esp32c3]
|
||||
@@ -242,12 +247,34 @@ build_flags = -g
|
||||
-DCONFIG_IDF_TARGET_ESP32C3
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-DCO
|
||||
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
makuna/NeoPixelBus @ 2.6.7
|
||||
makuna/NeoPixelBus @ 2.6.9
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
[esp32s3]
|
||||
;; generic definitions for all ESP32-S3 boards
|
||||
build_flags = -g
|
||||
-DESP32
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_ARCH_ESP32S3
|
||||
-DCONFIG_IDF_TARGET_ESP32S3
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-DCO
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT, ARDUINO_USB_MSC_ON_BOOT, ARDUINO_USB_DFU_ON_BOOT
|
||||
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
;; NeoPixelBus 2.7.1 is the first that has official support for ESP32-S3
|
||||
makuna/NeoPixelBus @ ~2.7.1
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# WLED BUILDS
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -258,8 +285,9 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 -D WLED_DISABLE_BLYNK #-DWLED_DISABLE_2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
|
||||
[env:esp8266_2m]
|
||||
board = esp_wroom_02
|
||||
@@ -267,7 +295,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 -D WLED_DISABLE_BLYNK
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp01_1m_full]
|
||||
@@ -276,7 +304,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D WLED_DISABLE_BLYNK
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:esp07]
|
||||
@@ -320,26 +348,35 @@ lib_deps = ${esp8266.lib_deps}
|
||||
[env:esp32dev]
|
||||
board = esp32dev
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:esp32dev_qio80]
|
||||
board = esp32dev
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_qio80 -D WLED_DISABLE_BLYNK #-D WLED_DISABLE_BROWNOUT_DET
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
|
||||
[env:esp32_eth]
|
||||
board = esp32-poe
|
||||
platform = ${esp32.platform}
|
||||
platform_packages = ${esp32.platform_packages}
|
||||
upload_speed = 921600
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
# ESP32 ETH build that fits in old 1M app space (disables Blynk, Cronixie, and Hue sync)
|
||||
[env:esp32_eth_ota1mapp]
|
||||
extends = env:esp32_eth
|
||||
build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet_OTA -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D WLED_DISABLE_BLYNK -D WLED_DISABLE_CRONIXIE -D WLED_DISABLE_HUESYNC
|
||||
|
||||
[env:esp32s2_saola]
|
||||
board = esp32-s2-saola-1
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
@@ -349,18 +386,60 @@ board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
board_build.flash_mode = qio
|
||||
upload_speed = 460800
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=S2_saola
|
||||
lib_deps = ${esp32s2.lib_deps}
|
||||
|
||||
[env:esp32c3]
|
||||
board = esp32-c3-devkitm-1
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
||||
platform_packages =
|
||||
platform = espressif32@5.1.1
|
||||
framework = arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual USB
|
||||
upload_speed = 460800
|
||||
build_unflags = ${common.build_unflags}
|
||||
lib_deps = ${esp32c3.lib_deps}
|
||||
|
||||
[env:esp32s3dev_8MB]
|
||||
;; ESP32-S3-DevKitC-1 development board, with 8MB FLASH, no PSRAM (flash_mode: qio)
|
||||
board = esp32-s3-devkitc-1
|
||||
platform = espressif32@5.1.1
|
||||
platform_packages =
|
||||
upload_speed = 921600 ; or 460800
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32s3.build_flags}
|
||||
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
|
||||
;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MSC_ON_BOOT=0 -D ARDUINO_DFU_ON_BOOT=0 ; -D ARDUINO_USB_MODE=0 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||
;-D WLED_DEBUG
|
||||
lib_deps = ${esp32s3.lib_deps}
|
||||
board_build.partitions = tools/WLED_ESP32_8MB.csv
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
; board_build.flash_mode = dio ;; try this if you have problems at startup
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:esp32s3dev_8MB_PSRAM]
|
||||
;; ESP32-TinyS3 development board, with 8MB FLASH and 8MB PSRAM (memory_type: qio_opi, qio_qspi, or opi_opi)
|
||||
;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860
|
||||
;board = esp32s3box ; -> error: 'esp32_adc2gpio' was not declared in this scope
|
||||
board = esp32-s3-devkitc-1 ; -> compiles, but does not support PSRAM
|
||||
platform = espressif32 @ ~5.2.0
|
||||
platform_packages =
|
||||
upload_speed = 921600
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32s3.build_flags}
|
||||
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D ARDUINO_USB_MODE=1 -D ARDUINO_USB_MSC_ON_BOOT=0 ; -D ARDUINO_USB_CDC_ON_BOOT=0
|
||||
; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM
|
||||
-D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used
|
||||
lib_deps = ${esp32s3.lib_deps}
|
||||
board_build.partitions = tools/WLED_ESP32_8MB.csv
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:esp8285_4CH_MagicHome]
|
||||
board = esp8285
|
||||
platform = ${common.platform_wled_default}
|
||||
@@ -423,6 +502,29 @@ build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
[env:lolin_s2_mini]
|
||||
platform = espressif32@5.1.1
|
||||
board = lolin_s2_mini
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=LolinS2
|
||||
-DBOARD_HAS_PSRAM
|
||||
-D ARDUINO_USB_CDC_ON_BOOT
|
||||
-D WLED_USE_PSRAM
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D LEDPIN=16
|
||||
-D BTNPIN=18
|
||||
-D RLYPIN=9
|
||||
-D IRPIN=7
|
||||
-D HW_PIN_SCL=35
|
||||
-D HW_PIN_SDA=33
|
||||
-D HW_PIN_CLOCKSPI=7
|
||||
-D HW_PIN_DATASPI=11
|
||||
-D HW_PIN_MISOSPI=9
|
||||
; -D STATUSLED=15
|
||||
lib_deps = ${esp32s2.lib_deps}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# custom board configurations
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -441,9 +543,12 @@ build_flags = ${common.build_flags_esp32}
|
||||
-D USERMOD_DALLASTEMPERATURE
|
||||
-D USERMOD_FOUR_LINE_DISPLAY
|
||||
-D TEMPERATURE_PIN=23
|
||||
-D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI
|
||||
-D USERMOD_AUDIOREACTIVE
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
OneWire@~2.3.5
|
||||
olikraus/U8g2 @ ^2.28.8
|
||||
https://github.com/blazoncek/arduinoFFT.git
|
||||
board_build.partitions = ${esp32.default_partitions}
|
||||
|
||||
[env:m5atom]
|
||||
@@ -482,21 +587,14 @@ board_build.ldscript = ${common.ldscript_2m512k}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_USE_IC_CCT -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# travis test board configurations
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[env:travis_esp8266]
|
||||
extends = env:d1_mini
|
||||
build_type = debug
|
||||
[env:MY9291]
|
||||
board = esp01_1m
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} ${common.debug_flags} ${common.build_flags_all_features}
|
||||
|
||||
[env:travis_esp32]
|
||||
extends = env:esp32dev
|
||||
; build_type = debug
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_flags_all_features}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA -D USERMOD_MY9291
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# codm pixel controller board configurations
|
||||
@@ -534,7 +632,7 @@ build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_D
|
||||
-D LEDPIN=12
|
||||
-D RLYPIN=27
|
||||
-D BTNPIN=34
|
||||
-D WLED_DISABLE_INFRARED
|
||||
-D WLED_DISABLE_BLYNK
|
||||
-D DEFAULT_LED_COUNT=6
|
||||
# Display config
|
||||
-D ST7789_DRIVER
|
||||
|
||||
@@ -19,10 +19,14 @@ build_flags = ${common.build_flags_esp8266}
|
||||
; *** Use custom settings from file my_config.h
|
||||
-DWLED_USE_MY_CONFIG
|
||||
; *********************************************************************
|
||||
;
|
||||
;
|
||||
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
|
||||
;
|
||||
; disable specific features
|
||||
; -D WLED_DISABLE_OTA
|
||||
; -D WLED_DISABLE_ALEXA
|
||||
; -D WLED_DISABLE_BLYNK
|
||||
; -D WLED_DISABLE_CRONIXIE
|
||||
; -D WLED_DISABLE_HUESYNC
|
||||
; -D WLED_DISABLE_INFRARED
|
||||
; -D WLED_DISABLE_WEBSOCKETS
|
||||
@@ -45,3 +49,18 @@ build_flags = ${common.build_flags_esp8266}
|
||||
; for the Magic Home LED Controller use PWM pins 5,12,13,15
|
||||
; for the H801 controller use PINs 15,13,12,14 (W2 = 04)
|
||||
; for the BW-LT11 controller use PINs 12,4,14,5
|
||||
;
|
||||
; set the name of the module - make sure there is a quote-backslash-quote before the name and a backslash-quote-quote after the name
|
||||
; -D SERVERNAME="\"WLED\""
|
||||
;
|
||||
; set the number of LEDs
|
||||
; -D DEFAULT_LED_COUNT=30
|
||||
;
|
||||
; set milliampere limit when using ESP pin to power leds
|
||||
; -D ABL_MILLIAMPS_DEFAULT=850
|
||||
;
|
||||
; enable IR by setting remote type
|
||||
; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote
|
||||
;
|
||||
; set default color order of your led strip
|
||||
; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB
|
||||
|
||||
24
readme.md
24
readme.md
@@ -15,11 +15,11 @@
|
||||
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102!
|
||||
|
||||
## ⚙️ Features
|
||||
- WS2812FX library integrated for over 100 special effects
|
||||
- WS2812FX library with more than 100 special effects
|
||||
- FastLED noise effects and 50 palettes
|
||||
- Modern UI with color, effect and segment controls
|
||||
- Segments to set different effects and colors to parts of the LEDs
|
||||
- Settings page - configuration over network
|
||||
- Segments to set different effects and colors to user defined parts of the LED string
|
||||
- Settings page - configuration via the network
|
||||
- Access Point and station mode - automatic failsafe AP
|
||||
- Up to 10 LED outputs per instance
|
||||
- Support for RGBW strips
|
||||
@@ -27,8 +27,8 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
|
||||
- Presets can be used to automatically execute API calls
|
||||
- Nightlight function (gradually dims down)
|
||||
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
|
||||
- Configurable analog clock + support for the Cronixie kit by Diamex
|
||||
- Configurable Auto Brightness limit for safer operation
|
||||
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
|
||||
- Configurable Auto Brightness limit for safe operation
|
||||
- Filesystem-based config for easier backup of presets and settings
|
||||
|
||||
## 💡 Supported light control interfaces
|
||||
@@ -51,7 +51,7 @@ A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control
|
||||
|
||||
See the [documentation on our official site](https://kno.wled.ge)!
|
||||
|
||||
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials made by the community and helpful tools to help you get your new lamp up and running!
|
||||
[On this page](https://kno.wled.ge/basics/tutorials/) you can find excellent tutorials and tools to help you get your new project up and running!
|
||||
|
||||
## 🖼️ User interface
|
||||
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
|
||||
@@ -70,12 +70,16 @@ Join the Discord server to discuss everything about WLED!
|
||||
<a href="https://discord.gg/KuqP7NE"><img src="https://discordapp.com/api/guilds/473448917040758787/widget.png?style=banner2" width="25%"></a>
|
||||
|
||||
Check out the WLED [Discourse forum](https://wled.discourse.group)!
|
||||
You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com), but please only do so if you want to talk to me privately.
|
||||
If WLED really brightens up your every day, you can [](https://paypal.me/aircoookie)
|
||||
|
||||
You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com), but please, only do so if you want to talk to me privately.
|
||||
|
||||
If WLED really brightens up your day, you can [](https://paypal.me/aircoookie)
|
||||
|
||||
|
||||
*Disclaimer:*
|
||||
If you are sensitive to photosensitive epilepsy it is not recommended that you use this software.
|
||||
In case you still want to try, don't use strobe, lighting or noise modes or high effect speed settings.
|
||||
|
||||
If you are prone to photosensitive epilepsy, we recommended you do **not** use this software.
|
||||
If you still want to try, don't use strobe, lighting or noise modes or high effect speed settings.
|
||||
|
||||
As per the MIT license, I assume no liability for any damage to you or any other person or equipment.
|
||||
|
||||
|
||||
@@ -1,54 +1,66 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# This file is autogenerated by pip-compile with python 3.8
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile
|
||||
#
|
||||
aiofiles==0.6.0
|
||||
aiofiles==0.8.0
|
||||
# via platformio
|
||||
ajsonrpc==1.1.0
|
||||
ajsonrpc==1.2.0
|
||||
# via platformio
|
||||
bottle==0.12.19
|
||||
anyio==3.6.1
|
||||
# via starlette
|
||||
async-timeout==4.0.2
|
||||
# via zeroconf
|
||||
bottle==0.12.23
|
||||
# via platformio
|
||||
certifi==2020.12.5
|
||||
certifi==2022.12.7
|
||||
# via requests
|
||||
chardet==4.0.0
|
||||
charset-normalizer==2.1.1
|
||||
# via requests
|
||||
click==7.1.2
|
||||
click==8.1.3
|
||||
# via
|
||||
# platformio
|
||||
# uvicorn
|
||||
colorama==0.4.4
|
||||
colorama==0.4.5
|
||||
# via platformio
|
||||
h11==0.12.0
|
||||
h11==0.13.0
|
||||
# via
|
||||
# uvicorn
|
||||
# wsproto
|
||||
idna==2.10
|
||||
# via requests
|
||||
ifaddr==0.1.7
|
||||
idna==3.3
|
||||
# via
|
||||
# anyio
|
||||
# requests
|
||||
ifaddr==0.2.0
|
||||
# via zeroconf
|
||||
marshmallow==3.11.1
|
||||
marshmallow==3.17.0
|
||||
# via platformio
|
||||
platformio==5.1.1
|
||||
packaging==21.3
|
||||
# via marshmallow
|
||||
platformio==6.1.4
|
||||
# via -r requirements.in
|
||||
pyelftools==0.27
|
||||
pyelftools==0.29
|
||||
# via platformio
|
||||
pyparsing==3.0.9
|
||||
# via packaging
|
||||
pyserial==3.5
|
||||
# via platformio
|
||||
requests==2.25.1
|
||||
requests==2.28.1
|
||||
# via platformio
|
||||
semantic-version==2.8.5
|
||||
semantic-version==2.10.0
|
||||
# via platformio
|
||||
starlette==0.14.2
|
||||
sniffio==1.2.0
|
||||
# via anyio
|
||||
starlette==0.20.4
|
||||
# via platformio
|
||||
tabulate==0.8.9
|
||||
tabulate==0.8.10
|
||||
# via platformio
|
||||
urllib3==1.26.5
|
||||
urllib3==1.26.11
|
||||
# via requests
|
||||
uvicorn==0.13.4
|
||||
uvicorn==0.18.2
|
||||
# via platformio
|
||||
wsproto==1.0.0
|
||||
wsproto==1.1.0
|
||||
# via platformio
|
||||
zeroconf==0.28.8
|
||||
zeroconf==0.39.0
|
||||
# via platformio
|
||||
|
||||
6
tools/WLED_ESP32-wrover_4MB.csv
Normal file
6
tools/WLED_ESP32-wrover_4MB.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x180000,
|
||||
app1, app, ota_1, 0x190000,0x180000,
|
||||
spiffs, data, spiffs, 0x310000,0xF0000,
|
||||
|
325
tools/cdata.js
325
tools/cdata.js
@@ -16,20 +16,31 @@
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const inliner = require("inliner");
|
||||
const zlib = require("zlib");
|
||||
const CleanCSS = require("clean-css");
|
||||
const MinifyHTML = require("html-minifier-terser").minify;
|
||||
const packageJson = require("../package.json");
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function hexdump(buffer) {
|
||||
function hexdump(buffer,isHex=false) {
|
||||
let lines = [];
|
||||
|
||||
for (let i = 0; i < buffer.length; i += 16) {
|
||||
let block = buffer.slice(i, i + 16); // cut buffer into blocks of 16
|
||||
for (let i = 0; i < buffer.length; i +=(isHex?32:16)) {
|
||||
var block;
|
||||
let hexArray = [];
|
||||
|
||||
for (let value of block) {
|
||||
hexArray.push("0x" + value.toString(16).padStart(2, "0"));
|
||||
if (isHex) {
|
||||
block = buffer.slice(i, i + 32)
|
||||
for (let j = 0; j < block.length; j +=2 ) {
|
||||
hexArray.push("0x" + block.slice(j,j+2))
|
||||
}
|
||||
} else {
|
||||
block = buffer.slice(i, i + 16); // cut buffer into blocks of 16
|
||||
for (let value of block) {
|
||||
hexArray.push("0x" + value.toString(16).padStart(2, "0"));
|
||||
}
|
||||
}
|
||||
|
||||
let hexString = hexArray.join(", ");
|
||||
@@ -40,9 +51,6 @@ function hexdump(buffer) {
|
||||
return lines.join(",\n");
|
||||
}
|
||||
|
||||
const inliner = require("inliner");
|
||||
const zlib = require("zlib");
|
||||
|
||||
function strReplace(str, search, replacement) {
|
||||
return str.split(search).join(replacement);
|
||||
}
|
||||
@@ -56,16 +64,52 @@ function adoptVersionAndRepo(html) {
|
||||
html = strReplace(html, "https://github.com/atuline/WLED", repoUrl);
|
||||
html = strReplace(html, "https://github.com/Aircoookie/WLED", repoUrl);
|
||||
}
|
||||
|
||||
let version = packageJson.version;
|
||||
if (version) {
|
||||
html = strReplace(html, "##VERSION##", version);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function writeHtmlGzipped(sourceFile, resultFile) {
|
||||
function filter(str, type) {
|
||||
str = adoptVersionAndRepo(str);
|
||||
if (type === undefined) {
|
||||
return str;
|
||||
} else if (type == "css-minify") {
|
||||
return new CleanCSS({}).minify(str).styles;
|
||||
} else if (type == "js-minify") {
|
||||
return MinifyHTML('<script>' + str + '</script>', {
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: false,
|
||||
removeComments: true,
|
||||
}).replace(/<[\/]*script>/g,'');
|
||||
} else if (type == "html-minify") {
|
||||
return MinifyHTML(str, {
|
||||
collapseWhitespace: true,
|
||||
maxLineLength: 80,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: false,
|
||||
removeComments: true,
|
||||
});
|
||||
} else if (type == "html-minify-ui") {
|
||||
return MinifyHTML(str, {
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
maxLineLength: 80,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: false,
|
||||
removeComments: true,
|
||||
});
|
||||
} else {
|
||||
console.warn("Unknown filter: " + type);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
function writeHtmlGzipped(sourceFile, resultFile, page) {
|
||||
console.info("Reading " + sourceFile);
|
||||
new inliner(sourceFile, function (error, html) {
|
||||
console.info("Inlined " + html.length + " characters");
|
||||
@@ -95,8 +139,8 @@ function writeHtmlGzipped(sourceFile, resultFile) {
|
||||
*/
|
||||
|
||||
// Autogenerated from ${sourceFile}, do not edit!!
|
||||
const uint16_t PAGE_index_L = ${result.length};
|
||||
const uint8_t PAGE_index[] PROGMEM = {
|
||||
const uint16_t PAGE_${page}_L = ${result.length};
|
||||
const uint8_t PAGE_${page}[] PROGMEM = {
|
||||
${array}
|
||||
};
|
||||
`;
|
||||
@@ -106,41 +150,6 @@ ${array}
|
||||
});
|
||||
}
|
||||
|
||||
const CleanCSS = require("clean-css");
|
||||
const MinifyHTML = require("html-minifier-terser").minify;
|
||||
|
||||
function filter(str, type) {
|
||||
str = adoptVersionAndRepo(str);
|
||||
|
||||
if (type === undefined) {
|
||||
return str;
|
||||
} else if (type == "css-minify") {
|
||||
return new CleanCSS({}).minify(str).styles;
|
||||
} else if (type == "html-minify") {
|
||||
return MinifyHTML(str, {
|
||||
collapseWhitespace: true,
|
||||
maxLineLength: 80,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: false,
|
||||
removeComments: true,
|
||||
});
|
||||
} else if (type == "html-minify-ui") {
|
||||
return MinifyHTML(str, {
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
maxLineLength: 80,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
continueOnParseError: false,
|
||||
removeComments: true,
|
||||
});
|
||||
} else {
|
||||
console.warn("Unknown filter: " + type);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
function specToChunk(srcDir, s) {
|
||||
if (s.method == "plaintext") {
|
||||
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
||||
@@ -153,6 +162,21 @@ const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${
|
||||
|
||||
`;
|
||||
return s.mangle ? s.mangle(chunk) : chunk;
|
||||
} else if (s.method == "gzip") {
|
||||
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
||||
var str = buf.toString('utf-8');
|
||||
if (s.mangle) str = s.mangle(str);
|
||||
const zip = zlib.gzipSync(filter(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION });
|
||||
const result = hexdump(zip.toString('hex'), true);
|
||||
const chunk = `
|
||||
// Autogenerated from ${srcDir}/${s.file}, do not edit!!
|
||||
const uint16_t ${s.name}_length = ${zip.length};
|
||||
const uint8_t ${s.name}[] PROGMEM = {
|
||||
${result}
|
||||
};
|
||||
|
||||
`;
|
||||
return chunk;
|
||||
} else if (s.method == "binary") {
|
||||
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
||||
const result = hexdump(buf);
|
||||
@@ -164,7 +188,7 @@ ${result}
|
||||
};
|
||||
|
||||
`;
|
||||
return s.mangle ? s.mangle(chunk) : chunk;
|
||||
return chunk;
|
||||
} else {
|
||||
console.warn("Unknown method: " + s.method);
|
||||
return undefined;
|
||||
@@ -194,160 +218,111 @@ function writeChunks(srcDir, specs, resultFile) {
|
||||
fs.writeFileSync(resultFile, src);
|
||||
}
|
||||
|
||||
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h");
|
||||
|
||||
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
|
||||
writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple');
|
||||
/*
|
||||
writeChunks(
|
||||
"wled00/data",
|
||||
[
|
||||
{
|
||||
file: "simple.css",
|
||||
name: "PAGE_simpleCss",
|
||||
method: "gzip",
|
||||
filter: "css-minify",
|
||||
},
|
||||
{
|
||||
file: "simple.js",
|
||||
name: "PAGE_simpleJs",
|
||||
method: "gzip",
|
||||
filter: "js-minify",
|
||||
},
|
||||
{
|
||||
file: "simple.htm",
|
||||
name: "PAGE_simple",
|
||||
method: "gzip",
|
||||
filter: "html-minify-ui",
|
||||
}
|
||||
],
|
||||
"wled00/html_simplex.h"
|
||||
);
|
||||
*/
|
||||
writeChunks(
|
||||
"wled00/data",
|
||||
[
|
||||
{
|
||||
file: "style.css",
|
||||
name: "PAGE_settingsCss",
|
||||
prepend: "=====(<style>",
|
||||
append: "</style>)=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "css-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace("%%","%")
|
||||
},
|
||||
{
|
||||
file: "settings.htm",
|
||||
name: "PAGE_settings",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace("%", "%%")
|
||||
.replace(/Usermods\<\/button\>\<\/form\>/gms, "Usermods\<\/button\>\<\/form\>%DMXMENU%"),
|
||||
},
|
||||
{
|
||||
file: "settings_wifi.htm",
|
||||
name: "PAGE_settings_wifi",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
file: "settings_leds.htm",
|
||||
name: "PAGE_settings_leds",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
file: "settings_dmx.htm",
|
||||
name: "PAGE_settings_dmx",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) => {
|
||||
const nocss = str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
);
|
||||
return `
|
||||
#ifdef WLED_ENABLE_DMX
|
||||
${nocss}
|
||||
#else
|
||||
const char PAGE_settings_dmx[] PROGMEM = R"=====()=====";
|
||||
#endif
|
||||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
file: "settings_ui.htm",
|
||||
name: "PAGE_settings_ui",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
file: "settings_sync.htm",
|
||||
name: "PAGE_settings_sync",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"),
|
||||
},
|
||||
{
|
||||
file: "settings_time.htm",
|
||||
name: "PAGE_settings_time",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"),
|
||||
},
|
||||
{
|
||||
file: "settings_sec.htm",
|
||||
name: "PAGE_settings_sec",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
file: "settings_um.htm",
|
||||
name: "PAGE_settings_um",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
},
|
||||
{
|
||||
file: "settings_2D.htm",
|
||||
name: "PAGE_settings_2D",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
file: "settings_pin.htm",
|
||||
name: "PAGE_settings_pin",
|
||||
method: "gzip",
|
||||
filter: "html-minify"
|
||||
}
|
||||
],
|
||||
"wled00/html_settings.h"
|
||||
@@ -359,9 +334,7 @@ writeChunks(
|
||||
{
|
||||
file: "usermod.htm",
|
||||
name: "PAGE_usermod",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'),
|
||||
@@ -393,41 +366,43 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
|
||||
{
|
||||
file: "update.htm",
|
||||
name: "PAGE_update",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"</script><script src=\"/settings/s.js?p=9\"></script>"
|
||||
)
|
||||
},
|
||||
{
|
||||
file: "welcome.htm",
|
||||
name: "PAGE_welcome",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
file: "liveview.htm",
|
||||
name: "PAGE_liveview",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
file: "liveviewws.htm",
|
||||
name: "PAGE_liveviewws",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
file: "liveviewws2D.htm",
|
||||
name: "PAGE_liveviewws2D",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
file: "404.htm",
|
||||
name: "PAGE_404",
|
||||
prepend: "=====(",
|
||||
append: ")=====",
|
||||
method: "plaintext",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
},
|
||||
{
|
||||
@@ -435,6 +410,16 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
|
||||
name: "favicon",
|
||||
method: "binary",
|
||||
},
|
||||
{
|
||||
file: "iro.js",
|
||||
name: "iroJs",
|
||||
method: "gzip"
|
||||
},
|
||||
{
|
||||
file: "rangetouch.js",
|
||||
name: "rangetouchJs",
|
||||
method: "gzip"
|
||||
}
|
||||
],
|
||||
"wled00/html_other.h"
|
||||
);
|
||||
|
||||
15
usermods/ADS1115_v2/ChannelSettings.h
Normal file
15
usermods/ADS1115_v2/ChannelSettings.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "wled.h"
|
||||
|
||||
namespace ADS1115
|
||||
{
|
||||
struct ChannelSettings {
|
||||
const String settingName;
|
||||
bool isEnabled;
|
||||
String name;
|
||||
String units;
|
||||
const uint16_t mux;
|
||||
float multiplier;
|
||||
float offset;
|
||||
uint8_t decimals;
|
||||
};
|
||||
}
|
||||
10
usermods/ADS1115_v2/readme.md
Normal file
10
usermods/ADS1115_v2/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# ADS1115 16-Bit ADC with four inputs
|
||||
|
||||
This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI.
|
||||
|
||||
Configuration is performed via the Usermod menu. There are no parameters to set in code!
|
||||
|
||||
## Installation
|
||||
|
||||
Add the build flag `-D USERMOD_ADS1115` to your platformio environment.
|
||||
Uncomment libraries with comment `#For ADS1115 sensor uncomment following`
|
||||
255
usermods/ADS1115_v2/usermod_ads1115.h
Normal file
255
usermods/ADS1115_v2/usermod_ads1115.h
Normal file
@@ -0,0 +1,255 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include <Adafruit_ADS1X15.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "ChannelSettings.h"
|
||||
|
||||
using namespace ADS1115;
|
||||
|
||||
class ADS1115Usermod : public Usermod {
|
||||
public:
|
||||
void setup() {
|
||||
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V
|
||||
|
||||
if (!ads.begin()) {
|
||||
Serial.println("Failed to initialize ADS");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!initChannel()) {
|
||||
isInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
startReading();
|
||||
|
||||
isEnabled = true;
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (isEnabled && millis() - lastTime > loopInterval) {
|
||||
lastTime = millis();
|
||||
|
||||
// If we don't have new data, skip this iteration.
|
||||
if (!ads.conversionComplete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateResult();
|
||||
moveToNextChannel();
|
||||
startReading();
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) user = root.createNestedObject(F("u"));
|
||||
|
||||
for (uint8_t i = 0; i < channelsCount; i++) {
|
||||
ChannelSettings* settingsPtr = &(channelSettings[i]);
|
||||
|
||||
if (!settingsPtr->isEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonArray lightArr = user.createNestedArray(settingsPtr->name); //name
|
||||
float value = round((readings[i] + settingsPtr->offset) * settingsPtr->multiplier, settingsPtr->decimals);
|
||||
lightArr.add(value); //value
|
||||
lightArr.add(" " + settingsPtr->units); //unit
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(F("ADC ADS1115"));
|
||||
|
||||
for (uint8_t i = 0; i < channelsCount; i++) {
|
||||
ChannelSettings* settingsPtr = &(channelSettings[i]);
|
||||
JsonObject channel = top.createNestedObject(settingsPtr->settingName);
|
||||
channel[F("Enabled")] = settingsPtr->isEnabled;
|
||||
channel[F("Name")] = settingsPtr->name;
|
||||
channel[F("Units")] = settingsPtr->units;
|
||||
channel[F("Multiplier")] = settingsPtr->multiplier;
|
||||
channel[F("Offset")] = settingsPtr->offset;
|
||||
channel[F("Decimals")] = settingsPtr->decimals;
|
||||
}
|
||||
|
||||
top[F("Loop Interval")] = loopInterval;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root[F("ADC ADS1115")];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
bool hasEnabledChannels = false;
|
||||
|
||||
for (uint8_t i = 0; i < channelsCount && configComplete; i++) {
|
||||
ChannelSettings* settingsPtr = &(channelSettings[i]);
|
||||
JsonObject channel = top[settingsPtr->settingName];
|
||||
|
||||
configComplete &= !channel.isNull();
|
||||
|
||||
configComplete &= getJsonValue(channel[F("Enabled")], settingsPtr->isEnabled);
|
||||
configComplete &= getJsonValue(channel[F("Name")], settingsPtr->name);
|
||||
configComplete &= getJsonValue(channel[F("Units")], settingsPtr->units);
|
||||
configComplete &= getJsonValue(channel[F("Multiplier")], settingsPtr->multiplier);
|
||||
configComplete &= getJsonValue(channel[F("Offset")], settingsPtr->offset);
|
||||
configComplete &= getJsonValue(channel[F("Decimals")], settingsPtr->decimals);
|
||||
|
||||
hasEnabledChannels |= settingsPtr->isEnabled;
|
||||
}
|
||||
|
||||
configComplete &= getJsonValue(top[F("Loop Interval")], loopInterval);
|
||||
|
||||
isEnabled = isInitialized && configComplete && hasEnabledChannels;
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_ADS1115;
|
||||
}
|
||||
|
||||
private:
|
||||
static const uint8_t channelsCount = 8;
|
||||
|
||||
ChannelSettings channelSettings[channelsCount] = {
|
||||
{
|
||||
"Differential reading from AIN0 (P) and AIN1 (N)",
|
||||
false,
|
||||
"Differential AIN0 AIN1",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_DIFF_0_1,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Differential reading from AIN0 (P) and AIN3 (N)",
|
||||
false,
|
||||
"Differential AIN0 AIN3",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_DIFF_0_3,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Differential reading from AIN1 (P) and AIN3 (N)",
|
||||
false,
|
||||
"Differential AIN1 AIN3",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_DIFF_1_3,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Differential reading from AIN2 (P) and AIN3 (N)",
|
||||
false,
|
||||
"Differential AIN2 AIN3",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_DIFF_2_3,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Single-ended reading from AIN0",
|
||||
false,
|
||||
"Single-ended AIN0",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_SINGLE_0,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Single-ended reading from AIN1",
|
||||
false,
|
||||
"Single-ended AIN1",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_SINGLE_1,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Single-ended reading from AIN2",
|
||||
false,
|
||||
"Single-ended AIN2",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_SINGLE_2,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
{
|
||||
"Single-ended reading from AIN3",
|
||||
false,
|
||||
"Single-ended AIN3",
|
||||
"V",
|
||||
ADS1X15_REG_CONFIG_MUX_SINGLE_3,
|
||||
1,
|
||||
0,
|
||||
3
|
||||
},
|
||||
};
|
||||
float readings[channelsCount] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
unsigned long loopInterval = 1000;
|
||||
unsigned long lastTime = 0;
|
||||
|
||||
Adafruit_ADS1115 ads;
|
||||
uint8_t activeChannel;
|
||||
|
||||
bool isEnabled = false;
|
||||
bool isInitialized = false;
|
||||
|
||||
static float round(float value, uint8_t decimals) {
|
||||
return roundf(value * powf(10, decimals)) / powf(10, decimals);
|
||||
}
|
||||
|
||||
bool initChannel() {
|
||||
for (uint8_t i = 0; i < channelsCount; i++) {
|
||||
if (channelSettings[i].isEnabled) {
|
||||
activeChannel = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
activeChannel = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void moveToNextChannel() {
|
||||
uint8_t oldActiveChannel = activeChannel;
|
||||
|
||||
do
|
||||
{
|
||||
if (++activeChannel >= channelsCount){
|
||||
activeChannel = 0;
|
||||
}
|
||||
}
|
||||
while (!channelSettings[activeChannel].isEnabled && oldActiveChannel != activeChannel);
|
||||
}
|
||||
|
||||
void startReading() {
|
||||
ads.startADCReading(channelSettings[activeChannel].mux, /*continuous=*/false);
|
||||
}
|
||||
|
||||
void updateResult() {
|
||||
int16_t results = ads.getLastConversionResults();
|
||||
readings[activeChannel] = ads.computeVolts(results);
|
||||
}
|
||||
};
|
||||
256
usermods/Analog_Clock/Analog_Clock.h
Normal file
256
usermods/Analog_Clock/Analog_Clock.h
Normal file
@@ -0,0 +1,256 @@
|
||||
#pragma once
|
||||
#include "wled.h"
|
||||
|
||||
/*
|
||||
* Usermod for analog clock
|
||||
*/
|
||||
extern Timezone* tz;
|
||||
|
||||
class AnalogClockUsermod : public Usermod {
|
||||
private:
|
||||
static constexpr uint32_t refreshRate = 50; // per second
|
||||
static constexpr uint32_t refreshDelay = 1000 / refreshRate;
|
||||
|
||||
struct Segment {
|
||||
// config
|
||||
int16_t firstLed = 0;
|
||||
int16_t lastLed = 59;
|
||||
int16_t centerLed = 0;
|
||||
|
||||
// runtime
|
||||
int16_t size;
|
||||
|
||||
Segment() {
|
||||
update();
|
||||
}
|
||||
|
||||
void validateAndUpdate() {
|
||||
if (firstLed < 0 || firstLed >= strip.getLengthTotal() ||
|
||||
lastLed < firstLed || lastLed >= strip.getLengthTotal()) {
|
||||
*this = {};
|
||||
return;
|
||||
}
|
||||
if (centerLed < firstLed || centerLed > lastLed) {
|
||||
centerLed = firstLed;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
size = lastLed - firstLed + 1;
|
||||
}
|
||||
};
|
||||
|
||||
// configuration (available in API and stored in flash)
|
||||
bool enabled = false;
|
||||
Segment mainSegment;
|
||||
bool hourMarksEnabled = true;
|
||||
uint32_t hourMarkColor = 0xFF0000;
|
||||
uint32_t hourColor = 0x0000FF;
|
||||
uint32_t minuteColor = 0x00FF00;
|
||||
bool secondsEnabled = true;
|
||||
Segment secondsSegment;
|
||||
uint32_t secondColor = 0xFF0000;
|
||||
bool blendColors = true;
|
||||
uint16_t secondsEffect = 0;
|
||||
|
||||
// runtime
|
||||
bool initDone = false;
|
||||
uint32_t lastOverlayDraw = 0;
|
||||
|
||||
void validateAndUpdate() {
|
||||
mainSegment.validateAndUpdate();
|
||||
secondsSegment.validateAndUpdate();
|
||||
if (secondsEffect < 0 || secondsEffect > 1) {
|
||||
secondsEffect = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t adjustToSegment(double progress, Segment const& segment) {
|
||||
int16_t led = segment.centerLed + progress * segment.size;
|
||||
return led > segment.lastLed
|
||||
? segment.firstLed + led - segment.lastLed - 1
|
||||
: led;
|
||||
}
|
||||
|
||||
void setPixelColor(uint16_t n, uint32_t c) {
|
||||
if (!blendColors) {
|
||||
strip.setPixelColor(n, c);
|
||||
} else {
|
||||
uint32_t oldC = strip.getPixelColor(n);
|
||||
strip.setPixelColor(n, qadd32(oldC, c));
|
||||
}
|
||||
}
|
||||
|
||||
String colorToHexString(uint32_t c) {
|
||||
char buffer[9];
|
||||
sprintf(buffer, "%06X", c);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool hexStringToColor(String const& s, uint32_t& c, uint32_t def) {
|
||||
char *ep;
|
||||
unsigned long long r = strtoull(s.c_str(), &ep, 16);
|
||||
if (*ep == 0) {
|
||||
c = r;
|
||||
return true;
|
||||
} else {
|
||||
c = def;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) {
|
||||
uint32_t ms = time.ms % 1000;
|
||||
uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2;
|
||||
setPixelColor(secondLed, gamma32(scale32(secondColor, b0)));
|
||||
uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2;
|
||||
setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1)));
|
||||
}
|
||||
|
||||
static inline uint32_t qadd32(uint32_t c1, uint32_t c2) {
|
||||
return RGBW32(
|
||||
qadd8(R(c1), R(c2)),
|
||||
qadd8(G(c1), G(c2)),
|
||||
qadd8(B(c1), B(c2)),
|
||||
qadd8(W(c1), W(c2))
|
||||
);
|
||||
}
|
||||
|
||||
static inline uint32_t scale32(uint32_t c, fract8 scale) {
|
||||
return RGBW32(
|
||||
scale8(R(c), scale),
|
||||
scale8(G(c), scale),
|
||||
scale8(B(c), scale),
|
||||
scale8(W(c), scale)
|
||||
);
|
||||
}
|
||||
|
||||
static inline int16_t dec(int16_t n, int16_t i, Segment const& seg) {
|
||||
return n - seg.firstLed >= i
|
||||
? n - i
|
||||
: seg.lastLed - seg.firstLed - i + n + 1;
|
||||
}
|
||||
|
||||
static inline int16_t inc(int16_t n, int16_t i, Segment const& seg) {
|
||||
int16_t r = n + i;
|
||||
if (r > seg.lastLed) {
|
||||
return seg.firstLed + n - seg.lastLed;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public:
|
||||
AnalogClockUsermod() {
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
initDone = true;
|
||||
validateAndUpdate();
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (millis() - lastOverlayDraw > refreshDelay) {
|
||||
strip.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void handleOverlayDraw() override {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastOverlayDraw = millis();
|
||||
|
||||
auto time = toki.getTime();
|
||||
double secondP = second(localTime) / 60.0;
|
||||
double minuteP = minute(localTime) / 60.0;
|
||||
double hourP = (hour(localTime) % 12) / 12.0 + minuteP / 12.0;
|
||||
|
||||
if (hourMarksEnabled) {
|
||||
for (int Led = 0; Led <= 55; Led = Led + 5)
|
||||
{
|
||||
int16_t hourmarkled = adjustToSegment(Led / 60.0, mainSegment);
|
||||
setPixelColor(hourmarkled, hourMarkColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (secondsEnabled) {
|
||||
int16_t secondLed = adjustToSegment(secondP, secondsSegment);
|
||||
|
||||
switch (secondsEffect) {
|
||||
case 0: // no effect
|
||||
setPixelColor(secondLed, secondColor);
|
||||
break;
|
||||
|
||||
case 1: // fading seconds
|
||||
secondsEffectSineFade(secondLed, time);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: move to secondsTrailEffect
|
||||
// for (uint16_t i = 1; i < secondsTrail + 1; ++i) {
|
||||
// uint16_t trailLed = dec(secondLed, i, secondsSegment);
|
||||
// uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1);
|
||||
// setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright)));
|
||||
// }
|
||||
}
|
||||
|
||||
setPixelColor(adjustToSegment(minuteP, mainSegment), minuteColor);
|
||||
setPixelColor(adjustToSegment(hourP, mainSegment), hourColor);
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root) override {
|
||||
validateAndUpdate();
|
||||
|
||||
JsonObject top = root.createNestedObject("Analog Clock");
|
||||
top["Overlay Enabled"] = enabled;
|
||||
top["First LED (Main Ring)"] = mainSegment.firstLed;
|
||||
top["Last LED (Main Ring)"] = mainSegment.lastLed;
|
||||
top["Center/12h LED (Main Ring)"] = mainSegment.centerLed;
|
||||
top["Hour Marks Enabled"] = hourMarksEnabled;
|
||||
top["Hour Mark Color (RRGGBB)"] = colorToHexString(hourMarkColor);
|
||||
top["Hour Color (RRGGBB)"] = colorToHexString(hourColor);
|
||||
top["Minute Color (RRGGBB)"] = colorToHexString(minuteColor);
|
||||
top["Show Seconds"] = secondsEnabled;
|
||||
top["First LED (Seconds Ring)"] = secondsSegment.firstLed;
|
||||
top["Last LED (Seconds Ring)"] = secondsSegment.lastLed;
|
||||
top["Center/12h LED (Seconds Ring)"] = secondsSegment.centerLed;
|
||||
top["Second Color (RRGGBB)"] = colorToHexString(secondColor);
|
||||
top["Seconds Effect (0-1)"] = secondsEffect;
|
||||
top["Blend Colors"] = blendColors;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root) override {
|
||||
JsonObject top = root["Analog Clock"];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
String color;
|
||||
configComplete &= getJsonValue(top["Overlay Enabled"], enabled, false);
|
||||
configComplete &= getJsonValue(top["First LED (Main Ring)"], mainSegment.firstLed, 0);
|
||||
configComplete &= getJsonValue(top["Last LED (Main Ring)"], mainSegment.lastLed, 59);
|
||||
configComplete &= getJsonValue(top["Center/12h LED (Main Ring)"], mainSegment.centerLed, 0);
|
||||
configComplete &= getJsonValue(top["Hour marks Enabled"], hourMarksEnabled, false);
|
||||
configComplete &= getJsonValue(top["Hour mark Color (RRGGBB)"], color, "FF0000") && hexStringToColor(color, hourMarkColor, 0x0000FF);
|
||||
configComplete &= getJsonValue(top["Hour Color (RRGGBB)"], color, "0000FF") && hexStringToColor(color, hourColor, 0x0000FF);
|
||||
configComplete &= getJsonValue(top["Minute Color (RRGGBB)"], color, "00FF00") && hexStringToColor(color, minuteColor, 0x00FF00);
|
||||
configComplete &= getJsonValue(top["Show Seconds"], secondsEnabled, true);
|
||||
configComplete &= getJsonValue(top["First LED (Seconds Ring)"], secondsSegment.firstLed, 0);
|
||||
configComplete &= getJsonValue(top["Last LED (Seconds Ring)"], secondsSegment.lastLed, 59);
|
||||
configComplete &= getJsonValue(top["Center/12h LED (Seconds Ring)"], secondsSegment.centerLed, 0);
|
||||
configComplete &= getJsonValue(top["Second Color (RRGGBB)"], color, "FF0000") && hexStringToColor(color, secondColor, 0xFF0000);
|
||||
configComplete &= getJsonValue(top["Seconds Effect (0-1)"], secondsEffect, 0);
|
||||
configComplete &= getJsonValue(top["Blend Colors"], blendColors, true);
|
||||
|
||||
if (initDone) {
|
||||
validateAndUpdate();
|
||||
}
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId() override {
|
||||
return USERMOD_ID_ANALOG_CLOCK;
|
||||
}
|
||||
};
|
||||
@@ -65,7 +65,7 @@ class Animated_Staircase : public Usermod {
|
||||
// The maximum number of configured segments.
|
||||
// Dynamically updated based on user configuration.
|
||||
byte maxSegmentId = 1;
|
||||
byte mainSegmentId = 0;
|
||||
byte minSegmentId = 0;
|
||||
|
||||
// These values are used by the API to read the
|
||||
// last sensor state, or trigger a sensor
|
||||
@@ -91,8 +91,7 @@ class Animated_Staircase : public Usermod {
|
||||
static const char _topEchoCm[];
|
||||
static const char _bottomEchoCm[];
|
||||
|
||||
void publishMqtt(bool bottom, const char* state)
|
||||
{
|
||||
void publishMqtt(bool bottom, const char* state) {
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
@@ -102,27 +101,23 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
|
||||
void updateSegments() {
|
||||
mainSegmentId = strip.getMainSegmentId();
|
||||
WS2812FX::Segment* segments = strip.getSegments();
|
||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||
if (!segments->isActive()) {
|
||||
maxSegmentId = i - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = minSegmentId; i < maxSegmentId; i++) {
|
||||
Segment &seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue; // skip gaps
|
||||
if (i >= onIndex && i < offIndex) {
|
||||
segments->setOption(SEG_OPTION_ON, 1, i);
|
||||
|
||||
seg.setOption(SEG_OPTION_ON, true);
|
||||
// We may need to copy mode and colors from segment 0 to make sure
|
||||
// changes are propagated even when the config is changed during a wipe
|
||||
// segments->mode = mainsegment.mode;
|
||||
// segments->colors[0] = mainsegment.colors[0];
|
||||
// seg.setMode(mainsegment.mode);
|
||||
// seg.setColor(0, mainsegment.colors[0]);
|
||||
} else {
|
||||
segments->setOption(SEG_OPTION_ON, 0, i);
|
||||
seg.setOption(SEG_OPTION_ON, false);
|
||||
}
|
||||
// Always mark segments as "transitional", we are animating the staircase
|
||||
segments->setOption(SEG_OPTION_TRANSITIONAL, 1, i);
|
||||
//seg.setOption(SEG_OPTION_TRANSITIONAL, true); // not needed anymore as setOption() does it
|
||||
}
|
||||
strip.trigger(); // force strip refresh
|
||||
stateChanged = true; // inform external devices/UI of change
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
}
|
||||
|
||||
@@ -208,9 +203,9 @@ class Animated_Staircase : public Usermod {
|
||||
if (onIndex == offIndex) {
|
||||
// Position the indices for a correct on-swipe
|
||||
if (swipe == SWIPE_UP) {
|
||||
onIndex = mainSegmentId;
|
||||
onIndex = minSegmentId;
|
||||
} else {
|
||||
onIndex = maxSegmentId+1;
|
||||
onIndex = maxSegmentId;
|
||||
}
|
||||
offIndex = onIndex;
|
||||
}
|
||||
@@ -222,7 +217,7 @@ class Animated_Staircase : public Usermod {
|
||||
}
|
||||
|
||||
void autoPowerOff() {
|
||||
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
||||
if ((millis() - lastSwitchTime) > on_time_ms) {
|
||||
// if sensors are still on, do nothing
|
||||
if (bottomSensorState || topSensorState) return;
|
||||
|
||||
@@ -239,10 +234,12 @@ class Animated_Staircase : public Usermod {
|
||||
if ((millis() - lastTime) > segment_delay_ms) {
|
||||
lastTime = millis();
|
||||
|
||||
byte oldOn = onIndex;
|
||||
byte oldOff = offIndex;
|
||||
if (on) {
|
||||
// Turn on all segments
|
||||
onIndex = MAX(mainSegmentId, onIndex - 1);
|
||||
offIndex = MIN(maxSegmentId + 1, offIndex + 1);
|
||||
onIndex = MAX(minSegmentId, onIndex - 1);
|
||||
offIndex = MIN(maxSegmentId, offIndex + 1);
|
||||
} else {
|
||||
if (swipe == SWIPE_UP) {
|
||||
onIndex = MIN(offIndex, onIndex + 1);
|
||||
@@ -250,7 +247,7 @@ class Animated_Staircase : public Usermod {
|
||||
offIndex = MAX(onIndex, offIndex - 1);
|
||||
}
|
||||
}
|
||||
updateSegments();
|
||||
if (oldOn != onIndex || oldOff != offIndex) updateSegments(); // reduce the number of updates to necessary ones
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,16 +285,22 @@ class Animated_Staircase : public Usermod {
|
||||
pinMode(topPIRorTriggerPin, OUTPUT);
|
||||
pinMode(topEchoPin, INPUT);
|
||||
}
|
||||
onIndex = minSegmentId = strip.getMainSegmentId(); // it may not be the best idea to start with main segment as it may not be the first one
|
||||
offIndex = maxSegmentId = strip.getLastActiveSegmentId() + 1;
|
||||
|
||||
// shorten the strip transition time to be equal or shorter than segment delay
|
||||
transitionDelayTemp = transitionDelay = segment_delay_ms;
|
||||
strip.setTransition(segment_delay_ms/100);
|
||||
strip.trigger();
|
||||
} else {
|
||||
// Restore segment options
|
||||
WS2812FX::Segment* segments = strip.getSegments();
|
||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
||||
if (!segments->isActive()) {
|
||||
maxSegmentId = i - 1;
|
||||
break;
|
||||
}
|
||||
segments->setOption(SEG_OPTION_ON, 1, i);
|
||||
for (int i = 0; i <= strip.getLastActiveSegmentId(); i++) {
|
||||
Segment &seg = strip.getSegment(i);
|
||||
if (!seg.isActive()) continue; // skip vector gaps
|
||||
seg.setOption(SEG_OPTION_ON, true);
|
||||
}
|
||||
strip.trigger(); // force strip update
|
||||
stateChanged = true; // inform external dvices/UI of change
|
||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||
}
|
||||
@@ -333,8 +336,10 @@ class Animated_Staircase : public Usermod {
|
||||
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
minSegmentId = strip.getMainSegmentId(); // it may not be the best idea to start with main segment as it may not be the first one
|
||||
maxSegmentId = strip.getLastActiveSegmentId() + 1;
|
||||
checkSensors();
|
||||
autoPowerOff();
|
||||
if (on) autoPowerOff();
|
||||
updateSwipe();
|
||||
}
|
||||
|
||||
@@ -393,19 +398,29 @@ class Animated_Staircase : public Usermod {
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
bool en = enabled;
|
||||
JsonObject staircase = root[FPSTR(_name)];
|
||||
if (!staircase.isNull()) {
|
||||
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
||||
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
||||
en = staircase[FPSTR(_enabled)].as<bool>();
|
||||
} else {
|
||||
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
||||
enabled = (bool)(str!="off"); // off is guaranteed to be present
|
||||
en = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
if (en != enabled) enable(en);
|
||||
readSensorsFromJson(staircase);
|
||||
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
||||
}
|
||||
}
|
||||
|
||||
void appendConfigData() {
|
||||
//oappend(SET_F("dd=addDropdown('staircase','selectfield');"));
|
||||
//oappend(SET_F("addOption(dd,'1st value',0);"));
|
||||
//oappend(SET_F("addOption(dd,'2nd value',1);"));
|
||||
//oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Writes the configuration to internal flash memory.
|
||||
*/
|
||||
@@ -500,22 +515,22 @@ class Animated_Staircase : public Usermod {
|
||||
* tab of the web-UI.
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
JsonObject staircase = root["u"];
|
||||
if (staircase.isNull()) {
|
||||
staircase = root.createNestedObject("u");
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) {
|
||||
user = root.createNestedObject("u");
|
||||
}
|
||||
|
||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase")); // name
|
||||
String btn = F("<button class=\"btn infobtn\" onclick=\"requestJson({staircase:{enabled:");
|
||||
if (enabled) {
|
||||
btn += F("false}});\">");
|
||||
btn += F("enabled");
|
||||
} else {
|
||||
btn += F("true}});\">");
|
||||
btn += F("disabled");
|
||||
}
|
||||
btn += F("</button>");
|
||||
usermodEnabled.add(btn); // value
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name)); // name
|
||||
|
||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
uiDomString += enabled ? F(":false}});\">") : F(":true}});\">");
|
||||
uiDomString += F("<i class=\"icons ");
|
||||
uiDomString += enabled ? "on" : "off";
|
||||
uiDomString += F("\"></i></button>");
|
||||
infoArr.add(uiDomString);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Usermod Animated Staircase
|
||||
This usermod makes your staircase look cool by switching it on with an animation. It uses
|
||||
This usermod makes your staircase look cool by illuminating it with an animation. It uses
|
||||
PIR or ultrasonic sensors at the top and bottom of your stairs to:
|
||||
|
||||
- Light up the steps in your walking direction, leading the way.
|
||||
- Light up the steps in the direction you're walking.
|
||||
- Switch off the steps after you, in the direction of the last detected movement.
|
||||
- Always switch on when one of the sensors detects movement, even if an effect
|
||||
is still running. It can therewith handle multiple people on the stairs gracefully.
|
||||
is still running. It can gracefully handle multiple people on the stairs.
|
||||
|
||||
The Animated Staircase can be controlled by the WLED API. Change settings such as
|
||||
speed, on/off time and distance settings by sending an HTTP request, see below.
|
||||
speed, on/off time and distance by sending an HTTP request, see below.
|
||||
|
||||
## WLED integration
|
||||
To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://github.com/Aircoookie/WLED/wiki/Compiling-WLED).
|
||||
@@ -20,17 +20,16 @@ Edit `usermods_list.cpp`:
|
||||
2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file
|
||||
3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function.
|
||||
|
||||
You can configure usermod using Usermods settings page.
|
||||
Please enter GPIO pins for PIR sensors or ultrasonic sensor (trigger and echo).
|
||||
You can configure usermod using the Usermods settings page.
|
||||
Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo).
|
||||
If you use PIR sensor enter -1 for echo pin.
|
||||
Maximum distance for ultrasonic sensor can be configured as a time needed for echo (see below).
|
||||
Maximum distance for ultrasonic sensor can be configured as the time needed for an echo (see below).
|
||||
|
||||
## Hardware installation
|
||||
1. Stick the LED strip under each step of the stairs.
|
||||
2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step
|
||||
of your stairs.
|
||||
1. Attach the LED strip to each step of the stairs.
|
||||
2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step.
|
||||
3. Connect the data-out pin at the end of each strip per step to the data-in pin on the
|
||||
other end of the next step, creating one large virtual LED strip.
|
||||
next step, creating one large virtual LED strip.
|
||||
4. Mount sensors of choice at the bottom and top of the stairs and connect them to the ESP.
|
||||
5. To make sure all LEDs get enough power and have your staircase lighted evenly, power each
|
||||
step from one side, using at least AWG14 or 2.5mm^2 cable. Don't connect them serial as you
|
||||
@@ -62,7 +61,7 @@ or remove them and put everything on one line.
|
||||
|
||||
To read the current settings, open a browser to `http://xxx.xxx.xxx.xxx/json/state` (use your WLED
|
||||
device IP address). The device will respond with a json object containing all WLED settings.
|
||||
The staircase settings and sensor states are inside the WLED status element:
|
||||
The staircase settings and sensor states are inside the WLED "state" element:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -70,14 +69,14 @@ The staircase settings and sensor states are inside the WLED status element:
|
||||
"staircase": {
|
||||
"enabled": true,
|
||||
"bottom-sensor": false,
|
||||
"tops-ensor": false
|
||||
"top-sensor": false
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Enable/disable the usermod
|
||||
By disabling the usermod you will be able to keep the LED's on, independent from the sensor
|
||||
activity. This enables to play with the lights without the usermod switching them on or off.
|
||||
activity. This enables you to play with the lights without the usermod switching them on or off.
|
||||
|
||||
To disable the usermod:
|
||||
|
||||
@@ -92,17 +91,17 @@ To enable the usermod again, use `"enabled":true`.
|
||||
Alternatively you can use _Usermod_ Settings page where you can change other parameters as well.
|
||||
|
||||
### Changing animation parameters and detection range of the ultrasonic HC-SR04 sensor
|
||||
Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation and so on.
|
||||
Using _Usermod_ Settings page you can define different usermod parameters, includng sensor pins, delay between segment activation etc.
|
||||
|
||||
When an ultrasonic sensor is enabled you can enter maximum detection distance in centimeters separately for top and bottom sensors.
|
||||
|
||||
**Please note:** that using an HC-SR04 sensor, particularly when detecting echos at longer
|
||||
distances creates delays in the WLED software, and _might_ introduce timing hickups in your animations or
|
||||
**Please note:** using an HC-SR04 sensor, particularly when detecting echos at longer
|
||||
distances creates delays in the WLED software, _might_ introduce timing hiccups in your animation or
|
||||
a less responsive web interface. It is therefore advised to keep the detection distance as short as possible.
|
||||
|
||||
### Animation triggering through the API
|
||||
Instead of stairs activation by one of the sensors, you can also trigger the animation through
|
||||
the API. To simulate triggering the bottom sensor, use:
|
||||
In addition to activation by one of the stair sensors, you can also trigger the animation manually
|
||||
via the API. To simulate triggering the bottom sensor, use:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
@@ -110,7 +109,7 @@ curl -X POST -H "Content-Type: application/json" \
|
||||
xxx.xxx.xxx.xxx/json/state
|
||||
```
|
||||
|
||||
Likewise, to trigger the top sensor, use:
|
||||
Likewise, to trigger the top sensor:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
@@ -119,7 +118,7 @@ curl -X POST -H "Content-Type: application/json" \
|
||||
```
|
||||
**MQTT**
|
||||
You can publish a message with either `up` or `down` on topic `/swipe` to trigger animation.
|
||||
You can also use `on` or `off` for enabling or disabling usermod.
|
||||
You can also use `on` or `off` for enabling or disabling the usermod.
|
||||
|
||||
Have fun with this usermod.<br/>
|
||||
www.rolfje.com
|
||||
@@ -128,4 +127,4 @@ Modifications @blazoncek
|
||||
|
||||
## Change log
|
||||
2021-04
|
||||
* Adaptation for runtime configuration.
|
||||
* Adaptation for runtime configuration.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
; Options
|
||||
; -------
|
||||
; USERMOD_BH1750 - define this to have this user mod included wled00\usermods_list.cpp
|
||||
; USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
; USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL - the min number of milliseconds between measurements, defaults to 500ms
|
||||
; USERMOD_BH1750_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
; USERMOD_BH1750_OFFSET_VALUE - the offset value to report on, defaults to 1
|
||||
;
|
||||
[env:usermod_BH1750_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BH1750
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
claws/BH1750 @ ^1.2.0
|
||||
@@ -1,24 +1,49 @@
|
||||
# BH1750 usermod
|
||||
|
||||
This usermod will read from an ambient light sensor like the BH1750 sensor.
|
||||
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
|
||||
This usermod will read from an ambient light sensor like the BH1750.
|
||||
The luminance is displayed in both the Info section of the web UI, as well as published to the `/luminance` MQTT topic if enabled.
|
||||
|
||||
## Installation
|
||||
## Dependencies
|
||||
- Libraries
|
||||
- `claws/BH1750 @^1.2.0`
|
||||
- This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`).
|
||||
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||
|
||||
Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`.
|
||||
## Compiliation
|
||||
|
||||
### Define Your Options
|
||||
To enable, compile with `USERMOD_BH1750` defined (e.g. in `platformio_override.ini`)
|
||||
```ini
|
||||
[env:usermod_BH1750_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BH1750
|
||||
lib_deps =
|
||||
${esp8266.lib_deps}
|
||||
claws/BH1750 @ ^1.2.0
|
||||
```
|
||||
|
||||
* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms
|
||||
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds
|
||||
* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1
|
||||
### Configuration Options
|
||||
The following settings can be set at compile-time but are configurable on the usermod menu (except First Measurement time):
|
||||
* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms
|
||||
* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms
|
||||
* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1
|
||||
* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10000 ms
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
In addition, the Usermod screen allows you to:
|
||||
- enable/disable the usermod
|
||||
- Enable Home Assistant Discovery of usermod
|
||||
- Configure the SCL/SDA pins
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:usermod_BH1750_d1_mini`.
|
||||
## API
|
||||
The following method is available to interact with the usermod from other code modules:
|
||||
- `getIlluminance` read the brightness from the sensor
|
||||
|
||||
## Change Log
|
||||
Jul 2022
|
||||
- Added Home Assistant Discovery
|
||||
- Implemented PinManager to register pins
|
||||
- Made pins configurable in usermod menu
|
||||
- Added API call to read luminance from other modules
|
||||
- Enhanced info-screen outputs
|
||||
- Updated `readme.md`
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// force the compiler to show a warning to confirm that this file is included
|
||||
#warning **** Included USERMOD_BH1750 ****
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
@@ -39,7 +42,7 @@ private:
|
||||
bool getLuminanceComplete = false;
|
||||
|
||||
// flag set at startup
|
||||
bool disabled = false;
|
||||
bool enabled = true;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
@@ -47,6 +50,24 @@ private:
|
||||
static const char _maxReadInterval[];
|
||||
static const char _minReadInterval[];
|
||||
static const char _offset[];
|
||||
static const char _HomeAssistantDiscovery[];
|
||||
|
||||
// set the default pins based on the architecture, these get overridden by Usermod menu settings
|
||||
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else // ESP8266 boards
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
|
||||
bool initDone = false;
|
||||
bool sensorFound = false;
|
||||
|
||||
// Home Assistant and MQTT
|
||||
String mqttLuminanceTopic = F("");
|
||||
bool mqttInitialized = false;
|
||||
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages
|
||||
|
||||
BH1750 lightMeter;
|
||||
float lastLux = -1000;
|
||||
@@ -55,17 +76,64 @@ private:
|
||||
{
|
||||
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
|
||||
}
|
||||
|
||||
// set up Home Assistant discovery entries
|
||||
void _mqttInitialize()
|
||||
{
|
||||
mqttLuminanceTopic = String(mqttDeviceTopic) + F("/brightness");
|
||||
|
||||
if (HomeAssistantDiscovery) _createMqttSensor(F("Brightness"), mqttLuminanceTopic, F("Illuminance"), F(" lx"));
|
||||
}
|
||||
|
||||
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||
{
|
||||
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||
|
||||
StaticJsonDocument<600> doc;
|
||||
|
||||
doc[F("name")] = String(serverDescription) + F(" ") + name;
|
||||
doc[F("state_topic")] = topic;
|
||||
doc[F("unique_id")] = String(mqttClientID) + name;
|
||||
if (unitOfMeasurement != "")
|
||||
doc[F("unit_of_measurement")] = unitOfMeasurement;
|
||||
if (deviceClass != "")
|
||||
doc[F("device_class")] = deviceClass;
|
||||
doc[F("expire_after")] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||
device[F("manufacturer")] = F("WLED");
|
||||
device[F("model")] = F("FOSS");
|
||||
device[F("sw_version")] = versionString;
|
||||
|
||||
String temp;
|
||||
serializeJson(doc, temp);
|
||||
DEBUG_PRINTLN(t);
|
||||
DEBUG_PRINTLN(temp);
|
||||
|
||||
mqtt->publish(t.c_str(), 0, true, temp.c_str());
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
Wire.begin();
|
||||
lightMeter.begin();
|
||||
bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used
|
||||
PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins
|
||||
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
|
||||
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, po)) return;
|
||||
|
||||
Wire.begin(ioPin[1], ioPin[0]);
|
||||
|
||||
sensorFound = lightMeter.begin();
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (disabled || strip.isUpdating())
|
||||
if ((!enabled) || strip.isUpdating())
|
||||
return;
|
||||
|
||||
unsigned long now = millis();
|
||||
@@ -90,18 +158,25 @@ public:
|
||||
lastSend = millis();
|
||||
if (WLED_MQTT_CONNECTED)
|
||||
{
|
||||
char subuf[45];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/luminance"));
|
||||
mqtt->publish(subuf, 0, true, String(lux).c_str());
|
||||
if (!mqttInitialized)
|
||||
{
|
||||
_mqttInitialize();
|
||||
mqttInitialized = true;
|
||||
}
|
||||
mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str());
|
||||
DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx"));
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
|
||||
DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline float getIlluminance() {
|
||||
return (float)lastLux;
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
@@ -109,18 +184,85 @@ public:
|
||||
user = root.createNestedObject(F("u"));
|
||||
|
||||
JsonArray lux_json = user.createNestedArray(F("Luminance"));
|
||||
|
||||
if (!getLuminanceComplete)
|
||||
{
|
||||
if (!sensorFound) {
|
||||
// if no sensor
|
||||
lux_json.add(F("BH1750 "));
|
||||
lux_json.add(F("Not Found"));
|
||||
} else if (!getLuminanceComplete) {
|
||||
// if we haven't read the sensor yet, let the user know
|
||||
// that we are still waiting for the first measurement
|
||||
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||
lux_json.add(F(" sec until read"));
|
||||
return;
|
||||
// that we are still waiting for the first measurement
|
||||
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||
lux_json.add(F(" sec until read"));
|
||||
return;
|
||||
} else {
|
||||
lux_json.add(lastLux);
|
||||
lux_json.add(F(" lx"));
|
||||
}
|
||||
}
|
||||
|
||||
// (called from set.cpp) stores persistent properties to cfg.json
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
// we add JSON object.
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
|
||||
top[FPSTR(_offset)] = offset;
|
||||
JsonArray io_pin = top.createNestedArray(F("pin"));
|
||||
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
|
||||
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
|
||||
|
||||
DEBUG_PRINTLN(F("BH1750 config saved."));
|
||||
}
|
||||
|
||||
// called before setup() to populate properties from values stored in cfg.json
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
|
||||
|
||||
// we look for JSON object.
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull())
|
||||
{
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINT(F("BH1750"));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled, false);
|
||||
configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, 10000); //ms
|
||||
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms
|
||||
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false);
|
||||
configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1);
|
||||
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing parameters from settings page
|
||||
bool pinsChanged = false;
|
||||
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
|
||||
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
|
||||
PinOwner po = PinOwner::UM_BH1750;
|
||||
if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
setup();
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[F("pin")].isNull();
|
||||
}
|
||||
|
||||
lux_json.add(lastLux);
|
||||
lux_json.add(F(" lx"));
|
||||
return configComplete;
|
||||
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
@@ -128,45 +270,6 @@ public:
|
||||
return USERMOD_ID_BH1750;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
// we add JSON object.
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_enabled)] = !disabled;
|
||||
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||
top[FPSTR(_offset)] = offset;
|
||||
|
||||
DEBUG_PRINTLN(F("Photoresistor config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
// we look for JSON object.
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull())
|
||||
{
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
disabled = !(top[FPSTR(_enabled)] | !disabled);
|
||||
maxReadingInterval = (top[FPSTR(_maxReadInterval)] | maxReadingInterval); // ms
|
||||
minReadingInterval = (top[FPSTR(_minReadInterval)] | minReadingInterval); // ms
|
||||
offset = top[FPSTR(_offset)] | offset;
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
@@ -174,4 +277,5 @@ const char Usermod_BH1750::_name[] PROGMEM = "BH1750";
|
||||
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
|
||||
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
||||
const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-read-interval-ms";
|
||||
const char Usermod_BH1750::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscoveryLux";
|
||||
const char Usermod_BH1750::_offset[] PROGMEM = "offset-lx";
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "wled.h"
|
||||
/*
|
||||
* Register your v2 usermods here!
|
||||
*/
|
||||
#ifdef USERMOD_BH1750
|
||||
#include "../usermods/BH1750_v2/usermod_BH1750.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
#ifdef USERMOD_BH1750
|
||||
usermods.add(new Usermod_BH1750());
|
||||
#endif
|
||||
}
|
||||
@@ -1,40 +1,90 @@
|
||||
Hello! I have written a v2 usermod for the BME280/BMP280 sensor based on the [existing v1 usermod](https://github.com/Aircoookie/WLED/blob/master/usermods/Wemos_D1_mini%2BWemos32_mini_shield/usermod_bme280.cpp). It is not just a refactor, there are many changes which I made to fit my use case, and I hope they will fit the use cases of others as well! Most notably, this usermod is *just* for the BME280 and does not control a display like in the v1 usermod designed for the WeMos shield.
|
||||
# Usermod BME280
|
||||
This Usermod is designed to read a `BME280` or `BMP280` sensor and output the following:
|
||||
- Temperature
|
||||
- Humidity (`BME280` only)
|
||||
- Pressure
|
||||
- Heat Index (`BME280` only)
|
||||
- Dew Point (`BME280` only)
|
||||
|
||||
- Requires libraries `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280)) and `Wire`. Please add these under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||
- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
|
||||
- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else listening on the serial TX pin of your board will get confused by log messages!
|
||||
Configuration is performed via the Usermod menu. There are no parameters to set in code! The following settings can be configured in the Usermod Menu:
|
||||
- Temperature Decimals (number of decimal places to output)
|
||||
- Humidity Decimals
|
||||
- Pressure Decimals
|
||||
- Temperature Interval (how many seconds between temperature and humidity measurements)
|
||||
- Pressure Interval
|
||||
- Publish Always (turn off to only publish changes, on to publish whether or not value changed)
|
||||
- Use Celsius (turn off to use Fahrenheit)
|
||||
- Home Assistant Discovery (turn on to sent MQTT Discovery entries for Home Assistant)
|
||||
- SCL/SDA GPIO Pins
|
||||
|
||||
To enable, compile with `USERMOD_BME280` defined (i.e. `platformio_override.ini`)
|
||||
Dependencies
|
||||
- Libraries
|
||||
- `BME280@~3.0.0` (by [finitespace](https://github.com/finitespace/BME280))
|
||||
- `Wire`
|
||||
- These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||
- Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||
- This usermod also writes to serial (GPIO1 on ESP8266). Please make sure nothing else is listening to the serial TX pin or your board will get confused by log messages!
|
||||
|
||||
In addition to outputting via MQTT, you can read the values from the Info Screen on the dashboard page of the device's web interface.
|
||||
|
||||
Methods also exist to read the read/calculated values from other WLED modules through code.
|
||||
- `getTemperatureC()`
|
||||
- `getTemperatureF()`
|
||||
- `getHumidity()`
|
||||
- `getPressure()`
|
||||
- `getDewPointC()`
|
||||
- `getDewPointF()`
|
||||
- `getHeatIndexC()`
|
||||
- `getHeatIndexF()`
|
||||
|
||||
# Compiling
|
||||
|
||||
To enable, compile with `USERMOD_BME280` defined (e.g. in `platformio_override.ini`)
|
||||
```ini
|
||||
[env:usermod_bme280_d1_mini]
|
||||
extends = env:d1_mini
|
||||
build_flags =
|
||||
${common.build_flags_esp8266}
|
||||
-D USERMOD_BME280
|
||||
```
|
||||
or define `USERMOD_BME280` in `my_config.h`
|
||||
```c++
|
||||
#define USERMOD_BME280
|
||||
lib_deps =
|
||||
${esp8266.lib_deps}
|
||||
BME280@~3.0.0
|
||||
Wire
|
||||
```
|
||||
|
||||
Changes include:
|
||||
- Adjustable measure intervals
|
||||
- Temperature and pressure have separate intervals due to pressure not frequently changing at any constant altitude
|
||||
- Adjustment of number of decimal places in published sensor values
|
||||
- Separate adjustment for temperature, humidity and pressure values
|
||||
- Values are rounded to the specified number of decimal places
|
||||
- Pressure measured in units of hPa instead of Pa
|
||||
- Calculation of heat index (apparent temperature) and dew point
|
||||
- These, along with humidity measurements, are disabled if the sensor is a BMP280
|
||||
- 16x oversampling of sensor during measurement
|
||||
- Values are only published if they are different from the previous value
|
||||
- Values are published on startup (continually until the MQTT broker acknowledges a successful publication)
|
||||
|
||||
Adjustments are made through preprocessor definitions at the start of the class definition.
|
||||
|
||||
MQTT topics are as follows:
|
||||
# MQTT
|
||||
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
|
||||
Measurement type | MQTT topic
|
||||
--- | ---
|
||||
Temperature | `<deviceTopic>/temperature`
|
||||
Humidity | `<deviceTopic>/humidity`
|
||||
Pressure | `<deviceTopic>/pressure`
|
||||
Heat index | `<deviceTopic>/heat_index`
|
||||
Dew point | `<deviceTopic>/dew_point`
|
||||
Dew point | `<deviceTopic>/dew_point`
|
||||
|
||||
If you are using Home Assistant, and `Home Assistant Discovery` is turned on, Home Assistant should automatically detect a new device, provided you have the MQTT integration installed. The device is separate from the main WLED device and will contain sensors for Pressure, Humidity, Temperature, Dew Point and Heat Index.
|
||||
|
||||
# Revision History
|
||||
Jul 2022
|
||||
- Added Home Assistant Discovery
|
||||
- Added API interface to output data
|
||||
- Removed compile-time variables
|
||||
- Added usermod menu interface
|
||||
- Added value outputs to info screen
|
||||
- Updated `readme.md`
|
||||
- Registered usermod
|
||||
- Implemented PinManager for usermod
|
||||
- Implemented reallocation of pins without reboot
|
||||
|
||||
Apr 2021
|
||||
- Added `Publish Always` option
|
||||
|
||||
Dec 2020
|
||||
- Ported to V2 Usermod format
|
||||
- Customizable `measure intervals`
|
||||
- Customizable number of `decimal places` in published sensor values
|
||||
- Pressure measured in units of hPa instead of Pa
|
||||
- Calculation of heat index (apparent temperature) and dew point
|
||||
- `16x oversampling` of sensor during measurement
|
||||
- Values only published if they are different from the previous value
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// force the compiler to show a warning to confirm that this file is included
|
||||
#warning **** Included USERMOD_BME280 version 2.0 ****
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
@@ -9,43 +12,26 @@
|
||||
class UsermodBME280 : public Usermod
|
||||
{
|
||||
private:
|
||||
// User-defined configuration
|
||||
#define Celsius // Show temperature mesaurement in Celcius. Comment out for Fahrenheit
|
||||
#define TemperatureDecimals 1 // Number of decimal places in published temperaure values
|
||||
#define HumidityDecimals 2 // Number of decimal places in published humidity values
|
||||
#define PressureDecimals 2 // Number of decimal places in published pressure values
|
||||
#define TemperatureInterval 5 // Interval to measure temperature (and humidity, dew point if available) in seconds
|
||||
#define PressureInterval 300 // Interval to measure pressure in seconds
|
||||
#define PublishAlways 0 // Publish values even when they have not changed
|
||||
|
||||
// NOTE: Do not implement any compile-time variables, anything the user needs to configure
|
||||
// should be configurable from the Usermod menu using the methods below
|
||||
// key settings set via usermod menu
|
||||
uint8_t TemperatureDecimals = 0; // Number of decimal places in published temperaure values
|
||||
uint8_t HumidityDecimals = 0; // Number of decimal places in published humidity values
|
||||
uint8_t PressureDecimals = 0; // Number of decimal places in published pressure values
|
||||
uint16_t TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
|
||||
uint16_t PressureInterval = 300; // Interval to measure pressure in seconds
|
||||
bool PublishAlways = false; // Publish values even when they have not changed
|
||||
bool UseCelsius = true; // Use Celsius for Reporting
|
||||
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
|
||||
bool enabled = true;
|
||||
|
||||
// Sanity checks
|
||||
#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
|
||||
#define TemperatureDecimals 0
|
||||
#endif
|
||||
#if !defined(HumidityDecimals) || HumidityDecimals < 0
|
||||
#define HumidityDecimals 0
|
||||
#endif
|
||||
#if !defined(PressureDecimals) || PressureDecimals < 0
|
||||
#define PressureDecimals 0
|
||||
#endif
|
||||
#if !defined(TemperatureInterval) || TemperatureInterval < 0
|
||||
#define TemperatureInterval 1
|
||||
#endif
|
||||
#if !defined(PressureInterval) || PressureInterval < 0
|
||||
#define PressureInterval TemperatureInterval
|
||||
#endif
|
||||
#if !defined(PublishAlways)
|
||||
#define PublishAlways 0
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
|
||||
uint8_t SCL_PIN = 22;
|
||||
uint8_t SDA_PIN = 21;
|
||||
#else // ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
// set the default pins based on the architecture, these get overridden by Usermod menu settings
|
||||
#ifdef ESP8266
|
||||
//uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
||||
#endif
|
||||
int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup()
|
||||
bool initDone = false;
|
||||
|
||||
// BME280 sensor settings
|
||||
BME280I2C::Settings settings{
|
||||
@@ -75,6 +61,7 @@ private:
|
||||
float sensorHeatIndex;
|
||||
float sensorDewPoint;
|
||||
float sensorPressure;
|
||||
String tempScale;
|
||||
// Track previous sensor values
|
||||
float lastTemperature;
|
||||
float lastHumidity;
|
||||
@@ -82,43 +69,131 @@ private:
|
||||
float lastDewPoint;
|
||||
float lastPressure;
|
||||
|
||||
// Store packet IDs of MQTT publications
|
||||
uint16_t mqttTemperaturePub = 0;
|
||||
uint16_t mqttPressurePub = 0;
|
||||
// MQTT topic strings for publishing Home Assistant discovery topics
|
||||
bool mqttInitialized = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
// Read the BME280/BMP280 Sensor (which one runs depends on whether Celsius or Farenheit being set in Usermod Menu)
|
||||
void UpdateBME280Data(int SensorType)
|
||||
{
|
||||
float _temperature, _humidity, _pressure;
|
||||
#ifdef Celsius
|
||||
|
||||
if (UseCelsius) {
|
||||
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
||||
#else
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
tempScale = F("°C");
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
}
|
||||
} else {
|
||||
BME280::TempUnit tempUnit(BME280::TempUnit_Fahrenheit);
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Fahrenheit);
|
||||
#endif
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
sensorTemperature = _temperature;
|
||||
sensorHumidity = _humidity;
|
||||
sensorPressure = _pressure;
|
||||
tempScale = F("°F");
|
||||
if (sensorType == 1)
|
||||
{
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procedure to define all MQTT discovery Topics
|
||||
void _mqttInitialize()
|
||||
{
|
||||
char mqttTemperatureTopic[128];
|
||||
char mqttHumidityTopic[128];
|
||||
char mqttPressureTopic[128];
|
||||
char mqttHeatIndexTopic[128];
|
||||
char mqttDewPointTopic[128];
|
||||
snprintf_P(mqttTemperatureTopic, 127, PSTR("%s/temperature"), mqttDeviceTopic);
|
||||
snprintf_P(mqttPressureTopic, 127, PSTR("%s/pressure"), mqttDeviceTopic);
|
||||
snprintf_P(mqttHumidityTopic, 127, PSTR("%s/humidity"), mqttDeviceTopic);
|
||||
snprintf_P(mqttHeatIndexTopic, 127, PSTR("%s/heat_index"), mqttDeviceTopic);
|
||||
snprintf_P(mqttDewPointTopic, 127, PSTR("%s/dew_point"), mqttDeviceTopic);
|
||||
|
||||
if (HomeAssistantDiscovery) {
|
||||
_createMqttSensor(F("Temperature"), mqttTemperatureTopic, "temperature", tempScale);
|
||||
_createMqttSensor(F("Pressure"), mqttPressureTopic, "pressure", F("hPa"));
|
||||
_createMqttSensor(F("Humidity"), mqttHumidityTopic, "humidity", F("%"));
|
||||
_createMqttSensor(F("HeatIndex"), mqttHeatIndexTopic, "temperature", tempScale);
|
||||
_createMqttSensor(F("DewPoint"), mqttDewPointTopic, "temperature", tempScale);
|
||||
}
|
||||
}
|
||||
|
||||
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||
{
|
||||
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||
|
||||
StaticJsonDocument<600> doc;
|
||||
|
||||
doc[F("name")] = String(serverDescription) + " " + name;
|
||||
doc[F("state_topic")] = topic;
|
||||
doc[F("unique_id")] = String(mqttClientID) + name;
|
||||
if (unitOfMeasurement != "")
|
||||
doc[F("unit_of_measurement")] = unitOfMeasurement;
|
||||
if (deviceClass != "")
|
||||
doc[F("device_class")] = deviceClass;
|
||||
doc[F("expire_after")] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||
device[F("manufacturer")] = F("WLED");
|
||||
device[F("model")] = F("FOSS");
|
||||
device[F("sw_version")] = versionString;
|
||||
|
||||
String temp;
|
||||
serializeJson(doc, temp);
|
||||
DEBUG_PRINTLN(t);
|
||||
DEBUG_PRINTLN(temp);
|
||||
|
||||
mqtt->publish(t.c_str(), 0, true, temp.c_str());
|
||||
}
|
||||
|
||||
void publishMqtt(const char *topic, const char* state) {
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[128];
|
||||
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, topic);
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void setup()
|
||||
{
|
||||
Wire.begin(SDA_PIN, SCL_PIN);
|
||||
bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used
|
||||
PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins
|
||||
PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins
|
||||
if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; }
|
||||
|
||||
Wire.begin(ioPin[1], ioPin[0]);
|
||||
|
||||
if (!bme.begin())
|
||||
{
|
||||
sensorType = 0;
|
||||
Serial.println("Could not find BME280I2C sensor!");
|
||||
DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -126,69 +201,68 @@ public:
|
||||
{
|
||||
case BME280::ChipModel_BME280:
|
||||
sensorType = 1;
|
||||
Serial.println("Found BME280 sensor! Success.");
|
||||
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
|
||||
break;
|
||||
case BME280::ChipModel_BMP280:
|
||||
sensorType = 2;
|
||||
Serial.println("Found BMP280 sensor! No Humidity available.");
|
||||
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
|
||||
break;
|
||||
default:
|
||||
sensorType = 0;
|
||||
Serial.println("Found UNKNOWN sensor! Error!");
|
||||
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
|
||||
}
|
||||
}
|
||||
initDone=true;
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
|
||||
// BME280 sensor MQTT publishing
|
||||
// Check if sensor present and MQTT Connected, otherwise it will crash the MCU
|
||||
if (sensorType != 0 && mqtt != nullptr)
|
||||
// Check if sensor present and Connected, otherwise it will crash the MCU
|
||||
if (sensorType != 0)
|
||||
{
|
||||
// Timer to fetch new temperature, humidity and pressure data at intervals
|
||||
timer = millis();
|
||||
|
||||
if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000 || mqttTemperaturePub == 0)
|
||||
if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000)
|
||||
{
|
||||
lastTemperatureMeasure = timer;
|
||||
|
||||
UpdateBME280Data(sensorType);
|
||||
|
||||
float temperature = roundf(sensorTemperature * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
float temperature = roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
float humidity, heatIndex, dewPoint;
|
||||
|
||||
// If temperature has changed since last measure, create string populated with device topic
|
||||
// from the UI and values read from sensor, then publish to broker
|
||||
if (temperature != lastTemperature || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/temperature";
|
||||
mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(temperature, TemperatureDecimals).c_str());
|
||||
publishMqtt("temperature", String(temperature, TemperatureDecimals).c_str());
|
||||
}
|
||||
|
||||
lastTemperature = temperature; // Update last sensor temperature for next loop
|
||||
|
||||
if (sensorType == 1) // Only if sensor is a BME280
|
||||
{
|
||||
humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
|
||||
heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
||||
humidity = roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals);
|
||||
heatIndex = roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
dewPoint = roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
|
||||
if (humidity != lastHumidity || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/humidity";
|
||||
mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str());
|
||||
publishMqtt("humidity", String(humidity, HumidityDecimals).c_str());
|
||||
}
|
||||
|
||||
if (heatIndex != lastHeatIndex || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/heat_index";
|
||||
mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str());
|
||||
publishMqtt("heat_index", String(heatIndex, TemperatureDecimals).c_str());
|
||||
}
|
||||
|
||||
if (dewPoint != lastDewPoint || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/dew_point";
|
||||
mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str());
|
||||
publishMqtt("dew_point", String(dewPoint, TemperatureDecimals).c_str());
|
||||
}
|
||||
|
||||
lastHumidity = humidity;
|
||||
@@ -197,20 +271,208 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (timer - lastPressureMeasure >= PressureInterval * 1000 || mqttPressurePub == 0)
|
||||
if (timer - lastPressureMeasure >= PressureInterval * 1000)
|
||||
{
|
||||
lastPressureMeasure = timer;
|
||||
|
||||
float pressure = roundf(sensorPressure * pow(10, PressureDecimals)) / pow(10, PressureDecimals);
|
||||
float pressure = roundf(sensorPressure * powf(10, PressureDecimals)) / powf(10, PressureDecimals);
|
||||
|
||||
if (pressure != lastPressure || PublishAlways)
|
||||
{
|
||||
String topic = String(mqttDeviceTopic) + "/pressure";
|
||||
mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str());
|
||||
publishMqtt("pressure", String(pressure, PressureDecimals).c_str());
|
||||
}
|
||||
|
||||
lastPressure = pressure;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void onMqttConnect(bool sessionPresent)
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED && !mqttInitialized)
|
||||
{
|
||||
_mqttInitialize();
|
||||
mqttInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* API calls te enable data exchange between WLED modules
|
||||
*/
|
||||
inline float getTemperatureC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
}
|
||||
|
||||
inline float getTemperatureF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
inline float getHumidity() {
|
||||
return (float)roundf(sensorHumidity * powf(10, HumidityDecimals));
|
||||
}
|
||||
|
||||
inline float getPressure() {
|
||||
return (float)roundf(sensorPressure * powf(10, PressureDecimals));
|
||||
}
|
||||
|
||||
inline float getDewPointC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
}
|
||||
|
||||
inline float getDewPointF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
inline float getHeatIndexC() {
|
||||
if (UseCelsius) {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
} else {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) * 1.8f + 32;
|
||||
}
|
||||
}
|
||||
|
||||
inline float getHeatIndexF() {
|
||||
if (UseCelsius) {
|
||||
return ((float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals) -32) * 0.56f;
|
||||
} else {
|
||||
return (float)roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
// Publish Sensor Information to Info Page
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull()) user = root.createNestedObject(F("u"));
|
||||
|
||||
if (sensorType==0) //No Sensor
|
||||
{
|
||||
// if we sensor not detected, let the user know
|
||||
JsonArray temperature_json = user.createNestedArray(F("BME/BMP280 Sensor"));
|
||||
temperature_json.add(F("Not Found"));
|
||||
}
|
||||
else if (sensorType==2) //BMP280
|
||||
{
|
||||
|
||||
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
|
||||
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
|
||||
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)));
|
||||
temperature_json.add(tempScale);
|
||||
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
|
||||
pressure_json.add(F("hPa"));
|
||||
}
|
||||
else if (sensorType==1) //BME280
|
||||
{
|
||||
JsonArray temperature_json = user.createNestedArray(F("Temperature"));
|
||||
JsonArray humidity_json = user.createNestedArray(F("Humidity"));
|
||||
JsonArray pressure_json = user.createNestedArray(F("Pressure"));
|
||||
JsonArray heatindex_json = user.createNestedArray(F("Heat Index"));
|
||||
JsonArray dewpoint_json = user.createNestedArray(F("Dew Point"));
|
||||
temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
temperature_json.add(tempScale);
|
||||
humidity_json.add(roundf(sensorHumidity * powf(10, HumidityDecimals)));
|
||||
humidity_json.add(F("%"));
|
||||
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)));
|
||||
pressure_json.add(F("hPa"));
|
||||
heatindex_json.add(roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
heatindex_json.add(tempScale);
|
||||
dewpoint_json.add(roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals));
|
||||
dewpoint_json.add(tempScale);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Save Usermod Config Settings
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[F("TemperatureDecimals")] = TemperatureDecimals;
|
||||
top[F("HumidityDecimals")] = HumidityDecimals;
|
||||
top[F("PressureDecimals")] = PressureDecimals;
|
||||
top[F("TemperatureInterval")] = TemperatureInterval;
|
||||
top[F("PressureInterval")] = PressureInterval;
|
||||
top[F("PublishAlways")] = PublishAlways;
|
||||
top[F("UseCelsius")] = UseCelsius;
|
||||
top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery;
|
||||
JsonArray io_pin = top.createNestedArray(F("pin"));
|
||||
for (byte i=0; i<2; i++) io_pin.add(ioPin[i]);
|
||||
top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page
|
||||
DEBUG_PRINTLN(F("BME280 config saved."));
|
||||
}
|
||||
|
||||
// Read Usermod Config Settings
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINT(F(_name));
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
|
||||
configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1);
|
||||
configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0);
|
||||
configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0);
|
||||
configComplete &= getJsonValue(top[F("TemperatureInterval")], TemperatureInterval, 30);
|
||||
configComplete &= getJsonValue(top[F("PressureInterval")], PressureInterval, 30);
|
||||
configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false);
|
||||
configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true);
|
||||
configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false);
|
||||
for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]);
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
if (!initDone) {
|
||||
// first run: reading from cfg.json
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
// changing parameters from settings page
|
||||
bool pinsChanged = false;
|
||||
for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed
|
||||
if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones
|
||||
PinOwner po = PinOwner::UM_BME280;
|
||||
if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
|
||||
pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins
|
||||
for (byte i=0; i<2; i++) ioPin[i] = newPin[i];
|
||||
setup();
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[F("pin")].isNull();
|
||||
}
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_BME280;
|
||||
}
|
||||
};
|
||||
|
||||
const char UsermodBME280::_name[] PROGMEM = "BME280/BMP280";
|
||||
const char UsermodBME280::_enabled[] PROGMEM = "enabled";
|
||||
|
||||
8
usermods/Cronixie/readme.md
Normal file
8
usermods/Cronixie/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Cronixie clock usermod
|
||||
|
||||
This usermod supports driving the Cronixie M and L clock kits by Diamex.
|
||||
|
||||
## Installation
|
||||
|
||||
Compile and upload after adding `-D USERMOD_CRONIXIE` to `build_flags` of your PlatformIO environment.
|
||||
Make sure the Auto Brightness Limiter is enabled at 420mA (!) and configure 60 WS281x LEDs.
|
||||
301
usermods/Cronixie/usermod_cronixie.h
Normal file
301
usermods/Cronixie/usermod_cronixie.h
Normal file
@@ -0,0 +1,301 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
class UsermodCronixie : public Usermod {
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
char cronixieDisplay[7] = "HHMMSS";
|
||||
byte _digitOut[6] = {10,10,10,10,10,10};
|
||||
byte dP[6] = {255, 255, 255, 255, 255, 255};
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
bool backlight = true;
|
||||
|
||||
public:
|
||||
void initCronixie()
|
||||
{
|
||||
if (dP[0] == 255) // if dP[0] is 255, cronixie is not yet init'ed
|
||||
{
|
||||
setCronixie();
|
||||
strip.getSegment(0).grouping = 10; // 10 LEDs per digit
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!toki.isTick()) return;
|
||||
initCronixie();
|
||||
_overlayCronixie();
|
||||
strip.trigger();
|
||||
}
|
||||
|
||||
byte getSameCodeLength(char code, int index, char const cronixieDisplay[])
|
||||
{
|
||||
byte counter = 0;
|
||||
|
||||
for (int i = index+1; i < 6; i++)
|
||||
{
|
||||
if (cronixieDisplay[i] == code)
|
||||
{
|
||||
counter++;
|
||||
} else {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void setCronixie()
|
||||
{
|
||||
/*
|
||||
* digit purpose index
|
||||
* 0-9 | 0-9 (incl. random)
|
||||
* 10 | blank
|
||||
* 11 | blank, bg off
|
||||
* 12 | test upw.
|
||||
* 13 | test dnw.
|
||||
* 14 | binary AM/PM
|
||||
* 15 | BB upper +50 for no trailing 0
|
||||
* 16 | BBB
|
||||
* 17 | BBBB
|
||||
* 18 | BBBBB
|
||||
* 19 | BBBBBB
|
||||
* 20 | H
|
||||
* 21 | HH
|
||||
* 22 | HHH
|
||||
* 23 | HHHH
|
||||
* 24 | M
|
||||
* 25 | MM
|
||||
* 26 | MMM
|
||||
* 27 | MMMM
|
||||
* 28 | MMMMM
|
||||
* 29 | MMMMMM
|
||||
* 30 | S
|
||||
* 31 | SS
|
||||
* 32 | SSS
|
||||
* 33 | SSSS
|
||||
* 34 | SSSSS
|
||||
* 35 | SSSSSS
|
||||
* 36 | Y
|
||||
* 37 | YY
|
||||
* 38 | YYYY
|
||||
* 39 | I
|
||||
* 40 | II
|
||||
* 41 | W
|
||||
* 42 | WW
|
||||
* 43 | D
|
||||
* 44 | DD
|
||||
* 45 | DDD
|
||||
* 46 | V
|
||||
* 47 | VV
|
||||
* 48 | VVV
|
||||
* 49 | VVVV
|
||||
* 50 | VVVVV
|
||||
* 51 | VVVVVV
|
||||
* 52 | v
|
||||
* 53 | vv
|
||||
* 54 | vvv
|
||||
* 55 | vvvv
|
||||
* 56 | vvvvv
|
||||
* 57 | vvvvvv
|
||||
*/
|
||||
|
||||
//H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year
|
||||
//M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year
|
||||
//S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week
|
||||
//B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5
|
||||
|
||||
//Y YearLower | YY - Year LU | YYYY - Std.
|
||||
//I MonthLower | II - Month of Year
|
||||
//W Week of Month | WW Week of Year
|
||||
//D Day of Week | DD Day Of Month | DDD Day Of Year
|
||||
|
||||
DEBUG_PRINT("cset ");
|
||||
DEBUG_PRINTLN(cronixieDisplay);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
dP[i] = 10;
|
||||
switch (cronixieDisplay[i])
|
||||
{
|
||||
case '_': dP[i] = 10; break;
|
||||
case '-': dP[i] = 11; break;
|
||||
case 'r': dP[i] = random(1,7); break; //random btw. 1-6
|
||||
case 'R': dP[i] = random(0,10); break; //random btw. 0-9
|
||||
//case 't': break; //Test upw.
|
||||
//case 'T': break; //Test dnw.
|
||||
case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break;
|
||||
case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break;
|
||||
case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break;
|
||||
case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break;
|
||||
case 'A': dP[i] = 108; i++; break;
|
||||
case 'a': dP[i] = 58; i++; break;
|
||||
case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break;
|
||||
case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break;
|
||||
case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; break; //refresh more often bc. of secs
|
||||
case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; break;
|
||||
case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break;
|
||||
case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break;
|
||||
case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M.
|
||||
case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break;
|
||||
//case 'W': break;
|
||||
//case 'w': break;
|
||||
case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break;
|
||||
case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break;
|
||||
case '0': dP[i] = 0; break;
|
||||
case '1': dP[i] = 1; break;
|
||||
case '2': dP[i] = 2; break;
|
||||
case '3': dP[i] = 3; break;
|
||||
case '4': dP[i] = 4; break;
|
||||
case '5': dP[i] = 5; break;
|
||||
case '6': dP[i] = 6; break;
|
||||
case '7': dP[i] = 7; break;
|
||||
case '8': dP[i] = 8; break;
|
||||
case '9': dP[i] = 9; break;
|
||||
//case 'V': break; //user var0
|
||||
//case 'v': break; //user var1
|
||||
}
|
||||
}
|
||||
DEBUG_PRINT("result ");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
DEBUG_PRINT((int)dP[i]);
|
||||
DEBUG_PRINT(" ");
|
||||
}
|
||||
DEBUG_PRINTLN((int)dP[5]);
|
||||
|
||||
_overlayCronixie(); // refresh
|
||||
}
|
||||
|
||||
void _overlayCronixie()
|
||||
{
|
||||
byte h = hour(localTime);
|
||||
byte h0 = h;
|
||||
byte m = minute(localTime);
|
||||
byte s = second(localTime);
|
||||
byte d = day(localTime);
|
||||
byte mi = month(localTime);
|
||||
int y = year(localTime);
|
||||
//this has to be changed in time for 22nd century
|
||||
y -= 2000; if (y<0) y += 30; //makes countdown work
|
||||
|
||||
if (useAMPM && !countdownMode)
|
||||
{
|
||||
if (h>12) h-=12;
|
||||
else if (h==0) h+=12;
|
||||
}
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (dP[i] < 12) _digitOut[i] = dP[i];
|
||||
else {
|
||||
if (dP[i] < 65)
|
||||
{
|
||||
switch(dP[i])
|
||||
{
|
||||
case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH
|
||||
case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM
|
||||
case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS
|
||||
|
||||
case 20: _digitOut[i] = h- (h/10)*10; break; //H
|
||||
case 24: _digitOut[i] = m/10; break; //M
|
||||
case 30: _digitOut[i] = s/10; break; //S
|
||||
|
||||
case 43: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D
|
||||
case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD
|
||||
case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II
|
||||
case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY
|
||||
case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY
|
||||
|
||||
//case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI)
|
||||
//case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB
|
||||
case 14: _digitOut[i] = (h0>11)?1:0; break; //B
|
||||
}
|
||||
} else
|
||||
{
|
||||
switch(dP[i])
|
||||
{
|
||||
case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh
|
||||
case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm
|
||||
case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss
|
||||
//case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI)
|
||||
//case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb
|
||||
case 64: _digitOut[i] = (h0>11)?1:10; break; //b
|
||||
|
||||
case 93: _digitOut[i] = weekday(localTime); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d
|
||||
case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd
|
||||
case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii
|
||||
case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy
|
||||
case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4};
|
||||
|
||||
for (uint16_t i = 0; i < 6; i++)
|
||||
{
|
||||
byte o = 10*i;
|
||||
byte excl = 10;
|
||||
if(_digitOut[i] < 10) excl = offsets[_digitOut[i]];
|
||||
excl += o;
|
||||
|
||||
if (backlight && _digitOut[i] <11)
|
||||
{
|
||||
uint32_t col = gamma32(strip.getSegment(0).colors[1]);
|
||||
for (uint16_t j=o; j< o+10; j++) {
|
||||
if (j != excl) strip.setPixelColor(j, col);
|
||||
}
|
||||
} else
|
||||
{
|
||||
for (uint16_t j=o; j< o+10; j++) {
|
||||
if (j != excl) strip.setPixelColor(j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
root["nx"] = cronixieDisplay;
|
||||
}
|
||||
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
if (root["nx"].is<const char*>()) {
|
||||
strncpy(cronixieDisplay, root["nx"], 6);
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(F("Cronixie"));
|
||||
top["backlight"] = backlight;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||
|
||||
JsonObject top = root[F("Cronixie")];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
|
||||
configComplete &= getJsonValue(top["backlight"], backlight);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_CRONIXIE;
|
||||
}
|
||||
};
|
||||
@@ -6,12 +6,13 @@
|
||||
; USERMOD_DHT_CELSIUS - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
|
||||
; USERMOD_DHT_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds
|
||||
; USERMOD_DHT_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
|
||||
; USERMOD_DHT_MQTT - publish measurements to the MQTT broker
|
||||
; USERMOD_DHT_STATS - For debug, report delay stats
|
||||
|
||||
[env:d1_mini_usermod_dht_C]
|
||||
extends = env:d1_mini
|
||||
build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_deps = ${env:d1_mini.lib_deps}
|
||||
https://github.com/alwynallan/DHT_nonblocking
|
||||
|
||||
[env:custom32_LEDPIN_16_usermod_dht_C]
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# DHT Temperature/Humidity sensor usermod
|
||||
|
||||
This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor.
|
||||
The sensor readings are displayed in the Info section of the web UI.
|
||||
The sensor readings are displayed in the Info section of the web UI (and optionally sent to an MQTT broker).
|
||||
|
||||
If sensor is not detected after a while (10 update intervals), this usermod will be disabled.
|
||||
If sensor is not detected after 10 update intervals, the usermod will be disabled.
|
||||
|
||||
If enabled, measured temperature and humidity will be published to the following MQTT topics
|
||||
* `{devceTopic}/dht/temperature`
|
||||
* `{devceTopic}/dht/humidity`
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -11,12 +15,13 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_DHT` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_DHT` - define this to include this user mod wled00\usermods_list.cpp
|
||||
* `USERMOD_DHT_DHTTYPE` - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22
|
||||
* `USERMOD_DHT_PIN` - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board
|
||||
* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported
|
||||
* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds
|
||||
* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90 seconds
|
||||
* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees Celsius, otherwise Fahrenheit will be reported
|
||||
* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60000 ms
|
||||
* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90000 ms
|
||||
* `USERMOD_DHT_MQTT` - publish measurements to an MQTT broker
|
||||
* `USERMOD_DHT_STATS` - For debug, report delay stats
|
||||
|
||||
## Project link
|
||||
@@ -29,13 +34,15 @@ If you are using `platformio_override.ini`, you should be able to refresh the ta
|
||||
|
||||
|
||||
## Change Log
|
||||
|
||||
2022-10-15
|
||||
* Add ability to publish sensor readings to an MQTT broker
|
||||
* fix compilation error for sample [env:d1_mini_usermod_dht_C] task
|
||||
2020-02-04
|
||||
* Change default QuinLed pin to Q2
|
||||
* Instead of trying to keep updates at constant cadence, space readings out by measurement interval; hope this helps to avoid occasional bursts of readings with errors
|
||||
* Instead of trying to keep updates at constant cadence, space out readings by measurement interval. Hopefully, this helps eliminate occasional bursts of readings with errors
|
||||
* Add some more (optional) stats
|
||||
2020-02-03
|
||||
* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking
|
||||
* The new library serializes/delays up to 5ms for the sensor readout
|
||||
2020-02-02
|
||||
* The new library serializes/delays up to 5ms for the sensor readout
|
||||
2020-02-02
|
||||
* Created
|
||||
|
||||
@@ -62,6 +62,10 @@ class UsermodDHT : public Usermod {
|
||||
float humidity, temperature = 0;
|
||||
bool initializing = true;
|
||||
bool disabled = false;
|
||||
#ifdef USERMOD_DHT_MQTT
|
||||
char dhtMqttTopic[64];
|
||||
size_t dhtMqttTopicLen;
|
||||
#endif
|
||||
#ifdef USERMOD_DHT_STATS
|
||||
unsigned long nextResetStatsTime = 0;
|
||||
uint16_t updates = 0;
|
||||
@@ -76,6 +80,10 @@ class UsermodDHT : public Usermod {
|
||||
void setup() {
|
||||
nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
|
||||
lastReadTime = millis();
|
||||
#ifdef USERMOD_DHT_MQTT
|
||||
sprintf(dhtMqttTopic, "%s/dht", mqttDeviceTopic);
|
||||
dhtMqttTopicLen = strlen(dhtMqttTopic);
|
||||
#endif
|
||||
#ifdef USERMOD_DHT_STATS
|
||||
nextResetStatsTime = millis() + 60*60*1000;
|
||||
#endif
|
||||
@@ -110,10 +118,29 @@ class UsermodDHT : public Usermod {
|
||||
temperature = tempC * 9 / 5 + 32;
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_DHT_MQTT
|
||||
// 10^n where n is number of decimal places to display in mqtt message. Please adjust buff size together with this constant
|
||||
#define FLOAT_PREC 100
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
char buff[10];
|
||||
|
||||
strcpy(dhtMqttTopic + dhtMqttTopicLen, "/temperature");
|
||||
sprintf(buff, "%d.%d", (int)temperature, ((int)(temperature * FLOAT_PREC)) % FLOAT_PREC);
|
||||
mqtt->publish(dhtMqttTopic, 0, false, buff);
|
||||
|
||||
sprintf(buff, "%d.%d", (int)humidity, ((int)(humidity * FLOAT_PREC)) % FLOAT_PREC);
|
||||
strcpy(dhtMqttTopic + dhtMqttTopicLen, "/humidity");
|
||||
mqtt->publish(dhtMqttTopic, 0, false, buff);
|
||||
|
||||
dhtMqttTopic[dhtMqttTopicLen] = '\0';
|
||||
}
|
||||
#undef FLOAT_PREC
|
||||
#endif
|
||||
|
||||
nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL;
|
||||
lastReadTime = millis();
|
||||
initializing = false;
|
||||
|
||||
|
||||
#ifdef USERMOD_DHT_STATS
|
||||
unsigned long icalc = millis() - currentIteration;
|
||||
if (icalc > maxIteration) {
|
||||
@@ -134,7 +161,7 @@ class UsermodDHT : public Usermod {
|
||||
dcalc = millis() - dcalc;
|
||||
if (dcalc > maxDelay) {
|
||||
maxDelay = dcalc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) {
|
||||
@@ -207,7 +234,7 @@ class UsermodDHT : public Usermod {
|
||||
temp.add("°F");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_DHT;
|
||||
|
||||
@@ -207,6 +207,17 @@ class MyExampleUsermod : public Usermod {
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw()
|
||||
{
|
||||
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
|
||||
@@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI {
|
||||
private:
|
||||
uint8_t digits[NUM_DIGITS];
|
||||
|
||||
|
||||
// These read 16- and 32-bit types from the SD card file.
|
||||
// BMP data is stored little-endian, Arduino is little-endian too.
|
||||
// May need to reverse subscript order if porting elsewhere.
|
||||
@@ -33,7 +34,16 @@ private:
|
||||
}
|
||||
|
||||
uint16_t output_buffer[TFT_HEIGHT][TFT_WIDTH];
|
||||
|
||||
int16_t w = 135, h = 240, x = 0, y = 0, bufferedDigit = 255;
|
||||
uint16_t digitR, digitG, digitB, dimming = 255;
|
||||
uint32_t digitColor = 0;
|
||||
|
||||
void drawBuffer() {
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
pushImage(x, y, w, h, (uint16_t *)output_buffer);
|
||||
setSwapBytes(oldSwapBytes);
|
||||
}
|
||||
|
||||
// These BMP functions are stolen directly from the TFT_SPIFFS_BMP example in the TFT_eSPI library.
|
||||
// Unfortunately, they aren't part of the library itself, so I had to copy them.
|
||||
@@ -41,44 +51,69 @@ private:
|
||||
|
||||
//// BEGIN STOLEN CODE
|
||||
|
||||
// Draw directly from file stored in RGB565 format
|
||||
// Draw directly from file stored in RGB565 format. Fastest
|
||||
bool drawBin(const char *filename) {
|
||||
fs::File bmpFS;
|
||||
|
||||
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print(F("File not found: "));
|
||||
Serial.println(filename);
|
||||
return(false);
|
||||
}
|
||||
|
||||
size_t sz = bmpFS.size();
|
||||
if (sz <= 64800)
|
||||
{
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
|
||||
int16_t h = sz / (135 * 2);
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
int16_t y = (height() - h) /2;
|
||||
|
||||
bmpFS.read((uint8_t *) output_buffer,sz);
|
||||
|
||||
if (!realtimeMode || realtimeOverride) strip.service();
|
||||
|
||||
pushImage(0, y, 135, h, (uint16_t *)output_buffer);
|
||||
|
||||
setSwapBytes(oldSwapBytes);
|
||||
if (sz > 64800) {
|
||||
bmpFS.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t r, g, b, dimming = 255;
|
||||
int16_t row, col;
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
w = 135;
|
||||
h = sz / (w * 2);
|
||||
x = 0;
|
||||
y = (height() - h) /2;
|
||||
|
||||
uint8_t lineBuffer[w * 2];
|
||||
|
||||
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) strip.service();
|
||||
|
||||
// 0,0 coordinates are top left
|
||||
for (row = 0; row < h; row++) {
|
||||
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t PixM, PixL;
|
||||
|
||||
// Colors are already in 16-bit R5, G6, B5 format
|
||||
for (col = 0; col < w; col++)
|
||||
{
|
||||
if (dimming == 255 && !digitColor) { // not needed, copy directly
|
||||
output_buffer[row][col] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
|
||||
} else {
|
||||
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
|
||||
PixM = lineBuffer[col*2+1];
|
||||
PixL = lineBuffer[col*2];
|
||||
// align to 8-bit value (MSB left aligned)
|
||||
r = (PixM) & 0xF8;
|
||||
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
|
||||
b = (PixL << 3) & 0xF8;
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
|
||||
return(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool drawBmp(const char *filename) {
|
||||
@@ -87,78 +122,175 @@ private:
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print(F("File not found: "));
|
||||
Serial.println(filename);
|
||||
return(false);
|
||||
}
|
||||
|
||||
uint32_t seekOffset;
|
||||
int16_t w, h, row;
|
||||
uint8_t r, g, b;
|
||||
uint32_t seekOffset, headerSize, paletteSize = 0;
|
||||
int16_t row;
|
||||
uint16_t r, g, b, dimming = 255, bitDepth;
|
||||
|
||||
uint16_t magic = read16(bmpFS);
|
||||
if (magic == 0xFFFF) {
|
||||
if (magic != ('B' | ('M' << 8))) { // File not found or not a BMP
|
||||
Serial.println(F("BMP not found!"));
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
}
|
||||
|
||||
if (magic != 0x4D42) {
|
||||
Serial.print(F("File not a BMP. Magic: "));
|
||||
Serial.println(magic);
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
seekOffset = read32(bmpFS);
|
||||
read32(bmpFS);
|
||||
w = read32(bmpFS);
|
||||
h = read32(bmpFS);
|
||||
read32(bmpFS); // filesize in bytes
|
||||
read32(bmpFS); // reserved
|
||||
seekOffset = read32(bmpFS); // start of bitmap
|
||||
headerSize = read32(bmpFS); // header size
|
||||
w = read32(bmpFS); // width
|
||||
h = read32(bmpFS); // height
|
||||
read16(bmpFS); // color planes (must be 1)
|
||||
bitDepth = read16(bmpFS);
|
||||
|
||||
if ((read16(bmpFS) != 1) || (read16(bmpFS) != 24) || (read32(bmpFS) != 0)) {
|
||||
if (read32(bmpFS) != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) {
|
||||
Serial.println(F("BMP format not recognized."));
|
||||
bmpFS.close();
|
||||
return(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
//draw img that is shorter than 240pix into the center
|
||||
int16_t y = (height() - h) /2;
|
||||
uint32_t palette[256];
|
||||
if (bitDepth <= 8) // 1,4,8 bit bitmap: read color palette
|
||||
{
|
||||
read32(bmpFS); read32(bmpFS); read32(bmpFS); // size, w resolution, h resolution
|
||||
paletteSize = read32(bmpFS);
|
||||
if (paletteSize == 0) paletteSize = bitDepth * bitDepth; //if 0, size is 2^bitDepth
|
||||
bmpFS.seek(14 + headerSize); // start of color palette
|
||||
for (uint16_t i = 0; i < paletteSize; i++) {
|
||||
palette[i] = read32(bmpFS);
|
||||
}
|
||||
}
|
||||
|
||||
// draw img that is shorter than 240pix into the center
|
||||
x = (width() - w) /2;
|
||||
y = (height() - h) /2;
|
||||
|
||||
bool oldSwapBytes = getSwapBytes();
|
||||
setSwapBytes(true);
|
||||
bmpFS.seek(seekOffset);
|
||||
|
||||
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
||||
uint8_t lineBuffer[w * 3 + padding];
|
||||
uint32_t lineSize = ((bitDepth * w +31) >> 5) * 4;
|
||||
uint8_t lineBuffer[lineSize];
|
||||
|
||||
uint8_t serviceStrip = (!realtimeMode || realtimeOverride) ? 7 : 0;
|
||||
uint8_t serviceStrip = (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) ? 7 : 0;
|
||||
// row is decremented as the BMP image is drawn bottom up
|
||||
for (row = h-1; row >= 0; row--) {
|
||||
if ((row & 0b00000111) == serviceStrip) strip.service(); //still refresh backlight to mitigate stutter every few rows
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t* bptr = lineBuffer;
|
||||
|
||||
// Convert 24 to 16 bit colours while copying to output buffer.
|
||||
// Convert 24 to 16 bit colors while copying to output buffer.
|
||||
for (uint16_t col = 0; col < w; col++)
|
||||
{
|
||||
b = *bptr++;
|
||||
g = *bptr++;
|
||||
r = *bptr++;
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
if (bitDepth == 24) {
|
||||
b = *bptr++;
|
||||
g = *bptr++;
|
||||
r = *bptr++;
|
||||
} else {
|
||||
uint32_t c = 0;
|
||||
if (bitDepth == 8) {
|
||||
c = palette[*bptr++];
|
||||
}
|
||||
else if (bitDepth == 4) {
|
||||
c = palette[(*bptr >> ((col & 0x01)?0:4)) & 0x0F];
|
||||
if (col & 0x01) bptr++;
|
||||
}
|
||||
else { // bitDepth == 1
|
||||
c = palette[(*bptr >> (7 - (col & 0x07))) & 0x01];
|
||||
if ((col & 0x07) == 0x07) bptr++;
|
||||
}
|
||||
b = c; g = c >> 8; r = c >> 16;
|
||||
}
|
||||
if (dimming != 255) { // only dimm when needed
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xFF) >> 3);
|
||||
}
|
||||
}
|
||||
|
||||
pushImage(0, y, w, h, (uint16_t *)output_buffer);
|
||||
setSwapBytes(oldSwapBytes);
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
return(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool drawClk(const char *filename) {
|
||||
fs::File bmpFS;
|
||||
|
||||
// Open requested file on SD card
|
||||
bmpFS = WLED_FS.open(filename, "r");
|
||||
|
||||
if (!bmpFS)
|
||||
{
|
||||
Serial.print("File not found: ");
|
||||
Serial.println(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t r, g, b, dimming = 255, magic;
|
||||
int16_t row, col;
|
||||
|
||||
magic = read16(bmpFS);
|
||||
if (magic != 0x4B43) { // look for "CK" header
|
||||
Serial.print(F("File not a CLK. Magic: "));
|
||||
Serial.println(magic);
|
||||
bmpFS.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
w = read16(bmpFS);
|
||||
h = read16(bmpFS);
|
||||
x = (width() - w) / 2;
|
||||
y = (height() - h) / 2;
|
||||
|
||||
uint8_t lineBuffer[w * 2];
|
||||
|
||||
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) strip.service();
|
||||
|
||||
// 0,0 coordinates are top left
|
||||
for (row = 0; row < h; row++) {
|
||||
|
||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||
uint8_t PixM, PixL;
|
||||
|
||||
// Colors are already in 16-bit R5, G6, B5 format
|
||||
for (col = 0; col < w; col++)
|
||||
{
|
||||
if (dimming == 255 && !digitColor) { // not needed, copy directly
|
||||
output_buffer[row][col+x] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]);
|
||||
} else {
|
||||
// 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB
|
||||
PixM = lineBuffer[col*2+1];
|
||||
PixL = lineBuffer[col*2];
|
||||
// align to 8-bit value (MSB left aligned)
|
||||
r = (PixM) & 0xF8;
|
||||
g = ((PixM << 5) | (PixL >> 3)) & 0xFC;
|
||||
b = (PixL << 3) & 0xF8;
|
||||
r *= dimming; g *= dimming; b *= dimming;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
if (digitColor) { // grayscale pixel coloring
|
||||
uint8_t l = (r > g) ? ((r > b) ? r:b) : ((g > b) ? g:b);
|
||||
r = g = b = l;
|
||||
r *= digitR; g *= digitG; b *= digitB;
|
||||
r = r >> 8; g = g >> 8; b = b >> 8;
|
||||
}
|
||||
output_buffer[row][col+x] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawBuffer();
|
||||
|
||||
bmpFS.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
TFTs() : TFT_eSPI(), chip_select()
|
||||
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
||||
@@ -167,6 +299,9 @@ public:
|
||||
enum show_t { no, yes, force };
|
||||
// A digit of 0xFF means blank the screen.
|
||||
const static uint8_t blanked = 255;
|
||||
|
||||
uint8_t tubeSegment = 1;
|
||||
uint8_t digitOffset = 0;
|
||||
|
||||
void begin() {
|
||||
pinMode(TFT_ENABLE_PIN, OUTPUT);
|
||||
@@ -182,34 +317,60 @@ public:
|
||||
|
||||
void showDigit(uint8_t digit) {
|
||||
chip_select.setDigit(digit);
|
||||
uint8_t digitToDraw = digits[digit];
|
||||
if (digitToDraw < 10) digitToDraw += digitOffset;
|
||||
|
||||
if (digits[digit] == blanked) {
|
||||
fillScreen(TFT_BLACK);
|
||||
if (digitToDraw == blanked) {
|
||||
fillScreen(TFT_BLACK); return;
|
||||
}
|
||||
else {
|
||||
// Filenames are no bigger than "255.bmp\0"
|
||||
char file_name[10];
|
||||
sprintf(file_name, "/%d.bmp", digits[digit]);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
drawBmp(file_name);
|
||||
} else {
|
||||
sprintf(file_name, "/%d.bin", digits[digit]);
|
||||
drawBin(file_name);
|
||||
}
|
||||
|
||||
// if last digit was the same, skip loading from FS to buffer
|
||||
if (!digitColor && digitToDraw == bufferedDigit) drawBuffer();
|
||||
digitR = R(digitColor); digitG = G(digitColor); digitB = B(digitColor);
|
||||
|
||||
// Filenames are no bigger than "254.bmp\0"
|
||||
char file_name[10];
|
||||
// Fastest, raw RGB565
|
||||
sprintf(file_name, "/%d.bin", digitToDraw);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
if (drawBin(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fast, raw RGB565, see https://github.com/aly-fly/EleksTubeHAX on how to create this clk format
|
||||
sprintf(file_name, "/%d.clk", digitToDraw);
|
||||
if (WLED_FS.exists(file_name)) {
|
||||
if (drawClk(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
// Slow, regular RGB888 or 1,4,8 bit palette BMP
|
||||
sprintf(file_name, "/%d.bmp", digitToDraw);
|
||||
if (drawBmp(file_name)) bufferedDigit = digitToDraw;
|
||||
return;
|
||||
}
|
||||
|
||||
void setDigit(uint8_t digit, uint8_t value, show_t show=yes) {
|
||||
uint8_t old_value = digits[digit];
|
||||
digits[digit] = value;
|
||||
|
||||
|
||||
// Color in grayscale bitmaps if Segment 1 exists
|
||||
// TODO If secondary and tertiary are black, color all in primary,
|
||||
// else color first three from Seg 1 color slots and last three from Seg 2 color slots
|
||||
Segment& seg1 = strip.getSegment(tubeSegment);
|
||||
if (seg1.isActive()) {
|
||||
digitColor = strip.getPixelColor(seg1.start + digit);
|
||||
dimming = seg1.opacity;
|
||||
} else {
|
||||
digitColor = 0;
|
||||
dimming = 255;
|
||||
}
|
||||
|
||||
if (show != no && (old_value != value || show == force)) {
|
||||
showDigit(digit);
|
||||
}
|
||||
}
|
||||
uint8_t getDigit(uint8_t digit) { return digits[digit]; }
|
||||
uint8_t getDigit(uint8_t digit) {return digits[digit];}
|
||||
|
||||
void showAllDigits() { for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit); }
|
||||
void showAllDigits() {for (uint8_t digit=0; digit < NUM_DIGITS; digit++) showDigit(digit);}
|
||||
|
||||
// Making chip_select public so we don't have to proxy all methods, and the caller can just use it directly.
|
||||
ChipSelect chip_select;
|
||||
|
||||
@@ -5,16 +5,17 @@ It enables running all WLED effects on the background SK6812 lighting, while dis
|
||||
Code is largely based on https://github.com/SmittyHalibut/EleksTubeHAX by Mark Smith!
|
||||
|
||||
Supported:
|
||||
- Display with custom bitmaps or raw RGB565 images (.bin) from filesystem
|
||||
- Display with custom bitmaps (.bmp) or raw RGB565 images (.bin) from filesystem
|
||||
- Background lighting
|
||||
- Power button
|
||||
- All 4 hardware buttons
|
||||
- RTC (with RTC usermod)
|
||||
- Standard WLED time features (NTP, DST, timezones)
|
||||
|
||||
Not supported:
|
||||
- 3 navigation buttons, on-device setup
|
||||
- On-device setup with buttons (WiFi setup only)
|
||||
|
||||
Your images must be exactly 135 pixels wide and 1-240 pixels high.
|
||||
Your images must be 1-135 pixels wide and 1-240 pixels high.
|
||||
BMP 1, 4, 8, and 24 bits per pixel formats are supported.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -25,7 +26,20 @@ Use LED pin 12, relay pin 27 and button pin 34.
|
||||
|
||||
## Use of RGB565 images
|
||||
|
||||
Binary 16-bit per pixel RGB565 format `.bin` images are now supported. This has the benefit of only using 2/3rds of the file size a `.bmp` has.
|
||||
The drawback is that this format cannot be handled by common image programs and that an extra conversion step is needed.
|
||||
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`)
|
||||
Thank you to @RedNax67 for adding .bin support.
|
||||
Binary 16-bit per pixel RGB565 format `.bin` and `.clk` images are now supported. This has the benefit of using only 2/3rds of the file space a 24 BPP `.bmp` occupies.
|
||||
The drawback is this format cannot be handled by common image programs and an extra conversion step is needed.
|
||||
You can use https://lvgl.io/tools/imageconverter to convert your .bmp to a .bin file (settings `True color` and `Binary RGB565`).
|
||||
Thank you to @RedNax67 for adding .bin and .clk support.
|
||||
For most clockface designs, using 4 or 8 BPP BMP format will reduce file size even more:
|
||||
|
||||
| Bits per pixel | File size in kB (for 135x240 img) | % of 24 BPP BMP | Max unique colors
|
||||
| --- | --- | --- | --- |
|
||||
24 | 98 | 100% | 16M (66K)
|
||||
16 (.clk) | 64.8 | 66% | 66K
|
||||
8 | 33.7 | 34% | 256
|
||||
4 | 16.4 | 17% | 16
|
||||
1 | 4.9 | 5% | 2
|
||||
|
||||
Comparison 1 vs. 4 vs. 8 vs. 24 BPP. With this clockface on the actual clock, 4 bit looks good, and 8 bit is almost indistinguishable from 24 bit.
|
||||
|
||||

|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
|
||||
class ElekstubeIPSUsermod : public Usermod {
|
||||
private:
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _tubeSeg[];
|
||||
static const char _digitOffset[];
|
||||
|
||||
char cronixieDisplay[7] = "HHMMSS";
|
||||
|
||||
TFTs tfts;
|
||||
void updateClockDisplay(TFTs::show_t show=TFTs::yes) {
|
||||
bool set[6] = {false};
|
||||
@@ -21,6 +28,8 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
set[i] = false; //display HHMMSS time
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t hrTens = hr/10;
|
||||
uint8_t mi = minute(localTime);
|
||||
@@ -37,6 +46,10 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
unsigned long lastTime = 0;
|
||||
public:
|
||||
|
||||
uint8_t lastBri;
|
||||
uint32_t lastCols[6];
|
||||
TFTs::show_t fshow=TFTs::yes;
|
||||
|
||||
void setup() {
|
||||
tfts.begin();
|
||||
tfts.fillScreen(TFT_BLACK);
|
||||
@@ -47,14 +60,99 @@ class ElekstubeIPSUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (toki.isTick()) {
|
||||
updateLocalTime();
|
||||
updateClockDisplay();
|
||||
if (!toki.isTick()) return;
|
||||
updateLocalTime();
|
||||
|
||||
Segment& seg1 = strip.getSegment(tfts.tubeSegment);
|
||||
if (seg1.isActive()) {
|
||||
bool update = false;
|
||||
if (seg1.opacity != lastBri) update = true;
|
||||
lastBri = seg1.opacity;
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
uint32_t c = strip.getPixelColor(seg1.start + i);
|
||||
if (c != lastCols[i]) update = true;
|
||||
lastCols[i] = c;
|
||||
}
|
||||
if (update) fshow=TFTs::force;
|
||||
} else if (lastCols[0] != 0) { // Segment 1 deleted
|
||||
fshow=TFTs::force;
|
||||
lastCols[0] = 0;
|
||||
}
|
||||
|
||||
updateClockDisplay(fshow);
|
||||
fshow=TFTs::yes;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
||||
*/
|
||||
void addToConfig(JsonObject &root) {
|
||||
// we add JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
top[FPSTR(_tubeSeg)] = tfts.tubeSegment;
|
||||
top[FPSTR(_digitOffset)] = tfts.digitOffset;
|
||||
DEBUG_PRINTLN(F("EleksTube config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root) {
|
||||
// we look for JSON object: {"EleksTubeIPS": {"tubeSegment": 1, "digitOffset": 0}}
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
tfts.tubeSegment = top[FPSTR(_tubeSeg)] | tfts.tubeSegment;
|
||||
uint8_t digitOffsetPrev = tfts.digitOffset;
|
||||
tfts.digitOffset = top[FPSTR(_digitOffset)] | tfts.digitOffset;
|
||||
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
|
||||
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
|
||||
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_digitOffset)].isNull();
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
root["nx"] = cronixieDisplay;
|
||||
root[FPSTR(_digitOffset)] = tfts.digitOffset;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
if (root["nx"].is<const char*>()) {
|
||||
strncpy(cronixieDisplay, root["nx"], 6);
|
||||
}
|
||||
|
||||
uint8_t digitOffsetPrev = tfts.digitOffset;
|
||||
tfts.digitOffset = root[FPSTR(_digitOffset)] | tfts.digitOffset;
|
||||
if (tfts.digitOffset > 240) tfts.digitOffset = 240;
|
||||
if (tfts.digitOffset != digitOffsetPrev) fshow=TFTs::force;
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_ELEKSTUBE_IPS;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char ElekstubeIPSUsermod::_name[] PROGMEM = "EleksTubeIPS";
|
||||
const char ElekstubeIPSUsermod::_tubeSeg[] PROGMEM = "tubeSegment";
|
||||
const char ElekstubeIPSUsermod::_digitOffset[] PROGMEM = "digitOffset";
|
||||
|
||||
@@ -10,7 +10,7 @@ For BME280 sensor use usermod_bme280.cpp. Copy to wled00 and rename to usermod.c
|
||||
## Features
|
||||
- SSD1306 128x32 and 128x64 I2C OLED display
|
||||
- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect)
|
||||
- Auto display shutoff for saving display lifetime
|
||||
- Auto display shutoff for extending display lifetime
|
||||
- Dallas temperature sensor
|
||||
- Reporting temperature to MQTT broker
|
||||
|
||||
@@ -39,15 +39,15 @@ default_envs = esp07
|
||||
...
|
||||
lib_deps_external =
|
||||
...
|
||||
#For use SSD1306 OLED display uncomment following
|
||||
#To use the SSD1306 OLED display, uncomment following
|
||||
U8g2@~2.27.3
|
||||
#For Dallas sensor uncomment following 2 lines
|
||||
#For Dallas sensor, uncomment the following 2 lines
|
||||
DallasTemperature@~3.8.0
|
||||
OneWire@~2.3.5
|
||||
...
|
||||
```
|
||||
|
||||
For BME280 sensor uncomment `U8g2@~2.27.3`,`BME280@~3.0.0 under` `[common]` section in `platformio.ini`:
|
||||
For BME280 sensor, uncomment `U8g2@~2.27.3`,`BME280@~3.0.0 under` `[common]` section in `platformio.ini`:
|
||||
```ini
|
||||
# platformio.ini
|
||||
...
|
||||
@@ -60,7 +60,7 @@ default_envs = esp07
|
||||
...
|
||||
lib_deps_external =
|
||||
...
|
||||
#For use SSD1306 OLED display uncomment following
|
||||
#To use the SSD1306 OLED display, uncomment following
|
||||
U8g2@~2.27.3
|
||||
#For BME280 sensor uncomment following
|
||||
BME280@~3.0.0
|
||||
|
||||
@@ -148,58 +148,14 @@ void userLoop() {
|
||||
|
||||
// Third row with mode name
|
||||
u8x8.setCursor(2, 2);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
char lineBuffer[17];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
u8x8.setCursor(2, 3);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||
|
||||
@@ -191,58 +191,14 @@ void userLoop() {
|
||||
|
||||
// Third row with mode name
|
||||
u8x8.setCursor(2, 2);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
char lineBuffer[17];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
u8x8.setCursor(2, 3);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
**Attention: This usermod compiles only for ESP8266**
|
||||
|
||||
This usermod-v2 modification performs a ping request to the local IP address every 60 seconds. By this procedure the net services of WLED remains accessible in some problematic WLAN environments.
|
||||
This usermod-v2 modification performs a ping request to a local IP address every 60 seconds. This ensures WLED net services remain accessible in some problematic WLAN environments.
|
||||
|
||||
The modification works with static or DHCP IP address configuration.
|
||||
|
||||
_Story:_
|
||||
|
||||
Unfortunately, with all ESP projects where a web server or other network services are running, I have the problem that after some time the web server is no longer accessible. Now I found out that the connection is at least reestablished when a ping request is executed by the device.
|
||||
Unfortunately, with many ESP projects where a web server or other network services are running, after some time, the connecton to the web server is lost.
|
||||
The connection can be reestablished with a ping request from the device.
|
||||
|
||||
With this modification, in the worst case, the network functions are not available for 60 seconds until the next ping request.
|
||||
With this modification, in the worst case, the network functions are not available until the next ping request. (60 seconds)
|
||||
|
||||
## Webinterface
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The JSON IR remote allows users to customize IR remote behavior without writing custom code and compiling.
|
||||
It also enables using any remote that is compatible with your IR receiver. Using the JSON IR remote, you can
|
||||
The JSON IR remote enables users to customize IR remote behavior without writing custom code and compiling.
|
||||
It also allows using any remote compatible with your IR receiver. Using the JSON IR remote, you can
|
||||
map buttons from any remote to any HTTP request API or JSON API command.
|
||||
|
||||
## Usage
|
||||
|
||||
321
usermods/MY9291/MY92xx.h
Normal file
321
usermods/MY9291/MY92xx.h
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
|
||||
MY92XX LED Driver for Arduino
|
||||
Based on the C driver by MaiKe Labs
|
||||
|
||||
Copyright (c) 2016 - 2026 MaiKe Labs
|
||||
Copyright (C) 2017 - 2018 Xose Pérez for the Arduino compatible library
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _my92xx_h
|
||||
#define _my92xx_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef DEBUG_MY92XX
|
||||
#if ARDUINO_ARCH_ESP8266
|
||||
#define DEBUG_MSG_MY92XX(...) DEBUG_MY92XX.printf( __VA_ARGS__ )
|
||||
#elif ARDUINO_ARCH_AVR
|
||||
#define DEBUG_MSG_MY92XX(...) { char buffer[80]; snprintf(buffer, sizeof(buffer), __VA_ARGS__ ); DEBUG_MY92XX.print(buffer); }
|
||||
#endif
|
||||
#else
|
||||
#define DEBUG_MSG_MY92XX(...)
|
||||
#endif
|
||||
|
||||
typedef enum my92xx_model_t {
|
||||
MY92XX_MODEL_MY9291 = 0X00,
|
||||
MY92XX_MODEL_MY9231 = 0X01,
|
||||
} my92xx_model_t;
|
||||
|
||||
typedef enum my92xx_cmd_one_shot_t {
|
||||
MY92XX_CMD_ONE_SHOT_DISABLE = 0X00,
|
||||
MY92XX_CMD_ONE_SHOT_ENFORCE = 0X01,
|
||||
} my92xx_cmd_one_shot_t;
|
||||
|
||||
typedef enum my92xx_cmd_reaction_t {
|
||||
MY92XX_CMD_REACTION_FAST = 0X00,
|
||||
MY92XX_CMD_REACTION_SLOW = 0X01,
|
||||
} my92xx_cmd_reaction_t;
|
||||
|
||||
typedef enum my92xx_cmd_bit_width_t {
|
||||
MY92XX_CMD_BIT_WIDTH_16 = 0X00,
|
||||
MY92XX_CMD_BIT_WIDTH_14 = 0X01,
|
||||
MY92XX_CMD_BIT_WIDTH_12 = 0X02,
|
||||
MY92XX_CMD_BIT_WIDTH_8 = 0X03,
|
||||
} my92xx_cmd_bit_width_t;
|
||||
|
||||
typedef enum my92xx_cmd_frequency_t {
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_1 = 0X00,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_4 = 0X01,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_16 = 0X02,
|
||||
MY92XX_CMD_FREQUENCY_DIVIDE_64 = 0X03,
|
||||
} my92xx_cmd_frequency_t;
|
||||
|
||||
typedef enum my92xx_cmd_scatter_t {
|
||||
MY92XX_CMD_SCATTER_APDM = 0X00,
|
||||
MY92XX_CMD_SCATTER_PWM = 0X01,
|
||||
} my92xx_cmd_scatter_t;
|
||||
|
||||
typedef struct {
|
||||
my92xx_cmd_scatter_t scatter : 1;
|
||||
my92xx_cmd_frequency_t frequency : 2;
|
||||
my92xx_cmd_bit_width_t bit_width : 2;
|
||||
my92xx_cmd_reaction_t reaction : 1;
|
||||
my92xx_cmd_one_shot_t one_shot : 1;
|
||||
unsigned char resv : 1;
|
||||
} __attribute__((aligned(1), packed)) my92xx_cmd_t;
|
||||
|
||||
#define MY92XX_COMMAND_DEFAULT { \
|
||||
.scatter = MY92XX_CMD_SCATTER_APDM, \
|
||||
.frequency = MY92XX_CMD_FREQUENCY_DIVIDE_1, \
|
||||
.bit_width = MY92XX_CMD_BIT_WIDTH_8, \
|
||||
.reaction = MY92XX_CMD_REACTION_FAST, \
|
||||
.one_shot = MY92XX_CMD_ONE_SHOT_DISABLE, \
|
||||
.resv = 0 \
|
||||
}
|
||||
|
||||
class my92xx {
|
||||
|
||||
public:
|
||||
|
||||
my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command);
|
||||
unsigned char getChannels();
|
||||
void setChannel(unsigned char channel, unsigned int value);
|
||||
unsigned int getChannel(unsigned char channel);
|
||||
void setState(bool state);
|
||||
bool getState();
|
||||
void update();
|
||||
|
||||
private:
|
||||
|
||||
void _di_pulse(unsigned int times);
|
||||
void _dcki_pulse(unsigned int times);
|
||||
void _set_cmd(my92xx_cmd_t command);
|
||||
void _send();
|
||||
void _write(unsigned int data, unsigned char bit_length);
|
||||
|
||||
my92xx_cmd_t _command;
|
||||
my92xx_model_t _model = MY92XX_MODEL_MY9291;
|
||||
unsigned char _chips = 1;
|
||||
unsigned char _channels;
|
||||
uint16_t* _value;
|
||||
bool _state = false;
|
||||
unsigned char _pin_di;
|
||||
unsigned char _pin_dcki;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#if ARDUINO_ARCH_ESP8266
|
||||
|
||||
extern "C" {
|
||||
void os_delay_us(unsigned int);
|
||||
}
|
||||
|
||||
#elif ARDUINO_ARCH_AVR
|
||||
|
||||
#define os_delay_us delayMicroseconds
|
||||
|
||||
#endif
|
||||
|
||||
void my92xx::_di_pulse(unsigned int times) {
|
||||
for (unsigned int i = 0; i < times; i++) {
|
||||
digitalWrite(_pin_di, HIGH);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void my92xx::_dcki_pulse(unsigned int times) {
|
||||
for (unsigned int i = 0; i < times; i++) {
|
||||
digitalWrite(_pin_dcki, HIGH);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void my92xx::_write(unsigned int data, unsigned char bit_length) {
|
||||
|
||||
unsigned int mask = (0x01 << (bit_length - 1));
|
||||
|
||||
for (unsigned int i = 0; i < bit_length / 2; i++) {
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
|
||||
digitalWrite(_pin_dcki, HIGH);
|
||||
data = data << 1;
|
||||
digitalWrite(_pin_di, (data & mask) ? HIGH : LOW);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
data = data << 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void my92xx::_set_cmd(my92xx_cmd_t command) {
|
||||
|
||||
// ets_intr_lock();
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
|
||||
// pulse's rising edge convert to command mode.
|
||||
_di_pulse(12);
|
||||
|
||||
// Delay >12us, begin send CMD data
|
||||
os_delay_us(12);
|
||||
|
||||
// Send CMD data
|
||||
unsigned char command_data = *(unsigned char*)(&command);
|
||||
for (unsigned char i = 0; i < _chips; i++) {
|
||||
_write(command_data, 8);
|
||||
}
|
||||
|
||||
// TStart > 12us. Delay 12 us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 16 DI pulse,at 14 pulse's falling edge store CMD data, and
|
||||
// at 16 pulse's falling edge convert to duty mode.
|
||||
_di_pulse(16);
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// ets_intr_unlock();
|
||||
|
||||
}
|
||||
|
||||
void my92xx::_send() {
|
||||
|
||||
#ifdef DEBUG_MY92XX
|
||||
DEBUG_MSG_MY92XX("[MY92XX] Refresh: %s (", _state ? "ON" : "OFF");
|
||||
for (unsigned char channel = 0; channel < _channels; channel++) {
|
||||
DEBUG_MSG_MY92XX(" %d", _value[channel]);
|
||||
}
|
||||
DEBUG_MSG_MY92XX(" )\n");
|
||||
#endif
|
||||
|
||||
unsigned char bit_length = 8;
|
||||
switch (_command.bit_width) {
|
||||
case MY92XX_CMD_BIT_WIDTH_16:
|
||||
bit_length = 16;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_14:
|
||||
bit_length = 14;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_12:
|
||||
bit_length = 12;
|
||||
break;
|
||||
case MY92XX_CMD_BIT_WIDTH_8:
|
||||
bit_length = 8;
|
||||
break;
|
||||
default:
|
||||
bit_length = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
// ets_intr_lock();
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send color data
|
||||
for (unsigned char channel = 0; channel < _channels; channel++) {
|
||||
_write(_state ? _value[channel] : 0, bit_length);
|
||||
}
|
||||
|
||||
// TStart > 12us. Ready for send DI pulse.
|
||||
os_delay_us(12);
|
||||
|
||||
// Send 8 DI pulse. After 8 pulse falling edge, store old data.
|
||||
_di_pulse(8);
|
||||
|
||||
// TStop > 12us.
|
||||
os_delay_us(12);
|
||||
|
||||
// ets_intr_unlock();
|
||||
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
unsigned char my92xx::getChannels() {
|
||||
return _channels;
|
||||
}
|
||||
|
||||
void my92xx::setChannel(unsigned char channel, unsigned int value) {
|
||||
if (channel < _channels) {
|
||||
_value[channel] = value;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int my92xx::getChannel(unsigned char channel) {
|
||||
if (channel < _channels) {
|
||||
return _value[channel];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool my92xx::getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
void my92xx::setState(bool state) {
|
||||
_state = state;
|
||||
}
|
||||
|
||||
void my92xx::update() {
|
||||
_send();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
my92xx::my92xx(my92xx_model_t model, unsigned char chips, unsigned char di, unsigned char dcki, my92xx_cmd_t command) : _command(command) {
|
||||
|
||||
_model = model;
|
||||
_chips = chips;
|
||||
_pin_di = di;
|
||||
_pin_dcki = dcki;
|
||||
|
||||
// Init channels
|
||||
if (_model == MY92XX_MODEL_MY9291) {
|
||||
_channels = 4 * _chips;
|
||||
}
|
||||
else if (_model == MY92XX_MODEL_MY9231) {
|
||||
_channels = 3 * _chips;
|
||||
}
|
||||
_value = new uint16_t[_channels];
|
||||
for (unsigned char i = 0; i < _channels; i++) {
|
||||
_value[i] = 0;
|
||||
}
|
||||
|
||||
// Init GPIO
|
||||
pinMode(_pin_di, OUTPUT);
|
||||
pinMode(_pin_dcki, OUTPUT);
|
||||
digitalWrite(_pin_di, LOW);
|
||||
digitalWrite(_pin_dcki, LOW);
|
||||
|
||||
// Clear all duty register
|
||||
_dcki_pulse(32 * _chips);
|
||||
|
||||
// Send init command
|
||||
_set_cmd(command);
|
||||
|
||||
DEBUG_MSG_MY92XX("[MY92XX] Initialized\n");
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
45
usermods/MY9291/usermode_MY9291.h
Normal file
45
usermods/MY9291/usermode_MY9291.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include "MY92xx.h"
|
||||
|
||||
#define MY92XX_MODEL MY92XX_MODEL_MY9291
|
||||
#define MY92XX_CHIPS 1
|
||||
#define MY92XX_DI_PIN 13
|
||||
#define MY92XX_DCKI_PIN 15
|
||||
|
||||
#define MY92XX_RED 0
|
||||
#define MY92XX_GREEN 1
|
||||
#define MY92XX_BLUE 2
|
||||
#define MY92XX_WHITE 3
|
||||
|
||||
class MY9291Usermod : public Usermod {
|
||||
private:
|
||||
my92xx _my92xx = my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND_DEFAULT);
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
_my92xx.setState(true);
|
||||
}
|
||||
|
||||
void connected() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t c = strip.getPixelColor(0);
|
||||
int w = ((c >> 24) & 0xff) * bri / 255.0;
|
||||
int r = ((c >> 16) & 0xff) * bri / 255.0;
|
||||
int g = ((c >> 8) & 0xff) * bri / 255.0;
|
||||
int b = (c & 0xff) * bri / 255.0;
|
||||
_my92xx.setChannel(MY92XX_RED, r);
|
||||
_my92xx.setChannel(MY92XX_GREEN, g);
|
||||
_my92xx.setChannel(MY92XX_BLUE, b);
|
||||
_my92xx.setChannel(MY92XX_WHITE, w);
|
||||
_my92xx.update();
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_MY9291;
|
||||
}
|
||||
};
|
||||
@@ -7,71 +7,35 @@ _Story:_
|
||||
I use the PIR Sensor to automatically turn on the WLED analog clock in my home office room when I am there.
|
||||
The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wiki/Control-a-relay-with-WLED) to keep the power consumption low when it is switched off.
|
||||
|
||||
## Webinterface
|
||||
## Web interface
|
||||
|
||||
The info page in the web interface shows the remaining time of the off timer. Usermod can also be temporarily disbled/enabled from the info page by clicking PIR button.
|
||||
|
||||
## Sensor connection
|
||||
|
||||
My setup uses an HC-SR501 or HC-SR602 sensor, a HC-SR505 should also work.
|
||||
My setup uses an HC-SR501 or HC-SR602 sensor, an HC-SR505 should also work.
|
||||
|
||||
The usermod uses GPIO13 (D1 mini pin D7) by default for the sensor signal but can be changed in the Usermod settings page.
|
||||
The usermod uses GPIO13 (D1 mini pin D7) by default for the sensor signal, but can be changed in the Usermod settings page.
|
||||
[This example page](http://www.esp8266learning.com/wemos-mini-pir-sensor-example.php) describes how to connect the sensor.
|
||||
|
||||
Use the potentiometers on the sensor to set the time-delay to the minimum and the sensitivity to about half, or slightly above.
|
||||
Use the potentiometers on the sensor to set the time delay to the minimum and the sensitivity to about half, or slightly above.
|
||||
You can also use usermod's off timer instead of sensor's. In such case rotate the potentiometer to its shortest time possible (or use SR602 which lacks such potentiometer).
|
||||
|
||||
## Usermod installation
|
||||
|
||||
1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory.
|
||||
2. Register the usermod by adding `#include "usermod_PIR_sensor_switch.h"` in the top and `registerUsermod(new PIRsensorSwitch());` in the bottom of `usermods_list.cpp`.
|
||||
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin. You can also change the default off time by adding `-D PIR_SENSOR_OFF_SEC=30`.
|
||||
|
||||
Example **usermods_list.cpp**:
|
||||
|
||||
```cpp
|
||||
#include "wled.h"
|
||||
/*
|
||||
* Register your v2 usermods here!
|
||||
* (for v1 usermods using just usermod.cpp, you can ignore this file)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Add/uncomment your usermod filename here (and once more below)
|
||||
* || || ||
|
||||
* \/ \/ \/
|
||||
*/
|
||||
//#include "usermod_v2_example.h"
|
||||
//#include "usermod_temperature.h"
|
||||
//#include "usermod_v2_empty.h"
|
||||
#include "usermod_PIR_sensor_switch.h"
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
/*
|
||||
* Add your usermod class name here
|
||||
* || || ||
|
||||
* \/ \/ \/
|
||||
*/
|
||||
//usermods.add(new MyExampleUsermod());
|
||||
//usermods.add(new UsermodTemperature());
|
||||
//usermods.add(new UsermodRenameMe());
|
||||
usermods.add(new PIRsensorSwitch());
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionaly `-D PIR_SENSOR_PIN=16` to override default pin.
|
||||
|
||||
## API to enable/disable the PIR sensor from outside. For example from another usermod.
|
||||
## API to enable/disable the PIR sensor from outside. For example from another usermod:
|
||||
|
||||
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
||||
|
||||
When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`.
|
||||
Usermod can also be configured to just send MQTT message and not change WLED state using settings page as well as responding to motion only during nighttime (assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
|
||||
Usermod can also be configured to send just the MQTT message but not change WLED state using settings page as well as responding to motion only at night
|
||||
(assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
|
||||
|
||||
### There are two options to get access to the usermod instance:
|
||||
|
||||
1. Include `usermod_PIR_sensor_switch.h` **before** you include the other usermod in `usermods_list.cpp'
|
||||
1. Include `usermod_PIR_sensor_switch.h` **before** you include other usermods in `usermods_list.cpp'
|
||||
|
||||
or
|
||||
|
||||
@@ -100,7 +64,7 @@ class MyUsermod : public Usermod {
|
||||
|
||||
### Configuration options
|
||||
|
||||
Usermod can be configured in Usermods settings page.
|
||||
Usermod can be configured via the Usermods settings page.
|
||||
|
||||
* `PIRenabled` - enable/disable usermod
|
||||
* `pin` - dynamically change GPIO pin where PIR sensor is attached to ESP
|
||||
@@ -108,8 +72,8 @@ Usermod can be configured in Usermods settings page.
|
||||
* `on-preset` - preset triggered when PIR activates (if this is 0 it will just turn WLED on)
|
||||
* `off-preset` - preset triggered when PIR deactivates (if this is 0 it will just turn WLED off)
|
||||
* `nighttime-only` - enable triggering only between sunset and sunrise (you will need to set up _NTP_, _Lat_ & _Lon_ in Time & Macro settings)
|
||||
* `mqtt-only` - only send MQTT messages, do not interact with WLED
|
||||
* `off-only` - only trigger presets or turn WLED on/off in WLED is not already on (displaying effect)
|
||||
* `mqtt-only` - send only MQTT messages, do not interact with WLED
|
||||
* `off-only` - only trigger presets or turn WLED on/off if WLED is not already on (displaying effect)
|
||||
* `notifications` - enable or disable sending notifications to other WLED instances using Sync button
|
||||
|
||||
|
||||
@@ -121,4 +85,9 @@ Have fun - @gegu & @blazoncek
|
||||
|
||||
2021-11
|
||||
* Added information about dynamic configuration options
|
||||
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
|
||||
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
|
||||
|
||||
2022-11
|
||||
* Added compile time option for off timer.
|
||||
* Added Home Assistant autodiscovery MQTT broadcast.
|
||||
* Updated info on compiling.
|
||||
|
||||
@@ -1,452 +1,505 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef PIR_SENSOR_PIN
|
||||
// compatible with QuinLED-Dig-Uno
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define PIR_SENSOR_PIN 23 // Q4
|
||||
#else //ESP8266 boards
|
||||
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This usermod handles PIR sensor states.
|
||||
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
|
||||
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
|
||||
*
|
||||
*
|
||||
* Usermods allow you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
|
||||
* Multiple v2 usermods can be added to one compilation easily.
|
||||
*
|
||||
* Creating a usermod:
|
||||
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
|
||||
* Please remember to rename the class and file to a descriptive name.
|
||||
* You may also use multiple .h and .cpp files.
|
||||
*
|
||||
* Using a usermod:
|
||||
* 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
|
||||
* 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
|
||||
*/
|
||||
|
||||
class PIRsensorSwitch : public Usermod
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
PIRsensorSwitch() {}
|
||||
/**
|
||||
* desctructor
|
||||
*/
|
||||
~PIRsensorSwitch() {}
|
||||
|
||||
/**
|
||||
* Enable/Disable the PIR sensor
|
||||
*/
|
||||
void EnablePIRsensor(bool en) { enabled = en; }
|
||||
/**
|
||||
* Get PIR sensor enabled/disabled state
|
||||
*/
|
||||
bool PIRsensorEnabled() { return enabled; }
|
||||
|
||||
private:
|
||||
|
||||
byte prevPreset = 0;
|
||||
byte prevPlaylist = 0;
|
||||
bool savedState = false;
|
||||
|
||||
uint32_t offTimerStart = 0; // off timer start time
|
||||
byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
|
||||
byte sensorPinState = LOW; // current PIR sensor pin state
|
||||
bool initDone = false; // status of initialization
|
||||
bool PIRtriggered = false;
|
||||
unsigned long lastLoop = 0;
|
||||
|
||||
// configurable parameters
|
||||
bool enabled = true; // PIR sensor enabled
|
||||
int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
|
||||
uint32_t m_switchOffDelay = 600000; // delay before switch off after the sensor state goes LOW (10min)
|
||||
uint8_t m_onPreset = 0; // on preset
|
||||
uint8_t m_offPreset = 0; // off preset
|
||||
bool m_nightTimeOnly = false; // flag to indicate that PIR sensor should activate WLED during nighttime only
|
||||
bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled)
|
||||
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
|
||||
bool m_offOnly = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _switchOffDelay[];
|
||||
static const char _enabled[];
|
||||
static const char _onPreset[];
|
||||
static const char _offPreset[];
|
||||
static const char _nightTime[];
|
||||
static const char _mqttOnly[];
|
||||
static const char _offOnly[];
|
||||
static const char _notify[];
|
||||
|
||||
/**
|
||||
* check if it is daytime
|
||||
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||
*/
|
||||
bool isDayTime() {
|
||||
updateLocalTime();
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t mi = minute(localTime);
|
||||
|
||||
if (sunrise && sunset) {
|
||||
if (hour(sunrise)<hr && hour(sunset)>hr) {
|
||||
return true;
|
||||
} else {
|
||||
if (hour(sunrise)==hr && minute(sunrise)<mi) {
|
||||
return true;
|
||||
}
|
||||
if (hour(sunset)==hr && minute(sunset)>mi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch strip on/off
|
||||
*/
|
||||
void switchStrip(bool switchOn)
|
||||
{
|
||||
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
|
||||
PIRtriggered = switchOn;
|
||||
if (switchOn) {
|
||||
if (m_onPreset) {
|
||||
if (currentPlaylist>0) prevPlaylist = currentPlaylist;
|
||||
else if (currentPreset>0) prevPreset = currentPreset;
|
||||
else {
|
||||
saveTemporaryPreset();
|
||||
savedState = true;
|
||||
prevPlaylist = 0;
|
||||
prevPreset = 0;
|
||||
}
|
||||
applyPreset(m_onPreset, NotifyUpdateMode);
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri == 0) {
|
||||
bri = briLast;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
} else {
|
||||
if (m_offPreset) {
|
||||
applyPreset(m_offPreset, NotifyUpdateMode);
|
||||
return;
|
||||
} else if (prevPlaylist) {
|
||||
applyPreset(prevPlaylist, NotifyUpdateMode);
|
||||
prevPlaylist = 0;
|
||||
return;
|
||||
} else if (prevPreset) {
|
||||
applyPreset(prevPreset, NotifyUpdateMode);
|
||||
prevPreset = 0;
|
||||
return;
|
||||
} else if (savedState) {
|
||||
applyTemporaryPreset();
|
||||
savedState = false;
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri != 0) {
|
||||
briLast = bri;
|
||||
bri = 0;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void publishMqtt(const char* state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED){
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/motion"));
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and update PIR sensor state.
|
||||
* Initilize/reset switch off timer
|
||||
*/
|
||||
bool updatePIRsensorState()
|
||||
{
|
||||
bool pinState = digitalRead(PIRsensorPin);
|
||||
if (pinState != sensorPinState) {
|
||||
sensorPinState = pinState; // change previous state
|
||||
|
||||
if (sensorPinState == HIGH) {
|
||||
offTimerStart = 0;
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||
publishMqtt("on");
|
||||
} else /*if (bri != 0)*/ {
|
||||
// start switch off timer
|
||||
offTimerStart = millis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch off the strip if the delay has elapsed
|
||||
*/
|
||||
bool handleOffTimer()
|
||||
{
|
||||
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay)
|
||||
{
|
||||
if (enabled == true)
|
||||
{
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
|
||||
publishMqtt("off");
|
||||
}
|
||||
offTimerStart = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/**
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
// PIR Sensor mode INPUT_PULLUP
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
} else {
|
||||
if (PIRsensorPin >= 0) {
|
||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||
}
|
||||
PIRsensorPin = -1; // allocation failed
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
// only check sensors 4x/s
|
||||
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
|
||||
lastLoop = millis();
|
||||
|
||||
if (!updatePIRsensorState()) {
|
||||
handleOffTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
*
|
||||
* Add PIR sensor state and switch off timer duration to jsoninfo
|
||||
*/
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
String uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
if (enabled) {
|
||||
uiDomString += F(":false}});\">");
|
||||
uiDomString += F("PIR <i class=\"icons\"></i>");
|
||||
} else {
|
||||
uiDomString += F(":true}});\">");
|
||||
uiDomString += F("PIR <i class=\"icons\"></i>");
|
||||
}
|
||||
uiDomString += F("</button>");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
|
||||
if (enabled) {
|
||||
if (offTimerStart > 0)
|
||||
{
|
||||
uiDomString = "";
|
||||
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
|
||||
if (offSeconds >= 3600)
|
||||
{
|
||||
uiDomString += (offSeconds / 3600);
|
||||
uiDomString += F("h ");
|
||||
offSeconds %= 3600;
|
||||
}
|
||||
if (offSeconds >= 60)
|
||||
{
|
||||
uiDomString += (offSeconds / 60);
|
||||
offSeconds %= 60;
|
||||
}
|
||||
else if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += 0;
|
||||
}
|
||||
if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += F("min ");
|
||||
}
|
||||
uiDomString += (offSeconds);
|
||||
infoArr.add(uiDomString + F("s"));
|
||||
} else {
|
||||
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
||||
}
|
||||
} else {
|
||||
infoArr.add(F("disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod[FPSTR(_enabled)].is<bool>()) {
|
||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* provide the changeable values
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||
top["pin"] = PIRsensorPin;
|
||||
top[FPSTR(_onPreset)] = m_onPreset;
|
||||
top[FPSTR(_offPreset)] = m_offPreset;
|
||||
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||
top[FPSTR(_offOnly)] = m_offOnly;
|
||||
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
|
||||
DEBUG_PRINTLN(F("PIR config saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the changeable values
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
bool oldEnabled = enabled;
|
||||
int8_t oldPin = PIRsensorPin;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
PIRsensorPin = top["pin"] | PIRsensorPin;
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
|
||||
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
|
||||
|
||||
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
|
||||
m_onPreset = max(0,min(250,(int)m_onPreset));
|
||||
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
|
||||
m_offPreset = max(0,min(250,(int)m_offPreset));
|
||||
|
||||
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
||||
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
||||
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
||||
|
||||
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
|
||||
|
||||
if (!initDone) {
|
||||
// reading config prior to setup()
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
||||
// check if pin is OK
|
||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||
// if we are changing pin in settings page
|
||||
// deallocate old pin
|
||||
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
|
||||
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
} else {
|
||||
// allocation failed
|
||||
PIRsensorPin = -1;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_notify)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_PIRSWITCH;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
|
||||
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
|
||||
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
|
||||
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
|
||||
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
|
||||
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef PIR_SENSOR_PIN
|
||||
// compatible with QuinLED-Dig-Uno
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define PIR_SENSOR_PIN 23 // Q4
|
||||
#else //ESP8266 boards
|
||||
#define PIR_SENSOR_PIN 13 // Q4 (D7 on D1 mini)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef PIR_SENSOR_OFF_SEC
|
||||
#define PIR_SENSOR_OFF_SEC 600
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This usermod handles PIR sensor states.
|
||||
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
|
||||
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
|
||||
*
|
||||
*
|
||||
* Usermods allow you to add own functionality to WLED more easily
|
||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
||||
*
|
||||
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
|
||||
* Multiple v2 usermods can be added to one compilation easily.
|
||||
*/
|
||||
|
||||
class PIRsensorSwitch : public Usermod
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
PIRsensorSwitch() {}
|
||||
// destructor
|
||||
~PIRsensorSwitch() {}
|
||||
|
||||
//Enable/Disable the PIR sensor
|
||||
void EnablePIRsensor(bool en) { enabled = en; }
|
||||
|
||||
// Get PIR sensor enabled/disabled state
|
||||
bool PIRsensorEnabled() { return enabled; }
|
||||
|
||||
private:
|
||||
|
||||
byte prevPreset = 0;
|
||||
byte prevPlaylist = 0;
|
||||
|
||||
uint32_t offTimerStart = 0; // off timer start time
|
||||
byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE
|
||||
byte sensorPinState = LOW; // current PIR sensor pin state
|
||||
bool initDone = false; // status of initialization
|
||||
bool PIRtriggered = false;
|
||||
unsigned long lastLoop = 0;
|
||||
|
||||
// configurable parameters
|
||||
bool enabled = true; // PIR sensor enabled
|
||||
int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
|
||||
uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min)
|
||||
uint8_t m_onPreset = 0; // on preset
|
||||
uint8_t m_offPreset = 0; // off preset
|
||||
bool m_nightTimeOnly = false; // flag to indicate that PIR sensor should activate WLED during nighttime only
|
||||
bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled)
|
||||
// flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR)
|
||||
bool m_offOnly = false;
|
||||
bool m_offMode = offMode;
|
||||
|
||||
// Home Assistant
|
||||
bool HomeAssistantDiscovery = false; // is HA discovery turned on
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _switchOffDelay[];
|
||||
static const char _enabled[];
|
||||
static const char _onPreset[];
|
||||
static const char _offPreset[];
|
||||
static const char _nightTime[];
|
||||
static const char _mqttOnly[];
|
||||
static const char _offOnly[];
|
||||
static const char _haDiscovery[];
|
||||
static const char _notify[];
|
||||
|
||||
/**
|
||||
* check if it is daytime
|
||||
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||
*/
|
||||
bool isDayTime() {
|
||||
updateLocalTime();
|
||||
uint8_t hr = hour(localTime);
|
||||
uint8_t mi = minute(localTime);
|
||||
|
||||
if (sunrise && sunset) {
|
||||
if (hour(sunrise)<hr && hour(sunset)>hr) {
|
||||
return true;
|
||||
} else {
|
||||
if (hour(sunrise)==hr && minute(sunrise)<mi) {
|
||||
return true;
|
||||
}
|
||||
if (hour(sunset)==hr && minute(sunset)>mi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch strip on/off
|
||||
*/
|
||||
void switchStrip(bool switchOn)
|
||||
{
|
||||
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing
|
||||
if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing
|
||||
PIRtriggered = switchOn;
|
||||
if (switchOn) {
|
||||
if (m_onPreset) {
|
||||
if (currentPlaylist>0 && !offMode) {
|
||||
prevPlaylist = currentPlaylist;
|
||||
unloadPlaylist();
|
||||
} else if (currentPreset>0 && !offMode) {
|
||||
prevPreset = currentPreset;
|
||||
} else {
|
||||
saveTemporaryPreset();
|
||||
prevPlaylist = 0;
|
||||
prevPreset = 255;
|
||||
}
|
||||
applyPreset(m_onPreset, NotifyUpdateMode);
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri == 0) {
|
||||
bri = briLast;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
} else {
|
||||
if (m_offPreset) {
|
||||
if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(m_offPreset, NotifyUpdateMode);
|
||||
return;
|
||||
} else if (prevPlaylist) {
|
||||
if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode);
|
||||
prevPlaylist = 0;
|
||||
return;
|
||||
} else if (prevPreset) {
|
||||
if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); }
|
||||
else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); }
|
||||
prevPreset = 0;
|
||||
return;
|
||||
}
|
||||
// preset not assigned
|
||||
if (bri != 0) {
|
||||
briLast = bri;
|
||||
bri = 0;
|
||||
stateUpdated(NotifyUpdateMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void publishMqtt(const char* state)
|
||||
{
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
char subuf[64];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/motion"));
|
||||
mqtt->publish(subuf, 0, false, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||
void publishHomeAssistantAutodiscovery()
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
StaticJsonDocument<600> doc;
|
||||
char uid[24], json_str[1024], buf[128];
|
||||
|
||||
sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40
|
||||
doc[F("name")] = buf;
|
||||
sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40
|
||||
doc[F("stat_t")] = buf;
|
||||
doc[F("pl_on")] = "on";
|
||||
doc[F("pl_off")] = "off";
|
||||
sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str());
|
||||
doc[F("uniq_id")] = uid;
|
||||
doc[F("dev_cla")] = F("motion");
|
||||
doc[F("exp_aft")] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("ids")] = String(F("wled-sensor-")) + mqttClientID;
|
||||
device[F("mf")] = "WLED";
|
||||
device[F("mdl")] = F("FOSS");
|
||||
device[F("sw")] = versionString;
|
||||
|
||||
sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid);
|
||||
DEBUG_PRINTLN(buf);
|
||||
size_t payload_size = serializeJson(doc, json_str);
|
||||
DEBUG_PRINTLN(json_str);
|
||||
|
||||
mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and update PIR sensor state.
|
||||
* Initilize/reset switch off timer
|
||||
*/
|
||||
bool updatePIRsensorState()
|
||||
{
|
||||
bool pinState = digitalRead(PIRsensorPin);
|
||||
if (pinState != sensorPinState) {
|
||||
sensorPinState = pinState; // change previous state
|
||||
|
||||
if (sensorPinState == HIGH) {
|
||||
offTimerStart = 0;
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||
else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
|
||||
publishMqtt("on");
|
||||
} else {
|
||||
// start switch off timer
|
||||
offTimerStart = millis();
|
||||
if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* switch off the strip if the delay has elapsed
|
||||
*/
|
||||
bool handleOffTimer()
|
||||
{
|
||||
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
|
||||
offTimerStart = 0;
|
||||
if (enabled == true) {
|
||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false);
|
||||
else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND);
|
||||
publishMqtt("off");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
/**
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
* You can use it to initialize variables, sensors or similar.
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
// PIR Sensor mode INPUT_PULLUP
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
} else {
|
||||
if (PIRsensorPin >= 0) {
|
||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
||||
}
|
||||
PIRsensorPin = -1; // allocation failed
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* onMqttConnect() is called when MQTT connection is established
|
||||
*/
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
if (HomeAssistantDiscovery) {
|
||||
publishHomeAssistantAutodiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
// only check sensors 4x/s
|
||||
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
|
||||
lastLoop = millis();
|
||||
|
||||
if (!updatePIRsensorState()) {
|
||||
handleOffTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
*
|
||||
* Add PIR sensor state and switch off timer duration to jsoninfo
|
||||
*/
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||
|
||||
String uiDomString;
|
||||
if (enabled) {
|
||||
if (offTimerStart > 0)
|
||||
{
|
||||
uiDomString = "";
|
||||
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
|
||||
if (offSeconds >= 3600)
|
||||
{
|
||||
uiDomString += (offSeconds / 3600);
|
||||
uiDomString += F("h ");
|
||||
offSeconds %= 3600;
|
||||
}
|
||||
if (offSeconds >= 60)
|
||||
{
|
||||
uiDomString += (offSeconds / 60);
|
||||
offSeconds %= 60;
|
||||
}
|
||||
else if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += 0;
|
||||
}
|
||||
if (uiDomString.length() > 0)
|
||||
{
|
||||
uiDomString += F("min ");
|
||||
}
|
||||
uiDomString += (offSeconds);
|
||||
infoArr.add(uiDomString + F("s"));
|
||||
} else {
|
||||
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
||||
}
|
||||
} else {
|
||||
infoArr.add(F("disabled"));
|
||||
}
|
||||
|
||||
uiDomString = F(" <button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
if (enabled) {
|
||||
uiDomString += F(":false}});\">");
|
||||
uiDomString += F("<i class=\"icons on\"></i>");
|
||||
} else {
|
||||
uiDomString += F(":true}});\">");
|
||||
uiDomString += F("<i class=\"icons off\"></i>");
|
||||
}
|
||||
uiDomString += F("</button>");
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
JsonObject sensor = root[F("sensor")];
|
||||
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
|
||||
sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
/*
|
||||
void addToJsonState(JsonObject &root)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
|
||||
void readFromJsonState(JsonObject &root)
|
||||
{
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod[FPSTR(_enabled)].is<bool>()) {
|
||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* provide the changeable values
|
||||
*/
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||
top["pin"] = PIRsensorPin;
|
||||
top[FPSTR(_onPreset)] = m_onPreset;
|
||||
top[FPSTR(_offPreset)] = m_offPreset;
|
||||
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||
top[FPSTR(_offOnly)] = m_offOnly;
|
||||
top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
|
||||
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
|
||||
DEBUG_PRINTLN(F("PIR config saved."));
|
||||
}
|
||||
|
||||
void appendConfigData()
|
||||
{
|
||||
oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field
|
||||
oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the changeable values
|
||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
||||
*
|
||||
* The function should return true if configuration was successfully loaded or false if there was no configuration.
|
||||
*/
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
bool oldEnabled = enabled;
|
||||
int8_t oldPin = PIRsensorPin;
|
||||
|
||||
DEBUG_PRINT(FPSTR(_name));
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
PIRsensorPin = top["pin"] | PIRsensorPin;
|
||||
|
||||
enabled = top[FPSTR(_enabled)] | enabled;
|
||||
|
||||
m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
|
||||
|
||||
m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
|
||||
m_onPreset = max(0,min(250,(int)m_onPreset));
|
||||
m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
|
||||
m_offPreset = max(0,min(250,(int)m_offPreset));
|
||||
|
||||
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
||||
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
||||
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
||||
HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
|
||||
|
||||
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
|
||||
|
||||
if (!initDone) {
|
||||
// reading config prior to setup()
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
||||
// check if pin is OK
|
||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
||||
// if we are changing pin in settings page
|
||||
// deallocate old pin
|
||||
pinManager.deallocatePin(oldPin, PinOwner::UM_PIR);
|
||||
if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
||||
} else {
|
||||
// allocation failed
|
||||
PIRsensorPin = -1;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
sensorPinState = digitalRead(PIRsensorPin);
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_haDiscovery)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_PIRSWITCH;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char PIRsensorSwitch::_name[] PROGMEM = "PIRsensorSwitch";
|
||||
const char PIRsensorSwitch::_enabled[] PROGMEM = "PIRenabled";
|
||||
const char PIRsensorSwitch::_switchOffDelay[] PROGMEM = "PIRoffSec";
|
||||
const char PIRsensorSwitch::_onPreset[] PROGMEM = "on-preset";
|
||||
const char PIRsensorSwitch::_offPreset[] PROGMEM = "off-preset";
|
||||
const char PIRsensorSwitch::_nightTime[] PROGMEM = "nighttime-only";
|
||||
const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only";
|
||||
const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only";
|
||||
const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery";
|
||||
const char PIRsensorSwitch::_notify[] PROGMEM = "notifications";
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
v2 Usermod to to control PWM fan with RPM feedback and temperature control
|
||||
|
||||
This usermod requires Dallas Temperature usermod to obtain temperature information. If this is not available the fan will always run at 100% speed.
|
||||
If the fan does not have _tacho_ (RPM) output you can set the _tacho-pin_ to -1 to not use that feature.
|
||||
This usermod requires the Dallas Temperature usermod to obtain temperature information. If it's not available, the fan will run at 100% speed.
|
||||
If the fan does not have _tachometer_ (RPM) output you can set the _tachometer-pin_ to -1 to disable that feature.
|
||||
|
||||
You can also set the thershold temperature at which fan runs at lowest speed. If the actual temperature measured will be 3°C greater than threshold temperature the fan will run at 100%.
|
||||
You can also set the thershold temperature at which fan runs at lowest speed. If the measured temperature is 3°C greater than the threshold temperature, the fan will run at 100%.
|
||||
|
||||
If the _tacho_ is supported the current speed (in RPM) will be repored in WLED Info page.
|
||||
If the _tachometer_ is supported, the current speed (in RPM) will be displayed on the WLED Info page.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -19,8 +19,8 @@ You will also need `-D USERMOD_DALLASTEMPERATURE`.
|
||||
All of the parameters are configured during run-time using Usermods settings page.
|
||||
This includes:
|
||||
|
||||
* PWM output pin
|
||||
* tacho input pin
|
||||
* PWM output pin (can be configured at compile time `-D PWM_PIN=xx`)
|
||||
* tachometer input pin (can be configured at compile time `-D TACHO_PIN=xx`)
|
||||
* sampling frequency in seconds
|
||||
* threshold temperature in degees C
|
||||
|
||||
@@ -30,7 +30,16 @@ _NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency
|
||||
|
||||
No special requirements.
|
||||
|
||||
## Control PWM fan speed using JSON API
|
||||
|
||||
e.g. you can use `{"PWM-fan":{"speed":30,"lock":true}}` to lock fan speed to 30 percent of maximum. (replace 30 with an arbitrary value between 0 and 100)
|
||||
If you include `speed` property you can set fan speed as a percentage (%) of maximum speed.
|
||||
If you include `lock` property you can lock (_true_) or unlock (_false_) the fan speed.
|
||||
If the fan speed is unlocked, it will revert to temperature controlled speed on the next update cycle. Once fan speed is locked it will remain so until it is unlocked by the next API call.
|
||||
|
||||
## Change Log
|
||||
|
||||
2021-10
|
||||
* First public release
|
||||
2022-05
|
||||
* Added JSON API call to allow changing of speed
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
// https://github.com/KlausMu/esp32-fan-controller/tree/main/src
|
||||
// adapted for WLED usermod by @blazoncek
|
||||
|
||||
#ifndef TACHO_PIN
|
||||
#define TACHO_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef PWM_PIN
|
||||
#define PWM_PIN -1
|
||||
#endif
|
||||
|
||||
// tacho counter
|
||||
static volatile unsigned long counter_rpm = 0;
|
||||
@@ -31,18 +38,20 @@ class PWMFanUsermod : public Usermod {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint8_t pwmChannel = 255;
|
||||
#endif
|
||||
bool lockFan = false;
|
||||
|
||||
#ifdef USERMOD_DALLASTEMPERATURE
|
||||
UsermodTemperature* tempUM;
|
||||
#endif
|
||||
|
||||
// configurable parameters
|
||||
int8_t tachoPin = -1;
|
||||
int8_t pwmPin = -1;
|
||||
int8_t tachoPin = TACHO_PIN;
|
||||
int8_t pwmPin = PWM_PIN;
|
||||
uint8_t tachoUpdateSec = 30;
|
||||
float targetTemperature = 25.0;
|
||||
uint8_t minPWMValuePct = 50;
|
||||
float targetTemperature = 35.0;
|
||||
uint8_t minPWMValuePct = 0;
|
||||
uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups.
|
||||
uint8_t pwmValuePct = 0;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
@@ -53,6 +62,8 @@ class PWMFanUsermod : public Usermod {
|
||||
static const char _tachoUpdateSec[];
|
||||
static const char _minPWMValuePct[];
|
||||
static const char _IRQperRotation[];
|
||||
static const char _speed[];
|
||||
static const char _lock[];
|
||||
|
||||
void initTacho(void) {
|
||||
if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){
|
||||
@@ -73,6 +84,8 @@ class PWMFanUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void updateTacho(void) {
|
||||
// store milliseconds when tacho was measured the last time
|
||||
msLastTachoMeasurement = millis();
|
||||
if (tachoPin < 0) return;
|
||||
|
||||
// start of tacho measurement
|
||||
@@ -83,8 +96,6 @@ class PWMFanUsermod : public Usermod {
|
||||
last_rpm /= tachoUpdateSec;
|
||||
// reset counter
|
||||
counter_rpm = 0;
|
||||
// store milliseconds when tacho was measured the last time
|
||||
msLastTachoMeasurement = millis();
|
||||
// attach interrupt again
|
||||
attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING);
|
||||
}
|
||||
@@ -92,6 +103,7 @@ class PWMFanUsermod : public Usermod {
|
||||
// https://randomnerdtutorials.com/esp32-pwm-arduino-ide/
|
||||
void initPWMfan(void) {
|
||||
if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
|
||||
enabled = false;
|
||||
pwmPin = -1;
|
||||
return;
|
||||
}
|
||||
@@ -123,7 +135,7 @@ class PWMFanUsermod : public Usermod {
|
||||
}
|
||||
|
||||
void updateFanSpeed(uint8_t pwmValue){
|
||||
if (pwmPin < 0) return;
|
||||
if (!enabled || pwmPin < 0) return;
|
||||
|
||||
#ifdef ESP8266
|
||||
analogWrite(pwmPin, pwmValue);
|
||||
@@ -148,7 +160,7 @@ class PWMFanUsermod : public Usermod {
|
||||
int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100);
|
||||
int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100;
|
||||
|
||||
if ((temp == NAN) || (temp <= 0.0)) {
|
||||
if ((temp == NAN) || (temp <= -100.0)) {
|
||||
DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255."));
|
||||
} else if (difftemp <= 0.0) {
|
||||
// Temperature is below target temperature. Run fan at minimum speed.
|
||||
@@ -198,7 +210,7 @@ class PWMFanUsermod : public Usermod {
|
||||
if ((now - msLastTachoMeasurement) < (tachoUpdateSec * 1000)) return;
|
||||
|
||||
updateTacho();
|
||||
setFanPWMbasedOnTemperature();
|
||||
if (!lockFan) setFanPWMbasedOnTemperature();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -207,12 +219,41 @@ class PWMFanUsermod : public Usermod {
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
if (tachoPin < 0) return;
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
JsonArray data = user.createNestedArray(FPSTR(_name));
|
||||
data.add(last_rpm);
|
||||
data.add(F("rpm"));
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({'");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F("':{'");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
uiDomString += F("':");
|
||||
uiDomString += enabled ? "false" : "true";
|
||||
uiDomString += F("}});\"><i class=\"icons ");
|
||||
uiDomString += enabled ? "on" : "off";
|
||||
uiDomString += F("\"></i></button>");
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
if (enabled) {
|
||||
JsonArray infoArr = user.createNestedArray(F("Manual"));
|
||||
String uiDomString = F("<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({'");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F("':{'");
|
||||
uiDomString += FPSTR(_speed);
|
||||
uiDomString += F("':parseInt(this.value)}});\" oninput=\"updateTrail(this);\" max=100 min=0 type=\"range\" value=");
|
||||
uiDomString += pwmValuePct;
|
||||
uiDomString += F(" /><div class=\"sliderdisplay\"></div></div></div>"); //<output class=\"sliderbubble\"></output>
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
JsonArray data = user.createNestedArray(F("Speed"));
|
||||
if (tachoPin >= 0) {
|
||||
data.add(last_rpm);
|
||||
data.add(F("rpm"));
|
||||
} else {
|
||||
if (lockFan) data.add(F("locked"));
|
||||
else data.add(F("auto"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -226,9 +267,24 @@ class PWMFanUsermod : public Usermod {
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
//void readFromJsonState(JsonObject& root) {
|
||||
// if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
//}
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod[FPSTR(_enabled)].is<bool>()) {
|
||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||
if (!enabled) updateFanSpeed(0);
|
||||
}
|
||||
if (enabled && !usermod[FPSTR(_speed)].isNull() && usermod[FPSTR(_speed)].is<int>()) {
|
||||
pwmValuePct = usermod[FPSTR(_speed)].as<int>();
|
||||
updateFanSpeed((constrain(pwmValuePct,0,100) * 255) / 100);
|
||||
if (pwmValuePct) lockFan = true;
|
||||
}
|
||||
if (enabled && !usermod[FPSTR(_lock)].isNull() && usermod[FPSTR(_lock)].is<bool>()) {
|
||||
lockFan = usermod[FPSTR(_lock)].as<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||
@@ -330,3 +386,5 @@ const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C";
|
||||
const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s";
|
||||
const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent";
|
||||
const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation";
|
||||
const char PWMFanUsermod::_speed[] PROGMEM = "speed";
|
||||
const char PWMFanUsermod::_lock[] PROGMEM = "lock";
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# QuinLED Dig Uno board
|
||||
|
||||
These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up.
|
||||
This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI.
|
||||
|
||||
Installation of file: Copy and replace file in wled00 directory
|
||||
|
||||
## Project link
|
||||
|
||||
* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link
|
||||
|
||||
### Platformio requirements
|
||||
|
||||
Uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`:
|
||||
|
||||
```ini
|
||||
# platformio.ini
|
||||
...
|
||||
[platformio]
|
||||
...
|
||||
; default_envs = esp07
|
||||
default_envs = d1_mini
|
||||
...
|
||||
[common]
|
||||
...
|
||||
lib_deps_external =
|
||||
...
|
||||
#For use SSD1306 OLED display uncomment following
|
||||
U8g2@~2.27.3
|
||||
#For Dallas sensor uncomment following 2 lines
|
||||
DallasTemperature@~3.8.0
|
||||
OneWire@~2.3.5
|
||||
...
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
#include "wled.h"
|
||||
//Intiating code for QuinLED Dig-Uno temp sensor
|
||||
//Uncomment Celsius if that is your prefered temperature scale
|
||||
#include <DallasTemperature.h> //Dallastemperature sensor
|
||||
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
|
||||
OneWire oneWire(18);
|
||||
#else //ESP8266 boards
|
||||
OneWire oneWire(14);
|
||||
#endif
|
||||
DallasTemperature sensor(&oneWire);
|
||||
long temptimer = millis();
|
||||
long lastMeasure = 0;
|
||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
||||
void userSetup()
|
||||
{
|
||||
// Start the DS18B20 sensor
|
||||
sensor.begin();
|
||||
}
|
||||
|
||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
||||
void userConnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void userLoop()
|
||||
{
|
||||
temptimer = millis();
|
||||
|
||||
// Timer to publishe new temperature every 60 seconds
|
||||
if (temptimer - lastMeasure > 60000) {
|
||||
lastMeasure = temptimer;
|
||||
|
||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||
if (mqtt != nullptr){
|
||||
sensor.requestTemperatures();
|
||||
|
||||
//Gets prefered temperature scale based on selection in definitions section
|
||||
#ifdef Celsius
|
||||
float board_temperature = sensor.getTempCByIndex(0);
|
||||
#else
|
||||
float board_temperature = sensors.getTempFByIndex(0);
|
||||
#endif
|
||||
|
||||
//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server.
|
||||
char subuf[38];
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat(subuf, "/temperature");
|
||||
mqtt->publish(subuf, 0, true, String(board_temperature).c_str());
|
||||
return;}
|
||||
return;}
|
||||
return;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# DS1307/DS3231 Real time clock
|
||||
|
||||
Gets the time from I2C RTC module on boot. This allows clocks to operate e.g. if temporarily no WiFi is available.
|
||||
Gets the time from I2C RTC module on boot. This allows clock operation if WiFi is not available.
|
||||
The stored time is updated each time NTP is synced.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
#include "src/dependencies/time/DS1307RTC.h"
|
||||
#include "wled.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
//Connect DS1307 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL))
|
||||
|
||||
class RTCUsermod : public Usermod {
|
||||
@@ -20,8 +12,9 @@ class RTCUsermod : public Usermod {
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { disabled = true; return; }
|
||||
RTC.begin();
|
||||
time_t rtcTime = RTC.get();
|
||||
if (rtcTime) {
|
||||
toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC);
|
||||
@@ -44,13 +37,13 @@ class RTCUsermod : public Usermod {
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("RTC");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
// void addToConfig(JsonObject& root)
|
||||
// {
|
||||
// JsonObject top = root.createNestedObject("RTC");
|
||||
// JsonArray pins = top.createNestedArray("pin");
|
||||
// pins.add(i2c_scl);
|
||||
// pins.add(i2c_sda);
|
||||
// }
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
|
||||
76
usermods/RelayBlinds/index.htm
Normal file
76
usermods/RelayBlinds/index.htm
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Blinds</title>
|
||||
<script>
|
||||
strA = "";
|
||||
function send()
|
||||
{
|
||||
nocache = "&nocache=" + Math.random() * 1000000;
|
||||
var request = new XMLHttpRequest();
|
||||
// send HTTP request
|
||||
request.open("GET", "win/" + strA +nocache, true);
|
||||
request.send(null);
|
||||
strA = "";
|
||||
}
|
||||
function up()
|
||||
{
|
||||
strA = "&U0=2";
|
||||
send();
|
||||
}
|
||||
function down()
|
||||
{
|
||||
strA = "&U0=1";
|
||||
send();
|
||||
}
|
||||
function OpenSettings()
|
||||
{
|
||||
window.open("/settings", "_self");
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
background: linear-gradient(45deg,#0ca,#0ac);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
svg {
|
||||
width: 30vw;
|
||||
padding: 2vh;
|
||||
}
|
||||
.tool_box {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
<style id="holderjs-style" type="text/css"></style></head>
|
||||
<body class=" __plain_text_READY__">
|
||||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<symbol id="icon-box-add" viewBox="0 0 32 32">
|
||||
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM16 26l-10-8h6v-6h8v6h6l-10 8zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-box-remove" viewBox="0 0 32 32">
|
||||
<path d="M26 2h-20l-6 6v21c0 0.552 0.448 1 1 1h30c0.552 0 1-0.448 1-1v-21l-6-6zM20 20v6h-8v-6h-6l10-8 10 8h-6zM4.828 6l2-2h18.343l2 2h-22.343z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-cog" viewBox="0 0 32 32">
|
||||
<path d="M29.181 19.070c-1.679-2.908-0.669-6.634 2.255-8.328l-3.145-5.447c-0.898 0.527-1.943 0.829-3.058 0.829-3.361 0-6.085-2.742-6.085-6.125h-6.289c0.008 1.044-0.252 2.103-0.811 3.070-1.679 2.908-5.411 3.897-8.339 2.211l-3.144 5.447c0.905 0.515 1.689 1.268 2.246 2.234 1.676 2.903 0.672 6.623-2.241 8.319l3.145 5.447c0.895-0.522 1.935-0.82 3.044-0.82 3.35 0 6.067 2.725 6.084 6.092h6.289c-0.003-1.034 0.259-2.080 0.811-3.038 1.676-2.903 5.399-3.894 8.325-2.219l3.145-5.447c-0.899-0.515-1.678-1.266-2.232-2.226zM16 22.479c-3.578 0-6.479-2.901-6.479-6.479s2.901-6.479 6.479-6.479c3.578 0 6.479 2.901 6.479 6.479s-2.901 6.479-6.479 6.479z"></path>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
<div id="tbB" class="tool_box">
|
||||
<svg id="upb" onclick="up()"><use xlink:href="#icon-box-remove"></use></svg>
|
||||
<svg id="dnb" onclick="down()"><use xlink:href="#icon-box-add"></use></svg>
|
||||
<svg id="stb" onclick="OpenSettings()"><use xlink:href="#icon-cog"></use></svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
usermods/RelayBlinds/presets.json
Normal file
1
usermods/RelayBlinds/presets.json
Normal file
@@ -0,0 +1 @@
|
||||
{"0":{},"2":{"n":"▲","win":"U0=2"},"1":{"n":"▼","win":"U0=1"}}
|
||||
8
usermods/RelayBlinds/readme.md
Normal file
8
usermods/RelayBlinds/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# RelayBlinds usermod
|
||||
|
||||
This simple usermod toggles two relay pins momentarily (defaults to 500ms) when `userVar0` is set.
|
||||
e.g. can be used to "push" the buttons of a window blinds motor controller.
|
||||
|
||||
v1 usermod. Please replace usermod.cpp in the `wled00` directory with the one in this file.
|
||||
You may upload `index.htm` to `[WLED-IP]/edit` to replace the default lighting UI with a simple Up/Down button one.
|
||||
A simple `presets.json` file is available. This makes the relay actions controllable via two presets to facilitate control e.g. the default UI or Alexa.
|
||||
83
usermods/RelayBlinds/usermod.cpp
Normal file
83
usermods/RelayBlinds/usermod.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "wled.h"
|
||||
|
||||
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
|
||||
|
||||
//gets called once at boot. Do all initialization that doesn't depend on network here
|
||||
void userSetup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
||||
void userConnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Physical IO
|
||||
*/
|
||||
#define PIN_UP_RELAY 4
|
||||
#define PIN_DN_RELAY 5
|
||||
#define PIN_ON_TIME 500
|
||||
bool upActive = false, upActiveBefore = false, downActive = false, downActiveBefore = false;
|
||||
unsigned long upStartTime = 0, downStartTime = 0;
|
||||
|
||||
void handleRelay()
|
||||
{
|
||||
//up and down relays
|
||||
if (userVar0) {
|
||||
upActive = true;
|
||||
if (userVar0 == 1) {
|
||||
upActive = false;
|
||||
downActive = true;
|
||||
}
|
||||
userVar0 = 0;
|
||||
}
|
||||
|
||||
if (upActive)
|
||||
{
|
||||
if(!upActiveBefore)
|
||||
{
|
||||
pinMode(PIN_UP_RELAY, OUTPUT);
|
||||
digitalWrite(PIN_UP_RELAY, LOW);
|
||||
upActiveBefore = true;
|
||||
upStartTime = millis();
|
||||
DEBUG_PRINTLN("UPA");
|
||||
}
|
||||
if (millis()- upStartTime > PIN_ON_TIME)
|
||||
{
|
||||
upActive = false;
|
||||
DEBUG_PRINTLN("UPN");
|
||||
}
|
||||
} else if (upActiveBefore)
|
||||
{
|
||||
pinMode(PIN_UP_RELAY, INPUT);
|
||||
upActiveBefore = false;
|
||||
}
|
||||
|
||||
if (downActive)
|
||||
{
|
||||
if(!downActiveBefore)
|
||||
{
|
||||
pinMode(PIN_DN_RELAY, OUTPUT);
|
||||
digitalWrite(PIN_DN_RELAY, LOW);
|
||||
downActiveBefore = true;
|
||||
downStartTime = millis();
|
||||
}
|
||||
if (millis()- downStartTime > PIN_ON_TIME)
|
||||
{
|
||||
downActive = false;
|
||||
}
|
||||
} else if (downActiveBefore)
|
||||
{
|
||||
pinMode(PIN_DN_RELAY, INPUT);
|
||||
downActiveBefore = false;
|
||||
}
|
||||
}
|
||||
|
||||
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
|
||||
void userLoop()
|
||||
{
|
||||
handleRelay();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# SN_Photoresistor usermod
|
||||
|
||||
This usermod will read from an attached photoresistor sensor like the KY-018 sensor.
|
||||
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
|
||||
This usermod will read from an attached photoresistor sensor like the KY-018.
|
||||
The luminance is displayed in both the Info section of the web UI as well as published to the `/luminance` MQTT topic, if enabled.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -9,15 +9,15 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_SN_PHOTORESISTOR` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds
|
||||
* `USERMOD_SN_PHOTORESISTOR_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
|
||||
* `USERMOD_SN_PHOTORESISTOR_REFERENCE_VOLTAGE` - the voltage supplied to the sensor, defaults to 5v
|
||||
* `USERMOD_SN_PHOTORESISTOR_ADC_PRECISION` - the ADC precision is the number of distinguishable ADC inputs, defaults to 1024.0 (10 bits)
|
||||
* `USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE` - the resistor size, defaults to 10000.0 (10K hms)
|
||||
* `USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE` - the offset value to report on, defaults to 25
|
||||
* `USERMOD_SN_PHOTORESISTOR` - Enables this user mod. wled00\usermods_list.cpp
|
||||
* `USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL` - Number of milliseconds between measurements. Defaults to 60000 ms
|
||||
* `USERMOD_SN_PHOTORESISTOR_FIRST_MEASUREMENT_AT` - Number of milliseconds after boot to take first measurement. Defaults to 20000 ms
|
||||
* `USERMOD_SN_PHOTORESISTOR_REFERENCE_VOLTAGE` - Voltage supplied to the sensor. Defaults to 5v
|
||||
* `USERMOD_SN_PHOTORESISTOR_ADC_PRECISION` - ADC precision. Defaults to 10 bits
|
||||
* `USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE` - Resistor size, defaults to 10000.0 (10K Ohms)
|
||||
* `USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE` - Offset value to report on. Defaults to 25
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
All parameters can be configured at runtime via the Usermods settings page.
|
||||
|
||||
## Project link
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# ST7789 TFT IPS Color display 240x240pxwith ESP32 boards
|
||||
# Using the ST7789 TFT IPS 240x240 pixel color display with ESP32 boards
|
||||
|
||||
This usermod allow to use 240x240 display to display following:
|
||||
This usermod enables display of the following:
|
||||
|
||||
* Current date and time;
|
||||
* Network SSID;
|
||||
* IP address;
|
||||
* WiFi signal strength;
|
||||
* Brightness;
|
||||
* Chosen effect;
|
||||
* Chosen palette;
|
||||
* Selected effect;
|
||||
* Selected palette;
|
||||
* Effect speed and intensity;
|
||||
* Estimated current in mA;
|
||||
|
||||
## Hardware
|
||||
@@ -38,35 +41,37 @@ lib_deps =
|
||||
...
|
||||
```
|
||||
|
||||
Also, while in the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows:
|
||||
In the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows:
|
||||
|
||||
Add lines to section:
|
||||
Add the following lines to section:
|
||||
|
||||
```ini
|
||||
default_envs = esp32dev
|
||||
build_flags = ${common.build_flags_esp32}
|
||||
-D USERMOD_ST7789_DISPLAY
|
||||
|
||||
-DUSER_SETUP_LOADED=1
|
||||
-DST7789_DRIVER=1
|
||||
-DTFT_WIDTH=240
|
||||
-DTFT_HEIGHT=240
|
||||
-DCGRAM_OFFSET=1
|
||||
-DTFT_MOSI=21
|
||||
-DTFT_SCLK=22
|
||||
-DTFT_DC=27
|
||||
-DTFT_RST=26
|
||||
-DTFT_BL=14
|
||||
-DLOAD_GLCD=1
|
||||
;optional for WROVER
|
||||
;-DCONFIG_SPIRAM_SUPPORT=1
|
||||
```
|
||||
|
||||
Save the `platformio.ini` file. Once this is saved, the required library files should be automatically downloaded for modifications in a later step.
|
||||
Save the `platformio.ini` file. Once saved, the required library files should be automatically downloaded for modifications in a later step.
|
||||
|
||||
### TFT_eSPI Library Adjustments
|
||||
|
||||
We need to modify a file in the `TFT_eSPI` library. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI` folder.
|
||||
If you are not using PlatformIO, you need to modify a file in the `TFT_eSPI` library. If you followed the directions to modify and save the `platformio.ini` file above, the `Setup24_ST7789.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI/User_Setups/` folder.
|
||||
|
||||
Modify the `User_Setup_Select.h` file as follows:
|
||||
Edit `Setup_ST7789.h` file and uncomment and change GPIO pin numbers in lines containing `TFT_MOSI`, `TFT_SCLK`, `TFT_RST`, `TFT_DC`.
|
||||
|
||||
* Comment out the following line (which is the 'default' setup file):
|
||||
Modify the `User_Setup_Select.h` by uncommenting the line containing `#include <User_Setups/Setup24_ST7789.h>` and commenting out the line containing `#include <User_Setup.h>`.
|
||||
|
||||
```ini
|
||||
//#include <User_Setup.h> // Default setup is root library folder
|
||||
```
|
||||
|
||||
* Add following line:
|
||||
|
||||
```ini
|
||||
#include <User_Setups/Setup_ST7789_Display.h> // Setup file for ESP32 ST7789V SPI bus TFT
|
||||
```
|
||||
|
||||
* Copy file `"Setup_ST7789_Display.h"` from usermod folder to `/.pio/libdeps/esp32dev/TFT_eSPI/User_Setups`
|
||||
If your display uses the backlight enable pin, add this definition: #define TFT_BL with backlight enable GPIO number.
|
||||
|
||||
@@ -7,33 +7,55 @@
|
||||
#include <TFT_eSPI.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#define USERMOD_ST7789_DISPLAY 97
|
||||
|
||||
#ifndef TFT_DISPOFF
|
||||
#define TFT_DISPOFF 0x28
|
||||
#ifndef USER_SETUP_LOADED
|
||||
#ifndef ST7789_DRIVER
|
||||
#error Please define ST7789_DRIVER
|
||||
#endif
|
||||
#ifndef TFT_WIDTH
|
||||
#error Please define TFT_WIDTH
|
||||
#endif
|
||||
#ifndef TFT_HEIGHT
|
||||
#error Please define TFT_HEIGHT
|
||||
#endif
|
||||
#ifndef TFT_MOSI
|
||||
#error Please define TFT_MOSI
|
||||
#endif
|
||||
#ifndef TFT_SCLK
|
||||
#error Please define TFT_SCLK
|
||||
#endif
|
||||
#ifndef TFT_DC
|
||||
#error Please define TFT_DC
|
||||
#endif
|
||||
#ifndef TFT_RST
|
||||
#error Please define TFT_RST
|
||||
#endif
|
||||
#ifndef LOAD_GLCD
|
||||
#error Please define LOAD_GLCD
|
||||
#endif
|
||||
#endif
|
||||
#ifndef TFT_BL
|
||||
#define TFT_BL -1
|
||||
#endif
|
||||
|
||||
#ifndef TFT_SLPIN
|
||||
#define TFT_SLPIN 0x10
|
||||
#endif
|
||||
#define USERMOD_ID_ST7789_DISPLAY 97
|
||||
|
||||
#define TFT_MOSI 21
|
||||
#define TFT_SCLK 22
|
||||
#define TFT_DC 18
|
||||
#define TFT_RST 5
|
||||
#define TFT_BL 26 // Display backlight control pin
|
||||
TFT_eSPI tft = TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); // Invoke custom library
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI(240, 240); // Invoke custom library
|
||||
// Extra char (+1) for null
|
||||
#define LINE_BUFFER_SIZE 20
|
||||
|
||||
// How often we are redrawing screen
|
||||
#define USER_LOOP_REFRESH_RATE_MS 1000
|
||||
|
||||
extern int getSignalQuality(int rssi);
|
||||
|
||||
|
||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||
class St7789DisplayUsermod : public Usermod {
|
||||
private:
|
||||
//Private class members. You can declare variables and functions only accessible to your usermod here
|
||||
unsigned long lastTime = 0;
|
||||
bool enabled = true;
|
||||
|
||||
bool displayTurnedOff = false;
|
||||
long lastRedraw = 0;
|
||||
@@ -45,9 +67,70 @@ class St7789DisplayUsermod : public Usermod {
|
||||
uint8_t knownBrightness = 0;
|
||||
uint8_t knownMode = 0;
|
||||
uint8_t knownPalette = 0;
|
||||
uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2
|
||||
uint8_t knownEffectSpeed = 0;
|
||||
uint8_t knownEffectIntensity = 0;
|
||||
uint8_t knownMinute = 99;
|
||||
uint8_t knownHour = 99;
|
||||
|
||||
const uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2
|
||||
long lastUpdate = 0;
|
||||
|
||||
void center(String &line, uint8_t width) {
|
||||
int len = line.length();
|
||||
if (len<width) for (byte i=(width-len)/2; i>0; i--) line = ' ' + line;
|
||||
for (byte i=line.length(); i<width; i++) line += ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the current date and time in large characters
|
||||
* on the middle rows. Based 24 or 12 hour depending on
|
||||
* the useAMPM configuration.
|
||||
*/
|
||||
void showTime() {
|
||||
if (!ntpEnabled) return;
|
||||
char lineBuffer[LINE_BUFFER_SIZE];
|
||||
|
||||
updateLocalTime();
|
||||
byte minuteCurrent = minute(localTime);
|
||||
byte hourCurrent = hour(localTime);
|
||||
//byte secondCurrent = second(localTime);
|
||||
knownMinute = minuteCurrent;
|
||||
knownHour = hourCurrent;
|
||||
|
||||
byte currentMonth = month(localTime);
|
||||
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime));
|
||||
tft.setTextColor(TFT_SILVER);
|
||||
tft.setCursor(84, 0);
|
||||
tft.setTextSize(2);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
byte showHour = hourCurrent;
|
||||
boolean isAM = false;
|
||||
if (useAMPM) {
|
||||
if (showHour == 0) {
|
||||
showHour = 12;
|
||||
isAM = true;
|
||||
} else if (showHour > 12) {
|
||||
showHour -= 12;
|
||||
isAM = false;
|
||||
} else {
|
||||
isAM = true;
|
||||
}
|
||||
}
|
||||
|
||||
sprintf_P(lineBuffer, PSTR("%2d:%02d"), (useAMPM ? showHour : hourCurrent), minuteCurrent);
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.setTextSize(4);
|
||||
tft.setCursor(60, 24);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
tft.setTextSize(2);
|
||||
tft.setCursor(186, 24);
|
||||
//sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
|
||||
if (useAMPM) tft.print(isAM ? "AM" : "PM");
|
||||
//else tft.print(lineBuffer);
|
||||
}
|
||||
|
||||
public:
|
||||
//Functions called by WLED
|
||||
|
||||
@@ -57,6 +140,9 @@ class St7789DisplayUsermod : public Usermod {
|
||||
*/
|
||||
void setup()
|
||||
{
|
||||
PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; }
|
||||
|
||||
tft.init();
|
||||
tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip.
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
@@ -65,10 +151,10 @@ class St7789DisplayUsermod : public Usermod {
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.setTextSize(2);
|
||||
tft.print("Loading...");
|
||||
if (TFT_BL > 0)
|
||||
{ // TFT_BL has been set in the TFT_eSPI library
|
||||
pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode
|
||||
digitalWrite(TFT_BL, HIGH); // Turn backlight on.
|
||||
if (TFT_BL >= 0)
|
||||
{
|
||||
pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode
|
||||
digitalWrite(TFT_BL, HIGH); // Turn backlight on.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,192 +177,153 @@ class St7789DisplayUsermod : public Usermod {
|
||||
* Instead, use a timer check as shown here.
|
||||
*/
|
||||
void loop() {
|
||||
// Check if we time interval for redrawing passes.
|
||||
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS)
|
||||
char buff[LINE_BUFFER_SIZE];
|
||||
|
||||
// Check if we time interval for redrawing passes.
|
||||
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastUpdate = millis();
|
||||
lastUpdate = millis();
|
||||
|
||||
// Turn off display after 5 minutes with no change.
|
||||
if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000)
|
||||
// Turn off display after 5 minutes with no change.
|
||||
if (!displayTurnedOff && millis() - lastRedraw > 5*60*1000)
|
||||
{
|
||||
digitalWrite(TFT_BL, LOW); // Turn backlight off.
|
||||
if (TFT_BL >= 0) digitalWrite(TFT_BL, LOW); // Turn backlight off.
|
||||
displayTurnedOff = true;
|
||||
}
|
||||
|
||||
// Check if values which are shown on display changed from the last time.
|
||||
if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP()))
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownBrightness != bri)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownMode != strip.getMainSegment().mode)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
else if (knownPalette != strip.getMainSegment().palette)
|
||||
{
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
if (!needRedraw)
|
||||
{
|
||||
return;
|
||||
}
|
||||
needRedraw = false;
|
||||
|
||||
if (displayTurnedOff)
|
||||
{
|
||||
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on.
|
||||
displayTurnedOff = false;
|
||||
}
|
||||
lastRedraw = millis();
|
||||
|
||||
// Update last known values.
|
||||
#if defined(ESP8266)
|
||||
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
||||
#else
|
||||
knownSsid = WiFi.SSID();
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextSize(2);
|
||||
// First row with Wifi name
|
||||
tft.setTextColor(TFT_SILVER);
|
||||
tft.setCursor(3, 40);
|
||||
tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
|
||||
// Print `~` char to indicate that SSID is longer, than our dicplay
|
||||
if (knownSsid.length() > tftcharwidth)
|
||||
tft.print("~");
|
||||
|
||||
// Second row with AP IP and Password or IP
|
||||
tft.setTextColor(TFT_GREEN);
|
||||
tft.setTextSize(2);
|
||||
tft.setCursor(3, 64);
|
||||
// Print AP IP and password in AP mode or knownIP if AP not active.
|
||||
|
||||
if (apActive)
|
||||
{
|
||||
tft.setTextColor(TFT_YELLOW);
|
||||
tft.print("AP IP: ");
|
||||
tft.print(knownIp);
|
||||
tft.setCursor(3,86);
|
||||
tft.setTextColor(TFT_YELLOW);
|
||||
tft.print("AP Pass:");
|
||||
tft.print(apPass);
|
||||
}
|
||||
else
|
||||
{
|
||||
tft.setTextColor(TFT_GREEN);
|
||||
tft.print("IP: ");
|
||||
tft.print(knownIp);
|
||||
tft.setCursor(3,86);
|
||||
//tft.print("Signal Strength: ");
|
||||
//tft.print(i.wifi.signal);
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.print("Bri: ");
|
||||
tft.print(((float(bri)/255)*100),0);
|
||||
tft.print("%");
|
||||
}
|
||||
|
||||
// Third row with mode name
|
||||
tft.setCursor(3, 108);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++)
|
||||
{
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol)
|
||||
// Check if values which are shown on display changed from the last time.
|
||||
if ((((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) ||
|
||||
(knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) ||
|
||||
(knownBrightness != bri) ||
|
||||
(knownEffectSpeed != strip.getMainSegment().speed) ||
|
||||
(knownEffectIntensity != strip.getMainSegment().intensity) ||
|
||||
(knownMode != strip.getMainSegment().mode) ||
|
||||
(knownPalette != strip.getMainSegment().palette))
|
||||
{
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
tft.setTextColor(TFT_MAGENTA);
|
||||
tft.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
needRedraw = true;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > tftcharwidth - 1))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
tft.setTextColor(TFT_YELLOW);
|
||||
tft.setCursor(3, 130);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++)
|
||||
{
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol)
|
||||
|
||||
if (!needRedraw)
|
||||
{
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
tft.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
return;
|
||||
}
|
||||
// The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode)
|
||||
if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1))
|
||||
break;
|
||||
}
|
||||
// Fifth row with estimated mA usage
|
||||
tft.setTextColor(TFT_SILVER);
|
||||
tft.setCursor(3, 152);
|
||||
// Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate).
|
||||
tft.print("Current: ");
|
||||
tft.print(strip.currentMilliamps);
|
||||
tft.print("mA");
|
||||
needRedraw = false;
|
||||
|
||||
if (displayTurnedOff)
|
||||
{
|
||||
digitalWrite(TFT_BL, HIGH); // Turn backlight on.
|
||||
displayTurnedOff = false;
|
||||
}
|
||||
lastRedraw = millis();
|
||||
|
||||
// Update last known values.
|
||||
#if defined(ESP8266)
|
||||
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
|
||||
#else
|
||||
knownSsid = WiFi.SSID();
|
||||
#endif
|
||||
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
|
||||
knownBrightness = bri;
|
||||
knownMode = strip.getMainSegment().mode;
|
||||
knownPalette = strip.getMainSegment().palette;
|
||||
knownEffectSpeed = strip.getMainSegment().speed;
|
||||
knownEffectIntensity = strip.getMainSegment().intensity;
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
|
||||
showTime();
|
||||
|
||||
tft.setTextSize(2);
|
||||
|
||||
// Wifi name
|
||||
tft.setTextColor(TFT_GREEN);
|
||||
tft.setCursor(0, 60);
|
||||
String line = knownSsid.substring(0, tftcharwidth-1);
|
||||
// Print `~` char to indicate that SSID is longer, than our display
|
||||
if (knownSsid.length() > tftcharwidth) line = line.substring(0, tftcharwidth-1) + '~';
|
||||
center(line, tftcharwidth);
|
||||
tft.print(line.c_str());
|
||||
|
||||
// Print AP IP and password in AP mode or knownIP if AP not active.
|
||||
if (apActive)
|
||||
{
|
||||
tft.setCursor(0, 84);
|
||||
tft.print("AP IP: ");
|
||||
tft.print(knownIp);
|
||||
tft.setCursor(0,108);
|
||||
tft.print("AP Pass:");
|
||||
tft.print(apPass);
|
||||
}
|
||||
else
|
||||
{
|
||||
tft.setCursor(0, 84);
|
||||
line = knownIp.toString();
|
||||
center(line, tftcharwidth);
|
||||
tft.print(line.c_str());
|
||||
// percent brightness
|
||||
tft.setCursor(0, 120);
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.print("Bri: ");
|
||||
tft.print((((int)bri*100)/255));
|
||||
tft.print("%");
|
||||
// signal quality
|
||||
tft.setCursor(124,120);
|
||||
tft.print("Sig: ");
|
||||
if (getSignalQuality(WiFi.RSSI()) < 10) {
|
||||
tft.setTextColor(TFT_RED);
|
||||
} else if (getSignalQuality(WiFi.RSSI()) < 25) {
|
||||
tft.setTextColor(TFT_ORANGE);
|
||||
} else {
|
||||
tft.setTextColor(TFT_GREEN);
|
||||
}
|
||||
tft.print(getSignalQuality(WiFi.RSSI()));
|
||||
tft.setTextColor(TFT_WHITE);
|
||||
tft.print("%");
|
||||
}
|
||||
|
||||
// mode name
|
||||
tft.setTextColor(TFT_CYAN);
|
||||
tft.setCursor(0, 144);
|
||||
char lineBuffer[tftcharwidth+1];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, tftcharwidth);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
// palette name
|
||||
tft.setTextColor(TFT_YELLOW);
|
||||
tft.setCursor(0, 168);
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, tftcharwidth);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
tft.setCursor(0, 192);
|
||||
tft.setTextColor(TFT_SILVER);
|
||||
sprintf_P(buff, PSTR("FX Spd:%3d Int:%3d"), effectSpeed, effectIntensity);
|
||||
tft.print(buff);
|
||||
|
||||
// Fifth row with estimated mA usage
|
||||
tft.setTextColor(TFT_SILVER);
|
||||
tft.setCursor(0, 216);
|
||||
// Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate).
|
||||
tft.print("Current: ");
|
||||
tft.setTextColor(TFT_ORANGE);
|
||||
tft.print(strip.currentMilliamps);
|
||||
tft.print("mA");
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
* Below it is shown how this could be used for e.g. a light sensor
|
||||
*/
|
||||
/*
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
int reading = 20;
|
||||
//this code adds "u":{"Light":[20," lux"]} to the info object
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray lightArr = user.createNestedArray("Light"); //name
|
||||
lightArr.add(reading); //value
|
||||
lightArr.add(" lux"); //unit
|
||||
JsonArray lightArr = user.createNestedArray("ST7789"); //name
|
||||
lightArr.add(enabled?F("installed"):F("disabled")); //unit
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
@@ -295,7 +342,7 @@ class St7789DisplayUsermod : public Usermod {
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
||||
//userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||
}
|
||||
|
||||
@@ -316,8 +363,16 @@ class St7789DisplayUsermod : public Usermod {
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("exampleUsermod");
|
||||
top["great"] = userVar0; //save this var persistently whenever settings are saved
|
||||
JsonObject top = root.createNestedObject("ST7789");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(TFT_MOSI);
|
||||
pins.add(TFT_MISO);
|
||||
pins.add(TFT_SCLK);
|
||||
pins.add(TFT_CS);
|
||||
pins.add(TFT_DC);
|
||||
pins.add(TFT_RST);
|
||||
pins.add(TFT_BL);
|
||||
//top["great"] = userVar0; //save this var persistently whenever settings are saved
|
||||
}
|
||||
|
||||
|
||||
@@ -329,10 +384,11 @@ class St7789DisplayUsermod : public Usermod {
|
||||
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
|
||||
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||
*/
|
||||
void readFromConfig(JsonObject& root)
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root["top"];
|
||||
userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot)
|
||||
//JsonObject top = root["top"];
|
||||
//userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -342,7 +398,7 @@ class St7789DisplayUsermod : public Usermod {
|
||||
*/
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ST7789_DISPLAY;
|
||||
return USERMOD_ID_ST7789_DISPLAY;
|
||||
}
|
||||
|
||||
//More methods can be added in the future, this example will then be extended.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Setup for the ESP32 board with 1.5" 240x240 display
|
||||
|
||||
// See SetupX_Template.h for all options available
|
||||
|
||||
#define ST7789_DRIVER
|
||||
#define TFT_SDA_READ // Display has a bidirectionsl SDA pin
|
||||
|
||||
#define TFT_WIDTH 240
|
||||
#define TFT_HEIGHT 240
|
||||
|
||||
#define CGRAM_OFFSET // Library will add offsets required
|
||||
|
||||
//#define TFT_MISO -1
|
||||
|
||||
#define TFT_MOSI 21
|
||||
#define TFT_SCLK 22
|
||||
//#define TFT_CS 5
|
||||
#define TFT_DC 18
|
||||
#define TFT_RST 5
|
||||
|
||||
#define TFT_BL 26 // Display backlight control pin
|
||||
|
||||
#define TFT_BACKLIGHT_ON HIGH // HIGH or LOW are options
|
||||
|
||||
#define LOAD_GLCD
|
||||
#define LOAD_FONT2
|
||||
#define LOAD_FONT4
|
||||
#define LOAD_FONT6
|
||||
#define LOAD_FONT7
|
||||
#define LOAD_FONT8
|
||||
#define LOAD_GFXFF
|
||||
|
||||
//#define SMOOTH_FONT
|
||||
|
||||
//#define SPI_FREQUENCY 27000000
|
||||
#define SPI_FREQUENCY 40000000 // Maximum for ILI9341
|
||||
|
||||
|
||||
#define SPI_READ_FREQUENCY 6000000 // 6 MHz is the maximum SPI read speed for the ST7789V
|
||||
69
usermods/Si7021_MQTT_HA/readme.md
Normal file
69
usermods/Si7021_MQTT_HA/readme.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Si7021 to MQTT (with Home Assistant Auto Discovery) usermod
|
||||
|
||||
This usermod implements support for [Si7021 I²C temperature and humidity sensors](https://www.silabs.com/documents/public/data-sheets/Si7021-A20.pdf).
|
||||
|
||||
As of this writing, the sensor data will *not* be shown on the WLED UI, but it _is_ published via MQTT to WLED's "built-in" MQTT device topic.
|
||||
|
||||
```
|
||||
temperature: $mqttDeviceTopic/si7021_temperature
|
||||
humidity: $mqttDeviceTopic/si7021_humidity
|
||||
```
|
||||
|
||||
The following sensors can also be published:
|
||||
|
||||
```
|
||||
heat_index: $mqttDeviceTopic/si7021_heat_index
|
||||
dew_point: $mqttDeviceTopic/si7021_dew_point
|
||||
absolute_humidity: $mqttDeviceTopic/si7021_absolute_humidity
|
||||
```
|
||||
|
||||
Sensor data will be updated/sent every 60 seconds.
|
||||
|
||||
This usermod also supports Home Assistant Auto Discovery.
|
||||
|
||||
## Settings via Usermod Setup
|
||||
|
||||
- `enabled`: Enables this usermod
|
||||
- `Send Dew Point, Abs. Humidity and Heat Index`: Enables additional sensors
|
||||
- `Home Assistant MQTT Auto-Discovery`: Enables Home Assistant Auto Discovery
|
||||
|
||||
# Installation
|
||||
|
||||
## Hardware
|
||||
|
||||
Attach the Si7021 sensor to the I²C interface.
|
||||
|
||||
Default PINs ESP32:
|
||||
|
||||
```
|
||||
SCL_PIN = 22;
|
||||
SDA_PIN = 21;
|
||||
```
|
||||
|
||||
Default PINs ESP8266:
|
||||
|
||||
```
|
||||
SCL_PIN = 5;
|
||||
SDA_PIN = 4;
|
||||
```
|
||||
|
||||
## Software
|
||||
|
||||
Add to `build_flags` in platformio.ini:
|
||||
|
||||
```
|
||||
-D USERMOD_SI7021_MQTT_HA
|
||||
```
|
||||
|
||||
Add to `lib_deps` in platformio.ini:
|
||||
|
||||
```
|
||||
adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||
BME280@~3.0.0
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
- Aircoookie for making WLED
|
||||
- Other usermod creators for example code (`sensors_to_mqtt` and `multi_relay` especially)
|
||||
- You, for reading this
|
||||
236
usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h
Normal file
236
usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h
Normal file
@@ -0,0 +1,236 @@
|
||||
#pragma once
|
||||
|
||||
// this is remixed from usermod_v2_SensorsToMqtt.h (sensors_to_mqtt usermod)
|
||||
// and usermod_multi_relay.h (multi_relay usermod)
|
||||
|
||||
#include "wled.h"
|
||||
#include <Adafruit_Si7021.h>
|
||||
#include <EnvironmentCalculations.h> // EnvironmentCalculations::HeatIndex(), ::DewPoint(), ::AbsoluteHumidity()
|
||||
|
||||
Adafruit_Si7021 si7021;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards
|
||||
uint8_t SCL_PIN = 22;
|
||||
uint8_t SDA_PIN = 21;
|
||||
#else //ESP8266 boards
|
||||
uint8_t SCL_PIN = 5;
|
||||
uint8_t SDA_PIN = 4;
|
||||
#endif
|
||||
|
||||
class Si7021_MQTT_HA : public Usermod
|
||||
{
|
||||
private:
|
||||
bool sensorInitialized = false;
|
||||
bool mqttInitialized = false;
|
||||
float sensorTemperature = 0;
|
||||
float sensorHumidity = 0;
|
||||
float sensorHeatIndex = 0;
|
||||
float sensorDewPoint = 0;
|
||||
float sensorAbsoluteHumidity= 0;
|
||||
String mqttTemperatureTopic = "";
|
||||
String mqttHumidityTopic = "";
|
||||
String mqttHeatIndexTopic = "";
|
||||
String mqttDewPointTopic = "";
|
||||
String mqttAbsoluteHumidityTopic = "";
|
||||
unsigned long nextMeasure = 0;
|
||||
bool enabled = false;
|
||||
bool haAutoDiscovery = true;
|
||||
bool sendAdditionalSensors = true;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _sendAdditionalSensors[];
|
||||
static const char _haAutoDiscovery[];
|
||||
|
||||
void _initializeSensor()
|
||||
{
|
||||
sensorInitialized = si7021.begin();
|
||||
Serial.printf("Si7021_MQTT_HA: sensorInitialized = %d\n", sensorInitialized);
|
||||
}
|
||||
|
||||
void _initializeMqtt()
|
||||
{
|
||||
mqttTemperatureTopic = String(mqttDeviceTopic) + "/si7021_temperature";
|
||||
mqttHumidityTopic = String(mqttDeviceTopic) + "/si7021_humidity";
|
||||
mqttHeatIndexTopic = String(mqttDeviceTopic) + "/si7021_heat_index";
|
||||
mqttDewPointTopic = String(mqttDeviceTopic) + "/si7021_dew_point";
|
||||
mqttAbsoluteHumidityTopic = String(mqttDeviceTopic) + "/si7021_absolute_humidity";
|
||||
|
||||
// Update and publish sensor data
|
||||
_updateSensorData();
|
||||
_publishSensorData();
|
||||
|
||||
if (haAutoDiscovery) {
|
||||
_publishHAMqttSensor("temperature", "Temperature", mqttTemperatureTopic, "temperature", "°C");
|
||||
_publishHAMqttSensor("humidity", "Humidity", mqttHumidityTopic, "humidity", "%");
|
||||
if (sendAdditionalSensors) {
|
||||
_publishHAMqttSensor("heat_index", "Heat Index", mqttHeatIndexTopic, "temperature", "°C");
|
||||
_publishHAMqttSensor("dew_point", "Dew Point", mqttDewPointTopic, "", "°C");
|
||||
_publishHAMqttSensor("absolute_humidity", "Absolute Humidity", mqttAbsoluteHumidityTopic, "", "g/m³");
|
||||
}
|
||||
}
|
||||
|
||||
mqttInitialized = true;
|
||||
}
|
||||
|
||||
void _publishHAMqttSensor(
|
||||
const String &name,
|
||||
const String &friendly_name,
|
||||
const String &state_topic,
|
||||
const String &deviceClass,
|
||||
const String &unitOfMeasurement)
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
String topic = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config";
|
||||
|
||||
StaticJsonDocument<300> doc;
|
||||
|
||||
doc["name"] = String(serverDescription) + " " + friendly_name;
|
||||
doc["state_topic"] = state_topic;
|
||||
doc["unique_id"] = String(mqttClientID) + name;
|
||||
if (unitOfMeasurement != "")
|
||||
doc["unit_of_measurement"] = unitOfMeasurement;
|
||||
if (deviceClass != "")
|
||||
doc["device_class"] = deviceClass;
|
||||
doc["expire_after"] = 1800;
|
||||
|
||||
JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device
|
||||
device["name"] = String(serverDescription);
|
||||
device["model"] = "WLED";
|
||||
device["manufacturer"] = "Aircoookie";
|
||||
device["identifiers"] = String("wled-") + String(serverDescription);
|
||||
device["sw_version"] = VERSION;
|
||||
|
||||
String payload;
|
||||
serializeJson(doc, payload);
|
||||
|
||||
mqtt->publish(topic.c_str(), 0, true, payload.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSensorData()
|
||||
{
|
||||
sensorTemperature = si7021.readTemperature();
|
||||
sensorHumidity = si7021.readHumidity();
|
||||
|
||||
// Serial.print("Si7021_MQTT_HA: Temperature: ");
|
||||
// Serial.print(sensorTemperature, 2);
|
||||
// Serial.print("\tHumidity: ");
|
||||
// Serial.print(sensorHumidity, 2);
|
||||
|
||||
if (sendAdditionalSensors) {
|
||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
sensorDewPoint = EnvironmentCalculations::DewPoint(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
sensorAbsoluteHumidity = EnvironmentCalculations::AbsoluteHumidity(sensorTemperature, sensorHumidity, envTempUnit);
|
||||
|
||||
// Serial.print("\tHeat Index: ");
|
||||
// Serial.print(sensorHeatIndex, 2);
|
||||
// Serial.print("\tDew Point: ");
|
||||
// Serial.print(sensorDewPoint, 2);
|
||||
// Serial.print("\tAbsolute Humidity: ");
|
||||
// Serial.println(sensorAbsoluteHumidity, 2);
|
||||
}
|
||||
// else
|
||||
// Serial.println("");
|
||||
}
|
||||
|
||||
void _publishSensorData()
|
||||
{
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
mqtt->publish(mqttTemperatureTopic.c_str(), 0, false, String(sensorTemperature).c_str());
|
||||
mqtt->publish(mqttHumidityTopic.c_str(), 0, false, String(sensorHumidity).c_str());
|
||||
if (sendAdditionalSensors) {
|
||||
mqtt->publish(mqttHeatIndexTopic.c_str(), 0, false, String(sensorHeatIndex).c_str());
|
||||
mqtt->publish(mqttDewPointTopic.c_str(), 0, false, String(sensorDewPoint).c_str());
|
||||
mqtt->publish(mqttAbsoluteHumidityTopic.c_str(), 0, false, String(sensorAbsoluteHumidity).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_sendAdditionalSensors)] = sendAdditionalSensors;
|
||||
top[FPSTR(_haAutoDiscovery)] = haAutoDiscovery;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
|
||||
bool configComplete = !top.isNull();
|
||||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||
configComplete &= getJsonValue(top[FPSTR(_sendAdditionalSensors)], sendAdditionalSensors);
|
||||
configComplete &= getJsonValue(top[FPSTR(_haAutoDiscovery)], haAutoDiscovery);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
if (mqttDeviceTopic[0] != 0)
|
||||
_initializeMqtt();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
if (enabled) {
|
||||
Serial.println("Si7021_MQTT_HA: Starting!");
|
||||
Wire.begin(SDA_PIN, SCL_PIN);
|
||||
Serial.println("Si7021_MQTT_HA: Initializing sensors.. ");
|
||||
_initializeSensor();
|
||||
}
|
||||
}
|
||||
|
||||
// gets called every time WiFi is (re-)connected.
|
||||
void connected()
|
||||
{
|
||||
nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
yield();
|
||||
if (!enabled || strip.isUpdating()) return; // !sensorFound ||
|
||||
|
||||
unsigned long tempTimer = millis();
|
||||
|
||||
if (tempTimer > nextMeasure) {
|
||||
nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds
|
||||
|
||||
if (!sensorInitialized) {
|
||||
Serial.println("Si7021_MQTT_HA: Error! Sensors not initialized in loop()!");
|
||||
_initializeSensor();
|
||||
return; // lets try again next loop
|
||||
}
|
||||
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
if (!mqttInitialized)
|
||||
_initializeMqtt();
|
||||
|
||||
// Update and publish sensor data
|
||||
_updateSensorData();
|
||||
_publishSensorData();
|
||||
}
|
||||
else {
|
||||
Serial.println("Si7021_MQTT_HA: Missing MQTT connection. Not publishing data");
|
||||
mqttInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_SI7021_MQTT_HA;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char Si7021_MQTT_HA::_name[] PROGMEM = "Si7021 MQTT (Home Assistant)";
|
||||
const char Si7021_MQTT_HA::_enabled[] PROGMEM = "enabled";
|
||||
const char Si7021_MQTT_HA::_sendAdditionalSensors[] PROGMEM = "Send Dew Point, Abs. Humidity and Heat Index";
|
||||
const char Si7021_MQTT_HA::_haAutoDiscovery[] PROGMEM = "Home Assistant MQTT Auto-Discovery";
|
||||
@@ -1,19 +1,19 @@
|
||||
# TTGO T-Display ESP32 with 240x135 TFT via SPI with TFT_eSPI
|
||||
This usermod allows use of the TTGO T-Display ESP32 module with integrated 240x135 display
|
||||
This usermod enables use of the TTGO 240x135 T-Display ESP32 module
|
||||
for controlling WLED and showing the following information:
|
||||
* Current SSID
|
||||
* IP address if obtained
|
||||
* If connected to a network, current brightness % is shown
|
||||
* in AP mode AP IP and password are shown
|
||||
* IP address, if obtained
|
||||
* If connected to a network, current brightness percentage is shown
|
||||
* In AP mode, AP, IP and password are shown
|
||||
* Current effect
|
||||
* Current palette
|
||||
* Estimated current in mA is shown (NOTE: for this to be a reasonable value, the correct LED type must be specified in the LED Prefs section)
|
||||
* Estimated current in mA (NOTE: for this to be a reasonable value, the correct LED type must be specified in the LED Prefs section)
|
||||
|
||||
Button pin is mapped to the onboard button next to the side actuated reset button of the TTGO T-Display board.
|
||||
Button pin is mapped to the onboard button adjacent to the reset button of the TTGO T-Display board.
|
||||
|
||||
I have designed a 3D printed case around this board and an ["ElectroCookie"](https://amzn.to/2WCNeeA) project board, a [level shifter](https://amzn.to/3hbKu18), a [buck regulator](https://amzn.to/3mLMy0W), and a DC [power jack](https://amzn.to/3phj9NZ). I use 12V WS2815 LED strips for my projects, and power them with 12V power supplies, so the regulator drops the voltage to the 5V level I need to power the ESP module and the level shifter. If there is any interest in this case, which elevates the board and display on some custom extended headers to make place the screen at the top of the enclosure (with accessible buttons), let me know, and I could post the STL files. It is a bit tricky to get the height correct, so I also designed a one-time use 3D printed solder fixture to set the board in the right location and at the correct height for the housing. (It is one-time use because it has to be cut off after soldering to be able to remove it). I didn't think the effort to make it in multiple pieces was worthwhile.
|
||||
I have designed a 3D printed case around this board and an ["ElectroCookie"](https://amzn.to/2WCNeeA) project board, a [level shifter](https://amzn.to/3hbKu18), a [buck regulator](https://amzn.to/3mLMy0W), and a DC [power jack](https://amzn.to/3phj9NZ). I use 12V WS2815 LED strips for my projects, and power them with 12V power supplies. The regulator supplies 5V for the ESP module and the level shifter. If there is any interest in this case which elevates the board and display on custom extended standoffs to place the screen at the top of the enclosure (with accessible buttons), let me know, and I will post the STL files. It is a bit tricky to get the height correct, so I also designed a one-time use 3D printed solder fixture to set the board in the right location and at the correct height for the housing. (It is one-time use because it has to be cut off after soldering to be able to remove it). I didn't think the effort to make it in multiple pieces was worthwhile.
|
||||
|
||||
Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo.
|
||||
Based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo.
|
||||
|
||||
## Hardware
|
||||

|
||||
@@ -30,8 +30,8 @@ Usermod based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED rep
|
||||
Functionality checked with:
|
||||
* TTGO T-Display
|
||||
* PlatformIO
|
||||
* Group of 4 individual Neopixels from Adafruit, and a several full strings of 12v WS2815 LEDs.
|
||||
* The hardware design shown above should be limited to shorter strings. For larger strings, I use a different setup with a dedicated 12v power supply and power them directly off the supply (in addition to dropping the 12v supply down to 5v with a buck regulator for the ESP module and level shifter).
|
||||
* Group of 4 individual Neopixels from Adafruit and several full strings of 12v WS2815 LEDs.
|
||||
* The hardware design shown above should be limited to shorter strings. For larger strings, I use a different setup with a dedicated 12v power supply and power them directly from said supply (in addition to dropping the 12v to 5v with a buck regulator for the ESP module and level shifter).
|
||||
|
||||
## Setup Needed:
|
||||
* As with all usermods, copy the usermod.cpp file from the TTGO-T-Display usermod folder to the wled00 folder (replacing the default usermod.cpp file).
|
||||
@@ -51,24 +51,24 @@ lib_deps =
|
||||
...
|
||||
```
|
||||
|
||||
Also, while in the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows:
|
||||
In the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows:
|
||||
|
||||
Comment out the line described below:
|
||||
```ini
|
||||
# Travis CI binaries (comment this out when building for single board)
|
||||
; default_envs = travis_esp8266, esp01, esp01_1m_ota, travis_esp32
|
||||
# Release binaries
|
||||
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3
|
||||
```
|
||||
and UNCOMMENT the following line in the 'Single binaries' section:
|
||||
and uncomment the following line in the 'Single binaries' section:
|
||||
```ini
|
||||
default_envs = esp32dev
|
||||
```
|
||||
Save the `platformio.ini` file. Once this is saved, the required library files should be automatically downloaded for modifications in a later step.
|
||||
Save the `platformio.ini` file. Once saved, the required library files should be automatically downloaded for modifications in a later step.
|
||||
|
||||
### Platformio_overrides.ini (added)
|
||||
Copy the `platformio_overrides.ini` file which is contained in the `usermods/TTGO-T-Display/` folder into the root of your project folder. This file contains an override that remaps the button pin of WLED to use the on-board button to the right of the USB-C connector (when viewed with the port oriented downward - see hardware photo).
|
||||
|
||||
### TFT_eSPI Library Adjustments (board selection)
|
||||
We need to modify a file in the `TFT_eSPI` library to select the correct board. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI_ID1559` folder.
|
||||
You need to modify a file in the `TFT_eSPI` library to select the correct board. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI_ID1559` folder.
|
||||
|
||||
Modify the `User_Setup_Select.h` file as follows:
|
||||
* Comment out the following line (which is the 'default' setup file):
|
||||
@@ -80,12 +80,12 @@ Modify the `User_Setup_Select.h` file as follows:
|
||||
#include <User_Setups/Setup25_TTGO_T_Display.h> // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT
|
||||
```
|
||||
|
||||
Run the build and it should complete correctly. If you see a failure like this:
|
||||
Build the file. If you see a failure like this:
|
||||
```ini
|
||||
xtensa-esp32-elf-g++: error: wled00\wled00.ino.cpp: No such file or directory
|
||||
xtensa-esp32-elf-g++: fatal error: no input files
|
||||
```
|
||||
Just try building again - I find that sometimes this happens on the first build attempt and subsequent attempts will build correctly.
|
||||
try building again. Sometimes this happens on the first build attempt and subsequent attempts build correctly.
|
||||
|
||||
## Arduino IDE
|
||||
- UNTESTED
|
||||
- UNTESTED
|
||||
|
||||
@@ -177,58 +177,15 @@ void userLoop() {
|
||||
|
||||
// Third row with mode name
|
||||
tft.setCursor(1, 68);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
tft.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > tftcharwidth - 1))
|
||||
break;
|
||||
}
|
||||
char lineBuffer[tftcharwidth+1];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, tftcharwidth);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
// Fourth row with palette name
|
||||
tft.setCursor(1, 90);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
tft.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
// The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode)
|
||||
if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1))
|
||||
break;
|
||||
}
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, tftcharwidth);
|
||||
tft.print(lineBuffer);
|
||||
|
||||
// Fifth row with estimated mA usage
|
||||
tft.setCursor(1, 112);
|
||||
// Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate).
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Temperature usermod
|
||||
|
||||
Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` by srg74 and 400killer!
|
||||
This usermod will read from an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno)
|
||||
The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled.
|
||||
This usermod may be expanded with support for different sensor types in the future.
|
||||
Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` usermod by srg74 and 400killer!
|
||||
Reads an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno)
|
||||
Temperature is displayed in both the Info section of the web UI as well as published to the `/temperature` MQTT topic, if enabled.
|
||||
May be expanded with support for different sensor types in the future.
|
||||
|
||||
If temperature sensor is not detected during boot, this usermod will be disabled.
|
||||
|
||||
@@ -13,10 +13,10 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
|
||||
* `USERMOD_DALLASTEMPERATURE` - enables this user mod wled00/usermods_list.cpp
|
||||
* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - umber of milliseconds after boot to take first measurement, defaults to 20000 ms
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page, including pin, selection to display temerature in degrees Celsius or Farenheit mand measurement interval.
|
||||
All parameters can be configured at runtime via the Usermods settings page, including pin, temperature in degrees Celsius or Farenheit and measurement interval.
|
||||
|
||||
## Project link
|
||||
|
||||
@@ -50,9 +50,9 @@ lib_deps =
|
||||
## Change Log
|
||||
|
||||
2020-09-12
|
||||
* Changed to use async, non-blocking implementation
|
||||
* Do not report low temperatures that indicate an error to mqtt
|
||||
* Changed to use async non-blocking implementation
|
||||
* Do not report erroneous low temperatures to MQTT
|
||||
* Disable plugin if temperature sensor not detected
|
||||
* Report the number of seconds until the first read in the info screen instead of sensor error
|
||||
2021-04
|
||||
* Adaptation for runtime configuration.
|
||||
* Adaptation for runtime configuration.
|
||||
|
||||
@@ -46,6 +46,8 @@ class UsermodTemperature : public Usermod {
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
bool HApublished = false;
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
@@ -132,6 +134,28 @@ class UsermodTemperature : public Usermod {
|
||||
return false;
|
||||
}
|
||||
|
||||
void publishHomeAssistantAutodiscovery() {
|
||||
if (!WLED_MQTT_CONNECTED) return;
|
||||
|
||||
char json_str[1024], buf[128];
|
||||
size_t payload_size;
|
||||
StaticJsonDocument<1024> json;
|
||||
|
||||
sprintf_P(buf, PSTR("%s Temperature"), serverDescription);
|
||||
json[F("name")] = buf;
|
||||
strcpy(buf, mqttDeviceTopic);
|
||||
strcat_P(buf, PSTR("/temperature"));
|
||||
json[F("state_topic")] = buf;
|
||||
json[F("device_class")] = F("temperature");
|
||||
json[F("unique_id")] = escapedMac.c_str();
|
||||
json[F("unit_of_measurement")] = F("°C");
|
||||
payload_size = serializeJson(json, json_str);
|
||||
|
||||
sprintf_P(buf, PSTR("homeassistant/sensor/%s/config"), escapedMac.c_str());
|
||||
mqtt->publish(buf, 0, true, json_str, payload_size);
|
||||
HApublished = true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
@@ -206,6 +230,23 @@ class UsermodTemperature : public Usermod {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* connected() is called every time the WiFi is (re)connected
|
||||
* Use it to initialize network interfaces
|
||||
*/
|
||||
//void connected() {}
|
||||
|
||||
/**
|
||||
* subscribe to MQTT topic if needed
|
||||
*/
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
//(re)subscribe to required topics
|
||||
//char subuf[64];
|
||||
if (mqttDeviceTopic[0] != 0) {
|
||||
publishHomeAssistantAutodiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* API calls te enable data exchange between WLED modules
|
||||
*/
|
||||
@@ -229,7 +270,6 @@ class UsermodTemperature : public Usermod {
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray temp = user.createNestedArray(FPSTR(_name));
|
||||
//temp.add(F("Loaded."));
|
||||
|
||||
if (temperature <= -100.0f) {
|
||||
temp.add(0);
|
||||
@@ -238,8 +278,13 @@ class UsermodTemperature : public Usermod {
|
||||
}
|
||||
|
||||
temp.add(degC ? getTemperatureC() : getTemperatureF());
|
||||
if (degC) temp.add(F("°C"));
|
||||
else temp.add(F("°F"));
|
||||
temp.add(degC ? F("°C") : F("°F"));
|
||||
|
||||
JsonObject sensor = root[F("sensor")];
|
||||
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
|
||||
temp = sensor.createNestedArray(F("temp"));
|
||||
temp.add(degC ? temperature : (float)temperature * 1.8f + 32);
|
||||
temp.add(degC ? F("°C") : F("°F"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
WLED v2 UserMod for running macros at sunrise and sunset.
|
||||
|
||||
At the time of this text, this user mod requires code to be changed to set certain variables:
|
||||
1. To reflect the user's graphical location (latitude/longitude) used for calculating apparent sunrise/sunset
|
||||
2. To specify which macros will be run at sunrise and/or sunset. (defaults to 15 at sunrise and 16 at sunset)
|
||||
3. To optionally provide an offset from sunrise/sunset, in minutes (max of +/- 2 hours), when the macro will be run.
|
||||
|
||||
In addition, WLED must be configured to get time from NTP (and the time must be retrieved via NTP.)
|
||||
|
||||
Please open the UserMod_SunRiseAndSet.h file for instructions on what needs to be changed, where to copy files, etc.
|
||||
|
||||
If this usermod proves useful enough, the code might eventually be updated to allow prompting for the required information
|
||||
via the web interface and to store settings in EEPROM instead of hard-coding in the .h file.
|
||||
|
||||
This usermod has only been tested on the esp32dev platform, but there's no reason it wouldn't work on other platforms.
|
||||
@@ -1,166 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include <Dusk2Dawn.h>
|
||||
|
||||
/*
|
||||
*
|
||||
* REQUIREMENTS:
|
||||
* The Dusk2Dawn library must be installed. This can be found at https://github.com/dmkishi/Dusk2Dawn. The 1.0.1 version of this library found via
|
||||
* Arduino or platformio library managers is buggy and won't compile. The latest version from github should be used.
|
||||
*
|
||||
* NTP must be enabled and functional. It simply makes no sense to have events on sunrise/sunset when an accurate time isn't available.
|
||||
*
|
||||
* The user's geographical latitude and longitude must be configured (in decimal, not degrees/minutes/etc) using m_fLatitude and m_fLongitude
|
||||
*
|
||||
* if desired, an offset of up to +/- 2 hours can be specified for each of sunrise/sunset using m_sunriseOffset and m_sunsetOffset (defaults to 0)
|
||||
*
|
||||
* The specific macro to run at sunrise and/or sunset can be changed using m_sunriseMacro and m_sunsetMacro. (defaults to 15 and 16)
|
||||
*
|
||||
* From the Dusk2Dawn library:
|
||||
* HINT: An easy way to find the longitude and latitude for any location is
|
||||
* to find the spot in Google Maps, right click the place on the map, and
|
||||
* select "What's here?". At the bottom, you’ll see a card with the
|
||||
* coordinates.
|
||||
*
|
||||
* Once configured, copy UserMod_SunRiseAndSet.h to the sketch file (the same folder as wled00.ino exists),
|
||||
* and then edit "usermods_list.cpp":
|
||||
* Add '#include "UserMod_SunRiseAndSet.h"' in the 'includes' area
|
||||
* Add 'usermods.add(new UserMod_SunRiseAndSet());' in the registerUsermods() area
|
||||
*
|
||||
*/
|
||||
|
||||
class UserMod_SunRiseAndSet : public Usermod
|
||||
{
|
||||
private:
|
||||
|
||||
/**** USER SETTINGS ****/
|
||||
|
||||
float m_fLatitude = 40.6; // latitude where sunrise/set are calculated
|
||||
float m_fLongitude = -79.80; // longitude where sunrise/set are calculated
|
||||
int8_t m_sunriseOffset = 0; // offset from sunrise, in minutes, when macro should be run (negative for before sunrise, positive for after sunrise)
|
||||
int8_t m_sunsetOffset = 0; // offset from sunset, in minutes, when macro should be run (negative for before sunset, positive for after sunset)
|
||||
uint8_t m_sunriseMacro = 15; // macro number to run at sunrise
|
||||
uint8_t m_sunsetMacro = 16; // macro number to run at sunset
|
||||
|
||||
/**** END OF USER SETTINGS. DO NOT EDIT BELOW THIS LINE! ****/
|
||||
|
||||
|
||||
Dusk2Dawn *m_pD2D = NULL; // this must be dynamically allocated in order for parameters to be loaded from EEPROM
|
||||
|
||||
int m_nUserSunrise = -1; // time, in minutes from midnight, of sunrise
|
||||
int m_nUserSunset = -1; // time, in minutes from midnight, of sunset
|
||||
|
||||
byte m_nLastRunMinute = -1; // indicates what minute the userloop was last run - used so that the code only runs once per minute
|
||||
|
||||
public:
|
||||
|
||||
virtual void setup(void)
|
||||
{
|
||||
/* TODO: From EEPROM, load the following variables:
|
||||
*
|
||||
* int16_t latitude16 = 4060; // user provided latitude, multiplied by 100 and rounded
|
||||
* int16_t longitude16 = -7980; // user provided longitude, multiplied by 100 and rounded.
|
||||
* int8_t sunrise_offset = 0; // number of minutes to offset the sunrise macro trigger (positive for minutes after sunrise, negative for minutes before)
|
||||
* int8_t sunset_offset = 0; // number of minutes to offset the sunset macro trigger (positive for minutes after sunset, negative for minutes before)
|
||||
*
|
||||
* then:
|
||||
* m_fLatitude = (float)latitude / 100.0;
|
||||
* m_fLongitude = (float)longitude / 100.0;
|
||||
* m_sunriseOffset = sunrise_offset;
|
||||
* m_sunsetOffset = sunset_offset;
|
||||
*/
|
||||
|
||||
if ((0.0 != m_fLatitude) || (0.0 != m_fLongitude))
|
||||
{
|
||||
m_pD2D = new Dusk2Dawn (m_fLatitude, m_fLongitude, 0 /* UTC */);
|
||||
// can't really check for failures. if the alloc fails, the mod just doesn't work.
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// without NTP, or a configured lat/long, none of this stuff is going to work...
|
||||
// As an alternative, need to figure out how to determine if the user has manually set the clock or not.
|
||||
if (m_pD2D && (999000000L != ntpLastSyncTime))
|
||||
{
|
||||
// to prevent needing to import all the timezone stuff from other modules, work completely in UTC
|
||||
time_t timeUTC = toki.second();
|
||||
tmElements_t tmNow;
|
||||
breakTime(timeUTC, tmNow);
|
||||
int nCurMinute = tmNow.Minute;
|
||||
|
||||
if (m_nLastRunMinute != nCurMinute) //only check once a new minute begins
|
||||
{
|
||||
m_nLastRunMinute = nCurMinute;
|
||||
int numMinutes = (60 * tmNow.Hour) + m_nLastRunMinute; // how many minutes into the day are we?
|
||||
|
||||
// check to see if sunrise/sunset should be re-determined. Only do this if neither sunrise nor sunset
|
||||
// are set. That happens when the device has just stated, and after both sunrise/sunset have already run.
|
||||
if ((-1 == m_nUserSunrise) && (-1 == m_nUserSunset))
|
||||
{
|
||||
m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440;
|
||||
m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440;
|
||||
if (m_nUserSunrise > numMinutes) // has sunrise already passed? if so, recompute for tomorrow
|
||||
{
|
||||
breakTime(timeUTC + (60*60*24), tmNow);
|
||||
m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440;
|
||||
if (m_nUserSunset > numMinutes) // if sunset has also passed, recompute that as well
|
||||
{
|
||||
m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440;
|
||||
}
|
||||
}
|
||||
// offset by user provided values. becuase the offsets are signed bytes, the max offset is just over 2 hours.
|
||||
m_nUserSunrise += m_sunriseOffset;
|
||||
m_nUserSunset += m_sunsetOffset;
|
||||
}
|
||||
|
||||
if (numMinutes == m_nUserSunrise) // Good Morning!
|
||||
{
|
||||
if (m_sunriseMacro)
|
||||
applyMacro(m_sunriseMacro); // run macro 15
|
||||
m_nUserSunrise = -1;
|
||||
}
|
||||
else if (numMinutes == m_nUserSunset) // Good Night!
|
||||
{
|
||||
if (m_sunsetMacro)
|
||||
applyMacro(m_sunsetMacro); // run macro 16
|
||||
m_nUserSunset = -1;
|
||||
}
|
||||
} // if (m_nLastRunMinute != nCurMinute)
|
||||
} // if (m_pD2D && (999000000L != ntpLastSyncTime))
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
JsonObject user = root["SunRiseAndSet"];
|
||||
if (user.isNull()) user = root.createNestedObject("SunRiseAndSet");
|
||||
|
||||
char buf[10];
|
||||
if (-1 != m_nUserSunrise)
|
||||
{
|
||||
snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunrise / 60, m_nUserSunrise % 60);
|
||||
user["rise"] = buf;
|
||||
}
|
||||
if (-1 != m_nUserSunset)
|
||||
{
|
||||
snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunset / 60, m_nUserSunset % 60);
|
||||
user["set"] = buf;
|
||||
}
|
||||
JsonObject vars = user.createNestedObject("vars");
|
||||
vars["lat"] = m_fLatitude;
|
||||
vars["long"] = m_fLongitude;
|
||||
vars["rise_mac"] = m_sunriseMacro;
|
||||
vars["set_mac"] = m_sunsetMacro;
|
||||
vars["rise_off"] = m_sunriseOffset;
|
||||
vars["set_off"] = m_sunsetOffset;
|
||||
}
|
||||
|
||||
~UserMod_SunRiseAndSet(void)
|
||||
{
|
||||
if (m_pD2D) delete m_pD2D;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
# Description
|
||||
|
||||
That usermod implements support of simple hand gestures with VL53L0X sensor: on/off and brightness correction.
|
||||
It can be useful for kitchen strips to avoid any touches.
|
||||
- on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros)
|
||||
- brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode.
|
||||
Configure brightness by changing distance to the sensor (see parameters below for customization).
|
||||
"macroLongPress" is also called here.
|
||||
|
||||
## Installation
|
||||
Implements support of simple hand gestures via a VL53L0X sensor: on/off and brightness adjustment.
|
||||
Useful for controlling strips when you want to avoid touching anything.
|
||||
- on/off - swipe your hand below the sensor ("shortPressAction" is called. Can be customized via WLED macros)
|
||||
- brightness adjustment - hold your hand below the sensor for 1 second to switch to "brightness" mode.
|
||||
adjust the brightness by changing the distance between your hand and the sensor (see parameters below for customization).
|
||||
|
||||
## Installation
|
||||
|
||||
1. Attach VL53L0X sensor to i2c pins according to default pins for your board.
|
||||
2. Add `-D USERMOD_VL53L0X_GESTURES` to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
|
||||
In my case, for example: `build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES`
|
||||
In my case, for example: `build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES`
|
||||
3. Add "pololu/VL53L0X" dependency below to `lib_deps` like this:
|
||||
```ini
|
||||
lib_deps = ${env.lib_deps}
|
||||
@@ -21,15 +20,10 @@ lib_deps = ${env.lib_deps}
|
||||
My entire `platformio_override.ini` for example (for nodemcu board):
|
||||
```ini
|
||||
[platformio]
|
||||
default_envs = nodemcu
|
||||
default_envs = nodemcuv2
|
||||
|
||||
[env:nodemcu]
|
||||
board = nodemcu
|
||||
platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES
|
||||
[env:nodemcuv2]
|
||||
build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES
|
||||
lib_deps = ${env.lib_deps}
|
||||
pololu/VL53L0X @ ^1.3.0
|
||||
```
|
||||
pololu/VL53L0X @ ^1.3.0
|
||||
```
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
* It can be useful for kitchen strips to avoid any touches.
|
||||
* - on/off - just swipe a hand below your sensor ("shortPressAction" is called and can be customized through WLED macros)
|
||||
* - brightness correction - keep your hand below sensor for 1 second to switch to "brightness" mode.
|
||||
* Configure brightness by changing distance to the sensor (see parameters below for customization).
|
||||
* "macroLongPress" is also called here.
|
||||
Configure brightness by changing distance to the sensor (see parameters below for customization).
|
||||
*
|
||||
* Enabling this mod usermod:
|
||||
* Enabling this usermod:
|
||||
* 1. Attach VL53L0X sensor to i2c pins according to default pins for your board.
|
||||
* 2. Add "-D USERMOD_VL53L0X_GESTURES" to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
|
||||
* In my case, for example: build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D USERMOD_VL53L0X_GESTURES
|
||||
* 3. Add "pololu/VL53L0X" dependency to lib_deps like this:
|
||||
* 2. Add `-D USERMOD_VL53L0X_GESTURES` to your build flags at platformio.ini (plaformio_override.ini) for needed environment.
|
||||
* In my case, for example: `build_flags = ${env.build_flags} -D USERMOD_VL53L0X_GESTURES`
|
||||
* 3. Add "pololu/VL53L0X" dependency below to `lib_deps` like this:
|
||||
* lib_deps = ${env.lib_deps}
|
||||
* pololu/VL53L0X @ ^1.3.0
|
||||
*/
|
||||
@@ -21,28 +20,20 @@
|
||||
#include <Wire.h>
|
||||
#include <VL53L0X.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
#ifndef VL53L0X_MAX_RANGE_MM
|
||||
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions
|
||||
#define VL53L0X_MAX_RANGE_MM 230 // max height in millimeters to react for motions
|
||||
#endif
|
||||
|
||||
#ifndef VL53L0X_MIN_RANGE_OFFSET
|
||||
#define VL53L0X_MIN_RANGE_OFFSET 60 // minimal range in millimiters that sensor can detect. Used in long motions to correct brightnes calculation.
|
||||
#define VL53L0X_MIN_RANGE_OFFSET 60 // minimal range in millimeters that sensor can detect. Used in long motions to correct brightness calculation.
|
||||
#endif
|
||||
|
||||
#ifndef VL53L0X_DELAY_MS
|
||||
#define VL53L0X_DELAY_MS 100 // how often to get data from sensor
|
||||
#define VL53L0X_DELAY_MS 100 // how often to get data from sensor
|
||||
#endif
|
||||
|
||||
#ifndef VL53L0X_LONG_MOTION_DELAY_MS
|
||||
#define VL53L0X_LONG_MOTION_DELAY_MS 1000 // how often to get data from sensor
|
||||
#define VL53L0X_LONG_MOTION_DELAY_MS 1000 // switch onto "long motion" action after this delay
|
||||
#endif
|
||||
|
||||
class UsermodVL53L0XGestures : public Usermod {
|
||||
@@ -55,11 +46,11 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
bool wasMotionBefore = false;
|
||||
bool isLongMotion = false;
|
||||
unsigned long motionStartTime = 0;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
|
||||
Wire.begin();
|
||||
|
||||
@@ -80,40 +71,34 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
lastTime = millis();
|
||||
|
||||
int range = sensor.readRangeSingleMillimeters();
|
||||
DEBUG_PRINTF(F("range: %d, brightness: %d"), range, bri);
|
||||
DEBUG_PRINTF("range: %d, brightness: %d\r\n", range, bri);
|
||||
|
||||
if (range < VL53L0X_MAX_RANGE_MM)
|
||||
{
|
||||
if (!wasMotionBefore)
|
||||
{
|
||||
motionStartTime = millis();
|
||||
DEBUG_PRINTF(F("motionStartTime: %d"), motionStartTime);
|
||||
DEBUG_PRINTF("motionStartTime: %d\r\n", motionStartTime);
|
||||
}
|
||||
wasMotionBefore = true;
|
||||
|
||||
if (millis() - motionStartTime > VL53L0X_LONG_MOTION_DELAY_MS) //long motion
|
||||
{
|
||||
DEBUG_PRINTF(F("long motion: %d"), motionStartTime);
|
||||
DEBUG_PRINTF("long motion: %d\r\n", motionStartTime);
|
||||
if (!isLongMotion)
|
||||
{
|
||||
if (macroLongPress)
|
||||
{
|
||||
applyMacro(macroLongPress);
|
||||
}
|
||||
isLongMotion = true;
|
||||
}
|
||||
|
||||
// set brightness according to range
|
||||
bri = (VL53L0X_MAX_RANGE_MM - max(range, VL53L0X_MIN_RANGE_OFFSET)) * 255 / (VL53L0X_MAX_RANGE_MM - VL53L0X_MIN_RANGE_OFFSET);
|
||||
DEBUG_PRINTF(F("new brightness: %d"), bri);
|
||||
DEBUG_PRINTF("new brightness: %d", bri);
|
||||
stateUpdated(1);
|
||||
}
|
||||
} else if (wasMotionBefore) { //released
|
||||
long dur = millis() - motionStartTime;
|
||||
|
||||
if (!isLongMotion)
|
||||
{ //short press
|
||||
DEBUG_PRINTF(F("shortPressAction..."));
|
||||
DEBUG_PRINTLN(F("shortPressAction..."));
|
||||
shortPressAction();
|
||||
}
|
||||
wasMotionBefore = false;
|
||||
@@ -127,13 +112,13 @@ class UsermodVL53L0XGestures : public Usermod {
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("VL53L0x");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
// void addToConfig(JsonObject& root)
|
||||
// {
|
||||
// JsonObject top = root.createNestedObject("VL53L0x");
|
||||
// JsonArray pins = top.createNestedArray("pin");
|
||||
// pins.add(i2c_scl);
|
||||
// pins.add(i2c_sda);
|
||||
// }
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
## Features
|
||||
- SSD1306 128x32 or 128x64 I2C OLED display
|
||||
- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect)
|
||||
- Auto display shutoff for saving display lifetime
|
||||
- Auto display shutoff for extending display lifetime
|
||||
- Dallas temperature sensor
|
||||
- Reporting temperature to MQTT broker
|
||||
- Relay for energy saving
|
||||
- Relay for saving energy
|
||||
|
||||
## Hardware
|
||||

|
||||
|
||||
@@ -185,58 +185,14 @@ void userLoop() {
|
||||
|
||||
// Third row with mode name
|
||||
u8x8.setCursor(2, 2);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
char lineBuffer[17];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
u8x8.setCursor(2, 3);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||
|
||||
@@ -191,58 +191,14 @@ void userLoop() {
|
||||
|
||||
// Third row with mode name
|
||||
u8x8.setCursor(2, 2);
|
||||
uint8_t qComma = 0;
|
||||
bool insideQuotes = false;
|
||||
uint8_t printedChars = 0;
|
||||
char singleJsonSymbol;
|
||||
char lineBuffer[17];
|
||||
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
// Find the mode name in JSON
|
||||
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownMode))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
// Fourth row with palette name
|
||||
u8x8.setCursor(2, 3);
|
||||
qComma = 0;
|
||||
insideQuotes = false;
|
||||
printedChars = 0;
|
||||
// Looking for palette name in JSON.
|
||||
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
|
||||
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
|
||||
switch (singleJsonSymbol) {
|
||||
case '"':
|
||||
insideQuotes = !insideQuotes;
|
||||
break;
|
||||
case '[':
|
||||
case ']':
|
||||
break;
|
||||
case ',':
|
||||
qComma++;
|
||||
default:
|
||||
if (!insideQuotes || (qComma != knownPalette))
|
||||
break;
|
||||
u8x8.print(singleJsonSymbol);
|
||||
printedChars++;
|
||||
}
|
||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
||||
break;
|
||||
}
|
||||
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||
u8x8.print(lineBuffer);
|
||||
|
||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||
|
||||
1820
usermods/audioreactive/audio_reactive.h
Normal file
1820
usermods/audioreactive/audio_reactive.h
Normal file
File diff suppressed because it is too large
Load Diff
687
usermods/audioreactive/audio_source.h
Normal file
687
usermods/audioreactive/audio_source.h
Normal file
@@ -0,0 +1,687 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wire.h>
|
||||
#include "wled.h"
|
||||
#include <driver/i2s.h>
|
||||
#include <driver/adc.h>
|
||||
#include <soc/i2s_reg.h> // needed for SPH0465 timing workaround (classic ESP32)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#include <driver/adc_deprecated.h>
|
||||
#include <driver/adc_types_deprecated.h>
|
||||
#endif
|
||||
// type of i2s_config_t.SampleRate was changed from "int" to "unsigned" in IDF 4.4.x
|
||||
#define SRate_t uint32_t
|
||||
#else
|
||||
#define SRate_t int
|
||||
#endif
|
||||
|
||||
//#include <driver/i2s_std.h>
|
||||
//#include <driver/i2s_pdm.h>
|
||||
//#include <driver/i2s_tdm.h>
|
||||
//#include <driver/gpio.h>
|
||||
|
||||
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents
|
||||
// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||
// there are two things in these MCUs that could lead to problems with audio processing:
|
||||
// * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x)
|
||||
// * single core, so FFT task might slow down other things like LED updates
|
||||
#if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1)
|
||||
#error This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2.
|
||||
#else
|
||||
#warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* ToDo: remove. ES7243 is controlled via compiler defines
|
||||
Until this configuration is moved to the webinterface
|
||||
*/
|
||||
|
||||
// if you have problems to get your microphone work on the left channel, uncomment the following line
|
||||
//#define I2S_USE_RIGHT_CHANNEL // (experimental) define this to use right channel (digital mics only)
|
||||
|
||||
// Uncomment the line below to utilize ADC1 _exclusively_ for I2S sound input.
|
||||
// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches"
|
||||
// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed;
|
||||
// for example if you want to read "analog buttons"
|
||||
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up
|
||||
|
||||
// data type requested from the I2S driver - currently we always use 32bit
|
||||
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
|
||||
|
||||
#ifdef I2S_USE_16BIT_SAMPLES
|
||||
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT
|
||||
#define I2S_datatype int16_t
|
||||
#define I2S_unsigned_datatype uint16_t
|
||||
#define I2S_data_size I2S_BITS_PER_CHAN_16BIT
|
||||
#undef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
#else
|
||||
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT
|
||||
//#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_24BIT
|
||||
#define I2S_datatype int32_t
|
||||
#define I2S_unsigned_datatype uint32_t
|
||||
#define I2S_data_size I2S_BITS_PER_CHAN_32BIT
|
||||
#define I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
#endif
|
||||
|
||||
/* There are several (confusing) options in IDF 4.4.x:
|
||||
* I2S_CHANNEL_FMT_RIGHT_LEFT, I2S_CHANNEL_FMT_ALL_RIGHT and I2S_CHANNEL_FMT_ALL_LEFT stands for stereo mode, which means two channels will transport different data.
|
||||
* I2S_CHANNEL_FMT_ONLY_RIGHT and I2S_CHANNEL_FMT_ONLY_LEFT they are mono mode, both channels will only transport same data.
|
||||
* I2S_CHANNEL_FMT_MULTIPLE means TDM channels, up to 16 channel will available, and they are stereo as default.
|
||||
* if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case.
|
||||
*/
|
||||
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3))
|
||||
// espressif bug: only_left has no sound, left and right are swapped
|
||||
// https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138)
|
||||
// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918)
|
||||
// https://github.com/espressif/esp-idf/issues/6625 I2S: left/right channels are swapped for read (IDFGH-4826)
|
||||
#ifdef I2S_USE_RIGHT_CHANNEL
|
||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||
#define I2S_MIC_CHANNEL_TEXT "right channel only (work-around swapped channel bug in IDF 4.4)."
|
||||
#define I2S_PDM_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||
#define I2S_PDM_MIC_CHANNEL_TEXT "right channel only"
|
||||
#else
|
||||
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ALL_LEFT
|
||||
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_RIGHT_LEFT
|
||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||
#define I2S_MIC_CHANNEL_TEXT "left channel only (work-around swapped channel bug in IDF 4.4)."
|
||||
#define I2S_PDM_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||
#define I2S_PDM_MIC_CHANNEL_TEXT "left channel only."
|
||||
#endif
|
||||
|
||||
#else
|
||||
// not swapped
|
||||
#ifdef I2S_USE_RIGHT_CHANNEL
|
||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||
#define I2S_MIC_CHANNEL_TEXT "right channel only."
|
||||
#else
|
||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||
#define I2S_MIC_CHANNEL_TEXT "left channel only."
|
||||
#endif
|
||||
#define I2S_PDM_MIC_CHANNEL I2S_MIC_CHANNEL
|
||||
#define I2S_PDM_MIC_CHANNEL_TEXT I2S_MIC_CHANNEL_TEXT
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* Interface class
|
||||
AudioSource serves as base class for all microphone types
|
||||
This enables accessing all microphones with one single interface
|
||||
which simplifies the caller code
|
||||
*/
|
||||
class AudioSource {
|
||||
public:
|
||||
/* All public methods are virtual, so they can be overridden
|
||||
Everything but the destructor is also removed, to make sure each mic
|
||||
Implementation provides its version of this function
|
||||
*/
|
||||
virtual ~AudioSource() {};
|
||||
|
||||
/* Initialize
|
||||
This function needs to take care of anything that needs to be done
|
||||
before samples can be obtained from the microphone.
|
||||
*/
|
||||
virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0;
|
||||
|
||||
/* Deinitialize
|
||||
Release all resources and deactivate any functionality that is used
|
||||
by this microphone
|
||||
*/
|
||||
virtual void deinitialize() = 0;
|
||||
|
||||
/* getSamples
|
||||
Read num_samples from the microphone, and store them in the provided
|
||||
buffer
|
||||
*/
|
||||
virtual void getSamples(float *buffer, uint16_t num_samples) = 0;
|
||||
|
||||
/* check if the audio source driver was initialized successfully */
|
||||
virtual bool isInitialized(void) {return(_initialized);}
|
||||
|
||||
/* identify Audiosource type - I2S-ADC or I2S-digital */
|
||||
typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType;
|
||||
virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method
|
||||
|
||||
protected:
|
||||
/* Post-process audio sample - currently on needed for I2SAdcSource*/
|
||||
virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing
|
||||
|
||||
// Private constructor, to make sure it is not callable except from derived classes
|
||||
AudioSource(SRate_t sampleRate, int blockSize, float sampleScale) :
|
||||
_sampleRate(sampleRate),
|
||||
_blockSize(blockSize),
|
||||
_initialized(false),
|
||||
_sampleScale(sampleScale)
|
||||
{};
|
||||
|
||||
SRate_t _sampleRate; // Microphone sampling rate
|
||||
int _blockSize; // I2S block size
|
||||
bool _initialized; // Gets set to true if initialization is successful
|
||||
float _sampleScale; // pre-scaling factor for I2S samples
|
||||
};
|
||||
|
||||
/* Basic I2S microphone source
|
||||
All functions are marked virtual, so derived classes can replace them
|
||||
*/
|
||||
class I2SSource : public AudioSource {
|
||||
public:
|
||||
I2SSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||
AudioSource(sampleRate, blockSize, sampleScale) {
|
||||
_config = {
|
||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
|
||||
.sample_rate = _sampleRate,
|
||||
.bits_per_sample = I2S_SAMPLE_RESOLUTION,
|
||||
.channel_format = I2S_MIC_CHANNEL,
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
|
||||
//.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = _blockSize,
|
||||
.use_apll = 0,
|
||||
.bits_per_chan = I2S_data_size,
|
||||
#else
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = _blockSize,
|
||||
.use_apll = false
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
|
||||
if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) {
|
||||
if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) ||
|
||||
!pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206
|
||||
DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: ws=%d, sd=%d\n", i2swsPin, i2ssdPin);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// i2ssckPin needs special treatment, since it might be unused on PDM mics
|
||||
if (i2sckPin != I2S_PIN_NO_CHANGE) {
|
||||
if (!pinManager.allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) {
|
||||
DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
#if !defined(SOC_I2S_SUPPORTS_PDM_RX)
|
||||
#warning this MCU does not support PDM microphones
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
// This is an I2S PDM microphone, these microphones only use a clock and
|
||||
// data line, to make it simpler to debug, use the WS pin as CLK and SD pin as DATA
|
||||
// example from espressif: https://github.com/espressif/esp-idf/blob/release/v4.4/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/i2s_recorder_main.c
|
||||
|
||||
// note to self: PDM has known bugs on S3, and does not work on C3
|
||||
// * S3: PDM sample rate only at 50% of expected rate: https://github.com/espressif/esp-idf/issues/9893
|
||||
// * S3: I2S PDM has very low amplitude: https://github.com/espressif/esp-idf/issues/8660
|
||||
// * C3: does not support PDM to PCM input. SoC would allow PDM RX, but there is no hardware to directly convert to PCM so it will not work. https://github.com/espressif/esp-idf/issues/8796
|
||||
|
||||
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm if clock pin not provided. PDM is not supported on ESP32-S2. PDM RX not supported on ESP32-C3
|
||||
_config.channel_format =I2S_PDM_MIC_CHANNEL; // seems that PDM mono mode always uses left channel.
|
||||
_config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
if (mclkPin != I2S_PIN_NO_CHANGE) {
|
||||
_config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality, and to avoid glitches.
|
||||
// //_config.fixed_mclk = 512 * _sampleRate;
|
||||
// //_config.fixed_mclk = 256 * _sampleRate;
|
||||
}
|
||||
|
||||
#if !defined(SOC_I2S_SUPPORTS_APLL)
|
||||
#warning this MCU does not have an APLL high accuracy clock for audio
|
||||
// S3: not supported; S2: supported; C3: not supported
|
||||
_config.use_apll = false; // APLL not supported on this MCU
|
||||
#endif
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
if (ESP.getChipRevision() == 0) _config.use_apll = false; // APLL is broken on ESP32 revision 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Reserve the master clock pin if provided
|
||||
_mclkPin = mclkPin;
|
||||
if (mclkPin != I2S_PIN_NO_CHANGE) {
|
||||
if(!pinManager.allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) {
|
||||
DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin);
|
||||
return;
|
||||
} else
|
||||
_routeMclk(mclkPin);
|
||||
}
|
||||
|
||||
_pinConfig = {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
.mck_io_num = mclkPin, // "classic" ESP32 supports setting MCK on GPIO0/GPIO1/GPIO3 only. i2s_set_pin() will fail if wrong mck_io_num is provided.
|
||||
#endif
|
||||
.bck_io_num = i2sckPin,
|
||||
.ws_io_num = i2swsPin,
|
||||
.data_out_num = I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = i2ssdPin
|
||||
};
|
||||
|
||||
//DEBUGSR_PRINTF("[AR] I2S: SD=%d, WS=%d, SCK=%d, MCLK=%d\n", i2ssdPin, i2swsPin, i2sckPin, mclkPin);
|
||||
|
||||
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("AR: Failed to install i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUGSR_PRINTF("AR: I2S#0 driver %s aPLL; fixed_mclk=%d.\n", _config.use_apll? "uses":"without", _config.fixed_mclk);
|
||||
DEBUGSR_PRINTF("AR: %d bits, Sample scaling factor = %6.4f\n", _config.bits_per_sample, _sampleScale);
|
||||
if (_config.mode & I2S_MODE_PDM) {
|
||||
DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in PDM MASTER mode."));
|
||||
} else {
|
||||
DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in MASTER mode."));
|
||||
}
|
||||
|
||||
err = i2s_set_pin(I2S_NUM_0, &_pinConfig);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("AR: Failed to set i2s pin config: %d\n", err);
|
||||
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
||||
return;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed.
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("AR: Failed to configure i2s clocks: %d\n", err);
|
||||
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
virtual void deinitialize() {
|
||||
_initialized = false;
|
||||
esp_err_t err = i2s_driver_uninstall(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive);
|
||||
if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive);
|
||||
if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive);
|
||||
// Release the master clock pin
|
||||
if (_mclkPin != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_mclkPin, PinOwner::UM_Audioreactive);
|
||||
}
|
||||
|
||||
virtual void getSamples(float *buffer, uint16_t num_samples) {
|
||||
if (_initialized) {
|
||||
esp_err_t err;
|
||||
size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
|
||||
I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */
|
||||
|
||||
err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to get samples: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// For correct operation, we need to read exactly sizeof(samples) bytes from i2s
|
||||
if (bytes_read != sizeof(newSamples)) {
|
||||
DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store samples in sample buffer and update DC offset
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
|
||||
newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples)
|
||||
|
||||
float currSample = 0.0f;
|
||||
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
|
||||
currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places
|
||||
#else
|
||||
currSample = (float) newSamples[i]; // 16bit input -> use as-is
|
||||
#endif
|
||||
buffer[i] = currSample;
|
||||
buffer[i] *= _sampleScale; // scale samples
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void _routeMclk(int8_t mclkPin) {
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// MCLK routing by writing registers is not needed any more with IDF > 4.4.0
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
// this way of MCLK routing only works on "classic" ESP32
|
||||
/* Enable the mclk routing depending on the selected mclk pin (ESP32: only 0,1,3)
|
||||
Only I2S_NUM_0 is supported
|
||||
*/
|
||||
if (mclkPin == GPIO_NUM_0) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
WRITE_PERI_REG(PIN_CTRL,0xFFF0);
|
||||
} else if (mclkPin == GPIO_NUM_1) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3);
|
||||
WRITE_PERI_REG(PIN_CTRL, 0xF0F0);
|
||||
} else {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2);
|
||||
WRITE_PERI_REG(PIN_CTRL, 0xFF00);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
i2s_config_t _config;
|
||||
i2s_pin_config_t _pinConfig;
|
||||
int8_t _mclkPin;
|
||||
};
|
||||
|
||||
/* ES7243 Microphone
|
||||
This is an I2S microphone that requires ininitialization over
|
||||
I2C before I2S data can be received
|
||||
*/
|
||||
class ES7243 : public I2SSource {
|
||||
private:
|
||||
// I2C initialization functions for ES7243
|
||||
void _es7243I2cBegin() {
|
||||
bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
|
||||
if (i2c_initialized == false) {
|
||||
DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver."));
|
||||
}
|
||||
}
|
||||
|
||||
void _es7243I2cWrite(uint8_t reg, uint8_t val) {
|
||||
#ifndef ES7243_ADDR
|
||||
Wire.beginTransmission(0x13);
|
||||
#define ES7243_ADDR 0x13 // default address
|
||||
#else
|
||||
Wire.beginTransmission(ES7243_ADDR);
|
||||
#endif
|
||||
Wire.write((uint8_t)reg);
|
||||
Wire.write((uint8_t)val);
|
||||
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
|
||||
if (i2cErr != 0) {
|
||||
DEBUGSR_PRINTF("AR: ES7243 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, ES7243_ADDR, reg, val);
|
||||
}
|
||||
}
|
||||
|
||||
void _es7243InitAdc() {
|
||||
_es7243I2cBegin();
|
||||
_es7243I2cWrite(0x00, 0x01);
|
||||
_es7243I2cWrite(0x06, 0x00);
|
||||
_es7243I2cWrite(0x05, 0x1B);
|
||||
_es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S
|
||||
_es7243I2cWrite(0x08, 0x43);
|
||||
_es7243I2cWrite(0x05, 0x13);
|
||||
}
|
||||
|
||||
public:
|
||||
ES7243(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||
I2SSource(sampleRate, blockSize, sampleScale) {
|
||||
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||
};
|
||||
|
||||
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
|
||||
// check that pins are valid
|
||||
if ((sdaPin < 0) || (sclPin < 0)) {
|
||||
DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((i2sckPin < 0) || (mclkPin < 0)) {
|
||||
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve SDA and SCL pins of the I2C interface
|
||||
PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } };
|
||||
if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) {
|
||||
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
|
||||
DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
|
||||
return;
|
||||
}
|
||||
|
||||
pin_ES7243_SDA = sdaPin;
|
||||
pin_ES7243_SCL = sclPin;
|
||||
|
||||
// First route mclk, then configure ADC over I2C, then configure I2S
|
||||
_es7243InitAdc();
|
||||
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
// Release SDA and SCL pins of the I2C interface
|
||||
PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } };
|
||||
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
|
||||
I2SSource::deinitialize();
|
||||
}
|
||||
|
||||
private:
|
||||
int8_t pin_ES7243_SDA;
|
||||
int8_t pin_ES7243_SCL;
|
||||
};
|
||||
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
#if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC)
|
||||
#warning this MCU does not support analog sound input
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only availeable in "classic" ESP32
|
||||
|
||||
/* ADC over I2S Microphone
|
||||
This microphone is an ADC pin sampled via the I2S interval
|
||||
This allows to use the I2S API to obtain ADC samples with high sample rates
|
||||
without the need of manual timing of the samples
|
||||
*/
|
||||
class I2SAdcSource : public I2SSource {
|
||||
public:
|
||||
I2SAdcSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||
I2SSource(sampleRate, blockSize, sampleScale) {
|
||||
_config = {
|
||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
|
||||
.sample_rate = _sampleRate,
|
||||
.bits_per_sample = I2S_SAMPLE_RESOLUTION,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
|
||||
#else
|
||||
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
#endif
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = _blockSize,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0
|
||||
};
|
||||
}
|
||||
|
||||
/* identify Audiosource type - I2S-ADC*/
|
||||
AudioSourceType getType(void) {return(Type_I2SAdc);}
|
||||
|
||||
void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
|
||||
_myADCchannel = 0x0F;
|
||||
if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) {
|
||||
DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin);
|
||||
return;
|
||||
}
|
||||
_audioPin = audioPin;
|
||||
|
||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
||||
if (channel > 9) {
|
||||
DEBUGSR_PRINTF("Incompatible GPIO used for analog audio input: %d\n", _audioPin);
|
||||
return;
|
||||
} else {
|
||||
adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
|
||||
_myADCchannel = channel;
|
||||
}
|
||||
|
||||
// Install Driver
|
||||
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution
|
||||
|
||||
// Enable I2S mode of ADC
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel));
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// see example in https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino
|
||||
adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11); // configure ADC input amplification
|
||||
|
||||
#if defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
// according to docs from espressif, the ADC needs to be started explicitly
|
||||
// fingers crossed
|
||||
err = i2s_adc_enable(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err);
|
||||
//return;
|
||||
}
|
||||
#else
|
||||
// bugfix: do not disable ADC initially - its already disabled after driver install.
|
||||
//err = i2s_adc_disable(I2S_NUM_0);
|
||||
// //err = i2s_stop(I2S_NUM_0);
|
||||
//if (err != ESP_OK) {
|
||||
// DEBUGSR_PRINTF("Failed to initially disable i2s adc: %d\n", err);
|
||||
//}
|
||||
#endif
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
I2S_datatype postProcessSample(I2S_datatype sample_in) {
|
||||
static I2S_datatype lastADCsample = 0; // last good sample
|
||||
static unsigned int broken_samples_counter = 0; // number of consecutive broken (and fixed) ADC samples
|
||||
I2S_datatype sample_out = 0;
|
||||
|
||||
// bring sample down down to 16bit unsigned
|
||||
I2S_unsigned_datatype rawData = * reinterpret_cast<I2S_unsigned_datatype *> (&sample_in); // C++ acrobatics to get sample as "unsigned"
|
||||
#ifndef I2S_USE_16BIT_SAMPLES
|
||||
rawData = (rawData >> 16) & 0xFFFF; // scale input down from 32bit -> 16bit
|
||||
I2S_datatype lastGoodSample = lastADCsample / 16384 ; // prepare "last good sample" accordingly (26bit-> 12bit with correct sign handling)
|
||||
#else
|
||||
rawData = rawData & 0xFFFF; // input is already in 16bit, just mask off possible junk
|
||||
I2S_datatype lastGoodSample = lastADCsample * 4; // prepare "last good sample" accordingly (10bit-> 12bit)
|
||||
#endif
|
||||
|
||||
// decode ADC sample data fields
|
||||
uint16_t the_channel = (rawData >> 12) & 0x000F; // upper 4 bit = ADC channel
|
||||
uint16_t the_sample = rawData & 0x0FFF; // lower 12bit -> ADC sample (unsigned)
|
||||
I2S_datatype finalSample = (int(the_sample) - 2048); // convert unsigned sample to signed (centered at 0);
|
||||
|
||||
if ((the_channel != _myADCchannel) && (_myADCchannel != 0x0F)) { // 0x0F means "don't know what my channel is"
|
||||
// fix bad sample
|
||||
finalSample = lastGoodSample; // replace with last good ADC sample
|
||||
broken_samples_counter ++;
|
||||
if (broken_samples_counter > 256) _myADCchannel = 0x0F; // too many bad samples in a row -> disable sample corrections
|
||||
//Serial.print("\n!ADC rogue sample 0x"); Serial.print(rawData, HEX); Serial.print("\tchannel:");Serial.println(the_channel);
|
||||
} else broken_samples_counter = 0; // good sample - reset counter
|
||||
|
||||
// back to original resolution
|
||||
#ifndef I2S_USE_16BIT_SAMPLES
|
||||
finalSample = finalSample << 16; // scale up from 16bit -> 32bit;
|
||||
#endif
|
||||
|
||||
finalSample = finalSample / 4; // mimic old analog driver behaviour (12bit -> 10bit)
|
||||
sample_out = (3 * finalSample + lastADCsample) / 4; // apply low-pass filter (2-tap FIR)
|
||||
//sample_out = (finalSample + lastADCsample) / 2; // apply stronger low-pass filter (2-tap FIR)
|
||||
|
||||
lastADCsample = sample_out; // update ADC last sample
|
||||
return(sample_out);
|
||||
}
|
||||
|
||||
|
||||
void getSamples(float *buffer, uint16_t num_samples) {
|
||||
/* Enable ADC. This has to be enabled and disabled directly before and
|
||||
* after sampling, otherwise Wifi dies
|
||||
*/
|
||||
if (_initialized) {
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
// old code - works for me without enable/disable, at least on ESP32.
|
||||
//esp_err_t err = i2s_start(I2S_NUM_0);
|
||||
esp_err_t err = i2s_adc_enable(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
I2SSource::getSamples(buffer, num_samples);
|
||||
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
// old code - works for me without enable/disable, at least on ESP32.
|
||||
err = i2s_adc_disable(I2S_NUM_0); //i2s_adc_disable() may cause crash with IDF 4.4 (https://github.com/espressif/arduino-esp32/issues/6832)
|
||||
//err = i2s_stop(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
pinManager.deallocatePin(_audioPin, PinOwner::UM_Audioreactive);
|
||||
_initialized = false;
|
||||
_myADCchannel = 0x0F;
|
||||
|
||||
esp_err_t err;
|
||||
#if defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
// according to docs from espressif, the ADC needs to be stopped explicitly
|
||||
// fingers crossed
|
||||
err = i2s_adc_disable(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err);
|
||||
}
|
||||
#endif
|
||||
|
||||
i2s_stop(I2S_NUM_0);
|
||||
err = i2s_driver_uninstall(I2S_NUM_0);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int8_t _audioPin;
|
||||
int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined"
|
||||
};
|
||||
#endif
|
||||
|
||||
/* SPH0645 Microphone
|
||||
This is an I2S microphone with some timing quirks that need
|
||||
special consideration.
|
||||
*/
|
||||
|
||||
// https://github.com/espressif/esp-idf/issues/7192 SPH0645 i2s microphone issue when migrate from legacy esp-idf version (IDFGH-5453)
|
||||
// a user recommended this: Try to set .communication_format to I2S_COMM_FORMAT_STAND_I2S and call i2s_set_clk() after i2s_set_pin().
|
||||
class SPH0654 : public I2SSource {
|
||||
public:
|
||||
SPH0654(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||
I2SSource(sampleRate, blockSize, sampleScale)
|
||||
{}
|
||||
|
||||
void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
|
||||
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin);
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// these registers are only existing in "classic" ESP32
|
||||
REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9));
|
||||
REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT);
|
||||
#else
|
||||
#warning FIX ME! Please.
|
||||
#endif
|
||||
}
|
||||
};
|
||||
73
usermods/audioreactive/readme.md
Normal file
73
usermods/audioreactive/readme.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Audioreactive usermod
|
||||
|
||||
Enabless controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
|
||||
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).
|
||||
|
||||
Does audio processing and provides data structure that specially written effects can use.
|
||||
|
||||
**does not** provide effects or draw anything to an LED strip/matrix.
|
||||
|
||||
## Additional Documentation
|
||||
This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and a lot of documentation and information can be found in the [SR-WLED wiki](https://github.com/atuline/WLED/wiki):
|
||||
* [getting started with audio](https://github.com/atuline/WLED/wiki/First-Time-Setup#sound)
|
||||
* [Sound settings](https://github.com/atuline/WLED/wiki/Sound-Settings) - similar to options on the usemod settings page in WLED.
|
||||
* [Digital Audio](https://github.com/atuline/WLED/wiki/Digital-Microphone-Hookup)
|
||||
* [Analog Audio](https://github.com/atuline/WLED/wiki/Analog-Audio-Input-Options)
|
||||
* [UDP Sound sync](https://github.com/atuline/WLED/wiki/UDP-Sound-Sync)
|
||||
|
||||
|
||||
## Supported MCUs
|
||||
This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support.
|
||||
|
||||
It will compile succesfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
|
||||
|
||||
Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3.
|
||||
|
||||
Currently ESP8266 is not supported, due to low speed and small RAM of this chip.
|
||||
There are however plans to create a lightweight audioreactive for the 8266, with reduced features.
|
||||
## Installation
|
||||
|
||||
### using customised _arduinoFFT_ library for use with this usermod
|
||||
Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment `build_flags`, as well as `https://github.com/blazoncek/arduinoFFT.git` to your `lib_deps`.
|
||||
If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed.
|
||||
|
||||
Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git
|
||||
|
||||
### using latest (develop) _arduinoFFT_ library
|
||||
Alternatively, you can use the latest arduinoFFT development version.
|
||||
ArduinoFFT `develop` library is slightly more accurate, and slighly faster than our customised library, however also needs additional 2kB RAM.
|
||||
|
||||
* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT`
|
||||
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`
|
||||
|
||||
## Configuration
|
||||
|
||||
All parameters are runtime configurable. Some may require a hard reset after changing them (I2S microphone or selected GPIOs).
|
||||
|
||||
If you want to define default GPIOs during compile time, use the following (default values in parentheses):
|
||||
|
||||
- `-D SR_DMTYPE=x` : defines digital microphone type: 0=analog, 1=generic I2S (default), 2=ES7243 I2S, 3=SPH0645 I2S, 4=generic I2S with master clock, 5=PDM I2S
|
||||
- `-D AUDIOPIN=x` : GPIO for analog microphone/AUX-in (36)
|
||||
- `-D I2S_SDPIN=x` : GPIO for SD pin on digital microphone (32)
|
||||
- `-D I2S_WSPIN=x` : GPIO for WS pin on digital microphone (15)
|
||||
- `-D I2S_CKPIN=x` : GPIO for SCK pin on digital microphone (14)
|
||||
- `-D MCLK_PIN=x` : GPIO for master clock pin on digital Line-In boards (-1)
|
||||
- `-D ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1)
|
||||
- `-D ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1)
|
||||
|
||||
**NOTE** I2S is used for analog audio sampling. Hence, the analog *buttons* (i.e. potentiometers) are disabled when running this usermod with an analog microphone.
|
||||
|
||||
### Advanced Compile-Time Options
|
||||
You can use the following additional flags in your `build_flags`
|
||||
* `-D SR_SQUELCH=x` : Default "squelch" setting (10)
|
||||
* `-D SR_GAIN=x` : Default "gain" setting (60)
|
||||
* `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this).
|
||||
* `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM ressources (not recommended unless you absolutely need this).
|
||||
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
|
||||
* `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE)
|
||||
* `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB.
|
||||
|
||||
## Release notes
|
||||
|
||||
* 2022-06 Ported from [soundreactive WLED](https://github.com/atuline/WLED) - by @blazoncek (AKA Blaz Kristan) and the [SR-WLED team](https://github.com/atuline/WLED/wiki#sound-reactive-wled-fork-team).
|
||||
* 2022-11 Updated to align with "[MoonModules/WLED](https://amg.wled.me)" audioreactive usermod - by @softhack007 (AKA Frank Möhle).
|
||||
@@ -1,12 +1,11 @@
|
||||
# :battery: Battery status/level Usermod :battery:
|
||||
|
||||
This Usermod allows you to monitor the battery level of your battery powered project.
|
||||
Enables battery level monitoring of your project.
|
||||
|
||||
You can see the battery level and voltage in the `info modal`.
|
||||
|
||||
For this to work the positive side of the (18650) battery must be connected to pin `A0` of the d1mini/esp8266 with a 100k ohm resistor (see [Useful Links](#useful-links)).
|
||||
For this to work, the positive side of the (18650) battery must be connected to pin `A0` of the d1 mini/esp8266 with a 100k Ohm resistor (see [Useful Links](#useful-links)).
|
||||
|
||||
If you have a esp32 board it is best to connect the positive side of the battery to ADC1 (GPIO32 - GPIO39)
|
||||
If you have an ESP32 board, connect the positive side of the battery to ADC1 (GPIO32 - GPIO39)
|
||||
|
||||
<p align="center">
|
||||
<img width="300" src="assets/battery_info_screen.png">
|
||||
@@ -23,16 +22,16 @@ define `USERMOD_BATTERY_STATUS_BASIC` in `my_config.h`
|
||||
|
||||
### Define Your Options
|
||||
|
||||
* `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_BATTERY_MEASUREMENT_PIN` - defaults to A0 on esp8266 and GPIO32 on esp32
|
||||
* `USERMOD_BATTERY_MEASUREMENT_INTERVAL` - the frequency to check the battery, defaults to 30 seconds
|
||||
* `USERMOD_BATTERY_MIN_VOLTAGE` - minimum voltage of the Battery used, default is 2.6 (18650 battery standard)
|
||||
* `USERMOD_BATTERY_MAX_VOLTAGE` - maximum voltage of the Battery used, default is 4.2 (18650 battery standard)
|
||||
* `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp
|
||||
* `USERMOD_BATTERY_MEASUREMENT_PIN` - defaults to A0 on ESP8266 and GPIO32 on ESP32
|
||||
* `USERMOD_BATTERY_MEASUREMENT_INTERVAL` - battery check interval. defaults to 30 seconds
|
||||
* `USERMOD_BATTERY_MIN_VOLTAGE` - minimum battery voltage. default is 2.6 (18650 battery standard)
|
||||
* `USERMOD_BATTERY_MAX_VOLTAGE` - maximum battery voltage. default is 4.2 (18650 battery standard)
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
All parameters can be configured at runtime via the Usermods settings page.
|
||||
|
||||
## Important :warning:
|
||||
* Make sure you know your battery specification ! not every battery is the same !
|
||||
* Make sure you know your battery specifications! All batteries are **NOT** the same!
|
||||
* Example:
|
||||
|
||||
| Your battery specification table | | Options you can define |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Blynk controllable relay
|
||||
This usermod allows controlling a relay state from the user variables. It also allows the user variables to be set over Blynk.
|
||||
Enables controlling a relay state via user variables. Allows the user variables to be set via Blynk.
|
||||
|
||||
Optionally, the servo can have a reset timer to go back to it's default state after an interval. This interval is set through userVar1.
|
||||
Optionally, the servo can have a reset timer to return to its default state after a user definable interval. The interval is set via userVar1.
|
||||
|
||||
## Instalation
|
||||
|
||||
Replace the WLED06_usermod.ino file in Aircoookies WLED folder with the one here.
|
||||
Replace the WLED06_usermod.ino file in Aircoookies WLED folder, with the one here.
|
||||
|
||||
## Customizations
|
||||
|
||||
@@ -16,13 +16,13 @@ Update the following parameters in WLED06_usermod.ino to configure the mod's beh
|
||||
#define RELAY_PIN 5
|
||||
//Which pin state should the relay default to
|
||||
#define RELAY_PIN_DEFAULT LOW
|
||||
//If >0 The controller returns to RELAY_PIN_DEFAULT after this time in milliseconds
|
||||
//If >0 The controller returns to RELAY_PIN_DEFAULT after this time, in milliseconds
|
||||
#define RELAY_PIN_TIMER_DEFAULT 3000
|
||||
|
||||
//Blynk virtual pin for controlling relay
|
||||
#define BLYNK_USER_VAR0_PIN V9
|
||||
//Blynk virtual pin for controlling relay timer
|
||||
#define BLYNK_USER_VAR1_PIN V10
|
||||
//Number of milliseconds between updating blynk
|
||||
//Number of milliseconds between Blynk updates
|
||||
#define BLYNK_RELAY_UPDATE_INTERVAL 5000
|
||||
```
|
||||
|
||||
457
usermods/boblight/boblight.h
Normal file
457
usermods/boblight/boblight.h
Normal file
@@ -0,0 +1,457 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
/*
|
||||
* Usermod that implements BobLight "ambilight" protocol
|
||||
*
|
||||
* See the accompanying README.md file for more info.
|
||||
*/
|
||||
|
||||
#ifndef BOB_PORT
|
||||
#define BOB_PORT 19333 // Default boblightd port
|
||||
#endif
|
||||
|
||||
class BobLightUsermod : public Usermod {
|
||||
typedef struct _LIGHT {
|
||||
char lightname[5];
|
||||
float hscan[2];
|
||||
float vscan[2];
|
||||
} light_t;
|
||||
|
||||
private:
|
||||
unsigned long lastTime = 0;
|
||||
bool enabled = false;
|
||||
bool initDone = false;
|
||||
|
||||
light_t *lights = nullptr;
|
||||
uint16_t numLights = 0; // 16 + 9 + 16 + 9
|
||||
uint16_t top, bottom, left, right; // will be filled in readFromConfig()
|
||||
uint16_t pct;
|
||||
|
||||
WiFiClient bobClient;
|
||||
WiFiServer *bob;
|
||||
uint16_t bobPort = BOB_PORT;
|
||||
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
|
||||
/*
|
||||
# boblight
|
||||
# Copyright (C) Bob 2009
|
||||
#
|
||||
# makeboblight.sh created by Adam Boeglin <adamrb@gmail.com>
|
||||
#
|
||||
# boblight is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# boblight is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// fills the lights[] array with position & depth of scan for each LED
|
||||
void fillBobLights(int bottom, int left, int top, int right, float pct_scan) {
|
||||
|
||||
int lightcount = 0;
|
||||
int total = top+left+right+bottom;
|
||||
int bcount;
|
||||
|
||||
if (total > strip.getLengthTotal()) {
|
||||
DEBUG_PRINTLN(F("BobLight: Too many lights."));
|
||||
return;
|
||||
}
|
||||
|
||||
// start left part of bottom strip (clockwise direction, 1st half)
|
||||
if (bottom > 0) {
|
||||
bcount = 1;
|
||||
float brange = 100.0/bottom;
|
||||
float bcurrent = 50.0;
|
||||
if (bottom < top) {
|
||||
int diff = top - bottom;
|
||||
brange = 100.0/top;
|
||||
bcurrent -= (diff/2)*brange;
|
||||
}
|
||||
while (bcount <= bottom/2) {
|
||||
float btop = bcurrent - brange;
|
||||
String name = "b"+String(bcount);
|
||||
strncpy(lights[lightcount].lightname, name.c_str(), 4);
|
||||
lights[lightcount].hscan[0] = btop;
|
||||
lights[lightcount].hscan[1] = bcurrent;
|
||||
lights[lightcount].vscan[0] = 100 - pct_scan;
|
||||
lights[lightcount].vscan[1] = 100;
|
||||
lightcount+=1;
|
||||
bcurrent = btop;
|
||||
bcount+=1;
|
||||
}
|
||||
}
|
||||
|
||||
// left side
|
||||
if (left > 0) {
|
||||
int lcount = 1;
|
||||
float lrange = 100.0/left;
|
||||
float lcurrent = 100.0;
|
||||
while (lcount <= left) {
|
||||
float ltop = lcurrent - lrange;
|
||||
String name = "l"+String(lcount);
|
||||
strncpy(lights[lightcount].lightname, name.c_str(), 4);
|
||||
lights[lightcount].hscan[0] = 0;
|
||||
lights[lightcount].hscan[1] = pct_scan;
|
||||
lights[lightcount].vscan[0] = ltop;
|
||||
lights[lightcount].vscan[1] = lcurrent;
|
||||
lightcount+=1;
|
||||
lcurrent = ltop;
|
||||
lcount+=1;
|
||||
}
|
||||
}
|
||||
|
||||
// top side
|
||||
if (top > 0) {
|
||||
int tcount = 1;
|
||||
float trange = 100.0/top;
|
||||
float tcurrent = 0;
|
||||
while (tcount <= top) {
|
||||
float ttop = tcurrent + trange;
|
||||
String name = "t"+String(tcount);
|
||||
strncpy(lights[lightcount].lightname, name.c_str(), 4);
|
||||
lights[lightcount].hscan[0] = tcurrent;
|
||||
lights[lightcount].hscan[1] = ttop;
|
||||
lights[lightcount].vscan[0] = 0;
|
||||
lights[lightcount].vscan[1] = pct_scan;
|
||||
lightcount+=1;
|
||||
tcurrent = ttop;
|
||||
tcount+=1;
|
||||
}
|
||||
}
|
||||
|
||||
// right side
|
||||
if (right > 0) {
|
||||
int rcount = 1;
|
||||
float rrange = 100.0/right;
|
||||
float rcurrent = 0;
|
||||
while (rcount <= right) {
|
||||
float rtop = rcurrent + rrange;
|
||||
String name = "r"+String(rcount);
|
||||
strncpy(lights[lightcount].lightname, name.c_str(), 4);
|
||||
lights[lightcount].hscan[0] = 100-pct_scan;
|
||||
lights[lightcount].hscan[1] = 100;
|
||||
lights[lightcount].vscan[0] = rcurrent;
|
||||
lights[lightcount].vscan[1] = rtop;
|
||||
lightcount+=1;
|
||||
rcurrent = rtop;
|
||||
rcount+=1;
|
||||
}
|
||||
}
|
||||
|
||||
// right side of bottom strip (2nd half)
|
||||
if (bottom > 0) {
|
||||
float brange = 100.0/bottom;
|
||||
float bcurrent = 100;
|
||||
if (bottom < top) {
|
||||
brange = 100.0/top;
|
||||
}
|
||||
while (bcount <= bottom) {
|
||||
float btop = bcurrent - brange;
|
||||
String name = "b"+String(bcount);
|
||||
strncpy(lights[lightcount].lightname, name.c_str(), 4);
|
||||
lights[lightcount].hscan[0] = btop;
|
||||
lights[lightcount].hscan[1] = bcurrent;
|
||||
lights[lightcount].vscan[0] = 100 - pct_scan;
|
||||
lights[lightcount].vscan[1] = 100;
|
||||
lightcount+=1;
|
||||
bcurrent = btop;
|
||||
bcount+=1;
|
||||
}
|
||||
}
|
||||
|
||||
numLights = lightcount;
|
||||
|
||||
#if WLED_DEBUG
|
||||
DEBUG_PRINTLN(F("Fill light data: "));
|
||||
DEBUG_PRINTF(" lights %d\n", numLights);
|
||||
for (int i=0; i<numLights; i++) {
|
||||
DEBUG_PRINTF(" light %s scan %2.1f %2.1f %2.1f %2.1f\n", lights[i].lightname, lights[i].vscan[0], lights[i].vscan[1], lights[i].hscan[0], lights[i].hscan[1]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BobSync() { yield(); } // allow other tasks, should also be used to force pixel redraw (not with WLED)
|
||||
void BobClear() { for (size_t i=0; i<numLights; i++) setRealtimePixel(i, 0, 0, 0, 0); }
|
||||
void pollBob();
|
||||
|
||||
public:
|
||||
|
||||
void setup() {
|
||||
uint16_t totalLights = bottom + left + top + right;
|
||||
if ( totalLights > strip.getLengthTotal() ) {
|
||||
DEBUG_PRINTLN(F("BobLight: Too many lights."));
|
||||
DEBUG_PRINTF("%d+%d+%d+%d>%d\n", bottom, left, top, right, strip.getLengthTotal());
|
||||
totalLights = strip.getLengthTotal();
|
||||
top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f);
|
||||
left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f);
|
||||
}
|
||||
lights = new light_t[totalLights];
|
||||
if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights
|
||||
else enable(false);
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void connected() {
|
||||
// we can only start server when WiFi is connected
|
||||
if (!bob) bob = new WiFiServer(bobPort, 1);
|
||||
bob->begin();
|
||||
bob->setNoDelay(true);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!enabled || strip.isUpdating()) return;
|
||||
if (millis() - lastTime > 10) {
|
||||
lastTime = millis();
|
||||
pollBob();
|
||||
}
|
||||
}
|
||||
|
||||
void enable(bool en) { enabled = en; }
|
||||
|
||||
/**
|
||||
* handling of MQTT message
|
||||
* topic only contains stripped topic (part after /wled/MAC)
|
||||
* topic should look like: /swipe with amessage of [up|down]
|
||||
*/
|
||||
bool onMqttMessage(char* topic, char* payload) {
|
||||
//if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) {
|
||||
// String action = payload;
|
||||
// if (action == "on") {
|
||||
// enable(true);
|
||||
// return true;
|
||||
// } else if (action == "off") {
|
||||
// enable(false);
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* subscribe to MQTT topic for controlling usermod
|
||||
*/
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
//char subuf[64];
|
||||
//if (mqttDeviceTopic[0] != 0) {
|
||||
// strcpy(subuf, mqttDeviceTopic);
|
||||
// strcat_P(subuf, PSTR("/subtopic"));
|
||||
// mqtt->subscribe(subuf, 0);
|
||||
//}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_enabled);
|
||||
uiDomString += enabled ? F(":false}});\">") : F(":true}});\">");
|
||||
uiDomString += F("<i class=\"icons ");
|
||||
uiDomString += enabled ? "on" : "off";
|
||||
uiDomString += F("\"></i></button>");
|
||||
infoArr.add(uiDomString);
|
||||
}
|
||||
|
||||
/*
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||
bool en = enabled;
|
||||
JsonObject um = root[FPSTR(_name)];
|
||||
if (!um.isNull()) {
|
||||
if (um[FPSTR(_enabled)].is<bool>()) {
|
||||
en = um[FPSTR(_enabled)].as<bool>();
|
||||
} else {
|
||||
String str = um[FPSTR(_enabled)]; // checkbox -> off or on
|
||||
en = (bool)(str!="off"); // off is guaranteed to be present
|
||||
}
|
||||
if (en != enabled && lights) {
|
||||
enable(en);
|
||||
if (!enabled && bob && bob->hasClient()) {
|
||||
if (bobClient) bobClient.stop();
|
||||
bobClient = bob->available();
|
||||
BobClear();
|
||||
exitRealtime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void appendConfigData() {
|
||||
//oappend(SET_F("dd=addDropdown('usermod','selectfield');"));
|
||||
//oappend(SET_F("addOption(dd,'1st value',0);"));
|
||||
//oappend(SET_F("addOption(dd,'2nd value',1);"));
|
||||
oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field
|
||||
oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field
|
||||
oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field
|
||||
oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field
|
||||
oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject umData = root.createNestedObject(FPSTR(_name));
|
||||
umData[FPSTR(_enabled)] = enabled;
|
||||
umData[F("port")] = bobPort;
|
||||
umData[F("top")] = top;
|
||||
umData[F("bottom")] = bottom;
|
||||
umData[F("left")] = left;
|
||||
umData[F("right")] = right;
|
||||
umData[F("pct")] = pct;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root) {
|
||||
JsonObject umData = root[FPSTR(_name)];
|
||||
bool configComplete = !umData.isNull();
|
||||
|
||||
bool en = enabled;
|
||||
configComplete &= getJsonValue(umData[FPSTR(_enabled)], en);
|
||||
enable(en);
|
||||
|
||||
configComplete &= getJsonValue(umData[F("port")], bobPort);
|
||||
configComplete &= getJsonValue(umData[F("bottom")], bottom, 16);
|
||||
configComplete &= getJsonValue(umData[F("top")], top, 16);
|
||||
configComplete &= getJsonValue(umData[F("left")], left, 9);
|
||||
configComplete &= getJsonValue(umData[F("right")], right, 9);
|
||||
configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%]
|
||||
pct = MIN(50,MAX(1,pct));
|
||||
|
||||
uint16_t totalLights = bottom + left + top + right;
|
||||
if (initDone && numLights != totalLights) {
|
||||
if (lights) delete[] lights;
|
||||
setup();
|
||||
}
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
/*
|
||||
* handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors.
|
||||
* Use this to blank out some LEDs or set them to a different color regardless of the set effect mode.
|
||||
* Commonly used for custom clocks (Cronixie, 7 segment)
|
||||
*/
|
||||
void handleOverlayDraw() {
|
||||
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
|
||||
}
|
||||
|
||||
uint16_t getId() { return USERMOD_ID_BOBLIGHT; }
|
||||
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char BobLightUsermod::_name[] PROGMEM = "BobLight";
|
||||
const char BobLightUsermod::_enabled[] PROGMEM = "enabled";
|
||||
|
||||
// main boblight handling (definition here prevents inlining)
|
||||
void BobLightUsermod::pollBob() {
|
||||
|
||||
//check if there are any new clients
|
||||
if (bob && bob->hasClient()) {
|
||||
//find free/disconnected spot
|
||||
if (!bobClient || !bobClient.connected()) {
|
||||
if (bobClient) bobClient.stop();
|
||||
bobClient = bob->available();
|
||||
DEBUG_PRINTLN(F("Boblight: Client connected."));
|
||||
}
|
||||
//no free/disconnected spot so reject
|
||||
WiFiClient bobClientTmp = bob->available();
|
||||
bobClientTmp.stop();
|
||||
BobClear();
|
||||
exitRealtime();
|
||||
}
|
||||
|
||||
//check clients for data
|
||||
if (bobClient && bobClient.connected()) {
|
||||
realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected
|
||||
|
||||
//get data from the client
|
||||
while (bobClient.available()) {
|
||||
String input = bobClient.readStringUntil('\n');
|
||||
// DEBUG_PRINT("Client: "); DEBUG_PRINTLN(input); // may be to stressful on Serial
|
||||
if (input.startsWith(F("hello"))) {
|
||||
DEBUG_PRINTLN(F("hello"));
|
||||
bobClient.print(F("hello\n"));
|
||||
} else if (input.startsWith(F("ping"))) {
|
||||
DEBUG_PRINTLN(F("ping 1"));
|
||||
bobClient.print(F("ping 1\n"));
|
||||
} else if (input.startsWith(F("get version"))) {
|
||||
DEBUG_PRINTLN(F("version 5"));
|
||||
bobClient.print(F("version 5\n"));
|
||||
} else if (input.startsWith(F("get lights"))) {
|
||||
char tmp[64];
|
||||
String answer = "";
|
||||
sprintf_P(tmp, PSTR("lights %d\n"), numLights);
|
||||
DEBUG_PRINT(tmp);
|
||||
answer.concat(tmp);
|
||||
for (int i=0; i<numLights; i++) {
|
||||
sprintf_P(tmp, PSTR("light %s scan %2.1f %2.1f %2.1f %2.1f\n"), lights[i].lightname, lights[i].vscan[0], lights[i].vscan[1], lights[i].hscan[0], lights[i].hscan[1]);
|
||||
DEBUG_PRINT(tmp);
|
||||
answer.concat(tmp);
|
||||
}
|
||||
bobClient.print(answer);
|
||||
} else if (input.startsWith(F("set priority"))) {
|
||||
DEBUG_PRINTLN(F("set priority not implemented"));
|
||||
// not implemented
|
||||
} else if (input.startsWith(F("set light "))) { // <id> <cmd in rgb, speed, interpolation> <value> ...
|
||||
input.remove(0,10);
|
||||
String tmp = input.substring(0,input.indexOf(' '));
|
||||
|
||||
int light_id = -1;
|
||||
for (uint16_t i=0; i<numLights; i++) {
|
||||
if (strncmp(lights[i].lightname, tmp.c_str(), 4) == 0) {
|
||||
light_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (light_id == -1) return;
|
||||
|
||||
input.remove(0,input.indexOf(' ')+1);
|
||||
if (input.startsWith(F("rgb "))) {
|
||||
input.remove(0,4);
|
||||
tmp = input.substring(0,input.indexOf(' '));
|
||||
uint8_t red = (uint8_t)(255.0f*tmp.toFloat());
|
||||
input.remove(0,input.indexOf(' ')+1); // remove first float value
|
||||
tmp = input.substring(0,input.indexOf(' '));
|
||||
uint8_t green = (uint8_t)(255.0f*tmp.toFloat());
|
||||
input.remove(0,input.indexOf(' ')+1); // remove second float value
|
||||
tmp = input.substring(0,input.indexOf(' '));
|
||||
uint8_t blue = (uint8_t)(255.0f*tmp.toFloat());
|
||||
|
||||
//strip.setPixelColor(light_id, RGBW32(red, green, blue, 0));
|
||||
setRealtimePixel(light_id, red, green, blue, 0);
|
||||
} // currently no support for interpolation or speed, we just ignore this
|
||||
} else if (input.startsWith(F("sync"))) {
|
||||
BobSync();
|
||||
} else {
|
||||
// Client sent gibberish
|
||||
DEBUG_PRINTLN(F("Client sent gibberish."));
|
||||
bobClient.stop();
|
||||
bobClient = bob->available();
|
||||
BobClear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
usermods/boblight/readme.md
Normal file
37
usermods/boblight/readme.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# BobLight usermod
|
||||
|
||||
This usermod allows displaying BobLight ambilight protocol on WLED device with a limited command set (not a full implementation).
|
||||
BobLight protocol uses a TCP connection which guarantees packet delivery at the possible expense of latency delays. It is not very efficient (as it uses plaintext comands) so is not suited for large number of LEDs.
|
||||
|
||||
This implementation is intended for TV backlight in combination with XBMC/Kodi BobLight add-on.
|
||||
|
||||
The LEDs can be configured in usermod settings page. The configuration is simple: you enter the number of LED pixels on each side of your TV (top, right, bottom, left).
|
||||
The LEDs should be wired in a clockwise orientation starting in the middle of bottom side (left half of bottom leds is where the string should start).
|
||||
|
||||
```
|
||||
+-------->-------+
|
||||
| |
|
||||
^ v
|
||||
| |
|
||||
+---<--+ ---<---+
|
||||
^
|
||||
start
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Add `-D USERMOD_BOBLIGHT` to your PlatformIO environment.
|
||||
If you are not using PlatformIO (which you should) try adding `#define USERMOD_BOBLIGHT` to *my_config.h*.
|
||||
|
||||
## Configuration
|
||||
|
||||
All parameters are runtime configurable though changing port may require reboot.
|
||||
|
||||
If you want to define default port during compile time use the following (default values in parentheses):
|
||||
|
||||
- `BOB_PORT=x` : defines default TCP port for usermod to listen on (19333)
|
||||
|
||||
|
||||
## Release notes
|
||||
|
||||
2022-11 Initial implementation by @blazoncek (AKA Blaz Kristan)
|
||||
@@ -1,13 +1,13 @@
|
||||
# MPU-6050 Six-Axis (Gyro + Accelerometer) Driver
|
||||
|
||||
This usermod-v2 modification allows the connection of a MPU-6050 IMU sensor to
|
||||
allow for effects that are controlled by the orientation or motion of the WLED Device.
|
||||
v2 of this usermod enables connection of a MPU-6050 IMU sensor to
|
||||
work with effects controlled by the orientation or motion of the WLED Device.
|
||||
|
||||
The MPU6050 has a built in "Digital Motion Processor" which does a lot of the heavy
|
||||
lifting in integrating the gyro and accel measurements to get potentially more
|
||||
The MPU6050 has a built in "Digital Motion Processor" which does the "heavy lifting"
|
||||
integrating the gyro and accelerometer measurements to get potentially more
|
||||
useful gravity vector and orientation output.
|
||||
|
||||
It is pretty straightforward to comment out some of the variables being read off the device if they're not needed to save CPU/Mem/Bandwidth.
|
||||
It is fairly straightforward to comment out variables being read from the device if they're not needed. Saves CPU/Memory/Bandwidth.
|
||||
|
||||
_Story:_
|
||||
|
||||
@@ -36,7 +36,7 @@ lib_deps =
|
||||
AsyncTCP@1.0.3
|
||||
Esp Async WebServer@1.2.0
|
||||
IRremoteESP8266@2.7.3
|
||||
I2Cdevlib-MPU6050@fbde122cc5
|
||||
jrowberg/I2Cdevlib-MPU6050@^1.0.0
|
||||
```
|
||||
|
||||
## Wiring
|
||||
@@ -78,7 +78,7 @@ to the info object
|
||||
## Usermod installation
|
||||
|
||||
1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory.
|
||||
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
|
||||
2. Register the usermod by adding `#include "usermod_mpu6050_imu.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
|
||||
|
||||
Example **usermods_list.cpp**:
|
||||
|
||||
|
||||
@@ -42,14 +42,6 @@
|
||||
#include "Wire.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define HW_PIN_SCL 22
|
||||
#define HW_PIN_SDA 21
|
||||
#else
|
||||
#define HW_PIN_SCL 5
|
||||
#define HW_PIN_SDA 4
|
||||
#endif
|
||||
|
||||
// ================================================================
|
||||
// === INTERRUPT DETECTION ROUTINE ===
|
||||
// ================================================================
|
||||
@@ -93,7 +85,7 @@ class MPU6050Driver : public Usermod {
|
||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||
*/
|
||||
void setup() {
|
||||
PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } };
|
||||
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
|
||||
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
|
||||
// join I2C bus (I2Cdev library doesn't do this automatically)
|
||||
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
|
||||
@@ -258,20 +250,20 @@ class MPU6050Driver : public Usermod {
|
||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void addToJsonState(JsonObject& root)
|
||||
{
|
||||
//void addToJsonState(JsonObject& root)
|
||||
//{
|
||||
//root["user0"] = userVar0;
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
/*
|
||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
||||
* Values in the state object may be modified by connected clients
|
||||
*/
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
//void readFromJsonState(JsonObject& root)
|
||||
//{
|
||||
//if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!"));
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
/*
|
||||
@@ -279,13 +271,13 @@ class MPU6050Driver : public Usermod {
|
||||
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject("MPU6050_IMU");
|
||||
JsonArray pins = top.createNestedArray("pin");
|
||||
pins.add(HW_PIN_SCL);
|
||||
pins.add(HW_PIN_SDA);
|
||||
}
|
||||
// void addToConfig(JsonObject& root)
|
||||
// {
|
||||
// JsonObject top = root.createNestedObject("MPU6050_IMU");
|
||||
// JsonArray pins = top.createNestedArray("pin");
|
||||
// pins.add(HW_PIN_SCL);
|
||||
// pins.add(HW_PIN_SDA);
|
||||
// }
|
||||
|
||||
/*
|
||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
# Multi Relay
|
||||
|
||||
This usermod-v2 modification allows the connection of multiple relays each with individual delay and on/off mode.
|
||||
This usermod-v2 modification allows the connection of multiple relays, each with individual delay and on/off mode.
|
||||
|
||||
## HTTP API
|
||||
All responses are returned as JSON.
|
||||
All responses are returned in JSON format.
|
||||
|
||||
* Status Request: `http://[device-ip]/relays`
|
||||
* Switch Command: `http://[device-ip]/relays?switch=1,0,1,1`
|
||||
|
||||
The number of numbers behind the switch parameter must correspond to the number of relays. The number 1 switches the relay on. The number 0 switches the relay off.
|
||||
The number of values behind the switch parameter must correspond to the number of relays. The value 1 switches the relay on, 0 switches it off.
|
||||
|
||||
* Toggle Command: `http://[device-ip]/relays?toggle=1,0,1,1`
|
||||
|
||||
The number of numbers behind the parameter switch must correspond to the number of relays. The number 1 causes a toggling of the relay. The number 0 leaves the state of the device.
|
||||
The number of values behind the parameter switch must correspond to the number of relays. The value 1 causes the relay to toggle, 0 leaves its state unchanged.
|
||||
|
||||
Examples
|
||||
Examples:
|
||||
1. total of 4 relays, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0`
|
||||
2. total of 3 relays, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1`
|
||||
|
||||
## JSON API
|
||||
You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json`
|
||||
|
||||
You can toggle the relay state by sending the following JSON object to: `http://[device-ip]/json`
|
||||
|
||||
Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}`
|
||||
|
||||
Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
|
||||
Switch relay 3 and 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}`
|
||||
|
||||
|
||||
## MQTT API
|
||||
|
||||
* `wled`/_deviceMAC_/`relay`/`0`/`command` `on`|`off`|`toggle`
|
||||
* `wled`/_deviceMAC_/`relay`/`1`/`command` `on`|`off`|`toggle`
|
||||
|
||||
When relay is switched it will publish a message:
|
||||
When a relay is switched, a message is published:
|
||||
|
||||
* `wled`/_deviceMAC_/`relay`/`0` `on`|`off`
|
||||
|
||||
@@ -42,7 +42,7 @@ When relay is switched it will publish a message:
|
||||
or
|
||||
2. Use `#define USERMOD_MULTI_RELAY` in wled.h or `-D USERMOD_MULTI_RELAY` in your platformio.ini
|
||||
|
||||
You can override the default maximum number (4) of relays by defining MULTI_RELAY_MAX_RELAYS.
|
||||
You can override the default maximum number of relays (which is 4) by defining MULTI_RELAY_MAX_RELAYS.
|
||||
|
||||
Example **usermods_list.cpp**:
|
||||
|
||||
@@ -78,14 +78,16 @@ void registerUsermods()
|
||||
|
||||
## Configuration
|
||||
|
||||
Usermod can be configured in Usermods settings page.
|
||||
Usermod can be configured via the Usermods settings page.
|
||||
|
||||
* `enabled` - enable/disable usermod
|
||||
* `pin` - GPIO pin where relay is attached to ESP
|
||||
* `pin` - ESP GPIO pin the relay is connected to (can be configured at compile time `-D MULTI_RELAY_PINS=xx,xx,...`)
|
||||
* `delay-s` - delay in seconds after on/off command is received
|
||||
* `active-high` - toggle high/low activation of relay (can be used to reverse relay states)
|
||||
* `external` - if enabled WLED does not control relay, it can only be triggered by external command (MQTT, HTTP, JSON or button)
|
||||
* `active-high` - assign high/low activation of relay (can be used to reverse relay states)
|
||||
* `external` - if enabled, WLED does not control relay, it can only be triggered by an external command (MQTT, HTTP, JSON or button)
|
||||
* `button` - button (from LED Settings) that controls this relay
|
||||
* `broadcast`- time in seconds between MQTT relay-state broadcasts
|
||||
* `HA-discovery`- enable Home Assistant auto discovery
|
||||
|
||||
If there is no MultiRelay section, just save current configuration and re-open Usermods settings page.
|
||||
|
||||
@@ -97,4 +99,4 @@ Have fun - @blazoncek
|
||||
|
||||
2021-11
|
||||
* Added information about dynamic configuration options
|
||||
* Added button support.
|
||||
* Added button support.
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#define MULTI_RELAY_MAX_RELAYS 4
|
||||
#endif
|
||||
|
||||
#ifndef MULTI_RELAY_PINS
|
||||
#define MULTI_RELAY_PINS -1
|
||||
#endif
|
||||
|
||||
#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing)
|
||||
|
||||
#define ON true
|
||||
@@ -76,7 +80,7 @@ class MultiRelay : public Usermod {
|
||||
void handleOffTimer() {
|
||||
unsigned long now = millis();
|
||||
bool activeRelays = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].active && _switchTimerStart > 0 && now - _switchTimerStart > (_relay[i].delay*1000)) {
|
||||
if (!_relay[i].external) toggleRelay(i);
|
||||
_relay[i].active = false;
|
||||
@@ -177,8 +181,9 @@ class MultiRelay : public Usermod {
|
||||
* constructor
|
||||
*/
|
||||
MultiRelay() {
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
_relay[i].pin = -1;
|
||||
const int8_t defPins[] = {MULTI_RELAY_PINS};
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;
|
||||
_relay[i].delay = 0;
|
||||
_relay[i].mode = false;
|
||||
_relay[i].active = false;
|
||||
@@ -221,7 +226,7 @@ class MultiRelay : public Usermod {
|
||||
|
||||
uint8_t getActiveRelayCount() {
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -263,7 +268,7 @@ class MultiRelay : public Usermod {
|
||||
strcat_P(subuf, PSTR("/relay/#"));
|
||||
mqtt->subscribe(subuf, 0);
|
||||
if (HAautodiscovery) publishHomeAssistantAutodiscovery();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0) continue;
|
||||
publishMqtt(i); //publish current state
|
||||
}
|
||||
@@ -271,7 +276,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
|
||||
void publishHomeAssistantAutodiscovery() {
|
||||
for (uint8_t i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
char uid[24], json_str[1024], buf[128];
|
||||
size_t payload_size;
|
||||
sprintf_P(uid, PSTR("%s_sw%d"), escapedMac.c_str(), i);
|
||||
@@ -288,8 +293,8 @@ class MultiRelay : public Usermod {
|
||||
|
||||
json[F("stat_t")] = "~";
|
||||
json[F("cmd_t")] = F("~/command");
|
||||
json[F("pl_off")] = F("off");
|
||||
json[F("pl_on")] = F("on");
|
||||
json[F("pl_off")] = "off";
|
||||
json[F("pl_on")] = "on";
|
||||
json[F("uniq_id")] = uid;
|
||||
|
||||
strcpy(buf, mqttDeviceTopic); //max length: 33 + 7 = 40
|
||||
@@ -315,7 +320,7 @@ class MultiRelay : public Usermod {
|
||||
*/
|
||||
void setup() {
|
||||
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0) continue;
|
||||
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
|
||||
_relay[i].pin = -1; // allocation failed
|
||||
@@ -352,7 +357,7 @@ class MultiRelay : public Usermod {
|
||||
if (_oldMode != offMode) {
|
||||
_oldMode = offMode;
|
||||
_switchTimerStart = millis();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
|
||||
}
|
||||
}
|
||||
@@ -377,7 +382,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b && _relay[i].external) {
|
||||
handled = true;
|
||||
}
|
||||
@@ -397,8 +402,8 @@ class MultiRelay : public Usermod {
|
||||
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
|
||||
|
||||
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b) {
|
||||
switchRelay(i, buttonPressedBefore[b]);
|
||||
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
|
||||
}
|
||||
@@ -445,8 +450,8 @@ class MultiRelay : public Usermod {
|
||||
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
|
||||
buttonWaitTime[b] = 0;
|
||||
//shortPressAction(b); //not exposed
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b) {
|
||||
toggleRelay(i);
|
||||
}
|
||||
}
|
||||
@@ -463,13 +468,17 @@ class MultiRelay : public Usermod {
|
||||
if (user.isNull())
|
||||
user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name)); //name
|
||||
infoArr.add(String(getActiveRelayCount()));
|
||||
infoArr.add(F(" relays"));
|
||||
|
||||
String uiDomString;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0 || !_relay[i].external) continue;
|
||||
uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
|
||||
uiDomString = F("Relay "); uiDomString += i;
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
|
||||
uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR(_relay_str);
|
||||
@@ -478,12 +487,10 @@ class MultiRelay : public Usermod {
|
||||
uiDomString += F(",on:");
|
||||
uiDomString += _relay[i].state ? "false" : "true";
|
||||
uiDomString += F("}});\">");
|
||||
uiDomString += F("Relay ");
|
||||
uiDomString += i;
|
||||
uiDomString += F(" <i class=\"icons\"></i></button>");
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
|
||||
infoArr.add(_relay[i].state ? "on" : "off");
|
||||
uiDomString += F("<i class=\"icons");
|
||||
uiDomString += _relay[i].state ? F(" on") : F(" off");
|
||||
uiDomString += F("\"></i></button>");
|
||||
infoArr.add(uiDomString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,7 +507,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
#if MULTI_RELAY_MAX_RELAYS > 1
|
||||
JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin < 0) continue;
|
||||
JsonObject relay = rel_arr.createNestedObject();
|
||||
relay[FPSTR(_relay_str)] = i;
|
||||
@@ -520,14 +527,24 @@ class MultiRelay : public Usermod {
|
||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod["on"].is<bool>() && usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(usermod[FPSTR(_relay_str)].as<int>(), usermod["on"].as<bool>());
|
||||
if (usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
int rly = usermod[FPSTR(_relay_str)].as<int>();
|
||||
if (usermod["on"].is<bool>()) {
|
||||
switchRelay(rly, usermod["on"].as<bool>());
|
||||
} else if (usermod["on"].is<const char*>() && usermod["on"].as<const char*>()[0] == 't') {
|
||||
toggleRelay(rly);
|
||||
}
|
||||
}
|
||||
} else if (root[FPSTR(_name)].is<JsonArray>()) {
|
||||
JsonArray relays = root[FPSTR(_name)].as<JsonArray>();
|
||||
for (JsonVariant r : relays) {
|
||||
if (r["on"].is<bool>() && r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(r[FPSTR(_relay_str)].as<int>(), r["on"].as<bool>());
|
||||
if (r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
int rly = r[FPSTR(_relay_str)].as<int>();
|
||||
if (r["on"].is<bool>()) {
|
||||
switchRelay(rly, r["on"].as<bool>());
|
||||
} else if (r["on"].is<const char*>() && r["on"].as<const char*>()[0] == 't') {
|
||||
toggleRelay(rly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,7 +558,7 @@ class MultiRelay : public Usermod {
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_broadcast)] = periodicBroadcastSec;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
JsonObject relay = top.createNestedObject(parName);
|
||||
relay["pin"] = _relay[i].pin;
|
||||
@@ -575,7 +592,7 @@ class MultiRelay : public Usermod {
|
||||
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
|
||||
HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery;
|
||||
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
oldPin[i] = _relay[i].pin;
|
||||
_relay[i].pin = top[parName]["pin"] | _relay[i].pin;
|
||||
@@ -599,12 +616,12 @@ class MultiRelay : public Usermod {
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
// deallocate all pins 1st
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
|
||||
if (oldPin[i]>=0) {
|
||||
pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay);
|
||||
}
|
||||
// allocate new pins
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
|
||||
if (!_relay[i].external) {
|
||||
_relay[i].state = !offMode;
|
||||
@@ -619,7 +636,7 @@ class MultiRelay : public Usermod {
|
||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||
}
|
||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||
return !top[FPSTR(_broadcast)].isNull();
|
||||
return !top[FPSTR(_HAautodiscovery)].isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Photoresister sensor with MQTT
|
||||
|
||||
This simple usermod allows attaching a photoresistor sensor like the KY-018 and publish the readings in percentage over MQTT. The frequency of MQTT messages can be modified, and there is a threshold value that can be set so that significant changes in the readings can be published immediately instead of waiting for the next update. This was found to be a good compromise between spamming MQTT messages and delayed updates.
|
||||
Enables attaching a photoresistor sensor like the KY-018 and publishing the readings as a percentage, via MQTT. The frequency of MQTT messages is user definable.
|
||||
A threshold value can be set so significant changes in the readings are published immediately vice waiting for the next update. This was found to be a good compromise between excessive MQTT traffic and delayed updates.
|
||||
|
||||
I also found it useful to limit the frequency of analog pin reads because otherwise the board hangs.
|
||||
I also found it useful to limit the frequency of analog pin reads, otherwise the board hangs.
|
||||
|
||||
This usermod has only been tested with the KY-018 sensor though should work for any other analog pin sensor. Note that this does not control the LED strip directly, it only publishes MQTT readings for use with other integrations like Home Assistant.
|
||||
This usermod has only been tested with the KY-018 sensor though it should work for any other analog pin sensor.
|
||||
Note: this does not control the LED strip directly, it only publishes MQTT readings for use with other integrations like Home Assistant.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
### Shift Light for Project Cars
|
||||
|
||||
Turn your WLED lights into a rev light and shift indicator for Project Cars.
|
||||
It's easy to use.
|
||||
|
||||
It is pretty straight forward to use.
|
||||
1. Make sure your WLED device and your PC/console are on the same network and can talk to each other
|
||||
|
||||
1. Make sure, your WLED device and your PC/console are on the same network and can talk to each other
|
||||
|
||||
2. Go to the gameplay settings menu in PCARS and enable UDP. There are 9 numbers you can choose from. This is the refresh rate. The lower the number, the better. But you might run into problems at faster rates.
|
||||
2. Go to the gameplay settings menu in PCARS and enable UDP. There are 9 numbers you can choose from. This is the refresh rate. The lower the number, the better. However, you might run into problems at faster rates.
|
||||
|
||||
| Number | Updates/Second |
|
||||
| ------ | -------------- |
|
||||
@@ -20,4 +19,5 @@ It is pretty straight forward to use.
|
||||
| 8 | 05 |
|
||||
| 9 | 1 |
|
||||
|
||||
3. once you enter a race, WLED should automatically shift to PCARS mode. Done.
|
||||
3. Once you enter a race, WLED should automatically shift to PCARS mode.
|
||||
4. Done.
|
||||
|
||||
27
usermods/pwm_outputs/readme.md
Normal file
27
usermods/pwm_outputs/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# PWM outputs
|
||||
|
||||
v2 Usermod to add generic PWM outputs to WLED. Usermode could be used to control servo motors, LED brightness or any other device controlled by PWM signal.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_PWM_OUTPUTS` to your `platformio.ini` (or `platformio_override.ini`). By default upt to 3 PWM outputs could be configured, to increase that limit add build argument `-D USERMOD_PWM_OUTPUT_PINS=10` (replace 10 by desired amount).
|
||||
|
||||
Currently only ESP32 is supported.
|
||||
|
||||
## Configuration
|
||||
|
||||
By default PWM outputs are disabled, navigate to Usermods settings and configure desired PWM pins and frequencies.
|
||||
|
||||
## Usage
|
||||
|
||||
If PWM output is configured, it starts to publish its duty cycle value (0-1) both to state JSON and to info JSON (visible in UI info panel). To set PWM duty cycle, use JSON api (over HTTP or over Serial)
|
||||
|
||||
```json
|
||||
{
|
||||
"pwm": {
|
||||
"0": {"duty": 0.1},
|
||||
"1": {"duty": 0.2},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
221
usermods/pwm_outputs/usermod_pwm_outputs.h
Normal file
221
usermods/pwm_outputs/usermod_pwm_outputs.h
Normal file
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef ESP32
|
||||
#error This usermod does not support the ESP8266.
|
||||
#endif
|
||||
|
||||
#ifndef USERMOD_PWM_OUTPUT_PINS
|
||||
#define USERMOD_PWM_OUTPUT_PINS 3
|
||||
#endif
|
||||
|
||||
|
||||
class PwmOutput {
|
||||
public:
|
||||
|
||||
void open(int8_t pin, uint32_t freq) {
|
||||
|
||||
if (enabled_) {
|
||||
if (pin == pin_ && freq == freq_) {
|
||||
return; // PWM output is already open
|
||||
} else {
|
||||
close(); // Config has changed, close and reopen
|
||||
}
|
||||
}
|
||||
|
||||
pin_ = pin;
|
||||
freq_ = freq;
|
||||
if (pin_ < 0)
|
||||
return;
|
||||
|
||||
DEBUG_PRINTF("pwm_output[%d]: setup to freq %d\n", pin_, freq_);
|
||||
if (!pinManager.allocatePin(pin_, true, PinOwner::UM_PWM_OUTPUTS))
|
||||
return;
|
||||
|
||||
channel_ = pinManager.allocateLedc(1);
|
||||
if (channel_ == 255) {
|
||||
DEBUG_PRINTF("pwm_output[%d]: failed to quire ledc\n", pin_);
|
||||
pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS);
|
||||
return;
|
||||
}
|
||||
|
||||
ledcSetup(channel_, freq_, bit_depth_);
|
||||
ledcAttachPin(pin_, channel_);
|
||||
DEBUG_PRINTF("pwm_output[%d]: init successful\n", pin_);
|
||||
enabled_ = true;
|
||||
}
|
||||
|
||||
void close() {
|
||||
DEBUG_PRINTF("pwm_output[%d]: close\n", pin_);
|
||||
if (!enabled_)
|
||||
return;
|
||||
pinManager.deallocatePin(pin_, PinOwner::UM_PWM_OUTPUTS);
|
||||
if (channel_ != 255)
|
||||
pinManager.deallocateLedc(channel_, 1);
|
||||
channel_ = 255;
|
||||
duty_ = 0.0f;
|
||||
enabled_ = false;
|
||||
}
|
||||
|
||||
void setDuty(const float duty) {
|
||||
DEBUG_PRINTF("pwm_output[%d]: set duty %f\n", pin_, duty);
|
||||
if (!enabled_)
|
||||
return;
|
||||
duty_ = min(1.0f, max(0.0f, duty));
|
||||
const uint32_t value = static_cast<uint32_t>((1 << bit_depth_) * duty_);
|
||||
ledcWrite(channel_, value);
|
||||
}
|
||||
|
||||
void setDuty(const uint16_t duty) {
|
||||
setDuty(static_cast<float>(duty) / 65535.0f);
|
||||
}
|
||||
|
||||
bool isEnabled() const {
|
||||
return enabled_;
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject& pwmState) const {
|
||||
pwmState[F("duty")] = duty_;
|
||||
}
|
||||
|
||||
void readFromJsonState(JsonObject& pwmState) {
|
||||
if (pwmState.isNull()) {
|
||||
return;
|
||||
}
|
||||
float duty;
|
||||
if (getJsonValue(pwmState[F("duty")], duty)) {
|
||||
setDuty(duty);
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& user) const {
|
||||
if (!enabled_)
|
||||
return;
|
||||
char buffer[12];
|
||||
sprintf_P(buffer, PSTR("PWM pin %d"), pin_);
|
||||
JsonArray data = user.createNestedArray(buffer);
|
||||
data.add(1e2f * duty_);
|
||||
data.add(F("%"));
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& pwmConfig) const {
|
||||
pwmConfig[F("pin")] = pin_;
|
||||
pwmConfig[F("freq")] = freq_;
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& pwmConfig) {
|
||||
if (pwmConfig.isNull())
|
||||
return false;
|
||||
|
||||
bool configComplete = true;
|
||||
int8_t newPin = pin_;
|
||||
uint32_t newFreq = freq_;
|
||||
configComplete &= getJsonValue(pwmConfig[F("pin")], newPin);
|
||||
configComplete &= getJsonValue(pwmConfig[F("freq")], newFreq);
|
||||
|
||||
open(newPin, newFreq);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
private:
|
||||
int8_t pin_ {-1};
|
||||
uint32_t freq_ {50};
|
||||
static const uint8_t bit_depth_ {12};
|
||||
uint8_t channel_ {255};
|
||||
float duty_ {0.0f};
|
||||
bool enabled_ {false};
|
||||
};
|
||||
|
||||
|
||||
class PwmOutputsUsermod : public Usermod {
|
||||
public:
|
||||
|
||||
static const char USERMOD_NAME[];
|
||||
static const char PWM_STATE_NAME[];
|
||||
|
||||
void setup() {
|
||||
// By default all PWM outputs are disabled, no setup do be done
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
||||
void addToJsonState(JsonObject& root) {
|
||||
JsonObject pwmStates = root.createNestedObject(PWM_STATE_NAME);
|
||||
for (int i = 0; i < USERMOD_PWM_OUTPUT_PINS; i++) {
|
||||
const PwmOutput& pwm = pwms_[i];
|
||||
if (!pwm.isEnabled())
|
||||
continue;
|
||||
char buffer[4];
|
||||
sprintf_P(buffer, PSTR("%d"), i);
|
||||
JsonObject pwmState = pwmStates.createNestedObject(buffer);
|
||||
pwm.addToJsonState(pwmState);
|
||||
}
|
||||
}
|
||||
|
||||
void readFromJsonState(JsonObject& root) {
|
||||
JsonObject pwmStates = root[PWM_STATE_NAME];
|
||||
if (pwmStates.isNull())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < USERMOD_PWM_OUTPUT_PINS; i++) {
|
||||
PwmOutput& pwm = pwms_[i];
|
||||
if (!pwm.isEnabled())
|
||||
continue;
|
||||
char buffer[4];
|
||||
sprintf_P(buffer, PSTR("%d"), i);
|
||||
JsonObject pwmState = pwmStates[buffer];
|
||||
pwm.readFromJsonState(pwmState);
|
||||
}
|
||||
}
|
||||
|
||||
void addToJsonInfo(JsonObject& root) {
|
||||
JsonObject user = root[F("u")];
|
||||
if (user.isNull())
|
||||
user = root.createNestedObject(F("u"));
|
||||
|
||||
for (int i = 0; i < USERMOD_PWM_OUTPUT_PINS; i++) {
|
||||
const PwmOutput& pwm = pwms_[i];
|
||||
pwm.addToJsonInfo(user);
|
||||
}
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root) {
|
||||
JsonObject top = root.createNestedObject(USERMOD_NAME);
|
||||
for (int i = 0; i < USERMOD_PWM_OUTPUT_PINS; i++) {
|
||||
const PwmOutput& pwm = pwms_[i];
|
||||
char buffer[8];
|
||||
sprintf_P(buffer, PSTR("PWM %d"), i);
|
||||
JsonObject pwmConfig = top.createNestedObject(buffer);
|
||||
pwm.addToConfig(pwmConfig);
|
||||
}
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject& root) {
|
||||
JsonObject top = root[USERMOD_NAME];
|
||||
if (top.isNull())
|
||||
return false;
|
||||
|
||||
bool configComplete = true;
|
||||
for (int i = 0; i < USERMOD_PWM_OUTPUT_PINS; i++) {
|
||||
PwmOutput& pwm = pwms_[i];
|
||||
char buffer[8];
|
||||
sprintf_P(buffer, PSTR("PWM %d"), i);
|
||||
JsonObject pwmConfig = top[buffer];
|
||||
configComplete &= pwm.readFromConfig(pwmConfig);
|
||||
}
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_PWM_OUTPUTS;
|
||||
}
|
||||
|
||||
private:
|
||||
PwmOutput pwms_[USERMOD_PWM_OUTPUT_PINS];
|
||||
|
||||
};
|
||||
|
||||
const char PwmOutputsUsermod::USERMOD_NAME[] PROGMEM = "PwmOutputs";
|
||||
const char PwmOutputsUsermod::PWM_STATE_NAME[] PROGMEM = "pwm";
|
||||
@@ -1,5 +1,5 @@
|
||||
# QuinLED-An-Penta
|
||||
The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), like using the OLED and the SHT30 temperature/humidity sensor.
|
||||
The (un)official usermod to get the best out of the QuinLED-An-Penta (https://quinled.info/quinled-an-penta/), e.g. using the OLED and the SHT30 temperature/humidity sensor.
|
||||
|
||||
## Requirements
|
||||
* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2
|
||||
@@ -31,15 +31,15 @@ lib_deps = ${esp32.lib_deps}
|
||||
## Some words about the (optional) OLED
|
||||
This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller OLED or an OLED using a different driver will result in unexpected results.
|
||||
I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED.
|
||||
Also note, you need to have an **SPI** driven OLED, **not i2c**!
|
||||
Note: you _must_ use an **SPI** driven OLED, **not an i2c one**!
|
||||
|
||||
### Limitations combined with Ethernet
|
||||
The initial development of this mod had been done with a beta version of the QuinLED-An-Penta, which had a different IO layout for the OLED: The CS pin used to be IO_0, but has been changed to IO27 with the first v1 public release. Unfortunately, IO27 is used by the Ethernet boards, so WLED will not let you enable the OLED screen, if you're using it with Ethernet. This unfortunately makes the development I've done to support/show Ethernet information void, as it cannot be used.
|
||||
However (and I've not tried this, as I don't own a v1 board): You can try to modify this mod and try to use IO27 for the OLED and share it with the Ethernet board. It is "just" the chip select pin, so there is a chance that both can coexist and use the same IO. You need to skip WLEDs PinManager for the CS pin, so WLED will not block using it. If you don't know how this works: Leave it. If you know what I'm talking about: Try it and please let me know on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
The initial development of this mod was done with a beta version of the QuinLED-An-Penta, which had a different IO layout for the OLED: The CS pin _was_ IO_0, but has been changed to IO27 with the first v1 public release. Unfortunately, IO27 is used by Ethernet boards, so WLED will not let you enable the OLED screen, if you're using it with Ethernet. Unfortunately, that makes the development I've done to support/show Ethernet information invalid, as it cannot be used.
|
||||
However, (and I've not tried this, as I don't own a v1 board) you can modify this usermod and try to use IO27 for the OLED and share it with the Ethernet board. It is "just" the chip select pin, so there is a chance that both can coexist and use the same IO. You need to skip WLEDs PinManager for the CS pin, so WLED will not block using it. If you don't know how this works, don't change it. If you know what I'm talking about, try it and please let me know on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
|
||||
### My OLED flickers after some time, what should I do?
|
||||
That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose its settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display.
|
||||
If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display.
|
||||
That's a tricky one. During development I saw that the OLED sometimes starts to "drop out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to lose its settings then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which re-initializes the display.
|
||||
If you're facing this issue, you can enable a setting which will call the `begin()` roughly every 60 seconds between page changes. This will make the page change take ~500ms, but will fix the display.
|
||||
|
||||
|
||||
## Configuration
|
||||
@@ -53,11 +53,11 @@ Navigate to the "Config" and then to the "Usermods" section. If you compiled WLE
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* OLED-Flip-Screen-180:
|
||||
* What it does: Flips the screen 180° / upside-down
|
||||
* What it does: Flips the screen 180°
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
* OLED-Seconds-Per-Page:
|
||||
* What it does: Defines how long the OLED should stay on one page in seconds before changing to the next
|
||||
* What it does: Number of seconds the OLED should stay on one page before changing pages
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: 10
|
||||
* OLED-Fix-Bugged-Screen:
|
||||
@@ -76,4 +76,4 @@ Navigate to the "Config" and then to the "Usermods" section. If you compiled WLE
|
||||
* First implementation.
|
||||
|
||||
## Credits
|
||||
ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
This folder serves as a repository for usermods (custom `usermod.cpp` files)!
|
||||
|
||||
If you have created an usermod that you believe is useful (for example to support a particular sensor, display, feature...), feel free to contribute by opening a pull request!
|
||||
If you have created a usermod you believe is useful (for example to support a particular sensor, display, feature...), feel free to contribute by opening a pull request!
|
||||
|
||||
In order for other people to be able to have fun with your usermod, please keep these points in mind:
|
||||
|
||||
- Create a folder in this folder with a descriptive name (for example `usermod_ds18b20_temp_sensor_mqtt`)
|
||||
- Include your custom files
|
||||
- If your usermod requires changes to other WLED files, please write a `readme.md` outlining the steps one has to take to use the usermod
|
||||
- If your usermod requires changes to other WLED files, please write a `readme.md` outlining the steps one needs to take
|
||||
- Create a pull request!
|
||||
- If your feature is useful for the majority of WLED users, I will consider adding it to the base code!
|
||||
|
||||
While I do my best to not break too much, keep in mind that as WLED is being updated, usermods might break.
|
||||
While I do my best to not break too much, keep in mind that as WLED is updated, usermods might break.
|
||||
I am not actively maintaining any usermod in this directory, that is your responsibility as the creator of the usermod.
|
||||
|
||||
For new usermods, I would recommend trying out the new v2 usermod API, which allows installing multiple usermods at once and new functions!
|
||||
|
||||
@@ -5,8 +5,8 @@ This usermod-v2 adds support for the awesome RGB Rotary Encoder Board by Adam Ze
|
||||
https://user-images.githubusercontent.com/3090131/124680599-0180ab80-dec7-11eb-9065-a6d08ebe0287.mp4
|
||||
|
||||
## Credits
|
||||
The actual / original code that does the different LED modes is from Adam Zeloof. So I don't take credit for these. But I ported it to WLED, which involved replacing the LED library he used (because, guess what, WLED already has one; so no need to add another one, but use whatever WLED uses), plus the rotary encoder library, because that one was not compatible with ESP, only Arduino.
|
||||
So it was quite more work than I hoped, but I got there eventually :)
|
||||
The actual / original code that controls the LED modes is from Adam Zeloof. I take no credit for it. I ported it to WLED, which involved replacing the LED library he used, (because WLED already has one, so no need to add another one) plus the rotary encoder library because it was not compatible with ESP, only Arduino.
|
||||
It was quite a bit more work than I hoped, but I got there eventually :)
|
||||
|
||||
## Requirements
|
||||
* "ESP Rotary" by Lennart Hennigs, v1.5.0 or higher: https://github.com/LennartHennigs/ESPRotary
|
||||
@@ -33,25 +33,25 @@ lib_deps = ${esp8266.lib_deps}
|
||||
```
|
||||
|
||||
## How to connect the board to your ESP
|
||||
We gonna need (minimum) three or (maximum) four GPIOs for the board:
|
||||
* "ea": Basically tells if the encoder goes into one or the other direction
|
||||
* "eb": Same thing, but the other direction
|
||||
* "di": LED data in. To actually control the LEDs
|
||||
* *(optional)* "sw": The integrated switch in the rotary encoder. Can be omitted for the bare functionality of just controlling the brightness
|
||||
We'll need (minimum) three or (maximum) four GPIOs for the board:
|
||||
* "ea": reports the encoder direction
|
||||
* "eb": Same thing, opposite direction
|
||||
* "di": LED data in.
|
||||
* *(optional)* "sw": The integrated switch in the rotary encoder. Can be omitted for the bare functionality of controlling only the brightness
|
||||
|
||||
We also gonna need some power, so:
|
||||
We'll also need power:
|
||||
|
||||
* "vdd": Needs to be connected to **+5V**.
|
||||
* "gnd": Well, it's GND.
|
||||
* "gnd": Ground.
|
||||
|
||||
You can freely pick the GPIOs, it doesn't matter. Those will be configured in the "Usermods" section in the WLED web panel:
|
||||
You can freely pick the GPIOs, it doesn't matter. Those will be configured in the "Usermods" section of the WLED web panel:
|
||||
|
||||
## Configuration
|
||||
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D RGB_ROTARY_ENCODER`, you will see the config for it there. The settings there are the GPIOs we mentioned before (*Note: The switch pin is not there, as this can just be configured the "normal" button on the "LED Preferences" page*), plus a few more:
|
||||
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D RGB_ROTARY_ENCODER`, you will see the config for it there. The settings there are the aforementioned GPIOs, (*Note: The switch pin is not there, as this can just be configured the "normal" button on the "LED Preferences" page*) plus a few more:
|
||||
* LED pin:
|
||||
* Possible values: Any valid and available GPIO
|
||||
* Default: 3
|
||||
* What it does: Pin to control the LED ring
|
||||
* What it does: controls the LED ring
|
||||
* ea pin:
|
||||
* Possible values: Any valid and available GPIO
|
||||
* Default: 15
|
||||
@@ -63,7 +63,7 @@ Navigate to the "Config" and then to the "Usermods" section. If you compiled WLE
|
||||
* LED Mode:
|
||||
* Possible values: 1-3
|
||||
* Default: 3
|
||||
* What it does: The usermod provides three different modes of how the LEDs can look like. Here's an example: https://github.com/isotope-engineering/RGB-Encoder-Board/blob/master/images/rgb-encoder-animations.gif
|
||||
* What it does: The usermod provides three different modes of how the LEDs can appear. Here's an example: https://github.com/isotope-engineering/RGB-Encoder-Board/blob/master/images/rgb-encoder-animations.gif
|
||||
* Up left is "1"
|
||||
* Up right is not supported / doesn't make sense for brightness control
|
||||
* Bottom left is "2"
|
||||
@@ -71,15 +71,15 @@ Navigate to the "Config" and then to the "Usermods" section. If you compiled WLE
|
||||
* LED Brightness:
|
||||
* Possible values: 1-255
|
||||
* Default: 64
|
||||
* What it does: Brightness of the LED ring
|
||||
* What it does: sets LED ring Brightness
|
||||
* Steps per click:
|
||||
* Possible values: Any positive number
|
||||
* Default: 4
|
||||
* What it does: With each "click", a rotary encoder actually increments it's "steps". Most rotary encoder do four "steps" per "click". I know this sounds super weird, so just leave this the default value, unless your rotary encoder behaves weirdly, like with one click, it makes two LEDs light up, or you sometimes need two click for one LED. Then you should play around with this value or write a small sketch using the same "ESP Rotary" library and read out the steps it does.
|
||||
* What it does: With each "click", a rotary encoder actually increments its "steps". Most rotary encoders produce four "steps" per "click". Leave this at the default value unless your rotary encoder behaves strangely. e.g. with one click, it makes two LEDs light up, or you need two clicks for one LED. If that's the case, adjust this value or write a small sketch using the same "ESP Rotary" library and read out the steps it produce.
|
||||
* Increment per click:
|
||||
* Possible values: Any positive number
|
||||
* Default: 5
|
||||
* What it does: Most rotary encoder have 20 "clicks", so basically 20 positions. This value should be set to 100 / `number of clicks`
|
||||
* What it does: Most rotary encoders have 20 "clicks" or positions. This value should be set to 100/`number of clicks`
|
||||
|
||||
## Change log
|
||||
2021-07
|
||||
|
||||
34
usermods/sd_card/readme.md
Normal file
34
usermods/sd_card/readme.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# SD-card mod
|
||||
|
||||
## Build
|
||||
- modify `platformio.ini` and add to the `build_flags` of your configuration the following
|
||||
- choose the way your SD is connected
|
||||
1. via `-D WLED_USE_SD_MMC` when connected via MMC
|
||||
2. via `-D WLED_USE_SD_SPI` when connected via SPI (use usermod page to setup SPI pins)
|
||||
|
||||
### Test
|
||||
- enable `-D SD_PRINT_HOME_DIR` and `-D WLED_DEBUG`
|
||||
- this will print all files in `/` on boot via serial
|
||||
|
||||
## Configuration
|
||||
### MMC
|
||||
- The MMC port / pins needs no configuration as they are specified by Espressif
|
||||
### SPI
|
||||
- The SPI port / pins can be modified via the WLED web-UI: `Config → Usermod → SD Card`
|
||||
| option | effect | default |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------ | ------- |
|
||||
| `pinSourceSelect` | GPIO that is connected to SD's `SS`(source select) / `CS`(chip select) | 16 |
|
||||
| `pinSourceClock` | GPIO that is connected to SD's `SCLK` (source clock) / `CLK`(clock) | 14 |
|
||||
| `pinPoci` | GPIO that is connected to SD's `POCI`<sup>☨</sup> (Peripheral-Out-Ctrl-In) / `MISO` (deprecated) | 36 |
|
||||
| `pinPico` | GPIO that is connected to SD's `PICO`<sup>☨</sup> (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 14 |
|
||||
| `sdEnable` | Enable to read data from the SD-card | true |
|
||||
|
||||
<sup>☨</sup><sub>Following new naming convention of [OSHWA](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/)</sub>
|
||||
|
||||
## Usage in other mods
|
||||
- creates a macro `SD_ADAPTER` which is either mapped to `SD` or `SD_MMC` (see `SD_Test.ino` how to use SD / SD_MMC functions)
|
||||
|
||||
- checks if the specified file is available on the SD card
|
||||
```cpp
|
||||
bool file_onSD(const char *filepath) {...}
|
||||
```
|
||||
243
usermods/sd_card/usermod_sd_card.h
Normal file
243
usermods/sd_card/usermod_sd_card.h
Normal file
@@ -0,0 +1,243 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
|
||||
// SD connected via MMC / SPI
|
||||
#if defined(WLED_USE_SD_MMC)
|
||||
#define USED_STORAGE_FILESYSTEMS "SD MMC, LittleFS"
|
||||
#define SD_ADAPTER SD_MMC
|
||||
#include "SD_MMC.h"
|
||||
// SD connected via SPI (adjustable via usermod config)
|
||||
#elif defined(WLED_USE_SD_SPI)
|
||||
#define SD_ADAPTER SD
|
||||
#define USED_STORAGE_FILESYSTEMS "SD SPI, LittleFS"
|
||||
#include "SD.h"
|
||||
#include "SPI.h"
|
||||
#endif
|
||||
|
||||
#ifdef WLED_USE_SD_MMC
|
||||
#elif defined(WLED_USE_SD_SPI)
|
||||
SPIClass spiPort = SPIClass(VSPI);
|
||||
#endif
|
||||
|
||||
void listDir( const char * dirname, uint8_t levels);
|
||||
|
||||
class UsermodSdCard : public Usermod {
|
||||
private:
|
||||
bool sdInitDone = false;
|
||||
|
||||
#ifdef WLED_USE_SD_SPI
|
||||
int8_t configPinSourceSelect = 16;
|
||||
int8_t configPinSourceClock = 14;
|
||||
int8_t configPinPoci = 36; // confusing names? Then have a look :)
|
||||
int8_t configPinPico = 15; // https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/
|
||||
|
||||
//acquired and initialize the SPI port
|
||||
void init_SD_SPI()
|
||||
{
|
||||
if(!configSdEnabled) return;
|
||||
if(sdInitDone) return;
|
||||
|
||||
PinManagerPinType pins[5] = {
|
||||
{ configPinSourceSelect, true },
|
||||
{ configPinSourceClock, true },
|
||||
{ configPinPoci, false },
|
||||
{ configPinPico, true }
|
||||
};
|
||||
|
||||
if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) {
|
||||
DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name);
|
||||
sdInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool returnOfInitSD = false;
|
||||
|
||||
#if defined(WLED_USE_SD_SPI)
|
||||
spiPort.begin(configPinSourceClock, configPinPoci, configPinPico, configPinSourceSelect);
|
||||
returnOfInitSD = SD_ADAPTER.begin(configPinSourceSelect, spiPort);
|
||||
#endif
|
||||
|
||||
if(!returnOfInitSD) {
|
||||
DEBUG_PRINTF("[%s] SPI begin failed!\n", _name);
|
||||
sdInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
sdInitDone = true;
|
||||
}
|
||||
|
||||
//deinitialize the acquired SPI port
|
||||
void deinit_SD_SPI()
|
||||
{
|
||||
if(!sdInitDone) return;
|
||||
|
||||
SD_ADAPTER.end();
|
||||
|
||||
DEBUG_PRINTF("[%s] deallocate pins!\n", _name);
|
||||
pinManager.deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard);
|
||||
pinManager.deallocatePin(configPinSourceClock, PinOwner::UM_SdCard);
|
||||
pinManager.deallocatePin(configPinPoci, PinOwner::UM_SdCard);
|
||||
pinManager.deallocatePin(configPinPico, PinOwner::UM_SdCard);
|
||||
|
||||
sdInitDone = false;
|
||||
}
|
||||
|
||||
// some SPI pin was changed, while SPI was initialized, reinit to new port
|
||||
void reinit_SD_SPI()
|
||||
{
|
||||
deinit_SD_SPI();
|
||||
init_SD_SPI();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WLED_USE_SD_MMC
|
||||
void init_SD_MMC() {
|
||||
if(sdInitDone) return;
|
||||
bool returnOfInitSD = false;
|
||||
returnOfInitSD = SD_ADAPTER.begin();
|
||||
DEBUG_PRINTF("[%s] MMC begin\n", _name);
|
||||
|
||||
if(!returnOfInitSD) {
|
||||
DEBUG_PRINTF("[%s] MMC begin failed!\n", _name);
|
||||
sdInitDone = false;
|
||||
return;
|
||||
}
|
||||
|
||||
sdInitDone = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
static bool configSdEnabled;
|
||||
static const char _name[];
|
||||
|
||||
void setup() {
|
||||
DEBUG_PRINTF("[%s] usermod loaded \n", _name);
|
||||
#if defined(WLED_USE_SD_SPI)
|
||||
init_SD_SPI();
|
||||
#elif defined(WLED_USE_SD_MMC)
|
||||
init_SD_MMC();
|
||||
#endif
|
||||
|
||||
#if defined(SD_ADAPTER) && defined(SD_PRINT_HOME_DIR)
|
||||
listDir("/", 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop(){
|
||||
|
||||
}
|
||||
|
||||
uint16_t getId()
|
||||
{
|
||||
return USERMOD_ID_SD_CARD;
|
||||
}
|
||||
|
||||
void addToConfig(JsonObject& root)
|
||||
{
|
||||
#ifdef WLED_USE_SD_SPI
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top["pinSourceSelect"] = configPinSourceSelect;
|
||||
top["pinSourceClock"] = configPinSourceClock;
|
||||
top["pinPoci"] = configPinPoci;
|
||||
top["pinPico"] = configPinPico;
|
||||
top["sdEnabled"] = configSdEnabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
#ifdef WLED_USE_SD_SPI
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t oldPinSourceSelect = configPinSourceSelect;
|
||||
uint8_t oldPinSourceClock = configPinSourceClock;
|
||||
uint8_t oldPinPoci = configPinPoci;
|
||||
uint8_t oldPinPico = configPinPico;
|
||||
bool oldSdEnabled = configSdEnabled;
|
||||
|
||||
getJsonValue(top["pinSourceSelect"], configPinSourceSelect);
|
||||
getJsonValue(top["pinSourceClock"], configPinSourceClock);
|
||||
getJsonValue(top["pinPoci"], configPinPoci);
|
||||
getJsonValue(top["pinPico"], configPinPico);
|
||||
getJsonValue(top["sdEnabled"], configSdEnabled);
|
||||
|
||||
if(configSdEnabled != oldSdEnabled) {
|
||||
configSdEnabled ? init_SD_SPI() : deinit_SD_SPI();
|
||||
DEBUG_PRINTF("[%s] SD card %s\n", _name, configSdEnabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
if( configSdEnabled && (
|
||||
oldPinSourceSelect != configPinSourceSelect ||
|
||||
oldPinSourceClock != configPinSourceClock ||
|
||||
oldPinPoci != configPinPoci ||
|
||||
oldPinPico != configPinPico)
|
||||
)
|
||||
{
|
||||
DEBUG_PRINTF("[%s] Init SD card based of config\n", _name);
|
||||
DEBUG_PRINTF("[%s] Config changes \n - SS: %d -> %d\n - MI: %d -> %d\n - MO: %d -> %d\n - En: %d -> %d\n", _name, oldPinSourceSelect, configPinSourceSelect, oldPinSourceClock, configPinSourceClock, oldPinPoci, configPinPoci, oldPinPico, configPinPico);
|
||||
reinit_SD_SPI();
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const char UsermodSdCard::_name[] PROGMEM = "SD Card";
|
||||
bool UsermodSdCard::configSdEnabled = true;
|
||||
|
||||
#ifdef SD_ADAPTER
|
||||
//checks if the file is available on SD card
|
||||
bool file_onSD(const char *filepath)
|
||||
{
|
||||
#ifdef WLED_USE_SD_SPI
|
||||
if(!UsermodSdCard::configSdEnabled) return false;
|
||||
#endif
|
||||
|
||||
uint8_t cardType = SD_ADAPTER.cardType();
|
||||
if(cardType == CARD_NONE) {
|
||||
DEBUG_PRINTF("[%s] not attached / cardType none\n", UsermodSdCard::_name);
|
||||
return false; // no SD card attached
|
||||
}
|
||||
if(cardType == CARD_MMC || cardType == CARD_SD || cardType == CARD_SDHC)
|
||||
{
|
||||
return SD_ADAPTER.exists(filepath);
|
||||
}
|
||||
|
||||
return false; // unknown card type
|
||||
}
|
||||
|
||||
void listDir( const char * dirname, uint8_t levels){
|
||||
DEBUG_PRINTF("Listing directory: %s\n", dirname);
|
||||
|
||||
File root = SD_ADAPTER.open(dirname);
|
||||
if(!root){
|
||||
DEBUG_PRINTF("Failed to open directory\n");
|
||||
return;
|
||||
}
|
||||
if(!root.isDirectory()){
|
||||
DEBUG_PRINTF("Not a directory\n");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = root.openNextFile();
|
||||
while(file){
|
||||
if(file.isDirectory()){
|
||||
DEBUG_PRINTF(" DIR : %s\n",file.name());
|
||||
if(levels){
|
||||
listDir(file.name(), levels -1);
|
||||
}
|
||||
} else {
|
||||
DEBUG_PRINTF(" FILE: %s SIZE: %d\n",file.name(), file.size());
|
||||
}
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,12 +1,12 @@
|
||||
# Sensors To Home Assistant (or mqtt)
|
||||
# Send sensor data To Home Assistant
|
||||
|
||||
This usermod will publish values of the BMP280, CCS811 and Si7021 sensors to Home Assistant via MQTT.
|
||||
Publishes BMP280, CCS811 and Si7021 measurements to Home Assistant via MQTT.
|
||||
|
||||
Its using home assistant automatic device discovery feature.
|
||||
Uses Home Assistant Automatic Device Discovery.
|
||||
|
||||
The use of Home Assistant is not mandatory; it will publish the sensor values via MQTT just fine without it.
|
||||
The use of Home Assistant is not mandatory. The mod will publish sensor values via MQTT just fine without it.
|
||||
|
||||
Its resusing the mqtt connection set in the WLED web user interface.
|
||||
Uses the MQTT connection set in the WLED web user interface.
|
||||
|
||||
## Maintainer
|
||||
|
||||
@@ -15,12 +15,12 @@ twitter.com/mpronk89
|
||||
## Features
|
||||
|
||||
- Reads BMP280, CCS811 and Si7021 senors
|
||||
- Publishes via MQTT, configured via webui of wled
|
||||
- Publishes via MQTT, configured via WLED webUI
|
||||
- Announces device in Home Assistant for easy setup
|
||||
- Efficient energy usage
|
||||
- Updates every 60 seconds
|
||||
|
||||
## Example mqtt topics:
|
||||
## Example MQTT topics:
|
||||
|
||||
`$mqttDeviceTopic` is set in webui of WLED!
|
||||
|
||||
@@ -40,7 +40,7 @@ IAQ: $mqttDeviceTopic/iaq
|
||||
### Requirements
|
||||
|
||||
1. BMP280/CCS811/Si7021 sensor. E.g. https://aliexpress.com/item/32979998543.html
|
||||
2. A microcontroller which can talk i2c, e.g. esp32
|
||||
2. A microcontroller that supports i2c. e.g. esp32
|
||||
|
||||
### installation
|
||||
|
||||
@@ -77,7 +77,7 @@ SDA_PIN = 4;
|
||||
adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||
```
|
||||
|
||||
The #ifdefs in `usermods_list.cpp` should do the rest :)
|
||||
The #ifdefs in `usermods_list.cpp` should do the rest
|
||||
|
||||
# Credits
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
# Seven Segment Display
|
||||
|
||||
Usermod that uses the overlay feature to create a configurable seven segment display.
|
||||
This has only been tested on a single configuration. Colon support is entirely untested.
|
||||
Uses the overlay feature to create a configurable seven segment display.
|
||||
This has only been tested on a single configuration. Colon support has _not_ been tested.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the compile-time option `-D USERMOD_SEVEN_SEGMENT` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SEVEN_SEGMENT` in `my_config.h`.
|
||||
|
||||
## Settings
|
||||
Settings can be controlled through both the usermod setting page and through MQTT with a raw payload.
|
||||
Settings can be controlled via both the usermod setting page and through MQTT with a raw payload.
|
||||
##### Example
|
||||
Topic ```<mqttDeviceTopic||mqttGroupTopic>/sevenSeg/perSegment/set```
|
||||
Payload ```3```
|
||||
#### perSegment -- ssLEDPerSegment
|
||||
The number of individual LEDs per segment. There are 7 segments per digit.
|
||||
The number of individual LEDs per segment. 7 segments per digit.
|
||||
#### perPeriod -- ssLEDPerPeriod
|
||||
The number of individual LEDs per period. A ':' has 2x periods.
|
||||
The number of individual LEDs per period. A ':' (colon) has two periods.
|
||||
#### startIdx -- ssStartLED
|
||||
Index of the LED that the display starts at. Allows a seven segment display to be in the middle of a string.
|
||||
Index of the LED the display starts at. Enabless a seven segment display to be in the middle of a string.
|
||||
#### timeEnable -- ssTimeEnabled
|
||||
When true, when displayMask is configured for a time output and no message is set the time will be displayed.
|
||||
When true, when displayMask is configured for a time output and no message is set, the time will be displayed.
|
||||
#### scrollSpd -- ssScrollSpeed
|
||||
Time, in milliseconds, between message shifts when the length of displayMsg exceeds the length of the displayMask.
|
||||
#### displayMask -- ssDisplayMask
|
||||
@@ -35,9 +35,9 @@ All others for alpha numeric, (will be blank when displaying time)
|
||||
```HHMMSS ```
|
||||
```hh:MM:SS ```
|
||||
#### displayMsg -- ssDisplayMessage
|
||||
Message to be displayed across the display. If the length exceeds the length of the displayMask the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'.
|
||||
Message to be displayed. If the message length exceeds the length of displayMask, the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'.
|
||||
#### displayCfg -- ssDisplayConfig
|
||||
The order that your LEDs are configured. All seven segments in the display need to be wired the same way.
|
||||
The order your LEDs are configured in. All segments in the display need to be wired the same way.
|
||||
<pre>
|
||||
-------
|
||||
/ A / 0 - EDCGFAB
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Seven Segment Display Reloaded
|
||||
|
||||
Usermod that uses the overlay feature to create a configurable seven segment display.
|
||||
Uses the overlay feature to create a configurable seven segment display.
|
||||
Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/)
|
||||
Very loosely based on the existing usermod "seven segment display".
|
||||
|
||||
@@ -12,26 +12,26 @@ Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `plat
|
||||
For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
|
||||
|
||||
## Settings
|
||||
All settings can be controlled the usermod setting page.
|
||||
All settings can be controlled via the usermod settings page.
|
||||
Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state.
|
||||
|
||||
### enabled
|
||||
Enables/disables this overlay usermod
|
||||
Enables/disables this usermod
|
||||
|
||||
### inverted
|
||||
Enables the inverted mode in which the background should be enabled and the digits should be black (leds off)
|
||||
Enables the inverted mode in which the background should be enabled and the digits should be black (LEDs off)
|
||||
|
||||
### Colon-blinking
|
||||
Enables the blinking colon(s) if they are defined
|
||||
|
||||
### enable-auto-brightness
|
||||
Enables the auto brightness feature. Can be only used with the usermod SN_Photoresistor installed.
|
||||
Enables the auto brightness feature. Can be used only when the usermod SN_Photoresistor is installed.
|
||||
|
||||
### auto-brightness-min / auto-brightness-max
|
||||
The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
|
||||
The mapping is 0 - 1000 lux will be mapped to auto-brightness-min - auto-brightness-max
|
||||
The mapping, 0 - 1000 lux, will be mapped to auto-brightness-min and auto-brightness-max
|
||||
|
||||
The mA current protection of WLED will override the calculated value if it is too high.
|
||||
WLED current protection will override the calculated value if it is too high.
|
||||
|
||||
### Display-Mask
|
||||
Defines the type of the time/date display.
|
||||
@@ -61,7 +61,7 @@ See following example for usage.
|
||||
|
||||
## Example
|
||||
|
||||
Example for Leds definition
|
||||
Example of an LED definition:
|
||||
```
|
||||
< A >
|
||||
/\ /\
|
||||
@@ -74,15 +74,15 @@ E C
|
||||
< D >
|
||||
```
|
||||
|
||||
Leds or Range of Leds are seperated by a comma ","
|
||||
LEDs or Range of LEDs are separated by a comma ","
|
||||
|
||||
Segments are seperated by a semicolon ";" and are read as A;B;C;D;E;F;G
|
||||
Segments are separated by a semicolon ";" and are read as A;B;C;D;E;F;G
|
||||
|
||||
Digits are seperated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
|
||||
Digits are separated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
|
||||
|
||||
Ranges are defined as lower to higher (lower first)
|
||||
|
||||
For example, an clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
|
||||
For example, a clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
|
||||
|
||||
- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
|
||||
|
||||
@@ -96,18 +96,18 @@ or
|
||||
|
||||
depending on the orientation.
|
||||
|
||||
# The example detailed:
|
||||
# Example details:
|
||||
hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
|
||||
|
||||
there are two digits seperated by ":"
|
||||
there are two digits separated by ":"
|
||||
|
||||
- 59,46;47-48;50-51;52-53;54-55;57-58;49,56
|
||||
- 0,13;1-2;4-5;6-7;8-9;11-12;3,10
|
||||
|
||||
In the first digit,
|
||||
the **segment A** consists of the leds number **59 and 46**., **segment B** consists of the leds number **47, 48** and so on
|
||||
the **segment A** consists of the LEDs number **59 and 46**., **segment B** consists of the LEDs number **47, 48** and so on
|
||||
|
||||
The second digit starts again with **segment A** and leds **0 and 13**, **segment B** consists of the leds number **1 and 2** and so on
|
||||
The second digit starts again with **segment A** and LEDs **0 and 13**, **segment B** consists of the LEDs number **1 and 2** and so on
|
||||
|
||||
### first digit of the hour
|
||||
- Segment A: 59, 46
|
||||
@@ -126,4 +126,4 @@ The second digit starts again with **segment A** and leds **0 and 13**, **segmen
|
||||
- Segment D: 6, 7
|
||||
- Segment E: 8, 9
|
||||
- Segment F: 11, 12
|
||||
- Segment G: 3, 10
|
||||
- Segment G: 3, 10
|
||||
|
||||
@@ -384,7 +384,7 @@ public:
|
||||
if (!umSSDRDisplayTime || strip.isUpdating()) {
|
||||
return;
|
||||
}
|
||||
#ifdef USERMOD_ID_SN_PHOTORESISTOR
|
||||
#ifdef USERMOD_SN_PHOTORESISTOR
|
||||
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
|
||||
if (ptr != nullptr) {
|
||||
uint16_t lux = ptr->getLastLDRValue();
|
||||
|
||||
56
usermods/sht/readme.md
Normal file
56
usermods/sht/readme.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# SHT
|
||||
Usermod to support various SHT i2c sensors like the SHT30, SHT31, SHT35 and SHT85
|
||||
|
||||
## Requirements
|
||||
* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85
|
||||
|
||||
## Usermod installation
|
||||
Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one, add the buildflag `-D USERMOD_SHT` and the below library dependencies.
|
||||
|
||||
ESP32:
|
||||
```
|
||||
[env:custom_esp32dev_usermod_sht]
|
||||
extends = env:esp32dev
|
||||
build_flags = ${common.build_flags_esp32}
|
||||
-D USERMOD_SHT
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
robtillaart/SHT85@~0.3.3
|
||||
```
|
||||
|
||||
ESP8266:
|
||||
```
|
||||
[env:custom_d1_mini_usermod_sht]
|
||||
extends = env:d1_mini
|
||||
build_flags = ${common.build_flags_esp8266}
|
||||
-D USERMOD_SHT
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
robtillaart/SHT85@~0.3.3
|
||||
```
|
||||
|
||||
## MQTT Discovery for Home Assistant
|
||||
If you're using Home Assistant and want to have the temperature and humidity available as entities in HA, you can tick the "Add-To-Home-Assistant-MQTT-Discovery" option in the usermod settings. If you have an MQTT broker configured under "Sync Settings" and it is connected, the mod will publish the auto discovery message to your broker and HA will instantly find it and create an entity each for the temperature and humidity.
|
||||
|
||||
### Publishing readings via MQTT
|
||||
Regardless of having MQTT discovery ticked or not, the mod will always report temperature and humidity to the WLED MQTT topic of that instance, if you have a broker configured and it's connected.
|
||||
|
||||
## Configuration
|
||||
Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D USERMOD_SHT`, you will see the config for it there:
|
||||
* SHT-Type:
|
||||
* What it does: Select the SHT sensor type you want to use
|
||||
* Possible values: SHT30, SHT31, SHT35, SHT85
|
||||
* Default: SHT30
|
||||
* Unit:
|
||||
* What it does: Select which unit should be used to display the temperature in the info section. Also used when sending via MQTT discovery, see below.
|
||||
* Possible values: Celsius, Fahrenheit
|
||||
* Default: Celsius
|
||||
* Add-To-HA-MQTT-Discovery:
|
||||
* What it does: Makes the temperature and humidity available via MQTT discovery, so they're automatically added to Home Assistant, because that way it's typesafe.
|
||||
* Possible values: Enabled/Disabled
|
||||
* Default: Disabled
|
||||
|
||||
## Change log
|
||||
2022-12
|
||||
* First implementation.
|
||||
|
||||
## Credits
|
||||
ezcGman | Andy: Find me on the Intermit.Tech (QuinLED) Discord server: https://discord.gg/WdbAauG
|
||||
492
usermods/sht/usermod_sht.h
Normal file
492
usermods/sht/usermod_sht.h
Normal file
@@ -0,0 +1,492 @@
|
||||
#pragma once
|
||||
|
||||
#include "SHT85.h"
|
||||
|
||||
#define USERMOD_SHT_TYPE_SHT30 0
|
||||
#define USERMOD_SHT_TYPE_SHT31 1
|
||||
#define USERMOD_SHT_TYPE_SHT35 2
|
||||
#define USERMOD_SHT_TYPE_SHT85 3
|
||||
|
||||
class ShtUsermod : public Usermod
|
||||
{
|
||||
private:
|
||||
bool enabled = false; // Is usermod enabled or not
|
||||
bool firstRunDone = false; // Remembers if the first config load run had been done
|
||||
bool pinAllocDone = true; // Remembers if we have allocated pins
|
||||
bool initDone = false; // Remembers if the mod has been completely initialised
|
||||
bool haMqttDiscovery = false; // Is MQTT discovery enabled or not
|
||||
bool haMqttDiscoveryDone = false; // Remembers if we already published the HA discovery topics
|
||||
|
||||
// SHT vars
|
||||
SHT *shtTempHumidSensor; // Instance of SHT lib
|
||||
byte shtType = 0; // SHT sensor type to be used. Default: SHT30
|
||||
byte unitOfTemp = 0; // Temperature unit to be used. Default: Celsius (0 = Celsius, 1 = Fahrenheit)
|
||||
bool shtInitDone = false; // Remembers if SHT sensor has been initialised
|
||||
bool shtReadDataSuccess = false; // Did we have a successful data read and is a valid temperature and humidity available?
|
||||
const byte shtI2cAddress = 0x44; // i2c address of the sensor. 0x44 is the default for all SHT sensors. Change this, if needed
|
||||
unsigned long shtLastTimeUpdated = 0; // Remembers when we read data the last time
|
||||
bool shtDataRequested = false; // Reading data is done async. This remembers if we asked the sensor to read data
|
||||
float shtCurrentTempC = 0; // Last read temperature in Celsius
|
||||
float shtCurrentTempF = 0; // Last read temperature in Fahrenheit
|
||||
float shtCurrentHumidity = 0; // Last read humidity in RH%
|
||||
|
||||
|
||||
void initShtTempHumiditySensor();
|
||||
void cleanupShtTempHumiditySensor();
|
||||
void cleanup();
|
||||
bool isShtReady();
|
||||
|
||||
void publishTemperatureAndHumidityViaMqtt();
|
||||
void publishHomeAssistantAutodiscovery();
|
||||
void appendDeviceToMqttDiscoveryMessage(JsonDocument& root);
|
||||
|
||||
public:
|
||||
// Strings to reduce flash memory usage (used more than twice)
|
||||
static const char _name[];
|
||||
static const char _enabled[];
|
||||
static const char _shtType[];
|
||||
static const char _unitOfTemp[];
|
||||
static const char _haMqttDiscovery[];
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
void onMqttConnect(bool sessionPresent);
|
||||
void appendConfigData();
|
||||
void addToConfig(JsonObject &root);
|
||||
bool readFromConfig(JsonObject &root);
|
||||
void addToJsonInfo(JsonObject& root);
|
||||
|
||||
float getTemperatureC();
|
||||
float getTemperatureF();
|
||||
float getHumidity();
|
||||
|
||||
uint16_t getId() { return USERMOD_ID_SHT; }
|
||||
};
|
||||
|
||||
// Strings to reduce flash memory usage (used more than twice)
|
||||
const char ShtUsermod::_name[] PROGMEM = "SHT-Sensor";
|
||||
const char ShtUsermod::_enabled[] PROGMEM = "Enabled";
|
||||
const char ShtUsermod::_shtType[] PROGMEM = "SHT-Type";
|
||||
const char ShtUsermod::_unitOfTemp[] PROGMEM = "Unit";
|
||||
const char ShtUsermod::_haMqttDiscovery[] PROGMEM = "Add-To-HA-MQTT-Discovery";
|
||||
|
||||
/**
|
||||
* Initialise SHT sensor.
|
||||
*
|
||||
* Using the correct constructor according to config and initialises it using the
|
||||
* global i2c pins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::initShtTempHumiditySensor()
|
||||
{
|
||||
switch (shtType) {
|
||||
case USERMOD_SHT_TYPE_SHT30: shtTempHumidSensor = (SHT *) new SHT30(); break;
|
||||
case USERMOD_SHT_TYPE_SHT31: shtTempHumidSensor = (SHT *) new SHT31(); break;
|
||||
case USERMOD_SHT_TYPE_SHT35: shtTempHumidSensor = (SHT *) new SHT35(); break;
|
||||
case USERMOD_SHT_TYPE_SHT85: shtTempHumidSensor = (SHT *) new SHT85(); break;
|
||||
}
|
||||
|
||||
shtTempHumidSensor->begin(shtI2cAddress, i2c_sda, i2c_scl);
|
||||
if (shtTempHumidSensor->readStatus() == 0xFFFF) {
|
||||
DEBUG_PRINTF("[%s] SHT init failed!\n", _name);
|
||||
cleanupShtTempHumiditySensor();
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
shtInitDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the SHT sensor.
|
||||
*
|
||||
* Properly calls "reset" for the sensor then releases it from memory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::cleanupShtTempHumiditySensor()
|
||||
{
|
||||
if (isShtReady()) {
|
||||
shtTempHumidSensor->reset();
|
||||
}
|
||||
|
||||
delete shtTempHumidSensor;
|
||||
|
||||
shtInitDone = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the mod completely.
|
||||
*
|
||||
* Calls ::cleanupShtTempHumiditySensor() to cleanup the SHT sensor and
|
||||
* deallocates pins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::cleanup()
|
||||
{
|
||||
if (isShtReady()) {
|
||||
cleanupShtTempHumiditySensor();
|
||||
}
|
||||
|
||||
if (pinAllocDone) {
|
||||
PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } };
|
||||
pinManager.deallocateMultiplePins(pins, 2, PinOwner::HW_I2C);
|
||||
pinAllocDone = false;
|
||||
}
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the SHT sensor has been initialised.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
bool ShtUsermod::isShtReady()
|
||||
{
|
||||
return shtInitDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish temperature and humidity to WLED device topic.
|
||||
*
|
||||
* Will add a "/temperature" and "/humidity" topic to the WLED device topic.
|
||||
* Temperature will be written in configured unit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::publishTemperatureAndHumidityViaMqtt() {
|
||||
if (!WLED_MQTT_CONNECTED) return;
|
||||
char buf[128];
|
||||
|
||||
snprintf_P(buf, 127, PSTR("%s/temperature"), mqttDeviceTopic);
|
||||
mqtt->publish(buf, 0, false, String((unitOfTemp ? getTemperatureF() : getTemperatureC())).c_str());
|
||||
snprintf_P(buf, 127, PSTR("%s/humidity"), mqttDeviceTopic);
|
||||
mqtt->publish(buf, 0, false, String(shtCurrentHumidity).c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, publishes HA MQTT device discovery topics.
|
||||
*
|
||||
* Will make Home Assistant add temperature and humidity as entities automatically.
|
||||
*
|
||||
* Note: Whenever usermods are part of the WLED integration in HA, this can be dropped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::publishHomeAssistantAutodiscovery() {
|
||||
if (!WLED_MQTT_CONNECTED) return;
|
||||
|
||||
char json_str[1024], buf[128];
|
||||
size_t payload_size;
|
||||
StaticJsonDocument<1024> json;
|
||||
|
||||
snprintf_P(buf, 127, PSTR("%s Temperature"), serverDescription);
|
||||
json[F("name")] = buf;
|
||||
snprintf_P(buf, 127, PSTR("%s/temperature"), mqttDeviceTopic);
|
||||
json[F("stat_t")] = buf;
|
||||
json[F("dev_cla")] = F("temperature");
|
||||
json[F("stat_cla")] = F("measurement");
|
||||
snprintf_P(buf, 127, PSTR("%s-temperature"), escapedMac.c_str());
|
||||
json[F("uniq_id")] = buf;
|
||||
json[F("unit_of_meas")] = F("°C");
|
||||
appendDeviceToMqttDiscoveryMessage(json);
|
||||
payload_size = serializeJson(json, json_str);
|
||||
snprintf_P(buf, 127, PSTR("homeassistant/sensor/%s/%s-temperature/config"), escapedMac.c_str(), escapedMac.c_str());
|
||||
mqtt->publish(buf, 0, true, json_str, payload_size);
|
||||
|
||||
json.clear();
|
||||
|
||||
snprintf_P(buf, 127, PSTR("%s Humidity"), serverDescription);
|
||||
json[F("name")] = buf;
|
||||
snprintf_P(buf, 127, PSTR("%s/humidity"), mqttDeviceTopic);
|
||||
json[F("stat_t")] = buf;
|
||||
json[F("dev_cla")] = F("humidity");
|
||||
json[F("stat_cla")] = F("measurement");
|
||||
snprintf_P(buf, 127, PSTR("%s-humidity"), escapedMac.c_str());
|
||||
json[F("uniq_id")] = buf;
|
||||
json[F("unit_of_meas")] = F("%");
|
||||
appendDeviceToMqttDiscoveryMessage(json);
|
||||
payload_size = serializeJson(json, json_str);
|
||||
snprintf_P(buf, 127, PSTR("homeassistant/sensor/%s/%s-humidity/config"), escapedMac.c_str(), escapedMac.c_str());
|
||||
mqtt->publish(buf, 0, true, json_str, payload_size);
|
||||
|
||||
haMqttDiscoveryDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to add device information to MQTT discovery topic.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) {
|
||||
JsonObject device = root.createNestedObject("dev");
|
||||
device[F("ids")] = escapedMac.c_str();
|
||||
device[F("name")] = serverDescription;
|
||||
device[F("sw")] = versionString;
|
||||
device[F("mdl")] = ESP.getChipModel();
|
||||
device[F("mf")] = F("espressif");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the mod.
|
||||
*
|
||||
* Allocates i2c pins as PinOwner::HW_I2C, so they can be allocated multiple times.
|
||||
* And calls ::initShtTempHumiditySensor() to initialise the sensor.
|
||||
*
|
||||
* @see Usermod::setup()
|
||||
* @see UsermodManager::setup()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::setup()
|
||||
{
|
||||
if (enabled) {
|
||||
PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } };
|
||||
// GPIOs can be set to -1 and allocateMultiplePins() will return true, so check they're gt zero
|
||||
if (i2c_sda < 0 || i2c_scl < 0 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) {
|
||||
DEBUG_PRINTF("[%s] SHT pin allocation failed!\n", _name);
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
pinAllocDone = true;
|
||||
|
||||
initShtTempHumiditySensor();
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
firstRunDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually reading data (async) from the sensor every 30 seconds.
|
||||
*
|
||||
* If last reading is at least 30 seconds, it will trigger a reading using
|
||||
* SHT::requestData(). We will then continiously check SHT::dataReady() if
|
||||
* data is ready to be read. If so, it's read, stored locally and published
|
||||
* via MQTT.
|
||||
*
|
||||
* @see Usermod::loop()
|
||||
* @see UsermodManager::loop()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::loop()
|
||||
{
|
||||
if (!enabled || !initDone || strip.isUpdating()) return;
|
||||
|
||||
if (isShtReady()) {
|
||||
if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) {
|
||||
shtTempHumidSensor->requestData();
|
||||
shtDataRequested = true;
|
||||
|
||||
shtLastTimeUpdated = millis();
|
||||
}
|
||||
|
||||
if (shtDataRequested) {
|
||||
if (shtTempHumidSensor->dataReady()) {
|
||||
if (shtTempHumidSensor->readData(false)) {
|
||||
shtCurrentTempC = shtTempHumidSensor->getTemperature();
|
||||
shtCurrentTempF = shtTempHumidSensor->getFahrenheit();
|
||||
shtCurrentHumidity = shtTempHumidSensor->getHumidity();
|
||||
|
||||
publishTemperatureAndHumidityViaMqtt();
|
||||
shtReadDataSuccess = true;
|
||||
}
|
||||
else {
|
||||
shtReadDataSuccess = false;
|
||||
}
|
||||
|
||||
shtDataRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever MQTT is connected, publish HA autodiscovery topics.
|
||||
*
|
||||
* Is only donce once.
|
||||
*
|
||||
* @see Usermod::onMqttConnect()
|
||||
* @see UsermodManager::onMqttConnect()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::onMqttConnect(bool sessionPresent) {
|
||||
if (haMqttDiscovery && !haMqttDiscoveryDone) publishHomeAssistantAutodiscovery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dropdown for sensor type and unit to UM config page.
|
||||
*
|
||||
* @see Usermod::appendConfigData()
|
||||
* @see UsermodManager::appendConfigData()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::appendConfigData() {
|
||||
oappend(SET_F("dd=addDropdown('"));
|
||||
oappend(_name);
|
||||
oappend(SET_F("','"));
|
||||
oappend(_shtType);
|
||||
oappend(SET_F("');"));
|
||||
oappend(SET_F("addOption(dd,'SHT30',0);"));
|
||||
oappend(SET_F("addOption(dd,'SHT31',1);"));
|
||||
oappend(SET_F("addOption(dd,'SHT35',2);"));
|
||||
oappend(SET_F("addOption(dd,'SHT85',3);"));
|
||||
oappend(SET_F("dd=addDropdown('"));
|
||||
oappend(_name);
|
||||
oappend(SET_F("','"));
|
||||
oappend(_unitOfTemp);
|
||||
oappend(SET_F("');"));
|
||||
oappend(SET_F("addOption(dd,'Celsius',0);"));
|
||||
oappend(SET_F("addOption(dd,'Fahrenheit',1);"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add config data to be stored in cfg.json.
|
||||
*
|
||||
* @see Usermod::addToConfig()
|
||||
* @see UsermodManager::addToConfig()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::addToConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_shtType)] = shtType;
|
||||
top[FPSTR(_unitOfTemp)] = unitOfTemp;
|
||||
top[FPSTR(_haMqttDiscovery)] = haMqttDiscovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply config on boot or save of UM config page.
|
||||
*
|
||||
* This is called whenever WLED boots and loads cfg.json, or when the UM config
|
||||
* page is saved. Will properly re-instantiate the SHT class upon type change and
|
||||
* publish HA discovery after enabling.
|
||||
*
|
||||
* @see Usermod::readFromConfig()
|
||||
* @see UsermodManager::readFromConfig()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
bool ShtUsermod::readFromConfig(JsonObject &root)
|
||||
{
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (top.isNull()) {
|
||||
DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool oldEnabled = enabled;
|
||||
byte oldShtType = shtType;
|
||||
bool oldHaMqttDiscovery = haMqttDiscovery;
|
||||
|
||||
getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||
getJsonValue(top[FPSTR(_shtType)], shtType);
|
||||
getJsonValue(top[FPSTR(_unitOfTemp)], unitOfTemp);
|
||||
getJsonValue(top[FPSTR(_haMqttDiscovery)], haMqttDiscovery);
|
||||
|
||||
// First run: reading from cfg.json, nothing to do here, will be all done in setup()
|
||||
if (!firstRunDone) {
|
||||
DEBUG_PRINTF("[%s] First run, nothing to do\n", _name);
|
||||
}
|
||||
// Check if mod has been en-/disabled
|
||||
else if (enabled != oldEnabled) {
|
||||
enabled ? setup() : cleanup();
|
||||
DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name);
|
||||
}
|
||||
// Config has been changed, so adopt to changes
|
||||
else if (enabled) {
|
||||
if (oldShtType != shtType) {
|
||||
cleanupShtTempHumiditySensor();
|
||||
initShtTempHumiditySensor();
|
||||
}
|
||||
|
||||
if (oldHaMqttDiscovery != haMqttDiscovery && haMqttDiscovery) {
|
||||
publishHomeAssistantAutodiscovery();
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("[%s] Config (re)loaded\n", _name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the temperature and humidity actually to the info section and /json info.
|
||||
*
|
||||
* This is called every time the info section is opened ot /json is called.
|
||||
*
|
||||
* @see Usermod::addToJsonInfo()
|
||||
* @see UsermodManager::addToJsonInfo()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void ShtUsermod::addToJsonInfo(JsonObject& root)
|
||||
{
|
||||
if (!enabled && !isShtReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray jsonTemp = user.createNestedArray(F("Temperature"));
|
||||
JsonArray jsonHumidity = user.createNestedArray(F("Humidity"));
|
||||
|
||||
if (shtLastTimeUpdated == 0 || !shtReadDataSuccess) {
|
||||
jsonTemp.add(0);
|
||||
jsonHumidity.add(0);
|
||||
if (shtLastTimeUpdated == 0) {
|
||||
jsonTemp.add(F(" Not read yet"));
|
||||
jsonHumidity.add(F(" Not read yet"));
|
||||
}
|
||||
else {
|
||||
jsonTemp.add(F(" Error"));
|
||||
jsonHumidity.add(F(" Error"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
jsonHumidity.add(shtCurrentHumidity);
|
||||
jsonHumidity.add(F(" RH"));
|
||||
|
||||
unitOfTemp ? jsonTemp.add(getTemperatureF()) : jsonTemp.add(getTemperatureC());
|
||||
unitOfTemp ? jsonTemp.add(F(" °F")) : jsonTemp.add(F(" °C"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for last read temperature in Celsius.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float ShtUsermod::getTemperatureC() {
|
||||
return shtCurrentTempC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for last read temperature in Fahrenheit.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float ShtUsermod::getTemperatureF() {
|
||||
return shtCurrentTempF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for last read humidity in RH%.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float ShtUsermod::getHumidity() {
|
||||
return shtCurrentHumidity;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user