mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-04 02:48:13 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2495843ff | ||
|
|
95e9f9a751 | ||
|
|
396a6e07db | ||
|
|
7735d7f0cb | ||
|
|
93711d981d | ||
|
|
3af9706f87 | ||
|
|
66e6088bf1 | ||
|
|
bb1629af90 | ||
|
|
da99137023 | ||
|
|
86d9004c9b | ||
|
|
350f19cd93 | ||
|
|
5776341b86 | ||
|
|
ac42d9a342 | ||
|
|
a7d1755bcf | ||
|
|
55f1fd7ce6 | ||
|
|
9401c20df6 | ||
|
|
69aea5ce35 | ||
|
|
9f111ab8bd | ||
|
|
bb1008435f | ||
|
|
d8046146b5 | ||
|
|
3054123b7f | ||
|
|
3b488423a8 | ||
|
|
aa4705e1e3 | ||
|
|
ac38bc20f5 | ||
|
|
10c068178e | ||
|
|
1b2cc44bc3 | ||
|
|
9cd5f62f9b | ||
|
|
1788aa3c4a | ||
|
|
c4ab152566 | ||
|
|
e5174d1009 | ||
|
|
f056893f87 | ||
|
|
f119b02815 | ||
|
|
f5db6b13d1 | ||
|
|
b94f34f06a | ||
|
|
a0a7b7e263 | ||
|
|
d533f4d754 | ||
|
|
d62f680132 | ||
|
|
c2e1e7f95c | ||
|
|
4b1226bd05 | ||
|
|
64c987a560 | ||
|
|
ca373b2b8e | ||
|
|
afb5bb61b8 | ||
|
|
b2a17d81bf | ||
|
|
e6a0813666 | ||
|
|
2b40362ff7 | ||
|
|
32655b7226 | ||
|
|
b67b7828b9 | ||
|
|
8e4c612fa3 | ||
|
|
7e2fa84daf | ||
|
|
0fad7f50d1 | ||
|
|
47342ed6f7 | ||
|
|
9333ec68ed | ||
|
|
922059b472 | ||
|
|
b541d9cd9c | ||
|
|
7ebbcfe360 | ||
|
|
a37f4fc41c | ||
|
|
e978bc059c | ||
|
|
94020043a5 | ||
|
|
8cc5f290fa | ||
|
|
4cca898927 | ||
|
|
76efbd5c36 | ||
|
|
d82d0a1b9f | ||
|
|
b1720d2981 | ||
|
|
d8f8d5478a | ||
|
|
45675eae3e | ||
|
|
91451f58bf | ||
|
|
304d489c2f | ||
|
|
607dfdb606 | ||
|
|
0d84861270 | ||
|
|
e76e4c27af | ||
|
|
c2af8e55d6 | ||
|
|
20535edf27 | ||
|
|
73da98b3ec | ||
|
|
c09a3584dd | ||
|
|
0b734828ba | ||
|
|
5656e5707b | ||
|
|
3efb536629 | ||
|
|
9df7acdeeb | ||
|
|
18145e42ba | ||
|
|
0e2016879b | ||
|
|
680968ff30 | ||
|
|
94529cd08b | ||
|
|
7d95727761 | ||
|
|
f328cd05cd | ||
|
|
72956f563f | ||
|
|
1cc7df59c2 | ||
|
|
439af6e6b1 | ||
|
|
99f770c493 | ||
|
|
5a663db42e | ||
|
|
ba3f1986f2 | ||
|
|
6a92492bee | ||
|
|
0d07e09a44 | ||
|
|
d6bf07fd71 | ||
|
|
5a8e869a7e | ||
|
|
465b44d734 | ||
|
|
eb27c525e3 | ||
|
|
3a303eeb45 | ||
|
|
9b8de0d77f | ||
|
|
df250c9074 | ||
|
|
b735c61f6e | ||
|
|
cad9ffc83f | ||
|
|
e9bbb6c588 | ||
|
|
88c378f0a2 | ||
|
|
aa8856adf3 | ||
|
|
4611c75dfb | ||
|
|
8cf3b43efb | ||
|
|
ca5df00d60 | ||
|
|
4c0d7fa777 | ||
|
|
ccf04aa5b0 | ||
|
|
297123b5d7 | ||
|
|
32b6bda04c | ||
|
|
bacaf521e4 | ||
|
|
430a9bcdcb | ||
|
|
26a33e3e11 | ||
|
|
118ca1ec30 | ||
|
|
fe9774fe0f | ||
|
|
7cdaf5c785 | ||
|
|
0080e3581e | ||
|
|
6eb8432357 | ||
|
|
71937d1155 | ||
|
|
9f5981e2fd | ||
|
|
701d58fd07 | ||
|
|
dbefaed1a2 | ||
|
|
476952c2da | ||
|
|
48d4dbcfe5 | ||
|
|
9e4b7cb66d | ||
|
|
7fa6387d99 | ||
|
|
993b1ad9a9 | ||
|
|
696b920273 | ||
|
|
4ff7d7049a | ||
|
|
30efa2e028 | ||
|
|
6d23d7591f | ||
|
|
9eee18ab32 | ||
|
|
0ae78ee339 | ||
|
|
15459925b0 | ||
|
|
d5536fbc69 | ||
|
|
ac2cd86f21 | ||
|
|
3545410ba4 | ||
|
|
dda1c45fa9 | ||
|
|
297a1af260 | ||
|
|
c34770ec32 | ||
|
|
1b5d71bac7 | ||
|
|
6fb63f834a | ||
|
|
2b24b91f88 | ||
|
|
b676daf37c | ||
|
|
7bdcfd501e | ||
|
|
60efff754e | ||
|
|
62d951c8e4 | ||
|
|
bb8aec4ce5 | ||
|
|
201ad2e838 | ||
|
|
d04d1f677b | ||
|
|
9abf5abdf6 | ||
|
|
5820298640 | ||
|
|
4dcf0cc78b | ||
|
|
94bb90322b | ||
|
|
b90af1ac4d | ||
|
|
e0c5fb7daf | ||
|
|
e9dec77a83 | ||
|
|
8e0a17e5c8 | ||
|
|
58baeaba6f | ||
|
|
5ded0c9ef8 | ||
|
|
7d498ed50a | ||
|
|
f2ed2cb997 | ||
|
|
b8205e71f3 | ||
|
|
605d1e897a | ||
|
|
01bd8a398b | ||
|
|
65fec94317 | ||
|
|
06c63720f6 | ||
|
|
14aeb7575f | ||
|
|
afd328b3b2 | ||
|
|
9e408e41a8 | ||
|
|
6522cb30a8 | ||
|
|
260a9abee8 | ||
|
|
3c2a05312b | ||
|
|
b05a71b618 | ||
|
|
f9a47786b7 | ||
|
|
a2c33744f6 | ||
|
|
317c026952 | ||
|
|
b5671834e4 | ||
|
|
7078bd1786 | ||
|
|
1af8f4540d | ||
|
|
b257590e98 | ||
|
|
ca4b63220c | ||
|
|
0ab747c62a | ||
|
|
c6e2b1b349 | ||
|
|
9c7fed4fe1 | ||
|
|
c56eec9018 | ||
|
|
e7bb9bc28e | ||
|
|
137410e184 | ||
|
|
86e413dc2a | ||
|
|
8d34744b29 | ||
|
|
833420fe99 | ||
|
|
b7ca079d9d | ||
|
|
13b43076f9 | ||
|
|
cd01cbf77d | ||
|
|
483b83489b | ||
|
|
7bffda3fe6 | ||
|
|
590e212ddb | ||
|
|
379ffee475 | ||
|
|
1e628625aa | ||
|
|
c9393744a4 | ||
|
|
918e02ddfd | ||
|
|
9cd81e3af9 | ||
|
|
ba6336587e | ||
|
|
6a85acdf85 | ||
|
|
88b97c7caa | ||
|
|
ea4ee025f2 | ||
|
|
278c57b4ec | ||
|
|
91826229cb | ||
|
|
9cec5a25a8 | ||
|
|
da02b59a21 | ||
|
|
e9a02d4668 | ||
|
|
b4368308ca | ||
|
|
47ce0110af | ||
|
|
eb5ba12df1 | ||
|
|
d70f358f3e | ||
|
|
1f3b2e34ae | ||
|
|
0994c4b3f8 | ||
|
|
0d61a6b55f | ||
|
|
e830fa26e3 | ||
|
|
acdad80a43 | ||
|
|
8f2c7ae870 | ||
|
|
713fcfc740 | ||
|
|
e6d21b8436 | ||
|
|
0aecb22904 | ||
|
|
fd7f400b25 | ||
|
|
d8c81ecca1 | ||
|
|
8ecf0575a1 | ||
|
|
e9d0012cce | ||
|
|
c987ee93c8 | ||
|
|
8279c5be2a | ||
|
|
5acc7cc9aa | ||
|
|
14cf6b81d4 | ||
|
|
36ed8c1af6 | ||
|
|
a581de29f3 | ||
|
|
4ab63aa1e9 | ||
|
|
c2081aaae7 | ||
|
|
abd52d4754 | ||
|
|
5052c675be | ||
|
|
755aca1643 | ||
|
|
068c32ad27 | ||
|
|
fa76838ee5 | ||
|
|
4d999b1456 | ||
|
|
78ca3ee39a | ||
|
|
68f5195260 | ||
|
|
4086e46f6c | ||
|
|
cf9d85fd6d | ||
|
|
7c05228def | ||
|
|
924cc06bb2 | ||
|
|
c3c741a2c0 | ||
|
|
6e057dfbb2 | ||
|
|
c8a8cc2f31 | ||
|
|
c12d3c4a6e | ||
|
|
9f6dcad791 | ||
|
|
62ba184cc8 | ||
|
|
cfe2619069 | ||
|
|
810f144cff | ||
|
|
553ae00bac | ||
|
|
d0bdf7bdaa | ||
|
|
8452df79bb | ||
|
|
72b5331610 | ||
|
|
9d061ab24e | ||
|
|
927e3e8db0 | ||
|
|
deeff9fa89 | ||
|
|
654d692025 | ||
|
|
f1779c97a1 | ||
|
|
9553022b40 | ||
|
|
c77ab9e62c | ||
|
|
2c28203c29 | ||
|
|
05807f56d8 | ||
|
|
fd30eff2bf | ||
|
|
aa20fbc789 | ||
|
|
ac089992e8 | ||
|
|
2001438d6f | ||
|
|
5928293c89 | ||
|
|
5e9b0091da | ||
|
|
aa59dcfe0f | ||
|
|
5bab658031 | ||
|
|
08b2f4d83d | ||
|
|
2d2b9f82f3 | ||
|
|
ef8bef1f4d | ||
|
|
ef09264b6d | ||
|
|
f9091951be | ||
|
|
925eb79066 | ||
|
|
917f8bbfb0 | ||
|
|
620ca70086 | ||
|
|
55d7ccbd7a | ||
|
|
f199906e9d | ||
|
|
70776a0a05 | ||
|
|
94754e60b5 | ||
|
|
8ce698427d | ||
|
|
8984863fe6 | ||
|
|
cdd8a406a8 | ||
|
|
d395e496a1 | ||
|
|
e87141eb04 | ||
|
|
8c1e890c36 | ||
|
|
109a618c1f | ||
|
|
ce37e5c8ff | ||
|
|
8503260101 | ||
|
|
2ce0850d80 | ||
|
|
3bf0d4bed3 | ||
|
|
69c60e8e4e | ||
|
|
a83ac6e926 | ||
|
|
14f70dde76 | ||
|
|
5fa729170a | ||
|
|
b78efd36df | ||
|
|
8448d66ea7 | ||
|
|
bcf1dd69ff | ||
|
|
7820fb72e7 | ||
|
|
3bec5ade1a | ||
|
|
4d37843af8 | ||
|
|
87fcf08ee0 | ||
|
|
f5dd402ba2 | ||
|
|
960ef96805 | ||
|
|
d157694b12 | ||
|
|
1e90d22d3b | ||
|
|
f16af759d1 | ||
|
|
e8297f9b8f | ||
|
|
8bfc228695 | ||
|
|
9b100c34dc | ||
|
|
c47fc907a9 | ||
|
|
34e30277d7 | ||
|
|
cd9e2070c3 | ||
|
|
1431ca7615 | ||
|
|
a9507c98b5 | ||
|
|
65d3affda3 | ||
|
|
fe8d2970a0 | ||
|
|
d805cbdc0c | ||
|
|
3e8560c570 | ||
|
|
848a107b16 | ||
|
|
5578cb85c6 | ||
|
|
e24108fa45 | ||
|
|
2dd2eb1d7e | ||
|
|
04a39c2917 | ||
|
|
dac0018d77 | ||
|
|
c48173e32a | ||
|
|
133dd8cd52 | ||
|
|
0dbffbb730 | ||
|
|
8aca0beccb | ||
|
|
77bd201d13 | ||
|
|
0780e4fe02 | ||
|
|
c710e9ecbe | ||
|
|
2fdba90efb | ||
|
|
9d9d314a0c | ||
|
|
bd59d98b91 | ||
|
|
f122cc9509 | ||
|
|
1c72f4c944 | ||
|
|
42532f995b | ||
|
|
f714000fb9 | ||
|
|
83e9bbc9db | ||
|
|
ef8e26c87e | ||
|
|
0d04a14352 | ||
|
|
f9b86f6870 | ||
|
|
e933a20d20 | ||
|
|
6119a9efdf | ||
|
|
9eac697606 | ||
|
|
87ec3c8d8d | ||
|
|
73b465329c | ||
|
|
245d24b31b | ||
|
|
1fc27318d3 | ||
|
|
6eb463d646 | ||
|
|
fcc662c70a | ||
|
|
60c1b8617d | ||
|
|
28a0dcbf13 | ||
|
|
0602cdcb41 | ||
|
|
6205ddc3d8 | ||
|
|
e759a48847 | ||
|
|
b3d45031a5 | ||
|
|
db74856951 | ||
|
|
0b68256215 | ||
|
|
6e05a9ff27 | ||
|
|
41cb00ecaa | ||
|
|
4d2a6b2bac | ||
|
|
1e85c0e35e | ||
|
|
82701d9ec7 | ||
|
|
e071e3ad8f | ||
|
|
e394283fda | ||
|
|
4e4942b511 | ||
|
|
1fb70a9b44 | ||
|
|
f0d3a24c11 | ||
|
|
86a97d7669 | ||
|
|
506b3c48f3 | ||
|
|
16bd0f4f3c | ||
|
|
78f00ffe0a | ||
|
|
a72930eebf | ||
|
|
8d967a6dfc | ||
|
|
321034b99e | ||
|
|
d8c7d2a6f0 | ||
|
|
fc48649129 | ||
|
|
12c114d11b | ||
|
|
2c84651286 | ||
|
|
b34efd0206 | ||
|
|
54904d316b | ||
|
|
35e9f41b8b | ||
|
|
b5508762ef | ||
|
|
3a66412db4 | ||
|
|
5def8f84d4 | ||
|
|
e1a263b47f | ||
|
|
3828790109 | ||
|
|
a7fb6d5caf | ||
|
|
08fbe310b7 | ||
|
|
9e29835ac8 | ||
|
|
e454c9969a | ||
|
|
08efbfcfe7 | ||
|
|
726bdfae46 | ||
|
|
61916627ed | ||
|
|
d1656421bb | ||
|
|
fac59e1982 | ||
|
|
31be25d54b | ||
|
|
994609f1e0 | ||
|
|
4319a66da9 | ||
|
|
998243c60c | ||
|
|
9af72535ef | ||
|
|
2cedf7b368 | ||
|
|
af3c3671a2 | ||
|
|
80a8dfd554 | ||
|
|
bc06bb0a1b | ||
|
|
57a9f17335 | ||
|
|
b2f87abd1b | ||
|
|
01538e69c1 | ||
|
|
3c0c3ff93e | ||
|
|
f99eda8a9f | ||
|
|
d71a14194b | ||
|
|
95d116cea3 | ||
|
|
f4a577bbc2 | ||
|
|
634fac4be0 | ||
|
|
b67d1656d8 | ||
|
|
01b909a1d2 | ||
|
|
5edf38550a | ||
|
|
7458674c7b | ||
|
|
0486034c6a | ||
|
|
6029a39f05 | ||
|
|
3e9f9a103e | ||
|
|
062e66bb3c | ||
|
|
7b52e709db | ||
|
|
135e872caa | ||
|
|
6515190124 | ||
|
|
32075b32cd | ||
|
|
6b6a960075 | ||
|
|
5aebb1dd51 | ||
|
|
293f705b0f | ||
|
|
e9d75a08b8 | ||
|
|
709cfffb95 | ||
|
|
6c08094e41 | ||
|
|
4a98ba0e29 | ||
|
|
3f93afe9c2 | ||
|
|
eb771a0608 | ||
|
|
89fcfe9680 | ||
|
|
b61bc261bf | ||
|
|
decd2942c5 | ||
|
|
dd8bbea8c5 | ||
|
|
bdb1ab3c32 | ||
|
|
e173946284 | ||
|
|
f7d4c166ae | ||
|
|
893568052a | ||
|
|
9967d27148 | ||
|
|
cc4acba4ab | ||
|
|
87009dc443 | ||
|
|
2f76338ec7 | ||
|
|
f3c1964e1c | ||
|
|
13132edd9e | ||
|
|
0a7d7469fd | ||
|
|
725f824e58 | ||
|
|
a6438edb3c | ||
|
|
9669bfd1a3 | ||
|
|
210b4c5d01 | ||
|
|
ef962adc57 | ||
|
|
c9665adfaf | ||
|
|
12fde678e5 | ||
|
|
351761b9a7 | ||
|
|
0fd0c33222 | ||
|
|
636027cabc | ||
|
|
7882c0e9a9 | ||
|
|
9d6ce13b92 | ||
|
|
72bcbd8a80 | ||
|
|
56ad40d083 | ||
|
|
22e35b2a03 | ||
|
|
f93d444966 | ||
|
|
d90f6854e9 | ||
|
|
a4993a8441 | ||
|
|
768ead409e | ||
|
|
fb29462e73 | ||
|
|
2447a3fd74 | ||
|
|
e45bdb1831 | ||
|
|
a2355889ba | ||
|
|
4014daeb71 | ||
|
|
7d7e228b9e | ||
|
|
9a85b107fa | ||
|
|
8190ec6077 | ||
|
|
3771a79a55 | ||
|
|
4d94476fa3 | ||
|
|
5d9a51f671 | ||
|
|
e50d30cd5e | ||
|
|
6e1e698cf9 | ||
|
|
f06f4ea5dc | ||
|
|
646af21d30 | ||
|
|
3f8f3eb1ad | ||
|
|
bd8320a45a | ||
|
|
58428ec1f8 | ||
|
|
999da6d1a1 | ||
|
|
368ea928f0 | ||
|
|
7acc77bcb1 | ||
|
|
2e7af253ab | ||
|
|
d0e70fdd6b | ||
|
|
8f9edd18a6 | ||
|
|
89eb952d18 | ||
|
|
d1e395e78f | ||
|
|
286e5d13d9 | ||
|
|
ff26ed4e4c | ||
|
|
edce3fc84c | ||
|
|
f26638e38b | ||
|
|
1b8d870c22 | ||
|
|
34d9a975b5 | ||
|
|
e2d7ae7533 | ||
|
|
41810a344a | ||
|
|
6e1104efbf | ||
|
|
422ce3e165 | ||
|
|
7f414f5224 | ||
|
|
5b997e0597 | ||
|
|
6335512ded | ||
|
|
7446edf547 | ||
|
|
4775b1df51 | ||
|
|
8a8e363cc3 | ||
|
|
b5218d1b93 | ||
|
|
e0e0150f78 | ||
|
|
5685e7d9b4 | ||
|
|
35a1ab2e8d | ||
|
|
8483118860 | ||
|
|
49c6cec998 | ||
|
|
a1c53d785f | ||
|
|
debb20e41b | ||
|
|
d20b85a7b8 | ||
|
|
c3ff582374 | ||
|
|
cccc7c9ae6 | ||
|
|
318742e183 | ||
|
|
7fbb941383 | ||
|
|
4873c7c632 | ||
|
|
f0441f65b7 | ||
|
|
ea1a09130f | ||
|
|
8b685d6826 | ||
|
|
621ac63154 | ||
|
|
d6cf147e5b | ||
|
|
30fd2b13a6 | ||
|
|
6131c2d38a | ||
|
|
bbc98a7e74 | ||
|
|
9353fb01d3 | ||
|
|
8f6950d3eb | ||
|
|
0865a36a5e | ||
|
|
c6591ce7b9 | ||
|
|
0e75b669b7 | ||
|
|
3717e323b9 | ||
|
|
1282c4d5b5 | ||
|
|
862e2dbde2 | ||
|
|
aaf2544c93 | ||
|
|
e25277b20c | ||
|
|
944450840a | ||
|
|
540ee694fe | ||
|
|
71f997b5c7 | ||
|
|
fd6bf74720 | ||
|
|
65ad141008 | ||
|
|
4add3127d4 | ||
|
|
3e2f8ae9d8 | ||
|
|
bbe984c93b | ||
|
|
cf1b1a93a6 | ||
|
|
0ec7a58b7b | ||
|
|
7da803389c | ||
|
|
5a3829abf9 | ||
|
|
6459e4fd4d | ||
|
|
df38c54a66 | ||
|
|
65cb95585d | ||
|
|
9d3cc69b56 | ||
|
|
5dab5a4685 | ||
|
|
69e08e7871 | ||
|
|
c9af1b11a7 | ||
|
|
cac71e368e | ||
|
|
2267f34fb5 | ||
|
|
205d30e958 | ||
|
|
7dc75eacde | ||
|
|
9a4c0f1580 | ||
|
|
ccfca4b663 | ||
|
|
41214ad7eb | ||
|
|
501c824993 | ||
|
|
73f2a8c366 | ||
|
|
38b6c878a2 | ||
|
|
370be061e4 | ||
|
|
2209f987b7 | ||
|
|
7f0bd1e41f | ||
|
|
55577692fe | ||
|
|
2d021bbab8 | ||
|
|
256c889715 | ||
|
|
c3fd7d9622 | ||
|
|
3c280b12a8 | ||
|
|
080f7751e0 | ||
|
|
1d0970a882 | ||
|
|
bef46b9e72 | ||
|
|
4f1196aa69 | ||
|
|
ea67dc22c0 | ||
|
|
b091ee00d8 | ||
|
|
ec1d474d7b | ||
|
|
7d01b99e72 | ||
|
|
29bb8a7d22 | ||
|
|
b95cadbe9a | ||
|
|
e1e061e46a | ||
|
|
686f7f74ee | ||
|
|
669dad4206 | ||
|
|
8e7b43a029 | ||
|
|
fe772f6bf5 | ||
|
|
193fdf177c | ||
|
|
613ec354bd | ||
|
|
6dbc0017ba | ||
|
|
37e100672a | ||
|
|
7d42423894 | ||
|
|
85b2319b59 | ||
|
|
dbf91a4e8c | ||
|
|
aad1a8d9d2 | ||
|
|
c0f29a6fc3 | ||
|
|
34e1516524 | ||
|
|
740005b21e | ||
|
|
b42b2f23c2 | ||
|
|
0e4af48d54 | ||
|
|
9d00937f34 | ||
|
|
0cd0b75d9e | ||
|
|
c88df86313 | ||
|
|
1c191c1d14 | ||
|
|
12fc6516d2 | ||
|
|
448e079bc8 | ||
|
|
b4cc27cb56 | ||
|
|
f9d1c9bd6f | ||
|
|
d9c27b98d4 | ||
|
|
4f0a87a0a5 | ||
|
|
9ce498c5b6 | ||
|
|
393e7e45df | ||
|
|
1e10ffa49a | ||
|
|
2610dcb2c5 | ||
|
|
ed80635e32 | ||
|
|
c52186079a | ||
|
|
7f12e202d4 | ||
|
|
97e7d30c66 | ||
|
|
04552e40d8 | ||
|
|
0d8b96ebf3 | ||
|
|
5827c2f8cb | ||
|
|
8871bdd311 | ||
|
|
338544bc6a | ||
|
|
43051f9f51 | ||
|
|
7ab5f6d5b2 | ||
|
|
3a0723cf66 | ||
|
|
103c37d71f | ||
|
|
42241d6e36 | ||
|
|
b17c328f0f | ||
|
|
c1f018ca7c | ||
|
|
d6e091f587 | ||
|
|
8b579bb8a7 | ||
|
|
b2f6ff4a1f | ||
|
|
805072c4c4 | ||
|
|
247aecb2fc | ||
|
|
c78b76ba2e | ||
|
|
85d7f2a549 | ||
|
|
16c37e5389 | ||
|
|
b963e7f95a | ||
|
|
1bf3fc04ea | ||
|
|
4f5e21647f | ||
|
|
f2bbb5f4e2 | ||
|
|
34b7d1ea03 | ||
|
|
28cf8eed16 | ||
|
|
51432d0ef7 | ||
|
|
c651ab4b62 | ||
|
|
3123eac5c8 | ||
|
|
8f03b5df10 | ||
|
|
2911bb33ee | ||
|
|
2074d85da7 | ||
|
|
593eda9b87 | ||
|
|
3579733f5c | ||
|
|
6d4c971b49 | ||
|
|
1b8ce61522 | ||
|
|
f1a1f44a08 | ||
|
|
915023ccb9 | ||
|
|
792ed84f4b | ||
|
|
5ee1ae78c0 | ||
|
|
4cceb95a42 | ||
|
|
c6027058c9 | ||
|
|
493a73c205 | ||
|
|
e9c4cdb2c6 | ||
|
|
768a965810 | ||
|
|
c6ecad1b3a | ||
|
|
5a5f1c631b | ||
|
|
573f8113ef | ||
|
|
440f312575 | ||
|
|
dd7a686181 | ||
|
|
6a139688f3 | ||
|
|
b855c0a440 | ||
|
|
02de95c529 | ||
|
|
e18bc49a2a | ||
|
|
704a7ae393 | ||
|
|
2c41af00f5 | ||
|
|
afbad01ee1 | ||
|
|
cba606b846 | ||
|
|
8d5e9e3977 | ||
|
|
24de319c7e | ||
|
|
ad623cb586 | ||
|
|
f2f0aabe3e | ||
|
|
37cdda33c7 | ||
|
|
c9ca210803 | ||
|
|
2db93d28c7 | ||
|
|
d140ccff84 | ||
|
|
a51298ea54 | ||
|
|
f1fd553ac6 | ||
|
|
caa32c4bc2 | ||
|
|
0e6ed38986 | ||
|
|
a39b96be9c | ||
|
|
713bf957c3 | ||
|
|
74ceba7409 | ||
|
|
08bb2322aa | ||
|
|
9ab4deaf5b | ||
|
|
36ad6f80c4 | ||
|
|
2fac07cbf0 | ||
|
|
85dd747949 | ||
|
|
32495668d8 | ||
|
|
fc84e7a260 | ||
|
|
933f86720a | ||
|
|
abbc110656 | ||
|
|
17f2316ea8 | ||
|
|
24d317b9da | ||
|
|
40d522d8b6 | ||
|
|
0f2ba754e2 | ||
|
|
f65972aa55 | ||
|
|
e235a45d5a | ||
|
|
aeba5478ee | ||
|
|
b9f6b472a7 | ||
|
|
b9fcc8fe22 | ||
|
|
5a06f29c5b | ||
|
|
4fdefaf67d | ||
|
|
3a9d70f581 | ||
|
|
906a9d89cb | ||
|
|
c0187a60e1 | ||
|
|
0ab6f57239 | ||
|
|
9917f619b3 | ||
|
|
8f2791f908 | ||
|
|
6d92ae5a1f | ||
|
|
8d7da2fd8b | ||
|
|
94115006f6 | ||
|
|
00b14d5d1c | ||
|
|
bff3d75f91 | ||
|
|
e3d91a03ff | ||
|
|
b3e19041ff | ||
|
|
10088c71f3 | ||
|
|
01a5646317 | ||
|
|
5066b86102 | ||
|
|
f1e657e2b8 | ||
|
|
7f6039a6b7 | ||
|
|
24a25fc687 | ||
|
|
128214a159 | ||
|
|
718a8c8f4c | ||
|
|
3c8af53440 | ||
|
|
042640924f | ||
|
|
428c9fcbd5 | ||
|
|
9e265b44b5 | ||
|
|
fe17307e09 | ||
|
|
3fafb3304a | ||
|
|
f8ae461bfd | ||
|
|
d27fa7df61 | ||
|
|
16bc717ceb | ||
|
|
8065a1eaa4 | ||
|
|
174bb615e6 | ||
|
|
82309e7a84 | ||
|
|
b682d8996b | ||
|
|
c5b3637350 | ||
|
|
5b3ac4236e | ||
|
|
87c2eb42b6 | ||
|
|
10f17d0472 | ||
|
|
7ec3b99020 | ||
|
|
49104d4bbb | ||
|
|
41b29fa08c | ||
|
|
ef70b49e98 | ||
|
|
4a6631511b | ||
|
|
a7b992450f | ||
|
|
10db048d61 | ||
|
|
3ae0b40ad9 | ||
|
|
7495fe2999 | ||
|
|
c9a7112cbb | ||
|
|
32ccac5936 | ||
|
|
32d6f11217 | ||
|
|
13cd054c15 | ||
|
|
c2cbe3015b | ||
|
|
4e71d53c54 | ||
|
|
02c531a247 | ||
|
|
6270630d1a | ||
|
|
7279333a17 | ||
|
|
6319955cdc | ||
|
|
14cd0d563a | ||
|
|
fd3f5bbb2d | ||
|
|
9e3061201e | ||
|
|
208ae5c7ea | ||
|
|
56d3e6d737 | ||
|
|
5eea21afa9 | ||
|
|
e4605775a2 | ||
|
|
9e5c1b5fe2 | ||
|
|
3891c0a631 | ||
|
|
fe8f38aa59 | ||
|
|
851a38e7fa | ||
|
|
7f6bd107e6 | ||
|
|
28ed839274 | ||
|
|
c358378a27 | ||
|
|
d090dd3dd7 | ||
|
|
10d6deadc2 | ||
|
|
19d578e04b | ||
|
|
881f50fb7e | ||
|
|
57fc15a20a | ||
|
|
941865ec85 | ||
|
|
6f84964894 | ||
|
|
88d2cfbff4 | ||
|
|
839a421ff1 | ||
|
|
7a57a1395d | ||
|
|
31d53b18be | ||
|
|
8d4f745abf | ||
|
|
e970686064 | ||
|
|
159674bbba | ||
|
|
7ad6470adf | ||
|
|
de85162267 | ||
|
|
8ddc86f470 | ||
|
|
c9e82ae920 | ||
|
|
0741dbde2e | ||
|
|
e3b7dc2641 | ||
|
|
d07f73263a | ||
|
|
f60a1d1b06 | ||
|
|
424f48f0a8 | ||
|
|
03a40915ee | ||
|
|
b7d230cc40 | ||
|
|
8cbd0fa54e | ||
|
|
0749e1bdd7 | ||
|
|
655db109c1 | ||
|
|
109dfa5c58 | ||
|
|
3cc1fabae7 | ||
|
|
81045e6433 | ||
|
|
84752d8027 | ||
|
|
36840fc712 | ||
|
|
3378a4a846 | ||
|
|
21f1016d89 | ||
|
|
4f86945d7e | ||
|
|
278c320e22 | ||
|
|
29edaed0e7 | ||
|
|
32e51ea37b | ||
|
|
9346cee76c | ||
|
|
19d6101a1b | ||
|
|
68201cd965 | ||
|
|
1fade32d66 | ||
|
|
158dac1e68 | ||
|
|
721bb417bf | ||
|
|
0c5e005866 | ||
|
|
0a1bd9f934 | ||
|
|
5e487d6edc | ||
|
|
1f9a13a42e | ||
|
|
3796af5930 | ||
|
|
8af108262c | ||
|
|
b63e8b575b | ||
|
|
a17fabd7fe | ||
|
|
5a34002d5d | ||
|
|
6295d128aa | ||
|
|
724447a654 | ||
|
|
9f6cc23beb | ||
|
|
6e1b2ed32c | ||
|
|
8a17ef9c03 | ||
|
|
3de9a34e3b | ||
|
|
4db3eca51e | ||
|
|
ca5609d8e6 | ||
|
|
53df6ac612 | ||
|
|
108af72d5f | ||
|
|
5ae132a7e9 | ||
|
|
5a6a365f0a | ||
|
|
92debb4bf8 | ||
|
|
ea9134afd2 | ||
|
|
bce3607504 | ||
|
|
051966c856 | ||
|
|
d0ed01433d | ||
|
|
7b4fd38b6b | ||
|
|
58d23513a1 | ||
|
|
39da3e6d6c | ||
|
|
6749122eed | ||
|
|
f210000c94 | ||
|
|
327db84d18 | ||
|
|
ff636abea8 | ||
|
|
951b39b088 | ||
|
|
6ba4a2523c | ||
|
|
dfd48c2c81 | ||
|
|
3eb8d39565 | ||
|
|
ae95ca2c7b | ||
|
|
edbaf5acdd | ||
|
|
7c4963fac2 | ||
|
|
c2bdbfafb9 | ||
|
|
4e57d74e87 | ||
|
|
5e27194204 | ||
|
|
b516085a3d | ||
|
|
f333f271e4 | ||
|
|
35bf4a94bd | ||
|
|
e01101eff1 | ||
|
|
39f20a6adc | ||
|
|
3d7074166c | ||
|
|
415f98d9fb | ||
|
|
e5edf536a5 | ||
|
|
f9a8f5bdab | ||
|
|
64075bc7f9 | ||
|
|
9eec7f97dd | ||
|
|
0c6d4df7a7 | ||
|
|
aaad9ca4c3 | ||
|
|
7a5a72eed4 | ||
|
|
89cfdfc67f | ||
|
|
374e9f515f | ||
|
|
9d16a68665 | ||
|
|
ea994d5ccd | ||
|
|
97398d7ce2 | ||
|
|
9f4156437b | ||
|
|
cb7cbec1f8 | ||
|
|
f3db838ae1 | ||
|
|
1d6a7275e7 | ||
|
|
1ee8f5a1a9 | ||
|
|
076a355098 | ||
|
|
14c5169ac9 | ||
|
|
b09cc31416 | ||
|
|
41b4717148 | ||
|
|
d8d26b26c2 | ||
|
|
98c78a2929 | ||
|
|
d86351dff4 | ||
|
|
e9cf051cb2 | ||
|
|
08ae001a83 | ||
|
|
b49849f8c0 | ||
|
|
603f2b5631 | ||
|
|
55b336e6d8 | ||
|
|
625c8e2f9e | ||
|
|
26ba373009 | ||
|
|
8c1e070582 | ||
|
|
3e66273c3e | ||
|
|
9da297f405 | ||
|
|
18cb6af0e4 | ||
|
|
a6dc373f5c | ||
|
|
25b880cf9b | ||
|
|
5730ce351a | ||
|
|
62f5ce6e0c | ||
|
|
91383f9bc1 | ||
|
|
ca3a726e88 | ||
|
|
43a035091e | ||
|
|
d98a59cca3 | ||
|
|
a247a5ecb4 | ||
|
|
c43551f85a | ||
|
|
cbb32fdd61 | ||
|
|
bc4c07f315 | ||
|
|
369b627b3b | ||
|
|
089c487576 | ||
|
|
ba3a2c8451 | ||
|
|
8e7663b52b | ||
|
|
78f7de38ad | ||
|
|
3d3518a10a | ||
|
|
e342fd9b71 | ||
|
|
addb045a0c | ||
|
|
bab1201dcb | ||
|
|
611246bd75 | ||
|
|
3dcf3c0927 | ||
|
|
7f2fdee9c8 | ||
|
|
1c890c0c49 | ||
|
|
5243036dbf | ||
|
|
a94d897ee4 | ||
|
|
aa734eb606 | ||
|
|
a863167b23 | ||
|
|
79eb0fee47 | ||
|
|
3e10f984e1 | ||
|
|
17410d38c2 | ||
|
|
1f7dc79b20 | ||
|
|
95b48454e8 | ||
|
|
4424e66222 | ||
|
|
75d007d1c8 | ||
|
|
46929e0ee6 | ||
|
|
8e28011f13 | ||
|
|
617e52cb84 | ||
|
|
2c735ab999 | ||
|
|
fc4942db6e | ||
|
|
3914fee227 | ||
|
|
ffc85a81fe | ||
|
|
1c4011bfb4 | ||
|
|
57ef33a7fc | ||
|
|
9a76b3d94d | ||
|
|
0af804a2f1 | ||
|
|
4d715890f6 | ||
|
|
f5fa886c93 | ||
|
|
e49335d811 | ||
|
|
8e07025e38 | ||
|
|
7cc86e57be | ||
|
|
b22686c8a1 | ||
|
|
2b9ddea331 | ||
|
|
6aaa83a266 | ||
|
|
b1b34a3c97 | ||
|
|
44c85a2315 | ||
|
|
da66231e47 | ||
|
|
2e08e7de32 | ||
|
|
ebdfa063a7 | ||
|
|
a3a04f1f9a | ||
|
|
cfb0d2bc5f | ||
|
|
e8d1ea17cb | ||
|
|
0d5ebde9af | ||
|
|
6894c89441 | ||
|
|
b0c61688d6 | ||
|
|
68762e1741 | ||
|
|
a21f8cbc39 | ||
|
|
19d533b7d5 | ||
|
|
ccfddefaa1 | ||
|
|
95253e834d | ||
|
|
53a27f154e | ||
|
|
53c8addfdc | ||
|
|
e5c263c414 | ||
|
|
e015efe8d0 | ||
|
|
5f78d38f46 | ||
|
|
fa04972cb1 | ||
|
|
8697cab0ae | ||
|
|
c8d89db529 | ||
|
|
ff973e1639 | ||
|
|
d61d9a67b1 | ||
|
|
a4795b77f7 | ||
|
|
46a9431475 | ||
|
|
a832af02ca | ||
|
|
d4540f1197 | ||
|
|
d1639e228a | ||
|
|
fdfaecce8a | ||
|
|
380a63d519 | ||
|
|
7e1455778d | ||
|
|
00fb96aa29 | ||
|
|
7fb35a9540 | ||
|
|
cb02e72123 | ||
|
|
85317530e8 | ||
|
|
e55741f201 | ||
|
|
8595ea5d2c | ||
|
|
9a3f501a24 | ||
|
|
3b6d5f790a | ||
|
|
dbb091d1b7 | ||
|
|
867eb4e689 | ||
|
|
42cd1aeb8d | ||
|
|
a3678c6c22 | ||
|
|
0fb86c3d8c | ||
|
|
4db69a3790 | ||
|
|
2761656d78 | ||
|
|
e56d22cfb1 | ||
|
|
c13c076b46 | ||
|
|
f820dc78be | ||
|
|
af507d798e | ||
|
|
a52740ecff | ||
|
|
1f691ae35c | ||
|
|
65c06c6e2b | ||
|
|
3777fcc846 | ||
|
|
de2deb8e69 | ||
|
|
7189e36c59 | ||
|
|
8dbe045655 | ||
|
|
ead2b7353c | ||
|
|
ceaf53f604 | ||
|
|
e5879b2101 | ||
|
|
2a50d797a0 | ||
|
|
2af1c62bd1 | ||
|
|
f3be7c7c0b | ||
|
|
90024b8603 | ||
|
|
d883ce8ae1 | ||
|
|
8364a6a85d | ||
|
|
8c906af563 | ||
|
|
5aafb89a3f | ||
|
|
b266cd5934 | ||
|
|
1a61362f74 | ||
|
|
7625680daf | ||
|
|
d7976381e6 | ||
|
|
5c6252fe45 | ||
|
|
a5ff5cb9e4 | ||
|
|
d66cd47df8 | ||
|
|
eb560629d6 | ||
|
|
0897c5800c | ||
|
|
648274ab3d | ||
|
|
2f4015e797 | ||
|
|
a702fa6c75 | ||
|
|
9a571226cd | ||
|
|
fa05d2c574 | ||
|
|
0ae9ca3d55 | ||
|
|
d6aec20bc3 | ||
|
|
3fdf53bf06 | ||
|
|
a1050d014f | ||
|
|
52d5427654 | ||
|
|
e8c0b52470 | ||
|
|
4763783fe1 | ||
|
|
ad6b8bdc86 | ||
|
|
d09aff2f6d | ||
|
|
0b6341bea4 | ||
|
|
6465d72f6e | ||
|
|
33704176e0 | ||
|
|
df5857464a | ||
|
|
eb434a0187 | ||
|
|
d7e3c8d005 | ||
|
|
3d2453acc5 | ||
|
|
342eacfe37 | ||
|
|
cf51018512 | ||
|
|
db2a1336e9 | ||
|
|
b151c19b3c | ||
|
|
2aef1344b4 | ||
|
|
4592914fad | ||
|
|
8e710b9d92 | ||
|
|
eee3fb72b2 | ||
|
|
12de39135d | ||
|
|
80470fb553 | ||
|
|
31ab2b67d0 | ||
|
|
4d18bfb113 | ||
|
|
37dbd667f4 | ||
|
|
e43a7d0b75 | ||
|
|
c6f7ac0012 | ||
|
|
64798ebbf1 | ||
|
|
985f8118dd | ||
|
|
69ddda0ee4 | ||
|
|
f59c150729 | ||
|
|
14ce25081a | ||
|
|
81b37dfffc | ||
|
|
1f4a8fe499 | ||
|
|
7b9804d5a2 | ||
|
|
a93b601cd7 | ||
|
|
6525a0bd3d | ||
|
|
2c357d7e86 | ||
|
|
5f28843843 | ||
|
|
6e43c1eb41 | ||
|
|
66c653ff94 | ||
|
|
606a1c3128 | ||
|
|
63dd714805 | ||
|
|
9160b5bd3b | ||
|
|
04a9dda74b | ||
|
|
d1ab6ec6db | ||
|
|
b62f488b6b | ||
|
|
8185eaa002 | ||
|
|
8592647cbf | ||
|
|
f2580463e4 | ||
|
|
c4563802f3 | ||
|
|
68e3eb5bc8 | ||
|
|
cadf6b4a80 | ||
|
|
75f34d9b59 | ||
|
|
47d9082e18 | ||
|
|
0aecffc822 | ||
|
|
179c0309a5 | ||
|
|
89af31245b | ||
|
|
68e32a10c0 | ||
|
|
cee18a7ce3 | ||
|
|
f9c4c87c61 | ||
|
|
06d75bf7b1 | ||
|
|
9637d9473e | ||
|
|
0de9d12930 | ||
|
|
eb4594c8e4 | ||
|
|
58eb7113e7 | ||
|
|
898768092c | ||
|
|
c4905c23b7 | ||
|
|
3950a3b859 | ||
|
|
8f90e0f5d7 | ||
|
|
0fc051a445 | ||
|
|
5e9248651f | ||
|
|
362a561a35 | ||
|
|
136c0c9cba | ||
|
|
486ea7db2c | ||
|
|
ada93b721a | ||
|
|
29b2f02ae4 | ||
|
|
004f222f40 | ||
|
|
612e18be35 | ||
|
|
0b0eb6159c | ||
|
|
1a44dd53e6 | ||
|
|
65349cb546 | ||
|
|
e9b36f9084 | ||
|
|
bd9c3b31fb | ||
|
|
c819d46902 | ||
|
|
2edd37eeb8 | ||
|
|
45a325bd34 | ||
|
|
9c72bf98f7 | ||
|
|
628307dbf7 | ||
|
|
c8455f5cd0 | ||
|
|
7360be5f60 | ||
|
|
3135bc5e89 | ||
|
|
c810633ee3 | ||
|
|
0ddb835072 | ||
|
|
a8c13a7c96 | ||
|
|
7a17c98828 | ||
|
|
8ffb4cbfa9 | ||
|
|
0cc4487e42 | ||
|
|
b696b2c29a | ||
|
|
bd0da6c7a6 | ||
|
|
1f500fddd1 | ||
|
|
a231ae5deb | ||
|
|
5f8f1ce5e6 | ||
|
|
022b15d2c8 | ||
|
|
2a0ac55f87 | ||
|
|
abedb61ca1 | ||
|
|
fd15f986d3 | ||
|
|
60c22d672f | ||
|
|
1b261f2371 | ||
|
|
31887ff36a | ||
|
|
c3d01d33e1 | ||
|
|
e0b8acb8dd | ||
|
|
039702327e | ||
|
|
fac1575c96 | ||
|
|
560d4113f9 | ||
|
|
c93cd80f58 | ||
|
|
04d47882a7 | ||
|
|
028bdeeb17 | ||
|
|
44e828e77a | ||
|
|
801ae82ef5 | ||
|
|
a209509d98 | ||
|
|
74bccc04f7 | ||
|
|
fd27cef1ae | ||
|
|
6708ddf37b | ||
|
|
83c271fed4 | ||
|
|
8a375ec340 | ||
|
|
f99156e29f | ||
|
|
413b76cd81 | ||
|
|
d11c763bce | ||
|
|
f9cc4b641f | ||
|
|
852d90beec | ||
|
|
745c3a91fd | ||
|
|
276795079e | ||
|
|
faf307c7bb | ||
|
|
566bcbe063 | ||
|
|
b99487785d | ||
|
|
0962d718ec | ||
|
|
7f11a5d144 | ||
|
|
e6217a18fc | ||
|
|
ae007befd0 | ||
|
|
43d8d94dd2 | ||
|
|
53733447ae | ||
|
|
6f727ff2df | ||
|
|
3660ccdda5 | ||
|
|
f775d9c22c | ||
|
|
99031ce133 | ||
|
|
b306a5ca72 | ||
|
|
33a5e5b3cb | ||
|
|
2582dd8735 | ||
|
|
f82141c98c | ||
|
|
9cde9a8170 | ||
|
|
3f617c7455 | ||
|
|
6f0c16d74f | ||
|
|
7b23c89204 | ||
|
|
55c751c133 | ||
|
|
0f75d745fe | ||
|
|
ba1aa5496d |
@@ -0,0 +1,23 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
compile-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
environment: ["esp8266", "esp32"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade platformio==6.1.19
|
||||
- run: pio test -e ${{ matrix.environment }} --without-uploading --without-testing
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install --upgrade platformio==6.1.19
|
||||
- run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME"
|
||||
- run: gh release create "$GITHUB_REF_NAME" --generate-notes --title "$GITHUB_REF_NAME"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# PlatformIO
|
||||
.pio/
|
||||
.pioenvs/
|
||||
.piolibdeps/
|
||||
.vscode/
|
||||
.cache/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
__pycache__/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode/
|
||||
!.vscode/extensions.json
|
||||
|
||||
# PlatformIO project overrides
|
||||
/platformio_override.ini
|
||||
/build_output/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build artifacts
|
||||
*.o
|
||||
*.elf
|
||||
*.hex
|
||||
*.bin
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
node_modules/
|
||||
@@ -0,0 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- Establish `device-framework` as the independently maintained canonical branch.
|
||||
- Add safe default parameter construction and allocation-failure handling.
|
||||
- Pin the DFTE dependency used by PlatformIO builds.
|
||||
|
||||
@@ -20,3 +20,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
This is a fork of tzapu/WiFiManager (https://github.com/tzapu/WiFiManager)
|
||||
Original work Copyright (c) 2015 tzapu
|
||||
|
||||
This software has been modified and includes contributions Copyright (c) 2024
|
||||
Alex Hope-O'Connor (https://github.com/alexhopeoconnor). The original
|
||||
license and copyright notice above must be preserved in all copies as required
|
||||
by the MIT License.
|
||||
|
||||
@@ -1,299 +1,203 @@
|
||||
# WiFiManager
|
||||
ESP8266 WiFi Connection manager with fallback web configuration portal
|
||||
|
||||
The configuration portal is of the captive variety, so on various devices it will present the configuration dialogue as soon as you connect to the created access point.
|
||||
This repository is a **breaking fork** of upstream [`tzapu/WiFiManager`](https://github.com/tzapu/WiFiManager).
|
||||
It is not a drop-in replacement for upstream behavior, APIs, templates, or portal customization patterns.
|
||||
|
||||
First attempt at a library. Lots more changes and fixes to do. Contributions are welcome.
|
||||
If you are evaluating this fork, assume that core web-portal architecture has changed and review the code before adopting it in an existing upstream-based project.
|
||||
|
||||
#### This works with the ESP8266 Arduino platform with a recent stable release(2.0.0 or newer) https://github.com/esp8266/Arduino
|
||||
**This repository:** [alexhopeoconnor/WiFiManager](https://github.com/alexhopeoconnor/WiFiManager)
|
||||
**Upstream:** [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager)
|
||||
|
||||
## Contents
|
||||
- [How it works](#how-it-works)
|
||||
- [Wishlist](#wishlist)
|
||||
- [Quick start](#quick-start)
|
||||
- Installing
|
||||
- [Through Boards Manager](#install-through-boards-manager)
|
||||
- [From Github](#checkout-from-github)
|
||||
- [Using](#using)
|
||||
- [Documentation](#documentation)
|
||||
- [Access Point Password](#password-protect-the-configuration-access-point)
|
||||
- [Callbacks](#callbacks)
|
||||
- [Timeout](#timeout)
|
||||
- [On Demand Configuration](#on-demand-configuration-portal)
|
||||
- [Custom Parameters](#custom-parameters)
|
||||
- [Custom IP Configuration](#custom-ip-configuration)
|
||||
- [Filter Low Quality Networks](#filter-networks)
|
||||
- [Debug Output](#debug)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Releases](#releases)
|
||||
- [Contributors](#contributions-and-thanks)
|
||||
## Breaking Changes
|
||||
|
||||
This fork intentionally modernizes and restructures the configuration portal.
|
||||
Notable differences from upstream include:
|
||||
|
||||
## How It Works
|
||||
- when your ESP starts up, it sets it up in Station mode and tries to connect to a previously saved Access Point
|
||||
- if this is unsuccessful (or no previous network saved) it moves the ESP into Access Point mode and spins up a DNS and WebServer (default ip 192.168.4.1)
|
||||
- using any wifi enabled device with a browser (computer, phone, tablet) connect to the newly created Access Point
|
||||
- because of the Captive Portal and the DNS server you will either get a 'Join to network' type of popup or get any domain you try to access redirected to the configuration portal
|
||||
- choose one of the access points scanned, enter password, click save
|
||||
- ESP will try to connect. If successful, it relinquishes control back to your app. If not, reconnect to AP and reconfigure.
|
||||
- The portal now serves a **single HTML shell** from `GET /`.
|
||||
- Client navigation is handled as a **SPA with hash routing**.
|
||||
- Interactive behavior is exposed through **JSON APIs under `/api/...`** plus firmware upload at **`POST /u`**.
|
||||
- Legacy multi-page portal routes and legacy root-template override paths have been removed.
|
||||
- The portal rendering pipeline is built around **DFTE** instead of upstream's monolithic HTML string assembly.
|
||||
- JSON endpoints are expected to be **data-first**, not derived from generated HTML fragments.
|
||||
|
||||
## How It Looks
|
||||
 
|
||||
## Current Improvements And Modernization
|
||||
|
||||
## Wishlist
|
||||
- ~~remove dependency on EEPROM library~~
|
||||
- ~~move HTML Strings to PROGMEM~~
|
||||
- ~~cleanup and streamline code~~ (although this is ongoing)
|
||||
- if timeout is set, extend it when a page is fetched in AP mode
|
||||
- ~~add ability to configure more parameters than ssid/password~~
|
||||
- ~~maybe allow setting ip of ESP after reboot~~
|
||||
- ~~add to Arduino Boards Manager~~
|
||||
- add to PlatformIO
|
||||
- add multiple sets of network credentials
|
||||
- allow users to customize CSS
|
||||
This fork currently includes the following architectural improvements:
|
||||
|
||||
## Quick Start
|
||||
- A **single-shell portal architecture** with embedded bootstrap JSON and embedded application JS.
|
||||
- A **SPA-based configuration UI** for WiFi setup, parameters, info views, device actions, and OTA flow.
|
||||
- A **clean API surface** for WiFi scanning, WiFi save, parameters, info, status, restart, erase, portal exit, and captive-portal close behavior.
|
||||
- **Captive portal redirect handling** retained while removing duplicate legacy UI architecture.
|
||||
- **Data-first JSON generation** for portal APIs, including info/device/about data, instead of HTML-to-JSON parsing.
|
||||
- **Capability-driven UI flags** in bootstrap/API payloads so features like info, update, erase, and action visibility can be controlled by backend state.
|
||||
- **Portal bootstrap contract v2**: nested `brand`, `context`, `pages`, `actions`, `layout`, `extraHomeCards`; Wi-Fi meta params include `kind` (`field` | `html`) for first-class custom HTML parameters.
|
||||
- **SPA-native feedback UX** using in-DOM dialog/toast behavior rather than page-based action flows.
|
||||
- **Connect-on-save handoff**: when portal save triggers a station connect, `/api/wifi/connect-status` now reports a brief success state with `stationIp` and `redirectUrl` so the SPA can show the new address and redirect before the AP shuts down.
|
||||
- **Request-scoped shell rendering**: the root portal page is built for each `GET /` from `WM_ROOT_SHELL_TEMPLATE` using a fresh placeholder registry. Shell inputs are `%PAGE_TITLE%`, `%STYLES%`, `%BOOTSTRAP_JSON%`, `%PORTAL_APP_JS%`, and `%PORTAL_APPEND_JS%` — filled in `WiFiManagerHandlers` from WiFiManager state and embedded assets (not from a server-wide template registry).
|
||||
- **Customization via WiFiManager `portal*` APIs** (`portalSetBrandTitle`, `portalSetPageInfoVisible`, `portalSetLayoutParamsLocation`, `portalAddParameter`, asset hooks, etc.) and JSON under `/api/...`, not by exposing placeholder-registry mutation to consumers.
|
||||
- A clearer separation between:
|
||||
- shell rendering (handlers + SPA bootstrap)
|
||||
- JSON API responses
|
||||
- captive portal behavior
|
||||
- OTA handling
|
||||
- Updated tests focused on the **shell contract**, **bootstrap payloads**, and **API JSON shapes** rather than removed legacy portal pages.
|
||||
|
||||
### Installing
|
||||
You can either install through the Arduino Boards Manager or checkout the latest changes or a release from github
|
||||
## Portal Customization Boundary
|
||||
|
||||
#### Install through Boards Manager
|
||||
__Currently version 0.6 works with release 2.0.0 or newer of the [ESP8266 core for Arduino](https://github.com/esp8266/Arduino)__
|
||||
- in Arduino IDE got to Sketch/Include Library/Manage Libraries
|
||||

|
||||
Stable, supported portal customization is intentionally scoped:
|
||||
|
||||
- search for WiFiManager
|
||||

|
||||
- `portalSetBrand*` for title, intro text, and logo SVG, plus `portalSetContextIdentityText(...)` for the user-facing runtime identity string on the home view.
|
||||
- `portalSetPage*`, `portalSetAction*`, `portalSetLayout*`, and `portalSetBehavior*` for built-in portal capabilities and runtime behavior.
|
||||
- `portalAddParameter(...)` for first-class custom parameters, including raw HTML blocks inside parameter-rendering surfaces (`#/wifi` or `#/setup`).
|
||||
- `portalAddInfoSection(...)` and `portalAddHomeCard(...)` for structured extra content rendered by the built-in SPA.
|
||||
- `portalAppendCss(...)`, `portalOverrideCss(...)`, and `portalAppendJs(...)` for light theming and enhancement hooks.
|
||||
|
||||
- click Install and start [using it](#using)
|
||||
Not part of the stable API:
|
||||
|
||||
#### Checkout from github
|
||||
__Github version works with the HEAD (as of 18 jan 2016) or newer of the [ESP8266 core for Arduino](https://github.com/esp8266/Arduino)__
|
||||
- Checkout library to your Arduino libraries folder
|
||||
- arbitrary HTML injection into home/info/nav/shell
|
||||
- replacing built-in SPA routing or action flow
|
||||
- depending on undocumented DOM IDs or route internals
|
||||
- treating `include/templates/*` as a supported consumer override surface
|
||||
|
||||
Appended JS should enhance rather than replace the built-in SPA. The documented hook contract is:
|
||||
|
||||
- `wm:ready` with `detail.boot`
|
||||
- `wm:view-changed` with `detail.route`
|
||||
|
||||
If a consumer needs custom live widgets, new primary navigation concepts, or new backend-to-frontend workflows, that is considered **fork territory** rather than portal customization.
|
||||
|
||||
## Portal Customization Example
|
||||
|
||||
### Using
|
||||
- Include in your sketch
|
||||
```cpp
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h>
|
||||
WiFiManager wm;
|
||||
|
||||
wm.portalSetBrandTitle("Solar Battery Monitor Setup");
|
||||
wm.portalSetContextIdentityText("Solar Battery Monitor");
|
||||
wm.portalSetBrandHomeIntro(
|
||||
"Connect your monitor to WiFi, then review battery and inverter settings."
|
||||
);
|
||||
wm.portalSetBrandLogoSvg(
|
||||
"<svg viewBox='0 0 24 24' aria-hidden='true'>"
|
||||
"<path d='M12 2L4 12h5l-1 10 8-10h-5z'></path>"
|
||||
"</svg>"
|
||||
);
|
||||
|
||||
wm.portalSetPageInfoVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetActionEraseVisible(false);
|
||||
wm.portalSetActionRestartVisible(true);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);
|
||||
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
wm.portalSetBehaviorConnectOnSave(true);
|
||||
wm.portalSetBehaviorExitAllowed(true);
|
||||
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Masked);
|
||||
wm.portalSetFieldStaticIpVisibility(PortalFieldVisibility::Auto);
|
||||
wm.portalSetFieldStaticDnsVisibility(PortalFieldVisibility::Auto);
|
||||
|
||||
WiFiManagerParameter mqttHost(
|
||||
"mqtt_host",
|
||||
"MQTT host",
|
||||
"broker.local",
|
||||
64,
|
||||
"placeholder='broker.local'"
|
||||
);
|
||||
wm.portalAddParameter(&mqttHost);
|
||||
|
||||
// Raw HTML remains first-class for custom parameters, but only inside the
|
||||
// parameter-rendering surfaces (#/wifi or #/setup), not arbitrary portal regions.
|
||||
WiFiManagerParameter gpsHelp(
|
||||
"<div class='wm-callout wm-callout--info'>"
|
||||
"<p>GPS options below apply only when a GPS module is connected.</p>"
|
||||
"</div>"
|
||||
);
|
||||
wm.portalAddParameter(&gpsHelp);
|
||||
|
||||
PortalInfoSection battery;
|
||||
battery.id = "battery";
|
||||
battery.title = "Battery";
|
||||
battery.items.push_back({"soc", "State of charge", "84%"});
|
||||
battery.items.push_back({"voltage", "Voltage", "13.2V"});
|
||||
wm.portalAddInfoSection(battery);
|
||||
|
||||
PortalHomeCard solar;
|
||||
solar.id = "solar";
|
||||
solar.title = "Solar summary";
|
||||
solar.kind = PortalHomeCardKind::KeyValue;
|
||||
solar.items.push_back({"pv", "PV input", "420W"});
|
||||
solar.items.push_back({"load", "Load", "180W"});
|
||||
wm.portalAddHomeCard(solar);
|
||||
|
||||
wm.portalAppendCss(
|
||||
".wm-brand-logo svg{width:40px;height:40px;display:block;}"
|
||||
".wm-hero-intro{max-width:42ch;}"
|
||||
);
|
||||
|
||||
wm.portalAppendJs(
|
||||
"document.addEventListener('wm:ready', function(e){"
|
||||
" console.log('Portal booted', e.detail.boot);"
|
||||
"});"
|
||||
);
|
||||
```
|
||||
|
||||
- Initialize library, in your setup function add
|
||||
```cpp
|
||||
WiFiManager wifiManager;
|
||||
```
|
||||
## WiFi Connect Status API
|
||||
|
||||
- Also in the setup function add
|
||||
```cpp
|
||||
//first parameter is name of access point, second is the password
|
||||
wifiManager.autoConnect("AP-NAME", "AP-PASSWORD");
|
||||
```
|
||||
if you just want an unsecured access point
|
||||
```cpp
|
||||
wifiManager.autoConnect("AP-NAME");
|
||||
```
|
||||
or if you want to use and auto generated name from 'ESP' and the esp's Chip ID use
|
||||
```cpp
|
||||
wifiManager.autoConnect();
|
||||
```
|
||||
When `portalSetBehaviorConnectOnSave(true)` is enabled, saving WiFi credentials queues a station join and the SPA polls `GET /api/wifi/connect-status`.
|
||||
|
||||
After you write your sketch and start the ESP, it will try to connect to WiFi. If it fails it starts in Access Point mode.
|
||||
While in AP mode, connect to it then open a browser to the gateway IP, default 192.168.4.1, configure wifi, save and it should reboot and connect.
|
||||
Response shape:
|
||||
|
||||
Also see [examples](https://github.com/tzapu/WiFiManager/tree/master/examples).
|
||||
|
||||
## Documentation
|
||||
|
||||
#### Password protect the configuration Access Point
|
||||
You can and should password protect the configuration access point. Simply add the password as a second parameter to `autoConnect`.
|
||||
A short password seems to have unpredictable results so use one that's around 8 characters or more in length.
|
||||
The guidelines are that a wifi password must consist of 8 to 63 ASCII-encoded characters in the range of 32 to 126 (decimal)
|
||||
```cpp
|
||||
wifiManager.autoConnect("AutoConnectAP", "password")
|
||||
```
|
||||
|
||||
#### Callbacks
|
||||
##### Enter Config mode
|
||||
Use this if you need to do something when your device enters configuration mode on failed WiFi connection attempt.
|
||||
Before `autoConnect()`
|
||||
```cpp
|
||||
wifiManager.setAPCallback(configModeCallback);
|
||||
```
|
||||
`configModeCallback` declaration and example
|
||||
```cpp
|
||||
void configModeCallback (WiFiManager *myWiFiManager) {
|
||||
Serial.println("Entered config mode");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
Serial.println(myWiFiManager->getConfigPortalSSID());
|
||||
```json
|
||||
{
|
||||
"state": "waiting | success | failed",
|
||||
"message": "human readable status",
|
||||
"wifiStatus": "WL_CONNECTED",
|
||||
"stationIp": "192.168.1.42",
|
||||
"redirectUrl": "http://192.168.1.42/"
|
||||
}
|
||||
```
|
||||
|
||||
##### Save settings
|
||||
This gets called when custom parameters have been set **AND** a connection has been established. Use it to set a flag, so when all the configuration finishes, you can save the extra parameters somewhere.
|
||||
Notes:
|
||||
|
||||
See [AutoConnectWithFSParameters Example](https://github.com/tzapu/WiFiManager/tree/master/examples/AutoConnectWithFSParameters).
|
||||
```cpp
|
||||
wifiManager.setSaveConfigCallback(saveConfigCallback);
|
||||
```
|
||||
`saveConfigCallback` declaration and example
|
||||
```cpp
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
- `stationIp` and `redirectUrl` are present only after a successful station join.
|
||||
- If the portal HTTP server is not on port `80`, `redirectUrl` includes the active port.
|
||||
- On success, WiFiManager keeps the portal alive briefly so the client can read the success payload and navigate to the new device address before the captive AP is shut down.
|
||||
- This improves the handoff on typical home networks, but it is not a universal guarantee: client captive-portal helpers, browser behavior, DHCP timing, and network isolation can still affect whether the redirect completes automatically.
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
Serial.println("Should save config");
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
## Dependencies
|
||||
|
||||
This fork depends on **DFTE** ([Device Framework Template Engine](https://github.com/alexhopeoconnor/DFTE)) and **ESP32Async/ESPAsyncWebServer**.
|
||||
|
||||
For this repo's `platformio.ini` test setup, DFTE is expected as a sibling checkout:
|
||||
|
||||
```ini
|
||||
lib_deps =
|
||||
ESP32Async/ESPAsyncWebServer@3.9.1
|
||||
symlink://../DFTE
|
||||
```
|
||||
|
||||
#### Timeout
|
||||
If you need to set a timeout so the ESP doesn't hang waiting to be configured, for instance after a power failure, you can add
|
||||
```cpp
|
||||
wifiManager.setTimeout(180);
|
||||
```
|
||||
which will wait 3 minutes (180 seconds). When the time passes, the autoConnect function will return, no matter the outcome.
|
||||
Check for connection and if it's still not established do whatever is needed (on some modules I restart them to retry, on others I enter deep sleep)
|
||||
## Installation
|
||||
|
||||
#### On Demand Configuration Portal
|
||||
If you would rather start the configuration portal on demand rather than automatically on a failed connection attempt, then this is for you.
|
||||
|
||||
Instead of calling `autoConnect()` which does all the connecting and failover configuration portal setup for you, you need to use `startConfigPortal()`. __Do not use BOTH.__
|
||||
|
||||
Example usage
|
||||
```cpp
|
||||
void loop() {
|
||||
// is configuration portal requested?
|
||||
if ( digitalRead(TRIGGER_PIN) == LOW ) {
|
||||
WiFiManager wifiManager;
|
||||
wifiManager.startConfigPortal("OnDemandAP");
|
||||
Serial.println("connected...yeey :)");
|
||||
}
|
||||
}
|
||||
```
|
||||
See example for a more complex version. [OnDemandConfigPortal](https://github.com/tzapu/WiFiManager/tree/master/examples/OnDemandConfigPortal)
|
||||
|
||||
### Custom Parameters
|
||||
You can use WiFiManager to collect more parameters than just SSID and password.
|
||||
This could be helpful for configuring stuff like MQTT host and port, [blynk](http://www.blynk.cc) or [emoncms](http://emoncms.org) tokens, just to name a few.
|
||||
**You are responsible for saving and loading these custom values.** The library just collects and displays the data for you as a convenience.
|
||||
Usage scenario would be:
|
||||
- load values from somewhere (EEPROM/FS) or generate some defaults
|
||||
- add the custom parameters to WiFiManager using
|
||||
```cpp
|
||||
// id/name, placeholder/prompt, default, length
|
||||
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
|
||||
wifiManager.addParameter(&custom_mqtt_server);
|
||||
|
||||
```
|
||||
- if connection to AP fails, configuration portal starts and you can set /change the values (or use on demand configuration portal)
|
||||
- once configuration is done and connection is established [save config callback]() is called
|
||||
- once WiFiManager returns control to your application, read and save the new values using the `WiFiManagerParameter` object.
|
||||
```cpp
|
||||
mqtt_server = custom_mqtt_server.getValue();
|
||||
```
|
||||
This feature is a lot more involved than all the others, so here are some examples to fully show how it is done
|
||||
- Save and load custom parameters to file system in json form [AutoConnectWithFSParameters](https://github.com/tzapu/WiFiManager/tree/master/examples/AutoConnectWithFSParameters)
|
||||
- *Save and load custom parameters to EEPROM* (not done yet)
|
||||
|
||||
|
||||
### Custom Access Point IP Configuration
|
||||
This will set your captive portal to a specific IP should you need/want such a feature. Add the following snippet before `autoConnect()`
|
||||
```cpp
|
||||
//set custom ip for portal
|
||||
wifiManager.setAPStaticIPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));
|
||||
```ini
|
||||
[env:your_environment]
|
||||
platform = espressif8266 ; or espressif32
|
||||
board = d1_mini ; your board
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
https://github.com/alexhopeoconnor/WiFiManager.git
|
||||
```
|
||||
|
||||
### Custom Station (client) Static IP Configuration
|
||||
This will make use the specified IP configuration instead of using DHCP in station mode.
|
||||
```cpp
|
||||
wifiManager.setSTAStaticIPConfig(IPAddress(192,168,0,99), IPAddress(192,168,0,1), IPAddress(255,255,255,0));
|
||||
```
|
||||
There are a couple of examples in the examples folder that show you how to set a static IP and even how to configure it through the web configuration portal.
|
||||
Or a local path:
|
||||
|
||||
#### Filter Networks
|
||||
If you would like to filter low signal quality networks you can tell WiFiManager to not show networks below an arbitrary quality %;
|
||||
```cpp
|
||||
wifiManager.setMinimumSignalQuality(10);
|
||||
```
|
||||
will not show networks under 10% signal quality. If you omit the parameter it defaults to 8%;
|
||||
|
||||
#### Debug
|
||||
Debug is enabled by default on Serial. To disable add before autoConnect
|
||||
```cpp
|
||||
wifiManager.setDebugOutput(false);
|
||||
```ini
|
||||
lib_deps =
|
||||
file:///path/to/WiFiManager
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
If you get compilation errors, more often than not, you may need to install a newer version of the ESP8266 core for Arduino.
|
||||
## AI Assistance Notice
|
||||
|
||||
I am trying to keep releases working with release versions of the core, so they can be installed through boards manager, but if you checkout the latest version directly from github, sometimes, the library will only work if you update the ESP8266 core to the latest version because I am using some newly added function.
|
||||
Parts of this codebase have been developed and refactored with the aid of AI coding agents under human direction and review.
|
||||
|
||||
## License
|
||||
|
||||
If you connect to the created configuration Access Point but the configuration portal does not show up, just open a browser and type in the IP of the web portal, by default `192.168.4.1`.
|
||||
|
||||
|
||||
## Releases
|
||||
#### 0.6
|
||||
- custom parameters
|
||||
- prettier
|
||||
- on demand config portal
|
||||
- commit #100 :D
|
||||
|
||||
##### 0.5
|
||||
- Added to Arduino Boards Manager - Thanks Max
|
||||
- moved most stuff to PROGMEM
|
||||
- added signal quality and a nice little padlock to show which networks are encrypted
|
||||
|
||||
##### v0.4 - all of it user contributed changes - Thank you
|
||||
- added ability to password protect the configuration Access Point
|
||||
- callback for enter configuration mode
|
||||
- memory allocation improvements
|
||||
|
||||
##### v0.3
|
||||
- removed the need for EEPROM and works with the 2.0.0 and above stable release of the ESP8266 for Arduino IDE package
|
||||
- removed restart on save of credentials
|
||||
- updated examples
|
||||
|
||||
##### v0.2
|
||||
needs the latest staging version (or at least a recent release of the staging version) to work
|
||||
|
||||
##### v0.1
|
||||
works with the staging release ver. 1.6.5-1044-g170995a, built on Aug 10, 2015 of the ESP8266 Arduino library.
|
||||
|
||||
|
||||
### Contributions and thanks
|
||||
The support and help I got from the community has been nothing short of phenomenal. I can't thank you guys enough. This is my first real attept in developing open source stuff and I must say, now I understand why people are so dedicated to it, it is because of all the wonderful people involved.
|
||||
|
||||
__THANK YOU__
|
||||
|
||||
[Maximiliano Duarte](https://github.com/domonetic)
|
||||
|
||||
[alltheblinkythings](https://github.com/alltheblinkythings)
|
||||
|
||||
[Niklas Wall](https://github.com/niklaswall)
|
||||
|
||||
[Jakub Piasecki](https://github.com/zaporylie)
|
||||
|
||||
[Peter Allan](https://github.com/alwynallan)
|
||||
|
||||
[John Little](https://github.com/j0hnlittle)
|
||||
|
||||
[markaswift](https://github.com/markaswift)
|
||||
|
||||
[franklinvv](https://github.com/franklinvv)
|
||||
|
||||
[Alberto Ricci Bitti](https://github.com/riccibitti)
|
||||
|
||||
[SebiPanther](https://github.com/SebiPanther)
|
||||
|
||||
[jonathanendersby](https://github.com/jonathanendersby)
|
||||
|
||||
[walthercarsten](https://github.com/walthercarsten)
|
||||
|
||||
Sorry if i have missed anyone.
|
||||
|
||||
#### Inspiration
|
||||
- http://www.esp8266.com/viewtopic.php?f=29&t=2520
|
||||
See [LICENSE](LICENSE).
|
||||
|
||||
-663
@@ -1,663 +0,0 @@
|
||||
/**************************************************************
|
||||
WiFiManager is a library for the ESP8266/Arduino platform
|
||||
(https://github.com/esp8266/Arduino) to enable easy
|
||||
configuration and reconfiguration of WiFi credentials using a Captive Portal
|
||||
inspired by:
|
||||
http://www.esp8266.com/viewtopic.php?f=29&t=2520
|
||||
https://github.com/chriscook8/esp-arduino-apboot
|
||||
https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced
|
||||
Built by AlexT https://github.com/tzapu
|
||||
Licensed under MIT license
|
||||
**************************************************************/
|
||||
|
||||
#include "WiFiManager.h"
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length) {
|
||||
_id = id;
|
||||
_placeholder = placeholder;
|
||||
_length = length;
|
||||
_value = new char[length+1];
|
||||
for (int i = 0; i < length; i++) {
|
||||
_value[i] = 0;
|
||||
}
|
||||
if (defaultValue != NULL) {
|
||||
strncpy(_value, defaultValue, length);
|
||||
}
|
||||
}
|
||||
|
||||
const char* WiFiManagerParameter::getValue() {
|
||||
return _value;
|
||||
}
|
||||
const char* WiFiManagerParameter::getID() {
|
||||
return _id;
|
||||
}
|
||||
const char* WiFiManagerParameter::getPlaceholder() {
|
||||
return _placeholder;
|
||||
}
|
||||
int WiFiManagerParameter::getValueLength() {
|
||||
return _length;
|
||||
}
|
||||
|
||||
WiFiManager::WiFiManager() {
|
||||
}
|
||||
|
||||
void WiFiManager::addParameter(WiFiManagerParameter *p) {
|
||||
_params[_paramsCount] = p;
|
||||
_paramsCount++;
|
||||
DEBUG_WM("Adding parameter");
|
||||
DEBUG_WM(p->getID());
|
||||
}
|
||||
|
||||
void WiFiManager::setupConfigPortal() {
|
||||
dnsServer.reset(new DNSServer());
|
||||
server.reset(new ESP8266WebServer(80));
|
||||
|
||||
DEBUG_WM(F(""));
|
||||
start = millis();
|
||||
|
||||
DEBUG_WM(F("Configuring access point... "));
|
||||
DEBUG_WM(_apName);
|
||||
if (_apPassword != NULL) {
|
||||
if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63) {
|
||||
// fail passphrase to short or long!
|
||||
DEBUG_WM(F("Invalid AccessPoint password. Ignoring"));
|
||||
_apPassword = NULL;
|
||||
}
|
||||
DEBUG_WM(_apPassword);
|
||||
}
|
||||
|
||||
//optional soft ip config
|
||||
if (_ap_static_ip) {
|
||||
DEBUG_WM(F("Custom AP IP/GW/Subnet"));
|
||||
WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn);
|
||||
}
|
||||
|
||||
if (_apPassword != NULL) {
|
||||
WiFi.softAP(_apName, _apPassword);//password option
|
||||
} else {
|
||||
WiFi.softAP(_apName);
|
||||
}
|
||||
|
||||
delay(500); // Without delay I've seen the IP address blank
|
||||
DEBUG_WM(F("AP IP address: "));
|
||||
DEBUG_WM(WiFi.softAPIP());
|
||||
|
||||
/* Setup the DNS server redirecting all the domains to the apIP */
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
|
||||
|
||||
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
|
||||
server->on("/", std::bind(&WiFiManager::handleRoot, this));
|
||||
server->on("/wifi", std::bind(&WiFiManager::handleWifi, this, true));
|
||||
server->on("/0wifi", std::bind(&WiFiManager::handleWifi, this, false));
|
||||
server->on("/wifisave", std::bind(&WiFiManager::handleWifiSave, this));
|
||||
server->on("/i", std::bind(&WiFiManager::handleInfo, this));
|
||||
server->on("/r", std::bind(&WiFiManager::handleReset, this));
|
||||
server->on("/generate_204", std::bind(&WiFiManager::handle204, this)); //Android/Chrome OS captive portal check.
|
||||
server->on("/fwlink", std::bind(&WiFiManager::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||
server->onNotFound (std::bind(&WiFiManager::handleNotFound, this));
|
||||
server->begin(); // Web server start
|
||||
DEBUG_WM(F("HTTP server started"));
|
||||
}
|
||||
|
||||
boolean WiFiManager::autoConnect() {
|
||||
String ssid = "ESP" + String(ESP.getChipId());
|
||||
return autoConnect(ssid.c_str(), NULL);
|
||||
}
|
||||
|
||||
boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) {
|
||||
DEBUG_WM(F(""));
|
||||
DEBUG_WM(F("AutoConnect"));
|
||||
|
||||
// read eeprom for ssid and pass
|
||||
//String ssid = getSSID();
|
||||
//String pass = getPassword();
|
||||
|
||||
// attempt to connect; should it fail, fall back to AP
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
if (connectWifi("", "") == WL_CONNECTED) {
|
||||
DEBUG_WM(F("IP Address:"));
|
||||
DEBUG_WM(WiFi.localIP());
|
||||
//connected
|
||||
return true;
|
||||
}
|
||||
|
||||
return startConfigPortal(apName, apPassword);
|
||||
}
|
||||
|
||||
boolean WiFiManager::startConfigPortal(char const *apName, char const *apPassword) {
|
||||
//setup AP
|
||||
WiFi.mode(WIFI_AP);
|
||||
|
||||
_apName = apName;
|
||||
_apPassword = apPassword;
|
||||
|
||||
//notify we entered AP mode
|
||||
if ( _apcallback != NULL) {
|
||||
_apcallback(this);
|
||||
}
|
||||
|
||||
connect = false;
|
||||
setupConfigPortal();
|
||||
|
||||
while (timeout == 0 || millis() < start + timeout) {
|
||||
//DNS
|
||||
dnsServer->processNextRequest();
|
||||
//HTTP
|
||||
server->handleClient();
|
||||
|
||||
|
||||
if (connect) {
|
||||
connect = false;
|
||||
delay(2000);
|
||||
DEBUG_WM(F("Connecting to new AP"));
|
||||
|
||||
// using user-provided _ssid, _pass in place of system-stored ssid and pass
|
||||
if (connectWifi(_ssid, _pass) != WL_CONNECTED) {
|
||||
DEBUG_WM(F("Failed to connect."));
|
||||
} else {
|
||||
//connected
|
||||
WiFi.mode(WIFI_STA);
|
||||
//notify that configuration has changed and any optional parameters should be saved
|
||||
if ( _savecallback != NULL) {
|
||||
//todo: check if any custom parameters actually exist, and check if they really changed maybe
|
||||
_savecallback();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(_shouldBreakAfterConfig) {
|
||||
//flag set to exit after config after trying to connect
|
||||
//notify that configuration has changed and any optional parameters should be saved
|
||||
if ( _savecallback != NULL) {
|
||||
//todo: check if any custom parameters actually exist, and check if they really changed maybe
|
||||
_savecallback();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
server.reset();
|
||||
dnsServer.reset();
|
||||
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
|
||||
int WiFiManager::connectWifi(String ssid, String pass) {
|
||||
DEBUG_WM(F("Connecting as wifi client..."));
|
||||
|
||||
// check if we've got static_ip settings, if we do, use those.
|
||||
if (_sta_static_ip) {
|
||||
DEBUG_WM(F("Custom STA IP/GW/Subnet"));
|
||||
WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn);
|
||||
DEBUG_WM(WiFi.localIP());
|
||||
}
|
||||
|
||||
if(ssid !="" && pass != "") {
|
||||
WiFi.begin(ssid.c_str(), pass.c_str());
|
||||
} else {
|
||||
DEBUG_WM("Using last saved values, should be faster");
|
||||
WiFi.begin();
|
||||
}
|
||||
|
||||
int connRes = WiFi.waitForConnectResult();
|
||||
DEBUG_WM ("Connection result: ");
|
||||
DEBUG_WM ( connRes );
|
||||
return connRes;
|
||||
}
|
||||
|
||||
|
||||
String WiFiManager::getSSID() {
|
||||
if (_ssid == "") {
|
||||
DEBUG_WM(F("Reading SSID"));
|
||||
_ssid = WiFi.SSID();
|
||||
DEBUG_WM(F("SSID: "));
|
||||
DEBUG_WM(_ssid);
|
||||
}
|
||||
return _ssid;
|
||||
}
|
||||
|
||||
String WiFiManager::getPassword() {
|
||||
if (_pass == "") {
|
||||
DEBUG_WM(F("Reading Password"));
|
||||
_pass = WiFi.psk();
|
||||
DEBUG_WM("Password: " + _pass);
|
||||
//DEBUG_WM(_pass);
|
||||
}
|
||||
return _pass;
|
||||
}
|
||||
|
||||
String WiFiManager::getConfigPortalSSID() {
|
||||
return _apName;
|
||||
}
|
||||
|
||||
String WiFiManager::urldecode(const char *src)
|
||||
{
|
||||
String decoded = "";
|
||||
char a, b;
|
||||
while (*src) {
|
||||
if ((*src == '%') &&
|
||||
((a = src[1]) && (b = src[2])) &&
|
||||
(isxdigit(a) && isxdigit(b))) {
|
||||
if (a >= 'a')
|
||||
a -= 'a' - 'A';
|
||||
if (a >= 'A')
|
||||
a -= ('A' - 10);
|
||||
else
|
||||
a -= '0';
|
||||
if (b >= 'a')
|
||||
b -= 'a' - 'A';
|
||||
if (b >= 'A')
|
||||
b -= ('A' - 10);
|
||||
else
|
||||
b -= '0';
|
||||
|
||||
decoded += char(16 * a + b);
|
||||
src += 3;
|
||||
} else if (*src == '+') {
|
||||
decoded += ' ';
|
||||
*src++;
|
||||
} else {
|
||||
decoded += *src;
|
||||
*src++;
|
||||
}
|
||||
}
|
||||
decoded += '\0';
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
void WiFiManager::resetSettings() {
|
||||
DEBUG_WM(F("settings invalidated"));
|
||||
DEBUG_WM(F("THIS MAY CAUSE AP NOT TO STRT UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA."));
|
||||
WiFi.disconnect(true);
|
||||
//delay(200);
|
||||
}
|
||||
|
||||
void WiFiManager::setTimeout(unsigned long seconds) {
|
||||
timeout = seconds * 1000;
|
||||
}
|
||||
|
||||
void WiFiManager::setDebugOutput(boolean debug) {
|
||||
_debug = debug;
|
||||
}
|
||||
|
||||
void WiFiManager::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) {
|
||||
_ap_static_ip = ip;
|
||||
_ap_static_gw = gw;
|
||||
_ap_static_sn = sn;
|
||||
}
|
||||
|
||||
void WiFiManager::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) {
|
||||
_sta_static_ip = ip;
|
||||
_sta_static_gw = gw;
|
||||
_sta_static_sn = sn;
|
||||
}
|
||||
|
||||
void WiFiManager::setMinimumSignalQuality(int quality) {
|
||||
_minimumQuality = quality;
|
||||
}
|
||||
|
||||
void WiFiManager::setBreakAfterConfig(boolean shouldBreak) {
|
||||
_shouldBreakAfterConfig = shouldBreak;
|
||||
}
|
||||
|
||||
/** Handle root or redirect to captive portal */
|
||||
void WiFiManager::handleRoot() {
|
||||
DEBUG_WM(F("Handle root"));
|
||||
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
|
||||
return;
|
||||
}
|
||||
|
||||
String page = FPSTR(HTTP_HEAD);
|
||||
page.replace("{v}", "Options");
|
||||
page += FPSTR(HTTP_SCRIPT);
|
||||
page += FPSTR(HTTP_STYLE);
|
||||
page += FPSTR(HTTP_HEAD_END);
|
||||
page += "<h1>";
|
||||
page += _apName;
|
||||
page += "</h1>";
|
||||
page += F("<h3>WiFiManager</h3>");
|
||||
page += FPSTR(HTTP_PORTAL_OPTIONS);
|
||||
page += FPSTR(HTTP_END);
|
||||
|
||||
server->send(200, "text/html", page);
|
||||
|
||||
}
|
||||
|
||||
/** Wifi config page handler */
|
||||
void WiFiManager::handleWifi(boolean scan) {
|
||||
server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server->sendHeader("Pragma", "no-cache");
|
||||
server->sendHeader("Expires", "-1");
|
||||
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
server->send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
|
||||
String head = FPSTR(HTTP_HEAD);
|
||||
head.replace("{v}", "Config ESP");
|
||||
server->sendContent(head);
|
||||
server->sendContent_P(HTTP_SCRIPT);
|
||||
server->sendContent_P(HTTP_STYLE);
|
||||
server->sendContent_P(HTTP_HEAD_END);
|
||||
|
||||
if (scan) {
|
||||
int n = WiFi.scanNetworks();
|
||||
DEBUG_WM(F("Scan done"));
|
||||
if (n == 0) {
|
||||
DEBUG_WM(F("No networks found"));
|
||||
server->sendContent("No networks found. Refresh to scan again.");
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
DEBUG_WM(WiFi.SSID(i));
|
||||
DEBUG_WM(WiFi.RSSI(i));
|
||||
int quality = getRSSIasQuality(WiFi.RSSI(i));
|
||||
|
||||
if (_minimumQuality == -1 || _minimumQuality < quality) {
|
||||
String item = FPSTR(HTTP_ITEM);
|
||||
String rssiQ;
|
||||
rssiQ += quality;
|
||||
item.replace("{v}", WiFi.SSID(i));
|
||||
item.replace("{r}", rssiQ);
|
||||
if (WiFi.encryptionType(i) != ENC_TYPE_NONE) {
|
||||
item.replace("{i}", FPSTR(HTTP_ITEM_PADLOCK));
|
||||
} else {
|
||||
item.replace("{i}", "");
|
||||
}
|
||||
//DEBUG_WM(item);
|
||||
server->sendContent(item);
|
||||
delay(0);
|
||||
} else {
|
||||
DEBUG_WM(F("Skipping due to quality"));
|
||||
}
|
||||
|
||||
}
|
||||
server->sendContent("<br/>");
|
||||
}
|
||||
}
|
||||
|
||||
server->sendContent_P(HTTP_FORM_START);
|
||||
char parLength[2];
|
||||
// add the extra parameters to the form
|
||||
for (int i = 0; i < _paramsCount; i++) {
|
||||
if (_params[i] == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
String pitem = FPSTR(HTTP_FORM_PARAM);
|
||||
pitem.replace("{i}", _params[i]->getID());
|
||||
pitem.replace("{n}", _params[i]->getID());
|
||||
pitem.replace("{p}", _params[i]->getPlaceholder());
|
||||
snprintf(parLength, 2, "%d", _params[i]->getValueLength());
|
||||
pitem.replace("{l}", parLength);
|
||||
pitem.replace("{v}", _params[i]->getValue());
|
||||
|
||||
server->sendContent(pitem);
|
||||
}
|
||||
if (_params[0] != NULL) {
|
||||
server->sendContent_P("<br/>");
|
||||
}
|
||||
|
||||
if(_sta_static_ip) {
|
||||
|
||||
String item = FPSTR(HTTP_FORM_PARAM);
|
||||
item.replace("{i}", "ip");
|
||||
item.replace("{n}", "ip");
|
||||
item.replace("{p}", "Static IP");
|
||||
item.replace("{l}", "15");
|
||||
item.replace("{v}", _sta_static_ip.toString());
|
||||
|
||||
server->sendContent(item);
|
||||
|
||||
item = FPSTR(HTTP_FORM_PARAM);
|
||||
item.replace("{i}", "gw");
|
||||
item.replace("{n}", "gw");
|
||||
item.replace("{p}", "Static Gateway");
|
||||
item.replace("{l}", "15");
|
||||
item.replace("{v}", _sta_static_gw.toString());
|
||||
|
||||
server->sendContent(item);
|
||||
|
||||
item = FPSTR(HTTP_FORM_PARAM);
|
||||
item.replace("{i}", "sn");
|
||||
item.replace("{n}", "sn");
|
||||
item.replace("{p}", "Subnet");
|
||||
item.replace("{l}", "15");
|
||||
item.replace("{v}", _sta_static_sn.toString());
|
||||
|
||||
server->sendContent(item);
|
||||
|
||||
server->sendContent_P("<br/>");
|
||||
}
|
||||
|
||||
server->sendContent_P(HTTP_FORM_END);
|
||||
server->sendContent_P(HTTP_SCAN_LINK);
|
||||
server->sendContent_P(HTTP_END);
|
||||
server->client().stop();
|
||||
|
||||
DEBUG_WM(F("Sent config page"));
|
||||
}
|
||||
|
||||
/** Handle the WLAN save form and redirect to WLAN config page again */
|
||||
void WiFiManager::handleWifiSave() {
|
||||
DEBUG_WM(F("WiFi save"));
|
||||
|
||||
//SAVE/connect here
|
||||
_ssid = urldecode(server->arg("s").c_str());
|
||||
_pass = urldecode(server->arg("p").c_str());
|
||||
|
||||
//parameters
|
||||
for (int i = 0; i < _paramsCount; i++) {
|
||||
if (_params[i] == NULL) {
|
||||
break;
|
||||
}
|
||||
//read parameter
|
||||
String value = urldecode(server->arg(_params[i]->getID()).c_str());
|
||||
//store it in array
|
||||
value.toCharArray(_params[i]->_value, _params[i]->_length);
|
||||
DEBUG_WM(F("Parameter"));
|
||||
DEBUG_WM(_params[i]->getID());
|
||||
DEBUG_WM(value);
|
||||
}
|
||||
|
||||
if(server->arg("ip") != "") {
|
||||
DEBUG_WM(F("static ip"));
|
||||
DEBUG_WM(server->arg("ip"));
|
||||
_sta_static_ip.fromString(server->arg("ip"));
|
||||
}
|
||||
if(server->arg("gw") != "") {
|
||||
DEBUG_WM(F("static gateway"));
|
||||
DEBUG_WM(server->arg("gw"));
|
||||
_sta_static_gw.fromString(server->arg("gw"));
|
||||
}
|
||||
if(server->arg("sn") != "") {
|
||||
DEBUG_WM(F("static netmask"));
|
||||
DEBUG_WM(server->arg("sn"));
|
||||
_sta_static_sn.fromString(server->arg("sn"));
|
||||
}
|
||||
|
||||
server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server->sendHeader("Pragma", "no-cache");
|
||||
server->sendHeader("Expires", "-1");
|
||||
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
server->send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
|
||||
String head = FPSTR(HTTP_HEAD);
|
||||
head.replace("{v}", "Credentials Saved");
|
||||
server->sendContent(head);
|
||||
server->sendContent_P(HTTP_SCRIPT);
|
||||
server->sendContent_P(HTTP_STYLE);
|
||||
server->sendContent_P(HTTP_HEAD_END);
|
||||
|
||||
server->sendContent_P(HTTP_SAVED);
|
||||
|
||||
server->sendContent_P(HTTP_END);
|
||||
server->client().stop();
|
||||
|
||||
DEBUG_WM(F("Sent wifi save page"));
|
||||
|
||||
connect = true; //signal ready to connect/reset
|
||||
}
|
||||
|
||||
/** Handle the info page */
|
||||
void WiFiManager::handleInfo() {
|
||||
DEBUG_WM(F("Info"));
|
||||
|
||||
String page = FPSTR(HTTP_HEAD);
|
||||
page.replace("{v}", "Info");
|
||||
page += FPSTR(HTTP_SCRIPT);
|
||||
page += FPSTR(HTTP_STYLE);
|
||||
page += FPSTR(HTTP_HEAD_END);
|
||||
page += F("<dl>");
|
||||
page += F("<dt>Chip ID</dt><dd>");
|
||||
page += ESP.getChipId();
|
||||
page += F("</dd>");
|
||||
page += F("<dt>Flash Chip ID</dt><dd>");
|
||||
page += ESP.getFlashChipId();
|
||||
page += F("</dd>");
|
||||
page += F("<dt>Flash Size</dt><dd>");
|
||||
page += ESP.getFlashChipSize();
|
||||
page += F(" bytes</dd>");
|
||||
page += F("<dt>Soft AP IP</dt><dd>");
|
||||
page += WiFi.softAPIP().toString();
|
||||
page += F("</dd>");
|
||||
page += F("<dt>Soft AP MAC</dt><dd>");
|
||||
page += WiFi.softAPmacAddress();
|
||||
page += F("</dd>");
|
||||
page += F("<dt>Station MAC</dt><dd>");
|
||||
page += WiFi.macAddress();
|
||||
page += F("</dd>");
|
||||
page += F("</dl>");
|
||||
page += FPSTR(HTTP_END);
|
||||
|
||||
server->send(200, "text/html", page);
|
||||
|
||||
DEBUG_WM(F("Sent info page"));
|
||||
}
|
||||
|
||||
/** Handle the reset page */
|
||||
void WiFiManager::handleReset() {
|
||||
DEBUG_WM(F("Reset"));
|
||||
|
||||
String page = FPSTR(HTTP_HEAD);
|
||||
page.replace("{v}", "Info");
|
||||
page += FPSTR(HTTP_SCRIPT);
|
||||
page += FPSTR(HTTP_STYLE);
|
||||
page += FPSTR(HTTP_HEAD_END);
|
||||
page += F("Module will reset in a few seconds.");
|
||||
page += FPSTR(HTTP_END);
|
||||
server->send(200, "text/html", page);
|
||||
|
||||
DEBUG_WM(F("Sent reset page"));
|
||||
delay(5000);
|
||||
ESP.reset();
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void WiFiManager::handle204() {
|
||||
DEBUG_WM(F("204 No Response"));
|
||||
server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server->sendHeader("Pragma", "no-cache");
|
||||
server->sendHeader("Expires", "-1");
|
||||
server->send ( 204, "text/plain", "");
|
||||
}
|
||||
|
||||
void WiFiManager::handleNotFound() {
|
||||
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
|
||||
return;
|
||||
}
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server->uri();
|
||||
message += "\nMethod: ";
|
||||
message += ( server->method() == HTTP_GET ) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server->args();
|
||||
message += "\n";
|
||||
|
||||
for ( uint8_t i = 0; i < server->args(); i++ ) {
|
||||
message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n";
|
||||
}
|
||||
server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server->sendHeader("Pragma", "no-cache");
|
||||
server->sendHeader("Expires", "-1");
|
||||
server->send ( 404, "text/plain", message );
|
||||
}
|
||||
|
||||
|
||||
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
|
||||
boolean WiFiManager::captivePortal() {
|
||||
if (!isIp(server->hostHeader()) ) {
|
||||
DEBUG_WM(F("Request redirected to captive portal"));
|
||||
server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true);
|
||||
server->send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server->client().stop(); // Stop is needed because we sent no content length
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//start up config portal callback
|
||||
void WiFiManager::setAPCallback( void (*func)(WiFiManager* myWiFiManager) ) {
|
||||
_apcallback = func;
|
||||
}
|
||||
|
||||
//start up save config callback
|
||||
void WiFiManager::setSaveConfigCallback( void (*func)(void) ) {
|
||||
_savecallback = func;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Generic>
|
||||
void WiFiManager::DEBUG_WM(Generic text) {
|
||||
if (_debug) {
|
||||
Serial.print("*WM: ");
|
||||
Serial.println(text);
|
||||
}
|
||||
}
|
||||
|
||||
int WiFiManager::getRSSIasQuality(int RSSI) {
|
||||
int quality = 0;
|
||||
|
||||
if (RSSI <= -100) {
|
||||
quality = 0;
|
||||
} else if (RSSI >= -50) {
|
||||
quality = 100;
|
||||
} else {
|
||||
quality = 2 * (RSSI + 100);
|
||||
}
|
||||
return quality;
|
||||
}
|
||||
|
||||
/** Is this an IP? */
|
||||
boolean WiFiManager::isIp(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
int c = str.charAt(i);
|
||||
if (c != '.' && (c < '0' || c > '9')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** IP to String? */
|
||||
String WiFiManager::toStringIp(IPAddress ip) {
|
||||
String res = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
res += String((ip >> (8 * i)) & 0xFF) + ".";
|
||||
}
|
||||
res += String(((ip >> 8 * 3)) & 0xFF);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
/**************************************************************
|
||||
WiFiManager is a library for the ESP8266/Arduino platform
|
||||
(https://github.com/esp8266/Arduino) to enable easy
|
||||
configuration and reconfiguration of WiFi credentials using a Captive Portal
|
||||
inspired by:
|
||||
http://www.esp8266.com/viewtopic.php?f=29&t=2520
|
||||
https://github.com/chriscook8/esp-arduino-apboot
|
||||
https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced
|
||||
Built by AlexT https://github.com/tzapu
|
||||
Licensed under MIT license
|
||||
**************************************************************/
|
||||
|
||||
#ifndef WiFiManager_h
|
||||
#define WiFiManager_h
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
#include <memory>
|
||||
|
||||
|
||||
const char HTTP_200[] PROGMEM = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
|
||||
const char HTTP_HEAD[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
|
||||
const char HTTP_STYLE[] PROGMEM = "<style>div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align:center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}</style>";
|
||||
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
|
||||
const char HTTP_HEAD_END[] PROGMEM = "</head><body><div style='text-align: left; display: inline-block;'>";
|
||||
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
|
||||
const char HTTP_ITEM[] PROGMEM = "<div><a href='#' onclick='c(this)'>{v}</a> {r}% {i}</div>";
|
||||
const char HTTP_ITEM_PADLOCK[] PROGMEM = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==' width='13px'/>";
|
||||
const char HTTP_FORM_START[] PROGMEM = "<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 type='password' placeholder='password'><br/>";
|
||||
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' length={l} placeholder='{p}' value='{v}'>";
|
||||
const char HTTP_FORM_END[] PROGMEM = "<br/><button type='submit'>save</button></form>";
|
||||
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><div style=\"text-align: center\"><a href=\"/wifi\">Scan</a></div>";
|
||||
|
||||
const char HTTP_SAVED[] PROGMEM = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
|
||||
const char HTTP_END[] PROGMEM = "</div></body></html>";
|
||||
|
||||
#define WIFI_MANAGER_MAX_PARAMS 10
|
||||
|
||||
class WiFiManagerParameter {
|
||||
public:
|
||||
WiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length);
|
||||
|
||||
const char *getID();
|
||||
const char *getValue();
|
||||
const char *getPlaceholder();
|
||||
int getValueLength();
|
||||
private:
|
||||
const char *_id;
|
||||
const char *_placeholder;
|
||||
char *_value;
|
||||
int _length;
|
||||
|
||||
friend class WiFiManager;
|
||||
};
|
||||
|
||||
|
||||
class WiFiManager
|
||||
{
|
||||
public:
|
||||
WiFiManager();
|
||||
|
||||
boolean autoConnect();
|
||||
boolean autoConnect(char const *apName, char const *apPassword = NULL);
|
||||
|
||||
//if you want to always start the config portal, without trying to connect first
|
||||
boolean startConfigPortal(char const *apName, char const *apPassword = NULL);
|
||||
|
||||
String getSSID();
|
||||
String getPassword();
|
||||
|
||||
// get the AP name of the config portal, so it can be used in the callback
|
||||
String getConfigPortalSSID();
|
||||
|
||||
void resetSettings();
|
||||
//for convenience
|
||||
String urldecode(const char*);
|
||||
|
||||
//sets timeout before webserver loop ends and exits even if there has been no setup.
|
||||
//usefully for devices that failed to connect at some point and got stuck in a webserver loop
|
||||
//in seconds
|
||||
void setTimeout(unsigned long seconds);
|
||||
void setDebugOutput(boolean debug);
|
||||
//defaults to not showing anything under 8% signal quality if called
|
||||
void setMinimumSignalQuality(int quality = 8);
|
||||
//sets a custom ip /gateway /subnet configuration
|
||||
void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn);
|
||||
//sets config for a static IP
|
||||
void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn);
|
||||
//called when AP mode and config portal is started
|
||||
void setAPCallback( void (*func)(WiFiManager*) );
|
||||
//called when settings have been changed and connection was successful
|
||||
void setSaveConfigCallback( void (*func)(void) );
|
||||
//adds a custom parameter
|
||||
void addParameter(WiFiManagerParameter *p);
|
||||
//if this is set, it will exit after config, even if connection is unsucessful.
|
||||
void setBreakAfterConfig(boolean shouldBreak);
|
||||
|
||||
private:
|
||||
std::unique_ptr<DNSServer> dnsServer;
|
||||
std::unique_ptr<ESP8266WebServer> server;
|
||||
|
||||
const int WM_DONE = 0;
|
||||
const int WM_WAIT = 10;
|
||||
|
||||
//const String HTTP_HEAD = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><title>{v}</title>";
|
||||
|
||||
void setupConfigPortal();
|
||||
|
||||
int _eepromStart;
|
||||
const char* _apName = "no-net";
|
||||
const char* _apPassword = NULL;
|
||||
String _ssid = "";
|
||||
String _pass = "";
|
||||
unsigned long timeout = 0;
|
||||
unsigned long start = 0;
|
||||
IPAddress _ap_static_ip;
|
||||
IPAddress _ap_static_gw;
|
||||
IPAddress _ap_static_sn;
|
||||
IPAddress _sta_static_ip;
|
||||
IPAddress _sta_static_gw;
|
||||
IPAddress _sta_static_sn;
|
||||
int _paramsCount = 0;
|
||||
int _minimumQuality = -1;
|
||||
boolean _shouldBreakAfterConfig = false;
|
||||
|
||||
//String getEEPROMString(int start, int len);
|
||||
//void setEEPROMString(int start, int len, String string);
|
||||
|
||||
int status = WL_IDLE_STATUS;
|
||||
int connectWifi(String ssid, String pass);
|
||||
|
||||
void handleRoot();
|
||||
void handleWifi(boolean scan);
|
||||
void handleWifiSave();
|
||||
void handleInfo();
|
||||
void handleReset();
|
||||
void handleNotFound();
|
||||
void handle204();
|
||||
boolean captivePortal();
|
||||
|
||||
// DNS server
|
||||
const byte DNS_PORT = 53;
|
||||
|
||||
//helpers
|
||||
int getRSSIasQuality(int RSSI);
|
||||
boolean isIp(String str);
|
||||
String toStringIp(IPAddress ip);
|
||||
|
||||
boolean connect;
|
||||
boolean _debug = true;
|
||||
|
||||
void (*_apcallback)(WiFiManager*) = NULL;
|
||||
void (*_savecallback)(void) = NULL;
|
||||
|
||||
WiFiManagerParameter* _params[WIFI_MANAGER_MAX_PARAMS];
|
||||
|
||||
template <typename Generic>
|
||||
void DEBUG_WM(Generic text);
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,38 +0,0 @@
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
//reset saved settings
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set custom ip for portal
|
||||
//wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));
|
||||
|
||||
//fetches ssid and pass from eeprom and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
wifiManager.autoConnect("AutoConnectAP");
|
||||
//or use this for auto generated name ESP + ChipID
|
||||
//wifiManager.autoConnect();
|
||||
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
#include <FS.h> //this needs to be first, or it all crashes and burns...
|
||||
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
|
||||
|
||||
//define your default values here, if there are different values in config.json, they are overwritten.
|
||||
char mqtt_server[40];
|
||||
char mqtt_port[6] = "8080";
|
||||
char blynk_token[34] = "YOUR_BLYNK_TOKEN";
|
||||
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
Serial.println("Should save config");
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
|
||||
//clean FS, for testing
|
||||
//SPIFFS.format();
|
||||
|
||||
//read configuration from FS json
|
||||
Serial.println("mounting FS...");
|
||||
|
||||
if (SPIFFS.begin()) {
|
||||
Serial.println("mounted file system");
|
||||
if (SPIFFS.exists("/config.json")) {
|
||||
//file exists, reading and loading
|
||||
Serial.println("reading config file");
|
||||
File configFile = SPIFFS.open("/config.json", "r");
|
||||
if (configFile) {
|
||||
Serial.println("opened config file");
|
||||
size_t size = configFile.size();
|
||||
// Allocate a buffer to store contents of the file.
|
||||
std::unique_ptr<char[]> buf(new char[size]);
|
||||
|
||||
configFile.readBytes(buf.get(), size);
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.parseObject(buf.get());
|
||||
json.printTo(Serial);
|
||||
if (json.success()) {
|
||||
Serial.println("\nparsed json");
|
||||
|
||||
strcpy(mqtt_server, json["mqtt_server"]);
|
||||
strcpy(mqtt_port, json["mqtt_port"]);
|
||||
strcpy(blynk_token, json["blynk_token"]);
|
||||
|
||||
} else {
|
||||
Serial.println("failed to load json config");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Serial.println("failed to mount FS");
|
||||
}
|
||||
//end read
|
||||
|
||||
|
||||
|
||||
// The extra parameters to be configured (can be either global or just in the setup)
|
||||
// After connecting, parameter.getValue() will get you the configured value
|
||||
// id/name placeholder/prompt default length
|
||||
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
|
||||
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 5);
|
||||
WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 32);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
|
||||
//set config save notify callback
|
||||
wifiManager.setSaveConfigCallback(saveConfigCallback);
|
||||
|
||||
//set static ip
|
||||
wifiManager.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0));
|
||||
|
||||
//add all your parameters here
|
||||
wifiManager.addParameter(&custom_mqtt_server);
|
||||
wifiManager.addParameter(&custom_mqtt_port);
|
||||
wifiManager.addParameter(&custom_blynk_token);
|
||||
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set minimu quality of signal so it ignores AP's under that quality
|
||||
//defaults to 8%
|
||||
//wifiManager.setMinimumSignalQuality();
|
||||
|
||||
//sets timeout until configuration portal gets turned off
|
||||
//useful to make it all retry or go to sleep
|
||||
//in seconds
|
||||
//wifiManager.setTimeout(120);
|
||||
|
||||
//fetches ssid and pass and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if (!wifiManager.autoConnect("AutoConnectAP", "password")) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
|
||||
//read updated parameters
|
||||
strcpy(mqtt_server, custom_mqtt_server.getValue());
|
||||
strcpy(mqtt_port, custom_mqtt_port.getValue());
|
||||
strcpy(blynk_token, custom_blynk_token.getValue());
|
||||
|
||||
//save the custom parameters to FS
|
||||
if (shouldSaveConfig) {
|
||||
Serial.println("saving config");
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
json["mqtt_server"] = mqtt_server;
|
||||
json["mqtt_port"] = mqtt_port;
|
||||
json["blynk_token"] = blynk_token;
|
||||
|
||||
File configFile = SPIFFS.open("/config.json", "w");
|
||||
if (!configFile) {
|
||||
Serial.println("failed to open config file for writing");
|
||||
}
|
||||
|
||||
json.printTo(Serial);
|
||||
json.printTo(configFile);
|
||||
configFile.close();
|
||||
//end save
|
||||
}
|
||||
|
||||
Serial.println("local ip");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
|
||||
}
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
#include <FS.h> //this needs to be first, or it all crashes and burns...
|
||||
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
|
||||
|
||||
//define your default values here, if there are different values in config.json, they are overwritten.
|
||||
//length should be max size + 1
|
||||
char mqtt_server[40];
|
||||
char mqtt_port[6] = "8080";
|
||||
char blynk_token[33] = "YOUR_BLYNK_TOKEN";
|
||||
//default custom static IP
|
||||
char static_ip[16] = "10.0.1.56";
|
||||
char static_gw[16] = "10.0.1.1";
|
||||
char static_sn[16] = "255.255.255.0";
|
||||
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
Serial.println("Should save config");
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
|
||||
//clean FS, for testing
|
||||
//SPIFFS.format();
|
||||
|
||||
//read configuration from FS json
|
||||
Serial.println("mounting FS...");
|
||||
|
||||
if (SPIFFS.begin()) {
|
||||
Serial.println("mounted file system");
|
||||
if (SPIFFS.exists("/config.json")) {
|
||||
//file exists, reading and loading
|
||||
Serial.println("reading config file");
|
||||
File configFile = SPIFFS.open("/config.json", "r");
|
||||
if (configFile) {
|
||||
Serial.println("opened config file");
|
||||
size_t size = configFile.size();
|
||||
// Allocate a buffer to store contents of the file.
|
||||
std::unique_ptr<char[]> buf(new char[size]);
|
||||
|
||||
configFile.readBytes(buf.get(), size);
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.parseObject(buf.get());
|
||||
json.printTo(Serial);
|
||||
if (json.success()) {
|
||||
Serial.println("\nparsed json");
|
||||
|
||||
strcpy(mqtt_server, json["mqtt_server"]);
|
||||
strcpy(mqtt_port, json["mqtt_port"]);
|
||||
strcpy(blynk_token, json["blynk_token"]);
|
||||
|
||||
if(json["ip"]) {
|
||||
Serial.println("setting custom ip from config");
|
||||
//static_ip = json["ip"];
|
||||
strcpy(static_ip, json["ip"]);
|
||||
strcpy(static_gw, json["gateway"]);
|
||||
strcpy(static_sn, json["subnet"]);
|
||||
//strcat(static_ip, json["ip"]);
|
||||
//static_gw = json["gateway"];
|
||||
//static_sn = json["subnet"];
|
||||
Serial.println(static_ip);
|
||||
/* Serial.println("converting ip");
|
||||
IPAddress ip = ipFromCharArray(static_ip);
|
||||
Serial.println(ip);*/
|
||||
} else {
|
||||
Serial.println("no custom ip in config");
|
||||
}
|
||||
} else {
|
||||
Serial.println("failed to load json config");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Serial.println("failed to mount FS");
|
||||
}
|
||||
//end read
|
||||
Serial.println(static_ip);
|
||||
Serial.println(blynk_token);
|
||||
Serial.println(mqtt_server);
|
||||
|
||||
|
||||
// The extra parameters to be configured (can be either global or just in the setup)
|
||||
// After connecting, parameter.getValue() will get you the configured value
|
||||
// id/name placeholder/prompt default length
|
||||
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
|
||||
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 5);
|
||||
WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 34);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
|
||||
//set config save notify callback
|
||||
wifiManager.setSaveConfigCallback(saveConfigCallback);
|
||||
|
||||
//set static ip
|
||||
IPAddress _ip,_gw,_sn;
|
||||
_ip.fromString(static_ip);
|
||||
_gw.fromString(static_gw);
|
||||
_sn.fromString(static_sn);
|
||||
|
||||
wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);
|
||||
|
||||
//add all your parameters here
|
||||
wifiManager.addParameter(&custom_mqtt_server);
|
||||
wifiManager.addParameter(&custom_mqtt_port);
|
||||
wifiManager.addParameter(&custom_blynk_token);
|
||||
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set minimu quality of signal so it ignores AP's under that quality
|
||||
//defaults to 8%
|
||||
wifiManager.setMinimumSignalQuality();
|
||||
|
||||
//sets timeout until configuration portal gets turned off
|
||||
//useful to make it all retry or go to sleep
|
||||
//in seconds
|
||||
//wifiManager.setTimeout(120);
|
||||
|
||||
//fetches ssid and pass and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if (!wifiManager.autoConnect("AutoConnectAP", "password")) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
|
||||
//read updated parameters
|
||||
strcpy(mqtt_server, custom_mqtt_server.getValue());
|
||||
strcpy(mqtt_port, custom_mqtt_port.getValue());
|
||||
strcpy(blynk_token, custom_blynk_token.getValue());
|
||||
|
||||
//save the custom parameters to FS
|
||||
if (shouldSaveConfig) {
|
||||
Serial.println("saving config");
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
json["mqtt_server"] = mqtt_server;
|
||||
json["mqtt_port"] = mqtt_port;
|
||||
json["blynk_token"] = blynk_token;
|
||||
|
||||
json["ip"] = WiFi.localIP().toString();
|
||||
json["gateway"] = WiFi.gatewayIP().toString();
|
||||
json["subnet"] = WiFi.subnetMask().toString();
|
||||
|
||||
File configFile = SPIFFS.open("/config.json", "w");
|
||||
if (!configFile) {
|
||||
Serial.println("failed to open config file for writing");
|
||||
}
|
||||
|
||||
json.prettyPrintTo(Serial);
|
||||
json.printTo(configFile);
|
||||
configFile.close();
|
||||
//end save
|
||||
}
|
||||
|
||||
Serial.println("local ip");
|
||||
Serial.println(WiFi.localIP());
|
||||
Serial.println(WiFi.gatewayIP());
|
||||
Serial.println(WiFi.subnetMask());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include "WiFiManager.h" //https://github.com/tzapu/WiFiManager
|
||||
|
||||
void configModeCallback (WiFiManager *myWiFiManager) {
|
||||
Serial.println("Entered config mode");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
//if you used auto generated SSID, print it
|
||||
Serial.println(myWiFiManager->getConfigPortalSSID());
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
|
||||
wifiManager.setAPCallback(configModeCallback);
|
||||
|
||||
//fetches ssid and pass and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if(!wifiManager.autoConnect()) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
//for LED status
|
||||
#include <Ticker.h>
|
||||
Ticker ticker;
|
||||
|
||||
void tick()
|
||||
{
|
||||
//toggle state
|
||||
int state = digitalRead(BUILTIN_LED); // get the current state of GPIO1 pin
|
||||
digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state
|
||||
}
|
||||
|
||||
//gets called when WiFiManager enters configuration mode
|
||||
void configModeCallback (WiFiManager *myWiFiManager) {
|
||||
Serial.println("Entered config mode");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
//if you used auto generated SSID, print it
|
||||
Serial.println(myWiFiManager->getConfigPortalSSID());
|
||||
//entered config mode, make led toggle faster
|
||||
ticker.attach(0.2, tick);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
|
||||
//set led pin as output
|
||||
pinMode(BUILTIN_LED, OUTPUT);
|
||||
// start ticker with 0.5 because we start in AP mode and try to connect
|
||||
ticker.attach(0.6, tick);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
|
||||
wifiManager.setAPCallback(configModeCallback);
|
||||
|
||||
//fetches ssid and pass and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if (!wifiManager.autoConnect()) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
ticker.detach();
|
||||
//keep LED on
|
||||
digitalWrite(BUILTIN_LED, LOW);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include <FS.h> //this needs to be first, or it all crashes and burns...
|
||||
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
|
||||
//exit after config instead of connecting
|
||||
wifiManager.setBreakAfterConfig(true);
|
||||
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
|
||||
//tries to connect to last known settings
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP" with password "password"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if (!wifiManager.autoConnect("AutoConnectAP", "password")) {
|
||||
Serial.println("failed to connect, we should reset as see if it connects");
|
||||
delay(3000);
|
||||
ESP.reset();
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
|
||||
|
||||
Serial.println("local ip");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//sets timeout until configuration portal gets turned off
|
||||
//useful to make it all retry or go to sleep
|
||||
//in seconds
|
||||
wifiManager.setTimeout(180);
|
||||
|
||||
//fetches ssid and pass and tries to connect
|
||||
//if it does not connect it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
if(!wifiManager.autoConnect("AutoConnectAP")) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
|
||||
|
||||
//needed for library
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
|
||||
|
||||
// select wich pin will trigger the configuraton portal when set to LOW
|
||||
// ESP-01 users please note: the only pins available (0 and 2), are shared
|
||||
// with the bootloader, so always set them HIGH at power-up
|
||||
#define TRIGGER_PIN 0
|
||||
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.println("\n Starting");
|
||||
|
||||
pinMode(TRIGGER_PIN, INPUT);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
// is configuration portal requested?
|
||||
if ( digitalRead(TRIGGER_PIN) == LOW ) {
|
||||
//WiFiManager
|
||||
//Local intialization. Once its business is done, there is no need to keep it around
|
||||
WiFiManager wifiManager;
|
||||
|
||||
//reset settings - for testing
|
||||
//wifiManager.resetSettings();
|
||||
|
||||
//sets timeout until configuration portal gets turned off
|
||||
//useful to make it all retry or go to sleep
|
||||
//in seconds
|
||||
//wifiManager.setTimeout(120);
|
||||
|
||||
//it starts an access point with the specified name
|
||||
//here "AutoConnectAP"
|
||||
//and goes into a blocking loop awaiting configuration
|
||||
|
||||
//WITHOUT THIS THE AP DOES NOT SEEM TO WORK PROPERLY WITH SDK 1.5 , update to at least 1.5.1
|
||||
//WiFi.mode(WIFI_STA);
|
||||
|
||||
if (!wifiManager.startConfigPortal("OnDemandAP")) {
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(3000);
|
||||
//reset and try again, or maybe put it to deep sleep
|
||||
ESP.reset();
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
}
|
||||
|
||||
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>
|
||||
<style>.c{text-align: center} div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;}</style>
|
||||
<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>
|
||||
</head><body><div style="text-align: left; display: inline-block;">
|
||||
<div><a href='#' onclick='c(this)'>{v}</a> {r}% {i}</div>
|
||||
<div><a href='#' onclick='c(this)'>{v}</a> 100% <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAACVBMVEUAAAC7u7v///9etNz6AAAAI0lEQVQI133GsQ3AMAAEoXv2H9qNJacKFbEaWTQJ+uQB/nMdEaYAXeJkZ9AAAAAASUVORK5CYII='/></div>
|
||||
<div><a href='#' onclick='c(this)'>Vila17</a> 95% <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAADFBMVEUAAAAAtgD///8AAADV1YIHAAAAA3RSTlMAAAD6dsTeAAAAHUlEQVR4AWMAAmYgYGBkArHAAMoEYnQmMxSQywQAKvMA3+mlSFAAAAAASUVORK5CYII='/></div>
|
||||
<div><a href='#'>LilRidingHoodz</a> 100% <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAABlBMVEUAAAD///+l2Z/dAAAAIklEQVR4AWNgZGRkAAIQBURQDEZAgM5kgAJGEAQDwkxkbQAOhgBBk64+mQAAAABJRU5ErkJggg=='/></div>
|
||||
<div><a href='#'>Marcelica</a> 100% <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAACVBMVEUAAAC7u7v///9etNz6AAAAJ0lEQVR4AY3GsREAIBSDUD77D63x4tlKw0MdmD0ijTO9f6SFnv7YXB4qAIVn0xoMAAAAAElFTkSuQmCC'/></div>
|
||||
<div><a href='#'>PMica</a> 110% <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==' width="13px"/></div>
|
||||
<br/>
|
||||
<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 placeholder='password'><br/><button type='submit'>save</button></form>
|
||||
<br/><div></div>
|
||||
<br/>
|
||||
<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/>
|
||||
<form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form>
|
||||
<br>
|
||||
<div class="c"><a href=\"/wifi\">Scan</a></div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -1,40 +0,0 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For WifiManager
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
WiFiManager KEYWORD1
|
||||
WiFiManagerParameter KEYWORD1
|
||||
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
autoConnect KEYWORD2
|
||||
getSSID KEYWORD2
|
||||
getPassword KEYWORD2
|
||||
getConfigPortalSSID KEYWORD2
|
||||
resetSettings KEYWORD2
|
||||
urldecode KEYWORD2
|
||||
setTimeout KEYWORD2
|
||||
setDebugOutput KEYWORD2
|
||||
setMinimumSignalQuality KEYWORD2
|
||||
setAPStaticIPConfig KEYWORD2
|
||||
setSTAStaticIPConfig KEYWORD2
|
||||
setAPCallback KEYWORD2
|
||||
setSaveConfigCallback KEYWORD2
|
||||
addParameter KEYWORD2
|
||||
|
||||
getID KEYWORD2
|
||||
getValue KEYWORD2
|
||||
getPlaceholder KEYWORD2
|
||||
getValueLength KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
# LITERAL1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* WiFiManagerDfteLogger.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Optional bridge from DFTE's logging hooks to WiFiManager::log.
|
||||
* Enabled only when the sketch / library build defines WM_DFTE_LOGGING.
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerDfteLogger_h
|
||||
#define WiFiManagerDfteLogger_h
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#include <DeviceFrameworkTemplateEngineDebug.h>
|
||||
#include "WiFiManager.h"
|
||||
|
||||
/**
|
||||
* Forwards DeviceFrameworkTemplateEngineLogger calls to WiFiManager logging
|
||||
* with subsystem WM/DFTE.
|
||||
*/
|
||||
class WiFiManagerDfteLogger : public DeviceFrameworkTemplateEngineLogger {
|
||||
public:
|
||||
explicit WiFiManagerDfteLogger(WiFiManager* wm) : _wm(wm) {}
|
||||
|
||||
void error(const String& msg) override { logLine(WiFiManagerLogLevel::Error, msg); }
|
||||
void warn(const String& msg) override { logLine(WiFiManagerLogLevel::Warn, msg); }
|
||||
void info(const String& msg) override { logLine(WiFiManagerLogLevel::Debug, msg); }
|
||||
void debug(const String& msg) override { logLine(WiFiManagerLogLevel::Trace, msg); }
|
||||
|
||||
private:
|
||||
WiFiManager* _wm;
|
||||
|
||||
void logLine(WiFiManagerLogLevel level, const String& msg) {
|
||||
if (!_wm) {
|
||||
return;
|
||||
}
|
||||
_wm->log(level, "WM/DFTE", msg);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ESP8266 || ESP32
|
||||
|
||||
#endif // WiFiManagerDfteLogger_h
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* WiFiManagerHandlers.h
|
||||
*
|
||||
* HTTP request handlers for WiFiManager: single HTML shell + JSON APIs.
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerHandlers_h
|
||||
#define WiFiManagerHandlers_h
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <memory>
|
||||
#include "WiFiManager.h"
|
||||
|
||||
#ifndef WM_TEMPLATE_REGISTRY_CAPACITY
|
||||
#define WM_TEMPLATE_REGISTRY_CAPACITY 16
|
||||
#endif
|
||||
|
||||
// Portal UI customization is driven by WiFiManager `portal*` APIs and JSON (/api/...) — not by mutating template
|
||||
// placeholder registries.
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// FORM FIELD NAMES (for IP configuration forms)
|
||||
|
||||
const char S_ip[] PROGMEM = "ip";
|
||||
const char S_gw[] PROGMEM = "gw";
|
||||
const char S_sn[] PROGMEM = "sn";
|
||||
const char S_dns[] PROGMEM = "dns";
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HTTP HEADERS
|
||||
|
||||
const char HTTP_HEAD_CT[] PROGMEM = "text/html";
|
||||
const char HTTP_HEAD_CT2[] PROGMEM = "text/plain";
|
||||
const char HTTP_HEAD_CORS[] PROGMEM = "Access-Control-Allow-Origin";
|
||||
const char HTTP_HEAD_CORS_ALLOW_ALL[] PROGMEM = "*";
|
||||
|
||||
class WiFiManagerHandlers {
|
||||
public:
|
||||
WiFiManagerHandlers(WiFiManager* wm);
|
||||
|
||||
void handleRoot(AsyncWebServerRequest *request);
|
||||
void handleNotFound(AsyncWebServerRequest *request);
|
||||
void handleRequest(AsyncWebServerRequest *request);
|
||||
void handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
void handleUpdateDone(AsyncWebServerRequest *request);
|
||||
|
||||
void handleApiBootstrap(AsyncWebServerRequest *request);
|
||||
void handleApiWifiScanStatus(AsyncWebServerRequest *request);
|
||||
void handleApiWifiScan(AsyncWebServerRequest *request);
|
||||
void handleApiWifiMeta(AsyncWebServerRequest *request);
|
||||
void handleApiWifiSave(AsyncWebServerRequest *request);
|
||||
void handleApiWifiConnectStatus(AsyncWebServerRequest *request);
|
||||
void handleApiParamsGet(AsyncWebServerRequest *request);
|
||||
void handleApiParamsSave(AsyncWebServerRequest *request);
|
||||
void handleApiInfo(AsyncWebServerRequest *request);
|
||||
void handleApiStatus(AsyncWebServerRequest *request);
|
||||
void handleApiDeviceRestart(AsyncWebServerRequest *request);
|
||||
void handleApiDeviceErase(AsyncWebServerRequest *request, boolean optionalErase = false);
|
||||
void handleApiPortalClose(AsyncWebServerRequest *request);
|
||||
void handleApiPortalExit(AsyncWebServerRequest *request);
|
||||
|
||||
boolean captivePortal(AsyncWebServerRequest *request);
|
||||
void stopCaptivePortal();
|
||||
|
||||
/** True when the HTTP Host header does not match the portal’s canonical host:port (captive redirect needed). */
|
||||
static bool shouldRedirectCaptiveForHost(const String& requestHost, const String& serverLocWithPort);
|
||||
|
||||
void doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs);
|
||||
|
||||
/** Portal bootstrap JSON (same payload as GET /api/bootstrap). Public for tests and host integration. */
|
||||
String buildPortalBootstrapJson();
|
||||
/** Same JSON body as GET /api/wifi/meta (tests + embedding). */
|
||||
String buildApiWifiMetaJson();
|
||||
/** Same JSON body as GET /api/info (tests + embedding). */
|
||||
String buildApiInfoJson();
|
||||
/** Same JSON body as GET /api/params (tests + embedding). */
|
||||
String buildApiParamsGetJson();
|
||||
/** Same JSON body as GET /api/status (tests + embedding). */
|
||||
String buildApiStatusJson();
|
||||
/** GET /api/wifi/connect-status (portal connect progress). */
|
||||
String buildApiWifiConnectStatusJson();
|
||||
|
||||
/** Fixed JSON bodies for POST action endpoints (single source for handlers + tests). */
|
||||
static String jsonApiWifiScanAccepted();
|
||||
static String jsonApiDeviceRestartScheduled();
|
||||
static String jsonApiParamsSaveOk();
|
||||
static String jsonApiPortalCloseOk();
|
||||
static String jsonApiPortalExitOk();
|
||||
static String jsonApiPortalExitForbidden();
|
||||
static String jsonApiOtaUpdateSuccess();
|
||||
static String jsonApiEraseResponse(boolean success);
|
||||
|
||||
private:
|
||||
WiFiManager* _wm;
|
||||
void collectVisibleScanResults(std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
|
||||
void appendVisibleScanResultsJson(String& json, const std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
|
||||
|
||||
void applyWifiAndParamsFromRequest(AsyncWebServerRequest *request);
|
||||
void buildPlainStatusSummary(String& out);
|
||||
void appendPortalJsonStaticFields(String& json, bool& first);
|
||||
void appendPortalJsonCustomParams(String& json, bool& first);
|
||||
|
||||
String composePortalStylesheet() const;
|
||||
void appendPortalExtraInfoSectionsJson(String& json, bool& first);
|
||||
void appendPortalExtraHomeCardsJson(String& json, bool& first);
|
||||
|
||||
void appendJsonKvItem(String& json, bool& first, const char* key, const String& label, const String& value);
|
||||
void appendOneInfoItemForId(String& json, bool& first, const char* id);
|
||||
void appendInfoSectionFromIds(String& json, const char* const* ids, size_t count, bool& first);
|
||||
|
||||
void appendApiInfoActionsJson(String& json);
|
||||
|
||||
static void sendApiJson(AsyncWebServerRequest *request, int code, const String& json);
|
||||
};
|
||||
|
||||
#endif // defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#endif // WiFiManagerHandlers_h
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* WiFiManagerLogLevel.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Compile-time and runtime log levels for WiFiManager. Higher numeric values
|
||||
* mean more verbose output. Matches the old 0..5 tier model for build flags.
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerLogLevel_h
|
||||
#define WiFiManagerLogLevel_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/** Default subsystem tag for core WiFiManager messages (macro for C++11 embedded toolchains). */
|
||||
#define kWiFiMgrLogSubsystem "WM"
|
||||
|
||||
enum class WiFiManagerLogLevel : uint8_t {
|
||||
Silent = 0,
|
||||
Error = 1,
|
||||
Warn = 2,
|
||||
Info = 3,
|
||||
Debug = 4,
|
||||
Trace = 5,
|
||||
};
|
||||
|
||||
/** Full line delivered to Print or WiFiManagerLogSink (prefix and tags included). */
|
||||
struct WiFiManagerLogMessage {
|
||||
WiFiManagerLogLevel level;
|
||||
const char* subsystem;
|
||||
String line;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* WiFiManagerLogSink.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Optional host-provided sink for WiFiManager log output. When set, logs are
|
||||
* delivered here instead of the configured Print stream (usually Serial).
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerLogSink_h
|
||||
#define WiFiManagerLogSink_h
|
||||
|
||||
#include "WiFiManagerLogLevel.h"
|
||||
|
||||
class WiFiManagerLogSink {
|
||||
public:
|
||||
virtual void log(const WiFiManagerLogMessage& msg) = 0;
|
||||
virtual ~WiFiManagerLogSink() = default;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* WiFiManagerLogTemplates.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Template implementations for WiFiManager::log(...). Included at the end of WiFiManager.h.
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerLogTemplates_h
|
||||
#define WiFiManagerLogTemplates_h
|
||||
|
||||
#include "WiFiManagerLogLevel.h"
|
||||
#include <IPAddress.h>
|
||||
#include <WString.h>
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
inline String wmLogStringifyArg(const __FlashStringHelper* v) {
|
||||
return String(v);
|
||||
}
|
||||
|
||||
inline String wmLogStringifyArg(const IPAddress& v) {
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline String wmLogStringifyArg(const T& v) {
|
||||
return String(v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void WiFiManager::log(WiFiManagerLogLevel level, const char* subsystem, T&& text) {
|
||||
#ifndef WM_NO_LOG
|
||||
emitLogImpl(level, subsystem, wmLogStringifyArg(text), String());
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
inline void WiFiManager::log(WiFiManagerLogLevel level, const char* subsystem, T&& a, U&& b) {
|
||||
#ifndef WM_NO_LOG
|
||||
emitLogImpl(level, subsystem, wmLogStringifyArg(a), wmLogStringifyArg(b));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // ESP8266 || ESP32
|
||||
|
||||
#endif // WiFiManagerLogTemplates_h
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* WiFiManagerParameter.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerParameter_h
|
||||
#define WiFiManagerParameter_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class WiFiManager;
|
||||
|
||||
#ifndef WIFI_MANAGER_MAX_PARAMS
|
||||
#define WIFI_MANAGER_MAX_PARAMS 5 // params will autoincrement and realloc by this amount when max is reached
|
||||
#endif
|
||||
|
||||
#define WFM_LABEL_BEFORE 1
|
||||
#define WFM_LABEL_AFTER 2
|
||||
#define WFM_NO_LABEL 0
|
||||
#define WFM_LABEL_DEFAULT 1
|
||||
|
||||
class WiFiManagerParameter {
|
||||
public:
|
||||
/**
|
||||
Create custom parameters that can be added to the WiFiManager setup web page
|
||||
@id is used for HTTP queries and must not contain spaces nor other special characters
|
||||
*/
|
||||
WiFiManagerParameter();
|
||||
explicit WiFiManagerParameter(const char *custom);
|
||||
WiFiManagerParameter(const char *id, const char *label);
|
||||
WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length);
|
||||
WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom);
|
||||
WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement);
|
||||
virtual ~WiFiManagerParameter();
|
||||
|
||||
WiFiManagerParameter(const WiFiManagerParameter&) = delete;
|
||||
WiFiManagerParameter& operator=(const WiFiManagerParameter&) = delete;
|
||||
|
||||
const char *getID() const;
|
||||
const char *getValue() const;
|
||||
const char *getLabel() const;
|
||||
const char *getPlaceholder() const; // @deprecated, use getLabel
|
||||
int getValueLength() const;
|
||||
int getLabelPlacement() const;
|
||||
virtual const char *getCustomHTML() const;
|
||||
void setValue(const char *defaultValue, int length);
|
||||
|
||||
protected:
|
||||
void init(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement);
|
||||
|
||||
const char *_id;
|
||||
const char *_label;
|
||||
char *_value;
|
||||
int _length;
|
||||
int _labelPlacement;
|
||||
|
||||
const char *_customHTML;
|
||||
friend class WiFiManager;
|
||||
};
|
||||
|
||||
#endif // WiFiManagerParameter_h
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* WiFiManagerServer.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* HTTP server for the config portal: single HTML shell (GET /), JSON under /api/..., OTA POST /u,
|
||||
* captive-portal redirects, and 404 handling. No legacy page routes.
|
||||
*
|
||||
* Supported surface (authoritative; registerRoutes mirrors this):
|
||||
* GET /
|
||||
* GET /api/bootstrap
|
||||
* GET /api/wifi/scan-status
|
||||
* POST /api/wifi/scan
|
||||
* GET /api/wifi/meta
|
||||
* POST /api/wifi/save
|
||||
* GET /api/wifi/connect-status
|
||||
* GET /api/params
|
||||
* POST /api/params/save
|
||||
* GET /api/info
|
||||
* GET /api/status
|
||||
* POST /api/device/restart
|
||||
* POST /api/device/erase
|
||||
* POST /api/portal/close
|
||||
* POST /api/portal/exit
|
||||
* POST /u (multipart firmware upload; JSON completion response)
|
||||
* notFound -> captive redirect or 404
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerServer_h
|
||||
#define WiFiManagerServer_h
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <DNSServer.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class WiFiManager;
|
||||
class WiFiManagerHandlers;
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
class WiFiManagerDfteLogger;
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HTTP routes (portal API only; no legacy /wifi, /info, … page URLs)
|
||||
|
||||
const char R_root[] PROGMEM = "/";
|
||||
|
||||
const char R_api_bootstrap[] PROGMEM = "/api/bootstrap";
|
||||
const char R_api_wifi_scan_status[] PROGMEM = "/api/wifi/scan-status";
|
||||
const char R_api_wifi_scan[] PROGMEM = "/api/wifi/scan";
|
||||
const char R_api_wifi_meta[] PROGMEM = "/api/wifi/meta";
|
||||
const char R_api_wifi_save[] PROGMEM = "/api/wifi/save";
|
||||
const char R_api_wifi_connect_status[] PROGMEM = "/api/wifi/connect-status";
|
||||
const char R_api_params[] PROGMEM = "/api/params";
|
||||
const char R_api_params_save[] PROGMEM = "/api/params/save";
|
||||
const char R_api_info[] PROGMEM = "/api/info";
|
||||
const char R_api_status[] PROGMEM = "/api/status";
|
||||
const char R_api_device_restart[] PROGMEM = "/api/device/restart";
|
||||
const char R_api_device_erase[] PROGMEM = "/api/device/erase";
|
||||
const char R_api_portal_close[] PROGMEM = "/api/portal/close";
|
||||
const char R_api_portal_exit[] PROGMEM = "/api/portal/exit";
|
||||
|
||||
const char R_updatedone[] PROGMEM = "/u";
|
||||
|
||||
class WiFiManagerServer {
|
||||
public:
|
||||
WiFiManagerServer(WiFiManager* wm);
|
||||
~WiFiManagerServer();
|
||||
|
||||
void createServer(uint16_t port);
|
||||
void registerRoutes();
|
||||
void setupDNSD();
|
||||
void processDNS();
|
||||
void shutdownServer();
|
||||
AsyncWebServer* getServer() { return server.get(); }
|
||||
DNSServer* getDNSServer() { return dnsServer.get(); }
|
||||
|
||||
private:
|
||||
WiFiManager* _wm;
|
||||
std::unique_ptr<WiFiManagerHandlers> _handlers;
|
||||
std::unique_ptr<AsyncWebServer> server;
|
||||
std::unique_ptr<DNSServer> dnsServer;
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
std::unique_ptr<WiFiManagerDfteLogger> _dfteLogger;
|
||||
bool _wmOwnsDfteLogSink = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#endif // WiFiManagerServer_h
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* WiFiManagerUtils.h
|
||||
* Pure utility functions for WiFiManager
|
||||
* String conversions, HTML encoding, and helper calculations
|
||||
* These functions have no dependency on WiFiManager instance state
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#ifndef _WIFI_MANAGER_UTILS_H_
|
||||
#define _WIFI_MANAGER_UTILS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Forward declaration
|
||||
class IPAddress;
|
||||
|
||||
namespace WiFiManagerUtils {
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// WIFI STATUS ARRAYS (for status string conversion)
|
||||
|
||||
extern const char * const WIFI_STA_STATUS[] PROGMEM;
|
||||
extern const char * const WIFI_MODES[] PROGMEM;
|
||||
|
||||
#ifdef ESP32
|
||||
extern const char * const AUTH_MODE_NAMES[] PROGMEM;
|
||||
#elif defined(ESP8266)
|
||||
extern const char * const AUTH_MODE_NAMES[] PROGMEM;
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// STRING CONVERSION UTILITIES
|
||||
|
||||
/**
|
||||
* Convert WiFi status code to readable string
|
||||
* @param status WiFi status code
|
||||
* @return Status string or "Unknown" if invalid
|
||||
*/
|
||||
String getStatusString(uint8_t status);
|
||||
|
||||
|
||||
/**
|
||||
* Convert WiFi mode code to readable string
|
||||
* @param mode WiFi mode code
|
||||
* @return Mode string or "Unknown" if invalid
|
||||
*/
|
||||
String getModeString(uint8_t mode);
|
||||
|
||||
/**
|
||||
* Convert encryption/auth mode to readable string
|
||||
* @param authmode Encryption type code
|
||||
* @return Encryption type string
|
||||
*/
|
||||
String getEncryptionString(uint8_t authmode);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HTML UTILITIES
|
||||
|
||||
/**
|
||||
* Encode HTML entities in a string
|
||||
* @param str String to encode
|
||||
* @param whitespace If true, also encode spaces as  
|
||||
* @return Encoded string
|
||||
*/
|
||||
String htmlEntities(const String& str, bool whitespace = false);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// IP/STRING UTILITIES
|
||||
|
||||
/**
|
||||
* Check if string is a valid IP address
|
||||
* @param str String to check
|
||||
* @return true if valid IP address
|
||||
*/
|
||||
bool isValidIP(const String& str);
|
||||
|
||||
/**
|
||||
* Convert IPAddress to String
|
||||
* @param ip IPAddress to convert
|
||||
* @return IP address string
|
||||
*/
|
||||
String ipToString(IPAddress ip);
|
||||
|
||||
/**
|
||||
* Validate AP password format (8-63 characters)
|
||||
* @param password Password to validate
|
||||
* @return true if valid
|
||||
*/
|
||||
bool isValidAPPassword(const String& password);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HELPER CALCULATIONS
|
||||
|
||||
/**
|
||||
* Convert RSSI value to quality percentage (0-100)
|
||||
* @param rssi RSSI value in dBm
|
||||
* @return Quality percentage (0-100)
|
||||
*/
|
||||
int rssiToQuality(int rssi);
|
||||
|
||||
} // namespace WiFiManagerUtils
|
||||
|
||||
#endif // _WIFI_MANAGER_UTILS_H_
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* CSS.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* CSS stylesheet for WiFiManager web interface.
|
||||
* Embedded styles to avoid external file dependencies.
|
||||
*/
|
||||
|
||||
#ifndef _CSS_TEMPLATES_H_
|
||||
#define _CSS_TEMPLATES_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char CSS_STYLE[] PROGMEM = "<style>"
|
||||
/* Design tokens */
|
||||
":root{"
|
||||
"--wm-bg:#f0f4f8;"
|
||||
"--wm-surface:#ffffff;"
|
||||
"--wm-text:#0f172a;"
|
||||
"--wm-muted:#64748b;"
|
||||
"--wm-border:#e2e8f0;"
|
||||
"--wm-brand:#0ea5e9;"
|
||||
"--wm-brand-hover:#0284c7;"
|
||||
"--wm-danger:#dc2626;"
|
||||
"--wm-danger-hover:#b91c1c;"
|
||||
"--wm-success:#16a34a;"
|
||||
"--wm-radius:12px;"
|
||||
"--wm-radius-sm:8px;"
|
||||
"--wm-shadow:0 1px 3px rgba(15,23,42,.08),0 4px 16px rgba(15,23,42,.06);"
|
||||
"--wm-font:system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;"
|
||||
"}"
|
||||
"body.invert{"
|
||||
"--wm-bg:#0c1222;"
|
||||
"--wm-surface:#151d2e;"
|
||||
"--wm-text:#f1f5f9;"
|
||||
"--wm-muted:#94a3b8;"
|
||||
"--wm-border:#334155;"
|
||||
"--wm-brand:#38bdf8;"
|
||||
"--wm-brand-hover:#7dd3fc;"
|
||||
"--wm-danger:#f87171;"
|
||||
"--wm-danger-hover:#fca5a5;"
|
||||
"--wm-success:#4ade80;"
|
||||
"--wm-shadow:0 4px 24px rgba(0,0,0,.45);"
|
||||
"}"
|
||||
/* Base */
|
||||
"*,*::before,*::after{box-sizing:border-box}"
|
||||
"body.portal{margin:0;min-height:100vh;background:var(--wm-bg);color:var(--wm-text);"
|
||||
"font-family:var(--wm-font);font-size:16px;line-height:1.5;-webkit-text-size-adjust:100%;text-align:left}"
|
||||
"a{color:var(--wm-brand);font-weight:600;text-decoration:none}"
|
||||
"a:hover{color:var(--wm-brand-hover);text-decoration:underline}"
|
||||
/* Layout shell */
|
||||
".wm-layout{width:100%;max-width:420px;margin:0 auto;padding:16px 18px 28px;}"
|
||||
"h1,h2,h3{margin:0 0 .35em;font-weight:700;letter-spacing:-.02em}"
|
||||
"h1{font-size:1.5rem}"
|
||||
"h2{font-size:1.15rem}"
|
||||
"h3{font-size:1rem}"
|
||||
"p{margin:.5em 0}"
|
||||
/* Top navigation — single row; overflow scrollable but scrollbar hidden (touch still pans) */
|
||||
".wm-nav{display:flex;flex-wrap:nowrap;align-items:stretch;gap:4px;margin:0 0 20px;padding:4px 6px;"
|
||||
"overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;-ms-overflow-style:none;"
|
||||
"background:var(--wm-surface);border-radius:var(--wm-radius);box-shadow:var(--wm-shadow);border:1px solid var(--wm-border)}"
|
||||
".wm-nav::-webkit-scrollbar{display:none;width:0;height:0}"
|
||||
".wm-nav-link{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;padding:6px 8px;border-radius:var(--wm-radius-sm);"
|
||||
"font-size:.8rem;font-weight:600;color:var(--wm-muted);text-decoration:none;border:1px solid transparent;white-space:nowrap}"
|
||||
".wm-nav-link:hover{color:var(--wm-brand);background:rgba(14,165,233,.08);text-decoration:none}"
|
||||
"body.invert .wm-nav-link:hover{background:rgba(56,189,248,.12)}"
|
||||
".wm-nav-link--active{color:var(--wm-text);background:rgba(14,165,233,.15);border-color:rgba(14,165,233,.35)}"
|
||||
"body.invert .wm-nav-link--active{background:rgba(56,189,248,.2);border-color:rgba(56,189,248,.4)}"
|
||||
".wm-nav .wm-icon-label{gap:4px}"
|
||||
".wm-nav .wm-icon{width:.95rem;height:.95rem;flex:0 0 .95rem}"
|
||||
".wm-icon-label{display:inline-flex;align-items:center;gap:8px;min-width:0}"
|
||||
".wm-icon{display:inline-flex;align-items:center;justify-content:center;width:1.1rem;height:1.1rem;flex:0 0 1.1rem;color:currentColor}"
|
||||
".wm-icon svg{display:block;width:100%;height:100%}"
|
||||
/* Hero / page chrome */
|
||||
".wm-hero{margin-bottom:20px}"
|
||||
".wm-hero-title{font-size:1.65rem;line-height:1.2;margin-bottom:.25em}"
|
||||
".wm-hero-sub{font-size:1rem;color:var(--wm-muted);font-weight:500;margin:0}"
|
||||
".wm-brand-logo{margin-bottom:12px;line-height:0}"
|
||||
".wm-brand-logo svg{display:block;max-width:160px;height:auto}"
|
||||
".wm-hero-intro{font-size:.95rem;color:var(--wm-muted);margin:.5em 0 0;line-height:1.45;max-width:42ch}"
|
||||
".wm-page-head{margin-bottom:16px}"
|
||||
".wm-page-head h1{font-size:1.45rem}"
|
||||
".wm-page-desc{font-size:.95rem;color:var(--wm-muted);margin:.35em 0 0}"
|
||||
/* Cards */
|
||||
".wm-card{background:var(--wm-surface);border:1px solid var(--wm-border);border-radius:var(--wm-radius);"
|
||||
"box-shadow:var(--wm-shadow);padding:18px 16px;margin-bottom:16px;text-align:left}"
|
||||
".wm-card-title{font-size:1rem;margin:0 0 12px;padding-bottom:10px;border-bottom:1px solid var(--wm-border);"
|
||||
"color:var(--wm-text)}"
|
||||
".wm-card-subtitle{margin:-4px 0 12px;font-size:.9rem;line-height:1.45;color:var(--wm-muted)}"
|
||||
".wm-lead{margin:0 0 8px;font-size:.95rem;color:var(--wm-muted);line-height:1.5}"
|
||||
".wm-home-summary{margin:0 0 14px;font-size:1rem;font-weight:600;line-height:1.45;color:var(--wm-text)}"
|
||||
/* Callout / status */
|
||||
".wm-callout{padding:12px 14px;border-radius:var(--wm-radius-sm);border:1px solid var(--wm-border);"
|
||||
"background:rgba(14,165,233,.06);margin-bottom:16px}"
|
||||
"body.invert .wm-callout{background:rgba(56,189,248,.08)}"
|
||||
".wm-callout--info{border-left:4px solid var(--wm-brand)}"
|
||||
".wm-info-summary{font-weight:600;color:var(--wm-text)}"
|
||||
".wm-status{margin:0 0 12px;padding:10px 12px;font-size:.92rem;font-weight:700;color:var(--wm-brand);"
|
||||
"text-align:center;border-radius:var(--wm-radius-sm);background:rgba(14,165,233,.08);border:1px solid rgba(14,165,233,.18)}"
|
||||
".wm-status--warning{color:#c2410c;background:rgba(249,115,22,.12);border-color:rgba(249,115,22,.28)}"
|
||||
".wm-status--caution{color:#a16207;background:rgba(234,179,8,.14);border-color:rgba(234,179,8,.3)}"
|
||||
".wm-status--danger{color:var(--wm-danger);background:rgba(220,38,38,.12);border-color:rgba(220,38,38,.28)}"
|
||||
"body.invert .wm-status{background:rgba(56,189,248,.12);border-color:rgba(56,189,248,.22)}"
|
||||
"body.invert .wm-status--warning{background:rgba(249,115,22,.18);border-color:rgba(249,115,22,.34)}"
|
||||
"body.invert .wm-status--caution{background:rgba(234,179,8,.18);border-color:rgba(234,179,8,.34)}"
|
||||
"body.invert .wm-status--danger{background:rgba(220,38,38,.18);border-color:rgba(220,38,38,.34)}"
|
||||
/* Form fields */
|
||||
".wm-field{margin-bottom:14px;text-align:left}"
|
||||
".wm-field:last-child{margin-bottom:0}"
|
||||
".wm-field label{display:block;font-size:.82rem;font-weight:600;color:var(--wm-muted);margin-bottom:6px}"
|
||||
"input[type=text],input[type=password],input[type=number],input[type=email],select{"
|
||||
"display:block;width:100%;padding:12px 14px;font-size:1rem;font-family:inherit;"
|
||||
"color:var(--wm-text);background:var(--wm-bg);border:1px solid var(--wm-border);border-radius:var(--wm-radius-sm);"
|
||||
"-webkit-appearance:none;appearance:none}"
|
||||
"input:focus,select:focus{outline:2px solid var(--wm-brand);outline-offset:0;border-color:var(--wm-brand)}"
|
||||
"input[type=radio],input[type=checkbox]{width:auto;margin-right:8px;accent-color:var(--wm-brand)}"
|
||||
".wm-checkbox-row{display:flex;align-items:center;gap:8px;margin:12px 0;font-size:.92rem}"
|
||||
".wm-checkbox-row label{display:inline;margin:0;font-weight:500;color:var(--wm-text)}"
|
||||
".wm-form-actions{margin-top:16px}"
|
||||
/* Buttons */
|
||||
".wm-btn{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 18px;"
|
||||
"font-size:1rem;font-weight:600;font-family:inherit;border-radius:var(--wm-radius-sm);border:0;cursor:pointer;"
|
||||
"transition:background .15s,transform .1s,opacity .15s}"
|
||||
".wm-btn:disabled{opacity:.5;cursor:not-allowed}"
|
||||
".wm-btn:active{transform:scale(.98)}"
|
||||
"a.wm-btn{box-sizing:border-box;text-decoration:none}"
|
||||
".wm-btn .wm-icon-label{justify-content:center}"
|
||||
".wm-btn--block{width:100%}"
|
||||
".wm-btn--primary{background:var(--wm-brand);color:#fff}"
|
||||
".wm-btn--primary:hover{background:var(--wm-brand-hover)}"
|
||||
".wm-btn--secondary{background:var(--wm-bg);color:var(--wm-text);border:1px solid var(--wm-border)}"
|
||||
".wm-btn--secondary:hover{border-color:var(--wm-brand);color:var(--wm-brand)}"
|
||||
".wm-btn--danger{background:var(--wm-danger);color:#fff}"
|
||||
".wm-btn--danger:hover{background:var(--wm-danger-hover)}"
|
||||
"input[type=file]{width:100%;padding:10px;border:1px dashed var(--wm-border);border-radius:var(--wm-radius-sm);"
|
||||
"background:var(--wm-bg);font-size:.9rem}"
|
||||
/* Legacy: unclassed submit in forms */
|
||||
"button[type=submit],.wm-form-actions .wm-btn--primary{background:var(--wm-brand);color:#fff}"
|
||||
"button[type=submit]:hover{background:var(--wm-brand-hover)}"
|
||||
/* Device actions / button stack */
|
||||
".wm-actions-stack{display:flex;flex-direction:column;gap:10px;margin-top:4px}"
|
||||
".wm-btn-group{display:flex;flex-direction:column;gap:10px}"
|
||||
/* WiFi scan list */
|
||||
".wm-scan-list{margin:0 0 12px;border:1px solid var(--wm-border);border-radius:var(--wm-radius-sm);"
|
||||
"overflow:hidden;background:var(--wm-bg)}"
|
||||
".wm-scan-row{display:flex;align-items:center;gap:10px;min-height:48px;padding:10px 12px;"
|
||||
"border-bottom:1px solid var(--wm-border);text-decoration:none;color:inherit;cursor:pointer}"
|
||||
".wm-scan-row:last-child{border-bottom:0}"
|
||||
".wm-scan-row:hover{background:rgba(14,165,233,.1)}"
|
||||
"body.invert .wm-scan-row:hover{background:rgba(56,189,248,.12)}"
|
||||
".wm-scan-ssid{flex:1;min-width:0;font-weight:600;font-size:.95rem;text-align:left;word-break:break-word}"
|
||||
".wm-scan-meta{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-shrink:0;min-width:78px}"
|
||||
".wm-scan-signal{display:block;flex-shrink:0;font-size:.8rem;color:var(--wm-muted);font-weight:600;min-width:38px;text-align:right}"
|
||||
".wm-scan-lock,.wm-scan-bars{display:block;flex:0 0 16px}"
|
||||
// quality icons
|
||||
".q{position:relative;height:16px;width:16px;margin:0;padding:0;flex-shrink:0}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px}.ql .q{float:left}.q:after,.q:before{content:'';position:absolute;left:0;top:0;width:16px;height:16px;display:block;background-repeat:no-repeat;background-position:16px 0;"
|
||||
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
|
||||
".wm-scan-lock:after,.wm-scan-bars:before{content:none}"
|
||||
"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
|
||||
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
|
||||
"background-size: 95px 16px;}}"
|
||||
// messages from firmware / server
|
||||
".msg{padding:16px;margin:12px 0;border-radius:var(--wm-radius-sm);border:1px solid var(--wm-border);"
|
||||
"border-left-width:4px;border-left-color:var(--wm-muted);background:var(--wm-surface);text-align:left}"
|
||||
".msg h4{margin:0 0 8px;font-size:1rem}"
|
||||
".msg.P{border-left-color:var(--wm-brand)}.msg.P h4{color:var(--wm-brand)}"
|
||||
".msg.D{border-left-color:var(--wm-danger)}.msg.D h4{color:var(--wm-danger)}"
|
||||
".msg.S{border-left-color:var(--wm-success)}.msg.S h4{color:var(--wm-success)}"
|
||||
"dt{font-weight:700;font-size:.85rem;color:var(--wm-muted)}dd{margin:2px 0 12px 0;padding:0;font-size:.95rem}"
|
||||
"td{vertical-align:top}"
|
||||
".h{display:none}"
|
||||
/* Info page key-value grid */
|
||||
".wm-kv{margin:0;padding:0}"
|
||||
".wm-kv dt{padding-top:10px}"
|
||||
".wm-kv dt:first-of-type{padding-top:0}"
|
||||
".wm-kv dd{margin-left:0;padding-bottom:4px;border-bottom:1px solid var(--wm-border)}"
|
||||
".wm-kv dd:last-of-type{border-bottom:0}"
|
||||
/* Feedback areas */
|
||||
"#wm-wifi-msg,#wm-param-msg{font-size:.9rem;margin-top:12px;min-height:1.2em;color:var(--wm-muted)}"
|
||||
"#wm-ota-log{font-size:.8rem;background:var(--wm-bg);padding:12px;border-radius:var(--wm-radius-sm);"
|
||||
"border:1px solid var(--wm-border);overflow:auto;max-height:160px}"
|
||||
// invert legacy body
|
||||
"body.invert{background-color:var(--wm-bg)}"
|
||||
"body.invert .msg{border-color:var(--wm-border)}"
|
||||
"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1)}"
|
||||
":disabled{opacity:.55}"
|
||||
// toast + modal
|
||||
".wm-toast{position:fixed;left:50%;top:12px;transform:translateX(-50%);z-index:10001;max-width:92%;padding:12px 16px;border-radius:var(--wm-radius-sm);"
|
||||
"box-shadow:var(--wm-shadow);font-size:.95rem;text-align:center;font-weight:600}"
|
||||
".wm-toast.ok{background:rgba(22,163,74,.15);border:1px solid var(--wm-success);color:var(--wm-text)}"
|
||||
".wm-toast.err{background:rgba(220,38,38,.12);border:1px solid var(--wm-danger);color:var(--wm-text)}"
|
||||
".wm-dialog{position:fixed;inset:0;z-index:10000;display:none;align-items:center;justify-content:center;padding:16px;box-sizing:border-box}"
|
||||
".wm-dialog-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.5)}"
|
||||
".wm-dialog-panel{position:relative;z-index:1;background:var(--wm-surface);border-radius:var(--wm-radius);padding:20px 18px;max-width:400px;width:100%;"
|
||||
"box-shadow:var(--wm-shadow);border:1px solid var(--wm-border);text-align:left}"
|
||||
".wm-dialog-msg{margin:0 0 16px;line-height:1.45;color:var(--wm-text)}"
|
||||
".wm-dialog-actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}"
|
||||
".wm-dialog-actions .wm-btn{min-width:108px}"
|
||||
".wm-dialog-ok,.wm-btn.wm-dialog-ok{background:var(--wm-brand)!important;color:#fff!important}"
|
||||
".wm-dialog-ok:hover{background:var(--wm-brand-hover)!important}"
|
||||
"body.invert .wm-toast.ok{background:rgba(22,163,74,.2)}"
|
||||
"body.invert .wm-toast.err{background:rgba(220,38,38,.2)}"
|
||||
"</style>";
|
||||
|
||||
#endif // _CSS_TEMPLATES_H_
|
||||
@@ -0,0 +1,800 @@
|
||||
/**
|
||||
* PortalAppJS.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Embedded SPA for WiFiManager config portal (hash routing + JSON APIs).
|
||||
*/
|
||||
|
||||
#ifndef _WM_PORTAL_APP_JS_H_
|
||||
#define _WM_PORTAL_APP_JS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char PORTAL_APP_JS[] PROGMEM = R"rawliteral(
|
||||
(function(){
|
||||
'use strict';
|
||||
var boot={};
|
||||
try{var el=document.getElementById('wm-bootstrap');if(el&&el.textContent)boot=JSON.parse(el.textContent);}catch(e){boot={};}
|
||||
|
||||
// --- Core / DOM ---
|
||||
function $(id){return document.getElementById(id);}
|
||||
function esc(t){if(!t)return'';return String(t).replace(/[&<>"']/g,function(c){return({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);});}
|
||||
|
||||
// --- Toast / dialog ---
|
||||
var _wmToastTimer=null;
|
||||
var _wmPortalTimeoutTimer=null;
|
||||
function showToast(msg,isErr){
|
||||
var el=$('wm-toast');
|
||||
if(!el)return;
|
||||
el.textContent=msg||'';
|
||||
el.className='wm-toast '+(isErr?'err':'ok');
|
||||
el.style.display='block';
|
||||
if(_wmToastTimer)clearTimeout(_wmToastTimer);
|
||||
_wmToastTimer=setTimeout(function(){el.style.display='none';el.textContent='';},4500);
|
||||
}
|
||||
function showConfirm(msg,onOk){
|
||||
var dlg=$('wm-dialog'),dmsg=$('wm-dialog-msg'),bk=$('wm-dialog-backdrop'),ok=$('wm-dialog-ok'),cancel=$('wm-dialog-cancel');
|
||||
if(!dlg||!dmsg||!ok||!cancel){if(onOk)onOk();return;}
|
||||
dmsg.textContent=msg||'';
|
||||
dlg.style.display='flex';
|
||||
dlg.setAttribute('aria-hidden','false');
|
||||
function hide(){
|
||||
dlg.style.display='none';
|
||||
dlg.setAttribute('aria-hidden','true');
|
||||
ok.onclick=null;
|
||||
cancel.onclick=null;
|
||||
if(bk)bk.onclick=null;
|
||||
}
|
||||
function onCancel(){hide();}
|
||||
cancel.onclick=onCancel;
|
||||
if(bk)bk.onclick=onCancel;
|
||||
ok.onclick=function(){hide();if(onOk)onOk();};
|
||||
}
|
||||
|
||||
function bindPortalChromeEvents(){
|
||||
var back=$('wm-nav-back');
|
||||
if(back){
|
||||
back.addEventListener('click', function(ev){ ev.preventDefault(); history.back(); });
|
||||
}
|
||||
var r=$('wm-restart-btn'), e=$('wm-exit-btn'), c=$('wm-close-captive-btn'), er=$('wm-erase-btn');
|
||||
if(r && window.portalRestart) r.addEventListener('click', window.portalRestart);
|
||||
if(e && window.portalExit) e.addEventListener('click', window.portalExit);
|
||||
if(c && window.portalCloseCaptive) c.addEventListener('click', window.portalCloseCaptive);
|
||||
if(er && window.portalErase) er.addEventListener('click', window.portalErase);
|
||||
}
|
||||
|
||||
function bindFormSubmitHandlers(){
|
||||
var wf=$('wm-wifi-form'), pf=$('wm-param-form'), of=$('wm-ota-form');
|
||||
if(wf && window.portalWifiSave) wf.addEventListener('submit', window.portalWifiSave);
|
||||
if(pf && window.portalParamSave) pf.addEventListener('submit', window.portalParamSave);
|
||||
if(of && window.portalOtaSubmit) of.addEventListener('submit', window.portalOtaSubmit);
|
||||
}
|
||||
|
||||
// --- View shell ---
|
||||
function stopPortalTimeoutCountdown(){
|
||||
if(_wmPortalTimeoutTimer){
|
||||
clearInterval(_wmPortalTimeoutTimer);
|
||||
_wmPortalTimeoutTimer=null;
|
||||
}
|
||||
}
|
||||
|
||||
function setView(html){
|
||||
var a=$('app');
|
||||
if(!a)return;
|
||||
stopPortalTimeoutCountdown();
|
||||
stopWifiScanPolling();
|
||||
stopWifiConnectPolling();
|
||||
a.innerHTML="<div class='wm-layout'>"+html+"</div>";
|
||||
bindPortalChromeEvents();
|
||||
bindFormSubmitHandlers();
|
||||
}
|
||||
|
||||
function formatDurationSeconds(totalSeconds){
|
||||
totalSeconds=Math.max(0,Math.floor(totalSeconds||0));
|
||||
var hours=Math.floor(totalSeconds/3600);
|
||||
var minutes=Math.floor((totalSeconds%3600)/60);
|
||||
var seconds=totalSeconds%60;
|
||||
if(hours>0){
|
||||
return hours+":"+String(minutes).padStart(2,'0')+":"+String(seconds).padStart(2,'0');
|
||||
}
|
||||
return minutes+":"+String(seconds).padStart(2,'0');
|
||||
}
|
||||
|
||||
function startPortalTimeoutCountdown(initialSeconds){
|
||||
var el=$('wm-portal-timeout');
|
||||
var remaining=Math.max(0,Math.floor(initialSeconds||0));
|
||||
if(!el||remaining<=0)return;
|
||||
function levelClass(seconds){
|
||||
if(seconds<10)return'wm-status--danger';
|
||||
if(seconds<30)return'wm-status--caution';
|
||||
if(seconds<60)return'wm-status--warning';
|
||||
return'';
|
||||
}
|
||||
function render(){
|
||||
if(!el)return;
|
||||
el.className='wm-status'+(levelClass(remaining)?' '+levelClass(remaining):'');
|
||||
el.textContent='Captive portal timeout: '+formatDurationSeconds(remaining);
|
||||
}
|
||||
render();
|
||||
_wmPortalTimeoutTimer=setInterval(function(){
|
||||
remaining=Math.max(0,remaining-1);
|
||||
render();
|
||||
if(remaining<=0)stopPortalTimeoutCountdown();
|
||||
},1000);
|
||||
}
|
||||
|
||||
// --- Field render (WiFi / params meta) ---
|
||||
function renderStandardField(f){
|
||||
var id=f.id||f.name||'';
|
||||
var nm=f.name||f.id||'';
|
||||
var domId=(id==='s')?'wm-s':(id==='p')?'wm-p':('wm-f-'+id);
|
||||
var t=f.type||'text';
|
||||
var h='<div class="wm-field">';
|
||||
h+='<label for="'+domId+'">'+esc(f.label||'')+'</label>';
|
||||
h+='<input id="'+domId+'" name="'+esc(nm)+'" type="'+esc(t)+'"';
|
||||
if(f.maxlength)h+=' maxlength="'+f.maxlength+'"';
|
||||
if(f.value!==undefined&&f.value!==null)h+=' value="'+esc(f.value)+'"';
|
||||
if(f.placeholder)h+=' placeholder="'+esc(f.placeholder)+'"';
|
||||
if(f.customAttrs)h+=' '+f.customAttrs;
|
||||
h+='/>';
|
||||
h+='</div>';
|
||||
return h;
|
||||
}
|
||||
|
||||
function renderMetaField(f){
|
||||
if(!f)return'';
|
||||
if(f.kind==='html')return f.html||'';
|
||||
if(f.kind==='field' || !f.kind)return renderStandardField(f);
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderFieldList(arr){
|
||||
if(!arr||!arr.length)return'';
|
||||
var h='',i;
|
||||
for(i=0;i<arr.length;i++)h+=renderMetaField(arr[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
function iconSvg(name){
|
||||
var body='';
|
||||
switch(name){
|
||||
case 'home':
|
||||
body="<path d='M3 10.5 12 3l9 7.5'/><path d='M5 9.5V20h14V9.5'/>";
|
||||
break;
|
||||
case 'wifi':
|
||||
body="<path d='M4.5 9.5a12 12 0 0 1 15 0'/><path d='M7.5 12.5a7.5 7.5 0 0 1 9 0'/><path d='M10.5 15.5a3.5 3.5 0 0 1 3 0'/><circle cx='12' cy='19' r='1.2' fill='currentColor' stroke='none'/>";
|
||||
break;
|
||||
case 'setup':
|
||||
body="<line x1='5' y1='6' x2='19' y2='6'/><circle cx='9' cy='6' r='2'/><line x1='5' y1='12' x2='19' y2='12'/><circle cx='15' cy='12' r='2'/><line x1='5' y1='18' x2='19' y2='18'/><circle cx='11' cy='18' r='2'/>";
|
||||
break;
|
||||
case 'info':
|
||||
body="<circle cx='12' cy='12' r='9'/><line x1='12' y1='10.5' x2='12' y2='16.5'/><circle cx='12' cy='7.2' r='1' fill='currentColor' stroke='none'/>";
|
||||
break;
|
||||
case 'update':
|
||||
body="<path d='M12 4v10'/><path d='m8.5 10.5 3.5 3.5 3.5-3.5'/><path d='M5 19h14'/>";
|
||||
break;
|
||||
case 'back':
|
||||
body="<path d='M10 6 4 12l6 6'/><path d='M4 12h16'/>";
|
||||
break;
|
||||
case 'restart':
|
||||
body="<path d='M20 12a8 8 0 1 1-2.3-5.7'/><path d='M20 4v5h-5'/>";
|
||||
break;
|
||||
case 'exit':
|
||||
body="<path d='M10 6H6v12h4'/><path d='M14 8l4 4-4 4'/><path d='M9 12h9'/>";
|
||||
break;
|
||||
case 'close':
|
||||
body="<path d='M4 12a8 8 0 1 0 16 0A8 8 0 1 0 4 12'/><path d='m8.5 8.5 7 7'/>";
|
||||
break;
|
||||
case 'erase':
|
||||
body="<path d='M5 7h14'/><path d='M9 7V5h6v2'/><path d='M8 7l1 12h6l1-12'/><path d='M10 10v6'/><path d='M14 10v6'/>";
|
||||
break;
|
||||
case 'status':
|
||||
body="<circle cx='12' cy='12' r='9'/><path d='M12 7v5l3 2'/>";
|
||||
break;
|
||||
case 'device':
|
||||
body="<rect x='7' y='7' width='10' height='10' rx='1.5'/><path d='M9 1v4M15 1v4M9 19v4M15 19v4M1 9h4M1 15h4M19 9h4M19 15h4'/>";
|
||||
break;
|
||||
case 'diagnostics':
|
||||
body="<path d='M4 17h3l2-5 3 3 2-7 2 9h4'/>";
|
||||
break;
|
||||
case 'about':
|
||||
body="<circle cx='12' cy='12' r='9'/><path d='M12 10.5v5.5'/><circle cx='12' cy='7.2' r='1' fill='currentColor' stroke='none'/>";
|
||||
break;
|
||||
case 'actions':
|
||||
body="<path d='M12 3v18'/><path d='M3 12h18'/><path d='m6.5 6.5 11 11'/><path d='m17.5 6.5-11 11'/>";
|
||||
break;
|
||||
case 'scan':
|
||||
body="<path d='M4 7h16'/><path d='M4 12h10'/><path d='M4 17h7'/><path d='m16 15 4 4'/><circle cx='14' cy='13' r='4'/>";
|
||||
break;
|
||||
case 'save':
|
||||
body="<path d='M5 4h11l3 3v13H5z'/><path d='M8 4v5h8'/><path d='M9 19v-5h6v5'/>";
|
||||
break;
|
||||
case 'connect':
|
||||
body="<path d='M4.5 12a7.5 7.5 0 0 1 7.5-7.5'/><path d='m17 6 2 2-2 2'/><path d='M12 4.5H19'/>";
|
||||
break;
|
||||
case 'upload':
|
||||
body="<path d='M12 20V8'/><path d='m8.5 11.5 3.5-3.5 3.5 3.5'/><path d='M5 20h14'/>";
|
||||
break;
|
||||
}
|
||||
if(!body)return'';
|
||||
return "<span class='wm-icon wm-icon--"+name+"' aria-hidden='true'><svg viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'>"+body+"</svg></span>";
|
||||
}
|
||||
|
||||
function labelWithIcon(name,label){
|
||||
return "<span class='wm-icon-label'>"+iconSvg(name)+"<span>"+esc(label||'')+"</span></span>";
|
||||
}
|
||||
|
||||
function cardTitleHtml(name,title){
|
||||
return "<h2 class='wm-card-title'>"+labelWithIcon(name,title)+"</h2>";
|
||||
}
|
||||
|
||||
function sectionCardHtml(name,title,subtitle,rows){
|
||||
if(!rows||!rows.length)return'';
|
||||
var h="<div class='wm-card'>"+cardTitleHtml(name,title);
|
||||
if(subtitle)h+="<p class='wm-card-subtitle'>"+esc(subtitle)+"</p>";
|
||||
h+="<dl class='wm-kv'>";
|
||||
for(var i=0;i<rows.length;i++){
|
||||
h+="<dt>"+esc(rows[i].label)+"</dt><dd>"+rows[i].value+"</dd>";
|
||||
}
|
||||
h+="</dl></div>";
|
||||
return h;
|
||||
}
|
||||
|
||||
function rowsWithoutKeys(rows,blocked){
|
||||
if(!rows||!rows.length)return[];
|
||||
var out=[],i,row,key;
|
||||
for(i=0;i<rows.length;i++){
|
||||
row=rows[i];
|
||||
if(!row)continue;
|
||||
key=row.key||'';
|
||||
if(blocked.indexOf(key)!==-1)continue;
|
||||
out.push(row);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function navBar(active){
|
||||
active=active||'home';
|
||||
var pages=boot.pages||{};
|
||||
var layout=boot.layout||{};
|
||||
var actions=boot.actions||{};
|
||||
var pinWifi=(layout.paramsLocation||'wifi')==='wifi';
|
||||
var ps=pages.setup||{};
|
||||
var pi=pages.info||{};
|
||||
var pu=pages.update||{};
|
||||
var h="<nav class='wm-nav' aria-label='Configuration'>";
|
||||
h+="<a class='wm-nav-link"+(active==='home'?' wm-nav-link--active':'')+"' href='#/'>"+labelWithIcon('home','Home')+"</a>";
|
||||
h+="<a class='wm-nav-link"+(active==='wifi'?' wm-nav-link--active':'')+"' href='#/wifi'>"+labelWithIcon('wifi','WiFi')+"</a>";
|
||||
if(!pinWifi && ps.visible!==false){h+="<a class='wm-nav-link"+(active==='setup'?' wm-nav-link--active':'')+"' href='#/setup'>"+labelWithIcon('setup','Setup')+"</a>";}
|
||||
if(pi.visible!==false){h+="<a class='wm-nav-link"+(active==='info'?' wm-nav-link--active':'')+"' href='#/info'>"+labelWithIcon('info','Info')+"</a>";}
|
||||
if(pu.visible){h+="<a class='wm-nav-link"+(active==='update'?' wm-nav-link--active':'')+"' href='#/update'>"+labelWithIcon('update','Update')+"</a>";}
|
||||
if(actions.back&&actions.back.visible){h+="<a href='#' class='wm-nav-link' id='wm-nav-back'>"+labelWithIcon('back','Back')+"</a>";}
|
||||
h+="</nav>";
|
||||
return h;
|
||||
}
|
||||
|
||||
function deviceActionsHtml(){
|
||||
var actions=boot.actions||{};
|
||||
var h='', ok=false;
|
||||
if(actions.restart&&actions.restart.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-restart-btn'>"+labelWithIcon('restart','Restart device')+"</button>";ok=true;}
|
||||
if(actions.exitPortal&&actions.exitPortal.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-exit-btn'>"+labelWithIcon('exit','Exit portal')+"</button>";ok=true;}
|
||||
if(actions.closeCaptive&&actions.closeCaptive.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-close-captive-btn'>"+labelWithIcon('close','Stop captive portal')+"</button>";ok=true;}
|
||||
if(!ok)return'';
|
||||
return "<div class='wm-card'>"+cardTitleHtml('device','Device')+"<p class='wm-card-subtitle'>Quick device-level actions while the portal is open.</p><div class='wm-btn-group'>"+h+"</div></div>";
|
||||
}
|
||||
|
||||
function renderExtraHomeCards(cards){
|
||||
if(!cards||!cards.length)return'';
|
||||
var out='',i,c,j,k,cls;
|
||||
for(i=0;i<cards.length;i++){
|
||||
c=cards[i];
|
||||
if(!c)continue;
|
||||
out+="<div class='wm-card'>";
|
||||
if(c.title){out+="<h2 class='wm-card-title'>"+esc(c.title)+"</h2>";}
|
||||
if(c.kind==='text'||c.kind==='callout'){
|
||||
cls=c.kind==='callout'?'wm-callout wm-callout--info':'wm-lead';
|
||||
out+="<p class='"+cls+"'>"+esc(c.text||'')+"</p>";
|
||||
}else{
|
||||
out+="<dl class='wm-kv'>";
|
||||
if(c.items){for(j=0;j<c.items.length;j++){k=c.items[j];out+="<dt>"+esc(k.label)+"</dt><dd>"+esc(k.value)+"</dd>";}}
|
||||
out+="</dl>";
|
||||
}
|
||||
out+="</div>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function viewHome(){
|
||||
var brand=boot.brand||{};
|
||||
var ctx=boot.context||{};
|
||||
var t=esc(brand.title||'WiFiManager');
|
||||
var ident=ctx.identityText?esc(ctx.identityText):'';
|
||||
var summary=ctx.statusSummary||'';
|
||||
var timeoutSeconds=ctx.portalTimeoutSecondsRemaining||0;
|
||||
var html=navBar('home');
|
||||
html+="<header class='wm-hero'>";
|
||||
if(brand.logoSvg){html+="<div class='wm-brand-logo'>"+brand.logoSvg+"</div>";}
|
||||
html+="<h1 class='wm-hero-title'>"+t+"</h1>";
|
||||
if(ident){html+="<p class='wm-hero-sub'>"+ident+"</p>";}
|
||||
if(brand.homeIntro){html+="<p class='wm-hero-intro'>"+esc(brand.homeIntro)+"</p>";}
|
||||
html+="</header>";
|
||||
html+="<div class='wm-card'>"+cardTitleHtml('status','Status');
|
||||
if(summary){html+="<p class='wm-home-summary'>"+esc(summary)+"</p>";}
|
||||
if(timeoutSeconds>0){html+="<p class='wm-status' id='wm-portal-timeout'></p>";}
|
||||
html+="<p class='wm-lead'>Use <strong>WiFi</strong> to scan networks and connect. Open <strong>Info</strong> for addresses and firmware actions.</p>";
|
||||
html+="</div>";
|
||||
html+=renderExtraHomeCards(boot.extraHomeCards||[]);
|
||||
html+=deviceActionsHtml();
|
||||
setView(html);
|
||||
if(timeoutSeconds>0)startPortalTimeoutCountdown(timeoutSeconds);
|
||||
}
|
||||
|
||||
// --- HTTP ---
|
||||
function api(path, opt){
|
||||
opt=opt||{};
|
||||
return fetch(path,{method:opt.method||'GET',headers:opt.headers,body:opt.body,credentials:'same-origin'})
|
||||
.then(function(r){return r.text().then(function(t){return {ok:r.ok,status:r.status,body:t};});});
|
||||
}
|
||||
|
||||
// --- WiFi scan view ---
|
||||
var _wmWifiScanPollTimer=null;
|
||||
var _wmWifiScanPollToken=0;
|
||||
|
||||
function getScanResultsBox(){
|
||||
return $('wm-scan-results');
|
||||
}
|
||||
|
||||
function setScanRefreshEnabled(enabled){
|
||||
var btn=$('wm-refresh-scan');
|
||||
if(btn)btn.disabled=(enabled===false);
|
||||
}
|
||||
|
||||
function stopWifiScanPolling(){
|
||||
_wmWifiScanPollToken++;
|
||||
if(_wmWifiScanPollTimer){
|
||||
clearTimeout(_wmWifiScanPollTimer);
|
||||
_wmWifiScanPollTimer=null;
|
||||
}
|
||||
setScanRefreshEnabled(true);
|
||||
}
|
||||
|
||||
function startWifiScanPolling(box){
|
||||
stopWifiScanPolling();
|
||||
setScanRefreshEnabled(false);
|
||||
var token=++_wmWifiScanPollToken;
|
||||
function poll(){
|
||||
api('/api/wifi/scan-status').then(function(res){
|
||||
if(token!==_wmWifiScanPollToken)return;
|
||||
var d={};
|
||||
try{d=JSON.parse(res.body);}catch(e){return;}
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanList(d);
|
||||
if(!d.scanning){
|
||||
stopWifiScanPolling();
|
||||
return;
|
||||
}
|
||||
_wmWifiScanPollTimer=setTimeout(poll,800);
|
||||
}).catch(function(){
|
||||
if(token!==_wmWifiScanPollToken)return;
|
||||
stopWifiScanPolling();
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanError('Portal became unreachable while scanning.');
|
||||
});
|
||||
}
|
||||
poll();
|
||||
}
|
||||
|
||||
function renderScanList(data){
|
||||
if(!data||data.scanning){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>Scanning for networks…</p></div>";
|
||||
}
|
||||
if(data.error==='timeout'){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>Scan timed out. Try Scan again.</p></div>";
|
||||
}
|
||||
if(data.error==='failed'){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>Scan failed.</p></div>";
|
||||
}
|
||||
if(!data.networks||data.count===0){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>No networks found.</p></div>";
|
||||
}
|
||||
var html="<div class='wm-scan-list'>",i,n,pct,qi,enc;
|
||||
for(i=0;i<data.networks.length;i++){
|
||||
n=data.networks[i];
|
||||
pct=(n.quality||0)+'%';
|
||||
qi=Math.round((n.quality/100)*3)+1;if(qi<1)qi=1;if(qi>4)qi=4;
|
||||
html+="<a class='wm-scan-row' href='#p' data-ssid='"+esc(n.ssid)+"'>";
|
||||
html+="<span class='wm-scan-ssid'>"+esc(n.ssid)+"</span>";
|
||||
html+="<span class='wm-scan-meta'>";
|
||||
if(n.encrypted){html+="<span class='wm-scan-lock q l' aria-hidden='true'></span>";}
|
||||
html+="<span role='img' aria-label='"+pct+"' title='"+pct+"' class='wm-scan-bars q q-"+qi+"'></span>";
|
||||
html+="<span class='wm-scan-signal'>"+pct+"</span>";
|
||||
html+="</span>";
|
||||
html+="</a>";
|
||||
}
|
||||
html+="</div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderScanError(msg){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>"+esc(msg||'Failed to reach portal while scanning.')+"</p></div>";
|
||||
}
|
||||
|
||||
function shouldAutoStartWifiScan(data){
|
||||
if(!data)return true;
|
||||
if(data.scanning)return false;
|
||||
if((data.lastscan||0)>0)return false;
|
||||
if(data.results_valid)return false;
|
||||
if(data.error==='timeout' || data.error==='failed')return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function wifiRefresh(){
|
||||
var box=getScanResultsBox();
|
||||
if(!box)return;
|
||||
setScanRefreshEnabled(false);
|
||||
box.innerHTML="<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>Starting scan…</p></div>";
|
||||
api('/api/wifi/scan',{method:'POST'}).then(function(res){
|
||||
box=getScanResultsBox()||box;
|
||||
if(!res.ok){
|
||||
setScanRefreshEnabled(true);
|
||||
if(box)box.innerHTML=renderScanError('Failed to start WiFi scan.');
|
||||
showToast('Failed to start WiFi scan.',true);
|
||||
return;
|
||||
}
|
||||
startWifiScanPolling(box);
|
||||
}).catch(function(){
|
||||
stopWifiScanPolling();
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanError('Failed to reach portal while starting scan.');
|
||||
showToast('Failed to reach portal while starting scan.',true);
|
||||
});
|
||||
}
|
||||
|
||||
function bindWifiViewEvents(){
|
||||
var refreshBtn=$('wm-refresh-scan');
|
||||
if(refreshBtn){
|
||||
refreshBtn.addEventListener('click', wifiRefresh);
|
||||
}
|
||||
|
||||
var showPass=$('wm-showpass');
|
||||
if(showPass){
|
||||
showPass.addEventListener('change', function(){
|
||||
var p=$('wm-p');
|
||||
if(p)p.type=this.checked?'text':'password';
|
||||
});
|
||||
}
|
||||
|
||||
var scanResults=$('wm-scan-results');
|
||||
if(scanResults){
|
||||
scanResults.addEventListener('click', function(ev){
|
||||
var el=ev.target;
|
||||
while(el && el !== scanResults){
|
||||
if(el.tagName==='A' && el.getAttribute('data-ssid')){
|
||||
ev.preventDefault();
|
||||
var inp=$('wm-s');
|
||||
var pass=$('wm-p');
|
||||
if(inp)inp.value=el.getAttribute('data-ssid')||'';
|
||||
if(pass){
|
||||
if(pass.scrollIntoView)pass.scrollIntoView({behavior:'smooth',block:'center'});
|
||||
if(pass.focus)pass.focus();
|
||||
}else if(inp){
|
||||
if(inp.scrollIntoView)inp.scrollIntoView({behavior:'smooth',block:'center'});
|
||||
if(inp.focus)inp.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
el=el.parentElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function viewWifi(){
|
||||
setView(navBar('wifi')+"<div class='wm-card'><p class='wm-lead'>Loading WiFi options…</p></div>");
|
||||
api('/api/wifi/meta').then(function(res){
|
||||
try{var m=JSON.parse(res.body);}catch(e){m={};}
|
||||
var html=navBar('wifi');
|
||||
html+="<div class='wm-page-head'><h1>WiFi</h1><p class='wm-page-desc'>Pick a network below, confirm SSID and password, then connect.</p></div>";
|
||||
html+="<div class='wm-card'>"+cardTitleHtml('scan','Networks nearby');
|
||||
html+="<div id='wm-scan-results'></div>";
|
||||
html+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-refresh-scan'>"+labelWithIcon('scan','Scan again')+"</button></div>";
|
||||
html+="<div class='wm-card'>"+cardTitleHtml('wifi','Network details');
|
||||
html+="<form id='wm-wifi-form'>";
|
||||
html+=renderFieldList(m.wifiFields||[]);
|
||||
var wf=m.wifiFields||[];
|
||||
var hasPass=false;
|
||||
var i;
|
||||
for(i=0;i<wf.length;i++){if(wf[i]&&wf[i].id==='p'){hasPass=true;break;}}
|
||||
if(hasPass){
|
||||
html+="<div class='wm-checkbox-row'><input type='checkbox' id='wm-showpass'/> <label for='wm-showpass'>Show password</label></div>";
|
||||
}
|
||||
html+=renderFieldList(m.staticFields||[]);
|
||||
html+=renderFieldList(m.params||[]);
|
||||
html+="<div class='wm-form-actions'><button type='submit' class='wm-btn wm-btn--primary wm-btn--block'>"+labelWithIcon('connect','Save and connect')+"</button></div></form>";
|
||||
html+="<div id='wm-wifi-msg'></div></div>";
|
||||
setView(html);
|
||||
bindWifiViewEvents();
|
||||
setScanRefreshEnabled(true);
|
||||
api('/api/wifi/scan-status').then(function(r2){
|
||||
try{var d=JSON.parse(r2.body);}catch(e){d={};}
|
||||
var box=getScanResultsBox();
|
||||
if(shouldAutoStartWifiScan(d)){
|
||||
wifiRefresh();
|
||||
return;
|
||||
}
|
||||
if(box)box.innerHTML=renderScanList(d);
|
||||
if(d&&d.scanning){startWifiScanPolling(box);}
|
||||
}).catch(function(){
|
||||
var box=getScanResultsBox();
|
||||
if(box)box.innerHTML=renderScanError('Failed to reach portal while loading scan status.');
|
||||
setScanRefreshEnabled(true);
|
||||
});
|
||||
}).catch(function(){
|
||||
setView(navBar('wifi')+"<div class='wm-card'><p class='wm-lead'>Failed to load WiFi options. Reconnect to the portal and try again.</p></div>");
|
||||
});
|
||||
}
|
||||
|
||||
// --- WiFi connect polling ---
|
||||
var _wmWifiConnectPollTimer=null;
|
||||
var _wmWifiConnectPollToken=0;
|
||||
var _wmWifiConnectPending=false;
|
||||
|
||||
function stopWifiConnectPolling(){
|
||||
_wmWifiConnectPollToken++;
|
||||
if(_wmWifiConnectPollTimer){
|
||||
clearTimeout(_wmWifiConnectPollTimer);
|
||||
_wmWifiConnectPollTimer=null;
|
||||
}
|
||||
}
|
||||
|
||||
function pollWifiConnectStatus(){
|
||||
stopWifiConnectPolling();
|
||||
var token=++_wmWifiConnectPollToken;
|
||||
function poll(){
|
||||
api('/api/wifi/connect-status').then(function(res){
|
||||
if(token!==_wmWifiConnectPollToken)return;
|
||||
var j={};
|
||||
var msg=$('wm-wifi-msg');
|
||||
try{j=JSON.parse(res.body);}catch(e){}
|
||||
if(msg&&j.message){
|
||||
msg.innerHTML=esc(j.message);
|
||||
}
|
||||
if(j.state==='success'){
|
||||
stopWifiConnectPolling();
|
||||
_wmWifiConnectPending=false;
|
||||
if(msg && j.stationIp){
|
||||
msg.innerHTML="Connected to WiFi. Redirecting to <a href='"+esc(j.redirectUrl||("http://"+j.stationIp+"/"))+"'>"+esc(j.stationIp)+"</a>…";
|
||||
}
|
||||
showToast(j.message||'WiFi connected',false);
|
||||
if(j.redirectUrl){
|
||||
setTimeout(function(){ window.location.href=j.redirectUrl; }, 1400);
|
||||
}else{
|
||||
location.hash='#/';
|
||||
}
|
||||
}else if(j.state==='failed'){
|
||||
stopWifiConnectPolling();
|
||||
_wmWifiConnectPending=false;
|
||||
showToast(j.message||'WiFi connect failed',true);
|
||||
}else{
|
||||
_wmWifiConnectPollTimer=setTimeout(poll,700);
|
||||
}
|
||||
}).catch(function(){
|
||||
if(token!==_wmWifiConnectPollToken)return;
|
||||
stopWifiConnectPolling();
|
||||
var msg=$('wm-wifi-msg');
|
||||
if(msg){
|
||||
msg.innerHTML=_wmWifiConnectPending
|
||||
? 'Portal became unreachable after saving. If the device joined your WiFi network, reconnect to it there.'
|
||||
: 'Portal became unreachable while checking connection status.';
|
||||
}
|
||||
_wmWifiConnectPending=false;
|
||||
});
|
||||
}
|
||||
poll();
|
||||
}
|
||||
|
||||
window.portalWifiSave=function(ev){
|
||||
ev.preventDefault();
|
||||
var fd=new FormData(document.getElementById('wm-wifi-form'));
|
||||
var msg=$('wm-wifi-msg');
|
||||
if(msg)msg.innerHTML='Submitting...';
|
||||
api('/api/wifi/save',{method:'POST',body:fd}).then(function(res){
|
||||
var j={};
|
||||
try{j=JSON.parse(res.body);}catch(e){}
|
||||
if(msg){
|
||||
msg.innerHTML=(j&&j.message)?esc(j.message):(res.ok?'Queued.':'Error');
|
||||
}
|
||||
if(res.status===202){
|
||||
_wmWifiConnectPending=true;
|
||||
pollWifiConnectStatus();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
// --- Info view ---
|
||||
function viewInfo(){
|
||||
setView(navBar('info')+"<div class='wm-card'><p class='wm-lead'>Loading device info…</p></div>");
|
||||
api('/api/info').then(function(res){
|
||||
try{var d=JSON.parse(res.body);}catch(e){d={};}
|
||||
var html=navBar('info');
|
||||
html+="<div class='wm-page-head'><h1>Device info</h1><p class='wm-page-desc'>Connection status, hardware, and actions.</p></div>";
|
||||
var st=d.status;
|
||||
if(st && typeof st==='object' && !Array.isArray(st)){
|
||||
html+="<div class='wm-card'>"+cardTitleHtml('status','Status');
|
||||
if(st.summary){html+="<p class='wm-callout wm-callout--info wm-info-summary'>"+esc(st.summary)+"</p>";}
|
||||
html+="<dl class='wm-kv'>";
|
||||
html+="<dt>Connected</dt><dd>"+esc(st.connected?'Yes':'No')+"</dd>";
|
||||
if(st.ssid!==undefined && st.ssid!=='')html+="<dt>SSID</dt><dd>"+esc(st.ssid)+"</dd>";
|
||||
if(st.stationIp)html+="<dt>Station IP</dt><dd>"+esc(st.stationIp)+"</dd>";
|
||||
if(st.apIp)html+="<dt>Portal IP</dt><dd>"+esc(st.apIp)+"</dd>";
|
||||
html+="</dl></div>";
|
||||
} else {
|
||||
html+=sectionCardHtml('status','Status','Connection summary and main addresses.',d.status||[]);
|
||||
}
|
||||
html+=sectionCardHtml('device','Device','Runtime, memory, and hardware details.',d.device||[]);
|
||||
html+=sectionCardHtml('diagnostics','WiFi details','Network diagnostics and interface metadata.',rowsWithoutKeys(d.wifi||[],['conx','stassid','staip','apip']));
|
||||
html+=sectionCardHtml('about','About','Library, SDK, and build information.',d.about||[]);
|
||||
if(d.extraSections){
|
||||
var xi,zs,zrows,zi,it;
|
||||
for(xi=0;xi<d.extraSections.length;xi++){
|
||||
zs=d.extraSections[xi];
|
||||
zrows=[];
|
||||
if(zs.items){for(zi=0;zi<zs.items.length;zi++){it=zs.items[zi];zrows.push({label:it.label,value:esc(it.value)});}}
|
||||
html+=sectionCardHtml('info',zs.title||'Details','',zrows);
|
||||
}
|
||||
}
|
||||
var act=d.actions||{};
|
||||
var actionsHtml='', hasActions=false;
|
||||
if(act.showRestart){actionsHtml+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-restart-btn'>"+labelWithIcon('restart','Restart device')+"</button>";hasActions=true;}
|
||||
if(act.showExitPortal){actionsHtml+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-exit-btn'>"+labelWithIcon('exit','Exit portal')+"</button>";hasActions=true;}
|
||||
if(act.showCloseCaptive){actionsHtml+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-close-captive-btn'>"+labelWithIcon('close','Stop captive portal')+"</button>";hasActions=true;}
|
||||
if(act.showUpdate){actionsHtml+="<a class='wm-btn wm-btn--secondary wm-btn--block' href='#/update'>"+labelWithIcon('update','Firmware update')+"</a>";hasActions=true;}
|
||||
if(act.showErase){actionsHtml+="<button type='button' class='wm-btn wm-btn--danger wm-btn--block' id='wm-erase-btn'>"+labelWithIcon('erase','Erase WiFi config')+"</button>";hasActions=true;}
|
||||
if(hasActions){
|
||||
html+="<div class='wm-card'>"+cardTitleHtml('actions','Actions')+"<p class='wm-card-subtitle'>Maintenance and recovery actions for this device.</p><div class='wm-btn-group'>"+actionsHtml+"</div></div>";
|
||||
}
|
||||
setView(html);
|
||||
});
|
||||
}
|
||||
|
||||
function portalConfirmPost(url, promptMsg){
|
||||
showConfirm(promptMsg||'Continue?',function(){
|
||||
api(url,{method:'POST'}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
var m=(j&&j.message)?j.message:(res.ok?'OK':'Error');
|
||||
showToast(m,!res.ok);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.portalRestart=function(){portalConfirmPost('/api/device/restart','Restart the device?');};
|
||||
window.portalExit=function(){portalConfirmPost('/api/portal/exit','Exit the configuration portal?');};
|
||||
window.portalCloseCaptive=function(){portalConfirmPost('/api/portal/close','Stop redirecting clients to this portal (captive detection off)?');};
|
||||
|
||||
window.portalErase=function(){
|
||||
showConfirm('Erase WiFi configuration?',function(){
|
||||
api('/api/device/erase',{method:'POST'}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
showToast((j&&j.message)?j.message:(res.ok?'Done':'Error'),!res.ok);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// --- Params / OTA ---
|
||||
function viewSetup(){
|
||||
setView(navBar('setup')+"<div class='wm-card'><p class='wm-lead'>Loading parameters…</p></div>");
|
||||
api('/api/params').then(function(res){
|
||||
try{var d=JSON.parse(res.body);}catch(e){d={};}
|
||||
var html=navBar('setup');
|
||||
html+="<div class='wm-page-head'><h1>Setup</h1><p class='wm-page-desc'>Custom parameters stored on this device.</p></div>";
|
||||
html+="<div class='wm-card'><form id='wm-param-form'>";
|
||||
html+=renderFieldList(d.params||[]);
|
||||
html+="<div class='wm-form-actions'><button type='submit' class='wm-btn wm-btn--primary wm-btn--block'>"+labelWithIcon('save','Save parameters')+"</button></div>";
|
||||
html+="</form><div id='wm-param-msg'></div></div>";
|
||||
setView(html);
|
||||
});
|
||||
}
|
||||
|
||||
window.portalParamSave=function(ev){
|
||||
ev.preventDefault();
|
||||
var fd=new FormData(document.getElementById('wm-param-form'));
|
||||
var msg=$('wm-param-msg');
|
||||
if(msg)msg.innerHTML='Saving...';
|
||||
api('/api/params/save',{method:'POST',body:fd}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
if(msg)msg.innerHTML=(j&&j.message)?esc(j.message):'Saved.';
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
function viewUpdate(){
|
||||
var pages=boot.pages||{};
|
||||
var pupd=pages.update||{};
|
||||
var html=navBar('update');
|
||||
html+="<div class='wm-page-head'><h1>Firmware update</h1><p class='wm-page-desc'>Flash a new firmware build (.bin). The device reboots when done.</p></div>";
|
||||
if(!pupd.visible){
|
||||
html+="<div class='wm-card'><p class='wm-lead'>Firmware update is disabled for this configuration.</p></div>";
|
||||
setView(html);
|
||||
return;
|
||||
}
|
||||
html+="<div class='wm-card'><form id='wm-ota-form'>";
|
||||
html+="<div class='wm-field'><label for='wm-ota-file'>Firmware file</label>";
|
||||
html+="<input type='file' id='wm-ota-file' name='update' accept='.bin,.bin.gz'/></div>";
|
||||
html+="<div class='wm-form-actions'><button type='submit' class='wm-btn wm-btn--primary wm-btn--block'>"+labelWithIcon('upload','Upload firmware')+"</button></div>";
|
||||
html+="</form>";
|
||||
html+="<p class='wm-lead' style='margin-top:14px;margin-bottom:8px'>Choose the correct binary for this chip. Upload progress appears below.</p>";
|
||||
html+="<pre id='wm-ota-log'></pre></div>";
|
||||
setView(html);
|
||||
}
|
||||
|
||||
window.portalOtaSubmit=function(ev){
|
||||
ev.preventDefault();
|
||||
var fi=$('wm-ota-file');
|
||||
var logEl=$('wm-ota-log');
|
||||
if(!fi||!fi.files||!fi.files[0]){
|
||||
if(logEl)logEl.textContent='Choose a firmware file first.';
|
||||
return false;
|
||||
}
|
||||
if(logEl)logEl.textContent='Uploading…';
|
||||
var xhr=new XMLHttpRequest();
|
||||
xhr.upload.onprogress=function(e){
|
||||
if(!logEl||!e.lengthComputable)return;
|
||||
logEl.textContent='Uploading… '+Math.round(100*e.loaded/e.total)+'%';
|
||||
};
|
||||
xhr.onreadystatechange=function(){
|
||||
if(xhr.readyState!==4)return;
|
||||
var t=xhr.responseText||'';
|
||||
if(logEl)logEl.textContent=t;
|
||||
var httpOk=xhr.status>=200&&xhr.status<300;
|
||||
try{
|
||||
var j=JSON.parse(t);
|
||||
if(j&&j.message){
|
||||
if(logEl)logEl.textContent=j.message;
|
||||
showToast(j.message,j.ok===false);
|
||||
return;
|
||||
}
|
||||
}catch(e){}
|
||||
showToast(httpOk?'Upload finished.':('HTTP '+xhr.status),!httpOk);
|
||||
};
|
||||
xhr.open('POST','/u');
|
||||
var fd=new FormData();
|
||||
fd.append('update',fi.files[0]);
|
||||
xhr.send(fd);
|
||||
return false;
|
||||
};
|
||||
|
||||
// --- Routing ---
|
||||
function route(){
|
||||
var h=location.hash||'#/';
|
||||
if(h.indexOf('#/')!==0)h='#/';
|
||||
var pages=boot.pages||{};
|
||||
var ps=pages.setup||{};
|
||||
var pi=pages.info||{};
|
||||
var pu=pages.update||{};
|
||||
if(h==='#/setup'&&ps.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/info'&&pi.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/update'&&pu.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/'||h==='#'){viewHome();}
|
||||
else if(h==='#/wifi'){viewWifi();}
|
||||
else if(h==='#/info'){viewInfo();}
|
||||
else if(h==='#/setup'){viewSetup();}
|
||||
else if(h==='#/update'){viewUpdate();}
|
||||
else {viewHome();h='#/';}
|
||||
try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:h}}));}catch(e){}
|
||||
}
|
||||
|
||||
function bootPortal(){
|
||||
route();
|
||||
try{document.dispatchEvent(new CustomEvent('wm:ready',{detail:{boot:boot}}));}catch(e){}
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange',route);
|
||||
window.addEventListener('load',bootPortal);
|
||||
})();
|
||||
|
||||
)rawliteral";
|
||||
|
||||
#endif // _WM_PORTAL_APP_JS_H_
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* RootShell.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Single-page portal shell: only HTML document served for GET /.
|
||||
* Dynamic UI is driven by embedded JS + JSON APIs under /api/...
|
||||
*
|
||||
* Placeholder keys (%PAGE_TITLE%, %STYLES%, %BOOTSTRAP_JSON%, %PORTAL_APP_JS%, %PORTAL_APPEND_JS%) are filled
|
||||
* per request in WiFiManagerHandlers::handleRoot; do not treat placeholders as a customization API.
|
||||
*/
|
||||
|
||||
#ifndef _WM_ROOT_SHELL_TEMPLATE_H_
|
||||
#define _WM_ROOT_SHELL_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_ROOT_SHELL_TEMPLATE[] PROGMEM =
|
||||
"<!DOCTYPE html>"
|
||||
"<html lang='en'>"
|
||||
"<head>"
|
||||
"<meta name='format-detection' content='telephone=no'>"
|
||||
"<meta charset='UTF-8'/>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
|
||||
"<title>%PAGE_TITLE%</title>"
|
||||
"%STYLES%"
|
||||
"</head>"
|
||||
"<body class='portal'>"
|
||||
"<div id='wm-toast' class='wm-toast' aria-live='polite' role='status' style='display:none'></div>"
|
||||
"<div id='wm-dialog' class='wm-dialog' style='display:none' aria-hidden='true'>"
|
||||
"<div class='wm-dialog-backdrop' id='wm-dialog-backdrop'></div>"
|
||||
"<div class='wm-dialog-panel'>"
|
||||
"<p class='wm-dialog-msg' id='wm-dialog-msg'></p>"
|
||||
"<div class='wm-dialog-actions'>"
|
||||
"<button type='button' id='wm-dialog-cancel' class='wm-btn wm-btn--secondary'>Cancel</button>"
|
||||
"<button type='button' id='wm-dialog-ok' class='wm-btn wm-btn--primary wm-dialog-ok'>OK</button>"
|
||||
"</div></div></div>"
|
||||
"<div id='app'></div>"
|
||||
"<script id='wm-bootstrap' type='application/json'>%BOOTSTRAP_JSON%</script>"
|
||||
"<script>"
|
||||
"%PORTAL_APP_JS%"
|
||||
"</script>"
|
||||
"<script>"
|
||||
"%PORTAL_APPEND_JS%"
|
||||
"</script>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
|
||||
#endif // _WM_ROOT_SHELL_TEMPLATE_H_
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* WiFiManagerParameter.cpp
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include "WiFiManagerParameter.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter()
|
||||
: WiFiManagerParameter("")
|
||||
{
|
||||
}
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *custom) {
|
||||
_id = NULL;
|
||||
_label = NULL;
|
||||
_length = 0;
|
||||
_value = nullptr;
|
||||
_labelPlacement = WFM_LABEL_DEFAULT;
|
||||
_customHTML = custom;
|
||||
}
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label) {
|
||||
init(id, label, "", 0, "", WFM_LABEL_DEFAULT);
|
||||
}
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length) {
|
||||
init(id, label, defaultValue, length, "", WFM_LABEL_DEFAULT);
|
||||
}
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom) {
|
||||
init(id, label, defaultValue, length, custom, WFM_LABEL_DEFAULT);
|
||||
}
|
||||
|
||||
WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement) {
|
||||
init(id, label, defaultValue, length, custom, labelPlacement);
|
||||
}
|
||||
|
||||
void WiFiManagerParameter::init(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement) {
|
||||
_id = id;
|
||||
_label = label;
|
||||
_labelPlacement = labelPlacement;
|
||||
_customHTML = custom;
|
||||
_length = 0;
|
||||
_value = nullptr;
|
||||
setValue(defaultValue,length);
|
||||
}
|
||||
|
||||
WiFiManagerParameter::~WiFiManagerParameter() {
|
||||
if (_value != NULL) {
|
||||
delete[] _value;
|
||||
}
|
||||
_length=0; // setting length 0, ideally the entire parameter should be removed, or added to wifimanager scope so it follows
|
||||
}
|
||||
|
||||
// WiFiManagerParameter& WiFiManagerParameter::operator=(const WiFiManagerParameter& rhs){
|
||||
// Serial.println("copy assignment op called");
|
||||
// (*this->_value) = (*rhs._value);
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// @note debug is not available in wmparameter class
|
||||
void WiFiManagerParameter::setValue(const char *defaultValue, int length) {
|
||||
if (!_id || length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_length != length || _value == nullptr) {
|
||||
char* replacement = new (std::nothrow) char[static_cast<size_t>(length) + 1];
|
||||
if (!replacement) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete[] _value;
|
||||
_value = replacement;
|
||||
_length = length;
|
||||
}
|
||||
|
||||
memset(_value, 0, static_cast<size_t>(_length) + 1);
|
||||
|
||||
if (defaultValue != nullptr) {
|
||||
strncpy(_value, defaultValue, _length);
|
||||
}
|
||||
}
|
||||
const char* WiFiManagerParameter::getValue() const {
|
||||
// Serial.println(printf("Address of _value is %p\n", (void *)_value));
|
||||
return _value;
|
||||
}
|
||||
const char* WiFiManagerParameter::getID() const {
|
||||
return _id;
|
||||
}
|
||||
const char* WiFiManagerParameter::getPlaceholder() const {
|
||||
return _label;
|
||||
}
|
||||
const char* WiFiManagerParameter::getLabel() const {
|
||||
return _label;
|
||||
}
|
||||
int WiFiManagerParameter::getValueLength() const {
|
||||
return _length;
|
||||
}
|
||||
int WiFiManagerParameter::getLabelPlacement() const {
|
||||
return _labelPlacement;
|
||||
}
|
||||
const char* WiFiManagerParameter::getCustomHTML() const {
|
||||
return _customHTML;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* WiFiManagerServer.cpp
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* HTTP server lifecycle and route registration only. HTML shell rendering is request-scoped in
|
||||
* WiFiManagerHandlers (WM_ROOT_SHELL_TEMPLATE + per-request placeholder registry).
|
||||
* Route inventory matches WiFiManagerServer.h (GET /, /api/..., POST /u, onNotFound).
|
||||
*/
|
||||
|
||||
#include "WiFiManagerServer.h"
|
||||
#include "WiFiManager.h"
|
||||
#include "WiFiManagerHandlers.h"
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
#include "WiFiManagerDfteLogger.h"
|
||||
#include <DeviceFrameworkTemplateEngineDebug.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
WiFiManagerServer::WiFiManagerServer(WiFiManager* wm)
|
||||
: _wm(wm), _handlers(std::make_unique<WiFiManagerHandlers>(wm)) {}
|
||||
|
||||
WiFiManagerServer::~WiFiManagerServer() = default;
|
||||
|
||||
void WiFiManagerServer::createServer(uint16_t port) {
|
||||
if (server) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("Server already exists, shutting down first"));
|
||||
#endif
|
||||
shutdownServer();
|
||||
}
|
||||
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("Starting Web Portal"));
|
||||
#endif
|
||||
|
||||
if (port != 80) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("http server started with custom port: "), port);
|
||||
#endif
|
||||
}
|
||||
|
||||
server.reset(new AsyncWebServer(port));
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
// DFTE logging is still owned by server lifecycle; it is intentionally independent of
|
||||
// request-scoped shell rendering / placeholder registration.
|
||||
if (!deviceFrameworkTemplateEngineIsLoggingEnabled()) {
|
||||
if (!_dfteLogger) {
|
||||
_dfteLogger = std::make_unique<WiFiManagerDfteLogger>(_wm);
|
||||
}
|
||||
deviceFrameworkTemplateEngineEnableLogging(_dfteLogger.get(), static_cast<const void*>(this));
|
||||
_wmOwnsDfteLogSink = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerRoutes() {
|
||||
if (!server) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem, F("[ERROR] Server not created, call createServer() first"));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (_wm->_webservercallback != NULL) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
|
||||
#endif
|
||||
_wm->_webservercallback();
|
||||
}
|
||||
|
||||
// Shell: only HTML document. JSON + actions + OTA below (no legacy /wifi, /info, … routes).
|
||||
|
||||
server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleRoot(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_api_bootstrap), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiBootstrap(request);
|
||||
});
|
||||
server->on(WM_G(R_api_wifi_scan_status), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiWifiScanStatus(request);
|
||||
});
|
||||
server->on(WM_G(R_api_wifi_scan), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiWifiScan(request);
|
||||
});
|
||||
server->on(WM_G(R_api_wifi_meta), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiWifiMeta(request);
|
||||
});
|
||||
server->on(WM_G(R_api_wifi_save), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiWifiSave(request);
|
||||
});
|
||||
server->on(WM_G(R_api_wifi_connect_status), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiWifiConnectStatus(request);
|
||||
});
|
||||
server->on(WM_G(R_api_params), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiParamsGet(request);
|
||||
});
|
||||
server->on(WM_G(R_api_params_save), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiParamsSave(request);
|
||||
});
|
||||
server->on(WM_G(R_api_info), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiInfo(request);
|
||||
});
|
||||
server->on(WM_G(R_api_status), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiStatus(request);
|
||||
});
|
||||
server->on(WM_G(R_api_device_restart), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiDeviceRestart(request);
|
||||
});
|
||||
server->on(WM_G(R_api_device_erase), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiDeviceErase(request);
|
||||
});
|
||||
server->on(WM_G(R_api_portal_close), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiPortalClose(request);
|
||||
});
|
||||
server->on(WM_G(R_api_portal_exit), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiPortalExit(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_updatedone), HTTP_POST,
|
||||
[this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleUpdateDone(request);
|
||||
},
|
||||
[this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) {
|
||||
this->_handlers->handleUpdating(request, filename, index, data, len, final);
|
||||
});
|
||||
|
||||
server->onNotFound([this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleNotFound(request);
|
||||
});
|
||||
|
||||
server->begin();
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("HTTP server started"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiManagerServer::setupDNSD() {
|
||||
dnsServer.reset(new DNSServer());
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("dns server started with ip: "), WiFi.softAPIP());
|
||||
#endif
|
||||
const uint8_t DNS_PORT = 53;
|
||||
dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP());
|
||||
}
|
||||
|
||||
void WiFiManagerServer::processDNS() {
|
||||
if (dnsServer) {
|
||||
dnsServer->processNextRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManagerServer::shutdownServer() {
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
if (_wmOwnsDfteLogSink) {
|
||||
deviceFrameworkTemplateEngineDisableLoggingForOwner(static_cast<const void*>(this));
|
||||
_dfteLogger.reset();
|
||||
_wmOwnsDfteLogSink = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (server) {
|
||||
server.reset();
|
||||
}
|
||||
|
||||
if (dnsServer) {
|
||||
dnsServer->stop();
|
||||
dnsServer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* WiFiManagerUtils.cpp
|
||||
* Implementation of pure utility functions for WiFiManager
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include "WiFiManagerUtils.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
namespace WiFiManagerUtils {
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// WIFI STATUS ARRAYS
|
||||
|
||||
const char * const WIFI_STA_STATUS[] PROGMEM = {
|
||||
"WL_IDLE_STATUS", // 0 STATION_IDLE
|
||||
"WL_NO_SSID_AVAIL", // 1 STATION_NO_AP_FOUND
|
||||
"WL_SCAN_COMPLETED", // 2
|
||||
"WL_CONNECTED", // 3 STATION_GOT_IP
|
||||
"WL_CONNECT_FAILED", // 4 STATION_CONNECT_FAIL, STATION_WRONG_PASSWORD(NI)
|
||||
"WL_CONNECTION_LOST", // 5
|
||||
"WL_DISCONNECTED", // 6
|
||||
"WL_STATION_WRONG_PASSWORD" // 7 KLUDGE
|
||||
};
|
||||
|
||||
#ifdef ESP32
|
||||
const char * const AUTH_MODE_NAMES[] PROGMEM = {
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA_PSK",
|
||||
"WPA2_PSK",
|
||||
"WPA_WPA2_PSK",
|
||||
"WPA2_ENTERPRISE",
|
||||
"MAX"
|
||||
};
|
||||
#elif defined(ESP8266)
|
||||
const char * const AUTH_MODE_NAMES[] PROGMEM = {
|
||||
"",
|
||||
"",
|
||||
"WPA_PSK", // 2 ENC_TYPE_TKIP
|
||||
"",
|
||||
"WPA2_PSK", // 4 ENC_TYPE_CCMP
|
||||
"WEP", // 5 ENC_TYPE_WEP
|
||||
"",
|
||||
"OPEN", //7 ENC_TYPE_NONE
|
||||
"WPA_WPA2_PSK", // 8 ENC_TYPE_AUTO
|
||||
};
|
||||
#endif
|
||||
|
||||
const char* const WIFI_MODES[] PROGMEM = { "NULL", "STA", "AP", "STA+AP" };
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// STRING CONVERSION UTILITIES
|
||||
|
||||
String getStatusString(uint8_t status) {
|
||||
if(status <= 7) return FPSTR(WIFI_STA_STATUS[status]);
|
||||
return F("Unknown");
|
||||
}
|
||||
|
||||
|
||||
String getModeString(uint8_t mode) {
|
||||
if(mode <= 3) return FPSTR(WIFI_MODES[mode]);
|
||||
return F("Unknown");
|
||||
}
|
||||
|
||||
String getEncryptionString(uint8_t authmode) {
|
||||
return FPSTR(AUTH_MODE_NAMES[authmode]);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HTML UTILITIES
|
||||
|
||||
String htmlEntities(const String& str, bool whitespace) {
|
||||
String result = str;
|
||||
result.replace("&","&");
|
||||
result.replace("<","<");
|
||||
result.replace(">",">");
|
||||
result.replace("'","'");
|
||||
if(whitespace) result.replace(" "," ");
|
||||
return result;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// IP/STRING UTILITIES
|
||||
|
||||
bool isValidIP(const String& str) {
|
||||
for (size_t i = 0; i < str.length(); i++) {
|
||||
int c = str.charAt(i);
|
||||
if (c != '.' && (c < '0' || c > '9')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String ipToString(IPAddress ip) {
|
||||
String res = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
res += String((ip >> (8 * i)) & 0xFF) + ".";
|
||||
}
|
||||
res += String(((ip >> 8 * 3)) & 0xFF);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isValidAPPassword(const String& password) {
|
||||
if (password.length() < 8 || password.length() > 63) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HELPER CALCULATIONS
|
||||
|
||||
int rssiToQuality(int rssi) {
|
||||
int quality = 0;
|
||||
if (rssi <= -100) {
|
||||
quality = 0;
|
||||
} else if (rssi >= -50) {
|
||||
quality = 100;
|
||||
} else {
|
||||
quality = 2 * (rssi + 100);
|
||||
}
|
||||
return quality;
|
||||
}
|
||||
|
||||
} // namespace WiFiManagerUtils
|
||||
|
||||
+55
-8
@@ -1,13 +1,60 @@
|
||||
{
|
||||
"name": "WifiManager",
|
||||
"keywords": "wifi, wi-fi",
|
||||
"description": "ESP8266 WiFi Connection manager with fallback web configuration portal **USE version 0.6 together with stable 2.0.0 ESP8266 core for Arduino and use 0.7 for core 2.1.0.rc2 or newer**",
|
||||
"repository":
|
||||
{
|
||||
"name": "WiFiManager",
|
||||
"version": "3.0.0",
|
||||
"keywords": [
|
||||
"wifi",
|
||||
"wi-fi",
|
||||
"esp",
|
||||
"esp8266",
|
||||
"esp32",
|
||||
"espressif8266",
|
||||
"espressif32",
|
||||
"nodemcu",
|
||||
"wemos",
|
||||
"arduino"
|
||||
],
|
||||
"description": "Maintained ESP8266/ESP32 WiFi configuration portal with asynchronous web support.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "tzapu",
|
||||
"url": "https://github.com/tzapu"
|
||||
},
|
||||
{
|
||||
"name": "tablatronix",
|
||||
"url": "https://github.com/tablatronix",
|
||||
"maintainer": true
|
||||
},
|
||||
{
|
||||
"name": "Alex Hope-O'Connor",
|
||||
"url": "https://github.com/alexhopeoconnor"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tzapu/WiFiManager.git"
|
||||
"url": "https://github.com/alexhopeoconnor/WiFiManager.git"
|
||||
},
|
||||
"frameworks": "arduino",
|
||||
"platforms": "espressif",
|
||||
"version": "0.6"
|
||||
"platforms": [
|
||||
"espressif8266",
|
||||
"espressif32"
|
||||
],
|
||||
"dependencies": {
|
||||
"ESP32Async/ESPAsyncWebServer": "3.9.1",
|
||||
"DeviceFrameworkTemplateEngine": "https://github.com/alexhopeoconnor/DFTE.git#v1.0.1"
|
||||
},
|
||||
"build": {
|
||||
"includeDir": "lib/WiFiManager/include",
|
||||
"srcDir": "lib/WiFiManager/src"
|
||||
},
|
||||
"export": {
|
||||
"include": [
|
||||
"lib/WiFiManager/include",
|
||||
"lib/WiFiManager/src",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"library.json"
|
||||
]
|
||||
},
|
||||
"$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
|
||||
"homepage": "https://github.com/alexhopeoconnor/WiFiManager"
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
name=WiFiManager
|
||||
version=0.7
|
||||
author=tzapu
|
||||
maintainer=tzapu
|
||||
sentence=ESP8266 WiFi Connection manager with fallback web configuration portal
|
||||
paragraph=Library for configuring ESP8266 modules WiFi credentials at runtime. **USE version 0.6 together with stable 2.0.0 ESP8266 core for Arduino and use 0.7 for core 2.1.0.rc2 or newer**
|
||||
category=Communication
|
||||
url=https://github.com/tzapu/WiFiManager.git
|
||||
architectures=esp8266
|
||||
@@ -0,0 +1,44 @@
|
||||
; PlatformIO Project Configuration File for WiFiManager Library
|
||||
; This file is used for building tests
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
board = d1_mini
|
||||
framework = arduino
|
||||
; Postmortem jump relocation fix for large binaries (see ESP8266-LINKER-WORKAROUND.md in arduino-home-assistant)
|
||||
platform_packages =
|
||||
platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3
|
||||
monitor_speed = 115200
|
||||
test_framework = unity
|
||||
test_build_src = no
|
||||
build_flags =
|
||||
-DWM_LOG_LEVEL=5
|
||||
-DUNIT_TEST
|
||||
lib_deps =
|
||||
ESP32Async/ESPAsyncWebServer@3.9.1
|
||||
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.0.1
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32@6.13.0
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
test_framework = unity
|
||||
test_build_src = no
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
build_flags =
|
||||
-std=gnu++14
|
||||
-DWM_LOG_LEVEL=5
|
||||
-DUNIT_TEST
|
||||
lib_deps =
|
||||
ESP32Async/ESPAsyncWebServer@3.9.1
|
||||
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.0.1
|
||||
|
||||
; Optional: compile tests with DFTE logs bridged into WiFiManager::log (see README)
|
||||
[env:esp8266_dfte_log]
|
||||
extends = env:esp8266
|
||||
build_flags =
|
||||
${env:esp8266.build_flags}
|
||||
-DWM_DFTE_LOGGING
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 vMAJOR.MINOR.PATCH [--tag]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
tag="${1:-}"
|
||||
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || usage
|
||||
[[ "${2:-}" == "" || "${2:-}" == "--tag" ]] || usage
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
version="${tag#v}"
|
||||
manifest_version="$(sed -n 's/.*"version": "\([^"]*\)".*/\1/p' "$root/library.json" | head -n 1)"
|
||||
|
||||
if [[ "$manifest_version" != "$version" ]]; then
|
||||
echo "library.json is $manifest_version; expected $version for $tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "$root/library.properties" ]]; then
|
||||
properties_version="$(sed -n 's/^version=//p' "$root/library.properties" | head -n 1)"
|
||||
if [[ "$properties_version" != "$version" ]]; then
|
||||
echo "library.properties is $properties_version; expected $version for $tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
package_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$package_dir"' EXIT
|
||||
pio pkg pack "$root" --output "$package_dir/package.tar.gz" >/dev/null
|
||||
echo "Validated PlatformIO package for $tag"
|
||||
|
||||
if [[ "${2:-}" == "--tag" ]]; then
|
||||
git -C "$root" diff --quiet
|
||||
git -C "$root" diff --cached --quiet
|
||||
git -C "$root" tag -a "$tag" -m "Release $tag"
|
||||
echo "Created $tag. Push the branch and tag; GitHub Actions will publish the release."
|
||||
fi
|
||||
@@ -0,0 +1,180 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "test_main.h"
|
||||
|
||||
// Test case array
|
||||
TestCase tests[] = {
|
||||
// Basic tests
|
||||
TEST_ENTRY(test_basic_wifimanager_instantiation),
|
||||
TEST_ENTRY(test_reset_settings),
|
||||
// JSON API shape (buildApiInfoJson is large — run with fresh heap, before stress tests)
|
||||
TEST_ENTRY(test_captive_redirect_host_rule),
|
||||
TEST_ENTRY(test_api_wifi_meta_json_shape),
|
||||
TEST_ENTRY(test_api_wifi_meta_password_field_type),
|
||||
TEST_ENTRY(test_api_wifi_connect_status_success_redirect),
|
||||
TEST_ENTRY(test_api_info_json_shape),
|
||||
TEST_ENTRY(test_api_params_json_shape),
|
||||
TEST_ENTRY(test_api_status_json_shape),
|
||||
|
||||
// Configuration tests
|
||||
TEST_ENTRY(test_set_and_get_hostname),
|
||||
TEST_ENTRY(test_get_default_ap_name),
|
||||
TEST_ENTRY(test_get_wifi_status_string),
|
||||
TEST_ENTRY(test_get_mode_string),
|
||||
TEST_ENTRY(test_get_rssi_as_quality),
|
||||
|
||||
// Portal lifecycle tests
|
||||
TEST_ENTRY(test_start_config_portal),
|
||||
TEST_ENTRY(test_start_config_portal_with_password),
|
||||
TEST_ENTRY(test_start_config_portal_auto_name),
|
||||
TEST_ENTRY(test_stop_config_portal),
|
||||
TEST_ENTRY(test_config_portal_infrastructure),
|
||||
TEST_ENTRY(test_start_web_portal),
|
||||
TEST_ENTRY(test_stop_web_portal),
|
||||
TEST_ENTRY(test_config_portal_multiple_start_stop),
|
||||
TEST_ENTRY(test_config_portal_already_active),
|
||||
TEST_ENTRY(test_get_config_portal_ssid),
|
||||
TEST_ENTRY(test_bootstrap_json_portal_feature_flags),
|
||||
TEST_ENTRY(test_bootstrap_json_contract_v2),
|
||||
TEST_ENTRY(test_bootstrap_json_snapshot_consistency),
|
||||
TEST_ENTRY(test_root_render_interleaved_context_isolation),
|
||||
|
||||
// Non-blocking tests
|
||||
TEST_ENTRY(test_nonblocking_process),
|
||||
TEST_ENTRY(test_client_check_setters),
|
||||
TEST_ENTRY(test_process_required_for_timeout),
|
||||
|
||||
// Callback tests
|
||||
TEST_ENTRY(test_ap_callback),
|
||||
TEST_ENTRY(test_web_server_callback),
|
||||
TEST_ENTRY(test_multiple_callbacks),
|
||||
|
||||
// Parameter tests
|
||||
TEST_ENTRY(test_create_parameter),
|
||||
TEST_ENTRY(test_add_parameter),
|
||||
TEST_ENTRY(test_get_parameters),
|
||||
TEST_ENTRY(test_parameter_with_custom_html),
|
||||
TEST_ENTRY(test_parameter_value_length),
|
||||
TEST_ENTRY(test_parameter_label_placement),
|
||||
TEST_ENTRY(test_multiple_parameters),
|
||||
TEST_ENTRY(test_parameter_placeholder),
|
||||
|
||||
// Static IP tests
|
||||
TEST_ENTRY(test_set_ap_static_ip_config),
|
||||
TEST_ENTRY(test_ap_static_ip_application),
|
||||
|
||||
// WiFi connection tests
|
||||
TEST_ENTRY(test_autoconnect_fallback_to_portal),
|
||||
TEST_ENTRY(test_connectwifi_ssid_not_found),
|
||||
TEST_ENTRY(test_connection_state_transitions),
|
||||
TEST_ENTRY(test_autoconnect_with_timeout),
|
||||
|
||||
// Callback firing tests
|
||||
TEST_ENTRY(test_timeout_callback_fires),
|
||||
TEST_ENTRY(test_config_reset_callback_fires),
|
||||
TEST_ENTRY(test_ap_callback_fires_improved),
|
||||
TEST_ENTRY(test_web_server_callback_fires_improved),
|
||||
|
||||
// DNS lifecycle tests
|
||||
TEST_ENTRY(test_dns_server_created),
|
||||
TEST_ENTRY(test_dns_server_cleanup),
|
||||
TEST_ENTRY(test_dns_server_lifecycle_cycles),
|
||||
|
||||
// Error condition tests
|
||||
TEST_ENTRY(test_connection_failure_handling),
|
||||
TEST_ENTRY(test_resource_cleanup_after_error),
|
||||
TEST_ENTRY(test_multiple_rapid_start_stop),
|
||||
|
||||
// Parameter value tests
|
||||
TEST_ENTRY(test_parameter_value_set_directly),
|
||||
TEST_ENTRY(test_parameter_value_length_validation),
|
||||
TEST_ENTRY(test_parameter_value_persistence),
|
||||
TEST_ENTRY(test_parameter_value_update),
|
||||
TEST_ENTRY(test_multiple_parameters_different_values),
|
||||
|
||||
// WiFi scanning tests
|
||||
TEST_ENTRY(test_wifi_scan_initiates),
|
||||
TEST_ENTRY(test_async_scan_behavior),
|
||||
TEST_ENTRY(test_scan_status_checking),
|
||||
TEST_ENTRY(test_scan_completion_wait),
|
||||
TEST_ENTRY(test_scan_cancels_when_connect_pending),
|
||||
TEST_ENTRY(test_scan_cancels_when_lifecycle_blocked),
|
||||
TEST_ENTRY(test_scan_generation_invalidated_on_reset),
|
||||
|
||||
// Template rendering tests
|
||||
TEST_ENTRY(test_shell_template_renders_core_placeholders),
|
||||
TEST_ENTRY(test_shell_template_renders_dynamic_styles_with_percent_values),
|
||||
|
||||
// State transition tests
|
||||
TEST_ENTRY(test_portal_to_connected_transition),
|
||||
TEST_ENTRY(test_concurrent_operations),
|
||||
TEST_ENTRY(test_state_consistency_during_portal),
|
||||
TEST_ENTRY(test_state_transitions_multiple_cycles),
|
||||
|
||||
// Integration tests
|
||||
TEST_ENTRY(test_autoconnect_fallback_flow),
|
||||
TEST_ENTRY(test_portal_lifecycle_with_connection_attempt),
|
||||
TEST_ENTRY(test_parameter_add_and_retrieve),
|
||||
TEST_ENTRY(test_complete_flow_reset_autoconnect_portal),
|
||||
TEST_ENTRY(test_portal_with_parameters_and_infrastructure),
|
||||
|
||||
// Stress tests
|
||||
TEST_ENTRY(test_many_start_stop_cycles),
|
||||
TEST_ENTRY(test_long_running_portal),
|
||||
TEST_ENTRY(test_rapid_portal_start_stop),
|
||||
TEST_ENTRY(test_multiple_parameters_stress),
|
||||
TEST_ENTRY(test_portal_with_timeout_stress),
|
||||
};
|
||||
|
||||
const size_t TEST_COUNT = sizeof(tests) / sizeof(TestCase);
|
||||
|
||||
// Test state variables
|
||||
size_t next_index = 0;
|
||||
bool begun = false;
|
||||
|
||||
// Test setup and teardown
|
||||
void setUp(void) {
|
||||
// No per-test setup needed
|
||||
}
|
||||
|
||||
void tearDown(void) {
|
||||
// No per-test teardown needed
|
||||
}
|
||||
|
||||
// Arduino setup and loop functions (required by framework)
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(2000); // Give time for serial to initialize
|
||||
|
||||
Serial.println("\n[TEST] =============================================");
|
||||
Serial.println("[TEST] === WiFiManager Test Setup ===");
|
||||
Serial.println("[TEST] Starting WiFiManager tests...");
|
||||
Serial.println("[TEST] =============================================");
|
||||
|
||||
UNITY_BEGIN(); // Start Unity test framework
|
||||
begun = true; // Start tests immediately
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Run one test and return immediately
|
||||
if (begun && next_index < TEST_COUNT) {
|
||||
TestCase& t = tests[next_index];
|
||||
Serial.print("\n[TEST] ==== Running test: ");
|
||||
Serial.print(t.name);
|
||||
Serial.println(" ====");
|
||||
UnityDefaultTestRun(t.fn, t.name, t.line);
|
||||
next_index++;
|
||||
return; // yield quickly
|
||||
}
|
||||
|
||||
// All tests completed
|
||||
if (begun && next_index >= TEST_COUNT) {
|
||||
Serial.println("\n[TEST] =============================================");
|
||||
Serial.println("[TEST] === All tests completed ===");
|
||||
Serial.println("[TEST] =============================================");
|
||||
UNITY_END(); // prints Unity summary
|
||||
begun = false; // avoid repeating
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifndef TEST_MAIN_H
|
||||
#define TEST_MAIN_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Test function type
|
||||
using TestFn = void(*)();
|
||||
|
||||
// Test case structure
|
||||
struct TestCase {
|
||||
const char* name;
|
||||
TestFn fn;
|
||||
uint16_t line;
|
||||
};
|
||||
|
||||
#define TEST_ENTRY(fn) { #fn, fn, __LINE__ }
|
||||
|
||||
// Basic tests
|
||||
void test_basic_wifimanager_instantiation();
|
||||
void test_reset_settings();
|
||||
|
||||
// Configuration tests
|
||||
void test_set_and_get_hostname();
|
||||
void test_get_default_ap_name();
|
||||
void test_get_wifi_status_string();
|
||||
void test_get_mode_string();
|
||||
void test_get_rssi_as_quality();
|
||||
|
||||
// Portal lifecycle tests
|
||||
void test_start_config_portal();
|
||||
void test_start_config_portal_with_password();
|
||||
void test_start_config_portal_auto_name();
|
||||
void test_stop_config_portal();
|
||||
void test_config_portal_infrastructure();
|
||||
void test_start_web_portal();
|
||||
void test_stop_web_portal();
|
||||
void test_config_portal_multiple_start_stop();
|
||||
void test_config_portal_already_active();
|
||||
void test_get_config_portal_ssid();
|
||||
void test_bootstrap_json_portal_feature_flags();
|
||||
void test_bootstrap_json_contract_v2();
|
||||
void test_bootstrap_json_snapshot_consistency();
|
||||
void test_root_render_interleaved_context_isolation();
|
||||
|
||||
// Non-blocking tests
|
||||
void test_nonblocking_process();
|
||||
void test_client_check_setters();
|
||||
void test_process_required_for_timeout();
|
||||
|
||||
// Callback tests
|
||||
void test_ap_callback();
|
||||
void test_web_server_callback();
|
||||
void test_multiple_callbacks();
|
||||
|
||||
// Parameter tests
|
||||
void test_create_parameter();
|
||||
void test_add_parameter();
|
||||
void test_get_parameters();
|
||||
void test_parameter_with_custom_html();
|
||||
void test_parameter_value_length();
|
||||
void test_parameter_label_placement();
|
||||
void test_multiple_parameters();
|
||||
void test_parameter_placeholder();
|
||||
|
||||
// Static IP tests
|
||||
void test_set_ap_static_ip_config();
|
||||
void test_ap_static_ip_application();
|
||||
|
||||
// Advanced options tests
|
||||
|
||||
// WiFi connection tests
|
||||
void test_autoconnect_fallback_to_portal();
|
||||
void test_connectwifi_ssid_not_found();
|
||||
void test_connection_state_transitions();
|
||||
void test_autoconnect_with_timeout();
|
||||
|
||||
// Callback firing tests
|
||||
void test_timeout_callback_fires();
|
||||
void test_config_reset_callback_fires();
|
||||
void test_ap_callback_fires_improved();
|
||||
void test_web_server_callback_fires_improved();
|
||||
|
||||
// DNS lifecycle tests
|
||||
void test_dns_server_created();
|
||||
void test_dns_server_cleanup();
|
||||
void test_dns_server_lifecycle_cycles();
|
||||
|
||||
// Error condition tests
|
||||
void test_connection_failure_handling();
|
||||
void test_resource_cleanup_after_error();
|
||||
void test_multiple_rapid_start_stop();
|
||||
|
||||
// Parameter value tests
|
||||
void test_parameter_value_set_directly();
|
||||
void test_parameter_value_length_validation();
|
||||
void test_parameter_value_persistence();
|
||||
void test_parameter_value_update();
|
||||
void test_multiple_parameters_different_values();
|
||||
|
||||
// WiFi scanning tests
|
||||
void test_wifi_scan_initiates();
|
||||
void test_async_scan_behavior();
|
||||
void test_scan_status_checking();
|
||||
void test_scan_completion_wait();
|
||||
void test_scan_cancels_when_connect_pending();
|
||||
void test_scan_cancels_when_lifecycle_blocked();
|
||||
void test_scan_generation_invalidated_on_reset();
|
||||
|
||||
// Template rendering tests
|
||||
void test_shell_template_renders_core_placeholders();
|
||||
void test_shell_template_renders_dynamic_styles_with_percent_values();
|
||||
|
||||
// API JSON + captive helper tests
|
||||
void test_captive_redirect_host_rule();
|
||||
void test_api_wifi_meta_json_shape();
|
||||
void test_api_wifi_meta_password_field_type();
|
||||
void test_api_wifi_connect_status_success_redirect();
|
||||
void test_api_info_json_shape();
|
||||
void test_api_params_json_shape();
|
||||
void test_api_status_json_shape();
|
||||
|
||||
// State transition tests
|
||||
void test_portal_to_connected_transition();
|
||||
void test_concurrent_operations();
|
||||
void test_state_consistency_during_portal();
|
||||
void test_state_transitions_multiple_cycles();
|
||||
|
||||
// Integration tests
|
||||
void test_autoconnect_fallback_flow();
|
||||
void test_portal_lifecycle_with_connection_attempt();
|
||||
void test_parameter_add_and_retrieve();
|
||||
void test_complete_flow_reset_autoconnect_portal();
|
||||
void test_portal_with_parameters_and_infrastructure();
|
||||
|
||||
// Stress tests
|
||||
void test_many_start_stop_cycles();
|
||||
void test_long_running_portal();
|
||||
void test_rapid_portal_start_stop();
|
||||
void test_multiple_parameters_stress();
|
||||
void test_portal_with_timeout_stress();
|
||||
|
||||
#endif // TEST_MAIN_H
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Test advanced configuration options - consolidated into logical groups
|
||||
|
||||
// Test connection behavior options
|
||||
void test_connection_behavior_options() {
|
||||
Serial.println("[TEST] Testing connection behavior options...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test connection options
|
||||
wm.setBreakAfterConfig(true);
|
||||
wm.setBreakAfterConfig(false);
|
||||
wm.setSaveConnect(true);
|
||||
wm.setSaveConnect(false);
|
||||
wm.setRestorePersistent(true);
|
||||
wm.setRestorePersistent(false);
|
||||
wm.setConnectRetries(1);
|
||||
wm.setConnectRetries(3);
|
||||
wm.setConnectRetries(5);
|
||||
wm.setSaveConnectTimeout(0);
|
||||
wm.setSaveConnectTimeout(10);
|
||||
wm.setSaveConnectTimeout(30);
|
||||
wm.setEnableConfigPortal(true);
|
||||
wm.setEnableConfigPortal(false);
|
||||
wm.setDisableConfigPortal(true);
|
||||
wm.setDisableConfigPortal(false);
|
||||
wm.portalSetBehaviorConnectOnSave(true);
|
||||
wm.portalSetBehaviorConnectOnSave(false);
|
||||
wm.portalSetBehaviorExitAllowed(true);
|
||||
wm.portalSetBehaviorExitAllowed(false);
|
||||
wm.portalSetBehaviorConnectTimeoutSeconds(15);
|
||||
wm.portalSetBehaviorPortalTimeoutSeconds(20);
|
||||
wm.portalSetBehaviorAutoReconnect(true);
|
||||
wm.portalSetBehaviorAutoReconnect(false);
|
||||
wm.portalSetBehaviorApClientCheck(true);
|
||||
wm.portalSetBehaviorApClientCheck(false);
|
||||
wm.portalSetBehaviorWebClientCheck(true);
|
||||
wm.portalSetBehaviorWebClientCheck(false);
|
||||
wm.preloadWiFi("TestSSID", "TestPassword");
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Connection behavior options executed without crash");
|
||||
|
||||
Serial.println("[TEST] Connection behavior options test completed successfully");
|
||||
}
|
||||
|
||||
// Test UI display options
|
||||
void test_ui_display_options() {
|
||||
Serial.println("[TEST] Testing UI display options...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test display toggles
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Actual);
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Masked);
|
||||
wm.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Hidden);
|
||||
wm.setScanDispPerc(true);
|
||||
wm.setScanDispPerc(false);
|
||||
wm.portalSetActionEraseVisible(true);
|
||||
wm.portalSetActionEraseVisible(false);
|
||||
wm.portalSetActionRestartVisible(true);
|
||||
wm.portalSetActionRestartVisible(false);
|
||||
wm.portalSetActionExitVisible(true);
|
||||
wm.portalSetActionExitVisible(false);
|
||||
wm.portalSetActionCloseCaptiveVisible(true);
|
||||
wm.portalSetActionCloseCaptiveVisible(false);
|
||||
wm.portalSetPageUpdateVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetPageSetupVisible(true);
|
||||
wm.portalSetPageSetupVisible(false);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::WiFiPage);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);
|
||||
wm.portalSetActionBackVisible(true);
|
||||
wm.portalSetActionBackVisible(false);
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UI display options executed without crash");
|
||||
|
||||
Serial.println("[TEST] UI display options test completed successfully");
|
||||
}
|
||||
|
||||
// Test custom HTML/UI customization
|
||||
void test_ui_customization() {
|
||||
Serial.println("[TEST] Testing UI customization...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Minimal customization retained: title
|
||||
wm.portalSetBrandTitle("MyDevice");
|
||||
|
||||
// All setters executed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "UI customization options executed without crash");
|
||||
|
||||
Serial.println("[TEST] UI customization test completed successfully");
|
||||
}
|
||||
|
||||
void test_debug_soft_ap_config() {
|
||||
Serial.println("[TEST] Testing debugSoftAPConfig()...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// Debug output should not crash
|
||||
wm.debugSoftAPConfig();
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] debugSoftAPConfig() test completed successfully");
|
||||
}
|
||||
|
||||
void test_debug_platform_info() {
|
||||
Serial.println("[TEST] Testing debugPlatformInfo()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Debug output should not crash
|
||||
wm.debugPlatformInfo();
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "debugPlatformInfo() executed without crash");
|
||||
|
||||
Serial.println("[TEST] debugPlatformInfo() test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerHandlers.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
void test_captive_redirect_host_rule() {
|
||||
Serial.println("[TEST] Testing captive redirect host comparison...");
|
||||
|
||||
TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("captive.apple.com", "192.168.4.1"));
|
||||
TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.2", "192.168.4.1"));
|
||||
TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.1", "192.168.4.1"));
|
||||
TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("example.com", ""));
|
||||
|
||||
Serial.println("[TEST] Captive redirect host rule test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_wifi_meta_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/wifi/meta JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiWifiMetaJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifiFields\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"staticFields\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"canRefreshScan\":true")));
|
||||
|
||||
WiFiManagerParameter fieldParam("mqtt_host", "MQTT host", "broker.local", 64, "placeholder='broker.local'");
|
||||
WiFiManagerParameter htmlParam("<div class='wm-callout wm-callout--info'><p>GPS settings...</p></div>");
|
||||
wm.portalAddParameter(&fieldParam);
|
||||
wm.portalAddParameter(&htmlParam);
|
||||
j = handlers.buildApiWifiMetaJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"field\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"html\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"html\":\"<div class='wm-callout wm-callout--info'><p>GPS settings...</p></div>\"}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifiFields\":[{\"id\":\"s\"")));
|
||||
|
||||
Serial.println("[TEST] WiFi meta JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_wifi_meta_password_field_type() {
|
||||
Serial.println("[TEST] Testing /api/wifi/meta password field type from custom attrs...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
WiFiManagerParameter pwd("mqttpass", "MQTT password", "secret", 24, " type='password' autocorrect='off'");
|
||||
wm.portalAddParameter(&pwd);
|
||||
|
||||
String j = handlers.buildApiWifiMetaJson();
|
||||
const char* p = j.c_str();
|
||||
const char* chunk = strstr(p, "\"id\":\"mqttpass\"");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(chunk, "mqttpass param missing from meta JSON");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(chunk, "\"type\":\"password\""),
|
||||
"customAttrs type=password must map to JSON type password");
|
||||
|
||||
Serial.println("[TEST] WiFi meta password field type test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_wifi_connect_status_success_redirect() {
|
||||
Serial.println("[TEST] Testing /api/wifi/connect-status success redirect payload...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
#ifdef UNIT_TEST
|
||||
wm.wmTestSetPortalConnectSuccess("WiFi connected. Redirecting to 192.168.1.42", "192.168.1.42");
|
||||
#endif
|
||||
String j = handlers.buildApiWifiConnectStatusJson();
|
||||
const char* p = j.c_str();
|
||||
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"state\":\"success\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"stationIp\":\"192.168.1.42\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"redirectUrl\":\"http://192.168.1.42/\""));
|
||||
|
||||
Serial.println("[TEST] WiFi connect-status success redirect payload test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_info_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/info JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
PortalInfoSection battery;
|
||||
battery.id = "battery";
|
||||
battery.title = "Battery";
|
||||
battery.items.push_back({"soc", "State of charge", "84%"});
|
||||
battery.items.push_back({"voltage", "Voltage", "13.2V"});
|
||||
wm.portalAddInfoSection(battery);
|
||||
String j = handlers.buildApiInfoJson();
|
||||
const char* p = j.c_str();
|
||||
// Shape checks only: "status" is a nested object (},\n"device" not ],"device"); require
|
||||
// a tail marker so we know the full String was built (heap is OK). Middle sections
|
||||
// (wifi / about) are not asserted — long runs fragment heap and can truncate long JSONs.
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
TEST_ASSERT_GREATER_THAN_INT_MESSAGE(200, (int)j.length(),
|
||||
"api/info body unexpectedly short; likely heap/fragmentation");
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"status\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"connected\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "},\"device\":["));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"extraSections\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"id\":\"battery\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"title\":\"Battery\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"label\":\"State of charge\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"showUpdate\":"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"key\":\"aboutver\",\"label\":\"WiFiManager\",\"value\":\""));
|
||||
TEST_ASSERT_NOT_NULL(strstr(p, "\"},{\"key\":\"aboutarduinover\""));
|
||||
|
||||
Serial.println("[TEST] API info JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_params_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/params JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiParamsGetJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"showBack\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\":[")));
|
||||
|
||||
Serial.println("[TEST] API params JSON shape test completed successfully");
|
||||
}
|
||||
|
||||
void test_api_status_json_shape() {
|
||||
Serial.println("[TEST] Testing /api/status JSON shape...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
String j = handlers.buildApiStatusJson();
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"text\"")));
|
||||
TEST_ASSERT_EQUAL('{', j.charAt(0));
|
||||
TEST_ASSERT_EQUAL('}', j.charAt(j.length() - 1));
|
||||
|
||||
Serial.println("[TEST] API status JSON shape test completed successfully");
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test basic WiFiManager instantiation
|
||||
void test_basic_wifimanager_instantiation() {
|
||||
Serial.println("[TEST] Testing basic WiFiManager instantiation...");
|
||||
|
||||
// Test default constructor
|
||||
WiFiManager wm;
|
||||
|
||||
// Test that we can call basic methods without crashing
|
||||
String defaultName = wm.getDefaultAPName();
|
||||
TEST_ASSERT_GREATER_THAN(0, defaultName.length());
|
||||
|
||||
// Test default state - portal should not be active
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
TEST_ASSERT_FALSE(wm.getWebPortalActive());
|
||||
|
||||
Serial.println("[TEST] Basic WiFiManager instantiation test completed successfully");
|
||||
}
|
||||
|
||||
// Test resetSettings clears credentials
|
||||
void test_reset_settings() {
|
||||
Serial.println("[TEST] Testing resetSettings()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Ensure WiFi is in a stable state before resetting
|
||||
delay(100);
|
||||
|
||||
// Reset settings (should not crash)
|
||||
wm.resetSettings();
|
||||
|
||||
// Small delay to allow WiFi operations to complete
|
||||
delay(100);
|
||||
|
||||
// Verify WiFi is not saved after reset
|
||||
// Note: ESP32 uses NVS for WiFi credentials which behaves differently than ESP8266's EEPROM
|
||||
// On ESP32, getWiFiIsSaved() may still return TRUE due to NVS persistence differences
|
||||
#ifdef ESP32
|
||||
// ESP32: Due to NVS behavior, getWiFiIsSaved() may return TRUE even after resetSettings()
|
||||
// This is a known platform difference - the reset is still effective, just detected differently
|
||||
bool wifiIsSaved = wm.getWiFiIsSaved();
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "resetSettings() executed on ESP32 (NVS behavior differs from ESP8266)");
|
||||
Serial.print("[TEST] ESP32: getWiFiIsSaved() = ");
|
||||
Serial.println(wifiIsSaved ? "TRUE" : "FALSE");
|
||||
#else
|
||||
// ESP8266: Should return FALSE after resetSettings()
|
||||
TEST_ASSERT_FALSE(wm.getWiFiIsSaved());
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] resetSettings() test completed successfully");
|
||||
}
|
||||
|
||||
// Test disconnect - verify it doesn't crash and can be called multiple times
|
||||
void test_disconnect() {
|
||||
Serial.println("[TEST] Testing disconnect()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Disconnect should not crash even if not connected
|
||||
(void)wm.disconnect();
|
||||
|
||||
// Can be called multiple times safely
|
||||
(void)wm.disconnect();
|
||||
|
||||
Serial.println("[TEST] disconnect() test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Callback flags (static to avoid duplicate definitions)
|
||||
static bool timeout_callback_fired = false;
|
||||
static bool config_reset_callback_fired = false;
|
||||
|
||||
// Callback functions (static to avoid duplicate definitions)
|
||||
static void timeout_callback() {
|
||||
timeout_callback_fired = true;
|
||||
}
|
||||
|
||||
static void config_reset_callback() {
|
||||
config_reset_callback_fired = true;
|
||||
}
|
||||
|
||||
// Reset callback flags (static to avoid duplicate definitions)
|
||||
static void reset_callback_flags() {
|
||||
timeout_callback_fired = false;
|
||||
config_reset_callback_fired = false;
|
||||
}
|
||||
|
||||
// Test timeout callback fires
|
||||
void test_timeout_callback_fires() {
|
||||
Serial.println("[TEST] Testing timeout callback firing...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeoutCallback(timeout_callback);
|
||||
|
||||
wm.setConfigPortalTimeout(2); // 2 second timeout
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Process until timeout
|
||||
unsigned long start = millis();
|
||||
while (wm.getConfigPortalActive() && (millis() - start < 5000)) {
|
||||
wm.process();
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// Callback should have fired when timeout occurred
|
||||
TEST_ASSERT_TRUE(timeout_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Timeout callback firing test completed successfully");
|
||||
}
|
||||
|
||||
// Test config reset callback fires
|
||||
void test_config_reset_callback_fires() {
|
||||
Serial.println("[TEST] Testing config reset callback firing...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigResetCallback(config_reset_callback);
|
||||
|
||||
// Ensure WiFi is in a stable state before resetting
|
||||
delay(100);
|
||||
|
||||
// Call resetSettings which should trigger callback
|
||||
wm.resetSettings();
|
||||
|
||||
// Allow WiFi operations to complete
|
||||
delay(100);
|
||||
|
||||
// Callback should have fired
|
||||
TEST_ASSERT_TRUE(config_reset_callback_fired);
|
||||
|
||||
Serial.println("[TEST] Config reset callback firing test completed successfully");
|
||||
}
|
||||
|
||||
// Test AP callback fires (improve existing test)
|
||||
void test_ap_callback_fires_improved() {
|
||||
Serial.println("[TEST] Testing AP callback firing (improved)...");
|
||||
|
||||
bool ap_callback_fired = false;
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setAPCallback([&](WiFiManager* wm) {
|
||||
ap_callback_fired = true;
|
||||
(void)wm; // Suppress unused parameter warning
|
||||
});
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Callback should have fired when AP starts
|
||||
TEST_ASSERT_TRUE(ap_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] AP callback firing (improved) test completed successfully");
|
||||
}
|
||||
|
||||
// Test web server callback fires (improve existing test)
|
||||
void test_web_server_callback_fires_improved() {
|
||||
Serial.println("[TEST] Testing web server callback firing (improved)...");
|
||||
|
||||
bool web_server_callback_fired = false;
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setWebServerCallback([&]() {
|
||||
web_server_callback_fired = true;
|
||||
});
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Wait for server to initialize
|
||||
unsigned long start = millis();
|
||||
while (!web_server_callback_fired && (millis() - start < 1000)) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Callback should have fired when server starts
|
||||
TEST_ASSERT_TRUE(web_server_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Web server callback firing (improved) test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Callback flags
|
||||
bool ap_callback_fired = false;
|
||||
bool web_server_callback_fired = false;
|
||||
bool save_config_callback_fired = false;
|
||||
bool pre_save_config_callback_fired = false;
|
||||
bool save_params_callback_fired = false;
|
||||
bool pre_save_params_callback_fired = false;
|
||||
bool config_reset_callback_fired = false;
|
||||
bool config_portal_timeout_callback_fired = false;
|
||||
|
||||
// Callback functions
|
||||
void ap_callback(WiFiManager* wm) {
|
||||
ap_callback_fired = true;
|
||||
}
|
||||
|
||||
void web_server_callback() {
|
||||
web_server_callback_fired = true;
|
||||
}
|
||||
|
||||
void save_config_callback() {
|
||||
save_config_callback_fired = true;
|
||||
}
|
||||
|
||||
void pre_save_config_callback() {
|
||||
pre_save_config_callback_fired = true;
|
||||
}
|
||||
|
||||
void save_params_callback(WiFiManager::WiFiManagerRequestArgs requestArgs) {
|
||||
save_params_callback_fired = true;
|
||||
// Test that we can access request args
|
||||
(void)requestArgs; // Suppress unused variable warning
|
||||
}
|
||||
|
||||
void pre_save_params_callback() {
|
||||
pre_save_params_callback_fired = true;
|
||||
}
|
||||
|
||||
void config_reset_callback() {
|
||||
config_reset_callback_fired = true;
|
||||
}
|
||||
|
||||
void config_portal_timeout_callback() {
|
||||
config_portal_timeout_callback_fired = true;
|
||||
}
|
||||
|
||||
// Reset all callback flags
|
||||
void reset_callback_flags() {
|
||||
ap_callback_fired = false;
|
||||
web_server_callback_fired = false;
|
||||
save_config_callback_fired = false;
|
||||
pre_save_config_callback_fired = false;
|
||||
save_params_callback_fired = false;
|
||||
pre_save_params_callback_fired = false;
|
||||
config_reset_callback_fired = false;
|
||||
config_portal_timeout_callback_fired = false;
|
||||
}
|
||||
|
||||
void test_ap_callback() {
|
||||
Serial.println("[TEST] Testing AP callback...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setAPCallback(ap_callback);
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Call process() to ensure portal is fully initialized
|
||||
wm.process();
|
||||
|
||||
// Callback should have fired when AP starts
|
||||
TEST_ASSERT_TRUE(ap_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] AP callback test completed successfully");
|
||||
}
|
||||
|
||||
void test_web_server_callback() {
|
||||
Serial.println("[TEST] Testing web server callback...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setWebServerCallback(web_server_callback);
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Call process() to ensure server is initialized
|
||||
wm.process();
|
||||
delay(100); // Give time for server to start
|
||||
|
||||
// Callback should have fired when server starts
|
||||
TEST_ASSERT_TRUE(web_server_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Web server callback test completed successfully");
|
||||
}
|
||||
|
||||
// Test callback registration - verify all callbacks can be registered without crash
|
||||
void test_callback_registration() {
|
||||
Serial.println("[TEST] Testing callback registration...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Register all callbacks - verify registration doesn't crash
|
||||
wm.setConfigResetCallback(config_reset_callback);
|
||||
wm.setSaveConfigCallback(save_config_callback);
|
||||
wm.setPreSaveConfigCallback(pre_save_config_callback);
|
||||
wm.setSaveParamsCallback(save_params_callback);
|
||||
wm.setPreSaveParamsCallback(pre_save_params_callback);
|
||||
wm.setConfigPortalTimeoutCallback(config_portal_timeout_callback);
|
||||
|
||||
// Call resetSettings to verify callback registration doesn't interfere
|
||||
wm.resetSettings();
|
||||
|
||||
// Verify registration doesn't crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "All callbacks registered without crash");
|
||||
|
||||
Serial.println("[TEST] Callback registration test completed successfully");
|
||||
}
|
||||
|
||||
void test_multiple_callbacks() {
|
||||
Serial.println("[TEST] Testing multiple callbacks together...");
|
||||
|
||||
reset_callback_flags();
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Register all callbacks
|
||||
wm.setAPCallback(ap_callback);
|
||||
wm.setWebServerCallback(web_server_callback);
|
||||
wm.setSaveConfigCallback(save_config_callback);
|
||||
wm.setPreSaveConfigCallback(pre_save_config_callback);
|
||||
wm.setSaveParamsCallback(save_params_callback);
|
||||
wm.setPreSaveParamsCallback(pre_save_params_callback);
|
||||
wm.setConfigResetCallback(config_reset_callback);
|
||||
wm.setConfigPortalTimeoutCallback(config_portal_timeout_callback);
|
||||
|
||||
// Start portal - should fire AP and web server callbacks
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Call process() to ensure portal is fully initialized
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// Verify AP callback fired
|
||||
TEST_ASSERT_TRUE(ap_callback_fired);
|
||||
TEST_ASSERT_TRUE(web_server_callback_fired);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Multiple callbacks test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Test configuration API methods
|
||||
// Consolidated setter tests - verify setters don't crash and can be called multiple times
|
||||
void test_configuration_setters() {
|
||||
Serial.println("[TEST] Testing configuration setters...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test timeout setters
|
||||
wm.setConfigPortalTimeout(0);
|
||||
wm.setConfigPortalTimeout(30);
|
||||
wm.setConfigPortalTimeout(300);
|
||||
wm.setConnectTimeout(0);
|
||||
wm.setConnectTimeout(20);
|
||||
wm.setConnectTimeout(60);
|
||||
|
||||
// Test HTTP port
|
||||
wm.setHttpPort(80);
|
||||
wm.setHttpPort(8080);
|
||||
|
||||
// Test WiFi quality and filtering
|
||||
wm.setMinimumSignalQuality(-1); // Disable filter
|
||||
wm.setMinimumSignalQuality(0);
|
||||
wm.setMinimumSignalQuality(50);
|
||||
wm.setMinimumSignalQuality(100);
|
||||
wm.setRemoveDuplicateAPs(true);
|
||||
wm.setRemoveDuplicateAPs(false);
|
||||
|
||||
// Test display options
|
||||
wm.setShowStaticFields(true);
|
||||
wm.setShowStaticFields(false);
|
||||
wm.setShowDnsFields(true);
|
||||
wm.setShowDnsFields(false);
|
||||
|
||||
// Test portal behavior
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(false);
|
||||
|
||||
// Test custom title path
|
||||
wm.portalSetBrandTitle("TestTitle");
|
||||
|
||||
// Call setters multiple times with different values.
|
||||
wm.setConfigPortalTimeout(60);
|
||||
wm.setConnectTimeout(30);
|
||||
wm.setHttpPort(8080);
|
||||
wm.setMinimumSignalQuality(50);
|
||||
|
||||
Serial.println("[TEST] Configuration setters test completed successfully");
|
||||
}
|
||||
|
||||
// Test hostname setter/getter - can verify behavior
|
||||
void test_set_and_get_hostname() {
|
||||
Serial.println("[TEST] Testing setHostname() and getWiFiHostname()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.setHostname("test-hostname"), "Valid char* hostname should be accepted");
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.setHostname(String(" trimmed-hostname ")), "Valid String hostname should be trimmed and accepted");
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.setHostname(""), "Empty hostname should clear the configured hostname");
|
||||
|
||||
String tooLongHostname = "123456789012345678901234567890123";
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setHostname(tooLongHostname), "Hostnames longer than 32 chars should be rejected");
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setHostname("bad host"), "Hostnames with spaces should be rejected");
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setHostname("-leading"), "Hostnames cannot start with a hyphen");
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.setHostname("trailing-"), "Hostnames cannot end with a hyphen");
|
||||
|
||||
(void)wm.getWiFiHostname();
|
||||
|
||||
Serial.println("[TEST] setHostname() and getWiFiHostname() test completed successfully");
|
||||
}
|
||||
|
||||
// Test WiFi AP configuration setters - consolidated
|
||||
void test_wifi_ap_configuration_setters() {
|
||||
Serial.println("[TEST] Testing WiFi AP configuration setters...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test AP channel
|
||||
wm.setWiFiAPChannel(0); // Auto
|
||||
wm.setWiFiAPChannel(1);
|
||||
wm.setWiFiAPChannel(11);
|
||||
|
||||
// Test AP hidden
|
||||
wm.setWiFiAPHidden(true);
|
||||
wm.setWiFiAPHidden(false);
|
||||
|
||||
// Test country code
|
||||
wm.setCountry("US");
|
||||
wm.setCountry("CN");
|
||||
|
||||
// Test clean connect
|
||||
wm.setCleanConnect(true);
|
||||
wm.setCleanConnect(false);
|
||||
|
||||
// Test auto reconnect
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
wm.setWiFiAutoReconnect(false);
|
||||
|
||||
Serial.println("[TEST] WiFi AP configuration setters test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_default_ap_name() {
|
||||
Serial.println("[TEST] Testing getDefaultAPName()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
String apName = wm.getDefaultAPName();
|
||||
|
||||
// Should return a non-empty string (chip ID based)
|
||||
TEST_ASSERT_GREATER_THAN(0, apName.length());
|
||||
|
||||
Serial.println("[TEST] getDefaultAPName() test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_wifi_status_string() {
|
||||
Serial.println("[TEST] Testing getWLStatusString()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test with status code
|
||||
String status1 = wm.getWLStatusString(WL_IDLE_STATUS);
|
||||
String status2 = wm.getWLStatusString(WL_CONNECTED);
|
||||
String status3 = wm.getWLStatusString(WL_DISCONNECTED);
|
||||
|
||||
// All should return non-empty strings
|
||||
TEST_ASSERT_GREATER_THAN(0, status1.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, status2.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, status3.length());
|
||||
|
||||
// Test without parameter (uses current WiFi.status())
|
||||
String status4 = wm.getWLStatusString();
|
||||
TEST_ASSERT_GREATER_THAN(0, status4.length());
|
||||
|
||||
Serial.println("[TEST] getWLStatusString() test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_mode_string() {
|
||||
Serial.println("[TEST] Testing getModeString()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
String mode1 = wm.getModeString(WIFI_OFF);
|
||||
String mode2 = wm.getModeString(WIFI_STA);
|
||||
String mode3 = wm.getModeString(WIFI_AP);
|
||||
String mode4 = wm.getModeString(WIFI_AP_STA);
|
||||
|
||||
// All should return non-empty strings
|
||||
TEST_ASSERT_GREATER_THAN(0, mode1.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, mode2.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, mode3.length());
|
||||
TEST_ASSERT_GREATER_THAN(0, mode4.length());
|
||||
|
||||
Serial.println("[TEST] getModeString() test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_rssi_as_quality() {
|
||||
Serial.println("[TEST] Testing getRSSIasQuality()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test various RSSI values
|
||||
int quality1 = wm.getRSSIasQuality(-30); // Very strong
|
||||
int quality2 = wm.getRSSIasQuality(-70); // Good
|
||||
int quality3 = wm.getRSSIasQuality(-90); // Weak
|
||||
|
||||
// Quality should be 0-100
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, quality1);
|
||||
TEST_ASSERT_LESS_OR_EQUAL(100, quality1);
|
||||
TEST_ASSERT_GREATER_OR_EQUAL(0, quality2);
|
||||
TEST_ASSERT_LESS_OR_EQUAL(100, quality2);
|
||||
|
||||
// Stronger signal should have higher quality
|
||||
TEST_ASSERT_GREATER_THAN(quality3, quality1);
|
||||
|
||||
Serial.println("[TEST] getRSSIasQuality() test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test DNS server is created when portal starts
|
||||
void test_dns_server_created() {
|
||||
Serial.println("[TEST] Testing DNS server creation...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Wait for DNS server to be created
|
||||
unsigned long start = millis();
|
||||
while (!wm.getDNSServer() && (millis() - start < 1000)) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// DNS server should be created when portal starts
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] DNS server creation test completed successfully");
|
||||
}
|
||||
|
||||
// Test DNS server cleanup when portal stops
|
||||
void test_dns_server_cleanup() {
|
||||
Serial.println("[TEST] Testing DNS server cleanup...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Wait for DNS server to be created
|
||||
unsigned long start = millis();
|
||||
while (!wm.getDNSServer() && (millis() - start < 1000)) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Verify DNS server exists
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
// Stop portal
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
// DNS server should be cleaned up (set to nullptr or deleted)
|
||||
// Note: Actual cleanup verification depends on implementation
|
||||
// For now, verify portal is stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] DNS server cleanup test completed successfully");
|
||||
}
|
||||
|
||||
// Test DNS server lifecycle with multiple start/stop cycles
|
||||
void test_dns_server_lifecycle_cycles() {
|
||||
Serial.println("[TEST] Testing DNS server lifecycle with multiple cycles...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Cycle 1
|
||||
wm.startConfigPortal("TestAP1");
|
||||
wm.process();
|
||||
|
||||
unsigned long start = millis();
|
||||
while (!wm.getDNSServer() && (millis() - start < 1000)) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
delay(200);
|
||||
|
||||
// Cycle 2
|
||||
wm.startConfigPortal("TestAP2");
|
||||
wm.process();
|
||||
|
||||
start = millis();
|
||||
while (!wm.getDNSServer() && (millis() - start < 1000)) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
Serial.println("[TEST] DNS server lifecycle cycles test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test invalid AP password (too short)
|
||||
void test_invalid_ap_password_too_short() {
|
||||
Serial.println("[TEST] Testing invalid AP password (too short)...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Password < 8 chars - should handle gracefully
|
||||
wm.startConfigPortal("TestAP", "short"); // < 8 chars
|
||||
|
||||
// Portal should either not start or use default behavior
|
||||
// Verify it doesn't crash
|
||||
wm.process();
|
||||
|
||||
// Portal may or may not be active (implementation dependent)
|
||||
// Just verify no crash
|
||||
if (wm.getConfigPortalActive()) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Invalid AP password (too short) handled without crash");
|
||||
|
||||
Serial.println("[TEST] Invalid AP password (too short) test completed successfully");
|
||||
}
|
||||
|
||||
// Test invalid AP password (too long)
|
||||
void test_invalid_ap_password_too_long() {
|
||||
Serial.println("[TEST] Testing invalid AP password (too long)...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Password > 63 chars - should handle gracefully
|
||||
String longPassword = String('a', 64); // 64 chars
|
||||
wm.startConfigPortal("TestAP", longPassword.c_str());
|
||||
|
||||
// Portal should handle gracefully
|
||||
wm.process();
|
||||
|
||||
// Portal may or may not be active (implementation dependent)
|
||||
// Just verify no crash
|
||||
if (wm.getConfigPortalActive()) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Invalid AP password (too long) handled without crash");
|
||||
|
||||
Serial.println("[TEST] Invalid AP password (too long) test completed successfully");
|
||||
}
|
||||
|
||||
// Test empty SSID
|
||||
void test_empty_ssid() {
|
||||
Serial.println("[TEST] Testing empty SSID...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Empty SSID - should use default AP name
|
||||
wm.startConfigPortal(""); // Empty SSID
|
||||
|
||||
wm.process();
|
||||
|
||||
// Portal should start with default name or handle gracefully
|
||||
if (wm.getConfigPortalActive()) {
|
||||
String ssid = wm.getConfigPortalSSID();
|
||||
// Should have a valid SSID (either default or empty handled)
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Empty SSID handled gracefully");
|
||||
wm.stopConfigPortal();
|
||||
} else {
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Empty SSID handled gracefully (portal not started)");
|
||||
}
|
||||
|
||||
Serial.println("[TEST] Empty SSID test completed successfully");
|
||||
}
|
||||
|
||||
// Test very long SSID
|
||||
void test_very_long_ssid() {
|
||||
Serial.println("[TEST] Testing very long SSID...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Very long SSID (> 32 chars) - should handle gracefully
|
||||
String longSSID = String('A', 64); // 64 chars
|
||||
wm.startConfigPortal(longSSID.c_str());
|
||||
|
||||
wm.process();
|
||||
|
||||
// Portal should handle gracefully (may truncate or reject)
|
||||
if (wm.getConfigPortalActive()) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Very long SSID handled without crash");
|
||||
|
||||
Serial.println("[TEST] Very long SSID test completed successfully");
|
||||
}
|
||||
|
||||
// Test connection failure handling
|
||||
void test_connection_failure_handling() {
|
||||
Serial.println("[TEST] Testing connection failure handling...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
// Attempt connection via autoConnect (will fail)
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should handle failure gracefully
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Verify WiFi state is consistent
|
||||
wl_status_t status = WiFi.status();
|
||||
TEST_ASSERT_NOT_EQUAL(WL_CONNECTED, status);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Connection failure handling test completed successfully");
|
||||
}
|
||||
|
||||
// Test resource cleanup after error
|
||||
void test_resource_cleanup_after_error() {
|
||||
Serial.println("[TEST] Testing resource cleanup after error...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Stop portal (simulating cleanup after potential error)
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
// Verify portal is stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify we can start again (resources cleaned up)
|
||||
wm.startConfigPortal("TestAP2");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Resource cleanup after error test completed successfully");
|
||||
}
|
||||
|
||||
// Test multiple rapid start/stop calls
|
||||
void test_multiple_rapid_start_stop() {
|
||||
Serial.println("[TEST] Testing multiple rapid start/stop calls...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Rapid start/stop cycles
|
||||
for (int i = 0; i < 5; i++) {
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
delay(10);
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Final state should be stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Multiple rapid start/stop calls test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerParameter.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test autoConnect fallback flow
|
||||
void test_autoconnect_fallback_flow() {
|
||||
Serial.println("[TEST] Testing autoConnect fallback flow...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// 1. Device starts
|
||||
// 2. autoConnect() called with no saved credentials
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// 3. Should fail to connect (no saved credentials)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// 4. Portal should start automatically as fallback
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] autoConnect fallback flow test completed successfully");
|
||||
}
|
||||
|
||||
// Test portal lifecycle with connection attempt
|
||||
void test_portal_lifecycle_with_connection_attempt() {
|
||||
Serial.println("[TEST] Testing portal lifecycle with connection attempt...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// 1. Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// 2. Attempt connection (while portal active) via preloadWiFi
|
||||
wm.preloadWiFi("NonExistentSSID_12345", "password");
|
||||
|
||||
// 3. Verify state handling - portal should still be active
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Connection should fail (verify WiFi is not connected)
|
||||
wl_status_t status = WiFi.status();
|
||||
TEST_ASSERT_NOT_EQUAL(WL_CONNECTED, status);
|
||||
|
||||
// 4. Stop portal
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Portal lifecycle with connection attempt test completed successfully");
|
||||
}
|
||||
|
||||
// Test parameter add and retrieve in portal
|
||||
void test_parameter_add_and_retrieve() {
|
||||
Serial.println("[TEST] Testing parameter add and retrieve in portal...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// 1. Add parameters
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
wm.portalAddParameter(¶m);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, wm.getParametersCount());
|
||||
|
||||
// 2. Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// 3. Verify parameters are accessible
|
||||
TEST_ASSERT_EQUAL(1, wm.getParametersCount());
|
||||
|
||||
WiFiManagerParameter** params = wm.getParameters();
|
||||
TEST_ASSERT_NOT_NULL(params);
|
||||
TEST_ASSERT_EQUAL_STRING("server", params[0]->getID());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Parameter add and retrieve in portal test completed successfully");
|
||||
}
|
||||
|
||||
// Test complete flow: reset -> autoConnect -> portal
|
||||
void test_complete_flow_reset_autoconnect_portal() {
|
||||
Serial.println("[TEST] Testing complete flow: reset -> autoConnect -> portal...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// 1. Reset settings
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings();
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
// 2. autoConnect (should fail and start portal)
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// 3. Verify portal started
|
||||
TEST_ASSERT_FALSE(result); // Connection failed
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive()); // Portal started
|
||||
|
||||
// 4. Process portal
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// 5. Verify portal is still active
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// 6. Stop portal
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Complete flow: reset -> autoConnect -> portal test completed successfully");
|
||||
}
|
||||
|
||||
// Test portal with parameters and infrastructure
|
||||
void test_portal_with_parameters_and_infrastructure() {
|
||||
Serial.println("[TEST] Testing portal with parameters and infrastructure...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Add parameters
|
||||
WiFiManagerParameter p1("server", "Server", "192.168.1.1", 40);
|
||||
WiFiManagerParameter p2("port", "Port", "1883", 6);
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Verify portal is active
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify infrastructure
|
||||
TEST_ASSERT_NOT_NULL(wm.getServer());
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
// Verify parameters
|
||||
TEST_ASSERT_EQUAL(2, wm.getParametersCount());
|
||||
|
||||
// Verify WiFi mode
|
||||
WiFiMode_t mode = WiFi.getMode();
|
||||
TEST_ASSERT_TRUE((mode & WIFI_AP) != 0);
|
||||
|
||||
// Verify AP IP
|
||||
IPAddress apIP = WiFi.softAPIP();
|
||||
TEST_ASSERT_NOT_EQUAL(IPAddress(0,0,0,0), apIP);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Portal with parameters and infrastructure test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Test process() doesn't block - verify it must be called periodically
|
||||
void test_nonblocking_process() {
|
||||
Serial.println("[TEST] Testing process() calls (non-blocking behavior)...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
// Verify portal is active
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process multiple times - should not block
|
||||
for (int i = 0; i < 10; i++) {
|
||||
unsigned long start = millis();
|
||||
wm.process();
|
||||
unsigned long elapsed = millis() - start;
|
||||
|
||||
// Each call should be fast (< 100ms) - verifies non-blocking behavior
|
||||
TEST_ASSERT_LESS_THAN(100, elapsed);
|
||||
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Portal should still be active after multiple process() calls
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Non-blocking process() test completed successfully");
|
||||
}
|
||||
|
||||
// Test client check setters - verify they don't interfere with portal operation
|
||||
void test_client_check_setters() {
|
||||
Serial.println("[TEST] Testing client check setters...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Test AP client check
|
||||
wm.setAPClientCheck(true);
|
||||
wm.setAPClientCheck(false);
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal and verify it works with client check settings
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.process();
|
||||
delay(100);
|
||||
wm.stopConfigPortal();
|
||||
|
||||
// Test web portal client check
|
||||
wm.setWebPortalClientCheck(true);
|
||||
wm.setWebPortalClientCheck(false);
|
||||
|
||||
// Start web portal and verify it works
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startWebPortal();
|
||||
TEST_ASSERT_TRUE(wm.getWebPortalActive());
|
||||
wm.process();
|
||||
wm.stopWebPortal();
|
||||
|
||||
Serial.println("[TEST] Client check setters test completed successfully");
|
||||
}
|
||||
|
||||
void test_nonblocking_timeout_behavior() {
|
||||
Serial.println("[TEST] Testing timeout behavior (requires process() calls)...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(2); // 2 second timeout
|
||||
wm.startConfigPortal("TestAP");
|
||||
|
||||
unsigned long start = millis();
|
||||
bool stillActive = true;
|
||||
|
||||
// Process until timeout or max wait
|
||||
while (stillActive && (millis() - start < 5000)) {
|
||||
wm.process();
|
||||
|
||||
// Check if portal is still active (may become inactive after timeout)
|
||||
if (!wm.getConfigPortalActive()) {
|
||||
stillActive = false;
|
||||
break; // Exit loop if portal has closed
|
||||
}
|
||||
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// After timeout, portal should be inactive (may have auto-closed)
|
||||
// Just verify we can check the state without crashing
|
||||
bool portalState = wm.getConfigPortalActive();
|
||||
|
||||
// Only call stopConfigPortal if portal is still active
|
||||
// If it auto-closed on timeout, calling stopConfigPortal may crash
|
||||
if (portalState) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
|
||||
// Test completed without crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "Timeout test completed without crash");
|
||||
|
||||
Serial.println("[TEST] Timeout behavior test completed successfully");
|
||||
}
|
||||
|
||||
// Test that process() is required for timeouts to work
|
||||
void test_process_required_for_timeout() {
|
||||
Serial.println("[TEST] Testing that process() is required for timeouts...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(1); // 1 second timeout
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Wait 2 seconds WITHOUT calling process()
|
||||
delay(2000);
|
||||
|
||||
// Portal should still be active because process() wasn't called
|
||||
// (timeout checking only happens in process())
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Now call process() - timeout should be checked
|
||||
unsigned long start = millis();
|
||||
bool stillActive = true;
|
||||
|
||||
// Process until timeout or max wait
|
||||
while (stillActive && (millis() - start < 3000)) {
|
||||
wm.process();
|
||||
|
||||
if (!wm.getConfigPortalActive()) {
|
||||
stillActive = false;
|
||||
break;
|
||||
}
|
||||
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// Portal should have timed out after process() was called
|
||||
// (may have auto-closed, but at least we verified process() is needed)
|
||||
bool portalState = wm.getConfigPortalActive();
|
||||
|
||||
if (portalState) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
|
||||
Serial.println("[TEST] process() required for timeout test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerParameter.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test parameter value set directly
|
||||
void test_parameter_value_set_directly() {
|
||||
Serial.println("[TEST] Testing parameter value set directly...");
|
||||
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
|
||||
// Set value directly
|
||||
param.setValue("newvalue", 40);
|
||||
|
||||
// Verify value was set
|
||||
TEST_ASSERT_EQUAL_STRING("newvalue", param.getValue());
|
||||
|
||||
Serial.println("[TEST] Parameter value set directly test completed successfully");
|
||||
}
|
||||
|
||||
// Test parameter value length validation
|
||||
void test_parameter_value_length_validation() {
|
||||
Serial.println("[TEST] Testing parameter value length validation...");
|
||||
|
||||
WiFiManagerParameter param("server", "Server", "", 10);
|
||||
|
||||
// Set value within length
|
||||
param.setValue("short", 10);
|
||||
TEST_ASSERT_EQUAL_STRING("short", param.getValue());
|
||||
|
||||
// Set value at length limit
|
||||
param.setValue("1234567890", 10); // Exactly 10 chars
|
||||
String value = param.getValue();
|
||||
TEST_ASSERT_LESS_OR_EQUAL(10, value.length());
|
||||
|
||||
Serial.println("[TEST] Parameter value length validation test completed successfully");
|
||||
}
|
||||
|
||||
// Test parameter value persistence
|
||||
void test_parameter_value_persistence() {
|
||||
Serial.println("[TEST] Testing parameter value persistence...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
wm.portalAddParameter(¶m);
|
||||
|
||||
// Set value
|
||||
param.setValue("persisted_value", 40);
|
||||
TEST_ASSERT_EQUAL_STRING("persisted_value", param.getValue());
|
||||
|
||||
// Verify parameter is still accessible through WiFiManager
|
||||
TEST_ASSERT_EQUAL(1, wm.getParametersCount());
|
||||
|
||||
WiFiManagerParameter** params = wm.getParameters();
|
||||
TEST_ASSERT_NOT_NULL(params);
|
||||
TEST_ASSERT_EQUAL_STRING("persisted_value", params[0]->getValue());
|
||||
|
||||
Serial.println("[TEST] Parameter value persistence test completed successfully");
|
||||
}
|
||||
|
||||
// Test parameter value update
|
||||
void test_parameter_value_update() {
|
||||
Serial.println("[TEST] Testing parameter value update...");
|
||||
|
||||
WiFiManagerParameter param("server", "Server", "default", 40);
|
||||
|
||||
// Set initial value
|
||||
param.setValue("initial", 40);
|
||||
TEST_ASSERT_EQUAL_STRING("initial", param.getValue());
|
||||
|
||||
// Update value
|
||||
param.setValue("updated", 40);
|
||||
TEST_ASSERT_EQUAL_STRING("updated", param.getValue());
|
||||
|
||||
Serial.println("[TEST] Parameter value update test completed successfully");
|
||||
}
|
||||
|
||||
// Test multiple parameters with different values
|
||||
void test_multiple_parameters_different_values() {
|
||||
Serial.println("[TEST] Testing multiple parameters with different values...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
WiFiManagerParameter p1("server", "Server", "192.168.1.1", 40);
|
||||
WiFiManagerParameter p2("port", "Port", "1883", 6);
|
||||
WiFiManagerParameter p3("key", "Key", "default_key", 32);
|
||||
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
wm.portalAddParameter(&p3);
|
||||
|
||||
// Set different values
|
||||
p1.setValue("10.0.0.1", 40);
|
||||
p2.setValue("8883", 6);
|
||||
p3.setValue("new_key", 32);
|
||||
|
||||
// Verify all values are correct
|
||||
TEST_ASSERT_EQUAL_STRING("10.0.0.1", p1.getValue());
|
||||
TEST_ASSERT_EQUAL_STRING("8883", p2.getValue());
|
||||
TEST_ASSERT_EQUAL_STRING("new_key", p3.getValue());
|
||||
|
||||
// Verify through WiFiManager
|
||||
WiFiManagerParameter** params = wm.getParameters();
|
||||
TEST_ASSERT_EQUAL_STRING("10.0.0.1", params[0]->getValue());
|
||||
TEST_ASSERT_EQUAL_STRING("8883", params[1]->getValue());
|
||||
TEST_ASSERT_EQUAL_STRING("new_key", params[2]->getValue());
|
||||
|
||||
Serial.println("[TEST] Multiple parameters with different values test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerParameter.h>
|
||||
|
||||
// Test custom parameters
|
||||
void test_create_parameter() {
|
||||
Serial.println("[TEST] Testing WiFiManagerParameter creation...");
|
||||
|
||||
// Create parameter with basic constructor
|
||||
WiFiManagerParameter param("test_id", "Test Label", "default_value", 40);
|
||||
|
||||
// Verify basic getters work
|
||||
TEST_ASSERT_EQUAL_STRING("test_id", param.getID());
|
||||
TEST_ASSERT_EQUAL_STRING("Test Label", param.getLabel());
|
||||
TEST_ASSERT_EQUAL_STRING("default_value", param.getValue());
|
||||
TEST_ASSERT_EQUAL(40, param.getValueLength());
|
||||
|
||||
Serial.println("[TEST] WiFiManagerParameter creation test completed successfully");
|
||||
}
|
||||
|
||||
void test_add_parameter() {
|
||||
Serial.println("[TEST] Testing portalAddParameter()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
WiFiManagerParameter param1("param1", "Parameter 1", "value1", 40);
|
||||
WiFiManagerParameter param2("param2", "Parameter 2", "value2", 40);
|
||||
|
||||
// Add parameters
|
||||
bool result1 = wm.portalAddParameter(¶m1);
|
||||
bool result2 = wm.portalAddParameter(¶m2);
|
||||
|
||||
TEST_ASSERT_TRUE(result1);
|
||||
TEST_ASSERT_TRUE(result2);
|
||||
|
||||
// Verify parameters are stored
|
||||
TEST_ASSERT_EQUAL(2, wm.getParametersCount());
|
||||
wm.portalClearParameters();
|
||||
TEST_ASSERT_EQUAL(0, wm.getParametersCount());
|
||||
|
||||
Serial.println("[TEST] portalAddParameter() test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_parameters() {
|
||||
Serial.println("[TEST] Testing getParameters() and getParametersCount()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
WiFiManagerParameter param1("param1", "Parameter 1", "value1", 40);
|
||||
WiFiManagerParameter param2("param2", "Parameter 2", "value2", 40);
|
||||
WiFiManagerParameter param3("param3", "Parameter 3", "value3", 40);
|
||||
|
||||
wm.portalAddParameter(¶m1);
|
||||
wm.portalAddParameter(¶m2);
|
||||
wm.portalAddParameter(¶m3);
|
||||
|
||||
// Get count
|
||||
int count = wm.getParametersCount();
|
||||
TEST_ASSERT_EQUAL(3, count);
|
||||
|
||||
// Get parameters array
|
||||
WiFiManagerParameter** params = wm.getParameters();
|
||||
TEST_ASSERT_NOT_NULL(params);
|
||||
|
||||
// Verify we can access parameters
|
||||
TEST_ASSERT_EQUAL_STRING("param1", params[0]->getID());
|
||||
TEST_ASSERT_EQUAL_STRING("param2", params[1]->getID());
|
||||
TEST_ASSERT_EQUAL_STRING("param3", params[2]->getID());
|
||||
|
||||
Serial.println("[TEST] getParameters() test completed successfully");
|
||||
}
|
||||
|
||||
void test_parameter_with_custom_html() {
|
||||
Serial.println("[TEST] Testing WiFiManagerParameter with custom HTML...");
|
||||
|
||||
// Create parameter with custom HTML
|
||||
const char* custom_html = "<input type='text' name='custom' value='test'>";
|
||||
WiFiManagerParameter param(custom_html);
|
||||
|
||||
// Verify custom HTML is accessible
|
||||
const char* html = param.getCustomHTML();
|
||||
TEST_ASSERT_NOT_NULL(html);
|
||||
|
||||
Serial.println("[TEST] Parameter with custom HTML test completed successfully");
|
||||
}
|
||||
|
||||
void test_parameter_value_length() {
|
||||
Serial.println("[TEST] Testing WiFiManagerParameter value length...");
|
||||
|
||||
WiFiManagerParameter param("test_id", "Test Label", "default", 10);
|
||||
|
||||
TEST_ASSERT_EQUAL(10, param.getValueLength());
|
||||
|
||||
// Try to set value within length
|
||||
param.setValue("short", 10);
|
||||
TEST_ASSERT_EQUAL_STRING("short", param.getValue());
|
||||
|
||||
Serial.println("[TEST] Parameter value length test completed successfully");
|
||||
}
|
||||
|
||||
void test_parameter_label_placement() {
|
||||
Serial.println("[TEST] Testing WiFiManagerParameter label placement...");
|
||||
|
||||
WiFiManagerParameter param("test_id", "Test Label", "default", 40, "", WFM_LABEL_BEFORE);
|
||||
|
||||
TEST_ASSERT_EQUAL(WFM_LABEL_BEFORE, param.getLabelPlacement());
|
||||
|
||||
WiFiManagerParameter param2("test_id2", "Test Label 2", "default", 40, "", WFM_LABEL_AFTER);
|
||||
|
||||
TEST_ASSERT_EQUAL(WFM_LABEL_AFTER, param2.getLabelPlacement());
|
||||
|
||||
Serial.println("[TEST] Parameter label placement test completed successfully");
|
||||
}
|
||||
|
||||
void test_multiple_parameters() {
|
||||
Serial.println("[TEST] Testing multiple parameters...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Add several parameters
|
||||
WiFiManagerParameter p1("mqtt_server", "MQTT Server", "192.168.1.1", 40);
|
||||
WiFiManagerParameter p2("mqtt_port", "MQTT Port", "1883", 6);
|
||||
WiFiManagerParameter p3("api_key", "API Key", "default_key", 32);
|
||||
|
||||
wm.portalAddParameter(&p1);
|
||||
wm.portalAddParameter(&p2);
|
||||
wm.portalAddParameter(&p3);
|
||||
|
||||
TEST_ASSERT_EQUAL(3, wm.getParametersCount());
|
||||
|
||||
WiFiManagerParameter** params = wm.getParameters();
|
||||
|
||||
// Verify all parameters are accessible
|
||||
TEST_ASSERT_EQUAL_STRING("mqtt_server", params[0]->getID());
|
||||
TEST_ASSERT_EQUAL_STRING("mqtt_port", params[1]->getID());
|
||||
TEST_ASSERT_EQUAL_STRING("api_key", params[2]->getID());
|
||||
|
||||
Serial.println("[TEST] Multiple parameters test completed successfully");
|
||||
}
|
||||
|
||||
void test_parameter_placeholder() {
|
||||
Serial.println("[TEST] Testing WiFiManagerParameter placeholder (deprecated)...");
|
||||
|
||||
WiFiManagerParameter param("test_id", "Test Label", "default", 40);
|
||||
|
||||
// getPlaceholder() is deprecated but should still work
|
||||
const char* placeholder = param.getPlaceholder();
|
||||
// May return label or empty, just verify no crash
|
||||
TEST_ASSERT_NOT_NULL(placeholder);
|
||||
|
||||
Serial.println("[TEST] Parameter placeholder test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Test config portal lifecycle
|
||||
void test_start_config_portal() {
|
||||
Serial.println("[TEST] Testing startConfigPortal()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal with SSID only
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] startConfigPortal() test completed successfully");
|
||||
}
|
||||
|
||||
void test_start_config_portal_with_password() {
|
||||
Serial.println("[TEST] Testing startConfigPortal() with password...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal with SSID and password
|
||||
wm.startConfigPortal("TestAP2", "password123");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] startConfigPortal() with password test completed successfully");
|
||||
}
|
||||
|
||||
void test_start_config_portal_auto_name() {
|
||||
Serial.println("[TEST] Testing startConfigPortal() with auto-generated name...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal without SSID (uses chip ID)
|
||||
wm.startConfigPortal();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100); // Give it time to initialize
|
||||
|
||||
// Verify we can get the SSID
|
||||
String ssid = wm.getConfigPortalSSID();
|
||||
TEST_ASSERT_GREATER_THAN(0, ssid.length());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] startConfigPortal() auto-name test completed successfully");
|
||||
}
|
||||
|
||||
void test_stop_config_portal() {
|
||||
Serial.println("[TEST] Testing stopConfigPortal()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// Stop portal
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] stopConfigPortal() test completed successfully");
|
||||
}
|
||||
|
||||
void test_config_portal_infrastructure() {
|
||||
Serial.println("[TEST] Testing config portal infrastructure...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(200); // Give time for initialization
|
||||
|
||||
// Verify server object exists (must check while portal is active)
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(wm.getServer(), "Server object should exist when portal is active");
|
||||
|
||||
// Verify DNS server exists
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(wm.getDNSServer(), "DNS server object should exist when portal is active");
|
||||
|
||||
// Verify WiFi is in AP mode
|
||||
WiFiMode_t mode = WiFi.getMode();
|
||||
TEST_ASSERT_TRUE_MESSAGE((mode & WIFI_AP) != 0, "WiFi should be in AP mode");
|
||||
|
||||
// Verify AP has an IP
|
||||
IPAddress apIP = WiFi.softAPIP();
|
||||
TEST_ASSERT_NOT_EQUAL(IPAddress(0,0,0,0), apIP);
|
||||
|
||||
// Verify AP SSID matches
|
||||
String apSSID = WiFi.softAPSSID();
|
||||
TEST_ASSERT_EQUAL_STRING("TestAP", apSSID.c_str());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Config portal infrastructure test completed successfully");
|
||||
}
|
||||
|
||||
void test_start_web_portal() {
|
||||
Serial.println("[TEST] Testing startWebPortal()...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
// Note: Web portal requires STA mode, but for testing we can just verify it starts
|
||||
// In real usage, WiFi would need to be connected first
|
||||
wm.startWebPortal();
|
||||
|
||||
TEST_ASSERT_TRUE(wm.getWebPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
|
||||
// Verify server object exists
|
||||
TEST_ASSERT_NOT_NULL(wm.getServer());
|
||||
|
||||
wm.stopWebPortal();
|
||||
|
||||
Serial.println("[TEST] startWebPortal() test completed successfully");
|
||||
}
|
||||
|
||||
void test_stop_web_portal() {
|
||||
Serial.println("[TEST] Testing stopWebPortal()...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(5);
|
||||
|
||||
wm.startWebPortal();
|
||||
TEST_ASSERT_TRUE(wm.getWebPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
wm.stopWebPortal();
|
||||
TEST_ASSERT_FALSE(wm.getWebPortalActive());
|
||||
|
||||
Serial.println("[TEST] stopWebPortal() test completed successfully");
|
||||
}
|
||||
|
||||
void test_config_portal_multiple_start_stop() {
|
||||
Serial.println("[TEST] Testing multiple config portal start/stop cycles...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// First cycle
|
||||
wm.startConfigPortal("TestAP1");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.process();
|
||||
delay(100);
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
delay(200);
|
||||
|
||||
// Second cycle
|
||||
wm.startConfigPortal("TestAP2");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.process();
|
||||
delay(100);
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
delay(200);
|
||||
|
||||
// Third cycle
|
||||
wm.startConfigPortal("TestAP3");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.process();
|
||||
delay(100);
|
||||
wm.stopConfigPortal();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Multiple config portal cycles test completed successfully");
|
||||
}
|
||||
|
||||
void test_config_portal_already_active() {
|
||||
Serial.println("[TEST] Testing startConfigPortal() when already active...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// Try to start again (should silently fail when already active)
|
||||
wm.startConfigPortal("TestAP2");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive()); // Still active with original AP name
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Config portal already active test completed successfully");
|
||||
}
|
||||
|
||||
void test_get_config_portal_ssid() {
|
||||
Serial.println("[TEST] Testing getConfigPortalSSID()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start with known SSID
|
||||
wm.startConfigPortal("MyTestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
// Verify SSID matches what we set
|
||||
String ssid = wm.getConfigPortalSSID();
|
||||
TEST_ASSERT_EQUAL_STRING("MyTestAP", ssid.c_str());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] getConfigPortalSSID() test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerHandlers.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "templates/RootShell.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kEmptyTemplateChunk[] PROGMEM = "";
|
||||
const char kTestTitle[] PROGMEM = "WiFiManager";
|
||||
|
||||
const char* dynamicStringGetter(void* userData) {
|
||||
const auto* value = static_cast<const String*>(userData);
|
||||
return value ? value->c_str() : "";
|
||||
}
|
||||
|
||||
size_t dynamicStringLengthGetter(const char* data, void* /*userData*/) {
|
||||
return data ? strlen(data) : 0;
|
||||
}
|
||||
|
||||
void configureDescriptor(DynamicDataDescriptor& descriptor, String& value) {
|
||||
descriptor.getter = &dynamicStringGetter;
|
||||
descriptor.getLength = &dynamicStringLengthGetter;
|
||||
descriptor.userData = &value;
|
||||
}
|
||||
|
||||
void renderContextsInterleaved(TemplateContext& first, String& firstOutput,
|
||||
TemplateContext& second, String& secondOutput) {
|
||||
uint8_t firstBuffer[31];
|
||||
uint8_t secondBuffer[31];
|
||||
bool firstDone = false;
|
||||
bool secondDone = false;
|
||||
|
||||
while (!firstDone || !secondDone) {
|
||||
if (!firstDone) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(first, firstBuffer, sizeof(firstBuffer));
|
||||
if (written == 0) {
|
||||
firstDone = true;
|
||||
} else {
|
||||
firstOutput.concat(reinterpret_cast<const char*>(firstBuffer), written);
|
||||
}
|
||||
}
|
||||
|
||||
if (!secondDone) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(second, secondBuffer, sizeof(secondBuffer));
|
||||
if (written == 0) {
|
||||
secondDone = true;
|
||||
} else {
|
||||
secondOutput.concat(reinterpret_cast<const char*>(secondBuffer), written);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void test_bootstrap_json_portal_feature_flags() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON portal feature flags...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
String idleBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_EQUAL(-1, idleBootstrap.indexOf(F("\"closeCaptive\":{\"visible\":true")));
|
||||
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.portalSetBehaviorCaptivePortalEnabled(true);
|
||||
|
||||
String apBootstrap = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"closeCaptive\":{\"visible\":true")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"exitPortal\":{\"visible\":true")));
|
||||
|
||||
wm.wmTestSetPortalActive(false);
|
||||
|
||||
Serial.println("[TEST] Bootstrap portal feature flags test completed successfully");
|
||||
}
|
||||
|
||||
void test_bootstrap_json_contract_v2() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON v2 contract...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
wm.portalSetBrandTitle("Solar Battery Monitor Setup");
|
||||
wm.portalSetContextIdentityText("Solar Battery Monitor");
|
||||
wm.portalSetBrandHomeIntro("Connect this device to WiFi and finish setup.");
|
||||
wm.portalSetBrandLogoSvg("<svg viewBox='0 0 24 24'></svg>");
|
||||
wm.portalSetPageInfoVisible(true);
|
||||
wm.portalSetPageUpdateVisible(false);
|
||||
wm.portalSetPageSetupVisible(true);
|
||||
wm.portalSetActionEraseVisible(false);
|
||||
wm.portalSetActionBackVisible(true);
|
||||
wm.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage);
|
||||
wm.setConfigPortalTimeout(90);
|
||||
|
||||
PortalHomeCard card;
|
||||
card.id = "solar";
|
||||
card.title = "Solar summary";
|
||||
card.kind = PortalHomeCardKind::KeyValue;
|
||||
card.items.push_back({"pv", "PV input", "420W"});
|
||||
wm.portalAddHomeCard(card);
|
||||
|
||||
String j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"contractVersion\":2")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"brand\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"title\":\"Solar Battery Monitor Setup\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"homeIntro\":\"Connect this device to WiFi and finish setup.\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"logoSvg\":\"<svg viewBox='0 0 24 24'></svg>\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"context\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"identityText\":\"Solar Battery Monitor\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"pages\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"setup\":{\"visible\":true}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\":{")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"erase\":{\"visible\":false}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"back\":{\"visible\":true}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"layout\":{\"paramsLocation\":\"setup\"}")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"extraHomeCards\":[")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"id\":\"solar\"")));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"kind\":\"kv\"")));
|
||||
|
||||
wm.startWebPortal();
|
||||
j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"portalActive\":true")));
|
||||
wm.stopWebPortal();
|
||||
|
||||
wm.wmTestSetPortalActive(true);
|
||||
j = handlers.buildPortalBootstrapJson();
|
||||
TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"portalTimeoutSecondsRemaining\":")));
|
||||
TEST_ASSERT_EQUAL(-1, j.indexOf(F("\"portalTimeoutSecondsRemaining\":0")));
|
||||
wm.wmTestSetPortalActive(false);
|
||||
|
||||
Serial.println("[TEST] Bootstrap JSON v2 contract test completed successfully");
|
||||
}
|
||||
|
||||
void test_bootstrap_json_snapshot_consistency() {
|
||||
Serial.println("[TEST] Testing bootstrap JSON snapshot consistency...");
|
||||
|
||||
WiFiManager wm;
|
||||
WiFiManagerHandlers handlers(&wm);
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.startConfigPortal("RootSnapshotAP");
|
||||
wm.process();
|
||||
delay(100);
|
||||
|
||||
String snap1 = handlers.buildPortalBootstrapJson();
|
||||
String snap2 = handlers.buildPortalBootstrapJson();
|
||||
|
||||
TEST_ASSERT_GREATER_THAN(32, snap1.length());
|
||||
TEST_ASSERT_EQUAL_STRING(snap1.c_str(), snap2.c_str());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Bootstrap JSON snapshot consistency test completed successfully");
|
||||
}
|
||||
|
||||
void test_root_render_interleaved_context_isolation() {
|
||||
Serial.println("[TEST] Testing root render interleaved context isolation...");
|
||||
|
||||
PlaceholderRegistry registryA(8);
|
||||
PlaceholderRegistry registryB(8);
|
||||
|
||||
registryA.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryA.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryA.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
|
||||
registryB.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
|
||||
registryB.registerProgmemData("%PAGE_TITLE%", kTestTitle);
|
||||
registryB.registerProgmemData("%PORTAL_APPEND_JS%", kEmptyTemplateChunk);
|
||||
|
||||
String bootstrapA = F("{\"ctx\":\"A\"}");
|
||||
String appJsA = F("// shell A");
|
||||
String bootstrapB = F("{\"ctx\":\"B\"}");
|
||||
String appJsB = F("// shell B");
|
||||
|
||||
DynamicDataDescriptor bootstrapDescriptorA{};
|
||||
DynamicDataDescriptor appJsDescriptorA{};
|
||||
DynamicDataDescriptor bootstrapDescriptorB{};
|
||||
DynamicDataDescriptor appJsDescriptorB{};
|
||||
|
||||
configureDescriptor(bootstrapDescriptorA, bootstrapA);
|
||||
configureDescriptor(appJsDescriptorA, appJsA);
|
||||
configureDescriptor(bootstrapDescriptorB, bootstrapB);
|
||||
configureDescriptor(appJsDescriptorB, appJsB);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptorA),
|
||||
"Registry A bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicData("%PORTAL_APP_JS%", &appJsDescriptorA),
|
||||
"Registry A app JS placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptorB),
|
||||
"Registry B bootstrap placeholder should register");
|
||||
TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicData("%PORTAL_APP_JS%", &appJsDescriptorB),
|
||||
"Registry B app JS placeholder should register");
|
||||
|
||||
TemplateContext contextA;
|
||||
TemplateContext contextB;
|
||||
contextA.setRegistry(®istryA);
|
||||
contextB.setRegistry(®istryB);
|
||||
|
||||
TemplateRenderer::initializeContext(contextA, WM_ROOT_SHELL_TEMPLATE);
|
||||
TemplateRenderer::initializeContext(contextB, WM_ROOT_SHELL_TEMPLATE);
|
||||
|
||||
String outputA;
|
||||
String outputB;
|
||||
renderContextsInterleaved(contextA, outputA, contextB, outputB);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"A\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("// shell A"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"B\"}"));
|
||||
TEST_ASSERT_EQUAL(-1, outputA.indexOf("// shell B"));
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"B\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("// shell B"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"A\"}"));
|
||||
TEST_ASSERT_EQUAL(-1, outputB.indexOf("// shell A"));
|
||||
|
||||
Serial.println("[TEST] Root render interleaved context isolation test completed successfully");
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test portal to connected transition (state consistency)
|
||||
void test_portal_to_connected_transition() {
|
||||
Serial.println("[TEST] Testing portal to connected transition...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Verify portal is active
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
TEST_ASSERT_FALSE(wm.getWebPortalActive());
|
||||
|
||||
// Portal state should be consistent
|
||||
// While portal is active, WiFi should be in AP mode
|
||||
WiFiMode_t mode = WiFi.getMode();
|
||||
TEST_ASSERT_TRUE((mode & WIFI_AP) != 0);
|
||||
|
||||
// Stop portal
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
// Verify portal is stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Portal to connected transition test completed successfully");
|
||||
}
|
||||
|
||||
// Test concurrent operations (portal + connection attempt)
|
||||
void test_concurrent_operations() {
|
||||
Serial.println("[TEST] Testing concurrent operations...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Attempt connection while portal is active (via preloadWiFi)
|
||||
// This should either be ignored or handled gracefully
|
||||
wm.preloadWiFi("NonExistentSSID_12345", "password");
|
||||
|
||||
// Portal should still be active (connection attempt shouldn't stop it)
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify WiFi is not connected (SSID doesn't exist)
|
||||
wl_status_t status = WiFi.status();
|
||||
TEST_ASSERT_NOT_EQUAL(WL_CONNECTED, status);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Concurrent operations test completed successfully");
|
||||
}
|
||||
|
||||
// Test state consistency during portal operation
|
||||
void test_state_consistency_during_portal() {
|
||||
Serial.println("[TEST] Testing state consistency during portal operation...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Verify state consistency
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
TEST_ASSERT_NOT_NULL(wm.getServer());
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
// Process multiple times
|
||||
for (int i = 0; i < 10; i++) {
|
||||
wm.process();
|
||||
delay(10);
|
||||
|
||||
// State should remain consistent
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
}
|
||||
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
// After stop, portal should be inactive
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] State consistency during portal operation test completed successfully");
|
||||
}
|
||||
|
||||
// Test state transitions with multiple start/stop cycles
|
||||
void test_state_transitions_multiple_cycles() {
|
||||
Serial.println("[TEST] Testing state transitions with multiple cycles...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
for (int cycle = 0; cycle < 3; cycle++) {
|
||||
// Start state
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Active state
|
||||
wm.process();
|
||||
delay(100);
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Stop portal
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
delay(100);
|
||||
}
|
||||
|
||||
Serial.println("[TEST] State transitions with multiple cycles test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// Test static IP configuration
|
||||
// Test AP static IP configuration - verifies IP is actually applied
|
||||
void test_set_ap_static_ip_config() {
|
||||
Serial.println("[TEST] Testing setAPStaticIPConfig()...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
IPAddress ip(192, 168, 4, 1);
|
||||
IPAddress gw(192, 168, 4, 1);
|
||||
IPAddress sn(255, 255, 255, 0);
|
||||
|
||||
// Set AP static IP
|
||||
wm.setAPStaticIPConfig(ip, gw, sn);
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Start portal and verify IP is actually set
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(200); // Give time for IP to be configured
|
||||
|
||||
// Verify AP IP matches what we configured
|
||||
IPAddress apIP = WiFi.softAPIP();
|
||||
TEST_ASSERT_EQUAL(ip, apIP);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] setAPStaticIPConfig() test completed successfully");
|
||||
}
|
||||
|
||||
// Test STA static IP configuration - consolidated
|
||||
void test_sta_static_ip_configuration() {
|
||||
Serial.println("[TEST] Testing STA static IP configuration...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
IPAddress ip(192, 168, 1, 100);
|
||||
IPAddress gw(192, 168, 1, 1);
|
||||
IPAddress sn(255, 255, 255, 0);
|
||||
IPAddress dns(8, 8, 8, 8);
|
||||
|
||||
// Test setSTAStaticIPConfig without DNS
|
||||
wm.setSTAStaticIPConfig(ip, gw, sn);
|
||||
|
||||
// Test setSTAStaticIPConfig with DNS
|
||||
wm.setSTAStaticIPConfig(ip, gw, sn, dns);
|
||||
|
||||
// Verify no crash
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "STA static IP configuration setters executed without crash");
|
||||
|
||||
Serial.println("[TEST] STA static IP configuration test completed successfully");
|
||||
}
|
||||
|
||||
// Note: test_show_static_fields and test_show_dns_fields removed -
|
||||
// already covered in test_configuration_setters() in test_configuration.cpp
|
||||
|
||||
void test_ap_static_ip_application() {
|
||||
Serial.println("[TEST] Testing AP static IP application...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
IPAddress customIP(10, 0, 1, 1);
|
||||
IPAddress customGW(10, 0, 1, 1);
|
||||
IPAddress customSN(255, 255, 255, 0);
|
||||
|
||||
wm.setAPStaticIPConfig(customIP, customGW, customSN);
|
||||
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Call process() to simulate real usage
|
||||
wm.process();
|
||||
delay(200); // Give time for AP to configure
|
||||
|
||||
IPAddress apIP = WiFi.softAPIP();
|
||||
|
||||
// AP should have the configured IP
|
||||
TEST_ASSERT_EQUAL(customIP, apIP);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] AP static IP application test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerParameter.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test many start/stop cycles for memory leaks
|
||||
void test_many_start_stop_cycles() {
|
||||
Serial.println("[TEST] Testing many start/stop cycles for memory leaks...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Test 10 start/stop cycles (enough to verify memory leaks and resource cleanup)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Verify portal started
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
|
||||
// Verify portal stopped
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
delay(10); // Small delay between cycles
|
||||
}
|
||||
|
||||
// Verify no crashes or memory leaks (by checking we can still start/stop)
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Many start/stop cycles test completed successfully");
|
||||
}
|
||||
|
||||
// Test long running portal
|
||||
void test_long_running_portal() {
|
||||
Serial.println("[TEST] Testing long running portal...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(30); // Longer timeout for long running test
|
||||
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Process for extended period (enough to verify stability)
|
||||
for (int i = 0; i < 50; i++) {
|
||||
wm.process();
|
||||
|
||||
// Verify portal remains active
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify infrastructure remains
|
||||
TEST_ASSERT_NOT_NULL(wm.getServer());
|
||||
TEST_ASSERT_NOT_NULL(wm.getDNSServer());
|
||||
|
||||
delay(10);
|
||||
}
|
||||
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Long running portal test completed successfully");
|
||||
}
|
||||
|
||||
// Test rapid portal start/stop
|
||||
void test_rapid_portal_start_stop() {
|
||||
Serial.println("[TEST] Testing rapid portal start/stop...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Rapid start/stop cycles (reduced for faster execution)
|
||||
for (int i = 0; i < 5; i++) {
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
wm.stopConfigPortal();
|
||||
wm.process();
|
||||
}
|
||||
|
||||
// Verify final state
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify we can still start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Rapid portal start/stop test completed successfully");
|
||||
}
|
||||
|
||||
// Test multiple parameters stress
|
||||
void test_multiple_parameters_stress() {
|
||||
Serial.println("[TEST] Testing multiple parameters stress...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.setConfigPortalTimeout(10);
|
||||
|
||||
// Add many parameters
|
||||
WiFiManagerParameter* params[20];
|
||||
for (int i = 0; i < 20; i++) {
|
||||
char id[20];
|
||||
char label[30];
|
||||
sprintf(id, "param%d", i);
|
||||
sprintf(label, "Parameter %d", i);
|
||||
params[i] = new WiFiManagerParameter(id, label, "default", 40);
|
||||
wm.portalAddParameter(params[i]);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(20, wm.getParametersCount());
|
||||
|
||||
// Start portal
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
// Verify all parameters accessible
|
||||
WiFiManagerParameter** paramArray = wm.getParameters();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
TEST_ASSERT_NOT_NULL(paramArray[i]);
|
||||
}
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
// Cleanup - Parameters are managed by WiFiManager, but we allocated with new
|
||||
// Note: WiFiManagerParameter doesn't have virtual destructor, but this is just cleanup
|
||||
for (int i = 0; i < 20; i++) {
|
||||
delete params[i];
|
||||
}
|
||||
|
||||
Serial.println("[TEST] Multiple parameters stress test completed successfully");
|
||||
}
|
||||
|
||||
// Test portal with timeout stress
|
||||
void test_portal_with_timeout_stress() {
|
||||
Serial.println("[TEST] Testing portal with timeout stress...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Multiple cycles with different timeouts
|
||||
for (int timeout = 1; timeout <= 5; timeout++) {
|
||||
wm.setConfigPortalTimeout(timeout);
|
||||
wm.startConfigPortal("TestAP");
|
||||
wm.process();
|
||||
|
||||
// Process until timeout or max wait
|
||||
unsigned long start = millis();
|
||||
unsigned long maxWait = (timeout + 1) * 1000UL;
|
||||
while (wm.getConfigPortalActive() && (millis() - start < maxWait)) {
|
||||
wm.process();
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// Portal may have timed out or still be active
|
||||
if (wm.getConfigPortalActive()) {
|
||||
wm.stopConfigPortal();
|
||||
}
|
||||
wm.process();
|
||||
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// Verify final state
|
||||
TEST_ASSERT_FALSE(wm.getConfigPortalActive());
|
||||
|
||||
Serial.println("[TEST] Portal with timeout stress test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <TemplateEngine.h>
|
||||
#include "templates/RootShell.h"
|
||||
#include "templates/PortalAppJS.h"
|
||||
#include "../test_main.h"
|
||||
|
||||
namespace {
|
||||
|
||||
String renderTemplate(const char* templateData, PlaceholderRegistry& registry) {
|
||||
TemplateContext context;
|
||||
context.setRegistry(®istry);
|
||||
TemplateRenderer::initializeContext(context, templateData);
|
||||
|
||||
uint8_t buffer[128];
|
||||
String output;
|
||||
while (true) {
|
||||
size_t written = TemplateRenderer::renderNextChunk(context, buffer, sizeof(buffer));
|
||||
if (written == 0) {
|
||||
break;
|
||||
}
|
||||
output.concat(reinterpret_cast<const char*>(buffer), written);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const char kEmpty[] PROGMEM = "";
|
||||
const char kDocTitle[] PROGMEM = "Config ESP";
|
||||
const char kBootstrapJson[] PROGMEM = "{\"title\":\"Test\"}";
|
||||
const char kPortalAppJs[] PROGMEM = "console.log('portal');";
|
||||
const char kPortalAppendJs[] PROGMEM = "";
|
||||
|
||||
const char* dynamicStringGetter(void* userData) {
|
||||
const auto* value = static_cast<const String*>(userData);
|
||||
return value ? value->c_str() : "";
|
||||
}
|
||||
|
||||
size_t dynamicStringLengthGetter(const char* data, void* /*userData*/) {
|
||||
return data ? strlen(data) : 0;
|
||||
}
|
||||
|
||||
void configureDescriptor(DynamicDataDescriptor& descriptor, String& value) {
|
||||
descriptor.getter = &dynamicStringGetter;
|
||||
descriptor.getLength = &dynamicStringLengthGetter;
|
||||
descriptor.userData = &value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void test_shell_template_renders_core_placeholders() {
|
||||
Serial.println("[TEST] Testing portal shell template rendering...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
registry.registerProgmemData("%STYLES%", kEmpty);
|
||||
registry.registerProgmemData("%PAGE_TITLE%", kDocTitle);
|
||||
registry.registerProgmemData("%BOOTSTRAP_JSON%", kBootstrapJson);
|
||||
registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs);
|
||||
registry.registerProgmemData("%PORTAL_APPEND_JS%", kPortalAppendJs);
|
||||
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<title>Config ESP</title>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<div id='app'></div>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-toast'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-dialog'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-bootstrap'"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("{\"title\":\"Test\"}"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("console.log('portal')"));
|
||||
|
||||
Serial.println("[TEST] Portal shell template rendering test completed successfully");
|
||||
}
|
||||
|
||||
void test_shell_template_renders_dynamic_styles_with_percent_values() {
|
||||
Serial.println("[TEST] Testing portal shell dynamic styles placeholder with percent values...");
|
||||
|
||||
PlaceholderRegistry registry(8);
|
||||
String styles = F("<style>body{width:100%;max-width:92%}</style>");
|
||||
String title = F("Config ESP");
|
||||
String bootstrap = F("{\"title\":\"Test\"}");
|
||||
String appendJs = F("");
|
||||
DynamicDataDescriptor stylesDescriptor{};
|
||||
DynamicDataDescriptor titleDescriptor{};
|
||||
DynamicDataDescriptor bootstrapDescriptor{};
|
||||
DynamicDataDescriptor appendJsDescriptor{};
|
||||
|
||||
configureDescriptor(stylesDescriptor, styles);
|
||||
configureDescriptor(titleDescriptor, title);
|
||||
configureDescriptor(bootstrapDescriptor, bootstrap);
|
||||
configureDescriptor(appendJsDescriptor, appendJs);
|
||||
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%STYLES%", &stylesDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%PAGE_TITLE%", &titleDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%BOOTSTRAP_JSON%", &bootstrapDescriptor));
|
||||
TEST_ASSERT_TRUE(registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs));
|
||||
TEST_ASSERT_TRUE(registry.registerDynamicData("%PORTAL_APPEND_JS%", &appendJsDescriptor));
|
||||
|
||||
String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<style>body{width:100%;max-width:92%}</style>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("<title>Config ESP</title>"));
|
||||
TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("{\"title\":\"Test\"}"));
|
||||
|
||||
Serial.println("[TEST] Dynamic styles placeholder percent rendering test completed successfully");
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
// Test autoConnect fallback to portal when no saved credentials
|
||||
void test_autoconnect_fallback_to_portal() {
|
||||
Serial.println("[TEST] Testing autoConnect fallback to portal...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should fail to connect (no saved credentials)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Should start portal as fallback
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] autoConnect fallback to portal test completed successfully");
|
||||
}
|
||||
|
||||
// Test connectWifi with non-existent SSID (using autoConnect instead)
|
||||
void test_connectwifi_ssid_not_found() {
|
||||
Serial.println("[TEST] Testing connection with non-existent SSID...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
// Use autoConnect with non-existent SSID (will fail to connect)
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should fail to connect (no saved credentials or SSID not found)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Verify WiFi is not connected
|
||||
wl_status_t status = WiFi.status();
|
||||
TEST_ASSERT_NOT_EQUAL(WL_CONNECTED, status);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Connection with non-existent SSID test completed successfully");
|
||||
}
|
||||
|
||||
// Test connect retry count setting
|
||||
void test_connectwifi_retry_count() {
|
||||
Serial.println("[TEST] Testing connect retry count setting...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
// Set retry count
|
||||
wm.setConnectRetries(3);
|
||||
|
||||
// Verify setting is stored (by attempting connection via autoConnect)
|
||||
// The retry logic should be called internally
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should fail (not connected)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Verify no crash with retry setting
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "autoConnect with retry count executed without crash");
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Connect retry count test completed successfully");
|
||||
}
|
||||
|
||||
// Test connect timeout setting
|
||||
void test_connectwifi_timeout_setting() {
|
||||
Serial.println("[TEST] Testing connect timeout setting...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
// Ensure WiFi is in a stable state before resetting
|
||||
delay(100);
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
|
||||
// Allow WiFi operations to complete
|
||||
delay(100);
|
||||
|
||||
// Set timeout
|
||||
wm.setConnectTimeout(5);
|
||||
|
||||
// Verify timeout setting is used (by attempting connection via autoConnect)
|
||||
wm.setConfigPortalTimeout(5);
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should fail (not connected)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Verify no crash with timeout setting
|
||||
TEST_ASSERT_TRUE_MESSAGE(true, "autoConnect with timeout setting executed without crash");
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Connect timeout setting test completed successfully");
|
||||
}
|
||||
|
||||
// Test connection state transitions
|
||||
void test_connection_state_transitions() {
|
||||
Serial.println("[TEST] Testing connection state transitions...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
// Attempt connection via autoConnect (will fail)
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.autoConnect("TestAP");
|
||||
|
||||
// Wait a bit for state to potentially change
|
||||
delay(100);
|
||||
|
||||
// Verify state transition (may be IDLE, DISCONNECTED, or NO_SSID_AVAIL)
|
||||
wl_status_t finalStatus = WiFi.status();
|
||||
|
||||
// Should not be CONNECTED (since no saved credentials)
|
||||
TEST_ASSERT_NOT_EQUAL(WL_CONNECTED, finalStatus);
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] Connection state transitions test completed successfully");
|
||||
}
|
||||
|
||||
// Test autoConnect with timeout
|
||||
void test_autoconnect_with_timeout() {
|
||||
Serial.println("[TEST] Testing autoConnect with timeout...");
|
||||
|
||||
WiFiManager wm;
|
||||
delay(100); // Ensure WiFi is in a stable state
|
||||
wm.resetSettings(); // Clear saved credentials
|
||||
delay(100); // Allow WiFi operations to complete
|
||||
|
||||
wm.setConfigPortalTimeout(5);
|
||||
wm.setConnectTimeout(3);
|
||||
|
||||
bool result = wm.autoConnect("TestAP");
|
||||
|
||||
// Should fail to connect (no saved credentials)
|
||||
TEST_ASSERT_FALSE(result);
|
||||
|
||||
// Should start portal as fallback
|
||||
TEST_ASSERT_TRUE(wm.getConfigPortalActive());
|
||||
|
||||
wm.stopConfigPortal();
|
||||
|
||||
Serial.println("[TEST] autoConnect with timeout test completed successfully");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include <unity.h>
|
||||
#include <Arduino.h>
|
||||
#include <WiFiManager.h>
|
||||
#include "../test_main.h"
|
||||
|
||||
void test_wifi_scan_initiates() {
|
||||
Serial.println("[TEST] Testing async scan request queues...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.requestAsyncScan(true);
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_QUEUED, wm.getScanState());
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Explicit refresh should mark scan scheduling as pending");
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_SCHEDULE_USER_REFRESH,
|
||||
wm.getScanSnapshot().scheduledReason);
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.hasValidScanResults(),
|
||||
"Queueing a scan should invalidate previous cached results");
|
||||
|
||||
Serial.println("[TEST] Async scan request queues test completed successfully");
|
||||
}
|
||||
|
||||
void test_async_scan_behavior() {
|
||||
Serial.println("[TEST] Testing repeated scan requests coalesce...");
|
||||
|
||||
WiFiManager wm;
|
||||
wm.requestAsyncScan(true);
|
||||
wm.requestAsyncScan(true);
|
||||
wm.requestAsyncScan(true);
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_QUEUED, wm.getScanState());
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Repeated refresh clicks should still leave one pending schedule");
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_SCHEDULE_USER_REFRESH,
|
||||
wm.getScanSnapshot().scheduledReason);
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.hasValidScanResults(),
|
||||
"Repeated refresh requests should keep a single queued scan lifecycle");
|
||||
|
||||
Serial.println("[TEST] Repeated scan requests coalesce test completed successfully");
|
||||
}
|
||||
|
||||
void test_scan_status_checking() {
|
||||
Serial.println("[TEST] Testing cached scan snapshot state...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
std::vector<WiFiManager::WiFiScanNetwork> results = {
|
||||
{ "Office", -48, 1 },
|
||||
{ "Guest", -70, 0 }
|
||||
};
|
||||
wm.wmTestInjectScanResults(results);
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.hasValidScanResults(),
|
||||
"Injected scan snapshot should be marked valid");
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_COMPLETE, wm.getScanState());
|
||||
TEST_ASSERT_EQUAL(static_cast<size_t>(2), wm.getScanResults().size());
|
||||
TEST_ASSERT_EQUAL_STRING("Office", wm.getScanResults()[0].ssid.c_str());
|
||||
#else
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Cached scan snapshot state test completed successfully");
|
||||
}
|
||||
|
||||
void test_scan_completion_wait() {
|
||||
Serial.println("[TEST] Testing scan timeout transition...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.wmTestForceScanState(WiFiManager::WM_SCAN_RUNNING);
|
||||
wm.wmTestSetScanStartedAt(millis() - 20000);
|
||||
wm.wmTestSetScanTimeoutMs(1000);
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_TIMEOUT, wm.getScanState());
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.hasValidScanResults(),
|
||||
"Timed out scans should not leave cached results marked valid");
|
||||
#else
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan timeout transition test completed successfully");
|
||||
}
|
||||
|
||||
void test_scan_cancels_when_connect_pending() {
|
||||
Serial.println("[TEST] Testing scan cancellation during connect...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.wmTestForceScanState(WiFiManager::WM_SCAN_RUNNING);
|
||||
wm.wmTestSetScanStartedAt(millis());
|
||||
wm.wmTestSetConnectPending(true);
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_IDLE, wm.getScanState());
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Connect flow should cancel any in-flight async scan");
|
||||
#else
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan cancellation during connect test completed successfully");
|
||||
}
|
||||
|
||||
void test_scan_cancels_when_lifecycle_blocked() {
|
||||
Serial.println("[TEST] Testing scan cancellation during lifecycle block...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
wm.wmTestSetPortalActive(true);
|
||||
wm.requestAsyncScan(true);
|
||||
wm.wmTestSetScanLifecycleBlocked(true);
|
||||
wm.process();
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_IDLE, wm.getScanState());
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().schedulePending,
|
||||
"Lifecycle blocking should leave scan engine idle");
|
||||
#else
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan cancellation during lifecycle block test completed successfully");
|
||||
}
|
||||
|
||||
void test_scan_generation_invalidated_on_reset() {
|
||||
Serial.println("[TEST] Testing scan generation invalidation on reset...");
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
wm.wmTestSetScanGenerations(4, 4, 4);
|
||||
wm.wmTestSetScanCompletionPending(3);
|
||||
wm.wmTestClearScanResults();
|
||||
|
||||
TEST_ASSERT_EQUAL(WiFiManager::WM_SCAN_IDLE, wm.getScanState());
|
||||
TEST_ASSERT_TRUE_MESSAGE(wm.getScanSnapshot().generation > 4,
|
||||
"Resetting scan state should invalidate older completion generations");
|
||||
TEST_ASSERT_FALSE_MESSAGE(wm.getScanSnapshot().completionPending,
|
||||
"Resetting scan state should clear pending completion callbacks");
|
||||
#else
|
||||
TEST_IGNORE_MESSAGE("UNIT_TEST helpers unavailable");
|
||||
#endif
|
||||
|
||||
Serial.println("[TEST] Scan generation invalidation on reset test completed successfully");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user