Compare commits
2997 Commits
v0.13.0-b7
...
v0.15.0-b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8b11880b0 | ||
|
|
0ff4016250 | ||
|
|
e753f3849e | ||
|
|
7b248c8fb2 | ||
|
|
f0e4dd90ee | ||
|
|
c540ec5417 | ||
|
|
3615ab535b | ||
|
|
b8d6ebe882 | ||
|
|
1f27872294 | ||
|
|
57b01c2711 | ||
|
|
5f3e3d7796 | ||
|
|
d13b8c8421 | ||
|
|
0af3063127 | ||
|
|
17e1975dd8 | ||
|
|
166eeacab9 | ||
|
|
017169f1a1 | ||
|
|
ed0e73803f | ||
|
|
f4475b9d2a | ||
|
|
8a1df9afee | ||
|
|
9cfb56d9c6 | ||
|
|
25ade86994 | ||
|
|
c10ec2962b | ||
|
|
1bb06106fd | ||
|
|
bc18cdbeea | ||
|
|
67df5d6dcc | ||
|
|
68a7282b27 | ||
|
|
5732b66c5a | ||
|
|
f1cce8ef46 | ||
|
|
e47932c9aa | ||
|
|
3a70fb0e15 | ||
|
|
d5da84dcb2 | ||
|
|
1bd051d703 | ||
|
|
abb55f8e4b | ||
|
|
46f6a257d0 | ||
|
|
97e4eec5be | ||
|
|
69b99abb7d | ||
|
|
08f6d5a683 | ||
|
|
627bea30ed | ||
|
|
caf0a5bbe8 | ||
|
|
501f988b0f | ||
|
|
5f0c6fce74 | ||
|
|
78089107b7 | ||
|
|
f3b0906cea | ||
|
|
9f581c6181 | ||
|
|
31b232d3f3 | ||
|
|
77e6a6de61 | ||
|
|
85702a8142 | ||
|
|
0bcdc9641d | ||
|
|
bfd1bdfc64 | ||
|
|
c1fed6d469 | ||
|
|
79ffe021e4 | ||
|
|
a1c348b365 | ||
|
|
904b1f114b | ||
|
|
63f481d498 | ||
|
|
fc5c19788e | ||
|
|
56e0bde8ab | ||
|
|
db2cdca71b | ||
|
|
faa2fba6b9 | ||
|
|
f727ea2874 | ||
|
|
4a7ef07089 | ||
|
|
0df726cdab | ||
|
|
2f6f0d2f3e | ||
|
|
fae5938b0a | ||
|
|
2ec3639900 | ||
|
|
4513404629 | ||
|
|
13bfda56ef | ||
|
|
075c164407 | ||
|
|
dcb5049f97 | ||
|
|
d24cf14009 | ||
|
|
3d34802ab2 | ||
|
|
2aec21a6d0 | ||
|
|
19ccff9ff1 | ||
|
|
1dd9c6754c | ||
|
|
f9467ceaf1 | ||
|
|
f51da4f0c4 | ||
|
|
2eff389fff | ||
|
|
77ca2ec0e9 | ||
|
|
77e6ea8a6f | ||
|
|
e33299bbd7 | ||
|
|
74e273274c | ||
|
|
02bf5902f0 | ||
|
|
5f41de8938 | ||
|
|
f71d839009 | ||
|
|
3cb6b17316 | ||
|
|
c0cb677a4c | ||
|
|
a5a6a8eaee | ||
|
|
ecb861de56 | ||
|
|
1d20f18d3f | ||
|
|
68f6b3452e | ||
|
|
60075f6e8c | ||
|
|
1d7789f544 | ||
|
|
bd10a9aa26 | ||
|
|
1ceeed38bc | ||
|
|
2e1e917952 | ||
|
|
9e0b91ac17 | ||
|
|
ea83ec496b | ||
|
|
f26bb26ffa | ||
|
|
28348f919b | ||
|
|
c84d4c637d | ||
|
|
04706cfa9c | ||
|
|
3078bea7cc | ||
|
|
c91e0fc988 | ||
|
|
5183af4e8a | ||
|
|
1ff5cb0596 | ||
|
|
522e5e7957 | ||
|
|
9e468bd059 | ||
|
|
ecc9443677 | ||
|
|
6b8d8bf735 | ||
|
|
43d024fe42 | ||
|
|
c016dedea4 | ||
|
|
b9ca2cfe90 | ||
|
|
d3492b5b6c | ||
|
|
3682cd6a5e | ||
|
|
b209b1e481 | ||
|
|
4afed48f58 | ||
|
|
6a18ce078e | ||
|
|
bc5aadff7d | ||
|
|
de89b516dc | ||
|
|
4dbe9a7015 | ||
|
|
a320f16404 | ||
|
|
da79b93387 | ||
|
|
b88c300d04 | ||
|
|
88372cd516 | ||
|
|
b3206121cc | ||
|
|
5bccb5fc42 | ||
|
|
47e1cbdaff | ||
|
|
52020cbe26 | ||
|
|
d33651c25b | ||
|
|
18e9f9c304 | ||
|
|
2607c44fbb | ||
|
|
a6e536189c | ||
|
|
3f9a6cae53 | ||
|
|
cd5494fdd2 | ||
|
|
5ab1b14d6b | ||
|
|
379f181362 | ||
|
|
dd19aa63d0 | ||
|
|
6df3b417a9 | ||
|
|
fa76431dd6 | ||
|
|
6504fb68b6 | ||
|
|
2ff49cf657 | ||
|
|
fa1aa1fe80 | ||
|
|
85b95a2940 | ||
|
|
5e38039c4d | ||
|
|
4df936a437 | ||
|
|
736a8b1b80 | ||
|
|
22f6128bc4 | ||
|
|
db475b6998 | ||
|
|
6daf7f6322 | ||
|
|
16086c0961 | ||
|
|
e88c81ad0d | ||
|
|
a2b9aed40d | ||
|
|
caa4fe1ec4 | ||
|
|
25fb878e54 | ||
|
|
a1d6ffadad | ||
|
|
3b89814b69 | ||
|
|
071e0be445 | ||
|
|
bed364d75e | ||
|
|
d2984e9e16 | ||
|
|
05a8c692f2 | ||
|
|
fd9570e782 | ||
|
|
35d26afcb4 | ||
|
|
2245ee6fce | ||
|
|
ff10130176 | ||
|
|
a13f1a9bee | ||
|
|
3cc60fa4d4 | ||
|
|
bd69c24231 | ||
|
|
c7d292a716 | ||
|
|
74bc159a52 | ||
|
|
9f99a1896d | ||
|
|
8110259d1c | ||
|
|
1048bf993a | ||
|
|
3eba0f4e0d | ||
|
|
d7e0b364d1 | ||
|
|
25dd43b949 | ||
|
|
69da2f4b23 | ||
|
|
d48bab02a1 | ||
|
|
886120fe9f | ||
|
|
bdd4d9f3ff | ||
|
|
6276c2f1f5 | ||
|
|
b3acc97d03 | ||
|
|
24bd1db4fc | ||
|
|
e83d3cb4a3 | ||
|
|
674481f0d1 | ||
|
|
5d271d21bc | ||
|
|
4e4493e627 | ||
|
|
1154905818 | ||
|
|
0d3ea848c2 | ||
|
|
8ffe1e65fd | ||
|
|
127ea7e351 | ||
|
|
34b16f1919 | ||
|
|
fa32faf759 | ||
|
|
6f3d7e76c9 | ||
|
|
57665e8964 | ||
|
|
5c502b1fe4 | ||
|
|
6d1410741d | ||
|
|
3e20724058 | ||
|
|
197f47befe | ||
|
|
d126611e87 | ||
|
|
1b75be5710 | ||
|
|
6272969983 | ||
|
|
084fc2fcd1 | ||
|
|
b2e68db380 | ||
|
|
459156fe57 | ||
|
|
7abfe68458 | ||
|
|
442d7a7226 | ||
|
|
80ebcb2c28 | ||
|
|
cd928bc586 | ||
|
|
d2b4d25317 | ||
|
|
a418cd2a2a | ||
|
|
94cdd88474 | ||
|
|
1bdf3876fc | ||
|
|
30435e6406 | ||
|
|
bd1c06a7a7 | ||
|
|
8358272b9a | ||
|
|
ff3cce0ed2 | ||
|
|
c3787af29d | ||
|
|
38539aac74 | ||
|
|
58e8346209 | ||
|
|
ba9ce4adf2 | ||
|
|
bd60fe5a13 | ||
|
|
b72f3baab7 | ||
|
|
d1d54ce9c8 | ||
|
|
954f26308b | ||
|
|
1cafa0b33f | ||
|
|
18c17168e1 | ||
|
|
d3a97f1062 | ||
|
|
d18f078b1e | ||
|
|
9ffcde878a | ||
|
|
aa970d6ca5 | ||
|
|
503c68320a | ||
|
|
a106342439 | ||
|
|
00f5471270 | ||
|
|
3b0e6ec65c | ||
|
|
8691ddc081 | ||
|
|
24c5935661 | ||
|
|
bff6697690 | ||
|
|
02405b4856 | ||
|
|
78b37b592e | ||
|
|
4db88cf86b | ||
|
|
a7e17eabff | ||
|
|
93d9ce18b2 | ||
|
|
6f7ac93d84 | ||
|
|
fa5d60ca26 | ||
|
|
20ed81cd86 | ||
|
|
6f38874096 | ||
|
|
b7db5be4df | ||
|
|
6d278994ec | ||
|
|
157dbffc59 | ||
|
|
f21ab3588d | ||
|
|
2900bda8f9 | ||
|
|
5f37c19d42 | ||
|
|
6f6356e617 | ||
|
|
5d152baac0 | ||
|
|
d7739f9764 | ||
|
|
d6e73fde50 | ||
|
|
fd149b3f46 | ||
|
|
d1d45e7166 | ||
|
|
4b19759dd6 | ||
|
|
47f44680a3 | ||
|
|
0a344ada97 | ||
|
|
3b98d01f71 | ||
|
|
23d8000239 | ||
|
|
c32ee40ca0 | ||
|
|
213f45494f | ||
|
|
85a51e6c42 | ||
|
|
ecfdc6f0a8 | ||
|
|
1c1c3fd832 | ||
|
|
7bafe995e5 | ||
|
|
3c23672347 | ||
|
|
33fe68d7eb | ||
|
|
b031fa1531 | ||
|
|
dab4dc3f41 | ||
|
|
78096803ea | ||
|
|
f1987b9544 | ||
|
|
8b6bf08a23 | ||
|
|
2640203c88 | ||
|
|
2dafa9644f | ||
|
|
92ebeddcb0 | ||
|
|
ecee073e08 | ||
|
|
6d1b9ffad2 | ||
|
|
7ee4b54154 | ||
|
|
e3271b8082 | ||
|
|
c6ff45f959 | ||
|
|
5d3f22e06a | ||
|
|
c77f6c5f7b | ||
|
|
6d03c3a54c | ||
|
|
92dc63e5c9 | ||
|
|
505d319e01 | ||
|
|
62f845a94e | ||
|
|
7a9eff7f35 | ||
|
|
b849ea8eaa | ||
|
|
f1635fa302 | ||
|
|
6110b72b87 | ||
|
|
6de617ecd5 | ||
|
|
7c8df97968 | ||
|
|
acf6736afd | ||
|
|
5f2480c3d9 | ||
|
|
0593a078c6 | ||
|
|
323c70dcdf | ||
|
|
a1b0f84444 | ||
|
|
df6c271830 | ||
|
|
12bf04826a | ||
|
|
52a1b0453c | ||
|
|
0dcb56eab5 | ||
|
|
a42f78b08b | ||
|
|
505768db04 | ||
|
|
c74db95c14 | ||
|
|
af3f27feae | ||
|
|
7b366d49d2 | ||
|
|
88b30e7e28 | ||
|
|
05c0febd04 | ||
|
|
9d70ec56f2 | ||
|
|
7f8ec59939 | ||
|
|
0398ec40b7 | ||
|
|
f55465f8b8 | ||
|
|
a4384bd340 | ||
|
|
a2368a75f7 | ||
|
|
68e5e74882 | ||
|
|
0637c1c9d4 | ||
|
|
0453a5fb3d | ||
|
|
6322ab9f07 | ||
|
|
21d0f10dd7 | ||
|
|
51b9e81c3d | ||
|
|
7723011594 | ||
|
|
91c11a18e0 | ||
|
|
215ba35fe1 | ||
|
|
7e17011ebc | ||
|
|
ab13db73e7 | ||
|
|
cb5eb9edc7 | ||
|
|
509675fe66 | ||
|
|
da5c12f466 | ||
|
|
f2ad10bbce | ||
|
|
99a7bece2e | ||
|
|
b305fd8865 | ||
|
|
c0beb621e2 | ||
|
|
da6d64e581 | ||
|
|
a6d58ee360 | ||
|
|
2a480ab7db | ||
|
|
5c09ee29db | ||
|
|
15526bd6e8 | ||
|
|
c8f48168b4 | ||
|
|
d937d473f9 | ||
|
|
66f14c6343 | ||
|
|
f25fadafd8 | ||
|
|
d9b0df94e2 | ||
|
|
200d11ca99 | ||
|
|
f8c48ef60a | ||
|
|
1fc0680c71 | ||
|
|
ec4afb2cbc | ||
|
|
a2a632c415 | ||
|
|
962e64106c | ||
|
|
c40b6088ee | ||
|
|
0d1a254ca8 | ||
|
|
fdbcb61a12 | ||
|
|
6435cb1466 | ||
|
|
4739cfab9a | ||
|
|
00a73e9cc8 | ||
|
|
0ff5dec004 | ||
|
|
41129cf379 | ||
|
|
c8c394b4e9 | ||
|
|
59886a1528 | ||
|
|
a28d2c869f | ||
|
|
6f1fff44fa | ||
|
|
108978d1a5 | ||
|
|
a4733b4d91 | ||
|
|
66f4671ec0 | ||
|
|
c932621b5d | ||
|
|
21173dc907 | ||
|
|
f2c30ba3f7 | ||
|
|
989bdfb0d5 | ||
|
|
5761dce957 | ||
|
|
95e2e574b8 | ||
|
|
c789d80ce5 | ||
|
|
8422ffcd19 | ||
|
|
2d30535b69 | ||
|
|
0a815179a2 | ||
|
|
aa24e5d284 | ||
|
|
23aad24027 | ||
|
|
7ae5459fe7 | ||
|
|
0a8a88421e | ||
|
|
4f42a176ef | ||
|
|
4457067045 | ||
|
|
e7dc2048ad | ||
|
|
af6094bb06 | ||
|
|
0ab139cb24 | ||
|
|
500a7301f2 | ||
|
|
c92cef1780 | ||
|
|
49ceac1ac7 | ||
|
|
276a93605d | ||
|
|
0150c3fe23 | ||
|
|
34674501c3 | ||
|
|
f6206d4c30 | ||
|
|
6dcd9596a2 | ||
|
|
e165838e54 | ||
|
|
eae1707e97 | ||
|
|
ed2950f73b | ||
|
|
7eae8f68d8 | ||
|
|
0ab2d18b52 | ||
|
|
8ab621bc91 | ||
|
|
41e51bbeb5 | ||
|
|
66d9e8c038 | ||
|
|
f5e47b2b74 | ||
|
|
678a823e8b | ||
|
|
246746a82e | ||
|
|
00038453e1 | ||
|
|
9ce485eade | ||
|
|
8a6ff5a42b | ||
|
|
1eaac0072d | ||
|
|
35716a7e4c | ||
|
|
e0f89beebb | ||
|
|
b8bf2a707c | ||
|
|
7d616be8f4 | ||
|
|
0a7e9f9f8f | ||
|
|
226e188903 | ||
|
|
590d454119 | ||
|
|
06d5bd799f | ||
|
|
7fe6541b7c | ||
|
|
0f30fa5fb9 | ||
|
|
2c362315ad | ||
|
|
7dd79edc52 | ||
|
|
3d6f25a9d3 | ||
|
|
b3c21feba3 | ||
|
|
fe54fadbf8 | ||
|
|
caa0722d49 | ||
|
|
fee32622f1 | ||
|
|
d1910e4274 | ||
|
|
45eb11d7f4 | ||
|
|
874179fa75 | ||
|
|
8b16cb4bae | ||
|
|
777ed55c21 | ||
|
|
fcafd8b05e | ||
|
|
5f1cadfea4 | ||
|
|
917a981096 | ||
|
|
89f8b6060f | ||
|
|
345346315a | ||
|
|
f1c27e1798 | ||
|
|
4316024dc7 | ||
|
|
ca05aa84ff | ||
|
|
d10d7dc298 | ||
|
|
063af1dbc7 | ||
|
|
ef6fe43251 | ||
|
|
2659055c31 | ||
|
|
f5ed757780 | ||
|
|
e114b842ba | ||
|
|
12e2116acb | ||
|
|
bccc97d25f | ||
|
|
a9bcc75733 | ||
|
|
7b87475af8 | ||
|
|
2ac218886a | ||
|
|
97503897f0 | ||
|
|
7e06e32cb6 | ||
|
|
15b813c6af | ||
|
|
a71c9107bd | ||
|
|
4c2110189b | ||
|
|
df750c2a71 | ||
|
|
3eb412b750 | ||
|
|
5c7b7e4182 | ||
|
|
2b0fad87d2 | ||
|
|
8817d41275 | ||
|
|
04ef3f9496 | ||
|
|
fbe26e13ae | ||
|
|
4408dffa87 | ||
|
|
546192865c | ||
|
|
7afa550ba4 | ||
|
|
21a9372320 | ||
|
|
f4a2eec984 | ||
|
|
94b9455c9c | ||
|
|
c41c71c8c1 | ||
|
|
dde647c570 | ||
|
|
445a89ba6e | ||
|
|
07495f6621 | ||
|
|
5952edc550 | ||
|
|
b66a038a2f | ||
|
|
df1c8a64e5 | ||
|
|
3e2aebcd10 | ||
|
|
0d279cb4dd | ||
|
|
3942a76d15 | ||
|
|
1bebf3d3d6 | ||
|
|
d5f54c240a | ||
|
|
8868ecd11a | ||
|
|
8a31c57bfa | ||
|
|
7e2cce4a8d | ||
|
|
bfb217c203 | ||
|
|
593e55af95 | ||
|
|
29af62f956 | ||
|
|
2b022e1871 | ||
|
|
783a4d3996 | ||
|
|
ddfe925f8c | ||
|
|
1735bcab21 | ||
|
|
ff6cacf766 | ||
|
|
2a72eae77f | ||
|
|
39fb7fdfe3 | ||
|
|
e0facf35c8 | ||
|
|
73d48c8ac4 | ||
|
|
5080059143 | ||
|
|
90d5b77d74 | ||
|
|
e0f987328b | ||
|
|
04c3a925cc | ||
|
|
219b29991b | ||
|
|
5120045459 | ||
|
|
3bca4a73f4 | ||
|
|
b4857ab036 | ||
|
|
f3ae041691 | ||
|
|
a84627a465 | ||
|
|
78aeca6399 | ||
|
|
5dd8f0a0b7 | ||
|
|
9378fc7276 | ||
|
|
3a0d8069b6 | ||
|
|
bab42014a4 | ||
|
|
74fed5695f | ||
|
|
3be368c333 | ||
|
|
a4a8e2614e | ||
|
|
58eb151ba1 | ||
|
|
21d21ea7f0 | ||
|
|
45dc5e236d | ||
|
|
0597eeed6b | ||
|
|
0a66fe196a | ||
|
|
3f21b4aa7d | ||
|
|
b97186c5bf | ||
|
|
874b24fb32 | ||
|
|
97889b5e55 | ||
|
|
4b134194cf | ||
|
|
24b60a25d5 | ||
|
|
3e069376a7 | ||
|
|
5bdb2c9e86 | ||
|
|
71b0c5f805 | ||
|
|
a1f62e7a1f | ||
|
|
186c40801a | ||
|
|
bf867e84d6 | ||
|
|
27035a8e1d | ||
|
|
db3770c3e5 | ||
|
|
46337ca554 | ||
|
|
4f0f288000 | ||
|
|
8060bae1b8 | ||
|
|
ea32c97b82 | ||
|
|
99efbd30f1 | ||
|
|
a9eb7cb4fd | ||
|
|
a11cab0f88 | ||
|
|
447324b59f | ||
|
|
e580e815f0 | ||
|
|
5fc00840f2 | ||
|
|
ff59dcd66e | ||
|
|
0a1d82de2a | ||
|
|
26837645ac | ||
|
|
c318699d6a | ||
|
|
e5caab635a | ||
|
|
d5f96295ff | ||
|
|
4a6c78aaf3 | ||
|
|
c4d214f5c0 | ||
|
|
1ed7e6cb1c | ||
|
|
6b7f80f24a | ||
|
|
220217561a | ||
|
|
75e857a510 | ||
|
|
052bdd8eb0 | ||
|
|
9e54de8a8a | ||
|
|
fd2809e367 | ||
|
|
18874c2069 | ||
|
|
115eb0ddef | ||
|
|
9940258725 | ||
|
|
74e14a4fc3 | ||
|
|
90ea386c62 | ||
|
|
a5b132dfd7 | ||
|
|
157debf8e4 | ||
|
|
74a5528f17 | ||
|
|
21de9e938a | ||
|
|
9c4d3cd6e2 | ||
|
|
0616d184e0 | ||
|
|
6f8b3a6242 | ||
|
|
9a006dc84a | ||
|
|
d2dbaf52a1 | ||
|
|
0a9145cd3c | ||
|
|
e778b02c0c | ||
|
|
8fb5f0ef3c | ||
|
|
43f5e4360d | ||
|
|
cdc8640218 | ||
|
|
21f8d7a967 | ||
|
|
801d92bbba | ||
|
|
77116172e4 | ||
|
|
9d3c0f4ff0 | ||
|
|
6382d2b730 | ||
|
|
6ad4493a91 | ||
|
|
f070dc5527 | ||
|
|
fc6e7c81d3 | ||
|
|
59a725c52c | ||
|
|
51b3d7cb4a | ||
|
|
3baff4e675 | ||
|
|
06911e7ad0 | ||
|
|
4e0f3e35db | ||
|
|
fd6ce57003 | ||
|
|
4f55be4f19 | ||
|
|
bd20c83919 | ||
|
|
48f8a45031 | ||
|
|
680cec5abb | ||
|
|
e8b3c66c21 | ||
|
|
ecdc3be52e | ||
|
|
301bdf2186 | ||
|
|
1c1b67e000 | ||
|
|
662870baf4 | ||
|
|
bc85520f5e | ||
|
|
d3be7a3edf | ||
|
|
6cd0da821a | ||
|
|
1333c41811 | ||
|
|
55eceb4080 | ||
|
|
e95629f827 | ||
|
|
6332ee6edb | ||
|
|
901d56f898 | ||
|
|
010c3494fd | ||
|
|
5ebc345e95 | ||
|
|
b3a768a04b | ||
|
|
f414af4d82 | ||
|
|
b743ca8eb1 | ||
|
|
a3a5bffce4 | ||
|
|
7971f3cbd8 | ||
|
|
d1b80aa4d4 | ||
|
|
08d9f7d967 | ||
|
|
5f62b4a89d | ||
|
|
776502e6db | ||
|
|
39e0a1fb9e | ||
|
|
5ed02d2f0d | ||
|
|
72e864b013 | ||
|
|
5ab012163e | ||
|
|
1900686bc4 | ||
|
|
809a294f9d | ||
|
|
cf3f6ede72 | ||
|
|
b76a90304d | ||
|
|
7af5b24b03 | ||
|
|
1f81fb9284 | ||
|
|
4e105492ca | ||
|
|
abf7dd5fa8 | ||
|
|
6c877b6d44 | ||
|
|
86d2998dc0 | ||
|
|
201daf8ff3 | ||
|
|
1140f5f6cb | ||
|
|
0a91d60677 | ||
|
|
5cd8f5681e | ||
|
|
b2babd9502 | ||
|
|
10faaaf531 | ||
|
|
7642c5a50d | ||
|
|
42dba3170d | ||
|
|
5c90a742ba | ||
|
|
9e57ef074d | ||
|
|
9e9b1c3c98 | ||
|
|
13d5deddec | ||
|
|
8522760c70 | ||
|
|
5bc2282ac5 | ||
|
|
2944b2a8f6 | ||
|
|
264480b9bc | ||
|
|
7d52cc46dc | ||
|
|
37c9fd278c | ||
|
|
3d6fe0a565 | ||
|
|
da488f76d2 | ||
|
|
6d28de77d1 | ||
|
|
a8bd46c521 | ||
|
|
1ec3a74d4c | ||
|
|
076449a33d | ||
|
|
1b5cc2abf1 | ||
|
|
b176225910 | ||
|
|
082d1eea48 | ||
|
|
e99dfbae0d | ||
|
|
d6735bed4f | ||
|
|
b024d93fa0 | ||
|
|
dbe8554724 | ||
|
|
9f79e64678 | ||
|
|
f513cae66e | ||
|
|
37b807210e | ||
|
|
15e26ba929 | ||
|
|
db1795981f | ||
|
|
2a40baf509 | ||
|
|
2dbf72e452 | ||
|
|
67ae716c60 | ||
|
|
8dfb130d19 | ||
|
|
2a42fea32a | ||
|
|
f69edefddf | ||
|
|
6f3b5fc559 | ||
|
|
7275a34e1f | ||
|
|
687e2ecaaa | ||
|
|
64312aedae | ||
|
|
391876e557 | ||
|
|
cb95a7d418 | ||
|
|
830f806dee | ||
|
|
21c2316f6b | ||
|
|
930a1ae450 | ||
|
|
aec331b5a6 | ||
|
|
1db6c281b1 | ||
|
|
5b70ca81a7 | ||
|
|
d00df785a5 | ||
|
|
d0c6e7a285 | ||
|
|
058995a0b6 | ||
|
|
fd3be887b4 | ||
|
|
84802d9065 | ||
|
|
90d696d826 | ||
|
|
aeb9e2ad9f | ||
|
|
26ab1bfd4f | ||
|
|
23d44cb28b | ||
|
|
de4b352495 | ||
|
|
d635117194 | ||
|
|
c0412d368e | ||
|
|
7ec30f0796 | ||
|
|
9815a55221 | ||
|
|
7c108e5128 | ||
|
|
4873031c37 | ||
|
|
15d23870c1 | ||
|
|
2abbf7e762 | ||
|
|
1578aa7cb6 | ||
|
|
f2230751e7 | ||
|
|
8fc0dda26d | ||
|
|
0ab71badfa | ||
|
|
0f24d924f9 | ||
|
|
518a0de95f | ||
|
|
fa5648c0c3 | ||
|
|
bd620a764d | ||
|
|
cff42b5d27 | ||
|
|
25eef2a40e | ||
|
|
db276d5f1f | ||
|
|
ce0d906c35 | ||
|
|
520826abc0 | ||
|
|
5e1d6014d5 | ||
|
|
32af17317c | ||
|
|
3e7cc8a0a0 | ||
|
|
f717ce52fa | ||
|
|
d880f3c628 | ||
|
|
0a97717b35 | ||
|
|
029403ea10 | ||
|
|
e4ec65622f | ||
|
|
3ee3b97255 | ||
|
|
a3dd6ce891 | ||
|
|
0c97d7701e | ||
|
|
b852b6f55c | ||
|
|
d3b7b857a4 | ||
|
|
63ad3f9290 | ||
|
|
40cc30e054 | ||
|
|
23374e98ae | ||
|
|
40f544fffc | ||
|
|
4fc69c9a2c | ||
|
|
2e5a485804 | ||
|
|
2d10b5be90 | ||
|
|
b0d0bb7953 | ||
|
|
8c69b85280 | ||
|
|
9d6d1e4ff8 | ||
|
|
e71c72b4a2 | ||
|
|
bf4b29b3e3 | ||
|
|
8e0809ceba | ||
|
|
230af48899 | ||
|
|
9dc459192e | ||
|
|
8de2301c6b | ||
|
|
f66b343edd | ||
|
|
ff82d36e94 | ||
|
|
c3e937f5e0 | ||
|
|
25c5e823d9 | ||
|
|
528b8b1271 | ||
|
|
b88344a6c0 | ||
|
|
9e135cc81d | ||
|
|
72ef288357 | ||
|
|
426ac29ca5 | ||
|
|
ceb330e3e4 | ||
|
|
7ea8f741f2 | ||
|
|
443c9fba03 | ||
|
|
0227f79bd7 | ||
|
|
2812eaa13d | ||
|
|
cb293fcdb3 | ||
|
|
706428fe98 | ||
|
|
698f5f951e | ||
|
|
155c12f657 | ||
|
|
a36c731d15 | ||
|
|
6a793536dd | ||
|
|
27532a4237 | ||
|
|
467f69f50e | ||
|
|
32e724e744 | ||
|
|
5fb891c495 | ||
|
|
d56cefdc7a | ||
|
|
80c67d37c0 | ||
|
|
1e29d9463e | ||
|
|
75f6a53269 | ||
|
|
b5a8f3156c | ||
|
|
de245c08ce | ||
|
|
76a7f25083 | ||
|
|
d9a96fbb29 | ||
|
|
16617660c3 | ||
|
|
a83d9a075f | ||
|
|
eca3fb1e19 | ||
|
|
41bc799c3f | ||
|
|
44726c7411 | ||
|
|
8f7f9ec367 | ||
|
|
21de8f2284 | ||
|
|
15797a89e7 | ||
|
|
c340a1d47f | ||
|
|
71cec39bc4 | ||
|
|
882542318c | ||
|
|
7238ef4f55 | ||
|
|
1049d65621 | ||
|
|
552ae836e9 | ||
|
|
1852c0a88c | ||
|
|
d0d399bdfc | ||
|
|
3975fe0f02 | ||
|
|
0739cfc240 | ||
|
|
f31bf4f7c9 | ||
|
|
f413671915 | ||
|
|
e67a210e95 | ||
|
|
51dfa9a247 | ||
|
|
7c121b8ba6 | ||
|
|
ae92a5c25e | ||
|
|
fabd3b8421 | ||
|
|
94d754e63a | ||
|
|
f869542a47 | ||
|
|
2b616b688d | ||
|
|
0925ea6f9c | ||
|
|
87b5565679 | ||
|
|
adcecf969a | ||
|
|
f3831665d4 | ||
|
|
9db8d44654 | ||
|
|
e1075a3bbf | ||
|
|
3b6f499fc5 | ||
|
|
2c1fbe103b | ||
|
|
1543862f3b | ||
|
|
5b7b34a9f0 | ||
|
|
fb6271eecf | ||
|
|
031526b619 | ||
|
|
e42ca2dbf2 | ||
|
|
4f5816f42e | ||
|
|
555d0d3378 | ||
|
|
1495b756d3 | ||
|
|
7f78d00b97 | ||
|
|
bfd58b3cdf | ||
|
|
e260b9473a | ||
|
|
ccc56d446f | ||
|
|
31a4e38bc0 | ||
|
|
284366e6bb | ||
|
|
9c6dda9bd2 | ||
|
|
e5f6f8dfa7 | ||
|
|
0232117de5 | ||
|
|
96700fe3d1 | ||
|
|
367a4e17a2 | ||
|
|
eba0d7c1e0 | ||
|
|
341a4a0d60 | ||
|
|
d116424241 | ||
|
|
db881ee011 | ||
|
|
34bed44a9b | ||
|
|
d431aa4b59 | ||
|
|
2512bebc62 | ||
|
|
309dbd7585 | ||
|
|
a1f28caa8e | ||
|
|
4204393337 | ||
|
|
789e34702b | ||
|
|
47bc1bf88d | ||
|
|
9867227ccd | ||
|
|
26302d478e | ||
|
|
0decf94c9c | ||
|
|
0ae78efd43 | ||
|
|
b5af6b0bf9 | ||
|
|
3eabefd3d6 | ||
|
|
7ff6a6e0e7 | ||
|
|
94dc611024 | ||
|
|
0b28107432 | ||
|
|
fe717dad7f | ||
|
|
fb3816e2e5 | ||
|
|
535bf4c848 | ||
|
|
1fc60b0682 | ||
|
|
302f1ee8e3 | ||
|
|
42aec56b8e | ||
|
|
199c00c59b | ||
|
|
107bb14555 | ||
|
|
5820792013 | ||
|
|
0e20248494 | ||
|
|
c4f4b247bc | ||
|
|
0d66bc49c2 | ||
|
|
bdc78a826f | ||
|
|
6570062e61 | ||
|
|
820facb833 | ||
|
|
a933fcf7e2 | ||
|
|
2d3039c6a2 | ||
|
|
aca01044f4 | ||
|
|
dffd1b1d69 | ||
|
|
14fc64f2f1 | ||
|
|
04791d06d0 | ||
|
|
a485451328 | ||
|
|
c7d399c122 | ||
|
|
df155bed89 | ||
|
|
3ee3630d22 | ||
|
|
6b2911b8c8 | ||
|
|
d474200d7f | ||
|
|
cc6f6277f6 | ||
|
|
0521e988bc | ||
|
|
832ff39eb6 | ||
|
|
134798e82f | ||
|
|
b6f43966ea | ||
|
|
1dab26bcbc | ||
|
|
afa7be6fdc | ||
|
|
36290c20aa | ||
|
|
c33c7eb68e | ||
|
|
5da33afecd | ||
|
|
a49b49aaa9 | ||
|
|
225fd0d05b | ||
|
|
c43b4f9cf0 | ||
|
|
3581f4c87e | ||
|
|
7a4e0cc850 | ||
|
|
ba49da75de | ||
|
|
f329a5950e | ||
|
|
d95158003c | ||
|
|
1be6223a31 | ||
|
|
76178b423e | ||
|
|
195e537499 | ||
|
|
418abc2b0a | ||
|
|
277f0346f2 | ||
|
|
6148cbb122 | ||
|
|
3847bfc41a | ||
|
|
783424dd26 | ||
|
|
4c1861dd27 | ||
|
|
7f6486c77d | ||
|
|
1b2a50f9a3 | ||
|
|
5dadf92a62 | ||
|
|
76821addd7 | ||
|
|
a769c55c72 | ||
|
|
347ec91bfc | ||
|
|
4245767357 | ||
|
|
74d196ad50 | ||
|
|
3c4649748d | ||
|
|
22985900a8 | ||
|
|
86300a8e3c | ||
|
|
32ab2ae201 | ||
|
|
23e4a2e28e | ||
|
|
40515e62ac | ||
|
|
b56490650c | ||
|
|
b5751795b5 | ||
|
|
43d6151506 | ||
|
|
8ccfb606c0 | ||
|
|
3b74cd5676 | ||
|
|
26e766ee19 | ||
|
|
6f96dc8f23 | ||
|
|
1c3fdb73fb | ||
|
|
332be7edd6 | ||
|
|
76e0e935f0 | ||
|
|
7e6eb65950 | ||
|
|
b0a56a431b | ||
|
|
166316e0c5 | ||
|
|
4194e66d81 | ||
|
|
c9b9d86892 | ||
|
|
f2d00e6e42 | ||
|
|
acfc166a11 | ||
|
|
3255382132 | ||
|
|
25831bfb60 | ||
|
|
d011ca0626 | ||
|
|
5eadbe7ecd | ||
|
|
ea7e0c6204 | ||
|
|
2dcb126e6d | ||
|
|
438525e8ec | ||
|
|
eb66a403d9 | ||
|
|
26ac612474 | ||
|
|
2e28df3e8a | ||
|
|
43613e3b10 | ||
|
|
555dd2e726 | ||
|
|
3260f46543 | ||
|
|
f9de23402a | ||
|
|
9e155cf94a | ||
|
|
dcace43ce2 | ||
|
|
527e3d6cd0 | ||
|
|
befce5b887 | ||
|
|
9929c96650 | ||
|
|
c7d45c28cf | ||
|
|
87cc3fd714 | ||
|
|
282d58a6fe | ||
|
|
b4aec91d67 | ||
|
|
d2a149ee23 | ||
|
|
8a72f94b3d | ||
|
|
fc4ed1c50b | ||
|
|
ae1d0a18f3 | ||
|
|
9709ca331e | ||
|
|
b4a9641c31 | ||
|
|
cc68e6b6e6 | ||
|
|
60c47cfca1 | ||
|
|
720abd4e04 | ||
|
|
b4fe694d1b | ||
|
|
c117785554 | ||
|
|
a9d6a15924 | ||
|
|
e9723de6c3 | ||
|
|
b8c61b5236 | ||
|
|
da0cf01eee | ||
|
|
b77e1eb94b | ||
|
|
e40f266042 | ||
|
|
69fb15d3cb | ||
|
|
188956a4af | ||
|
|
c7dd4a7f2b | ||
|
|
111f1729c9 | ||
|
|
164372b46a | ||
|
|
1af3794fed | ||
|
|
885f4ded0c | ||
|
|
9bb018d8d9 | ||
|
|
58187e00e6 | ||
|
|
5110a8c636 | ||
|
|
ddbe883d47 | ||
|
|
945ac82b6a | ||
|
|
84148ad07a | ||
|
|
25553a28a1 | ||
|
|
c2f5846a8e | ||
|
|
3f98e9451a | ||
|
|
6c72194346 | ||
|
|
822dd24756 | ||
|
|
262ed38429 | ||
|
|
5ff66ce4dd | ||
|
|
ca73b8e0b8 | ||
|
|
2d49ace0e3 | ||
|
|
d52f22044e | ||
|
|
2bf052aac9 | ||
|
|
a79c9d5f4f | ||
|
|
6f9bcf1858 | ||
|
|
d1b00ba95d | ||
|
|
bb45bee7f8 | ||
|
|
246f0b21fd | ||
|
|
394443b6d1 | ||
|
|
fc1dd2daac | ||
|
|
ba1b6f321e | ||
|
|
06402cf1c6 | ||
|
|
dfc33389d8 | ||
|
|
4e83752655 | ||
|
|
7e28718681 | ||
|
|
bf452e922a | ||
|
|
4911a74cac | ||
|
|
a2bda5a4db | ||
|
|
800abc605f | ||
|
|
ac83b67632 | ||
|
|
00bc7dccb9 | ||
|
|
04aa9f0e61 | ||
|
|
353e97a4c1 | ||
|
|
7b28387bb4 | ||
|
|
1889fe23c5 | ||
|
|
9832fbe042 | ||
|
|
084070475d | ||
|
|
cb42ca8765 | ||
|
|
45d7e66488 | ||
|
|
93a1616933 | ||
|
|
9a87a2ff0d | ||
|
|
bdff05feaf | ||
|
|
937e3d0b94 | ||
|
|
8503aba583 | ||
|
|
bb8223d4ff | ||
|
|
af3c8f66f7 | ||
|
|
3097a1b17e | ||
|
|
54b80f74b2 | ||
|
|
9a78d28cd7 | ||
|
|
5df197e814 | ||
|
|
5fe09e9787 | ||
|
|
6be5360bdd | ||
|
|
2b9007958b | ||
|
|
2f9c126d34 | ||
|
|
1ed817932b | ||
|
|
0ccadb246f | ||
|
|
b67235a7e5 | ||
|
|
93853613bd | ||
|
|
1ecb4fedcc | ||
|
|
c8fdf3731a | ||
|
|
f8e766ffc0 | ||
|
|
050489dd80 | ||
|
|
2db966ba47 | ||
|
|
aa54d65f63 | ||
|
|
37dc17ae15 | ||
|
|
cd6b3d7dee | ||
|
|
aec0bc5029 | ||
|
|
e010e67717 | ||
|
|
44197d91c6 | ||
|
|
e3ee48b52e | ||
|
|
8ccf349458 | ||
|
|
2fce15db94 | ||
|
|
0cf50e8000 | ||
|
|
5b9630cf46 | ||
|
|
7dcbb409a9 | ||
|
|
286e057fae | ||
|
|
7d4fe341f1 | ||
|
|
76acb7bb58 | ||
|
|
5ef7cd7bdd | ||
|
|
ebb4628e66 | ||
|
|
0928060c75 | ||
|
|
d8f9a9a03c | ||
|
|
abfb8bbc34 | ||
|
|
82e01f7b17 | ||
|
|
a97609e920 | ||
|
|
f1e1bd41b9 | ||
|
|
57d35858d8 | ||
|
|
0b956c66c1 | ||
|
|
b1dfa1db61 | ||
|
|
cb579ecc62 | ||
|
|
ae235aa58c | ||
|
|
6302056182 | ||
|
|
5e20abd7f1 | ||
|
|
72a72dbc88 | ||
|
|
4766666913 | ||
|
|
fa6070c680 | ||
|
|
6267d11e51 | ||
|
|
822298ab66 | ||
|
|
2ad3ab7f0d | ||
|
|
f437fd6cd6 | ||
|
|
42b247756a | ||
|
|
fbbf2d5eb3 | ||
|
|
788a276616 | ||
|
|
196779ffb6 | ||
|
|
59a144baed | ||
|
|
ad825b80b0 | ||
|
|
45a0061660 | ||
|
|
8831c76fb4 | ||
|
|
66616e1cab | ||
|
|
fa281a0df0 | ||
|
|
d48a96599f | ||
|
|
7de7ef8e8c | ||
|
|
406a254523 | ||
|
|
eabd6f60ef | ||
|
|
e416d06aa9 | ||
|
|
65e073e6b8 | ||
|
|
45ac0d5dc6 | ||
|
|
c9ef034ab8 | ||
|
|
c89f38f4f8 | ||
|
|
858b57d77a | ||
|
|
9b87892036 | ||
|
|
ed69692f08 | ||
|
|
7119999df8 | ||
|
|
c16462a0ce | ||
|
|
272f96b405 | ||
|
|
3e519001a3 | ||
|
|
f442aad962 | ||
|
|
fa9b151c36 | ||
|
|
481bd6f57a | ||
|
|
f015227fc8 | ||
|
|
61ba16b779 | ||
|
|
f6e86bfcf8 | ||
|
|
498dd76730 | ||
|
|
ebd909dfe7 | ||
|
|
067ed1258b | ||
|
|
ef3c72a24f | ||
|
|
b257f476c9 | ||
|
|
d3af2be79a | ||
|
|
94aeb19245 | ||
|
|
e4ee392c27 | ||
|
|
cf48ad06ed | ||
|
|
63df85f7f9 | ||
|
|
b29611509e | ||
|
|
dd1cf0ec56 | ||
|
|
c04c73bbd7 | ||
|
|
0d287283d4 | ||
|
|
4ea5723b7f | ||
|
|
ea964124d6 | ||
|
|
75244853c1 | ||
|
|
c361c34b16 | ||
|
|
264b3a785b | ||
|
|
bb15e1d8ac | ||
|
|
68860ae866 | ||
|
|
61637f12c7 | ||
|
|
7538649e81 | ||
|
|
31efbe915e | ||
|
|
9e69627626 | ||
|
|
dd9da2853a | ||
|
|
b4f08fa8d5 | ||
|
|
670461c66f | ||
|
|
f6092b9128 | ||
|
|
a690cb36ff | ||
|
|
5ca8f4a3aa | ||
|
|
ba6e2f0a54 | ||
|
|
149f4e38a0 | ||
|
|
b5ee170726 | ||
|
|
04961880fe | ||
|
|
e22e8ffa0e | ||
|
|
d18d800947 | ||
|
|
3ca58ee65f | ||
|
|
21387b9a83 | ||
|
|
ccb0d491ed | ||
|
|
daa3200713 | ||
|
|
fec2d1f7ee | ||
|
|
eb8e95723c | ||
|
|
999bec19f1 | ||
|
|
dfb8de2349 | ||
|
|
59f1cdcc82 | ||
|
|
3eb8be6239 | ||
|
|
7dfc4a651d | ||
|
|
189d145393 | ||
|
|
26bec11d76 | ||
|
|
1e7071bff3 | ||
|
|
92390d1d59 | ||
|
|
b9b072119b | ||
|
|
b6d9fd8030 | ||
|
|
d383bc93c7 | ||
|
|
af88c68fca | ||
|
|
24537c4fdc | ||
|
|
130f495fb6 | ||
|
|
9d22a06969 | ||
|
|
e3783e0236 | ||
|
|
a5161eb7f1 | ||
|
|
82e448de7c | ||
|
|
680afe972e | ||
|
|
69ab2ce402 | ||
|
|
4374930065 | ||
|
|
9f3520cba5 | ||
|
|
d20cdc099d | ||
|
|
926e9ff3de | ||
|
|
70c277d8a1 | ||
|
|
0a5aac724a | ||
|
|
bb91d5495f | ||
|
|
995d94c124 | ||
|
|
ee7036f13d | ||
|
|
5a8a8dc292 | ||
|
|
7d84de6690 | ||
|
|
3520f9e9c2 | ||
|
|
b2df7981a9 | ||
|
|
9831e7bc3b | ||
|
|
90e37439e5 | ||
|
|
65f2490e89 | ||
|
|
c14a921e5a | ||
|
|
2bc8ffefce | ||
|
|
b88d8c85cb | ||
|
|
693f52d984 | ||
|
|
56a3bff42e | ||
|
|
bffeec1615 | ||
|
|
e4a9f115cb | ||
|
|
a717238f76 | ||
|
|
4a567ab97c | ||
|
|
90431b662b | ||
|
|
1c8f349a62 | ||
|
|
217004c70c | ||
|
|
a75608628e | ||
|
|
33130f39ee | ||
|
|
cae43e97cd | ||
|
|
cdfc0f6b71 | ||
|
|
bf6a18a414 | ||
|
|
16b66afa7a | ||
|
|
9e446210fb | ||
|
|
b0118d2d57 | ||
|
|
bc90309dd6 | ||
|
|
52c4093fb0 | ||
|
|
b47c12cbee | ||
|
|
fb14bc6016 | ||
|
|
cd6862b1a7 | ||
|
|
3d9160f2fa | ||
|
|
157a5d9902 | ||
|
|
f4972e2be2 | ||
|
|
85c8e6ba42 | ||
|
|
8e79bd8785 | ||
|
|
1ace7ce254 | ||
|
|
a00be5b60c | ||
|
|
aabe8d1d5e | ||
|
|
3da086438b | ||
|
|
c257c86387 | ||
|
|
ff3ae14c29 | ||
|
|
cd82a34392 | ||
|
|
baacd55910 | ||
|
|
511b7c4d92 | ||
|
|
f38851b7c6 | ||
|
|
432c5837f0 | ||
|
|
cc599f544a | ||
|
|
e886c85134 | ||
|
|
05eb716b85 | ||
|
|
61eb7b0a6a | ||
|
|
f0dade5856 | ||
|
|
8567f6b13c | ||
|
|
b740316918 | ||
|
|
2119d08543 | ||
|
|
599ff66522 | ||
|
|
6d2eb04ada | ||
|
|
0aea75edb7 | ||
|
|
2ca8231ab4 | ||
|
|
e00116551c | ||
|
|
4d55e05b07 | ||
|
|
9ff3f85432 | ||
|
|
65c584aeda | ||
|
|
2540a2dfd9 | ||
|
|
70e9187bcb | ||
|
|
5e2fa13471 | ||
|
|
4f8610f895 | ||
|
|
95f9e97af8 | ||
|
|
695b073080 | ||
|
|
3a28935bfe | ||
|
|
468ed1a9ce | ||
|
|
a6052d7900 | ||
|
|
e42836b07f | ||
|
|
16373919d4 | ||
|
|
92f9c908f6 | ||
|
|
480e1e17c8 | ||
|
|
274f5f2f1f | ||
|
|
02d4f9cbba | ||
|
|
d10daf0991 | ||
|
|
8c9656b799 | ||
|
|
396ea3d0ee | ||
|
|
3efee4a855 | ||
|
|
ece6759fa7 | ||
|
|
246d945f39 | ||
|
|
4a3bc486d0 | ||
|
|
996d041581 | ||
|
|
d48e4adcd4 | ||
|
|
7f84b7ab83 | ||
|
|
3e26bd6a17 | ||
|
|
e964c62907 | ||
|
|
fbb4965263 | ||
|
|
88139d95a7 | ||
|
|
5875b1988b | ||
|
|
2a2091595b | ||
|
|
d77883dd7a | ||
|
|
adea7dadec | ||
|
|
cf4ce2dc08 | ||
|
|
e74dfb2ba6 | ||
|
|
aaea9ff018 | ||
|
|
206b88eba2 | ||
|
|
8f5373f034 | ||
|
|
dfa0a16487 | ||
|
|
8b9f6f49ef | ||
|
|
bcac978810 | ||
|
|
d17a41f7f1 | ||
|
|
503f71f004 | ||
|
|
4543288ea7 | ||
|
|
9307105b3f | ||
|
|
567daf9946 | ||
|
|
558b22c36a | ||
|
|
f076dddfe3 | ||
|
|
af44730418 | ||
|
|
4ec1140cb4 | ||
|
|
2a5d20058f | ||
|
|
51f38e0a76 | ||
|
|
54eb42d658 | ||
|
|
a7a6f4cec6 | ||
|
|
3968a8e0dc | ||
|
|
9e8ff27a7f | ||
|
|
7dd3d2b040 | ||
|
|
13678cb8d5 | ||
|
|
890aa6f9ac | ||
|
|
646cf44b83 | ||
|
|
bf789ca97b | ||
|
|
9b90ff10f4 | ||
|
|
0cc719a823 | ||
|
|
4cd026dfe9 | ||
|
|
3120b49dba | ||
|
|
fb1999c474 | ||
|
|
2c37961f7b | ||
|
|
1abc863f82 | ||
|
|
c9be03c0bc | ||
|
|
fd89209233 | ||
|
|
2e362fbb64 | ||
|
|
11b687cdc2 | ||
|
|
747c920420 | ||
|
|
cac51737cb | ||
|
|
7789379914 | ||
|
|
08e2bfe9a2 | ||
|
|
56a854ec88 | ||
|
|
de4ff4e58d | ||
|
|
991c2afedb | ||
|
|
cded92662f | ||
|
|
2c3fa0fd8f | ||
|
|
ec08432f92 | ||
|
|
1bab4d6937 | ||
|
|
d1fed11d0d | ||
|
|
e96053e268 | ||
|
|
9b98cbb894 | ||
|
|
349578fb6e | ||
|
|
7c186e4fcc | ||
|
|
a4fcbb9f67 | ||
|
|
763b64cc57 | ||
|
|
d57e6c5bf2 | ||
|
|
80711cc00a | ||
|
|
8b61b9ebfe | ||
|
|
e00e778bce | ||
|
|
81e70925c4 | ||
|
|
ddd32bd600 | ||
|
|
2713573b9b | ||
|
|
cf2e8bbc0b | ||
|
|
bc56c1a0e1 | ||
|
|
bfbf7af411 | ||
|
|
c151221d12 | ||
|
|
b8489724ef | ||
|
|
7a2f556682 | ||
|
|
92d883db87 | ||
|
|
cb931d7af0 | ||
|
|
6b54b57cb9 | ||
|
|
1ca4348ca0 | ||
|
|
3ca7006e3a | ||
|
|
339d2a7bf3 | ||
|
|
0d3debf9b9 | ||
|
|
7f74a4f4b5 | ||
|
|
220718cb58 | ||
|
|
be2acbd3eb | ||
|
|
9249c74b7b | ||
|
|
2b87817ba2 | ||
|
|
883c0f9dfe | ||
|
|
3ffc58d442 | ||
|
|
1bb4b0156f | ||
|
|
92d2be3f5e | ||
|
|
a7cded21f7 | ||
|
|
74156b7ed8 | ||
|
|
821f320347 | ||
|
|
eee9274098 | ||
|
|
8dd1f89225 | ||
|
|
f2459ea904 | ||
|
|
e51f7bfbff | ||
|
|
e2215ced34 | ||
|
|
b14c8e82a0 | ||
|
|
e7d50d2614 | ||
|
|
bca92883d2 | ||
|
|
ed865e38de | ||
|
|
dec45109d3 | ||
|
|
48c267c5c1 | ||
|
|
c041d39cab | ||
|
|
52c18e77ae | ||
|
|
52a189cdd2 | ||
|
|
dca8a47da8 | ||
|
|
6be9317fd7 | ||
|
|
bee99ca8d0 | ||
|
|
48dc89cf13 | ||
|
|
fc7f609234 | ||
|
|
0bed9b3c2e | ||
|
|
31fa73518b | ||
|
|
c6fd11157a | ||
|
|
ca73a57de7 | ||
|
|
38a545af92 | ||
|
|
b91b340afd | ||
|
|
8e7b1c97df | ||
|
|
c9c55fe0c9 | ||
|
|
d553160d65 | ||
|
|
b8a4df2f50 | ||
|
|
36edbf6ea9 | ||
|
|
f966535ea9 | ||
|
|
07cc26a144 | ||
|
|
178c4d15b7 | ||
|
|
b0b68c695c | ||
|
|
219a3658d6 | ||
|
|
d2310fc2ea | ||
|
|
3fe0ccd934 | ||
|
|
f902ebadcc | ||
|
|
57323af167 | ||
|
|
20b0b5fc8e | ||
|
|
bb72b8cc93 | ||
|
|
fec5516da9 | ||
|
|
ec9a092751 | ||
|
|
f78f8b6b12 | ||
|
|
f97b79bc16 | ||
|
|
42c8a77755 | ||
|
|
c692cc6a70 | ||
|
|
2b8d8d4e9c | ||
|
|
2ae8032ace | ||
|
|
c4416584de | ||
|
|
86d8b49113 | ||
|
|
7a5d870f67 | ||
|
|
b43459232a | ||
|
|
e2b4e60c9e | ||
|
|
17543535e3 | ||
|
|
901ce23cd2 | ||
|
|
1b52d8065e | ||
|
|
c6db901051 | ||
|
|
39edb1ad37 | ||
|
|
a397aa188c | ||
|
|
dd08751f3f | ||
|
|
ef6a9184ba | ||
|
|
575fb6fc60 | ||
|
|
4147d6c67e | ||
|
|
115c17ab90 | ||
|
|
d892c7290c | ||
|
|
63d8a902d5 | ||
|
|
43152fcf19 | ||
|
|
1f135f1fa5 | ||
|
|
c71d378eab | ||
|
|
bb82bf762f | ||
|
|
6fa5689aaf | ||
|
|
d78bef72ea | ||
|
|
e410de9552 | ||
|
|
8dc262b415 | ||
|
|
7fa494815f | ||
|
|
929bb70e5a | ||
|
|
36fb262fa0 | ||
|
|
b8cc783583 | ||
|
|
67b3d383e4 | ||
|
|
9144ccac6b | ||
|
|
04020d5ae2 | ||
|
|
de4f1d09af | ||
|
|
d16f9efeec | ||
|
|
8ba5dfe447 | ||
|
|
27d7f5f190 | ||
|
|
c43c4c42c8 | ||
|
|
3759071449 | ||
|
|
613283f656 | ||
|
|
506b6b51ce | ||
|
|
c7eccfb714 | ||
|
|
85d59945a0 | ||
|
|
98be19b29f | ||
|
|
4c8b490c89 | ||
|
|
8dd1745149 | ||
|
|
66406d86c1 | ||
|
|
35832b07b9 | ||
|
|
357683cbb9 | ||
|
|
15bc6159f9 | ||
|
|
7cdafa76a5 | ||
|
|
e84b0c91f8 | ||
|
|
cecffee3d3 | ||
|
|
d039a40d7c | ||
|
|
90463d8613 | ||
|
|
90c965a6ba | ||
|
|
e961691645 | ||
|
|
6270d2408f | ||
|
|
7ef1842237 | ||
|
|
faf616cbea | ||
|
|
4a09e18d9a | ||
|
|
983aca515d | ||
|
|
eb184d3c68 | ||
|
|
187ecf511f | ||
|
|
0ab35a3ca3 | ||
|
|
8e208bc76d | ||
|
|
1b4d92007e | ||
|
|
4101d7664d | ||
|
|
e128c3094a | ||
|
|
95869eeb70 | ||
|
|
d977bbd61c | ||
|
|
a16a6211e2 | ||
|
|
a75013e43e | ||
|
|
1e157e95b6 | ||
|
|
04dbfcd0e6 | ||
|
|
73440e2287 | ||
|
|
ee4459691f | ||
|
|
379c6045b9 | ||
|
|
00d0ddb4b5 | ||
|
|
be08c01be6 | ||
|
|
fb6abe34df | ||
|
|
b0d107f916 | ||
|
|
6d1ff7c3f3 | ||
|
|
6f67132f4b | ||
|
|
859d21162c | ||
|
|
c739a7ea2f | ||
|
|
7e48875fd4 | ||
|
|
474e86306f | ||
|
|
b436a660f1 | ||
|
|
d36460e30b | ||
|
|
0e236f9d88 | ||
|
|
72eb61951b | ||
|
|
22b2503839 | ||
|
|
d7b5719dfd | ||
|
|
4322d195d3 | ||
|
|
4ecad65926 | ||
|
|
f12025b86e | ||
|
|
5cfea54b06 | ||
|
|
c24b75953a | ||
|
|
f3d52f4932 | ||
|
|
ea6d339b9c | ||
|
|
284a9999b3 | ||
|
|
b241872a00 | ||
|
|
b7034d3213 | ||
|
|
0a0ced3e8e | ||
|
|
e7449b4d56 | ||
|
|
fab34c9e49 | ||
|
|
7df08c2120 | ||
|
|
b94e0ef797 | ||
|
|
00fed4f995 | ||
|
|
e8a7802e94 | ||
|
|
bfbc1ebb13 | ||
|
|
a802bb2736 | ||
|
|
3c5838cafd | ||
|
|
9217e8336d | ||
|
|
13cfc2d7bc | ||
|
|
171cebed1c | ||
|
|
1dcef87e1c | ||
|
|
23fb602a33 | ||
|
|
a8a549e8fc | ||
|
|
44790e99ea | ||
|
|
a3f6717c59 | ||
|
|
9587480e29 | ||
|
|
c51dbae8b6 | ||
|
|
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 | ||
|
|
f38747aa28 | ||
|
|
88d05578a8 | ||
|
|
b24c8b3410 | ||
|
|
2c8dbb94fc | ||
|
|
3da2ec5112 | ||
|
|
c3545ef060 | ||
|
|
4308a7cf79 | ||
|
|
e629c90a71 | ||
|
|
e7f07f5bfc | ||
|
|
da7972c119 | ||
|
|
efc476e50b | ||
|
|
33f4e8cf73 | ||
|
|
643f300792 | ||
|
|
bd601ad2da | ||
|
|
9f1a7a1c20 | ||
|
|
bd721c1310 | ||
|
|
98138a02e3 | ||
|
|
f7004e7a7c | ||
|
|
1db25d4b20 | ||
|
|
507e198b70 | ||
|
|
5c721ee435 | ||
|
|
9a44f0c869 | ||
|
|
8c5f9b4501 | ||
|
|
713bf66a12 | ||
|
|
78e9f5bd1a | ||
|
|
f03abf2600 | ||
|
|
d5eee5b56c | ||
|
|
8899684092 | ||
|
|
8e30e4925c | ||
|
|
906c7a8ea1 | ||
|
|
324fc149b3 | ||
|
|
29b1f2afae | ||
|
|
87fa14b281 | ||
|
|
b6db86da50 | ||
|
|
043947fdcb | ||
|
|
713509527a | ||
|
|
ae7eedf523 | ||
|
|
0a1bd748d7 | ||
|
|
1b351b7743 | ||
|
|
e409bd298a | ||
|
|
caef289b9b | ||
|
|
84628bd9fc | ||
|
|
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 | ||
|
|
56a74bc54c | ||
|
|
a20358b61b | ||
|
|
341d9d1697 | ||
|
|
9cee424383 | ||
|
|
37cb51cfd6 | ||
|
|
84b7bfb989 | ||
|
|
04d17e4839 |
4
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
github: [Aircoookie]
|
github: [Aircoookie,blazoncek]
|
||||||
custom: ['https://paypal.me/Aircoookie']
|
custom: ['https://paypal.me/Aircoookie','https://paypal.me/blazoncek']
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -45,7 +45,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: What version of WLED?
|
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"
|
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:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
@@ -56,6 +56,9 @@ body:
|
|||||||
options:
|
options:
|
||||||
- ESP8266
|
- ESP8266
|
||||||
- ESP32
|
- ESP32
|
||||||
|
- ESP32-S3
|
||||||
|
- ESP32-S2
|
||||||
|
- ESP32-C3
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
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
@@ -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.
|
|
||||||
20
.github/stale.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 120
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 7
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- keep
|
|
||||||
- enhancement
|
|
||||||
- confirmed
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: stale
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
Hey! This issue has been open for quite some time without any new comments now.
|
|
||||||
It will be closed automatically in a week if no further activity occurs.
|
|
||||||
|
|
||||||
Thank you for using WLED!
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
||||||
30
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 12 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-stale: 120
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: 'stale'
|
||||||
|
stale-pr-label: 'stale'
|
||||||
|
exempt-issue-labels: 'pinned,keep,enhancement,confirmed'
|
||||||
|
exempt-pr-labels: 'pinned,keep,enhancement,confirmed'
|
||||||
|
exempt-all-milestones: true
|
||||||
|
operations-per-run: 1000
|
||||||
|
stale-issue-message: >
|
||||||
|
Hey! This issue has been open for quite some time without any new comments now.
|
||||||
|
It will be closed automatically in a week if no further activity occurs.
|
||||||
|
|
||||||
|
Thank you for using WLED! ✨
|
||||||
|
stale-pr-message: >
|
||||||
|
Hey! This pull request has been open for quite some time without any new comments now.
|
||||||
|
It will be closed automatically in a week if no further activity occurs.
|
||||||
|
|
||||||
|
Thank you for contributing to WLED! ❤️
|
||||||
80
.github/workflows/wled-ci.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: PlatformIO CI
|
name: WLED CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
@@ -8,21 +8,17 @@ jobs:
|
|||||||
name: Gather Environments
|
name: Gather Environments
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache pip
|
- uses: actions/setup-python@v5
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
python-version: '3.12'
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
cache: 'pip'
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pip-
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
- name: Install PlatformIO
|
- name: Install PlatformIO
|
||||||
run: pip install -r requirements.txt
|
run: pip install -r requirements.txt
|
||||||
- name: Get default environments
|
- name: Get default environments
|
||||||
id: envs
|
id: envs
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=environments::$(pio project config --json-output | jq -cr '.[0][1][0][1]')"
|
echo "environments=$(pio project config --json-output | jq -cr '.[0][1][0][1]')" >> $GITHUB_OUTPUT
|
||||||
outputs:
|
outputs:
|
||||||
environments: ${{ steps.envs.outputs.environments }}
|
environments: ${{ steps.envs.outputs.environments }}
|
||||||
|
|
||||||
@@ -32,55 +28,67 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: get_default_envs
|
needs: get_default_envs
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
|
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Cache pip
|
- name: Set up Node.js
|
||||||
uses: actions/cache@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
cache: 'npm'
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
- run: npm ci
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pip-
|
|
||||||
- name: Cache PlatformIO
|
- name: Cache PlatformIO
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: |
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
~/.platformio/.cache
|
||||||
|
~/.buildcache
|
||||||
|
build_output
|
||||||
|
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
|
||||||
|
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
cache: 'pip'
|
||||||
- name: Install PlatformIO
|
- name: Install PlatformIO
|
||||||
run: pip install -r requirements.txt
|
run: pip install -r requirements.txt
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
env:
|
|
||||||
WLED_RELEASE: True
|
|
||||||
run: pio run -e ${{ matrix.environment }}
|
run: pio run -e ${{ matrix.environment }}
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: firmware-${{ matrix.environment }}
|
name: firmware-${{ matrix.environment }}
|
||||||
path: |
|
path: |
|
||||||
build_output/firmware/*.bin
|
build_output/release/*.bin
|
||||||
build_output/firmware/*.gz
|
build_output/release/*_ESP02*.bin.gz
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
name: firmware-release
|
|
||||||
path: build_output/release/*.bin
|
|
||||||
release:
|
release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [get_default_envs, build]
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: firmware-release
|
merge-multiple: true
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
draft: True
|
draft: True
|
||||||
files: |
|
files: |
|
||||||
*.bin
|
*.bin
|
||||||
env:
|
*.bin.gz
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
|
testCdata:
|
||||||
|
name: Test cdata.js
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
cache: 'npm'
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm test
|
||||||
|
|||||||
29
.gitignore
vendored
@@ -1,17 +1,24 @@
|
|||||||
.pio
|
|
||||||
.cache
|
.cache
|
||||||
|
.clang-format
|
||||||
|
.direnv
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
.pio
|
||||||
.pioenvs
|
.pioenvs
|
||||||
.piolibdeps
|
.piolibdeps
|
||||||
.vscode
|
.vscode
|
||||||
!.vscode/extensions.json
|
|
||||||
/wled00/Release
|
esp01-update.sh
|
||||||
|
platformio_override.ini
|
||||||
|
replace_fs.py
|
||||||
|
wled-update.sh
|
||||||
|
|
||||||
|
/build_output/
|
||||||
|
/node_modules/
|
||||||
|
|
||||||
/wled00/extLibs
|
/wled00/extLibs
|
||||||
/platformio_override.ini
|
/wled00/LittleFS
|
||||||
/wled00/my_config.h
|
/wled00/my_config.h
|
||||||
/build_output
|
/wled00/Release
|
||||||
.DS_Store
|
/wled00/wled00.ino.cpp
|
||||||
.gitignore
|
/wled00/html_*.h
|
||||||
.clang-format
|
|
||||||
node_modules
|
|
||||||
.idea
|
|
||||||
.direnv
|
|
||||||
|
|||||||
2
.gitpod.Dockerfile
vendored
@@ -1,5 +1,3 @@
|
|||||||
FROM gitpod/workspace-full
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
USER gitpod
|
USER gitpod
|
||||||
|
|
||||||
RUN pip3 install -U platformio
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- command: platformio run
|
- command: pip3 install -U platformio && platformio run
|
||||||
|
|
||||||
image:
|
image:
|
||||||
file: .gitpod.Dockerfile
|
file: .gitpod.Dockerfile
|
||||||
|
|
||||||
vscode:
|
vscode:
|
||||||
extensions:
|
extensions:
|
||||||
- ms-vscode.cpptools@0.26.3:u3GsZ5PK12Ddr79vh4TWgQ==
|
- Atishay-Jain.All-Autocomplete
|
||||||
- eamodio.gitlens@10.2.1:e0IYyp0efFqVsrZwsIe8CA==
|
- esbenp.prettier-vscode
|
||||||
- Atishay-Jain.All-Autocomplete@0.0.23:fbZNfSpnd8XkAHGfAPS2rA==
|
- shardulm94.trailing-spaces
|
||||||
- 2gua.rainbow-brackets@0.0.6:Tbu8dTz0i+/bgcKQTQ5b8g==
|
|
||||||
|
|||||||
6
.vscode/tasks.json
vendored
@@ -9,8 +9,8 @@
|
|||||||
],
|
],
|
||||||
"dependsOrder": "sequence",
|
"dependsOrder": "sequence",
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
"$platformio",
|
"$platformio"
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "PlatformIO",
|
"type": "PlatformIO",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"task": "Build",
|
"task": "Build",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true,
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
"$platformio"
|
"$platformio"
|
||||||
|
|||||||
606
CHANGELOG.md
@@ -1,6 +1,604 @@
|
|||||||
## WLED changelog
|
## WLED changelog
|
||||||
|
|
||||||
### Builds after release 0.12.0
|
#### Build 2406290
|
||||||
|
- WLED 0.15.0-b4 release
|
||||||
|
- LED settings bus management update (WARNING only allow available outputs)
|
||||||
|
- Add ETH support for LILYGO-POE-Pro (#4030 by @rorosaurus)
|
||||||
|
- Update usermod_sn_photoresistor (#4017 by @xkvmoto)
|
||||||
|
- Several internal fixes and optimisations
|
||||||
|
- move LED_BUILTIN handling to BusManager class
|
||||||
|
- reduce max panels (web server limitation)
|
||||||
|
- edit WiFi TX power (ESP32)
|
||||||
|
- keep current ledmap ID in UI
|
||||||
|
- limit outputs in UI based on length
|
||||||
|
- wifi.ap addition to JSON Info (JSON API)
|
||||||
|
- relay pin init bugfix
|
||||||
|
- file editor button in UI
|
||||||
|
- ESP8266: update was restarting device on some occasions
|
||||||
|
- a bit of throttling in UI (for ESP8266)
|
||||||
|
|
||||||
|
#### Build 2406120
|
||||||
|
- Update NeoPixelBus to v2.8.0
|
||||||
|
- Increased LED outputs one ESP32 using parallel I2S (up to 17)
|
||||||
|
- use single/mono I2S + 4x RMT for 5 outputs or less
|
||||||
|
- use parallel x8 I2S + 8x RMT for >5 outputs (limit of 300 LEDs per output)
|
||||||
|
- Fixed code of Smartnest and updated documentation (#4001 by @DevilPro1)
|
||||||
|
- ESP32-S3 WiFi fix (#4010 by @cstruck)
|
||||||
|
- TetrisAI usermod fix (#3897 by @muebau)
|
||||||
|
- ESP-NOW usermod hook
|
||||||
|
- Update wled.h regarding OTA Password (#3993 by @gsieben)
|
||||||
|
- Usermod BME68X Sensor Implementation (#3994 by @gsieben)
|
||||||
|
- Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors (#3977 by @LordMike)
|
||||||
|
- Update Battery usermod documentation (#3968 by @adamsthws)
|
||||||
|
- Add INA226 usermod for reading current and power over i2c (#3986 by @LordMike)
|
||||||
|
- Bugfixes: #3991
|
||||||
|
- Several internal fixes and optimisations (WARNING: some effects may be broken that rely on overflow/narrow width)
|
||||||
|
- replace uint8_t and uint16_t with unsigned
|
||||||
|
- replace in8_t and int16_t with int
|
||||||
|
- reduces code by 1kB
|
||||||
|
|
||||||
|
#### Build 2405180
|
||||||
|
- WLED 0.14.4 release
|
||||||
|
- Fix for #3978
|
||||||
|
- Official 0.15.0-b3 release
|
||||||
|
- Merge 0.14.3 fixes into 0_15
|
||||||
|
- Added Pinwheel Expand 1D->2D effect mapping mode (#3961 by @Brandon502)
|
||||||
|
- Add changeable i2c address to BME280 usermod (#3966 by @LordMike)
|
||||||
|
- Effect: Firenoise - add palette selection
|
||||||
|
- Experimental parallel I2S support for ESP32 (compile time option)
|
||||||
|
- increased outputs to 17
|
||||||
|
- increased max possible color order overrides
|
||||||
|
- use WLED_USE_PARALLEL_I2S during compile
|
||||||
|
WARNING: Do not set up more than 256 LEDs per output when using parallel I2S with NeoPixelBus less than 2.9.0
|
||||||
|
- Update Usermod: Battery (#3964 by @adamsthws)
|
||||||
|
- Update Usermod: BME280 (#3965 by @LordMike)
|
||||||
|
- TM1914 chip support (#3913)
|
||||||
|
- Ignore brightness in Peek
|
||||||
|
- Antialiased line & circle drawing functions
|
||||||
|
- Enabled some audioreactive effects for single pixel strips/segments (#3942 by @gaaat98)
|
||||||
|
- Usermod Battery: Added Support for different battery types, Optimized file structure (#3003 by @itCarl)
|
||||||
|
- Skip playlist entry API (#3946 by @freakintoddles2)
|
||||||
|
- various optimisations and bugfixes (#3987, #3978)
|
||||||
|
|
||||||
|
#### Build 2405030
|
||||||
|
- Using brightness in analog clock overlay (#3944 by @paspiz85)
|
||||||
|
- Add Webpage shortcuts (#3945 by @w00000dy)
|
||||||
|
- ArtNet Poll reply (#3892 by @askask)
|
||||||
|
- Improved brightness change via long button presses (#3933 by @gaaat98)
|
||||||
|
- Relay open drain output (#3920 by @Suxsem)
|
||||||
|
- NEW JSON API: release info (update page, `info.release`)
|
||||||
|
- update esp32 platform to arduino-esp32 v2.0.9 (#3902)
|
||||||
|
- various optimisations and bugfixes (#3952, #3922, #3878, #3926, #3919, #3904 @DedeHai)
|
||||||
|
|
||||||
|
#### Build 2404120
|
||||||
|
- v0.15.0-b3
|
||||||
|
- fix for #3896 & WS2815 current saving
|
||||||
|
- conditional compile for AA setPixelColor()
|
||||||
|
|
||||||
|
#### Build 2404100
|
||||||
|
- Internals: #3859, #3862, #3873, #3875
|
||||||
|
- Prefer I2S1 over RMT on ESP32
|
||||||
|
- usermod for Adafruit MAX17048 (#3667 by @ccruz09)
|
||||||
|
- Runtime detection of ESP32 PICO, general PSRAM support
|
||||||
|
- Extend JSON API "info" object
|
||||||
|
- add "clock" - CPU clock in MHz
|
||||||
|
- add "flash" - flash size in MB
|
||||||
|
- Fix for #3879
|
||||||
|
- Analog PWM fix for ESP8266 (#3887 by @gaaat98)
|
||||||
|
- Fix for #3870 (#3880 by @DedeHai)
|
||||||
|
- ESP32 S3/S2 touch fix (#3798 by @DedeHai)
|
||||||
|
- PIO env. PSRAM fix for S3 & S3 with 4M flash
|
||||||
|
- audioreactive always included for S3 & S2
|
||||||
|
- Fix for #3889
|
||||||
|
- BREAKING: Effect: modified KITT (Scanner) (#3763)
|
||||||
|
|
||||||
|
#### Build 2404040
|
||||||
|
- WLED 0.14.3 release
|
||||||
|
- Fix for transition 0 (#3854, #3832, #3720)
|
||||||
|
- Fix for #3855 via #3873 (by @willmmiles)
|
||||||
|
|
||||||
|
#### Build 2403280
|
||||||
|
- Individual color channel control for JSON API (fixes #3860)
|
||||||
|
- "col":[int|string|object|array, int|string|object|array, int|string|object|array]
|
||||||
|
int = Kelvin temperature or 0 for black
|
||||||
|
string = hex representation of [WW]RRGGBB
|
||||||
|
object = individual channel control {"r":0,"g":127,"b":255,"w":255}, each being optional (valid to send {})
|
||||||
|
array = direct channel values [r,g,b,w] (w element being optional)
|
||||||
|
- runtime selection for CCT IC (Athom 15W bulb)
|
||||||
|
- #3850 (by @w00000dy)
|
||||||
|
- Rotary encoder palette count bugfix
|
||||||
|
- bugfixes and optimisations
|
||||||
|
|
||||||
|
#### Build 2403240
|
||||||
|
- v0.15.0-b2
|
||||||
|
- WS2805 support (RGB + WW + CW, 600kbps)
|
||||||
|
- Unified PSRAM use
|
||||||
|
- NeoPixelBus v2.7.9
|
||||||
|
- Ubiquitous PSRAM mode for all variants of ESP32
|
||||||
|
- SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC)
|
||||||
|
- Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`)
|
||||||
|
- FW1906 Support (#3810 by @deece and @Robert-github-com)
|
||||||
|
- ESPAsyncWebServer 2.2.0 (#3828 by @willmmiles)
|
||||||
|
- Bugfixes: #3843, #3844
|
||||||
|
|
||||||
|
#### Build 2403190
|
||||||
|
- limit max PWM frequency (fix incorrect PWM resolution)
|
||||||
|
- Segment UI bugfix
|
||||||
|
- Updated AsyncWebServer (by @wlillmmiles)
|
||||||
|
- Simpler boot preset (fix for #3806)
|
||||||
|
- Effect: Fix for 2D Drift animation (#3816 by @BaptisteHudyma)
|
||||||
|
- Effect: Add twin option to 2D Drift
|
||||||
|
- MQTT cleanup
|
||||||
|
- DDP: Support sources that don't push (#3833 by @willmmiles)
|
||||||
|
- Usermod: Tetris AI usermod (#3711 by @muebau)
|
||||||
|
|
||||||
|
#### Build 2403171
|
||||||
|
- merge 0.14.2 changes into 0.15
|
||||||
|
|
||||||
|
#### Build 2403070
|
||||||
|
- Add additional segment options when controlling over e1.31 (#3616 by @demophoon)
|
||||||
|
- LockedJsonResponse: Release early if possible (#3760 by @willmmiles)
|
||||||
|
- Update setup-node and cache usermods in wled-ci.yml (#3737 by @WoodyLetsCode)
|
||||||
|
- Fix preset sorting (#3790 by @WoodyLetsCode)
|
||||||
|
- compile time button configuration #3792
|
||||||
|
- remove IR config if not compiled
|
||||||
|
- additional string optimisations
|
||||||
|
- Better low brightness level PWM handling (fixes #2767, #2868)
|
||||||
|
|
||||||
|
#### Build 2402290
|
||||||
|
- Multiple analog button fix for #3549
|
||||||
|
- Preset caching on chips with PSRAM (credit @akaricchi)
|
||||||
|
- Fixing stairway usermod and adding buildflags (by @lost-hope)
|
||||||
|
- ESP-NOW packet modification
|
||||||
|
- JSON buffer lock error messages / Reduce wait time for lock to 100ms
|
||||||
|
- Reduce string RAM usage for ESP8266
|
||||||
|
- Fixing a potential array bounds violation in ESPDMX
|
||||||
|
- Move timezone table to PROGMEM (#3766 by @willmmiles)
|
||||||
|
- Reposition upload warning message. (fixes #3778)
|
||||||
|
- ABL display fix & optimisation
|
||||||
|
- Add virtual Art-Net RGBW option (#3783 by @shammy642)
|
||||||
|
|
||||||
|
#### Build 2402090
|
||||||
|
- Added new Ethernet controller RGB2Go Tetra (duplicate of ESP3DEUXQuattro)
|
||||||
|
- Usermod: httpPullLightControl (#3560 by @roelbroersma)
|
||||||
|
- DMX: S2 & C3 support via modified ESPDMX
|
||||||
|
- Bugfix: prevent cleaning of JSON buffer after a failed lock attempt (BufferGuard)
|
||||||
|
- Product/Brand override (API & AP SSID) (#3750 by @moustachauve)
|
||||||
|
|
||||||
|
#### Build 2402060
|
||||||
|
- WLED version 0.15.0-b1
|
||||||
|
- Harmonic Random Cycle palette (#3729 by @dedehai)
|
||||||
|
- Multi PIR sensor usermod (added support for attaching multiple PIR sensors)
|
||||||
|
- Removed obsolete (and nonfunctional) usermods
|
||||||
|
|
||||||
|
#### Build 2309120 till build 2402010
|
||||||
|
- WLED version 0.15.0-a0
|
||||||
|
- Multi-WiFi support. Add up to 3 (or more via cusom compile) WiFis to connect to (with help from @JPZV)
|
||||||
|
- Temporary AP. Use your WLED in public with temporary AP.
|
||||||
|
- Github CI build system enhancements (#3718 by @WoodyLetsCode)
|
||||||
|
- Accessibility: Node list ( #3715 by @WoodyLetsCode)
|
||||||
|
- Analog clock overlay enhancement (#3489 by @WoodyLetsCode)
|
||||||
|
- ESP32-POE-WROVER from Olimex ethernet support (#3625 by @m-wachter)
|
||||||
|
- APA106 support (#3580 by @itstefanjanos)
|
||||||
|
- BREAKING: Effect: updated Palette effect to support 2D (#3683 by @TripleWhy)
|
||||||
|
- "SuperSync" from WLED MM (by @MoonModules)
|
||||||
|
- Effect: DNA Spiral Effect Speed Fix (#3723 by @Derek4aty1)
|
||||||
|
- Fix for #3693
|
||||||
|
- Orange flash fix (#3196) for transitions
|
||||||
|
- Add own background image upload (#3596 by @WoodyLetsCode)
|
||||||
|
- WLED time overrides (`WLED_NTP_ENABLED`, `WLED_TIMEZONE`, `WLED_UTC_OFFSET`, `WLED_LAT` and `WLED_LON`)
|
||||||
|
- Better sorting and naming of static palettes (by @WoodyLetsCode)
|
||||||
|
- ANIMartRIX usermod and effects (#3673 by @netmindz)
|
||||||
|
- Use canvas instead of CSS gradient for liveview (#3621 by @zanhecht)
|
||||||
|
- Fix for #3672
|
||||||
|
- ColoOrderMap W channel swap (color order overrides now have W swap)
|
||||||
|
- En-/disable LED maps when receiving realtime data (#3554 by @ezcGman)
|
||||||
|
- Added PWM frequency selection to UI (Settings)
|
||||||
|
- Automatically build UI before compiling (#3598, #3666 by @WoodyLetsCode)
|
||||||
|
- Internal: Added *suspend* API to `strip` (`WS2812FX class`)
|
||||||
|
- Possible fix for #3589 & partial fix for #3605
|
||||||
|
- MPU6050 upgrade (#3654 by @willmmiles)
|
||||||
|
- UI internals (#3656 by @WoodyLetsCode)
|
||||||
|
- ColorPicker fix (#3658 by @WoodyLetsCode)
|
||||||
|
- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447)
|
||||||
|
- Effect: Fireworks 1D (fix for matrix trailing strip)
|
||||||
|
- BREAKING: Reduced number of segments (12) on ESP8266 due to less available RAM
|
||||||
|
- Increased available effect data buffer (increases more if board has PSRAM)
|
||||||
|
- Custom palette editor mobile UI enhancement (by @imeszaros)
|
||||||
|
- Per port Auto Brightness Limiter (ABL)
|
||||||
|
- Use PSRAM for JSON buffer (double size, larger ledmaps, up to 2k)
|
||||||
|
- Reduced heap fragmentation by allocating ledmap array only once and not deallocating effect buffer
|
||||||
|
- HTTP retries on failed UI load
|
||||||
|
- UI Search: scroll to top (#3587 by @WoodyLetsCode)
|
||||||
|
- Return to inline iro.js and rangetouch.js (#3597 by @WoodyLetsCode)
|
||||||
|
- Better caching (#3591 by @WoodyLetsCode)
|
||||||
|
- Do not send 404 for missing `skin.css` (#3590 by @WoodyLetsCode)
|
||||||
|
- Simplified UI rework (#3511 by @WoodyLetsCode)
|
||||||
|
- Domoticz device ID for PIR and Temperature usermods
|
||||||
|
- Bugfix for UCS8904 `hasWhite()`
|
||||||
|
- Better search in UI (#3540 by @WoodyLetsCode)
|
||||||
|
- Seeding FastLED PRNG (#3552 by @TripleWhy)
|
||||||
|
- WIZ Smart Button support (#3547 by @micw)
|
||||||
|
- New button type (button switch, fix for #3537)
|
||||||
|
- Pixel Magic Tool update (#3483 by @ajotanc)
|
||||||
|
- Effect: 2D Matrix fix for gaps
|
||||||
|
- Bugfix #3526, #3533, #3561
|
||||||
|
- Spookier Halloween Eyes (#3501)
|
||||||
|
- Compile time options for Multi Relay usermod (#3498)
|
||||||
|
- Effect: Fix for Dissolve (#3502)
|
||||||
|
- Better reverse proxy support (nested paths)
|
||||||
|
- Implement global JSON API boolean toggle (i.e. instead of "var":true or "var":false -> "var":"t").
|
||||||
|
- Sort presets by ID
|
||||||
|
- Fix for #3641, #3312, #3367, #3637, #3646, #3447, #3632, #3496, #2922, #3593, #3514, #3522, #3578 (partial), #3606 (@WoodyLetsCode)
|
||||||
|
- Improved random bg image and added random bg image options (@WoodyLetsCode, #3481)
|
||||||
|
- Audio palettes (Audioreactive usermod, credit @netmindz)
|
||||||
|
- Better UI tooltips (@ajotnac, #3464)
|
||||||
|
- Better effect filters (filter dropdown)
|
||||||
|
- UDP sync fix (for #3487)
|
||||||
|
- Power button override (solves #3431)
|
||||||
|
- Additional HTTP request throttling (ESP8266)
|
||||||
|
- Additional UI/UX improvements
|
||||||
|
- Segment class optimisations (internal)
|
||||||
|
- ESP-NOW sync
|
||||||
|
- ESP-NOW Wiz remote JSON overrides (similar to IR JSON) & bugfixes
|
||||||
|
- Gamma correction for custom palettes (#3399).
|
||||||
|
- Restore presets from browser local storage
|
||||||
|
- Optional effect blending
|
||||||
|
- Restructured UDP Sync (internal)
|
||||||
|
- Remove sync receive
|
||||||
|
- Sync clarification
|
||||||
|
- Disallow 2D effects on non-2D segments
|
||||||
|
- Return of 2 audio simulations
|
||||||
|
- Bugfix in sync #3344 (internal)
|
||||||
|
- remove excessive segments
|
||||||
|
- ignore inactive segments if not syncing bounds
|
||||||
|
- send UDP/WS on segment change
|
||||||
|
- pop_back() when removing last segment
|
||||||
|
|
||||||
|
#### Build 2403170
|
||||||
|
- WLED 0.14.2 release
|
||||||
|
|
||||||
|
#### Build 2403110
|
||||||
|
- Beta WLED 0.14.2-b2
|
||||||
|
- New AsyncWebServer (improved performance and reduced memory use)
|
||||||
|
- New builds for ESP8266 with 160MHz CPU clock
|
||||||
|
- Fixing stairway usermod and adding buildflags (#3758 by @lost-hope)
|
||||||
|
- Fixing a potential array bounds violation in ESPDMX
|
||||||
|
- Reduced RAM usage (moved strings and TZ data (by @willmmiles) to PROGMEM)
|
||||||
|
- LockedJsonResponse: Release early if possible (by @willmmiles)
|
||||||
|
|
||||||
|
#### Build 2402120
|
||||||
|
- Beta WLED 0.14.2-b1
|
||||||
|
- Possible fix for #3589 & partial fix for #3605
|
||||||
|
- Prevent JSON buffer clear after failed lock attempt
|
||||||
|
- Multiple analog button fix for #3549
|
||||||
|
- UM Audioreactive: add two compiler options (#3732 by @wled-install)
|
||||||
|
- Fix for #3693
|
||||||
|
|
||||||
|
#### Build 2401141
|
||||||
|
- Official release of WLED 0.14.1
|
||||||
|
- Fix for #3566, #3665, #3672
|
||||||
|
- Sorting of palettes in custom palette editor (#3674 by @WoodyLetsCode)
|
||||||
|
|
||||||
|
#### Build 2401060
|
||||||
|
- Version bump: 0.14.1-b3
|
||||||
|
- Global JSON buffer guarding (#3648 by @willmmiles, resolves #3641, #3312, #3367, #3637, #3646, #3447)
|
||||||
|
- Fix for #3632
|
||||||
|
- Custom palette editor mobile UI enhancement (#3617 by @imeszaros)
|
||||||
|
- changelog update
|
||||||
|
|
||||||
|
#### Build 2312290
|
||||||
|
- Fix for #3622, #3613, #3609
|
||||||
|
- Various tweaks and fixes
|
||||||
|
- changelog update
|
||||||
|
|
||||||
|
#### Build 2312230
|
||||||
|
- Version bump: 0.14.1-b2
|
||||||
|
- Fix for Pixel Magic button
|
||||||
|
- Fix for #2922 (option to force WiFi PHY mode to G on ESP8266)
|
||||||
|
- Fix for #3601, #3400 (incorrect sunrise/sunset, #3612 by @softhack007)
|
||||||
|
|
||||||
|
#### Build 2312180
|
||||||
|
- Bugfixes (#3593, #3490, #3573, #3517, #3561, #3555, #3541, #3536, #3515, #3522, #3533, #3508)
|
||||||
|
- Various other internal cleanups and optimisations
|
||||||
|
|
||||||
|
#### Build 2311160
|
||||||
|
- Version bump: 0.14.1-b1
|
||||||
|
- Bugfixes (#3526, #3502, #3496, #3484, #3487, #3445, #3466, #3296, #3382, #3312)
|
||||||
|
- New feature: Sort presets by ID
|
||||||
|
- New usermod: LDR sensor (#3490 by @JeffWDH)
|
||||||
|
- Effect: Twinklefox & Tinklecat metadata fix
|
||||||
|
- Effect: separate #HH and #MM for Scrolling Text (#3480)
|
||||||
|
- SSDR usermod enhancements (#3368)
|
||||||
|
- PWM fan usermod enhancements (#3414)
|
||||||
|
|
||||||
|
#### Build 2310010, build 2310130
|
||||||
|
- Release of WLED version 0.14.0 "Hoshi"
|
||||||
|
- Bugfixes for #3400, #3403, #3405
|
||||||
|
- minor HTML optimizations
|
||||||
|
- audioreactive: bugfix for UDP sound sync (partly initialized packets)
|
||||||
|
|
||||||
|
#### Build 2309240
|
||||||
|
- Release of WLED beta version 0.14.0-b6 "Hoshi"
|
||||||
|
- Effect bugfixes and improvements (Meteor, Meteor Smooth, Scrolling Text)
|
||||||
|
- audioreactive: bugfixes for ES8388 and ES7243 init; minor improvements for analog inputs
|
||||||
|
|
||||||
|
#### Build 2309100
|
||||||
|
- Release of WLED beta version 0.14.0-b5 "Hoshi"
|
||||||
|
- New standard esp32 build with audioreactive
|
||||||
|
- Effect blending bugfixes, and minor optimizations
|
||||||
|
|
||||||
|
#### Build 2309050
|
||||||
|
- Effect blending (#3311) (finally effect transitions!)
|
||||||
|
*WARNING*: May not work well with ESP8266, with plenty of segments or usermods (low RAM condition)!!!
|
||||||
|
- Added receive and send sync groups to JSON API (#3317) (you can change sync groups using preset)
|
||||||
|
- Internal temperature usermod (#3246)
|
||||||
|
- MQTT server and topic length overrides (#3354) (new build flags)
|
||||||
|
- Animated Staircase usermod enhancement (#3348) (on/off toggle/relay control)
|
||||||
|
- Added local time info to Info page (#3351)
|
||||||
|
- New effect: Rolling Balls (a.k.a. linear bounce) (#1039)
|
||||||
|
- Various bug fixes and enhancements.
|
||||||
|
|
||||||
|
#### Build 2308110
|
||||||
|
- Release of WLED beta version 0.14.0-b4 "Hoshi"
|
||||||
|
- Reset effect data immediately upon mode change
|
||||||
|
|
||||||
|
#### Build 2308030
|
||||||
|
- Improved random palette handling and blending
|
||||||
|
- Soap bugfix
|
||||||
|
- Fix ESP-NOW crash with AP mode Always
|
||||||
|
|
||||||
|
#### Build 2307180
|
||||||
|
- Bus-level global buffering (#3280)
|
||||||
|
- Removed per-segment LED buffer (SEGMENT.leds)
|
||||||
|
- various fixes and improvements (ESP variants platform 5.3.0, effect optimizations, /json/cfg pin allocation)
|
||||||
|
|
||||||
|
#### Build 2307130
|
||||||
|
- larger `oappend()` stack buffer (3.5k) for ESP32
|
||||||
|
- Preset cycle bugfix (#3262)
|
||||||
|
- Rotary encoder ALT fix for large LED count (#3276)
|
||||||
|
- effect updates (2D Plasmaball), `blur()` speedup
|
||||||
|
- On/Off toggle from nodes view (may show unknown device type on older versions) (#3291)
|
||||||
|
- various fixes and improvements (ABL, crashes when changing presets with different segments)
|
||||||
|
|
||||||
|
#### Build 2306270
|
||||||
|
- ESP-NOW remote support (#3237)
|
||||||
|
- Pixel Magic tool (display pixel art) (#3249)
|
||||||
|
- Websocket (peek) fallback when connection cannot be established, WS retries (#3267)
|
||||||
|
- Add WiFi network scan RPC command to Improv Serial (#3271)
|
||||||
|
- Longer (custom option available) segment name for ESP32
|
||||||
|
- various fixes and improvements
|
||||||
|
|
||||||
|
#### Build 2306210
|
||||||
|
- 0.14.0-b3 release
|
||||||
|
- respect global I2C in all usermods (no local initialization of I2C bus)
|
||||||
|
- Multi relay usermod compile-time enabled option (-D MULTI_RELAY_ENABLED=true|false)
|
||||||
|
|
||||||
|
#### Build 2306180
|
||||||
|
- Added client-side option for applying effect defaults from metadata
|
||||||
|
- Improved ESP8266 stability by reducing WebSocket response resends
|
||||||
|
- Updated ESP8266 core to 3.1.2
|
||||||
|
|
||||||
|
#### Build 2306141
|
||||||
|
- Lissajous improvements
|
||||||
|
- Scrolling Text improvements (leading 0)
|
||||||
|
|
||||||
|
#### Build 2306140
|
||||||
|
- Add settings PIN (un)locking to JSON post API
|
||||||
|
|
||||||
|
#### Build 2306130
|
||||||
|
- Bumped version to 0.14-b3 (beta 3)
|
||||||
|
- added pin dropdowns in LED preferences (not for LED pins) and usermods
|
||||||
|
- introduced (unused ATM) NeoGammaWLEDMethod class
|
||||||
|
- Reverse proxy support
|
||||||
|
- PCF8754 support for Rotary encoder (requires wiring INT pin to ESP GPIO)
|
||||||
|
- Rely on global I2C pins for usermods (breaking change)
|
||||||
|
- various fixes and enhancements
|
||||||
|
|
||||||
|
#### Build 2306020
|
||||||
|
- Support for segment sets (PR #3171)
|
||||||
|
- Reduce sound simulation modes to 2 to facilitate segment sets
|
||||||
|
- Trigger button immediately on press if all configured presets are the same (PR #3226)
|
||||||
|
- Changes for allowing Alexa to change light color to White when auto-calculating from RGB (PR #3211)
|
||||||
|
|
||||||
|
#### Build 2305280
|
||||||
|
- DDP protocol update (#3193)
|
||||||
|
- added PCF8574 I2C port expander support for Multi relay usermod
|
||||||
|
- MQTT multipacket (fragmented) message fix
|
||||||
|
- added option to retain MQTT brightness and color messages
|
||||||
|
- new ethernet board: @srg74 Ethernet Shield
|
||||||
|
- new 2D effects: Soap (#3184) & Octopus & Waving cell (credit @St3P40 https://github.com/80Stepko08)
|
||||||
|
- various fixes and enhancements
|
||||||
|
|
||||||
|
#### Build 2305090
|
||||||
|
- new ethernet board: @Wladi ABC! WLED Eth
|
||||||
|
- Battery usermod voltage calculation (#3116)
|
||||||
|
- custom palette editor (#3164)
|
||||||
|
- improvements in Dancing Shadows and Tartan effects
|
||||||
|
- UCS389x support
|
||||||
|
- switched to NeoPixelBus 2.7.5 (replaced NeoPixelBrightnessBus with NeoPixelBusLg)
|
||||||
|
- SPI bus clock selection (for LEDs) (#3173)
|
||||||
|
- DMX mode preset fix (#3134)
|
||||||
|
- iOS fix for scroll (#3182)
|
||||||
|
- Wordclock "Norddeutsch" fix (#3161)
|
||||||
|
- various fixes and enhancements
|
||||||
|
|
||||||
|
#### Build 2304090
|
||||||
|
- updated Arduino ESP8266 core to 4.1.0 (newer compiler)
|
||||||
|
- updated NeoPixelBus to 2.7.3 (with support for UCS890x chipset)
|
||||||
|
- better support for ESP32-C3, ESP32-S2 and ESP32-S3 (Arduino ESP32 core 5.2.0)
|
||||||
|
- iPad/tablet with 1024 pixels width in landscape orientation PC mode support (#3153)
|
||||||
|
- fix for Pixel Art Converter (#3155)
|
||||||
|
|
||||||
|
#### Build 2303240
|
||||||
|
- Peek scaling of large 2D matrices
|
||||||
|
- Added 0D (1 pixel) metadata for effects & enhance 0D (analog strip) UI handling
|
||||||
|
- Added ability to disable ADAlight (-D WLED_DISABLE_ADALIGHT)
|
||||||
|
- Fixed APA102 output on Ethernet enabled controllers
|
||||||
|
- Added ArtNet virtual/network output (#3121)
|
||||||
|
- Klipper usermod (#3106)
|
||||||
|
- Remove DST from CST timezone
|
||||||
|
- various fixes and enhancements
|
||||||
|
|
||||||
|
#### Build 2302180
|
||||||
|
|
||||||
|
- Removed Blynk support (servers shut down on 31st Dec 2022)
|
||||||
|
- Added `ledgap.json` to complement ledmaps for 2D matrices
|
||||||
|
- Added support for white addressable strips (#3073)
|
||||||
|
- Ability to use SHT temperature usermod with PWM fan usermod
|
||||||
|
- Added `onStateChange()` callback to usermods (#3081)
|
||||||
|
- Refactored `bus_manager` [internal]
|
||||||
|
- Dual 1D & 2D mode (add 1D strip after the matrix)
|
||||||
|
- Removed 1D -> 2D mapping for individual pixel control
|
||||||
|
- effect tweak: Fireworks 1D
|
||||||
|
- various bugfixes
|
||||||
|
|
||||||
|
#### Build 2301240
|
||||||
|
|
||||||
|
- Version bump to v0.14.0-b2 "Hoshi"
|
||||||
|
- PixelArt converter (convert any image to pixel art and display it on a matrix) (PR #3042)
|
||||||
|
- various effect updates and optimisations
|
||||||
|
- added Overlay option to some effects (allows overlapping segments)
|
||||||
|
- added gradient text on Scrolling Text
|
||||||
|
- added #DDMM, #MMDD & #HHMM date and time options for Scrolling Text effect (PR #2990)
|
||||||
|
- deprecated: Dynamic Smooth, Dissolve Rnd, Solid Glitter
|
||||||
|
- optimised & enhanced loading of default values
|
||||||
|
- new effect: Distortion Waves (2D)
|
||||||
|
- 2D support for Ripple effect
|
||||||
|
- slower minimum speed for Railway effect
|
||||||
|
- DMX effect mode & segment controls (PR #2891)
|
||||||
|
- Optimisations for conditional compiles (further reduction of code size)
|
||||||
|
- better UX with effect sliders (PR #3012)
|
||||||
|
- enhanced support for ESP32 variants: C3, S2 & S3
|
||||||
|
- usermod enhancements (PIR, Temperature, Battery (PR #2975), Analog Clock (PR #2993))
|
||||||
|
- new usermod SHT (PR #2963)
|
||||||
|
- 2D matrix set up with gaps or irregular panels (breaking change!) (PR #2892)
|
||||||
|
- palette blending/transitions
|
||||||
|
- random palette smooth changes
|
||||||
|
- hex color notations in custom palettes
|
||||||
|
- allow more virtual buses
|
||||||
|
- plethora of bugfixes
|
||||||
|
|
||||||
|
### WLED release 0.14.0-b1
|
||||||
|
|
||||||
|
#### Build 2212222
|
||||||
|
|
||||||
|
- Version bump to v0.14.0-b1 "Hoshi"
|
||||||
|
- 2D matrix support (including mapping 1D effects to 2D and 2D peek)
|
||||||
|
- [internal] completely rewritten Segment & WS2812FX handling code
|
||||||
|
- [internal] ability to add custom effects via usermods
|
||||||
|
- [internal] set of 2D drawing functions
|
||||||
|
- transitions on every segment (including ESP8266)
|
||||||
|
- enhanced old and new 2D effects (metadata: default values)
|
||||||
|
- custom palettes (up to 10; upload palette0.json, palette1.json, ...)
|
||||||
|
- custom effect sliders and options, quick filters
|
||||||
|
- global I2C and SPI GPIO allocation (for usermods)
|
||||||
|
- usermod settings page enhancements (dropdown & info)
|
||||||
|
- asynchronous preset loading (and added "pd" JSON API call for direct preset apply)
|
||||||
|
- new usermod Boblight (PR #2917)
|
||||||
|
- new usermod PWM Outputs (PR #2912)
|
||||||
|
- new usermod Audioreactive
|
||||||
|
- new usermod Word Clock Matrix (PR #2743)
|
||||||
|
- new usermod Ping Pong Clock (PR #2746)
|
||||||
|
- new usermod ADS1115 (PR #2752)
|
||||||
|
- new usermod Analog Clock (PR #2736)
|
||||||
|
- various usermod enhancements and updates
|
||||||
|
- allow disabling pull-up resistors on buttons
|
||||||
|
- SD card support (PR #2877)
|
||||||
|
- enhanced HTTP API to support custom effect sliders & options (X1, X2, X3, M1, M2, M3)
|
||||||
|
- multiple UDP sync message retries (PR #2830)
|
||||||
|
- network debug printer (PR #2870)
|
||||||
|
- automatic UI PC mode on large displays
|
||||||
|
- removed support for upgrading from pre-0.10 (EEPROM)
|
||||||
|
- support for setting GPIO level when LEDs are off (RMT idle level, ESP32 only) (PR #2478)
|
||||||
|
- Pakistan time-zone (PKT)
|
||||||
|
- ArtPoll support
|
||||||
|
- TM1829 LED support
|
||||||
|
- experimental support for ESP32 S2, S3 and C3
|
||||||
|
- general improvements and bugfixes
|
||||||
|
|
||||||
|
### 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
|
#### Build 2202222
|
||||||
|
|
||||||
@@ -136,7 +734,7 @@
|
|||||||
|
|
||||||
- Added application level pong websockets reply (#2139)
|
- Added application level pong websockets reply (#2139)
|
||||||
- Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2
|
- Use AsyncTCP 1.0.3 as it mitigates the flickering issue from 0.13.0-b2
|
||||||
- Fixed transition manually updated in preset overriden by field value
|
- Fixed transition manually updated in preset overridden by field value
|
||||||
|
|
||||||
#### Build 2108050
|
#### Build 2108050
|
||||||
|
|
||||||
@@ -665,7 +1263,7 @@
|
|||||||
|
|
||||||
#### Build 2011040
|
#### Build 2011040
|
||||||
|
|
||||||
- Inversed Rain direction (fixes #1147)
|
- Inverted Rain direction (fixes #1147)
|
||||||
|
|
||||||
#### Build 2011010
|
#### Build 2011010
|
||||||
|
|
||||||
@@ -876,7 +1474,7 @@
|
|||||||
|
|
||||||
- Added module info page to web UI
|
- Added module info page to web UI
|
||||||
- Added realtime override functionality to web UI
|
- Added realtime override functionality to web UI
|
||||||
- Added individial segment power and brightness to web UI
|
- Added individual segment power and brightness to web UI
|
||||||
- Added feature to one-click select single segment only by tapping segment name
|
- Added feature to one-click select single segment only by tapping segment name
|
||||||
- Removed palette jumping to default if color is changed
|
- Removed palette jumping to default if color is changed
|
||||||
|
|
||||||
|
|||||||
92
CONTRIBUTING.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
## Thank you for making WLED better!
|
||||||
|
|
||||||
|
Here are a few suggestions to make it easier for you to contribute!
|
||||||
|
|
||||||
|
### Describe your PR
|
||||||
|
|
||||||
|
Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing.
|
||||||
|
|
||||||
|
A good description helps us to review and understand your proposed changes. For example, you could say a few words about
|
||||||
|
* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
|
||||||
|
* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
|
||||||
|
* testing you performed, known limitations, open ends you possibly could not solve.
|
||||||
|
* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉)
|
||||||
|
|
||||||
|
### Target branch for pull requests
|
||||||
|
|
||||||
|
Please make all PRs against the `0_15` branch.
|
||||||
|
|
||||||
|
### 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 brackets 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 parenthesis 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 hard character limit for a comment within a line,
|
||||||
|
though as a rule of thumb consider wrapping after 120 characters.
|
||||||
|
Inline comments are OK if they describe that line only and are not exceedingly wide.
|
||||||
2689
package-lock.json
generated
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wled",
|
"name": "wled",
|
||||||
"version": "0.13.0-b7",
|
"version": "0.15.0-b4",
|
||||||
"description": "Tools for WLED project",
|
"description": "Tools for WLED project",
|
||||||
"main": "tools/cdata.js",
|
"main": "tools/cdata.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node tools/cdata.js",
|
"build": "node tools/cdata.js",
|
||||||
|
"test": "node --test",
|
||||||
"dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js"
|
"dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -22,10 +23,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Aircoookie/WLED#readme",
|
"homepage": "https://github.com/Aircoookie/WLED#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clean-css": "^4.2.3",
|
"clean-css": "^5.3.3",
|
||||||
"html-minifier-terser": "^5.1.1",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"inliner": "^1.13.1",
|
"inliner": "^1.13.1",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^3.0.2"
|
||||||
"zlib": "^1.0.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
pio-scripts/build_ui.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Import('env')
|
||||||
|
|
||||||
|
env.Execute("npm run build")
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
# Little convenience script to get an object dump
|
# Little convenience script to get an object dump
|
||||||
|
# You may add "-S" to the objdump commandline (i.e. replace "-D -C " with "-d -S -C ")
|
||||||
|
# to get source code intermixed with disassembly (SLOW !)
|
||||||
|
|
||||||
Import('env')
|
Import('env')
|
||||||
|
|
||||||
def obj_dump_after_elf(source, target, env):
|
def obj_dump_after_elf(source, target, env):
|
||||||
|
platform = env.PioPlatform()
|
||||||
|
board = env.BoardConfig()
|
||||||
|
mcu = board.get("build.mcu", "esp32")
|
||||||
|
|
||||||
print("Create firmware.asm")
|
print("Create firmware.asm")
|
||||||
env.Execute("xtensa-lx106-elf-objdump "+ "-D " + str(target[0]) + " > "+ "${PROGNAME}.asm")
|
if mcu == "esp8266":
|
||||||
|
env.Execute("xtensa-lx106-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
|
||||||
|
if mcu == "esp32":
|
||||||
|
env.Execute("xtensa-esp32-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
|
||||||
|
if mcu == "esp32s2":
|
||||||
|
env.Execute("xtensa-esp32s2-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
|
||||||
|
if mcu == "esp32s3":
|
||||||
|
env.Execute("xtensa-esp32s3-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
|
||||||
|
if mcu == "esp32c3":
|
||||||
|
env.Execute("riscv32-esp-elf-objdump "+ "-D -C " + str(target[0]) + " > "+ "$BUILD_DIR/${PROGNAME}.asm")
|
||||||
|
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [obj_dump_after_elf])
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [obj_dump_after_elf])
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import shutil
|
|||||||
import gzip
|
import gzip
|
||||||
|
|
||||||
OUTPUT_DIR = "build_output{}".format(os.path.sep)
|
OUTPUT_DIR = "build_output{}".format(os.path.sep)
|
||||||
|
#OUTPUT_DIR = os.path.join("build_output")
|
||||||
|
|
||||||
def _get_cpp_define_value(env, define):
|
def _get_cpp_define_value(env, define):
|
||||||
define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define]
|
define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define]
|
||||||
@@ -13,57 +14,52 @@ def _get_cpp_define_value(env, define):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _create_dirs(dirs=["firmware", "map"]):
|
def _create_dirs(dirs=["map", "release", "firmware"]):
|
||||||
# check if output directories exist and create if necessary
|
|
||||||
if not os.path.isdir(OUTPUT_DIR):
|
|
||||||
os.mkdir(OUTPUT_DIR)
|
|
||||||
|
|
||||||
for d in dirs:
|
for d in dirs:
|
||||||
if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)):
|
os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True)
|
||||||
os.mkdir("{}{}".format(OUTPUT_DIR, d))
|
|
||||||
|
def create_release(source):
|
||||||
|
release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
|
||||||
|
if release_name:
|
||||||
|
version = _get_cpp_define_value(env, "WLED_VERSION")
|
||||||
|
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
|
||||||
|
release_gz_file = release_file + ".gz"
|
||||||
|
print(f"Copying {source} to {release_file}")
|
||||||
|
shutil.copy(source, release_file)
|
||||||
|
bin_gzip(release_file, release_gz_file)
|
||||||
|
else:
|
||||||
|
variant = env["PIOENV"]
|
||||||
|
bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant)
|
||||||
|
print(f"Copying {source} to {bin_file}")
|
||||||
|
shutil.copy(source, bin_file)
|
||||||
|
|
||||||
def bin_rename_copy(source, target, env):
|
def bin_rename_copy(source, target, env):
|
||||||
_create_dirs()
|
_create_dirs()
|
||||||
variant = env["PIOENV"]
|
variant = env["PIOENV"]
|
||||||
|
builddir = os.path.join(env["PROJECT_BUILD_DIR"], variant)
|
||||||
|
source_map = os.path.join(builddir, env["PROGNAME"] + ".map")
|
||||||
|
|
||||||
# create string with location and file names based on variant
|
# create string with location and file names based on variant
|
||||||
map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant)
|
map_file = "{}map{}{}.map".format(OUTPUT_DIR, os.path.sep, variant)
|
||||||
bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant)
|
|
||||||
|
|
||||||
release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
|
create_release(str(target[0]))
|
||||||
|
|
||||||
if release_name:
|
|
||||||
_create_dirs(["release"])
|
|
||||||
version = _get_cpp_define_value(env, "WLED_VERSION")
|
|
||||||
release_file = "{}release{}WLED_{}_{}.bin".format(OUTPUT_DIR, os.path.sep, version, release_name)
|
|
||||||
shutil.copy(str(target[0]), release_file)
|
|
||||||
|
|
||||||
# check if new target files exist and remove if necessary
|
|
||||||
for f in [map_file, bin_file]:
|
|
||||||
if os.path.isfile(f):
|
|
||||||
os.remove(f)
|
|
||||||
|
|
||||||
# copy firmware.bin to firmware/<variant>.bin
|
|
||||||
shutil.copy(str(target[0]), bin_file)
|
|
||||||
|
|
||||||
# copy firmware.map to map/<variant>.map
|
# copy firmware.map to map/<variant>.map
|
||||||
if os.path.isfile("firmware.map"):
|
if os.path.isfile("firmware.map"):
|
||||||
shutil.move("firmware.map", map_file)
|
print("Found linker mapfile firmware.map")
|
||||||
|
shutil.copy("firmware.map", map_file)
|
||||||
|
if os.path.isfile(source_map):
|
||||||
|
print(f"Found linker mapfile {source_map}")
|
||||||
|
shutil.copy(source_map, map_file)
|
||||||
|
|
||||||
def bin_gzip(source, target, env):
|
def bin_gzip(source, target):
|
||||||
_create_dirs()
|
# only create gzip for esp8266
|
||||||
variant = env["PIOENV"]
|
if not env["PIOPLATFORM"] == "espressif8266":
|
||||||
|
return
|
||||||
|
|
||||||
# create string with location and file names based on variant
|
print(f"Creating gzip file {target} from {source}")
|
||||||
bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant)
|
with open(source,"rb") as fp:
|
||||||
gzip_file = "{}firmware{}{}.bin.gz".format(OUTPUT_DIR, os.path.sep, variant)
|
with gzip.open(target, "wb", compresslevel = 9) as f:
|
||||||
|
|
||||||
# check if new target files exist and remove if necessary
|
|
||||||
if os.path.isfile(gzip_file): os.remove(gzip_file)
|
|
||||||
|
|
||||||
# write gzip firmware file
|
|
||||||
with open(bin_file,"rb") as fp:
|
|
||||||
with gzip.open(gzip_file, "wb", compresslevel = 9) as f:
|
|
||||||
shutil.copyfileobj(fp, f)
|
shutil.copyfileobj(fp, f)
|
||||||
|
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_rename_copy, bin_gzip])
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", bin_rename_copy)
|
||||||
|
|||||||
618
platformio.ini
@@ -6,37 +6,11 @@
|
|||||||
# ENVIRONMENTS
|
# ENVIRONMENTS
|
||||||
#
|
#
|
||||||
# Please uncomment one of the lines below to select your board(s)
|
# 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)
|
# CI/release binaries
|
||||||
; default_envs = travis_esp8266, travis_esp32
|
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
|
||||||
|
|
||||||
# Release binaries
|
|
||||||
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32_eth_ota1mapp, esp32s2_saola, esp32c3
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Single binaries (uncomment your board)
|
|
||||||
; default_envs = elekstube_ips
|
|
||||||
; default_envs = nodemcuv2
|
|
||||||
; default_envs = esp8266_2m
|
|
||||||
; default_envs = esp01_1m_full
|
|
||||||
; default_envs = esp07
|
|
||||||
; default_envs = d1_mini
|
|
||||||
; default_envs = heltec_wifi_kit_8
|
|
||||||
; default_envs = h803wf
|
|
||||||
; default_envs = d1_mini_debug
|
|
||||||
; default_envs = d1_mini_ota
|
|
||||||
; default_envs = esp32dev
|
|
||||||
; default_envs = esp8285_4CH_MagicHome
|
|
||||||
; default_envs = esp8285_H801
|
|
||||||
; default_envs = d1_mini_5CH_Shojo_PCB
|
|
||||||
; default_envs = wemos_shield_esp32
|
|
||||||
; default_envs = m5atom
|
|
||||||
; default_envs = esp32_eth
|
|
||||||
; default_envs = esp32_eth_ota1mapp
|
|
||||||
; default_envs = esp32s2_saola
|
|
||||||
|
|
||||||
src_dir = ./wled00
|
src_dir = ./wled00
|
||||||
data_dir = ./wled00/data
|
data_dir = ./wled00/data
|
||||||
@@ -56,24 +30,36 @@ arduino_core_2_6_3 = espressif8266@2.3.3
|
|||||||
arduino_core_2_7_4 = espressif8266@2.6.2
|
arduino_core_2_7_4 = espressif8266@2.6.2
|
||||||
arduino_core_3_0_0 = espressif8266@3.0.0
|
arduino_core_3_0_0 = espressif8266@3.0.0
|
||||||
arduino_core_3_0_2 = espressif8266@3.2.0
|
arduino_core_3_0_2 = espressif8266@3.2.0
|
||||||
|
arduino_core_3_1_0 = espressif8266@4.1.0
|
||||||
|
arduino_core_3_1_2 = espressif8266@4.2.1
|
||||||
|
|
||||||
# Development platforms
|
# Development platforms
|
||||||
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
|
arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop
|
||||||
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
|
arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage
|
||||||
|
|
||||||
# Platform to use for ESP8266
|
# Platform to use for ESP8266
|
||||||
platform_wled_default = ${common.arduino_core_2_7_4}
|
platform_wled_default = ${common.arduino_core_3_1_2}
|
||||||
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
|
# We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization
|
||||||
platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
|
#platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
|
||||||
platformio/toolchain-xtensa @ ~2.40802.200502
|
platform_packages = platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502
|
||||||
platformio/tool-esptool @ ~1.413.0
|
platformio/tool-esptool #@ ~1.413.0
|
||||||
platformio/tool-esptoolpy @ ~1.30000.0
|
platformio/tool-esptoolpy #@ ~1.30000.0
|
||||||
|
|
||||||
|
## previous platform for 8266, in case of problems with the new one
|
||||||
|
## you'll need makuna/NeoPixelBus@ 2.6.9 for arduino_core_3_0_2, which does not support Ucs890x
|
||||||
|
;; platform_wled_default = ${common.arduino_core_3_0_2}
|
||||||
|
;; platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7
|
||||||
|
;; platformio/toolchain-xtensa @ ~2.40802.200502
|
||||||
|
;; platformio/tool-esptool @ ~1.413.0
|
||||||
|
;; platformio/tool-esptoolpy @ ~1.30000.0
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# FLAGS: DEBUG
|
# FLAGS: DEBUG
|
||||||
#
|
# esp8266 : see https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
||||||
|
# esp32 : see https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
debug_flags = -D DEBUG=1 -D WLED_DEBUG -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
|
debug_flags = -D DEBUG=1 -D WLED_DEBUG
|
||||||
|
-DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM ;; for esp8266
|
||||||
# if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h"
|
# if needed (for memleaks etc) also add; -DDEBUG_ESP_OOM -include "umm_malloc/umm_malloc_cfg.h"
|
||||||
# -DDEBUG_ESP_CORE is not working right now
|
# -DDEBUG_ESP_CORE is not working right now
|
||||||
|
|
||||||
@@ -105,6 +91,7 @@ build_flags =
|
|||||||
-DBEARSSL_SSL_BASIC
|
-DBEARSSL_SSL_BASIC
|
||||||
-D CORE_DEBUG_LEVEL=0
|
-D CORE_DEBUG_LEVEL=0
|
||||||
-D NDEBUG
|
-D NDEBUG
|
||||||
|
-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus
|
||||||
#build_flags for the IRremoteESP8266 library (enabled decoders have to appear here)
|
#build_flags for the IRremoteESP8266 library (enabled decoders have to appear here)
|
||||||
-D _IR_ENABLE_DEFAULT_=false
|
-D _IR_ENABLE_DEFAULT_=false
|
||||||
-D DECODE_HASH=true
|
-D DECODE_HASH=true
|
||||||
@@ -113,20 +100,9 @@ build_flags =
|
|||||||
-D DECODE_SAMSUNG=true
|
-D DECODE_SAMSUNG=true
|
||||||
-D DECODE_LG=true
|
-D DECODE_LG=true
|
||||||
-DWLED_USE_MY_CONFIG
|
-DWLED_USE_MY_CONFIG
|
||||||
; -D USERMOD_SENSORSTOMQTT
|
|
||||||
|
|
||||||
build_unflags =
|
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}
|
|
||||||
|
|
||||||
ldscript_1m128k = eagle.flash.1m128.ld
|
ldscript_1m128k = eagle.flash.1m128.ld
|
||||||
ldscript_2m512k = eagle.flash.2m512.ld
|
ldscript_2m512k = eagle.flash.2m512.ld
|
||||||
ldscript_2m1m = eagle.flash.2m1m.ld
|
ldscript_2m1m = eagle.flash.2m1m.ld
|
||||||
@@ -138,6 +114,8 @@ extra_scripts =
|
|||||||
post:pio-scripts/output_bins.py
|
post:pio-scripts/output_bins.py
|
||||||
post:pio-scripts/strip-floats.py
|
post:pio-scripts/strip-floats.py
|
||||||
pre:pio-scripts/user_config_copy.py
|
pre:pio-scripts/user_config_copy.py
|
||||||
|
pre:pio-scripts/build_ui.py
|
||||||
|
; post:pio-scripts/obj-dump.py ;; convenience script to create a disassembly dump of the firmware (hardcore debugging)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# COMMON SETTINGS:
|
# COMMON SETTINGS:
|
||||||
@@ -146,35 +124,54 @@ extra_scripts =
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.flash_mode = dout
|
board_build.flash_mode = dout
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
# slow upload speed (comment this out with a ';' when building for development use)
|
# slow upload speed but most compatible (use platformio_override.ini to use faster speed)
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
# fast upload speed (remove ';' when building for development use)
|
|
||||||
; upload_speed = 921600
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# LIBRARIES: required dependencies
|
# LIBRARIES: required dependencies
|
||||||
# Please note that we don't always use the latest version of a library.
|
# Please note that we don't always use the latest version of a library.
|
||||||
#
|
#
|
||||||
# The following libraries have been included (and some of them changd) in the source:
|
# The following libraries have been included (and some of them changed) in the source:
|
||||||
# ArduinoJson@5.13.5, Blynk@0.5.4(changed), E131@1.0.0(changed), Time@1.5, Timezone@1.2.1
|
# ArduinoJson@5.13.5, E131@1.0.0(changed), Time@1.5, Timezone@1.2.1
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
lib_compat_mode = strict
|
lib_compat_mode = strict
|
||||||
lib_deps =
|
lib_deps =
|
||||||
fastled/FastLED @ 3.5.0
|
fastled/FastLED @ 3.6.0
|
||||||
IRremoteESP8266 @ 2.8.1
|
IRremoteESP8266 @ 2.8.2
|
||||||
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4
|
makuna/NeoPixelBus @ 2.8.0
|
||||||
|
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||||
|
https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1
|
||||||
|
# for I2C interface
|
||||||
|
;Wire
|
||||||
|
# ESP-NOW library
|
||||||
|
;gmag11/QuickESPNow @ ~0.7.0
|
||||||
|
https://github.com/blazoncek/QuickESPNow.git#optional-debug
|
||||||
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
|
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
|
||||||
#TFT_eSPI
|
#TFT_eSPI
|
||||||
#For use SSD1306 OLED display uncomment following
|
#For compatible OLED display uncomment following
|
||||||
#U8g2@~2.27.2
|
#olikraus/U8g2 #@ ~2.33.15
|
||||||
#For Dallas sensor uncomment following 2 lines
|
#For Dallas sensor uncomment following
|
||||||
#OneWire@~2.3.5
|
#paulstoffregen/OneWire @ ~2.3.8
|
||||||
#milesburton/DallasTemperature@^3.9.0
|
|
||||||
#For BME280 sensor uncomment following
|
#For BME280 sensor uncomment following
|
||||||
#BME280 @ ~3.0.0
|
#BME280 @ ~3.0.0
|
||||||
;adafruit/Adafruit BMP280 Library @ 2.1.0
|
;adafruit/Adafruit BMP280 Library @ 2.1.0
|
||||||
;adafruit/Adafruit CCS811 Library @ 1.0.4
|
;adafruit/Adafruit CCS811 Library @ 1.0.4
|
||||||
;adafruit/Adafruit Si7021 Library @ 1.4.0
|
;adafruit/Adafruit Si7021 Library @ 1.4.0
|
||||||
|
#For ADS1115 sensor uncomment following
|
||||||
|
;adafruit/Adafruit BusIO @ 1.13.2
|
||||||
|
;adafruit/Adafruit ADS1X15 @ 2.4.0
|
||||||
|
#For MAX1704x Lipo Monitor / Fuel Gauge uncomment following
|
||||||
|
; https://github.com/adafruit/Adafruit_BusIO @ 1.14.5
|
||||||
|
; https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2
|
||||||
|
#For MPU6050 IMU uncomment follwoing
|
||||||
|
;electroniccats/MPU6050 @1.0.1
|
||||||
|
# For -D USERMOD_ANIMARTRIX
|
||||||
|
# CC BY-NC 3.0 licensed effects by Stefan Petrick, include this usermod only if you accept the terms!
|
||||||
|
;https://github.com/netmindz/animartrix.git#18bf17389e57c69f11bc8d04ebe1d215422c7fb7
|
||||||
|
# SHT85
|
||||||
|
;robtillaart/SHT85@~0.3.3
|
||||||
|
# Audioreactive usermod
|
||||||
|
;kosme/arduinoFFT @ 2.0.1
|
||||||
|
|
||||||
extra_scripts = ${scripts_defaults.extra_scripts}
|
extra_scripts = ${scripts_defaults.extra_scripts}
|
||||||
|
|
||||||
@@ -183,7 +180,8 @@ build_flags =
|
|||||||
-DESP8266
|
-DESP8266
|
||||||
-DFP_IN_IROM
|
-DFP_IN_IROM
|
||||||
;-Wno-deprecated-declarations
|
;-Wno-deprecated-declarations
|
||||||
-Wno-register
|
;-Wno-register ;; leaves some warnings when compiling C files: command-line option '-Wno-register' is valid for C++/ObjC++ but not for C
|
||||||
|
;-Dregister= # remove warnings in C++17 due to use of deprecated register keyword by the FastLED library ;; warning: this can be dangerous
|
||||||
-Wno-misleading-indentation
|
-Wno-misleading-indentation
|
||||||
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
|
; NONOSDK22x_190703 = 2.2.2-dev(38a443e)
|
||||||
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
|
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703
|
||||||
@@ -195,58 +193,112 @@ build_flags =
|
|||||||
-DVTABLES_IN_FLASH
|
-DVTABLES_IN_FLASH
|
||||||
; restrict to minimal mime-types
|
; restrict to minimal mime-types
|
||||||
-DMIMETYPE_MINIMAL
|
-DMIMETYPE_MINIMAL
|
||||||
|
; other special-purpose framework flags (see https://docs.platformio.org/en/latest/platforms/espressif8266.html)
|
||||||
|
; decrease code cache size and increase IRAM to fit all pixel functions
|
||||||
|
-D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'"
|
||||||
|
; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
|
||||||
#https://github.com/lorol/LITTLEFS.git
|
#https://github.com/lorol/LITTLEFS.git
|
||||||
# ESPAsyncTCP @ 1.2.0
|
ESPAsyncTCP @ 1.2.2
|
||||||
ESPAsyncUDP
|
ESPAsyncUDP
|
||||||
makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 and newer do not compile on ESP core < 3.0.0
|
${env.lib_deps}
|
||||||
|
|
||||||
[esp32]
|
[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 = espressif32@3.5.0
|
||||||
|
platform_packages = framework-arduinoespressif32 @ https://github.com/Aircoookie/arduino-esp32.git#1.0.6.4
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DARDUINO_ARCH_ESP32
|
-DARDUINO_ARCH_ESP32
|
||||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
-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
|
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
||||||
-D LOROL_LITTLEFS
|
-D LOROL_LITTLEFS
|
||||||
|
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||||
|
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
|
||||||
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
||||||
|
extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
|
||||||
|
big_partitions = tools/WLED_ESP32_4MB_256KB_FS.csv ;; 1.8MB firmware, 256KB filesystem, coredump support
|
||||||
|
large_partitions = tools/WLED_ESP32_8MB.csv
|
||||||
|
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
|
||||||
https://github.com/lorol/LITTLEFS.git
|
https://github.com/lorol/LITTLEFS.git
|
||||||
makuna/NeoPixelBus @ 2.6.7
|
|
||||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||||
|
${env.lib_deps}
|
||||||
|
# additional build flags for audioreactive
|
||||||
|
AR_build_flags = -D USERMOD_AUDIOREACTIVE
|
||||||
|
AR_lib_deps = kosme/arduinoFFT @ 2.0.1
|
||||||
|
|
||||||
|
[esp32_idf_V4]
|
||||||
|
;; experimental build environment for ESP32 using ESP-IDF 4.4.x / arduino-esp32 v2.0.5
|
||||||
|
;; very similar to the normal ESP32 flags, but omitting Lorol LittleFS, as littlefs is included in the new framework already.
|
||||||
|
;;
|
||||||
|
;; please note that you can NOT update existing ESP32 installs with a "V4" build. Also updating by OTA will not work properly.
|
||||||
|
;; You need to completely erase your device (esptool erase_flash) first, then install the "V4" build from VSCode+platformio.
|
||||||
|
platform = espressif32@ ~6.3.2
|
||||||
|
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
|
||||||
|
build_flags = -g
|
||||||
|
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
||||||
|
-DARDUINO_ARCH_ESP32 -DESP32
|
||||||
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||||
|
lib_deps =
|
||||||
|
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
[esp32s2]
|
[esp32s2]
|
||||||
|
;; generic definitions for all ESP32-S2 boards
|
||||||
|
platform = espressif32@ ~6.3.2
|
||||||
|
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DARDUINO_ARCH_ESP32
|
-DARDUINO_ARCH_ESP32
|
||||||
-DARDUINO_ARCH_ESP32S2
|
-DARDUINO_ARCH_ESP32S2
|
||||||
-DCONFIG_IDF_TARGET_ESP32S2
|
-DCONFIG_IDF_TARGET_ESP32S2=1
|
||||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
|
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
|
||||||
-DCO
|
-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
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
|
||||||
makuna/NeoPixelBus @ 2.6.7
|
|
||||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
[esp32c3]
|
[esp32c3]
|
||||||
|
;; generic definitions for all ESP32-C3 boards
|
||||||
|
platform = espressif32@ ~6.3.2
|
||||||
|
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
|
||||||
build_flags = -g
|
build_flags = -g
|
||||||
-DARDUINO_ARCH_ESP32
|
-DARDUINO_ARCH_ESP32
|
||||||
-DARDUINO_ARCH_ESP32C3
|
-DARDUINO_ARCH_ESP32C3
|
||||||
-DCONFIG_IDF_TARGET_ESP32C3
|
-DCONFIG_IDF_TARGET_ESP32C3=1
|
||||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
-DCO
|
-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 =
|
lib_deps =
|
||||||
${env.lib_deps}
|
|
||||||
makuna/NeoPixelBus @ 2.6.7
|
|
||||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
|
[esp32s3]
|
||||||
|
;; generic definitions for all ESP32-S3 boards
|
||||||
|
platform = espressif32@ ~6.3.2
|
||||||
|
platform_packages = platformio/framework-arduinoespressif32 @ 3.20009.0 ;; select arduino-esp32 v2.0.9 (arduino-esp32 2.0.10 thru 2.0.14 are buggy so avoid them)
|
||||||
|
build_flags = -g
|
||||||
|
-DESP32
|
||||||
|
-DARDUINO_ARCH_ESP32
|
||||||
|
-DARDUINO_ARCH_ESP32S3
|
||||||
|
-DCONFIG_IDF_TARGET_ESP32S3=1
|
||||||
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
|
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=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
|
||||||
|
lib_deps =
|
||||||
|
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# WLED BUILDS
|
# WLED BUILDS
|
||||||
@@ -258,8 +310,14 @@ platform = ${common.platform_wled_default}
|
|||||||
platform_packages = ${common.platform_packages}
|
platform_packages = ${common.platform_packages}
|
||||||
board_build.ldscript = ${common.ldscript_4m1m}
|
board_build.ldscript = ${common.ldscript_4m1m}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D
|
||||||
lib_deps = ${esp8266.lib_deps}
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
monitor_filters = esp8266_exception_decoder
|
||||||
|
|
||||||
|
[env:nodemcuv2_160]
|
||||||
|
extends = env:nodemcuv2
|
||||||
|
board_build.f_cpu = 160000000L
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
|
||||||
|
|
||||||
[env:esp8266_2m]
|
[env:esp8266_2m]
|
||||||
board = esp_wroom_02
|
board = esp_wroom_02
|
||||||
@@ -267,289 +325,195 @@ platform = ${common.platform_wled_default}
|
|||||||
platform_packages = ${common.platform_packages}
|
platform_packages = ${common.platform_packages}
|
||||||
board_build.ldscript = ${common.ldscript_2m512k}
|
board_build.ldscript = ${common.ldscript_2m512k}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02
|
||||||
lib_deps = ${esp8266.lib_deps}
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:esp8266_2m_160]
|
||||||
|
extends = env:esp8266_2m
|
||||||
|
board_build.f_cpu = 160000000L
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160
|
||||||
|
|
||||||
[env:esp01_1m_full]
|
[env:esp01_1m_full]
|
||||||
board = esp01_1m
|
board = esp01_1m
|
||||||
platform = ${common.platform_wled_default}
|
platform = ${common.platform_wled_default}
|
||||||
platform_packages = ${common.platform_packages}
|
platform_packages = ${common.platform_packages}
|
||||||
board_build.ldscript = ${common.ldscript_1m128k}
|
board_build.ldscript = ${common.ldscript_1m128k}
|
||||||
build_unflags = ${common.build_unflags}
|
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.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA
|
||||||
|
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
|
||||||
lib_deps = ${esp8266.lib_deps}
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
[env:esp07]
|
[env:esp01_1m_full_160]
|
||||||
board = esp07
|
extends = env:esp01_1m_full
|
||||||
platform = ${common.platform_wled_default}
|
board_build.f_cpu = 160000000L
|
||||||
platform_packages = ${common.platform_packages}
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
|
||||||
board_build.ldscript = ${common.ldscript_4m1m}
|
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags_esp8266}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:d1_mini]
|
|
||||||
board = d1_mini
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
platform_packages = ${common.platform_packages}
|
|
||||||
upload_speed = 921600
|
|
||||||
board_build.ldscript = ${common.ldscript_4m1m}
|
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags_esp8266}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
monitor_filters = esp8266_exception_decoder
|
|
||||||
|
|
||||||
[env:heltec_wifi_kit_8]
|
|
||||||
board = d1_mini
|
|
||||||
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}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:h803wf]
|
|
||||||
board = d1_mini
|
|
||||||
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 LEDPIN=1 -D WLED_DISABLE_INFRARED
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:esp32dev]
|
[env:esp32dev]
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
platform = ${esp32.platform}
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
build_unflags = ${common.build_unflags}
|
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
|
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET
|
||||||
lib_deps = ${esp32.lib_deps}
|
lib_deps = ${esp32.lib_deps}
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
|
||||||
|
[env:esp32dev_8M]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET
|
||||||
|
${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
|
${esp32.AR_lib_deps}
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
board_build.partitions = ${esp32.large_partitions}
|
||||||
|
; board_build.f_flash = 80000000L
|
||||||
|
|
||||||
|
[env:esp32dev_audioreactive]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET
|
||||||
|
${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32.lib_deps}
|
||||||
|
${esp32.AR_lib_deps}
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
; board_build.f_flash = 80000000L
|
||||||
|
; board_build.flash_mode = dio
|
||||||
|
|
||||||
[env:esp32_eth]
|
[env:esp32_eth]
|
||||||
board = esp32-poe
|
board = esp32-poe
|
||||||
platform = ${esp32.platform}
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
build_unflags = ${common.build_unflags}
|
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.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
|
||||||
|
-D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
|
||||||
lib_deps = ${esp32.lib_deps}
|
lib_deps = ${esp32.lib_deps}
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
|
||||||
# ESP32 ETH build that fits in old 1M app space (disables Blynk, Cronixie, and Hue sync)
|
[env:esp32_wrover]
|
||||||
[env:esp32_eth_ota1mapp]
|
extends = esp32_idf_V4
|
||||||
extends = env:esp32_eth
|
platform = ${esp32_idf_V4.platform}
|
||||||
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
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
|
board = ttgo-t7-v14-mini32
|
||||||
[env:esp32s2_saola]
|
board_build.f_flash = 80000000L
|
||||||
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
|
|
||||||
platform_packages =
|
|
||||||
framework = arduino
|
|
||||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
|
||||||
board_build.flash_mode = qio
|
board_build.flash_mode = qio
|
||||||
upload_speed = 460800
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
lib_deps = ${esp32s2.lib_deps}
|
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER
|
||||||
|
-DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html
|
||||||
|
-D LEDPIN=25
|
||||||
|
; ${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
|
; ${esp32.AR_lib_deps}
|
||||||
|
|
||||||
[env:esp32c3]
|
[env:esp32c3dev]
|
||||||
board = esp32-c3-devkitm-1
|
extends = esp32c3
|
||||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip
|
platform = ${esp32c3.platform}
|
||||||
platform_packages =
|
platform_packages = ${esp32c3.platform_packages}
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
|
board = esp32-c3-devkitm-1
|
||||||
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3
|
||||||
|
-D WLED_WATCHDOG_TIMEOUT=0
|
||||||
|
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
|
||||||
|
;-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip
|
||||||
upload_speed = 460800
|
upload_speed = 460800
|
||||||
build_unflags = ${common.build_unflags}
|
build_unflags = ${common.build_unflags}
|
||||||
lib_deps = ${esp32c3.lib_deps}
|
lib_deps = ${esp32c3.lib_deps}
|
||||||
|
|
||||||
[env:esp8285_4CH_MagicHome]
|
[env:esp32s3dev_16MB_opi]
|
||||||
board = esp8285
|
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
|
||||||
platform = ${common.platform_wled_default}
|
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
|
||||||
platform_packages = ${common.platform_packages}
|
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
|
||||||
board_build.ldscript = ${common.ldscript_1m128k}
|
platform = ${esp32s3.platform}
|
||||||
build_unflags = ${common.build_unflags}
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:esp8285_H801]
|
|
||||||
board = esp8285
|
|
||||||
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_DISABLE_OTA
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:d1_mini_5CH_Shojo_PCB]
|
|
||||||
board = d1_mini
|
|
||||||
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_USE_SHOJO_PCB
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# DEVELOPMENT BOARDS
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
[env:d1_mini_debug]
|
|
||||||
board = d1_mini
|
|
||||||
build_type = debug
|
|
||||||
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} ${common.debug_flags}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:d1_mini_ota]
|
|
||||||
board = d1_mini
|
|
||||||
upload_protocol = espota
|
|
||||||
# exchange for your WLED IP
|
|
||||||
upload_port = "10.10.1.27"
|
|
||||||
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}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:anavi_miracle_controller]
|
|
||||||
board = d1_mini
|
|
||||||
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 LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# custom board configurations
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
[env:wemos_shield_esp32]
|
|
||||||
board = esp32dev
|
|
||||||
platform = espressif32@3.2
|
|
||||||
upload_speed = 460800
|
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags_esp32}
|
|
||||||
-D LEDPIN=16
|
|
||||||
-D RLYPIN=19
|
|
||||||
-D BTNPIN=17
|
|
||||||
-D IRPIN=18
|
|
||||||
-D UWLED_USE_MY_CONFIG
|
|
||||||
-D USERMOD_DALLASTEMPERATURE
|
|
||||||
-D USERMOD_FOUR_LINE_DISPLAY
|
|
||||||
-D TEMPERATURE_PIN=23
|
|
||||||
lib_deps = ${esp32.lib_deps}
|
|
||||||
OneWire@~2.3.5
|
|
||||||
olikraus/U8g2 @ ^2.28.8
|
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
|
||||||
|
|
||||||
[env:m5atom]
|
|
||||||
board = esp32dev
|
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39
|
|
||||||
lib_deps = ${esp32.lib_deps}
|
|
||||||
platform = espressif32@3.2
|
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
|
||||||
|
|
||||||
[env:sp501e]
|
|
||||||
board = esp_wroom_02
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
board_build.ldscript = ${common.ldscript_2m512k}
|
|
||||||
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=1
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:sp511e]
|
|
||||||
board = esp_wroom_02
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
board_build.ldscript = ${common.ldscript_2m512k}
|
|
||||||
build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:athom7w]
|
|
||||||
board = esp_wroom_02
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
board_build.ldscript = ${common.ldscript_2m512k}
|
|
||||||
build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:athom15w]
|
|
||||||
board = esp_wroom_02
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
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
|
|
||||||
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}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# codm pixel controller board configurations
|
|
||||||
# codm-controller-0.6 can also be used for the TYWE3S controller
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
[env:codm-controller-0.6]
|
|
||||||
board = esp_wroom_02
|
|
||||||
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}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
[env:codm-controller-0.6-rev2]
|
|
||||||
board = esp_wroom_02
|
|
||||||
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}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# EleksTube-IPS
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
[env:elekstube_ips]
|
|
||||||
board = esp32dev
|
|
||||||
platform = espressif32@3.2
|
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
|
build_unflags = ${common.build_unflags}
|
||||||
-D USERMOD_RTC
|
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi
|
||||||
-D USERMOD_ELEKSTUBE_IPS
|
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
|
||||||
-D LEDPIN=12
|
;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip
|
||||||
-D RLYPIN=27
|
-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||||
-D BTNPIN=34
|
-DBOARD_HAS_PSRAM
|
||||||
-D WLED_DISABLE_INFRARED
|
${esp32.AR_build_flags}
|
||||||
-D DEFAULT_LED_COUNT=6
|
lib_deps = ${esp32s3.lib_deps}
|
||||||
# Display config
|
${esp32.AR_lib_deps}
|
||||||
-D ST7789_DRIVER
|
board_build.partitions = ${esp32.extreme_partitions}
|
||||||
-D TFT_WIDTH=135
|
board_build.f_flash = 80000000L
|
||||||
-D TFT_HEIGHT=240
|
board_build.flash_mode = qio
|
||||||
-D CGRAM_OFFSET
|
|
||||||
-D TFT_SDA_READ
|
|
||||||
-D TFT_MOSI=23
|
|
||||||
-D TFT_SCLK=18
|
|
||||||
-D TFT_DC=25
|
|
||||||
-D TFT_RST=26
|
|
||||||
-D SPI_FREQUENCY=40000000
|
|
||||||
-D USER_SETUP_LOADED
|
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
lib_deps =
|
|
||||||
${esp32.lib_deps}
|
[env:esp32s3dev_8MB_opi]
|
||||||
TFT_eSPI @ ^2.3.70
|
;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
|
||||||
|
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
|
||||||
|
board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB
|
||||||
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
|
upload_speed = 921600
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi
|
||||||
|
-D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0
|
||||||
|
;-D ARDUINO_USB_CDC_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_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||||
|
-DBOARD_HAS_PSRAM
|
||||||
|
${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32s3.lib_deps}
|
||||||
|
${esp32.AR_lib_deps}
|
||||||
|
board_build.partitions = ${esp32.large_partitions}
|
||||||
|
board_build.f_flash = 80000000L
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
|
||||||
|
[env:esp32s3_4M_qspi]
|
||||||
|
;; ESP32-S3, with 4MB FLASH and <= 4MB PSRAM (memory_type: qio_qspi)
|
||||||
|
board = lolin_s3_mini ;; -S3 mini, 4MB flash 2MB PSRAM
|
||||||
|
platform = ${esp32s3.platform}
|
||||||
|
platform_packages = ${esp32s3.platform_packages}
|
||||||
|
upload_speed = 921600
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB")
|
||||||
|
-DBOARD_HAS_PSRAM
|
||||||
|
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||||
|
-D WLED_WATCHDOG_TIMEOUT=0
|
||||||
|
${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32s3.lib_deps}
|
||||||
|
${esp32.AR_lib_deps}
|
||||||
board_build.partitions = ${esp32.default_partitions}
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
board_build.f_flash = 80000000L
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
|
||||||
|
[env:lolin_s2_mini]
|
||||||
|
platform = ${esp32s2.platform}
|
||||||
|
platform_packages = ${esp32s2.platform_packages}
|
||||||
|
board = lolin_s2_mini
|
||||||
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
board_build.f_flash = 80000000L
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-DARDUINO_USB_MSC_ON_BOOT=0
|
||||||
|
-DARDUINO_USB_DFU_ON_BOOT=0
|
||||||
|
-DBOARD_HAS_PSRAM
|
||||||
|
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||||
|
-D WLED_WATCHDOG_TIMEOUT=0
|
||||||
|
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||||
|
-D LEDPIN=16
|
||||||
|
-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
|
||||||
|
${esp32.AR_build_flags}
|
||||||
|
lib_deps = ${esp32s2.lib_deps}
|
||||||
|
${esp32.AR_lib_deps}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
# Example PlatformIO Project Configuration Override
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copy to platformio_override.ini to activate overrides
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Please visit documentation: https://docs.platformio.org/page/projectconf.html
|
|
||||||
|
|
||||||
[platformio]
|
|
||||||
default_envs = WLED_tasmota_1M
|
|
||||||
|
|
||||||
[env:WLED_tasmota_1M]
|
|
||||||
board = esp01_1m
|
|
||||||
platform = ${common.platform_wled_default}
|
|
||||||
platform_packages = ${common.platform_packages}
|
|
||||||
board_build.ldscript = ${common.ldscript_1m128k}
|
|
||||||
lib_deps = ${esp8266.lib_deps}
|
|
||||||
build_unflags = ${common.build_unflags}
|
|
||||||
build_flags = ${common.build_flags_esp8266}
|
|
||||||
; *********************************************************************
|
|
||||||
; *** Use custom settings from file my_config.h
|
|
||||||
-DWLED_USE_MY_CONFIG
|
|
||||||
; *********************************************************************
|
|
||||||
; -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
|
|
||||||
; PIN defines - uncomment and change, if needed:
|
|
||||||
; -D LEDPIN=2
|
|
||||||
; -D BTNPIN=0
|
|
||||||
; -D TOUCHPIN=T0
|
|
||||||
; -D IRPIN=4
|
|
||||||
; -D RLYPIN=12
|
|
||||||
; -D RLYMDE=1
|
|
||||||
; digital LED strip types - uncomment only one ! - this will disable WS281x / SK681x support
|
|
||||||
; -D USE_APA102
|
|
||||||
; -D USE_WS2801
|
|
||||||
; -D USE_LPD8806
|
|
||||||
; PIN defines for 2 wire LEDs
|
|
||||||
-D CLKPIN=0
|
|
||||||
-D DATAPIN=2
|
|
||||||
; to drive analog LED strips (aka 5050) hardware configuration is no longer necessary
|
|
||||||
; configure the settings in the UI as follows (hard):
|
|
||||||
; 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
|
|
||||||
515
platformio_override.sample.ini
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# Example PlatformIO Project Configuration Override
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copy to platformio_override.ini to activate overrides
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Please visit documentation: https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = WLED_tasmota_1M # define as many as you need
|
||||||
|
|
||||||
|
#----------
|
||||||
|
# SAMPLE
|
||||||
|
#----------
|
||||||
|
[env:WLED_generic8266_1M]
|
||||||
|
extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options)
|
||||||
|
; board = esp01_1m # uncomment when ou need different board
|
||||||
|
; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform
|
||||||
|
; platform_packages = ${common.platform_packages}
|
||||||
|
; board_build.ldscript = ${common.ldscript_1m128k}
|
||||||
|
; upload_speed = 921600 # fast upload speed (remove ';' if your board supports fast upload speed)
|
||||||
|
# Sample libraries used for various usermods. Uncomment when using particular usermod.
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
; olikraus/U8g2 # @~2.33.15
|
||||||
|
; paulstoffregen/OneWire@~2.3.8
|
||||||
|
; adafruit/Adafruit Unified Sensor@^1.1.4
|
||||||
|
; adafruit/DHT sensor library@^1.4.1
|
||||||
|
; adafruit/Adafruit BME280 Library@^2.2.2
|
||||||
|
; Wire
|
||||||
|
; robtillaart/SHT85@~0.3.3
|
||||||
|
; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug
|
||||||
|
; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library
|
||||||
|
; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||||
|
;
|
||||||
|
; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above.
|
||||||
|
;
|
||||||
|
; Set a release name that may be used to distinguish required binary for flashing
|
||||||
|
; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS
|
||||||
|
;
|
||||||
|
; disable specific features
|
||||||
|
; -D WLED_DISABLE_OTA
|
||||||
|
; -D WLED_DISABLE_ALEXA
|
||||||
|
; -D WLED_DISABLE_HUESYNC
|
||||||
|
; -D WLED_DISABLE_LOXONE
|
||||||
|
; -D WLED_DISABLE_INFRARED
|
||||||
|
; -D WLED_DISABLE_WEBSOCKETS
|
||||||
|
; -D WLED_DISABLE_MQTT
|
||||||
|
; -D WLED_DISABLE_ADALIGHT
|
||||||
|
; -D WLED_DISABLE_2D
|
||||||
|
; -D WLED_DISABLE_PXMAGIC
|
||||||
|
; -D WLED_DISABLE_ESPNOW
|
||||||
|
; -D WLED_DISABLE_BROWNOUT_DET
|
||||||
|
;
|
||||||
|
; enable optional built-in features
|
||||||
|
; -D WLED_ENABLE_PIXART
|
||||||
|
; -D WLED_ENABLE_USERMOD_PAGE # if created
|
||||||
|
; -D WLED_ENABLE_DMX
|
||||||
|
;
|
||||||
|
; PIN defines - uncomment and change, if needed:
|
||||||
|
; -D LEDPIN=2
|
||||||
|
; or use this for multiple outputs
|
||||||
|
; -D DATA_PINS=1,3
|
||||||
|
; -D BTNPIN=0
|
||||||
|
; -D IRPIN=4
|
||||||
|
; -D RLYPIN=12
|
||||||
|
; -D RLYMDE=1
|
||||||
|
; -D RLYODRAIN=0
|
||||||
|
; -D LED_BUILTIN=2 # GPIO of built-in LED
|
||||||
|
;
|
||||||
|
; Limit max buses
|
||||||
|
; -D WLED_MAX_BUSSES=2
|
||||||
|
; -D WLED_MAX_ANALOG_CHANNELS=3 # only 3 PWM HW pins available
|
||||||
|
; -D WLED_MAX_DIGITAL_CHANNELS=2 # only 2 HW accelerated pins available
|
||||||
|
;
|
||||||
|
; Configure default WiFi
|
||||||
|
; -D CLIENT_SSID='"MyNetwork"'
|
||||||
|
; -D CLIENT_PASS='"Netw0rkPassw0rd"'
|
||||||
|
;
|
||||||
|
; Configure and use Ethernet
|
||||||
|
; -D WLED_USE_ETHERNET
|
||||||
|
; -D WLED_ETH_DEFAULT=5
|
||||||
|
; do not use pins 5, (16,) 17, 18, 19, 21, 22, 23, 25, 26, 27 for anything but ethernet
|
||||||
|
; -D PHY_ADDR=0 -D ETH_PHY_POWER=5 -D ETH_PHY_MDC=23 -D ETH_PHY_MDIO=18
|
||||||
|
; -D ETH_CLK_MODE=ETH_CLOCK_GPIO17_OUT
|
||||||
|
;
|
||||||
|
; NTP time configuration
|
||||||
|
; -D WLED_NTP_ENABLED=true
|
||||||
|
; -D WLED_TIMEZONE=2
|
||||||
|
; -D WLED_LAT=48.86
|
||||||
|
; -D WLED_LON=2.33
|
||||||
|
;
|
||||||
|
; Use Watchdog timer with 10s guard
|
||||||
|
; -D WLED_WATCHDOG_TIMEOUT=10
|
||||||
|
;
|
||||||
|
; Create debug build (with remote debug)
|
||||||
|
; -D WLED_DEBUG
|
||||||
|
; -D WLED_DEBUG_HOST='"192.168.0.100"'
|
||||||
|
; -D WLED_DEBUG_PORT=7868
|
||||||
|
;
|
||||||
|
; Use Autosave usermod and set it to do save after 90s
|
||||||
|
; -D USERMOD_AUTO_SAVE
|
||||||
|
; -D AUTOSAVE_AFTER_SEC=90
|
||||||
|
;
|
||||||
|
; Use AHT10/AHT15/AHT20 usermod
|
||||||
|
; -D USERMOD_AHT10
|
||||||
|
;
|
||||||
|
; Use INA226 usermod
|
||||||
|
; -D USERMOD_INA226
|
||||||
|
;
|
||||||
|
; Use 4 Line Display usermod with SPI display
|
||||||
|
; -D USERMOD_FOUR_LINE_DISPLAY
|
||||||
|
; -D USE_ALT_DISPlAY # mandatory
|
||||||
|
; -DFLD_SPI_DEFAULT
|
||||||
|
; -D FLD_TYPE=SSD1306_SPI64
|
||||||
|
; -D FLD_PIN_CLOCKSPI=14
|
||||||
|
; -D FLD_PIN_DATASPI=13
|
||||||
|
; -D FLD_PIN_DC=26
|
||||||
|
; -D FLD_PIN_CS=15
|
||||||
|
; -D FLD_PIN_RESET=27
|
||||||
|
;
|
||||||
|
; Use Rotary encoder usermod (in conjunction with 4LD)
|
||||||
|
; -D USERMOD_ROTARY_ENCODER_UI
|
||||||
|
; -D ENCODER_DT_PIN=5
|
||||||
|
; -D ENCODER_CLK_PIN=18
|
||||||
|
; -D ENCODER_SW_PIN=19
|
||||||
|
;
|
||||||
|
; Use Dallas DS18B20 temperature sensor usermod and configure it to use GPIO13
|
||||||
|
; -D USERMOD_DALLASTEMPERATURE
|
||||||
|
; -D TEMPERATURE_PIN=13
|
||||||
|
;
|
||||||
|
; Use Multi Relay usermod and configure it to use 6 relays and appropriate GPIO
|
||||||
|
; -D USERMOD_MULTI_RELAY
|
||||||
|
; -D MULTI_RELAY_MAX_RELAYS=6
|
||||||
|
; -D MULTI_RELAY_PINS=12,23,22,21,24,25
|
||||||
|
;
|
||||||
|
; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s
|
||||||
|
; -D USERMOD_PIRSWITCH
|
||||||
|
; -D PIR_SENSOR_PIN=4 # use -1 to disable usermod
|
||||||
|
; -D PIR_SENSOR_OFF_SEC=60
|
||||||
|
; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering)
|
||||||
|
;
|
||||||
|
; Use Audioreactive usermod and configure I2S microphone
|
||||||
|
; -D USERMOD_AUDIOREACTIVE
|
||||||
|
; -D AUDIOPIN=-1
|
||||||
|
; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM
|
||||||
|
; -D I2S_SDPIN=36
|
||||||
|
; -D I2S_WSPIN=23
|
||||||
|
; -D I2S_CKPIN=19
|
||||||
|
;
|
||||||
|
; Use PWM fan usermod
|
||||||
|
; -D USERMOD_PWM_FAN
|
||||||
|
; -D TACHO_PIN=33
|
||||||
|
; -D PWM_PIN=32
|
||||||
|
;
|
||||||
|
; Use built-in or custom LED as a status indicator (assumes LED is connected to GPIO16)
|
||||||
|
; -D STATUSLED=16
|
||||||
|
;
|
||||||
|
; 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
|
||||||
|
; or this for multiple outputs
|
||||||
|
; -D PIXEL_COUNTS=30,30
|
||||||
|
;
|
||||||
|
; set the default LED type
|
||||||
|
; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx)
|
||||||
|
;
|
||||||
|
; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs
|
||||||
|
; -D ABL_MILLIAMPS_DEFAULT=850
|
||||||
|
; -D LED_MILLIAMPS_DEFAULT=55
|
||||||
|
;
|
||||||
|
; 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
|
||||||
|
;
|
||||||
|
; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues)
|
||||||
|
; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1
|
||||||
|
;
|
||||||
|
; configure I2C and SPI interface (for various hardware)
|
||||||
|
; -D I2CSDAPIN=33 # initialise interface
|
||||||
|
; -D I2CSCLPIN=35 # initialise interface
|
||||||
|
; -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# PRE-CONFIGURED DEVELOPMENT BOARDS AND CONTROLLERS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[env:esp07]
|
||||||
|
board = esp07
|
||||||
|
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.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:d1_mini]
|
||||||
|
board = d1_mini
|
||||||
|
platform = ${common.platform_wled_default}
|
||||||
|
platform_packages = ${common.platform_packages}
|
||||||
|
upload_speed = 921600
|
||||||
|
board_build.ldscript = ${common.ldscript_4m1m}
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
monitor_filters = esp8266_exception_decoder
|
||||||
|
|
||||||
|
[env:heltec_wifi_kit_8]
|
||||||
|
board = d1_mini
|
||||||
|
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.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:h803wf]
|
||||||
|
board = d1_mini
|
||||||
|
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.build_flags} -D LEDPIN=1 -D WLED_DISABLE_INFRARED
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:esp32dev_qio80]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32.build_flags} #-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:esp32dev_V4_dio80]
|
||||||
|
;; experimental ESP32 env using ESP-IDF V4.4.x
|
||||||
|
;; Warning: this build environment is not stable!!
|
||||||
|
;; please erase your device before installing.
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32_idf_V4.platform}
|
||||||
|
platform_packages = ${esp32_idf_V4.platform_packages}
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} #-D WLED_DISABLE_BROWNOUT_DET
|
||||||
|
lib_deps = ${esp32_idf_V4.lib_deps}
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
board_build.partitions = ${esp32_idf_V4.default_partitions}
|
||||||
|
board_build.f_flash = 80000000L
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
|
||||||
|
[env:esp32s2_saola]
|
||||||
|
board = esp32-s2-saola-1
|
||||||
|
platform = ${esp32s2.platform}
|
||||||
|
platform_packages = ${esp32s2.platform_packages}
|
||||||
|
framework = arduino
|
||||||
|
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}
|
||||||
|
;-DLOLIN_WIFI_FIX ;; try this in case Wifi does not work
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
lib_deps = ${esp32s2.lib_deps}
|
||||||
|
|
||||||
|
[env:esp32s3dev_8MB_PSRAM_qspi]
|
||||||
|
;; ESP32-TinyS3 development board, with 8MB FLASH and PSRAM (memory_type: qio_qspi)
|
||||||
|
extends = env:esp32s3dev_8MB_PSRAM_opi
|
||||||
|
;board = um_tinys3 ; -> needs workaround from https://github.com/Aircoookie/WLED/pull/2905#issuecomment-1328049860
|
||||||
|
board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support
|
||||||
|
board_build.arduino.memory_type = qio_qspi ;; use with PSRAM: 2MB or 4MB
|
||||||
|
|
||||||
|
[env:esp8285_4CH_MagicHome]
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D WLED_DISABLE_OTA
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:esp8285_H801]
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D WLED_DISABLE_OTA
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:d1_mini_5CH_Shojo_PCB]
|
||||||
|
board = d1_mini
|
||||||
|
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.build_flags} -D WLED_USE_SHOJO_PCB
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:d1_mini_debug]
|
||||||
|
board = d1_mini
|
||||||
|
build_type = debug
|
||||||
|
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.build_flags} ${common.debug_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:d1_mini_ota]
|
||||||
|
board = d1_mini
|
||||||
|
upload_protocol = espota
|
||||||
|
# exchange for your WLED IP
|
||||||
|
upload_port = "10.10.1.27"
|
||||||
|
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.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:anavi_miracle_controller]
|
||||||
|
board = d1_mini
|
||||||
|
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.build_flags} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:esp32c3dev_2MB]
|
||||||
|
;; for ESP32-C3 boards with 2MB flash (instead of 4MB).
|
||||||
|
;; this board need a specific partition file. OTA not possible.
|
||||||
|
extends = esp32c3
|
||||||
|
platform = ${esp32c3.platform}
|
||||||
|
platform_packages = ${esp32c3.platform_packages}
|
||||||
|
board = esp32-c3-devkitm-1
|
||||||
|
build_flags = ${common.build_flags} ${esp32c3.build_flags}
|
||||||
|
-D WLED_WATCHDOG_TIMEOUT=0
|
||||||
|
-D WLED_DISABLE_OTA
|
||||||
|
; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
upload_speed = 115200
|
||||||
|
lib_deps = ${esp32c3.lib_deps}
|
||||||
|
board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
|
||||||
|
[env:wemos_shield_esp32]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
upload_speed = 460800
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32.build_flags}
|
||||||
|
-D LEDPIN=16
|
||||||
|
-D RLYPIN=19
|
||||||
|
-D BTNPIN=17
|
||||||
|
-D IRPIN=18
|
||||||
|
-D UWLED_USE_MY_CONFIG
|
||||||
|
-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]
|
||||||
|
board = esp32dev
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=27 -D BTNPIN=39
|
||||||
|
lib_deps = ${esp32.lib_deps}
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
|
|
||||||
|
[env:sp501e]
|
||||||
|
board = esp_wroom_02
|
||||||
|
platform = ${common.platform_wled_default}
|
||||||
|
board_build.ldscript = ${common.ldscript_2m512k}
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=1
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:sp511e]
|
||||||
|
board = esp_wroom_02
|
||||||
|
platform = ${common.platform_wled_default}
|
||||||
|
board_build.ldscript = ${common.ldscript_2m512k}
|
||||||
|
build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:Athom_RGBCW] ;7w and 5w(GU10) bulbs
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,13,5
|
||||||
|
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:Athom_15w_RGBCW] ;15w bulb
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D BTNPIN=-1 -D RLYPIN=-1 -D DATA_PINS=4,12,14,5,13
|
||||||
|
-D DEFAULT_LED_TYPE=TYPE_ANALOG_5CH -D WLED_DISABLE_INFRARED -D WLED_MAX_CCT_BLEND=0 -D WLED_USE_IC_CCT
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:Athom_3Pin_Controller] ;small controller with only data
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:Athom_4Pin_Controller] ; With clock and data interface
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:Athom_5Pin_Controller] ;Analog light strip controller
|
||||||
|
board = esp8285
|
||||||
|
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.build_flags} -D BTNPIN=0 -D RLYPIN=-1 DATA_PINS=4,12,14,13 -D WLED_DISABLE_INFRARED
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[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.build_flags} -D WLED_DISABLE_OTA -D USERMOD_MY9291
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# codm pixel controller board configurations
|
||||||
|
# codm-controller-0_6 can also be used for the TYWE3S controller
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[env:codm-controller-0_6]
|
||||||
|
board = esp_wroom_02
|
||||||
|
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.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
[env:codm-controller-0_6-rev2]
|
||||||
|
board = esp_wroom_02
|
||||||
|
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.build_flags}
|
||||||
|
lib_deps = ${esp8266.lib_deps}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# EleksTube-IPS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
[env:elekstube_ips]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
upload_speed = 921600
|
||||||
|
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
|
||||||
|
-D USERMOD_RTC
|
||||||
|
-D USERMOD_ELEKSTUBE_IPS
|
||||||
|
-D LEDPIN=12
|
||||||
|
-D RLYPIN=27
|
||||||
|
-D BTNPIN=34
|
||||||
|
-D DEFAULT_LED_COUNT=6
|
||||||
|
# Display config
|
||||||
|
-D ST7789_DRIVER
|
||||||
|
-D TFT_WIDTH=135
|
||||||
|
-D TFT_HEIGHT=240
|
||||||
|
-D CGRAM_OFFSET
|
||||||
|
-D TFT_SDA_READ
|
||||||
|
-D TFT_MOSI=23
|
||||||
|
-D TFT_SCLK=18
|
||||||
|
-D TFT_DC=25
|
||||||
|
-D TFT_RST=26
|
||||||
|
-D SPI_FREQUENCY=40000000
|
||||||
|
-D USER_SETUP_LOADED
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
TFT_eSPI @ ^2.3.70
|
||||||
|
board_build.partitions = ${esp32.default_partitions}
|
||||||
31
readme.md
@@ -3,7 +3,7 @@
|
|||||||
<a href="https://github.com/Aircoookie/WLED/releases"><img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a>
|
<a href="https://github.com/Aircoookie/WLED/releases"><img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a>
|
||||||
<a href="https://raw.githubusercontent.com/Aircoookie/WLED/master/LICENSE"><img src="https://img.shields.io/github/license/Aircoookie/wled?color=blue&style=flat-square"></a>
|
<a href="https://raw.githubusercontent.com/Aircoookie/WLED/master/LICENSE"><img src="https://img.shields.io/github/license/Aircoookie/wled?color=blue&style=flat-square"></a>
|
||||||
<a href="https://wled.discourse.group"><img src="https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square"></a>
|
<a href="https://wled.discourse.group"><img src="https://img.shields.io/discourse/topics?colorB=blue&label=forum&server=https%3A%2F%2Fwled.discourse.group%2F&style=flat-square"></a>
|
||||||
<a href="https://discord.gg/KuqP7NE"><img src="https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square"></a>
|
<a href="https://discord.gg/QAh7wJHrRM"><img src="https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square"></a>
|
||||||
<a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
|
<a href="https://kno.wled.ge"><img src="https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square"></a>
|
||||||
<a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a>
|
<a href="https://github.com/Aircoookie/WLED-App"><img src="https://img.shields.io/badge/app-wled-blue.svg?style=flat-square"></a>
|
||||||
<a href="https://gitpod.io/#https://github.com/Aircoookie/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a>
|
<a href="https://gitpod.io/#https://github.com/Aircoookie/WLED"><img src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?style=flat-square&logo=gitpod"></a>
|
||||||
@@ -15,27 +15,26 @@
|
|||||||
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!
|
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
|
## ⚙️ Features
|
||||||
- WS2812FX library integrated for over 100 special effects
|
- WS2812FX library with more than 100 special effects
|
||||||
- FastLED noise effects and 50 palettes
|
- FastLED noise effects and 50 palettes
|
||||||
- Modern UI with color, effect and segment controls
|
- Modern UI with color, effect and segment controls
|
||||||
- Segments to set different effects and colors to parts of the LEDs
|
- Segments to set different effects and colors to user defined parts of the LED string
|
||||||
- Settings page - configuration over network
|
- Settings page - configuration via the network
|
||||||
- Access Point and station mode - automatic failsafe AP
|
- Access Point and station mode - automatic failsafe AP
|
||||||
- Up to 10 LED outputs per instance
|
- Up to 10 LED outputs per instance
|
||||||
- Support for RGBW strips
|
- Support for RGBW strips
|
||||||
- Up to 250 user presets to save and load colors/effects easily, supports cycling through them.
|
- Up to 250 user presets to save and load colors/effects easily, supports cycling through them.
|
||||||
- Presets can be used to automatically execute API calls
|
- Presets can be used to automatically execute API calls
|
||||||
- Nightlight function (gradually dims down)
|
- Nightlight function (gradually dims down)
|
||||||
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
|
- Full OTA software updateability (HTTP + ArduinoOTA), password protectable
|
||||||
- Configurable analog clock + support for the Cronixie kit by Diamex
|
- Configurable analog clock (Cronixie, 7-segment and EleksTube IPS clock support via usermods)
|
||||||
- Configurable Auto Brightness limit for safer operation
|
- Configurable Auto Brightness limit for safe operation
|
||||||
- Filesystem-based config for easier backup of presets and settings
|
- Filesystem-based config for easier backup of presets and settings
|
||||||
|
|
||||||
## 💡 Supported light control interfaces
|
## 💡 Supported light control interfaces
|
||||||
- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033)
|
- WLED app for [Android](https://play.google.com/store/apps/details?id=com.aircoookie.WLED) and [iOS](https://apps.apple.com/us/app/wled/id1475695033)
|
||||||
- JSON and HTTP request APIs
|
- JSON and HTTP request APIs
|
||||||
- MQTT
|
- MQTT
|
||||||
- Blynk IoT
|
|
||||||
- E1.31, Art-Net, DDP and TPM2.net
|
- E1.31, Art-Net, DDP and TPM2.net
|
||||||
- [diyHue](https://github.com/diyhue/diyHue) (Wled is supported by diyHue, including Hue Sync Entertainment under udp. Thanks to [Gregory Mallios](https://github.com/gmallios))
|
- [diyHue](https://github.com/diyhue/diyHue) (Wled is supported by diyHue, including Hue Sync Entertainment under udp. Thanks to [Gregory Mallios](https://github.com/gmallios))
|
||||||
- [Hyperion](https://github.com/hyperion-project/hyperion.ng)
|
- [Hyperion](https://github.com/hyperion-project/hyperion.ng)
|
||||||
@@ -51,7 +50,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)!
|
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
|
## 🖼️ 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%">
|
<img src="/images/macbook-pro-space-gray-on-the-wooden-table.jpg" width="50%"><img src="/images/walking-with-iphone-x.jpg" width="50%">
|
||||||
@@ -67,15 +66,19 @@ Credits [here](https://kno.wled.ge/about/contributors/)!
|
|||||||
|
|
||||||
Join the Discord server to discuss everything about WLED!
|
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>
|
<a href="https://discord.gg/QAh7wJHrRM"><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)!
|
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:*
|
*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.
|
As per the MIT license, I assume no liability for any damage to you or any other person or equipment.
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,62 @@
|
|||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile
|
# This file is autogenerated by pip-compile with Python 3.12
|
||||||
# To update, run:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile
|
# pip-compile
|
||||||
#
|
#
|
||||||
aiofiles==0.6.0
|
aiofiles==22.1.0
|
||||||
# via platformio
|
# via platformio
|
||||||
ajsonrpc==1.1.0
|
ajsonrpc==1.2.0
|
||||||
# via platformio
|
# via platformio
|
||||||
bottle==0.12.19
|
anyio==3.6.2
|
||||||
|
# via starlette
|
||||||
|
bottle==0.12.25
|
||||||
# via platformio
|
# via platformio
|
||||||
certifi==2020.12.5
|
certifi==2023.7.22
|
||||||
# via requests
|
# via requests
|
||||||
chardet==4.0.0
|
charset-normalizer==3.1.0
|
||||||
# via requests
|
# via requests
|
||||||
click==7.1.2
|
click==8.1.3
|
||||||
# via
|
# via
|
||||||
# platformio
|
# platformio
|
||||||
# uvicorn
|
# uvicorn
|
||||||
colorama==0.4.4
|
colorama==0.4.6
|
||||||
# via platformio
|
# via
|
||||||
h11==0.12.0
|
# click
|
||||||
|
# platformio
|
||||||
|
h11==0.14.0
|
||||||
# via
|
# via
|
||||||
# uvicorn
|
# uvicorn
|
||||||
# wsproto
|
# wsproto
|
||||||
idna==2.10
|
idna==3.7
|
||||||
# via requests
|
# via
|
||||||
ifaddr==0.1.7
|
# anyio
|
||||||
# via zeroconf
|
# requests
|
||||||
marshmallow==3.11.1
|
marshmallow==3.19.0
|
||||||
# via platformio
|
# via platformio
|
||||||
platformio==5.1.1
|
packaging==23.1
|
||||||
|
# via marshmallow
|
||||||
|
platformio==6.1.14
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pyelftools==0.27
|
pyelftools==0.29
|
||||||
# via platformio
|
# via platformio
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
# via platformio
|
# via platformio
|
||||||
requests==2.25.1
|
requests==2.32.0
|
||||||
# via platformio
|
# via platformio
|
||||||
semantic-version==2.8.5
|
semantic-version==2.10.0
|
||||||
# via platformio
|
# via platformio
|
||||||
starlette==0.14.2
|
sniffio==1.3.0
|
||||||
|
# via anyio
|
||||||
|
starlette==0.23.1
|
||||||
# via platformio
|
# via platformio
|
||||||
tabulate==0.8.9
|
tabulate==0.9.0
|
||||||
# via platformio
|
# via platformio
|
||||||
urllib3==1.26.5
|
typing-extensions==4.11.0
|
||||||
|
# via starlette
|
||||||
|
urllib3==1.26.19
|
||||||
# via requests
|
# via requests
|
||||||
uvicorn==0.13.4
|
uvicorn==0.20.0
|
||||||
# via platformio
|
# via platformio
|
||||||
wsproto==1.0.0
|
wsproto==1.2.0
|
||||||
# via platformio
|
|
||||||
zeroconf==0.28.8
|
|
||||||
# via platformio
|
# via platformio
|
||||||
|
|||||||
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, 0x1A0000,
|
||||||
|
app1, app, ota_1, 0x1B0000,0x1A0000,
|
||||||
|
spiffs, data, spiffs, 0x350000,0xB0000,
|
||||||
|
8
tools/WLED_ESP32_16MB_9MB_FS.csv
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
|
app0, app, ota_0, 0x10000, 0x300000,
|
||||||
|
app1, app, ota_1, 0x310000,0x300000,
|
||||||
|
spiffs, data, spiffs, 0x610000,0x9E0000,
|
||||||
|
coredump, data, coredump,,64K
|
||||||
|
# to create/use ffat, see https://github.com/marcmerlin/esp32_fatfsimage
|
||||||
|
5
tools/WLED_ESP32_2MB_noOTA.csv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 20K,
|
||||||
|
otadata, data, ota, 0xe000, 8K,
|
||||||
|
app0, app, ota_0, 0x10000, 1536K,
|
||||||
|
spiffs, data, spiffs, 0x190000, 384K,
|
||||||
|
7
tools/WLED_ESP32_4MB_256KB_FS.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
|
app0, app, ota_0, 0x10000, 0x1D0000,
|
||||||
|
app1, app, ota_1, 0x1E0000,0x1D0000,
|
||||||
|
spiffs, data, spiffs, 0x3B0000,0x40000,
|
||||||
|
coredump, data, coredump,,64K
|
||||||
|
6
tools/WLED_ESP32_4MB_700k_FS.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, 0x1A0000,
|
||||||
|
app1, app, ota_1, 0x1B0000,0x1A0000,
|
||||||
|
spiffs, data, spiffs, 0x350000,0xB0000,
|
||||||
|
@@ -3,4 +3,5 @@ nvs, data, nvs, 0x9000, 0x5000,
|
|||||||
otadata, data, ota, 0xe000, 0x2000,
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
app0, app, ota_0, 0x10000, 0x200000,
|
app0, app, ota_0, 0x10000, 0x200000,
|
||||||
app1, app, ota_1, 0x210000,0x200000,
|
app1, app, ota_1, 0x210000,0x200000,
|
||||||
spiffs, data, spiffs, 0x410000,0x3F0000,
|
spiffs, data, spiffs, 0x410000,0x3E0000,
|
||||||
|
coredump, data, coredump,,64K
|
||||||
|
|||||||
|
212
tools/cdata-test.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const { describe, it, before, after } = require('node:test');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const child_process = require('child_process');
|
||||||
|
const util = require('util');
|
||||||
|
const execPromise = util.promisify(child_process.exec);
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'test'; // Set the environment to testing
|
||||||
|
const cdata = require('./cdata.js');
|
||||||
|
|
||||||
|
describe('Function', () => {
|
||||||
|
const testFolderPath = path.join(__dirname, 'testFolder');
|
||||||
|
const oldFilePath = path.join(testFolderPath, 'oldFile.txt');
|
||||||
|
const newFilePath = path.join(testFolderPath, 'newFile.txt');
|
||||||
|
|
||||||
|
// Create a temporary file before the test
|
||||||
|
before(() => {
|
||||||
|
// Create test folder
|
||||||
|
if (!fs.existsSync(testFolderPath)) {
|
||||||
|
fs.mkdirSync(testFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an old file
|
||||||
|
fs.writeFileSync(oldFilePath, 'This is an old file.');
|
||||||
|
// Modify the 'mtime' to simulate an old file
|
||||||
|
const oldTime = new Date();
|
||||||
|
oldTime.setFullYear(oldTime.getFullYear() - 1);
|
||||||
|
fs.utimesSync(oldFilePath, oldTime, oldTime);
|
||||||
|
|
||||||
|
// Create a new file
|
||||||
|
fs.writeFileSync(newFilePath, 'This is a new file.');
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete the temporary files after the test
|
||||||
|
after(() => {
|
||||||
|
fs.rmSync(testFolderPath, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isFileNewerThan', async () => {
|
||||||
|
it('should return true if the file is newer than the provided time', async () => {
|
||||||
|
const pastTime = Date.now() - 10000; // 10 seconds ago
|
||||||
|
assert.strictEqual(cdata.isFileNewerThan(newFilePath, pastTime), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the file is older than the provided time', async () => {
|
||||||
|
assert.strictEqual(cdata.isFileNewerThan(oldFilePath, Date.now()), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if the file does not exist', async () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
cdata.isFileNewerThan('nonexistent.txt', Date.now());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isAnyFileInFolderNewerThan', async () => {
|
||||||
|
it('should return true if a file in the folder is newer than the given time', async () => {
|
||||||
|
const time = fs.statSync(path.join(testFolderPath, 'oldFile.txt')).mtime;
|
||||||
|
assert.strictEqual(cdata.isAnyFileInFolderNewerThan(testFolderPath, time), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if no files in the folder are newer than the given time', async () => {
|
||||||
|
assert.strictEqual(cdata.isAnyFileInFolderNewerThan(testFolderPath, new Date()), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if the folder does not exist', async () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
cdata.isAnyFileInFolderNewerThan('nonexistent', new Date());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Script', () => {
|
||||||
|
const folderPath = 'wled00';
|
||||||
|
const dataPath = path.join(folderPath, 'data');
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
// Backup files
|
||||||
|
fs.cpSync("wled00/data", "wled00Backup", { recursive: true });
|
||||||
|
fs.cpSync("tools/cdata.js", "cdata.bak.js");
|
||||||
|
fs.cpSync("package.json", "package.bak.json");
|
||||||
|
});
|
||||||
|
after(() => {
|
||||||
|
// Restore backup
|
||||||
|
fs.rmSync("wled00/data", { recursive: true });
|
||||||
|
fs.renameSync("wled00Backup", "wled00/data");
|
||||||
|
fs.rmSync("tools/cdata.js");
|
||||||
|
fs.renameSync("cdata.bak.js", "tools/cdata.js");
|
||||||
|
fs.rmSync("package.json");
|
||||||
|
fs.renameSync("package.bak.json", "package.json");
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete all html_*.h files
|
||||||
|
async function deleteBuiltFiles() {
|
||||||
|
const files = await fs.promises.readdir(folderPath);
|
||||||
|
await Promise.all(files.map(file => {
|
||||||
|
if (file.startsWith('html_') && path.extname(file) === '.h') {
|
||||||
|
return fs.promises.unlink(path.join(folderPath, file));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if html_*.h files were created
|
||||||
|
async function checkIfBuiltFilesExist() {
|
||||||
|
const files = await fs.promises.readdir(folderPath);
|
||||||
|
const htmlFiles = files.filter(file => file.startsWith('html_') && path.extname(file) === '.h');
|
||||||
|
assert(htmlFiles.length > 0, 'html_*.h files were not created');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runAndCheckIfBuiltFilesExist() {
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
await checkIfBuiltFilesExist();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkIfFileWasNewlyCreated(file) {
|
||||||
|
const modifiedTime = fs.statSync(file).mtimeMs;
|
||||||
|
assert(Date.now() - modifiedTime < 500, file + ' was not modified');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testFileModification(sourceFilePath, resultFile) {
|
||||||
|
// run cdata.js to ensure html_*.h files are created
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
|
||||||
|
// modify file
|
||||||
|
fs.appendFileSync(sourceFilePath, ' ');
|
||||||
|
// delay for 1 second to ensure the modified time is different
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// run script cdata.js again and wait for it to finish
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
|
||||||
|
await checkIfFileWasNewlyCreated(path.join(folderPath, resultFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('should build if', () => {
|
||||||
|
it('html_*.h files are missing', async () => {
|
||||||
|
await deleteBuiltFiles();
|
||||||
|
await runAndCheckIfBuiltFilesExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only one html_*.h file is missing', async () => {
|
||||||
|
// run script cdata.js and wait for it to finish
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
|
||||||
|
// delete a random html_*.h file
|
||||||
|
let files = await fs.promises.readdir(folderPath);
|
||||||
|
let htmlFiles = files.filter(file => file.startsWith('html_') && path.extname(file) === '.h');
|
||||||
|
const randomFile = htmlFiles[Math.floor(Math.random() * htmlFiles.length)];
|
||||||
|
await fs.promises.unlink(path.join(folderPath, randomFile));
|
||||||
|
|
||||||
|
await runAndCheckIfBuiltFilesExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('script was executed with -f or --force', async () => {
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
await execPromise('node tools/cdata.js --force');
|
||||||
|
await checkIfFileWasNewlyCreated(path.join(folderPath, 'html_ui.h'));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
await execPromise('node tools/cdata.js -f');
|
||||||
|
await checkIfFileWasNewlyCreated(path.join(folderPath, 'html_ui.h'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a file changes', async () => {
|
||||||
|
await testFileModification(path.join(dataPath, 'index.htm'), 'html_ui.h');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a inlined file changes', async () => {
|
||||||
|
await testFileModification(path.join(dataPath, 'index.js'), 'html_ui.h');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a settings file changes', async () => {
|
||||||
|
await testFileModification(path.join(dataPath, 'settings_leds.htm'), 'html_ui.h');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the favicon changes', async () => {
|
||||||
|
await testFileModification(path.join(dataPath, 'favicon.ico'), 'html_ui.h');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cdata.js changes', async () => {
|
||||||
|
await testFileModification('tools/cdata.js', 'html_ui.h');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('package.json changes', async () => {
|
||||||
|
await testFileModification('package.json', 'html_ui.h');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should not build if', () => {
|
||||||
|
it('the files are already built', async () => {
|
||||||
|
await deleteBuiltFiles();
|
||||||
|
|
||||||
|
// run script cdata.js and wait for it to finish
|
||||||
|
let startTime = Date.now();
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
const firstRunTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
// run script cdata.js and wait for it to finish
|
||||||
|
startTime = Date.now();
|
||||||
|
await execPromise('node tools/cdata.js');
|
||||||
|
const secondRunTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
// check if second run was faster than the first (must be at least 2x faster)
|
||||||
|
assert(secondRunTime < firstRunTime / 2, 'html_*.h files were rebuilt');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
463
tools/cdata.js
@@ -2,7 +2,7 @@
|
|||||||
* Writes compressed C arrays of data files (web interface)
|
* Writes compressed C arrays of data files (web interface)
|
||||||
* How to use it?
|
* How to use it?
|
||||||
*
|
*
|
||||||
* 1) Install Node 11+ and npm
|
* 1) Install Node 20+ and npm
|
||||||
* 2) npm install
|
* 2) npm install
|
||||||
* 3) npm run build
|
* 3) npm run build
|
||||||
*
|
*
|
||||||
@@ -15,22 +15,65 @@
|
|||||||
* It uses NodeJS packages to inline, minify and GZIP files. See writeHtmlGzipped and writeChunks invocations at the bottom of the page.
|
* It uses NodeJS packages to inline, minify and GZIP files. See writeHtmlGzipped and writeChunks invocations at the bottom of the page.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("node:fs");
|
||||||
|
const path = require("path");
|
||||||
|
const inliner = require("inliner");
|
||||||
|
const zlib = require("node:zlib");
|
||||||
|
const CleanCSS = require("clean-css");
|
||||||
|
const minifyHtml = require("html-minifier-terser").minify;
|
||||||
const packageJson = require("../package.json");
|
const packageJson = require("../package.json");
|
||||||
|
|
||||||
/**
|
// Export functions for testing
|
||||||
|
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
|
||||||
|
|
||||||
|
const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"]
|
||||||
|
|
||||||
|
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
|
||||||
|
const wledBanner = `
|
||||||
|
\t\x1b[34m ## ## ## ###### ######
|
||||||
|
\t\x1b[34m## ## ## ## ## ## ##
|
||||||
|
\t\x1b[34m## ## ## ## ###### ## ##
|
||||||
|
\t\x1b[34m## ## ## ## ## ## ##
|
||||||
|
\t\x1b[34m ## ## ###### ###### ######
|
||||||
|
\t\t\x1b[36m build script for web UI
|
||||||
|
\x1b[0m`;
|
||||||
|
|
||||||
|
const singleHeader = `/*
|
||||||
|
* Binary array for the Web UI.
|
||||||
|
* gzip is used for smaller size and improved speeds.
|
||||||
*
|
*
|
||||||
|
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
||||||
|
* to find out how to easily modify the web UI source!
|
||||||
*/
|
*/
|
||||||
function hexdump(buffer) {
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const multiHeader = `/*
|
||||||
|
* More web UI HTML source arrays.
|
||||||
|
* This file is auto generated, please don't make any changes manually.
|
||||||
|
*
|
||||||
|
* Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
||||||
|
* to find out how to easily modify the web UI source!
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
|
function hexdump(buffer, isHex = false) {
|
||||||
let lines = [];
|
let lines = [];
|
||||||
|
|
||||||
for (let i = 0; i < buffer.length; i += 16) {
|
for (let i = 0; i < buffer.length; i += (isHex ? 32 : 16)) {
|
||||||
let block = buffer.slice(i, i + 16); // cut buffer into blocks of 16
|
var block;
|
||||||
let hexArray = [];
|
let hexArray = [];
|
||||||
|
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) {
|
for (let value of block) {
|
||||||
hexArray.push("0x" + value.toString(16).padStart(2, "0"));
|
hexArray.push("0x" + value.toString(16).padStart(2, "0"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let hexString = hexArray.join(", ");
|
let hexString = hexArray.join(", ");
|
||||||
let line = ` ${hexString}`;
|
let line = ` ${hexString}`;
|
||||||
@@ -40,161 +83,162 @@ function hexdump(buffer) {
|
|||||||
return lines.join(",\n");
|
return lines.join(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
const inliner = require("inliner");
|
|
||||||
const zlib = require("zlib");
|
|
||||||
|
|
||||||
function strReplace(str, search, replacement) {
|
|
||||||
return str.split(search).join(replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
function adoptVersionAndRepo(html) {
|
function adoptVersionAndRepo(html) {
|
||||||
let repoUrl = packageJson.repository ? packageJson.repository.url : undefined;
|
let repoUrl = packageJson.repository ? packageJson.repository.url : undefined;
|
||||||
if (repoUrl) {
|
if (repoUrl) {
|
||||||
repoUrl = repoUrl.replace(/^git\+/, "");
|
repoUrl = repoUrl.replace(/^git\+/, "");
|
||||||
repoUrl = repoUrl.replace(/\.git$/, "");
|
repoUrl = repoUrl.replace(/\.git$/, "");
|
||||||
// Replace we
|
html = html.replaceAll("https://github.com/atuline/WLED", repoUrl);
|
||||||
html = strReplace(html, "https://github.com/atuline/WLED", repoUrl);
|
html = html.replaceAll("https://github.com/Aircoookie/WLED", repoUrl);
|
||||||
html = strReplace(html, "https://github.com/Aircoookie/WLED", repoUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = packageJson.version;
|
let version = packageJson.version;
|
||||||
if (version) {
|
if (version) {
|
||||||
html = strReplace(html, "##VERSION##", version);
|
html = html.replaceAll("##VERSION##", version);
|
||||||
}
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeHtmlGzipped(sourceFile, resultFile) {
|
async function minify(str, type = "plain") {
|
||||||
console.info("Reading " + sourceFile);
|
const options = {
|
||||||
new inliner(sourceFile, function (error, html) {
|
collapseWhitespace: true,
|
||||||
console.info("Inlined " + html.length + " characters");
|
collapseBooleanAttributes: true,
|
||||||
html = filter(html, "html-minify-ui");
|
collapseInlineTagWhitespace: true,
|
||||||
console.info("Minified to " + html.length + " characters");
|
minifyCSS: true,
|
||||||
|
minifyJS: true,
|
||||||
if (error) {
|
removeAttributeQuotes: true,
|
||||||
console.warn(error);
|
removeComments: true,
|
||||||
throw error;
|
sortAttributes: true,
|
||||||
}
|
sortClassName: true,
|
||||||
|
|
||||||
html = adoptVersionAndRepo(html);
|
|
||||||
zlib.gzip(html, { level: zlib.constants.Z_BEST_COMPRESSION }, function (error, result) {
|
|
||||||
if (error) {
|
|
||||||
console.warn(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info("Compressed " + result.length + " bytes");
|
|
||||||
const array = hexdump(result);
|
|
||||||
const src = `/*
|
|
||||||
* Binary array for the Web UI.
|
|
||||||
* gzip is used for smaller size and improved speeds.
|
|
||||||
*
|
|
||||||
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
|
||||||
* to find out how to easily modify the web UI source!
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Autogenerated from ${sourceFile}, do not edit!!
|
|
||||||
const uint16_t PAGE_index_L = ${result.length};
|
|
||||||
const uint8_t PAGE_index[] PROGMEM = {
|
|
||||||
${array}
|
|
||||||
};
|
};
|
||||||
`;
|
|
||||||
console.info("Writing " + resultFile);
|
|
||||||
fs.writeFileSync(resultFile, src);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const CleanCSS = require("clean-css");
|
if (type == "plain") {
|
||||||
const MinifyHTML = require("html-minifier-terser").minify;
|
|
||||||
|
|
||||||
function filter(str, type) {
|
|
||||||
str = adoptVersionAndRepo(str);
|
|
||||||
|
|
||||||
if (type === undefined) {
|
|
||||||
return str;
|
return str;
|
||||||
} else if (type == "css-minify") {
|
} else if (type == "css-minify") {
|
||||||
return new CleanCSS({}).minify(str).styles;
|
return new CleanCSS({}).minify(str).styles;
|
||||||
|
} else if (type == "js-minify") {
|
||||||
|
return await minifyHtml('<script>' + str + '</script>', options).replace(/<[\/]*script>/g, '');
|
||||||
} else if (type == "html-minify") {
|
} else if (type == "html-minify") {
|
||||||
return MinifyHTML(str, {
|
return await minifyHtml(str, options);
|
||||||
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) {
|
throw new Error("Unknown filter: " + type);
|
||||||
if (s.method == "plaintext") {
|
}
|
||||||
|
|
||||||
|
async function writeHtmlGzipped(sourceFile, resultFile, page) {
|
||||||
|
console.info("Reading " + sourceFile);
|
||||||
|
new inliner(sourceFile, async function (error, html) {
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
html = adoptVersionAndRepo(html);
|
||||||
|
const originalLength = html.length;
|
||||||
|
html = await minify(html, "html-minify");
|
||||||
|
const result = zlib.gzipSync(html, { level: zlib.constants.Z_BEST_COMPRESSION });
|
||||||
|
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
|
||||||
|
const array = hexdump(result);
|
||||||
|
let src = singleHeader;
|
||||||
|
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`;
|
||||||
|
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
|
||||||
|
console.info("Writing " + resultFile);
|
||||||
|
fs.writeFileSync(resultFile, src);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function specToChunk(srcDir, s) {
|
||||||
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
||||||
const str = buf.toString("utf-8");
|
let chunk = `\n// Autogenerated from ${srcDir}/${s.file}, do not edit!!\n`
|
||||||
const chunk = `
|
|
||||||
// Autogenerated from ${srcDir}/${s.file}, do not edit!!
|
|
||||||
const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${
|
|
||||||
s.append || ""
|
|
||||||
}";
|
|
||||||
|
|
||||||
`;
|
if (s.method == "plaintext" || s.method == "gzip") {
|
||||||
|
let str = buf.toString("utf-8");
|
||||||
|
str = adoptVersionAndRepo(str);
|
||||||
|
const originalLength = str.length;
|
||||||
|
if (s.method == "gzip") {
|
||||||
|
if (s.mangle) str = s.mangle(str);
|
||||||
|
const zip = zlib.gzipSync(await minify(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION });
|
||||||
|
console.info("Minified and compressed " + s.file + " from " + originalLength + " to " + zip.length + " bytes");
|
||||||
|
const result = hexdump(zip);
|
||||||
|
chunk += `const uint16_t ${s.name}_length = ${zip.length};\n`;
|
||||||
|
chunk += `const uint8_t ${s.name}[] PROGMEM = {\n${result}\n};\n\n`;
|
||||||
|
return chunk;
|
||||||
|
} else {
|
||||||
|
const minified = await minify(str, s.filter);
|
||||||
|
console.info("Minified " + s.file + " from " + originalLength + " to " + minified.length + " bytes");
|
||||||
|
chunk += `const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${minified}${s.append || ""}";\n\n`;
|
||||||
return s.mangle ? s.mangle(chunk) : chunk;
|
return s.mangle ? s.mangle(chunk) : chunk;
|
||||||
|
}
|
||||||
} else if (s.method == "binary") {
|
} else if (s.method == "binary") {
|
||||||
const buf = fs.readFileSync(srcDir + "/" + s.file);
|
|
||||||
const result = hexdump(buf);
|
const result = hexdump(buf);
|
||||||
const chunk = `
|
chunk += `const uint16_t ${s.name}_length = ${buf.length};\n`;
|
||||||
// Autogenerated from ${srcDir}/${s.file}, do not edit!!
|
chunk += `const uint8_t ${s.name}[] PROGMEM = {\n${result}\n};\n\n`;
|
||||||
const uint16_t ${s.name}_length = ${result.length};
|
return chunk;
|
||||||
const uint8_t ${s.name}[] PROGMEM = {
|
|
||||||
${result}
|
|
||||||
};
|
|
||||||
|
|
||||||
`;
|
|
||||||
return s.mangle ? s.mangle(chunk) : chunk;
|
|
||||||
} else {
|
|
||||||
console.warn("Unknown method: " + s.method);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeChunks(srcDir, specs, resultFile) {
|
throw new Error("Unknown method: " + s.method);
|
||||||
let src = `/*
|
}
|
||||||
* More web UI HTML source arrays.
|
|
||||||
* This file is auto generated, please don't make any changes manually.
|
async function writeChunks(srcDir, specs, resultFile) {
|
||||||
* Instead, see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
|
let src = multiHeader;
|
||||||
* to find out how to easily modify the web UI source!
|
for (const s of specs) {
|
||||||
*/
|
|
||||||
`;
|
|
||||||
specs.forEach((s) => {
|
|
||||||
try {
|
|
||||||
console.info("Reading " + srcDir + "/" + s.file + " as " + s.name);
|
console.info("Reading " + srcDir + "/" + s.file + " as " + s.name);
|
||||||
src += specToChunk(srcDir, s);
|
src += await specToChunk(srcDir, s);
|
||||||
} catch (e) {
|
|
||||||
console.warn(
|
|
||||||
"Failed " + s.name + " from " + srcDir + "/" + s.file,
|
|
||||||
e.message.length > 60 ? e.message.substring(0, 60) : e.message
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
console.info("Writing " + src.length + " characters into " + resultFile);
|
console.info("Writing " + src.length + " characters into " + resultFile);
|
||||||
fs.writeFileSync(resultFile, src);
|
fs.writeFileSync(resultFile, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h");
|
// Check if a file is newer than a given time
|
||||||
|
function isFileNewerThan(filePath, time) {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
return stats.mtimeMs > time;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any file in a folder (or its subfolders) is newer than a given time
|
||||||
|
function isAnyFileInFolderNewerThan(folderPath, time) {
|
||||||
|
const files = fs.readdirSync(folderPath, { withFileTypes: true });
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(folderPath, file.name);
|
||||||
|
if (isFileNewerThan(filePath, time)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (file.isDirectory() && isAnyFileInFolderNewerThan(filePath, time)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the web UI is already built
|
||||||
|
function isAlreadyBuilt(webUIPath, packageJsonPath = "package.json") {
|
||||||
|
let lastBuildTime = Infinity;
|
||||||
|
|
||||||
|
for (const file of output) {
|
||||||
|
try {
|
||||||
|
lastBuildTime = Math.min(lastBuildTime, fs.statSync(file).mtimeMs);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'ENOENT') throw e;
|
||||||
|
console.info("File " + file + " does not exist. Rebuilding...");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isAnyFileInFolderNewerThan(webUIPath, lastBuildTime) && !isFileNewerThan(packageJsonPath, lastBuildTime) && !isFileNewerThan(__filename, lastBuildTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run this script if we're in a test environment
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(wledBanner);
|
||||||
|
|
||||||
|
if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.argv[2] !== '-f') {
|
||||||
|
console.info("Web UI is already built");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
|
||||||
|
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
|
||||||
|
writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
|
||||||
|
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
|
||||||
|
|
||||||
writeChunks(
|
writeChunks(
|
||||||
"wled00/data",
|
"wled00/data",
|
||||||
@@ -202,152 +246,77 @@ writeChunks(
|
|||||||
{
|
{
|
||||||
file: "style.css",
|
file: "style.css",
|
||||||
name: "PAGE_settingsCss",
|
name: "PAGE_settingsCss",
|
||||||
prepend: "=====(<style>",
|
method: "gzip",
|
||||||
append: "</style>)=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "css-minify",
|
filter: "css-minify",
|
||||||
|
mangle: (str) =>
|
||||||
|
str
|
||||||
|
.replace("%%", "%")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "settings.htm",
|
file: "settings.htm",
|
||||||
name: "PAGE_settings",
|
name: "PAGE_settings",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
mangle: (str) =>
|
|
||||||
str
|
|
||||||
.replace("%", "%%")
|
|
||||||
.replace(/Usermods\<\/button\>\<\/form\>/gms, "Usermods\<\/button\>\<\/form\>%DMXMENU%"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "settings_wifi.htm",
|
file: "settings_wifi.htm",
|
||||||
name: "PAGE_settings_wifi",
|
name: "PAGE_settings_wifi",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_leds.htm",
|
||||||
name: "PAGE_settings_leds",
|
name: "PAGE_settings_leds",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_dmx.htm",
|
||||||
name: "PAGE_settings_dmx",
|
name: "PAGE_settings_dmx",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_ui.htm",
|
||||||
name: "PAGE_settings_ui",
|
name: "PAGE_settings_ui",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_sync.htm",
|
||||||
name: "PAGE_settings_sync",
|
name: "PAGE_settings_sync",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_time.htm",
|
||||||
name: "PAGE_settings_time",
|
name: "PAGE_settings_time",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_sec.htm",
|
||||||
name: "PAGE_settings_sec",
|
name: "PAGE_settings_sec",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
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",
|
file: "settings_um.htm",
|
||||||
name: "PAGE_settings_um",
|
name: "PAGE_settings_um",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
mangle: (str) =>
|
},
|
||||||
str
|
{
|
||||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
file: "settings_2D.htm",
|
||||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
name: "PAGE_settings_2D",
|
||||||
.replace(
|
method: "gzip",
|
||||||
/function GetV().*\<\/script\>/gms,
|
filter: "html-minify",
|
||||||
"function GetV() {var d=document;\n"
|
},
|
||||||
),
|
{
|
||||||
|
file: "settings_pin.htm",
|
||||||
|
name: "PAGE_settings_pin",
|
||||||
|
method: "gzip",
|
||||||
|
filter: "html-minify"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wled00/html_settings.h"
|
"wled00/html_settings.h"
|
||||||
@@ -359,9 +328,7 @@ writeChunks(
|
|||||||
{
|
{
|
||||||
file: "usermod.htm",
|
file: "usermod.htm",
|
||||||
name: "PAGE_usermod",
|
name: "PAGE_usermod",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
mangle: (str) =>
|
mangle: (str) =>
|
||||||
str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'),
|
str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'),
|
||||||
@@ -393,48 +360,44 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
|
|||||||
{
|
{
|
||||||
file: "update.htm",
|
file: "update.htm",
|
||||||
name: "PAGE_update",
|
name: "PAGE_update",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
|
mangle: (str) =>
|
||||||
|
str
|
||||||
|
.replace(
|
||||||
|
/function GetV().*\<\/script\>/gms,
|
||||||
|
"</script><script src=\"/settings/s.js?p=9\"></script>"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "welcome.htm",
|
file: "welcome.htm",
|
||||||
name: "PAGE_welcome",
|
name: "PAGE_welcome",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "liveview.htm",
|
file: "liveview.htm",
|
||||||
name: "PAGE_liveview",
|
name: "PAGE_liveview",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "liveviewws.htm",
|
file: "liveviewws2D.htm",
|
||||||
name: "PAGE_liveviewws",
|
name: "PAGE_liveviewws2D",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "404.htm",
|
file: "404.htm",
|
||||||
name: "PAGE_404",
|
name: "PAGE_404",
|
||||||
prepend: "=====(",
|
method: "gzip",
|
||||||
append: ")=====",
|
|
||||||
method: "plaintext",
|
|
||||||
filter: "html-minify",
|
filter: "html-minify",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
file: "favicon.ico",
|
file: "favicon.ico",
|
||||||
name: "favicon",
|
name: "favicon",
|
||||||
method: "binary",
|
method: "binary",
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
"wled00/html_other.h"
|
"wled00/html_other.h"
|
||||||
);
|
);
|
||||||
|
|||||||
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
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
36
usermods/AHT10_v2/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Usermod AHT10
|
||||||
|
This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following:
|
||||||
|
- Temperature
|
||||||
|
- Humidity
|
||||||
|
|
||||||
|
Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu:
|
||||||
|
- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39).
|
||||||
|
- SensorType, one of:
|
||||||
|
- 0 - AHT10
|
||||||
|
- 1 - AHT15
|
||||||
|
- 2 - AHT20
|
||||||
|
- CheckInterval: Number of seconds between readings
|
||||||
|
- Decimals: Number of decimals to put in the output
|
||||||
|
|
||||||
|
Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||||
|
- Libraries
|
||||||
|
- `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10))
|
||||||
|
- `Wire`
|
||||||
|
|
||||||
|
## Author
|
||||||
|
[@LordMike](https://github.com/LordMike)
|
||||||
|
|
||||||
|
# Compiling
|
||||||
|
|
||||||
|
To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`)
|
||||||
|
```ini
|
||||||
|
[env:aht10_example]
|
||||||
|
extends = env:esp32dev
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags} ${esp32.build_flags}
|
||||||
|
-D USERMOD_AHT10
|
||||||
|
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
enjoyneering/AHT10@~1.1.0
|
||||||
|
```
|
||||||
9
usermods/AHT10_v2/platformio_override.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[env:aht10_example]
|
||||||
|
extends = env:esp32dev
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags} ${esp32.build_flags}
|
||||||
|
-D USERMOD_AHT10
|
||||||
|
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
enjoyneering/AHT10@~1.1.0
|
||||||
327
usermods/AHT10_v2/usermod_aht10.h
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include <AHT10.h>
|
||||||
|
|
||||||
|
#define AHT10_SUCCESS 1
|
||||||
|
|
||||||
|
class UsermodAHT10 : public Usermod
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const char _name[];
|
||||||
|
|
||||||
|
unsigned long _lastLoopCheck = 0;
|
||||||
|
|
||||||
|
bool _settingEnabled : 1; // Enable the usermod
|
||||||
|
bool _mqttPublish : 1; // Publish mqtt values
|
||||||
|
bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change
|
||||||
|
bool _mqttHomeAssistant : 1; // Enable Home Assistant docs
|
||||||
|
bool _initDone : 1; // Initialization is done
|
||||||
|
|
||||||
|
// Settings. Some of these are stored in a different format than they're user settings - so we don't have to convert at runtime
|
||||||
|
uint8_t _i2cAddress = AHT10_ADDRESS_0X38;
|
||||||
|
ASAIR_I2C_SENSOR _ahtType = AHT10_SENSOR;
|
||||||
|
uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds
|
||||||
|
float _decimalFactor = 100; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
|
||||||
|
|
||||||
|
uint8_t _lastStatus = 0;
|
||||||
|
float _lastHumidity = 0;
|
||||||
|
float _lastTemperature = 0;
|
||||||
|
|
||||||
|
#ifndef WLED_MQTT_DISABLE
|
||||||
|
float _lastHumiditySent = 0;
|
||||||
|
float _lastTemperatureSent = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AHT10 *_aht = nullptr;
|
||||||
|
|
||||||
|
float truncateDecimals(float val)
|
||||||
|
{
|
||||||
|
return roundf(val * _decimalFactor) / _decimalFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeAht()
|
||||||
|
{
|
||||||
|
if (_aht != nullptr)
|
||||||
|
{
|
||||||
|
delete _aht;
|
||||||
|
}
|
||||||
|
|
||||||
|
_aht = new AHT10(_i2cAddress, _ahtType);
|
||||||
|
|
||||||
|
_lastStatus = 0;
|
||||||
|
_lastHumidity = 0;
|
||||||
|
_lastTemperature = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~UsermodAHT10()
|
||||||
|
{
|
||||||
|
delete _aht;
|
||||||
|
_aht = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
void mqttInitialize()
|
||||||
|
{
|
||||||
|
// This is a generic "setup mqtt" function, So we must abort if we're not to do mqtt
|
||||||
|
if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char topic[128];
|
||||||
|
snprintf_P(topic, 127, "%s/temperature", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Temperature"), topic, F("temperature"), F("°C"));
|
||||||
|
|
||||||
|
snprintf_P(topic, 127, "%s/humidity", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Humidity"), topic, F("humidity"), F("%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange)
|
||||||
|
{
|
||||||
|
// Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
|
// Only report if the change is larger than the required diff
|
||||||
|
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange))
|
||||||
|
{
|
||||||
|
char subuf[128];
|
||||||
|
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
|
||||||
|
mqtt->publish(subuf, 0, false, String(state).c_str());
|
||||||
|
|
||||||
|
lastState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
|
||||||
|
void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||||
|
{
|
||||||
|
String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config");
|
||||||
|
|
||||||
|
StaticJsonDocument<600> doc;
|
||||||
|
|
||||||
|
doc[F("name")] = 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_BRAND);
|
||||||
|
device[F("model")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
initializeAht();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// if usermod is disabled or called during strip updating just exit
|
||||||
|
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||||
|
if (!_settingEnabled || strip.isUpdating())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// do your magic here
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
|
if (currentTime - _lastLoopCheck < _checkInterval)
|
||||||
|
return;
|
||||||
|
_lastLoopCheck = currentTime;
|
||||||
|
|
||||||
|
_lastStatus = _aht->readRawData();
|
||||||
|
|
||||||
|
if (_lastStatus == AHT10_ERROR)
|
||||||
|
{
|
||||||
|
// Perform softReset and retry
|
||||||
|
DEBUG_PRINTLN(F("AHTxx returned error, doing softReset"));
|
||||||
|
if (!_aht->softReset())
|
||||||
|
{
|
||||||
|
DEBUG_PRINTLN(F("softReset failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastStatus = _aht->readRawData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastStatus == AHT10_SUCCESS)
|
||||||
|
{
|
||||||
|
float temperature = truncateDecimals(_aht->readTemperature(AHT10_USE_READ_DATA));
|
||||||
|
float humidity = truncateDecimals(_aht->readHumidity(AHT10_USE_READ_DATA));
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
// Push to MQTT
|
||||||
|
|
||||||
|
// We can avoid reporting if the change is insignificant. The threshold chosen is below the level of accuracy, but way above 0.01 which is the precision of the value provided.
|
||||||
|
// The AHT10/15/20 has an accuracy of 0.3C in the temperature readings
|
||||||
|
mqttPublishIfChanged(F("temperature"), _lastTemperatureSent, temperature, 0.1f);
|
||||||
|
|
||||||
|
// The AHT10/15/20 has an accuracy in the humidity sensor of 2%
|
||||||
|
mqttPublishIfChanged(F("humidity"), _lastHumiditySent, humidity, 0.5f);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store
|
||||||
|
_lastTemperature = temperature;
|
||||||
|
_lastHumidity = humidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
void onMqttConnect(bool sessionPresent)
|
||||||
|
{
|
||||||
|
mqttInitialize();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_AHT10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject &root) override
|
||||||
|
{
|
||||||
|
// if "u" object does not exist yet wee need to create it
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull())
|
||||||
|
user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
#ifdef USERMOD_AHT10_DEBUG
|
||||||
|
JsonArray temp = user.createNestedArray(F("AHT last loop"));
|
||||||
|
temp.add(_lastLoopCheck);
|
||||||
|
|
||||||
|
temp = user.createNestedArray(F("AHT last status"));
|
||||||
|
temp.add(_lastStatus);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonArray jsonTemp = user.createNestedArray(F("Temperature"));
|
||||||
|
JsonArray jsonHumidity = user.createNestedArray(F("Humidity"));
|
||||||
|
|
||||||
|
if (_lastLoopCheck == 0)
|
||||||
|
{
|
||||||
|
// Before first run
|
||||||
|
jsonTemp.add(F("Not read yet"));
|
||||||
|
jsonHumidity.add(F("Not read yet"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastStatus != AHT10_SUCCESS)
|
||||||
|
{
|
||||||
|
jsonTemp.add(F("An error occurred"));
|
||||||
|
jsonHumidity.add(F("An error occurred"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonTemp.add(_lastTemperature);
|
||||||
|
jsonTemp.add(F("°C"));
|
||||||
|
|
||||||
|
jsonHumidity.add(_lastHumidity);
|
||||||
|
jsonHumidity.add(F("%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject &root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top[F("Enabled")] = _settingEnabled;
|
||||||
|
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
|
||||||
|
top[F("SensorType")] = _ahtType;
|
||||||
|
top[F("CheckInterval")] = _checkInterval / 1000;
|
||||||
|
top[F("Decimals")] = log10f(_decimalFactor);
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
top[F("MqttPublish")] = _mqttPublish;
|
||||||
|
top[F("MqttPublishAlways")] = _mqttPublishAlways;
|
||||||
|
top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("AHT10 config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject &root) override
|
||||||
|
{
|
||||||
|
// 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[FPSTR(_name)];
|
||||||
|
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
if (!configComplete)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool tmpBool = false;
|
||||||
|
configComplete &= getJsonValue(top[F("Enabled")], tmpBool);
|
||||||
|
if (configComplete)
|
||||||
|
_settingEnabled = tmpBool;
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
|
||||||
|
configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval);
|
||||||
|
if (configComplete)
|
||||||
|
{
|
||||||
|
if (1 <= _checkInterval && _checkInterval <= 600)
|
||||||
|
_checkInterval *= 1000;
|
||||||
|
else
|
||||||
|
// Invalid input
|
||||||
|
_checkInterval = 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor);
|
||||||
|
if (configComplete)
|
||||||
|
{
|
||||||
|
if (0 <= _decimalFactor && _decimalFactor <= 5)
|
||||||
|
_decimalFactor = pow10f(_decimalFactor);
|
||||||
|
else
|
||||||
|
// Invalid input
|
||||||
|
_decimalFactor = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tmpAhtType;
|
||||||
|
configComplete &= getJsonValue(top[F("SensorType")], tmpAhtType);
|
||||||
|
if (configComplete)
|
||||||
|
{
|
||||||
|
if (0 <= tmpAhtType && tmpAhtType <= 2)
|
||||||
|
_ahtType = static_cast<ASAIR_I2C_SENSOR>(tmpAhtType);
|
||||||
|
else
|
||||||
|
// Invalid input
|
||||||
|
_ahtType = ASAIR_I2C_SENSOR::AHT10_SENSOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool);
|
||||||
|
if (configComplete)
|
||||||
|
_mqttPublish = tmpBool;
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool);
|
||||||
|
if (configComplete)
|
||||||
|
_mqttPublishAlways = tmpBool;
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool);
|
||||||
|
if (configComplete)
|
||||||
|
_mqttHomeAssistant = tmpBool;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_initDone)
|
||||||
|
{
|
||||||
|
// Reloading config
|
||||||
|
initializeAht();
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
mqttInitialize();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_initDone = true;
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char UsermodAHT10::_name[] PROGMEM = "AHTxx";
|
||||||
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(F("Analog Clock"));
|
||||||
|
top[F("Overlay Enabled")] = enabled;
|
||||||
|
top[F("First LED (Main Ring)")] = mainSegment.firstLed;
|
||||||
|
top[F("Last LED (Main Ring)")] = mainSegment.lastLed;
|
||||||
|
top[F("Center/12h LED (Main Ring)")] = mainSegment.centerLed;
|
||||||
|
top[F("Hour Marks Enabled")] = hourMarksEnabled;
|
||||||
|
top[F("Hour Mark Color (RRGGBB)")] = colorToHexString(hourMarkColor);
|
||||||
|
top[F("Hour Color (RRGGBB)")] = colorToHexString(hourColor);
|
||||||
|
top[F("Minute Color (RRGGBB)")] = colorToHexString(minuteColor);
|
||||||
|
top[F("Show Seconds")] = secondsEnabled;
|
||||||
|
top[F("First LED (Seconds Ring)")] = secondsSegment.firstLed;
|
||||||
|
top[F("Last LED (Seconds Ring)")] = secondsSegment.lastLed;
|
||||||
|
top[F("Center/12h LED (Seconds Ring)")] = secondsSegment.centerLed;
|
||||||
|
top[F("Second Color (RRGGBB)")] = colorToHexString(secondColor);
|
||||||
|
top[F("Seconds Effect (0-1)")] = secondsEffect;
|
||||||
|
top[F("Blend Colors")] = blendColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject& root) override {
|
||||||
|
JsonObject top = root[F("Analog Clock")];
|
||||||
|
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
|
||||||
|
String color;
|
||||||
|
configComplete &= getJsonValue(top[F("Overlay Enabled")], enabled, false);
|
||||||
|
configComplete &= getJsonValue(top[F("First LED (Main Ring)")], mainSegment.firstLed, 0);
|
||||||
|
configComplete &= getJsonValue(top[F("Last LED (Main Ring)")], mainSegment.lastLed, 59);
|
||||||
|
configComplete &= getJsonValue(top[F("Center/12h LED (Main Ring)")], mainSegment.centerLed, 0);
|
||||||
|
configComplete &= getJsonValue(top[F("Hour Marks Enabled")], hourMarksEnabled, false);
|
||||||
|
configComplete &= getJsonValue(top[F("Hour Mark Color (RRGGBB)")], color, F("161616")) && hexStringToColor(color, hourMarkColor, 0x161616);
|
||||||
|
configComplete &= getJsonValue(top[F("Hour Color (RRGGBB)")], color, F("0000FF")) && hexStringToColor(color, hourColor, 0x0000FF);
|
||||||
|
configComplete &= getJsonValue(top[F("Minute Color (RRGGBB)")], color, F("00FF00")) && hexStringToColor(color, minuteColor, 0x00FF00);
|
||||||
|
configComplete &= getJsonValue(top[F("Show Seconds")], secondsEnabled, true);
|
||||||
|
configComplete &= getJsonValue(top[F("First LED (Seconds Ring)")], secondsSegment.firstLed, 0);
|
||||||
|
configComplete &= getJsonValue(top[F("Last LED (Seconds Ring)")], secondsSegment.lastLed, 59);
|
||||||
|
configComplete &= getJsonValue(top[F("Center/12h LED (Seconds Ring)")], secondsSegment.centerLed, 0);
|
||||||
|
configComplete &= getJsonValue(top[F("Second Color (RRGGBB)")], color, F("FF0000")) && hexStringToColor(color, secondColor, 0xFF0000);
|
||||||
|
configComplete &= getJsonValue(top[F("Seconds Effect (0-1)")], secondsEffect, 0);
|
||||||
|
configComplete &= getJsonValue(top[F("Blend Colors")], blendColors, true);
|
||||||
|
|
||||||
|
if (initDone) {
|
||||||
|
validateAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId() override {
|
||||||
|
return USERMOD_ID_ANALOG_CLOCK;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -25,6 +25,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
|
bool useUSSensorBottom = false; // using PIR or UltraSound sensor?
|
||||||
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
|
unsigned int topMaxDist = 50; // default maximum measured distance in cm, top
|
||||||
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
|
unsigned int bottomMaxDist = 50; // default maximum measured distance in cm, bottom
|
||||||
|
bool togglePower = false; // toggle power on/off with staircase on/off
|
||||||
|
|
||||||
/* runtime variables */
|
/* runtime variables */
|
||||||
bool initDone = false;
|
bool initDone = false;
|
||||||
@@ -65,7 +66,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
// The maximum number of configured segments.
|
// The maximum number of configured segments.
|
||||||
// Dynamically updated based on user configuration.
|
// Dynamically updated based on user configuration.
|
||||||
byte maxSegmentId = 1;
|
byte maxSegmentId = 1;
|
||||||
byte mainSegmentId = 0;
|
byte minSegmentId = 0;
|
||||||
|
|
||||||
// These values are used by the API to read the
|
// These values are used by the API to read the
|
||||||
// last sensor state, or trigger a sensor
|
// last sensor state, or trigger a sensor
|
||||||
@@ -90,39 +91,37 @@ class Animated_Staircase : public Usermod {
|
|||||||
static const char _bottomEcho_pin[];
|
static const char _bottomEcho_pin[];
|
||||||
static const char _topEchoCm[];
|
static const char _topEchoCm[];
|
||||||
static const char _bottomEchoCm[];
|
static const char _bottomEchoCm[];
|
||||||
|
static const char _togglePower[];
|
||||||
|
|
||||||
void publishMqtt(bool bottom, const char* state)
|
void publishMqtt(bool bottom, const char* state) {
|
||||||
{
|
#ifndef WLED_DISABLE_MQTT
|
||||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
if (WLED_MQTT_CONNECTED){
|
if (WLED_MQTT_CONNECTED){
|
||||||
char subuf[64];
|
char subuf[64];
|
||||||
sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom);
|
sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)bottom);
|
||||||
mqtt->publish(subuf, 0, false, state);
|
mqtt->publish(subuf, 0, false, state);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSegments() {
|
void updateSegments() {
|
||||||
mainSegmentId = strip.getMainSegmentId();
|
for (int i = minSegmentId; i < maxSegmentId; i++) {
|
||||||
WS2812FX::Segment* segments = strip.getSegments();
|
Segment &seg = strip.getSegment(i);
|
||||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
if (!seg.isActive()) continue; // skip gaps
|
||||||
if (!segments->isActive()) {
|
|
||||||
maxSegmentId = i - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= onIndex && i < offIndex) {
|
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
|
// 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
|
// changes are propagated even when the config is changed during a wipe
|
||||||
// segments->mode = mainsegment.mode;
|
// seg.setMode(mainsegment.mode);
|
||||||
// segments->colors[0] = mainsegment.colors[0];
|
// seg.setColor(0, mainsegment.colors[0]);
|
||||||
} else {
|
} else {
|
||||||
segments->setOption(SEG_OPTION_ON, 0, i);
|
seg.setOption(SEG_OPTION_ON, false);
|
||||||
}
|
}
|
||||||
// Always mark segments as "transitional", we are animating the staircase
|
// 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);
|
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +133,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
* received within this time, an object is detected
|
* received within this time, an object is detected
|
||||||
* and the function will return true.
|
* and the function will return true.
|
||||||
*
|
*
|
||||||
* The speed of sound is 343 meters per second at 20 degress Celcius.
|
* The speed of sound is 343 meters per second at 20 degrees Celsius.
|
||||||
* Since the sound has to travel back and forth, the detection
|
* Since the sound has to travel back and forth, the detection
|
||||||
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
|
* distance for the sensor in cm is (0.0343 * maxTimeUs) / 2.
|
||||||
*
|
*
|
||||||
@@ -199,6 +198,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
if (on) {
|
if (on) {
|
||||||
lastSensor = topSensorRead;
|
lastSensor = topSensorRead;
|
||||||
} else {
|
} else {
|
||||||
|
if (togglePower && onIndex == offIndex && offMode) toggleOnOff(); // toggle power on if off
|
||||||
// If the bottom sensor triggered, we need to swipe up, ON
|
// If the bottom sensor triggered, we need to swipe up, ON
|
||||||
swipe = bottomSensorRead;
|
swipe = bottomSensorRead;
|
||||||
|
|
||||||
@@ -208,9 +208,9 @@ class Animated_Staircase : public Usermod {
|
|||||||
if (onIndex == offIndex) {
|
if (onIndex == offIndex) {
|
||||||
// Position the indices for a correct on-swipe
|
// Position the indices for a correct on-swipe
|
||||||
if (swipe == SWIPE_UP) {
|
if (swipe == SWIPE_UP) {
|
||||||
onIndex = mainSegmentId;
|
onIndex = minSegmentId;
|
||||||
} else {
|
} else {
|
||||||
onIndex = maxSegmentId+1;
|
onIndex = maxSegmentId;
|
||||||
}
|
}
|
||||||
offIndex = onIndex;
|
offIndex = onIndex;
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void autoPowerOff() {
|
void autoPowerOff() {
|
||||||
if (on && ((millis() - lastSwitchTime) > on_time_ms)) {
|
if ((millis() - lastSwitchTime) > on_time_ms) {
|
||||||
// if sensors are still on, do nothing
|
// if sensors are still on, do nothing
|
||||||
if (bottomSensorState || topSensorState) return;
|
if (bottomSensorState || topSensorState) return;
|
||||||
|
|
||||||
@@ -239,10 +239,12 @@ class Animated_Staircase : public Usermod {
|
|||||||
if ((millis() - lastTime) > segment_delay_ms) {
|
if ((millis() - lastTime) > segment_delay_ms) {
|
||||||
lastTime = millis();
|
lastTime = millis();
|
||||||
|
|
||||||
|
byte oldOn = onIndex;
|
||||||
|
byte oldOff = offIndex;
|
||||||
if (on) {
|
if (on) {
|
||||||
// Turn on all segments
|
// Turn on all segments
|
||||||
onIndex = MAX(mainSegmentId, onIndex - 1);
|
onIndex = MAX(minSegmentId, onIndex - 1);
|
||||||
offIndex = MIN(maxSegmentId + 1, offIndex + 1);
|
offIndex = MIN(maxSegmentId, offIndex + 1);
|
||||||
} else {
|
} else {
|
||||||
if (swipe == SWIPE_UP) {
|
if (swipe == SWIPE_UP) {
|
||||||
onIndex = MIN(offIndex, onIndex + 1);
|
onIndex = MIN(offIndex, onIndex + 1);
|
||||||
@@ -250,11 +252,14 @@ class Animated_Staircase : public Usermod {
|
|||||||
offIndex = MAX(onIndex, offIndex - 1);
|
offIndex = MAX(onIndex, offIndex - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSegments();
|
if (oldOn != onIndex || oldOff != offIndex) {
|
||||||
|
updateSegments(); // reduce the number of updates to necessary ones
|
||||||
|
if (togglePower && onIndex == offIndex && !offMode && !on) toggleOnOff(); // toggle power off for all segments off
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sesnor values to JSON API
|
// send sensor values to JSON API
|
||||||
void writeSensorsToJson(JsonObject& staircase) {
|
void writeSensorsToJson(JsonObject& staircase) {
|
||||||
staircase[F("top-sensor")] = topSensorRead;
|
staircase[F("top-sensor")] = topSensorRead;
|
||||||
staircase[F("bottom-sensor")] = bottomSensorRead;
|
staircase[F("bottom-sensor")] = bottomSensorRead;
|
||||||
@@ -288,16 +293,23 @@ class Animated_Staircase : public Usermod {
|
|||||||
pinMode(topPIRorTriggerPin, OUTPUT);
|
pinMode(topPIRorTriggerPin, OUTPUT);
|
||||||
pinMode(topEchoPin, INPUT);
|
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
|
||||||
|
transitionDelay = segment_delay_ms;
|
||||||
|
strip.setTransition(segment_delay_ms);
|
||||||
|
strip.trigger();
|
||||||
} else {
|
} else {
|
||||||
|
if (togglePower && !on && offMode) toggleOnOff(); // toggle power on if off
|
||||||
// Restore segment options
|
// Restore segment options
|
||||||
WS2812FX::Segment* segments = strip.getSegments();
|
for (int i = 0; i <= strip.getLastActiveSegmentId(); i++) {
|
||||||
for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) {
|
Segment &seg = strip.getSegment(i);
|
||||||
if (!segments->isActive()) {
|
if (!seg.isActive()) continue; // skip vector gaps
|
||||||
maxSegmentId = i - 1;
|
seg.setOption(SEG_OPTION_ON, true);
|
||||||
break;
|
|
||||||
}
|
|
||||||
segments->setOption(SEG_OPTION_ON, 1, i);
|
|
||||||
}
|
}
|
||||||
|
strip.trigger(); // force strip update
|
||||||
|
stateChanged = true; // inform external devices/UI of change
|
||||||
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
colorUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||||
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
DEBUG_PRINTLN(F("Animated Staircase disabled."));
|
||||||
}
|
}
|
||||||
@@ -333,13 +345,16 @@ class Animated_Staircase : public Usermod {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (!enabled || strip.isUpdating()) return;
|
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();
|
checkSensors();
|
||||||
autoPowerOff();
|
if (on) autoPowerOff();
|
||||||
updateSwipe();
|
updateSwipe();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
uint16_t getId() { return USERMOD_ID_ANIMATED_STAIRCASE; }
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
/**
|
/**
|
||||||
* handling of MQTT message
|
* handling of MQTT message
|
||||||
* topic only contains stripped topic (part after /wled/MAC)
|
* topic only contains stripped topic (part after /wled/MAC)
|
||||||
@@ -377,6 +392,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
mqtt->subscribe(subuf, 0);
|
mqtt->subscribe(subuf, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void addToJsonState(JsonObject& root) {
|
void addToJsonState(JsonObject& root) {
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
@@ -393,19 +409,29 @@ class Animated_Staircase : public Usermod {
|
|||||||
*/
|
*/
|
||||||
void readFromJsonState(JsonObject& root) {
|
void readFromJsonState(JsonObject& root) {
|
||||||
if (!initDone) return; // prevent crash on boot applyPreset()
|
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
|
bool en = enabled;
|
||||||
JsonObject staircase = root[FPSTR(_name)];
|
JsonObject staircase = root[FPSTR(_name)];
|
||||||
if (!staircase.isNull()) {
|
if (!staircase.isNull()) {
|
||||||
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
if (staircase[FPSTR(_enabled)].is<bool>()) {
|
||||||
enabled = staircase[FPSTR(_enabled)].as<bool>();
|
en = staircase[FPSTR(_enabled)].as<bool>();
|
||||||
} else {
|
} else {
|
||||||
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
|
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);
|
readSensorsFromJson(staircase);
|
||||||
DEBUG_PRINTLN(F("Staircase sensor state read from API."));
|
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.
|
* Writes the configuration to internal flash memory.
|
||||||
*/
|
*/
|
||||||
@@ -425,6 +451,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
|
staircase[FPSTR(_bottomEcho_pin)] = useUSSensorBottom ? bottomEchoPin : -1;
|
||||||
staircase[FPSTR(_topEchoCm)] = topMaxDist;
|
staircase[FPSTR(_topEchoCm)] = topMaxDist;
|
||||||
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
|
staircase[FPSTR(_bottomEchoCm)] = bottomMaxDist;
|
||||||
|
staircase[FPSTR(_togglePower)] = togglePower;
|
||||||
DEBUG_PRINTLN(F("Staircase config saved."));
|
DEBUG_PRINTLN(F("Staircase config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,10 +492,12 @@ class Animated_Staircase : public Usermod {
|
|||||||
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
|
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
|
||||||
|
|
||||||
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
|
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
|
||||||
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected)
|
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
|
||||||
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
|
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
|
||||||
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
|
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
|
||||||
|
|
||||||
|
togglePower = top[FPSTR(_togglePower)] | togglePower; // staircase toggles power on/off
|
||||||
|
|
||||||
DEBUG_PRINT(FPSTR(_name));
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
if (!initDone) {
|
if (!initDone) {
|
||||||
// first run: reading from cfg.json
|
// first run: reading from cfg.json
|
||||||
@@ -492,7 +521,7 @@ class Animated_Staircase : public Usermod {
|
|||||||
if (changed) setup();
|
if (changed) setup();
|
||||||
}
|
}
|
||||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||||
return true;
|
return !top[FPSTR(_togglePower)].isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -500,22 +529,22 @@ class Animated_Staircase : public Usermod {
|
|||||||
* tab of the web-UI.
|
* tab of the web-UI.
|
||||||
*/
|
*/
|
||||||
void addToJsonInfo(JsonObject& root) {
|
void addToJsonInfo(JsonObject& root) {
|
||||||
JsonObject staircase = root["u"];
|
JsonObject user = root["u"];
|
||||||
if (staircase.isNull()) {
|
if (user.isNull()) {
|
||||||
staircase = root.createNestedObject("u");
|
user = root.createNestedObject("u");
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase")); // name
|
JsonArray infoArr = user.createNestedArray(FPSTR(_name)); // name
|
||||||
String btn = F("<button class=\"btn infobtn\" onclick=\"requestJson({staircase:{enabled:");
|
|
||||||
if (enabled) {
|
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||||
btn += F("false}});\">");
|
uiDomString += FPSTR(_name);
|
||||||
btn += F("enabled");
|
uiDomString += F(":{");
|
||||||
} else {
|
uiDomString += FPSTR(_enabled);
|
||||||
btn += F("true}});\">");
|
uiDomString += enabled ? F(":false}});\">") : F(":true}});\">");
|
||||||
btn += F("disabled");
|
uiDomString += F("<i class=\"icons ");
|
||||||
}
|
uiDomString += enabled ? "on" : "off";
|
||||||
btn += F("</button>");
|
uiDomString += F("\"></i></button>");
|
||||||
usermodEnabled.add(btn); // value
|
infoArr.add(uiDomString);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -532,3 +561,4 @@ const char Animated_Staircase::_bottomPIRorTrigger_pin[] PROGMEM = "bottomPIR
|
|||||||
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
|
const char Animated_Staircase::_bottomEcho_pin[] PROGMEM = "bottomEcho_pin";
|
||||||
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
|
const char Animated_Staircase::_topEchoCm[] PROGMEM = "top-dist-cm";
|
||||||
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
|
const char Animated_Staircase::_bottomEchoCm[] PROGMEM = "bottom-dist-cm";
|
||||||
|
const char Animated_Staircase::_togglePower[] PROGMEM = "toggle-on-off";
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# Usermod Animated Staircase
|
# 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:
|
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.
|
- 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
|
- 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
|
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
|
## 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).
|
To include this usermod in your WLED setup, you have to be able to [compile WLED from source](https://kno.wled.ge/advanced/compiling-wled/).
|
||||||
|
|
||||||
Before compiling, you have to make the following modifications:
|
Before compiling, you have to make the following modifications:
|
||||||
|
|
||||||
@@ -20,17 +20,16 @@ Edit `usermods_list.cpp`:
|
|||||||
2. add `#include "../usermods/Animated_Staircase/Animated_Staircase.h"` to the top of the file
|
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.
|
3. add `usermods.add(new Animated_Staircase());` to the end of the `void registerUsermods()` function.
|
||||||
|
|
||||||
You can configure usermod using Usermods settings page.
|
You can configure usermod using the Usermods settings page.
|
||||||
Please enter GPIO pins for PIR sensors or ultrasonic sensor (trigger and echo).
|
Please enter GPIO pins for PIR or ultrasonic sensors (trigger and echo).
|
||||||
If you use PIR sensor enter -1 for echo pin.
|
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
|
## Hardware installation
|
||||||
1. Stick the LED strip under each step of the 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
|
2. Connect the ESP8266 pin D4 or ESP32 pin D2 to the first LED data pin at the bottom step.
|
||||||
of your stairs.
|
|
||||||
3. Connect the data-out pin at the end of each strip per step to the data-in pin on the
|
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.
|
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
|
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
|
step from one side, using at least AWG14 or 2.5mm^2 cable. Don't connect them serial as you
|
||||||
@@ -39,7 +38,7 @@ Maximum distance for ultrasonic sensor can be configured as a time needed for ec
|
|||||||
You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor.
|
You _may_ need to use 10k pull-down resistors on the selected PIR pins, depending on the sensor.
|
||||||
|
|
||||||
## WLED configuration
|
## WLED configuration
|
||||||
1. In the WLED UI, confgure a segment for each step. The lowest step of the stairs is the
|
1. In the WLED UI, configure a segment for each step. The lowest step of the stairs is the
|
||||||
lowest segment id.
|
lowest segment id.
|
||||||
2. Save your segments into a preset.
|
2. Save your segments into a preset.
|
||||||
3. Ideally, add the preset in the config > LED setup menu to the "apply
|
3. Ideally, add the preset in the config > LED setup menu to the "apply
|
||||||
@@ -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
|
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.
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -70,14 +69,14 @@ The staircase settings and sensor states are inside the WLED status element:
|
|||||||
"staircase": {
|
"staircase": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"bottom-sensor": false,
|
"bottom-sensor": false,
|
||||||
"tops-ensor": false
|
"top-sensor": false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Enable/disable the usermod
|
### Enable/disable the usermod
|
||||||
By disabling the usermod you will be able to keep the LED's on, independent from the sensor
|
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:
|
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.
|
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
|
### 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, including 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.
|
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
|
**Please note:** 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
|
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.
|
a less responsive web interface. It is therefore advised to keep the detection distance as short as possible.
|
||||||
|
|
||||||
### Animation triggering through the API
|
### Animation triggering through the API
|
||||||
Instead of stairs activation by one of the sensors, you can also trigger the animation through
|
In addition to activation by one of the stair sensors, you can also trigger the animation manually
|
||||||
the API. To simulate triggering the bottom sensor, use:
|
via the API. To simulate triggering the bottom sensor, use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST -H "Content-Type: application/json" \
|
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
|
xxx.xxx.xxx.xxx/json/state
|
||||||
```
|
```
|
||||||
|
|
||||||
Likewise, to trigger the top sensor, use:
|
Likewise, to trigger the top sensor:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST -H "Content-Type: application/json" \
|
curl -X POST -H "Content-Type: application/json" \
|
||||||
@@ -119,7 +118,7 @@ curl -X POST -H "Content-Type: application/json" \
|
|||||||
```
|
```
|
||||||
**MQTT**
|
**MQTT**
|
||||||
You can publish a message with either `up` or `down` on topic `/swipe` to trigger animation.
|
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/>
|
Have fun with this usermod.<br/>
|
||||||
www.rolfje.com
|
www.rolfje.com
|
||||||
|
|||||||
@@ -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
|
# BH1750 usermod
|
||||||
|
|
||||||
This usermod will read from an ambient light sensor like the BH1750 sensor.
|
This usermod will read from an ambient light sensor like the BH1750.
|
||||||
The luminance is displayed both in the Info section of the web UI as well as published to the `/luminance` MQTT topic if enabled.
|
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`.
|
## Compilation
|
||||||
|
|
||||||
### 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
|
### 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_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_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
|
* `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
|
## API
|
||||||
|
The following method is available to interact with the usermod from other code modules:
|
||||||
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`.
|
- `getIlluminance` read the brightness from the sensor
|
||||||
|
|
||||||
## Change Log
|
## 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,7 +1,13 @@
|
|||||||
|
// force the compiler to show a warning to confirm that this file is included
|
||||||
|
#warning **** Included USERMOD_BH1750 ****
|
||||||
|
|
||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include <Wire.h>
|
|
||||||
#include <BH1750.h>
|
#include <BH1750.h>
|
||||||
|
|
||||||
// the max frequency to check photoresistor, 10 seconds
|
// the max frequency to check photoresistor, 10 seconds
|
||||||
@@ -19,7 +25,7 @@
|
|||||||
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
|
#define USERMOD_BH1750_FIRST_MEASUREMENT_AT 10000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// only report if differance grater than offset value
|
// only report if difference grater than offset value
|
||||||
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
#ifndef USERMOD_BH1750_OFFSET_VALUE
|
||||||
#define USERMOD_BH1750_OFFSET_VALUE 1
|
#define USERMOD_BH1750_OFFSET_VALUE 1
|
||||||
#endif
|
#endif
|
||||||
@@ -39,7 +45,7 @@ private:
|
|||||||
bool getLuminanceComplete = false;
|
bool getLuminanceComplete = false;
|
||||||
|
|
||||||
// flag set at startup
|
// flag set at startup
|
||||||
bool disabled = false;
|
bool enabled = true;
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
static const char _name[];
|
static const char _name[];
|
||||||
@@ -47,6 +53,15 @@ private:
|
|||||||
static const char _maxReadInterval[];
|
static const char _maxReadInterval[];
|
||||||
static const char _minReadInterval[];
|
static const char _minReadInterval[];
|
||||||
static const char _offset[];
|
static const char _offset[];
|
||||||
|
static const char _HomeAssistantDiscovery[];
|
||||||
|
|
||||||
|
bool initDone = false;
|
||||||
|
bool sensorFound = false;
|
||||||
|
|
||||||
|
// Home Assistant and MQTT
|
||||||
|
String mqttLuminanceTopic;
|
||||||
|
bool mqttInitialized = false;
|
||||||
|
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages
|
||||||
|
|
||||||
BH1750 lightMeter;
|
BH1750 lightMeter;
|
||||||
float lastLux = -1000;
|
float lastLux = -1000;
|
||||||
@@ -56,16 +71,56 @@ private:
|
|||||||
return isnan(prevValue) || newValue <= prevValue - maxDiff || newValue >= prevValue + maxDiff || (newValue == 0.0 && prevValue > 0.0);
|
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) + " " + 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_BRAND);
|
||||||
|
device[F("model")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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:
|
public:
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
Wire.begin();
|
if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; }
|
||||||
lightMeter.begin();
|
sensorFound = lightMeter.begin();
|
||||||
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (disabled || strip.isUpdating())
|
if ((!enabled) || strip.isUpdating())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
@@ -88,18 +143,27 @@ public:
|
|||||||
{
|
{
|
||||||
lastLux = lux;
|
lastLux = lux;
|
||||||
lastSend = millis();
|
lastSend = millis();
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
if (WLED_MQTT_CONNECTED)
|
if (WLED_MQTT_CONNECTED)
|
||||||
{
|
{
|
||||||
char subuf[45];
|
if (!mqttInitialized)
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
{
|
||||||
strcat_P(subuf, PSTR("/luminance"));
|
_mqttInitialize();
|
||||||
mqtt->publish(subuf, 0, true, String(lux).c_str());
|
mqttInitialized = true;
|
||||||
|
}
|
||||||
|
mqtt->publish(mqttLuminanceTopic.c_str(), 0, true, String(lux).c_str());
|
||||||
|
DEBUG_PRINTLN(F("Brightness: ") + String(lux) + F("lx"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEBUG_PRINTLN("Missing MQTT connection. Not publishing data");
|
DEBUG_PRINTLN(F("Missing MQTT connection. Not publishing data"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float getIlluminance() {
|
||||||
|
return (float)lastLux;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addToJsonInfo(JsonObject &root)
|
void addToJsonInfo(JsonObject &root)
|
||||||
@@ -109,43 +173,39 @@ public:
|
|||||||
user = root.createNestedObject(F("u"));
|
user = root.createNestedObject(F("u"));
|
||||||
|
|
||||||
JsonArray lux_json = user.createNestedArray(F("Luminance"));
|
JsonArray lux_json = user.createNestedArray(F("Luminance"));
|
||||||
|
if (!enabled) {
|
||||||
if (!getLuminanceComplete)
|
lux_json.add(F("disabled"));
|
||||||
{
|
} else 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
|
// if we haven't read the sensor yet, let the user know
|
||||||
// that we are still waiting for the first measurement
|
// that we are still waiting for the first measurement
|
||||||
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
lux_json.add((USERMOD_BH1750_FIRST_MEASUREMENT_AT - millis()) / 1000);
|
||||||
lux_json.add(F(" sec until read"));
|
lux_json.add(F(" sec until read"));
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
lux_json.add(lastLux);
|
lux_json.add(lastLux);
|
||||||
lux_json.add(F(" lx"));
|
lux_json.add(F(" lx"));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getId()
|
|
||||||
{
|
|
||||||
return USERMOD_ID_BH1750;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// (called from set.cpp) stores persistent properties to cfg.json
|
||||||
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
|
||||||
*/
|
|
||||||
void addToConfig(JsonObject &root)
|
void addToConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
// we add JSON object.
|
// we add JSON object.
|
||||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
||||||
top[FPSTR(_enabled)] = !disabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||||
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||||
|
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
|
||||||
top[FPSTR(_offset)] = offset;
|
top[FPSTR(_offset)] = offset;
|
||||||
|
|
||||||
DEBUG_PRINTLN(F("Photoresistor config saved."));
|
DEBUG_PRINTLN(F("BH1750 config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// called before setup() to populate properties from values stored in cfg.json
|
||||||
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
|
||||||
*/
|
|
||||||
bool readFromConfig(JsonObject &root)
|
bool readFromConfig(JsonObject &root)
|
||||||
{
|
{
|
||||||
// we look for JSON object.
|
// we look for JSON object.
|
||||||
@@ -153,20 +213,34 @@ public:
|
|||||||
if (top.isNull())
|
if (top.isNull())
|
||||||
{
|
{
|
||||||
DEBUG_PRINT(FPSTR(_name));
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
|
DEBUG_PRINT(F("BH1750"));
|
||||||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||||
return false;
|
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);
|
||||||
|
|
||||||
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_PRINT(FPSTR(_name));
|
||||||
|
if (!initDone) {
|
||||||
|
DEBUG_PRINTLN(F(" config loaded."));
|
||||||
|
} else {
|
||||||
DEBUG_PRINTLN(F(" config (re)loaded."));
|
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||||
|
|
||||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_BH1750;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
@@ -174,4 +248,5 @@ const char Usermod_BH1750::_name[] PROGMEM = "BH1750";
|
|||||||
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
|
const char Usermod_BH1750::_enabled[] PROGMEM = "enabled";
|
||||||
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
const char Usermod_BH1750::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
||||||
const char Usermod_BH1750::_minReadInterval[] PROGMEM = "min-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";
|
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,36 +1,61 @@
|
|||||||
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`).
|
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:
|
||||||
- Data is published over MQTT so make sure you've enabled the MQTT sync interface.
|
- The i2c address in decimal. Set it to either 118 (0x76, the default) or 119 (0x77).
|
||||||
- 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!
|
- 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
|
```ini
|
||||||
|
[env:usermod_bme280_d1_mini]
|
||||||
|
extends = env:d1_mini
|
||||||
build_flags =
|
build_flags =
|
||||||
${common.build_flags_esp8266}
|
${common.build_flags_esp8266}
|
||||||
-D USERMOD_BME280
|
-D USERMOD_BME280
|
||||||
```
|
lib_deps =
|
||||||
or define `USERMOD_BME280` in `my_config.h`
|
${esp8266.lib_deps}
|
||||||
```c++
|
BME280@~3.0.0
|
||||||
#define USERMOD_BME280
|
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
|
||||||
|
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
|
||||||
MQTT topics are as follows:
|
|
||||||
Measurement type | MQTT topic
|
Measurement type | MQTT topic
|
||||||
--- | ---
|
--- | ---
|
||||||
Temperature | `<deviceTopic>/temperature`
|
Temperature | `<deviceTopic>/temperature`
|
||||||
@@ -38,3 +63,29 @@ Humidity | `<deviceTopic>/humidity`
|
|||||||
Pressure | `<deviceTopic>/pressure`
|
Pressure | `<deviceTopic>/pressure`
|
||||||
Heat index | `<deviceTopic>/heat_index`
|
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,66 +1,42 @@
|
|||||||
|
// force the compiler to show a warning to confirm that this file is included
|
||||||
|
#warning **** Included USERMOD_BME280 version 2.0 ****
|
||||||
|
|
||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
|
||||||
#include <BME280I2C.h> // BME280 sensor
|
#include <BME280I2C.h> // BME280 sensor
|
||||||
#include <EnvironmentCalculations.h> // BME280 extended measurements
|
#include <EnvironmentCalculations.h> // BME280 extended measurements
|
||||||
|
|
||||||
class UsermodBME280 : public Usermod
|
class UsermodBME280 : public Usermod
|
||||||
{
|
{
|
||||||
private:
|
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
|
|
||||||
|
|
||||||
// Sanity checks
|
// NOTE: Do not implement any compile-time variables, anything the user needs to configure
|
||||||
#if !defined(TemperatureDecimals) || TemperatureDecimals < 0
|
// should be configurable from the Usermod menu using the methods below
|
||||||
#define TemperatureDecimals 0
|
// key settings set via usermod menu
|
||||||
#endif
|
uint8_t TemperatureDecimals = 0; // Number of decimal places in published temperaure values
|
||||||
#if !defined(HumidityDecimals) || HumidityDecimals < 0
|
uint8_t HumidityDecimals = 0; // Number of decimal places in published humidity values
|
||||||
#define HumidityDecimals 0
|
uint8_t PressureDecimals = 0; // Number of decimal places in published pressure values
|
||||||
#endif
|
uint16_t TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds
|
||||||
#if !defined(PressureDecimals) || PressureDecimals < 0
|
uint16_t PressureInterval = 300; // Interval to measure pressure in seconds
|
||||||
#define PressureDecimals 0
|
BME280I2C::I2CAddr I2CAddress = BME280I2C::I2CAddr_0x76; // i2c address, defaults to 0x76
|
||||||
#endif
|
bool PublishAlways = false; // Publish values even when they have not changed
|
||||||
#if !defined(TemperatureInterval) || TemperatureInterval < 0
|
bool UseCelsius = true; // Use Celsius for Reporting
|
||||||
#define TemperatureInterval 1
|
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
|
||||||
#endif
|
bool enabled = true;
|
||||||
#if !defined(PressureInterval) || PressureInterval < 0
|
|
||||||
#define PressureInterval TemperatureInterval
|
|
||||||
#endif
|
|
||||||
#if !defined(PublishAlways)
|
|
||||||
#define PublishAlways 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32 // ESP32 boards
|
// set the default pins based on the architecture, these get overridden by Usermod menu settings
|
||||||
uint8_t SCL_PIN = 22;
|
#ifdef ESP8266
|
||||||
uint8_t SDA_PIN = 21;
|
//uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||||
#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
|
#endif
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
// BME280 sensor settings
|
BME280I2C bme;
|
||||||
BME280I2C::Settings settings{
|
|
||||||
BME280::OSR_X16, // Temperature oversampling x16
|
|
||||||
BME280::OSR_X16, // Humidity oversampling x16
|
|
||||||
BME280::OSR_X16, // Pressure oversampling x16
|
|
||||||
// Defaults
|
|
||||||
BME280::Mode_Forced,
|
|
||||||
BME280::StandbyTime_1000ms,
|
|
||||||
BME280::Filter_Off,
|
|
||||||
BME280::SpiEnable_False,
|
|
||||||
BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76
|
|
||||||
};
|
|
||||||
|
|
||||||
BME280I2C bme{settings};
|
|
||||||
|
|
||||||
uint8_t sensorType;
|
uint8_t sensorType;
|
||||||
|
|
||||||
@@ -75,6 +51,7 @@ private:
|
|||||||
float sensorHeatIndex;
|
float sensorHeatIndex;
|
||||||
float sensorDewPoint;
|
float sensorDewPoint;
|
||||||
float sensorPressure;
|
float sensorPressure;
|
||||||
|
String tempScale;
|
||||||
// Track previous sensor values
|
// Track previous sensor values
|
||||||
float lastTemperature;
|
float lastTemperature;
|
||||||
float lastHumidity;
|
float lastHumidity;
|
||||||
@@ -82,20 +59,21 @@ private:
|
|||||||
float lastDewPoint;
|
float lastDewPoint;
|
||||||
float lastPressure;
|
float lastPressure;
|
||||||
|
|
||||||
// Store packet IDs of MQTT publications
|
// MQTT topic strings for publishing Home Assistant discovery topics
|
||||||
uint16_t mqttTemperaturePub = 0;
|
bool mqttInitialized = false;
|
||||||
uint16_t mqttPressurePub = 0;
|
|
||||||
|
|
||||||
|
// 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 Fahrenheit being set in Usermod Menu)
|
||||||
void UpdateBME280Data(int SensorType)
|
void UpdateBME280Data(int SensorType)
|
||||||
{
|
{
|
||||||
float _temperature, _humidity, _pressure;
|
float _temperature, _humidity, _pressure;
|
||||||
#ifdef Celsius
|
|
||||||
|
if (UseCelsius) {
|
||||||
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
|
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
|
||||||
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
EnvironmentCalculations::TempUnit envTempUnit(EnvironmentCalculations::TempUnit_Celsius);
|
||||||
#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);
|
||||||
@@ -103,22 +81,113 @@ private:
|
|||||||
sensorTemperature = _temperature;
|
sensorTemperature = _temperature;
|
||||||
sensorHumidity = _humidity;
|
sensorHumidity = _humidity;
|
||||||
sensorPressure = _pressure;
|
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);
|
||||||
|
BME280::PresUnit presUnit(BME280::PresUnit_hPa);
|
||||||
|
|
||||||
|
bme.read(_pressure, _temperature, _humidity, tempUnit, presUnit);
|
||||||
|
|
||||||
|
sensorTemperature = _temperature;
|
||||||
|
sensorHumidity = _humidity;
|
||||||
|
sensorPressure = _pressure;
|
||||||
|
tempScale = F("°F");
|
||||||
if (sensorType == 1)
|
if (sensorType == 1)
|
||||||
{
|
{
|
||||||
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
sensorHeatIndex = EnvironmentCalculations::HeatIndex(_temperature, _humidity, envTempUnit);
|
||||||
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
sensorDewPoint = EnvironmentCalculations::DewPoint(_temperature, _humidity, envTempUnit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
// Procedure to define all MQTT discovery Topics
|
||||||
void setup()
|
void _mqttInitialize()
|
||||||
{
|
{
|
||||||
Wire.begin(SDA_PIN, SCL_PIN);
|
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_BRAND);
|
||||||
|
device[F("model")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeBmeComms()
|
||||||
|
{
|
||||||
|
BME280I2C::Settings settings{
|
||||||
|
BME280::OSR_X16, // Temperature oversampling x16
|
||||||
|
BME280::OSR_X16, // Humidity oversampling x16
|
||||||
|
BME280::OSR_X16, // Pressure oversampling x16
|
||||||
|
BME280::Mode_Forced,
|
||||||
|
BME280::StandbyTime_1000ms,
|
||||||
|
BME280::Filter_Off,
|
||||||
|
BME280::SpiEnable_False,
|
||||||
|
I2CAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
bme.setSettings(settings);
|
||||||
|
|
||||||
if (!bme.begin())
|
if (!bme.begin())
|
||||||
{
|
{
|
||||||
sensorType = 0;
|
sensorType = 0;
|
||||||
Serial.println("Could not find BME280I2C sensor!");
|
DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -126,69 +195,76 @@ public:
|
|||||||
{
|
{
|
||||||
case BME280::ChipModel_BME280:
|
case BME280::ChipModel_BME280:
|
||||||
sensorType = 1;
|
sensorType = 1;
|
||||||
Serial.println("Found BME280 sensor! Success.");
|
DEBUG_PRINTLN(F("Found BME280 sensor! Success."));
|
||||||
break;
|
break;
|
||||||
case BME280::ChipModel_BMP280:
|
case BME280::ChipModel_BMP280:
|
||||||
sensorType = 2;
|
sensorType = 2;
|
||||||
Serial.println("Found BMP280 sensor! No Humidity available.");
|
DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available."));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sensorType = 0;
|
sensorType = 0;
|
||||||
Serial.println("Found UNKNOWN sensor! Error!");
|
DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
if (i2c_scl<0 || i2c_sda<0) { enabled = false; sensorType = 0; return; }
|
||||||
|
|
||||||
|
initializeBmeComms();
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
// BME280 sensor MQTT publishing
|
// BME280 sensor MQTT publishing
|
||||||
// Check if sensor present and MQTT Connected, otherwise it will crash the MCU
|
// Check if sensor present and Connected, otherwise it will crash the MCU
|
||||||
if (sensorType != 0 && mqtt != nullptr)
|
if (sensorType != 0)
|
||||||
{
|
{
|
||||||
// Timer to fetch new temperature, humidity and pressure data at intervals
|
// Timer to fetch new temperature, humidity and pressure data at intervals
|
||||||
timer = millis();
|
timer = millis();
|
||||||
|
|
||||||
if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000 || mqttTemperaturePub == 0)
|
if (timer - lastTemperatureMeasure >= TemperatureInterval * 1000)
|
||||||
{
|
{
|
||||||
lastTemperatureMeasure = timer;
|
lastTemperatureMeasure = timer;
|
||||||
|
|
||||||
UpdateBME280Data(sensorType);
|
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;
|
float humidity, heatIndex, dewPoint;
|
||||||
|
|
||||||
// If temperature has changed since last measure, create string populated with device topic
|
// 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
|
// from the UI and values read from sensor, then publish to broker
|
||||||
if (temperature != lastTemperature || PublishAlways)
|
if (temperature != lastTemperature || PublishAlways)
|
||||||
{
|
{
|
||||||
String topic = String(mqttDeviceTopic) + "/temperature";
|
publishMqtt("temperature", String(temperature, TemperatureDecimals).c_str());
|
||||||
mqttTemperaturePub = mqtt->publish(topic.c_str(), 0, false, String(temperature, TemperatureDecimals).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTemperature = temperature; // Update last sensor temperature for next loop
|
lastTemperature = temperature; // Update last sensor temperature for next loop
|
||||||
|
|
||||||
if (sensorType == 1) // Only if sensor is a BME280
|
if (sensorType == 1) // Only if sensor is a BME280
|
||||||
{
|
{
|
||||||
humidity = roundf(sensorHumidity * pow(10, HumidityDecimals)) / pow(10, HumidityDecimals);
|
humidity = roundf(sensorHumidity * powf(10, HumidityDecimals)) / powf(10, HumidityDecimals);
|
||||||
heatIndex = roundf(sensorHeatIndex * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
heatIndex = roundf(sensorHeatIndex * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||||
dewPoint = roundf(sensorDewPoint * pow(10, TemperatureDecimals)) / pow(10, TemperatureDecimals);
|
dewPoint = roundf(sensorDewPoint * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals);
|
||||||
|
|
||||||
if (humidity != lastHumidity || PublishAlways)
|
if (humidity != lastHumidity || PublishAlways)
|
||||||
{
|
{
|
||||||
String topic = String(mqttDeviceTopic) + "/humidity";
|
publishMqtt("humidity", String(humidity, HumidityDecimals).c_str());
|
||||||
mqtt->publish(topic.c_str(), 0, false, String(humidity, HumidityDecimals).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heatIndex != lastHeatIndex || PublishAlways)
|
if (heatIndex != lastHeatIndex || PublishAlways)
|
||||||
{
|
{
|
||||||
String topic = String(mqttDeviceTopic) + "/heat_index";
|
publishMqtt("heat_index", String(heatIndex, TemperatureDecimals).c_str());
|
||||||
mqtt->publish(topic.c_str(), 0, false, String(heatIndex, TemperatureDecimals).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dewPoint != lastDewPoint || PublishAlways)
|
if (dewPoint != lastDewPoint || PublishAlways)
|
||||||
{
|
{
|
||||||
String topic = String(mqttDeviceTopic) + "/dew_point";
|
publishMqtt("dew_point", String(dewPoint, TemperatureDecimals).c_str());
|
||||||
mqtt->publish(topic.c_str(), 0, false, String(dewPoint, TemperatureDecimals).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHumidity = humidity;
|
lastHumidity = humidity;
|
||||||
@@ -197,20 +273,209 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timer - lastPressureMeasure >= PressureInterval * 1000 || mqttPressurePub == 0)
|
if (timer - lastPressureMeasure >= PressureInterval * 1000)
|
||||||
{
|
{
|
||||||
lastPressureMeasure = timer;
|
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)
|
if (pressure != lastPressure || PublishAlways)
|
||||||
{
|
{
|
||||||
String topic = String(mqttDeviceTopic) + "/pressure";
|
publishMqtt("pressure", String(pressure, PressureDecimals).c_str());
|
||||||
mqttPressurePub = mqtt->publish(topic.c_str(), 0, true, String(pressure, PressureDecimals).c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPressure = pressure;
|
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)) / powf(10, TemperatureDecimals));
|
||||||
|
temperature_json.add(tempScale);
|
||||||
|
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / 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)) / powf(10, HumidityDecimals));
|
||||||
|
humidity_json.add(F("%"));
|
||||||
|
pressure_json.add(roundf(sensorPressure * powf(10, PressureDecimals)) / 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("I2CAddress")] = static_cast<uint8_t>(I2CAddress);
|
||||||
|
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;
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
uint8_t tmpI2cAddress;
|
||||||
|
configComplete &= getJsonValue(top[F("I2CAddress")], tmpI2cAddress, 0x76);
|
||||||
|
I2CAddress = static_cast<BME280I2C::I2CAddr>(tmpI2cAddress);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
|
if (!initDone) {
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
DEBUG_PRINTLN(F(" config loaded."));
|
||||||
|
} else {
|
||||||
|
// changing parameters from settings page
|
||||||
|
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||||
|
|
||||||
|
// Reset all known values
|
||||||
|
sensorType = 0;
|
||||||
|
sensorTemperature = 0;
|
||||||
|
sensorHumidity = 0;
|
||||||
|
sensorHeatIndex = 0;
|
||||||
|
sensorDewPoint = 0;
|
||||||
|
sensorPressure = 0;
|
||||||
|
lastTemperature = 0;
|
||||||
|
lastHumidity = 0;
|
||||||
|
lastHeatIndex = 0;
|
||||||
|
lastDewPoint = 0;
|
||||||
|
lastPressure = 0;
|
||||||
|
|
||||||
|
initializeBmeComms();
|
||||||
|
}
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId() {
|
||||||
|
return USERMOD_ID_BME280;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char UsermodBME280::_name[] PROGMEM = "BME280/BMP280";
|
||||||
|
const char UsermodBME280::_enabled[] PROGMEM = "enabled";
|
||||||
|
|||||||
BIN
usermods/BME68X_v2/BME680.pdf
Normal file
152
usermods/BME68X_v2/README.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Usermod BME68X
|
||||||
|
This usermod was developed for a BME680/BME68X sensor. The BME68X is not compatible with the BME280/BMP280 chip. It has its own library. The original 'BSEC Software Library' from Bosch was used to develop the code. The measured values are displayed on the WLED info page.
|
||||||
|
|
||||||
|
<p align="center"><img src="pics/pic1.png" style="width:60%;"></p>
|
||||||
|
|
||||||
|
In addition, the values are published on MQTT if this is active. The topic used for this is: 'wled/[MQTT Client ID]'. The Client ID is set in the WLED MQTT settings.
|
||||||
|
<p align="center"><img src="pics/pic2.png"></p>
|
||||||
|
|
||||||
|
If you use HomeAssistance discovery, the device tree for HomeAssistance is created. This is published under the topic 'homeassistant/sensor/[MQTT Client ID]' via MQTT.
|
||||||
|
<p align="center"><img src="pics/pic3.png"></p>
|
||||||
|
|
||||||
|
A device with the following sensors appears in HomeAssistant. Please note that MQTT must be activated in HomeAssistant.
|
||||||
|
<p align="center"><img src="pics/pic4.png" style="width:60%;"></p>
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
Raw sensor types
|
||||||
|
|
||||||
|
Sensor Accuracy Scale Range
|
||||||
|
--------------------------------------------------------------------------------------------------
|
||||||
|
Temperature +/- 1.0 °C/°F -40 to 85 °C
|
||||||
|
Humidity +/- 3 % 0 to 100 %
|
||||||
|
Pressure +/- 1 hPa 300 to 1100 hPa
|
||||||
|
Gas Resistance Ohm
|
||||||
|
|
||||||
|
The BSEC Library calculates the following values via the gas resistance
|
||||||
|
|
||||||
|
Sensor Accuracy Scale Range
|
||||||
|
--------------------------------------------------------------------------------------------------
|
||||||
|
IAQ value between 0 and 500
|
||||||
|
Static IAQ same as IAQ but for permanently installed devices
|
||||||
|
CO2 PPM
|
||||||
|
VOC PPM
|
||||||
|
Gas-Percentage %
|
||||||
|
|
||||||
|
|
||||||
|
In addition the usermod calculates
|
||||||
|
|
||||||
|
Sensor Accuracy Scale Range
|
||||||
|
--------------------------------------------------------------------------------------------------
|
||||||
|
Absolute humidity g/m³
|
||||||
|
Dew point °C/°F
|
||||||
|
|
||||||
|
### IAQ (Indoor Air Quality)
|
||||||
|
The IAQ is divided into the following value groups.
|
||||||
|
<p align="center"><img src="pics/pic5.png"></p>
|
||||||
|
|
||||||
|
For more detailed information, please consult the enclosed Bosch product description (BME680.pdf).
|
||||||
|
|
||||||
|
|
||||||
|
## Calibration of the device
|
||||||
|
|
||||||
|
The gas sensor of the BME68X must be calibrated. This differs from the BME280, which does not require any calibration.
|
||||||
|
There is a range of additional information for this, which the driver also provides. These values can be found in HomeAssistant under Diagnostics.
|
||||||
|
|
||||||
|
- **STABILIZATION_STATUS**: Gas sensor stabilization status [boolean] Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1).
|
||||||
|
- **RUN_IN_STATUS**: Gas sensor run-in status [boolean] Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization is finished (1)
|
||||||
|
|
||||||
|
Furthermore, all GAS based values have their own accuracy value. These have the following meaning:
|
||||||
|
|
||||||
|
- **Accuracy = 0** means the sensor is being stabilized (this can take a while on the first run)
|
||||||
|
- **Accuracy = 1** means that the previous measured values show too few differences and cannot be used for calibration. If the sensor is at accuracy 1 for too long, you must ensure that the ambient air is chaning. Opening the windows is fine. Or sometimes it is sufficient to breathe on the sensor for approx. 5 minutes.
|
||||||
|
- **Accuracy = 2** means the sensor is currently calibrating.
|
||||||
|
- **Accuracy = 3** means that the sensor has been successfully calibrated. Once accuracy 3 is reached, the calibration data is automatically written to the file system. This calibration data will be used again at the next start and will speed up the calibration.
|
||||||
|
|
||||||
|
The IAQ index is therefore only meaningful if IAQ Accuracy = 3. In addition to the value for IAQ, BSEC also provides us with CO2 and VOC equivalent values. When using the sensor, the calibration value should also always be read out and displayed or transmitted.
|
||||||
|
|
||||||
|
Reasonably reliable values are therefore only achieved when accuracy displays the value 3.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
The settings of the usermods are set in the usermod section of wled.
|
||||||
|
<p align="center"><img src="pics/pic6.png"></p>
|
||||||
|
|
||||||
|
The possible settings are
|
||||||
|
|
||||||
|
- **Enable:** Enables / disables the usermod
|
||||||
|
- **I2C address:** I2C address of the sensor. You can choose between 0X77 & 0X76. The default is 0x77.
|
||||||
|
- **Interval:** Specifies the interval of seconds at which the usermod should be executed. The default is every second.
|
||||||
|
- **Pub Chages Only:** If this item is active, the values are only published if they have changed since the last publication.
|
||||||
|
- **Pub Accuracy:** The Accuracy values associated with the gas values are also published.
|
||||||
|
- **Pub Calib State:** If this item is active, STABILIZATION_STATUS& RUN_IN_STATUS are also published.
|
||||||
|
- **Temp Scale:** Here you can choose between °C and °F.
|
||||||
|
- **Temp Offset:** The temperature offset is always set in °C. It must be converted for Fahrenheit.
|
||||||
|
- **HA Discovery:** If this item is active, the HomeAssistant sensor tree is created.
|
||||||
|
- **Pause While WLED Active:** If WLED has many LEDs to calculate, the computing power may no longer be sufficient to calculate the LEDs and read the sensor data. The LEDs then hang for a few microseconds, which can be seen. If this point is active, no sensor data is fetched as long as WLED is running.
|
||||||
|
- **Del Calibration Hist:** If a check mark is set here, the calibration file saved in the file system is deleted when the settings are saved.
|
||||||
|
|
||||||
|
### Sensors
|
||||||
|
Applies to all sensors. The number of decimal places is set here. If the sensor is set to -1, it will no longer be published. In addition, the IAQ values can be activated here in verbal form.
|
||||||
|
|
||||||
|
It is recommended to use the Static IAQ for the IAQ values. This is recommended by Bosch for statically placed devices.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Data is published over MQTT - make sure you've enabled the MQTT sync interface.
|
||||||
|
|
||||||
|
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.
|
||||||
|
- getTemperature(); The scale °C/°F is depended to the settings
|
||||||
|
- getHumidity();
|
||||||
|
- getPressure();
|
||||||
|
- getGasResistance();
|
||||||
|
- getAbsoluteHumidity();
|
||||||
|
- getDewPoint(); The scale °C/°F is depended to the settings
|
||||||
|
- getIaq();
|
||||||
|
- getStaticIaq();
|
||||||
|
- getCo2();
|
||||||
|
- getVoc();
|
||||||
|
- getGasPerc();
|
||||||
|
- getIaqAccuracy();
|
||||||
|
- getStaticIaqAccuracy();
|
||||||
|
- getCo2Accuracy();
|
||||||
|
- getVocAccuracy();
|
||||||
|
- getGasPercAccuracy();
|
||||||
|
- getStabStatus();
|
||||||
|
- getRunInStatus();
|
||||||
|
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
To enable, compile with `USERMOD_BME68X` defined (e.g. in `platformio_override.ini`) and add the `BSEC Software Library` to the lib_deps.
|
||||||
|
|
||||||
|
```
|
||||||
|
[env:esp32-BME680]
|
||||||
|
board = esp32dev
|
||||||
|
platform = ${esp32.platform}
|
||||||
|
platform_packages = ${esp32.platform_packages}
|
||||||
|
lib_deps = ${esp32.lib_deps}
|
||||||
|
boschsensortec/BSEC Software Library @ ^1.8.1492 ; USERMOD: BME680
|
||||||
|
build_unflags = ${common.build_unflags}
|
||||||
|
build_flags = ${common.build_flags_esp32}
|
||||||
|
-D USERMOD_BME68X ; USERMOD: BME680
|
||||||
|
```
|
||||||
|
|
||||||
|
## Revision History
|
||||||
|
### Version 1.0.0
|
||||||
|
- First version of the BME68X_v user module
|
||||||
|
### Version 1.0.1
|
||||||
|
- Rebased to WELD Version 0.15
|
||||||
|
- Reworked some default settings
|
||||||
|
- A problem with the default settings has been fixed
|
||||||
|
|
||||||
|
## Known problems
|
||||||
|
- MQTT goes online at device start. Shortly afterwards it goes offline and takes quite a while until it goes online again. The problem does not come from this user module, but from the WLED core.
|
||||||
|
- If you save the settings often, WLED can get stuck.
|
||||||
|
- If many LEDS are connected to WLED, reading the sensor can cause a small but noticeable hang. The "Pause While WLED Active" option was introduced as a workaround.
|
||||||
|
|
||||||
|
<div><img src="pics/GeoGab.svg" width="20%"/> </div>
|
||||||
|
Gabriel Sieben (gsieben@geogab.net)
|
||||||
76
usermods/BME68X_v2/pics/GeoGab.svg
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="enable-background:new 0 0 595.28 127.56;"
|
||||||
|
viewBox="0 0 600 135"
|
||||||
|
y="0px"
|
||||||
|
x="0px"
|
||||||
|
id="Layer_1"
|
||||||
|
version="1.1"><metadata
|
||||||
|
id="metadata2372"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs2370"><linearGradient
|
||||||
|
osb:paint="solid"
|
||||||
|
id="linearGradient3877"><stop
|
||||||
|
id="stop3875"
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#808285;stop-opacity:1;" /></linearGradient><clipPath
|
||||||
|
id="clipPath2379"
|
||||||
|
clipPathUnits="userSpaceOnUse"><g
|
||||||
|
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
|
||||||
|
id="use2381"><path
|
||||||
|
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
|
||||||
|
class="st0"
|
||||||
|
d="m 588.34,94.29 c 0,13.28 -10.77,24.04 -24.04,24.04 H 31.38 C 18.1,118.33 7.34,107.57 7.34,94.29 V 32.38 C 7.34,19.1 18.1,8.34 31.38,8.34 h 532.91 c 13.28,0 24.04,10.76 24.04,24.04 V 94.29 Z M 159.44,69.66 c 0,17.15 11.68,19.07 21.89,19.07 h 14.23 c 14.6,0 23.44,-3.2 23.44,-13.87 v -0.91 h -7.84 c -0.45,7.11 -4.47,8.39 -14.32,8.39 h -10.76 c -13.68,0 -18.79,-2.74 -18.79,-13.32 V 66.65 H 219 V 60.9 C 219,44.3 208.88,40.74 194.83,40.74 h -12.59 c -11.12,0 -22.8,1.92 -22.8,18.79 z m 51.72,-9.95 h -43.87 c 0.73,-10.58 3.65,-12.59 16.05,-12.59 h 11.49 c 12.77,0 16.33,4.74 16.33,9.49 z m 18.62,10.13 c 0,15.33 10.86,18.88 25.73,18.88 h 11.22 c 14.87,0 25.72,-3.56 25.72,-18.88 V 59.62 c 0,-15.32 -10.85,-18.88 -25.72,-18.88 h -11.22 c -14.87,0 -25.73,3.56 -25.73,18.88 z m 54.82,-0.46 c 0,10.86 -6.38,12.95 -15.51,12.95 h -15.96 c -9.12,0 -15.51,-2.09 -15.51,-12.95 v -9.31 c 0,-10.85 6.39,-12.95 15.51,-12.95 h 15.96 c 9.12,0 15.51,2.1 15.51,12.95 z m 173.79,18.61 v -31.2 c 0,-13.87 -9.85,-16.06 -24.45,-16.06 h -12.41 c -14.05,0 -19.43,3.47 -19.43,11.95 v 2.28 h 7.84 v -1.64 c 0,-3.83 1.92,-6.2 11.77,-6.2 h 11.49 c 12.04,0 17.33,1.09 17.33,9.49 v 8.76 h -0.18 c -2.74,-4.47 -6.39,-5.56 -16.6,-5.56 H 421.16 C 407.48,59.81 400,61.09 400,71.67 v 3.1 c 0,8.76 3.1,13.96 14.96,13.96 h 18.79 z m -7.84,-12.5 c 0,5.84 -5.47,6.84 -19.7,6.84 h -10.4 c -10.76,0 -12.59,-2.19 -12.59,-7.39 v -1.46 c 0,-5.84 2.83,-7.3 12.95,-7.3 h 12.04 c 12.04,0 17.7,0.82 17.7,7.2 z M 73.6,66.69 h 10.88 l 5.03,0.01 h 48.67 v 2.41 c 0,9.12 -5.47,12.23 -14.41,12.23 H 88.83 c -8.58,0 -15.51,-2.55 -15.51,-14.05 v -0.35 -0.26 z m -9.16,-0.01 c 0,0.06 0,0.14 0,0.2 0.67,14.68 6.68,21.76 23.47,21.76 h 36.85 c 16.51,0 22.35,-6.39 22.35,-24.36 V 59.4 H 89.51 L 85.39,59.39 H 73.32 v -0.01 -16.9 c 0,-11.49 6.93,-14.05 15.51,-14.05 H 112 c 22.07,0 25.81,0.91 25.81,13.23 h 8.39 v -2.37 c 0,-15.23 -12.68,-18.15 -24.54,-18.15 H 87.92 c -17.88,0 -23.53,8.03 -23.53,24.72 V 59.39 L 64.38,59.4 H 44.6 c -11.86,0 -24.54,2.92 -24.54,18.15 v 2.37 5.02 2.37 c 0,15.24 12.68,18.16 24.54,18.16 h 40.79 432.59 34.03 c 17.88,0 23.54,-8.03 23.54,-24.72 V 45.88 c 0,-16.69 -5.65,-24.72 -23.54,-24.72 l -0.16,-0.02 h -223 c -17.88,0 -23.53,8.03 -23.53,24.72 v 18.06 c 0,16.69 5.65,24.72 23.53,24.72 h 36.85 c 16.51,0 22.35,-6.39 22.35,-24.36 V 59.4 h -42.78 v 7.3 h 33.84 v 2.41 c 0,9.12 -5.47,12.23 -14.41,12.23 h -34.94 c -8.58,0 -15.5,-2.55 -15.5,-14.05 V 42.48 c 0,-11.49 6.93,-14.05 15.5,-14.05 l 142.5,0.02 v 61.24 l 24.35,0.73 h 10.95 c 14.23,0 24.18,-3.56 24.18,-19.61 V 62.6 c 0,-14.87 -5.75,-20.16 -24.63,-20.16 h -11.95 -14.88 -0.17 l -0.02,-14.01 h 71.75 l -0.75,0.02 c 8.58,0 15.51,2.55 15.51,14.04 v 41.62 c 0,11.49 -6.93,14.05 -15.51,14.05 H 518.97 86.87 54.27 c -22.08,0 -25.81,-0.91 -25.81,-13.23 v -5.02 c 0,-12.31 3.74,-13.23 25.81,-13.23 h 9.84 z m 459.45,4.04 c 0,9.03 -2.83,13.32 -15.6,13.32 h -12.77 c -11.67,0 -15.42,-4.93 -15.42,-13.41 v -8.85 c 0,-11.13 6.48,-12.95 17.06,-12.95 h 10.58 c 10.76,0 16.14,2.01 16.14,12.77 v 9.12 z"
|
||||||
|
id="path3935" /><path
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
|
||||||
|
class="st0"
|
||||||
|
d="m 611.07375,-48.387188 c 0,13.28 -10.77,24.039999 -24.04,24.039999 H 54.113747 c -13.27999,0 -24.04,-10.76 -24.04,-24.039999 v -61.910002 c 0,-13.28 10.76001,-24.04 24.04,-24.04 H 587.02375 c 13.28,0 24.04,10.76 24.04,24.04 v 61.910002 z"
|
||||||
|
id="path3937" /></g></clipPath><clipPath
|
||||||
|
id="clipPath2398"
|
||||||
|
clipPathUnits="userSpaceOnUse"><g
|
||||||
|
id="use2400"
|
||||||
|
style="fill:#268298;fill-opacity:1"><g
|
||||||
|
id="g3959"
|
||||||
|
clip-path="url(#clipPath2407)"
|
||||||
|
style="fill:#268298;fill-opacity:1"><g
|
||||||
|
id="g3957"
|
||||||
|
style="fill:#268298;fill-opacity:1"><rect
|
||||||
|
style="opacity:1;fill:#268298;fill-opacity:1;stroke:#000000;stroke-opacity:1"
|
||||||
|
id="rect3955"
|
||||||
|
width="350.98587"
|
||||||
|
height="147.57361"
|
||||||
|
x="95.224861"
|
||||||
|
y="-97.290329" /></g></g></g></clipPath><clipPath
|
||||||
|
id="clipPath2407"
|
||||||
|
clipPathUnits="userSpaceOnUse"><g
|
||||||
|
id="use2409"
|
||||||
|
style="fill:#268298;fill-opacity:1"><rect
|
||||||
|
style="opacity:1;fill:#268298;fill-opacity:1;stroke:#000000;stroke-opacity:1"
|
||||||
|
id="rect3963"
|
||||||
|
width="350.98587"
|
||||||
|
height="147.57361"
|
||||||
|
x="95.224861"
|
||||||
|
y="-97.290329" /></g></clipPath></defs>
|
||||||
|
<style
|
||||||
|
id="style2363"
|
||||||
|
type="text/css">
|
||||||
|
.st0{fill:#808285;}
|
||||||
|
</style>
|
||||||
|
<path
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:0.9859813;fill-rule:nonzero;stroke:#5c7823;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
|
||||||
|
class="st0"
|
||||||
|
d="M 600,103.51782 C 600,116.79902 588.8778,127.56 575.17384,127.56 H 24.826163 C 11.111876,127.56 5.1635108e-7,116.79902 5.1635108e-7,103.51782 V 41.602186 C 5.1635108e-7,28.320979 11.111876,17.560001 24.826163,17.560001 H 575.16351 c 13.71429,0 24.82616,10.760978 24.82616,24.042185 v 61.915634 z"
|
||||||
|
id="path2365-0" /><path
|
||||||
|
style="fill:#808285;fill-opacity:1;fill-rule:nonzero;stroke:#230065;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99607843"
|
||||||
|
class="st0"
|
||||||
|
d="M 600,103.51782 C 600,116.79902 588.8778,127.56 575.17384,127.56 H 24.826162 C 11.111877,127.56 5.1635111e-7,116.79902 5.1635111e-7,103.51782 V 41.602187 C 5.1635111e-7,28.320977 11.111876,17.559997 24.826162,17.559997 H 575.16351 c 13.71429,0 24.82616,10.76098 24.82616,24.04219 V 103.51782 Z M 157.07401,78.885567 c 0,17.15156 12.06196,19.07173 22.60585,19.07173 h 14.69535 c 15.07745,0 24.20654,-3.20029 24.20654,-13.87125 v -0.91009 h -8.09638 c -0.46472,7.11065 -4.61618,8.39076 -14.7883,8.39076 h -11.11188 c -14.12736,0 -19.40447,-2.74024 -19.40447,-13.3212 v -2.37022 h 53.40103 v -5.75052 c 0,-16.60151 -10.45094,-20.16183 -24.96041,-20.16183 h -13.00172 c -11.48365,0 -23.54561,1.92017 -23.54561,18.79171 z m 53.41136,-9.9509 h -45.30465 c 0.75387,-10.58096 3.76936,-12.59114 16.57487,-12.59114 h 11.86575 c 13.18761,0 16.86403,4.74043 16.86403,9.49086 z m 19.22891,10.13092 c 0,15.33139 11.21515,18.88171 26.57143,18.88171 h 11.58692 c 15.35629,0 26.56111,-3.56032 26.56111,-18.88171 v -10.22093 c 0,-15.32139 -11.20482,-18.88171 -26.56111,-18.88171 h -11.58692 c -15.35628,0 -26.57143,3.56032 -26.57143,18.88171 z m 56.61275,-0.46004 c 0,10.86098 -6.58865,12.95117 -16.01722,12.95117 h -16.48193 c -9.41825,0 -16.01721,-2.09019 -16.01721,-12.95117 v -9.31085 c 0,-10.85098 6.59896,-12.95117 16.01721,-12.95117 h 16.48193 c 9.41824,0 16.01722,2.10019 16.01722,12.95117 z m 179.47332,18.61169 v -31.20283 c 0,-13.87126 -10.17212,-16.06146 -25.24957,-16.06146 h -12.81584 c -14.50946,0 -20.0654,3.47031 -20.0654,11.95108 v 2.28021 h 8.09638 v -1.64015 c 0,-3.83035 1.98279,-6.20056 12.15491,-6.20056 h 11.86575 c 12.43373,0 17.89673,1.0901 17.89673,9.49086 v 8.7608 h -0.18589 c -2.8296,-4.47041 -6.59897,-5.56051 -17.14286,-5.56051 h -13.00172 c -14.12736,0 -21.85198,1.28012 -21.85198,11.86108 v 3.10028 c 0,8.76079 3.20138,13.96126 15.44923,13.96126 h 19.40447 z m -8.09639,-12.50114 c 0,5.84053 -5.64888,6.84062 -20.34423,6.84062 h -10.74011 c -11.11187,0 -13.00172,-2.19019 -13.00172,-7.39067 v -1.46013 c 0,-5.84053 2.92255,-7.30066 13.3735,-7.30066 h 12.43373 c 12.43374,0 18.27883,0.82007 18.27883,7.20065 z m -389.27711,-8.8008 h 11.2358 l 5.194492,0.01 h 50.261618 v 2.41021 c 0,9.12083 -5.64888,12.23111 -14.88124,12.23111 H 84.154905 c -8.860585,0 -16.017212,-2.55023 -16.017212,-14.05127 v -0.35003 -0.26003 z m -9.459552,-0.01 c 0,0.06 0,0.14002 0,0.20002 0.69191,14.68133 6.898451,21.76198 24.237521,21.76198 h 38.055071 c 17.04992,0 23.0809,-6.39058 23.0809,-24.36221 v -4.88045 H 84.857142 l -4.254733,-0.01 H 68.137693 v -0.01 -16.90153 c 0,-11.49104 7.156627,-14.05129 16.017212,-14.05129 h 23.927705 c 22.79174,0 26.65405,0.91009 26.65405,13.23121 h 8.66437 v -2.37021 c 0,-15.23139 -13.09466,-18.15166 -25.34251,-18.15166 H 83.215146 c -18.464717,0 -24.299483,8.03073 -24.299483,24.72226 v 13.53122 l -0.01033,0.01 h -20.42685 c -12.247849,0 -25.342513,2.92027 -25.342513,18.15165 v 2.37022 5.02045 2.37022 c 0,15.241393 13.094664,18.161653 25.342513,18.161653 h 42.123923 446.736664 35.14286 c 18.46471,0 24.30981,-8.03073 24.30981,-24.722253 v -34.87316 c 0,-16.69153 -5.83477,-24.72226 -24.30981,-24.72226 l -0.16523,-0.02 H 332.0241 c -18.46471,0 -24.29948,8.03073 -24.29948,24.72226 v 18.06163 c 0,16.69152 5.83477,24.72225 24.29948,24.72225 h 38.05508 c 17.04991,0 23.08089,-6.39058 23.08089,-24.36221 v -4.88045 h -44.179 v 7.30067 h 34.94664 v 2.41021 c 0,9.12083 -5.64888,12.23111 -14.88123,12.23111 h -36.08262 c -8.86059,0 -16.00688,-2.55023 -16.00688,-14.05127 v -24.81225 c 0,-11.49104 7.15662,-14.05129 16.00688,-14.05129 l 147.16007,0.02 v 61.24556 l 25.14629,0.73007 h 11.30809 c 14.69536,0 24.97074,-3.56033 24.97074,-19.61178 v -8.21075 c 0,-14.87135 -5.93803,-20.16183 -25.43545,-20.16183 h -12.34079 -15.36661 -0.17556 l -0.0207,-14.01128 h 74.09639 l -0.77453,0.02 c 8.86059,0 16.01721,2.55024 16.01721,14.04128 v 41.62378 c 0,11.491053 -7.15662,14.051283 -16.01721,14.051283 H 528.36145 82.130808 48.464716 c -22.802065,0 -26.654044,-0.91008 -26.654044,-13.231213 v -5.02046 c 0,-12.31111 3.862306,-13.2312 26.654044,-13.2312 h 10.161789 z m 474.475042,4.04037 c 0,9.03082 -2.92255,13.32121 -16.11015,13.32121 h -13.18761 c -12.05164,0 -15.92427,-4.93045 -15.92427,-13.41122 v -8.8508 c 0,-11.13101 6.69191,-12.95118 17.6179,-12.95118 h 10.92599 c 11.11188,0 16.66781,2.01019 16.66781,12.77116 v 9.12083 z"
|
||||||
|
id="path2365" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
BIN
usermods/BME68X_v2/pics/pic1.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
usermods/BME68X_v2/pics/pic2.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
usermods/BME68X_v2/pics/pic3.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
usermods/BME68X_v2/pics/pic4.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
usermods/BME68X_v2/pics/pic5.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
usermods/BME68X_v2/pics/pic6.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
1114
usermods/BME68X_v2/usermod_bme68x.h
Normal file
160
usermods/Battery/UMBattery.h
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#ifndef UMBBattery_h
|
||||||
|
#define UMBBattery_h
|
||||||
|
|
||||||
|
#include "battery_defaults.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battery base class
|
||||||
|
* all other battery classes should inherit from this
|
||||||
|
*/
|
||||||
|
class UMBattery
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float minVoltage;
|
||||||
|
float maxVoltage;
|
||||||
|
float voltage;
|
||||||
|
int8_t level = 100;
|
||||||
|
float calibration; // offset or calibration value to fine tune the calculated voltage
|
||||||
|
float voltageMultiplier; // ratio for the voltage divider
|
||||||
|
|
||||||
|
float linearMapping(float v, float min, float max, float oMin = 0.0f, float oMax = 100.0f)
|
||||||
|
{
|
||||||
|
return (v-min) * (oMax-oMin) / (max-min) + oMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
UMBattery()
|
||||||
|
{
|
||||||
|
this->setVoltageMultiplier(USERMOD_BATTERY_VOLTAGE_MULTIPLIER);
|
||||||
|
this->setCalibration(USERMOD_BATTERY_CALIBRATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void update(batteryConfig cfg)
|
||||||
|
{
|
||||||
|
if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage);
|
||||||
|
if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage);
|
||||||
|
if(cfg.level) this->setLevel(cfg.level);
|
||||||
|
if(cfg.calibration) this->setCalibration(cfg.calibration);
|
||||||
|
if(cfg.voltageMultiplier) this->setVoltageMultiplier(cfg.voltageMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponding battery curves
|
||||||
|
* calculates the level in % (0-100) with given voltage and possible voltage range
|
||||||
|
*/
|
||||||
|
virtual float mapVoltage(float v, float min, float max) = 0;
|
||||||
|
// {
|
||||||
|
// example implementation, linear mapping
|
||||||
|
// return (v-min) * 100 / (max-min);
|
||||||
|
// };
|
||||||
|
|
||||||
|
virtual void calculateAndSetLevel(float voltage) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Getter and Setter
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get lowest configured battery voltage
|
||||||
|
*/
|
||||||
|
virtual float getMinVoltage()
|
||||||
|
{
|
||||||
|
return this->minVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set lowest battery voltage
|
||||||
|
* can't be below 0 volt
|
||||||
|
*/
|
||||||
|
virtual void setMinVoltage(float voltage)
|
||||||
|
{
|
||||||
|
this->minVoltage = max(0.0f, voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get highest configured battery voltage
|
||||||
|
*/
|
||||||
|
virtual float getMaxVoltage()
|
||||||
|
{
|
||||||
|
return this->maxVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set highest battery voltage
|
||||||
|
* can't be below minVoltage
|
||||||
|
*/
|
||||||
|
virtual void setMaxVoltage(float voltage)
|
||||||
|
{
|
||||||
|
this->maxVoltage = max(getMinVoltage()+.5f, voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
float getVoltage()
|
||||||
|
{
|
||||||
|
return this->voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if voltage is within specified voltage range, allow 10% over/under voltage
|
||||||
|
*/
|
||||||
|
void setVoltage(float voltage)
|
||||||
|
{
|
||||||
|
// this->voltage = ( (voltage < this->getMinVoltage() * 0.85f) || (voltage > this->getMaxVoltage() * 1.1f) )
|
||||||
|
// ? -1.0f
|
||||||
|
// : voltage;
|
||||||
|
this->voltage = voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getLevel()
|
||||||
|
{
|
||||||
|
return this->level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLevel(float level)
|
||||||
|
{
|
||||||
|
this->level = constrain(level, 0.0f, 110.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the configured calibration value
|
||||||
|
* a offset value to fine-tune the calculated voltage.
|
||||||
|
*/
|
||||||
|
virtual float getCalibration()
|
||||||
|
{
|
||||||
|
return calibration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the voltage calibration offset value
|
||||||
|
* a offset value to fine-tune the calculated voltage.
|
||||||
|
*/
|
||||||
|
virtual void setCalibration(float offset)
|
||||||
|
{
|
||||||
|
calibration = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the configured calibration value
|
||||||
|
* a value to set the voltage divider ratio
|
||||||
|
*/
|
||||||
|
virtual float getVoltageMultiplier()
|
||||||
|
{
|
||||||
|
return voltageMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the voltage multiplier value
|
||||||
|
* a value to set the voltage divider ratio.
|
||||||
|
*/
|
||||||
|
virtual void setVoltageMultiplier(float multiplier)
|
||||||
|
{
|
||||||
|
voltageMultiplier = multiplier;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
BIN
usermods/Battery/assets/battery_connection_schematic_esp32.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 64 KiB |
BIN
usermods/Battery/assets/battery_info_screen.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
usermods/Battery/assets/battery_usermod_logo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
usermods/Battery/assets/installation_my_config_h.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
usermods/Battery/assets/installation_platformio_override_ini.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
140
usermods/Battery/battery_defaults.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef UMBDefaults_h
|
||||||
|
#define UMBDefaults_h
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
// pin defaults
|
||||||
|
// for the esp32 it is best to use the ADC1: GPIO32 - GPIO39
|
||||||
|
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html
|
||||||
|
#ifndef USERMOD_BATTERY_MEASUREMENT_PIN
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#define USERMOD_BATTERY_MEASUREMENT_PIN 35
|
||||||
|
#else //ESP8266 boards
|
||||||
|
#define USERMOD_BATTERY_MEASUREMENT_PIN A0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The initial delay before the first battery voltage reading after power-on.
|
||||||
|
// This allows the voltage to stabilize before readings are taken, improving accuracy of initial reading.
|
||||||
|
#ifndef USERMOD_BATTERY_INITIAL_DELAY
|
||||||
|
#define USERMOD_BATTERY_INITIAL_DELAY 10000 // (milliseconds)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the frequency to check the battery, 30 sec
|
||||||
|
#ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL
|
||||||
|
#define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Default Battery Type
|
||||||
|
* 0 = unkown
|
||||||
|
* 1 = Lipo
|
||||||
|
* 2 = Lion
|
||||||
|
*/
|
||||||
|
#ifndef USERMOD_BATTERY_DEFAULT_TYPE
|
||||||
|
#define USERMOD_BATTERY_DEFAULT_TYPE 0
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Unkown 'Battery' defaults
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE
|
||||||
|
// Extra save defaults
|
||||||
|
#define USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE 3.3f
|
||||||
|
#endif
|
||||||
|
#ifndef USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE
|
||||||
|
#define USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE 4.2f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Lithium polymer (Li-Po) defaults
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef USERMOD_BATTERY_LIPO_MIN_VOLTAGE
|
||||||
|
// LiPo "1S" Batteries should not be dischared below 3V !!
|
||||||
|
#define USERMOD_BATTERY_LIPO_MIN_VOLTAGE 3.2f
|
||||||
|
#endif
|
||||||
|
#ifndef USERMOD_BATTERY_LIPO_MAX_VOLTAGE
|
||||||
|
#define USERMOD_BATTERY_LIPO_MAX_VOLTAGE 4.2f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Lithium-ion (Li-Ion) defaults
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef USERMOD_BATTERY_LION_MIN_VOLTAGE
|
||||||
|
// default for 18650 battery
|
||||||
|
#define USERMOD_BATTERY_LION_MIN_VOLTAGE 2.6f
|
||||||
|
#endif
|
||||||
|
#ifndef USERMOD_BATTERY_LION_MAX_VOLTAGE
|
||||||
|
#define USERMOD_BATTERY_LION_MAX_VOLTAGE 4.2f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the default ratio for the voltage divider
|
||||||
|
#ifndef USERMOD_BATTERY_VOLTAGE_MULTIPLIER
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 2.0f
|
||||||
|
#else //ESP8266 boards
|
||||||
|
#define USERMOD_BATTERY_VOLTAGE_MULTIPLIER 4.2f
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USERMOD_BATTERY_AVERAGING_ALPHA
|
||||||
|
#define USERMOD_BATTERY_AVERAGING_ALPHA 0.1f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// offset or calibration value to fine tune the calculated voltage
|
||||||
|
#ifndef USERMOD_BATTERY_CALIBRATION
|
||||||
|
#define USERMOD_BATTERY_CALIBRATION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// auto-off feature
|
||||||
|
#ifndef USERMOD_BATTERY_AUTO_OFF_ENABLED
|
||||||
|
#define USERMOD_BATTERY_AUTO_OFF_ENABLED true
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USERMOD_BATTERY_AUTO_OFF_THRESHOLD
|
||||||
|
#define USERMOD_BATTERY_AUTO_OFF_THRESHOLD 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// low power indication feature
|
||||||
|
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED
|
||||||
|
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED true
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET
|
||||||
|
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD
|
||||||
|
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION
|
||||||
|
#define USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// battery types
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
unknown=0,
|
||||||
|
lipo=1,
|
||||||
|
lion=2
|
||||||
|
} batteryType;
|
||||||
|
|
||||||
|
// used for initial configuration after boot
|
||||||
|
typedef struct bconfig_t
|
||||||
|
{
|
||||||
|
batteryType type;
|
||||||
|
float minVoltage;
|
||||||
|
float maxVoltage;
|
||||||
|
float voltage; // current voltage
|
||||||
|
int8_t level; // current level
|
||||||
|
float calibration; // offset or calibration value to fine tune the calculated voltage
|
||||||
|
float voltageMultiplier;
|
||||||
|
} batteryConfig;
|
||||||
|
|
||||||
|
#endif
|
||||||
173
usermods/Battery/readme.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img width="700" src="assets/battery_usermod_logo.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Welcome to the battery usermod! 🔋
|
||||||
|
|
||||||
|
Enables battery level monitoring of your project.
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<img width="700" src="assets/battery_info_screen.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## ⚙️ Features
|
||||||
|
|
||||||
|
- 💯 Displays current battery voltage
|
||||||
|
- 🚥 Displays battery level
|
||||||
|
- 🚫 Auto-off with configurable threshold
|
||||||
|
- 🚨 Low power indicator with many configuration possibilities
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## 🎈 Installation
|
||||||
|
|
||||||
|
| **Option 1** | **Option 2** |
|
||||||
|
|--------------|--------------|
|
||||||
|
| In `wled00/my_config.h`<br>Add the line: `#define USERMOD_BATTERY`<br><br>[Example: my_config.h](assets/installation_my_config_h.png) | In `platformio_override.ini` (or `platformio.ini`)<br>Under: `build_flags =`, add the line: `-D USERMOD_BATTERY`<br><br>[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) |
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## 🔌 Example wiring
|
||||||
|
|
||||||
|
- (see [Useful Links](#useful-links)).
|
||||||
|
|
||||||
|
<table style="width: 100%; table-layout: fixed;">
|
||||||
|
<tr>
|
||||||
|
<!-- Column for the first image -->
|
||||||
|
<td style="width: 50%; vertical-align: bottom;">
|
||||||
|
<img width="300" src="assets/battery_connection_schematic_01.png" style="display: block;">
|
||||||
|
<p><strong>ESP8266</strong><br>
|
||||||
|
With a 100k Ohm resistor, connect the positive<br>
|
||||||
|
side of the battery to pin `A0`.</p>
|
||||||
|
</td>
|
||||||
|
<!-- Column for the second image -->
|
||||||
|
<td style="width: 50%; vertical-align: bottom;">
|
||||||
|
<img width="250" src="assets/battery_connection_schematic_esp32.png" style="display: block;">
|
||||||
|
<p><strong>ESP32</strong> (+S2, S3, C3 etc...)<br>
|
||||||
|
Use a voltage divider (two resistors of equal value).<br>
|
||||||
|
Connect to ADC1 (GPIO32 - GPIO39). GPIO35 is Default.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## Define Your Options
|
||||||
|
|
||||||
|
| Name | Unit | Description |
|
||||||
|
| ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- |
|
||||||
|
| `USERMOD_BATTERY` | | 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 GPIO35 on ESP32 |
|
||||||
|
| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | Battery check interval. defaults to 30 seconds |
|
||||||
|
| `USERMOD_BATTERY_INITIAL_DELAY` | ms | Delay before initial reading. defaults to 10 seconds to allow voltage stabilization |
|
||||||
|
| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | Minimum battery voltage. default is 2.6 (18650 battery standard) |
|
||||||
|
| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | Maximum battery voltage. default is 4.2 (18650 battery standard) |
|
||||||
|
| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | The capacity of all cells in parallel summed up |
|
||||||
|
| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | Offset / calibration number, fine tune the measured voltage by the microcontroller |
|
||||||
|
| Auto-Off | --- | --- |
|
||||||
|
| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | Enables auto-off |
|
||||||
|
| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | When this threshold is reached master power turns off |
|
||||||
|
| Low-Power-Indicator | --- | --- |
|
||||||
|
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | Enables low power indication |
|
||||||
|
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | When low power is detected then use this preset to indicate low power |
|
||||||
|
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | When this threshold is reached low power gets indicated |
|
||||||
|
| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | For this long the configured preset is played |
|
||||||
|
|
||||||
|
All parameters can be configured at runtime via the Usermods settings page.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
**NOTICE:** Each Battery type can be pre-configured individualy (in `my_config.h`)
|
||||||
|
|
||||||
|
| Name | Alias | `my_config.h` example |
|
||||||
|
| --------------- | ------------- | ------------------------------------- |
|
||||||
|
| Lithium Polymer | lipo (Li-Po) | `USERMOD_BATTERY_lipo_MIN_VOLTAGE` |
|
||||||
|
| Lithium Ionen | lion (Li-Ion) | `USERMOD_BATTERY_lion_TOTAL_CAPACITY` |
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## 🔧 Calibration
|
||||||
|
|
||||||
|
The calibration number is a value that is added to the final computed voltage after it has been scaled by the voltage multiplier.
|
||||||
|
|
||||||
|
It fine-tunes the voltage reading so that it more closely matches the actual battery voltage, compensating for inaccuracies inherent in the voltage divider resistors or the ESP's ADC measurements.
|
||||||
|
|
||||||
|
Set calibration either in the Usermods settings page or at compile time in `my_config.h` or `platformio_override.ini`.
|
||||||
|
|
||||||
|
It can be either a positive or negative number.
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## ⚠️ Important
|
||||||
|
|
||||||
|
Make sure you know your battery specifications! All batteries are **NOT** the same!
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
| Your battery specification table | | Options you can define |
|
||||||
|
| --------------------------------- | --------------- | ----------------------------- |
|
||||||
|
| Capacity | 3500mAh 12.5Wh | |
|
||||||
|
| Minimum capacity | 3350mAh 11.9Wh | |
|
||||||
|
| Rated voltage | 3.6V - 3.7V | |
|
||||||
|
| **Charging end voltage** | **4.2V ± 0.05** | `USERMOD_BATTERY_MAX_VOLTAGE` |
|
||||||
|
| **Discharge voltage** | **2.5V** | `USERMOD_BATTERY_MIN_VOLTAGE` |
|
||||||
|
| Max. discharge current (constant) | 10A (10000mA) | |
|
||||||
|
| max. charging current | 1.7A (1700mA) | |
|
||||||
|
| ... | ... | ... |
|
||||||
|
| .. | .. | .. |
|
||||||
|
|
||||||
|
Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833)
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## 🌐 Useful Links
|
||||||
|
|
||||||
|
- https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start
|
||||||
|
- https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
## 📝 Change Log
|
||||||
|
|
||||||
|
2024-05-11
|
||||||
|
|
||||||
|
- Documentation updated
|
||||||
|
|
||||||
|
2024-04-30
|
||||||
|
|
||||||
|
- Integrate factory pattern to make it easier to add other / custom battery types
|
||||||
|
- Update readme
|
||||||
|
- Improved initial reading accuracy by delaying initial measurement to allow voltage to stabilize at power-on
|
||||||
|
|
||||||
|
2023-01-04
|
||||||
|
|
||||||
|
- Basic support for LiPo rechargeable batteries (`-D USERMOD_BATTERY_USE_LIPO`)
|
||||||
|
- Improved support for ESP32 (read calibrated voltage)
|
||||||
|
- Corrected config saving (measurement pin, and battery min/max were lost)
|
||||||
|
- Various bugfixes
|
||||||
|
|
||||||
|
2022-12-25
|
||||||
|
|
||||||
|
- Added "auto-off" feature
|
||||||
|
- Added "low-power-indication" feature
|
||||||
|
- Added "calibration/offset" field to configuration page
|
||||||
|
- Added getter and setter, so that user usermods could interact with this one
|
||||||
|
- Update readme (added new options, made it markdownlint compliant)
|
||||||
|
|
||||||
|
2021-09-02
|
||||||
|
|
||||||
|
- Added "Battery voltage" to info
|
||||||
|
- Added circuit diagram to readme
|
||||||
|
- Added MQTT support, sending battery voltage
|
||||||
|
- Minor fixes
|
||||||
|
|
||||||
|
2021-08-15
|
||||||
|
|
||||||
|
- Changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries
|
||||||
|
- Updated readme, added specification table
|
||||||
|
|
||||||
|
2021-08-10
|
||||||
|
|
||||||
|
- Created
|
||||||
38
usermods/Battery/types/LionUMBattery.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef UMBLion_h
|
||||||
|
#define UMBLion_h
|
||||||
|
|
||||||
|
#include "../battery_defaults.h"
|
||||||
|
#include "../UMBattery.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiOn Battery
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LionUMBattery : public UMBattery
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
public:
|
||||||
|
LionUMBattery() : UMBattery()
|
||||||
|
{
|
||||||
|
this->setMinVoltage(USERMOD_BATTERY_LION_MIN_VOLTAGE);
|
||||||
|
this->setMaxVoltage(USERMOD_BATTERY_LION_MAX_VOLTAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
float mapVoltage(float v, float min, float max) override
|
||||||
|
{
|
||||||
|
return this->linearMapping(v, min, max); // basic mapping
|
||||||
|
};
|
||||||
|
|
||||||
|
void calculateAndSetLevel(float voltage) override
|
||||||
|
{
|
||||||
|
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void setMaxVoltage(float voltage) override
|
||||||
|
{
|
||||||
|
this->maxVoltage = max(getMinVoltage()+1.0f, voltage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
54
usermods/Battery/types/LipoUMBattery.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef UMBLipo_h
|
||||||
|
#define UMBLipo_h
|
||||||
|
|
||||||
|
#include "../battery_defaults.h"
|
||||||
|
#include "../UMBattery.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiPo Battery
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LipoUMBattery : public UMBattery
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
public:
|
||||||
|
LipoUMBattery() : UMBattery()
|
||||||
|
{
|
||||||
|
this->setMinVoltage(USERMOD_BATTERY_LIPO_MIN_VOLTAGE);
|
||||||
|
this->setMaxVoltage(USERMOD_BATTERY_LIPO_MAX_VOLTAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LiPo batteries have a differnt discharge curve, see
|
||||||
|
* https://blog.ampow.com/lipo-voltage-chart/
|
||||||
|
*/
|
||||||
|
float mapVoltage(float v, float min, float max) override
|
||||||
|
{
|
||||||
|
float lvl = 0.0f;
|
||||||
|
lvl = this->linearMapping(v, min, max); // basic mapping
|
||||||
|
|
||||||
|
if (lvl < 40.0f)
|
||||||
|
lvl = this->linearMapping(lvl, 0, 40, 0, 12); // last 45% -> drops very quickly
|
||||||
|
else {
|
||||||
|
if (lvl < 90.0f)
|
||||||
|
lvl = this->linearMapping(lvl, 40, 90, 12, 95); // 90% ... 40% -> almost linear drop
|
||||||
|
else // level > 90%
|
||||||
|
lvl = this->linearMapping(lvl, 90, 105, 95, 100); // highest 15% -> drop slowly
|
||||||
|
}
|
||||||
|
|
||||||
|
return lvl;
|
||||||
|
};
|
||||||
|
|
||||||
|
void calculateAndSetLevel(float voltage) override
|
||||||
|
{
|
||||||
|
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void setMaxVoltage(float voltage) override
|
||||||
|
{
|
||||||
|
this->maxVoltage = max(getMinVoltage()+0.7f, voltage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
39
usermods/Battery/types/UnkownUMBattery.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef UMBUnkown_h
|
||||||
|
#define UMBUnkown_h
|
||||||
|
|
||||||
|
#include "../battery_defaults.h"
|
||||||
|
#include "../UMBattery.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unkown / Default Battery
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UnkownUMBattery : public UMBattery
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
public:
|
||||||
|
UnkownUMBattery() : UMBattery()
|
||||||
|
{
|
||||||
|
this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE);
|
||||||
|
this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(batteryConfig cfg)
|
||||||
|
{
|
||||||
|
if(cfg.minVoltage) this->setMinVoltage(cfg.minVoltage); else this->setMinVoltage(USERMOD_BATTERY_UNKOWN_MIN_VOLTAGE);
|
||||||
|
if(cfg.maxVoltage) this->setMaxVoltage(cfg.maxVoltage); else this->setMaxVoltage(USERMOD_BATTERY_UNKOWN_MAX_VOLTAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
float mapVoltage(float v, float min, float max) override
|
||||||
|
{
|
||||||
|
return this->linearMapping(v, min, max); // basic mapping
|
||||||
|
};
|
||||||
|
|
||||||
|
void calculateAndSetLevel(float voltage) override
|
||||||
|
{
|
||||||
|
this->setLevel(this->mapVoltage(voltage, this->getMinVoltage(), this->getMaxVoltage()));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
797
usermods/Battery/usermod_v2_Battery.h
Normal file
@@ -0,0 +1,797 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include "battery_defaults.h"
|
||||||
|
#include "UMBattery.h"
|
||||||
|
#include "types/UnkownUMBattery.h"
|
||||||
|
#include "types/LionUMBattery.h"
|
||||||
|
#include "types/LiPoUMBattery.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Usermod by Maximilian Mewes
|
||||||
|
* E-mail: mewes.maximilian@gmx.de
|
||||||
|
* Created at: 25.12.2022
|
||||||
|
* If you have any questions, please feel free to contact me.
|
||||||
|
*/
|
||||||
|
class UsermodBattery : public Usermod
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// battery pin can be defined in my_config.h
|
||||||
|
int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN;
|
||||||
|
|
||||||
|
UMBattery* bat = new UnkownUMBattery();
|
||||||
|
batteryConfig cfg;
|
||||||
|
|
||||||
|
// Initial delay before first reading to allow voltage stabilization
|
||||||
|
unsigned long initialDelay = USERMOD_BATTERY_INITIAL_DELAY;
|
||||||
|
bool initialDelayComplete = false;
|
||||||
|
bool isFirstVoltageReading = true;
|
||||||
|
// how often to read the battery voltage
|
||||||
|
unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL;
|
||||||
|
unsigned long nextReadTime = 0;
|
||||||
|
unsigned long lastReadTime = 0;
|
||||||
|
// between 0 and 1, to control strength of voltage smoothing filter
|
||||||
|
float alpha = USERMOD_BATTERY_AVERAGING_ALPHA;
|
||||||
|
|
||||||
|
// auto shutdown/shutoff/master off feature
|
||||||
|
bool autoOffEnabled = USERMOD_BATTERY_AUTO_OFF_ENABLED;
|
||||||
|
uint8_t autoOffThreshold = USERMOD_BATTERY_AUTO_OFF_THRESHOLD;
|
||||||
|
|
||||||
|
// low power indicator feature
|
||||||
|
bool lowPowerIndicatorEnabled = USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED;
|
||||||
|
uint8_t lowPowerIndicatorPreset = USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET;
|
||||||
|
uint8_t lowPowerIndicatorThreshold = USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD;
|
||||||
|
uint8_t lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
|
||||||
|
uint8_t lowPowerIndicatorDuration = USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION;
|
||||||
|
bool lowPowerIndicationDone = false;
|
||||||
|
unsigned long lowPowerActivationTime = 0; // used temporary during active time
|
||||||
|
uint8_t lastPreset = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
bool initDone = false;
|
||||||
|
bool initializing = true;
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _readInterval[];
|
||||||
|
static const char _enabled[];
|
||||||
|
static const char _threshold[];
|
||||||
|
static const char _preset[];
|
||||||
|
static const char _duration[];
|
||||||
|
static const char _init[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for rounding floating point values
|
||||||
|
*/
|
||||||
|
float dot2round(float x)
|
||||||
|
{
|
||||||
|
float nx = (int)(x * 100 + .5);
|
||||||
|
return (float)(nx / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn off all leds
|
||||||
|
*/
|
||||||
|
void turnOff()
|
||||||
|
{
|
||||||
|
bri = 0;
|
||||||
|
stateUpdated(CALL_MODE_DIRECT_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate low power by activating a configured preset for a given time and then switching back to the preset that was selected previously
|
||||||
|
*/
|
||||||
|
void lowPowerIndicator()
|
||||||
|
{
|
||||||
|
if (!lowPowerIndicatorEnabled) return;
|
||||||
|
if (batteryPin < 0) return; // no measurement
|
||||||
|
if (lowPowerIndicationDone && lowPowerIndicatorReactivationThreshold <= bat->getLevel()) lowPowerIndicationDone = false;
|
||||||
|
if (lowPowerIndicatorThreshold <= bat->getLevel()) return;
|
||||||
|
if (lowPowerIndicationDone) return;
|
||||||
|
if (lowPowerActivationTime <= 1) {
|
||||||
|
lowPowerActivationTime = millis();
|
||||||
|
lastPreset = currentPreset;
|
||||||
|
applyPreset(lowPowerIndicatorPreset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowPowerActivationTime+(lowPowerIndicatorDuration*1000) <= millis()) {
|
||||||
|
lowPowerIndicationDone = true;
|
||||||
|
lowPowerActivationTime = 0;
|
||||||
|
applyPreset(lastPreset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read the battery voltage in different ways depending on the architecture
|
||||||
|
*/
|
||||||
|
float readVoltage()
|
||||||
|
{
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
// use calibrated millivolts analogread on esp32 (150 mV ~ 2450 mV default attentuation) and divide by 1000 to get from milivolts to volts and multiply by voltage multiplier and apply calibration value
|
||||||
|
return (analogReadMilliVolts(batteryPin) / 1000.0f) * bat->getVoltageMultiplier() + bat->getCalibration();
|
||||||
|
#else
|
||||||
|
// use analog read on esp8266 ( 0V ~ 1V no attenuation options) and divide by ADC precision 1023 and multiply by voltage multiplier and apply calibration value
|
||||||
|
return (analogRead(batteryPin) / 1023.0f) * bat->getVoltageMultiplier() + bat->getCalibration();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
// plug in the right battery type
|
||||||
|
if(cfg.type == (batteryType)lipo) {
|
||||||
|
bat = new LipoUMBattery();
|
||||||
|
} else if(cfg.type == (batteryType)lion) {
|
||||||
|
bat = new LionUMBattery();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the choosen battery type with configured values
|
||||||
|
bat->update(cfg);
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
bool success = false;
|
||||||
|
DEBUG_PRINTLN(F("Allocating battery pin..."));
|
||||||
|
if (batteryPin >= 0 && digitalPinToAnalogChannel(batteryPin) >= 0)
|
||||||
|
if (pinManager.allocatePin(batteryPin, false, PinOwner::UM_Battery)) {
|
||||||
|
DEBUG_PRINTLN(F("Battery pin allocation succeeded."));
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
DEBUG_PRINTLN(F("Battery pin allocation failed."));
|
||||||
|
batteryPin = -1; // allocation failed
|
||||||
|
} else {
|
||||||
|
pinMode(batteryPin, INPUT);
|
||||||
|
}
|
||||||
|
#else //ESP8266 boards have only one analog input pin A0
|
||||||
|
pinMode(batteryPin, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// First voltage reading is delayed to allow voltage stabilization after powering up
|
||||||
|
nextReadTime = millis() + initialDelay;
|
||||||
|
lastReadTime = millis();
|
||||||
|
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* connected() is called every time the WiFi is (re)connected
|
||||||
|
* Use it to initialize network interfaces
|
||||||
|
*/
|
||||||
|
void connected()
|
||||||
|
{
|
||||||
|
//Serial.println("Connected to WiFi!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if(strip.isUpdating()) return;
|
||||||
|
|
||||||
|
lowPowerIndicator();
|
||||||
|
|
||||||
|
// Handling the initial delay
|
||||||
|
if (!initialDelayComplete && millis() < nextReadTime)
|
||||||
|
return; // Continue to return until the initial delay is over
|
||||||
|
|
||||||
|
// Once the initial delay is over, set it as complete
|
||||||
|
if (!initialDelayComplete)
|
||||||
|
{
|
||||||
|
initialDelayComplete = true;
|
||||||
|
// Set the regular interval after initial delay
|
||||||
|
nextReadTime = millis() + readingInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the first voltage reading after the initial delay has elapsed
|
||||||
|
if (isFirstVoltageReading)
|
||||||
|
{
|
||||||
|
bat->setVoltage(readVoltage());
|
||||||
|
isFirstVoltageReading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms)
|
||||||
|
if (millis() < nextReadTime) return;
|
||||||
|
|
||||||
|
nextReadTime = millis() + readingInterval;
|
||||||
|
lastReadTime = millis();
|
||||||
|
|
||||||
|
if (batteryPin < 0) return; // nothing to read
|
||||||
|
|
||||||
|
initializing = false;
|
||||||
|
float rawValue = readVoltage();
|
||||||
|
|
||||||
|
// filter with exponential smoothing because ADC in esp32 is fluctuating too much for a good single readout
|
||||||
|
float filteredVoltage = bat->getVoltage() + alpha * (rawValue - bat->getVoltage());
|
||||||
|
|
||||||
|
bat->setVoltage(filteredVoltage);
|
||||||
|
// translate battery voltage into percentage
|
||||||
|
bat->calculateAndSetLevel(filteredVoltage);
|
||||||
|
|
||||||
|
// Auto off -- Master power off
|
||||||
|
if (autoOffEnabled && (autoOffThreshold >= bat->getLevel()))
|
||||||
|
turnOff();
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
// SmartHome stuff
|
||||||
|
// still don't know much about MQTT and/or HA
|
||||||
|
if (WLED_MQTT_CONNECTED) {
|
||||||
|
char buf[64]; // buffer for snprintf()
|
||||||
|
snprintf_P(buf, 63, PSTR("%s/voltage"), mqttDeviceTopic);
|
||||||
|
mqtt->publish(buf, 0, false, String(bat->getVoltage()).c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
if (batteryPin < 0) {
|
||||||
|
JsonArray infoVoltage = user.createNestedArray(F("Battery voltage"));
|
||||||
|
infoVoltage.add(F("n/a"));
|
||||||
|
infoVoltage.add(F(" invalid GPIO"));
|
||||||
|
return; // no GPIO - nothing to report
|
||||||
|
}
|
||||||
|
|
||||||
|
// info modal display names
|
||||||
|
JsonArray infoPercentage = user.createNestedArray(F("Battery level"));
|
||||||
|
JsonArray infoVoltage = user.createNestedArray(F("Battery voltage"));
|
||||||
|
JsonArray infoNextUpdate = user.createNestedArray(F("Next update"));
|
||||||
|
|
||||||
|
infoNextUpdate.add((nextReadTime - millis()) / 1000);
|
||||||
|
infoNextUpdate.add(F(" sec"));
|
||||||
|
|
||||||
|
if (initializing) {
|
||||||
|
infoPercentage.add(FPSTR(_init));
|
||||||
|
infoVoltage.add(FPSTR(_init));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bat->getLevel() < 0) {
|
||||||
|
infoPercentage.add(F("invalid"));
|
||||||
|
} else {
|
||||||
|
infoPercentage.add(bat->getLevel());
|
||||||
|
}
|
||||||
|
infoPercentage.add(F(" %"));
|
||||||
|
|
||||||
|
if (bat->getVoltage() < 0) {
|
||||||
|
infoVoltage.add(F("invalid"));
|
||||||
|
} else {
|
||||||
|
infoVoltage.add(dot2round(bat->getVoltage()));
|
||||||
|
}
|
||||||
|
infoVoltage.add(F(" V"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addBatteryToJsonObject(JsonObject& battery, bool forJsonState)
|
||||||
|
{
|
||||||
|
if(forJsonState) { battery[F("type")] = cfg.type; } else {battery[F("type")] = (String)cfg.type; } // has to be a String otherwise it won't get converted to a Dropdown
|
||||||
|
battery[F("min-voltage")] = bat->getMinVoltage();
|
||||||
|
battery[F("max-voltage")] = bat->getMaxVoltage();
|
||||||
|
battery[F("calibration")] = bat->getCalibration();
|
||||||
|
battery[F("voltage-multiplier")] = bat->getVoltageMultiplier();
|
||||||
|
battery[FPSTR(_readInterval)] = readingInterval;
|
||||||
|
|
||||||
|
JsonObject ao = battery.createNestedObject(F("auto-off")); // auto off section
|
||||||
|
ao[FPSTR(_enabled)] = autoOffEnabled;
|
||||||
|
ao[FPSTR(_threshold)] = autoOffThreshold;
|
||||||
|
|
||||||
|
JsonObject lp = battery.createNestedObject(F("indicator")); // low power section
|
||||||
|
lp[FPSTR(_enabled)] = lowPowerIndicatorEnabled;
|
||||||
|
lp[FPSTR(_preset)] = lowPowerIndicatorPreset; // dropdown trickery (String)lowPowerIndicatorPreset;
|
||||||
|
lp[FPSTR(_threshold)] = lowPowerIndicatorThreshold;
|
||||||
|
lp[FPSTR(_duration)] = lowPowerIndicatorDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getUsermodConfigFromJsonObject(JsonObject& battery)
|
||||||
|
{
|
||||||
|
getJsonValue(battery[F("type")], cfg.type);
|
||||||
|
getJsonValue(battery[F("min-voltage")], cfg.minVoltage);
|
||||||
|
getJsonValue(battery[F("max-voltage")], cfg.maxVoltage);
|
||||||
|
getJsonValue(battery[F("calibration")], cfg.calibration);
|
||||||
|
getJsonValue(battery[F("voltage-multiplier")], cfg.voltageMultiplier);
|
||||||
|
|
||||||
|
setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval);
|
||||||
|
|
||||||
|
JsonObject ao = battery[F("auto-off")];
|
||||||
|
setAutoOffEnabled(ao[FPSTR(_enabled)] | autoOffEnabled);
|
||||||
|
setAutoOffThreshold(ao[FPSTR(_threshold)] | autoOffThreshold);
|
||||||
|
|
||||||
|
JsonObject lp = battery[F("indicator")];
|
||||||
|
setLowPowerIndicatorEnabled(lp[FPSTR(_enabled)] | lowPowerIndicatorEnabled);
|
||||||
|
setLowPowerIndicatorPreset(lp[FPSTR(_preset)] | lowPowerIndicatorPreset);
|
||||||
|
setLowPowerIndicatorThreshold(lp[FPSTR(_threshold)] | lowPowerIndicatorThreshold);
|
||||||
|
lowPowerIndicatorReactivationThreshold = lowPowerIndicatorThreshold+10;
|
||||||
|
setLowPowerIndicatorDuration(lp[FPSTR(_duration)] | lowPowerIndicatorDuration);
|
||||||
|
|
||||||
|
if(initDone)
|
||||||
|
bat->update(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
JsonObject battery = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
|
if (battery.isNull())
|
||||||
|
battery = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
|
addBatteryToJsonObject(battery, true);
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("Battery state exposed in JSON API."));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 battery = root[FPSTR(_name)];
|
||||||
|
|
||||||
|
if (!battery.isNull()) {
|
||||||
|
getUsermodConfigFromJsonObject(battery);
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("Battery state read from JSON API."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
|
||||||
|
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
|
||||||
|
* If you want to force saving the current state, use serializeConfig() in your loop().
|
||||||
|
*
|
||||||
|
* CAUTION: serializeConfig() will initiate a filesystem write operation.
|
||||||
|
* It might cause the LEDs to stutter and will cause flash wear if called too often.
|
||||||
|
* Use it sparingly and always in the loop, never in network callbacks!
|
||||||
|
*
|
||||||
|
* addToConfig() will make your settings editable through the Usermod Settings page automatically.
|
||||||
|
*
|
||||||
|
* Usermod Settings Overview:
|
||||||
|
* - Numeric values are treated as floats in the browser.
|
||||||
|
* - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float
|
||||||
|
* before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and
|
||||||
|
* doubles are not supported, numbers will be rounded to the nearest float value when being parsed.
|
||||||
|
* The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38.
|
||||||
|
* - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a
|
||||||
|
* C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod.
|
||||||
|
* Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type
|
||||||
|
* used in the Usermod when reading the value from ArduinoJson.
|
||||||
|
* - Pin values can be treated differently from an integer value by using the key name "pin"
|
||||||
|
* - "pin" can contain a single or array of integer values
|
||||||
|
* - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins
|
||||||
|
* - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin)
|
||||||
|
* - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used
|
||||||
|
*
|
||||||
|
* See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings
|
||||||
|
*
|
||||||
|
* If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work.
|
||||||
|
* You will have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
||||||
|
* See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED
|
||||||
|
*
|
||||||
|
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||||
|
*/
|
||||||
|
void addToConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject battery = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
|
if (battery.isNull()) {
|
||||||
|
battery = root.createNestedObject(FPSTR(_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
battery[F("pin")] = batteryPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
addBatteryToJsonObject(battery, false);
|
||||||
|
|
||||||
|
// read voltage in case calibration or voltage multiplier changed to see immediate effect
|
||||||
|
bat->setVoltage(readVoltage());
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("Battery config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendConfigData()
|
||||||
|
{
|
||||||
|
// Total: 462 Bytes
|
||||||
|
oappend(SET_F("td=addDropdown('Battery', 'type');")); // 35 Bytes
|
||||||
|
oappend(SET_F("addOption(td, 'Unkown', '0');")); // 30 Bytes
|
||||||
|
oappend(SET_F("addOption(td, 'LiPo', '1');")); // 28 Bytes
|
||||||
|
oappend(SET_F("addOption(td, 'LiOn', '2');")); // 28 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:type',1,'<small style=\"color:orange\">requires reboot</small>');")); // 81 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:min-voltage', 1, 'v');")); // 40 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:max-voltage', 1, 'v');")); // 40 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:interval', 1, 'ms');")); // 38 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:auto-off:threshold', 1, '%');")); // 47 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:indicator:threshold', 1, '%');")); // 48 Bytes
|
||||||
|
oappend(SET_F("addInfo('Battery:indicator:duration', 1, 's');")); // 47 Bytes
|
||||||
|
|
||||||
|
// this option list would exeed the oappend() buffer
|
||||||
|
// a list of all presets to select one from
|
||||||
|
// oappend(SET_F("bd=addDropdown('Battery:low-power-indicator', 'preset');"));
|
||||||
|
// the loop generates: oappend(SET_F("addOption(bd, 'preset name', preset id);"));
|
||||||
|
// for(int8_t i=1; i < 42; i++) {
|
||||||
|
// oappend(SET_F("addOption(bd, 'Preset#"));
|
||||||
|
// oappendi(i);
|
||||||
|
// oappend(SET_F("',"));
|
||||||
|
// oappendi(i);
|
||||||
|
// oappend(SET_F(");"));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
|
||||||
|
* This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page)
|
||||||
|
*
|
||||||
|
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
|
||||||
|
* 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 :)
|
||||||
|
*
|
||||||
|
* Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
|
||||||
|
*
|
||||||
|
* getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present
|
||||||
|
* The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them
|
||||||
|
*
|
||||||
|
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
||||||
|
*/
|
||||||
|
bool readFromConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
int8_t newBatteryPin = batteryPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonObject battery = root[FPSTR(_name)];
|
||||||
|
if (battery.isNull())
|
||||||
|
{
|
||||||
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
|
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
newBatteryPin = battery[F("pin")] | newBatteryPin;
|
||||||
|
#endif
|
||||||
|
// calculateTimeLeftEnabled = battery[F("time-left")] | calculateTimeLeftEnabled;
|
||||||
|
setMinBatteryVoltage(battery[F("min-voltage")] | bat->getMinVoltage());
|
||||||
|
setMaxBatteryVoltage(battery[F("max-voltage")] | bat->getMaxVoltage());
|
||||||
|
setCalibration(battery[F("calibration")] | bat->getCalibration());
|
||||||
|
setVoltageMultiplier(battery[F("voltage-multiplier")] | bat->getVoltageMultiplier());
|
||||||
|
setReadingInterval(battery[FPSTR(_readInterval)] | readingInterval);
|
||||||
|
|
||||||
|
getUsermodConfigFromJsonObject(battery);
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
if (!initDone)
|
||||||
|
{
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
batteryPin = newBatteryPin;
|
||||||
|
DEBUG_PRINTLN(F(" config loaded."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||||
|
|
||||||
|
// changing parameters from settings page
|
||||||
|
if (newBatteryPin != batteryPin)
|
||||||
|
{
|
||||||
|
// deallocate pin
|
||||||
|
pinManager.deallocatePin(batteryPin, PinOwner::UM_Battery);
|
||||||
|
batteryPin = newBatteryPin;
|
||||||
|
// initialise
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return !battery[FPSTR(_readInterval)].isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TBD: Generate a preset sample for low power indication
|
||||||
|
* a button on the config page would be cool, currently not possible
|
||||||
|
*/
|
||||||
|
void generateExamplePreset()
|
||||||
|
{
|
||||||
|
// StaticJsonDocument<300> j;
|
||||||
|
// JsonObject preset = j.createNestedObject();
|
||||||
|
// preset["mainseg"] = 0;
|
||||||
|
// JsonArray seg = preset.createNestedArray("seg");
|
||||||
|
// JsonObject seg0 = seg.createNestedObject();
|
||||||
|
// seg0["id"] = 0;
|
||||||
|
// seg0["start"] = 0;
|
||||||
|
// seg0["stop"] = 60;
|
||||||
|
// seg0["grp"] = 0;
|
||||||
|
// seg0["spc"] = 0;
|
||||||
|
// seg0["on"] = true;
|
||||||
|
// seg0["bri"] = 255;
|
||||||
|
|
||||||
|
// JsonArray col0 = seg0.createNestedArray("col");
|
||||||
|
// JsonArray col00 = col0.createNestedArray();
|
||||||
|
// col00.add(255);
|
||||||
|
// col00.add(0);
|
||||||
|
// col00.add(0);
|
||||||
|
|
||||||
|
// seg0["fx"] = 1;
|
||||||
|
// seg0["sx"] = 128;
|
||||||
|
// seg0["ix"] = 128;
|
||||||
|
|
||||||
|
// savePreset(199, "Low power Indicator", preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Getter and Setter. Just in case some other usermod wants to interact with this in the future
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_BATTERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get currently active battery type
|
||||||
|
*/
|
||||||
|
batteryType getBatteryType()
|
||||||
|
{
|
||||||
|
return cfg.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
unsigned long getReadingInterval()
|
||||||
|
{
|
||||||
|
return readingInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minimum repetition is 3000ms (3s)
|
||||||
|
*/
|
||||||
|
void setReadingInterval(unsigned long newReadingInterval)
|
||||||
|
{
|
||||||
|
readingInterval = max((unsigned long)3000, newReadingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lowest configured battery voltage
|
||||||
|
*/
|
||||||
|
float getMinBatteryVoltage()
|
||||||
|
{
|
||||||
|
return bat->getMinVoltage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set lowest battery voltage
|
||||||
|
* can't be below 0 volt
|
||||||
|
*/
|
||||||
|
void setMinBatteryVoltage(float voltage)
|
||||||
|
{
|
||||||
|
bat->setMinVoltage(voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get highest configured battery voltage
|
||||||
|
*/
|
||||||
|
float getMaxBatteryVoltage()
|
||||||
|
{
|
||||||
|
return bat->getMaxVoltage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set highest battery voltage
|
||||||
|
* can't be below minBatteryVoltage
|
||||||
|
*/
|
||||||
|
void setMaxBatteryVoltage(float voltage)
|
||||||
|
{
|
||||||
|
bat->setMaxVoltage(voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the calculated voltage
|
||||||
|
* formula: (adc pin value / adc precision * max voltage) + calibration
|
||||||
|
*/
|
||||||
|
float getVoltage()
|
||||||
|
{
|
||||||
|
return bat->getVoltage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mapped battery level (0 - 100) based on voltage
|
||||||
|
* important: voltage can drop when a load is applied, so its only an estimate
|
||||||
|
*/
|
||||||
|
int8_t getBatteryLevel()
|
||||||
|
{
|
||||||
|
return bat->getLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured calibration value
|
||||||
|
* a offset value to fine-tune the calculated voltage.
|
||||||
|
*/
|
||||||
|
float getCalibration()
|
||||||
|
{
|
||||||
|
return bat->getCalibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the voltage calibration offset value
|
||||||
|
* a offset value to fine-tune the calculated voltage.
|
||||||
|
*/
|
||||||
|
void setCalibration(float offset)
|
||||||
|
{
|
||||||
|
bat->setCalibration(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the voltage multiplier value
|
||||||
|
* A multiplier that may need adjusting for different voltage divider setups
|
||||||
|
*/
|
||||||
|
void setVoltageMultiplier(float multiplier)
|
||||||
|
{
|
||||||
|
bat->setVoltageMultiplier(multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the voltage multiplier value
|
||||||
|
* A multiplier that may need adjusting for different voltage divider setups
|
||||||
|
*/
|
||||||
|
float getVoltageMultiplier()
|
||||||
|
{
|
||||||
|
return bat->getVoltageMultiplier();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get auto-off feature enabled status
|
||||||
|
* is auto-off enabled, true/false
|
||||||
|
*/
|
||||||
|
bool getAutoOffEnabled()
|
||||||
|
{
|
||||||
|
return autoOffEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set auto-off feature status
|
||||||
|
*/
|
||||||
|
void setAutoOffEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
autoOffEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get auto-off threshold in percent (0-100)
|
||||||
|
*/
|
||||||
|
int8_t getAutoOffThreshold()
|
||||||
|
{
|
||||||
|
return autoOffThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set auto-off threshold in percent (0-100)
|
||||||
|
*/
|
||||||
|
void setAutoOffThreshold(int8_t threshold)
|
||||||
|
{
|
||||||
|
autoOffThreshold = min((int8_t)100, max((int8_t)0, threshold));
|
||||||
|
// when low power indicator is enabled the auto-off threshold cannot be above indicator threshold
|
||||||
|
autoOffThreshold = lowPowerIndicatorEnabled /*&& autoOffEnabled*/ ? min(lowPowerIndicatorThreshold-1, (int)autoOffThreshold) : autoOffThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get low-power-indicator feature enabled status
|
||||||
|
* is the low-power-indicator enabled, true/false
|
||||||
|
*/
|
||||||
|
bool getLowPowerIndicatorEnabled()
|
||||||
|
{
|
||||||
|
return lowPowerIndicatorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set low-power-indicator feature status
|
||||||
|
*/
|
||||||
|
void setLowPowerIndicatorEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
lowPowerIndicatorEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get low-power-indicator preset to activate when low power is detected
|
||||||
|
*/
|
||||||
|
int8_t getLowPowerIndicatorPreset()
|
||||||
|
{
|
||||||
|
return lowPowerIndicatorPreset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set low-power-indicator preset to activate when low power is detected
|
||||||
|
*/
|
||||||
|
void setLowPowerIndicatorPreset(int8_t presetId)
|
||||||
|
{
|
||||||
|
// String tmp = ""; For what ever reason this doesn't work :(
|
||||||
|
// lowPowerIndicatorPreset = getPresetName(presetId, tmp) ? presetId : lowPowerIndicatorPreset;
|
||||||
|
lowPowerIndicatorPreset = presetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get low-power-indicator threshold in percent (0-100)
|
||||||
|
*/
|
||||||
|
int8_t getLowPowerIndicatorThreshold()
|
||||||
|
{
|
||||||
|
return lowPowerIndicatorThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set low-power-indicator threshold in percent (0-100)
|
||||||
|
*/
|
||||||
|
void setLowPowerIndicatorThreshold(int8_t threshold)
|
||||||
|
{
|
||||||
|
lowPowerIndicatorThreshold = threshold;
|
||||||
|
// when auto-off is enabled the indicator threshold cannot be below auto-off threshold
|
||||||
|
lowPowerIndicatorThreshold = autoOffEnabled /*&& lowPowerIndicatorEnabled*/ ? max(autoOffThreshold+1, (int)lowPowerIndicatorThreshold) : max(5, (int)lowPowerIndicatorThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get low-power-indicator duration in seconds
|
||||||
|
*/
|
||||||
|
int8_t getLowPowerIndicatorDuration()
|
||||||
|
{
|
||||||
|
return lowPowerIndicatorDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set low-power-indicator duration in seconds
|
||||||
|
*/
|
||||||
|
void setLowPowerIndicatorDuration(int8_t duration)
|
||||||
|
{
|
||||||
|
lowPowerIndicatorDuration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get low-power-indicator status when the indication is done this returns true
|
||||||
|
*/
|
||||||
|
bool getLowPowerIndicatorDone()
|
||||||
|
{
|
||||||
|
return lowPowerIndicationDone;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
|
const char UsermodBattery::_name[] PROGMEM = "Battery";
|
||||||
|
const char UsermodBattery::_readInterval[] PROGMEM = "interval";
|
||||||
|
const char UsermodBattery::_enabled[] PROGMEM = "enabled";
|
||||||
|
const char UsermodBattery::_threshold[] PROGMEM = "threshold";
|
||||||
|
const char UsermodBattery::_preset[] PROGMEM = "preset";
|
||||||
|
const char UsermodBattery::_duration[] PROGMEM = "duration";
|
||||||
|
const char UsermodBattery::_init[] PROGMEM = "init";
|
||||||
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.
|
||||||
302
usermods/Cronixie/usermod_cronixie.h
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#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(F("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(F("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);
|
||||||
|
setCronixie();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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_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_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
|
; USERMOD_DHT_STATS - For debug, report delay stats
|
||||||
|
|
||||||
[env:d1_mini_usermod_dht_C]
|
[env:d1_mini_usermod_dht_C]
|
||||||
extends = env:d1_mini
|
extends = env:d1_mini
|
||||||
build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS
|
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
|
https://github.com/alwynallan/DHT_nonblocking
|
||||||
|
|
||||||
[env:custom32_LEDPIN_16_usermod_dht_C]
|
[env:custom32_LEDPIN_16_usermod_dht_C]
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
# DHT Temperature/Humidity sensor usermod
|
# DHT Temperature/Humidity sensor usermod
|
||||||
|
|
||||||
This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor.
|
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
|
## Installation
|
||||||
|
|
||||||
@@ -11,12 +15,13 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
|||||||
|
|
||||||
### Define Your Options
|
### 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_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_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_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 60 seconds
|
* `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 90 seconds
|
* `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
|
* `USERMOD_DHT_STATS` - For debug, report delay stats
|
||||||
|
|
||||||
## Project link
|
## Project link
|
||||||
@@ -29,10 +34,12 @@ If you are using `platformio_override.ini`, you should be able to refresh the ta
|
|||||||
|
|
||||||
|
|
||||||
## Change Log
|
## 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
|
2020-02-04
|
||||||
* Change default QuinLed pin to Q2
|
* 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
|
* Add some more (optional) stats
|
||||||
2020-02-03
|
2020-02-03
|
||||||
* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking
|
* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include <dht_nonblocking.h>
|
#include <dht_nonblocking.h>
|
||||||
|
|
||||||
@@ -45,7 +49,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// how many seconds after boot to take first measurement, 90 seconds
|
// how many seconds after boot to take first measurement, 90 seconds
|
||||||
// 90 gives enough time to OTA update firmware if this crashses
|
// 90 gives enough time to OTA update firmware if this crashes
|
||||||
#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
|
#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT
|
||||||
#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
|
#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000
|
||||||
#endif
|
#endif
|
||||||
@@ -62,6 +66,10 @@ class UsermodDHT : public Usermod {
|
|||||||
float humidity, temperature = 0;
|
float humidity, temperature = 0;
|
||||||
bool initializing = true;
|
bool initializing = true;
|
||||||
bool disabled = false;
|
bool disabled = false;
|
||||||
|
#ifdef USERMOD_DHT_MQTT
|
||||||
|
char dhtMqttTopic[64];
|
||||||
|
size_t dhtMqttTopicLen;
|
||||||
|
#endif
|
||||||
#ifdef USERMOD_DHT_STATS
|
#ifdef USERMOD_DHT_STATS
|
||||||
unsigned long nextResetStatsTime = 0;
|
unsigned long nextResetStatsTime = 0;
|
||||||
uint16_t updates = 0;
|
uint16_t updates = 0;
|
||||||
@@ -76,6 +84,10 @@ class UsermodDHT : public Usermod {
|
|||||||
void setup() {
|
void setup() {
|
||||||
nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
|
nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT;
|
||||||
lastReadTime = millis();
|
lastReadTime = millis();
|
||||||
|
#ifdef USERMOD_DHT_MQTT
|
||||||
|
sprintf(dhtMqttTopic, "%s/dht", mqttDeviceTopic);
|
||||||
|
dhtMqttTopicLen = strlen(dhtMqttTopic);
|
||||||
|
#endif
|
||||||
#ifdef USERMOD_DHT_STATS
|
#ifdef USERMOD_DHT_STATS
|
||||||
nextResetStatsTime = millis() + 60*60*1000;
|
nextResetStatsTime = millis() + 60*60*1000;
|
||||||
#endif
|
#endif
|
||||||
@@ -110,6 +122,25 @@ class UsermodDHT : public Usermod {
|
|||||||
temperature = tempC * 9 / 5 + 32;
|
temperature = tempC * 9 / 5 + 32;
|
||||||
#endif
|
#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;
|
nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL;
|
||||||
lastReadTime = millis();
|
lastReadTime = millis();
|
||||||
initializing = false;
|
initializing = false;
|
||||||
|
|||||||
@@ -22,8 +22,12 @@
|
|||||||
|
|
||||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||||
class MyExampleUsermod : public Usermod {
|
class MyExampleUsermod : public Usermod {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Private class members. You can declare variables and functions only accessible to your usermod here
|
// Private class members. You can declare variables and functions only accessible to your usermod here
|
||||||
|
bool enabled = false;
|
||||||
|
bool initDone = false;
|
||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
|
|
||||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||||
@@ -37,15 +41,56 @@ class MyExampleUsermod : public Usermod {
|
|||||||
long testLong;
|
long testLong;
|
||||||
int8_t testPins[2];
|
int8_t testPins[2];
|
||||||
|
|
||||||
|
// string that are used multiple time (this will save some flash memory)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
|
||||||
|
|
||||||
|
// any private methods should go here (non-inline method should be defined out of class)
|
||||||
|
void publishMqtt(const char* state, bool retain = false); // example for publishing MQTT message
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//Functions called by WLED
|
|
||||||
|
// non WLED related methods, may be used for data exchange between usermods (non-inline methods should be defined out of class)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable the usermod
|
||||||
|
*/
|
||||||
|
inline void enable(bool enable) { enabled = enable; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get usermod enabled/disabled state
|
||||||
|
*/
|
||||||
|
inline bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
// in such case add the following to another usermod:
|
||||||
|
// in private vars:
|
||||||
|
// #ifdef USERMOD_EXAMPLE
|
||||||
|
// MyExampleUsermod* UM;
|
||||||
|
// #endif
|
||||||
|
// in setup()
|
||||||
|
// #ifdef USERMOD_EXAMPLE
|
||||||
|
// UM = (MyExampleUsermod*) usermods.lookup(USERMOD_ID_EXAMPLE);
|
||||||
|
// #endif
|
||||||
|
// somewhere in loop() or other member method
|
||||||
|
// #ifdef USERMOD_EXAMPLE
|
||||||
|
// if (UM != nullptr) isExampleEnabled = UM->isEnabled();
|
||||||
|
// if (!isExampleEnabled) UM->enable(true);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
|
||||||
|
// methods called by WLED (can be inlined as they are called only once but if you call them explicitly define them out of class)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* setup() is called once at boot. WiFi is not yet connected at this point.
|
* setup() is called once at boot. WiFi is not yet connected at this point.
|
||||||
|
* readFromConfig() is called prior to setup()
|
||||||
* You can use it to initialize variables, sensors or similar.
|
* You can use it to initialize variables, sensors or similar.
|
||||||
*/
|
*/
|
||||||
void setup() {
|
void setup() override {
|
||||||
|
// do your set-up here
|
||||||
//Serial.println("Hello from my usermod!");
|
//Serial.println("Hello from my usermod!");
|
||||||
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +98,7 @@ class MyExampleUsermod : public Usermod {
|
|||||||
* connected() is called every time the WiFi is (re)connected
|
* connected() is called every time the WiFi is (re)connected
|
||||||
* Use it to initialize network interfaces
|
* Use it to initialize network interfaces
|
||||||
*/
|
*/
|
||||||
void connected() {
|
void connected() override {
|
||||||
//Serial.println("Connected to WiFi!");
|
//Serial.println("Connected to WiFi!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +113,12 @@ class MyExampleUsermod : public Usermod {
|
|||||||
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
|
* 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds.
|
||||||
* Instead, use a timer check as shown here.
|
* Instead, use a timer check as shown here.
|
||||||
*/
|
*/
|
||||||
void loop() {
|
void loop() override {
|
||||||
|
// if usermod is disabled or called during strip updating just exit
|
||||||
|
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||||
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
|
// do your magic here
|
||||||
if (millis() - lastTime > 1000) {
|
if (millis() - lastTime > 1000) {
|
||||||
//Serial.println("I'm alive!");
|
//Serial.println("I'm alive!");
|
||||||
lastTime = millis();
|
lastTime = millis();
|
||||||
@@ -81,28 +131,39 @@ class MyExampleUsermod : public Usermod {
|
|||||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
* 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
|
* Below it is shown how this could be used for e.g. a light sensor
|
||||||
*/
|
*/
|
||||||
/*
|
void addToJsonInfo(JsonObject& root) override
|
||||||
void addToJsonInfo(JsonObject& root)
|
|
||||||
{
|
{
|
||||||
int reading = 20;
|
// if "u" object does not exist yet wee need to create it
|
||||||
//this code adds "u":{"Light":[20," lux"]} to the info object
|
|
||||||
JsonObject user = root["u"];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull()) user = root.createNestedObject("u");
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
JsonArray lightArr = user.createNestedArray("Light"); //name
|
//this code adds "u":{"ExampleUsermod":[20," lux"]} to the info object
|
||||||
lightArr.add(reading); //value
|
//int reading = 20;
|
||||||
lightArr.add(" lux"); //unit
|
//JsonArray lightArr = user.createNestedArray(FPSTR(_name))); //name
|
||||||
|
//lightArr.add(reading); //value
|
||||||
|
//lightArr.add(F(" lux")); //unit
|
||||||
|
|
||||||
|
// if you are implementing a sensor usermod, you may publish sensor data
|
||||||
|
//JsonObject sensor = root[F("sensor")];
|
||||||
|
//if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
|
||||||
|
//temp = sensor.createNestedArray(F("light"));
|
||||||
|
//temp.add(reading);
|
||||||
|
//temp.add(F("lux"));
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
* 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
|
* Values in the state object may be modified by connected clients
|
||||||
*/
|
*/
|
||||||
void addToJsonState(JsonObject& root)
|
void addToJsonState(JsonObject& root) override
|
||||||
{
|
{
|
||||||
//root["user0"] = userVar0;
|
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||||
|
|
||||||
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
|
if (usermod.isNull()) usermod = root.createNestedObject(FPSTR(_name));
|
||||||
|
|
||||||
|
//usermod["user0"] = userVar0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -110,9 +171,16 @@ class MyExampleUsermod : public Usermod {
|
|||||||
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
* 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
|
* Values in the state object may be modified by connected clients
|
||||||
*/
|
*/
|
||||||
void readFromJsonState(JsonObject& root)
|
void readFromJsonState(JsonObject& root) override
|
||||||
{
|
{
|
||||||
userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
|
|
||||||
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
|
if (!usermod.isNull()) {
|
||||||
|
// expect JSON usermod data in usermod name object: {"ExampleUsermod:{"user0":10}"}
|
||||||
|
userVar0 = usermod["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value
|
||||||
|
}
|
||||||
|
// you can as well check WLED state JSON keys
|
||||||
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
//if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,10 +220,12 @@ class MyExampleUsermod : public Usermod {
|
|||||||
*
|
*
|
||||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject& root)
|
void addToConfig(JsonObject& root) override
|
||||||
{
|
{
|
||||||
JsonObject top = root.createNestedObject("exampleUsermod");
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top["great"] = userVar0; //save these vars persistently whenever settings are saved
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
//save these vars persistently whenever settings are saved
|
||||||
|
top["great"] = userVar0;
|
||||||
top["testBool"] = testBool;
|
top["testBool"] = testBool;
|
||||||
top["testInt"] = testInt;
|
top["testInt"] = testInt;
|
||||||
top["testLong"] = testLong;
|
top["testLong"] = testLong;
|
||||||
@@ -183,12 +253,12 @@ class MyExampleUsermod : public Usermod {
|
|||||||
*
|
*
|
||||||
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
||||||
*/
|
*/
|
||||||
bool readFromConfig(JsonObject& root)
|
bool readFromConfig(JsonObject& root) override
|
||||||
{
|
{
|
||||||
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
// 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)
|
// 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["exampleUsermod"];
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
|
||||||
bool configComplete = !top.isNull();
|
bool configComplete = !top.isNull();
|
||||||
|
|
||||||
@@ -201,6 +271,8 @@ class MyExampleUsermod : public Usermod {
|
|||||||
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
|
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
|
||||||
configComplete &= getJsonValue(top["testInt"], testInt, 42);
|
configComplete &= getJsonValue(top["testInt"], testInt, 42);
|
||||||
configComplete &= getJsonValue(top["testLong"], testLong, -42424242);
|
configComplete &= getJsonValue(top["testLong"], testLong, -42424242);
|
||||||
|
|
||||||
|
// "pin" fields have special handling in settings page (or some_pin as well)
|
||||||
configComplete &= getJsonValue(top["pin"][0], testPins[0], -1);
|
configComplete &= getJsonValue(top["pin"][0], testPins[0], -1);
|
||||||
configComplete &= getJsonValue(top["pin"][1], testPins[1], -1);
|
configComplete &= getJsonValue(top["pin"][1], testPins[1], -1);
|
||||||
|
|
||||||
@@ -208,11 +280,102 @@ class MyExampleUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* appendConfigData() is called when user enters usermod settings page
|
||||||
|
* it may add additional metadata for certain entry fields (adding drop down is possible)
|
||||||
|
* be careful not to add too much as oappend() buffer is limited to 3k
|
||||||
|
*/
|
||||||
|
void appendConfigData() override
|
||||||
|
{
|
||||||
|
oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":great")); oappend(SET_F("',1,'<i>(this is a great config value)</i>');"));
|
||||||
|
oappend(SET_F("addInfo('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F(":testString")); oappend(SET_F("',1,'enter any string you want');"));
|
||||||
|
oappend(SET_F("dd=addDropdown('")); oappend(String(FPSTR(_name)).c_str()); oappend(SET_F("','testInt');"));
|
||||||
|
oappend(SET_F("addOption(dd,'Nothing',0);"));
|
||||||
|
oappend(SET_F("addOption(dd,'Everything',42);"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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() override
|
||||||
|
{
|
||||||
|
//strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handleButton() can be used to override default button behaviour. Returning true
|
||||||
|
* will prevent button working in a default way.
|
||||||
|
* Replicating button.cpp
|
||||||
|
*/
|
||||||
|
bool handleButton(uint8_t b) override {
|
||||||
|
yield();
|
||||||
|
// ignore certain button types as they may have other consequences
|
||||||
|
if (!enabled
|
||||||
|
|| buttonType[b] == BTN_TYPE_NONE
|
||||||
|
|| buttonType[b] == BTN_TYPE_RESERVED
|
||||||
|
|| buttonType[b] == BTN_TYPE_PIR_SENSOR
|
||||||
|
|| buttonType[b] == BTN_TYPE_ANALOG
|
||||||
|
|| buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handled = false;
|
||||||
|
// do your button handling here
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
/**
|
||||||
|
* handling of MQTT message
|
||||||
|
* topic only contains stripped topic (part after /wled/MAC)
|
||||||
|
*/
|
||||||
|
bool onMqttMessage(char* topic, char* payload) override {
|
||||||
|
// check if we received a command
|
||||||
|
//if (strlen(topic) == 8 && strncmp_P(topic, PSTR("/command"), 8) == 0) {
|
||||||
|
// String action = payload;
|
||||||
|
// if (action == "on") {
|
||||||
|
// enabled = true;
|
||||||
|
// return true;
|
||||||
|
// } else if (action == "off") {
|
||||||
|
// enabled = false;
|
||||||
|
// return true;
|
||||||
|
// } else if (action == "toggle") {
|
||||||
|
// enabled = !enabled;
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onMqttConnect() is called when MQTT connection is established
|
||||||
|
*/
|
||||||
|
void onMqttConnect(bool sessionPresent) override {
|
||||||
|
// do any MQTT related initialisation here
|
||||||
|
//publishMqtt("I am alive!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onStateChanged() is used to detect WLED state change
|
||||||
|
* @mode parameter is CALL_MODE_... parameter used for notifications
|
||||||
|
*/
|
||||||
|
void onStateChange(uint8_t mode) override {
|
||||||
|
// do something if WLED state changed (color, brightness, effect, preset, etc)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
|
* 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.
|
* This could be used in the future for the system to determine whether your usermod is installed.
|
||||||
*/
|
*/
|
||||||
uint16_t getId()
|
uint16_t getId() override
|
||||||
{
|
{
|
||||||
return USERMOD_ID_EXAMPLE;
|
return USERMOD_ID_EXAMPLE;
|
||||||
}
|
}
|
||||||
@@ -220,3 +383,24 @@ class MyExampleUsermod : public Usermod {
|
|||||||
//More methods can be added in the future, this example will then be extended.
|
//More methods can be added in the future, this example will then be extended.
|
||||||
//Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
|
//Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class!
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// add more strings here to reduce flash memory usage
|
||||||
|
const char MyExampleUsermod::_name[] PROGMEM = "ExampleUsermod";
|
||||||
|
const char MyExampleUsermod::_enabled[] PROGMEM = "enabled";
|
||||||
|
|
||||||
|
|
||||||
|
// implementation of non-inline member methods
|
||||||
|
|
||||||
|
void MyExampleUsermod::publishMqtt(const char* state, bool retain)
|
||||||
|
{
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
|
if (WLED_MQTT_CONNECTED) {
|
||||||
|
char subuf[64];
|
||||||
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
|
strcat_P(subuf, PSTR("/example"));
|
||||||
|
mqtt->publish(subuf, 0, retain, state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI {
|
|||||||
private:
|
private:
|
||||||
uint8_t digits[NUM_DIGITS];
|
uint8_t digits[NUM_DIGITS];
|
||||||
|
|
||||||
|
|
||||||
// These read 16- and 32-bit types from the SD card file.
|
// These read 16- and 32-bit types from the SD card file.
|
||||||
// BMP data is stored little-endian, Arduino is little-endian too.
|
// BMP data is stored little-endian, Arduino is little-endian too.
|
||||||
// May need to reverse subscript order if porting elsewhere.
|
// May need to reverse subscript order if porting elsewhere.
|
||||||
@@ -33,7 +34,16 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t output_buffer[TFT_HEIGHT][TFT_WIDTH];
|
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.
|
// 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.
|
// Unfortunately, they aren't part of the library itself, so I had to copy them.
|
||||||
@@ -41,44 +51,69 @@ private:
|
|||||||
|
|
||||||
//// BEGIN STOLEN CODE
|
//// 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) {
|
bool drawBin(const char *filename) {
|
||||||
fs::File bmpFS;
|
fs::File bmpFS;
|
||||||
|
|
||||||
|
|
||||||
// Open requested file on SD card
|
// Open requested file on SD card
|
||||||
bmpFS = WLED_FS.open(filename, "r");
|
bmpFS = WLED_FS.open(filename, "r");
|
||||||
|
|
||||||
if (!bmpFS)
|
size_t sz = bmpFS.size();
|
||||||
{
|
if (sz > 64800) {
|
||||||
Serial.print(F("File not found: "));
|
bmpFS.close();
|
||||||
Serial.println(filename);
|
return false;
|
||||||
return(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t sz = bmpFS.size();
|
uint16_t r, g, b, dimming = 255;
|
||||||
if (sz <= 64800)
|
int16_t row, col;
|
||||||
{
|
|
||||||
bool oldSwapBytes = getSwapBytes();
|
|
||||||
setSwapBytes(true);
|
|
||||||
|
|
||||||
int16_t h = sz / (135 * 2);
|
|
||||||
|
|
||||||
//draw img that is shorter than 240pix into the center
|
//draw img that is shorter than 240pix into the center
|
||||||
int16_t y = (height() - h) /2;
|
w = 135;
|
||||||
|
h = sz / (w * 2);
|
||||||
|
x = 0;
|
||||||
|
y = (height() - h) /2;
|
||||||
|
|
||||||
bmpFS.read((uint8_t *) output_buffer,sz);
|
uint8_t lineBuffer[w * 2];
|
||||||
|
|
||||||
if (!realtimeMode || realtimeOverride) strip.service();
|
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) strip.service();
|
||||||
|
|
||||||
pushImage(0, y, 135, h, (uint16_t *)output_buffer);
|
// 0,0 coordinates are top left
|
||||||
|
for (row = 0; row < h; row++) {
|
||||||
|
|
||||||
setSwapBytes(oldSwapBytes);
|
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();
|
bmpFS.close();
|
||||||
|
|
||||||
return(true);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool drawBmp(const char *filename) {
|
bool drawBmp(const char *filename) {
|
||||||
@@ -87,78 +122,175 @@ private:
|
|||||||
// Open requested file on SD card
|
// Open requested file on SD card
|
||||||
bmpFS = WLED_FS.open(filename, "r");
|
bmpFS = WLED_FS.open(filename, "r");
|
||||||
|
|
||||||
if (!bmpFS)
|
uint32_t seekOffset, headerSize, paletteSize = 0;
|
||||||
{
|
int16_t row;
|
||||||
Serial.print(F("File not found: "));
|
uint16_t r, g, b, dimming = 255, bitDepth;
|
||||||
Serial.println(filename);
|
|
||||||
return(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t seekOffset;
|
|
||||||
int16_t w, h, row;
|
|
||||||
uint8_t r, g, b;
|
|
||||||
|
|
||||||
uint16_t magic = read16(bmpFS);
|
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!"));
|
Serial.println(F("BMP not found!"));
|
||||||
bmpFS.close();
|
bmpFS.close();
|
||||||
return(false);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (magic != 0x4D42) {
|
(void) read32(bmpFS); // filesize in bytes
|
||||||
Serial.print(F("File not a BMP. Magic: "));
|
(void) read32(bmpFS); // reserved
|
||||||
Serial.println(magic);
|
seekOffset = read32(bmpFS); // start of bitmap
|
||||||
bmpFS.close();
|
headerSize = read32(bmpFS); // header size
|
||||||
return(false);
|
w = read32(bmpFS); // width
|
||||||
}
|
h = read32(bmpFS); // height
|
||||||
|
(void) read16(bmpFS); // color planes (must be 1)
|
||||||
|
bitDepth = read16(bmpFS);
|
||||||
|
|
||||||
read32(bmpFS);
|
if (read32(bmpFS) != 0 || (bitDepth != 24 && bitDepth != 1 && bitDepth != 4 && bitDepth != 8)) {
|
||||||
read32(bmpFS);
|
|
||||||
seekOffset = read32(bmpFS);
|
|
||||||
read32(bmpFS);
|
|
||||||
w = read32(bmpFS);
|
|
||||||
h = read32(bmpFS);
|
|
||||||
|
|
||||||
if ((read16(bmpFS) != 1) || (read16(bmpFS) != 24) || (read32(bmpFS) != 0)) {
|
|
||||||
Serial.println(F("BMP format not recognized."));
|
Serial.println(F("BMP format not recognized."));
|
||||||
bmpFS.close();
|
bmpFS.close();
|
||||||
return(false);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t palette[256];
|
||||||
|
if (bitDepth <= 8) // 1,4,8 bit bitmap: read color palette
|
||||||
|
{
|
||||||
|
(void) read32(bmpFS); (void) read32(bmpFS); (void) read32(bmpFS); // size, w resolution, h resolution
|
||||||
|
paletteSize = read32(bmpFS);
|
||||||
|
if (paletteSize == 0) paletteSize = 1 << 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
|
// draw img that is shorter than 240pix into the center
|
||||||
int16_t y = (height() - h) /2;
|
x = (width() - w) /2;
|
||||||
|
y = (height() - h) /2;
|
||||||
|
|
||||||
bool oldSwapBytes = getSwapBytes();
|
|
||||||
setSwapBytes(true);
|
|
||||||
bmpFS.seek(seekOffset);
|
bmpFS.seek(seekOffset);
|
||||||
|
|
||||||
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
uint32_t lineSize = ((bitDepth * w +31) >> 5) * 4;
|
||||||
uint8_t lineBuffer[w * 3 + padding];
|
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
|
// row is decremented as the BMP image is drawn bottom up
|
||||||
for (row = h-1; row >= 0; row--) {
|
for (row = h-1; row >= 0; row--) {
|
||||||
if ((row & 0b00000111) == serviceStrip) strip.service(); //still refresh backlight to mitigate stutter every few rows
|
if ((row & 0b00000111) == serviceStrip) strip.service(); //still refresh backlight to mitigate stutter every few rows
|
||||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||||
uint8_t* bptr = 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++)
|
for (uint16_t col = 0; col < w; col++)
|
||||||
{
|
{
|
||||||
|
if (bitDepth == 24) {
|
||||||
b = *bptr++;
|
b = *bptr++;
|
||||||
g = *bptr++;
|
g = *bptr++;
|
||||||
r = *bptr++;
|
r = *bptr++;
|
||||||
output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
} 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 dim 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);
|
drawBuffer();
|
||||||
setSwapBytes(oldSwapBytes);
|
|
||||||
|
|
||||||
bmpFS.close();
|
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:
|
public:
|
||||||
TFTs() : TFT_eSPI(), chip_select()
|
TFTs() : TFT_eSPI(), chip_select()
|
||||||
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
||||||
@@ -168,6 +300,9 @@ public:
|
|||||||
// A digit of 0xFF means blank the screen.
|
// A digit of 0xFF means blank the screen.
|
||||||
const static uint8_t blanked = 255;
|
const static uint8_t blanked = 255;
|
||||||
|
|
||||||
|
uint8_t tubeSegment = 1;
|
||||||
|
uint8_t digitOffset = 0;
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
pinMode(TFT_ENABLE_PIN, OUTPUT);
|
pinMode(TFT_ENABLE_PIN, OUTPUT);
|
||||||
digitalWrite(TFT_ENABLE_PIN, HIGH); //enable displays on boot
|
digitalWrite(TFT_ENABLE_PIN, HIGH); //enable displays on boot
|
||||||
@@ -182,27 +317,53 @@ public:
|
|||||||
|
|
||||||
void showDigit(uint8_t digit) {
|
void showDigit(uint8_t digit) {
|
||||||
chip_select.setDigit(digit);
|
chip_select.setDigit(digit);
|
||||||
|
uint8_t digitToDraw = digits[digit];
|
||||||
|
if (digitToDraw < 10) digitToDraw += digitOffset;
|
||||||
|
|
||||||
if (digits[digit] == blanked) {
|
if (digitToDraw == blanked) {
|
||||||
fillScreen(TFT_BLACK);
|
fillScreen(TFT_BLACK); return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Filenames are no bigger than "255.bmp\0"
|
// 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];
|
char file_name[10];
|
||||||
sprintf(file_name, "/%d.bmp", digits[digit]);
|
// Fastest, raw RGB565
|
||||||
|
sprintf(file_name, "/%d.bin", digitToDraw);
|
||||||
if (WLED_FS.exists(file_name)) {
|
if (WLED_FS.exists(file_name)) {
|
||||||
drawBmp(file_name);
|
if (drawBin(file_name)) bufferedDigit = digitToDraw;
|
||||||
} else {
|
return;
|
||||||
sprintf(file_name, "/%d.bin", digits[digit]);
|
|
||||||
drawBin(file_name);
|
|
||||||
}
|
}
|
||||||
|
// 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) {
|
void setDigit(uint8_t digit, uint8_t value, show_t show=yes) {
|
||||||
uint8_t old_value = digits[digit];
|
uint8_t old_value = digits[digit];
|
||||||
digits[digit] = value;
|
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)) {
|
if (show != no && (old_value != value || show == force)) {
|
||||||
showDigit(digit);
|
showDigit(digit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!
|
Code is largely based on https://github.com/SmittyHalibut/EleksTubeHAX by Mark Smith!
|
||||||
|
|
||||||
Supported:
|
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
|
- Background lighting
|
||||||
- Power button
|
- All 4 hardware buttons
|
||||||
- RTC (with RTC usermod)
|
- RTC (with RTC usermod)
|
||||||
- Standard WLED time features (NTP, DST, timezones)
|
- Standard WLED time features (NTP, DST, timezones)
|
||||||
|
|
||||||
Not supported:
|
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
|
## Installation
|
||||||
|
|
||||||
@@ -25,7 +26,20 @@ Use LED pin 12, relay pin 27 and button pin 34.
|
|||||||
|
|
||||||
## Use of RGB565 images
|
## 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.
|
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 that this format cannot be handled by common image programs and that an extra conversion step is needed.
|
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`)
|
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.
|
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 {
|
class ElekstubeIPSUsermod : public Usermod {
|
||||||
private:
|
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;
|
TFTs tfts;
|
||||||
void updateClockDisplay(TFTs::show_t show=TFTs::yes) {
|
void updateClockDisplay(TFTs::show_t show=TFTs::yes) {
|
||||||
bool set[6] = {false};
|
bool set[6] = {false};
|
||||||
@@ -21,6 +28,8 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
set[i] = false; //display HHMMSS time
|
set[i] = false; //display HHMMSS time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t hr = hour(localTime);
|
uint8_t hr = hour(localTime);
|
||||||
uint8_t hrTens = hr/10;
|
uint8_t hrTens = hr/10;
|
||||||
uint8_t mi = minute(localTime);
|
uint8_t mi = minute(localTime);
|
||||||
@@ -37,6 +46,10 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
uint8_t lastBri;
|
||||||
|
uint32_t lastCols[6];
|
||||||
|
TFTs::show_t fshow=TFTs::yes;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
tfts.begin();
|
tfts.begin();
|
||||||
tfts.fillScreen(TFT_BLACK);
|
tfts.fillScreen(TFT_BLACK);
|
||||||
@@ -47,10 +60,90 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (toki.isTick()) {
|
if (!toki.isTick()) return;
|
||||||
updateLocalTime();
|
updateLocalTime();
|
||||||
updateClockDisplay();
|
|
||||||
|
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()
|
uint16_t getId()
|
||||||
@@ -58,3 +151,8 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
return USERMOD_ID_ELEKSTUBE_IPS;
|
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
|
## Features
|
||||||
- SSD1306 128x32 and 128x64 I2C OLED display
|
- SSD1306 128x32 and 128x64 I2C OLED display
|
||||||
- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect)
|
- 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
|
- Dallas temperature sensor
|
||||||
- Reporting temperature to MQTT broker
|
- Reporting temperature to MQTT broker
|
||||||
|
|
||||||
@@ -39,15 +39,15 @@ default_envs = esp07
|
|||||||
...
|
...
|
||||||
lib_deps_external =
|
lib_deps_external =
|
||||||
...
|
...
|
||||||
#For use SSD1306 OLED display uncomment following
|
#To use the SSD1306 OLED display, uncomment following
|
||||||
U8g2@~2.27.3
|
U8g2@~2.27.3
|
||||||
#For Dallas sensor uncomment following 2 lines
|
#For Dallas sensor, uncomment the following 2 lines
|
||||||
DallasTemperature@~3.8.0
|
DallasTemperature@~3.8.0
|
||||||
OneWire@~2.3.5
|
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
|
```ini
|
||||||
# platformio.ini
|
# platformio.ini
|
||||||
...
|
...
|
||||||
@@ -60,7 +60,7 @@ default_envs = esp07
|
|||||||
...
|
...
|
||||||
lib_deps_external =
|
lib_deps_external =
|
||||||
...
|
...
|
||||||
#For use SSD1306 OLED display uncomment following
|
#To use the SSD1306 OLED display, uncomment following
|
||||||
U8g2@~2.27.3
|
U8g2@~2.27.3
|
||||||
#For BME280 sensor uncomment following
|
#For BME280 sensor uncomment following
|
||||||
BME280@~3.0.0
|
BME280@~3.0.0
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
||||||
@@ -11,23 +15,23 @@ OneWire oneWire(13);
|
|||||||
DallasTemperature sensor(&oneWire);
|
DallasTemperature sensor(&oneWire);
|
||||||
long temptimer = millis();
|
long temptimer = millis();
|
||||||
long lastMeasure = 0;
|
long lastMeasure = 0;
|
||||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||||
|
|
||||||
// If display does not work or looks corrupted check the
|
// If display does not work or looks corrupted check the
|
||||||
// constructor reference:
|
// constructor reference:
|
||||||
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
|
||||||
// or check the gallery:
|
// or check the gallery:
|
||||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
// --> First choice of cheap I2C OLED 128X32 0.91"
|
||||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||||
// gets called once at boot. Do all initialization that doesn't depend on
|
// gets called once at boot. Do all initialization that doesn't depend on
|
||||||
// network here
|
// network here
|
||||||
void userSetup() {
|
void userSetup() {
|
||||||
sensor.begin(); //Start Dallas temperature sensor
|
sensor.begin(); //Start Dallas temperature sensor
|
||||||
u8x8.begin();
|
u8x8.begin();
|
||||||
//u8x8.setFlipMode(1); //Uncoment if using WLED Wemos shield
|
//u8x8.setFlipMode(1); //Un-comment if using WLED Wemos shield
|
||||||
u8x8.setPowerSave(0);
|
u8x8.setPowerSave(0);
|
||||||
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
|
||||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||||
@@ -67,7 +71,7 @@ void userLoop() {
|
|||||||
if (mqtt != nullptr)
|
if (mqtt != nullptr)
|
||||||
{
|
{
|
||||||
sensor.requestTemperatures();
|
sensor.requestTemperatures();
|
||||||
//Gets prefered temperature scale based on selection in definitions section
|
//Gets preferred temperature scale based on selection in definitions section
|
||||||
#ifdef Celsius
|
#ifdef Celsius
|
||||||
float board_temperature = sensor.getTempCByIndex(0);
|
float board_temperature = sensor.getTempCByIndex(0);
|
||||||
#else
|
#else
|
||||||
@@ -134,11 +138,11 @@ void userLoop() {
|
|||||||
// First row with Wifi name
|
// First row with Wifi name
|
||||||
u8x8.setCursor(1, 0);
|
u8x8.setCursor(1, 0);
|
||||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
// Print `~` char to indicate that SSID is longer than our display
|
||||||
if (knownSsid.length() > u8x8.getCols())
|
if (knownSsid.length() > u8x8.getCols())
|
||||||
u8x8.print("~");
|
u8x8.print("~");
|
||||||
|
|
||||||
// Second row with IP or Psssword
|
// Second row with IP or Password
|
||||||
u8x8.setCursor(1, 1);
|
u8x8.setCursor(1, 1);
|
||||||
// Print password in AP mode and if led is OFF.
|
// Print password in AP mode and if led is OFF.
|
||||||
if (apActive && bri == 0)
|
if (apActive && bri == 0)
|
||||||
@@ -148,58 +152,14 @@ void userLoop() {
|
|||||||
|
|
||||||
// Third row with mode name
|
// Third row with mode name
|
||||||
u8x8.setCursor(2, 2);
|
u8x8.setCursor(2, 2);
|
||||||
uint8_t qComma = 0;
|
char lineBuffer[17];
|
||||||
bool insideQuotes = false;
|
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||||
uint8_t printedChars = 0;
|
u8x8.print(lineBuffer);
|
||||||
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;
|
|
||||||
u8x8.print(singleJsonSymbol);
|
|
||||||
printedChars++;
|
|
||||||
}
|
|
||||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Fourth row with palette name
|
// Fourth row with palette name
|
||||||
u8x8.setCursor(2, 3);
|
u8x8.setCursor(2, 3);
|
||||||
qComma = 0;
|
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||||
insideQuotes = false;
|
u8x8.print(lineBuffer);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
|
||||||
@@ -6,7 +10,7 @@
|
|||||||
|
|
||||||
void UpdateBME280Data();
|
void UpdateBME280Data();
|
||||||
|
|
||||||
#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit
|
#define Celsius // Show temperature measurement in Celsius otherwise is in Fahrenheit
|
||||||
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
|
BME280I2C bme; // Default : forced mode, standby time = 1000 ms
|
||||||
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
|
// Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off,
|
||||||
|
|
||||||
@@ -16,14 +20,14 @@ uint8_t SDA_PIN = 21;
|
|||||||
#else //ESP8266 boards
|
#else //ESP8266 boards
|
||||||
uint8_t SCL_PIN = 5;
|
uint8_t SCL_PIN = 5;
|
||||||
uint8_t SDA_PIN = 4;
|
uint8_t SDA_PIN = 4;
|
||||||
// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8
|
// uint8_t RST_PIN = 16; // Un-comment for Heltec WiFi-Kit-8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//The SCL and SDA pins are defined here.
|
//The SCL and SDA pins are defined here.
|
||||||
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
|
//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21
|
||||||
#define U8X8_PIN_SCL SCL_PIN
|
#define U8X8_PIN_SCL SCL_PIN
|
||||||
#define U8X8_PIN_SDA SDA_PIN
|
#define U8X8_PIN_SDA SDA_PIN
|
||||||
//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8
|
//#define U8X8_PIN_RESET RST_PIN // Un-comment for Heltec WiFi-Kit-8
|
||||||
|
|
||||||
// If display does not work or looks corrupted check the
|
// If display does not work or looks corrupted check the
|
||||||
// constructor reference:
|
// constructor reference:
|
||||||
@@ -32,9 +36,9 @@ uint8_t SDA_PIN = 4;
|
|||||||
// https://github.com/olikraus/u8g2/wiki/gallery
|
// https://github.com/olikraus/u8g2/wiki/gallery
|
||||||
// --> First choise of cheap I2C OLED 128X32 0.91"
|
// --> First choise of cheap I2C OLED 128X32 0.91"
|
||||||
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||||
// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3"
|
// --> Second choice of cheap I2C OLED 128X64 0.96" or 1.3"
|
||||||
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA
|
||||||
// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
// --> Third choice of Heltec WiFi-Kit-8 OLED 128X32 0.91"
|
||||||
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
|
//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8
|
||||||
// gets called once at boot. Do all initialization that doesn't depend on network here
|
// gets called once at boot. Do all initialization that doesn't depend on network here
|
||||||
|
|
||||||
@@ -177,11 +181,11 @@ void userLoop() {
|
|||||||
// First row with Wifi name
|
// First row with Wifi name
|
||||||
u8x8.setCursor(1, 0);
|
u8x8.setCursor(1, 0);
|
||||||
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
|
||||||
// Print `~` char to indicate that SSID is longer, than owr dicplay
|
// Print `~` char to indicate that SSID is longer than our display
|
||||||
if (knownSsid.length() > u8x8.getCols())
|
if (knownSsid.length() > u8x8.getCols())
|
||||||
u8x8.print("~");
|
u8x8.print("~");
|
||||||
|
|
||||||
// Second row with IP or Psssword
|
// Second row with IP or Password
|
||||||
u8x8.setCursor(1, 1);
|
u8x8.setCursor(1, 1);
|
||||||
// Print password in AP mode and if led is OFF.
|
// Print password in AP mode and if led is OFF.
|
||||||
if (apActive && bri == 0)
|
if (apActive && bri == 0)
|
||||||
@@ -191,58 +195,14 @@ void userLoop() {
|
|||||||
|
|
||||||
// Third row with mode name
|
// Third row with mode name
|
||||||
u8x8.setCursor(2, 2);
|
u8x8.setCursor(2, 2);
|
||||||
uint8_t qComma = 0;
|
char lineBuffer[17];
|
||||||
bool insideQuotes = false;
|
extractModeName(knownMode, JSON_mode_names, lineBuffer, 16);
|
||||||
uint8_t printedChars = 0;
|
u8x8.print(lineBuffer);
|
||||||
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;
|
|
||||||
u8x8.print(singleJsonSymbol);
|
|
||||||
printedChars++;
|
|
||||||
}
|
|
||||||
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Fourth row with palette name
|
// Fourth row with palette name
|
||||||
u8x8.setCursor(2, 3);
|
u8x8.setCursor(2, 3);
|
||||||
qComma = 0;
|
extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16);
|
||||||
insideQuotes = false;
|
u8x8.print(lineBuffer);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
|
||||||
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
u8x8.drawGlyph(0, 0, 80); // wifi icon
|
||||||
|
|||||||
@@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
**Attention: This usermod compiles only for ESP8266**
|
**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 WiFi environments.
|
||||||
|
|
||||||
The modification works with static or DHCP IP address configuration.
|
The modification works with static or DHCP IP address configuration.
|
||||||
|
|
||||||
_Story:_
|
_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
|
## Webinterface
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ The usermod supports the following state changes:
|
|||||||
|
|
||||||
| JSON key | Value range | Description |
|
| JSON key | Value range | Description |
|
||||||
|-------------|------------------|---------------------------------|
|
|-------------|------------------|---------------------------------|
|
||||||
| PingDelayMs | 5000 to 18000000 | Deactivdate/activate the sensor |
|
| PingDelayMs | 5000 to 18000000 | Deactivate/activate the sensor |
|
||||||
|
|
||||||
Changes also persist after a reboot.
|
Changes also persist after a reboot.
|
||||||
|
|
||||||
|
|||||||
77
usermods/INA226_v2/README.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Usermod INA226
|
||||||
|
|
||||||
|
This Usermod is designed to read values from an INA226 sensor and output the following:
|
||||||
|
- Current
|
||||||
|
- Voltage
|
||||||
|
- Power
|
||||||
|
- Shunt Voltage
|
||||||
|
- Overflow status
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The following settings can be configured in the Usermod Menu:
|
||||||
|
- **Enabled**: Enable or disable the usermod.
|
||||||
|
- **I2CAddress**: The I2C address in decimal. Default is 64 (0x40).
|
||||||
|
- **CheckInterval**: Number of seconds between readings. This should be higher than the time it takes to make a reading, determined by the two next options.
|
||||||
|
- **INASamples**: The number of samples to configure the INA226 to use for a measurement. Higher counts provide more accuracy. See the 'Understanding Samples and Conversion Times' section for more details.
|
||||||
|
- **INAConversionTime**: The time to use on converting and preparing readings on the INA226. Higher times provide more precision. See the 'Understanding Samples and Conversion Times' section for more details.
|
||||||
|
- **Decimals**: Number of decimals in the output.
|
||||||
|
- **ShuntResistor**: Shunt resistor value in milliohms. An R100 shunt resistor should be written as "100", while R010 should be "10".
|
||||||
|
- **CurrentRange**: Expected maximum current in milliamps (e.g., 5 A = 5000 mA).
|
||||||
|
- **MqttPublish**: Enable or disable MQTT publishing.
|
||||||
|
- **MqttPublishAlways**: Publish always, regardless if there is a change.
|
||||||
|
- **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
|
||||||
|
|
||||||
|
- Libraries
|
||||||
|
- `wollewald/INA226_WE@~1.2.9` (by [wollewald](https://registry.platformio.org/libraries/wollewald/INA226_WE))
|
||||||
|
- `Wire`
|
||||||
|
|
||||||
|
## Understanding Samples and Conversion Times
|
||||||
|
|
||||||
|
The INA226 uses a programmable ADC with configurable conversion times and averaging to optimize the measurement accuracy and speed. The conversion time and number of samples are determined based on the `INASamples` and `INAConversionTime` settings. The following table outlines the possible combinations:
|
||||||
|
|
||||||
|
| Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples |
|
||||||
|
|----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------|
|
||||||
|
| 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms |
|
||||||
|
| 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms |
|
||||||
|
| 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms |
|
||||||
|
| 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms |
|
||||||
|
| 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms |
|
||||||
|
| 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms |
|
||||||
|
| 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms |
|
||||||
|
| 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms |
|
||||||
|
|
||||||
|
It is important to pick a combination that provides the needed balance between accuracy and precision while ensuring new readings within the `CheckInterval` setting. When `USERMOD_INA226_DEBUG` is defined, the info pane contains the expected time to make a reading, which can be seen in the table above.
|
||||||
|
|
||||||
|
As an example, if you want a new reading every 5 seconds (`CheckInterval`), a valid combination is `256 samples` and `4156 μs` which would provide new values every 2.1 seconds.
|
||||||
|
|
||||||
|
The picked values also slightly affect power usage. If the `CheckInterval` is set to more than 20 seconds, the INA226 is configured in `triggered` reading mode, where it only uses power as long as it's working. Then the conversion time and average samples counts determine how long the chip stays turned on every `CheckInterval` time.
|
||||||
|
|
||||||
|
### Calculating Current and Power
|
||||||
|
|
||||||
|
The INA226 calculates current by measuring the differential voltage across a shunt resistor and using the calibration register value to convert this measurement into current. Power is calculated by multiplying the current by the bus voltage.
|
||||||
|
|
||||||
|
For detailed programming information and register configurations, refer to the [INA226 datasheet](https://www.ti.com/product/INA226).
|
||||||
|
|
||||||
|
## Author
|
||||||
|
[@LordMike](https://github.com/LordMike)
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
To enable, compile with `USERMOD_INA226` defined (e.g. in `platformio_override.ini`).
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[env:ina226_example]
|
||||||
|
extends = env:esp32dev
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags} ${esp32.build_flags}
|
||||||
|
-D USERMOD_INA226
|
||||||
|
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
wollewald/INA226_WE@~1.2.9
|
||||||
|
```
|
||||||
9
usermods/INA226_v2/platformio_override.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[env:ina226_example]
|
||||||
|
extends = env:esp32dev
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags} ${esp32.build_flags}
|
||||||
|
-D USERMOD_INA226
|
||||||
|
; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
wollewald/INA226_WE@~1.2.9
|
||||||
556
usermods/INA226_v2/usermod_ina226.h
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include <INA226_WE.h>
|
||||||
|
|
||||||
|
#define INA226_ADDRESS 0x40 // Default I2C address for INA226
|
||||||
|
|
||||||
|
#define DEFAULT_CHECKINTERVAL 60000
|
||||||
|
#define DEFAULT_INASAMPLES 128
|
||||||
|
#define DEFAULT_INASAMPLESENUM AVERAGE_128
|
||||||
|
#define DEFAULT_INACONVERSIONTIME 1100
|
||||||
|
#define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100
|
||||||
|
|
||||||
|
// A packed version of all INA settings enums and their human friendly counterparts packed into a 32 bit structure
|
||||||
|
// Some values are shifted and need to be preprocessed before usage
|
||||||
|
struct InaSettingLookup
|
||||||
|
{
|
||||||
|
uint16_t avgSamples : 11; // Max 1024, which could be in 10 bits if we shifted by 1; if we somehow handle the edge case with "1"
|
||||||
|
uint8_t avgEnum : 4; // Shift by 8 to get the INA226_AVERAGES value, accepts 0x00 to 0x0F, we need 0x00 to 0x0E
|
||||||
|
uint16_t convTimeUs : 14; // We could save 2 bits by shifting this, but we won't save anything at present.
|
||||||
|
INA226_CONV_TIME convTimeEnum : 3; // Only the lowest 3 bits are defined in the conversion time enumerations
|
||||||
|
};
|
||||||
|
|
||||||
|
const InaSettingLookup _inaSettingsLookup[] = {
|
||||||
|
{1024, AVERAGE_1024 >> 8, 8244, CONV_TIME_8244},
|
||||||
|
{512, AVERAGE_512 >> 8, 4156, CONV_TIME_4156},
|
||||||
|
{256, AVERAGE_256 >> 8, 2116, CONV_TIME_2116},
|
||||||
|
{128, AVERAGE_128 >> 8, 1100, CONV_TIME_1100},
|
||||||
|
{64, AVERAGE_64 >> 8, 588, CONV_TIME_588},
|
||||||
|
{16, AVERAGE_16 >> 8, 332, CONV_TIME_332},
|
||||||
|
{4, AVERAGE_4 >> 8, 204, CONV_TIME_204},
|
||||||
|
{1, AVERAGE_1 >> 8, 140, CONV_TIME_140}};
|
||||||
|
|
||||||
|
// Note: Will update the provided arg to be the correct value
|
||||||
|
INA226_AVERAGES getAverageEnum(uint16_t &samples)
|
||||||
|
{
|
||||||
|
for (const auto &setting : _inaSettingsLookup)
|
||||||
|
{
|
||||||
|
// If a user supplies 2000 samples, we serve up the highest possible value
|
||||||
|
if (samples >= setting.avgSamples)
|
||||||
|
{
|
||||||
|
samples = setting.avgSamples;
|
||||||
|
return static_cast<INA226_AVERAGES>(setting.avgEnum << 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default value if not found
|
||||||
|
samples = DEFAULT_INASAMPLES;
|
||||||
|
return DEFAULT_INASAMPLESENUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
INA226_CONV_TIME getConversionTimeEnum(uint16_t &timeUs)
|
||||||
|
{
|
||||||
|
for (const auto &setting : _inaSettingsLookup)
|
||||||
|
{
|
||||||
|
// If a user supplies 9000 μs, we serve up the highest possible value
|
||||||
|
if (timeUs >= setting.convTimeUs)
|
||||||
|
{
|
||||||
|
timeUs = setting.convTimeUs;
|
||||||
|
return setting.convTimeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default value if not found
|
||||||
|
timeUs = DEFAULT_INACONVERSIONTIME;
|
||||||
|
return DEFAULT_INACONVERSIONTIMEENUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsermodINA226 : public Usermod
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static const char _name[];
|
||||||
|
|
||||||
|
unsigned long _lastLoopCheck = 0;
|
||||||
|
unsigned long _lastTriggerTime = 0;
|
||||||
|
|
||||||
|
bool _settingEnabled : 1; // Enable the usermod
|
||||||
|
bool _mqttPublish : 1; // Publish MQTT values
|
||||||
|
bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change
|
||||||
|
bool _mqttHomeAssistant : 1; // Enable Home Assistant docs
|
||||||
|
bool _initDone : 1; // Initialization is done
|
||||||
|
bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered
|
||||||
|
bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements
|
||||||
|
uint16_t _settingInaConversionTimeUs : 12; // Conversion time, shift by 2
|
||||||
|
uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024
|
||||||
|
|
||||||
|
uint8_t _i2cAddress;
|
||||||
|
uint16_t _checkInterval; // milliseconds, user settings is in seconds
|
||||||
|
float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..)
|
||||||
|
uint16_t _shuntResistor; // Shunt resistor value in milliohms
|
||||||
|
uint16_t _currentRange; // Expected maximum current in milliamps
|
||||||
|
|
||||||
|
uint8_t _lastStatus = 0;
|
||||||
|
float _lastCurrent = 0;
|
||||||
|
float _lastVoltage = 0;
|
||||||
|
float _lastPower = 0;
|
||||||
|
float _lastShuntVoltage = 0;
|
||||||
|
bool _lastOverflow = false;
|
||||||
|
|
||||||
|
#ifndef WLED_MQTT_DISABLE
|
||||||
|
float _lastCurrentSent = 0;
|
||||||
|
float _lastVoltageSent = 0;
|
||||||
|
float _lastPowerSent = 0;
|
||||||
|
float _lastShuntVoltageSent = 0;
|
||||||
|
bool _lastOverflowSent = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
INA226_WE *_ina226 = nullptr;
|
||||||
|
|
||||||
|
float truncateDecimals(float val)
|
||||||
|
{
|
||||||
|
return roundf(val * _decimalFactor) / _decimalFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeINA226()
|
||||||
|
{
|
||||||
|
if (_ina226 != nullptr)
|
||||||
|
{
|
||||||
|
delete _ina226;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ina226 = new INA226_WE(_i2cAddress);
|
||||||
|
if (!_ina226->init())
|
||||||
|
{
|
||||||
|
DEBUG_PRINTLN(F("INA226 initialization failed!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ina226->setCorrectionFactor(1.0);
|
||||||
|
|
||||||
|
uint16_t tmpShort = _settingInaSamples;
|
||||||
|
_ina226->setAverage(getAverageEnum(tmpShort));
|
||||||
|
|
||||||
|
tmpShort = _settingInaConversionTimeUs << 2;
|
||||||
|
_ina226->setConversionTime(getConversionTimeEnum(tmpShort));
|
||||||
|
|
||||||
|
if (_checkInterval >= 20000)
|
||||||
|
{
|
||||||
|
_isTriggeredOperationMode = true;
|
||||||
|
_ina226->setMeasureMode(TRIGGERED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_isTriggeredOperationMode = false;
|
||||||
|
_ina226->setMeasureMode(CONTINUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ina226->setResistorRange(static_cast<float>(_shuntResistor) / 1000.0, static_cast<float>(_currentRange) / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetchAndPushValues()
|
||||||
|
{
|
||||||
|
_lastStatus = _ina226->getI2cErrorCode();
|
||||||
|
|
||||||
|
if (_lastStatus != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0);
|
||||||
|
float voltage = truncateDecimals(_ina226->getBusVoltage_V());
|
||||||
|
float power = truncateDecimals(_ina226->getBusPower() / 1000.0);
|
||||||
|
float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V());
|
||||||
|
bool overflow = _ina226->overflow;
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f);
|
||||||
|
mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f);
|
||||||
|
mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f);
|
||||||
|
mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f);
|
||||||
|
mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_lastCurrent = current;
|
||||||
|
_lastVoltage = voltage;
|
||||||
|
_lastPower = power;
|
||||||
|
_lastShuntVoltage = shuntVoltage;
|
||||||
|
_lastOverflow = overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleTriggeredMode(unsigned long currentTime)
|
||||||
|
{
|
||||||
|
if (_measurementTriggered)
|
||||||
|
{
|
||||||
|
// Test if we have a measurement every 400ms
|
||||||
|
if (currentTime - _lastTriggerTime >= 400)
|
||||||
|
{
|
||||||
|
_lastTriggerTime = currentTime;
|
||||||
|
if (_ina226->isBusy())
|
||||||
|
return;
|
||||||
|
|
||||||
|
fetchAndPushValues();
|
||||||
|
_measurementTriggered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (currentTime - _lastLoopCheck >= _checkInterval)
|
||||||
|
{
|
||||||
|
// Start a measurement and use isBusy() later to determine when it is done
|
||||||
|
_ina226->startSingleMeasurementNoWait();
|
||||||
|
_lastLoopCheck = currentTime;
|
||||||
|
_lastTriggerTime = currentTime;
|
||||||
|
_measurementTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleContinuousMode(unsigned long currentTime)
|
||||||
|
{
|
||||||
|
if (currentTime - _lastLoopCheck >= _checkInterval)
|
||||||
|
{
|
||||||
|
_lastLoopCheck = currentTime;
|
||||||
|
fetchAndPushValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~UsermodINA226()
|
||||||
|
{
|
||||||
|
delete _ina226;
|
||||||
|
_ina226 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
void mqttInitialize()
|
||||||
|
{
|
||||||
|
if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char topic[128];
|
||||||
|
snprintf_P(topic, 127, "%s/current", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Current"), topic, F("current"), F("A"));
|
||||||
|
|
||||||
|
snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V"));
|
||||||
|
|
||||||
|
snprintf_P(topic, 127, "%s/power", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Power"), topic, F("power"), F("W"));
|
||||||
|
|
||||||
|
snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic);
|
||||||
|
mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V"));
|
||||||
|
|
||||||
|
snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic);
|
||||||
|
mqttCreateHassBinarySensor(F("Overflow"), topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange)
|
||||||
|
{
|
||||||
|
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange))
|
||||||
|
{
|
||||||
|
char subuf[128];
|
||||||
|
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
|
||||||
|
mqtt->publish(subuf, 0, false, String(state).c_str());
|
||||||
|
|
||||||
|
lastState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttPublishIfChanged(const __FlashStringHelper *topic, bool &lastState, bool state)
|
||||||
|
{
|
||||||
|
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || lastState != state))
|
||||||
|
{
|
||||||
|
char subuf[128];
|
||||||
|
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
|
||||||
|
mqtt->publish(subuf, 0, false, state ? "true" : "false");
|
||||||
|
|
||||||
|
lastState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
|
||||||
|
{
|
||||||
|
String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config");
|
||||||
|
|
||||||
|
StaticJsonDocument<600> doc;
|
||||||
|
|
||||||
|
doc[F("name")] = 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"));
|
||||||
|
device[F("name")] = serverDescription;
|
||||||
|
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||||
|
device[F("manufacturer")] = F(WLED_BRAND);
|
||||||
|
device[F("model")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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 mqttCreateHassBinarySensor(const String &name, const String &topic)
|
||||||
|
{
|
||||||
|
String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + "/" + name + F("/config");
|
||||||
|
|
||||||
|
StaticJsonDocument<600> doc;
|
||||||
|
|
||||||
|
doc[F("name")] = name;
|
||||||
|
doc[F("state_topic")] = topic;
|
||||||
|
doc[F("unique_id")] = String(mqttClientID) + name;
|
||||||
|
|
||||||
|
JsonObject device = doc.createNestedObject(F("device"));
|
||||||
|
device[F("name")] = serverDescription;
|
||||||
|
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
|
||||||
|
device[F("manufacturer")] = F(WLED_BRAND);
|
||||||
|
device[F("model")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
UsermodINA226()
|
||||||
|
{
|
||||||
|
// Default values
|
||||||
|
_settingInaSamples = DEFAULT_INASAMPLES;
|
||||||
|
_settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME;
|
||||||
|
|
||||||
|
_i2cAddress = INA226_ADDRESS;
|
||||||
|
_checkInterval = DEFAULT_CHECKINTERVAL;
|
||||||
|
_decimalFactor = 100;
|
||||||
|
_shuntResistor = 1000;
|
||||||
|
_currentRange = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
initializeINA226();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (!_settingEnabled || strip.isUpdating())
|
||||||
|
return;
|
||||||
|
|
||||||
|
unsigned long currentTime = millis();
|
||||||
|
|
||||||
|
if (_isTriggeredOperationMode)
|
||||||
|
{
|
||||||
|
handleTriggeredMode(currentTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleContinuousMode(currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
void onMqttConnect(bool sessionPresent)
|
||||||
|
{
|
||||||
|
mqttInitialize();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_INA226;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject &root) override
|
||||||
|
{
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull())
|
||||||
|
user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
#ifdef USERMOD_INA226_DEBUG
|
||||||
|
JsonArray temp = user.createNestedArray(F("INA226 last loop"));
|
||||||
|
temp.add(_lastLoopCheck);
|
||||||
|
|
||||||
|
temp = user.createNestedArray(F("INA226 last status"));
|
||||||
|
temp.add(_lastStatus);
|
||||||
|
|
||||||
|
temp = user.createNestedArray(F("INA226 average samples"));
|
||||||
|
temp.add(_settingInaSamples);
|
||||||
|
temp.add(F("samples"));
|
||||||
|
|
||||||
|
temp = user.createNestedArray(F("INA226 conversion time"));
|
||||||
|
temp.add(_settingInaConversionTimeUs << 2);
|
||||||
|
temp.add(F("μs"));
|
||||||
|
|
||||||
|
// INA226 uses (2 * conversion time * samples) time to take a reading.
|
||||||
|
temp = user.createNestedArray(F("INA226 expected sample time"));
|
||||||
|
uint32_t sampleTimeNeededUs = (static_cast<uint32_t>(_settingInaConversionTimeUs) << 2) * _settingInaSamples * 2;
|
||||||
|
temp.add(truncateDecimals(sampleTimeNeededUs / 1000.0));
|
||||||
|
temp.add(F("ms"));
|
||||||
|
|
||||||
|
temp = user.createNestedArray(F("INA226 mode"));
|
||||||
|
temp.add(_isTriggeredOperationMode ? F("triggered") : F("continuous"));
|
||||||
|
|
||||||
|
if (_isTriggeredOperationMode)
|
||||||
|
{
|
||||||
|
temp = user.createNestedArray(F("INA226 triggered"));
|
||||||
|
temp.add(_measurementTriggered ? F("waiting for measurement") : F(""));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonArray jsonCurrent = user.createNestedArray(F("Current"));
|
||||||
|
JsonArray jsonVoltage = user.createNestedArray(F("Voltage"));
|
||||||
|
JsonArray jsonPower = user.createNestedArray(F("Power"));
|
||||||
|
JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage"));
|
||||||
|
JsonArray jsonOverflow = user.createNestedArray(F("Overflow"));
|
||||||
|
|
||||||
|
if (_lastLoopCheck == 0)
|
||||||
|
{
|
||||||
|
jsonCurrent.add(F("Not read yet"));
|
||||||
|
jsonVoltage.add(F("Not read yet"));
|
||||||
|
jsonPower.add(F("Not read yet"));
|
||||||
|
jsonShuntVoltage.add(F("Not read yet"));
|
||||||
|
jsonOverflow.add(F("Not read yet"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastStatus != 0)
|
||||||
|
{
|
||||||
|
jsonCurrent.add(F("An error occurred"));
|
||||||
|
jsonVoltage.add(F("An error occurred"));
|
||||||
|
jsonPower.add(F("An error occurred"));
|
||||||
|
jsonShuntVoltage.add(F("An error occurred"));
|
||||||
|
jsonOverflow.add(F("An error occurred"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonCurrent.add(_lastCurrent);
|
||||||
|
jsonCurrent.add(F("A"));
|
||||||
|
|
||||||
|
jsonVoltage.add(_lastVoltage);
|
||||||
|
jsonVoltage.add(F("V"));
|
||||||
|
|
||||||
|
jsonPower.add(_lastPower);
|
||||||
|
jsonPower.add(F("W"));
|
||||||
|
|
||||||
|
jsonShuntVoltage.add(_lastShuntVoltage);
|
||||||
|
jsonShuntVoltage.add(F("V"));
|
||||||
|
|
||||||
|
jsonOverflow.add(_lastOverflow ? F("true") : F("false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject &root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top[F("Enabled")] = _settingEnabled;
|
||||||
|
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
|
||||||
|
top[F("CheckInterval")] = _checkInterval / 1000;
|
||||||
|
top[F("INASamples")] = _settingInaSamples;
|
||||||
|
top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2;
|
||||||
|
top[F("Decimals")] = log10f(_decimalFactor);
|
||||||
|
top[F("ShuntResistor")] = _shuntResistor;
|
||||||
|
top[F("CurrentRange")] = _currentRange;
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
top[F("MqttPublish")] = _mqttPublish;
|
||||||
|
top[F("MqttPublishAlways")] = _mqttPublishAlways;
|
||||||
|
top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("INA226 config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject &root) override
|
||||||
|
{
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
if (!configComplete)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool tmpBool;
|
||||||
|
if (getJsonValue(top[F("Enabled")], tmpBool))
|
||||||
|
_settingEnabled = tmpBool;
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
|
||||||
|
if (getJsonValue(top[F("CheckInterval")], _checkInterval))
|
||||||
|
{
|
||||||
|
if (1 <= _checkInterval && _checkInterval <= 600)
|
||||||
|
_checkInterval *= 1000;
|
||||||
|
else
|
||||||
|
_checkInterval = DEFAULT_CHECKINTERVAL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
uint16_t tmpShort;
|
||||||
|
if (getJsonValue(top[F("INASamples")], tmpShort))
|
||||||
|
{
|
||||||
|
// The method below will fix the provided value to a valid one
|
||||||
|
getAverageEnum(tmpShort);
|
||||||
|
_settingInaSamples = tmpShort;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
if (getJsonValue(top[F("INAConversionTime")], tmpShort))
|
||||||
|
{
|
||||||
|
// The method below will fix the provided value to a valid one
|
||||||
|
getConversionTimeEnum(tmpShort);
|
||||||
|
_settingInaConversionTimeUs = tmpShort >> 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
if (getJsonValue(top[F("Decimals")], _decimalFactor))
|
||||||
|
{
|
||||||
|
if (0 <= _decimalFactor && _decimalFactor <= 5)
|
||||||
|
_decimalFactor = pow10f(_decimalFactor);
|
||||||
|
else
|
||||||
|
_decimalFactor = 100;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor);
|
||||||
|
configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange);
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
if (getJsonValue(top[F("MqttPublish")], tmpBool))
|
||||||
|
_mqttPublish = tmpBool;
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
if (getJsonValue(top[F("MqttPublishAlways")], tmpBool))
|
||||||
|
_mqttPublishAlways = tmpBool;
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
|
||||||
|
if (getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool))
|
||||||
|
_mqttHomeAssistant = tmpBool;
|
||||||
|
else
|
||||||
|
configComplete = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_initDone)
|
||||||
|
{
|
||||||
|
initializeINA226();
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
mqttInitialize();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_initDone = true;
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char UsermodINA226::_name[] PROGMEM = "INA226";
|
||||||
17
usermods/Internal_Temperature_v2/readme.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Internal Temperature Usermod
|
||||||
|
This usermod adds the temperature readout to the Info tab and also publishes that over the topic `mcutemp` topic.
|
||||||
|
|
||||||
|
## Important
|
||||||
|
A shown temp of 53,33°C might indicate that the internal temp is not supported.
|
||||||
|
|
||||||
|
ESP8266 does not have a internal temp sensor
|
||||||
|
|
||||||
|
ESP32S2 seems to crash on reading the sensor -> disabled
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Add a build flag `-D USERMOD_INTERNAL_TEMPERATURE` to your `platformio.ini` (or `platformio_override.ini`).
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
Soeren Willrodt [@lost-hope](https://github.com/lost-hope)
|
||||||
|
|
||||||
|
Dimitry Zhemkov [@dima-zhemkov](https://github.com/dima-zhemkov)
|
||||||
117
usermods/Internal_Temperature_v2/usermod_internal_temperature.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
class InternalTemperatureUsermod : public Usermod
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long loopInterval = 10000;
|
||||||
|
unsigned long lastTime = 0;
|
||||||
|
bool isEnabled = false;
|
||||||
|
float temperature = 0;
|
||||||
|
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
static const char _loopInterval[];
|
||||||
|
|
||||||
|
// any private methods should go here (non-inline method should be defined out of class)
|
||||||
|
void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// if usermod is disabled or called during strip updating just exit
|
||||||
|
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||||
|
if (!isEnabled || strip.isUpdating() || millis() - lastTime <= loopInterval)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastTime = millis();
|
||||||
|
|
||||||
|
#ifdef ESP8266 // ESP8266
|
||||||
|
// does not seem possible
|
||||||
|
temperature = -1;
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32S2) // ESP32S2
|
||||||
|
temperature = -1;
|
||||||
|
#else // ESP32 ESP32S3 and ESP32C3
|
||||||
|
temperature = roundf(temperatureRead() * 10) / 10;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
if (WLED_MQTT_CONNECTED)
|
||||||
|
{
|
||||||
|
char array[10];
|
||||||
|
snprintf(array, sizeof(array), "%f", temperature);
|
||||||
|
publishMqtt(array);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject &root)
|
||||||
|
{
|
||||||
|
if (!isEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if "u" object does not exist yet wee need to create it
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull())
|
||||||
|
user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
JsonArray userTempArr = user.createNestedArray(FPSTR(_name));
|
||||||
|
userTempArr.add(temperature);
|
||||||
|
userTempArr.add(F(" °C"));
|
||||||
|
|
||||||
|
// if "sensor" object does not exist yet wee need to create it
|
||||||
|
JsonObject sensor = root[F("sensor")];
|
||||||
|
if (sensor.isNull())
|
||||||
|
sensor = root.createNestedObject(F("sensor"));
|
||||||
|
|
||||||
|
JsonArray sensorTempArr = sensor.createNestedArray(FPSTR(_name));
|
||||||
|
sensorTempArr.add(temperature);
|
||||||
|
sensorTempArr.add(F("°C"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject &root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top[FPSTR(_enabled)] = isEnabled;
|
||||||
|
top[FPSTR(_loopInterval)] = loopInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject &root)
|
||||||
|
{
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
configComplete &= getJsonValue(top[FPSTR(_enabled)], isEnabled);
|
||||||
|
configComplete &= getJsonValue(top[FPSTR(_loopInterval)], loopInterval);
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_INTERNAL_TEMPERATURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char InternalTemperatureUsermod::_name[] PROGMEM = "Internal Temperature";
|
||||||
|
const char InternalTemperatureUsermod::_enabled[] PROGMEM = "Enabled";
|
||||||
|
const char InternalTemperatureUsermod::_loopInterval[] PROGMEM = "Loop Interval";
|
||||||
|
|
||||||
|
void InternalTemperatureUsermod::publishMqtt(const char *state, bool retain)
|
||||||
|
{
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
// Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
|
if (WLED_MQTT_CONNECTED)
|
||||||
|
{
|
||||||
|
char subuf[64];
|
||||||
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
|
strcat_P(subuf, PSTR("/mcutemp"));
|
||||||
|
mqtt->publish(subuf, 0, retain, state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
The JSON IR remote allows users to customize IR remote behavior without writing custom code and compiling.
|
The JSON IR remote enables 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
|
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.
|
map buttons from any remote to any HTTP request API or JSON API command.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
36
usermods/LD2410_v2/readme.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# BH1750 usermod
|
||||||
|
|
||||||
|
> This usermod requires a second UART and was only tested on the ESP32
|
||||||
|
|
||||||
|
|
||||||
|
This usermod will read from a LD2410 movement/presence sensor.
|
||||||
|
|
||||||
|
The movement and presence state are displayed in both the Info section of the web UI, as well as published to the `/movement` and `/stationary` MQTT topics respectively.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Libraries
|
||||||
|
- `ncmreynolds/ld2410@^0.1.3`
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
To enable, compile with `USERMOD_LD2410` defined (e.g. in `platformio_override.ini`)
|
||||||
|
```ini
|
||||||
|
[env:usermod_USERMOD_LD2410_esp32dev]
|
||||||
|
extends = env:esp32dev
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags_esp32}
|
||||||
|
-D USERMOD_LD2410
|
||||||
|
lib_deps =
|
||||||
|
${esp32.lib_deps}
|
||||||
|
ncmreynolds/ld2410@^0.1.3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
The Usermod screen allows you to:
|
||||||
|
- enable/disable the usermod
|
||||||
|
- Configure the RX/TX pins
|
||||||
|
|
||||||
|
## Change log
|
||||||
|
- 2024-06 Created by @wesleygas (https://github.com/wesleygas/)
|
||||||
237
usermods/LD2410_v2/usermod_ld2410.h
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#warning **** Included USERMOD_LD2410 ****
|
||||||
|
|
||||||
|
#ifndef WLED_ENABLE_MQTT
|
||||||
|
#error "This user mod requires MQTT to be enabled."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include <ld2410.h>
|
||||||
|
|
||||||
|
class LD2410Usermod : public Usermod {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool enabled = true;
|
||||||
|
bool initDone = false;
|
||||||
|
bool sensorFound = false;
|
||||||
|
unsigned long lastTime = 0;
|
||||||
|
unsigned long last_mqtt_sent = 0;
|
||||||
|
|
||||||
|
int8_t default_uart_rx = 19;
|
||||||
|
int8_t default_uart_tx = 18;
|
||||||
|
|
||||||
|
|
||||||
|
String mqttMovementTopic = F("");
|
||||||
|
String mqttStationaryTopic = F("");
|
||||||
|
bool mqttInitialized = false;
|
||||||
|
bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages
|
||||||
|
|
||||||
|
|
||||||
|
ld2410 radar;
|
||||||
|
bool stationary_detected = false;
|
||||||
|
bool last_stationary_state = false;
|
||||||
|
bool movement_detected = false;
|
||||||
|
bool last_movement_state = false;
|
||||||
|
|
||||||
|
// These config variables have defaults set inside readFromConfig()
|
||||||
|
int8_t uart_rx_pin;
|
||||||
|
int8_t uart_tx_pin;
|
||||||
|
|
||||||
|
// string that are used multiple time (this will save some flash memory)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
|
||||||
|
void publishMqtt(const char* topic, const char* state, bool retain); // example for publishing MQTT message
|
||||||
|
|
||||||
|
void _mqttInitialize()
|
||||||
|
{
|
||||||
|
mqttMovementTopic = String(mqttDeviceTopic) + F("/ld2410/movement");
|
||||||
|
mqttStationaryTopic = String(mqttDeviceTopic) + F("/ld2410/stationary");
|
||||||
|
if (HomeAssistantDiscovery){
|
||||||
|
_createMqttSensor(F("Movement"), mqttMovementTopic, F("motion"), F(""));
|
||||||
|
_createMqttSensor(F("Stationary"), mqttStationaryTopic, F("occupancy"), F(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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/binary_sensor/")) + mqttClientID + F("/") + name + F("/config");
|
||||||
|
|
||||||
|
StaticJsonDocument<600> doc;
|
||||||
|
|
||||||
|
doc[F("name")] = String(serverDescription) + F(" Module");
|
||||||
|
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;
|
||||||
|
doc[F("payload_off")] = "OFF";
|
||||||
|
doc[F("payload_on")] = "ON";
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
inline bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial1.begin(256000, SERIAL_8N1, uart_rx_pin, uart_tx_pin);
|
||||||
|
Serial.print(F("\nLD2410 radar sensor initialising: "));
|
||||||
|
if(radar.begin(Serial1)){
|
||||||
|
Serial.println(F("OK"));
|
||||||
|
} else {
|
||||||
|
Serial.println(F("not connected"));
|
||||||
|
}
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||||
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
radar.read();
|
||||||
|
unsigned long curr_time = millis();
|
||||||
|
if(curr_time - lastTime > 1000) //Try to Report every 1000ms
|
||||||
|
{
|
||||||
|
lastTime = curr_time;
|
||||||
|
sensorFound = radar.isConnected();
|
||||||
|
if(!sensorFound) return;
|
||||||
|
stationary_detected = radar.presenceDetected();
|
||||||
|
if(stationary_detected != last_stationary_state){
|
||||||
|
if (WLED_MQTT_CONNECTED){
|
||||||
|
publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false);
|
||||||
|
last_stationary_state = stationary_detected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
movement_detected = radar.movingTargetDetected();
|
||||||
|
if(movement_detected != last_movement_state){
|
||||||
|
if (WLED_MQTT_CONNECTED){
|
||||||
|
publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false);
|
||||||
|
last_movement_state = movement_detected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there hasn't been any activity, send current state to confirm sensor is alive
|
||||||
|
if(curr_time - last_mqtt_sent > 1000*60*5 && WLED_MQTT_CONNECTED){
|
||||||
|
publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false);
|
||||||
|
publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject& root)
|
||||||
|
{
|
||||||
|
// if "u" object does not exist yet wee need to create it
|
||||||
|
JsonObject user = root[F("u")];
|
||||||
|
if (user.isNull()) user = root.createNestedObject(F("u"));
|
||||||
|
|
||||||
|
JsonArray ld2410_sta_json = user.createNestedArray(F("LD2410 Stationary"));
|
||||||
|
JsonArray ld2410_mov_json = user.createNestedArray(F("LD2410 Movement"));
|
||||||
|
if (!enabled){
|
||||||
|
ld2410_sta_json.add(F("disabled"));
|
||||||
|
ld2410_mov_json.add(F("disabled"));
|
||||||
|
} else if(!sensorFound){
|
||||||
|
ld2410_sta_json.add(F("LD2410"));
|
||||||
|
ld2410_sta_json.add(" Not Found");
|
||||||
|
} else {
|
||||||
|
ld2410_sta_json.add("Sta ");
|
||||||
|
ld2410_sta_json.add(stationary_detected ? "ON":"OFF");
|
||||||
|
ld2410_mov_json.add("Mov ");
|
||||||
|
ld2410_mov_json.add(movement_detected ? "ON":"OFF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
//save these vars persistently whenever settings are saved
|
||||||
|
top["uart_rx_pin"] = default_uart_rx;
|
||||||
|
top["uart_tx_pin"] = default_uart_tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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[FPSTR(_name)];
|
||||||
|
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
if (!configComplete)
|
||||||
|
{
|
||||||
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
|
DEBUG_PRINT(F("LD2410"));
|
||||||
|
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
configComplete &= getJsonValue(top["uart_rx_pin"], uart_rx_pin, default_uart_rx);
|
||||||
|
configComplete &= getJsonValue(top["uart_tx_pin"], uart_tx_pin, default_uart_tx);
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
/**
|
||||||
|
* onMqttConnect() is called when MQTT connection is established
|
||||||
|
*/
|
||||||
|
void onMqttConnect(bool sessionPresent) {
|
||||||
|
// do any MQTT related initialisation here
|
||||||
|
if(!radar.isConnected()) return;
|
||||||
|
publishMqtt("/ld2410/status", "I am alive!", false);
|
||||||
|
if (!mqttInitialized)
|
||||||
|
{
|
||||||
|
_mqttInitialize();
|
||||||
|
mqttInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_LD2410;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// add more strings here to reduce flash memory usage
|
||||||
|
const char LD2410Usermod::_name[] PROGMEM = "LD2410Usermod";
|
||||||
|
const char LD2410Usermod::_enabled[] PROGMEM = "enabled";
|
||||||
|
|
||||||
|
|
||||||
|
// implementation of non-inline member methods
|
||||||
|
|
||||||
|
void LD2410Usermod::publishMqtt(const char* topic, const char* state, bool retain)
|
||||||
|
{
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
//Check if MQTT Connected, otherwise it will crash
|
||||||
|
if (WLED_MQTT_CONNECTED) {
|
||||||
|
last_mqtt_sent = millis();
|
||||||
|
char subuf[64];
|
||||||
|
strcpy(subuf, mqttDeviceTopic);
|
||||||
|
strcat(subuf, topic);
|
||||||
|
mqtt->publish(subuf, 0, retain, state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
26
usermods/LDR_Dusk_Dawn_v2/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# LDR_Dusk_Dawn_v2
|
||||||
|
This usermod will obtain readings from a Light Dependent Resistor (LDR) and will turn on/off specific presets based on those readings. This is useful for exterior lighting situations where you want the lights to only be on when it is dark out.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
Add "-D USERMOD_LDR_DUSK_DAWN" to your platformio.ini [common] build_flags and build.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
[common]
|
||||||
|
build_flags =
|
||||||
|
-D USERMOD_LDR_DUSK_DAWN # Enable LDR Dusk Dawn Usermod
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usermod Settings
|
||||||
|
Setting | Description | Default
|
||||||
|
--- | --- | ---
|
||||||
|
Enabled | Enable/Disable the LDR functionality. | Disabled
|
||||||
|
LDR Pin | The analog capable pin your LDR is connected to. | 34
|
||||||
|
Threshold Minutes | The number of minutes of consistent readings above/below the on/off threshold before the LED state will change. | 5
|
||||||
|
Threshold | The analog read value threshold from the LDR. Readings lower than this number will count towards changing the LED state to off. You can see the current LDR reading by going into the info section when LDR functionality is enabled. | 1000
|
||||||
|
On Preset | The WLED preset to be used for the LED on state. | 1
|
||||||
|
Off Preset | The WLED preset to be used for the LED off state. | 2
|
||||||
|
|
||||||
|
## Author
|
||||||
|
[@jeffwdh](https://github.com/jeffwdh)
|
||||||
|
jeffwdh@tarball.ca
|
||||||
153
usermods/LDR_Dusk_Dawn_v2/usermod_LDR_Dusk_Dawn_v2.h
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
|
// 8266 does not support analogRead on user selectable pins
|
||||||
|
#error only ESP32 is supported by usermod LDR_DUSK_DAWN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class LDR_Dusk_Dawn_v2 : public Usermod {
|
||||||
|
private:
|
||||||
|
// Defaults
|
||||||
|
bool ldrEnabled = false;
|
||||||
|
int ldrPin = 34; //A2 on Adafruit Huzzah32
|
||||||
|
int ldrThresholdMinutes = 5; // How many minutes of readings above/below threshold until it switches LED state
|
||||||
|
int ldrThreshold = 1000; // Readings higher than this number will turn off LED.
|
||||||
|
int ldrOnPreset = 1; // Default "On" Preset
|
||||||
|
int ldrOffPreset = 2; // Default "Off" Preset
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
bool initDone = false;
|
||||||
|
bool ldrEnabledPreviously = false; // Was LDR enabled for the previous check? First check is always no.
|
||||||
|
int ldrOffCount; // Number of readings above the threshold
|
||||||
|
int ldrOnCount; // Number of readings below the threshold
|
||||||
|
int ldrReading = 0; // Last LDR reading
|
||||||
|
int ldrLEDState; // Current LED on/off state
|
||||||
|
unsigned long lastMillis = 0;
|
||||||
|
static const char _name[];
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setup() {
|
||||||
|
// register ldrPin
|
||||||
|
if ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0)) {
|
||||||
|
if(!pinManager.allocatePin(ldrPin, false, PinOwner::UM_LDR_DUSK_DAWN)) ldrEnabled = false; // pin already in use -> disable usermod
|
||||||
|
else pinMode(ldrPin, INPUT); // alloc success -> configure pin for input
|
||||||
|
} else ldrEnabled = false; // invalid pin -> disable usermod
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Only update every 10 seconds
|
||||||
|
if (millis() - lastMillis > 10000) {
|
||||||
|
if ( (ldrEnabled == true)
|
||||||
|
&& (ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0) ) { // make sure that pin is valid for analogread()
|
||||||
|
// Default state is off
|
||||||
|
if (ldrEnabledPreviously == false) {
|
||||||
|
applyPreset(ldrOffPreset);
|
||||||
|
ldrEnabledPreviously = true;
|
||||||
|
ldrLEDState = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get LDR reading and increment counter by number of seconds since last read
|
||||||
|
ldrReading = analogRead(ldrPin);
|
||||||
|
if (ldrReading <= ldrThreshold) {
|
||||||
|
ldrOnCount = ldrOnCount + 10;
|
||||||
|
ldrOffCount = 0;
|
||||||
|
} else {
|
||||||
|
ldrOffCount = ldrOffCount + 10;
|
||||||
|
ldrOnCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ldrOnCount >= (ldrThresholdMinutes * 60)) {
|
||||||
|
ldrOnCount = 0;
|
||||||
|
// If LEDs were previously off, turn on
|
||||||
|
if (ldrLEDState == 0) {
|
||||||
|
applyPreset(ldrOnPreset);
|
||||||
|
ldrLEDState = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ldrOffCount >= (ldrThresholdMinutes * 60)) {
|
||||||
|
ldrOffCount = 0;
|
||||||
|
// If LEDs were previously on, turn off
|
||||||
|
if (ldrLEDState == 1) {
|
||||||
|
applyPreset(ldrOffPreset);
|
||||||
|
ldrLEDState = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// LDR is disabled, reset variables to default
|
||||||
|
ldrReading = 0;
|
||||||
|
ldrOnCount = 0;
|
||||||
|
ldrOffCount = 0;
|
||||||
|
ldrLEDState = 0;
|
||||||
|
ldrEnabledPreviously = false;
|
||||||
|
}
|
||||||
|
lastMillis = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject& root) {
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top["Enabled"] = ldrEnabled;
|
||||||
|
top["LDR Pin"] = ldrPin;
|
||||||
|
top["Threshold Minutes"] = ldrThresholdMinutes;
|
||||||
|
top["Threshold"] = ldrThreshold;
|
||||||
|
top["On Preset"] = ldrOnPreset;
|
||||||
|
top["Off Preset"] = ldrOffPreset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject& root) {
|
||||||
|
int8_t oldLdrPin = ldrPin;
|
||||||
|
JsonObject top = root[FPSTR(_name)];
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
configComplete &= getJsonValue(top["Enabled"], ldrEnabled);
|
||||||
|
configComplete &= getJsonValue(top["LDR Pin"], ldrPin);
|
||||||
|
configComplete &= getJsonValue(top["Threshold Minutes"], ldrThresholdMinutes);
|
||||||
|
configComplete &= getJsonValue(top["Threshold"], ldrThreshold);
|
||||||
|
configComplete &= getJsonValue(top["On Preset"], ldrOnPreset);
|
||||||
|
configComplete &= getJsonValue(top["Off Preset"], ldrOffPreset);
|
||||||
|
|
||||||
|
if (initDone && (ldrPin != oldLdrPin)) {
|
||||||
|
// pin changed - un-register previous pin, register new pin
|
||||||
|
if (oldLdrPin >= 0) pinManager.deallocatePin(oldLdrPin, PinOwner::UM_LDR_DUSK_DAWN);
|
||||||
|
setup(); // setup new pin
|
||||||
|
}
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject& root) {
|
||||||
|
// If "u" object does not exist yet we need to create it
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
JsonArray LDR_Enabled = user.createNestedArray("LDR dusk/dawn enabled");
|
||||||
|
LDR_Enabled.add(ldrEnabled);
|
||||||
|
if (!ldrEnabled) return; // do not add more if usermod is disabled
|
||||||
|
|
||||||
|
JsonArray LDR_Reading = user.createNestedArray("LDR reading");
|
||||||
|
LDR_Reading.add(ldrReading);
|
||||||
|
|
||||||
|
JsonArray LDR_State = user.createNestedArray("LDR turned LEDs on");
|
||||||
|
LDR_State.add(bool(ldrLEDState));
|
||||||
|
|
||||||
|
// Optional debug information:
|
||||||
|
//JsonArray LDR_On_Count = user.createNestedArray("LDR on count");
|
||||||
|
//LDR_On_Count.add(ldrOnCount);
|
||||||
|
|
||||||
|
//JsonArray LDR_Off_Count = user.createNestedArray("LDR off count");
|
||||||
|
//LDR_Off_Count.add(ldrOffCount);
|
||||||
|
|
||||||
|
//bool pinValid = ((ldrPin >= 0) && (digitalPinToAnalogChannel(ldrPin) >= 0));
|
||||||
|
//if (pinManager.getPinOwner(ldrPin) != PinOwner::UM_LDR_DUSK_DAWN) pinValid = false;
|
||||||
|
//JsonArray LDR_valid = user.createNestedArray(F("LDR pin"));
|
||||||
|
//LDR_valid.add(ldrPin);
|
||||||
|
//LDR_valid.add(pinValid ? F(" OK"): F(" invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId() {
|
||||||
|
return USERMOD_ID_LDR_DUSK_DAWN;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char LDR_Dusk_Dawn_v2::_name[] PROGMEM = "LDR_Dusk_Dawn_v2";
|
||||||
64
usermods/MAX17048_v2/readme.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Adafruit MAX17048 Usermod (LiPo & LiIon Battery Monitor & Fuel Gauge)
|
||||||
|
This usermod reads information from an Adafruit MAX17048 and outputs the following:
|
||||||
|
- Battery Voltage
|
||||||
|
- Battery Level Percentage
|
||||||
|
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
Libraries:
|
||||||
|
- `Adafruit_BusIO@~1.14.5` (by [adafruit](https://github.com/adafruit/Adafruit_BusIO))
|
||||||
|
- `Adafruit_MAX1704X@~1.0.2` (by [adafruit](https://github.com/adafruit/Adafruit_MAX1704X))
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
To enable, compile with `USERMOD_MAX17048` define in the build_flags (e.g. in `platformio.ini` or `platformio_override.ini`) such as in the example below:
|
||||||
|
```ini
|
||||||
|
[env:usermod_max17048_d1_mini]
|
||||||
|
extends = env:d1_mini
|
||||||
|
build_flags =
|
||||||
|
${common.build_flags_esp8266}
|
||||||
|
-D USERMOD_MAX17048
|
||||||
|
lib_deps =
|
||||||
|
${esp8266.lib_deps}
|
||||||
|
https://github.com/adafruit/Adafruit_BusIO @ 1.14.5
|
||||||
|
https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
The following settings can be set at compile-time but are configurable on the usermod menu (except First Monitor time):
|
||||||
|
- USERMOD_MAX17048_MIN_MONITOR_INTERVAL (the min number of milliseconds between checks, defaults to 10,000 ms)
|
||||||
|
- USERMOD_MAX17048_MAX_MONITOR_INTERVAL (the max number of milliseconds between checks, defaults to 10,000 ms)
|
||||||
|
- USERMOD_MAX17048_FIRST_MONITOR_AT
|
||||||
|
|
||||||
|
|
||||||
|
Additionally, the Usermod Menu allows you to:
|
||||||
|
- Enable or Disable the usermod
|
||||||
|
- Enable or Disable Home Assistant Discovery (turn on/off to sent MQTT Discovery entries for Home Assistant)
|
||||||
|
- Configure SCL/SDA GPIO Pins
|
||||||
|
|
||||||
|
## API
|
||||||
|
The following method is available to interact with the usermod from other code modules:
|
||||||
|
- `getBatteryVoltageV` read the last battery voltage (in Volt) obtained from the sensor
|
||||||
|
- `getBatteryPercent` reads the last battery percentage obtained from the sensor
|
||||||
|
|
||||||
|
## MQTT
|
||||||
|
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
|
||||||
|
Measurement type | MQTT topic
|
||||||
|
--- | ---
|
||||||
|
Battery Voltage | `<deviceTopic>/batteryVoltage`
|
||||||
|
Battery Percent | `<deviceTopic>/batteryPercent`
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
Carlos Cruz [@ccruz09](https://github.com/ccruz09)
|
||||||
|
|
||||||
|
|
||||||
|
## Revision History
|
||||||
|
Jan 2024
|
||||||
|
- Added Home Assistant Discovery
|
||||||
|
- Implemented PinManager to register pins
|
||||||
|
- Added API call for other modules to read battery voltage and percentage
|
||||||
|
- Added info-screen outputs
|
||||||
|
- Updated `readme.md`
|
||||||
281
usermods/MAX17048_v2/usermod_max17048.h
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
// force the compiler to show a warning to confirm that this file is included
|
||||||
|
#warning **** Included USERMOD_MAX17048 V2.0 ****
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include "Adafruit_MAX1704X.h"
|
||||||
|
|
||||||
|
|
||||||
|
// the max interval to check battery level, 10 seconds
|
||||||
|
#ifndef USERMOD_MAX17048_MAX_MONITOR_INTERVAL
|
||||||
|
#define USERMOD_MAX17048_MAX_MONITOR_INTERVAL 10000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the min interval to check battery level, 500 ms
|
||||||
|
#ifndef USERMOD_MAX17048_MIN_MONITOR_INTERVAL
|
||||||
|
#define USERMOD_MAX17048_MIN_MONITOR_INTERVAL 500
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// how many seconds after boot to perform the first check, 10 seconds
|
||||||
|
#ifndef USERMOD_MAX17048_FIRST_MONITOR_AT
|
||||||
|
#define USERMOD_MAX17048_FIRST_MONITOR_AT 10000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Usermod to display Battery Life using Adafruit's MAX17048 LiPoly/ LiIon Fuel Gauge and Battery Monitor.
|
||||||
|
*/
|
||||||
|
class Usermod_MAX17048 : public Usermod {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
|
unsigned long maxReadingInterval = USERMOD_MAX17048_MAX_MONITOR_INTERVAL;
|
||||||
|
unsigned long minReadingInterval = USERMOD_MAX17048_MIN_MONITOR_INTERVAL;
|
||||||
|
unsigned long lastCheck = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT);
|
||||||
|
unsigned long lastSend = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT);
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t VoltageDecimals = 3; // Number of decimal places in published voltage values
|
||||||
|
uint8_t PercentDecimals = 1; // Number of decimal places in published percent values
|
||||||
|
|
||||||
|
// string that are used multiple time (this will save some flash memory)
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
static const char _maxReadInterval[];
|
||||||
|
static const char _minReadInterval[];
|
||||||
|
static const char _HomeAssistantDiscovery[];
|
||||||
|
|
||||||
|
bool monitorFound = false;
|
||||||
|
bool firstReadComplete = false;
|
||||||
|
bool initDone = false;
|
||||||
|
|
||||||
|
Adafruit_MAX17048 maxLipo;
|
||||||
|
float lastBattVoltage = -10;
|
||||||
|
float lastBattPercent = -1;
|
||||||
|
|
||||||
|
// MQTT and Home Assistant Variables
|
||||||
|
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information
|
||||||
|
bool mqttInitialized = false;
|
||||||
|
|
||||||
|
void _mqttInitialize()
|
||||||
|
{
|
||||||
|
char mqttBatteryVoltageTopic[128];
|
||||||
|
char mqttBatteryPercentTopic[128];
|
||||||
|
|
||||||
|
snprintf_P(mqttBatteryVoltageTopic, 127, PSTR("%s/batteryVoltage"), mqttDeviceTopic);
|
||||||
|
snprintf_P(mqttBatteryPercentTopic, 127, PSTR("%s/batteryPercent"), mqttDeviceTopic);
|
||||||
|
|
||||||
|
if (HomeAssistantDiscovery) {
|
||||||
|
_createMqttSensor(F("BatteryVoltage"), mqttBatteryVoltageTopic, "voltage", F("V"));
|
||||||
|
_createMqttSensor(F("BatteryPercent"), mqttBatteryPercentTopic, "battery", F("%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
inline void enable(bool enable) { enabled = enable; }
|
||||||
|
|
||||||
|
inline bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// do your set-up here
|
||||||
|
if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; }
|
||||||
|
monitorFound = maxLipo.begin();
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// if usermod is disabled or called during strip updating just exit
|
||||||
|
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
|
||||||
|
if (!enabled || strip.isUpdating()) return;
|
||||||
|
|
||||||
|
unsigned long now = millis();
|
||||||
|
|
||||||
|
if (now - lastCheck < minReadingInterval) { return; }
|
||||||
|
|
||||||
|
bool shouldUpdate = now - lastSend > maxReadingInterval;
|
||||||
|
|
||||||
|
float battVoltage = maxLipo.cellVoltage();
|
||||||
|
float battPercent = maxLipo.cellPercent();
|
||||||
|
lastCheck = millis();
|
||||||
|
firstReadComplete = true;
|
||||||
|
|
||||||
|
if (shouldUpdate)
|
||||||
|
{
|
||||||
|
lastBattVoltage = roundf(battVoltage * powf(10, VoltageDecimals)) / powf(10, VoltageDecimals);
|
||||||
|
lastBattPercent = roundf(battPercent * powf(10, PercentDecimals)) / powf(10, PercentDecimals);
|
||||||
|
lastSend = millis();
|
||||||
|
|
||||||
|
publishMqtt("batteryVoltage", String(lastBattVoltage, VoltageDecimals).c_str());
|
||||||
|
publishMqtt("batteryPercent", String(lastBattPercent, PercentDecimals).c_str());
|
||||||
|
DEBUG_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V"));
|
||||||
|
DEBUG_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMqttConnect(bool sessionPresent)
|
||||||
|
{
|
||||||
|
if (WLED_MQTT_CONNECTED && !mqttInitialized)
|
||||||
|
{
|
||||||
|
_mqttInitialize();
|
||||||
|
mqttInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float getBatteryVoltageV() {
|
||||||
|
return (float) lastBattVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float getBatteryPercent() {
|
||||||
|
return (float) lastBattPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonInfo(JsonObject& root)
|
||||||
|
{
|
||||||
|
// if "u" object does not exist yet wee need to create it
|
||||||
|
JsonObject user = root["u"];
|
||||||
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
|
|
||||||
|
JsonArray battery_json = user.createNestedArray(F("Battery Monitor"));
|
||||||
|
if (!enabled) {
|
||||||
|
battery_json.add(F("Disabled"));
|
||||||
|
}
|
||||||
|
else if(!monitorFound) {
|
||||||
|
battery_json.add(F("MAX17048 Not Found"));
|
||||||
|
}
|
||||||
|
else if (!firstReadComplete) {
|
||||||
|
// if we haven't read the sensor yet, let the user know
|
||||||
|
// that we are still waiting for the first measurement
|
||||||
|
battery_json.add((USERMOD_MAX17048_FIRST_MONITOR_AT - millis()) / 1000);
|
||||||
|
battery_json.add(F(" sec until read"));
|
||||||
|
} else {
|
||||||
|
battery_json.add(F("Enabled"));
|
||||||
|
JsonArray voltage_json = user.createNestedArray(F("Battery Voltage"));
|
||||||
|
voltage_json.add(lastBattVoltage);
|
||||||
|
voltage_json.add(F("V"));
|
||||||
|
JsonArray percent_json = user.createNestedArray(F("Battery Percent"));
|
||||||
|
percent_json.add(lastBattPercent);
|
||||||
|
percent_json.add(F("%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToJsonState(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
|
if (usermod.isNull())
|
||||||
|
{
|
||||||
|
usermod = root.createNestedObject(FPSTR(_name));
|
||||||
|
}
|
||||||
|
usermod[FPSTR(_enabled)] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void readFromJsonState(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
|
if (!usermod.isNull())
|
||||||
|
{
|
||||||
|
if (usermod[FPSTR(_enabled)].is<bool>())
|
||||||
|
{
|
||||||
|
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
top[FPSTR(_maxReadInterval)] = maxReadingInterval;
|
||||||
|
top[FPSTR(_minReadInterval)] = minReadingInterval;
|
||||||
|
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery;
|
||||||
|
DEBUG_PRINT(F(_name));
|
||||||
|
DEBUG_PRINTLN(F(" config saved."));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFromConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, USERMOD_MAX17048_MAX_MONITOR_INTERVAL);
|
||||||
|
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, USERMOD_MAX17048_MIN_MONITOR_INTERVAL);
|
||||||
|
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false);
|
||||||
|
|
||||||
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
|
if (!initDone) {
|
||||||
|
// first run: reading from cfg.json
|
||||||
|
DEBUG_PRINTLN(F(" config loaded."));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||||
|
// changing parameters from settings page
|
||||||
|
}
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_MAX17048;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// add more strings here to reduce flash memory usage
|
||||||
|
const char Usermod_MAX17048::_name[] PROGMEM = "Adafruit MAX17048 Battery Monitor";
|
||||||
|
const char Usermod_MAX17048::_enabled[] PROGMEM = "enabled";
|
||||||
|
const char Usermod_MAX17048::_maxReadInterval[] PROGMEM = "max-read-interval-ms";
|
||||||
|
const char Usermod_MAX17048::_minReadInterval[] PROGMEM = "min-read-interval-ms";
|
||||||
|
const char Usermod_MAX17048::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscovery";
|
||||||
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
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,65 +13,29 @@ The info page in the web interface shows the remaining time of the off timer. Us
|
|||||||
|
|
||||||
## Sensor connection
|
## 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.
|
[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).
|
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
|
## Usermod installation
|
||||||
|
|
||||||
1. Copy the file `usermod_PIR_sensor_switch.h` to the `wled00` directory.
|
**NOTE:** Usermod has been included in master branch of WLED so it can be compiled in directly just by defining `-D USERMOD_PIRSWITCH` and optionally `-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`.
|
||||||
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`.
|
|
||||||
|
|
||||||
Example **usermods_list.cpp**:
|
## API to enable/disable the PIR sensor from outside. For example from another usermod:
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
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`.
|
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 latitude/longitude are set to determine sunrise/sunset times).
|
||||||
|
|
||||||
### There are two options to get access to the usermod instance:
|
### 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
|
or
|
||||||
|
|
||||||
@@ -100,7 +64,7 @@ class MyUsermod : public Usermod {
|
|||||||
|
|
||||||
### Configuration options
|
### Configuration options
|
||||||
|
|
||||||
Usermod can be configured in Usermods settings page.
|
Usermod can be configured via the Usermods settings page.
|
||||||
|
|
||||||
* `PIRenabled` - enable/disable usermod
|
* `PIRenabled` - enable/disable usermod
|
||||||
* `pin` - dynamically change GPIO pin where PIR sensor is attached to ESP
|
* `pin` - dynamically change GPIO pin where PIR sensor is attached to ESP
|
||||||
@@ -108,9 +72,12 @@ 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)
|
* `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)
|
* `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)
|
* `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
|
* `mqtt-only` - send only 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)
|
* `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
|
* `notifications` - enable or disable sending notifications to other WLED instances using Sync button
|
||||||
|
* `HA-discovery` - enable automatic discovery in Home Assistant
|
||||||
|
* `override` - override PIR input when WLED state is changed using UI
|
||||||
|
* `domoticz-idx` - Domoticz virtual switch ID (used with MQTT `domoticz/in`)
|
||||||
|
|
||||||
|
|
||||||
Have fun - @gegu & @blazoncek
|
Have fun - @gegu & @blazoncek
|
||||||
@@ -121,4 +88,16 @@ Have fun - @gegu & @blazoncek
|
|||||||
|
|
||||||
2021-11
|
2021-11
|
||||||
* Added information about dynamic configuration options
|
* Added information about dynamic configuration options
|
||||||
* Added option to temporary enable/disble usermod from WLED UI (Info dialog)
|
* Added option to temporary enable/disable 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.
|
||||||
|
|
||||||
|
2023-??
|
||||||
|
* Override option
|
||||||
|
* Domoticz virtual switch ID (used with MQTT `domoticz/in`)
|
||||||
|
|
||||||
|
2024-02
|
||||||
|
* Added compile time option to expand number of PIR sensors (they are logically ORed) `-D PIR_SENSOR_MAX_SENSORS=3`
|
||||||
@@ -11,72 +11,72 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef PIR_SENSOR_OFF_SEC
|
||||||
|
#define PIR_SENSOR_OFF_SEC 600
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PIR_SENSOR_MAX_SENSORS
|
||||||
|
#define PIR_SENSOR_MAX_SENSORS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This usermod handles PIR sensor states.
|
* This usermod handles PIR sensor states.
|
||||||
* The strip will be switched on and the off timer will be resetted when the sensor goes HIGH.
|
* 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.
|
* When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off.
|
||||||
*
|
* Maintained by: @blazoncek
|
||||||
*
|
*
|
||||||
* Usermods allow you to add own functionality to WLED more easily
|
* Usermods allow you to add own functionality to WLED more easily
|
||||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
* 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.
|
* 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.
|
* 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
|
class PIRsensorSwitch : public Usermod
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
// constructor
|
||||||
* constructor
|
|
||||||
*/
|
|
||||||
PIRsensorSwitch() {}
|
PIRsensorSwitch() {}
|
||||||
/**
|
// destructor
|
||||||
* desctructor
|
|
||||||
*/
|
|
||||||
~PIRsensorSwitch() {}
|
~PIRsensorSwitch() {}
|
||||||
|
|
||||||
/**
|
//Enable/Disable the PIR sensor
|
||||||
* Enable/Disable the PIR sensor
|
inline void EnablePIRsensor(bool en) { enabled = en; }
|
||||||
*/
|
|
||||||
void EnablePIRsensor(bool en) { enabled = en; }
|
// Get PIR sensor enabled/disabled state
|
||||||
/**
|
inline bool PIRsensorEnabled() { return enabled; }
|
||||||
* Get PIR sensor enabled/disabled state
|
|
||||||
*/
|
|
||||||
bool PIRsensorEnabled() { return enabled; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
byte prevPreset = 0;
|
byte prevPreset = 0;
|
||||||
byte prevPlaylist = 0;
|
byte prevPlaylist = 0;
|
||||||
bool savedState = false;
|
|
||||||
|
|
||||||
uint32_t offTimerStart = 0; // off timer start time
|
volatile unsigned long 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
|
volatile bool PIRtriggered = false; // did PIR trigger?
|
||||||
byte sensorPinState = LOW; // current PIR sensor pin state
|
|
||||||
bool initDone = false; // status of initialization
|
bool initDone = false; // status of initialization
|
||||||
bool PIRtriggered = false;
|
|
||||||
unsigned long lastLoop = 0;
|
unsigned long lastLoop = 0;
|
||||||
|
bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state
|
||||||
|
|
||||||
// configurable parameters
|
// configurable parameters
|
||||||
|
#if PIR_SENSOR_PIN < 0
|
||||||
|
bool enabled = false; // PIR sensor disabled
|
||||||
|
#else
|
||||||
bool enabled = true; // PIR sensor enabled
|
bool enabled = true; // PIR sensor enabled
|
||||||
int8_t PIRsensorPin = PIR_SENSOR_PIN; // PIR sensor pin
|
#endif
|
||||||
uint32_t m_switchOffDelay = 600000; // delay before switch off after the sensor state goes LOW (10min)
|
int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {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_onPreset = 0; // on preset
|
||||||
uint8_t m_offPreset = 0; // off 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_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)
|
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)
|
// 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_offOnly = false;
|
||||||
|
bool m_offMode = offMode;
|
||||||
|
bool m_override = false;
|
||||||
|
|
||||||
|
// Home Assistant
|
||||||
|
bool HomeAssistantDiscovery = false; // is HA discovery turned on
|
||||||
|
int16_t idx = -1; // Domoticz virtual switch idx
|
||||||
|
|
||||||
// strings to reduce flash memory usage (used more than twice)
|
// strings to reduce flash memory usage (used more than twice)
|
||||||
static const char _name[];
|
static const char _name[];
|
||||||
@@ -87,13 +87,124 @@ private:
|
|||||||
static const char _nightTime[];
|
static const char _nightTime[];
|
||||||
static const char _mqttOnly[];
|
static const char _mqttOnly[];
|
||||||
static const char _offOnly[];
|
static const char _offOnly[];
|
||||||
static const char _notify[];
|
static const char _haDiscovery[];
|
||||||
|
static const char _override[];
|
||||||
|
static const char _domoticzIDX[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if it is daytime
|
* check if it is daytime
|
||||||
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
* if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime
|
||||||
*/
|
*/
|
||||||
bool isDayTime() {
|
static bool isDayTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* switch strip on/off
|
||||||
|
*/
|
||||||
|
void switchStrip(bool switchOn);
|
||||||
|
void publishMqtt(bool switchOn);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and update PIR sensor state.
|
||||||
|
* Initialize/reset switch off timer
|
||||||
|
*/
|
||||||
|
bool updatePIRsensorState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* switch off the strip if the delay has elapsed
|
||||||
|
*/
|
||||||
|
bool handleOffTimer();
|
||||||
|
|
||||||
|
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() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
||||||
|
*/
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onStateChanged() is used to detect WLED state change
|
||||||
|
*/
|
||||||
|
void onStateChange(uint8_t mode) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provide the changeable values
|
||||||
|
*/
|
||||||
|
void addToConfig(JsonObject &root) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provide UI information and allow extending UI options
|
||||||
|
*/
|
||||||
|
void appendConfigData() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() override { 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::_override[] PROGMEM = "override";
|
||||||
|
const char PIRsensorSwitch::_domoticzIDX[] PROGMEM = "domoticz-idx";
|
||||||
|
|
||||||
|
bool PIRsensorSwitch::isDayTime() {
|
||||||
updateLocalTime();
|
updateLocalTime();
|
||||||
uint8_t hr = hour(localTime);
|
uint8_t hr = hour(localTime);
|
||||||
uint8_t mi = minute(localTime);
|
uint8_t mi = minute(localTime);
|
||||||
@@ -113,150 +224,184 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::switchStrip(bool switchOn)
|
||||||
* switch strip on/off
|
|
||||||
*/
|
|
||||||
void switchStrip(bool switchOn)
|
|
||||||
{
|
{
|
||||||
if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return;
|
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;
|
PIRtriggered = switchOn;
|
||||||
|
DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off");
|
||||||
if (switchOn) {
|
if (switchOn) {
|
||||||
if (m_onPreset) {
|
if (m_onPreset) {
|
||||||
if (currentPlaylist>0) prevPlaylist = currentPlaylist;
|
if (currentPlaylist>0 && !offMode) {
|
||||||
else if (currentPreset>0) prevPreset = currentPreset;
|
prevPlaylist = currentPlaylist;
|
||||||
else {
|
unloadPlaylist();
|
||||||
|
} else if (currentPreset>0 && !offMode) {
|
||||||
|
prevPreset = currentPreset;
|
||||||
|
} else {
|
||||||
saveTemporaryPreset();
|
saveTemporaryPreset();
|
||||||
savedState = true;
|
|
||||||
prevPlaylist = 0;
|
prevPlaylist = 0;
|
||||||
prevPreset = 0;
|
prevPreset = 255;
|
||||||
}
|
}
|
||||||
applyPreset(m_onPreset, NotifyUpdateMode);
|
applyPreset(m_onPreset, CALL_MODE_BUTTON_PRESET);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// preset not assigned
|
// preset not assigned
|
||||||
if (bri == 0) {
|
if (bri == 0) {
|
||||||
bri = briLast;
|
bri = briLast;
|
||||||
stateUpdated(NotifyUpdateMode);
|
stateUpdated(CALL_MODE_BUTTON);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (m_offPreset) {
|
if (m_offPreset) {
|
||||||
applyPreset(m_offPreset, NotifyUpdateMode);
|
applyPreset(m_offPreset, CALL_MODE_BUTTON_PRESET);
|
||||||
return;
|
return;
|
||||||
} else if (prevPlaylist) {
|
} else if (prevPlaylist) {
|
||||||
applyPreset(prevPlaylist, NotifyUpdateMode);
|
if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, CALL_MODE_BUTTON_PRESET);
|
||||||
prevPlaylist = 0;
|
prevPlaylist = 0;
|
||||||
return;
|
return;
|
||||||
} else if (prevPreset) {
|
} else if (prevPreset) {
|
||||||
applyPreset(prevPreset, NotifyUpdateMode);
|
if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, CALL_MODE_BUTTON_PRESET); }
|
||||||
|
else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); }
|
||||||
prevPreset = 0;
|
prevPreset = 0;
|
||||||
return;
|
return;
|
||||||
} else if (savedState) {
|
|
||||||
applyTemporaryPreset();
|
|
||||||
savedState = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// preset not assigned
|
// preset not assigned
|
||||||
if (bri != 0) {
|
if (bri != 0) {
|
||||||
briLast = bri;
|
briLast = bri;
|
||||||
bri = 0;
|
bri = 0;
|
||||||
stateUpdated(NotifyUpdateMode);
|
stateUpdated(CALL_MODE_BUTTON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void publishMqtt(const char* state)
|
void PIRsensorSwitch::publishMqtt(bool switchOn)
|
||||||
{
|
{
|
||||||
|
#ifndef WLED_DISABLE_MQTT
|
||||||
//Check if MQTT Connected, otherwise it will crash the 8266
|
//Check if MQTT Connected, otherwise it will crash the 8266
|
||||||
if (WLED_MQTT_CONNECTED) {
|
if (WLED_MQTT_CONNECTED) {
|
||||||
char subuf[64];
|
char buf[128];
|
||||||
strcpy(subuf, mqttDeviceTopic);
|
sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40
|
||||||
strcat_P(subuf, PSTR("/motion"));
|
mqtt->publish(buf, 0, false, switchOn?"on":"off");
|
||||||
mqtt->publish(subuf, 0, false, state);
|
// Domoticz formatted message
|
||||||
|
if (idx > 0) {
|
||||||
|
StaticJsonDocument <128> msg;
|
||||||
|
msg[F("idx")] = idx;
|
||||||
|
msg[F("RSSI")] = WiFi.RSSI();
|
||||||
|
msg[F("command")] = F("switchlight");
|
||||||
|
msg[F("switchcmd")] = switchOn ? F("On") : F("Off");
|
||||||
|
serializeJson(msg, buf, 128);
|
||||||
|
mqtt->publish("domoticz/in", 0, false, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::publishHomeAssistantAutodiscovery()
|
||||||
* Read and update PIR sensor state.
|
|
||||||
* Initilize/reset switch off timer
|
|
||||||
*/
|
|
||||||
bool updatePIRsensorState()
|
|
||||||
{
|
{
|
||||||
bool pinState = digitalRead(PIRsensorPin);
|
#ifndef WLED_DISABLE_MQTT
|
||||||
if (pinState != sensorPinState) {
|
if (WLED_MQTT_CONNECTED) {
|
||||||
sensorPinState = pinState; // change previous state
|
StaticJsonDocument<600> doc;
|
||||||
|
char uid[24], json_str[1024], buf[128];
|
||||||
|
|
||||||
if (sensorPinState == HIGH) {
|
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")] = F(WLED_BRAND);
|
||||||
|
device[F("mdl")] = F(WLED_PRODUCT_NAME);
|
||||||
|
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?
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PIRsensorSwitch::updatePIRsensorState()
|
||||||
|
{
|
||||||
|
bool stateChanged = false;
|
||||||
|
bool allOff = true;
|
||||||
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
|
||||||
|
if (PIRsensorPin[i] < 0) continue;
|
||||||
|
|
||||||
|
bool pinState = digitalRead(PIRsensorPin[i]);
|
||||||
|
if (pinState != sensorPinState[i]) {
|
||||||
|
sensorPinState[i] = pinState; // change previous state
|
||||||
|
stateChanged = true;
|
||||||
|
|
||||||
|
if (sensorPinState[i] == HIGH) {
|
||||||
offTimerStart = 0;
|
offTimerStart = 0;
|
||||||
|
allOff = false;
|
||||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true);
|
||||||
publishMqtt("on");
|
}
|
||||||
} else /*if (bri != 0)*/ {
|
}
|
||||||
|
}
|
||||||
|
if (stateChanged) {
|
||||||
|
publishMqtt(!allOff);
|
||||||
// start switch off timer
|
// start switch off timer
|
||||||
offTimerStart = millis();
|
if (allOff) offTimerStart = millis();
|
||||||
}
|
}
|
||||||
return true;
|
return stateChanged;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
bool PIRsensorSwitch::handleOffTimer()
|
||||||
* switch off the strip if the delay has elapsed
|
|
||||||
*/
|
|
||||||
bool handleOffTimer()
|
|
||||||
{
|
{
|
||||||
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay)
|
if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) {
|
||||||
{
|
|
||||||
if (enabled == true)
|
|
||||||
{
|
|
||||||
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false);
|
|
||||||
publishMqtt("off");
|
|
||||||
}
|
|
||||||
offTimerStart = 0;
|
offTimerStart = 0;
|
||||||
|
if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
|
||||||
//Functions called by WLED
|
//Functions called by WLED
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::setup()
|
||||||
* 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) {
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
|
||||||
|
sensorPinState[i] = LOW;
|
||||||
|
if (PIRsensorPin[i] < 0) continue;
|
||||||
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||||
if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) {
|
if (pinManager.allocatePin(PIRsensorPin[i], false, PinOwner::UM_PIR)) {
|
||||||
// PIR Sensor mode INPUT_PULLUP
|
// PIR Sensor mode INPUT_PULLDOWN
|
||||||
pinMode(PIRsensorPin, INPUT_PULLUP);
|
#ifdef ESP8266
|
||||||
sensorPinState = digitalRead(PIRsensorPin);
|
pinMode(PIRsensorPin[i], PIRsensorPin[i]==16 ? INPUT_PULLDOWN_16 : INPUT_PULLUP); // ESP8266 has INPUT_PULLDOWN on GPIO16 only
|
||||||
|
#else
|
||||||
|
pinMode(PIRsensorPin[i], INPUT_PULLDOWN);
|
||||||
|
#endif
|
||||||
|
sensorPinState[i] = digitalRead(PIRsensorPin[i]);
|
||||||
} else {
|
} else {
|
||||||
if (PIRsensorPin >= 0) {
|
DEBUG_PRINT(F("PIRSensorSwitch pin ")); DEBUG_PRINTLN(i); DEBUG_PRINTLN(F(" allocation failed."));
|
||||||
DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed."));
|
PIRsensorPin[i] = -1; // allocation failed
|
||||||
}
|
|
||||||
PIRsensorPin = -1; // allocation failed
|
|
||||||
enabled = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initDone = true;
|
initDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::onMqttConnect(bool sessionPresent)
|
||||||
* connected() is called every time the WiFi is (re)connected
|
|
||||||
* Use it to initialize network interfaces
|
|
||||||
*/
|
|
||||||
void connected()
|
|
||||||
{
|
{
|
||||||
|
if (HomeAssistantDiscovery) {
|
||||||
|
publishHomeAssistantAutodiscovery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::loop()
|
||||||
* loop() is called continuously. Here you can check for events, read sensors, etc.
|
|
||||||
*/
|
|
||||||
void loop()
|
|
||||||
{
|
{
|
||||||
// only check sensors 4x/s
|
// only check sensors 5x/s
|
||||||
if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return;
|
if (!enabled || millis() - lastLoop < 200) return;
|
||||||
lastLoop = millis();
|
lastLoop = millis();
|
||||||
|
|
||||||
if (!updatePIRsensorState()) {
|
if (!updatePIRsensorState()) {
|
||||||
@@ -264,80 +409,78 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::addToJsonInfo(JsonObject &root)
|
||||||
* 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"];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull()) user = root.createNestedObject("u");
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
String uiDomString = F("<button class=\"btn\" onclick=\"requestJson({");
|
bool state = LOW;
|
||||||
uiDomString += FPSTR(_name);
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
|
||||||
uiDomString += F(":{");
|
if (PIRsensorPin[i] >= 0) state |= sensorPinState[i];
|
||||||
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
|
|
||||||
|
|
||||||
|
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||||
|
|
||||||
|
String uiDomString;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (offTimerStart > 0)
|
if (offTimerStart > 0) {
|
||||||
{
|
|
||||||
uiDomString = "";
|
uiDomString = "";
|
||||||
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
|
unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000;
|
||||||
if (offSeconds >= 3600)
|
if (offSeconds >= 3600) {
|
||||||
{
|
|
||||||
uiDomString += (offSeconds / 3600);
|
uiDomString += (offSeconds / 3600);
|
||||||
uiDomString += F("h ");
|
uiDomString += F("h ");
|
||||||
offSeconds %= 3600;
|
offSeconds %= 3600;
|
||||||
}
|
}
|
||||||
if (offSeconds >= 60)
|
if (offSeconds >= 60) {
|
||||||
{
|
|
||||||
uiDomString += (offSeconds / 60);
|
uiDomString += (offSeconds / 60);
|
||||||
offSeconds %= 60;
|
offSeconds %= 60;
|
||||||
}
|
} else if (uiDomString.length() > 0) {
|
||||||
else if (uiDomString.length() > 0)
|
|
||||||
{
|
|
||||||
uiDomString += 0;
|
uiDomString += 0;
|
||||||
}
|
}
|
||||||
if (uiDomString.length() > 0)
|
if (uiDomString.length() > 0) {
|
||||||
{
|
|
||||||
uiDomString += F("min ");
|
uiDomString += F("min ");
|
||||||
}
|
}
|
||||||
uiDomString += (offSeconds);
|
uiDomString += (offSeconds);
|
||||||
infoArr.add(uiDomString + F("s"));
|
infoArr.add(uiDomString + F("s"));
|
||||||
} else {
|
} else {
|
||||||
infoArr.add(sensorPinState ? F("sensor on") : F("inactive"));
|
infoArr.add(state ? F("sensor on") : F("inactive"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
infoArr.add(F("disabled"));
|
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);
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
JsonObject sensor = root[F("sensor")];
|
||||||
|
if (sensor.isNull()) sensor = root.createNestedObject(F("sensor"));
|
||||||
|
sensor[F("motion")] = state || offTimerStart>0 ? true : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::onStateChange(uint8_t mode) {
|
||||||
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
if (!initDone) return;
|
||||||
* Values in the state object may be modified by connected clients
|
DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart);
|
||||||
*/
|
if (m_override && PIRtriggered && offTimerStart) { // debounce
|
||||||
/*
|
// checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger
|
||||||
void addToJsonState(JsonObject &root)
|
DEBUG_PRINTLN(F("PIR: Canceled."));
|
||||||
{
|
offTimerStart = 0;
|
||||||
|
PIRtriggered = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::readFromJsonState(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()
|
if (!initDone) return; // prevent crash on boot applyPreset()
|
||||||
JsonObject usermod = root[FPSTR(_name)];
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
@@ -348,35 +491,42 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PIRsensorSwitch::addToConfig(JsonObject &root)
|
||||||
/**
|
|
||||||
* provide the changeable values
|
|
||||||
*/
|
|
||||||
void addToConfig(JsonObject &root)
|
|
||||||
{
|
{
|
||||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top[FPSTR(_enabled)] = enabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000;
|
||||||
top["pin"] = PIRsensorPin;
|
JsonArray pinArray = top.createNestedArray("pin");
|
||||||
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) pinArray.add(PIRsensorPin[i]);
|
||||||
top[FPSTR(_onPreset)] = m_onPreset;
|
top[FPSTR(_onPreset)] = m_onPreset;
|
||||||
top[FPSTR(_offPreset)] = m_offPreset;
|
top[FPSTR(_offPreset)] = m_offPreset;
|
||||||
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
top[FPSTR(_nightTime)] = m_nightTimeOnly;
|
||||||
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
top[FPSTR(_mqttOnly)] = m_mqttOnly;
|
||||||
top[FPSTR(_offOnly)] = m_offOnly;
|
top[FPSTR(_offOnly)] = m_offOnly;
|
||||||
top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY);
|
top[FPSTR(_override)] = m_override;
|
||||||
|
top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery;
|
||||||
|
top[FPSTR(_domoticzIDX)] = idx;
|
||||||
DEBUG_PRINTLN(F("PIR config saved."));
|
DEBUG_PRINTLN(F("PIR config saved."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void PIRsensorSwitch::appendConfigData()
|
||||||
* 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;
|
oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field
|
||||||
int8_t oldPin = PIRsensorPin;
|
oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field
|
||||||
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
|
||||||
|
char str[128];
|
||||||
|
sprintf_P(str, PSTR("addInfo('PIRsensorSwitch:pin[]',%d,'','#%d');"), i, i);
|
||||||
|
oappend(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PIRsensorSwitch::readFromConfig(JsonObject &root)
|
||||||
|
{
|
||||||
|
int8_t oldPin[PIR_SENSOR_MAX_SENSORS];
|
||||||
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++) {
|
||||||
|
oldPin[i] = PIRsensorPin[i];
|
||||||
|
PIRsensorPin[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_PRINT(FPSTR(_name));
|
DEBUG_PRINT(FPSTR(_name));
|
||||||
JsonObject top = root[FPSTR(_name)];
|
JsonObject top = root[FPSTR(_name)];
|
||||||
@@ -385,7 +535,13 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PIRsensorPin = top["pin"] | PIRsensorPin;
|
JsonArray pins = top["pin"];
|
||||||
|
if (!pins.isNull()) {
|
||||||
|
for (size_t i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
|
||||||
|
if (i < pins.size()) PIRsensorPin[i] = pins[i] | PIRsensorPin[i];
|
||||||
|
} else {
|
||||||
|
PIRsensorPin[0] = top["pin"] | oldPin[0];
|
||||||
|
}
|
||||||
|
|
||||||
enabled = top[FPSTR(_enabled)] | enabled;
|
enabled = top[FPSTR(_enabled)] | enabled;
|
||||||
|
|
||||||
@@ -399,54 +555,19 @@ public:
|
|||||||
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
|
||||||
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
|
||||||
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
m_offOnly = top[FPSTR(_offOnly)] | m_offOnly;
|
||||||
|
m_override = top[FPSTR(_override)] | m_override;
|
||||||
NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY;
|
HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery;
|
||||||
|
idx = top[FPSTR(_domoticzIDX)] | idx;
|
||||||
|
|
||||||
if (!initDone) {
|
if (!initDone) {
|
||||||
// reading config prior to setup()
|
// reading config prior to setup()
|
||||||
DEBUG_PRINTLN(F(" config loaded."));
|
DEBUG_PRINTLN(F(" config loaded."));
|
||||||
} else {
|
} else {
|
||||||
if (oldPin != PIRsensorPin || oldEnabled != enabled) {
|
for (int i = 0; i < PIR_SENSOR_MAX_SENSORS; i++)
|
||||||
// check if pin is OK
|
if (oldPin[i] >= 0) pinManager.deallocatePin(oldPin[i], PinOwner::UM_PIR);
|
||||||
if (oldPin != PIRsensorPin && oldPin >= 0) {
|
setup();
|
||||||
// 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."));
|
DEBUG_PRINTLN(F(" config (re)loaded."));
|
||||||
}
|
}
|
||||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
||||||
return !top[FPSTR(_notify)].isNull();
|
return !(pins.isNull() || pins.size() != PIR_SENSOR_MAX_SENSORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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";
|
|
||||||
|
|||||||