From b938a7f7cfa4da1efbe53a8b173ddbe7b75738ce Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Thu, 8 Dec 2022 22:57:01 -0500 Subject: [PATCH] v1.0.0 for `ESP32 + LwIP LAN8720` #### Releases v1.0.0 1. Initial coding to port [ESPAsync_WiFiManager](https://github.com/khoih-prog/ESPAsync_WiFiManager) to ESP32 boards using `LwIP LAN8720 Ethernet`. 2. Use `allman astyle` --- .codespellrc | 7 + CONTRIBUTING.md | 79 + Images/Configuration_Standard.png | Bin 0 -> 17154 bytes Images/Info.png | Bin 0 -> 70967 bytes Images/Main.png | Bin 0 -> 13218 bytes Images/Saved.png | Bin 0 -> 2478 bytes changelog.md | 33 + .../Async_ConfigOnDoubleReset.ino | 723 ++++++++ .../Async_ConfigOnDoubleReset_TZ.ino | 696 ++++++++ .../Async_ConfigOnSwitch.ino | 798 +++++++++ .../Async_ConfigOnSwitchFS.ino | 1008 +++++++++++ .../Async_ConfigPortalParamsOnSwitch.ino | 907 ++++++++++ .../Async_ESP32_FSWebServer.ino | 819 +++++++++ examples/Async_ESP32_FSWebServer/README.md | 71 + .../data/CanadaFlag_1.png | Bin 0 -> 41214 bytes .../data/CanadaFlag_2.png | Bin 0 -> 8311 bytes .../data/CanadaFlag_3.jpg | Bin 0 -> 11156 bytes .../Async_ESP32_FSWebServer/data/edit.htm.gz | Bin 0 -> 4116 bytes .../Async_ESP32_FSWebServer/data/favicon.ico | Bin 0 -> 1150 bytes .../Async_ESP32_FSWebServer/data/graphs.js.gz | Bin 0 -> 1971 bytes .../Async_ESP32_FSWebServer/data/index.htm | 97 ++ .../pics/async-esp32fs.local.png | Bin 0 -> 20520 bytes .../pics/async-esp32fs.local_edit.png | Bin 0 -> 76964 bytes .../Async_ESP32_FSWebServer_DRD.ino | 877 ++++++++++ .../Async_ESP32_FSWebServer_DRD/README.md | 71 + .../data/CanadaFlag_1.png | Bin 0 -> 41214 bytes .../data/CanadaFlag_2.png | Bin 0 -> 8311 bytes .../data/CanadaFlag_3.jpg | Bin 0 -> 11156 bytes .../data/edit.htm.gz | Bin 0 -> 4116 bytes .../data/favicon.ico | Bin 0 -> 1150 bytes .../data/graphs.js.gz | Bin 0 -> 1971 bytes .../data/index.htm | 97 ++ .../pics/async-esp32fs.local.png | Bin 0 -> 20520 bytes .../pics/async-esp32fs.local_edit.png | Bin 0 -> 76964 bytes keywords.txt | 92 + library.json | 56 + library.properties | 12 + platformio/platformio.ini | 102 ++ src/AsyncWT32_ETH01_Manager.h | 33 + src/AsyncWT32_ETH01_Manager.hpp | 621 +++++++ src/AsyncWT32_ETH01_Manager_Debug.h | 92 + src/AsyncWT32_ETH01_Manager_Impl.h | 1321 ++++++++++++++ src/utils/TZ.h | 1526 +++++++++++++++++ travis/common.sh | 51 + utils/astyle_library.conf | 70 + utils/restyle.sh | 6 + 46 files changed, 10265 insertions(+) create mode 100644 .codespellrc create mode 100644 CONTRIBUTING.md create mode 100644 Images/Configuration_Standard.png create mode 100644 Images/Info.png create mode 100644 Images/Main.png create mode 100644 Images/Saved.png create mode 100644 changelog.md create mode 100644 examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino create mode 100644 examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino create mode 100644 examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino create mode 100644 examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino create mode 100644 examples/Async_ConfigPortalParamsOnSwitch/Async_ConfigPortalParamsOnSwitch.ino create mode 100644 examples/Async_ESP32_FSWebServer/Async_ESP32_FSWebServer.ino create mode 100644 examples/Async_ESP32_FSWebServer/README.md create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_1.png create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_2.png create mode 100644 examples/Async_ESP32_FSWebServer/data/CanadaFlag_3.jpg create mode 100644 examples/Async_ESP32_FSWebServer/data/edit.htm.gz create mode 100644 examples/Async_ESP32_FSWebServer/data/favicon.ico create mode 100644 examples/Async_ESP32_FSWebServer/data/graphs.js.gz create mode 100644 examples/Async_ESP32_FSWebServer/data/index.htm create mode 100644 examples/Async_ESP32_FSWebServer/pics/async-esp32fs.local.png create mode 100644 examples/Async_ESP32_FSWebServer/pics/async-esp32fs.local_edit.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/Async_ESP32_FSWebServer_DRD.ino create mode 100644 examples/Async_ESP32_FSWebServer_DRD/README.md create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_1.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_2.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/CanadaFlag_3.jpg create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/edit.htm.gz create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/favicon.ico create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/graphs.js.gz create mode 100644 examples/Async_ESP32_FSWebServer_DRD/data/index.htm create mode 100644 examples/Async_ESP32_FSWebServer_DRD/pics/async-esp32fs.local.png create mode 100644 examples/Async_ESP32_FSWebServer_DRD/pics/async-esp32fs.local_edit.png create mode 100644 keywords.txt create mode 100644 library.json create mode 100644 library.properties create mode 100644 platformio/platformio.ini create mode 100644 src/AsyncWT32_ETH01_Manager.h create mode 100644 src/AsyncWT32_ETH01_Manager.hpp create mode 100644 src/AsyncWT32_ETH01_Manager_Debug.h create mode 100644 src/AsyncWT32_ETH01_Manager_Impl.h create mode 100644 src/utils/TZ.h create mode 100644 travis/common.sh create mode 100644 utils/astyle_library.conf create mode 100644 utils/restyle.sh diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..00fe362 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,7 @@ +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +check-filenames = +check-hidden = +skip = ./.git,./src,./examples,./Packages_Patches,./LibraryPatches diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2752ca1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +## Contributing to AsyncWT32_ETH01_Manager + +### Reporting Bugs + +Please report bugs in AsyncWT32_ETH01_Manager if you find them. + +However, before reporting a bug please check through the following: + +* [Existing Open Issues](https://github.com/khoih-prog/AsyncWT32_ETH01_Manager/issues) - someone might have already encountered this. + +If you don't find anything, please [open a new issue](https://github.com/khoih-prog/AsyncWT32_ETH01_Manager/issues/new). + +### How to submit a bug report + +Please ensure to specify the following: + +* Arduino IDE version (e.g. 1.8.19) or Platform.io version +* Board Core Version (e.g. ESP32 core v2.0.5) +* Contextual information (e.g. what you were trying to achieve) +* Simplest possible steps to reproduce +* Anything that might be relevant in your opinion, such as: + * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` + * Network configuration + + +Please be educated, civilized and constructive. Disrespective posts against [GitHub Code of Conduct](https://docs.github.com/en/site-policy/github-terms/github-event-code-of-conduct) will be ignored and deleted. + + +### Example + +``` +Arduino IDE version: 1.8.19 +ESP32_DEV board +ESP32 core v2.0.5 +OS: Ubuntu 20.04 LTS +Linux xy-Inspiron-3593 5.15.0-56-generic #62~20.04.1-Ubuntu SMP Tue Nov 22 21:24:20 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux + +Context: +I encountered a crash while using this library +Steps to reproduce: +1. ... +2. ... +3. ... +4. ... +``` + +### Additional context + +Add any other context about the problem here. + +--- + +### Sending Feature Requests + +Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. + +There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/AsyncWT32_ETH01_Manager/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. + +--- + +### Sending Pull Requests + +Pull Requests with changes and fixes are also welcome! + +Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) + +1. Change directory to the library GitHub + +``` +xy@xy-Inspiron-3593:~$ cd Arduino/xy/AsyncWT32_ETH01_Manager_GitHub/ +xy@xy-Inspiron-3593:~/Arduino/xy/AsyncWT32_ETH01_Manager_GitHub$ +``` + +2. Issue astyle command + +``` +xy@xy-Inspiron-3593:~/Arduino/xy/AsyncWT32_ETH01_Manager_GitHub$ bash utils/restyle.sh +``` + diff --git a/Images/Configuration_Standard.png b/Images/Configuration_Standard.png new file mode 100644 index 0000000000000000000000000000000000000000..a9dcefb68a93fd99390db0ea3845246a7f6989ef GIT binary patch literal 17154 zcmcJ%1ymf}o;}(L2?P%o9FiuuyF&sE1b252+PF&s0U8hP5Zv88xVyW%yEO7T-+g!H zKi}LtZ`OP3^@0Ue)m>G6PSx4_xA#63C?_L^ibQ|}0021n7cwWS6f6ix88-*ca9c#+kAd`-`yJW5z2we685Y~j<9)uE z`k=7Bk4e#EUrMjwP3kqGDA)7MuAtc9(6RRhzcMx5Fpd<*Y-$U)iqkR z(@l*ncbQq|tAhE!T8?P}L#i3OMWV2zQhMF`=?cN0AG2LqRhjg{YOXKocxr=)9{lR< z3OV`R_KO~y0L~vdi51E-rKD)|X9-lOv=Q(DLKWdY9kQfGEHU{?rZk!O|@MmrfGh* zofwmaaMb%7t9H1$?U-Mit}86m@{*hMh4VtF>q!dO1?JtXUt&M);7w}8UP)f-{q}4GXCfpiH=wZdzv)YA~$#G#@mn- zd4#uEil`|}NXo`r*p=L}OK=H>x@LN%`!kv}iB5tmIGY6x>X8|UlRjQaT3I#M+CCm! z)a39}Jg#FLpp)CxM}x_d-b=02ZlS+f*d(lk(l6YmTV1f9pglk4X|h!H(x{4oGm6ti zb-ODKbSmXc`9?XaBBg*1RmbWZzf*q(sWF$T7e&>~Ci##FoK$Ca2MRJqp^;l(xak7d z?fJpujAN%=mU^67rbp5zGBzA`7>!)Ft_e@8Q(6r6SC14>ie|Nb)^0Xr$L<~NCl`1@ z)_CK&xHG||XJ)L{K2G&JAcn(Cv%{o!4jFS>>HDc^J?tRL{8W0j`jIS(k(SIgPV>&Q zfdZGu@995MG_J{gocIO39trk7Nlx(hwKU}LlikGa>F(q4Nigo-iI>VUEnUzDn)S6P zRhb3%%2wbEH8jMIP>!_3%|A3cU>zEj55EJAVVNjiU-=&)2YBOka~`ilQ_L4w&DgfQ zxvy^&v1WAG6W0`S%`FM)7^K*tED=1~XCGC?ltUPE%ti!gOe{AM^xbZ@v>4(f*=9n_ zvs6N%O?SbFihV7NnI$w&+orzsMQ7du`qvnxV^VH3xbq!U4{e8LJ4X>)B@bX(T88x# zhu3>5J7F^h5>EsV-3^&1V!RBXg+y(3N&o3-EQ>{JWCz*V-^o%1uQp^K$3PTu;*M+! z4tLse>-5De7-NwQHZ)P1JdI|8g51>Pu(7bXqs9R*bRw$Lt7<| zx85{nU&L<~eS=H+gCfbj68f%%QXbXCwG27vKlXPwBP1aZ<|oM6e@wh(i8~lF-Yj$9 zkYjaJK}!q7!iZ7xNTbAjabWYR)MEe3N6f$<-U~wuRWWM5Q&XwKoa*9&-1C@$PMz}) z2*vl2Yw)xO;mzkiOj#3@*diZ`ZGce&p03qfx#KqYWvsh`;gSbT7V|ttwb9SQr35sl zOWzBnl`ZSe&hBC-cQMmF=S~|N;E1uJp^!ZtNEnr@QM-HRnp51)0tEy8x#2ty1vNsK z_Wi?k@YV5co`|URSDpb+P*vyhweeGN+mLo>)m?bq!L^zNT2&~Igz4cO2kO&Y?1IHX z{N{zuMS_ICWCS}{o48tPko>IUWL_xYY)e1^G4r5?xJ0?bbo3Gp$&1ON-&4I`irj(M z_j|8gLO3T6I-Xbzp=)3L3Y(IR-1-UpI@V$uSt4h60}sW1=YwRnsf2TFHrd)5rdiBX zgNxFTZ0kgU>CHML#ss)pyvwj)R!WULA+@p*jpXXlfyyV^sEL2o3@!IOO;tYU{QE@i zWBT!fu~^cZEw`JgGU6yN49dMo!dq4#U)Sn$zC$t`4CvV1XoQX?qV!6A_;g@r17_|lR za@7~#n!Wc(D2dGdavt`qvAAz}8Z$uA{3tKpEtKn%{)O#bun1${o*63Eoe>j zK`qu`ndnF>1^QrA-E73i$9NhxTAUwaD-@39iC@1Ks~eFHCLYpa{J_lkF6lJ~dXR`L zps3Gqpk56;8+S7^Le9uA8o#5H3lAB4xS#644XN0R7GmRox?IImha{D3uw-C&%{HAG z^M##(oZ~%fF?uwLoz@ldOlyZ+`C>_8QbT5#$ho7xD>h2!cAdic;#(j&>L_RHZ`t6mdhkT2Zc>tRuNM`oYo<@B;5l=J_aJx~6}f<_ zvd8n!1{`z!z4tA(2FE}{DHCdUMjfQ|o>q&L13BY+yrB(Mb_3fOQ;;DvFzxZ2Wf4CI zp2vewzDfAOF0a+th+?2($IR6i5k+wV$1@vVc^>vQQ^j#=a&MmPvqg}%D2C(dS0mE1 z?$8e0z46?|<8Vrv8HanVo^?3K8ka&&JXLt_55b*5=_|!{%tU)6y3>ea7cuKyG3$?y z`iMRpt!78c7p9tIA2ng--k|J<%&(g z#SH&43?_>YN7SjXo$(qjBQV_xu`nyb8!i&pAP~{n za2S$MQR}!CRmwu(Kd%-Y&OdCJIWEh+qL!AHfhnDIb-VSOEXA|)a#;9!P1lfR^Pxqq zrMtsVQl&zQZ$nU!Fqo!#nahxGO{qvo0OQWZ=>BPEus#zCCw|eSpHd#Mdl~|H-r8=X;4O=cRbcLVZ<`tTGEmjZKRtRD&AWDVL{gE{-z;} z6ZmQNwIHG6E3(LN+e|0h@+of)k4s8n?bhg^`%n(&_w#3A(yAVDjUNP7Fx^}GYUCNs z@f4X=fRIAz8ejUcFTw5;a&rZZUCuPRg;SGF7?+QoS-N@pA$X-2s`&J7N5zZ}F}p|K z^E{?cmKDj0-}*&jJl4J9F9<#DpC@kP>JVF4TF8S^Rz<<$!+L zih_8a|HYqTAn)Sk|GqHzt8VyTml1$(?qcj$%37^n{Fs4MLG89$^70sA)PO}!PfyPT zDSzginS-6-6n7}{yG^_Le9PO#dVBAui!|u{eX7T~_~*A=VI6&dZ%~S;zTS$5g_Wn8 zyu95&%2c&-gRaNk7W0JJoGJUx~&MTv?iz_olc3z?5l2 zG&3{f46Z~6tgiXNDysjn!1|Z}T1Ui?{O)gLZ+ zHzr29G>E0pFKY0+hr&Pq;gk50Y_Pn<>8<;x>WHbPsv zb(GRx5GalSRjZ9jrg%zuriDaugZJgP!$#Bf(4^4eH(-HP3&a|iYw>>Z!m2r1^rks; z&K5k0MUU5H+ZY8!!=W*U)=t8PZq?aGk2N(n3mVI3JTqAyyrDHwUj)CYe_*l_sO@dNgHcnMc;8y=xX;`H}#y>ig!XJzT z==YomXYS82;^yYlkLmE9*Hjdp)X}symvMHTw!;~=2hACs?!5EO+u{nFP*FNT?wu>Y z2+6ykDd!}f;wettsN$)-P&!9*mK@rSi0q1c1(Op4SO$J>sO-Kk%AVdOT*=emcbvT^*=$@#ro zeMhbOnRT^G$)blKc7Ik3iO8P(_>$eGW8)4=)HJ@_F5IWfPQLpTNh}WsxGX9jl-8-fc zL533%g!?XtfMn$@AR2m{a9gf1@U*JuUJ!(qugu(X};Zx3^{aDHefZb#KKIRfbEj4-7p1sAX?&rEiF0uAs+T$2I} z5CXc3yuI!+>c!f0KGv^>f-k#B(Dd9Y0|Re>0n$nZXV3S(u+6 z-zztCJmYJ1I?|BG4h{-}eT=D43wF@7=esoTt3qAr+hcU^+m1M0FZE2a*<>i-8^c#fS51!D}>htmKu1Brp~6nI(Jl$PSldy?&#qjmL^n z-m+4@vi<(+m?Pho?}}^BXH;B+|4`={kZXS!6b<9W&E-#v zHk7J;8!7{Y$(j5ScYH?5i_>rzz({n>Bki1b+)?$1aV1=qrCnmhTki-JgrM}HL0#T| zR&zQx4nl>IvXgE!JP1tbBE-8E*UbtyToNf#-!S>|-EBs#K$mY?ZkkUX4^0K3x!(7= z-=76xt1CAqhAt0-582~o&8RsJKbcm2r5ed@A?tIMi7&cAg8+JSdMnNsaY6s=r*f1UWz&XO;d#Amr zJKE&AuXGF$^%b=I3=er@N8yl|bCA)jbs?9gCUfgAJuhP{dk%iig2Ek5t1nE%eCC=Q zY6hs)qLVx+u@F4N0tfq0mFB$p-0FpyGqyRqKUFrEL?eDz^epHVd2-Ev$V1EmEAdy_dT4K&`^M zL8wqa2a7pO_oUonfe(0vc(f|$+e5D5uG+;- zQ!5o>O(`$B5`x5`iA9AXw#uOgB9-Iwn5yfk+9Y0;%DU}p`I~NS$pUM^p%#91UH%n) zs{(zCbak3ZA%=)NEF2ygHm)Pr9W}4+LvrbYWEqbvJ}6S=kemf+f$+#5STDw zb{NIq2TJr1=pvpI4)U1%oX+wg+Ltd0bk;bo5T)%cA92IpSZZCfD|+a^H9Bcp8D!gNgnEMix)f zzBb;W1PWue_Qm0Q!~~KQjK!T7fkv&0EbjPVU6#QJF)XNLKQXdv9%~>Ubot2Kl(EdQ zAf&BL_slYO?jt{WS_Jx;dgF*~(XzL8RCO8;;n4~VZ=9i#&tXW|MyNo1F&P+ki zo`6#-A(-O-Hv@og(4?QA;rWztw+#_MQhwM0X9f7i!wC3I7US9X)3kK1?8}fXQEo9) zJkfGEnBODZj(T+Q>6_?Xy_zm}X}fC}gzC|Roin45RHnH2rxRO^;q3>&q$~J1a;B+- zBa4~)+Fd)i%AuIdp{U$R`-=dvQ*ncMZmR7Fhh$k!W{x>Lij%yh1N*{N2n0Ku5`*W^ z!BXb5{GA1LiwU5&C9p4bDQpyIN~Oj*iBq97uSOpa67wy+);^FXNm4NxE;rIxauX-u z$>?Us#x;j47C*2j@E8He?WB~Q&mQt;)->snE+I5^yHOE%S}@&??=)49D8&}mzm(A~ zsdW*M!6N5hDRuBF`r#C3pR>(|;V4FMvEc)@LLm>NTF%=+VMGsmPcpBq!?c1))H}d1 zqEVfGh9vKtWec4TdTnlvmiumIx&>bqemO-|C+S2{fY6pfS+zBxDT+$zf<}R9oeNL_ zX4$J;7X$LNbw#ukxxDR%lrxS9IXK8j3h`?O~hGzgyXCmL~IR}Vc}Dzm_eLer6M zR_rj{2qg?nP2JDAM&q`dEOHr4BG^!u*P!ei`4I_<4iy6;?g#n}W5GrO;js$ye7d=& ziOv1VM$OFgtuipyozsk4BSd0|T{(X>@Ko>C^APwA$w&{su)5!?%ssN*lvrrcA? z%C~btuMd%YC7m!_SKIvwu|62Wwk@qtQ0@Bu0r>rsc}ka>-9-4b^$f_sEf51EX193ctZgIo3EB2e zLhR6zE`>d`emt%TTlVrt@k`So+5Ha<$vO0qSp}$RQ&UH%v7e;*WSZX`r(u-Vuw1^GWtDG0-nx-?n7vw0oxa4m7Q3M zj$>P#VLJJQF6ROn&UIE>g4B2;tteYhY-CEHM)O3O=rjppz{uGL5xY{O=otPHJfb*S zx}=Xw@R^QjhXv+?rU}OU>azah1$uJ%)*8-*%pa9CY(sUzo8UgB+&Ix!&Rw~35BVW4wnT9RN*4&XdD*G@(Pd%GAI2s`n`t8OtW)&HbIgw& zetWWfAvDi2@X&-^K@(DC#I}S{q2vA4yk5sv0{CCOn!S2%XlZT&{7@;hnaeR#vw4L~ zBll8`9-=4g-e>*HpaNx?Tzm<8CAFlpyyAr%>LeFmR) z_tEQ8X3eQmhdJsS=Ci-`lJ=>A6T%oe6RCFeWd$_T!B5(`7PT zddPv}c(w7fW@1-5X+`5xDdx6srzx=uOz>f298~jt^^W3kGQ3D2+zuWy%)cQHKOs+J>y>f&U^zicmGGP*I7)! zgkfbd`6NqVZof#UU~AiqHYDtKOk3Z%xEPGq_t?&7PYo-cchc?$a$Uji+&vsPY~loe zma{jvGM3)Z8_Abwa^0Cfza!#o^*9P|E>E@PwdW*!2{2ko5${3MEO3U$yhBvwi!FZP z1*Wp4KVG2X<=`Ph8p9b0OVK~ew%H$0s{8!LdSz{os1caWx~m2*66E9?7CWz-I)~r+ zDr+Qwu8vcC_2~y>vqQx_E?;EWhyj{Si?(2KCT~$+hzf(SlVQ*`!60RI%}zj1sZ=T& z$g!r|3OM4j#9H(*g>M0viZCf^5S`=~%&`{|zy zl4(BZidQN->H6B*P%YA?$Ce;1jgQgNFuNPduP~^m`Q`mH3((B7;x5cV5R7avT`OEb zrK_aA^wto@-0*7_Xoja%1{ta1b>}*hrAH9SRR7t0>+Ffl&0WYAm+c7@&hC+4B4j!4 z88D4m$%3@o=IKeSy`!ovFAYLwLhub(3bV$UPtxO`zO`<}3x~`OGKyjYMfBHWGuJ>A zWVPxzaa*4(*BcIwz;FKCoqB!~3-4b^zl7CnoEe6asOOFJ`VF%%U{F$Sno}hFxhTaP!8W z{sR+enX&B+rb>2JI&8+=e+rzc2+5nU$VYsG;4vJdRY99@D3OB$BbBlUWGVedbEflj z{%AbH+MqqP4cgCMzUn9tL&qqmc2S{CHccIhD`Y2WsP+0UsDXD=D2Ta6p@ULS9B^59Na3{1KnRP7q}cr zu-Eny(K+5-A3$ysgz-N$9>AwDSL<X`j& zn@gs#S8sWS&+A(iGthm2Tk(SJG9~gKBHS$tOujv?b=feg|l z?!PhJD0bQiPJh*c3Wbyv2}@F~W}NcTC+5o^BxL5(6-44tR#h zbFg!9ZT|kDjP`5>md3}B{|e^jBy0R`r*i*O zsM?ul@P;w?R7*kLtaFMd{r24eb#AkO%0al}s~fX+=wLM^Lg^EEn7dFzH_T7w(wAs9 zaZFW!P!g|2mPp>siE?P7)HK}&fE*ff$9>Dhq9pJ#JyC$7XpjY)&6CH6g z6&AT?>D`?Da*mZ-I!sWr<+L_z>3I5xVaUd$gz=}n1$7pUz(_wzbtJXNMw6-nQboWzI%^b*ouS=d|iI+ z@^J`K69$vVR2O&6n9f02>wZ%?4O2kvlFORzMdf?uDJ^3ojeLJeix7xX2!?cGoH_;Z z8`32%gH4;9>4H;Yz^D3?7xJ`rt2UV@{k;&$TsMg71*kJ0FI&+!MqMP9H(*D?x%=V> zZw%R%Lgh`Aae~BKk$nhd74mESbQtcB7heq<%A?RCemiv6W7ABosVFmCnkQ~Og_n5^ z*F#{DR_@XeT4O2##Ec7~TX!T5?;Hu>Cp_vg@}qK{-{KoC8cDhp=LE*5 zagLp73=Fj0;&9s)O*gEhF<`g=4a{T1;AKUOLYQHPX)OKs(%H)TwLmV~Q7p|2uaM6~ z{RiC<<3v=7HBdE^Zt(&!E0MXa@={ToP-Pz>kAc#1g$hG46A2^;!KY>C6)+=);y_|f z(Xb%=7xtUeJgVL;JLlTX08+0%fGO)q0{FI21uLp&blk1>FW%m>0AQ8L3jhfY>-sx% z2Q4I~BtC3@y8@dsz$yk^|1VflCmaZq@c*sXkwkE}dAch!tmrF@-KA4R*Sk^B+$Tu)Auidq6~R4tY{lNzP62p*}=?-M(bsuMDgoK4q{oQ zj7ommY=%xu(D|HAhT64tOqL_XLIf2KmNd&5$)UoyH_&o+FPbfczR32m4(P%M$sh$( zghL?36;Of@-|XyvK%+tiZTVMp#DnA~MX#NpI{QrapUFn+Ol=OQc~*0JN1T%c@&^tY z3I?VdQrc#&)m`4b?J{a1tq_<(b<)OQ%)1^}ox!CxIzjg?e)0SFD8O;r;?n)9FM5t~ zDoisyUaZ#~JaY}UN_bCv*o)FMxVf@xQ@k1q&Z0=+QlW4grJ^gZ{s^yY;uf&{X7cO1 zg^uPZ>EvVWeXDYFhZ1CAMqwxY3e*>TO4g6|sXp`b^JWP!<%j>(QB(ha3Oqzb9B4Bn zuRs4%VqxCvjQCFl$6vxa1XcUg$miFQ%%1}uHFn}DvfRm>SMH(szk&ah!pXRF9L?cY zjcP^j?f^ITQ@}c($rV0HRXo;sJl2ddnTw}neVHKtI&#jtp-+kDEob>Xdra7b?^$NA zU53)A4A+^?VWh-cT=Mle1jaLR#=icHK7<6gZ(@bRw506jor$$Pm}!*oPuVQ>$%`Lj zAk)96q`)+PzI9EtCwE_`R>8(&Y*}*O7K@cTNIIrannX)>U4tTSpy~*d{a|rSiGd7i zHNZBWUKk}cxFvvI`zkYkZ!vpuN?3G=#$6K_74bMto|x#YkjL<1zv-rUm^ZVM@!p3k z=PYwOMG6*%dUX&Y$yOAPTVN^1h@zvEq-OxGvPNVj5#NN#D49tXW(lj|`zvIKFp2@`hO_!2@ag&kEU-9t(_8BZ0Pxva z5&uug#Z`GW#njYi*u8Gc0$3gEZ-MC`8&UwkeiHx?E_AJJ&Spa<%BE;R8(N(j?jKP4 z#&Qd3Th*Ocj1H!S9dLCF_&#n>?=+)D{QDTrnO5M`! zDqbITV8-e-MrPH}8kbdldN=-Ir|N5f>_u%hr8I{+3D2xJU5k_3p+%D5KhAow@sIs zQ|vQt@NQkR-TPTy-*l2pPE5O!rhzjiHRg>kA5b#jVPib{tU|fZI#lxIQ-xUY)n`?}WH@#)(@)v1jC<8b77CKZ3sc$7>k zI*<3UWVARYk|&JBK{36|Q?ZRf!_1-iXWy8w8ipLL9qw@?ORmFW;iG<{G&rjgzAAPa zLOXHYDyc21IvyQ{`CCMsvM?8tlj7q{k_}U7 z7&>{rV8?|e(9NRppyDo72W+ML2OHV6W?#oAqkmo!)to?#Vmxut0z`^mph)SmMv)rZ-xyT-AnNVR=>)iHEqX~{Jc>HTN)A9Ql?^Q*R~WoKc4v0fVQ9nMQifj^7{~)>VuE@lvn|abqL-A6q>)&C6~70svUd(Qb5VzMi*G zObZx|dja4}gqcRCREZdIoujtYzb((@E_?Ia+g^SXE2Hv-35a}uOr`%GGycC_a4=7A z&)`;4(gpD)xn|0gWc4DSmERaf9SfJbz+N7#DuvPCs(Dw5R2LC`jNIi_zBKQYuqFfM zH|8*zvB->wXhJoR^F&KiAZ=qiuJo~-bDGuK^w>oB4+~`7o7I=R8vX_#pI(to!~8Nv+26D$!Bk z;LeA_^%@e$+X#*NZK3qy1YcxNg>4}@?CPZ=vwriG);vZdlbeXPj(rTPzTIRrnpoQNP?>e> zf>L*$S`{(kol_9#@&!k0fpnzlZ(fhHBf5{8CNCWCSo>f|wDuIOEt2oLlgW&*ux7Nu-ha9x37$528UDjp%sSWkeJr2ypmKvNJ-^GQo~9C& z<$drR+4AtJxl~t{r}XqxMqNSoRff8mRUobH2m^ZSTmhqgk&<1ro=nAignHqA#u5+sjqU4VQS6pf=5|_ zTOkWg3%Dc8TmAsO?!KjBbb(tvjqScIJ6M!v;KlDE8(HsRm}xY@BFhlh!TJvc|L;ow z-)QY`UuH#~*@yoLJYTyeYiODkU3suAk}K)asxr0CFK`<~SQhE@;OmeO0TsAU9OF@7 z#Me5t!Nb_`Xs!3O9VQNe9k*VFCFL^cR(4#Rz2cW9|4$Hgx;ia^dF%fPqI1r&U!_WG zs+w1(-&vj!+eu(M6vYu8k=YgeLD1x6o3Rp;f&2OQ;*Me(8ZTQKOm9a8`fh_~Kb`ws zClwbrT@`;7qubS(TU-2k<+Nm=I!n{|{8xx%-p5PVSDDa)4V)itdS>+^p5J6p&w%D1 zF%b{rc_Zx9e22puH8RKbPb4vji`2OnS!cW5<-#w>H9bGaRAq2 z7R2!<6$3G=dKs|;0YifY`anW@kmY~JT>ot@b=m~J1XE3)fRMz6xf%_oqUi(GgR4S^ z20uRdwb!r{-KoH_ft^e^tg|xGF*VS zV<-(hce6WWt-qkJTcP($G-ph7sW(g|N3_${ec@OIah&ClIIz^!7^h z#w^Zi>LX&;<*@Jc@0(^{#<*Ec8H*HD>F;pU_txu@B4&n)yCQ{oK%Na`E&1`defp!T zjxpC?WcP{~J0z>}DnAFdj;;I9VYt1Qo0fXd2LRNX!Cd507;aDQe7H1*@nQ;Ch07OY z(s?ED|E2;?bZ~E0&s82#v~V;rJ*fLY6XS9*T>WPV@Vc8PbvNXnXySrF3dxVuUxtmk z3vVSo6W*LCa)O=V#d@Ot81R7)LWCIsXm;c0Kw=>_^5}nw;Ml*9S-U6`m++{k0qaGEvN7m*iX{ zKkU071MY2l8a-Iq8@av_GJZ<%X43sLtAW{p4P>E=&^fnC8IegG#G3AL=jOkfstiZw z`Gfc}SZ-v3fB`{)e_$mSP;Z(JHZsTkyrbmY3GKc zRIOY{9=mmgG`Q>1?NL#dzsr_rm>jUEKoNC!SUCcW=$$H59WuGtpDN6?-MZPR4}&eM zs;IQUQk~EDrO$~H+4uMNHVQ9RV_|;!q;?)MTE)vuERWa8%w zX34u`SYvspx_i5bw!svD{NAnIhcqZcyHUgX$?~-8Qfte1nC{HVq;PC?{U}yIYKjan z(aTy~IP72-0K_Lm_&DhRT(JMDtpC;2zyJ6OdK7YNZ-1v?ve_4TXGU)026`+1E`;1u zrR;_0-V3!JSVcl3J-LLw9r~(omjj_I%rnkNbG?Bgow&IB!N^z0+q2KA(>N;oQ7%6T ze|j=mU}MjxZZEFTyazUnk=_gI(k-RcMPz2YJZb`V)GFN@7+ffoR1@7iIqt9%F@Cyk zEBYPBe|)d<0wep`rQ@MZHGMGNh>ocw{-BU*Q(5F1C;aZ|NR-G=FurYc`bDkey%UBk zsNPG~Nb3|so&lFH&h{|^0vSp(l;8OFqTiE=Hjk9{J#hadu2hyqtYqb8L11PkIGV8Y z8D}g_43{uXZZ0)Z@o`9~yzZwe#@3&2`J6xEz2(2-{OC(Y$j8wGHpuA_7xz*=JK@~4 z)zn)tIe+FB6UM%OeyZRQSPadsTV-AV#*}Bt4It$)#vSjWJ6>@(UW&4CfniOQzjvFF z_6}P?UVcc?uO;{qE36~(ux{8fBu(qRXWWHn?H5HRJAyNN-;G`Me+s`&Nj<+| z;e8?_9Kpoc18ut)*ldr2D_AgaaMb&IJh@4e45{hgXIzq2Jbizu{W<-}-9D)w2|wZ? zfUr8fH9Bo~?CHSqBDXmLWo$#_`eew_6D}7G*5f$3i)9Zd%d@CJnzy)vw$G1?h3s9b zoP$>fq_vDnd)qPAfkaP$7Oc-@XAl9Nkf|4fX##Ku_uowq@W zJd75F;<>Q)&e`+(EvoKFT`i_3+)x`IVSlbxn)5G#LKXE?Je-VJ793d+|J=49N>0HR zHSGTc9aFX>qGpq=V4WWCYb*@f1Vj3^d^jUgsnBzgny1WzE$|KAJdSA-=WXuO<)|-F zBBj!_1jD_zts@!Z;nsa~8cpo=!S?emp?%;_ejO?^WOm;^j;1dU=HRUGykDbAI&2;| zrE13GZ!x;OX9x*q*9}F+u|OaRXWe+D+n(+xNv(sZK-e2kr9DRU>8c!mYwrz+c+C*r zX)|KT%YVO>j*-_iVo1tlq);icQ?`{jxaz))Kha26(#YCC?phZ*Y7$DX`xLMFn?ma@ zuDNA%;~pG!n!@e>b>-7IqOm=S2&n$)H`u?TrcRFPBhUa@&0t{2Nrkoc8wNF(#Wy=r zDKJ|1^XS5DzQOHGYy8F|5(dA@=EE_OfS|B^y4l@g;q!=YHUH4!is#@xC(Q;YQdj-! z=)LfV7Dnzi0(${&SXUb;hnkeRX)3C8LZU62mnY@QZDn3GHY2+(5^0^nMMGChzP4P{ z?RaCGk&aY@SgyPi>0UwM(r8k_L)G4)w_t4Lcc@A-$mUPhC9+iSf#egUGS^)#yTRUL zq>qZ;hXX`7g6}v&TW4o7Yo5L5~83c{}lG?}13p*Zsn^MO2>L6Tb~{=kNSr`4I*OGuV)ZxI%}CMDax7?-dog z1L2A}(-hf$?FWi}@+^HWjBs7Vz4L)UJnh4bGqf=~>+?z9QA&AFDf#m-zK5%F4i>zI{9L*4s$wm1mY_*qOXLN4sCx0(4fyewS(_|Dy` z*bBKW#Yt#UVG{rvskjq@&kpGpJIG8Fi%(65s+R+K)`LRTWjWi7vQ-(4)@3HGps z-?lPLVutQDeQKE}e6o(n0s@1am*0efM|KZNpWk;p@~h@jueJ*GT;14fWnPox2GZs{ z@AfRw!;*7S%uo0G3(v2pc~t>`m;QgA3jloeXBfE5ecDvb+Jr!&EoEeGUO!#X4!C>g zKddyDALjW1cS;Eb9tybU(waUieF~vknjpQ?E+xB+3;MZ!CoG6jURo%nOITZf02_fe z;rqX}6h^U?8Qmv8KgIQjTf2C#Fq(e(kRc#(a~ZHn$oq7}=wCVaahTxOBEkgFwYc_4 z1N@jmhk4SZzt_aPrkNi8tZ;gkg<}PF55M8q-zZ=4sNUezU7LhWnD{S(J50j+pVA~I z-q$$48npoN5fOIa%9BR(XxbNg6r%mjLliyF9w`4oLn@<3?xqN84g-ahyk6+hC@&ey zVn3Oq!d54gI4)nbNYZcF=h!;KtT`XM|N4vq7p{uZ(o)8#zSTD`e-B3V8Nmn~jN}-z z;H$^l7QN8hzJ(FMN%O1bA%X5$us2lmubDc)2nbcs4F~kn%@f8|Ve^A*>tN?~3!|7N zuo*(Wqz|jhCM&RsM`TxH%sT|cFj7^lZvvoL;o>{C;j~MAsFji+4fN!5&**T>G$p@&9Jb&V!o2D zE-B3EU*r@K5izgU!0v(_9-&r!J*fsZW5{I}b}D~+62pJcv40Zq|N5YYf4do${P`Dp f>$iZ8XLMW9UP#Ijp+4*>765S(8BmFkp3naUHWbz$ literal 0 HcmV?d00001 diff --git a/Images/Info.png b/Images/Info.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2f3db99d8b4240c3538e8aff8a1ab74066ebca GIT binary patch literal 70967 zcmcG#byOTtxb0b3u;2uOy9NvH?wa84jk~)ACxqa^gL@O)C1`L9jk~+MP3OLuJ2P*+ z`__AF&GbKgx_fnXRac$!efzg}MSW71K|>)#0RR9^PF7MC0ALyb0L};r7WxikqP8|seX>na;4f+z(O-kEM&C$Zm)5HY?s9CwYxq)2FLMD&_fE0lI7-6Y2bW8?0D>>Zb^PUFl=PQZc>cl&e_NZE$sTIm>f}N5s%o zdfm!S!?=CLh(y;F@0?q{UGJ6CTR!pk1I|I1;#T~x_}nn!NG6h$p_tOt|7!z^uYY#c zFf)MMR94Env3$6_#t zNx5|^e4_GWfbS{~CLG*g6`qE1&*?k@UTftCCQ5Hj7YKu)O zhDgPC+bU)HMc2|o8$GaTwf1`kOi7f!%k6!l;j7~QWbX7PqpfgKZzNRZ>LVUzP`UYr zY%n>kSP_%WKFnPUlgXGY7e|~I;CEwT|C>=q{HrKACN0w_ld$>CTM;Q_pOJ`Y*ED$N z0#S0@;S~HNe~VisqDvuKe>t&-DfsG7nA~)Vm<+h`<|rD$H?k%40ATr!3MnH|fP1LJ z{!bDeh`Yqk0j1b%M zKo$O;pBa&wnr>H0C0U&`bbnzAFAfh}B{wzkO9f7xmikl&WqHy|(CcO@@RW{!$wPQ< zTKMG?ORf96^#qo17JHV#xl|T*Ha0p^sh(a^5iFpGj*s3|qvOuRyF6fw$}Sx1G3BRp z89T%=T}HHHTGyZD_5w;uB!ZF?go&vVNJ>|Ksjwv}u^huqgmT?yElA=$?ON(0M&uF- zo*Fu=sjy{xLuIksSJ!v@Wwx5$(GDOI5FMyIMAA)WUdQ>bdS1&PP)xs{a#vkolp@;W*W9bE4=d>P*~ks6xH$7{^lJW(tmT4Jv^=regI-gzWS6g08j;M|VW35sy5;AyP4 zeFADe+A>ipuzq_q@A`ow1o0deSr9N0wV#c3U-iO{ofoOcM;4TynGk4x+4PC;O4QUR zEidxyZou)0S)rZL-gRqtzJjZoo|>G}Rs(MlD6 zb_*-5#`sluykCuh&oVm<%+Q zP~~h99}IQgPMC_Gq!CNqPx4Qv%yZ?!X-sDyQ3tUQl+(VW!;0&VmQ7CNKFRkQ)E>88 zlJK&D$@ft)XsPO`mgA`D5reqSEV-17@;&?v2R)1AGv|m3^p?43ej_VZ*zbcUJ$E9% zI`Dd6eLm~WyNgm`rbL2y-+`y|V}@(LWrx_`k4XodiR~^~otcZ?96sx8#izF;4@CEA6FsOrJxefxA!kUdFo0PZHW= z)F3u-BuqxGNUW#{R+9K6r>P@CEI}Ir!xR!O@(mQ>wufv{rf|z8MaX}ZVvcg#V+4Z5XeEs-vq{ch$6N7VIk**OgWF+=rt4+pK|_Sna|Q1Jw}X4`cR+W zNhX`5p__7{sH)HV@FSz$?r(&l!Igm{!-~EcS#0Op9o<5b@f9+*Ef?v>wDJ1GxOt-^ z=r$1-#$i97*fOiB~0jM!R7$neudvTLR&V{^rx?W9xB%SNPh51p=0bZxcd7E)!U z#NsYnhIu-Cy%rBq!*#Rh;%TwYe)V|IzC4b20128^x+)5WOVBO*#*bV5s;uC%2kU&u zGEt6~)jZ@Mn=Q}QU^^=)jHt_h?LY_-o_$w-T{eslkNL3-lxtJ%F+A649KW^2Kr57D zvCN`!Cgp$#tx`E@Rz-EoJ1t9^wNyrFOv#cs*kVx$Rv;7W_Q7>F4f^~XYZ;!xZoA`> z`HH+mdhwu7l!Mry7sObDRA&2D4w;u+pNaVu8&ZESs`(WQn~9PH8g+P4cp9DFtqRRZ zFIEo$@+)@aOZECC&T}F*{Ap_8;aQ-DT(yIJP>~$k3u#f5fNIyGuWEn9Nxv2+@NkQw)e@`0SD5P&LCuIbk+IS`)W47`Jc3QjJ*!p6|hITbeXm3wYz17)?1f6%+7nlXq`d(0sepqjy4@(0mA}tbN+TF#rC~-kaL-&A19+(`Cq8~2+N+eK>dOCcL@X|1%c82T zU8Q#dIW4J23>K=IA?d+=~4jW|5U zDE*_gm|KexZ;bVahNYREp4PF8$w>}c-w?b>fvDmxB;c6ycrYmnA>_S|L%n-J1ku%x zm&^~TNC@%bX*IGU`Z?<#*Ss!=E5v}a`8eESg+AC|(Kg=NBUCyfLMA%9iWx^$>Ru#x zQ=J_LwUVNWOKqK6A8fGA!)HF<$+;D{?-i3^ zd>C7vh6RZpxmFKnZEZ!d?QAtr$CP0TGmZ>OXbeaFuK4zKM%l#{viQIL zt-2a=gj`=tuK261$tF?%JwwtdXDtrm!oMDG-lh5zIcD_sJm^@6YKFkNgwF&vS3A3s zYZ(R2i&fWe(#7JpRSqus9{W*gwTgYZ^}%MD0SgjN!R!hw;6$Q`9~y>yxiCN3jBnyF ze4Hl3Q|{hUPuXN&wqRL9HhEA5rvsjO7YzV{I}6PF?9e8VA%!8vX53dgPI->l`GnNI zNRmEji_jrfgm8yl7ZZ1)k!pF5wTnH?v;iYIQE2gcHc8;XVx69#Q(3!=aZOqIt)B@Ch<7+~GIKo8 zxUQv2xLmxM3YGZ-t2CRnzV4aNC|lU!+#C77iC8KtbNbFh6EaIc@&nu&ZVsgXRA3NG2pZaNPlmvP#?9jG(Z^hj{^?TjJ z%K8tYatY{#A%5!W^zU%F9Q>iD((QmtkT5>Qg+Z?L*IOsz0cTGiX3YrCn@uH0y$&Sl zH$k2Y7x^$=*rPhH+L9O29@GBXQh=;{teKZF5r$||ti#x9whBz-isR+Ob3kWfzNBnY1NltjUIBD{sn>32>cTOsPED2`&HzVDap^}HPZhED(ii(l+^~; z*L1ys2?1uAZ(2?Jw25E=BrO|v9m+k&$rBD=iC7LZ{}l7Gjo+4Bdf@a?((a1Uc=#;5 z@@c=ZZfxCp)r&LNngT^B04bm0Gx}-5aZMOPp0*MBHVh!UGW_{0sND`Z_Bkr_b0n=` zl4qKVfDS+|PJtnlQ3a-RK0~Gu1TG@6@UKLwqtE_Gu%=BV+8;EXt7&em&vV+k2lva* z>R0@ zEh6vzVjH`ChWvdGW#M-y4lALX?9E>v0U%*idb4OUi9%hqWCyF8;36FXNI_fP)b?mH zUoEBd&i?&QtQ=Ct*$V1kenSVGb%E-ekn(?AX~L-_bykfyfk&QnDp7$yaECql8Xu(U zWk#4IqEM*|^-%S97e{w(qLRZ9(uN9j>NMdg1cTQ89Gmcm$cdm*g(hl6X)#9iJtc#8 z4l~n1cY?%558kQ?1**SvSR=qCKc{VOp%NG#iDK5ybs+d8LKW#G+pIG0_= zUfpN3ueA?n;jk4^)pTD_u`K;SxWF{~^G6c!+y zcwRo=p-dGo&j8#qSJ8)=s8WL=gc9ZReZpHTGzLs`T?=CvA`8y@8scJK^?jqbxQGnI z;#&r%oVB%DRIyu9?>5uxtZl0prjPn_+YNK_smNXYm*yX|WsxoZbO%jX=WEnLk9dkAXrk$fE=QH4Dc;H*b z%^Nfx9k<2|r5kkLQ{MR4|4!kaydfO*t2#^>p4oI+zPJlfy~azvRdb)js;wQc7A|0U z8Of<_Hr~n2qV}{!_xr4WRy{bsS;LKA?%8;sNAp+l`FIh}Ku+h_ySR82Bm}XrEWGG5 zcljFdSz|`Tylr2Ggkv}55v{L%j$;^T`s*BX#guftNC zfZONBEd3*^CFD+LCZYhuNCkw5zAp@k#2vQAHV>xzTxm|~%3btJy*=no8GjhRxcCF; zVl~4rlw~;|Z*)ml{cUbd9f+aVv!mf438M#=>LLo1aFB>DfoD5MpUOO1di;4$x!yVf z2r9WDtXD1O)&tMk1&y}APO{d%bW1663Vh7C(^S~pUcci$zStUId5xV$a?TBxIX*KfKHv%j!j8R_C_CYAEn zSGRoL+UaB80S(Z2)S#f55uunKNgQaicT>_Ieu+_nQK-&L2ZUO@M-gq&r`UmsRA<~d zA0K*fCE?0QHqGgCnP)ZxgGKBiLT+~>i8ePSG;8c9GmBr|<5%UeecSx?4$5X9TB*5V zeAa&nlM!X^=+k21{^lSwVa0Br3W2bf<~3NR6C^)*bUHJ0|C47`9&}UggBuqrm%(1u zs)Vt}U;b&TJ^bKKRs8=>U;U@h`Y#6m34$kZ&fo)J_S=1LD>Oe0*T4po3kni1;!<4I zp`oGOKL>~-^{`d7-z-c&=H)yNPk{BCFE-;u(9zJ^?{_k=qa?{O*M3VN3E4wizik)) z*XP^y^>xR#b7>csi_7=%T44SA{oS(3JMA`1-&m;+I`2hBy}qxxw#Z0GNBW-*N(3@j>yQ! zujgd1=YCSu!!?c__q3#>p%WYfY+tAM{W>UB$uVWAu?4x2axG6P8&@uT+M$~WkBHd3 zv<;oOgwofyXlO!4|D-Fc(ErD}m+Is{P&plDG|rEun(?EK9HYB^N)v5uZF2{K$1O%b zu&kh^zWXi`{72-ABhzQ^aXSShpAp2EDFsC@AE=(b|D&BV;9lD9ktkkWxP1B4_~|>y zvGu68 zcm52qz+}!2?GBsQ_2WJywXdkA6w5|pA;F8EZ(MCiKqi|^eHZF`$fTk{<BjILa)vQA)biGL3J5Z6AM%g*!h6)HiXmp2|F#&hq%)>|H zt*Zo$L7@|_alZP?%geR8r{6x(!2*$Gg8~80v*^e`knA$Ft3DC1(S#Z}Sd{ye$bE2Y zpNHVkp$&D1v<5t&0u>o#D-#W=Z-9gfS0`diRW$%JetPGhNLsW_nuK;bx|FfSNC43H zHg%JIrxzCbv3g4oC4ecGYIvohwq9$4g%QNYHFmB*(qaw^n72<3J7+dZkcg-fW--0P zf;I}8tHa*0r_i-uY`J&}C4y%X0V4+I`ng zl)<)wWuV^BvQ-yF_!7oG27{ctkvt~kXYuBbD1@M&?Ugg=qPRqtg=%k_jvw827DcDr z@U*IWwi}+JClrO1HK z9mPddK+uHm$caF}Z669@=1vR?@V$tE##1OeK(j`dVH?JlOOMQP!709_qp-)6F?p#b zUATOP#I*(hn1)}DlCi|caaL*{Omh}{U`0cN?GppZ)vyE3)6vZWuOa+{T6tAL@!vJU6ddki843)BYs<=Uj zdzG;*Oy{AFky!>|kIX5DOi(v%3ODFbR_r%vEo=HDF{DiEL*c45sN9AwZvG3^YtGCN zQm&T&&(WGAgOv8+2L~NJ`M+FRI%`obD6UeXTf^^gfyK>>F|AU~@}!R*&kk8Hs4*El z*gvj(5#(Vz)WTErfOCX{aal9z@Dz>a4_GfWqJ70#sY+eMEM+KRz)!SXy3H}4WF#OcvOqB!s&WC7n}sJ1p~-C85GlS0 znQZY}Zm8Qujy;2(pbg#*r z>PbOng+ZGmY%JgNipCW0n=T`fUd%%7^U}L4UH5eE*tCe2{kHk!VYaWUD~?5;WF95& zI>-IM{9@Z5ef^a>9g`G`S=EM+R@1mBatkIZWkM(2r`Fdl>+$59$o2#!Y$!JK=VVf_ z79Gr4$&)54RPmQ9)hdc(iT825vv`%zltvO7z#`~(UfOgQVbyQ?W!i^w6iyYIjkwt8 zDB`v^5hI8XtvYv2Z~psY{l96={_nurs~D+*!%r>bOp3p>Z9x(-V4H0yGiCgOfzdra zS)~&978U1DIXF(wtCdEJ7qouYbN+9Ir+ux(S;WZQEx{i&teOdkPTj_NmyywfX`Dq; z+)oJc?C3dytD)zy#Nvil(J9dkF0zRRuV3>yL6Ruh{;d6fjyTTapIWL{H8wb= zNxR_9HU1__o>KVQ(Z^S~f9$(uVglPY&;xU5JBSrJiiMv=#4f{%0)flwS3gAe2hop7;!Y;jhp?9Mrn(r(wAxV zEQaxq2{WKVYw_7;-Ro=*JfNd=bd=GySsC{bHHorue>@b)S@i$P0yL)yR33fcfiz?& zw?Ze675nO&8fqR(7+#DWJYvpf$)S)@K9mi1GFALQ?nmLaarK1k$LjfvB(-0f=&9Fz z<*08P&l3La=D45Iv1a7xZ7YYY-8qP zMN;6=I`M<--VrfF5Sx;o(d?W@OJ()%xds`zAb0=WboumEV`R@pdhTc0btqLV1IX2%a1@79d)J z1S?8?30+TdX|`Z~1#s_G8+gL~Dq=PbhBjwKvjK2Uv)ew&4+42Jd_zdamB;pt752pj zqI)CtssLtTNsv>I%bY|YhFj?J^a13t>n2Oa&Tyrc~V|vvf&D+8}7Yfu*{#Pq40nob!6(w zS*3RSb8!iGTKjDM7CTIIMbjxaaBn;@zHg0WA^=^wo*$BCJZeOfGYY{vri{W?Z?cUa zutTUksbBZ97?1*h1pNgMdad^@J@Hn^h8$4SNTqrBG8=5i7u;&LEIC_>rzRrX*r=y_ z_@RNldyT5FQ8t}A{O35A=nvd*oweh;f)%f+{q1(ufzL_JnsPy0WFL7Y^6ijxzvj&A z)q9Q>0^Csmb85E#F~l+q@NT z$1>5bi^V4h;Z8e&#pL%qK3fzPuSQyHs4N6a@vfTi8e70TdD3Cj(I{-;jvqW!@LqyL z^bgrq(XXe>P&p6XcXm9Kbc@nphVg0XR2W!u&(g)9yK*2K-LTVMO+i$zX*rjRdrLIj zV7+)!#(647ZdI$Fef@z}fpJ0Q;6e?4tm^HtkodNT*#KRc)`{x3(GeYGAc$#DgZX{O zyUBDF+Y9{Q?Vr+WmdSRLZ18>_hXsf%=7kV#Z~}JfeI1(S;dR)?WKY7hG55(QIX?@t z+AxEpDqEfumM(=i?YiB(-+bGc|Ia1bP7Epwj2~}~BhX2FZ7OrMvzop4xv}X+F+W>`t|6M%5Iy2+WCts+x`^f;MEt_XSnw zJT>kz4@Gt#r!NSeQiW}urI)#Ypx>J|I;Ma0*mdOl!`a)fLC+-BUvBzq+)L{eQ!-ml z;zqyy9?ZEVUKXt?sY8bWf*LhQYzhYv|E!c4N)4M|HEhCp)&JC*sD0|I{i1ciZST<5 zUT38jX7NHa;n}S5>iqjbxThM0)}}vprz5d){nh5gEMVKVlqQfl;gW(47pcjfF8%7@ z>E%mc)mWtWJZz2YxMsWWXuzhJPM5>oUXPORZdT>sd)t|e*kr`YFQ4Tw+QZzr_w7H1 zp#{pt#K!$DM=+6mNg$DJeNl;RoW>w6CqSm>Pct$7TYht7YZxeh2~o_DnnWOcsP0S> zaWrA<3B`E*o*tiGN9C!tyx#T~CH*I(qYNR*oKad%I*U zhYc2mOevE?Xvs#`@U-Z4$B!JUjkEiWmW5zhC4>6h3k8|A&mSm;R@zvqs}8@K?a>HS zxaO}Q&8cMVH6ya^@lLu^9oXxrdRaw2Hx7;W%p@PudQu>EfYqwnpKtkVp2o!IZXi{=NopvJM0HVm|J&Q2&1SOONJ*cNRHk^^ zxhnR_{Pp5e*uqn7@%(xJC`xFCu4p9u+Pz^Rh4iRxV)=gdZ%3!YqPxf0=koZwa_X}y zzuV1WclDnAyy)(|;bZ~@Y@Ec}GROATK%NdVR^cB^-I9*O2zN@JmAbB-%XXL%=JJ=zcX4GSGBV|r|jXSV^tb}O`f{H>l zmt6-SFOO8P_QcO6Ta{9J9yq(Mm}e=B68eLvKj zJH{U^CJwy7bkPC8x?ccq*EY^TN3!jWwd!hgR3b<#Ks!4N{)-n(Zt5~eM$Fet0sJ?J zfj(>e)#uezYN1t^_ktd5yxw6YMl`YL_+~g}a77u<5|}tI^g8T!YQWvotPwc>VB+|}d%RD9UT_Xl@_PJ&K@`Lx`ajWodc}5@+&vMjv`appzUOJEUJf4FG2ej@cxz%7j zA(2RY78IqJvqV&dg!7AkivU%82Je}!q3PVkG6|8pJ$I*;|bp8R1*hgiq#)ZI*fw7zCgx`*iS6>CKk(Zi0 zzT`Bqoqulm&NktFrEaAE&{}hrdhqiiKkrUtWsksK$5UloWVbJ>Fsz;nvf2O#~Mu7llK~PAlw?-MZWQDJ>X$@pWbfLeUn2iC&kJ;G{Dd z5w^>7s{Lw~>qC-w_2$Zd+3qfb%OXG53gY+!o;bGkhVJ;;enTboYKCqXe9yT_vHZIW zgQum^Pq^!o@y}I{oy(n**-OH|tcygUHjI$)ANYCnb{u~RA^6~e>L&;0)xD3uJXd8^9m3L z2yy~=nmZYJws*+o6qnDe?M;@F^wNtuV9IQUXJ?)Fb?oHw*N zt#?Pi`(RPKqxSH~c=Jqv(0-s0ahYd0^Ccp%btjo0GKYlG;Qx3d5rHmrZhAheP5$2k znnL_5F?#MkTE%b}Tb`I}*PbpRDFVTG)Ap1)pBnLuuZ$Xd7dLuZnf-AC!R?ueS3JZkQ*1z&qpGMWIAp z_<{rBvByGD4_|j*;6Z`sWA4B|>8ergX}E&Vg?8Vd6Yl1aIIZY`r(J#TOsL_NQpxDh zJf{4TprGqr(01_91L?GfFyg?&274t&_1U7ENRB0(+Eykouz9^#GRU~sm$C2o)23HS z|51c3E;QQD{&)V9f!3~NGfFZuZ&N#(!`!b}~&!8%N^^V<|7Fma}va z9|3_(fGi((wiux~Vt%QIdtm!UHD0G()0Q;HsY0W%9Wo~D=Rc0GQLn7)Ic2v{N$xe8 zi;sHS)Y978oYUjTU?52yW`E*5yrje!J<(!IJa7$Rq8bS{Jh6NL8_GeU68CEpXr-2r z0c*R_hws}Z=_m!(z5Okn-QVgo%5oy8@9nIwNAb@LJ_%zt9?7LnucZ<=wja#h;4*lT zVeVV}Aw|8|D$cCaBRRqU*tDsotT2@{6}i57{qvf{*ZLnuqPW6+_urtA4r@ZY73YoY z3=LnNGpbbBb&;ljfZv{Fqg)0RJ~X;;IUSRcb<`W&;9kr`p`w>er}CYeT6Q`nZmt-8 zBh%TTe|&M?iR?fAGduZPS9wti-q^G;@seMk%!Tqi z3uk^*;7O#cHdWf$6f}3RYo`#AfDH%m8E3QgueB2U_k@Q*bQ94G`2x{$iC>l-eC4W? za|Vv{d-FbVh}ZpqZCpgl6Zb!s_dG^#=h)*oX5v;Yd}x0QqcYq;yv$s*Q#X2@)z?J& zwUd?m3J&;u3}#-d-yXvcMGlST?<{IIl!DSRgK`hs7&oKi(8tBMhaW_o=U*ff>(W;Q zzNIInb^K$ja;fJec=QLqU8D1^xx$a-eXz1>i?xxJca!cp6ZAD}4dj78398GFy;nMm zy|~v8ZK6^!MPq@;3@F-Ftak~dGvuhDNd4(in8rGIfaHD7p1M%WhSI@?8Rc=YZa4r$ zP7S?998Wv4a4)|BAT3(c9Zf|Z?N%2b zvpoDsDO>?B$+NCg@xA-2k(Y1WG`Jz-yZ&qZ3HJ%{ZZYJcyE*Are@#oF!SvElp&d5_ zv0kFoP;p~_VLW41c^`jaa_nfHw#=}#-V~&ci9lmwBnl|=GfO07LY3rQg0DVE7dpPM zKTdGbv%WZ^jCZMB2#`0L*>YQU;XmU3@FD39jCQSunvi>!PIWdc&?2{2l-x2dpZrzq zGP!QYbZjdq)5L!Uaq8T&eZLcx;z&yQTW4Y2oMo+1j!PQD^l%5tHG(&W?+f3_f$4l{ zvou4B{QR)R@D@z{xX=aI-e4vjKZt8#1ojer;iN)-g-=k1@u|XYpdfl}kO%Mf+++`7o5i?U>)&E{ltc z$3o1_8^8WNnoKVZ{^fzhquh(G=}4;x{^Mj1s-j#ZW`?&Qv#vIEOb}HVQeusI)CG*5 z5X;FyJm#efT=sjDlJLsjHO4}h!0fHNF>Ci?*|A3iN~bY55sS{pB`Yz zG#F)S4KwZ^O&?@SH@?ZWE;7xVYhnkrS{$FE{c4uTD0EwONIhkORGmzHGNl-|7jnF2 z`rge`lz!~v`nl#yyxwI9_{awWc@w5U4{`=6N)C^ zP30j`>gF-pyN_J$>b4efy^`1X9ENo^C{|SgMGy?K3a3rVd`Sx_gU*=gpT32V)chym#!eWC#Jklyy z^;ynrA3Lw&ki>yt6Fpg7QWl=Zpwzleu#n*4Ald9xYOJZlj8#b!(#rR3)8d6CG<*1# zvge=Rv}^xw4MDa^lb+{L(rQwRN*P1jB*oRxVu$}@u0(`kUNaQ!`JDX2ffHX1*JfXT zz@caE$^3V<{r?rD{11O`;)l^j`=6B+L%Y3V3JMCC#hV#EXmc?a^Is-9|L@o)tApel z2akgf+~$=(LF_t12WZJM$6u&&uDD*L8%|_blb9`Zh1)ywiOi#-YC?9Rh~O)HRdH}Y z2b*7N!TUX(v%1NQivjFsg2XcvGRp_OrK-X;-;O%->wOOY$sfCqSr4}Eh0b;JxsP#I zb8j36%^6A$Up~z)i(Krl)$d%a7EHx}7&=!pXSpDRU$h84pJ4c{r5{ z-^7)JaOB74J{2{7dnNiEoDC5oQngNFqLDC|Ugm)ZzR)!e?VVRX_;2#h_rmc{lo@8K z22h-xUBDPmIA5%NLn~L^>vM~CS#BuPeC%pcIn7#X@Y45fPuAG|(T2bL?kbc!d6J=g z2C5F9DmH~DnkAZZo5?o!yswx75AW8JTa+@099fwcG;`Bl-&`3u%mnKH>KJ#b2#HE} z&Tl?7x4r93e?&6s+3gYGZxsp)%MV;|`vdXL#pu3gy^i0et1M?4kIwj&xDf^b+0gqo zG*${?8je52`Z7;=UWb|l{x15xInX#H-B1vN{zAcp0+6%qH9K$o3Ron&qk9F*5gtAL zY68pL$Daos6{EUu2hR1sJ~G6<{n_b0@H&MEIp!h#{;XhRxtvWRCAK+eaK(B2sB1H5 z6tm@r^cDfuVKIufoxk&SZMB@k6d*TVt}1O99*$FId9d_+&L$Bs6!Gx0#co%}yL$>Z zqt8G`1l~ihR016Q?OzM+Vb0y_O^vH-gR>KUypL-aFmL#WQ9Uw){^qUv7s!1jYy%0I zRj}|b8qELwo(eV28hsvupuPrM6^2%7Z@(u*_qd7}p1Ms@ae063GTthKvh zNls@xGOB*|+hrcr_qC;~_;@swat_0tQZB$D%)`gYuCqF?S>AMZhe%N46vPuFykKf@ zzK^_!cqMC%GX+}dR;R5Kwe>x53ieS7G7I)mM`pZV+mkcH${kIV(lc8E~9`(_3B zY92)Y{i{L5*y*MyGPlmGXz+2|LRf@9rOwmibvW7+|9l{}qoZQ!7o!wyVqt}*Rt_Nv zCz@{Dk#=K93XNq+IsaosKN^zK%)w0Vb>I&9Vd|;j#l2HimE{r==YHd3Y@h~f<~rkc z=NKJk5JjYp@2|@eY2m_hPL=xEyHs%VhMo9uHmQR3%7_H zT@vflxrqHLTpLYlXTWB^2yD-CV0mm?EmNEh;V(nNjvh4qP9Khs_gPI5DI!iq^&H<@ zq&Q4MFVZWl9@Cps^;Gzw_CmlSex!=x+rTS4w1}>Wk=_2=*h9QHqFTg? zzoGJM{OvbGT#5Zn-yNB`g}8hDyF>~#kjvTHi#SVppdD|hhE?R$%!8+%WHo0jdrr;Y z)Obj#sdlE}%6K>IWURC3=GlD_Tsu9+e1ZQw{@1(V#NqUeelcL@Fi`F>2X(+lW5Cn? zVK}hgp5N=@YVgacl=~?QAxrV;Fv;SJ=c)jxKQXXM@tV~3S%#jsl`-Z>8q!4+oxU`5H4Q3b>&l_+U#?#?klLVJrFZgj+2B3k3K3$^;m|h&o?JKm$hRG8Iw+oh zaZJ&*@aIMEQ?{Cj6b$rMdn*bFmF({I;q98U4DJDP(VwS}0WT^L3{-YiD}MK7^yk9h zG)rHZ7gVpuiU?c1&YaYAmW=ZyFOJRSM#t<^w@`k>tmWXpTp#j)I|5&ksufu?VN*%v z&9P-bQcB26`J=Q(M<5lIuR=<-KNTF0<4PuWfFpjgqv8hK8Az(|@NRivTrp6UHGt90=O<<0&X)CVj3bz8*elMhGl@VNSfVz^-);+~(hVT8t9jsM`!Npqf7Rc~gEGg=OBZq$ep3?i*q&4Z327l7sMf z-UNw72jOq{Z;vK0%#T*$hY*pLh={My(R~mR5#c!8>+b)9rojZ|0%!N;;zm4}NPsAA zJ7@K{w?FXnG`jNDUiyWwmkEUJs8i_>^jb3?!g+Z!Lelo>KKOSIY*&tedqM^o*RA`V z^%>p!gTrQmARyjxe)X^0r>wc@CU@J-K&9ClBdwygNOOmSAfrn5B! zm??enS6X{KX<2UNPUJ?3+_mKC)L{VjoEr3hZ#1gW9OEmtO_0g?i35}FS(}Q z+^?y?tX&!*3g=)*z<3;e5e4pC?*YL1O29e7zQNZP7D5!IP;Lg0zhrs#`YejPS-oBe z(EdRYXum_$XLbMC^?u^Ehg|e;64wi|YyOV7SJKXDDoRg&_^sy6O&iQW%lP84R4eT` zTR7<~`~wR~>EkNTHVCmiV$plY^^5X&$}DHjeS;*P9H_|ED^75^fU!1uw1B(M>;k3!rcig^6 zvbQ)zjvw(FwllgcNpW}@^3G+~N<6Y&nsW+WJCBNbj&_{%CJe2YKh*QimvzghuNGIF zG>Az<7toQ|WV&gN8yDz5>a^CG{JG~lur zj>KlS7#MF@9Gg6$A{9E3me6zctV>0x}xLAK?XA@InbCkD0xWmdFH(OhPDdpAG z6Wi%%QxnhT#>&@L*WBEbaw66-oQ#s7frW=gGn{PuWuhnK9qIO$@604QV1;`FvsLZe z4?tTP_%; z65g`DpVBouL-0D@B5!89Y@`>McL^B=+UPu=;{INMKefK)^YJ0){&@b|KH;iW9_?A; za)tk}9WjUK<0;tUfi=@w*gu<|W9!FvC=qY^pFm<+?#KIveuKxvn-}i$Vugo;MAuor z^p9q%5v(c({U=sd*{$GUbj0^W_;lOP-1W@|aog3cGm0Z!2>#p7+-9rkypcMWtzY|v zd>J=y9e)14X%wn_{*DA--a3edcT~8@sDcK|dIh+9vg5`_%I9u~$C#cyb1EMXgOTw! z5361HSU~%EB@S%a&Oo5+S!y#a>A%e7*O0RoSA3R4rD@t=G6y%kd+0gp674)5*&SW3+T|(% z0l;U1urmH@ndBOOn|qPgLz{}~!&t?=d+u7!>Xv+|w7dT+3$Xr{>CuW)KQDzHwY*Hg zn*gn5J{jXPLEw{>(r@(Z7P4sWN|TK=G=)lc-yt}^w4GCLXMo&%YU>D`F7E?Q$3k}_ z1KW{~+*uh+^(b!^akh@xmsOHxX!hv2 zjrzkIz_jLu=egF!sHf;CxVfhOpc~|#vVN`cD1xD`MYfXc@AREDq431&VXx77d^H~J zikMZ4EN2crMnUa-Ze>>egHE)z?hs>6Za`0HN=xl%D+FRSl84fKk=^*IT1CcgF3gT`A;`l8>}>Rd8B`@eg9 z`5Iz)UD)GYbyO?%w8*8hWjJ-~UY@+QO>>!ZX%-uC@VmirHDGTdH?<5uYQi;$tg(U( zJp*PG2%N?cM{57tF8lwV7qBJSh?W-eYp_7q7ITCKIiwBm<}>P5y}9(z5X-2AlJ9VK z$W@;y^d3I?c*~UcM;-Ah6#}3TyDyGIcpS_*E7y$P1&Y4VSj6poQ+p;AW+Z5zsC?vE zrO|qkN7;TJ3fkE2bFz9qc#R##)NUhxgbn`0q-Xfkz9H^-fu}y&Sr0YTw7E?C^}5%f z^U!Wp?K{gmR7!-6`->6#`W-zcLxsJBxhB-_S7WWiv|pNK-uYKFs8y!A*a&Q2ANaAxz^hOpA^mA^Ysp`u#Dn ze0jz7F+*7-22eeIG_w#QbQ03jLW~6i1WUpKW8*FyjcKLVpm$1NLw8-XdG;C^7FY>K zBV=r;hwgiS>WGs6dGKRVaJ8Sei8(B2rj3OK&i)U^-ZHAKuiY12NL!$IaW4gmyA?0) zS|n(34HS1R#ih6vD8-7q1aEP7mryKtiaXr=-~FC_?zwyKGwy&786hNN4kl|o^QRfZ zIr^;wd48 zU(Hn&$w!U-LgX#LmzZC_oS{5EVG-Qd)uIDgw~oI)FaNhb^$a3E=UvX4rhqGpfgpWE zs)p{L>O7y`tE}EUUYo?6?@&gc7ra#EHc9sh^-(FMR3n19H2ZuJj0$@;|J_el(??F~ zt{s1uReX-^xBif1Pk0O_1K#JeJ@2U`>Q)_zz!Serkf$ zP#;&;Lw;((tG`k*gfDOIPbhHgonC>Ex?Bs?eN*S$n?)V795eD=TF1o51iGorle8QA zHw2xgSgTVBXCcLeUcG;IY&^E>U$Z9^F=aH@OstRXs~9?Ng2LTRqKe;Z`S|!$h!n>( z&N8GGkGo6mEhbgGBlMc6OJz^{9klFzHrV9Z%Hzl#CmmGrTkh2z?_<@g3jMg6@9o4c{;y)k{Y3a~-B2wy) z+|$ndt-R#B`U?2#-EK%gANeYJv!K-9%DzrkXfFHpkZ4hqvTTbaNdMb=BWFnQ>RFSZ zxH|2=aSCl@W(x{gFE>+byJQlH(uDvv#-N~zW#g*-Jn%O zHKgf6`ntDB3P@_zWy^F?1CnxhdJ^crsGCKlu5TUJ;N33}9ETX=AosJ@D9Ss9RMmqu z)Uo72Quhi`nnY&aP99PyS$t6;S>5ucO@%hcwu1`UyQEd_OA;)I+73qwj|SD<7u(ei z34`~FeN-$0{c5q*3){`qXH1)JEw2+2#J`B-Y3e9KUg|8LFW@YU3kR868Pvhp-x<-Q zu)okt{VfZrfWh|#0!8vE@king9lu}w)12LrHZxOI1*G=W0?=`NPqS5|(ljo` zn0>3g@y^`CMf2kH-43~i&U~p(S)HzL1NE`V7%IP7)S~A3^-M_303C^B$8R3I!_ZEzXwGh$qngD&MYdmI|B`^|nEOa4l>uoS zq_z6~r323_m|Q&Gv=bK6>k|!pLdsqCkgO6{OUs#?0sv_Z@WfKxY#C2IJ=r#Q)Y-e` zOgoi!7b7{QSCIk)za#uyBFy4z+`cQ<8TR1mx3KFrsTGQ@ySaX6<k3ifp=bPd?gDA%!GnPJjxd99HrW;a*S z3e9O^OF+}DJt7TOg-u!DDl+?nSYPOz_q!PxPvD^ni;{Hc$0~Lj8wK&NoiY_i9 zQ{qqt5hnGYGPnQ!L0)e9+ml@{6BBCw^UZ3n6Elb9&RqN0*r4|RS-||CkrL-gF#~wk zv67A3HsU}RUpEoxH!5k;H%NZz&BjDe3E zQQnJ+E%jC_Z=cw+4j8wM_=m^7J_}g}^TcT=I?&a*{apk^IIj?PToq7XqHeP@ZQbz@V10Y^Yx(;sp2DH+^;$5{sUS0WX%?N&eSs&QoAst)&8UcIy%!zy&l@#-yv zmT+Ohk^>iLUWA`Kgiu1MADUzEM)Xw!?QP$?GCDowW^FuQw)Y?=7dYx`&M_!eT$8R_=dtC)8PNi_Sg-=4|q{n=j0=$`rV;)i~m1BKb z%8DAj5BisC?)C~%J+R1y!xM>yG#9niYK0GP%eFJyzL)KfBFrQowRG9;9sQl_EIExR z$y*sr?q+WH+QarY|F$T95@j8$YM4JdCySEPPr{ zU{}#fwMliUbZ;<@7`|~;D8&To3k{Z<^n7k-y-yx1cb+i8LV77lbb$Pd$&Q|u?@C90 z6yb<|Zn&UuoA>0I=tPH_Ss(0hf#67J+5KqJ^VWxr@0o#S#x^%%Q-KPaaN^om9y=Hw za!D6m7>tY@(SWa-=TYlKTaicyi_Xtg9~(}VfR-nKi=xWc`_3^FOLF+ zJ0QCLMWpxT#&tOfY-(dGAm}Da(AS5D4~w1rYV|`CCCFC(D*eGDY;t!}sb(M-?{T!Q zfsbnD?{_k5?C1S#4>d9U9U0dWhgj3Np1zeVLTBSkqH=O0P;u|gC20*0+WAQp3>jC- zg?lN4Bl+ zGf;!-Jbl_h;;{SPS&<1nM(WmfReV^2ad+pD;G@63w$eN#-kv`+j+lVVhk7vkz9Da4 z4SCep`$P*yYZf^B(T_upNTrCkfl;5+&HV4(3Dp0yfTVyUW|$R+9PXW>A1@Gtd682F zy*lwc+&T9O%KJqwHTlxqgaW$1{biC<&}-B9B4h3d{IIL!-8;yB_E=A?{}SV({S7id zi|E?q#e8rL+QR|D86KO(-9SwZ#pBR}uHQrXO3oz5#E%(|3AXP4Y(aS54lf2g81z=4 z0Iz)?oUo+!_>SgWkDhAKAJ0}xlRkTO=fmF8zW=Mqt1N4{TUYvFk-%I@blRyd?=m~a z0pf5Nu0h_Bu@wI8YN;ry^eV8oC)#{7$z|*N_E?nv97~lU~=^&KpaQ?l*zVOtYU;uoz=CYOFUrIuO>v@vM1|<1*ax%GfKG{ zTG98}4#PV0J@?hm{+u@vWYl@J)i3}_o-P|#RfXnx_BalJ8gPM+bg0Nk8B^`pUnE^C z$J=(#Yo8xw_FU7K(9hn~RxPPLVy$1O>^a9(s@)+0e%7t*J(iXOA9I2L2|BbLr27fi z!_8k<@Z)hM)fmFJMMOH={fh>|KCgMbHVyFEq6|pL$#auCpKP>jy~auNau*D@R>L~T zk>B6n*L0)@P4a6Ov=exka70Asp71OAH$R@k5$pDzTAP40Pwu(V(ohE6N zSNNo7{7c@^&Zyi2?8&`t_oWc14p@Ob{3dd**U6!!5{7!k_Ll2ueC$5YK6i$QxU4Kw z$WAX9kd<87S`3q<@lxP~-AXeIm{5zKG|#8D8$tv9(MUi#vLSh8Wr1>HdybxT4vZ$; z?o|gSm)KawEEZzu!j&|pzz?mnnSKn9V(u4~ho$xB7X|`@sQM7*H1)z_z3rl9I!RTJeNTJ$hf zbG9xEZ1D3@Fn#@E^aB6xYj*ZpCA)6tbVf^o-A186s$Zh}!eX_@ zNpGHxiqwj|^Mg@1Tnl033DN_NwPscZmh82tCy}*&`qPB?cL2_ifc0f^DcrlI-)ohD zx#s@zdRni3i3G_b0d$Tlbay6!M=h!B*XP5imZO-i8OWf=grbVXzh@vcnvc+j>lJ=q zHP7+I@fJG|vP{qPBG&0Q=UQ_*LkTuYu1ac(hxH&meo?Yj9K^$#gYe>q|4>ALedmk&>*!iP_l zTIBgP(8bwuQJN~V0EzA!oHUP=tzH{6C*?nlwycN^ibCAlM(_KR#smbYMz;0_o z+J*`0*Fnn=pMIz$Xb>47aS`h9yMZm~TCrKT{0R;|cpdQy05FXoT{z$8&;WqEJoIl% z2W&>+S6UT#NqCJg8*RF|=Xzjg4GBM&LiloQQ7aHUd+f59b}N2SoV;y$^s&w(`HE^O zaW>#mpX*IlxTokXlkC=j=R=y$A@n66DfPKME^c#_c;i%jB#R=;-*YrzOl%t>@5Pjm z(kr$uET%(%mAiZdEhv(Oe)?8g)r9$L2=_%u;NPdFNA*sc7K>@xUtQLL?qBe0ADta3 z<3ihhkG_5B;WT@H6nmZ$`SMR+8caV+c%bMrxf#;)KH)0S+1^{E#!@cr^&@Hn{a9m? z@trL;+u1Z7wsNn}-pMZI#rc4bY4Ey*C7Wj4kZ-VBC9-5NgY;AZ-#yAx5t{d@SepCq7hXCRe5(RHmbdO z>L~6&Y}i@1Dwm?p7q{Z?EEdi)Q}NClj|DC(XJ%rKh&mN+^dAB~OBi>EJN`gv1$>T- za)&jOrq2RO=4XiJ$D!>P?w|bYt9A&|o@802!+qE;tW8ZXI)}^yuWKLb>?f<*3Bzw9 z0&3d_epO!a8Rf$RH2gwD8t(NJj2C_~x|Kk@sg%lZjyGYMoAv%UEdy?(syU}llnq^x=S|s ziXJE07Ov+P+oZ6T_)Isq@#J)Mod%!=kHeLNtBoYtf%3CdT))>^ZYeUI9Tt z{`2Y$O{KPSbFtnhhp7)L!qi)nj!1Q{Z9h7BoHo2p{CeA$*D{DRuy6r740vJTnBKgE zzL&Lt{m?4hx$FcUD&^&E)0=MA1^?~-P45t3{@DW^DCm5met^^?^Yz5+KKR04Y69qLoYrdyMY|8oD5rIy>gY)Z&NBF7!Ci&x9R8$l; zmB=;46)j$7Was?xr$v>~ra8D8v8n6#PG~T{BoArr!LXfA+SU<=VsEbv;*2u!p=yiL zbH|kDd-1cU|M@tRXFZ+dwfo|!A8;@Tt^UXS`Jb_Q|3fare`vd0O4nKlOnVB5X+vwG zwOp?B{lzNp&Xh$D!q zMi@mbsG+@DP5SAla^vRUrAC1-csvPDYoaZ|@bV5$r4B(IMgj#3gz9N@Y>=mgd%Fcq z1|L$Z{6|XQMOG#8^pdlq;7u|X-eU;;MKlA9%~UzS#*kCJt8}KBZ6kC6u+1JVSxqzUCgvE#@89wPB4;tCrEtdtqZ|OJA>WUPV+k*8?yO0bs z9>%}GPu(o*dTf+b#S}m1a&?kcLHf52&x8ICaMW|Y>nkpDljw2qpl*Q^X~kQ`?F3Vl zBU5|R`x(<}PD?l;u)gwkm^)P|R5H)PTNZRCmAhJ>2S?9mlQq)l3B9`d>E1FFYq*Rz zJ(5I`@ZX@NS7-l;mWodw=QRGlh?aRN9Sw3{T?7DB)l!+7{ZxsCy0Ro9_yAsCR0!%5%!t4cdSE z%4>Nu1GqL!8cnzrHzED59U9JcI_+iP^P-*q0=!1fSb=SSG<*epOEhS_is%gx1-LZ% zGm46~;(naiaeA?kv=L%rd+5?>oX>HOYNELjSP|&$CGyW}r&%`5=p}RtH(0pAn;nqGZrkXQe({6m+z`9I_(F%# z)=CDp`}1$=I|1498fDBEMh%0ZtFR4kCr_=4MNuwkUdDdeFLgNe#o2$SFqX5KmaiHF z`1Kh)4$F*W7H|ynpI)yn?qQvZjK2`YUZ!{YWPam zQZvJGly_psd(BA`?}r`wDN$@XBblh@QDDDDO-$&#pV@a6I7O~Y^X5-IexKuCV@)=8 z2fO#-N@>zenQdAxmg*Y1M~I*0^$Sb{Fr?ZGc6J*Y+2RwC#p_ga<$vY~7hZRTMJOk& zs>I2Q0-C)BWY*yUaE&Hd%ks9{!n*{wYq4QILa;mO%f7%^d>$>e%&{4Zz`3HQ_BH?6 z&5o^lqvx{dK>aOcgR@-N7V2oFmX*T?+s0TNRofD`a#XwqpxJ=Oz5R!crA9^VPa;R( zZ`Q1RK1Kmn>RY@eW<{PqRbI$DEEOFR0@#f7dmJh_&hB{HSwdWS9POe$~Qsd^V&z?-!iR zH>}>>MlLN9rH0rNh1D892i_*VwIz9%UuKEEGA8blishl92eu~2BNA5{^a=BMb1xOD zGDU5d4qJ0SrWaBUC=<&>L7&IxKp_K>S-K@7+=&VND8m%EZHs{`WK z>Xh?AqEanBE60&8D~_s--<|^z7PDFOb8)iMq*Z42{OZR<4fU6tjwl7b+@aN@vSaVSj3CcRqrGNq*4%kgs|uUv2t;q08DJ;{)SV zKo?tCNJ5TZ-5=9Zaugn&{nB^vjd$RE0p0bNnp4_@8RDV*wni&n4P~tK z70MYqlWiFZ_;Sylm+zqXTq$OAiM)4QW@#c6NB0jMNh zf-aAUtJLgs_qZCUvIZ(DLN}Bjkb~0_XPYwI6N<~6Fj~=cLw)3$kk29w!z#=I=_{kr zr&bPC2=1hdMEMSJlSSrbD1-C;?bsY4BX_(}mb`>AKV@QEtii=^1<$%m2cD6W4mkCz6p3(<;%4DX0owRHLEtwWYhfHi5R7S3QKlWQ5KwM#S<)F{GT)T1` z#l9@2oD~ z2K^|9#QYMVLC^p;`&n_5?>-ATyg1hidiuXu9;&_wIdjndeqcYdCmw zC70c|)qVH%0nbLc#y}&*(F>s2YJpZMoz6UwhfkXt5*DDJ=2_6`E$8xH&gL}beQO6J zk2fR+R9ATY)&J9vFaDypKSn+v2n5ZhdwnNrzpvb;69GR3U9sL6>##P1zAnyl zP-1?QL|^E513nu83#4t1>A{Z87);4PHT0oPygrV!we#xD`D>)@f9A3J^82o!s_6c= zl`Yi`f#>m}cOJYe2V&WcDip2StpOS9c=Me652BkV;*{c##hZPf?-~@K{DAm=EF{H`NaoM%Xn3hc=(WfR6>bCVCSTOX`;U``jtT*I6$y zuy_K+R*e{f4kpGzHl@ffa}ICM?)Iccdpp6$2*LmUQk@p`>C8n-cRnG}(qtC?^Eh+O z!}Z;m5O$hG32D$pQ(e(JjsHUJDd*qJdN zjP6`kxz;ODiHyu`*;9LC!P*HrMN%yL0*$h8TapW(W~lUY9}l4sdG<$qtyc}V=9@C6 zRmL`=Hz~?@X{FzE0h*efcKf|2j{>FXWWI^D#}q?f{VcK_M1<-k6NdU-F}J8yP!C`7!3 zP?#XmIe@RL?X(ORdV@*F-1@lxIC;Q0TTM3#x0g04`Sxf5FQ(h=uXUSAYt%rPVXj*s z=|c;v^V$qP?9&S9fPx>}>B&i5ur?)`$cN-G1$qljQ6ox98+?1!R_jaPYCv;E_sRj}Yg?Sqp8TP#XdSjK}md{*~?1LogqhD(O=fQ$GhJG~MX zU-G;U2{NL8Qi$39UvRzXwn6E$A2z=xVt>K=1Fx{w&{mtdNJ4H-Y72T!f=FwrojiLB z3m!JV4N;KocH*_-`t0*%3}XJn(ZEk)FMnm+N42HD)ZSyk$4}(8uXI}IxA8( z-7xm*+jD<42i5o3mo$@SgbBD=EmszIo%V}24@N56nI;T8Q34D?{>VrG8fw1nd0=7M zAoAq?m9b!ygDHwpEDEK(gst9(+g(UW-p%^TwcDdhYk@JYC>J9d~P zipRrR(tMFxeW;JPPt&#eK*wo9!`F(ZzS7OOug9_so`J z2)aY~=!lw+ETHVm!sY1}Qw?z^zjb$SJw(~ZZ( zVardqOI>I^C_yy*0U}T$=?9dU70(MEF?*-ctVq-yBx&{tUwW>~VzI<%RgS7@qWeZW zC6{+2YRVh|=F{$?0*ec;=W9Yj#Es3N_Lcg4^IG(0Ku7%Wl5Wyl6d)`C)97*gMD3gY zqThoP`EyrQ0gFf^37t2jOQ*v{o5Zd|Nmc{>zLZyM?o|6X1#f#gnvhz*?s`WLtZ8ZA z+Lyr73T08c+IFuC+`yX1hT}quo6KrfMJwXrGyC05TN!%}Q(@ zL6Oss+TY&lUB|O_i|Eb^3xRxBG6e+xQug%qhw1ZSIal?f_w_-rgfZgQmi~tCkXhcN z5Rj0w;7Y$`CwubO*eDAj7wEvkGI~1IKevy3HQBJ+V9Bwzdld#0seD*@YOb3nl_VY$ zl3iFW`$U8<>WOlcCi!5*(ESz~8vdsu4{rKP>1B}zCS^sxQ57L+%q;y)=a5-i*LNJN zqsP%`C1o_ow|NFdxBJm&(Hk2(f4)2i02AHOvC>MXd~=h?_-vfdqM~tWfny9Y0oa5D z6raCIy`^TPMw0m3a?jU%xvjf7Z}I)gj`6jaDosQjk>8yqyvH!Csp;zO%L)Fk^T$p9 zcwzf&B*2i^P)cbl291LaahK%gFXQAG(>U89-wxMrZSieUB`sZJcf2g#0jr_{xNUSK zEi2PUhtxhyLS)asT^a}4*mNzn6qmZwpsqY+w2VrZM84}(d>A`lK8-(Yhn_FRw;Nn= zr9GCZoOosTlZhICYHxVNsTgy*^&Q`XRy1@LO;#fZRblyNQaES4WA(Fr8WFpl7Dfdo zmh*PTwFFNZdY%qbOWCXD{rCuoqi9o z6(cj;Fqf6ZeN2GU?c(6kX3^fuf6`TOBJ4)_hD&QDxNk0rFBcQ@y<3}u_FO;`zY-<@ zR29S89(B8`Jh3T-bRX{$*RBrg9!oIHUg;GU`6+2V%(qL?L}M>ASkt059*PSYcEWAm z>+ngx9T3o_<{0epE{!y9RKBCumVyf$E$GolBoz}F|c7x6no_PKsl zUEQ{}AY7eSI{zNZtiQ+NN{);D|Hv@SgY_m|Q{$clVsI)sCa0 z9tV2WHgUGM=Z`|$eBhES)LC1;)YF$BQ8jxVEbIJkx7x-A5Hu=XdBY7WDY#;!aUd5` z(PBzTl2d1q6tsseN*wFhZFn?z=piRad~tGLU2!p{C0tg1yRvjzErji>?=o*U7ClR! zBAdz(khcA?P-qn7+o6?py=9PI*I57TmF;ozWBYwSaH9%x&;sjmCy3C^a{W zO$7c^k4s77n0e+Z1exlS$?sIEX{PQhW!g@)=`Vqg-zG{+MfVx95-!s{Z0n!|@f|E8 z;WLZj3X7(4?3C5V(>DCNQ5>_(R&D#=jC4Q1wh9*iq$rkgrm>&_p35*XqcUID)FC;| zA^)POf@+s9ysx}F&m_lL{tbhM8T894u&^t4;EI?q)5jd+kDrM^bCh~*Tj48pp%8~N ziEo6;1o?(lhx5(`{oA6@!&T9O$v+jxl9n`{b{2%~xi zv3Ba65Ey77@2Jb~IzFRoh6dZvn{4w#0kwSEsL6NUIalP|=teW5m$$R}9)}mL&%oQ5 z*+pMvV>`3GI&FDgFaKsgpfuQ%i$DWb&I}-+ty_uxtV>6bD-LfrXw2WlXPpQ!3!NeDT&U%w?!) zvDH97JJDUyJvH$d%$1W%a_l`asx!bkU5R!OBwdq2ovA|4G?N6E%HeWQb(JQwgiamD^xI+B5Xu$tb4R=LF4JRDtAsa<|;`6e}4T64-BQbh28b;{ON?X-I%`H}qAl#Rvoi&2`ndJ8Kf*lpHQ?M%FArpd z+%fYS=4k!}3Pv<}#?9WV7BhrZU-*yrS(ifVV`&e_+C|{vML*CCeBFOI8^szFTDYas zYnF?wla&PqdYZd~t)CW-vz-(Qdslx6W3NV4S8fWKln1Mcel+Jenq7R8qe+QS!>w#zYc1xjhk1>3Lki$8kesZ5dG$-YTsBT| z`^-QnL-69Z&*`rEy=5oFEt|HfWBN~hMp+7Pud>DH))(9PiQ)RyCa(Z}=`mB24pC2^ zG;iB4M`NN{j<=YYWv*(n1*V$|uncIaR;A{iJ~-}WcEfVPjHU`~-AL*X%ITJ~IDFNw%?`}=qP-6v{ki{mfw0-l5*>>Xm@VZtf*b1h@rdPH$Aq*%kQT+3d&da!(uvJKq$z3KZ)(WdaIp-}GKr&+bbNQg-7%bpW|jWUt6a^P z7~#Vc?9X05d_3wP^OWjn7q420V3m>G^ zSjWu^pZBy0UGCAFMRZa^#*C(0!@sspkdMBMT4+c}~mD|jWs7zTKFJ_gH0J8$7 zvW1$VVkXTl6*#xPnEH;aij&wsqH4RoWy>(ceX6Or(eTZN2snISx&6vc; zIBr(WAQNonOnBODN!DdGrT5@faT! zFzmg?s;GuSTDu0}CRc7XDlL-+W-|w*uWZ13g({+${?DvZS)GxK;J18nt0T?dsh-Q3 z4O5Uq-}tv*k7wOXf20#Ie7y6!*Rt)h>Qt>`>6M6-LBzK9QwLCbv{oWhid!m#Eza(_ z#LeGNWAH1Mg5BMY&5j{95fgO~L)T%QmXNP97^7mJA??$Ck0 zz;zc`Ld>anK4ri7NX~s7!j3eNIr%I2Yhs_MhWR#Dh~Nt@DltW_emeu&qcQB3BfP|e zXZcDL1TwLr>)c|*-ep>E7^ZC1qRz>aH8>luU5m%RGKG;ac;@DvO&=ep>Z@=+b5`u0 zeJjMYc^gE}#XOzt-SW|s3oChQeftiZrG!z|b!O+Zxh2b`P@}$RHE`Z#w5OX(YljwA z50Am=U#Z&roo3j^#_Wt~u(bc_oiNK>WQlvMKY|AhaHUeg*KWvdxn1Gc!jp>aG$>-7 zS0)OWcTv%offcW==6ZM<2&ufN$KZ+)kKyx&Jg2_>1sSk5hgeO_=@nSuGvTAtg;_Oe zr>|_fK0O{s@p8R3i>2QcH!0J9&5#hpqD^vvEi~iDR)@;G?l?a4 zyog8eedDd62i|K9GpoAFWnl=B6SIzDZ>Y4!l)HZSv!|`P)TFyHvINt7EJ-%AWSQsT zf%h7yT&7nLPFgNnIMF|nhJ%2A9}5<+ZBwk_1;d=waM&5=stD_G3!OC#s#2$JWgwEX(j>hn3(oJF$2-W906CWp5k|l<2vPf z!Sk4}&$s+fdWm6U*>^YNfsg4$ZvyY5E^)-~{~k3#|J7;ngOEjs?(N&ArlzOA-8kbq z(o08|2?==rbXrVbs6CylA>Q*5v5Zp(8M=PwsNfdpCBv#JD%y*KV{xzV6;NvS9dSk*`rk zZMj=c+QSejkmZ79C!yp)j8`+IJ%9jPUy?Z`%Q&@GUs(Gq%3&5?tRJ0VCF$Q0Xn8hVNg7@~r0M!^)#NPs z-yn7*K0_-agA3tKnqSt*NUB9BY5sNeoCzdqww4azjQYVC6@{lHpp$Gh1$5oCt(gMA zv5OKY6(PRh+<2C!POCJyY7e2(+2rw^Jv5s{{zp%-h+eEg9}|IF#OrlzfTh-Nd|{Vi z5zHycnB10`*k0D_PYzv16UDfXpPioOxr%)NLpJ1tNK$M$7uu5$br%1ep(>!?V~`^V z?3X_0a|o7ZL}G=&lnPM+w1#BU)JSvyFb#kqI4Ibioh}|gayR=U51>)B#6}SNtzsY1 z0clM){9)D~06=SdRQx^%_8^yn3Lv;Gx{X53J)4pQH^(y1Ff&mGZ3;xWhEkyt6EdKo zymzyX`SzwnmI7UZWR)Hva(Rq{`gUkEAX5GRC2+YD=8Nu&GE3~kRzl3OUOBLv@9yWy z!95dGg=Y9nMA1k=h$;T6Qg*_anI4cHarjWeO~{Mrvvh)=pP@ZaGO@DyQS^#sM4V^x zYXLNCYouG=YsSp6sEB5`9O_}UX?_f#$9son{LGUA;jjr}#6CX}fzInBWDPP$$vItr zgcM^JF?T8_1siito|+cM291P4!Q(gcDu@Z zfJPzT7>V#eB!!F{`J2=|Gjuw&K$-nf&Jm## z-5l&9!RF0sB;Y2bu6z@YnQ%2C50Yg^W>@o|^68(iV%>FFcd&D>C9`}Z6{TZ4<;|pj$fE)MtIs3Q zp#ul4=)ih8h@}KQUbU+Kg~VvBq3fD{V+<~UFH`iqa$GaN!l72sp90A1u{fpu6x;;{ z0A?$ZMOykq66ATf36`CG_MYbV!|%OEt_Et&6>radm3kZ!YATE)nIb26s1v$9Ho0IU zEUyvQkXJb?v}Cj(cXq9&ep^wH0=qYj=C|x(M3Ejc82)oe^?Cq9#k@fllKBU_aKKX- zX~17Me5i5fz&FyZO!X>GF@cV|Z1;Ye0$qGH%NTl8lfLPA`_K)9R{9C<|1T{7zKWVC zo($4;(4y^xrQ&asw7ykW2!mZ%r&rw1S>N(t5*^=W%{qDEvT9ZVpH z1!>T(&?R5fXH^eWA&_(VX)8GExBoAMR;cWyyAxA}ApWkq@7o-sw=TVk$X$!r2dpRn zZZ*7&2WO;^3~9Ks;Z&DCaYc2oZ)M8=`B}*j0k>EHV(KaAZ!_E!fFa6l65T3=wVoz2 zm5?OpL_35I`7GkLr>{fY7ml4Vzzvmd)@VM zDXnC);hA!UjW1R{idE4PAHp6#0x=7Uv^<`ih(V=sIGU{d&r;pgSO=%-wfMvW>!ZPAvW5Zx-ug zS0h5*W>LB@G{qGvjeo-ZrE!q|?2Pv+TK+Mv>CBT^Ryjz0gK%i7J2q1~NS?XLyU7ocvCSJX-}abwG)x=1sIIt*LS%4+T}REEj}w21A47f{D9Ic_Pf znMF<+o&o^#v5O7?233P@jXIEq)hf2t=}~5`3hEj zu|aZ$1sVW5dxmhNu%^5_?WHvhRcB1792MW9Ag{vqT~g$>sFBQ*&M}&9)MbjnEtp|PwSuLtW>`l1*X;*8jcUISLm&)Mn)<{w)3WD_Ggptq(OZ< z?Z#WHkYT1GJLeV)RSIB`!k2t)o(hqH;O54Zi!GGkq2TJkb`aCpYC{ww@O#d9`!g6q5aiXmNR2pg1-Ryr(C$s0 z-M=E>1!s$iOEhMNAoQ1`rHnx&Dlp1_<}69XO-3YeQrjR*iEy^@s6;ED7U;5l3!+>T1-W5|5KM=r=he+)K|m z#1z#w|4j}BI1_#`euqVl4roy9ZhGf*T84JUNo#1##Vu1YMQgB6_xNa?Cb4U`i__0b@C51!0J}}fMU>~#f7K($r>3qy zee&xXUL{lsm0nxDUr7eP!ybq?>?MP$2nG{STKQ8;ZQ;|F>NzE4(9ih)6 z2g`^;{9v4VJM%C>78THA#$|v4h^wQl?!?f(PvZR<_#R~znIb&e* zW~|LdYMktfyepYSnrdx#11xBi@--w|SWK*zkB*MMHNvk;o4w|xnvs(hQX@l6DYj~4 zy&tY6S`KS-JKU*OT4KP;TBashEmuiX^Na_mr`1CH$Nv`h*UjgqGrBxd^4u@Oq{A9q zy)PJ9X|y5`1)hDCT5QWw^p+#L^A_1BmDz(rk>ZNJGgel?4Rl(VCR>pA@wkq6qiR542wzFK|lga2B^%loR=drMIj@B4f7pH_um zy|E7cs07gkO|22@87K4xe=+UZKH*jTYG6CDM&O1KB!RO)`bjJ)Xg(fm`;|Lizxn4v z;2d?8`kl~6J)K?tGjd>AxBzr6j1K>V&$H+!m9~B#{mR)fn^u&=-!}sfUJD^{u_tOFsl^Qv?D{rxBtJQGk z#8h#Ch{)n&ASRbal;3j zBmNryBiwlLaKUJ(_ioD7Mo{~KMj)PCLZFniylj-qzTc7)8}GAtTp~Gdt2(GChjpA$ z7Kjc@aWi_=A3_35p3cw|py*M*VTkwetZXv+QGl$Mm4k7|5E*8tRj!l|Ubx17E@xE< zGlcaU{UurXIdQ%z0u#8qU-!E3S4np$zvf@N6@Rpk0Yc&mykz+B)nUTZbPTkkS>-SJT@ysYjuz~!Hj9F|*l!#nFn z)R$q4e5y*Gm|gr^0RRkrqJxEdMBTD_zqwM2TILr=DR$TXOc0+)H1(kPy7)(!ubW9# z)O5Jl1a!70?|&bSV>YgJs{s%Pd?Q)4<`g z^+BJWujuxzxuO7Faa%~Fz{EzQ$K4)?zMnFd5SgeB!A0)0+nmSXm^Vrm1M2hHxZ9;Q z-~Ft(wVAUlPc_%9vJtfb=QJIS{nP=7BroR|k3hFR1+qST?LvX2ml5KOF_d4}r_ zM}%QIEjcby#?dw~L&OKiRi*seMRKZ2S{bf&q7*n_1;w5bu3kf8R(D$mlpCmn;w3kn zRdW1Pj8ZC!L^td~;A^P*R+~-l&vyK=964^~u6h@EYD25e_={z&6<9Sf^+N@QN2zg-|VX6Aeh@kK!p^D`M6?kVRM#E zdzDtEYVs>62w$h3f>!u`V|%1dl9?QQGcdz8H}Wzb*dpXneLXb#3nx7hv}O(Tx|td2 z-wawPe?DE=S;iPmcZyk5R)!&&86^JQl2zUkCL8MMqneMv9U2AAOK4uyj$m1 zoXJ??03`wmo|Mv+v&XjUPx}E~Q3nA`jO#^r=qbl?@(33v!S7<%IT`ZR7-F8A+gm^I zT#j^0(Q8|y`K11p{spP(!toa(_}J*fgm8!DC6huM+{s$KN7CX3oeE@OAK~A{5~N&_ z>fxZ%nI}18tRL~eizh}#H#9dECbu6Y^AI=JccV=O|2Zoz2ZN?|jI$J;cTiz&hW*R?sPkSG>Ti~vOaWFmT%g3t!W_kYamh68&mV=)K@8xRib{C!X z=iE6b7gCc}b{&VPz4!Vqea_VxX8wRbl~#H0-BrJMM?9?;Y%ju%Ox<;lcf8xE1e3xd zoWC$MI4Goh0nkdzJg&`sxlp8S_F^^>$;xLxxAYhSb;m?j#j>-8LzD7fTLDO_E!Vu|}Vtk3^0cD?J|IisDtwK0{-fS+@FnwA3z3;=~Q| z`83k)7KBuKEmV~kHPe1#8)DB89hkc#nJO|7qbcL zxN$}y0RX%H+xi!p2GW5via8y|bB*VJ&j1GME0Rnk0NBypQMSd%PX@ZHnQL6`HFN?m zc*wZjyrMv6ZoWSOIY!ZkZ(Agtc_5n*yl;)}>A_DWF!$sxFgR7ZXqrpPZ(Z=wp&45_ zxs06-U)+HE2CRt@J9>9Tt~Q8^vgC0GwlwS*;rpN^bJllk+y^H$MJ(2?ltOiXTKXik3Ft}_kq|xqm@@s)YF5p(|ft- zOz!Gl+sk-+3au@(@(qbgy6EWVdnH%8Y9jRhal*)@)l+fz88;dr{rP73&ZpaYs68c> z%Vgfn$KYMRxqf=mCd}f)!9|@e2Me9;C%}*5`V*wzxGeQF8;3e)&-1!RO6KzGz$kU{ zHJW?kYsoovZcSFEU~bB$5}aTOmqiC}!_Kz+Qu#yE`SavaDTKjX?#NPlo{{*^qzsls z-c2w1h4yxj;XVkx_YA2|7xa#O%aW6nr4@h>9+pztdgE&FXj>I5s@CY!~8Hi zm}-n*`1lCbd2M)51?Pf5cH-^l6(msmcdPK%pR-V{A?t38&HG}^3*h218mEY+_I%>+R z?4sD4vBx&V7Fy+Ux}yGUF2>I58hsL}_-;;67yi&68t_5UVkmcP@!T^2y-USSPJuYg z_yjim5N(K1o(T&0J_gQ-8yifsskRSE-fM2%W?YK zr#0(eb&x(9T}oe&0ZV_O#}66R=v!;MS7C$UecK zRs3lVwG9KvnWh^0riibj+>j*jlXUeZ+!_P3_c-ae*NxPK&xOQRyw#3k5PTOrH5F6K z%#_J?4e zly~SHKK+wc!$Ee}gV{{16lvW0S^L?Uhu}haE_KGFfEDQ=qG?bpt8~f*Q~&4 zW#l7o!fjD1*cyk);EncucSShJkG3n&_B%oa%)m2Ry+$>3lKot_WgI8m&c-6PrJv7n zb*c;THM~w0rT9#)wNMp-g}5oMa0OixwWSb@M*+s-msS}xy#Dpch3G@er8lM@*TG^b zllF(zNHP(b1k&-Eb9pnT&okNKs-}UmRT(K)2eT9Fn)H4eRXd@hCU3oh5sAx4FK#i( z=QBZksQz${piyH>VO44R6S5oDa}iC;%*-##8UO%&w`XtR^1@c5{u*y@gGZo6whSb@ z64BQCPjG0c*)eqdr705|^CL9BUe6KIkA#do?)!r2*T+!~1Zk1h12Q*wIx!WIHq^jr zxB&T&p1{Fe9qBL$v%mXD!2<;mUxN2BGv2J` zV0~WK$KbDA@7+U!d|f+(n};34PM!H))B_sO6w1F{tA{W~$O8PLB|J4g9mr*q9wjd` z1iW=q@`&|(UAaN0MB`6~0FvL@91N8%s2jb0dV1SWp=~#kbLJ}Z1q6k$&lf9L33RrP zm>fSvj`ro0%AEbNp42cH(^Zj;8gnFUVSu%JkWctbX9@$TG0DlKS7i@;9HX2b2)cs2_2$`e`!o=a9=%#Dc*4&Ci}IME!K+(^WdiOiiT|m zVvTgBuB!2RT9<(b^he_{I)f)(00)4B=5e3;mz;zKnWaNbh@c|~@7NG}5kLTkmtI72 z0rAYP_4NE`_;NEZpf1gpF6&$44#l)>UM&CChA-uEC&!PzX<5=C{7_94mFn^t*t2!+ zvMVZBL%8ORSZ6dG^YZdST^C{9w(*{9H!U}Pq{h&)pK7|h?Y>k?q%y)V8VRO-JzBdP zq!y{{rMB{oVVKxhDhMm>0GHJ--{w9l4}~S^el31cAc-s1BlVCfBF;C<1;qtKL#^t% zNP0{5NhTez_v%qfeN&;vdm$n~N)&7bYFinLyce zX|Y`+|v0RWkh>i8Y_ zjO=YrqNdodQj)d|Sr1<_t)KLuAC|6jVwWOXMixA2XiP8ZH>JJiIrMN%M#(CNW5p6; zC4Zw)lh$XzIf~3JQQU50tw9TMGq%8n{{d2vDsOYafrHc5>+t;5yh8t zG6{uhF5Cz;bOC@+AExlFVJVY+B{uTTLBga!3gpa;Z*g)k=W7g&RTv^9!;u7#ohu-G zgpK!3z0}sLYz2gosHWFJbvd@XA3@4xw+($jh0FQVs1C`6Hhv%>8+R!~bxA5fF#;M8 zaMK!AoiYqvORirdiT-WpRiN}`LTyMm!qEqjd(^V?;DjtWyfo)PYzN=i{OmU+cg_&g zC_VyhU0p|3ZXM{?#K;0KD5=XDn zMsQ5N&3hFMi{iF_-Fu`#a5=TzKIMJ(W;9s+q|L>{h_8nEAQ}B@m&S; z&9vwwb!o;LP7X@sS{(p5$MMQjnanGYqqSrkzFq1`xhza>f;JoEc1USD+1>78S5;w) zEGX~n)y(?TSR7NlqE}eTjZO9-C8}%*;u|yWw6=s+YTOEi|8tixDTOp(cVyaK52;8lv%ts zcF5pnYPQTn1ga|JX=Q4Iw}z15<}z|94pr9)zCI)8*z8Ou9ugoAH(w7`P0yHZI@XTl z&g(z}YV;P4iCyF60%eY0dp0xP8A@W*pXShryZSkRJlaPMa1cfr2>P|8X5MYfQu7apVf_a*i5k~&PX z(g`WlLT~qdM3YUPxu(6rpqpLFH6lD3eW)N>4ZIPnHs5?=06sE6zlB*pn8#Yc?_nI( zLQyo)J2T@#F_ouQpW<0n7}7eb7$FOy|~*@M+cIaJO)~ zl^Ufx@}Sj}AVHLznH6yaoG-XFA87mVf9L2)?A=p}JR)#H^w2zYv9*jYy9{$T-v3Jv z#R4#Hgf2brEt_2l$$i)Gyk)?f1pMhXzta7>qr>#~91!i|a!y+79(=At01Ig8t?}Wk zE2yY6bi|+r)~Xvq zF>f^;og#%z{p8y;Zz+}7i)L_bo4ShX)=TH(P!jEjXX^kgfI4QHv7KnrCOs};cZDS$ zt(tvLX*q}1BwqjMbZDK++W97EQg(Yo;oFck?n=EPH-W?2t=b$~N7ZCuh}*wONoEA>y9I05Xszhbw4wZWcFxJR83BOI$wSW% z1m`T7(&VH&&OY^69|2c4pV_zvv@mL)PRn)!WUqa_ z2JyW+Q9RvEKT|t}GCwuy$M~wXmKg7zL#9)YUi+7^_2G40jjc1H%O}2gZNJJ*UnaGa z46md31Z&IY%?i7WW^qPVBK3Vmk|B}Ip>J`q!LBx7-AXy{6VFY5)%fzJ0>!AJZRyI- z1@T3@HiRsMaHqzG%<7o6ou&Fpv)d)e>UjI&m2Kt}Q&#H(lHpYTSUscY!^G3t2TVUb zOsCtWgdu%n#vE6#2wg@@Xfr5xc!{*#cRo`g^hD)SpfSy|bCH{yIYL#~}$PQd=-$AwwsH&!ny$zN&k*2;^ea zb|4ini2+v{Wv`9~8%;dZ3oJ2Lqg41laB?^mOjP?~^_`PzjhRrj<1LE!niF(b8i)$|_Ak z71dRxZqPepfYV7qul0-!vD9_X^RXEXh36zGHGKX^AMN3G-^)4QuhX_w-3NS}d54Y4 z`-grim->9ozm`oq=2&0SjIEpUzwygp9^Wh@*>-H*#k6uC>t{!0fz-;!>-8ll` z-OGBu?v$z|YKpt?D#MML2Mi7X*m1q|%F3C#m!RzK>Lp|}#?vzTR4Qy)!_~>8R)STP zp+hkEbS>f*R52LYsRPM1LK51g>@ug%eiCtyz2F?Jt$;^qszPu&$?aljI(X)C)2q`D zSv+9~nlu)4Gh5o#)wYbgDCckJ4Oypp_rkJFCyvKuU%StdVDdH8_RlUD_3HNfRB;ApuGMO?u6%TL!NO`(Lm z?B?`pG+%d?WU0bzRncwk(HR52nD6D}LWT@-$U0=v6W!l`@g&}_y&Cp?$zL=f5w3}% z@cRYq;BvJSdB!{lsGCs93>Z6Guip2`AS577DqgH%zo3=Ikn^O?797MgmZ+d$sut%k zJ;v)MJ!nB~adh1yNIKcqqjY%-!^(E~o0XD|Iv8BFimzsxRFGsG5_MQfpCtO-hTfOO zbE}zO;c_@3x;6J*__4FCIHFJ-@rX^2<6V2}HC5dQKMxSGqu<#O1q=8hf{k?eC~+a+ zdO@Z`)#R6ipH-eU(n^qg*XgHr(>0k`eS@@-Sb86`)h#v#tsg>Z(#fuMpUunaSRI2` zuI(^PXZl?aZ$Sb5QPO;aseB(sMG;{1a%#T?f6^5sk#`uMs#cI&OL6X7`aD-q5pe1k zwW~dn_$U9gpW-!Khq0ab>Lg|$yVn;m5g^RL^Z>s8O-!IN_Q0F} z1)QGM#MSfiBC_=E3UF^ivM-eD^-;Sk1-q zDn>h@vG%N(2|6%&1fuv zV-~y1snp%}v=KO!UcCd87);ISb_kBQ9e+Accg+W{AuIUn|In(WGJ>u@I-loF zPq-Rc!LKX0`7js)gHaK#lVd1-)okT@wi1_13%t7$n0EGpgF;pm&!i$Ien_g5$P+hBU%Q&VMGGA$7V4LTVV++inF^>cczIbRD4Ci=#@#JgioF3jpy~=(A zjS_Hj>c1sdbSgg2njZ^1OXNqcz0@g(8#)jC*29*&n2B`}^)cFtl(GP%fUV|<@XUP9 zcwRa+@WSRFjh=nP-tc3%f|xYcTyXt?g=l~EsxN)t3WiDigeLMmb5d6Hya$Jz;jS7O zyj+GF5o5=fwf$bEDi5@lKNka`08yKGYO^_626w@CcbQ+`QW``a0Qkkt8aiu6TwBO^ z#%^gBL18CQ{$D>z&>%|u6ynt=8KBuSt|C(sBqhcppfk2xkp=+Z0ZN{yZU{bZmV0Z{ zcWsyERj1~QpyTA6+U88Es->wG)m?|GU+$o3;`i;bmlu`T53-k$@%$tXa`GQ<bH#IraG6Zz-xVR_lm3IniM~6s;&y-a8qy=$g=iWe(}i zE?L@FJDv|GE;CrVV;6sBPMS5ziRWu;#?)F`_F9Rk$!iW@%56Qt58pxJUA9@Ky0!PA z#24CFz((t&tW~~BJI?09roqf(wOFQV?Mtn?F$io88p`3*!#vCY35$-Ho-7Wt7fFc}iQC*X**Nk=0ZpOACXV=bn zI*^?vBTE$q)$<3azxKYwteHN{`{{U%DW5XP<)ZISt8)HbbJV={{{8iK`%przxs6TR z`TW?`rrgvgTL1_}9}}SDUF9RC3epq!(Vzq#+z5cnmw7?-q4NT-ju8m3Pq)OMJ3JwA z81@?RMeNgUmS~4uf`lNn3%q%n!WOW7%n@^NGHpVo`j9eQ+Iz-&Cgljx#&oPmeJ=a`CtQCQ#fe)oUwEGnByq>wCLyo z+?6;3b2({V?gpjz$sep~WkzB=6}KAJwI#+NeeCU$+)f$fQw5_Q`2>x~9f&&{JOBAX zR{++7+avFYG)HiV7w-{*2Xy-$zdw#X1gKT4xs;Y0u3=iXlx-^9tvg~ zGAEm7dG8D=XNWTf-zG-B&3;f4d7g$y{746!?&ylMFz(lsQB}%R`bQjBN5mlmjOTHq)n=vfbOU z3LzDK0o4OaDw>@OJs$gXBtH@jW-1zx_@?3VA%lQyM0iLYnF4hFN!A&mAW6!8T>ZM$ z{ZPj}s@bu`0cob&#~W;^{Vh7jvNg*tLM8m!V7ACnD<_$pW6akVwnrG&b^n@$J4 z2GRAWL1=*Oz|wYJStup#FAcyp<=TsB@CqTIhcR*7{g`0)0d2Q%6M&usD&oHiQq+N_ zAfSIYlo)k!=n$p8h6U&zLJZ-f|M*QoVHc!GVzJQN3V2+9(_) z`?N4<#^~QpGBRg1_&rBg@2nE$SiQiJMi{Nu#nyjSbRzwE`gBtof3M(P zx5M}wpVhVDWz{DqT4X6>ug=y=sho#t<6V1QARt=x<0@t&Co{`H^>6HOKQbNSCe*F; z2#6>u2W|V?*~8Q!iOJGJC3^jYSKtFcecH@6>di6K7-T6)gi#l*K#Zp(kbOQZbvcHA zT_>OqV7fW?WvBALZ|ewR+W%#ZfQp1~ykOR-7^GUhXlbS55Ei*~pk`e4BTe`pvIfNr z^vU7SOb^FTPYCuX93P;+?5n$h{oCcMKq4GKzmjw51LKqw+OW))CA(X14^L0!@&w2U z7Mj67*G_Fh>zjU~uC3#q#1UErO*u1*Ge3`_S~Ff5J)Np;f^BYr*6x}V8H^K~ntlkr z7m^?)wD07_QE;PSV?k?6jc1ed`LRjUHJ*t!q43ru)f%+UZ;r}I#8jOs zUW{_<7qHK6DrwJ+{M8~+-g~G*M71Vy3XQalefqX-RZ0}^xeuTAxa{;>8m$B*CV6ck z_dE{0hq{wbMzz%(0CkkYvOpS^Y%Vc$Z4rLa=){WTAB)ZLiwnQ)&qpFGC#AK8Rtb1> z{AV7Tx6$4WDw_N81+*D$JxMIkv2EXEvOcNk zK6quyHUxZ+oR(_3#X~ZEju&5#@Y;ByzG@W)m`7v3X_Kw*w5e;n}k1hK+py zV5U=sStyd5Lj!B7$f2Qs|_=5 z`a{UXFEaK-sC|nh@h4A|*A2<#W2J#qcA-tpcljiVKO_8kv&tpd&{D1|h#nfJWDJjY z^sAp`#^;w{!$gx*QMDFaiX-UH3f0ZQ1Slz^O0h~#amM^G?xZJ{ou4Yz0>w=1ZVEvNd zK_JeuerGc<6ab)8GGuS`pB)97(88BPSsA~$GCxFOg`y1_z8Dzor_@Ef40(i{wxuEg zW?Z^1<;vUqE@un3LHmCDf7{fgpWjbyv`DKVkj8SA3OKzQ-fyL;yof_@qw?yn;#He~ z`}!>VzKV~Y>3dJJ7gGJ{eHS0a;Hyt;AzLV!NNDSgj@^}*tdmCi(*RV+-)s#V7Ge*t z%3s6Ap6>DGhLK&Log;ay9QSuavOi19Wt*R`AK(tlpVS36cIiq}&X2p<{Ji$LFop0L zHDpp6JWr-H z=^{L?zNU{iKlJVmW-S}<x;P7FM9|_8|B+{AI4?3K1D+P6t z>NNSI52@VGuB{VEEj~VCx*?WT10?_Fp;FXzTQ2M& z6Qu~4#Ua9>6FGJS;A6%Zk9m4kLpAT0^cAW5ryL12>O_=}!}?Bl$KxMQbohX|s7^lznVSLm8yIdk^3AJ|Ra z=0?-em`Z1N<&=b4MN#xCH9om!pKv-9ahr6%`o#glW{po+vu@2cEn+5{dW; zFMvc8ZTajQO~iH(Aprdh?&sGO0Ki^7N)5rX*`714J2j9wVGv3huzf8S{qb4|n{XNMZ|vkL!6Te7>Lh7hrrlunTw# zW+s|4zO!6GPV4O@aeeCkjQesET9YNIv$3Yc35qM`)ix@1a17Kog97wa2hV#lfcykl zJ*qA*HVL>$4ari$w12Ag3c`wn-(6t%T~_Nw+Y~c%$nLn9kWKB}tlvTAc>JH;h(AyA$r@%XQwiZ`OHUOaAtWw?om<4|4UEgwD)Tp0-%ZZ*Zh^y)eJ*?Y|2l_-u zzh#W>bb88zx zj)hMZ;WFV5wl65N2KKC~ZwiH@*0VUnG_E;+Gsn%gjt;Q0p2iZm$5V56!Vg$%u?FHK z{e;?h-+o<`4capcy}7n7;}%BoagkwI86Irh1fSDPnnWHu$9T(m6dNgqh5tj##Jon7 z1bs?aEf`fB@Lj`dl9?j0IWiid%(iA%cm6bw)-(kvi6CvQkdNYdfjg=iee#+)CW;Na zPaSTqb!Ne^QUwCG+COK$#zN`D4V;g_0sUSE95RZ;gc~f{hCTSJc(T2`>b=xnB(;*N z{2of>pxa2p*E5b>3p)+E*;`Jxw!Bx88w;#h9C#FGUL~sbzAp_?Lhk@L^T>D=OEApXG$lv|1_n4DlM{a{+}wNZ`1vftC;kH z=+*)5O18jnxy3Oj`(NkMa101_x zO)JBgD=|A5Su8;`bWd!hU{Bk1B~rUD`b!73hPFX3p*PsmWq8Cs`fD2LgCxojFQc{- z!#}8ZX4BHr(uWTpcFmBPxinFdQa-*X^#v@N9nGS=0ClNsv0?WD?Dvan{|%PO;ajI- zVa%Yv?><<+<&^R!^9yBl8kn1{WQQ9 zUicS?apnKnFsrx|i47Be`cxFviA%4|nFOJ`rw0o9|FvD`Ui<$b!~f3{z(_rPTyudRui5Y&aTAB`E6Sus6Yx(^n;tqtgnmgwL zV8ycoN0@jg+991VJEh@G%pLZ5(5gr_;$*l7D{s76&VdLx5xYtgizh zN0@dn$5P0tyMJSVEUHxoJ9fn$72~%|lMks&)R5qL{n~3;LCuPgQ~X#&HU4EuTBDz^ z*V*JbN-;2RuX>R3GCzq!bO|hZl;72@^w~eDCMBy1tToxnv9c171HIQWUIdk0b9_5r zmX!JwbR+u-*{oidxm@wx7;s5Pp?4HAwJ%J>{OMk-rV3BgRtc#7$?xy9$_ZcjxnE59 z*xc&nv?i)C?0>KTN1hggXs3v5n&&x6cz)0DCQZGlfD~niOLKx94;(YAa?~NDs?vDy zF#rJRu3N+UHRYceKQl}Qn3%{?$jU<7xN4O{*Qc$iOn>~0#X*6D;Eb~w@_3&UYSj;y z@(AVAjV{E3D@^|W^=F6pkC0*OyTFN86oh0%sYtqsYOJVtX9(PFi8C%nYgTsz#cfmzfF9av*d za97y0Mygx98fEoCK$q*b{e^qoe0ObBJB@)QecknY8C8IgMz}*MGlS~Sr;h`19;IJ1 z19&HfBOCb@(hwBqGWzpL6|o>4_^Pi*Cm^LE^0s!0pGG|mb>XGVbz@6;JwG51<(t-< z3fie%{_^T@>DEyL$Mjv2$0GiQg_9)vIRYW=)#81ppB9y|65kuym?|GVbPQVS30v8WoE`fh{wt3OGCBt`iw zHvP3yb@@ig(U|XurF!xq#lURyKVqvsCrge8j?Y=Fpo$|{?RN0(ZjnFmLjSS!=vMochh^bBROK@rTT<;rTU}fet%@& z-BV;kte$`?0N{UWol(omL8j)_+1a^TQdwrT_Z`>XS21~EXp%IahCaVfe#^K*bo&8dMVFQBN0}D*UXjJ^newwH(!a zWK^KVK6d!XW^b1hlD$EJ>mQGKB|gtPXdbO`C+yq5pdo~Na%bu4@%pUb{s#SgM%`xtU<;pFSJ zMq<9cZ`c|lZ|806-PMl*_j+HXzh={OqEE zc38a7oA}lA=}5y@HRFrXjqw(@tX_3)j@Bi&&sgfbPO`6YN-7|u-_)u~>8Pk0rLJTe zC)B}Dv8t_;5Ig$VfUq=N;OBQG+`o$!z<*MiDB};7nO`1y8x+7@ z0boYb+^89Ax}REdhv>rieraQ-wBf@?qN71({r9+=I>bHEgpZbYpyg+{cgIR*3PaoX zQI|CCL-LLiw%*<#(WH>duLp)AV_}FIRjCiutK5|Dzls^%Wv*Vwz9WCcMcoh>4wWi+ zAJexq|H%8cC@f|M|FOSMHR2KQMI2XPuJ2;yH(iYH0OT5+Gq7^4|J!)9T_97t@isV> zot1gPcga&64N(ebq*lh*(rKT_bYW787@L_EqydTl;5*SsXDt4zAIL%-%r`% z5rO!C_;R!`ycU>i2!C8Ktymi3$x>m_M>+HIxOh1FK}<_dx$mwjLzE0RHpBq)T|8#8 zn3W4BWtZ#yzDfe>a$HZ(a7@|tZFn8oeZ=^q(afnk?5ba{3&!osp>9bgocQfO{|-V7 zYTnC~L~JPg)m|CeEo@>q$KAjCjXmS6+|-)-O0p>aLzHR-pF2hxy_oT^k&M1RW=o^C zvimY#caFDttuf2$X^k1F(cRr$X@5&TT0qtz0lwwyb-QVQmCF_O)teW1HCgruay;hT zItI5hm|AcCL?7RR=xl-+uQ3CPB3(*|Vrpw8t~ zHe*^ef!CN5+82rbB3)Jv5ndp^8<`O1=Gplu$ygh5EZx`cc+tIiv3X``?#LIWH1gMs(V2)8CBY?y)#L0=kuez@NAI4^dyYqQgGA*oqv$NV zhC|856-PF!_J+$rDR(F5XJhA9IC{uqlH&?Vp<|_1#}?E`rz2_wnH|gM$*+!&Q0t*S zpT<#)bZqU%eMQDvAp4s!4sY}8t@7C%+TXA8%6A}`b?Cjj#tVIgVNaJV!Gx}j#Lr(D zEI;7SLk5^+oN~lxJpe6IwY<;|bE+&!N8}SkCTz>2ivL?xH#5x&u>uWf50-YdSF6&j z2-^pQ6tPM2^GE~vojW__52v3176XrGRuS4 z;n63bJv&&*N@KdV`Y8umQOM^1@oVADgUBq`mD(&8fB-`ogJC{6bJuHN5K?;RXI{16 zFA^?N5be-M82I$>VwDVsF+BPU@uHBaVi*c*W>K^Sc5NL*E`*=^C1Cx>!l;czun?uO zq}X;DDfp_AJH4npEZSbpvyDDRt|OIq;dkx7?-Lm3-e88yTCiyRr>Vf~PEHLcE&kiv+p>K}bl3ILlo@96w-dw}@ ziV)c8FA&hVuzfdnx6QLtpWTMWQLu3o(wHtox0z@z9u^KT@r0zqgT?yQmy9-AK}b=w za;zr9jt&DzIbvIwg@xj4%C0>#Vb-M(_6kiGl7GWicZ2i8Zk&b~?5vb|!W*v5k&x+vdc! zPTudHd(XM|e82nO?yB0gYgboyt$Lod*3;;<>>TvJ&?kq`8xvGH|7x?J|vj2wc#!`JIpoxRV!~;WI(IUKIl13oR z=Kehpo$?wa9v&ku-Tlwds1MctXhH(0Ko|%^QqDVW0Os%lRYEBWgQ??+y;p&#QX8!T zwL4i=O~-{wElj~T01{JNj{EFyXRfB7^n^ItC*1JUZ}pl+z1L+h=aDNmWC<-Sp=yd4 zCpq?kiFWWsq9zd_hGP+)1UP*;XZ)|kZvQ7PmB0e+R1pxe#%iXo1Mnvfi%UqgCj+Dz zdBr8;Qc(@NEY3ICT6^>;!3mjwA3*(L`o1zD5s8<>w0#Td-PtD6NOd$Xb+09Svq4`Z zacBVc&B9Sd^@#WK{R0M7`sj4N~dX z;5ZidgoST1=Iyyw17-*U;M`zSw^ORN?R9Uyu|QLf;^c?NVyg z(h_H+W%DX#!idDnjRFLB*+t|thd+mIl@|nyk{yHJk zJ{j?rHd4N9FaW6(N?ZS*PKt+N$b^GBO5-+VmvCaNC|%+p8j!o%MRIW>4t;dU&4v=p z0Or(D^OuCR4YTE_wOp2QaZ3tfg4th6w+|d`zpCzoG1viAc?r3g!?FR#9VyXO7@a@k zTV8=TE=8Wj{6`fdj%peWD%l(Vs-rS1xTsWZQX0MUq`0k^^%e-iHOd&*LcsNds;J*f zChwvpFcN%E9xN2=JNP%WojF<;*Rm@0iKN5Saqt5b-U;wF_0)%a#pgw$M{H*I>5PQV zVb&;*6ic%I4^Zjc*WBBrZJ)1?_kqq>Xd$iYB*K+;2)0h%gdgC)V-RtIhsVw9LPUZ7 zhFs*Gg|yCcHbqdcFR@y1E2=d3dO!&e6k^!HjTAVCTx;CX6cJY>Un;uvdg$YDQ>TXT z`bM#qthd2VDlY5ta%v=)LA>6gI$VXum>QZ_P&pmHHdr?6 zzdDRC_?BUR6qyefzf>cMw6tzHrs)Hedn5~RE3nfZG@mlP4MYzP$rZ%g_%QxO#8ZVU z2t&EfmG!UBexjp!n*H5=Ra-{~4;woyhh=!G4UucXg7>*+7qZ_!KTGua)QCPmQZOLO zU?_&*`T1GMt4Yy}EAQCxQ>QZ@ngjfEg5gC4>fwIQW!XGn96$_`$XlvqJrE>{Rwl-4Y*Tc>;)Gl40jJpf~eyMarPg1|{=>1jh; zIv3?5R)l`){J)dB{IB#b|G9epT=ROYDXMbW`kaU!A08-x=B^3WBTba)wxxt?`#zx3 zEvbaAOKJeOyNcR?ky!j!4Mi3OW4dr!Ph<_Y8RV~ytQia|9`iSr-e^F6T`iz+z4bRZ z-dhrSun8qwT9;6~u(0i>HJ_m3M=Ckl-c1Zkr&If7Y7{ZD6e-Gg2ry}Ku;0S%M(tro zq-y-y6yEo9P%Jh{RkLDX3 z$d7#kD#iO^^^z_!?SDTS)?QSTNc+Z9Z|mt2BfCFANfxKbN>UXNw#QYU+OGH6iRntD zIal=eREvX2UIvY!l@SYWvzS(XmyWT3WJKPc!GhNBr|wYqZG@gsCy!&&B`WmD`%?)C ztPZ90R@ZcY5f0OHn`{be9__)>2re&;OWQqC%Z_%N@A6z@{-xYVRBy@I`zIBHQSnjC z0m!pP#zt;NCp0=fF(y#6kkH)D1t~t)E*_L)iz|WVs_=KK%EPtch{}|ck1F{n zWX)=;TObJ0#Ne(L|GExh=W<(v;QZaFsf#tK(VdI-(2}p=rHzQ5Y|?a){!6(CDHjBe z(oECU&cQy2vVHWn-)EH3+`G;I2Hb+(y4N{P!mqoZn|BLJ`!M^$6Pudy#Ydz2Y|89B&>W zyuOwIp>LkC2K2XWiMq$8o1KrhXa!#!H%dj(>*MBaH;3PCIsfckrkdM$)joudAn??a zkg2*NB{kp`?HgVPR@wqOHXy;=F1P!=vtCOH+tV=!t{53Lqh5ol_P5fEa#_8}s2G>= z^y#~|V2}$n+{%ecn>??q)OD|5k-paM)l~-X1iM+<>`eBmy3_9osQc=TjlGc-ch!d# zvfrEl01!i5zP*tp10rPA{1AiamQl%P2XDj8ee;^gxWNv!Gl8raq5 zAKi~z_ag~tuf0e}uyry6$tHJXVLgIJKh&xinR;-D9*+%+Rnl;8L-;M=G1Tvj+k%Av zjEnOf1I~IUnXt&J@vWPck$vDMB7m5I)(<)TLELkfw&yBozoY<4XQuRI^`zY*^phVy z$y>ab5Bvbq=%|19vXHCp&G$XF14rZgl~mOS{q#63`=hJN+iv)`rs}=CuCtH1s@@y8 zkxj7yaiM>70;V6;ua(~A9aY-JaX*aj)q1_o+ss1hdA-`GKe2N5otDa`2D`4sw4Ga1 z0irjc;bz&hX|jPOs|d89jTN2U2v157yRqVAjlQFP$XgTqmH z5)I^%RXGzSEHvz+nJSW^3|pX9kqNBM#E6D|o6pAENv!(QI|t*r^UMNYAVgnpkDdV^ z5+EeQ*8455{KA1%|~|fG5Ebz>5}FY#-;I{0hV4LlVe#9uPm_IFAP zD9Ho5v(B0YG*)`j4mg&|6o>fUQnT!p;o-Z+)7dXQ5LvJ&u*iTj?c%7X=3;6D^;09Zrv>Ae6{Z{lO2}+&Ewv&X@5iz(ahWU-i%s~G(06t%64-s2CeK{FKM|bb;sD=W z+F=nfS(P9kXl^_t$5POz#sdc!)U3c0S7kAr+-|z@58BmMf)l1**|AIt9b$!tmky0I z39oq=JS4{!V|)iQ7KJopR@^=}6uOw(HukOgrq=}E0X6_; zFit#qZS+X*xSFGkZ!G-$@K?*UBQ=eUEKjriIy#j>rE9BeA#1pSke@6~-eli1T(dH4 zfVo>dJ~qAm;)Ysdtu zvh%v>Bv9FHJ?v^*>zZ#O=lgfCOX=+6hJ~h7+eSe*9r?lW9wFKlA=vr84OEBcB`qE= zqwYNZ9^=jxZ{mi0ny&#~WWo4I1uQ#0R1_!vXJTk!Mw|-D4Q3?{kDY%+8cNd<{5Y?92)I0w(f&quv zr?u56O6vxij;M>m@_Avl1XOmKO2;N}*tBZ^m#2^eU(4ALaX?N7^W{AG(D`(dRPJ)W zV7caaLk7om!MZ+5Tu|KG*X6m5xlQG)fBd)GS{SenKlbDn5=P0Y|?4$_D=gVoY6VVoLgjfTE#pBR(lMg zvN)zIj<`%DKz)#p{ke&R7Ta|*hw&QU8rm$;U2B=G-A7z1gI{D)d62+T=U8=F zpZ7bZ_&WWW4a4iI@2po-e-cEOnx&C7w2qmg4w6~<7(#yw4rDZ%Sdf55J<<_K2L_xB zjrF!H((U_d^?V~9)bYH$IHd9s0Z$9fwbCUdEP-JSQP`0QX5jbZsP^r`q@>VKY-p5?KqNnQQ!*RfvRRS2%zn$HyM0#|y6K*cpZ>!&K$YOw zf4;F1f75`rg+jP#%ik5&rTI$$GewE!{31@BNLGsOl(Rmg1jFoyj_Kfz@xp9&PtRic z!Tq_}TOeV0GcXMMA`YE`0_z@`x67S>VlTtg>~NRZ zTxjiMr0*Mm9ts8c^O?esmt82ba)oqL^=J+$ZtbBiyHi>7zBMe%GgSZ zxuk(b5Q(^=4)G280a8v0t4Y$km$%~eNQ+R?q^pgl_r>aD^32N8?J?^lDr-glAs0n9 zo7~o-hb_>x_UXvbs6;*!#Fg)e{arM-k^GKbzm4{s1khUjC6C_qn=t+jC_~<3`}w;; zKQGNSm^+kzciMtixW<4b9X$Ym9Z)jwz9Sq%%}stG z6!N}HdUk}oUvs6%#C;~8c!8jMfaS4UC8qo9(2Fgke)9&-r=03DA4o-kmJEma?byRh zcU3_G>Vu-{yC_-va{ZCrVaF`qceu->r~EM;5xKnohlx%f7%p@1uL5KL9bGPj+BO5;{IQ^Y#TTSf*`L|MXA+|oNPXi(s=3moE zC{O0=#$w8iY1AakO~s8sccVGNHTwFE2vL>UrMhafvk2KXc){?r>t+RBv`I|Lr@Bkb z;sGGq00Cq37@Qo_IDF%*AH$Q$^$NsPTx&u&=}e5G6W)q14OV3All<)!DD6=a=C^u1 zA}RmJ{K)sc%h}%dH%@c*l^+M$F zz#946@PEzKg?}$vUM^R}K~R6t720C*sZT~aJ%1?a<9%#YLMu?RaQ zYCh;T3NBbrQw-hD84hxGLsDAjY~L2z+X(^wEk@isiTD@ehY|C&mz9*ZuA7p}rx}t8J=Pc1nP&sZ)uq3g-7+ zf^k%h&SUxJ5AmL~I{ooHd-ywD}5~S+lHmH6=O@IRcuQOL;KlBA$FVkmYA&ZurY7zX1Ua_b3d=#Gzo;K!w!PC>dBl=%yF-@kJ^NSx%G1Lk{AY|{OhR9_9Wg-qhluL+D9>uc zr(56v2I|*uUVCfF%w?(Db0ti1bUIWG=_A-=16Y7_@)$^~z?KWoOQ+Ka?Dv7}!6_^s z9>KM1`q_7E{?V)2@z8qGxTID+VeFg3b2-$BwOq7#=06adO~ev6#D9kj0JN4T1PEZR z!KoKWXZu*(mwtt0{T9h{>_kMZPotiH&!OXjw+ z9)GGfF50yfPn@tD-_%ecV>`;xJvw53td@$bXB51WrumM+{XP3xD_Sv!M+e)9)ND5) z;g-O?dox|<3JUEdOrJL9QiC*$63B!sJT65 z0_L099`PtRVeT6WUFxmi_}vc*fFTmR|3uE-63`|9X(D^KdY#mxf9Yx;53nowLo*_~ zL;Nt;o%yiarVph>#h5{34K+Mo8o|@}H>V+|l+$3%^hUHCc5!0-!bbg>uD+>Yp`)J7 z2Wf0gwjn^OMKH1iX8x3cQbYreb1l)c#v*cyOrcC$C~ zeUK#dJw^o}6Fp#_Q7?g<*0r*2LP?}2v!i`4x4|U8bs8Dp3~++|WuPPGuFfclLUSkA zRm0<5om+kgpQ`)u)~ONqIXC(SQu{m|NK7InFsRcHP)AI}3VSriEZ((|me@b+zo^u`)zi1QIEGbnK6LR#BDl6XfwW1Z)Z2C(<|p!zcbnq2fgYoD4=#>x~YHupzG)`qppYz#toZu)^MT|s8q(oWfre_OaYE`;kkG(`HFA|bn8!aaBG zOd!))T89(@@Ywr&%n4R(UVz3r-Wkn!7m>n0U$X!`rFZSxyo zw=Hr()85vRc@nB-BiS1;3X|v7`iAhi6DE-Dp)<3jaTKaA=cNb$AZE-a@V)=|yq15A z2phRi>$mV&-@Sq98No>7Qh=PiA(}hs$*XjqDd3s_BN}W+y120&Xzv*E}G z7UNP$rG8x14hw;?WA{T5apz^gDZF(CGrQ{KcdaTFi|&$!>CO zGRSyOoo;D?`CHZ@iLiWk{|#W^HI%b)Vc8tvJ>iQ;C6oH> zA@F1|f?J0;TcL1VN5t%X6fM-asgGRq@?7(^vQ0+5B`8{p$r)%;^iHM#lit6~z?lp% zF;G!B<;g1K*74s!7GIC-%UVs+|vcyn;;!?76)7G3vwi{7!VH#bp#FlM)ByatHv z;ME7V^7+Y1X{YpQ`{i~o@i|+&scT*s&(BOZg>7;E3&r_`s%Zaj$Jv~Q_3OO!x1+_w zJ9MXdb7mlFXe`HY&p=U%!dS8aMP5j%XL9iHVA2E+8rs$CHufdIH6$W$3t^J->y)K+ z1J2S!XlF&(AJ4d!m+70MOf=5{wD+NkOlLJ1f&=)0=^^1#i`rt!0T8QRUmyUqWo zBnE28_4C?gvT*z20`@R!8NS}sF0PpO&c3xCok1PymF6QmU7hm$!V)b-MA3Ua@y+w<%|>-~5qMU-hX2w1XQe^^|?7 zX}Z*?;vVc)>oX$i=*WS}4!+9j8^ehXV3E&Jzl-qVduDce_zoq$KN`Yz?vx$;ZDA|s9^|E(h zhmx5sX;yG34qfD**0Hvl81UrJlSv^NRu77(r`m)DQR67N6!#}88E?-21) z{~g=W9?%{Z>U;QP|5o06`Q>m0@nvlNKeWpK#*Az48CWM*Sa^zc|9ltwC$E7WAhjv;#qpUUexB77bjk;UnhS zb7j=o;;IKx9<;Xj`se+7_U@-scS3%1G`!`LYOWKFwu+PQ>kCBz@1Sh)=*y(+@&x(! zviQM5$baHl2-)48ZsQ26e4lrzM zD#@TX%yyv3q}A1B^ftt8dOy^c!np6ijjsb*c$MKZw^h=3sH1`13Bsb=zAyyQjBz0_ zQRH$irq`2t^>UL9yk>D{TQaP6y;vhw4^{HuZ}t`{t$-*q^wdFm{+Brmt65aZ4i@Y& zb`SkqxN7qfOa(LC7|Tpn9P()jxQ%TvtrMn4>m%LfsBHMcHJI2TR>LQRJlhxl94_mX z84ut3BEiT|9z7>99Io&72~*e~0?RP~0Gd(^@GS`?oh7g_he}p<_J`|~mrg9#5f(6j zu*0jnyR!b1Mrc3lTD>?a9?fMxsU)cyNK^_lun_{N5_Aa9j?yPVj+)v!mIm*co3pP*)P+iNA24sFPqlWJYW;3WIq0VZQjFo;W2czpp$hPLV$YViL__8{|rs)=K zdsRL@cI!7_m{+gafeqXhmD5)ZBojp*uV(u+-TX=Uyv_1XEv`mhTUjnx-i`g%-Lbm* zn$2=uCw9=t;LrEXUvAw#I*)EsD_O@HZSD_l5ZgC=SF_8igJMk10$3k&>|?o@#z2L$X}A zjs53Ar;|+{Ia8bAwXbD>5D{Q%IO*5pIO7G+Pb9AOEoB&(Mx0 z{oT6r7mmB>vCpuUgn3~$p@W^e0rs~!tIa3a-+XjuxwTTUEVGvf*U8NENc}eXoVU^M z-pD$J#x9qc_O%I2YUE>JQHcX~M1(7yb47QwGNSHR-Mjm&CzlRkD9B#R+j6W@xAL;d z{`kQO$ZKdxv)TBo`OjC!R}~wgNJ|gke+qZrN=8HD`?!bL>YW1e1x#C17N3=JTa(%Z zrhz1}VtZGZuzmG*QEf`^9IqOD*W5*mwj!@|USvb+_BX%U{4$XGHI4Z7c@Ek&yACnm~! zNOggSzCG5ZvG#XDW0v@v;uZxPOrzS5+y=9*BQogOHBIpFVD*B6%*Aw4L zS!v!NMhHqbHnn$^E~TG9)lK0~Qj(phw2HZYD7WN(uggobh+t{649b$Ov$VL9Snvn7 zy?|@-8+q|QLGa}*ORTYSMBYAu)))(au882HBb~S|#ICv$b z^caRCZSllmng}boe)-JaPOE?aE>4@X(VD)1yk=1)^o6WFyWK?UWW|y-=K}=|b1_2Q z@Y=myKG+Q2*B9+7d>!-P7Q*D)JuG!Ts^(=X%O1stW1x>1lHES+|Q1?dGw`ZpK2X z{A>zZx64aGcph(zhoQlNS4op%6|U9j)sf@7(+hP(y#C5g5KoZlg6o|Nr^#1#9bL4H zujy29-hDZOS4QVh#qJ$YDNHOgEC%PQkWx@eyemQ0$zCwm92jF!r|i_)P>Jq?CeUCF zc+%Y=86hJY$w2flP|si*F~=Uv((0sqOC56*D;c3Y8Pkr@vV7H>N+M8Conko#Z}C3k za5g<6l}n{9RFw~BOuZbr#$@*iFc#Dz2;uQPh;1R~EHz6rA;@_(ibTb$x^!kmqQ#$| zYzxU^bl<(FV{uLO+x%0NVG|IuAoJSFmp(bLO}So^M-q{TE9lyr`u2AU??rWf`u<05 zcv6Y3px8?%zh700{ZR|0rL5Sb8C4XF0uk5GuQz1e&Ddk{Kp@y4kUw&Top{c>`A;9a zPuPPC6CUr+C}g9kt$;xqil zBZe-H@D;n%X*-w92h2jiwi)KpUS?lBarE(mc6yU<-gEglPMz+zEOvjXD@u@(@f-%f zeQ)2Jv2-7Y+Y3QQJg%-iXdPMqvmN9?&Ix~#SRz7CpsdvToHLW#rf7dH=H{%w9~(aW z)9??B-;qm%=TB6#stiJOaV4LW7@zn#<{wHovZQpr7cMV_cnYP>uG|8-KSuFf8D}$US!PBkYDH{`;|{d6k_bXNX!vOtqSZF~=R0DH zWr`J<1onSNXXtI#IH4TgIe?4bn=%mTgCE!FCCwElNiCITFzY3i+%Q+kk7F#9lgCYM z=Mt5xiDc_hBs~HyG?Lj{pQ~|+c(WdPF%WH=pEZ>gbF(TAeh~ox2~$T-E@Mx83=yZV zkVW`0G@-_$JHZ$aLT6{0mMZDy!02emdXIgRVi(&3t1eqpAlpUabwvdNK6@}2zzHGZ z`uFJgds|aT^#cY(mQ`AhLq_TP$_f0T=nQ82<=gdpt44fR%~B~69tHQ3*cAfsxnR#dK!Fm@dco9o!?ywU09BxJ%wEIQVgDDHp>Ah z2?G7OzS&T8P2>AWBFj%xGaLB!GpJmB2F*N=Gi;yR8+SoeNLjs{ekoMfDIOt z2Wg|bSzBXdcU}PeJs4e7!Uj;qRRV*Kjybr5=K0%Aqy(Ldr@Nd|E{oQE1BVbM#a}`+ zu91=sLRQ8FgZdOW*~5nP<<-Koox)~~FZg7uxY(n*9DC312%>(AvTP2(@&Hb0z}n?@ zUewxGBeWs?3))fDD=~3ZV~L2h?PwNaE+QRUoz=<{FGJegF zc7(RH@I{}qx?6#Yq%374D=6j?@^i8~skG6v7pAnU334 z7f8!#EV~2MGp1f3C!67qK`70qb?M~Fr5_73%CvPEmiB@(f^++hjkzyrl2sLEDR_jw za`byV&k=$&;~Tqo1qYMrM<26S6t;Qw^03zHZe!Q+QN&e-h>Sm<~qs8-UEdt1EMM)U>>AnFp$!RT*ZD@HhJEdmqmG?ucg_gdoMRe4d zEk>G-B`xK}eRSMIlVv8S;8Qwht2im`6RX#t&ecDpZl%>K8e)Ae4hf?F+Du|re;Z;C zy1?)@P3bGCcuM0%`%v=`?$e<1nJy%rIcJF_rQ&ar=1v>G1-IR_I7Rf|3UmbZ)~1B| zS2ILg0)Yr(K6{|@&JV%y(bX3kN*u!NsnYU_BxxC1^pD@7ee&GW>D4OTxub$nUCutc z$C>Yh^){>W8NR$2WGq{WA+;?u9dk2E_4-)nrjCTwVL~N80+Rgvb!62nZN&q1x~;?> z1G~av8#$yeVkK(_^mIS+r2CMZwEHz08ivoFyrBiRe%o#M*!szg%<=PcfEXm(;s-)phMkdpy+7~#}h#J#1KT>>Qea#?$ zS5Qz`ZZKu}CQRb>SAV(5h1Lw2Sy@?gp7r`;-rj;|Q8`nm{HK({C9f{U5crR9Dp}kM z^pLnDiY1Iyg138FZQ7|rb(!_vrE7Dmq^tzA#aU;`t~q-lH-W9&t1q?Z4`szuzQ&d% zo%Li`7pS`1`$kJk?yc#7SI_kECezGA%Z}Zr<+%XZG_X`%LMUO^!;t68%U^n$Dx{7c zTCoF~9<8@Jw(S2vlH>@{tXw1??2SD^A|K$|7g``I(~PZ)r_E1KB$eG=lEA|Do{jSkK)J*(6(hy?Ynf{n*5Qi z;NTGcV`slfa}CqiJxr%Bd=J5W+OrKu$8?t|qJ~jEVx;Qwtet8yTIiSc#c8$6xSo%I zDMj$ovnWx=Fl&}~%BXB4UZyv{xla{_{)IJzAf`)AU17P3vI94N>8xk{@D{|&l+Bh} zAIvIl|9fdulf-NM_>DgY&1IWfsbYm!_@%&t%8I*5l=)unRbW!QSG-f3aor{KK>L*6 zC{^KG%9yNjy=nPdFxol_VG3NV(u=1H@hSTVAk&2d7cji>us9G%A5wwo?%_lD6GQ~-QD=>oZZq- zC_~?K!p&t@1aT$Rqe3wf?wm-&Lxs+aSiPAd?D1gN}DC_bM(`#>gb&e2>I2jH0zc<9USyRD=A%eLZi!JWFx}R0(9|vJsEiWT_b;o zXLl*1of+b)=zsB&APd5`R-Ob+BHDVs&ovUSt zhfhwyd5S+oq3No4^syZ(3~eIKM(e1pq`c|JH(>j|TBc??g*W@&=8`dukKnT{<0R;> z^yC=L3IAvRwHJd8-6x=M_)t*C4lz`(5(zIPKzopuWBzRK3@7pM0aKGs{f|8av?ZV* zdfijUHZwXvucNmy3wi-N7@4V?JcB6yuYj*9eNt7j^-N1JL zQPd}+8rz}=-W}V<`Wo|40CZCm+s(Ts=;|B`4bZJ*hOc)-;-e%?Z=br;T;rg9=cG&^ zQlVR#5PWx?n7%g_sZz&A>-##lHfL5$cwe;klJr%ITlJo$G75+zaNr~K0hz~xnATmX z2d!;5M+(i2-}EY&;rDpI_p#zOcyRV#vut2du>n!UG*Epin8#&QY5dfhtS!*RbBo1L zVkb8GY{xQOH)vqkB%7Xm{#ullCtdplyRPctj`*&t;BR=CW-Jh`Gs>~EBgYQCCu`Rh zJg6qy=-DFA4sD@i9f|L_^*SjIxTn*l-#i{Yyk6*=29F0Qyom5q)~)ts{AfYyt&e}x zPx3>(7}p(~6RJ0Q+T{^A?NiSl{(B_qky%=1PN4My=ElSPIp6zk$9|^GSmWF0~9F_`PAyJ!mnH$*P; z`AdqBrHuCRerxx`_vjX#dc5&>TnGSwbYnJ&mo&hCaU&n)cUc?{)-tXTF3KJDaSln{ z+|6=jL0JT{7IM6_d8fkjA$(L}PG{FI=e9e)hoTWO3o@!np-(hfjQx~FGH6AM!A)$Q z{II{#XY$IiNUN;D!>ZS@r=uO2agP?U?Q$4g(g0<$6{;;u0aQa4PiE}}jt4w7*a@NC z123Q?shp%y3Tleo_-x+czZsf{?j$`}mmWNoq)FU~a&Wa;(q2^ur(K%1_)0RAj~At~ z>3Q)nV-GTv#eB(!IPe50n9`Orp>&q=Mrqb5*L{J-e<^BcDi0-yjuM+*fff{>nyAnG zzNsQA9ch1p95%0W<=cIEFG?@dnFSj>_B~-OVhri?2iPlx@ul+yB$=_=WwJyY7nSTP zjBAA7;aT_C7W)GzZn+j1H+-r(rTTP!wE1Kwc1-OGF2%EiL?Ykda?t**cjlP6I+n|1N>cM$>oY3 z=2l#@PrhvmKDF{Eq0&Y$Wc}2bcGPWU91+A$@mWUPfIBr zGvfBj*Qj9cHzd+!p%BQYNI~M`E;(<5tAz)c9`(_kyG}0$nsuF#je{30$?{&tGp z#}qv=Fv3>p1KYS$HuB0`ThOvjfNAHAJo*$?dij6JV~urr#}g3A2n70*__xNRH|#Oj znH*!sfI9@~lb4@2O;OK?MO8wIJ2+<;g6x1Aeye${+>+`%@22r)`t6x44Hlvf7W%8% zjmhY0x*fy~2OcMz%qiBSi-d*eD}}hYSS*C@e}Ss4nd_ zSZCfuo_0yC2`0QJGR5Q==zcVn^>{q1u_MDS2jLX?C`Dwoq5}sy=l~aw;J01`io5Y0$lIHJKD*yTXeR4Q7^1#EKeKnW)4^8Ygjcxo3 zp9#UiKd~I|_n&%L3|p2gsD^x=r1H;&b6?97D#n!n1+Recm;}jDf@ytrQu&1F`X&pL zSMi4g7=V2!RwAfOHJ0}naP?CKizb$X0HuTE^Cr==j77#JizH?{pY-d2&AXz(7m;N5 zqf99=5pt40ygel1HxEil1$9NeGYXci`A~cC1h5q!HkB5-{)pJaz_tenVoV&VxGy~If9Kpc8v0DK;Be-?8!o&|Ep>0R?qbT95-ez%Is)}2+%4*EVHR8ldD9fH>K8rM)|el6Ol}pD_*#qTkQ>Zo;j_eP7m2n zHzAK(#CIULzWshOEt5TaGS>~EIk|et^Z=$w85zGv56wZ`z$%M`?qc+^BP1WuZa#;$i?eU~6ACMlJ(M#69e)%#8` zRB2@wDh^KRv%6CpA{F@$L^Y;q^+YF5cYh}+s_L-D!R=K|JUW^H4wqZOPnb0WakF4* zFKK_BGNuPhe6||2tjvzb#tV<_f=LXIcXl8{%IZT&vHE;p` za(a4DzR#I=jzy;VCu6D{-EzT`%layfFN|7SQ|q^L?NfNsXShi?uXm3jYbG60&mw6v zgUBIg@Nu0wKpeY7-_UwvI{7-a>)=IhwPn`5bJRoHhRckQTKWKK}$o-cPiMY@V6BLrwz> z{_%TYamgIBedML`IEs4Y2BbHs=;Fjss+yAl{3{d$!r1U9aAHKCN)^7M=^6iawU}@| z&P7ZffB~Y`)%B!EqZe89S^#qCh0w;ofBBdUl)ZdyFKn;l%Cy3!SLx-E4}a3HX8jxr za&UKMn<-zM57~|XhP(g(%{ny#IrV4Fu_$Lo4meNBGRjqO_fe{)d@0PjgT7ZcBpxI` zR;-31QtUhb)M#j0-+)RzrG5DC=nqr|M~L=qw##q7O3bCIf9h)%w_EnElN_GO=Yopj zD`E9Cx7S=E#*lC_PPwW2CG6;i;VacGZkdW9GmY&whcvHXrO45)$RAn(gJ2qm>D(dL z0`k>hfH|LjBZYP-0Ok2PI@vm`zx0rb=H%mWD;^*)F$!jIk-Z9I!YHD;{Q$ zu)+oA1=}dU^%Nn5&2863$fE_`ThRQ~eFn?DhBMocM{w7({!49y?49Yp7C#kF$egEb znt-d`d%bkCtzL?JEa`sdh@KC+br17l@Nwr5$8q-p!R0n;)uLW$-z=mMVT0#4K}&{V zh5sUd9hy0C%7BM4Zw(R~C^Y&fi^R_AGFq;6OE9?*2D^<-&&Hp~Y=2J%9F4a#Q*gEv z2MjRHzN8d0Hk8aZZ_v3bh5D4)x9IUNlslpGko=VZ=>?1^|L>!v*dse@YVCB|tE@=$ z`cjALs*;7MzEh&<2n4e^)VXm?Cey)xeW313`7Vd%dt5X@0)^Y^O)0+1AKNVnt1 z{B-GsDKpS&X9(pMfCi8lHkq2$`I{w!S}eVJt5tD19GeAR;24B4)8Sf`>XDvRtHQUa z9nu0`yaj;jt;<9}Aw1)BrjG^L5{Di+JiC=iv#yZO~g6P;|PT_mOU~z?3|MjURXAxwzg|(Qm^_7I;PRFt=x#xojEc|D-!VR#G z2bbrM)i}ytUZbM*alXFd^F^FEu0NokG{#KYZ)E z7$meucM-)sepCARrfq}`e1o(A6c}1Z%(mOCJEss?P(z-lU*5LQA#Nshw`7+~As>_Z zn+MwIxuk@ZM)c~kB-RX3m+j15yfc$-&hT$ra}J&M5XB`1+23Y1uw~HoQUxpuOOgQ= zZxrmNAw?-c4kVNu9YQ?;$nPKE(_0G6V1G|8l=zpfP5)k_wh~A`I3GhXE}7AjA`YrI z)LIjZKmu6rH*)^{G9JT8;}ybHrF16TGJx@)p}&vgOQu0UPed)*#DUGK?b24+m-d@m z*~!ZD;FI$=9ojx!f~(-NNJd)d(DMiPhuP?rSh~vgG62Q05WZo|=4!G@DT6;!%WAZC zf?Ix#?n!zHInY%p{`Ld~`Y=Zi0MHbKH+Q-*>*O`!Nf#K!1i%6tekq31=qCr_Nnx;du z2_{&U)C{^hrQTxuh!w`MjTu0nQ9JyeuL8GdB`Hv0k%462D}KV!zo0EL=BH_ac2(bh zjCOvb!$pr}P{I+^v53DNy}bMh4ot6-nD{<2jU~UQbvi@~HMlQ#41J+Yh6vBsvC+b9 z{LKO&?5fCi_wyFgZ$I+d3%pwqogU0!mj0RD{Wr6|8B~ES$YH{Ca*b;qM7I!6d(#gy zTOM{R;-8Db?p7VRxAiYp7aTyH;{v2AX&3SQQdG8-P_$-T9Z!&S(VGiq6J*9wymVQ# z=%LAD|8?(L>bvIFBu6*~_GaNaN-F~i5Z+@f5+MUf(@=%3nbi}PekNv$wV9k;mQyeI zEc}YGdOO9~pLiX28@HU4z7e%n z1Klaqr{(N)HuII-WV7qr`Mx*(%6VVb-NBUowINhTwM;~oL$Z8IcBZF2tu%n|Fa|bx zC%GaH0|EWzzMrl^kL{koG2NP&BMbGtw^DXvRk2=24zE-N^GzOj)}tHOyE!);ewRy* zfkxzdtikojX1^p^mGuuUHGQ%cxWy>1@rkyO3_~1POEm@sul|cnZ$7nr)g_eG5m%@F zAN~}iopALJfzE|`BlbP^MuY!R*PTAIp{QX152`I8x%Q;w>NJ*$Oi*j7rPO}O$+hq5 zk~UIHl~Ss$C5j_Rt;rO%C6-dv3L?raRiQyGjisWk*rN8WsJdp(moszDegA}a-cQf` z=6xhW7U{A6$Q&yNxtvh#X4uq#VG|)M;Sl>aCJr{TcJX(w@;(hJ)^orVSBf-BDF=^C zJYM?V}Y5Q0+CPVLPtDiZ*N2N@*JV_#a)lk1(JDqR?x<+}4;io>vzU-G#bSN^LXnppZ6ySdtyWA>IE-#ur@?hM&{>)PnnyLtNzTvpi@Bl{Es z-o6>T?5w{Acqjq`ScO*g4fpy!A}#Z|$_yjgS6z24x93R@yQQLb7-C*BPSfi_=WGeF zW_lqZa!9ItnoH%vSbH<8|09BBk<^4PLYVrM3uce~LvA2xP>-qw*v=^cpTv~_v$!~I z3-XJ;K{s$Dj&e+8B1>C;48h>!Oi344X3b79wbq>6N%14BB^5T0p~L$+ePKPi?j%=T*?hKXj`e;v-AyGDuF{1vC~{&tXgC!kQSf zoe(7Q1;XO#*Sxu6bGHdcK}!hlkHS~VLS<|7%;(bq2ZD0b{ix)*S<$+MUIEm$v^sFN zn!(U%YzvGh_CRR(Fngq8ih9mZJwI^DoK;uGE$bUK?}Zd`$@ zA6EXy65yBPwaVNkL#>xhnXKfsJ(J*LhQ(FUAWjU31DX24ZOIO zg}56k43}r%b&LXlCpXa@xP+NZ%py0QJJGW~;k5oQ=N_dz9NRi3rI;mfivE{nUO;-CS;cF;% zO*wBL+Rb&>^zB|ZObZ!W{m=q=SCXU=t!`>tiHtAwT^q4$yD2p}X7Pz5@Zd?%<=aaO zukwC{AU!y`8kkxrz1dWq|$DRt3UPr$PP~TJQ@KGIf8bT5>h%QzpAV3t4tp{nfF0PVyGiZ}`JW$oP z6mo9vmHNf2Fql14q@Y-@9^{{>@I~z7a8hUXJLCY-Wa?OG)|&m^t*VKpzqt# zr}>pLB1ge27-cqsffBsqE76uEdn`3Y-j}bL&Ed@XIao_)!TEk*VptT@I_~uG!Oteq zobT{Z(Q4Qs#G5dW6`f$DLSHUU1pa!@!}tBb9W3JeAgkFPZ7%qM#(3GJ?+m`VR!ebU zLvj844r<1k&}<*cD4aN3ose*_x2Mg05RfNK`s}>h6Q#3=DS)}$5gO0@aG97-Slu4A z0(n{vB=YkSVaIqpJ(<^m3y;GHf6)f z%}&yof)R@mrT<61XveOQq_o97&tFB$NU#cfi}^oFE03?ZZW7vVYL1p}9wsgpC~DU3Zf+JXW&yu3P*C2Y z$N)d7d(Q1Ig5A`nTj`IMy`&L+N+bc)DWu@&rO%O;38i)kR?m@1>>pmJ6#o#h zZGdlgkc?(Uht&jXsJHCXt&J_~keyn{a&w0Yx7Hw=*gw5uRWT7`A;k;hHhyhN6+ou^ z)|kX@S6VRYO1!Qz;#JTLdgn#;iPZPp(;2J7!&z`fHnrq9H!2D-Mi>^4?6`Ck}d{SzV?aDg^tV!MeJ05q_zSqXN(*MS zJ6TP<~3OeF@rmee2$;>I*;cmfR8$!)3d*QG|et6E88B=XVa-dlfYsBMlRs8 z-tPYtN9d@XI!cHWskGQm4s>)p><%M}#zs*ajiHrGVAZPwUmPqgEn#3lKfe9R@N(>l zap*PN1E!{}eF^_?C@=j{I))pJw2R|xpRlN(7tj3t742(>F?-4|gXxz-(EaUIT3Q-D z9xi$BpX6ulahB}F1QH}SH&@Qf0YM|NwY_*5Zbc2jWX5>xSgdf9VxpZvO!qaIli$Ml zpT-MXa;`5j**0$LS;@8@Yw3VBpJMm@N;GWZc2*8RsblKX)mX*Xy%;MAKcG{5@F)IW z&%fyLPcpya`gCw(HArk0fazxb-z_z4?E12hmhPLL2aT`qJmcJ(y zy)!$n_BtsmKAN@F$cfCn_j$j+BN`ckOfdi2@7>#j-)balO-3cPCxmz36D9mBhgMj* zE?SvSBEoP`os7(DS5GOvXYbHALae4XMVnq(YZ!-Gw)7mT ziDU7l-@rV9MH2CaIx8$@q}$FGsxG02M5wEgdqQyn*JGb{WHlkU7pZSfBm_q2oljXZ z|CqEIDAh@zk94P>sKp|MHF?=4zcF(vjAk&;HNLg@3Z~@KtJYK}PP8YP`_3PEDXaz5z%9tT zV~ZiwR}q32zJk-OsS-4dh2OG656!cXU&r|PdUuhxXccdiVSCL2Kts-a0fxf1n) z8Pz#ll95?4q}RH$Ba0tC=~|#a(*={;uUBm`52sS6@Zd?9*$yV<)U%E* zeDjX_LY&RwcPqsyu&}*4TCz=G_xBsONGuCS=q7AQ!|#MI}QgL%n(<2#Apf(ini8;QV2) z6vFt|+A&cgDvZxLH*Z~bY$Dpjp z;wz6|d7pZ_e{?i$VwMRsu|oFTrH(47(YmyIh%Q_RgP;sPn%D;AaOp)`rx`i#vd`a4 z0;8b}w-%;y4BLkj(&lNdXJjlb5+j--rnr+!wfVvCb<2x=p+JT6T3S+gfyuYZWVgQz zlr{y#_HDe}wW$Rfuc!N&5%ZYGtHL4vtlW_`UW9T+g3^msh}f@uS+YXAEEA4ZC-Cm; zVkK2)=s_`!E}ts%|>iiAXDRV0fe0 zu#cC7G_C3`^7HiL(oz+dAeAu+{|zC5^*lpg7HMc@B9=AM6rbFfD~u=5Yl;zAg1szs7kPVMWs>~Z^w62}EOcTeYDQt1v}Jo_MpjZYus(DMCN6R92(#d?v4S^9m_ zkG^f-PjT$H9Q%xJVg)gKqid{#E#*t|p=1ArWi4f&{PiRJ?b=3~&7 zsLTXoJ=Q*$CYLhv;6_i~%CvZGy=52!ctovP5J?=P-HiJ~RUcesj?-iA=<(etv`V*P z#>N);!a0|^{Dwyx(Ws~%GS<>f6m->EgI8f_fN0k;cn{Vxyovhcd-T^lUZ2dI&2ago zf}0o6%B}K`s|r56u^u`2fNvu-chrlA`26vtb?kNE+04P|03*iEk=!Cunbt1O)rjRU zdwpv_s+iZy+r`x%Pty{5_hDTJFmzFKr6@TNPM0RRJocMB=Wy1H>aA3w)!~Qw%H|j+|P`7DyAP7&^=KXZOM_V$Wsz+qYu0M zogFEt)5)G7v?P#LYtHUPQX&vrYrY^q^GYN)Y5WGsO#DV*McjZ1Aje_`e@p5!%#ayW zzN9EYM!fL^b@lp}fo+^Nb+kb6Ynev)izGAo7QzstE34)6Z|e-KlH)Yrp!Cu2qOJRW z^=Flpg>?}aEo6Lve=#Y2S&T&gpyeUneo$9~=kN|nL`KFLO00}X()RT>!QCyIv zWDi*==D!AcIc}d{d`}I&_{WOP75h%ZH zn(1a8$esLku2GA^TjEtN-Btdv#dy$dH@Do(cSvA09w|U2(w{jJOC&7f#qKbdo=%h$L;daMNmbP@lQ_x`dA#U@P4Hq?dikzF^%ad={ z1NuSGEW~R3yW2=^R8v&FMAf^Sr6cj6WU;Bf?>2%)0(V7KvIP;PsvF0dIDyg(lMtv% zNP`KwS$g5Jh??jg^%9PmM(p|Gkurm3di0dBMMTnVUhtJdpe2Kk$*{@eV$3m|{c&{c zIcz7c@SfVzi7bJH7b`ijP$Z|sPrf3`dFrEzEjE4H@}!PuPwmW#u6&HkMzR=pvfa)G z^9c!Og(bzguZ@`6ol#**US(IcMYkzQ^x@OlWf7N8-;}!aQ!}@pEU{F)?hXoF&4k=> z!jN}=E_9v-5wEdkdSfGhEQe^+)7}wHNT92);(4%DLX}f&QB4uW>9z#hZkxSBS|GU`hzsQ z&pp^XY&p7UKAr)v-8flY28?XusY8B^nNy76IZnQiLlfuNEz>@C7Wkd}%FcF0J!@du zY%%euSH-vYd3MAVUndSZe}&K0$u7%*R-#ItT7b6smXGlZ&5xmk_jhMFl+v_4v|Htok%`B*^je8e$uaXqNNRT6CJ1G_P1FZzFzG|Q(o zLusiPnQqJ&7~9Fa7NrC;&J5_Ns#rN&>eUg+9m(Uw)zQqg&_m&OO=wgK_Lr`lUqSwl znA>1BPS)+Ta8_z1};ewM#rr1A#UFCoMFzK&hus7pU+@uX_aE5+Z)e zp*n%&fxBb~T1~czHU1|Hc>m}cz;t&MWqO~~V_Ncr-G(}hc6_G(&FjB8-qCT}WK)YW zAAsy7N-T~V`v2f)|82zoqHq6!%>8B9P64{jkeNO5szPc%vO-p z1dS@IZ`-q*{>0$8g+jsoaNl|Dw;48Sj~}_IjE^4W@4$xf`1d2$$(_FwV@!<3gruC< zR?^0`VYEFGF?HKXEy9y-lJMwJs>Dg*jP6By*{@K~x;;PNN%Ezb9%%CCIyD>Y?nRUH9mR;; zk47e&X%`in+&z5&M%^4O(4DNq3tCb(WOpjRoY0N-1368n!?oWgU6y*KG`ZG;o2~`j zClWsrC)alt`Ed#euI^irP*n zoH5qfV#mO8>FU$rTt&iMnqm?#2p9>KRz?5pq|R60HqB=BxGO)qJaZILsb3XHKl3e> zB?o0>7r$Q($6Biid7TdyUZnA5e9bG}wTp}kc^~u(vR&&}fTeEj+_ZG@!+09sMt236 zh0IwPQxKTd-`4ka2Y`mA>>tR2{D@9J8?> zb)e}^g7g@X!l`IG$@pdRb2o+OLGGhT6W+ds=h&?QZ%+suq69>~Qx`%q)O6M9YQp(A zOK9~@eP@~lCp-pT0oMsxTipS2sEDE59&9Qq&Mn>{vk+-PBBOJ|EC^1GvHUB^b==q` z^)6*W1b58CuZYhL4xb@yCIV0C7##t_7YI*C={`O`ZGI8RaPC}IL`ut#4WLp(t>Wc! zDGcZ^QymtdqEhoO6SBYbzUy6u`JZEhC_+^)WFUe$x+{sYMwm^hB?25d;*{62Avryy zHh1E9hM4K{ybwlkBwe|!NAg1|6#&rrql=8K)Ul#%xghn)6X&p#Pv=r)f*$V0R%%u* z6b8+Jq=O9>EP&0uTK|?brsJhpG!$xG0t!$c$96qmmNSpNzndB#xc>cD-4ckj-ufalJ%AjDZXdF9!@nyM+_iBxpdUf&EV$4BqchR zlDX#!$cvOXuD5R;G;qXj=-elMUu9#rijLA&GEt>r3J0omxO(4{B?#&@HgPhVz7m-{fVUP9pui8wkSDIV^=#W>zs{v>z15xaIN;B+TswZZ8736tbhQ}7e&8B=--tP8 zYsI zk2tG!gb_5FdpnGxF=qzK)EHC|SsXkBG7cDLx*pv^M0CD^1hogPOB!$JeTgOte|G`BG7t8px@pI?!+B8X?9D69NpFzZ zre2v23q&IKQK`&q6AE2HsO{^WaZgbGx19X%$A82N|EKQ!Ps6;XW5~LGC!&AOEJR6A zLqYr)Rh~a6vs=LKT{^siBMa=G0zteaCj$~4EN5eK zzJWvVxG&>C+!d<|EOIKX88qVJ^=j)s$e->qtnAwW(H+J;y6ZelBUn3Df&*McUWk<)R|EuxzC-`m_O)uG z69)fAn^A8<2@rnKz{6j+3Eoc0!;jv4sPK@m?lTh|!o(^a^rTiflj}b=o;DHHbrpswH|YzuwB+RaFD~ShG=3n#e6<*)<`?9Ya7BpL0LVJB;Up-Ks#` zg!Na*O3;V-C@!4e08vQ%-}M-XHT@`f!K6ZkcbS|k=n5GzjTLv=3E`duOOx6sv;Urp z9d~NX3WP$N9;FfA+26lgSGV|ueC1Q&^J*BQrPyvD7ECNyuqUPxfmUm2jG30wlA|{{ zBpf^f0-C?a^@-ZHUpSJ`*~GN4A=p<QNKuvDhi|QQdcxY)Svf&cvM-* zWq5I#tlzwpIk>I4P71I5SdhkC?l8R<_UeGd7gDtI<1F|p_qY}bD|qSF=bJ_pSk}{% z0%drw)k<~>yjda>NPFSd=O0tEUrn4XQ;^1nL&iYzjE+1(vn2y7Kf!gFq6jmRfLxhGv2 z4%|X=(eozg;%K?+<6np5oF1QOT4d>>_6n9>x0oafc=e=q3Y-dXDFJERX&3KG7rCVn zcu?pqBoZg5vmFV>>6TN-l2d{=3eFp?c65Yk5GYlMQb4!noIM_8Q`O;6eGhjQLsi|T z{&a<`bPhZTfUi7dNmiP_y4x&+eKONelY&2zBF$>&D(UU;#sSUKJJ}tSKZl zUFTM<{qzA3^%VZ0jId-kH?~K;l4{r+Ah4KL?7pT;}XA#rL&G+!3jkN!@a{CWNO3bMl%PBhr*rN5z2 zm$G|5rV$6-qTL(K7ji!;x^T^2(wC2C@fns!ODCF-*!(3yE?{@S@okC_o%e&Gt^GTi zOULYvoJX~v4F+NBJnzwftE2#NRHIh)3|Eyf_kjomy>S3nLW#o$#pflOqjRO4`N;0p z{CKArj^)lowh66GP9T9WQ8`w`zKEhm7#$Sa1wK!gSdUV>ESG_(BRzImlgwFgNfShS znCMVY@Hzjw3t&BI@51n@s~U+4+wt>FsuX?g*#p%L`tqZx8pEP{x=|$*nu9$I){Tdz zcCQ9)1A&=s(g$2xOSleE%~sCyn8ao<KUo>vp40NScAJODGJw=*GB^k&0|jgW>` zCLgNJRV)RZBTN5M23~anoV(q`LKfBUu! z{)?-u8-3a|CXRU;m^Esy#%2^f^4*_jm*M+IjZWM(MXOZCDLw6I=zIF&NCxrV#iEPO zhDYT!+_jQ_M|>5UmA{%B9P96(Kt&}%?SQIO(V;xl#H{J7kL_lok|dpJT~Ep%UvP4v zeL;K6=Q7;AOsWSK!^jt*0%i5#s^o~fv@lPmsfuwZj7gg_lBxv)8V{5mk3XooH{;P# z3D@-vg|HIUAauiwQ^K|1c2dl$Ntin{xQ|9aWBNsCm|(ua;64H`{x}cE@&pt}pwDs+kYyOcQTEgw-_vQZ{E= z@V36K7xXLe-jL6;wmb$&p%>*6EZDI&1y_nR=K2oZjA@#&HuWAYu>E=gP2pRcy-wV- z@@N7D9Q*6Oo;xTLZmh99g|J5rGVSeNwA{^KEZy&emxjw&I_fqfV37OMotrn>`+nCQ zsf_rz)dnEQ>NTM#rTxlXkH-fn6nQm@rY;uOeU6 z+g)1b`P0q9;_B!#C4amUN$M!IlRLiDMDY%f1++l6dEyq_*cwdS;x(+aIOVjMz$fnO z8=5wmt`x>xuSpm_9^F^PzfY?O4yj%jscuIQ2(QZCja$N|k!uQ|1d{xr=u|CT9RooR zazdJY$0b)ox=*-;gY|<%Nfm*+3*`LZ0(Wr#FvS2SYw0`Ph6!g{BSpRm`(_uWB=(+| zM3Cm@SZ<)Z<6Z=w#KrW~v!;TT*|`A=svzQ6xa!pioAz9t#v#9gTjmv{Wlr_X^lr`z zNMJv&a-J$YA~;^LH>#nRB3_sbxa<`JNti)cF>tW(10Jkdb?P^2tzOzst(M;8aFlrd zw&K&1o0_yYLNG9L&V*8skp=D#PmavGmQ!x$4y$k6fVo`COR^ckr|1QHvV404~I9e^-%BVk1kaX zH?2w@?RM`1mqbr+#cgjk=IDjS`@@{S+pb?S1d4!{6ek22p;#_t5ywp+wo zV{NHg=2Z^25{o_bnD$(+1}WK=of#@z*Fs~Sp%>R)cJn>;U5obgJlxb^E=AwnYl$CG z-{;4pU>M0tDz3ZtIUjf?Fz8p2t3WE)we|AA$E#bmxI>UDe?7!yI|Y;~!Ti zWz7oED#d}Zk*$;u+)nbmMDy~2cv7}E7U%Hdn>}iKrJ~?A2dg;f78INt!r0Wt^cFw5 z#=}4{n$dBMhe^h7R&F;ffPAU8me;eOu8dK>F_*1%Z-9zQpydIvF>(VP`XNHp7AR`Iy4bQ<#wBtI^AN=-B^RnsjWdQCRpOzzxO7d>2w z>Oi-A%(_og^j=Q!?1{`=U*kmF80nu6q;J(Kg(E(D&CBg9duJmZ6PKLEJh;KSiQb>z z9NrF}1{tt@nBv{A_sr*NXv?n? z!DjYGU2`yKEd`|9C8Tm?d&dd^__mYIR(>_4c{&BF3o0#M`=C{RM-M3?>3vxBy*bq= zf2ialJ`!=tvOKLr0gRFyhK)4MCr)s;yji-@v_Phx)bjYND8)Bc}tC z!dRAd&EGU3kKpd`h_svm}I+3ul{T@%4XRSTKbrjqS7D)tEjkm-y6IS5P+_si3BYVKu&?=6QzxRLY#$pLq)nke!Z@ zMX4L1{i_dCjui^@$*?AKmG}$b59sH+EE(;!WqnI(DyGuKGb^JjwiH8M9S(8aXZ}zs zFkji?r+CM^5;t^}iqY}9zSfI;i)TP?$MId&`IAH;aB-Rv0D$m3h_7m#1VibeKj>Qk+?=b7{d^@iWoO&Boc+W=%YQuEv6V_Rob&gjzgJNeB0xG9l#ub{E z6Q!NvO1SZpDD^f7pmQmt0a`CLZ#6TMpbU#Y6o8EhUb8?N%UrS$21pNXARMJP~;3K(6;;BGk<1x>SXsn&JFeyKQB znw&0|U*C(v_f4o`R-LmvLG02Jauhm&i7t*sh&OXOYRe+*BE7UNx?Nofw?hn zZ9G|7xjea2|Ae~0du$yR!#_i$a%n!wG(A#2U@Z3pWh2M%ZGoho! zWsa}T&Va{SRjsdoBLw<>zr4)nd=LL&tjcn!pz{sYF)0}d{eZ5<16lp)z=3Pf-mQv~ z0|-K9copsVAhLY#%foh`b+BEpTVF;#ZU?$K2%6#tT-6hD={=7q|4dO*8GmM<`4)Qz ze7bt%O%n^GVVOPZDK4&lUZyF}hwq+edHFSbv^>4e)^!*xJtP4 zI)rG!^`X1Hda*>zYr^y!i#1TK z0)2e+vhTL>;?#A*-gCL>ma4^OwuYekQ&2>{-WEr$6?nX3N%&Q%$*hBGHj*3*ve{_8eNwZ}4*O ztS)x8aze9Zi0&(y2SprON-@ntUKw=Jk$H7{>^`W~O7(Og;?R#trS5J3h{5M$*x3Z@ zxM53?t;$^?<7sBYMSc8`_Chz}TGMRIyaz}1!Q%c#>UIv{rMD(tPgJ5OsmPJu$xrvC z?mifnU73xn+eKZ4yh&75n+!XLA)WXes(0F^xDApW;5!&C$k-w?&5OLr9@qR%ia$K)yMb4b@`T3gU4din<`FUUH z?0AHjpVuTQW*X?s4zmk@yJjJobWu7S8Of(9dJbLd4zlhaxh+pfhB6e_ASd5!qVWo5 zc-INC4;nPO#JpD+@tnIs?v`D)X$qY~&#y9S;lAA$*Q8wEl9810@WXOHOB)jzXOsT1 zUop8;TAwBQtNuh7HDU%FAmmCm$S%y|dF&syd7ig!4)BrMhADxkgdKc~e!1R9U-S8C z3eMepv*0P8=KoOFs7aXefP9l&mG0>6K790LOK|Vp4kRexHb6HsG4p;rJbpuI9b0!U zG&CPE8nJ+DqJPEpu*=o}o7`Bgt)_Kg1aE7-0wD}UO{QV?)sKFf#aY^UPwm&`Mp=M|kiGV((ZQeuoafiC6$;&st+!N{MrW&<2Rd28_;N$fM z;%3|mWNUi=b^8U>W~H!5^z`9D>A{)zNjnWws^8s3iBX&1e4V|=3=M7j`c8-;>9J-Z zmRW)m(d(zEsK#hFe96qDQVr*2E8i}V4-+9@P#TI~mpgjj%tq5;SrRU5Y&?=>;!jgT z`M#8)PjJGY{8bsTYJN5uZQyo-XX~()Sr(c#($-n ze6qhN6VMWdW%i-um1(o|I4^uf1O8oh@mJNuZdVAtfPlbb)k7A8-*Gq5Uv12|FSJ<_ zFfd*;FT$M{qN$~n2Odi-GVagvM?@cP`{^@MzkiXIlS|>XpQ|+MeQXxZEP0ZrFL--B z-R8Gbm_If))^pD&IbK7iJKPcY+~aV`KwI0oejYKB;mA&`e1=kAs?G9wwJYS~+oLN) zhUoc(T9nX9-z(qSpr-*)Z!mb-3z4SGFflt@$nY}qNxOa6T0?3od06+!daTlt^75a} ozltXC8dP5Xe`qBQ0}s!?O^weA3ei4RSfHTDNGJl!#Eb*}7wgG*f&c&j literal 0 HcmV?d00001 diff --git a/Images/Saved.png b/Images/Saved.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c89cb9d7076c3e3c9a8e06ba0bd14673eaf9b5 GIT binary patch literal 2478 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 Fe*ystmM8!K literal 0 HcmV?d00001 diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..e53af6e --- /dev/null +++ b/changelog.md @@ -0,0 +1,33 @@ +# AsyncWT32_ETH01_Manager Library + +[![arduino-library-badge](https://www.ardu-badge.com/badge/AsyncWT32_ETH01_Manager.svg?)](https://www.ardu-badge.com/AsyncWT32_ETH01_Manager) +[![GitHub release](https://img.shields.io/github/release/khoih-prog/AsyncWT32_ETH01_Manager.svg)](https://github.com/khoih-prog/AsyncWT32_ETH01_Manager/releases) +[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/AsyncWT32_ETH01_Manager/blob/main/LICENSE) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) +[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/AsyncWT32_ETH01_Manager.svg)](http://github.com/khoih-prog/AsyncWT32_ETH01_Manager/issues) + +Donate to my libraries using BuyMeACoffee + + + +--- +--- + +## Table of contents + +* [Changelog](#changelog) + * [Releases v1.0.0](#releases-v100) + + + +--- +--- + +## Changelog + +#### Releases v1.0.0 + +1. Initial coding to port [ESPAsync_WiFiManager](https://github.com/khoih-prog/ESPAsync_WiFiManager) to ESP32 boards using `LwIP LAN8720 Ethernet`. +2. Use `allman astyle` + + diff --git a/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino b/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino new file mode 100644 index 0000000..dadba45 --- /dev/null +++ b/examples/Async_ConfigOnDoubleReset/Async_ConfigOnDoubleReset.ino @@ -0,0 +1,723 @@ +/**************************************************************************************************************************** + Async_ConfigOnDoubleReset.ino + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for WT32_ETH01 to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +// Enter a MAC address and IP address for your controller below. +#define NUMBER_OF_MAC 20 + +byte mac[][NUMBER_OF_MAC] = +{ + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x01 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x02 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x04 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x05 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x06 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x07 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x08 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x09 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0A }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0B }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0C }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0D }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x0E }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x0F }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x10 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x11 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x12 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x13 }, + { 0xDE, 0xAD, 0xBE, 0xEF, 0xBE, 0x14 }, +}; + +// Select the IP address according to your local network +//IPAddress myIP(192, 168, 2, 232); +//IPAddress myGW(192, 168, 2, 1); +//IPAddress mySN(255, 255, 255, 0); + +// Google DNS Server IP +//IPAddress myDNS(8, 8, 8, 8); + +////////////////////////////////////////////////////////// + +// Optional values to override default settings +// Don't change unless you know what you're doing +// Optional values to override default settings +//#define SPI_HOST 1 +//#define SPI_CLOCK_MHZ 8 + +// Must connect INT to GPIOxx or not working +//#define INT_GPIO 4 + +//#define MISO_GPIO 19 +//#define MOSI_GPIO 23 +//#define SCK_GPIO 18 +//#define CS_GPIO 5 + +////////////////////////////////////////////////////////// + +#include + +//Ported to ESP32 +#include + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif +////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// Otherwise, library will use default EEPROM storage + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// Otherwise, library will use default EEPROM storage +#if USE_LITTLEFS + #define ESP_DRD_USE_LITTLEFS true + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM false +#elif USE_SPIFFS + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS true + #define ESP_DRD_USE_EEPROM false +#else + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM true +#endif + +#define DOUBLERESETDETECTOR_DEBUG true //false + +#include //https://github.com/khoih-prog/ESP_DoubleResetDetector + +// Number of seconds after reset during which a +// subseqent reset will be considered a double reset. +#define DRD_TIMEOUT 10 + +// RTC Memory Address for the DoubleResetDetector to use +#define DRD_ADDRESS 0 + +//DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); +DoubleResetDetector* drd;////// + +// Onboard LED I/O pin on NodeMCU board +const int PIN_LED = 2; // D4 on NodeMCU and WeMos. GPIO2/ADC12 of ESP32. Controls the onboard LED. + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") +////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true //false + +// To permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true //false + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +#define USING_CORS_FEATURE true + +//////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +//////////////////////////////////////////// + + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + +#define HTTP_PORT 80 + +/////////////////////////////////////////// +/****************************************** + // Defined in AsyncWT32_ETH01_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +#if USE_ESP_ETH_MANAGER_NTP + +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} + +#endif + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (WT32_ETH01_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +void check_status() +{ + static ulong checkstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + + current_millis = millis(); + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +void beginEthernet() +{ + // To be called before ETH.begin() + WT32_ETH01_onEvent(); + + //bool begin(uint8_t phy_addr=ETH_PHY_ADDR, int power=ETH_PHY_POWER, int mdc=ETH_PHY_MDC, int mdio=ETH_PHY_MDIO, + // eth_phy_type_t type=ETH_PHY_TYPE, eth_clock_mode_t clk_mode=ETH_CLK_MODE); + //ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLK_MODE); + ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER); +} + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + WT32_ETH01_waitForConnect(); +} + +void setup() +{ + // put your setup code here, to run once: + // initialize the LED digital pin as an output. + pinMode(PIN_LED, OUTPUT); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnDoubleReset using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WT32_ETH01_MANAGER_VERSION); + Serial.println(ESP_DOUBLE_RESET_DETECTOR_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet + if (!FileFS.begin(true)) + { + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, NULL, "AsyncConfigOnDoubleReset"); +#else + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "AsyncConfigOnDoubleReset"); +#endif + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncWT32_ETH01_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + AsyncWT32_ETH01_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (drd->detectDoubleReset()) + { + // DRD, disable timeout. + AsyncWT32_ETH01_manager.setConfigPortalTimeout(0); + + Serial.println(F("Open Config Portal without Timeout: Double Reset Detected")); + initialConfig = true; + } + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(PIN_LED, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncWT32_ETH01_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncWT32_ETH01_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncWT32_ETH01_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncWT32_ETH01_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncWT32_ETH01_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + +#if !USE_DHCP_IP + + // Reset to use new Static IP, if different from current ETH.localIP() + if (ETH.localIP() != EthSTA_IPconfig._sta_static_ip) + { + Serial.print(F("Current IP = ")); + Serial.print(ETH.localIP()); + Serial.print(F(". Reset to take new IP = ")); + Serial.println(EthSTA_IPconfig._sta_static_ip); + + ESP.restart(); + delay(2000); + } + +#endif + } + + digitalWrite(PIN_LED, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (WT32_ETH01_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +void loop() +{ + // Call the double reset detector loop method every so often, + // so that it can recognise when the timeout expires. + // You can also call drd.stop() when you wish to no longer + // consider the next reset as a double reset. + drd->loop(); + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino b/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino new file mode 100644 index 0000000..43cafa8 --- /dev/null +++ b/examples/Async_ConfigOnDoubleReset_TZ/Async_ConfigOnDoubleReset_TZ.ino @@ -0,0 +1,696 @@ +/**************************************************************************************************************************** + Async_ConfigOnDoubleReset_TZ.ino + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is intended to run on the WT32_ETH01 platform! Please check your Tools->Board setting. +#endif + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +#include + +//Ported to ESP32 +#include + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +////////////////////////////////////////////////////////////// + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// For ESP8266, You must select one to be true (RTC, EEPROM, SPIFFS or LITTLEFS) +// Otherwise, library will use default EEPROM storage + +// These defines must be put before #include +// to select where to store DoubleResetDetector's variable. +// For ESP32, You must select one to be true (EEPROM or SPIFFS) +// Otherwise, library will use default EEPROM storage +#if USE_LITTLEFS + #define ESP_DRD_USE_LITTLEFS true + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM false +#elif USE_SPIFFS + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS true + #define ESP_DRD_USE_EEPROM false +#else + #define ESP_DRD_USE_LITTLEFS false + #define ESP_DRD_USE_SPIFFS false + #define ESP_DRD_USE_EEPROM true +#endif + +#define DOUBLERESETDETECTOR_DEBUG true //false + +#include //https://github.com/khoih-prog/ESP_DoubleResetDetector + +// Number of seconds after reset during which a +// subseqent reset will be considered a double reset. +#define DRD_TIMEOUT 10 + +// RTC Memory Address for the DoubleResetDetector to use +#define DRD_ADDRESS 0 + +//DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); +DoubleResetDetector* drd;////// + +// Onboard LED I/O pin on NodeMCU board +const int PIN_LED = 2; // D4 on NodeMCU and WeMos. GPIO2/ADC12 of ESP32. Controls the onboard LED. + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true //false + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncWT32_ETH01_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (WT32_ETH01_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + + current_millis = millis(); + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + // To be called before ETH.begin() + WT32_ETH01_onEvent(); + + //bool begin(uint8_t phy_addr=ETH_PHY_ADDR, int power=ETH_PHY_POWER, int mdc=ETH_PHY_MDC, int mdio=ETH_PHY_MDIO, + // eth_phy_type_t type=ETH_PHY_TYPE, eth_clock_mode_t clk_mode=ETH_CLK_MODE); + //ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLK_MODE); + ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER); +} + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + WT32_ETH01_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + // put your setup code here, to run once: + // initialize the LED digital pin as an output. + pinMode(PIN_LED, OUTPUT); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnDoubleReset_TZ using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WT32_ETH01_MANAGER_VERSION); + Serial.println(ESP_DOUBLE_RESET_DETECTOR_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, NULL, "AsyncConfigOnDoubleReset"); +#else + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "AsyncConfigOnDoubleReset"); +#endif + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncWT32_ETH01_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + //If no access point name has been previously entered disable timeout. + AsyncWT32_ETH01_manager.setConfigPortalTimeout(120); + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored Credentials on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (drd->detectDoubleReset()) + { + // DRD, disable timeout. + AsyncWT32_ETH01_manager.setConfigPortalTimeout(0); + + Serial.println(F("Open Config Portal without Timeout: Double Reset Detected")); + initialConfig = true; + } + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncWT32_ETH01_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncWT32_ETH01_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncWT32_ETH01_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncWT32_ETH01_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncWT32_ETH01_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(PIN_LED, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (WT32_ETH01_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // Call the double reset detector loop method every so often, + // so that it can recognise when the timeout expires. + // You can also call drd.stop() when you wish to no longer + // consider the next reset as a double reset. + drd->loop(); + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino b/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino new file mode 100644 index 0000000..879a405 --- /dev/null +++ b/examples/Async_ConfigOnSwitch/Async_ConfigOnSwitch.ino @@ -0,0 +1,798 @@ +/**************************************************************************************************************************** + Async_ConfigOnSwitch.ino + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when no configuration has been previously entered or when a button is pushed. + It is the easiest scenario for configuration but requires a pin and a button on the ESP8266 device. + The Flash button is convenient for this on NodeMCU devices. + + Also in this example a password is required to connect to the configuration portal + network. This is inconvenient but means that only those who know the password or those + already connected to the target network can access the configuration portal and + the network credentials will be sent from the browser over an encrypted connection and + can not be read by observers. + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for WT32_ETH01 to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +////////////////////////////////////////////////////////////// + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +//For ESP32, To use ESP32 Dev Module, QIO, Flash 4MB/80MHz, Upload 921600 + +//Ported to ESP32 +#include + +////////////////////////////////////////////////////////////// + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +#define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 +#define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 + +#define PIN_D35 35 // Pin D35 mapped to pin GPIO35/ADC7 of ESP32 +#define PIN_D36 36 // Pin D36 mapped to pin GPIO36/ADC0/SVP of ESP32 +#define PIN_D39 39 // Pin D39 mapped to pin GPIO39/ADC3/SVN of ESP32 + +////////////////////////////////////////////////////////////// + +const int TRIGGER_PIN = PIN_D14; + +/* +Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection +not connected permanently to ground. Either trigger pin will work. +*/ +const int TRIGGER_PIN2 = PIN_D15; + +////////////////////////////////////////////////////////////// + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +////////////////////////////////////////////////////////////// + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session, or double reset detected +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncWT32_ETH01_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +void toggleLED() +{ + //toggle state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (WT32_ETH01_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + static ulong LEDstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + +#define LED_INTERVAL 2000L + + current_millis = millis(); + + if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0)) + { + // Toggle LED at LED_INTERVAL = 2s + toggleLED(); + LEDstatus_timeout = current_millis + LED_INTERVAL; + } + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + // To be called before ETH.begin() + WT32_ETH01_onEvent(); + + //bool begin(uint8_t phy_addr=ETH_PHY_ADDR, int power=ETH_PHY_POWER, int mdc=ETH_PHY_MDC, int mdio=ETH_PHY_MDIO, + // eth_phy_type_t type=ETH_PHY_TYPE, eth_clock_mode_t clk_mode=ETH_CLK_MODE); + //ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLK_MODE); + ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER); +} + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + WT32_ETH01_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + //set led pin as output + pinMode(LED_BUILTIN, OUTPUT); + + pinMode(TRIGGER_PIN, INPUT_PULLUP); + pinMode(TRIGGER_PIN2, INPUT_PULLUP); + + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnSwitch using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WT32_ETH01_MANAGER_VERSION); + + Serial.setDebugOutput(false); + + if (FORMAT_FILESYSTEM) + FileFS.format(); + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + unsigned long startedAt = millis(); + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, NULL, "AsyncConfigOnSwitch"); +#else + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "AsyncConfigOnSwitch"); +#endif + + AsyncWT32_ETH01_manager.setDebugOutput(true); + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncWT32_ETH01_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + //If no access point name has been previously entered disable timeout. + AsyncWT32_ETH01_manager.setConfigPortalTimeout(120); + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncWT32_ETH01_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncWT32_ETH01_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncWT32_ETH01_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncWT32_ETH01_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncWT32_ETH01_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (WT32_ETH01_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // is configuration portal requested? + if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW)) + { + Serial.println(F("\nConfiguration portal requested.")); + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, NULL, "ConfigOnSwitch"); +#else + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "ConfigOnSwitch"); +#endif + +#if !USE_DHCP_IP +#if USE_CONFIGURABLE_DNS + // Set static IP, Gateway, Subnetmask, DNS1 and DNS2 + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask, dns1IP, dns2IP); +#else + // Set static IP, Gateway, Subnetmask, Use auto DNS1 and DNS2. + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(stationIP, gatewayIP, netMask); +#endif +#endif + +#if USING_CORS_FEATURE + AsyncWT32_ETH01_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + //Check if there is stored credentials. + //If not found, device will remain in configuration mode until switched off via webserver. + Serial.println(F("Opening configuration portal. ")); + + if (loadConfigData()) + { + AsyncWT32_ETH01_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + } + else + { + // Enter CP only if no stored SSID on flash and file + AsyncWT32_ETH01_manager.setConfigPortalTimeout(0); + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + //Starts an access point + //and goes into a blocking loop awaiting configuration + if (!AsyncWT32_ETH01_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + Serial.print(F("Local IP: ")); + Serial.println(ETH.localIP()); + } + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncWT32_ETH01_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncWT32_ETH01_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncWT32_ETH01_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + +#if !USE_DHCP_IP + + // Reset to use new Static IP, if different from current ETH.localIP() + if (ETH.localIP() != EthSTA_IPconfig._sta_static_ip) + { + Serial.print(F("Current IP = ")); + Serial.print(ETH.localIP()); + Serial.print(F(". Reset to take new IP = ")); + Serial.println(EthSTA_IPconfig._sta_static_ip); + + ESP.restart(); + delay(2000); + } + +#endif + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + } + + // put your main code here, to run repeatedly + check_status(); +} diff --git a/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino b/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino new file mode 100644 index 0000000..d0c3ac4 --- /dev/null +++ b/examples/Async_ConfigOnSwitchFS/Async_ConfigOnSwitchFS.ino @@ -0,0 +1,1008 @@ +/**************************************************************************************************************************** + Async_ConfigOnSwitchFS.ino + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when the reset button is pressed twice. + This method works well on Wemos boards which have a single reset button on board. It avoids using a pin for launching the configuration portal. + + Settings + There are two values to be set in the sketch. + + DRD_TIMEOUT - Number of seconds to wait for the second reset. Set to 10 in the example. + DRD_ADDRESS - The address in ESP8266 RTC RAM to store the flag. This memory must not be used for other purposes in the same sketch. Set to 0 in the example. + + This example, originally relied on the Double Reset Detector library from https://github.com/datacute/DoubleResetDetector + To support ESP32, use ESP_DoubleResetDetector library from //https://github.com/khoih-prog/ESP_DoubleResetDetector + *****************************************************************************************************************************/ +/**************************************************************************************************************************** + This example will open a configuration portal when no configuration has been previously entered or when a button is pushed. + It is the easiest scenario for configuration but requires a pin and a button on the ESP8266 device. + The Flash button is convenient for this on NodeMCU devices. + + Also in this example a password is required to connect to the configuration portal + network. This is inconvenient but means that only those who know the password or those + already connected to the target network can access the configuration portal and + the network credentials will be sent from the browser over an encrypted connection and + can not be read by observers. + *****************************************************************************************************************************/ + +#if !( defined(ESP32) ) + #error This code is designed for WT32_ETH01 to run on ESP32 platform! Please check your Tools->Board setting. +#endif + +////////////////////////////////////////////////////////////// + +// Use from 0 to 4. Higher number, more debugging messages and memory usage. +#define _ESPASYNC_ETH_MGR_LOGLEVEL_ 4 + +// To not display stored SSIDs and PWDs on Config Portal, select false. Default is true +// Even the stored Credentials are not display, just leave them all blank to reconnect and reuse the stored Credentials +//#define DISPLAY_STORED_CREDENTIALS_IN_CP false + +////////////////////////////////////////////////////////////// + +#include +// Now support ArduinoJson 6.0.0+ ( tested with v6.14.1 ) +#include // get it from https://arduinojson.org/ or install via Arduino library manager + +//For ESP32, To use ESP32 Dev Module, QIO, Flash 4MB/80MHz, Upload 921600 +//Ported to ESP32 +#include + +////////////////////////////////////////////////////////////// + +// LittleFS has higher priority than SPIFFS +#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #define USE_LITTLEFS true + #define USE_SPIFFS false +#elif defined(ARDUINO_ESP32C3_DEV) + // For core v1.0.6-, ESP32-C3 only supporting SPIFFS and EEPROM. To use v2.0.0+ for LittleFS + #define USE_LITTLEFS false + #define USE_SPIFFS true +#endif + +#if USE_LITTLEFS + // Use LittleFS + #include "FS.h" + + // Check cores/esp32/esp_arduino_version.h and cores/esp32/core_version.h + //#if ( ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2, 0, 0) ) //(ESP_ARDUINO_VERSION_MAJOR >= 2) + #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.6 or 2.0.0+ + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS + + FS* filesystem = &LittleFS; + #define FileFS LittleFS + #define FS_Name "LittleFS" + #else + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using ESP32 Core 1.0.5-. You must install LITTLEFS library + #endif + + // The library has been merged into esp32 core from release 1.0.6 + #include // https://github.com/lorol/LITTLEFS + + FS* filesystem = &LITTLEFS; + #define FileFS LITTLEFS + #define FS_Name "LittleFS" + #endif + +#elif USE_SPIFFS + #include + FS* filesystem = &SPIFFS; + #define FileFS SPIFFS + #define FS_Name "SPIFFS" +#else + // +Use FFat + #include + FS* filesystem = &FFat; + #define FileFS FFat + #define FS_Name "FFat" +#endif + +////////////////////////////////////////////////////////////// + +#define LED_BUILTIN 2 +#define LED_ON HIGH +#define LED_OFF LOW + +#define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 +#define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 + +#define PIN_D35 35 // Pin D35 mapped to pin GPIO35/ADC7 of ESP32 +#define PIN_D36 36 // Pin D36 mapped to pin GPIO36/ADC0/SVP of ESP32 +#define PIN_D39 39 // Pin D39 mapped to pin GPIO39/ADC3/SVN of ESP32 + +#define PIN_SCL 22 // Pin SCL mapped to pin GPIO22/SCL of ESP32 +#define PIN_SDA 21 // Pin SDA mapped to pin GPIO21/SDA of ESP32 + +////////////////////////////////////////////////////////////// + +const int TRIGGER_PIN = PIN_D14; + +/* +Alternative trigger pin. Needs to be connected to a button to use this pin. It must be a momentary connection +not connected permanently to ground. Either trigger pin will work. +*/ +const int TRIGGER_PIN2 = PIN_D15; + +int pinSda = PIN_SDA; // Pin SDA mapped to pin GPIO21/SDA of ESP32 +int pinScl = PIN_SCL; // Pin SCL mapped to pin GPIO22/SCL of ESP32 + +////////////////////////////////////////////////////////////// + +const char* JSON_CONFIG_FILE = "/ConfigSW.json"; + +// Variables + +// Default configuration values +char thingspeakApiKey[17] = ""; +bool sensorDht22 = true; + +#define ThingSpeakAPI_Label "thingspeakApiKey" +#define SensorDht22_Label "SensorDHT22" +#define PinSDA_Label "PinSda" +#define PinSCL_Label "PinScl" + +////////////////////////////////////////////////////////////// + +// Function Prototypes + +bool readConfigFile(); +bool writeConfigFile(); + +// You only need to format the filesystem once +//#define FORMAT_FILESYSTEM true +#define FORMAT_FILESYSTEM false + +////////////////////////////////////////////////////////////// + +// Assuming max 49 chars +#define TZNAME_MAX_LEN 50 +#define TIMEZONE_MAX_LEN 50 + +typedef struct +{ + char TZ_Name[TZNAME_MAX_LEN]; // "America/Toronto" + char TZ[TIMEZONE_MAX_LEN]; // "EST5EDT,M3.2.0,M11.1.0" + uint16_t checksum; +} EthConfig; + +EthConfig Ethconfig; + +#define CONFIG_FILENAME F("/eth_cred.dat") + +////////////////////////////////////////////////////////////// + +// Indicates whether ESP has credentials saved from previous session +bool initialConfig = false; + +// Use false if you don't like to display Available Pages in Information Page of Config Portal +// Comment out or use true to display Available Pages in Information Page of Config Portal +// Must be placed before #include +#define USE_AVAILABLE_PAGES true + +// From v1.0.10 to permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa +// You have to explicitly specify false to disable the feature. +//#define USE_STATIC_IP_CONFIG_IN_CP false + +// Use false to disable NTP config. Advisable when using Cellphone, Tablet to access Config Portal. +// See Issue 23: On Android phone ConfigPortal is unresponsive (https://github.com/khoih-prog/ESP_WiFiManager/issues/23) +#define USE_ESP_ETH_MANAGER_NTP true + +// Just use enough to save memory. On ESP8266, can cause blank ConfigPortal screen +// if using too much memory +#define USING_AFRICA false +#define USING_AMERICA true +#define USING_ANTARCTICA false +#define USING_ASIA false +#define USING_ATLANTIC false +#define USING_AUSTRALIA false +#define USING_EUROPE false +#define USING_INDIAN false +#define USING_PACIFIC false +#define USING_ETC_GMT false + +// Use true to enable CloudFlare NTP service. System can hang if you don't have Internet access while accessing CloudFlare +// See Issue #21: CloudFlare link in the default portal (https://github.com/khoih-prog/ESP_WiFiManager/issues/21) +#define USE_CLOUDFLARE_NTP false + +// New in v1.0.11 +#define USING_CORS_FEATURE true + +////////////////////////////////////////////////////////////// + +// Use USE_DHCP_IP == true for dynamic DHCP IP, false to use static IP which you have to change accordingly to your network +#if (defined(USE_STATIC_IP_CONFIG_IN_CP) && !USE_STATIC_IP_CONFIG_IN_CP) + // Force DHCP to be true + #if defined(USE_DHCP_IP) + #undef USE_DHCP_IP + #endif + #define USE_DHCP_IP true +#else + // You can select DHCP or Static IP here + //#define USE_DHCP_IP true + #define USE_DHCP_IP false +#endif + +#if ( USE_DHCP_IP ) + // Use DHCP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using DHCP IP + #endif + + IPAddress stationIP = IPAddress(0, 0, 0, 0); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); + +#else + // Use static IP + + #if (_ESPASYNC_ETH_MGR_LOGLEVEL_ > 3) + #warning Using static IP + #endif + + IPAddress stationIP = IPAddress(192, 168, 2, 232); + IPAddress gatewayIP = IPAddress(192, 168, 2, 1); + IPAddress netMask = IPAddress(255, 255, 255, 0); +#endif + +////////////////////////////////////////////////////////////// + +#define USE_CONFIGURABLE_DNS true + +IPAddress dns1IP = gatewayIP; +IPAddress dns2IP = IPAddress(8, 8, 8, 8); + +#include //https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + +#define HTTP_PORT 80 + +////////////////////////////////////////////////////////////// + +/****************************************** + // Defined in AsyncWT32_ETH01_Manager.hpp + typedef struct + { + IPAddress _sta_static_ip; + IPAddress _sta_static_gw; + IPAddress _sta_static_sn; + #if USE_CONFIGURABLE_DNS + IPAddress _sta_static_dns1; + IPAddress _sta_static_dns2; + #endif + } ETH_STA_IPConfig; +******************************************/ + +ETH_STA_IPConfig EthSTA_IPconfig; + +////////////////////////////////////////////////////////////// + +void initSTAIPConfigStruct(ETH_STA_IPConfig &in_EthSTA_IPconfig) +{ + in_EthSTA_IPconfig._sta_static_ip = stationIP; + in_EthSTA_IPconfig._sta_static_gw = gatewayIP; + in_EthSTA_IPconfig._sta_static_sn = netMask; +#if USE_CONFIGURABLE_DNS + in_EthSTA_IPconfig._sta_static_dns1 = dns1IP; + in_EthSTA_IPconfig._sta_static_dns2 = dns2IP; +#endif +} + +////////////////////////////////////////////////////////////// + +void displayIPConfigStruct(ETH_STA_IPConfig in_EthSTA_IPconfig) +{ + LOGERROR3(F("stationIP ="), in_EthSTA_IPconfig._sta_static_ip, ", gatewayIP =", in_EthSTA_IPconfig._sta_static_gw); + LOGERROR1(F("netMask ="), in_EthSTA_IPconfig._sta_static_sn); +#if USE_CONFIGURABLE_DNS + LOGERROR3(F("dns1IP ="), in_EthSTA_IPconfig._sta_static_dns1, ", dns2IP =", in_EthSTA_IPconfig._sta_static_dns2); +#endif +} + +////////////////////////////////////////////////////////////// + +void toggleLED() +{ + //toggle state + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} + +////////////////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP +void printLocalTime() +{ + struct tm timeinfo; + + getLocalTime( &timeinfo ); + + // Valid only if year > 2000. + // You can get from timeinfo : tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec + if (timeinfo.tm_year > 100 ) + { + Serial.print("Local Date/Time: "); + Serial.print( asctime( &timeinfo ) ); + } +} +#endif + +////////////////////////////////////////////////////////////// + +void heartBeatPrint() +{ +#if USE_ESP_ETH_MANAGER_NTP + printLocalTime(); +#else + static int num = 1; + + if (WT32_ETH01_isConnected()) + Serial.print(F("H")); // H means connected to Ethernet + else + Serial.print(F("F")); // F means not connected to Ethernet + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } + +#endif +} + +////////////////////////////////////////////////////////////// + +void check_status() +{ + static ulong checkstatus_timeout = 0; + static ulong LEDstatus_timeout = 0; + + static ulong current_millis; + +#if USE_ESP_ETH_MANAGER_NTP +#define HEARTBEAT_INTERVAL 60000L +#else +#define HEARTBEAT_INTERVAL 10000L +#endif + +#define LED_INTERVAL 2000L + + current_millis = millis(); + + if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0)) + { + // Toggle LED at LED_INTERVAL = 2s + toggleLED(); + LEDstatus_timeout = current_millis + LED_INTERVAL; + } + + // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. + if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; + } +} + +////////////////////////////////////////////////////////////// + +int calcChecksum(uint8_t* address, uint16_t sizeToCalc) +{ + uint16_t checkSum = 0; + + for (uint16_t index = 0; index < sizeToCalc; index++) + { + checkSum += * ( ( (byte*) address ) + index); + } + + return checkSum; +} + +////////////////////////////////////////////////////////////// + +bool loadConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "r"); + LOGERROR(F("LoadCfgFile ")); + + memset((void *) &Ethconfig, 0, sizeof(Ethconfig)); + memset((void *) &EthSTA_IPconfig, 0, sizeof(EthSTA_IPconfig)); + + if (file) + { + file.readBytes((char *) &Ethconfig, sizeof(Ethconfig)); + file.readBytes((char *) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + + if ( Ethconfig.checksum != calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ) ) + { + LOGERROR(F("Ethconfig checksum wrong")); + + return false; + } + + displayIPConfigStruct(EthSTA_IPconfig); + + return true; + } + else + { + LOGERROR(F("failed")); + + return false; + } +} + +////////////////////////////////////////////////////////////// + +void saveConfigData() +{ + File file = FileFS.open(CONFIG_FILENAME, "w"); + LOGERROR(F("SaveCfgFile ")); + + if (file) + { + Ethconfig.checksum = calcChecksum( (uint8_t*) &Ethconfig, sizeof(Ethconfig) - sizeof(Ethconfig.checksum) ); + + file.write((uint8_t*) &Ethconfig, sizeof(Ethconfig)); + + displayIPConfigStruct(EthSTA_IPconfig); + + file.write((uint8_t*) &EthSTA_IPconfig, sizeof(EthSTA_IPconfig)); + file.close(); + + LOGERROR(F("OK")); + } + else + { + LOGERROR(F("failed")); + } +} + +////////////////////////////////////////////////////////////// + +bool readConfigFile() +{ + // this opens the config file in read-mode + File f = FileFS.open(JSON_CONFIG_FILE, "r"); + + if (!f) + { + Serial.println(F("Configuration file not found")); + + return false; + } + else + { + // we could open the file + size_t size = f.size(); + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size + 1]); + + // Read and store file contents in buf + f.readBytes(buf.get(), size); + // Closing file + f.close(); + // Using dynamic JSON buffer which is not the recommended memory model, but anyway + // See https://github.com/bblanchon/ArduinoJson/wiki/Memory%20model + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + DynamicJsonDocument json(1024); + auto deserializeError = deserializeJson(json, buf.get()); + + if ( deserializeError ) + { + Serial.println(F("JSON parseObject() failed")); + + return false; + } + + serializeJson(json, Serial); +#else + DynamicJsonBuffer jsonBuffer; + // Parse JSON string + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + // Test if parsing succeeds. + if (!json.success()) + { + Serial.println(F("JSON parseObject() failed")); + return false; + } + + json.printTo(Serial); +#endif + + // Parse all config file parameters, override + // local config variables with parsed values + if (json.containsKey(ThingSpeakAPI_Label)) + { + strcpy(thingspeakApiKey, json[ThingSpeakAPI_Label]); + } + + if (json.containsKey(SensorDht22_Label)) + { + sensorDht22 = json[SensorDht22_Label]; + } + + if (json.containsKey(PinSDA_Label)) + { + pinSda = json[PinSDA_Label]; + } + + if (json.containsKey(PinSCL_Label)) + { + pinScl = json[PinSCL_Label]; + } + } + + Serial.println(F("\nConfig file was successfully parsed")); + + return true; +} + +////////////////////////////////////////////////////////////// + +bool writeConfigFile() +{ + Serial.println(F("Saving config file")); + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + DynamicJsonDocument json(1024); +#else + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); +#endif + + // JSONify local configuration parameters + json[ThingSpeakAPI_Label] = thingspeakApiKey; + json[SensorDht22_Label] = sensorDht22; + json[PinSDA_Label] = pinSda; + json[PinSCL_Label] = pinScl; + + // Open file for writing + File f = FileFS.open(JSON_CONFIG_FILE, "w"); + + if (!f) + { + Serial.println(F("Failed to open config file for writing")); + + return false; + } + +#if (ARDUINOJSON_VERSION_MAJOR >= 6) + serializeJsonPretty(json, Serial); + // Write data to file and close it + serializeJson(json, f); +#else + json.prettyPrintTo(Serial); + // Write data to file and close it + json.printTo(f); +#endif + + f.close(); + + Serial.println(F("\nConfig file was successfully saved")); + + return true; +} + +////////////////////////////////////////////////////////////// + +void beginEthernet() +{ + // To be called before ETH.begin() + WT32_ETH01_onEvent(); + + //bool begin(uint8_t phy_addr=ETH_PHY_ADDR, int power=ETH_PHY_POWER, int mdc=ETH_PHY_MDC, int mdio=ETH_PHY_MDIO, + // eth_phy_type_t type=ETH_PHY_TYPE, eth_clock_mode_t clk_mode=ETH_CLK_MODE); + //ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLK_MODE); + ETH.begin(ETH_PHY_ADDR, ETH_PHY_POWER); +} + +void initEthernet() +{ +#if !( USE_DHCP_IP ) + displayIPConfigStruct(EthSTA_IPconfig); + + // Static IP, leave without this line to get IP via DHCP + //bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = 0, IPAddress dns2 = 0); + //ETH.config(stationIP, gatewayIP, netMask, dns1IP, dns2IP); + ETH.config(EthSTA_IPconfig._sta_static_ip, EthSTA_IPconfig._sta_static_gw, EthSTA_IPconfig._sta_static_sn, + EthSTA_IPconfig._sta_static_dns1); +#endif + + WT32_ETH01_waitForConnect(); +} + +////////////////////////////////////////////////////////////// + +void setup() +{ + //set led pin as output + pinMode(LED_BUILTIN, OUTPUT); + + // Put your setup code here, to run once + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print(F("\nStarting Async_ConfigOnSwichFS using ")); + Serial.print(FS_Name); + Serial.print(F(" on ")); + Serial.print(ARDUINO_BOARD); + Serial.print(F(" with ")); + Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WT32_ETH01_MANAGER_VERSION); + + // Initialize the LED digital pin as an output. + pinMode(LED_BUILTIN, OUTPUT); + // Initialize trigger pins + pinMode(TRIGGER_PIN, INPUT_PULLUP); + pinMode(TRIGGER_PIN2, INPUT_PULLUP); + + if (FORMAT_FILESYSTEM) + { + Serial.println(F("Forced Formatting.")); + FileFS.format(); + } + + // Format FileFS if not yet +#ifdef ESP32 + + if (!FileFS.begin(true)) +#else + if (!FileFS.begin()) +#endif + { +#ifdef ESP8266 + FileFS.format(); +#endif + + Serial.println(F("SPIFFS/LittleFS failed! Already tried formatting.")); + + if (!FileFS.begin()) + { + // prevents debug info from the library to hide err message. + delay(100); + +#if USE_LITTLEFS + Serial.println(F("LittleFS failed!. Please use SPIFFS or EEPROM. Stay forever")); +#else + Serial.println(F("SPIFFS failed!. Please use LittleFS or EEPROM. Stay forever")); +#endif + + while (true) + { + delay(1); + } + } + } + + beginEthernet(); + + initSTAIPConfigStruct(EthSTA_IPconfig); + + if (!readConfigFile()) + { + Serial.println(F("Failed to read ConfigFile, using default values")); + } + + unsigned long startedAt = millis(); + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, NULL, "ConfigOnSwitchFS"); +#else + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "ConfigOnSwitchFS"); +#endif + + AsyncWT32_ETH01_manager.setDebugOutput(true); + +#if !USE_DHCP_IP + // Set (static IP, Gateway, Subnetmask, DNS1 and DNS2) or (IP, Gateway, Subnetmask) + AsyncWT32_ETH01_manager.setSTAStaticIPConfig(EthSTA_IPconfig); +#endif + +#if USING_CORS_FEATURE + AsyncWT32_ETH01_manager.setCORSHeader("Your Access-Control-Allow-Origin"); +#endif + + bool configDataLoaded = false; + + if (loadConfigData()) + { + configDataLoaded = true; + + //If no access point name has been previously entered disable timeout. + AsyncWT32_ETH01_manager.setConfigPortalTimeout(120); + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + +#if USE_ESP_ETH_MANAGER_NTP + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + Serial.println(F("Current Timezone is not set. Enter Config Portal to set.")); + } + +#endif + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + + ////////////////////////////////// + + // Connect ETH now if using STA + initEthernet(); + + ////////////////////////////////// + + if (initialConfig) + { + Serial.print(F("Starting configuration portal @ ")); + Serial.println(ETH.localIP()); + + digitalWrite(LED_BUILTIN, LED_ON); // Turn led on as we are in configuration mode. + + //sets timeout in seconds until configuration portal gets turned off. + //If not specified device will remain in configuration mode until + //switched off via webserver or device is restarted. + //AsyncWT32_ETH01_manager.setConfigPortalTimeout(600); + + // Starts an access point + if (!AsyncWT32_ETH01_manager.startConfigPortal()) + Serial.println(F("Not connected to ETH network but continuing anyway.")); + else + { + Serial.println(F("ETH network connected...yeey :)")); + } + + +#if USE_ESP_ETH_MANAGER_NTP + String tempTZ = AsyncWT32_ETH01_manager.getTimezoneName(); + + if (strlen(tempTZ.c_str()) < sizeof(Ethconfig.TZ_Name) - 1) + strcpy(Ethconfig.TZ_Name, tempTZ.c_str()); + else + strncpy(Ethconfig.TZ_Name, tempTZ.c_str(), sizeof(Ethconfig.TZ_Name) - 1); + + const char * TZ_Result = AsyncWT32_ETH01_manager.getTZ(Ethconfig.TZ_Name); + + if (strlen(TZ_Result) < sizeof(Ethconfig.TZ) - 1) + strcpy(Ethconfig.TZ, TZ_Result); + else + strncpy(Ethconfig.TZ, TZ_Result, sizeof(Ethconfig.TZ_Name) - 1); + + if ( strlen(Ethconfig.TZ_Name) > 0 ) + { + LOGERROR3(F("Saving current TZ_Name ="), Ethconfig.TZ_Name, F(", TZ = "), Ethconfig.TZ); + + //configTzTime(Ethconfig.TZ, "pool.ntp.org" ); + configTzTime(Ethconfig.TZ, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); + } + else + { + LOGERROR(F("Current Timezone Name is not set. Enter Config Portal to set.")); + } + +#endif + + AsyncWT32_ETH01_manager.getSTAStaticIPConfig(EthSTA_IPconfig); + + saveConfigData(); + } + + digitalWrite(LED_BUILTIN, LED_OFF); // Turn led off as we are not in configuration mode. + + startedAt = millis(); + + Serial.print(F("After waiting ")); + Serial.print((float) (millis() - startedAt) / 1000); + Serial.print(F(" secs more in setup(), connection result is ")); + + if (WT32_ETH01_isConnected()) + { + Serial.print(F("connected. Local IP: ")); + Serial.println(ETH.localIP()); + } +} + +////////////////////////////////////////////////////////////// + +void loop() +{ + // is configuration portal requested? + if ((digitalRead(TRIGGER_PIN) == LOW) || (digitalRead(TRIGGER_PIN2) == LOW)) + { + Serial.println(F("\nConfiguration portal requested.")); + digitalWrite(LED_BUILTIN, LED_ON); // turn the LED on by making the voltage LOW to tell us we are in configuration mode. + + //Local intialization. Once its business is done, there is no need to keep it around + // Use this to default DHCP hostname to ESP32-XXXXXX + //AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer); + // Use this to personalize DHCP hostname (RFC952 conformed) + AsyncWebServer webServer(HTTP_PORT); + AsyncDNSServer dnsServer; + + AsyncWT32_ETH01_Manager AsyncWT32_ETH01_manager(&webServer, &dnsServer, "ConfigOnSwitchFS"); + + //Check if there is stored credentials. + //If not found, device will remain in configuration mode until switched off via webserver. + Serial.println(F("Opening configuration portal. ")); + + if (loadConfigData()) + { + AsyncWT32_ETH01_manager.setConfigPortalTimeout( + 120); //If no access point name has been previously entered disable timeout. + Serial.println(F("Got stored Credentials. Timeout 120s for Config Portal")); + } + else + { + // Enter CP only if no stored SSID on flash and file + Serial.println(F("Open Config Portal without Timeout: No stored Credentials.")); + initialConfig = true; + } + + // Extra parameters to be configured + // After connecting, parameter.getValue() will get you the configured value + // Format:
"; + +const char EM_FLDSET_START[] PROGMEM = "
"; +const char EM_FLDSET_END[] PROGMEM = "
"; + +//////////////////////////////////////////////////// + +const char EM_HTTP_PORTAL_OPTIONS[] PROGMEM = "



"; +const char EM_HTTP_ITEM[] PROGMEM = "
{v} {r}%
"; +const char JSON_ITEM[] PROGMEM = "{\"SSID\":\"{v}\", \"Encryption\":{i}, \"Quality\":\"{r}\"}"; + +//////////////////////////////////////////////////// + +// KH, update from v1.15.0 +// To permit display stored Credentials on CP +#ifndef DISPLAY_STORED_CREDENTIALS_IN_CP + #define DISPLAY_STORED_CREDENTIALS_IN_CP true +#endif + +#if ( (_WIFIMGR_LOGLEVEL_ > 3) && DISPLAY_STORED_CREDENTIALS_IN_CP ) + #warning Enable DISPLAY_STORED_CREDENTIALS_IN_CP +#endif + +#if DISPLAY_STORED_CREDENTIALS_IN_CP +const char EM_HTTP_FORM_START[] PROGMEM = "
"; +#else +const char EM_HTTP_FORM_START[] PROGMEM = ""; +#endif + +//////////////////////////////////////////////////// + +const char EM_HTTP_FORM_LABEL_BEFORE[] PROGMEM = "
"; +const char EM_HTTP_FORM_LABEL_AFTER[] PROGMEM = "
"; + +//////////////////////////////////////////////////// + +const char EM_HTTP_FORM_LABEL[] PROGMEM = ""; +const char EM_HTTP_FORM_PARAM[] PROGMEM = ""; + +const char EM_HTTP_FORM_END[] PROGMEM = "
"; + +//////////////////////////////////////////////////// + +const char EM_HTTP_SAVED[] PROGMEM = "
Credentials Saved
"; + +//////////////////////////////////////////////////// + +const char EM_HTTP_END[] PROGMEM = "
"; + +//////////////////////////////////////////////////// + +const char EM_HTTP_HEAD_CL[] = "Content-Length"; +const char EM_HTTP_HEAD_CT[] = "text/html"; +const char EM_HTTP_HEAD_CT2[] = "text/plain"; + +const char EM_HTTP_HEAD_JSON[] ="application/json"; + +//KH Add repeatedly used const +const char EM_HTTP_CACHE_CONTROL[] = "Cache-Control"; +const char EM_HTTP_NO_STORE[] = "no-cache, no-store, must-revalidate"; +const char EM_HTTP_PRAGMA[] = "Pragma"; +const char EM_HTTP_NO_CACHE[] = "no-cache"; +const char EM_HTTP_EXPIRES[] = "Expires"; +const char EM_HTTP_CORS[] = "Access-Control-Allow-Origin"; +const char EM_HTTP_CORS_ALLOW_ALL[] = "*"; + +//////////////////////////////////////////////////// + +#if USE_AVAILABLE_PAGES + const char EM_HTTP_AVAILABLE_PAGES[] PROGMEM = "

Available Pages

PageFunction
/Menu page.
/ethEnter ETH Config Page
/ethsaveSave Config. Portal Info with supplied variables.
/closeClose the Config Portal.
/iThis Info page.
/rDelete ETH configuration and reboot.
/stateCurrent device state in JSON format. Interface for ETH configuration.
"; +#else + const char EM_HTTP_AVAILABLE_PAGES[] PROGMEM = ""; +#endif + +//////////////////////////////////////////////////// + +#define ETH_MANAGER_MAX_PARAMS 20 + +//////////////////////////////////////////////////// + +// To permit autoConnect() to use STA static IP or DHCP IP. +#ifndef AUTOCONNECT_NO_INVALIDATE + #define AUTOCONNECT_NO_INVALIDATE true +#endif + +//////////////////////////////////////////////////// + +typedef struct +{ + const char *_id; + const char *_placeholder; + char *_value; + int _length; + int _labelPlacement; + +} WMParam_Data; + + +//////////////////////////////////////////////////// +//////////////////////////////////////////////////// + +class ESPAsync_EMParameter +{ + public: + + ESPAsync_EMParameter(const char *custom); + ESPAsync_EMParameter(const char *id, const char *placeholder, const char *defaultValue, const int& length, + const char *custom = "", const int& labelPlacement = WFM_LABEL_BEFORE); + + ESPAsync_EMParameter(const WMParam_Data& WMParam_data); + + ~ESPAsync_EMParameter(); + + void setWMParam_Data(const WMParam_Data& WMParam_data); + void getWMParam_Data(WMParam_Data& WMParam_data); + + const char *getID(); + const char *getValue(); + const char *getPlaceholder(); + int getValueLength(); + int getLabelPlacement(); + const char *getCustomHTML(); + + private: + + WMParam_Data _WMParam_data; + + const char *_customHTML; + + void init(const char *id, const char *placeholder, const char *defaultValue, const int& length, + const char *custom, const int& labelPlacement); + + friend class AsyncWT32_ETH01_Manager; +}; + +//////////////////////////////////////////////////// + +#define USE_DYNAMIC_PARAMS true +#define DEFAULT_PORTAL_TIMEOUT 60000L + +// To permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. +// You have to explicitly specify false to disable the feature. +#ifndef USE_STATIC_IP_CONFIG_IN_CP + #define USE_STATIC_IP_CONFIG_IN_CP true +#endif + +//////////////////////////////////////////////////// +//////////////////////////////////////////////////// + +class AsyncWT32_ETH01_Manager +{ + public: + + AsyncWT32_ETH01_Manager(AsyncWebServer * webserver, AsyncDNSServer *dnsserver, const char *iHostname = ""); + + ~AsyncWT32_ETH01_Manager(); + + // If you want to start the config portal + bool startConfigPortal(); + + //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 setConfigPortalTimeout is a new name for setTimeout + void setConfigPortalTimeout(const unsigned long& seconds); + void setTimeout(const unsigned long& seconds); + + //sets timeout for which to attempt connecting, usefull if you get a lot of failed connects + void setConnectTimeout(const unsigned long& seconds); + + void setDebugOutput(bool debug); + + //sets config for a static IP + void setSTAStaticIPConfig(const IPAddress& ip, const IPAddress& gw, const IPAddress& sn); + + void setSTAStaticIPConfig(const ETH_STA_IPConfig& EM_STA_IPconfig); + void getSTAStaticIPConfig(ETH_STA_IPConfig& EM_STA_IPconfig); + +#if USE_CONFIGURABLE_DNS + void setSTAStaticIPConfig(const IPAddress& ip, const IPAddress& gw, const IPAddress& sn, + const IPAddress& dns_address_1, const IPAddress& dns_address_2); +#endif + + //called when settings have been changed and connection was successful + void setSaveConfigCallback(void(*func)()); + +#if USE_DYNAMIC_PARAMS + //adds a custom parameter + bool addParameter(ESPAsync_EMParameter *p); +#else + //adds a custom parameter + void addParameter(ESPAsync_EMParameter *p); +#endif + + //if this is set, it will exit after config, even if connection is unsucessful. + void setBreakAfterConfig(bool shouldBreak); + + //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins) + //TODO + //if this is set, customise style + void setCustomHeadElement(const char* element); + +//////////////////////////////////////////////////// + + // For configuring CORS Header, default to EM_HTTP_CORS_ALLOW_ALL = "*" +#if USING_CORS_FEATURE + void setCORSHeader(const char* CORSHeaders) + { + _CORS_Header = CORSHeaders; + + LOGWARN1(F("Set CORS Header to : "), _CORS_Header); + } + + /////////////////////////// + + inline const char* getCORSHeader() + { + return _CORS_Header; + } +#endif + + /////////////////////////// + + //returns the list of Parameters + ESPAsync_EMParameter** getParameters(); + + // returns the Parameters Count + int getParametersCount(); + + /////////////////////////// + + void setHostname() + { + if (RFC952_hostname[0] != 0) + { + ETH.setHostname(RFC952_hostname); + } + } + +//////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP + + inline String getTimezoneName() + { + return _timezoneName; + } + + /////////////////////////// + + inline void setTimezoneName(const String& inTimezoneName) + { + _timezoneName = inTimezoneName; + } + + /////////////////////////// + + //See: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + // EST5EDT,M3.2.0,M11.1.0 (for America/New_York) + // EST5EDT is the name of the time zone + // EST is the abbreviation used when DST is off + // 6 hours is the time difference from GMT + // EDT is the abbreviation used when DST is on + // ,M3 is the third month + // .2 is the second occurrence of the day in the month + // .0 is Sunday + // ,M11 is the eleventh month + // .1 is the first occurrence of the day in the month + // .0 is Sunday + + const char * getTZ(const char * timezoneName) + { + //const char TZ_NAME[][TIMEZONE_MAX_LEN] + for (uint16_t index = 0; index < sizeof(TZ_NAME) / TIMEZONE_MAX_LEN; index++) + { + if ( !strncmp(timezoneName, (TZ_NAME[index]), strlen((TZ_NAME[index])) ) ) + { + yield(); + + return (ESP_TZ_NAME[index]); + } + } + + return ""; + } + + /////////////////////////// + + const char * getTZ(const String& timezoneName) + { + return getTZ(timezoneName.c_str()); + } + + /////////////////////////// + +#endif + + private: + + AsyncDNSServer *dnsServer; + + AsyncWebServer *server; + + bool needInfo = true; + String pager; + wl_status_t ethStatus; + +#define RFC952_HOSTNAME_MAXLEN 24 + char RFC952_hostname[RFC952_HOSTNAME_MAXLEN + 1]; + + char* getRFC952_hostname(const char* iHostname); + + void setupConfigPortal(); + void startWPS(); + + //////////////////////////////////////////////////// + +#if USE_ESP_ETH_MANAGER_NTP + // Timezone info + String _timezoneName = ""; +#endif + + //////////////////////////////////////////////////// + + unsigned long _configPortalTimeout = 0; + + unsigned long _connectTimeout = 0; + unsigned long _configPortalStart = 0; + + //////////////////////////////////////////////////// + + ETH_STA_IPConfig _ETH_STA_IPconfig = { IPAddress(0, 0, 0, 0), IPAddress(192, 168, 2, 1), IPAddress(255, 255, 255, 0), + IPAddress(192, 168, 2, 1), IPAddress(8, 8, 8, 8) }; + + //////////////////////////////////////////////////// + + int _paramsCount = 0; + int _minimumQuality = -1; + bool _removeDuplicateAPs = true; + bool _shouldBreakAfterConfig = false; + bool _tryWPS = false; + + const char* _customHeadElement = ""; + + int status = WL_IDLE_STATUS; + + // For configuring CORS Header, default to EM_HTTP_CORS_ALLOW_ALL = "*" +#if USING_CORS_FEATURE + const char* _CORS_Header = EM_HTTP_CORS_ALLOW_ALL; //"*"; +#endif + + wl_status_t waitForConnectResult(); + + void setInfo(); + String networkListAsString(); + + void handleRoot(AsyncWebServerRequest *request); + void handleETH(AsyncWebServerRequest *request); + void handleETHSave(AsyncWebServerRequest *request); + void handleServerClose(AsyncWebServerRequest *request); + void handleInfo(AsyncWebServerRequest *request); + void handleState(AsyncWebServerRequest *request); + void handleReset(AsyncWebServerRequest *request); + void handleNotFound(AsyncWebServerRequest *request); + bool captivePortal(AsyncWebServerRequest *request); + + void reportStatus(String& page); + + // DNS server + const byte DNS_PORT = 53; + + //helpers + bool isIp(const String& str); + String toStringIp(const IPAddress& ip); + + bool connect; + bool stopConfigPortal = false; + + bool _debug = false; //true; + + void(*_savecallback)() = NULL; + + //////////////////////////////////////////////////// + +#if USE_DYNAMIC_PARAMS + int _max_params; + ESPAsync_EMParameter** _params; +#else + ESPAsync_EMParameter* _params[ETH_MANAGER_MAX_PARAMS]; +#endif + + //////////////////////////////////////////////////// + + template + void DEBUG_WM(Generic text); + + /////////////////////////// + + template + auto optionalIPFromString(T *obj, const char *s) -> decltype(obj->fromString(s)) + { + return obj->fromString(s); + } + + /////////////////////////// + + auto optionalIPFromString(...) -> bool + { + LOGINFO("No IPAddress.fromString(), use ESP8266 core 2.1.0+ for Custom IP configuration to work."); + + return false; + } + + /////////////////////////// + +}; + +#endif // AsyncWT32_ETH01_Manager_hpp + diff --git a/src/AsyncWT32_ETH01_Manager_Debug.h b/src/AsyncWT32_ETH01_Manager_Debug.h new file mode 100644 index 0000000..731ec7d --- /dev/null +++ b/src/AsyncWT32_ETH01_Manager_Debug.h @@ -0,0 +1,92 @@ +/**************************************************************************************************************************** + AsyncWT32_ETH01_Manager_Debug.h + + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + + Version: 1.0.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 09/12/2022 Initial coding for WT32_ETH01 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef AsyncWT32_ETH01_Manager_Debug_H +#define AsyncWT32_ETH01_Manager_Debug_H + +#ifdef ESPASYNC_ETH_MGR_DEBUG_PORT + #define DBG_PORT_ESP_EM ESPASYNC_ETH_MGR_DEBUG_PORT +#else + #define DBG_PORT_ESP_EM Serial +#endif + +// Change _ESPASYNC_ETH_MGR_LOGLEVEL_ to set tracing and logging verbosity +// 0: DISABLED: no logging +// 1: ERROR: errors +// 2: WARN: errors and warnings +// 3: INFO: errors, warnings and informational (default) +// 4: DEBUG: errors, warnings, informational and debug + +#ifndef _ESPASYNC_ETH_MGR_LOGLEVEL_ + #define _ESPASYNC_ETH_MGR_LOGLEVEL_ 1 +#endif + +///////////////////////////////////////////////////////// + +const char ESP_EM_MARK[] = "[EM] "; +const char ESP_EM_SP[] = " "; + +#define ESP_EM_PRINT DBG_PORT_ESP_EM.print +#define ESP_EM_PRINTLN DBG_PORT_ESP_EM.println + +#define ESP_EM_PRINT_MARK ESP_EM_PRINT(ESP_EM_MARK) +#define ESP_EM_PRINT_SP ESP_EM_PRINT(ESP_EM_SP) + +///////////////////////////////////////////////////////// + +#define LOGERROR(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>0) { ESP_EM_PRINT_MARK; ESP_EM_PRINTLN(x); } +#define LOGERROR0(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>0) { ESP_EM_PRINT(x); } +#define LOGERROR1(x,y) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>0) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(y); } +#define LOGERROR2(x,y,z) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>0) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(z); } +#define LOGERROR3(x,y,z,w) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>0) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINT(z); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(w); } + +///////////////////////////////////////////////////////// + +#define LOGWARN(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>1) { ESP_EM_PRINT_MARK; ESP_EM_PRINTLN(x); } +#define LOGWARN0(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>1) { ESP_EM_PRINT(x); } +#define LOGWARN1(x,y) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>1) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(y); } +#define LOGWARN2(x,y,z) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>1) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(z); } +#define LOGWARN3(x,y,z,w) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>1) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINT(z); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(w); } + +///////////////////////////////////////////////////////// + +#define LOGINFO(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>2) { ESP_EM_PRINT_MARK; ESP_EM_PRINTLN(x); } +#define LOGINFO0(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>2) { ESP_EM_PRINT(x); } +#define LOGINFO1(x,y) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>2) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(y); } +#define LOGINFO2(x,y,z) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>2) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(z); } +#define LOGINFO3(x,y,z,w) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>2) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINT(z); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(w); } + +///////////////////////////////////////////////////////// + +#define LOGDEBUG(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>3) { ESP_EM_PRINT_MARK; ESP_EM_PRINTLN(x); } +#define LOGDEBUG0(x) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>3) { ESP_EM_PRINT(x); } +#define LOGDEBUG1(x,y) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>3) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(y); } +#define LOGDEBUG2(x,y,z) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>3) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(z); } +#define LOGDEBUG3(x,y,z,w) if(_ESPASYNC_ETH_MGR_LOGLEVEL_>3) { ESP_EM_PRINT_MARK; ESP_EM_PRINT(x); ESP_EM_PRINT_SP; ESP_EM_PRINT(y); ESP_EM_PRINT_SP; ESP_EM_PRINT(z); ESP_EM_PRINT_SP; ESP_EM_PRINTLN(w); } + +///////////////////////////////////////////////////////// + +#endif // AsyncWT32_ETH01_Manager_Debug_H + diff --git a/src/AsyncWT32_ETH01_Manager_Impl.h b/src/AsyncWT32_ETH01_Manager_Impl.h new file mode 100644 index 0000000..e1113d6 --- /dev/null +++ b/src/AsyncWT32_ETH01_Manager_Impl.h @@ -0,0 +1,1321 @@ +/**************************************************************************************************************************** + AsyncWT32_ETH01_Manager_Impl.h + + For Ethernet shields using WT32_ETH01 (ESP32 + LAN8720) + + AsyncWT32_ETH01_Manager is a library for the ESP32 with Ethernet LAN8720 to run Async Credential Manager + + Modified from + 1. Tzapu (https://github.com/tzapu/WiFiManager) + 2. Ken Taylor (https://github.com/kentaylor) + 3. Alan Steremberg (https://github.com/alanswx/ESPAsyncWiFiManager) + 4. Khoi Hoang (https://github.com/khoih-prog/ESPAsync_WiFiManager) + + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWT32_ETH01_Manager + Licensed under MIT license + + Version: 1.0.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 09/12/2022 Initial coding for WT32_ETH01 + *****************************************************************************************************************************/ + +#pragma once + +#ifndef AsyncWT32_ETH01_Manager_Impl_h +#define AsyncWT32_ETH01_Manager_Impl_h + +////////////////////////////////////////// + +ESPAsync_EMParameter::ESPAsync_EMParameter(const char *custom) +{ + _WMParam_data._id = NULL; + _WMParam_data._placeholder = NULL; + _WMParam_data._length = 0; + _WMParam_data._value = NULL; + _WMParam_data._labelPlacement = WFM_LABEL_BEFORE; + + _customHTML = custom; +} + +////////////////////////////////////////// + +ESPAsync_EMParameter::ESPAsync_EMParameter(const char *id, const char *placeholder, const char *defaultValue, + const int& length, const char *custom, const int& labelPlacement) +{ + init(id, placeholder, defaultValue, length, custom, labelPlacement); +} + +////////////////////////////////////////// + +// KH, using struct +ESPAsync_EMParameter::ESPAsync_EMParameter(const WMParam_Data& WMParam_data) +{ + init(WMParam_data._id, WMParam_data._placeholder, WMParam_data._value, + WMParam_data._length, "", WMParam_data._labelPlacement); +} + +////////////////////////////////////////// + +void ESPAsync_EMParameter::init(const char *id, const char *placeholder, const char *defaultValue, + const int& length, const char *custom, const int& labelPlacement) +{ + _WMParam_data._id = id; + _WMParam_data._placeholder = placeholder; + _WMParam_data._length = length; + _WMParam_data._labelPlacement = labelPlacement; + + _WMParam_data._value = new char[_WMParam_data._length + 1]; + + if (_WMParam_data._value != NULL) + { + memset(_WMParam_data._value, 0, _WMParam_data._length + 1); + + if (defaultValue != NULL) + { + strncpy(_WMParam_data._value, defaultValue, _WMParam_data._length); + } + } + + _customHTML = custom; +} + +////////////////////////////////////////// + +ESPAsync_EMParameter::~ESPAsync_EMParameter() +{ + if (_WMParam_data._value != NULL) + { + delete[] _WMParam_data._value; + } +} + +////////////////////////////////////////// + +// Using Struct to get/set whole data at once +void ESPAsync_EMParameter::setWMParam_Data(const WMParam_Data& WMParam_data) +{ + LOGINFO(F("setWMParam_Data")); + + memcpy(&_WMParam_data, &WMParam_data, sizeof(_WMParam_data)); +} + +////////////////////////////////////////// + +void ESPAsync_EMParameter::getWMParam_Data(WMParam_Data& WMParam_data) +{ + LOGINFO(F("getWMParam_Data")); + + memcpy(&WMParam_data, &_WMParam_data, sizeof(WMParam_data)); +} + +////////////////////////////////////////// + +const char* ESPAsync_EMParameter::getValue() +{ + return _WMParam_data._value; +} + +////////////////////////////////////////// + +const char* ESPAsync_EMParameter::getID() +{ + return _WMParam_data._id; +} + +////////////////////////////////////////// + +const char* ESPAsync_EMParameter::getPlaceholder() +{ + return _WMParam_data._placeholder; +} + +////////////////////////////////////////// + +int ESPAsync_EMParameter::getValueLength() +{ + return _WMParam_data._length; +} + +////////////////////////////////////////// + +int ESPAsync_EMParameter::getLabelPlacement() +{ + return _WMParam_data._labelPlacement; +} + +////////////////////////////////////////// + +const char* ESPAsync_EMParameter::getCustomHTML() +{ + return _customHTML; +} + +////////////////////////////////////////// + +/** + [getParameters description] + @access public +*/ +ESPAsync_EMParameter** AsyncWT32_ETH01_Manager::getParameters() +{ + return _params; +} + +////////////////////////////////////////// +////////////////////////////////////////// + +/** + [getParametersCount description] + @access public +*/ +int AsyncWT32_ETH01_Manager::getParametersCount() +{ + return _paramsCount; +} + +////////////////////////////////////////// + +char* AsyncWT32_ETH01_Manager::getRFC952_hostname(const char* iHostname) +{ + memset(RFC952_hostname, 0, sizeof(RFC952_hostname)); + + size_t len = (RFC952_HOSTNAME_MAXLEN < strlen(iHostname)) ? RFC952_HOSTNAME_MAXLEN : strlen(iHostname); + + size_t j = 0; + + for (size_t i = 0; i < len - 1; i++) + { + if (isalnum(iHostname[i]) || iHostname[i] == '-') + { + RFC952_hostname[j] = iHostname[i]; + j++; + } + } + + // no '-' as last char + if (isalnum(iHostname[len - 1]) || (iHostname[len - 1] != '-')) + RFC952_hostname[j] = iHostname[len - 1]; + + return RFC952_hostname; +} + +////////////////////////////////////////// + +AsyncWT32_ETH01_Manager::AsyncWT32_ETH01_Manager(AsyncWebServer * webserver, AsyncDNSServer *dnsserver, + const char *iHostname) +{ + + server = webserver; + dnsServer = dnsserver; + +#if USE_DYNAMIC_PARAMS + _max_params = ETH_MANAGER_MAX_PARAMS; + _params = (ESPAsync_EMParameter**) malloc(_max_params * sizeof(ESPAsync_EMParameter*)); +#endif + + if (iHostname[0] == 0) + { + String _hostname = "ESP32-" + String(ESP_getChipId(), HEX); + + _hostname.toUpperCase(); + + getRFC952_hostname(_hostname.c_str()); + + } + else + { + // Prepare and store the hostname only not NULL + getRFC952_hostname(iHostname); + } + + LOGWARN1(F("RFC925 Hostname ="), RFC952_hostname); + + setHostname(); +} + +////////////////////////////////////////// + +AsyncWT32_ETH01_Manager::~AsyncWT32_ETH01_Manager() +{ +#if USE_DYNAMIC_PARAMS + + if (_params != NULL) + { + LOGINFO(F("freeing allocated params!")); + + free(_params); + } + +#endif +} + +////////////////////////////////////////// + +#if USE_DYNAMIC_PARAMS + bool AsyncWT32_ETH01_Manager::addParameter(ESPAsync_EMParameter *p) +#else + void AsyncWT32_ETH01_Manager::addParameter(ESPAsync_EMParameter *p) +#endif +{ +#if USE_DYNAMIC_PARAMS + + if (_paramsCount == _max_params) + { + // rezise the params array + _max_params += ETH_MANAGER_MAX_PARAMS; + + LOGINFO1(F("Increasing _max_params to:"), _max_params); + + ESPAsync_EMParameter** new_params = (ESPAsync_EMParameter**)realloc(_params, + _max_params * sizeof(ESPAsync_EMParameter*)); + + if (new_params != NULL) + { + _params = new_params; + } + else + { + LOGINFO(F("ERROR: failed to realloc params, size not increased!")); + + return false; + } + } + + _params[_paramsCount] = p; + _paramsCount++; + + LOGINFO1(F("Adding parameter"), p->getID()); + + return true; + +#else + + // Danger here. Better to use Tzapu way here + if (_paramsCount < (ETH_MANAGER_MAX_PARAMS)) + { + _params[_paramsCount] = p; + _paramsCount++; + + LOGINFO1(F("Adding parameter"), p->getID()); + } + else + { + LOGINFO("Can't add parameter. Full"); + } + +#endif +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setupConfigPortal() +{ + stopConfigPortal = false; //Signal not to close config portal + +#if !( USING_ESP32_S2 || USING_ESP32_C3 ) + server->reset(); + + if (!dnsServer) + dnsServer = new AsyncDNSServer; + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + /* Setup the DNS server redirecting all the domains to the apIP */ + if (dnsServer) + { + dnsServer->setErrorReplyCode(AsyncDNSReplyCode::NoError); + + // AsyncDNSServer started with "*" domain name, all DNS requests will be passsed to ETH.localIP() + if (! dnsServer->start(DNS_PORT, "*", ETH.localIP())) + { + // No socket available + LOGERROR(F("Can't start DNS Server. No available socket")); + } + } + + _configPortalStart = millis(); + + LOGDEBUG1(F("_configPortalStart millis() ="), millis()); + + LOGWARN1(F("Config Portal IP address ="), ETH.localIP()); + + /* Setup web pages: root, eth config pages, SO captive portal detectors and not found. */ + + server->on("/", std::bind(&AsyncWT32_ETH01_Manager::handleRoot, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/eth", std::bind(&AsyncWT32_ETH01_Manager::handleETH, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/ethsave", std::bind(&AsyncWT32_ETH01_Manager::handleETHSave, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/close", std::bind(&AsyncWT32_ETH01_Manager::handleServerClose, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/i", std::bind(&AsyncWT32_ETH01_Manager::handleInfo, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/r", std::bind(&AsyncWT32_ETH01_Manager::handleReset, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->on("/state", std::bind(&AsyncWT32_ETH01_Manager::handleState, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. + server->on("/fwlink", std::bind(&AsyncWT32_ETH01_Manager::handleRoot, this, + std::placeholders::_1)).setFilter(ON_AP_FILTER); + server->onNotFound (std::bind(&AsyncWT32_ETH01_Manager::handleNotFound, this, std::placeholders::_1)); + + server->begin(); // Web server start + + LOGWARN(F("HTTP server started")); +} + +////////////////////////////////////////// + +bool AsyncWT32_ETH01_Manager::startConfigPortal() +{ + connect = false; + + setupConfigPortal(); + + bool TimedOut = true; + + LOGINFO("startConfigPortal : Enter loop"); + + while (true) + { + if (connect) + { + TimedOut = false; + + 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(); + } + + LOGDEBUG("Stop ConfigPortal: _shouldBreakAfterConfig"); + + break; + } + } + + if (stopConfigPortal) + { + TimedOut = false; + + LOGERROR("stopConfigPortal"); + + stopConfigPortal = false; + + break; + } + + if (_configPortalTimeout > 0 && ( millis() > _configPortalStart + _configPortalTimeout) ) + { + //LOGDEBUG3("startConfigPortal: timeout, _configPortalTimeout =", _configPortalTimeout, "millis() =", millis()); + + stopConfigPortal = false; + + break; + } + +#define TIME_BETWEEN_CONFIG_PORTAL_LOOP 50 + + vTaskDelay(TIME_BETWEEN_CONFIG_PORTAL_LOOP / portTICK_PERIOD_MS); + } + + //LOGDEBUG3("startConfigPortal: exit, _configPortalTimeout =", _configPortalTimeout, "millis() =", millis()); + +#if !( USING_ESP32_S2 || USING_ESP32_C3 ) + server->reset(); + dnsServer->stop(); +#endif + + return (WT32_ETH01_isConnected()); +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setTimeout(const unsigned long& seconds) +{ + setConfigPortalTimeout(seconds); +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setConfigPortalTimeout(const unsigned long& seconds) +{ + _configPortalTimeout = seconds * 1000; +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setConnectTimeout(const unsigned long& seconds) +{ + _connectTimeout = seconds * 1000; +} + +void AsyncWT32_ETH01_Manager::setDebugOutput(bool debug) +{ + _debug = debug; +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setBreakAfterConfig(bool shouldBreak) +{ + _shouldBreakAfterConfig = shouldBreak; +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::reportStatus(String& page) +{ + page += FPSTR(EM_HTTP_SCRIPT_NTP_MSG); +} + + + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setSTAStaticIPConfig(const IPAddress& ip, const IPAddress& gw, const IPAddress& sn) +{ + LOGINFO(F("setSTAStaticIPConfig")); + + _ETH_STA_IPconfig._sta_static_ip = ip; + _ETH_STA_IPconfig._sta_static_gw = gw; + _ETH_STA_IPconfig._sta_static_sn = sn; +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::setSTAStaticIPConfig(const ETH_STA_IPConfig& EM_STA_IPconfig) +{ + LOGINFO(F("setSTAStaticIPConfig")); + + memcpy((void *) &_ETH_STA_IPconfig, &EM_STA_IPconfig, sizeof(_ETH_STA_IPconfig)); +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::getSTAStaticIPConfig(ETH_STA_IPConfig& EM_STA_IPconfig) +{ + LOGINFO(F("getSTAStaticIPConfig")); + + memcpy((void *) &EM_STA_IPconfig, &_ETH_STA_IPconfig, sizeof(EM_STA_IPconfig)); +} + + +////////////////////////////////////////// + +#if USE_CONFIGURABLE_DNS +void AsyncWT32_ETH01_Manager::setSTAStaticIPConfig(const IPAddress& ip, const IPAddress& gw, const IPAddress& sn, + const IPAddress& dns_address_1, const IPAddress& dns_address_2) +{ + LOGINFO(F("setSTAStaticIPConfig for USE_CONFIGURABLE_DNS")); + + _ETH_STA_IPconfig._sta_static_ip = ip; + _ETH_STA_IPconfig._sta_static_gw = gw; + _ETH_STA_IPconfig._sta_static_sn = sn; + _ETH_STA_IPconfig._sta_static_dns1 = dns_address_1; //***** Added argument ***** + _ETH_STA_IPconfig._sta_static_dns2 = dns_address_2; //***** Added argument ***** +} +#endif + +////////////////////////////////////////// + +// Handle root or redirect to captive portal +void AsyncWT32_ETH01_Manager::handleRoot(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("handleRoot")); + + // Disable _configPortalTimeout when someone accessing Portal to give some time to config + _configPortalTimeout = 0; + + if (captivePortal(request)) + { + LOGDEBUG(F("handleRoot: captive portal exit")); + + // If captive portal redirect instead of displaying the error page. + return; + } + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Options"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_SCRIPT_NTP); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(EM_HTTP_HEAD_END); + + page += FPSTR(EM_FLDSET_START); + page += FPSTR(EM_HTTP_PORTAL_OPTIONS); + page += F("
"); + + reportStatus(page); + + page += F("
"); + page += FPSTR(EM_FLDSET_END); + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + // For configuring CORS Header, default to EM_HTTP_CORS_ALLOW_ALL = "*" + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) +} + +////////////////////////////////////////// + +// ETH config page handler +void AsyncWT32_ETH01_Manager::handleETH(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("Handle ETH")); + + // Disable _configPortalTimeout when someone accessing Portal to give some time to config + _configPortalTimeout = 0; + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Config ESP"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_SCRIPT_NTP); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(EM_HTTP_HEAD_END); + page += F("

Configuration

"); + + page += FPSTR(EM_HTTP_FORM_START); + + char parLength[2]; + + page += FPSTR(EM_FLDSET_START); + + // add the extra parameters to the form + for (int i = 0; i < _paramsCount; i++) + { + if (_params[i] == NULL) + { + break; + } + + String pitem; + + switch (_params[i]->getLabelPlacement()) + { + case WFM_LABEL_BEFORE: + pitem = FPSTR(EM_HTTP_FORM_LABEL_BEFORE); + break; + + case WFM_LABEL_AFTER: + pitem = FPSTR(EM_HTTP_FORM_LABEL_AFTER); + break; + + default: + // WFM_NO_LABEL + pitem = FPSTR(EM_HTTP_FORM_PARAM); + break; + } + + if (_params[i]->getID() != NULL) + { + 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()); + pitem.replace("{c}", _params[i]->getCustomHTML()); + } + else + { + pitem = _params[i]->getCustomHTML(); + } + + page += pitem; + } + + if (_paramsCount > 0) + { + page += FPSTR(EM_FLDSET_END); + } + + if (_params[0] != NULL) + { + page += "
"; + } + + LOGDEBUG1(F("Static IP ="), _ETH_STA_IPconfig._sta_static_ip.toString()); + + // KH, Comment out to permit changing from DHCP to static IP, or vice versa + // and add staticIP label in CP + + // To permit disable/enable StaticIP configuration in Config Portal from sketch. Valid only if DHCP is used. + // You'll loose the feature of dynamically changing from DHCP to static IP, or vice versa + // You have to explicitly specify false to disable the feature. + +#if !USE_STATIC_IP_CONFIG_IN_CP + + if (_ETH_STA_IPconfig._sta_static_ip) +#endif + { + page += FPSTR(EM_FLDSET_START); + + String item = FPSTR(EM_HTTP_FORM_LABEL); + + item += FPSTR(EM_HTTP_FORM_PARAM); + + item.replace("{i}", "ip"); + item.replace("{n}", "ip"); + item.replace("{p}", "Static IP"); + item.replace("{l}", "15"); + item.replace("{v}", _ETH_STA_IPconfig._sta_static_ip.toString()); + + page += item; + + item = FPSTR(EM_HTTP_FORM_LABEL); + item += FPSTR(EM_HTTP_FORM_PARAM); + + item.replace("{i}", "gw"); + item.replace("{n}", "gw"); + item.replace("{p}", "Gateway IP"); + item.replace("{l}", "15"); + item.replace("{v}", _ETH_STA_IPconfig._sta_static_gw.toString()); + + page += item; + + item = FPSTR(EM_HTTP_FORM_LABEL); + item += FPSTR(EM_HTTP_FORM_PARAM); + + item.replace("{i}", "sn"); + item.replace("{n}", "sn"); + item.replace("{p}", "Subnet"); + item.replace("{l}", "15"); + item.replace("{v}", _ETH_STA_IPconfig._sta_static_sn.toString()); + +#if USE_CONFIGURABLE_DNS + //***** Added for DNS address options ***** + page += item; + + item = FPSTR(EM_HTTP_FORM_LABEL); + item += FPSTR(EM_HTTP_FORM_PARAM); + + item.replace("{i}", "dns1"); + item.replace("{n}", "dns1"); + item.replace("{p}", "DNS1 IP"); + item.replace("{l}", "15"); + item.replace("{v}", _ETH_STA_IPconfig._sta_static_dns1.toString()); + + page += item; + + item = FPSTR(EM_HTTP_FORM_LABEL); + item += FPSTR(EM_HTTP_FORM_PARAM); + + item.replace("{i}", "dns2"); + item.replace("{n}", "dns2"); + item.replace("{p}", "DNS2 IP"); + item.replace("{l}", "15"); + item.replace("{v}", _ETH_STA_IPconfig._sta_static_dns2.toString()); + //***** End added for DNS address options ***** +#endif + + page += item; + page += FPSTR(EM_FLDSET_END); + page += "
"; + } + + page += FPSTR(EM_HTTP_SCRIPT_NTP_HIDDEN); + page += FPSTR(EM_HTTP_FORM_END); + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + LOGDEBUG(F("Sent config page")); +} + +////////////////////////////////////////// + +// Handle the WLAN save form and redirect to WLAN config page again +void AsyncWT32_ETH01_Manager::handleETHSave(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("ETH save")); + +#if USE_ESP_ETH_MANAGER_NTP + + if (request->hasArg("timezone")) + { + _timezoneName = request->arg("timezone"); //.c_str(); + + LOGDEBUG1(F("TZ ="), _timezoneName); + } + else + { + LOGDEBUG(F("No TZ arg")); + } + +#endif + + /////////////////////// + + //parameters + for (int i = 0; i < _paramsCount; i++) + { + if (_params[i] == NULL) + { + break; + } + + //read parameter + String value = request->arg(_params[i]->getID()).c_str(); + + //store it in array + value.toCharArray(_params[i]->_WMParam_data._value, _params[i]->_WMParam_data._length); + + LOGDEBUG2(F("Parameter and value :"), _params[i]->getID(), value); + } + + + if (request->hasArg("ip")) + { + String ip = request->arg("ip"); + + optionalIPFromString(&_ETH_STA_IPconfig._sta_static_ip, ip.c_str()); + + LOGDEBUG1(F("New Static IP ="), _ETH_STA_IPconfig._sta_static_ip.toString()); + } + + if (request->hasArg("gw")) + { + String gw = request->arg("gw"); + + optionalIPFromString(&_ETH_STA_IPconfig._sta_static_gw, gw.c_str()); + + LOGDEBUG1(F("New Static Gateway ="), _ETH_STA_IPconfig._sta_static_gw.toString()); + } + + if (request->hasArg("sn")) + { + String sn = request->arg("sn"); + + optionalIPFromString(&_ETH_STA_IPconfig._sta_static_sn, sn.c_str()); + + LOGDEBUG1(F("New Static Netmask ="), _ETH_STA_IPconfig._sta_static_sn.toString()); + } + +#if USE_CONFIGURABLE_DNS + + //***** Added for DNS Options ***** + if (request->hasArg("dns1")) + { + String dns1 = request->arg("dns1"); + + optionalIPFromString(&_ETH_STA_IPconfig._sta_static_dns1, dns1.c_str()); + + LOGDEBUG1(F("New Static DNS1 ="), _ETH_STA_IPconfig._sta_static_dns1.toString()); + } + + if (request->hasArg("dns2")) + { + String dns2 = request->arg("dns2"); + + optionalIPFromString(&_ETH_STA_IPconfig._sta_static_dns2, dns2.c_str()); + + LOGDEBUG1(F("New Static DNS2 ="), _ETH_STA_IPconfig._sta_static_dns2.toString()); + } + + //***** End added for DNS Options ***** +#endif + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Credentials Saved"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(EM_HTTP_HEAD_END); + page += FPSTR(EM_HTTP_SAVED); + + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + LOGDEBUG(F("Sent eth save page")); + + connect = true; //signal ready to connect/reset + + stopConfigPortal = true; //signal ready to shutdown config portal +} + +////////////////////////////////////////// + +// Handle shut down the server page +void AsyncWT32_ETH01_Manager::handleServerClose(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("Server Close")); + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Close Server"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(EM_HTTP_HEAD_END); + page += F("
"); + page += F("
"); + page += F("IP address is "); + page += ETH.localIP().toString(); + page += F("

"); + page += F("Portal closed...

"); + + //page += F("Push button on device to restart configuration server!"); + + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + stopConfigPortal = true; //signal ready to shutdown config portal + + LOGDEBUG(F("Sent server close page")); +} + +////////////////////////////////////////// + +// Handle the info page +void AsyncWT32_ETH01_Manager::handleInfo(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("Info")); + + // Disable _configPortalTimeout when someone accessing Portal to give some time to config + _configPortalTimeout = 0; + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Info"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_SCRIPT_NTP); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + + if (connect) + page += F(""); + + page += FPSTR(EM_HTTP_HEAD_END); + + page += F("
"); + + if (connect) + { + page += F("
Trying to connect
"); + page += ethStatus; + page += F("
"); + } + + page += pager; + page += F("

Information

"); + + reportStatus(page); + + page += FPSTR(EM_FLDSET_START); + page += F("

Device Data

"); + page += F(""); + page += F(""); + + page += F(""); + + page += F(""); + + page += F(""); + + page += F(""); + + page += F(""); + + page += F(""); + + page += F(""); + page += F("
NameValue
Chip ID"); + +#ifdef ESP8266 + page += String(ESP.getChipId(), HEX); +#else //ESP32 + + page += String(ESP_getChipId(), HEX); + page += F("
Chip OUI"); + page += F("0x"); + page += String(getChipOUI(), HEX); + page += F("
Chip Model"); + page += ESP.getChipModel(); + page += F(" Rev"); + page += ESP.getChipRevision(); +#endif + + page += F("
Flash Chip ID"); + +#ifdef ESP8266 + page += String(ESP.getFlashChipId(), HEX); +#else //ESP32 + // TODO + page += F("TODO"); +#endif + + page += F("
IDE Flash Size"); + page += ESP.getFlashChipSize(); + page += F(" bytes
Real Flash Size"); + +#ifdef ESP8266 + page += ESP.getFlashChipRealSize(); +#else //ESP32 + // TODO + page += F("TODO"); +#endif + + page += F(" bytes
Station IP"); + page += ETH.localIP().toString(); + page += F("
Station MAC"); + page += ETH.macAddress(); + page += F("
"); + + page += FPSTR(EM_FLDSET_END); + +#if USE_AVAILABLE_PAGES + page += FPSTR(EM_FLDSET_START); + page += FPSTR(EM_HTTP_AVAILABLE_PAGES); + page += FPSTR(EM_FLDSET_END); +#endif + + page += F("

More information about AsyncWT32_ETH01_Manager at"); + page += F("

https://github.com/khoih-prog/AsyncWT32_ETH01_Manager"); + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); + +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + LOGDEBUG(F("Info page sent")); +} + +////////////////////////////////////////// + +// Handle the state page +void AsyncWT32_ETH01_Manager::handleState(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("State-Json")); + + String page = F("{\"Soft_AP_IP\":\""); + + page += F("\",\"Station_IP\":\""); + page += ETH.localIP().toString(); + page += F("\","); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, "application/json", page); + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + +#if USING_CORS_FEATURE + response->addHeader(FPSTR(EM_HTTP_CORS), _CORS_Header); +#endif + + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + LOGDEBUG(F("Sent state page in json format")); +} + +////////////////////////////////////////// + +// Handle the reset page +void AsyncWT32_ETH01_Manager::handleReset(AsyncWebServerRequest *request) +{ + LOGDEBUG(F("Reset")); + + String page = FPSTR(EM_HTTP_HEAD_START); + page.replace("{v}", "Information"); + + page += FPSTR(EM_HTTP_SCRIPT); + page += FPSTR(EM_HTTP_STYLE); + page += _customHeadElement; + page += FPSTR(EM_HTTP_HEAD_END); + page += F("Resetting"); + page += FPSTR(EM_HTTP_END); + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, page); +#else + + AsyncWebServerResponse *response = request->beginResponse(200, EM_HTTP_HEAD_CT, page); + + response->addHeader(EM_HTTP_CACHE_CONTROL, EM_HTTP_NO_STORE); + response->addHeader(EM_HTTP_PRAGMA, EM_HTTP_NO_CACHE); + response->addHeader(EM_HTTP_EXPIRES, "-1"); + + request->send(response); +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) + + LOGDEBUG(F("Sent reset page")); + delay(5000); + + ESP.restart(); + + delay(2000); +} + +////////////////////////////////////////// + +void AsyncWT32_ETH01_Manager::handleNotFound(AsyncWebServerRequest *request) +{ + if (captivePortal(request)) + { + LOGDEBUG(F("handleNotFound: captive portal exit")); + + // If captive portal redirect instead of displaying the error page. + return; + } + + String message = "File Not Found\n\n"; + + message += "URI: "; + message += request->url(); + message += "\nMethod: "; + message += (request->method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += request->args(); + message += "\n"; + + for (uint8_t i = 0; i < request->args(); i++) + { + message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + +#if ( USING_ESP32_S2 || USING_ESP32_C3 ) + request->send(200, EM_HTTP_HEAD_CT, message); + + // Fix ESP32-S2 issue with WebServer (https://github.com/espressif/arduino-esp32/issues/4348) + delay(1); +#else + + AsyncWebServerResponse *response = request->beginResponse( 404, EM_HTTP_HEAD_CT2, message ); + + response->addHeader(FPSTR(EM_HTTP_CACHE_CONTROL), FPSTR(EM_HTTP_NO_STORE)); + response->addHeader(FPSTR(EM_HTTP_PRAGMA), FPSTR(EM_HTTP_NO_CACHE)); + response->addHeader(FPSTR(EM_HTTP_EXPIRES), "-1"); + + request->send(response); +#endif // ( USING_ESP32_S2 || USING_ESP32_C3 ) +} + +////////////////////////////////////////// + +/** + HTTPD redirector + 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. +*/ +bool AsyncWT32_ETH01_Manager::captivePortal(AsyncWebServerRequest *request) +{ + if (!isIp(request->host())) + { + LOGINFO(F("Request redirected to captive portal")); + LOGINFO1(F("Location http://"), toStringIp(request->client()->localIP())); + + AsyncWebServerResponse *response = request->beginResponse(302, EM_HTTP_HEAD_CT2, ""); + + response->addHeader("Location", String("http://") + toStringIp(request->client()->localIP())); + + request->send(response); + + return true; + } + + LOGDEBUG1(F("request host IP ="), request->host()); + + return false; +} + +////////////////////////////////////////// + +// start up save config callback +void AsyncWT32_ETH01_Manager::setSaveConfigCallback(void(*func)()) +{ + _savecallback = func; +} + +////////////////////////////////////////// + +// sets a custom element to add to head, like a new style tag +void AsyncWT32_ETH01_Manager::setCustomHeadElement(const char* element) +{ + _customHeadElement = element; +} + +////////////////////////////////////////// + +// Is this an IP? +bool AsyncWT32_ETH01_Manager::isIp(const String& str) +{ + for (unsigned int i = 0; i < str.length(); i++) + { + int c = str.charAt(i); + + if ( (c != '.') && (c != ':') && ( (c < '0') || (c > '9') ) ) + { + return false; + } + } + + return true; +} + +////////////////////////////////////////// + +// IP to String +String AsyncWT32_ETH01_Manager::toStringIp(const 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; +} + +////////////////////////////////////////// + +uint32_t getChipID() +{ + uint64_t chipId64 = 0; + + for (int i = 0; i < 6; i++) + { + chipId64 |= ( ( (uint64_t) ESP.getEfuseMac() >> (40 - (i * 8)) ) & 0xff ) << (i * 8); + } + + return (uint32_t) (chipId64 & 0xFFFFFF); +} + +////////////////////////////////////////// + +uint32_t getChipOUI() +{ + uint64_t chipId64 = 0; + + for (int i = 0; i < 6; i++) + { + chipId64 |= ( ( (uint64_t) ESP.getEfuseMac() >> (40 - (i * 8)) ) & 0xff ) << (i * 8); + } + + return (uint32_t) (chipId64 >> 24); +} + +////////////////////////////////////////// + +#endif // AsyncWT32_ETH01_Manager_Impl_h diff --git a/src/utils/TZ.h b/src/utils/TZ.h new file mode 100644 index 0000000..e96b0c7 --- /dev/null +++ b/src/utils/TZ.h @@ -0,0 +1,1526 @@ + +// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv +// by script /tools/TZupdate.sh +// Thu Nov 12 04:07:03 UTC 2020 +// +// This database is autogenerated from IANA timezone database +// https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv +// (using https://www.iana.org/time-zones) +// and can be updated on demand in this repository +// or by yourself using the above script + +#ifndef TZDB_H +#define TZDB_H + +//See: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html +// EST5EDT,M3.2.0,M11.1.0 (for America/New_York) +// EST5EDT is the name of the time zone +// EST is the abbreviation used when DST is off +// 6 hours is the time difference from GMT +// EDT is the abbreviation used when DST is on +// ,M3 is the third month +// .2 is the second occurrence of the day in the month +// .0 is Sunday +// ,M11 is the eleventh month +// .1 is the first occurrence of the day in the month +// .0 is Sunday + + +#if !defined(USING_AFRICA) + #define USING_AFRICA false +#endif + +#if !defined(USING_AMERICA) + #define USING_AMERICA true +#endif + +#if !defined(USING_ANTARCTICA) + #define USING_ANTARCTICA false +#endif + +#if !defined(USING_ASIA) + #define USING_ASIA false +#endif + +#if !defined(USING_ATLANTIC) + #define USING_ATLANTIC false +#endif + +#if !defined(USING_AUSTRALIA) + #define USING_AUSTRALIA true +#endif + +#if !defined(USING_EUROPE) + #define USING_EUROPE false +#endif + +#if !defined(USING_INDIAN) + #define USING_INDIAN false +#endif + +#if !defined(USING_PACIFIC) + #define USING_PACIFIC false +#endif + +#if !defined(USING_ETC_GMT) + #define USING_ETC_GMT false +#endif + +//////////////////////////////////////////////////// + +#define TZ_Africa_Abidjan ("GMT0") +#define TZ_Africa_Accra ("GMT0") +#define TZ_Africa_Addis_Ababa ("EAT-3") +#define TZ_Africa_Algiers ("CET-1") +#define TZ_Africa_Asmara ("EAT-3") +#define TZ_Africa_Bamako ("GMT0") +#define TZ_Africa_Bangui ("WAT-1") +#define TZ_Africa_Banjul ("GMT0") +#define TZ_Africa_Bissau ("GMT0") +#define TZ_Africa_Blantyre ("CAT-2") +#define TZ_Africa_Brazzaville ("WAT-1") +#define TZ_Africa_Bujumbura ("CAT-2") +#define TZ_Africa_Cairo ("EET-2") +#define TZ_Africa_Casablanca ("<+01>-1") +#define TZ_Africa_Ceuta ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Africa_Conakry ("GMT0") +#define TZ_Africa_Dakar ("GMT0") +#define TZ_Africa_Dar_es_Salaam ("EAT-3") +#define TZ_Africa_Djibouti ("EAT-3") +#define TZ_Africa_Douala ("WAT-1") +#define TZ_Africa_El_Aaiun ("<+01>-1") +#define TZ_Africa_Freetown ("GMT0") +#define TZ_Africa_Gaborone ("CAT-2") +#define TZ_Africa_Harare ("CAT-2") +#define TZ_Africa_Johannesburg ("SAST-2") +#define TZ_Africa_Juba ("EAT-3") +#define TZ_Africa_Kampala ("EAT-3") +#define TZ_Africa_Khartoum ("CAT-2") +#define TZ_Africa_Kigali ("CAT-2") +#define TZ_Africa_Kinshasa ("WAT-1") +#define TZ_Africa_Lagos ("WAT-1") +#define TZ_Africa_Libreville ("WAT-1") +#define TZ_Africa_Lome ("GMT0") +#define TZ_Africa_Luanda ("WAT-1") +#define TZ_Africa_Lubumbashi ("CAT-2") +#define TZ_Africa_Lusaka ("CAT-2") +#define TZ_Africa_Malabo ("WAT-1") +#define TZ_Africa_Maputo ("CAT-2") +#define TZ_Africa_Maseru ("SAST-2") +#define TZ_Africa_Mbabane ("SAST-2") +#define TZ_Africa_Mogadishu ("EAT-3") +#define TZ_Africa_Monrovia ("GMT0") +#define TZ_Africa_Nairobi ("EAT-3") +#define TZ_Africa_Ndjamena ("WAT-1") +#define TZ_Africa_Niamey ("WAT-1") +#define TZ_Africa_Nouakchott ("GMT0") +#define TZ_Africa_Ouagadougou ("GMT0") +#define TZ_Africa_PortomNovo ("WAT-1") +#define TZ_Africa_Sao_Tome ("GMT0") +#define TZ_Africa_Tripoli ("EET-2") +#define TZ_Africa_Tunis ("CET-1") +#define TZ_Africa_Windhoek ("CAT-2") +#define TZ_America_Adak ("HST10HDT,M3.2.0,M11.1.0") +#define TZ_America_Anchorage ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_Anguilla ("AST4") +#define TZ_America_Antigua ("AST4") +#define TZ_America_Araguaina ("<-03>3") +#define TZ_America_Argentina_Buenos_Aires ("<-03>3") +#define TZ_America_Argentina_Catamarca ("<-03>3") +#define TZ_America_Argentina_Cordoba ("<-03>3") +#define TZ_America_Argentina_Jujuy ("<-03>3") +#define TZ_America_Argentina_La_Rioja ("<-03>3") +#define TZ_America_Argentina_Mendoza ("<-03>3") +#define TZ_America_Argentina_Rio_Gallegos ("<-03>3") +#define TZ_America_Argentina_Salta ("<-03>3") +#define TZ_America_Argentina_San_Juan ("<-03>3") +#define TZ_America_Argentina_San_Luis ("<-03>3") +#define TZ_America_Argentina_Tucuman ("<-03>3") +#define TZ_America_Argentina_Ushuaia ("<-03>3") +#define TZ_America_Aruba ("AST4") +#define TZ_America_Asuncion ("<-04>4<-03>,M10.1.0/0,M3.4.0/0") +#define TZ_America_Atikokan ("EST5") +#define TZ_America_Bahia ("<-03>3") +#define TZ_America_Bahia_Banderas ("CST6CDT,M4.1.0,M10.5.0") +#define TZ_America_Barbados ("AST4") +#define TZ_America_Belem ("<-03>3") +#define TZ_America_Belize ("CST6") +#define TZ_America_BlancmSablon ("AST4") +#define TZ_America_Boa_Vista ("<-04>4") +#define TZ_America_Bogota ("<-05>5") +#define TZ_America_Boise ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Cambridge_Bay ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Campo_Grande ("<-04>4") +#define TZ_America_Cancun ("EST5") +#define TZ_America_Caracas ("<-04>4") +#define TZ_America_Cayenne ("<-03>3") +#define TZ_America_Cayman ("EST5") +#define TZ_America_Chicago ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Chihuahua ("MST7MDT,M4.1.0,M10.5.0") +#define TZ_America_Costa_Rica ("CST6") +#define TZ_America_Creston ("MST7") +#define TZ_America_Cuiaba ("<-04>4") +#define TZ_America_Curacao ("AST4") +#define TZ_America_Danmarkshavn ("GMT0") +#define TZ_America_Dawson ("MST7") +#define TZ_America_Dawson_Creek ("MST7") +#define TZ_America_Denver ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Detroit ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Dominica ("AST4") +#define TZ_America_Edmonton ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Eirunepe ("<-05>5") +#define TZ_America_El_Salvador ("CST6") +#define TZ_America_Fortaleza ("<-03>3") +#define TZ_America_Fort_Nelson ("MST7") +#define TZ_America_Glace_Bay ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_America_Godthab ("<-03>3<-02>,M3.5.0/-2,M10.5.0/-1") +#define TZ_America_Goose_Bay ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_America_Grand_Turk ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Grenada ("AST4") +#define TZ_America_Guadeloupe ("AST4") +#define TZ_America_Guatemala ("CST6") +#define TZ_America_Guayaquil ("<-05>5") +#define TZ_America_Guyana ("<-04>4") +#define TZ_America_Halifax ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_America_Havana ("CST5CDT,M3.2.0/0,M11.1.0/1") +#define TZ_America_Hermosillo ("MST7") +#define TZ_America_Indiana_Indianapolis ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Knox ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Marengo ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Petersburg ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Tell_City ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Vevay ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Vincennes ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Indiana_Winamac ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Inuvik ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Iqaluit ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Jamaica ("EST5") +#define TZ_America_Juneau ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_Kentucky_Louisville ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Kentucky_Monticello ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Kralendijk ("AST4") +#define TZ_America_La_Paz ("<-04>4") +#define TZ_America_Lima ("<-05>5") +#define TZ_America_Los_Angeles ("PST8PDT,M3.2.0,M11.1.0") +#define TZ_America_Lower_Princes ("AST4") +#define TZ_America_Maceio ("<-03>3") +#define TZ_America_Managua ("CST6") +#define TZ_America_Manaus ("<-04>4") +#define TZ_America_Marigot ("AST4") +#define TZ_America_Martinique ("AST4") +#define TZ_America_Matamoros ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Mazatlan ("MST7MDT,M4.1.0,M10.5.0") +#define TZ_America_Menominee ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Merida ("CST6CDT,M4.1.0,M10.5.0") +#define TZ_America_Metlakatla ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_Mexico_City ("CST6CDT,M4.1.0,M10.5.0") +#define TZ_America_Miquelon ("<-03>3<-02>,M3.2.0,M11.1.0") +#define TZ_America_Moncton ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_America_Monterrey ("CST6CDT,M4.1.0,M10.5.0") +#define TZ_America_Montevideo ("<-03>3") +#define TZ_America_Montreal ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Montserrat ("AST4") +#define TZ_America_Nassau ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_New_York ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Nipigon ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Nome ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_Noronha ("<-02>2") +#define TZ_America_North_Dakota_Beulah ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_North_Dakota_Center ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_North_Dakota_New_Salem ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Ojinaga ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_America_Panama ("EST5") +#define TZ_America_Pangnirtung ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Paramaribo ("<-03>3") +#define TZ_America_Phoenix ("MST7") +#define TZ_America_PortmaumPrince ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Port_of_Spain ("AST4") +#define TZ_America_Porto_Velho ("<-04>4") +#define TZ_America_Puerto_Rico ("AST4") +#define TZ_America_Punta_Arenas ("<-03>3") +#define TZ_America_Rainy_River ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Rankin_Inlet ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Recife ("<-03>3") +#define TZ_America_Regina ("CST6") +#define TZ_America_Resolute ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Rio_Branco ("<-05>5") +#define TZ_America_Santarem ("<-03>3") +#define TZ_America_Santiago ("<-04>4<-03>,M9.1.6/24,M4.1.6/24") +#define TZ_America_Santo_Domingo ("AST4") +#define TZ_America_Sao_Paulo ("<-03>3") +#define TZ_America_Scoresbysund ("<-01>1<+00>,M3.5.0/0,M10.5.0/1") +#define TZ_America_Sitka ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_St_Barthelemy ("AST4") +#define TZ_America_St_Johns ("NST3:30NDT,M3.2.0,M11.1.0") +#define TZ_America_St_Kitts ("AST4") +#define TZ_America_St_Lucia ("AST4") +#define TZ_America_St_Thomas ("AST4") +#define TZ_America_St_Vincent ("AST4") +#define TZ_America_Swift_Current ("CST6") +#define TZ_America_Tegucigalpa ("CST6") +#define TZ_America_Thule ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_America_Thunder_Bay ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Tijuana ("PST8PDT,M3.2.0,M11.1.0") +#define TZ_America_Toronto ("EST5EDT,M3.2.0,M11.1.0") +#define TZ_America_Tortola ("AST4") +#define TZ_America_Vancouver ("PST8PDT,M3.2.0,M11.1.0") +#define TZ_America_Whitehorse ("MST7") +#define TZ_America_Winnipeg ("CST6CDT,M3.2.0,M11.1.0") +#define TZ_America_Yakutat ("AKST9AKDT,M3.2.0,M11.1.0") +#define TZ_America_Yellowknife ("MST7MDT,M3.2.0,M11.1.0") +#define TZ_Antarctica_Casey ("<+11>-11") +#define TZ_Antarctica_Davis ("<+07>-7") +#define TZ_Antarctica_DumontDUrville ("<+10>-10") +#define TZ_Antarctica_Macquarie ("AEST-10AEDT,M10.1.0,M4.1.0/3") +#define TZ_Antarctica_Mawson ("<+05>-5") +#define TZ_Antarctica_McMurdo ("NZST-12NZDT,M9.5.0,M4.1.0/3") +#define TZ_Antarctica_Palmer ("<-03>3") +#define TZ_Antarctica_Rothera ("<-03>3") +#define TZ_Antarctica_Syowa ("<+03>-3") +#define TZ_Antarctica_Troll ("<+00>0<+02>-2,M3.5.0/1,M10.5.0/3") +#define TZ_Antarctica_Vostok ("<+06>-6") +#define TZ_Arctic_Longyearbyen ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Asia_Aden ("<+03>-3") +#define TZ_Asia_Almaty ("<+06>-6") +#define TZ_Asia_Amman ("EET-2EEST,M3.5.4/24,M10.5.5/1") +#define TZ_Asia_Anadyr ("<+12>-12") +#define TZ_Asia_Aqtau ("<+05>-5") +#define TZ_Asia_Aqtobe ("<+05>-5") +#define TZ_Asia_Ashgabat ("<+05>-5") +#define TZ_Asia_Atyrau ("<+05>-5") +#define TZ_Asia_Baghdad ("<+03>-3") +#define TZ_Asia_Bahrain ("<+03>-3") +#define TZ_Asia_Baku ("<+04>-4") +#define TZ_Asia_Bangkok ("<+07>-7") +#define TZ_Asia_Barnaul ("<+07>-7") +#define TZ_Asia_Beirut ("EET-2EEST,M3.5.0/0,M10.5.0/0") +#define TZ_Asia_Bishkek ("<+06>-6") +#define TZ_Asia_Brunei ("<+08>-8") +#define TZ_Asia_Chita ("<+09>-9") +#define TZ_Asia_Choibalsan ("<+08>-8") +#define TZ_Asia_Colombo ("<+0530>-5:30") +#define TZ_Asia_Damascus ("EET-2EEST,M3.5.5/0,M10.5.5/0") +#define TZ_Asia_Dhaka ("<+06>-6") +#define TZ_Asia_Dili ("<+09>-9") +#define TZ_Asia_Dubai ("<+04>-4") +#define TZ_Asia_Dushanbe ("<+05>-5") +#define TZ_Asia_Famagusta ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Asia_Gaza ("EET-2EEST,M3.4.4/48,M10.4.4/49") +#define TZ_Asia_Hebron ("EET-2EEST,M3.4.4/48,M10.4.4/49") +#define TZ_Asia_Ho_Chi_Minh ("<+07>-7") +#define TZ_Asia_Hong_Kong ("HKT-8") +#define TZ_Asia_Hovd ("<+07>-7") +#define TZ_Asia_Irkutsk ("<+08>-8") +#define TZ_Asia_Jakarta ("WIB-7") +#define TZ_Asia_Jayapura ("WIT-9") +#define TZ_Asia_Jerusalem ("IST-2IDT,M3.4.4/26,M10.5.0") +#define TZ_Asia_Kabul ("<+0430>-4:30") +#define TZ_Asia_Kamchatka ("<+12>-12") +#define TZ_Asia_Karachi ("PKT-5") +#define TZ_Asia_Kathmandu ("<+0545>-5:45") +#define TZ_Asia_Khandyga ("<+09>-9") +#define TZ_Asia_Kolkata ("IST-5:30") +#define TZ_Asia_Krasnoyarsk ("<+07>-7") +#define TZ_Asia_Kuala_Lumpur ("<+08>-8") +#define TZ_Asia_Kuching ("<+08>-8") +#define TZ_Asia_Kuwait ("<+03>-3") +#define TZ_Asia_Macau ("CST-8") +#define TZ_Asia_Magadan ("<+11>-11") +#define TZ_Asia_Makassar ("WITA-8") +#define TZ_Asia_Manila ("PST-8") +#define TZ_Asia_Muscat ("<+04>-4") +#define TZ_Asia_Nicosia ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Asia_Novokuznetsk ("<+07>-7") +#define TZ_Asia_Novosibirsk ("<+07>-7") +#define TZ_Asia_Omsk ("<+06>-6") +#define TZ_Asia_Oral ("<+05>-5") +#define TZ_Asia_Phnom_Penh ("<+07>-7") +#define TZ_Asia_Pontianak ("WIB-7") +#define TZ_Asia_Pyongyang ("KST-9") +#define TZ_Asia_Qatar ("<+03>-3") +#define TZ_Asia_Qyzylorda ("<+05>-5") +#define TZ_Asia_Riyadh ("<+03>-3") +#define TZ_Asia_Sakhalin ("<+11>-11") +#define TZ_Asia_Samarkand ("<+05>-5") +#define TZ_Asia_Seoul ("KST-9") +#define TZ_Asia_Shanghai ("CST-8") +#define TZ_Asia_Singapore ("<+08>-8") +#define TZ_Asia_Srednekolymsk ("<+11>-11") +#define TZ_Asia_Taipei ("CST-8") +#define TZ_Asia_Tashkent ("<+05>-5") +#define TZ_Asia_Tbilisi ("<+04>-4") +#define TZ_Asia_Tehran ("<+0330>-3:30<+0430>,J79/24,J263/24") +#define TZ_Asia_Thimphu ("<+06>-6") +#define TZ_Asia_Tokyo ("JST-9") +#define TZ_Asia_Tomsk ("<+07>-7") +#define TZ_Asia_Ulaanbaatar ("<+08>-8") +#define TZ_Asia_Urumqi ("<+06>-6") +#define TZ_Asia_UstmNera ("<+10>-10") +#define TZ_Asia_Vientiane ("<+07>-7") +#define TZ_Asia_Vladivostok ("<+10>-10") +#define TZ_Asia_Yakutsk ("<+09>-9") +#define TZ_Asia_Yangon ("<+0630>-6:30") +#define TZ_Asia_Yekaterinburg ("<+05>-5") +#define TZ_Asia_Yerevan ("<+04>-4") +#define TZ_Atlantic_Azores ("<-01>1<+00>,M3.5.0/0,M10.5.0/1") +#define TZ_Atlantic_Bermuda ("AST4ADT,M3.2.0,M11.1.0") +#define TZ_Atlantic_Canary ("WET0WEST,M3.5.0/1,M10.5.0") +#define TZ_Atlantic_Cape_Verde ("<-01>1") +#define TZ_Atlantic_Faroe ("WET0WEST,M3.5.0/1,M10.5.0") +#define TZ_Atlantic_Madeira ("WET0WEST,M3.5.0/1,M10.5.0") +#define TZ_Atlantic_Reykjavik ("GMT0") +#define TZ_Atlantic_South_Georgia ("<-02>2") +#define TZ_Atlantic_Stanley ("<-03>3") +#define TZ_Atlantic_St_Helena ("GMT0") +#define TZ_Australia_Adelaide ("ACST-9:30ACDT,M10.1.0,M4.1.0/3") +#define TZ_Australia_Brisbane ("AEST-10") +#define TZ_Australia_Broken_Hill ("ACST-9:30ACDT,M10.1.0,M4.1.0/3") +#define TZ_Australia_Currie ("AEST-10AEDT,M10.1.0,M4.1.0/3") +#define TZ_Australia_Darwin ("ACST-9:30") +#define TZ_Australia_Eucla ("<+0845>-8:45") +#define TZ_Australia_Hobart ("AEST-10AEDT,M10.1.0,M4.1.0/3") +#define TZ_Australia_Lindeman ("AEST-10") +#define TZ_Australia_Lord_Howe ("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0") +#define TZ_Australia_Melbourne ("AEST-10AEDT,M10.1.0,M4.1.0/3") +#define TZ_Australia_Perth ("AWST-8") +#define TZ_Australia_Sydney ("AEST-10AEDT,M10.1.0,M4.1.0/3") +#define TZ_Europe_Amsterdam ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Andorra ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Astrakhan ("<+04>-4") +#define TZ_Europe_Athens ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Belgrade ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Berlin ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Bratislava ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Brussels ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Bucharest ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Budapest ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Busingen ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Chisinau ("EET-2EEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Copenhagen ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Dublin ("IST-1GMT0,M10.5.0,M3.5.0/1") +#define TZ_Europe_Gibraltar ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Guernsey ("GMT0BST,M3.5.0/1,M10.5.0") +#define TZ_Europe_Helsinki ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Isle_of_Man ("GMT0BST,M3.5.0/1,M10.5.0") +#define TZ_Europe_Istanbul ("<+03>-3") +#define TZ_Europe_Jersey ("GMT0BST,M3.5.0/1,M10.5.0") +#define TZ_Europe_Kaliningrad ("EET-2") +#define TZ_Europe_Kiev ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Kirov ("<+03>-3") +#define TZ_Europe_Lisbon ("WET0WEST,M3.5.0/1,M10.5.0") +#define TZ_Europe_Ljubljana ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_London ("GMT0BST,M3.5.0/1,M10.5.0") +#define TZ_Europe_Luxembourg ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Madrid ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Malta ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Mariehamn ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Minsk ("<+03>-3") +#define TZ_Europe_Monaco ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Moscow ("MSK-3") +#define TZ_Europe_Oslo ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Paris ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Podgorica ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Prague ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Riga ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Rome ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Samara ("<+04>-4") +#define TZ_Europe_San_Marino ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Sarajevo ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Saratov ("<+04>-4") +#define TZ_Europe_Simferopol ("MSK-3") +#define TZ_Europe_Skopje ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Sofia ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Stockholm ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Tallinn ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Tirane ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Ulyanovsk ("<+04>-4") +#define TZ_Europe_Uzhgorod ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Vaduz ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Vatican ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Vienna ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Vilnius ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Volgograd ("<+04>-4") +#define TZ_Europe_Warsaw ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Zagreb ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Europe_Zaporozhye ("EET-2EEST,M3.5.0/3,M10.5.0/4") +#define TZ_Europe_Zurich ("CET-1CEST,M3.5.0,M10.5.0/3") +#define TZ_Indian_Antananarivo ("EAT-3") +#define TZ_Indian_Chagos ("<+06>-6") +#define TZ_Indian_Christmas ("<+07>-7") +#define TZ_Indian_Cocos ("<+0630>-6:30") +#define TZ_Indian_Comoro ("EAT-3") +#define TZ_Indian_Kerguelen ("<+05>-5") +#define TZ_Indian_Mahe ("<+04>-4") +#define TZ_Indian_Maldives ("<+05>-5") +#define TZ_Indian_Mauritius ("<+04>-4") +#define TZ_Indian_Mayotte ("EAT-3") +#define TZ_Indian_Reunion ("<+04>-4") +#define TZ_Pacific_Apia ("<+13>-13<+14>,M9.5.0/3,M4.1.0/4") +#define TZ_Pacific_Auckland ("NZST-12NZDT,M9.5.0,M4.1.0/3") +#define TZ_Pacific_Bougainville ("<+11>-11") +#define TZ_Pacific_Chatham ("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45") +#define TZ_Pacific_Chuuk ("<+10>-10") +#define TZ_Pacific_Easter ("<-06>6<-05>,M9.1.6/22,M4.1.6/22") +#define TZ_Pacific_Efate ("<+11>-11") +#define TZ_Pacific_Enderbury ("<+13>-13") +#define TZ_Pacific_Fakaofo ("<+13>-13") +#define TZ_Pacific_Fiji ("<+12>-12<+13>,M11.2.0,M1.2.3/99") +#define TZ_Pacific_Funafuti ("<+12>-12") +#define TZ_Pacific_Galapagos ("<-06>6") +#define TZ_Pacific_Gambier ("<-09>9") +#define TZ_Pacific_Guadalcanal ("<+11>-11") +#define TZ_Pacific_Guam ("ChST-10") +#define TZ_Pacific_Honolulu ("HST10") +#define TZ_Pacific_Kiritimati ("<+14>-14") +#define TZ_Pacific_Kosrae ("<+11>-11") +#define TZ_Pacific_Kwajalein ("<+12>-12") +#define TZ_Pacific_Majuro ("<+12>-12") +#define TZ_Pacific_Marquesas ("<-0930>9:30") +#define TZ_Pacific_Midway ("SST11") +#define TZ_Pacific_Nauru ("<+12>-12") +#define TZ_Pacific_Niue ("<-11>11") +#define TZ_Pacific_Norfolk ("<+11>-11<+12>,M10.1.0,M4.1.0/3") +#define TZ_Pacific_Noumea ("<+11>-11") +#define TZ_Pacific_Pago_Pago ("SST11") +#define TZ_Pacific_Palau ("<+09>-9") +#define TZ_Pacific_Pitcairn ("<-08>8") +#define TZ_Pacific_Pohnpei ("<+11>-11") +#define TZ_Pacific_Port_Moresby ("<+10>-10") +#define TZ_Pacific_Rarotonga ("<-10>10") +#define TZ_Pacific_Saipan ("ChST-10") +#define TZ_Pacific_Tahiti ("<-10>10") +#define TZ_Pacific_Tarawa ("<+12>-12") +#define TZ_Pacific_Tongatapu ("<+13>-13") +#define TZ_Pacific_Wake ("<+12>-12") +#define TZ_Pacific_Wallis ("<+12>-12") +#define TZ_Etc_GMT ("GMT0") +#define TZ_Etc_GMTm0 ("GMT0") +#define TZ_Etc_GMTm1 ("<+01>-1") +#define TZ_Etc_GMTm2 ("<+02>-2") +#define TZ_Etc_GMTm3 ("<+03>-3") +#define TZ_Etc_GMTm4 ("<+04>-4") +#define TZ_Etc_GMTm5 ("<+05>-5") +#define TZ_Etc_GMTm6 ("<+06>-6") +#define TZ_Etc_GMTm7 ("<+07>-7") +#define TZ_Etc_GMTm8 ("<+08>-8") +#define TZ_Etc_GMTm9 ("<+09>-9") +#define TZ_Etc_GMTm10 ("<+10>-10") +#define TZ_Etc_GMTm11 ("<+11>-11") +#define TZ_Etc_GMTm12 ("<+12>-12") +#define TZ_Etc_GMTm13 ("<+13>-13") +#define TZ_Etc_GMTm14 ("<+14>-14") +#define TZ_Etc_GMT0 ("GMT0") +#define TZ_Etc_GMTp0 ("GMT0") +#define TZ_Etc_GMTp1 ("<-01>1") +#define TZ_Etc_GMTp2 ("<-02>2") +#define TZ_Etc_GMTp3 ("<-03>3") +#define TZ_Etc_GMTp4 ("<-04>4") +#define TZ_Etc_GMTp5 ("<-05>5") +#define TZ_Etc_GMTp6 ("<-06>6") +#define TZ_Etc_GMTp7 ("<-07>7") +#define TZ_Etc_GMTp8 ("<-08>8") +#define TZ_Etc_GMTp9 ("<-09>9") +#define TZ_Etc_GMTp10 ("<-10>10") +#define TZ_Etc_GMTp11 ("<-11>11") +#define TZ_Etc_GMTp12 ("<-12>12") +#define TZ_Etc_UCT ("UTC0") +#define TZ_Etc_UTC ("UTC0") +#define TZ_Etc_Greenwich ("GMT0") +#define TZ_Etc_Universal ("UTC0") +#define TZ_Etc_Zulu ("UTC0") + +//////////////////////////////////////////////////////////// + +#define TIMEZONE_MAX_LEN 50 + +static const char TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ = +{ +#if USING_AFRICA + "Africa/Abidjan", //PSTR("GMT0") + "Africa/Accra", //PSTR("GMT0") + "Africa/Addis_Ababa", //PSTR("EAT-3") + "Africa/Algiers", //PSTR("CET-1") + "Africa/Asmara", //PSTR("EAT-3") + "Africa/Bamako", //PSTR("GMT0") + "Africa/Bangui", //PSTR("WAT-1") + "Africa/Banjul", //PSTR("GMT0") + "Africa/Bissau", //PSTR("GMT0") + "Africa/Blantyre", //PSTR("CAT-2") + "Africa/Brazzaville", //PSTR("WAT-1") + "Africa/Bujumbura", //PSTR("CAT-2") + "Africa/Cairo", //PSTR("EET-2") + "Africa/Casablanca", //PSTR("<+01>-1") + "Africa/Ceuta", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Africa/Conakry", //PSTR("GMT0") + "Africa/Dakar", //PSTR("GMT0") + "Africa/Dar_es_Salaam", //PSTR("EAT-3") + "Africa/Djibouti", //PSTR("EAT-3") + "Africa/Douala", //PSTR("WAT-1") + "Africa/El_Aaiun", //PSTR("<+01>-1") + "Africa/Freetown", //PSTR("GMT0") + "Africa/Gaborone", //PSTR("CAT-2") + "Africa/Harare", //PSTR("CAT-2") + "Africa/Johannesburg", //PSTR("SAST-2") + "Africa/Juba", //PSTR("EAT-3") + "Africa/Kampala", //PSTR("EAT-3") + "Africa/Khartoum", //PSTR("CAT-2") + "Africa/Kigali", //PSTR("CAT-2") + "Africa/Kinshasa", //PSTR("WAT-1") + "Africa/Lagos", //PSTR("WAT-1") + "Africa/Libreville", //PSTR("WAT-1") + "Africa/Lome", //PSTR("GMT0") + "Africa/Luanda", //PSTR("WAT-1") + "Africa/Lubumbashi", //PSTR("CAT-2") + "Africa/Lusaka", //PSTR("CAT-2") + "Africa/Malabo", //PSTR("WAT-1") + "Africa/Maputo", //PSTR("CAT-2") + "Africa/Maseru", //PSTR("SAST-2") + "Africa/Mbabane", //PSTR("SAST-2") + "Africa/Mogadishu", //PSTR("EAT-3") + "Africa/Monrovia", //PSTR("GMT0") + "Africa/Nairobi", //PSTR("EAT-3") + "Africa/Ndjamena", //PSTR("WAT-1") + "Africa/Niamey", //PSTR("WAT-1") + "Africa/Nouakchott", //PSTR("GMT0") + "Africa/Ouagadougou", //PSTR("GMT0") + "Africa/PortomNovo", //PSTR("WAT-1") + "Africa/Sao_Tome", //PSTR("GMT0") + "Africa/Tripoli", //PSTR("EET-2") + "Africa/Tunis", //PSTR("CET-1") + "Africa/Windhoek", //PSTR("CAT-2") +#endif + + +#if USING_AMERICA + "America/Adak", //PSTR("HST10HDT",M3.2.0",M11.1.0") + "America/Anchorage", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/Anguilla", //PSTR("AST4") + "America/Antigua", //PSTR("AST4") + "America/Araguaina", //PSTR("<-03>3") + "America/Argentina/Buenos_Aires", //PSTR("<-03>3") + "America/Argentina/Catamarca", //PSTR("<-03>3") + "America/Argentina/Cordoba", //PSTR("<-03>3") + "America/Argentina/Jujuy", //PSTR("<-03>3") + "America/Argentina/La_Rioja", //PSTR("<-03>3") + "America/Argentina/Mendoza", //PSTR("<-03>3") + "America/Argentina/Rio_Gallegos", //PSTR("<-03>3") + "America/Argentina/Salta", //PSTR("<-03>3") + "America/Argentina/San_Juan", //PSTR("<-03>3") + "America/Argentina/San_Luis", //PSTR("<-03>3") + "America/Argentina/Tucuman", //PSTR("<-03>3") + "America/Argentina/Ushuaia", //PSTR("<-03>3") + "America/Aruba", //PSTR("AST4") + "America/Asuncion", //PSTR("<-04>4<-03>",M10.1.0/0",M3.4.0/0") + "America/Atikokan", //PSTR("EST5") + "America/Bahia", //PSTR("<-03>3") + "America/Bahia_Banderas", //PSTR("CST6CDT",M4.1.0",M10.5.0") + "America/Barbados", //PSTR("AST4") + "America/Belem", //PSTR("<-03>3") + "America/Belize", //PSTR("CST6") + "America/BlancmSablon", //PSTR("AST4") + "America/Boa_Vista", //PSTR("<-04>4") + "America/Bogota", //PSTR("<-05>5") + "America/Boise", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Cambridge_Bay", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Campo_Grande", //PSTR("<-04>4") + "America/Cancun", //PSTR("EST5") + "America/Caracas", //PSTR("<-04>4") + "America/Cayenne", //PSTR("<-03>3") + "America/Cayman", //PSTR("EST5") + "America/Chicago", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Chihuahua", //PSTR("MST7MDT",M4.1.0",M10.5.0") + "America/Costa_Rica", //PSTR("CST6") + "America/Creston", //PSTR("MST7") + "America/Cuiaba", //PSTR("<-04>4") + "America/Curacao", //PSTR("AST4") + "America/Danmarkshavn", //PSTR("GMT0") + "America/Dawson", //PSTR("MST7") + "America/Dawson_Creek", //PSTR("MST7") + "America/Denver", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Detroit", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Dominica", //PSTR("AST4") + "America/Edmonton", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Eirunepe", //PSTR("<-05>5") + "America/El_Salvador", //PSTR("CST6") + "America/Fortaleza", //PSTR("<-03>3") + "America/Fort_Nelson", //PSTR("MST7") + "America/Glace_Bay", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "America/Godthab", //PSTR("<-03>3<-02>",M3.5.0/-2",M10.5.0/-1") + "America/Goose_Bay", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "America/Grand_Turk", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Grenada", //PSTR("AST4") + "America/Guadeloupe", //PSTR("AST4") + "America/Guatemala", //PSTR("CST6") + "America/Guayaquil", //PSTR("<-05>5") + "America/Guyana", //PSTR("<-04>4") + "America/Halifax", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "America/Havana", //PSTR("CST5CDT",M3.2.0/0",M11.1.0/1") + "America/Hermosillo", //PSTR("MST7") + "America/Indiana_Indianapolis", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Indiana_Knox", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Indiana_Marengo", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Indiana_Petersburg", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Indiana_Tell_City", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Indiana_Vevay", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Indiana_Vincennes", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Indiana_Winamac", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Inuvik", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Iqaluit", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Jamaica", //PSTR("EST5") + "America/Juneau", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/Kentucky_Louisville", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Kentucky_Monticello", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Kralendijk", //PSTR("AST4") + "America/La_Paz", //PSTR("<-04>4") + "America/Lima", //PSTR("<-05>5") + "America/Los_Angeles", //PSTR("PST8PDT",M3.2.0",M11.1.0") + "America/Lower_Princes", //PSTR("AST4") + "America/Maceio", //PSTR("<-03>3") + "America/Managua", //PSTR("CST6") + "America/Manaus", //PSTR("<-04>4") + "America/Marigot", //PSTR("AST4") + "America/Martinique", //PSTR("AST4") + "America/Matamoros", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Mazatlan", //PSTR("MST7MDT",M4.1.0",M10.5.0") + "America/Menominee", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Merida", //PSTR("CST6CDT",M4.1.0",M10.5.0") + "America/Metlakatla", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/Mexico_City", //PSTR("CST6CDT",M4.1.0",M10.5.0") + "America/Miquelon", //PSTR("<-03>3<-02>",M3.2.0",M11.1.0") + "America/Moncton", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "America/Monterrey", //PSTR("CST6CDT",M4.1.0",M10.5.0") + "America/Montevideo", //PSTR("<-03>3") + "America/Montreal", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Montserrat", //PSTR("AST4") + "America/Nassau", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/New_York", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Nipigon", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Nome", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/Noronha", //PSTR("<-02>2") + "America/North_Dakota_Beulah", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/North_Dakota_Center", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/North_Dakota_New_Salem", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Ojinaga", //PSTR("MST7MDT",M3.2.0",M11.1.0") + "America/Panama", //PSTR("EST5") + "America/Pangnirtung", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Paramaribo", //PSTR("<-03>3") + "America/Phoenix", //PSTR("MST7") + "America/PortmaumPrince", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Port_of_Spain", //PSTR("AST4") + "America/Porto_Velho", //PSTR("<-04>4") + "America/Puerto_Rico", //PSTR("AST4") + "America/Punta_Arenas", //PSTR("<-03>3") + "America/Rainy_River", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Rankin_Inlet", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Recife", //PSTR("<-03>3") + "America/Regina", //PSTR("CST6") + "America/Resolute", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Rio_Branco", //PSTR("<-05>5") + "America/Santarem", //PSTR("<-03>3") + "America/Santiago", //PSTR("<-04>4<-03>",M9.1.6/24",M4.1.6/24") + "America/Santo_Domingo", //PSTR("AST4") + "America/Sao_Paulo", //PSTR("<-03>3") + "America/Scoresbysund", //PSTR("<-01>1<+00>",M3.5.0/0",M10.5.0/1") + "America/Sitka", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/St_Barthelemy", //PSTR("AST4") + "America/St_Johns", //PSTR("NST3:30NDT",M3.2.0",M11.1.0") + "America/St_Kitts", //PSTR("AST4") + "America/St_Lucia", //PSTR("AST4") + "America/St_Thomas", //PSTR("AST4") + "America/St_Vincent", //PSTR("AST4") + "America/Swift_Current", //PSTR("CST6") + "America/Tegucigalpa", //PSTR("CST6") + "America/Thule", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "America/Thunder_Bay", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Tijuana", //PSTR("PST8PDT",M3.2.0",M11.1.0") + "America/Toronto", //PSTR("EST5EDT",M3.2.0",M11.1.0") + "America/Tortola", //PSTR("AST4") + "America/Vancouver", //PSTR("PST8PDT",M3.2.0",M11.1.0") + "America/Whitehorse", //PSTR("MST7") + "America/Winnipeg", //PSTR("CST6CDT",M3.2.0",M11.1.0") + "America/Yakutat", //PSTR("AKST9AKDT",M3.2.0",M11.1.0") + "America/Yellowknife", //PSTR("MST7MDT",M3.2.0",M11.1.0") +#endif + +#if USING_ANTARCTICA + "Antarctica/Casey", //PSTR("<+11>-11") + "Antarctica/Davis", //PSTR("<+07>-7") + "Antarctica/DumontDUrville", //PSTR("<+10>-10") + "Antarctica/Macquarie", //PSTR("AEST-10AEDT",M10.1.0",M4.1.0/3") + "Antarctica/Mawson", //PSTR("<+05>-5") + "Antarctica/McMurdo", //PSTR("NZST-12NZDT",M9.5.0",M4.1.0/3") + "Antarctica/Palmer", //PSTR("<-03>3") + "Antarctica/Rothera", //PSTR("<-03>3") + "Antarctica/Syowa", //PSTR("<+03>-3") + "Antarctica/Troll", //PSTR("<+00>0<+02>-2",M3.5.0/1",M10.5.0/3") + "Antarctica/Vostok", //PSTR("<+06>-6") + "Arctic/Longyearbyen", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") +#endif + +#if USING_ASIA + "Asia/Aden", //PSTR("<+03>-3") + "Asia/Almaty", //PSTR("<+06>-6") + "Asia/Amman", //PSTR("EET-2EEST",M3.5.4/24",M10.5.5/1") + "Asia/Anadyr", //PSTR("<+12>-12") + "Asia/Aqtau", //PSTR("<+05>-5") + "Asia/Aqtobe", //PSTR("<+05>-5") + "Asia/Ashgabat", //PSTR("<+05>-5") + "Asia/Atyrau", //PSTR("<+05>-5") + "Asia/Baghdad", //PSTR("<+03>-3") + "Asia/Bahrain", //PSTR("<+03>-3") + "Asia/Baku", //PSTR("<+04>-4") + "Asia/Bangkok", //PSTR("<+07>-7") + "Asia/Barnaul", //PSTR("<+07>-7") + "Asia/Beirut", //PSTR("EET-2EEST",M3.5.0/0",M10.5.0/0") + "Asia/Bishkek", //PSTR("<+06>-6") + "Asia/Brunei", //PSTR("<+08>-8") + "Asia/Chita", //PSTR("<+09>-9") + "Asia/Choibalsan", //PSTR("<+08>-8") + "Asia/Colombo", //PSTR("<+0530>-5:30") + "Asia/Damascus", //PSTR("EET-2EEST",M3.5.5/0",M10.5.5/0") + "Asia/Dhaka", //PSTR("<+06>-6") + "Asia/Dili", //PSTR("<+09>-9") + "Asia/Dubai", //PSTR("<+04>-4") + "Asia/Dushanbe", //PSTR("<+05>-5") + "Asia/Famagusta", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Asia/Gaza", //PSTR("EET-2EEST",M3.4.4/48",M10.4.4/49") + "Asia/Hebron", //PSTR("EET-2EEST",M3.4.4/48",M10.4.4/49") + "Asia/Ho_Chi_Minh", //PSTR("<+07>-7") + "Asia/Hong_Kong", //PSTR("HKT-8") + "Asia/Hovd", //PSTR("<+07>-7") + "Asia/Irkutsk", //PSTR("<+08>-8") + "Asia/Jakarta", //PSTR("WIB-7") + "Asia/Jayapura", //PSTR("WIT-9") + "Asia/Jerusalem", //PSTR("IST-2IDT",M3.4.4/26",M10.5.0") + "Asia/Kabul", //PSTR("<+0430>-4:30") + "Asia/Kamchatka", //PSTR("<+12>-12") + "Asia/Karachi", //PSTR("PKT-5") + "Asia/Kathmandu", //PSTR("<+0545>-5:45") + "Asia/Khandyga", //PSTR("<+09>-9") + "Asia/Kolkata", //PSTR("IST-5:30") + "Asia/Krasnoyarsk", //PSTR("<+07>-7") + "Asia/Kuala_Lumpur", //PSTR("<+08>-8") + "Asia/Kuching", //PSTR("<+08>-8") + "Asia/Kuwait", //PSTR("<+03>-3") + "Asia/Macau", //PSTR("CST-8") + "Asia/Magadan", //PSTR("<+11>-11") + "Asia/Makassar", //PSTR("WITA-8") + "Asia/Manila", //PSTR("PST-8") + "Asia/Muscat", //PSTR("<+04>-4") + "Asia/Nicosia", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Asia/Novokuznetsk", //PSTR("<+07>-7") + "Asia/Novosibirsk", //PSTR("<+07>-7") + "Asia/Omsk", //PSTR("<+06>-6") + "Asia/Oral", //PSTR("<+05>-5") + "Asia/Phnom_Penh", //PSTR("<+07>-7") + "Asia/Pontianak", //PSTR("WIB-7") + "Asia/Pyongyang", //PSTR("KST-9") + "Asia/Qatar", //PSTR("<+03>-3") + "Asia/Qyzylorda", //PSTR("<+05>-5") + "Asia/Riyadh", //PSTR("<+03>-3") + "Asia/Sakhalin", //PSTR("<+11>-11") + "Asia/Samarkand", //PSTR("<+05>-5") + "Asia/Seoul", //PSTR("KST-9") + "Asia/Shanghai", //PSTR("CST-8") + "Asia/Singapore", //PSTR("<+08>-8") + "Asia/Srednekolymsk", //PSTR("<+11>-11") + "Asia/Taipei", //PSTR("CST-8") + "Asia/Tashkent", //PSTR("<+05>-5") + "Asia/Tbilisi", //PSTR("<+04>-4") + "Asia/Tehran", //PSTR("<+0330>-3:30<+0430>",J79/24",J263/24") + "Asia/Thimphu", //PSTR("<+06>-6") + "Asia/Tokyo", //PSTR("JST-9") + "Asia/Tomsk", //PSTR("<+07>-7") + "Asia/Ulaanbaatar", //PSTR("<+08>-8") + "Asia/Urumqi", //PSTR("<+06>-6") + "Asia/UstmNera", //PSTR("<+10>-10") + "Asia/Vientiane", //PSTR("<+07>-7") + "Asia/Vladivostok", //PSTR("<+10>-10") + "Asia/Yakutsk", //PSTR("<+09>-9") + "Asia/Yangon", //PSTR("<+0630>-6:30") + "Asia/Yekaterinburg", //PSTR("<+05>-5") + "Asia/Yerevan", //PSTR("<+04>-4") +#endif + +#if USING_ATLANTIC + "Atlantic/Azores", //PSTR("<-01>1<+00>",M3.5.0/0",M10.5.0/1") + "Atlantic/Bermuda", //PSTR("AST4ADT",M3.2.0",M11.1.0") + "Atlantic/Canary", //PSTR("WET0WEST",M3.5.0/1",M10.5.0") + "Atlantic/Cape_Verde", //PSTR("<-01>1") + "Atlantic/Faroe", //PSTR("WET0WEST",M3.5.0/1",M10.5.0") + "Atlantic/Madeira", //PSTR("WET0WEST",M3.5.0/1",M10.5.0") + "Atlantic/Reykjavik", //PSTR("GMT0") + "Atlantic/South_Georgia", //PSTR("<-02>2") + "Atlantic/Stanley", //PSTR("<-03>3") + "Atlantic/St_Helena", //PSTR("GMT0") +#endif + +#if USING_AUSTRALIA + "Australia/Adelaide", //PSTR("ACST-9:30ACDT",M10.1.0",M4.1.0/3") + "Australia/Brisbane", //PSTR("AEST-10") + "Australia/Broken_Hill", //PSTR("ACST-9:30ACDT",M10.1.0",M4.1.0/3") + "Australia/Currie", //PSTR("AEST-10AEDT",M10.1.0",M4.1.0/3") + "Australia/Darwin", //PSTR("ACST-9:30") + "Australia/Eucla", //PSTR("<+0845>-8:45") + "Australia/Hobart", //PSTR("AEST-10AEDT",M10.1.0",M4.1.0/3") + "Australia/Lindeman", //PSTR("AEST-10") + "Australia/Lord_Howe", //PSTR("<+1030>-10:30<+11>-11",M10.1.0",M4.1.0") + "Australia/Melbourne", //PSTR("AEST-10AEDT",M10.1.0",M4.1.0/3") + "Australia/Perth", //PSTR("AWST-8") + "Australia/Sydney", //PSTR("AEST-10AEDT",M10.1.0",M4.1.0/3") +#endif + +#if USING_EUROPE + "Europe/Amsterdam", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Andorra", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Astrakhan", //PSTR("<+04>-4") + "Europe/Athens", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Belgrade", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Berlin", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Bratislava", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Brussels", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Bucharest", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Budapest", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Busingen", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Chisinau", //PSTR("EET-2EEST",M3.5.0",M10.5.0/3") + "Europe/Copenhagen", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Dublin", //PSTR("IST-1GMT0",M10.5.0",M3.5.0/1") + "Europe/Gibraltar", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Guernsey", //PSTR("GMT0BST",M3.5.0/1",M10.5.0") + "Europe/Helsinki", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Isle_of_Man", //PSTR("GMT0BST",M3.5.0/1",M10.5.0") + "Europe/Istanbul", //PSTR("<+03>-3") + "Europe/Jersey", //PSTR("GMT0BST",M3.5.0/1",M10.5.0") + "Europe/Kaliningrad", //PSTR("EET-2") + "Europe/Kiev", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Kirov", //PSTR("<+03>-3") + "Europe/Lisbon", //PSTR("WET0WEST",M3.5.0/1",M10.5.0") + "Europe/Ljubljana", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/London", //PSTR("GMT0BST",M3.5.0/1",M10.5.0") + "Europe/Luxembourg", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Madrid", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Malta", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Mariehamn", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Minsk", //PSTR("<+03>-3") + "Europe/Monaco", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Moscow", //PSTR("MSK-3") + "Europe/Oslo", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Paris", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Podgorica", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Prague", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Riga", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Rome", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Samara", //PSTR("<+04>-4") + "Europe/San_Marino", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Sarajevo", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Saratov", //PSTR("<+04>-4") + "Europe/Simferopol", //PSTR("MSK-3") + "Europe/Skopje", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Sofia", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Stockholm", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Tallinn", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Tirane", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Ulyanovsk", //PSTR("<+04>-4") + "Europe/Uzhgorod", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Vaduz", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Vatican", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Vienna", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Vilnius", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Volgograd", //PSTR("<+04>-4") + "Europe/Warsaw", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Zagreb", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") + "Europe/Zaporozhye", //PSTR("EET-2EEST",M3.5.0/3",M10.5.0/4") + "Europe/Zurich", //PSTR("CET-1CEST",M3.5.0",M10.5.0/3") +#endif + +#if USING_INDIAN + "Indian/Antananarivo", //PSTR("EAT-3") + "Indian/Chagos", //PSTR("<+06>-6") + "Indian/Christmas", //PSTR("<+07>-7") + "Indian/Cocos", //PSTR("<+0630>-6:30") + "Indian/Comoro", //PSTR("EAT-3") + "Indian/Kerguelen", //PSTR("<+05>-5") + "Indian/Mahe", //PSTR("<+04>-4") + "Indian/Maldives", //PSTR("<+05>-5") + "Indian/Mauritius", //PSTR("<+04>-4") + "Indian/Mayotte", //PSTR("EAT-3") + "Indian/Reunion", //PSTR("<+04>-4") +#endif + +#if USING_PACIFIC + "Pacific/Apia", //PSTR("<+13>-13<+14>",M9.5.0/3",M4.1.0/4") + "Pacific/Auckland", //PSTR("NZST-12NZDT",M9.5.0",M4.1.0/3") + "Pacific/Bougainville", //PSTR("<+11>-11") + "Pacific/Chatham", //PSTR("<+1245>-12:45<+1345>",M9.5.0/2:45",M4.1.0/3:45") + "Pacific/Chuuk", //PSTR("<+10>-10") + "Pacific/Easter", //PSTR("<-06>6<-05>",M9.1.6/22",M4.1.6/22") + "Pacific/Efate", //PSTR("<+11>-11") + "Pacific/Enderbury", //PSTR("<+13>-13") + "Pacific/Fakaofo", //PSTR("<+13>-13") + "Pacific/Fiji", //PSTR("<+12>-12<+13>",M11.2.0",M1.2.3/99") + "Pacific/Funafuti", //PSTR("<+12>-12") + "Pacific/Galapagos", //PSTR("<-06>6") + "Pacific/Gambier", //PSTR("<-09>9") + "Pacific/Guadalcanal", //PSTR("<+11>-11") + "Pacific/Guam", //PSTR("ChST-10") + "Pacific/Honolulu", //PSTR("HST10") + "Pacific/Kiritimati", //PSTR("<+14>-14") + "Pacific/Kosrae", //PSTR("<+11>-11") + "Pacific/Kwajalein", //PSTR("<+12>-12") + "Pacific/Majuro", //PSTR("<+12>-12") + "Pacific/Marquesas", //PSTR("<-0930>9:30") + "Pacific/Midway", //PSTR("SST11") + "Pacific/Nauru", //PSTR("<+12>-12") + "Pacific/Niue", //PSTR("<-11>11") + "Pacific/Norfolk", //PSTR("<+11>-11<+12>",M10.1.0",M4.1.0/3") + "Pacific/Noumea", //PSTR("<+11>-11") + "Pacific/Pago_Pago", //PSTR("SST11") + "Pacific/Palau", //PSTR("<+09>-9") + "Pacific/Pitcairn", //PSTR("<-08>8") + "Pacific/Pohnpei", //PSTR("<+11>-11") + "Pacific/Port_Moresby", //PSTR("<+10>-10") + "Pacific/Rarotonga", //PSTR("<-10>10") + "Pacific/Saipan", //PSTR("ChST-10") + "Pacific/Tahiti", //PSTR("<-10>10") + "Pacific/Tarawa", //PSTR("<+12>-12") + "Pacific/Tongatapu", //PSTR("<+13>-13") + "Pacific/Wake", //PSTR("<+12>-12") + "Pacific/Wallis", //PSTR("<+12>-12") +#endif + +#if USING_ETC_GMT + "Etc/GMT", //PSTR("GMT0") + "Etc/GMTm0", //PSTR("GMT0") + "Etc/GMTm1", //PSTR("<+01>-1") + "Etc/GMTm2", //PSTR("<+02>-2") + "Etc/GMTm3", //PSTR("<+03>-3") + "Etc/GMTm4", //PSTR("<+04>-4") + "Etc/GMTm5", //PSTR("<+05>-5") + "Etc/GMTm6", //PSTR("<+06>-6") + "Etc/GMTm7", //PSTR("<+07>-7") + "Etc/GMTm8", //PSTR("<+08>-8") + "Etc/GMTm9", //PSTR("<+09>-9") + "Etc/GMTm10", //PSTR("<+10>-10") + "Etc/GMTm11", //PSTR("<+11>-11") + "Etc/GMTm12", //PSTR("<+12>-12") + "Etc/GMTm13", //PSTR("<+13>-13") + "Etc/GMTm14", //PSTR("<+14>-14") + "Etc/GMT0", //PSTR("GMT0") + "Etc/GMTp0", //PSTR("GMT0") + "Etc/GMTp1", //PSTR("<-01>1") + "Etc/GMTp2", //PSTR("<-02>2") + "Etc/GMTp3", //PSTR("<-03>3") + "Etc/GMTp4", //PSTR("<-04>4") + "Etc/GMTp5", //PSTR("<-05>5") + "Etc/GMTp6", //PSTR("<-06>6") + "Etc/GMTp7", //PSTR("<-07>7") + "Etc/GMTp8", //PSTR("<-08>8") + "Etc/GMTp9", //PSTR("<-09>9") + "Etc/GMTp10", //PSTR("<-10>10") + "Etc/GMTp11", //PSTR("<-11>11") + "Etc/GMTp12", //PSTR("<-12>12") + "Etc/UCT", //PSTR("UTC0") + "Etc/UTC", //PSTR("UTC0") + "Etc/Greenwich", //PSTR("GMT0") + "Etc/Universal", //PSTR("UTC0") + "Etc/Zulu", //PSTR("UTC0") +#endif +}; + +//////////////////////////////////////////////////////////// + +static const char ESP_TZ_NAME[][TIMEZONE_MAX_LEN] /*PROGMEM*/ = +{ +#if USING_AFRICA + TZ_Africa_Abidjan, //PSTR("GMT0") + TZ_Africa_Accra, //PSTR("GMT0") + TZ_Africa_Addis_Ababa, //PSTR("EAT-3") + TZ_Africa_Algiers, //PSTR("CET-1") + TZ_Africa_Asmara, //PSTR("EAT-3") + TZ_Africa_Bamako, //PSTR("GMT0") + TZ_Africa_Bangui, //PSTR("WAT-1") + TZ_Africa_Banjul, //PSTR("GMT0") + TZ_Africa_Bissau, //PSTR("GMT0") + TZ_Africa_Blantyre, //PSTR("CAT-2") + TZ_Africa_Brazzaville, //PSTR("WAT-1") + TZ_Africa_Bujumbura, //PSTR("CAT-2") + TZ_Africa_Cairo, //PSTR("EET-2") + TZ_Africa_Casablanca, //PSTR("<+01>-1") + TZ_Africa_Ceuta, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Africa_Conakry, //PSTR("GMT0") + TZ_Africa_Dakar, //PSTR("GMT0") + TZ_Africa_Dar_es_Salaam, //PSTR("EAT-3") + TZ_Africa_Djibouti, //PSTR("EAT-3") + TZ_Africa_Douala, //PSTR("WAT-1") + TZ_Africa_El_Aaiun, //PSTR("<+01>-1") + TZ_Africa_Freetown, //PSTR("GMT0") + TZ_Africa_Gaborone, //PSTR("CAT-2") + TZ_Africa_Harare, //PSTR("CAT-2") + TZ_Africa_Johannesburg, //PSTR("SAST-2") + TZ_Africa_Juba, //PSTR("EAT-3") + TZ_Africa_Kampala, //PSTR("EAT-3") + TZ_Africa_Khartoum, //PSTR("CAT-2") + TZ_Africa_Kigali, //PSTR("CAT-2") + TZ_Africa_Kinshasa, //PSTR("WAT-1") + TZ_Africa_Lagos, //PSTR("WAT-1") + TZ_Africa_Libreville, //PSTR("WAT-1") + TZ_Africa_Lome, //PSTR("GMT0") + TZ_Africa_Luanda, //PSTR("WAT-1") + TZ_Africa_Lubumbashi, //PSTR("CAT-2") + TZ_Africa_Lusaka, //PSTR("CAT-2") + TZ_Africa_Malabo, //PSTR("WAT-1") + TZ_Africa_Maputo, //PSTR("CAT-2") + TZ_Africa_Maseru, //PSTR("SAST-2") + TZ_Africa_Mbabane, //PSTR("SAST-2") + TZ_Africa_Mogadishu, //PSTR("EAT-3") + TZ_Africa_Monrovia, //PSTR("GMT0") + TZ_Africa_Nairobi, //PSTR("EAT-3") + TZ_Africa_Ndjamena, //PSTR("WAT-1") + TZ_Africa_Niamey, //PSTR("WAT-1") + TZ_Africa_Nouakchott, //PSTR("GMT0") + TZ_Africa_Ouagadougou, //PSTR("GMT0") + TZ_Africa_PortomNovo, //PSTR("WAT-1") + TZ_Africa_Sao_Tome, //PSTR("GMT0") + TZ_Africa_Tripoli, //PSTR("EET-2") + TZ_Africa_Tunis, //PSTR("CET-1") + TZ_Africa_Windhoek, //PSTR("CAT-2") +#endif + +#if USING_AMERICA + TZ_America_Adak, //PSTR("HST10HDT,M3.2.0,M11.1.0") + TZ_America_Anchorage, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_Anguilla, //PSTR("AST4") + TZ_America_Antigua, //PSTR("AST4") + TZ_America_Araguaina, //PSTR("<-03>3") + TZ_America_Argentina_Buenos_Aires, //PSTR("<-03>3") + TZ_America_Argentina_Catamarca, //PSTR("<-03>3") + TZ_America_Argentina_Cordoba, //PSTR("<-03>3") + TZ_America_Argentina_Jujuy, //PSTR("<-03>3") + TZ_America_Argentina_La_Rioja, //PSTR("<-03>3") + TZ_America_Argentina_Mendoza, //PSTR("<-03>3") + TZ_America_Argentina_Rio_Gallegos, //PSTR("<-03>3") + TZ_America_Argentina_Salta, //PSTR("<-03>3") + TZ_America_Argentina_San_Juan, //PSTR("<-03>3") + TZ_America_Argentina_San_Luis, //PSTR("<-03>3") + TZ_America_Argentina_Tucuman, //PSTR("<-03>3") + TZ_America_Argentina_Ushuaia, //PSTR("<-03>3") + TZ_America_Aruba, //PSTR("AST4") + TZ_America_Asuncion, //PSTR("<-04>4<-03>,M10.1.0/0,M3.4.0/0") + TZ_America_Atikokan, //PSTR("EST5") + TZ_America_Bahia, //PSTR("<-03>3") + TZ_America_Bahia_Banderas, //PSTR("CST6CDT,M4.1.0,M10.5.0") + TZ_America_Barbados, //PSTR("AST4") + TZ_America_Belem, //PSTR("<-03>3") + TZ_America_Belize, //PSTR("CST6") + TZ_America_BlancmSablon, //PSTR("AST4") + TZ_America_Boa_Vista, //PSTR("<-04>4") + TZ_America_Bogota, //PSTR("<-05>5") + TZ_America_Boise, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Cambridge_Bay, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Campo_Grande, //PSTR("<-04>4") + TZ_America_Cancun, //PSTR("EST5") + TZ_America_Caracas, //PSTR("<-04>4") + TZ_America_Cayenne, //PSTR("<-03>3") + TZ_America_Cayman, //PSTR("EST5") + TZ_America_Chicago, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Chihuahua, //PSTR("MST7MDT,M4.1.0,M10.5.0") + TZ_America_Costa_Rica, //PSTR("CST6") + TZ_America_Creston, //PSTR("MST7") + TZ_America_Cuiaba, //PSTR("<-04>4") + TZ_America_Curacao, //PSTR("AST4") + TZ_America_Danmarkshavn, //PSTR("GMT0") + TZ_America_Dawson, //PSTR("MST7") + TZ_America_Dawson_Creek, //PSTR("MST7") + TZ_America_Denver, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Detroit, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Dominica, //PSTR("AST4") + TZ_America_Edmonton, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Eirunepe, //PSTR("<-05>5") + TZ_America_El_Salvador, //PSTR("CST6") + TZ_America_Fortaleza, //PSTR("<-03>3") + TZ_America_Fort_Nelson, //PSTR("MST7") + TZ_America_Glace_Bay, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_America_Godthab, //PSTR("<-03>3<-02>,M3.5.0/-2,M10.5.0/-1") + TZ_America_Goose_Bay, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_America_Grand_Turk, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Grenada, //PSTR("AST4") + TZ_America_Guadeloupe, //PSTR("AST4") + TZ_America_Guatemala, //PSTR("CST6") + TZ_America_Guayaquil, //PSTR("<-05>5") + TZ_America_Guyana, //PSTR("<-04>4") + TZ_America_Halifax, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_America_Havana, //PSTR("CST5CDT,M3.2.0/0,M11.1.0/1") + TZ_America_Hermosillo, //PSTR("MST7") + TZ_America_Indiana_Indianapolis, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Knox, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Marengo, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Petersburg, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Tell_City, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Vevay, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Vincennes, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Indiana_Winamac, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Inuvik, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Iqaluit, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Jamaica, //PSTR("EST5") + TZ_America_Juneau, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_Kentucky_Louisville, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Kentucky_Monticello, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Kralendijk, //PSTR("AST4") + TZ_America_La_Paz, //PSTR("<-04>4") + TZ_America_Lima, //PSTR("<-05>5") + TZ_America_Los_Angeles, //PSTR("PST8PDT,M3.2.0,M11.1.0") + TZ_America_Lower_Princes, //PSTR("AST4") + TZ_America_Maceio, //PSTR("<-03>3") + TZ_America_Managua, //PSTR("CST6") + TZ_America_Manaus, //PSTR("<-04>4") + TZ_America_Marigot, //PSTR("AST4") + TZ_America_Martinique, //PSTR("AST4") + TZ_America_Matamoros, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Mazatlan, //PSTR("MST7MDT,M4.1.0,M10.5.0") + TZ_America_Menominee, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Merida, //PSTR("CST6CDT,M4.1.0,M10.5.0") + TZ_America_Metlakatla, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_Mexico_City, //PSTR("CST6CDT,M4.1.0,M10.5.0") + TZ_America_Miquelon, //PSTR("<-03>3<-02>,M3.2.0,M11.1.0") + TZ_America_Moncton, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_America_Monterrey, //PSTR("CST6CDT,M4.1.0,M10.5.0") + TZ_America_Montevideo, //PSTR("<-03>3") + TZ_America_Montreal, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Montserrat, //PSTR("AST4") + TZ_America_Nassau, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_New_York, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Nipigon, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Nome, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_Noronha, //PSTR("<-02>2") + TZ_America_North_Dakota_Beulah, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_North_Dakota_Center, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_North_Dakota_New_Salem, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Ojinaga, //PSTR("MST7MDT,M3.2.0,M11.1.0") + TZ_America_Panama, //PSTR("EST5") + TZ_America_Pangnirtung, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Paramaribo, //PSTR("<-03>3") + TZ_America_Phoenix, //PSTR("MST7") + TZ_America_PortmaumPrince, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Port_of_Spain, //PSTR("AST4") + TZ_America_Porto_Velho, //PSTR("<-04>4") + TZ_America_Puerto_Rico, //PSTR("AST4") + TZ_America_Punta_Arenas, //PSTR("<-03>3") + TZ_America_Rainy_River, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Rankin_Inlet, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Recife, //PSTR("<-03>3") + TZ_America_Regina, //PSTR("CST6") + TZ_America_Resolute, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Rio_Branco, //PSTR("<-05>5") + TZ_America_Santarem, //PSTR("<-03>3") + TZ_America_Santiago, //PSTR("<-04>4<-03>,M9.1.6/24,M4.1.6/24") + TZ_America_Santo_Domingo, //PSTR("AST4") + TZ_America_Sao_Paulo, //PSTR("<-03>3") + TZ_America_Scoresbysund, //PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1") + TZ_America_Sitka, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_St_Barthelemy, //PSTR("AST4") + TZ_America_St_Johns, //PSTR("NST3:30NDT,M3.2.0,M11.1.0") + TZ_America_St_Kitts, //PSTR("AST4") + TZ_America_St_Lucia, //PSTR("AST4") + TZ_America_St_Thomas, //PSTR("AST4") + TZ_America_St_Vincent, //PSTR("AST4") + TZ_America_Swift_Current, //PSTR("CST6") + TZ_America_Tegucigalpa, //PSTR("CST6") + TZ_America_Thule, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_America_Thunder_Bay, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Tijuana, //PSTR("PST8PDT,M3.2.0,M11.1.0") + TZ_America_Toronto, //PSTR("EST5EDT,M3.2.0,M11.1.0") + TZ_America_Tortola, //PSTR("AST4") + TZ_America_Vancouver, //PSTR("PST8PDT,M3.2.0,M11.1.0") + TZ_America_Whitehorse, //PSTR("MST7") + TZ_America_Winnipeg, //PSTR("CST6CDT,M3.2.0,M11.1.0") + TZ_America_Yakutat, //PSTR("AKST9AKDT,M3.2.0,M11.1.0") + TZ_America_Yellowknife, //PSTR("MST7MDT,M3.2.0,M11.1.0") +#endif + +#if USING_ANTARCTICA + TZ_Antarctica_Casey, //PSTR("<+11>-11") + TZ_Antarctica_Davis, //PSTR("<+07>-7") + TZ_Antarctica_DumontDUrville, //PSTR("<+10>-10") + TZ_Antarctica_Macquarie, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3") + TZ_Antarctica_Mawson, //PSTR("<+05>-5") + TZ_Antarctica_McMurdo, //PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3") + TZ_Antarctica_Palmer, //PSTR("<-03>3") + TZ_Antarctica_Rothera, //PSTR("<-03>3") + TZ_Antarctica_Syowa, //PSTR("<+03>-3") + TZ_Antarctica_Troll, //PSTR("<+00>0<+02>-2,M3.5.0/1,M10.5.0/3") + TZ_Antarctica_Vostok, //PSTR("<+06>-6") + TZ_Arctic_Longyearbyen, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") +#endif + + +#if USING_ASIA + TZ_Asia_Aden, //PSTR("<+03>-3") + TZ_Asia_Almaty, //PSTR("<+06>-6") + TZ_Asia_Amman, //PSTR("EET-2EEST,M3.5.4/24,M10.5.5/1") + TZ_Asia_Anadyr, //PSTR("<+12>-12") + TZ_Asia_Aqtau, //PSTR("<+05>-5") + TZ_Asia_Aqtobe, //PSTR("<+05>-5") + TZ_Asia_Ashgabat, //PSTR("<+05>-5") + TZ_Asia_Atyrau, //PSTR("<+05>-5") + TZ_Asia_Baghdad, //PSTR("<+03>-3") + TZ_Asia_Bahrain, //PSTR("<+03>-3") + TZ_Asia_Baku, //PSTR("<+04>-4") + TZ_Asia_Bangkok, //PSTR("<+07>-7") + TZ_Asia_Barnaul, //PSTR("<+07>-7") + TZ_Asia_Beirut, //PSTR("EET-2EEST,M3.5.0/0,M10.5.0/0") + TZ_Asia_Bishkek, //PSTR("<+06>-6") + TZ_Asia_Brunei, //PSTR("<+08>-8") + TZ_Asia_Chita, //PSTR("<+09>-9") + TZ_Asia_Choibalsan, //PSTR("<+08>-8") + TZ_Asia_Colombo, //PSTR("<+0530>-5:30") + TZ_Asia_Damascus, //PSTR("EET-2EEST,M3.5.5/0,M10.5.5/0") + TZ_Asia_Dhaka, //PSTR("<+06>-6") + TZ_Asia_Dili, //PSTR("<+09>-9") + TZ_Asia_Dubai, //PSTR("<+04>-4") + TZ_Asia_Dushanbe, //PSTR("<+05>-5") + TZ_Asia_Famagusta, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Asia_Gaza, //PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49") + TZ_Asia_Hebron, //PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49") + TZ_Asia_Ho_Chi_Minh, //PSTR("<+07>-7") + TZ_Asia_Hong_Kong, //PSTR("HKT-8") + TZ_Asia_Hovd, //PSTR("<+07>-7") + TZ_Asia_Irkutsk, //PSTR("<+08>-8") + TZ_Asia_Jakarta, //PSTR("WIB-7") + TZ_Asia_Jayapura, //PSTR("WIT-9") + TZ_Asia_Jerusalem, //PSTR("IST-2IDT,M3.4.4/26,M10.5.0") + TZ_Asia_Kabul, //PSTR("<+0430>-4:30") + TZ_Asia_Kamchatka, //PSTR("<+12>-12") + TZ_Asia_Karachi, //PSTR("PKT-5") + TZ_Asia_Kathmandu, //PSTR("<+0545>-5:45") + TZ_Asia_Khandyga, //PSTR("<+09>-9") + TZ_Asia_Kolkata, //PSTR("IST-5:30") + TZ_Asia_Krasnoyarsk, //PSTR("<+07>-7") + TZ_Asia_Kuala_Lumpur, //PSTR("<+08>-8") + TZ_Asia_Kuching, //PSTR("<+08>-8") + TZ_Asia_Kuwait, //PSTR("<+03>-3") + TZ_Asia_Macau, //PSTR("CST-8") + TZ_Asia_Magadan, //PSTR("<+11>-11") + TZ_Asia_Makassar, //PSTR("WITA-8") + TZ_Asia_Manila, //PSTR("PST-8") + TZ_Asia_Muscat, //PSTR("<+04>-4") + TZ_Asia_Nicosia, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Asia_Novokuznetsk, //PSTR("<+07>-7") + TZ_Asia_Novosibirsk, //PSTR("<+07>-7") + TZ_Asia_Omsk, //PSTR("<+06>-6") + TZ_Asia_Oral, //PSTR("<+05>-5") + TZ_Asia_Phnom_Penh, //PSTR("<+07>-7") + TZ_Asia_Pontianak, //PSTR("WIB-7") + TZ_Asia_Pyongyang, //PSTR("KST-9") + TZ_Asia_Qatar, //PSTR("<+03>-3") + TZ_Asia_Qyzylorda, //PSTR("<+05>-5") + TZ_Asia_Riyadh, //PSTR("<+03>-3") + TZ_Asia_Sakhalin, //PSTR("<+11>-11") + TZ_Asia_Samarkand, //PSTR("<+05>-5") + TZ_Asia_Seoul, //PSTR("KST-9") + TZ_Asia_Shanghai, //PSTR("CST-8") + TZ_Asia_Singapore, //PSTR("<+08>-8") + TZ_Asia_Srednekolymsk, //PSTR("<+11>-11") + TZ_Asia_Taipei, //PSTR("CST-8") + TZ_Asia_Tashkent, //PSTR("<+05>-5") + TZ_Asia_Tbilisi, //PSTR("<+04>-4") + TZ_Asia_Tehran, //PSTR("<+0330>-3:30<+0430>,J79/24,J263/24") + TZ_Asia_Thimphu, //PSTR("<+06>-6") + TZ_Asia_Tokyo, //PSTR("JST-9") + TZ_Asia_Tomsk, //PSTR("<+07>-7") + TZ_Asia_Ulaanbaatar, //PSTR("<+08>-8") + TZ_Asia_Urumqi, //PSTR("<+06>-6") + TZ_Asia_UstmNera, //PSTR("<+10>-10") + TZ_Asia_Vientiane, //PSTR("<+07>-7") + TZ_Asia_Vladivostok, //PSTR("<+10>-10") + TZ_Asia_Yakutsk, //PSTR("<+09>-9") + TZ_Asia_Yangon, //PSTR("<+0630>-6:30") + TZ_Asia_Yekaterinburg, //PSTR("<+05>-5") + TZ_Asia_Yerevan, //PSTR("<+04>-4") +#endif + +#if USING_ATLANTIC + TZ_Atlantic_Azores, //PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1") + TZ_Atlantic_Bermuda, //PSTR("AST4ADT,M3.2.0,M11.1.0") + TZ_Atlantic_Canary, //PSTR("WET0WEST,M3.5.0/1,M10.5.0") + TZ_Atlantic_Cape_Verde, //PSTR("<-01>1") + TZ_Atlantic_Faroe, //PSTR("WET0WEST,M3.5.0/1,M10.5.0") + TZ_Atlantic_Madeira, //PSTR("WET0WEST,M3.5.0/1,M10.5.0") + TZ_Atlantic_Reykjavik, //PSTR("GMT0") + TZ_Atlantic_South_Georgia, //PSTR("<-02>2") + TZ_Atlantic_Stanley, //PSTR("<-03>3") + TZ_Atlantic_St_Helena, //PSTR("GMT0") +#endif + +#if USING_AUSTRALIA + TZ_Australia_Adelaide, //PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3") + TZ_Australia_Brisbane, //PSTR("AEST-10") + TZ_Australia_Broken_Hill, //PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3") + TZ_Australia_Currie, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3") + TZ_Australia_Darwin, //PSTR("ACST-9:30") + TZ_Australia_Eucla, //PSTR("<+0845>-8:45") + TZ_Australia_Hobart, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3") + TZ_Australia_Lindeman, //PSTR("AEST-10") + TZ_Australia_Lord_Howe, //PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0") + TZ_Australia_Melbourne, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3") + TZ_Australia_Perth, //PSTR("AWST-8") + TZ_Australia_Sydney, //PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3") +#endif + +#if USING_EUROPE + TZ_Europe_Amsterdam, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Andorra, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Astrakhan, //PSTR("<+04>-4") + TZ_Europe_Athens, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Belgrade, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Berlin, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Bratislava, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Brussels, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Bucharest, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Budapest, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Busingen, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Chisinau, //PSTR("EET-2EEST,M3.5.0,M10.5.0/3") + TZ_Europe_Copenhagen, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Dublin, //PSTR("IST-1GMT0,M10.5.0,M3.5.0/1") + TZ_Europe_Gibraltar, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Guernsey, //PSTR("GMT0BST,M3.5.0/1,M10.5.0") + TZ_Europe_Helsinki, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Isle_of_Man, //PSTR("GMT0BST,M3.5.0/1,M10.5.0") + TZ_Europe_Istanbul, //PSTR("<+03>-3") + TZ_Europe_Jersey, //PSTR("GMT0BST,M3.5.0/1,M10.5.0") + TZ_Europe_Kaliningrad, //PSTR("EET-2") + TZ_Europe_Kiev, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Kirov, //PSTR("<+03>-3") + TZ_Europe_Lisbon, //PSTR("WET0WEST,M3.5.0/1,M10.5.0") + TZ_Europe_Ljubljana, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_London, //PSTR("GMT0BST,M3.5.0/1,M10.5.0") + TZ_Europe_Luxembourg, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Madrid, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Malta, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Mariehamn, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Minsk, //PSTR("<+03>-3") + TZ_Europe_Monaco, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Moscow, //PSTR("MSK-3") + TZ_Europe_Oslo, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Paris, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Podgorica, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Prague, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Riga, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Rome, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Samara, //PSTR("<+04>-4") + TZ_Europe_San_Marino, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Sarajevo, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Saratov, //PSTR("<+04>-4") + TZ_Europe_Simferopol, //PSTR("MSK-3") + TZ_Europe_Skopje, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Sofia, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Stockholm, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Tallinn, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Tirane, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Ulyanovsk, //PSTR("<+04>-4") + TZ_Europe_Uzhgorod, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Vaduz, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Vatican, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Vienna, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Vilnius, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Volgograd, //PSTR("<+04>-4") + TZ_Europe_Warsaw, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Zagreb, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") + TZ_Europe_Zaporozhye, //PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4") + TZ_Europe_Zurich, //PSTR("CET-1CEST,M3.5.0,M10.5.0/3") +#endif + +#if USING_INDIAN + TZ_Indian_Antananarivo, //PSTR("EAT-3") + TZ_Indian_Chagos, //PSTR("<+06>-6") + TZ_Indian_Christmas, //PSTR("<+07>-7") + TZ_Indian_Cocos, //PSTR("<+0630>-6:30") + TZ_Indian_Comoro, //PSTR("EAT-3") + TZ_Indian_Kerguelen, //PSTR("<+05>-5") + TZ_Indian_Mahe, //PSTR("<+04>-4") + TZ_Indian_Maldives, //PSTR("<+05>-5") + TZ_Indian_Mauritius, //PSTR("<+04>-4") + TZ_Indian_Mayotte, //PSTR("EAT-3") + TZ_Indian_Reunion, //PSTR("<+04>-4") +#endif + +#if USING_PACIFIC + TZ_Pacific_Apia, //PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4") + TZ_Pacific_Auckland, //PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3") + TZ_Pacific_Bougainville, //PSTR("<+11>-11") + TZ_Pacific_Chatham, //PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45") + TZ_Pacific_Chuuk, //PSTR("<+10>-10") + TZ_Pacific_Easter, //PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22") + TZ_Pacific_Efate, //PSTR("<+11>-11") + TZ_Pacific_Enderbury, //PSTR("<+13>-13") + TZ_Pacific_Fakaofo, //PSTR("<+13>-13") + TZ_Pacific_Fiji, //PSTR("<+12>-12<+13>,M11.2.0,M1.2.3/99") + TZ_Pacific_Funafuti, //PSTR("<+12>-12") + TZ_Pacific_Galapagos, //PSTR("<-06>6") + TZ_Pacific_Gambier, //PSTR("<-09>9") + TZ_Pacific_Guadalcanal, //PSTR("<+11>-11") + TZ_Pacific_Guam, //PSTR("ChST-10") + TZ_Pacific_Honolulu, //PSTR("HST10") + TZ_Pacific_Kiritimati, //PSTR("<+14>-14") + TZ_Pacific_Kosrae, //PSTR("<+11>-11") + TZ_Pacific_Kwajalein, //PSTR("<+12>-12") + TZ_Pacific_Majuro, //PSTR("<+12>-12") + TZ_Pacific_Marquesas, //PSTR("<-0930>9:30") + TZ_Pacific_Midway, //PSTR("SST11") + TZ_Pacific_Nauru, //PSTR("<+12>-12") + TZ_Pacific_Niue, //PSTR("<-11>11") + TZ_Pacific_Norfolk, //PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3") + TZ_Pacific_Noumea, //PSTR("<+11>-11") + TZ_Pacific_Pago_Pago, //PSTR("SST11") + TZ_Pacific_Palau, //PSTR("<+09>-9") + TZ_Pacific_Pitcairn, //PSTR("<-08>8") + TZ_Pacific_Pohnpei, //PSTR("<+11>-11") + TZ_Pacific_Port_Moresby, //PSTR("<+10>-10") + TZ_Pacific_Rarotonga, //PSTR("<-10>10") + TZ_Pacific_Saipan, //PSTR("ChST-10") + TZ_Pacific_Tahiti, //PSTR("<-10>10") + TZ_Pacific_Tarawa, //PSTR("<+12>-12") + TZ_Pacific_Tongatapu, //PSTR("<+13>-13") + TZ_Pacific_Wake, //PSTR("<+12>-12") + TZ_Pacific_Wallis, //PSTR("<+12>-12") +#endif + +#if USING_ETC_GMT + TZ_Etc_GMT, //PSTR("GMT0") + TZ_Etc_GMTm0, //PSTR("GMT0") + TZ_Etc_GMTm1, //PSTR("<+01>-1") + TZ_Etc_GMTm2, //PSTR("<+02>-2") + TZ_Etc_GMTm3, //PSTR("<+03>-3") + TZ_Etc_GMTm4, //PSTR("<+04>-4") + TZ_Etc_GMTm5, //PSTR("<+05>-5") + TZ_Etc_GMTm6, //PSTR("<+06>-6") + TZ_Etc_GMTm7, //PSTR("<+07>-7") + TZ_Etc_GMTm8, //PSTR("<+08>-8") + TZ_Etc_GMTm9, //PSTR("<+09>-9") + TZ_Etc_GMTm10, //PSTR("<+10>-10") + TZ_Etc_GMTm11, //PSTR("<+11>-11") + TZ_Etc_GMTm12, //PSTR("<+12>-12") + TZ_Etc_GMTm13, //PSTR("<+13>-13") + TZ_Etc_GMTm14, //PSTR("<+14>-14") + TZ_Etc_GMT0, //PSTR("GMT0") + TZ_Etc_GMTp0, //PSTR("GMT0") + TZ_Etc_GMTp1, //PSTR("<-01>1") + TZ_Etc_GMTp2, //PSTR("<-02>2") + TZ_Etc_GMTp3, //PSTR("<-03>3") + TZ_Etc_GMTp4, //PSTR("<-04>4") + TZ_Etc_GMTp5, //PSTR("<-05>5") + TZ_Etc_GMTp6, //PSTR("<-06>6") + TZ_Etc_GMTp7, //PSTR("<-07>7") + TZ_Etc_GMTp8, //PSTR("<-08>8") + TZ_Etc_GMTp9, //PSTR("<-09>9") + TZ_Etc_GMTp10, //PSTR("<-10>10") + TZ_Etc_GMTp11, //PSTR("<-11>11") + TZ_Etc_GMTp12, //PSTR("<-12>12") + TZ_Etc_UCT, //PSTR("UTC0") + TZ_Etc_UTC, //PSTR("UTC0") + TZ_Etc_Greenwich, //PSTR("GMT0") + TZ_Etc_Universal, //PSTR("UTC0") + TZ_Etc_Zulu, //PSTR("UTC0") +#endif +}; + +#endif // TZDB_H diff --git a/travis/common.sh b/travis/common.sh new file mode 100644 index 0000000..d115085 --- /dev/null +++ b/travis/common.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +function build_examples() +{ + # track the exit code for this platform + local exit_code=0 + # loop through results and add them to the array + examples=($(find $PWD/examples/ -name "*.pde" -o -name "*.ino")) + + # get the last example in the array + local last="${examples[@]:(-1)}" + + # loop through example sketches + for example in "${examples[@]}"; do + + # store the full path to the example's sketch directory + local example_dir=$(dirname $example) + + # store the filename for the example without the path + local example_file=$(basename $example) + + echo "$example_file: " + local sketch="$example_dir/$example_file" + echo "$sketch" + #arduino -v --verbose-build --verify $sketch + + # verify the example, and save stdout & stderr to a variable + # we have to avoid reading the exit code of local: + # "when declaring a local variable in a function, the local acts as a command in its own right" + local build_stdout + build_stdout=$(arduino --verify $sketch 2>&1) + + # echo output if the build failed + if [ $? -ne 0 ]; then + # heavy X + echo -e "\xe2\x9c\x96" + echo -e "----------------------------- DEBUG OUTPUT -----------------------------\n" + echo "$build_stdout" + echo -e "\n------------------------------------------------------------------------\n" + + # mark as fail + exit_code=1 + + else + # heavy checkmark + echo -e "\xe2\x9c\x93" + fi + done + + return $exit_code +} diff --git a/utils/astyle_library.conf b/utils/astyle_library.conf new file mode 100644 index 0000000..8a73bc2 --- /dev/null +++ b/utils/astyle_library.conf @@ -0,0 +1,70 @@ +# Code formatting rules for Arduino libraries, modified from for KH libraries: +# +# https://github.com/arduino/Arduino/blob/master/build/shared/examples_formatter.conf +# + +# astyle --style=allman -s2 -t2 -C -S -xW -Y -M120 -f -p -xg -H -xb -c --xC120 -xL *.h *.cpp *.ino + +--mode=c +--lineend=linux +--style=allman + +# -r or -R +#--recursive + +# -c => Converts tabs into spaces +convert-tabs + +# -s2 => 2 spaces indentation +--indent=spaces=2 + +# -t2 => tab =2 spaces +#--indent=tab=2 + +# -C +--indent-classes + +# -S +--indent-switches + +# -xW +--indent-preproc-block + +# -Y => indent classes, switches (and cases), comments starting at column 1 +--indent-col1-comments + +# -M120 => maximum of 120 spaces to indent a continuation line +--max-continuation-indent=120 + +# -xC120 => max‑code‑length will break a line if the code exceeds # characters +--max-code-length=120 + +# -f => +--break-blocks + +# -p => put a space around operators +--pad-oper + +# -xg => Insert space padding after commas +--pad-comma + +# -H => put a space after if/for/while +pad-header + +# -xb => Break one line headers (e.g. if/for/while) +--break-one-line-headers + +# -c => Converts tabs into spaces +#--convert-tabs + +# if you like one-liners, keep them +#keep-one-line-statements + +# -xV +--attach-closing-while + +#unpad-paren + +# -xp +remove-comment-prefix + diff --git a/utils/restyle.sh b/utils/restyle.sh new file mode 100644 index 0000000..bcd846f --- /dev/null +++ b/utils/restyle.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +for dir in . ; do + find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.ino" \) -exec astyle --suffix=none --options=./utils/astyle_library.conf \{\} \; +done +