WMA-yyLNeNvFgyfPQ-L|SXMz+ikN}-st+OiI6aMZ=I_1znZT08
z4eHN4WpRsp;KBv>i>ii?Zd%gkML8GhBMv{<>1SKFYaQVQ0hVjenx+C(m<9P#DXt_a
zA-7dW#{&=Pr{`&URL5=td@hbwat6kgnK+Y6`jWFAtlqh(zOo-L5%Q2}4WcpMbnI43
zH}^cTFP#W}PGk)iVQ7+TF(iTE`)?G`(j_M~s2xKa_I=y3QoEkkYTHeCNg=()g!IDDfJ3RFH
zLTi{;i=$pS41<=@yBw7@o`6T*hPJ5_nv?M=Mhu+H)FOJd8qfuZi~wI_OOcd|7~emL
z#KlI)0~=*G+W=PYubV!!p6r-sq^T#M`&AjF0dSl?bL_SL)%K!YBLz4E0i4VqUkVF;aTkWu^4+nTony>eA)PX}qWKSnrwVs*`)uU6l
zd`O`Ko%4TjRn=uY&z2M&o2PXwVSfIEoo&HJEQ6Zg1*>ky4=TqnV@tc(zlXLOG^iyi8eTN&!Ub&{X{E@HsW-p?ua)JV
z>=6`4=pInel+5M8ubZUgbA&8EwP-#0P-DAsw}&G0>SR52waVbcUCvzJtHt}{eXTz8
z^DlesOKzt6$XXmVf$!nnc(;cy=V_6x!yU`Ax+>$~FSFJ&KC>rF?Q0m9cTbp_SO-nR
z;I`s8i?I@jUkVhHZ|`Nb>`Sxz!O#8bYjZ37ki!bQ<}Zi=F&Cb5XbHw_+=g@eA-_Zu
z$M=V6(F-d2l)UIKxv#=+cX3W9JP
z@SBYsZuuiY5X-Btj=iEmgDG0kLS=+&$E7ljG3CY6(GBe$!$It8@;!LBqE`h3O=W&J0jnz^i*91KYOAg(^mB5+Mr^ZBBTx9#I
z>5UrJz+9V?WuKRGPIxV>me5~}$G>gr(Hg!Zq==0W@m}Ne`AiMK`a$4V#y$^wkr50~
z=KbZ`g=Pc)%R+wY!mH86q&_p3s~Yjgx1sbva?RADL(&
z-E1uMO#E=Cv%p`Y%gc)?eL`#bKz=Lm!$f8An@;#
z?H$YMie;mvJ)U>NW=fzB0Nn)~r&W6Mg6{U|+!e!_WK-RZNyiHZJ0nxm-L0*S{-L3P
z(AH10pJSf+MTh6v9aCHXUgdOd{m$M{kW_aQrHmJQRHrlXUO!l5j1bFFC~-y1ic7%4
z-Jyc!mE-RGv&_Z{>jqLg_wt$~6GD
zU`S~0W^FqK?=km+XW;WwyS*k$gR{9`-)>m
zR>a1%+uU|ny*%uX7HUjSSG^iAzGSO
zZE_Ppn+5T*nso|;Jb=H|({A&rO51W5<>5R)a8yKOYf%)jg|gmE5>8|6u!9~Qe_O;GGDjq_>xw29D_!jD>z(%ao{
zDNpfihP308yIfVB%<~7H)vRdnMU|0>Az3Xwc1QVOBhx^^pwP+V$kx?Fn=
zKCFSL!(r{wP|ZM0XnzbjhF0o=CDyPz0CLS^jlafYWcaUkda3jkTm1I7A|i}D>}ltS
z6z;~xYXUB(t;PXGswhHsC%rKgnGpsg=jp8h2&>1}Zil~Za5^ck=6P=TH0TwKHt7p<
z6W?1l%Cnts%~LDxdMNUC9on{G+OoM`MpOPqE-t6*L8$9!j}6auXxT|ayJ5d)Hd47I4Zoi1ZYgRu21;FG`vH0)
z!k`6#Ml+1qEU`F~zyo#KE@v5BTa&fM>Fn%>-h30Eq!g7`#Aof2n@in<*?JcjhGWe-
z=i^z|`FWa}#HWVMJ>v>aU4zT<5_q%#l@r##xB0LKKTkxq5sf%vl6*l%FnTuqb>~Th
zuYlb-<2?}m@tL>5M&DZBpfIPXm^#Fz!|;U`
zftFwqh~8bSo3BW@jb9EJ9%a_P-6Us#244VZ>F7MJ7I%f6G9y4UNa)n`}~l21-|
z{Z)#)jM|s2o7Y?0Eo1K{2Gp0G4jn^Po;F7-*BIt49M`tv00B=Fh-iG@IHgl=qOmkH
zJtzr`rsn%r_c>AHc#C3MTEKQn60puf7_N>AXOjrsRVCaqhQ^ui)!4
z4oLQ8;`FQ#!2tZ5cPl$)ACv<!uRBqi_y#;YPhzODMWlaG(8UWTflUMG+Fgaka60C+GtkO2aIUM3E)+6tL?^ZOYU*k}1RwHzF(C^APUl1OWcSTKGl37*
zs%Y$DB)gML(2rW;zH$&PT@@Za1bG#n87}%w3Q#WA%cWL$4g-y#<~7^glO77vV&F8;i8o6X(;
zTsBJ;r|nm1P?aM3dd7ZqSq0(*JnYj$ZFU{W!%v2s-P#L9O8S6KcDPQB&$C>Gc69}e
z>nK&v`2|(uGL&T4sIbqgje>bIjhGXA_MOncf5nc5H{kxcA@F6-o{XlGcdN846I9v}
zBX$;|Qu+MZ^o-HgIcy1{lAAbG}c#aN6>M>))$QE?2Qt1bO2Hv4WP>t-?l20|R>y
zPAQUfy8&%a4M_~0GTR^J)MR!JVk@qFnTp7Ff4+D$+OJnI>iHWj<-ngM2H^rKk4iXy
zGLAqiWWTd#OuK(tITeFD-rwAqM=n|difh5EjGv?4T!v;p7bL8b=3ZT9LqM3;+!$kA
z`NF|nuQ)Nd)dp77`W>>6;z9Id!gV(D)7Ps8#b*Wi3dSCKe}pbJ_ZOmz=$Ix>SBHlW
znw^sM5*Gk3`+-X7P6i=x-JG<+;MhqU?ptGco*`e^ZGBZ-HgQwnna$^EeDHKe34t?S
zbgZT12b9y135J&S5~JjG@f7;UIJ%Y}W|TcSt3qK3A}4VIQk}|Jfk$>q6y1}B5Mpe2
z^|$J@JOjQ=;e#9Fd^Rv0FhWBXjU*w)mEV9o61R=z@*!!5U>uuXZ!+@a6f+Zs9xeQ-
z(1UekYl2;pZNL2r|0rmH4!Ag8c(rRPEN4KxJai~onZTbKIF8R4;h~K{bB_9{IJdvK
zyb(tQT#x{OwtWo$tgem2b9~37&R*~>^V@rZ@ZRoN`~(Q=zM!*-X(J2=jyeOpjF$-c
zq&q!&s7yhLYyP7|zfq=jOuoNnu2XA@vl0n(kkvGDpe^h-Dc3oLX#&3YP62R8euWKx
zI?!l4A%qHY+fqt`MoXcD#2Oyq)u8-P->$q`z)jLk%r;I5bC+);@Z>2u@&o!Qtf{_u
zDkJLw=6Pi;E-M2Bh;_b1xMd`G@-=jlXzTmh-dv7CVSNT6^#7btIq)f_I=;Ef>p~Oc
z7X($yB|jf8lZnz4LL&k=8J?}-MEXVvXn!$@1=FBW`pf0m_&$NL5Mq@(Fd&bc^J&R6
zFNZNa2pzDNlI5xE9%u)E7a}6Li!R?wSnDV1fya+)
z9&{X_frTVCofR|k`c;{esHvNmc$8ijQ2nJlP=+iehBDzj?90Jl3U!
zB5IAUR3^)q%_YFk19XxF$Uq;*64R$Z{%GqiEy?|rO-vh{
zC;U6!NDCy9VYw(x_v`n5dlbGRh>i9gmNci%5=84Q+NQh011lL&o2HBKsGmaCcJ);Z
z?&fgo*^19`guBCRbxdljw<>f0l2Id+9FOJ;0S151`LyEUm;4@H+Z~jV+iDUejcSI(_1&YO-83#iV}>C1_cd)ChK-o=fF8?Juw4&rcU34nWhfos08hoaBr6%QT+8dON}@Z
z)>wGczZ1~&&Y@Vzuqg-E>7jg~J9e+4!Bvb)OEBVV2Tw3imqAc!B&yEu{JujeM8Il3R+lYtcF25Z1
zdNj@sH~*8B)a_$^EyhjdkiE)Q!=TBJz4((eImGC09VISkt|8|y1mRz|su
z)-12vqDP3#D=N6!<)kQ8P9#>b-FgC{JoQIPTe=CbAfT_!{h2P(%iFFIqZtcK*BhXk
z?*4+zLk;bZ@y(^jZg(*!gR-AtSNAFax)KTrB3ih1@=Tinu2PJj5X#^4mLbYO@0jid
zfZT=t?jM?3GMg*6|3Z@#*^J_&4FyD*hN4U=SJrvbVz?BAqB?6?eM!W!+4^2QLe$@1
zzgeClnT}|b`6wLB^6d~k2BU!d0XAFJU^o+JVw0KRyGjy&`8m;DouEAIt|L@%rns9m
zv4w51iifI7o{HhkV{hjJlRqrDKjMmzbrHY6%f#LXjY6Ut!GsUjL*(<}r`J6|FUh~~
zF%@gT|H3?Vel(FG@unoD_m}yK0b<9+|?mbtm#VJcGbB;mh|ueR9h-nL4LyO
z&RN2`DmYqx8p~>{cb6ge<`kflhVE``?mADi#fklhzI5UpIza1RWD6mO=H%v%h^fuq
z#N&*V0}q?gx6`#){TbNEC^ZWEsu`PU<=`m3&=nshIj7Vg
zn0mbwYZulM)m@b8B0v+vKmn9oDI^PZm%GhtMuQIJ@4-v0`Z!cb$rq&IgG1LIw0yBY2bqrK!U
z%(mtO!t3qrnRM307ccICgZW13l~dF_0ev8&1?O<>T=!h(EEVR^DVKgZ;J(SIa=(R_
z&8So=e7Y!MAwQa#X_FGWJ~S%`k~y;te#Rm7dZL6Jocj%+PWfO0gbH>Zh*J*8f<*=W
z+YbY!RaeD~6ICYnhFSea;n(3Q7hZ4L-Mr{?DJg&WL@~ziZ+HuYDtN_CbO{1Xc(?eM
zovmzsYx-$R)OlQGg$oNFF`N#^N6DyHp|Q)gjpJjs8LK=sh68(%
zJJ&DKwD|RVT;Fn3p@td$vFnhZ?Di@@P8x6484J?TcX*);#G2$!l
z^OULRfZ5U3Zf`eID`ZnHGa_Aw^}_fhp?<6Dy)!VE3$gcv27q_?m5jW(J{6@iN)+o{
zi(;WAeFrEyvHtbSk3x#7N1tA~$l$Rh@SHliHP~t6H2}Y4d7Y#fG?I0%=k?Hu@(=VD
zs8HFtSOZq$4=v|t-v9WhT(PYoo=iP9Cl$>u*3}J=OU4*)VZ|x4J4KWpr7gPdvw9B3
zO2dw>dE=v{t4ro2C`uw+IyzQ^sj696Stb>KZsv6R^FOw^o!|ZHtKwVYpiljnttrK)
zQbxt%&d`ja>9nWM70zgjhUWXE7q?9T-O<`;lUW8=7B5LQKwWIHj}K&U&IHUhqs1eD
z%yWm!?u67@l;7GvhS8t>13I9=qn_}0SNoeSiBUG%JngQVp-qZnH+Hpk^(?HDQ*y;M
z3rkGR>G`B*7g3|W@y-N*KfxLTIZ(r2P|+2YPoThH*9QPo654&8wdzv4C*1{1OVzJ`
zg6QUKmzzCKS67xeKQU@IJK@`%Y~0tF$`CJgSg&mC8$LMOSR3YkmvQ71F?XX>&at_S
zdNdZgW5)w}=pPZ@>u4?385yk22EVT|hXX{kyIZl@YmvWNr}117u{kW~7ER?#XM8m5
zt);>2iv+Zwkk*&?V#vjiNLexgLq{rJL}3Q(8`kf+47|vx?H6a-ucYHW({=y?zc*9i
zg4Ga`geYl6R76Y+6YhiRXvcUIMKvRGzR|?G&ermp_Uab>%A{BSXju;yGD}s!Iqh
zr-l(yIRT1h9H2&e7=V7#C(r~ah`T$yYfck{1U&XCYkfUJR3rB%d%JpntEsBT1;auJ
z#l|Vlan{)U-CA}&xS^{ccbM3QB)|i1Qoed4;F(01x5;wm=wn+dUFHz6Z}V0}I;^y2
z{r$L`?5S_zv}fTX2L_zrJ3Tq6N&gI8zFL>YGKi}X--j;*vnvI*e6Rfi92F47Z4vMg
za$hbs+uNHwzU?>a2Rz)KtoF<-R0Y)q{qXx0nV$l5yGz5_)FrFkvl-B|hfh^Q0YBFY
zHNHXx)wTZV!P)jYsSw7MMiuEGBQyb8+AO<58&e}m35OjH`PW7i~m>TO2*&DqFq
zl2vAYi0dPPWsp-ivzi!AWxoJMWcl
z9S!%kxP`lziOQQ&7kpkThRehwa3BQe51=>!60{Qf|MBO`rP=)t{S435_SdU>Of#hn
zf%`T$LcCOUh?e_MBtXkSM}2y8(=uOHv(bITV!Uv+REODo@x$0yQDsF%zb{~9C*ST?
zN;v&{u)WRFW4usniC1kgO5lMfi^z*Tb0FX!r@Ibr_8B2rVMR@GT()K4co4t
zS!gHzv7_tsA!%cKJoM|Tr-h=aW-}8p$_|ui=uss{9NOP20ZWqrW`WVSX41Nt67b`V
z-zOPNGw!v%w_}kZ0M>*4q2K#>VUzoF9?{q^va4k*S){roAvAh_Diw={LY5!*`PxJ}CH~bD;P}UA)&<>~|1^e&Gqv
zTRFBY~i
zWNbx4x?B@^1G>k4^Lb>ZQ!a$&q{a`Jtzq&W6w?Kx(Ex6Pk`jXjkln|bILn!}%}nhW
z36Ri&F;L+7KXL5~ETze8U&Ab^p=cI3!ub4$#`j_0ZoEk1o41{R01Aa3WK8{D>)qlU
zz0-wXAuvD|)Z9V^`P_6_kC<@DJA$_!@S`Fx=t_arv(_a(%s-Bp;r>9<-Nz~J3OHgG+*3VGWDnEm6Y@Ek4eTM2q1!ldkaU-Fk_e>L;>kg
z4rL@sjn29mnZ00W2+?uk)>Hd$;8Jg<3*g=u-9;ZZf6&GUZ@$BP+HM}+!ZGm>_wzb^;E0(>u|
ziH;BJS5+*Gl5pOWDO9^3=ch#qnc{xu0h}Tk4`+UewxxFY;(;fC4%_*|?t3Q(zrr_)
z2JOX^jy0hPvZ$aRSra|aVTQ7ZDAn6hg6PrbW`}T^$n&g8whkI7HH@B&f(6*$!N4za
zB#$>o7eYIGj4+=pIZUR?GLfiXexL)t2v$%>dNftwTH*Fl0dQYG^E@TI7kV+kZB%#{UK~YROJ#m4PjfoBANVX!v9|*vrSE9MeqA`pH34SXj-+GMuu}%Nb
z@y!hPvm{2xvJ2@dQ81Llk0X}dvzk-$%IhXP0Kd+FKUR;*8lUh!Ta$489h$^s!qh12
z)_7$RU=jOa{kj-SxF?P-U@}ZCa5k7wq+O=xz>LzP
z5|Bu8j*yc%1@i|DI8I+q$|2{ZoYc(s#IZBDXF8_>^OC0WvH}nA5tg5DU4~_!QVp&b
zoc&&X(92;M`H-J81^ovLD)T$-_nY&1mL_&5*qRTPlh$3UynX_DYmOgb!PF_hC*SDxU;nQRYO}t!gVkg1W-K?Z)>TZjR04-X*lwjLqV
z)MkgSPI86^WVG0?kExD4eua)gf!@roqn;{paqEW(w+g9DEO(?dnE?FFE!B%ZQT
zvcDrS3+2Vs6e3CPQ8TI?+bVMVPV=WkmB3<-G^63)oeSTaZcZ-mR|(ukI$m@pZn;;H
z13#pL^}rBv^zkMl_X0c}&SIR_ak%gMGG8YH=Gr0jW!_~gUU-YZwl}Wx7p3##Z;SsdSgd}{hHO}c?lZzt$y~z()dQE)miV(7
zP~%=dt}Gw-(`GQN2ki?B!eFu3ao>yU0u%H>yDuv+IZOgO&&QalxH5R-tJO42IKs1a
zryK?kp>3+apu2QzeHDS5iWJJj+RhjEYV|SN7j(q>AdMzbu(CChsN)?rHpoO1qCe&<
zjOLpuX8)TP^)^Ok=L5`Q@rAU-y=QfGaJfF~?#m@A-(3Q7m^K)wE&CIMZ;vohgki=(Zz
zrFFzBtv|}AUz}bcebZuRnY2OOzh%MS@3}v9m@WSRI2M}F85lNUrikE&Tc;nNn6@<8
zdgfo{{y{$J8$82C55kzQ{HUZnf)!U=7*xuUY1%jh94}992+ibX}Io`JZZRae%$@t
z1qF|rIR8eO=G@<~WZ&lnncC$t`@cO&j_^s!`Wj-cW%3tn(Ht#5&f4dtiT`(L%KCo!
zTC&yD^h(H7(Uhe#OOr%Gn;a*ofcn9qAy==MmVV(m8~t_W&W|s8#pf^HoY*U_kRi~}
zkUZs%e4yj|y}(u1@Al>W{PXg$JKw8^m+rW@yKa!~2Hx|?tNYgai>y`FpB~lMW^XMj
z{@q*cpZHO-^};)L;J}qYL6_s=uqlCFU0g**!Hb$}@$TGQo)8v`Qa{Jr
zmo^6f|9HA=?v8@LCzrpN^5xB)rbm}Hee%*0VQ2(itH?BO%5#;r+Pc@JdDmyL2Dg2G
zcVky-arEr8Y0uJ@9dm1AV>2~PDK!OOQ2cY&yJX4C%*>fto38HN+2wUgE9;fitSGBp
zRlnHWj;W%6clwW@^skf0
ze}9~t{oG&f=Kl2O)4pm?*O_6P{A$U^#qIoW?ylXudh_JwxxG_cXLe2mcDJ_m&1+;d
ze(Q5)ZO)shZCS5xEvLtGZM5;m_Lt+ds@W
z@@3DZPoIFZ&{|X7lJ_ybN#|?fW)k3FX^x7EnlX2ph`g+zn2^Ye|L?ATR_A|zcVFSx
zPe(sLKR3s!@(-{Zf85_-u2u27M^9H@w|#F{^Xt*n{`vI|0Uy4v*s%jVbs
literal 0
HcmV?d00001
diff --git a/library.properties b/library.properties
new file mode 100644
index 0000000..0630d2f
--- /dev/null
+++ b/library.properties
@@ -0,0 +1,9 @@
+name=ESP3DLib
+version=1.0.0
+author=Luc Lebosse
+maintainer=Luc Lebosse,
+sentence=A 3D printer front end for ESP boards.
+paragraph=This library implements a 3D printer front end.
+category=Communication
+url=https://github.com/luc-github/ESP3DLib
+architectures=esp32
diff --git a/src/esp3dlib.cpp b/src/esp3dlib.cpp
new file mode 100644
index 0000000..249e52c
--- /dev/null
+++ b/src/esp3dlib.cpp
@@ -0,0 +1,61 @@
+/*
+ This file is part of ESP3DLib library for 3D printer.
+
+ ESP3D Firmware for 3D printer is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ ESP3D Firmware for 3D printer is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this Firmware. If not, see .
+
+ This firmware is using the standard arduino IDE with module to support ESP8266/ESP32:
+ https://github.com/esp8266/Arduino
+ https://github.com/espressif/arduino-esp32
+
+ Latest version of the code and documentation can be found here :
+ https://github.com/luc-github/ESP3D
+
+ Main author: luc lebosse
+
+*/
+#include "esp3dlib.h"
+
+void WiFiTaskfn( void * parameter )
+{
+ WiFiConfig::begin();
+ for(;;) {
+ wifi_config.handle();
+ wifi_config.wait(0); // Yield to other tasks
+ }
+ vTaskDelete( NULL );
+}
+
+
+
+
+//Contructor
+Esp3DLib::Esp3DLib()
+{
+
+}
+
+//Begin which setup everything
+void Esp3D::init()
+{
+
+ xTaskCreatePinnedToCore(
+ WiFiTaskfn, /* Task function. */
+ "WiFi Task", /* name of task. */
+ 10000, /* Stack size of task */
+ NULL, /* parameter of the task */
+ 1, /* priority of the task */
+ NULL, /* Task handle to keep track of created task */
+ 0 /* Core to run the task */
+ );
+}
diff --git a/src/esp3dlib.h b/src/esp3dlib.h
new file mode 100644
index 0000000..505b700
--- /dev/null
+++ b/src/esp3dlib.h
@@ -0,0 +1,35 @@
+/*
+ esp3dlib.h - esp3dlib class
+
+ Copyright (c) 2019 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifndef ESP3DLIB_H
+#define ESP3DLIB_H
+//be sure correct IDE and settings are used for ESP8266 or ESP32
+#if !defined(ARDUINO_ARCH_ESP32)
+#error Oops! Make sure you have 'ESP32' compatible board selected
+#endif
+
+#include "Arduino.h"
+class Esp3DLib
+{
+public:
+ Esp3DLib();
+ void init();
+};
+#endif
diff --git a/src/nofile.h b/src/nofile.h
new file mode 100644
index 0000000..f10f2d8
--- /dev/null
+++ b/src/nofile.h
@@ -0,0 +1,329 @@
+/*
+ nofile.h - ESP3D data file
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+//data generated by https://github.com/AraHaan/bin2c
+//bin2c Conversion Tool v0.14.0 - Windows - [FINAL].
+#define PAGE_NOFILES_SIZE 4862
+const char PAGE_NOFILES [] PROGMEM = {
+ 0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xED, 0x5C, 0x7D, 0x93, 0xDA, 0x46,
+ 0x93, 0xFF, 0x2A, 0xB2, 0x52, 0x36, 0x70, 0x2B, 0x40, 0x12, 0xAF, 0x8B, 0x16, 0xF2, 0x24, 0xB1,
+ 0x7D, 0xF1, 0x95, 0x13, 0xBB, 0xBC, 0xEB, 0x7B, 0xAE, 0x2A, 0x4E, 0xB9, 0x84, 0x34, 0x80, 0xCE,
+ 0x42, 0xD2, 0x49, 0xC3, 0xEE, 0x62, 0xC2, 0x77, 0xBF, 0xEE, 0x79, 0x91, 0x46, 0x42, 0xB0, 0xEC,
+ 0x26, 0x79, 0xF2, 0xFC, 0x91, 0x60, 0x23, 0x98, 0x99, 0xEE, 0xE9, 0xE9, 0xE9, 0xFE, 0x75, 0x4F,
+ 0x0F, 0xCE, 0xD5, 0x8A, 0xAE, 0xC3, 0xD9, 0xD5, 0x8A, 0xB8, 0xFE, 0xEC, 0x2A, 0xA3, 0xDB, 0x90,
+ 0xCC, 0xB0, 0x65, 0xB7, 0x88, 0x23, 0xDA, 0x5E, 0xB8, 0xEB, 0x20, 0xDC, 0x4E, 0x32, 0x37, 0xCA,
+ 0xDA, 0x19, 0x49, 0x83, 0x85, 0xD3, 0x5E, 0x67, 0x6D, 0x4A, 0xEE, 0x69, 0x3B, 0x0B, 0xBE, 0x92,
+ 0xB6, 0xEB, 0xFF, 0xEF, 0x26, 0xA3, 0x13, 0xCB, 0x34, 0x9F, 0x3B, 0xED, 0x3B, 0x32, 0xFF, 0x12,
+ 0xD0, 0x23, 0xBD, 0x8C, 0x1D, 0xB6, 0xC2, 0xD7, 0xE4, 0x7E, 0x3F, 0x8F, 0xFD, 0x6D, 0x69, 0x0A,
+ 0xFD, 0x47, 0x12, 0xDE, 0x12, 0x1A, 0x78, 0xAE, 0xF6, 0x33, 0xD9, 0x10, 0xDD, 0xC8, 0xBF, 0x1B,
+ 0xDF, 0xA5, 0x81, 0x1B, 0x1A, 0x8A, 0x0C, 0x0A, 0xAF, 0x7E, 0x72, 0xEF, 0x84, 0x41, 0x44, 0xDA,
+ 0x2B, 0x12, 0x2C, 0x57, 0x30, 0x57, 0xA7, 0x6F, 0x8F, 0x07, 0x23, 0xAB, 0xDF, 0x73, 0xBC, 0x38,
+ 0x8C, 0xD3, 0xC9, 0x37, 0xBD, 0x5E, 0xCF, 0x99, 0xBB, 0xDE, 0x97, 0x65, 0x1A, 0x6F, 0x22, 0xBF,
+ 0x2D, 0x5A, 0x17, 0x8B, 0xC5, 0xBE, 0xE3, 0x01, 0x1F, 0x17, 0x88, 0xD3, 0xDD, 0xDA, 0x4D, 0x97,
+ 0x41, 0xD4, 0x4E, 0x19, 0x0F, 0x77, 0x43, 0x63, 0x47, 0xB4, 0x84, 0x64, 0x21, 0x1A, 0x12, 0xD7,
+ 0xF7, 0x83, 0x68, 0xC9, 0x5B, 0xAC, 0x01, 0xCC, 0x2B, 0x5B, 0x38, 0x15, 0x36, 0xED, 0xA9, 0x3B,
+ 0x0F, 0xC9, 0x6E, 0x1E, 0xA7, 0x3E, 0x49, 0x27, 0xA6, 0xC3, 0x3F, 0xB4, 0xB3, 0xC4, 0xF5, 0x60,
+ 0x20, 0x34, 0xAC, 0xDD, 0xFB, 0xF6, 0x5D, 0xE0, 0xD3, 0x15, 0x53, 0xCA, 0xBE, 0xC3, 0xC6, 0xB7,
+ 0xF9, 0x30, 0xE2, 0xEF, 0x8A, 0x2E, 0x41, 0x3A, 0xB1, 0x92, 0x7B, 0x2D, 0x8B, 0xC3, 0xC0, 0xD7,
+ 0xBE, 0xF1, 0x7D, 0x5F, 0x4A, 0x35, 0x8F, 0x29, 0x8D, 0xD7, 0x13, 0x1B, 0x35, 0x49, 0x81, 0x6C,
+ 0x15, 0x50, 0xC2, 0x66, 0x21, 0x93, 0x28, 0xBE, 0x4B, 0xDD, 0x44, 0xCA, 0x36, 0xB1, 0xD7, 0xEB,
+ 0x3D, 0x5D, 0xED, 0xD8, 0x9E, 0xB8, 0x61, 0xB0, 0x8C, 0x26, 0x28, 0xBF, 0x98, 0x78, 0x46, 0x71,
+ 0x1B, 0x66, 0x34, 0x9D, 0x51, 0xDF, 0x38, 0x68, 0x5A, 0xE5, 0x4D, 0xCC, 0x36, 0xCA, 0xA3, 0xF2,
+ 0xA6, 0xD5, 0x4E, 0x4E, 0x35, 0x3E, 0xBE, 0x15, 0xB7, 0x24, 0xC5, 0x9D, 0x0C, 0x85, 0x08, 0x34,
+ 0x4E, 0xA4, 0x6A, 0xE0, 0x63, 0x65, 0x8D, 0x55, 0xA5, 0xD4, 0x08, 0x59, 0xD7, 0xB7, 0x3A, 0xEC,
+ 0x3B, 0x10, 0xBB, 0xAE, 0x6F, 0xB5, 0xAB, 0xD5, 0xF4, 0xA1, 0x14, 0x8F, 0xE2, 0x26, 0x76, 0x48,
+ 0xEC, 0xB5, 0x0D, 0xDB, 0x24, 0x68, 0x32, 0x9A, 0x06, 0x89, 0x22, 0xF8, 0x24, 0xA2, 0xAB, 0x76,
+ 0xBC, 0x68, 0xD3, 0x6D, 0x42, 0x9A, 0xB1, 0xEF, 0xB7, 0x76, 0x35, 0xB6, 0x7A, 0x89, 0xAF, 0xFD,
+ 0x3F, 0xD6, 0xC4, 0x0F, 0x5C, 0xAD, 0xB9, 0x06, 0x03, 0xE0, 0x7C, 0x47, 0x43, 0xD0, 0x79, 0x6B,
+ 0xA7, 0xD8, 0xB1, 0x68, 0x1F, 0xA0, 0x61, 0xD4, 0x10, 0x5C, 0x5E, 0xDA, 0xB5, 0x04, 0x97, 0xA3,
+ 0x23, 0x04, 0x96, 0x6D, 0x9A, 0xB5, 0x14, 0x96, 0xC5, 0x49, 0x3A, 0x91, 0x7B, 0xAB, 0x9A, 0xAD,
+ 0x10, 0xD9, 0xF3, 0xBC, 0x8A, 0xC3, 0x98, 0x55, 0x77, 0x31, 0xC1, 0x58, 0x32, 0x70, 0x63, 0x44,
+ 0x1C, 0xB0, 0xDA, 0x88, 0xD4, 0x78, 0x29, 0xF3, 0x5D, 0xAE, 0xD0, 0xD4, 0xF5, 0x83, 0x4D, 0x36,
+ 0x19, 0x82, 0x91, 0xD5, 0x38, 0x81, 0xBB, 0x4B, 0xE2, 0x2C, 0xA0, 0x41, 0x1C, 0x4D, 0x52, 0x12,
+ 0xBA, 0x34, 0xB8, 0x25, 0x8E, 0x1F, 0x64, 0x49, 0xE8, 0x6E, 0x27, 0xF3, 0x30, 0xF6, 0xBE, 0xE4,
+ 0x0E, 0x81, 0xE8, 0xA3, 0x31, 0xF7, 0x65, 0x3E, 0xE1, 0x13, 0x2F, 0x4E, 0x5D, 0x46, 0xC8, 0x64,
+ 0x28, 0xE4, 0xDF, 0x77, 0x5C, 0x0F, 0xF9, 0xEC, 0x0A, 0xC4, 0xA8, 0x91, 0xD0, 0x34, 0x4D, 0x39,
+ 0x50, 0x73, 0x0D, 0x77, 0xB2, 0x88, 0xBD, 0x4D, 0x06, 0xCF, 0x55, 0x0C, 0x36, 0xBF, 0x53, 0xC1,
+ 0x26, 0x71, 0x23, 0x12, 0xEE, 0x0E, 0x65, 0xAF, 0x07, 0xA7, 0x23, 0xFE, 0x5F, 0x56, 0x06, 0x82,
+ 0x9F, 0x44, 0xDD, 0x79, 0x7C, 0xDF, 0xCE, 0x56, 0xAE, 0x1F, 0xDF, 0x4D, 0x4C, 0x0D, 0xA9, 0xF0,
+ 0x6F, 0xBA, 0x9C, 0xBB, 0x4D, 0xD3, 0xC0, 0x57, 0xC7, 0x1C, 0xB4, 0x9C, 0x73, 0x06, 0x09, 0x49,
+ 0xDB, 0x0C, 0xA1, 0x73, 0xAD, 0x21, 0xB8, 0x89, 0x0E, 0x34, 0x76, 0x68, 0xDB, 0x1D, 0x6A, 0xF4,
+ 0x34, 0xE2, 0x0E, 0xF0, 0x25, 0x57, 0x20, 0x1A, 0x95, 0x35, 0x01, 0x12, 0x70, 0xD3, 0x90, 0xAB,
+ 0xEB, 0xA1, 0x6E, 0x8A, 0x3E, 0x34, 0xA3, 0x9A, 0x2E, 0xA1, 0xC9, 0x8A, 0xF7, 0x86, 0xEE, 0x1C,
+ 0x94, 0x2D, 0x2D, 0x20, 0x88, 0x18, 0x2E, 0x71, 0x43, 0x28, 0x43, 0x70, 0xC5, 0x98, 0x70, 0x15,
+ 0x2C, 0xBA, 0xDC, 0x71, 0x0C, 0x1B, 0xE1, 0xF6, 0x32, 0x43, 0x09, 0xA2, 0x45, 0x2C, 0xF7, 0xB3,
+ 0x07, 0xC6, 0x3F, 0x86, 0x2D, 0x5D, 0xC4, 0xE9, 0xBA, 0x8D, 0x9E, 0x91, 0xC6, 0xC5, 0x64, 0x7C,
+ 0x16, 0x3E, 0x03, 0x0B, 0x1C, 0x02, 0x0E, 0x7B, 0xFD, 0x22, 0x64, 0xA0, 0x19, 0x6B, 0x96, 0x2D,
+ 0x27, 0x3B, 0x37, 0x94, 0x0D, 0x06, 0x83, 0x63, 0xD6, 0x52, 0xB4, 0x06, 0x6B, 0x77, 0x29, 0x1D,
+ 0xEA, 0xC0, 0x86, 0xD0, 0x2F, 0xCF, 0xB2, 0xA1, 0x20, 0xCA, 0x08, 0xD5, 0x8E, 0x18, 0xC9, 0xA8,
+ 0x6C, 0x4A, 0x0F, 0x8E, 0x6D, 0xC7, 0x6D, 0x9A, 0x42, 0xF8, 0xE6, 0x0E, 0xAA, 0x5A, 0x80, 0x46,
+ 0xDC, 0x8C, 0x80, 0x6E, 0xDB, 0xF1, 0x86, 0x6A, 0x1D, 0x6B, 0x90, 0x19, 0x05, 0xDF, 0x83, 0xBE,
+ 0xB2, 0xC2, 0xB9, 0xAB, 0xED, 0xCA, 0xF6, 0x34, 0x1C, 0xBA, 0x0B, 0x72, 0xE9, 0x00, 0x05, 0x6A,
+ 0x12, 0x02, 0xEE, 0x13, 0x96, 0x66, 0x98, 0xD0, 0x39, 0x96, 0x1D, 0x96, 0x69, 0x1B, 0xD6, 0x68,
+ 0x60, 0xD8, 0xBD, 0x9E, 0xD1, 0x19, 0xB6, 0x84, 0x0C, 0xA8, 0xEB, 0xA4, 0xE2, 0xCC, 0xDC, 0x47,
+ 0xE6, 0x34, 0x3A, 0x66, 0x77, 0xEA, 0x60, 0xB3, 0x64, 0x66, 0x7D, 0xD3, 0x74, 0x94, 0x10, 0xED,
+ 0x91, 0x88, 0x92, 0xB4, 0x1A, 0x35, 0xD7, 0x81, 0xEF, 0x87, 0x84, 0x27, 0x60, 0xF1, 0xC6, 0x5B,
+ 0xB5, 0x11, 0x76, 0x40, 0x9F, 0x6B, 0x37, 0x0A, 0x92, 0x4D, 0xC8, 0x40, 0xCC, 0x39, 0xDE, 0xE3,
+ 0x6D, 0xD2, 0x0C, 0x54, 0x94, 0xC4, 0x01, 0x63, 0x7E, 0xA6, 0xC5, 0xB0, 0x7D, 0x4B, 0xDC, 0x14,
+ 0x24, 0x72, 0x4E, 0xA4, 0x19, 0x8F, 0xB4, 0xE7, 0x1A, 0x13, 0x5C, 0xC7, 0x5F, 0xDB, 0x9B, 0x0C,
+ 0x93, 0x25, 0x12, 0x12, 0x8F, 0x72, 0x71, 0x70, 0xAD, 0x07, 0x8D, 0xD5, 0x06, 0xA6, 0xF3, 0x76,
+ 0x92, 0xC2, 0x32, 0xD2, 0xED, 0x69, 0xB4, 0xEE, 0xF5, 0x46, 0xEE, 0x7C, 0x54, 0xC1, 0x20, 0x9B,
+ 0x0C, 0x7D, 0xB7, 0x5F, 0xE2, 0x22, 0x10, 0xDD, 0x28, 0xB5, 0x71, 0x68, 0x2F, 0x35, 0x31, 0x94,
+ 0x2F, 0x35, 0x4D, 0x6A, 0x28, 0x27, 0x87, 0x94, 0x07, 0xF1, 0xA1, 0x46, 0x58, 0x7B, 0x3C, 0x34,
+ 0x2F, 0xCD, 0x8A, 0xB0, 0x96, 0x6D, 0xCF, 0xFB, 0xE6, 0xDE, 0x73, 0x13, 0xDC, 0x54, 0x89, 0xC1,
+ 0x2C, 0x8D, 0x1A, 0x2B, 0x29, 0xA9, 0xB0, 0xB2, 0x71, 0x01, 0xCA, 0xA3, 0xD1, 0xC8, 0x39, 0xC8,
+ 0x02, 0xDD, 0x10, 0x4C, 0xAC, 0x04, 0xF2, 0x35, 0xC1, 0xF5, 0xB4, 0x51, 0x1C, 0x6C, 0xA5, 0xE0,
+ 0xDA, 0xCE, 0x36, 0x9E, 0x47, 0xB2, 0xAC, 0x26, 0x9F, 0xF1, 0x17, 0x0B, 0xD3, 0x1F, 0x57, 0x23,
+ 0xC1, 0x90, 0x5C, 0x7A, 0xC3, 0x3C, 0x84, 0x78, 0xA3, 0x61, 0xCF, 0x97, 0xAC, 0x7C, 0x37, 0x5A,
+ 0x82, 0xB6, 0x6A, 0xA0, 0xCF, 0xF6, 0x89, 0x4F, 0x2A, 0x9C, 0xC8, 0xDC, 0xF3, 0x7C, 0x4B, 0x72,
+ 0x72, 0x2F, 0xFB, 0xFD, 0xBE, 0xBD, 0xEF, 0xAC, 0xDC, 0xAC, 0x4D, 0xD2, 0x14, 0x20, 0xA7, 0x0C,
+ 0xDB, 0x65, 0x5A, 0x3E, 0xFA, 0xCF, 0x06, 0xC4, 0xA3, 0xD2, 0xD4, 0x62, 0xDA, 0xB8, 0xDF, 0x1B,
+ 0xF4, 0xFA, 0x4F, 0x46, 0x32, 0x74, 0xCD, 0x6F, 0x3C, 0x32, 0xEE, 0x8F, 0x7B, 0x8F, 0x91, 0xB1,
+ 0x4A, 0x5B, 0x92, 0x59, 0x88, 0xDB, 0xE6, 0x61, 0xB6, 0x46, 0xD3, 0x62, 0xF3, 0x4F, 0xEA, 0x9A,
+ 0xEF, 0xF1, 0xBF, 0x46, 0xD7, 0xB5, 0xF2, 0xD4, 0x6A, 0xDB, 0x9E, 0x0F, 0xFA, 0xB6, 0xF7, 0xFB,
+ 0xB4, 0x3D, 0x1C, 0xCD, 0xAD, 0xE1, 0xF8, 0x69, 0xDA, 0xE6, 0xB4, 0x15, 0xA9, 0x6B, 0xF5, 0x2D,
+ 0x7D, 0x04, 0x61, 0x45, 0x78, 0xC8, 0x49, 0x3C, 0xF1, 0x2F, 0xC1, 0x8C, 0x16, 0x55, 0xB7, 0xEB,
+ 0xF7, 0x16, 0x3D, 0x57, 0x65, 0x52, 0xC2, 0x3E, 0xD1, 0xA4, 0x00, 0x98, 0x68, 0x51, 0x90, 0x8F,
+ 0xB7, 0x4C, 0x0E, 0xC9, 0x26, 0x07, 0x64, 0xE7, 0xC0, 0x9E, 0x77, 0xD9, 0x33, 0x6D, 0xAF, 0x22,
+ 0xE6, 0x68, 0x68, 0x79, 0xD6, 0x25, 0x13, 0x33, 0x58, 0x2F, 0x77, 0x22, 0x96, 0xAD, 0xDC, 0xA8,
+ 0x9A, 0x12, 0x0F, 0xEB, 0xF0, 0x8A, 0x27, 0xE0, 0x9C, 0x56, 0x88, 0x50, 0x83, 0x25, 0x26, 0xBE,
+ 0x2A, 0xF3, 0x9A, 0x20, 0xE2, 0x5F, 0xEE, 0x78, 0x20, 0x38, 0x93, 0xF4, 0xF4, 0xCA, 0x7B, 0xA6,
+ 0x48, 0x3F, 0xE4, 0xD8, 0x87, 0x56, 0xFA, 0xD7, 0xAF, 0x2B, 0x04, 0xD1, 0x20, 0x43, 0xF8, 0x22,
+ 0x0D, 0x82, 0x1D, 0xA6, 0xF2, 0xD6, 0x89, 0xB0, 0xB1, 0x45, 0x10, 0x12, 0xF6, 0x9D, 0xBB, 0x6B,
+ 0x3E, 0xF6, 0xB2, 0x0F, 0xBB, 0x1A, 0x44, 0xC9, 0x86, 0xFE, 0x82, 0xA7, 0xE7, 0x29, 0x8E, 0xFB,
+ 0x75, 0x32, 0x91, 0xCB, 0xC2, 0xAF, 0xED, 0x4D, 0x12, 0xC6, 0xAE, 0xDF, 0x9E, 0x6F, 0x20, 0x9A,
+ 0xFD, 0x9D, 0x97, 0xFD, 0x6B, 0xF3, 0x32, 0xE7, 0xA4, 0x9B, 0x0F, 0xE6, 0x9E, 0x79, 0x10, 0xBA,
+ 0xFB, 0xC3, 0xF9, 0xD8, 0x77, 0x1F, 0xB5, 0xA9, 0xC2, 0x2A, 0xFE, 0xDE, 0xDA, 0x7F, 0x9F, 0xAD,
+ 0xED, 0x59, 0x73, 0xD3, 0xAF, 0x9E, 0xF4, 0xAD, 0xF9, 0xD0, 0x1F, 0x0F, 0x1E, 0xB7, 0xB5, 0x1C,
+ 0xC0, 0xFE, 0xDE, 0xDA, 0x7F, 0xF3, 0xAD, 0xB5, 0x87, 0x97, 0xEE, 0xDC, 0xDB, 0xE7, 0x40, 0x5D,
+ 0x82, 0xF3, 0x32, 0x7A, 0x2B, 0x68, 0x5E, 0x4A, 0x05, 0x04, 0x9A, 0x8B, 0x0A, 0xD3, 0x22, 0x8E,
+ 0x41, 0xA9, 0x27, 0x0A, 0x4C, 0xAC, 0xFE, 0xF2, 0xB4, 0x1A, 0xD3, 0x41, 0x9D, 0x17, 0x0D, 0x0E,
+ 0xC3, 0x24, 0xDF, 0xAB, 0xBE, 0x92, 0x34, 0xF4, 0xF0, 0xA5, 0x92, 0x2A, 0x9D, 0xBD, 0xFE, 0xE5,
+ 0xD8, 0x9F, 0x57, 0x54, 0x3F, 0x30, 0x9F, 0x3B, 0xB2, 0x6E, 0x0A, 0xD2, 0xCA, 0x9D, 0xC2, 0xCF,
+ 0x60, 0x3B, 0x6B, 0x5E, 0x66, 0xCC, 0x92, 0x20, 0xD2, 0xEC, 0x4C, 0xC3, 0xCD, 0x74, 0x53, 0x2D,
+ 0x88, 0x16, 0x41, 0x04, 0x96, 0xB0, 0xFF, 0xC7, 0x17, 0xB2, 0x5D, 0xA4, 0xEE, 0x9A, 0x64, 0x1A,
+ 0x0E, 0xD9, 0x99, 0xCF, 0x77, 0xCC, 0x5C, 0x30, 0x63, 0x9D, 0xA4, 0x31, 0x75, 0x29, 0x69, 0x9A,
+ 0xAD, 0x3D, 0x16, 0xAD, 0x0E, 0x3B, 0x7A, 0x43, 0x00, 0xD3, 0x65, 0x6B, 0xFF, 0x97, 0x68, 0x70,
+ 0x1D, 0xFB, 0x6E, 0x51, 0xFF, 0x62, 0x46, 0x94, 0x57, 0x63, 0x17, 0xC1, 0x3D, 0xF1, 0x9D, 0xAF,
+ 0xED, 0x20, 0xF2, 0xC9, 0x3D, 0x56, 0xDC, 0xCC, 0xA2, 0x10, 0xCC, 0x78, 0x61, 0x7D, 0xD9, 0x61,
+ 0x25, 0x62, 0x70, 0x5A, 0x68, 0x30, 0x1D, 0xA5, 0x38, 0x27, 0x35, 0x88, 0x9F, 0xD1, 0x5C, 0x16,
+ 0x21, 0x24, 0x1A, 0xAC, 0xA8, 0x56, 0x5B, 0x89, 0x3D, 0x6C, 0x55, 0x93, 0x90, 0x7E, 0x4B, 0x88,
+ 0xCA, 0xF2, 0x7F, 0x70, 0xC1, 0x5D, 0xB1, 0xA6, 0x52, 0x75, 0xD1, 0x32, 0xCB, 0x95, 0xC7, 0x52,
+ 0x55, 0x52, 0xED, 0x14, 0x45, 0xFE, 0x63, 0xB4, 0xA2, 0xFB, 0x18, 0x39, 0x5E, 0x0B, 0xE4, 0xE6,
+ 0x24, 0x0B, 0x13, 0x4A, 0x7D, 0x16, 0x4B, 0x50, 0x16, 0x42, 0x81, 0x59, 0xCA, 0xA5, 0xEC, 0x96,
+ 0x73, 0x58, 0xEB, 0xE6, 0x70, 0x58, 0xBA, 0xA8, 0x9A, 0xD4, 0xA8, 0xE3, 0x9B, 0x05, 0xC1, 0x97,
+ 0xD4, 0x03, 0x56, 0x72, 0x15, 0x2B, 0xB1, 0xC5, 0x84, 0x4E, 0x9E, 0xFC, 0xE2, 0xAB, 0x8E, 0x8B,
+ 0x8D, 0xAF, 0x63, 0xC5, 0xD9, 0x47, 0xAA, 0xAF, 0x54, 0x9E, 0x5C, 0xE0, 0x4B, 0x8A, 0x57, 0xAE,
+ 0x40, 0x9B, 0x42, 0x3A, 0xD9, 0x5B, 0x35, 0xF1, 0xA1, 0x94, 0x5E, 0x18, 0x4D, 0xBF, 0x33, 0x20,
+ 0xEB, 0xC7, 0x2F, 0xE5, 0x50, 0x9C, 0xDF, 0xB9, 0xDB, 0x27, 0xEE, 0x6D, 0xCA, 0xD6, 0xC8, 0xFB,
+ 0x06, 0x63, 0xF5, 0x2A, 0x26, 0xF3, 0x52, 0x42, 0x22, 0x0D, 0xB2, 0x7D, 0xA0, 0xCF, 0x0B, 0xD7,
+ 0xA3, 0xE1, 0xE8, 0x28, 0x3D, 0xBB, 0x57, 0xDC, 0x5F, 0x75, 0xF9, 0x4D, 0xEE, 0x55, 0x97, 0xDF,
+ 0xEB, 0xB2, 0xDB, 0xA6, 0x2B, 0x3F, 0xB8, 0xD5, 0x58, 0xFB, 0x54, 0xCF, 0x4D, 0xC8, 0x9D, 0xC3,
+ 0x62, 0x37, 0x94, 0x08, 0xE7, 0xE3, 0x97, 0x33, 0xA6, 0x3E, 0xFB, 0x6F, 0xAB, 0x63, 0x6B, 0x2F,
+ 0xA2, 0x79, 0x96, 0x38, 0xFC, 0xFD, 0xAA, 0x0B, 0xE4, 0xB3, 0x2B, 0x1E, 0x4D, 0x67, 0x57, 0x2B,
+ 0x7B, 0xF6, 0x86, 0x6A, 0x19, 0x21, 0xEB, 0x4C, 0xDB, 0xC6, 0x1B, 0xCD, 0x8F, 0xB5, 0x28, 0xA6,
+ 0xDA, 0xCA, 0xC5, 0x8B, 0x90, 0x68, 0xAB, 0x31, 0x87, 0xEF, 0xE0, 0x4D, 0xB2, 0x16, 0x91, 0x80,
+ 0xAE, 0x48, 0xAA, 0x34, 0x75, 0x96, 0x5F, 0x0D, 0x2D, 0x09, 0xB1, 0xC0, 0xAB, 0xF1, 0x90, 0xAF,
+ 0x05, 0x54, 0x8B, 0x53, 0xF8, 0xE2, 0x03, 0x9C, 0x21, 0xC3, 0x54, 0x5B, 0x04, 0xE9, 0xFA, 0x0E,
+ 0x62, 0xA5, 0x16, 0x2C, 0x80, 0x05, 0x1E, 0x84, 0xB1, 0xE4, 0x06, 0x2B, 0xB2, 0x67, 0x38, 0xA1,
+ 0xE7, 0x46, 0x30, 0x04, 0x14, 0x03, 0x78, 0xA3, 0x01, 0x7B, 0xA2, 0x4D, 0xB4, 0x2B, 0x57, 0xF3,
+ 0x42, 0x37, 0xCB, 0xA6, 0x7A, 0x7E, 0x8A, 0xD0, 0xB5, 0x55, 0x4A, 0x16, 0x53, 0x7D, 0x45, 0x69,
+ 0x92, 0x4D, 0xBA, 0xDD, 0x25, 0xC8, 0xB2, 0x99, 0xC3, 0x89, 0x7A, 0xDD, 0x0D, 0x37, 0x5E, 0x9B,
+ 0x7F, 0xED, 0xBE, 0xBA, 0x7E, 0xDF, 0x7B, 0xD9, 0xFE, 0xE7, 0xAB, 0xEF, 0x3F, 0xBE, 0xD1, 0x67,
+ 0x67, 0x0F, 0xBD, 0xEA, 0xBA, 0xA0, 0x61, 0xA9, 0x11, 0xD4, 0xAE, 0x98, 0x9D, 0x81, 0xB0, 0xAE,
+ 0x05, 0xFE, 0x54, 0xBF, 0x7E, 0xFF, 0xE6, 0xF5, 0xEB, 0x6B, 0xFD, 0xB0, 0x5B, 0xDE, 0xA3, 0xE8,
+ 0xB3, 0xD7, 0xD0, 0xBA, 0xD2, 0x5E, 0x43, 0x60, 0xCC, 0xB6, 0x19, 0x25, 0x6B, 0xA1, 0xE9, 0x03,
+ 0x02, 0xDC, 0x44, 0x60, 0xC4, 0x52, 0x28, 0x8D, 0xA5, 0x50, 0x3A, 0x46, 0x53, 0x3E, 0x0F, 0x4B,
+ 0x9F, 0x78, 0x1C, 0xD7, 0xB5, 0x08, 0xC2, 0xC8, 0x54, 0x5F, 0x6F, 0xB1, 0x31, 0xFB, 0xE5, 0x57,
+ 0x5D, 0x5B, 0x6F, 0x42, 0x1A, 0x24, 0xB8, 0xF1, 0xF2, 0x93, 0x3E, 0xD3, 0x04, 0x27, 0xA9, 0x31,
+ 0x1A, 0x69, 0x4A, 0x85, 0x52, 0x17, 0x33, 0xF0, 0x54, 0x8C, 0xCF, 0x51, 0xCA, 0xCE, 0x74, 0x50,
+ 0xBC, 0x17, 0x06, 0xDE, 0x17, 0x58, 0x23, 0x89, 0x7C, 0x9C, 0xAA, 0xD9, 0x72, 0x74, 0xED, 0xD6,
+ 0x0D, 0x37, 0x40, 0xF7, 0x91, 0x8D, 0xD5, 0x67, 0x25, 0x13, 0x4A, 0xD2, 0x78, 0x99, 0x62, 0x45,
+ 0x43, 0x58, 0xE1, 0x6D, 0x90, 0x05, 0xF3, 0x20, 0x0C, 0xE8, 0x76, 0xB2, 0x82, 0x7C, 0x8C, 0x44,
+ 0x52, 0xF4, 0x24, 0x5D, 0xF2, 0x29, 0xD9, 0x07, 0xB0, 0xFC, 0xA9, 0x0E, 0x86, 0x0D, 0x8B, 0xEF,
+ 0x4A, 0x16, 0x60, 0xD3, 0x29, 0xFF, 0x7B, 0xA0, 0xF7, 0xE3, 0xAA, 0xE3, 0x97, 0xD7, 0x57, 0x14,
+ 0xA8, 0xA8, 0xAF, 0x31, 0x87, 0x99, 0xEA, 0xE6, 0xF3, 0x5C, 0xA9, 0xE7, 0xA9, 0xA2, 0xB4, 0xEE,
+ 0x1F, 0xE2, 0x35, 0x24, 0x86, 0x7E, 0xB3, 0x81, 0xB7, 0x99, 0x0D, 0xA3, 0xE1, 0x86, 0x61, 0x43,
+ 0x51, 0xC3, 0x07, 0xB2, 0x00, 0x69, 0x57, 0x28, 0x39, 0xF5, 0x0F, 0x66, 0x45, 0x39, 0x73, 0x6E,
+ 0x3F, 0xA4, 0x04, 0x6C, 0xDF, 0x0F, 0xD2, 0x66, 0x4B, 0x57, 0x24, 0x81, 0x93, 0x3C, 0x8C, 0xCC,
+ 0x6E, 0x97, 0x92, 0xB2, 0x6F, 0x82, 0x4D, 0x33, 0x8C, 0xE3, 0x9F, 0x6F, 0x03, 0x72, 0xF7, 0x7D,
+ 0x0C, 0x1A, 0xC2, 0x03, 0x76, 0x1F, 0xFF, 0xC0, 0xF8, 0x14, 0xEC, 0x40, 0x83, 0xB6, 0x81, 0xAE,
+ 0x6D, 0x51, 0x77, 0xBA, 0xA4, 0xEE, 0x29, 0xD4, 0x36, 0x7C, 0x4E, 0x61, 0x90, 0x0D, 0x8F, 0x2D,
+ 0x7B, 0xC0, 0x2E, 0x86, 0x53, 0x5D, 0xA4, 0x79, 0x7A, 0xB7, 0xE0, 0x83, 0x43, 0xB7, 0x8C, 0x9D,
+ 0xE0, 0x63, 0x0D, 0x0A, 0x3E, 0xF8, 0xF9, 0x01, 0x3E, 0x98, 0x8F, 0x23, 0x1F, 0x8B, 0x0B, 0x64,
+ 0xC3, 0x23, 0x4F, 0x6E, 0xA1, 0x75, 0x2C, 0xBE, 0xDE, 0x09, 0x8E, 0x63, 0xD8, 0x6C, 0xC1, 0x84,
+ 0xE5, 0xC9, 0xFA, 0xEC, 0x02, 0x14, 0x08, 0x3C, 0x40, 0x8F, 0xA0, 0x8A, 0x99, 0x70, 0x11, 0xA1,
+ 0x53, 0xAE, 0x48, 0x34, 0x17, 0x9E, 0xCC, 0xE5, 0xEA, 0x13, 0x5F, 0x2B, 0xC3, 0xF3, 0x25, 0x98,
+ 0xF9, 0x26, 0x30, 0x53, 0x73, 0xE9, 0x2A, 0xA7, 0xC4, 0xEB, 0x3E, 0x69, 0xBC, 0x2A, 0x75, 0x17,
+ 0x6D, 0xA7, 0x2B, 0xED, 0x08, 0x1F, 0x92, 0x82, 0x7F, 0x29, 0xDD, 0xF2, 0xEB, 0xD2, 0xCE, 0x8B,
+ 0x83, 0x00, 0x1A, 0x21, 0x87, 0x64, 0x66, 0x84, 0x2B, 0xD5, 0x1C, 0x6E, 0xC0, 0xCA, 0x80, 0xF7,
+ 0x0A, 0xDB, 0x67, 0x3F, 0x83, 0x1F, 0xE4, 0x5F, 0xAE, 0x41, 0x4B, 0xF2, 0x4B, 0xC9, 0x80, 0x2A,
+ 0x6D, 0x62, 0x45, 0xAC, 0x55, 0x48, 0x2A, 0x26, 0x43, 0x07, 0xC8, 0x71, 0xE2, 0x33, 0xDA, 0x2A,
+ 0x1B, 0xC7, 0xE3, 0x82, 0x5C, 0xCF, 0x11, 0xDC, 0xE1, 0x91, 0x96, 0xFB, 0x63, 0x06, 0xE9, 0xE6,
+ 0x26, 0x2B, 0x34, 0x7A, 0xF0, 0x7E, 0x8E, 0x37, 0x16, 0xC8, 0x27, 0x21, 0xFE, 0x23, 0xC3, 0xFD,
+ 0x07, 0x80, 0xAF, 0xE4, 0xBD, 0x47, 0x71, 0xF0, 0xEE, 0x28, 0x0A, 0x2A, 0xF6, 0xF2, 0x34, 0xE4,
+ 0x03, 0xDE, 0x07, 0x18, 0xC0, 0x71, 0xEE, 0x10, 0xFD, 0x70, 0x3D, 0xEA, 0x8C, 0x8F, 0x81, 0xBE,
+ 0xC5, 0x5D, 0x0E, 0x7E, 0xF8, 0xB1, 0x1E, 0xFE, 0x72, 0xCE, 0x70, 0x84, 0x8C, 0xD8, 0xF0, 0x75,
+ 0xB6, 0xD4, 0x8F, 0xB3, 0x9F, 0x7D, 0x20, 0xB0, 0x79, 0x70, 0x06, 0x8E, 0x96, 0x79, 0xEC, 0xBD,
+ 0x73, 0x03, 0xDA, 0x81, 0xFF, 0xC0, 0xA9, 0x80, 0x89, 0xC2, 0xCA, 0x83, 0x1C, 0x89, 0x72, 0xCF,
+ 0xE1, 0x3D, 0x87, 0xC6, 0x5F, 0xDD, 0x74, 0xEE, 0x7E, 0x90, 0x85, 0x26, 0x70, 0xFE, 0xCD, 0xFD,
+ 0x88, 0xA5, 0x28, 0x65, 0x1B, 0x28, 0x65, 0x2D, 0x75, 0x5D, 0x3C, 0x2D, 0x85, 0x9E, 0x55, 0x6F,
+ 0xF6, 0x06, 0x44, 0xA7, 0xC1, 0x02, 0x0E, 0xEE, 0x98, 0xAD, 0x40, 0xF0, 0xEF, 0xD5, 0x18, 0x5A,
+ 0x91, 0x2E, 0xEA, 0x7C, 0x0D, 0x62, 0x25, 0xA5, 0x6E, 0x44, 0x0F, 0x9D, 0x9F, 0xC0, 0x21, 0x69,
+ 0x26, 0xB3, 0x8F, 0x70, 0xF0, 0x9D, 0x88, 0xE5, 0x55, 0x42, 0xA1, 0x7A, 0x49, 0x20, 0xAD, 0x81,
+ 0x93, 0xE7, 0x8B, 0xFC, 0x8C, 0xE7, 0xE6, 0xCF, 0xBC, 0x51, 0xA8, 0xBC, 0xB8, 0xF2, 0x2F, 0xD4,
+ 0xB6, 0x4A, 0xCF, 0x17, 0xE8, 0x3D, 0xF4, 0xDD, 0x01, 0x50, 0x3C, 0x42, 0xA8, 0x44, 0x90, 0xA8,
+ 0x82, 0xC9, 0xB6, 0x87, 0x85, 0xC3, 0xE0, 0x79, 0x44, 0x97, 0xC2, 0xE5, 0xCB, 0x4E, 0x26, 0x6D,
+ 0xFF, 0x98, 0xDB, 0x14, 0x71, 0x71, 0x33, 0x5F, 0x07, 0xF4, 0x03, 0xF9, 0xBF, 0x0D, 0x98, 0x1C,
+ 0x46, 0x33, 0xE1, 0x15, 0xBC, 0xBD, 0x16, 0x3C, 0x20, 0xD1, 0x0D, 0x12, 0x3A, 0x5B, 0x6C, 0x22,
+ 0x56, 0x6C, 0x01, 0x5F, 0xB8, 0x9D, 0xBB, 0x10, 0x09, 0x77, 0xB7, 0x70, 0x46, 0x06, 0x52, 0xC5,
+ 0xF9, 0x75, 0x83, 0x4E, 0xBD, 0x4D, 0x8A, 0x45, 0x14, 0x84, 0xEC, 0x0E, 0x1C, 0x3B, 0x03, 0xDA,
+ 0xD4, 0xBB, 0x7A, 0xCB, 0x88, 0xA6, 0xF0, 0x30, 0x82, 0xA9, 0xE5, 0x80, 0xB6, 0x9A, 0xE4, 0x02,
+ 0xE9, 0x7C, 0x21, 0x6F, 0x83, 0xC7, 0xD2, 0x86, 0x96, 0xCB, 0xF9, 0x49, 0x57, 0xD8, 0x4C, 0x1B,
+ 0xDD, 0x86, 0xA3, 0x1D, 0x8F, 0xE8, 0x9F, 0xF4, 0x59, 0x97, 0x79, 0x81, 0xEE, 0x04, 0x57, 0xB4,
+ 0x13, 0x92, 0x68, 0x49, 0x57, 0x6D, 0xCB, 0x69, 0x45, 0x17, 0x53, 0xFA, 0x4B, 0xF0, 0xEB, 0x05,
+ 0xCE, 0x7C, 0x64, 0xC6, 0x23, 0x13, 0xEA, 0x17, 0xD1, 0x85, 0xFE, 0xD0, 0xA4, 0xFA, 0x05, 0xE7,
+ 0x9E, 0xFB, 0xBB, 0x90, 0xC2, 0x08, 0x2E, 0x2E, 0x9C, 0x94, 0xD0, 0x4D, 0x1A, 0x69, 0x6C, 0x5A,
+ 0xD5, 0x39, 0xF5, 0x7D, 0xAE, 0x48, 0xB0, 0xAF, 0x6C, 0xF5, 0x39, 0x00, 0xC3, 0x51, 0x94, 0x59,
+ 0x64, 0x13, 0x0D, 0xBB, 0xDF, 0x90, 0x71, 0x9C, 0x7D, 0x96, 0xD9, 0x44, 0x03, 0xB3, 0x09, 0xCB,
+ 0x1E, 0xE3, 0xDF, 0x06, 0x2C, 0x5A, 0x9D, 0x4A, 0x24, 0x05, 0x8D, 0x81, 0xDD, 0x80, 0x60, 0xDE,
+ 0xB0, 0xE0, 0x01, 0xE1, 0xBF, 0x31, 0x6C, 0x60, 0xF8, 0xC7, 0x87, 0xE4, 0x3D, 0x28, 0x78, 0x8F,
+ 0x1A, 0xC2, 0x14, 0x1B, 0x18, 0xD6, 0xE1, 0xE4, 0xEA, 0x3B, 0x0D, 0xAD, 0x3B, 0x13, 0x3A, 0xAB,
+ 0x72, 0xAC, 0xE7, 0x61, 0x97, 0x79, 0xB0, 0xCC, 0xA0, 0x8E, 0x4B, 0xCF, 0xE4, 0x5C, 0xC6, 0x47,
+ 0xE4, 0x1A, 0x8E, 0x0A, 0x9E, 0x80, 0xAB, 0x67, 0x49, 0x66, 0x97, 0x79, 0x5A, 0x26, 0x67, 0x8A,
+ 0x4F, 0xC1, 0x75, 0xAC, 0x72, 0xED, 0x3F, 0x86, 0xA9, 0x7D, 0x59, 0xCB, 0xA4, 0x77, 0xE6, 0x72,
+ 0xFB, 0x9C, 0x4B, 0xBF, 0xC7, 0x45, 0x1B, 0x71, 0xC9, 0x46, 0x39, 0x4F, 0x85, 0xE5, 0xF0, 0x5C,
+ 0x9E, 0xC3, 0x3F, 0x81, 0xE7, 0xF8, 0x8F, 0xE0, 0xC9, 0xF3, 0x3F, 0xC5, 0xC0, 0xF1, 0x4C, 0x2F,
+ 0xED, 0x9B, 0x1B, 0xE9, 0xF9, 0xF6, 0x6D, 0xF7, 0xE1, 0x4F, 0x03, 0x82, 0x33, 0xF8, 0xA3, 0xE6,
+ 0x4F, 0x1B, 0x3F, 0x8D, 0x8C, 0x9E, 0xF6, 0xD6, 0x36, 0xC6, 0xDA, 0xDB, 0x91, 0x61, 0xF5, 0xD8,
+ 0xBB, 0xA9, 0xBD, 0xB5, 0xC4, 0x63, 0x6C, 0x58, 0x16, 0x7F, 0x0C, 0x78, 0xE3, 0x10, 0x1E, 0x26,
+ 0x7B, 0x5C, 0x1A, 0xD6, 0x88, 0xBD, 0x5F, 0xB2, 0x26, 0x1B, 0x86, 0xDB, 0xE2, 0x61, 0x1B, 0xD6,
+ 0x98, 0x3D, 0xC6, 0xAC, 0x6D, 0x88, 0x5C, 0x87, 0xDA, 0x57, 0x5C, 0x60, 0x1A, 0x7F, 0x81, 0x15,
+ 0xB2, 0xB3, 0x6A, 0x83, 0xA7, 0xBB, 0x0D, 0xB6, 0xD2, 0xDA, 0x85, 0xF2, 0xB4, 0xE6, 0x33, 0x1E,
+ 0x10, 0x48, 0x6B, 0xA7, 0x20, 0xC9, 0xC5, 0x94, 0x30, 0xF4, 0x51, 0x71, 0x44, 0x67, 0x29, 0x9E,
+ 0xA1, 0x03, 0x8E, 0xE8, 0xAD, 0x82, 0x07, 0x9C, 0x65, 0xB1, 0xFC, 0x7C, 0x0D, 0xF9, 0x69, 0xB4,
+ 0xCC, 0x9A, 0xC4, 0xA0, 0x52, 0x69, 0x80, 0x0B, 0xA4, 0x43, 0xE3, 0xB7, 0xF1, 0x1D, 0x49, 0x7F,
+ 0x80, 0xDC, 0xA0, 0xD9, 0x02, 0x98, 0xA5, 0x95, 0x16, 0x72, 0x45, 0xBF, 0x6D, 0x5B, 0x13, 0x32,
+ 0xA3, 0xDF, 0x5A, 0x13, 0xB3, 0x60, 0x8B, 0xB5, 0x3F, 0x97, 0x7A, 0x2B, 0x96, 0x69, 0xB1, 0x0C,
+ 0x11, 0x45, 0x44, 0xB4, 0x81, 0x5C, 0x1E, 0x00, 0x73, 0xD1, 0x84, 0xA7, 0x7A, 0x12, 0xBC, 0x66,
+ 0x83, 0x26, 0x9A, 0x7E, 0x41, 0x3A, 0x9C, 0xC0, 0xA0, 0x17, 0xE5, 0x21, 0xBF, 0xA9, 0x5F, 0x6E,
+ 0x62, 0xEA, 0x86, 0x1A, 0x2F, 0x96, 0x33, 0x22, 0x8A, 0x0D, 0xA7, 0x69, 0x20, 0xC0, 0xFB, 0x2A,
+ 0x09, 0x44, 0x6C, 0xFF, 0x34, 0xC5, 0x3B, 0xCF, 0xDB, 0x24, 0xBC, 0x4A, 0xAB, 0xE9, 0x6C, 0xE8,
+ 0xD5, 0x9A, 0x40, 0x1C, 0xD4, 0xD6, 0x41, 0x04, 0x06, 0xD3, 0x60, 0x99, 0x18, 0x47, 0x8C, 0x15,
+ 0x58, 0xD4, 0xB4, 0x71, 0x09, 0x9F, 0x78, 0x6C, 0x6B, 0xE0, 0x0C, 0x71, 0x4E, 0x0F, 0xB8, 0x0E,
+ 0x1B, 0xC8, 0x88, 0xC5, 0x21, 0xA2, 0xDA, 0xFF, 0x5C, 0x37, 0xFC, 0xD8, 0xDB, 0xAC, 0x61, 0x0F,
+ 0x3B, 0x4B, 0x42, 0x5F, 0x85, 0x04, 0x3F, 0x7E, 0xBF, 0x7D, 0x03, 0x7B, 0x27, 0x92, 0xEC, 0x56,
+ 0x27, 0x88, 0x22, 0x92, 0xFE, 0x78, 0xF3, 0xD3, 0xDB, 0x29, 0x35, 0x50, 0x93, 0x06, 0x6C, 0xF3,
+ 0x33, 0x35, 0xF8, 0x71, 0x25, 0x47, 0xA5, 0x78, 0x08, 0xB1, 0x87, 0xBE, 0xC1, 0x52, 0xCB, 0xBB,
+ 0x05, 0x46, 0x45, 0xA3, 0xD4, 0xC7, 0xC3, 0x96, 0xDD, 0x72, 0xD8, 0xEA, 0x68, 0x2A, 0xBD, 0x4C,
+ 0xBD, 0x57, 0x3E, 0x11, 0xB0, 0x4A, 0x71, 0x17, 0x86, 0x90, 0xA6, 0x69, 0x44, 0x17, 0x56, 0xEB,
+ 0xE1, 0x38, 0x86, 0x61, 0x11, 0x82, 0x99, 0xE2, 0xAD, 0x45, 0x4C, 0x03, 0xB3, 0x0C, 0x31, 0x25,
+ 0x01, 0x6C, 0x68, 0xCC, 0xE0, 0x2C, 0x80, 0x09, 0xA9, 0xCC, 0x37, 0xF5, 0x3D, 0x61, 0x77, 0x06,
+ 0x59, 0x07, 0xE4, 0xA3, 0x4D, 0x69, 0x72, 0x25, 0xD3, 0xAD, 0x5A, 0x75, 0x07, 0x33, 0x69, 0x83,
+ 0xB2, 0x47, 0x6B, 0xDF, 0x62, 0xC9, 0x00, 0xEA, 0x29, 0x98, 0x9A, 0x10, 0xBC, 0x25, 0x3F, 0xAE,
+ 0x0B, 0x07, 0xE2, 0x68, 0x4B, 0x6F, 0x5B, 0xA0, 0x57, 0x4E, 0xDF, 0x14, 0xFD, 0x10, 0x75, 0x3B,
+ 0x78, 0x3A, 0x6D, 0xBD, 0x78, 0xD1, 0x64, 0xCA, 0xBA, 0xF9, 0x30, 0x13, 0x46, 0xC1, 0xB2, 0x6E,
+ 0x80, 0x18, 0x15, 0x57, 0x14, 0xB8, 0xA9, 0x81, 0x18, 0x6D, 0xF6, 0x89, 0x16, 0x28, 0x63, 0x19,
+ 0x36, 0xA0, 0x84, 0x61, 0x5B, 0x88, 0x35, 0x36, 0x7E, 0x1E, 0xF2, 0xC7, 0x88, 0xB5, 0x59, 0x88,
+ 0x0F, 0x6F, 0x2D, 0x5B, 0xBC, 0x5B, 0x1A, 0x0E, 0xB3, 0xCE, 0x40, 0x0C, 0xBC, 0x52, 0xD0, 0xEE,
+ 0x2D, 0x1E, 0x91, 0xB7, 0xF8, 0x6C, 0x68, 0xF7, 0x36, 0x3C, 0x00, 0x59, 0xB7, 0x36, 0x8B, 0x80,
+ 0x15, 0x0E, 0xFC, 0x6B, 0x5B, 0x08, 0x6F, 0x35, 0xBA, 0x72, 0x89, 0x39, 0xAB, 0x81, 0xE0, 0x64,
+ 0x0A, 0x56, 0x3D, 0xCE, 0xCA, 0x32, 0xCF, 0xE0, 0x05, 0x6B, 0x3E, 0xE0, 0xD3, 0xAF, 0xF0, 0xE9,
+ 0x3F, 0x91, 0xCF, 0xB8, 0xC2, 0x67, 0x7C, 0x06, 0x1F, 0x59, 0x3B, 0x60, 0xF9, 0x13, 0x2C, 0xB3,
+ 0x71, 0x75, 0xF3, 0x52, 0xE4, 0x6A, 0x9F, 0x44, 0xB2, 0xF6, 0xA9, 0x91, 0x57, 0x48, 0x65, 0x19,
+ 0x3D, 0xB9, 0x77, 0xC0, 0x7C, 0x5D, 0x51, 0x47, 0x6C, 0x80, 0x1F, 0x33, 0xD0, 0x55, 0xCC, 0x04,
+ 0xED, 0xEC, 0xA2, 0x01, 0x49, 0xB9, 0x9B, 0x82, 0x2B, 0x4F, 0x3F, 0x83, 0x00, 0xD1, 0x97, 0x52,
+ 0x56, 0x5D, 0x94, 0x23, 0x67, 0x0D, 0x9C, 0xB9, 0x42, 0xCC, 0x75, 0x2E, 0xF2, 0x61, 0xAC, 0x29,
+ 0xDE, 0xBC, 0x9C, 0x81, 0x6C, 0x5C, 0xCA, 0x8A, 0x3D, 0x8A, 0xB1, 0x7C, 0x84, 0x34, 0x3B, 0xF3,
+ 0x79, 0x43, 0x9D, 0xEF, 0x93, 0x28, 0x1C, 0x7D, 0xD2, 0x15, 0x57, 0x7E, 0x09, 0xF1, 0x83, 0x92,
+ 0x26, 0x43, 0xAA, 0xB2, 0xE8, 0x7A, 0xA3, 0x85, 0x99, 0x26, 0x72, 0x56, 0xD3, 0xC5, 0x92, 0x54,
+ 0x38, 0x1F, 0xF7, 0x49, 0x7C, 0x43, 0x5F, 0x28, 0xFC, 0x2A, 0x04, 0xBF, 0x0A, 0xAB, 0x7E, 0x15,
+ 0x0A, 0xBF, 0x9A, 0x56, 0xFD, 0x2A, 0xFC, 0x43, 0xFD, 0x4A, 0xF1, 0xAA, 0x4B, 0x1E, 0x9E, 0x2F,
+ 0x31, 0xD0, 0x42, 0x90, 0x86, 0x78, 0x2C, 0xDE, 0x06, 0x18, 0x72, 0xFB, 0xE8, 0x45, 0x7D, 0xF4,
+ 0xBB, 0x01, 0x73, 0x3E, 0x9B, 0x0D, 0xC5, 0x07, 0x06, 0x6A, 0x74, 0xC5, 0x1E, 0xA3, 0x1F, 0xB0,
+ 0x77, 0x9B, 0x7B, 0x22, 0xF4, 0x9F, 0x17, 0xA7, 0x0B, 0xA3, 0xD2, 0x71, 0x5B, 0xCA, 0x27, 0x00,
+ 0x2D, 0xDF, 0xFE, 0x3C, 0xAB, 0x39, 0xBC, 0x99, 0x53, 0x51, 0x57, 0x09, 0xF5, 0xCA, 0x76, 0x85,
+ 0xC5, 0x76, 0x39, 0x72, 0xBF, 0x2A, 0x7D, 0x25, 0xE3, 0x90, 0xF2, 0x3C, 0xD9, 0x56, 0x8E, 0xCF,
+ 0xFF, 0x24, 0x73, 0x39, 0x1A, 0xEF, 0x8A, 0x9A, 0x54, 0x39, 0xE4, 0x1D, 0x25, 0x60, 0xA5, 0x3A,
+ 0x75, 0xAC, 0x3C, 0x2E, 0x16, 0x29, 0x89, 0xB0, 0x76, 0xCC, 0x94, 0xE2, 0x08, 0xAF, 0x12, 0x9A,
+ 0xFA, 0x0F, 0xFC, 0x83, 0xE6, 0x63, 0x17, 0x8E, 0x89, 0x17, 0xB8, 0x99, 0x3C, 0x3D, 0x00, 0x73,
+ 0x2C, 0xE5, 0x4F, 0x6C, 0x10, 0x81, 0x0C, 0xF6, 0x80, 0xA7, 0x4C, 0xC0, 0x4E, 0xB1, 0x85, 0x31,
+ 0xB0, 0x81, 0x71, 0xBA, 0x3D, 0xC1, 0x1B, 0xC6, 0x94, 0xD9, 0x2B, 0x05, 0x60, 0x71, 0x52, 0x4B,
+ 0x52, 0x88, 0x6C, 0x70, 0xB6, 0x7D, 0xCF, 0xCB, 0x35, 0xEC, 0xA2, 0xA1, 0xE0, 0xCD, 0x0A, 0x46,
+ 0x90, 0x13, 0x80, 0x6A, 0xA3, 0x4D, 0x18, 0x3E, 0x9B, 0x92, 0xCA, 0x3C, 0x9E, 0x64, 0x08, 0xF3,
+ 0x74, 0xC0, 0x09, 0xD7, 0xCD, 0x96, 0x32, 0x9D, 0x3A, 0x94, 0x45, 0x53, 0x9E, 0x4A, 0x44, 0xE4,
+ 0x4E, 0xFB, 0x9F, 0x9F, 0xDE, 0xFE, 0x48, 0x69, 0x22, 0x4E, 0xF0, 0x70, 0xA0, 0xD6, 0xBB, 0xCC,
+ 0x04, 0xBE, 0xE5, 0x3F, 0x85, 0x98, 0xC2, 0x9A, 0x20, 0x6E, 0x42, 0x26, 0x85, 0xAD, 0xBC, 0x6C,
+ 0x75, 0x41, 0x22, 0x2F, 0xF6, 0xC9, 0xC7, 0x0F, 0x6F, 0x9A, 0xB4, 0x65, 0xB0, 0x4E, 0x96, 0x34,
+ 0xA8, 0x1D, 0x6A, 0xE2, 0x72, 0x7C, 0x73, 0x45, 0xD1, 0xB6, 0xD5, 0x61, 0xAE, 0xD2, 0x29, 0x2A,
+ 0x59, 0xA2, 0xAA, 0x15, 0xC2, 0x9A, 0xA3, 0x4E, 0x1C, 0xC1, 0xE2, 0xFC, 0x2D, 0xA6, 0x4A, 0xC4,
+ 0x5B, 0xE1, 0xAF, 0x0B, 0xA7, 0x79, 0x6E, 0xD0, 0xDA, 0x41, 0xC6, 0xD9, 0x9F, 0x4E, 0xA3, 0x0E,
+ 0x1B, 0x83, 0xC9, 0x26, 0x69, 0x41, 0x93, 0x6D, 0x9A, 0xD8, 0xC8, 0xD3, 0x2B, 0xA9, 0xE3, 0xFF,
+ 0xBA, 0x7E, 0xF7, 0x33, 0x20, 0x7A, 0x0A, 0x09, 0x2E, 0x8E, 0xCF, 0x92, 0x38, 0xCA, 0xC8, 0x0D,
+ 0xB9, 0xA7, 0x27, 0x0C, 0xF6, 0x84, 0x88, 0xA2, 0xDA, 0x66, 0xD4, 0xA6, 0xC4, 0x7B, 0x12, 0xC2,
+ 0x36, 0x56, 0xCA, 0x23, 0x7B, 0x5C, 0x4D, 0x42, 0xA2, 0xA6, 0xFE, 0x9F, 0xAF, 0x6E, 0xE0, 0x5C,
+ 0x6F, 0x3C, 0x33, 0x5B, 0xD0, 0x94, 0xC1, 0xF6, 0x34, 0x2B, 0xDB, 0xC5, 0xCB, 0x8C, 0xBB, 0xBC,
+ 0x8D, 0xD9, 0x37, 0x2C, 0x8C, 0x08, 0xD0, 0x85, 0xED, 0x4C, 0x36, 0xAC, 0x04, 0x20, 0xB3, 0x6F,
+ 0xC2, 0x7F, 0xDF, 0xE0, 0x77, 0x45, 0xCA, 0xFC, 0x1F, 0x90, 0xBD, 0x1E, 0x5F, 0x17, 0xDE, 0xB6,
+ 0xB4, 0x3A, 0x3C, 0xA3, 0x3D, 0xE1, 0x7E, 0xE5, 0x7B, 0x20, 0x49, 0x20, 0x4A, 0xA1, 0x80, 0x6A,
+ 0x5A, 0xA7, 0xD3, 0xD1, 0x2F, 0xF0, 0xF0, 0xF0, 0x1A, 0xAF, 0xFF, 0x9B, 0x66, 0x0B, 0xF3, 0xDD,
+ 0xFD, 0x9E, 0x8B, 0x74, 0x12, 0x06, 0x64, 0xF1, 0xB6, 0xC5, 0x31, 0x07, 0x8F, 0x0E, 0xE6, 0xB3,
+ 0xA9, 0xAC, 0xB7, 0xB4, 0x76, 0x4F, 0x96, 0x09, 0x45, 0x3A, 0x01, 0x28, 0x6C, 0xE1, 0xC7, 0x0D,
+ 0xCE, 0x29, 0x7C, 0xE3, 0x75, 0x9C, 0xAE, 0x5F, 0xBA, 0xD4, 0x75, 0xA2, 0x8E, 0x9B, 0x24, 0xB8,
+ 0x49, 0x1C, 0x8E, 0xD4, 0x3C, 0xBB, 0x9A, 0x72, 0x52, 0x35, 0xD9, 0xDC, 0xF1, 0x90, 0x89, 0x65,
+ 0x1D, 0xC3, 0x57, 0x33, 0xF7, 0x8B, 0x50, 0xE0, 0xEB, 0xB5, 0x5E, 0x30, 0xF7, 0x8D, 0x90, 0x87,
+ 0x4B, 0xA3, 0x98, 0xAF, 0x28, 0x69, 0x1B, 0xA1, 0x71, 0xC8, 0xA0, 0xC5, 0x14, 0xED, 0xD6, 0x78,
+ 0xB2, 0xE3, 0x0A, 0x4B, 0x7B, 0xFF, 0xEE, 0xFA, 0x06, 0x4F, 0x13, 0x8C, 0x8F, 0xCE, 0x2C, 0xCE,
+ 0xED, 0x70, 0x15, 0x76, 0x20, 0x32, 0xBD, 0xBA, 0x05, 0x8E, 0x6F, 0x01, 0x90, 0x09, 0x00, 0x2C,
+ 0x6A, 0x87, 0x17, 0x9D, 0x01, 0x46, 0x8C, 0x67, 0x16, 0x0E, 0x8D, 0x23, 0x1C, 0x5A, 0xF1, 0x38,
+ 0xE6, 0x5E, 0x53, 0x37, 0xF7, 0xAF, 0xA7, 0xED, 0xD5, 0x13, 0x76, 0x29, 0xF7, 0xB9, 0xF3, 0x2C,
+ 0x4B, 0x4C, 0xA7, 0x3B, 0x35, 0x3E, 0xAA, 0xA0, 0x81, 0x5B, 0x46, 0x03, 0xE1, 0xBC, 0xEC, 0x5F,
+ 0x24, 0x34, 0xF5, 0xEF, 0xC0, 0xF9, 0xD8, 0x6F, 0xE1, 0xF1, 0x4C, 0x07, 0x1B, 0xE0, 0x3F, 0x83,
+ 0x63, 0x36, 0x28, 0x86, 0x79, 0x6D, 0xD4, 0xDA, 0x17, 0x7E, 0xAB, 0x5E, 0x10, 0xFC, 0xA9, 0x9E,
+ 0xBB, 0xB8, 0x3B, 0xC7, 0x77, 0xF1, 0x92, 0x40, 0x8D, 0x9C, 0x0F, 0x7B, 0x2D, 0x48, 0x79, 0x10,
+ 0xEB, 0x2A, 0x17, 0x37, 0xDA, 0xB7, 0x7A, 0x4B, 0xCA, 0x7D, 0x7C, 0x13, 0xEE, 0x7E, 0xA7, 0x73,
+ 0x17, 0xD7, 0x2F, 0x4F, 0x33, 0x02, 0x65, 0xFE, 0x27, 0x90, 0x73, 0xC5, 0x9D, 0x88, 0x49, 0xE7,
+ 0xAB, 0xFC, 0xC4, 0x58, 0xF1, 0x73, 0x01, 0x39, 0x91, 0xF8, 0x51, 0xD5, 0x54, 0xC7, 0x5F, 0x55,
+ 0x9D, 0xF6, 0x0C, 0xB6, 0xFD, 0x27, 0x10, 0x4C, 0x22, 0x52, 0x19, 0xC5, 0x8C, 0x07, 0x01, 0x4A,
+ 0xEF, 0xEA, 0x8F, 0x04, 0x26, 0x8E, 0x4B, 0x05, 0xDD, 0x23, 0xF0, 0x88, 0xFF, 0xFE, 0x03, 0x96,
+ 0xF2, 0xD7, 0x40, 0x92, 0x6A, 0x61, 0xE7, 0x82, 0xD2, 0xC1, 0xEE, 0x9E, 0xB8, 0x67, 0x3B, 0xC1,
+ 0x46, 0xDE, 0xB8, 0x3D, 0xCD, 0xC4, 0xFE, 0x10, 0x07, 0x39, 0xC6, 0x44, 0x5E, 0x38, 0x27, 0xF7,
+ 0x67, 0x7A, 0x97, 0x04, 0xD8, 0x3F, 0xCD, 0x17, 0x0F, 0xC9, 0x15, 0x19, 0x9D, 0x83, 0xBC, 0xAE,
+ 0x82, 0xE4, 0x88, 0x3B, 0x3A, 0x16, 0x7C, 0x64, 0xED, 0xF1, 0xC5, 0x0B, 0xBD, 0x5F, 0xFE, 0xAA,
+ 0xF6, 0xFE, 0xF6, 0x9B, 0xC0, 0x7C, 0x81, 0x75, 0x0B, 0x17, 0x6C, 0xDC, 0xD7, 0x5B, 0x86, 0x6E,
+ 0xC3, 0xE1, 0x56, 0x8E, 0x6A, 0x95, 0x07, 0x79, 0x6E, 0xE4, 0x81, 0x84, 0x18, 0x16, 0x1C, 0x16,
+ 0x37, 0x70, 0xCE, 0x9E, 0x3A, 0x9E, 0xC3, 0xA5, 0x11, 0x4D, 0x4D, 0xF0, 0xC1, 0x87, 0x1C, 0xDB,
+ 0x09, 0x3A, 0x58, 0x7B, 0xEC, 0x9B, 0x06, 0x9D, 0x66, 0x84, 0xBE, 0x41, 0x53, 0x01, 0x2D, 0x37,
+ 0x15, 0x6B, 0x8F, 0x2E, 0xA6, 0xD6, 0xC3, 0x00, 0xC1, 0xB7, 0x26, 0x3A, 0xC7, 0x0E, 0x0B, 0x93,
+ 0xEE, 0x5B, 0xED, 0xC8, 0x88, 0x66, 0x7D, 0x13, 0x8E, 0xEC, 0x1E, 0x58, 0x74, 0x9A, 0xCF, 0x0F,
+ 0xA9, 0x7E, 0x18, 0xF3, 0xBB, 0x59, 0xD0, 0x31, 0x9A, 0x0F, 0x1E, 0x31, 0x0C, 0x8B, 0xF4, 0xCA,
+ 0xE1, 0xB2, 0xA4, 0x3A, 0x0C, 0x95, 0x8F, 0x8A, 0xA5, 0x45, 0x30, 0x15, 0xB8, 0xF1, 0x16, 0x6F,
+ 0x3A, 0x9B, 0x27, 0xDC, 0xF9, 0x9C, 0x04, 0xFD, 0x38, 0xAD, 0xBC, 0xC4, 0x3E, 0x40, 0x61, 0xF6,
+ 0xDB, 0x73, 0xA5, 0x24, 0x5F, 0xC9, 0xE4, 0x4F, 0xC9, 0x73, 0x94, 0x27, 0x43, 0x76, 0x61, 0xB3,
+ 0xA7, 0xE9, 0x95, 0x3B, 0x67, 0xB1, 0x91, 0xE2, 0x50, 0x67, 0x9C, 0x88, 0xBA, 0x75, 0x97, 0xC2,
+ 0x15, 0x6A, 0xBC, 0x35, 0x65, 0xC3, 0xBE, 0xFD, 0x78, 0xFD, 0xEA, 0x83, 0x7A, 0x62, 0xC3, 0xAC,
+ 0x04, 0xC4, 0x8B, 0x28, 0x64, 0x2B, 0x17, 0xFA, 0x8B, 0xF7, 0xDF, 0x5D, 0x5F, 0xFF, 0xF3, 0xDD,
+ 0x87, 0x97, 0xF5, 0x43, 0x28, 0x0E, 0xB9, 0xFE, 0xF8, 0xFD, 0x4F, 0x6F, 0x6E, 0xA6, 0x5B, 0xCC,
+ 0x2A, 0x83, 0x3A, 0xC4, 0x0F, 0x1E, 0x38, 0xB9, 0xC1, 0xB1, 0x2D, 0x50, 0x8E, 0x6D, 0x2F, 0x5E,
+ 0x00, 0x84, 0x3F, 0x83, 0x26, 0xE9, 0x9A, 0x65, 0x0B, 0xD8, 0x1B, 0x81, 0x7A, 0x76, 0x8A, 0x58,
+ 0xD8, 0x08, 0xE4, 0xD9, 0x09, 0x95, 0xAA, 0x16, 0xB0, 0x21, 0x24, 0x39, 0x77, 0x41, 0xE4, 0xC7,
+ 0x77, 0x35, 0xD1, 0xE2, 0xF8, 0xED, 0x89, 0x73, 0xD5, 0x15, 0xD7, 0xD3, 0x57, 0x5D, 0xF1, 0x83,
+ 0x19, 0xF6, 0xFF, 0xCC, 0xF9, 0x7F, 0xE5, 0xCC, 0x32, 0xCA, 0x3A, 0x47, 0x00, 0x00
+};
diff --git a/src/sd_ESP32.cpp b/src/sd_ESP32.cpp
new file mode 100644
index 0000000..07e5984
--- /dev/null
+++ b/src/sd_ESP32.cpp
@@ -0,0 +1,300 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifdef ARDUINO_ARCH_ESP32
+#include "../../inc/MarlinConfigPre.h"
+#if ENABLED(SDSUPPORT)
+#include "../../sd/cardreader.h"
+#include "../../sd/SdVolume.h"
+#include "../../sd/SdFatStructs.h"
+#include "../../sd/SdFile.h"
+#include "sd_ESP32.h"
+
+
+//Cannot move to static variable due to conflict with ESP32 SD library
+SdFile workDir;
+dir_t dir_info;
+SdVolume sd_volume;
+
+
+ESP_SD::ESP_SD(){
+ _size = 0;
+ _pos = 0;
+ _readonly=true;
+ //ugly Workaround to not expose SdFile class which conflict with Native ESP32 SD class
+ _sdfile = new SdFile;
+}
+ESP_SD::~ESP_SD(){
+if (((SdFile *)_sdfile)->isOpen())close();
+if (_sdfile) delete (SdFile *) _sdfile;
+}
+
+bool ESP_SD::isopen(){
+ return (((SdFile *)_sdfile)->isOpen());
+}
+
+int8_t ESP_SD::card_status(){
+if (!IS_SD_INSERTED() || !card.isMounted()) return 0; //No sd
+if ( card.isPrinting() || card.isFileOpen() ) return -1; // busy
+return 1; //ok
+}
+
+bool ESP_SD::open(const char * path, bool readonly ){
+ if (path == NULL) return false;
+ String fullpath=path;
+ String pathname = fullpath.substring(0,fullpath.lastIndexOf("/"));
+ String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1));
+ if (pathname.length() == 0)pathname="/";
+ if (!openDir(pathname))return false;
+ _pos = 0;
+ _readonly = readonly;
+ return ((SdFile *)_sdfile)->open(&workDir, filename.c_str(), readonly?O_READ:(O_CREAT | O_APPEND | O_WRITE | O_TRUNC));
+}
+
+uint32_t ESP_SD::size(){
+ if(((SdFile *)_sdfile)->isOpen()) {
+ _size = ((SdFile *)_sdfile)->fileSize();
+ }
+ return _size ;
+}
+
+uint32_t ESP_SD::available(){
+ if(!((SdFile *)_sdfile)->isOpen() || !_readonly) return 0;
+ _size = ((SdFile *)_sdfile)->fileSize();
+ if (_size == 0) return 0;
+
+ return _size - _pos ;
+}
+
+void ESP_SD::close(){
+ if(((SdFile *)_sdfile)->isOpen()) {
+ ((SdFile *)_sdfile)->sync();
+ _size = ((SdFile *)_sdfile)->fileSize();
+ ((SdFile *)_sdfile)->close();
+ }
+}
+
+int16_t ESP_SD::write(const uint8_t * data, uint16_t len){
+ return ((SdFile *)_sdfile)->write(data, len);
+}
+
+int16_t ESP_SD::write(const uint8_t byte){
+ return ((SdFile *)_sdfile)->write(&byte, 1);
+}
+
+
+bool ESP_SD::exists(const char * path){
+ bool response = open(path);
+ if (response) close();
+ return response;
+}
+
+bool ESP_SD::remove(const char * path){
+ if (path == NULL) return false;
+ String fullpath=path;
+ String pathname = fullpath.substring(0,fullpath.lastIndexOf("/"));
+ String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1));
+ if (pathname.length() == 0)pathname="/";
+ if (!openDir(pathname))return false;
+ SdFile file;
+ return file.remove(&workDir, filename.c_str());
+}
+
+bool ESP_SD::dir_exists(const char * path){
+ return openDir(path);
+}
+
+bool ESP_SD::rmdir(const char * path){
+ if (path == NULL) return false;
+ String fullpath=path;
+ if (fullpath=="/") return false;
+ if (!openDir(fullpath))return false;
+ return workDir.rmRfStar();
+}
+
+bool ESP_SD::mkdir(const char * path){
+ if (path == NULL) return false;
+ String fullpath=path;
+ String pathname = fullpath.substring(0,fullpath.lastIndexOf("/"));
+ String filename = makeshortname(fullpath.substring(fullpath.lastIndexOf("/")+1));
+ if (pathname.length() == 0)pathname="/";
+ if (!openDir(pathname))return false;
+ SdFile file;
+ return file.mkdir(&workDir, filename.c_str());
+}
+
+int16_t ESP_SD::read(){
+ if (!_readonly) return 0;
+ int16_t v = ((SdFile *)_sdfile)->read();
+ if (v!=-1)_pos++;
+ return v;
+
+}
+
+uint16_t ESP_SD::read(uint8_t * buf, uint16_t nbyte){
+ if (!_readonly) return 0;
+ int16_t v = ((SdFile *)_sdfile)->read(buf, nbyte);
+ if (v!=-1)_pos+=v;
+ return v;
+}
+
+String ESP_SD::get_path_part(String data, int index){
+ int found = 0;
+ int strIndex[] = {0, -1};
+ int maxIndex;
+ String no_res;
+ String s = data;
+ s.trim();
+ if (s.length() == 0) return no_res;
+ maxIndex = s.length()-1;
+ if ((s[0] == '/') && (s.length() > 1)){
+ String s2 = &s[1];
+ s = s2;
+ }
+ for(int i=0; i<=maxIndex && found<=index; i++){
+ if(s.charAt(i)=='/' || i==maxIndex){
+ found++;
+ strIndex[0] = strIndex[1]+1;
+ strIndex[1] = (i == maxIndex) ? i+1 : i;
+ }
+ }
+
+ return found>index ? s.substring(strIndex[0], strIndex[1]) : no_res;
+}
+
+String ESP_SD::makeshortname(String longname, uint8_t index){
+ String s = longname;
+ String part_name;
+ String part_ext;
+ //Sanity check name is uppercase and no space
+ s.replace(" ","");
+ s.toUpperCase();
+ int pos = s.lastIndexOf(".");
+ //do we have extension ?
+ if (pos != -1) {
+ part_name = s.substring(0,pos);
+ if (part_name.lastIndexOf(".") !=-1) {
+ part_name.replace(".","");
+ //trick for short name but force ~1 at the end
+ part_name+=" ";
+ }
+ part_ext = s.substring(pos+1,pos+4);
+ } else {
+ part_name = s;
+ }
+ //check is under 8 char
+ if (part_name.length() > 8) {
+ //if not cut and use index
+ //check file exists is not part of this function
+ part_name = part_name.substring(0,6);
+ part_name += "~" + String(index);
+ }
+ //remove the possible " " for the trick
+ part_name.replace(" ","");
+ //create full short name
+ if (part_ext.length() > 0) part_name+="." + part_ext;
+ return part_name;
+}
+
+String ESP_SD::makepath83(String longpath){
+ String path;
+ String tmp;
+ int index = 0;
+ tmp = get_path_part(longpath,index);
+ while (tmp.length() > 0) {
+ path += "/";
+ //TODO need to check short name index (~1) match actually the long name...
+ path += makeshortname (tmp);
+ index++;
+ tmp = get_path_part(longpath,index);
+ }
+ return path;
+}
+
+
+uint32_t ESP_SD::card_total_space(){
+
+ return (512.00) * (sd_volume.clusterCount()) * (sd_volume.blocksPerCluster());
+}
+uint32_t ESP_SD::card_used_space(){
+ return (512.00) * (sd_volume.clusterCount() - sd_volume.freeClusterCount() ) * (sd_volume.blocksPerCluster());
+}
+
+bool ESP_SD::openDir(String path){
+ static SdFile root;
+ static String name;
+ int index = 0;
+ //SdFile *parent;
+ if(root.isOpen())root.close();
+ if (!sd_volume.init(&(card.getSd2Card()))) {
+ return false;
+ }
+ if (!root.openRoot(&sd_volume)){
+ return false;
+ }
+ root.rewind();
+ workDir = root;
+ //parent = &workDir;
+ name = get_path_part(path,index);
+ while ((name.length() > 0) && (name!="/")) {
+ SdFile newDir;
+ if (!newDir.open(&root, name.c_str(), O_READ)) {
+ return false;
+ }
+ workDir=newDir;
+ //parent = &workDir;
+ index++;
+ if (index > MAX_DIR_DEPTH) {
+ return false;
+ }
+ name = get_path_part(path,index);
+ }
+ return true;
+}
+//TODO may be add date and use a struct for all info
+bool ESP_SD::readDir(char name[13], uint32_t * size, bool * isFile){
+ if ((name == NULL) || (size==NULL)) {
+ return false;
+ }
+ * size = 0;
+ name[0]= 0;
+ * isFile = false;
+
+ if ((workDir.readDir(&dir_info, NULL)) > 0){
+ workDir.dirName(dir_info,name);
+ * size = dir_info.fileSize;
+ if (DIR_IS_FILE(&dir_info))* isFile = true;
+ return true;
+ }
+ return false;
+}
+
+
+//TODO
+/*
+bool SD_file_timestamp(const char * path, uint8_t flag, uint16_t year, uint8_t month, uint8_t day,
+ uint8_t hour, uint8_t minute, uint8_t second){
+}**/
+
+#endif
+
+#endif
diff --git a/src/sd_ESP32.h b/src/sd_ESP32.h
new file mode 100644
index 0000000..777852a
--- /dev/null
+++ b/src/sd_ESP32.h
@@ -0,0 +1,59 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef _ESP_SD_H_
+#define _ESP_SD_H_
+class ESP_SD{
+ public:
+ ESP_SD();
+ ~ESP_SD();
+ int8_t card_status();
+ uint32_t card_total_space();
+ uint32_t card_used_space();
+ bool open(const char * path, bool readonly = true );
+ void close();
+ int16_t write(const uint8_t * data, uint16_t len);
+ int16_t write(const uint8_t byte);
+ uint16_t read(uint8_t * buf, uint16_t nbyte);
+ int16_t read();
+ uint32_t size();
+ uint32_t available();
+ bool exists(const char * path);
+ bool dir_exists(const char * path);
+ bool remove(const char * path);
+ bool rmdir(const char * path);
+ bool mkdir(const char * path);
+ bool isopen();
+ String makepath83(String longpath);
+ String makeshortname(String longname, uint8_t index = 1);
+ bool openDir(String path);
+ bool readDir(char name[13], uint32_t * size, bool * isFile);
+ bool * isFile;
+ private:
+ void * _sdfile;
+ uint32_t _size;
+ uint32_t _pos;
+ bool _readonly;
+ String get_path_part(String data, int index);
+
+};
+#endif
diff --git a/src/serial2socket.cpp b/src/serial2socket.cpp
new file mode 100644
index 0000000..f5c603f
--- /dev/null
+++ b/src/serial2socket.cpp
@@ -0,0 +1,170 @@
+/*
+ serial2socket.cpp - serial 2 socket functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WIFISUPPORT)
+#include "serial2socket.h"
+#include "wificonfig.h"
+#include
+#include
+Serial_2_Socket Serial2Socket;
+
+
+Serial_2_Socket::Serial_2_Socket(){
+ _web_socket = NULL;
+ _TXbufferSize = 0;
+ _RXbufferSize = 0;
+ _RXbufferpos = 0;
+}
+Serial_2_Socket::~Serial_2_Socket(){
+ if (_web_socket) detachWS();
+ _TXbufferSize = 0;
+ _RXbufferSize = 0;
+ _RXbufferpos = 0;
+}
+void Serial_2_Socket::begin(long speed){
+ _TXbufferSize = 0;
+ _RXbufferSize = 0;
+ _RXbufferpos = 0;
+}
+
+void Serial_2_Socket::end(){
+ _TXbufferSize = 0;
+ _RXbufferSize = 0;
+ _RXbufferpos = 0;
+}
+
+long Serial_2_Socket::baudRate(){
+ return 0;
+}
+
+bool Serial_2_Socket::attachWS(void * web_socket){
+ if (web_socket) {
+ _web_socket = web_socket;
+ _TXbufferSize=0;
+ return true;
+ }
+ return false;
+}
+
+bool Serial_2_Socket::detachWS(){
+ _web_socket = NULL;
+ return true;
+}
+
+Serial_2_Socket::operator bool() const
+{
+ return true;
+}
+int Serial_2_Socket::available(){
+ return _RXbufferSize;
+}
+
+
+size_t Serial_2_Socket::write(uint8_t c)
+{
+ if(!_web_socket) return 0;
+ write(&c,1);
+ return 1;
+}
+
+size_t Serial_2_Socket::write(const uint8_t *buffer, size_t size)
+{
+ if((buffer == NULL) ||(!_web_socket)) {
+ if(buffer == NULL)log_i("[SOCKET]No buffer");
+ if(!_web_socket)log_i("[SOCKET]No socket");
+ return 0;
+ }
+#if defined(ENABLE_SERIAL2SOCKET_OUT)
+ if (_TXbufferSize==0)_lastflush = millis();
+ //send full line
+ if (_TXbufferSize + size > TXBUFFERSIZE) flush();
+ //need periodic check to force to flush in case of no end
+ for (int i = 0; i < size;i++){
+ _TXbuffer[_TXbufferSize] = buffer[i];
+ _TXbufferSize++;
+ }
+ log_i("[SOCKET]buffer size %d",_TXbufferSize);
+ handle_flush();
+#endif
+ return size;
+}
+
+int Serial_2_Socket::peek(void){
+ if (_RXbufferSize > 0)return _RXbuffer[_RXbufferpos];
+ else return -1;
+}
+
+bool Serial_2_Socket::push (const char * data){
+#if defined(ENABLE_SERIAL2SOCKET_IN)
+ int data_size = strlen(data);
+ if ((data_size + _RXbufferSize) <= RXBUFFERSIZE){
+ int current = _RXbufferpos + _RXbufferSize;
+ if (current > RXBUFFERSIZE) current = current - RXBUFFERSIZE;
+ for (int i = 0; i < data_size; i++){
+ if (current > (RXBUFFERSIZE-1)) current = 0;
+ _RXbuffer[current] = data[i];
+ current ++;
+ }
+ _RXbufferSize+=strlen(data);
+ return true;
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+int Serial_2_Socket::read(void){
+ if (_RXbufferSize > 0) {
+ int v = _RXbuffer[_RXbufferpos];
+ _RXbufferpos++;
+ if (_RXbufferpos > (RXBUFFERSIZE-1))_RXbufferpos = 0;
+ _RXbufferSize--;
+ return v;
+ } else return -1;
+}
+
+void Serial_2_Socket::handle_flush() {
+ if (_TXbufferSize > 0) {
+ if ((_TXbufferSize>=TXBUFFERSIZE) || ((millis()- _lastflush) > FLUSHTIMEOUT)) {
+ log_i("[SOCKET]need flush, buffer size %d",_TXbufferSize);
+ flush();
+ }
+ }
+}
+void Serial_2_Socket::flush(void){
+ if (_TXbufferSize > 0){
+ log_i("[SOCKET]flush data, buffer size %d",_TXbufferSize);
+ ((WebSocketsServer *)_web_socket)->broadcastBIN(_TXbuffer,_TXbufferSize);
+ //refresh timout
+ _lastflush = millis();
+ //reset buffer
+ _TXbufferSize = 0;
+ }
+}
+
+#endif // ENABLE_WIFI
+
+#endif // ARDUINO_ARCH_ESP32
diff --git a/src/serial2socket.h b/src/serial2socket.h
new file mode 100644
index 0000000..30a2cb0
--- /dev/null
+++ b/src/serial2socket.h
@@ -0,0 +1,81 @@
+/*
+ serial2socket.h - serial 2 socket functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+
+#ifndef _SERIAL_2_SOCKET_H_
+#define _SERIAL_2_SOCKET_H_
+
+#include "Print.h"
+#define TXBUFFERSIZE 1200
+#define RXBUFFERSIZE 128
+#define FLUSHTIMEOUT 500
+class Serial_2_Socket: public Print{
+ public:
+ Serial_2_Socket();
+ ~Serial_2_Socket();
+ size_t write(uint8_t c);
+ size_t write(const uint8_t *buffer, size_t size);
+
+ inline size_t write(const char * s)
+ {
+ return write((uint8_t*) s, strlen(s));
+ }
+ inline size_t write(unsigned long n)
+ {
+ return write((uint8_t) n);
+ }
+ inline size_t write(long n)
+ {
+ return write((uint8_t) n);
+ }
+ inline size_t write(unsigned int n)
+ {
+ return write((uint8_t) n);
+ }
+ inline size_t write(int n)
+ {
+ return write((uint8_t) n);
+ }
+ long baudRate();
+ void begin(long speed);
+ void end();
+ int available();
+ int peek(void);
+ int read(void);
+ bool push (const char * data);
+ void flush(void);
+ void handle_flush();
+ operator bool() const;
+ bool attachWS(void * web_socket);
+ bool detachWS();
+ private:
+ uint32_t _lastflush;
+ void * _web_socket;
+ uint8_t _TXbuffer[TXBUFFERSIZE];
+ uint16_t _TXbufferSize;
+ uint8_t _RXbuffer[RXBUFFERSIZE];
+ uint16_t _RXbufferSize;
+ uint16_t _RXbufferpos;
+};
+
+
+extern Serial_2_Socket Serial2Socket;
+
+#endif
diff --git a/src/web_server.cpp b/src/web_server.cpp
new file mode 100644
index 0000000..148481c
--- /dev/null
+++ b/src/web_server.cpp
@@ -0,0 +1,2565 @@
+/*
+ web_server.cpp - web server functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WIFISUPPORT)
+
+#include "wificonfig.h"
+
+#if defined (ENABLE_HTTP)
+#include "HAL.h"
+#include "../../gcode/queue.h"
+#include "wifiservices.h"
+#include "serial2socket.h"
+#include "web_server.h"
+#include
+#include "../../inc/Version.h"
+#include
+#include
+#include
+#if ENABLED(SDSUPPORT)
+#include "sd_ESP32.h"
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef ENABLE_MDNS
+#include
+#endif
+#ifdef ENABLE_SSDP
+#include
+#endif
+#ifdef ENABLE_CAPTIVE_PORTAL
+#include
+const byte DNS_PORT = 53;
+DNSServer dnsServer;
+#endif
+
+//embedded response file if no files on SPIFFS
+#include "nofile.h"
+
+//Upload status
+typedef enum {
+ UPLOAD_STATUS_NONE = 0,
+ UPLOAD_STATUS_FAILED = 1,
+ UPLOAD_STATUS_CANCELLED = 2,
+ UPLOAD_STATUS_SUCCESSFUL = 3,
+ UPLOAD_STATUS_ONGOING = 4
+} upload_status_type;
+
+#ifdef ENABLE_AUTHENTICATION
+#define DEFAULT_ADMIN_PWD "admin"
+#define DEFAULT_USER_PWD "user";
+#define DEFAULT_ADMIN_LOGIN "admin"
+#define DEFAULT_USER_LOGIN "user"
+#define ADMIN_PWD_ENTRY "ADMIN_PWD"
+#define USER_PWD_ENTRY "USER_PWD"
+#define AUTH_ENTRY_NB 20
+#define MAX_LOCAL_PASSWORD_LENGTH 16
+#define MIN_LOCAL_PASSWORD_LENGTH 1
+#endif
+
+//Default 404
+const char PAGE_404 [] = "\n\nRedirecting... \n\n\nUnknown page : $QUERY$- you will be redirected...\n
\nif not redirected, click here\n
\n\n\n\n\n\n\n\n";
+const char PAGE_CAPTIVE [] = "\n\nCaptive Portal \n\n\nCaptive Portal page : $QUERY$- you will be redirected...\n
\nif not redirected, click here\n
\n\n\n\n\n\n\n\n";
+
+
+Web_Server web_server;
+bool Web_Server::_setupdone = false;
+uint16_t Web_Server::_port = 0;
+String Web_Server::_hostname = "";
+uint16_t Web_Server::_data_port = 0;
+long Web_Server::_id_connection = 0;
+uint8_t Web_Server::_upload_status = UPLOAD_STATUS_NONE;
+WebServer * Web_Server::_webserver = NULL;
+WebSocketsServer * Web_Server::_socket_server = NULL;
+#ifdef ENABLE_AUTHENTICATION
+auth_ip * Web_Server::_head = NULL;
+uint8_t Web_Server::_nb_ip = 0;
+#define MAX_AUTH_IP 10
+#endif
+Web_Server::Web_Server(){
+
+}
+Web_Server::~Web_Server(){
+ end();
+}
+
+long Web_Server::get_client_ID() {
+ return _id_connection;
+}
+
+bool Web_Server::begin(){
+
+ bool no_error = true;
+ _setupdone = false;
+ Preferences prefs;
+ prefs.begin(NAMESPACE, true);
+ int8_t penabled = prefs.getChar(HTTP_ENABLE_ENTRY, DEFAULT_HTTP_STATE);
+ //Get http port
+ _port = prefs.getUShort(HTTP_PORT_ENTRY, DEFAULT_WEBSERVER_PORT);
+ //Get hostname
+ String defV = DEFAULT_HOSTNAME;
+ _hostname = prefs.getString(HOSTNAME_ENTRY, defV);
+ prefs.end();
+ if (penabled == 0) return false;
+ //create instance
+ _webserver= new WebServer(_port);
+#ifdef ENABLE_AUTHENTICATION
+ //here the list of headers to be recorded
+ const char * headerkeys[] = {"Cookie"} ;
+ size_t headerkeyssize = sizeof (headerkeys) / sizeof (char*);
+ //ask server to track these headers
+ _webserver->collectHeaders (headerkeys, headerkeyssize );
+#endif
+ _socket_server = new WebSocketsServer(_port + 1);
+ _socket_server->begin();
+ _socket_server->onEvent(handle_Websocket_Event);
+
+
+ //Websocket output
+ Serial2Socket.attachWS(_socket_server);
+
+ //Web server handlers
+ //trick to catch command line on "/" before file being processed
+ _webserver->on("/",HTTP_ANY, handle_root);
+
+ //Page not found handler
+ _webserver->onNotFound (handle_not_found);
+
+ //need to be there even no authentication to say to UI no authentication
+ _webserver->on("/login", HTTP_ANY, handle_login);
+
+ //web commands
+ _webserver->on ("/command", HTTP_ANY, handle_web_command);
+ _webserver->on ("/command_silent", HTTP_ANY, handle_web_command_silent);
+
+ //SPIFFS
+ _webserver->on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload);
+
+ //web update
+ _webserver->on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload);
+
+#if ENABLED(SDSUPPORT)
+ //Direct SD management
+ _webserver->on("/upload", HTTP_ANY, handle_direct_SDFileList,SDFile_direct_upload);
+#endif
+
+#ifdef ENABLE_CAPTIVE_PORTAL
+ if(WiFi.getMode() == WIFI_AP){
+ // if DNSServer is started with "*" for domain name, it will reply with
+ // provided IP to all DNS request
+ dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
+ _webserver->on ("/generate_204", HTTP_ANY, handle_root);
+ _webserver->on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, handle_root);
+ //do not forget the / at the end
+ _webserver->on ("/fwlink/", HTTP_ANY, handle_root);
+ }
+#endif
+
+#ifdef ENABLE_SSDP
+ //SSDP service presentation
+ if(WiFi.getMode() == WIFI_STA){
+ _webserver->on ("/description.xml", HTTP_GET, handle_SSDP);
+ //Add specific for SSDP
+ SSDP.setSchemaURL ("description.xml");
+ SSDP.setHTTPPort (_port);
+ SSDP.setName (_hostname);
+ SSDP.setURL ("/");
+ SSDP.setDeviceType ("upnp:rootdevice");
+ /*Any customization could be here
+ SSDP.setModelName (ESP32_MODEL_NAME);
+ SSDP.setModelURL (ESP32_MODEL_URL);
+ SSDP.setModelNumber (ESP_MODEL_NUMBER);
+ SSDP.setManufacturer (ESP_MANUFACTURER_NAME);
+ SSDP.setManufacturerURL (ESP_MANUFACTURER_URL);
+ */
+
+ //Start SSDP
+ MYSERIAL0.println("SSDP Started");
+ SSDP.begin();
+ }
+#endif
+ MYSERIAL0.println("HTTP Started");
+ //start webserver
+ _webserver->begin();
+#ifdef ENABLE_MDNS
+ //add mDNS
+ if(WiFi.getMode() == WIFI_STA){
+ MDNS.addService("http","tcp",_port);
+ }
+#endif
+ _setupdone = true;
+ return no_error;
+}
+
+void Web_Server::end(){
+ _setupdone = false;
+#ifdef ENABLE_SSDP
+ SSDP.end();
+#endif
+#ifdef ENABLE_MDNS
+ //remove mDNS
+ mdns_service_remove("_http", "_tcp");
+#endif
+ if (_socket_server) {
+ delete _socket_server;
+ _socket_server = NULL;
+ }
+ if (_webserver) {
+ delete _webserver;
+ _webserver = NULL;
+ }
+#ifdef ENABLE_AUTHENTICATION
+ while (_head) {
+ auth_ip * current = _head;
+ _head = _head->_next;
+ delete current;
+ }
+ _nb_ip = 0;
+#endif
+}
+
+//Root of Webserver/////////////////////////////////////////////////////
+
+void Web_Server::handle_root()
+{
+ String path = "/index.html";
+ String contentType = getContentType(path);
+ String pathWithGz = path + ".gz";
+ //if have a index.html or gzip version this is default root page
+ if((SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) && !_webserver->hasArg("forcefallback") && _webserver->arg("forcefallback")!="yes") {
+ if(SPIFFS.exists(pathWithGz)) {
+ path = pathWithGz;
+ }
+ File file = SPIFFS.open(path, FILE_READ);
+ _webserver->streamFile(file, contentType);
+ file.close();
+ return;
+ }
+ //if no lets launch the default content
+ _webserver->sendHeader("Content-Encoding", "gzip");
+ _webserver->send_P(200,"text/html",PAGE_NOFILES,PAGE_NOFILES_SIZE);
+}
+
+//Handle not registred path on SPIFFS neither SD ///////////////////////
+void Web_Server:: handle_not_found()
+{
+ if (is_authenticated() == LEVEL_GUEST) {
+ _webserver->sendContent_P("HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n");
+ //_webserver->client().stop();
+ return;
+ }
+ bool page_not_found = false;
+ String path = _webserver->urlDecode(_webserver->uri());
+ String contentType = getContentType(path);
+ String pathWithGz = path + ".gz";
+
+#if ENABLED(SDSUPPORT)
+ if ((path.substring(0,4) == "/SD/")) {
+ //remove /SD
+ path = path.substring(3);
+ ESP_SD SD_card;
+ if (SD_card.card_status() == 1) {
+ if (SD_card.exists(pathWithGz.c_str()) || SD_card.exists(path.c_str())) {
+ if (!SD_card.exists(path.c_str())) path = pathWithGz;
+ if(SD_card.open(path.c_str())) {
+ uint8_t buf[1200];
+ _webserver->setContentLength(SD_card.size());
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send(200, "application/octet-stream", "");
+
+ WiFiClient c = _webserver->client();
+ int16_t len = SD_card.read( buf, 1200);
+ while(len > 0) {
+ c.write(buf, len);
+ len = SD_card.read( buf, 1200);
+ wifi_config.wait(0);
+ handle();
+ }
+ SD_card.close();
+ return;
+ }
+ }
+ }
+
+ String content = "cannot find ";
+ content+=path;
+ _webserver->send(404,"text/plain",content.c_str());
+ return;
+ } else
+#endif
+ if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {
+ if(SPIFFS.exists(pathWithGz)) {
+ path = pathWithGz;
+ }
+ File file = SPIFFS.open(path, FILE_READ);
+ _webserver->streamFile(file, contentType);
+ file.close();
+ return;
+ } else {
+ page_not_found = true;
+ }
+
+ if (page_not_found ) {
+#ifdef ENABLE_CAPTIVE_PORTAL
+ if (WiFi.getMode()!=WIFI_STA ) {
+ String content=PAGE_CAPTIVE;
+ String stmp = WiFi.softAPIP().toString();
+ //Web address = ip + port
+ String KEY_IP = "$WEB_ADDRESS$";
+ String KEY_QUERY = "$QUERY$";
+ if (_port != 80) {
+ stmp+=":";
+ stmp+=String(_port);
+ }
+ content.replace(KEY_IP,stmp);
+ content.replace(KEY_IP,stmp);
+ content.replace(KEY_QUERY,_webserver->uri());
+ _webserver->send(200,"text/html",content);
+ return;
+ }
+#endif
+ path = "/404.htm";
+ contentType = getContentType(path);
+ pathWithGz = path + ".gz";
+ if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {
+ if(SPIFFS.exists(pathWithGz)) {
+ path = pathWithGz;
+ }
+ File file = SPIFFS.open(path, FILE_READ);
+ _webserver->streamFile(file, contentType);
+ file.close();
+
+ } else {
+ //if not template use default page
+ contentType = PAGE_404;
+ String stmp;
+ if (WiFi.getMode()==WIFI_STA ) {
+ stmp=WiFi.localIP().toString();
+ } else {
+ stmp=WiFi.softAPIP().toString();
+ }
+ //Web address = ip + port
+ String KEY_IP = "$WEB_ADDRESS$";
+ String KEY_QUERY = "$QUERY$";
+ if ( _port != 80) {
+ stmp+=":";
+ stmp+=String(_port);
+ }
+ contentType.replace(KEY_IP,stmp);
+ contentType.replace(KEY_QUERY,_webserver->uri());
+ _webserver->send(200,"text/html",contentType);
+ }
+ }
+}
+#ifdef ENABLE_SSDP
+//http SSDP xml presentation
+void Web_Server::handle_SSDP ()
+{
+ StreamString sschema ;
+ if (sschema.reserve (1024) ) {
+ String templ = ""
+ ""
+ ""
+ "1"
+ "0"
+ ""
+ "http://%s:%u/"
+ ""
+ "upnp:rootdevice"
+ "%s"
+ "/"
+ "%s"
+ "ESP32"
+ "Marlin"
+ "http://espressif.com/en/products/hardware/esp-wroom-32/overview"
+ "Espressif Systems"
+ "http://espressif.com"
+ "uuid:%s"
+ ""
+ "\r\n"
+ "\r\n";
+ char uuid[37];
+ String sip = WiFi.localIP().toString();
+ uint32_t chipId = (uint16_t) (ESP.getEfuseMac() >> 32);
+ sprintf (uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x",
+ (uint16_t) ( (chipId >> 16) & 0xff),
+ (uint16_t) ( (chipId >> 8) & 0xff),
+ (uint16_t) chipId & 0xff );
+ String serialNumber = String (chipId);
+ sschema.printf (templ.c_str(),
+ sip.c_str(),
+ _port,
+ _hostname.c_str(),
+ serialNumber.c_str(),
+ uuid);
+ _webserver->send (200, "text/xml", (String) sschema);
+ } else {
+ _webserver->send (500);
+ }
+}
+
+#endif
+
+//Handle web command query and send answer//////////////////////////////
+void Web_Server::handle_web_command ()
+{
+ //to save time if already disconnected
+ //if (_webserver->hasArg ("PAGEID") ) {
+ // if (_webserver->arg ("PAGEID").length() > 0 ) {
+ // if (_webserver->arg ("PAGEID").toInt() != _id_connection) {
+ // _webserver->send (200, "text/plain", "Invalid command");
+ // return;
+ // }
+ // }
+ //}
+ level_authenticate_type auth_level = is_authenticated();
+ String cmd = "";
+ if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) {
+ if (_webserver->hasArg ("plain") ) {
+ cmd = _webserver->arg ("plain");
+ } else {
+ cmd = _webserver->arg ("commandText");
+ }
+ } else {
+ _webserver->send (200, "text/plain", "Invalid command");
+ return;
+ }
+ //if it is internal command [ESPXXX]
+ cmd.trim();
+ int ESPpos = cmd.indexOf ("[ESP");
+ if (ESPpos > -1) {
+ //is there the second part?
+ int ESPpos2 = cmd.indexOf ("]", ESPpos);
+ if (ESPpos2 > -1) {
+ //Split in command and parameters
+ String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2);
+ String cmd_part2 = "";
+ //only [ESP800] is allowed login free if authentication is enabled
+ if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) {
+ _webserver->send (401, "text/plain", "Authentication failed!\n");
+ return;
+ }
+ //is there space for parameters?
+ if (ESPpos2 < cmd.length() ) {
+ cmd_part2 = cmd.substring (ESPpos2 + 1);
+ }
+ //if command is a valid number then execute command
+ if (cmd_part1.toInt() != 0) {
+ ESPResponseStream espresponse(_webserver);
+ //commmand is web only
+ execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, &espresponse);
+ //flush
+ espresponse.flush();
+ }
+ //if not is not a valid [ESPXXX] command
+ }
+ } else { //execute GCODE
+ if (auth_level == LEVEL_GUEST) {
+ _webserver->send (401, "text/plain", "Authentication failed!\n");
+ return;
+ }
+ //Instead of send several commands one by one by web / send full set and split here
+ String scmd;
+ String res = "Ok";
+ uint8_t sindex = 0;
+ scmd = get_Splited_Value(cmd,'\n', sindex);
+ while ( scmd != "" ){
+ scmd+="\n";
+ Serial2Socket.push(scmd.c_str());
+ //GCodeQueue::enqueue_one_now(scmd.c_str());
+ sindex++;
+ scmd = get_Splited_Value(cmd,'\n', sindex);
+ }
+ _webserver->send (200, "text/plain", res.c_str());
+ }
+}
+//Handle web command query and send answer//////////////////////////////
+void Web_Server::handle_web_command_silent ()
+{
+ //to save time if already disconnected
+ //if (_webserver->hasArg ("PAGEID") ) {
+ // if (_webserver->arg ("PAGEID").length() > 0 ) {
+ // if (_webserver->arg ("PAGEID").toInt() != _id_connection) {
+ // _webserver->send (200, "text/plain", "Invalid command");
+ // return;
+ // }
+ // }
+ //}
+ level_authenticate_type auth_level = is_authenticated();
+ String cmd = "";
+ if (_webserver->hasArg ("plain") || _webserver->hasArg ("commandText") ) {
+ if (_webserver->hasArg ("plain") ) {
+ cmd = _webserver->arg ("plain");
+ } else {
+ cmd = _webserver->arg ("commandText");
+ }
+ } else {
+ _webserver->send (200, "text/plain", "Invalid command");
+ return;
+ }
+ //if it is internal command [ESPXXX]
+ cmd.trim();
+ int ESPpos = cmd.indexOf ("[ESP");
+ if (ESPpos > -1) {
+ //is there the second part?
+ int ESPpos2 = cmd.indexOf ("]", ESPpos);
+ if (ESPpos2 > -1) {
+ //Split in command and parameters
+ String cmd_part1 = cmd.substring (ESPpos + 4, ESPpos2);
+ String cmd_part2 = "";
+ //only [ESP800] is allowed login free if authentication is enabled
+ if ( (auth_level == LEVEL_GUEST) && (cmd_part1.toInt() != 800) ) {
+ _webserver->send (401, "text/plain", "Authentication failed!\n");
+ return;
+ }
+ //is there space for parameters?
+ if (ESPpos2 < cmd.length() ) {
+ cmd_part2 = cmd.substring (ESPpos2 + 1);
+ }
+ //if command is a valid number then execute command
+ if (cmd_part1.toInt() != 0) {
+ //commmand is web only
+ if(execute_internal_command (cmd_part1.toInt(), cmd_part2, auth_level, NULL)) _webserver->send (200, "text/plain", "ok");
+ else _webserver->send (200, "text/plain", "error");
+ }
+ //if not is not a valid [ESPXXX] command
+ }
+ } else { //execute GCODE
+ if (auth_level == LEVEL_GUEST) {
+ _webserver->send (401, "text/plain", "Authentication failed!\n");
+ return;
+ }
+ //Instead of send several commands one by one by web / send full set and split here
+ String scmd;
+ uint8_t sindex = 0;
+ scmd = get_Splited_Value(cmd,'\n', sindex);
+ String res = "Ok";
+ while (scmd != "" ){
+ scmd+="\n";
+ Serial2Socket.push(scmd.c_str());
+ //GCodeQueue::enqueue_one_now(scmd.c_str());
+ sindex++;
+ scmd = get_Splited_Value(cmd,'\n', sindex);
+ }
+ _webserver->send (200, "text/plain", res.c_str());
+ }
+}
+
+
+bool Web_Server::execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level, ESPResponseStream *espresponse)
+{
+ bool response = true;
+ level_authenticate_type auth_type = auth_level;
+
+ //manage parameters
+ String parameter;
+ switch (cmd) {
+ //Get SD Card Status
+ //[ESP200]
+ case 200:
+ {
+ if (!espresponse) return false;
+ String resp = "No SD card";
+#if ENABLED(SDSUPPORT)
+ ESP_SD card;
+ int8_t state = card.card_status();
+ if (state == -1)resp="Busy";
+ else if (state == 1)resp="SD card detected";
+ else resp="No SD card";
+#endif
+ espresponse->println (resp.c_str());
+ }
+ break;
+ //Get full ESP32 wifi settings content
+ //[ESP400]
+ case 400:
+ {
+ String v;
+ String defV;
+ Preferences prefs;
+ if (!espresponse) return false;
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type == LEVEL_GUEST) return false;
+#endif
+ int8_t vi;
+ espresponse->print("{\"EEPROM\":[");
+ prefs.begin(NAMESPACE, true);
+ //1 - Hostname
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (HOSTNAME_ENTRY);
+ espresponse->print ("\",\"T\":\"S\",\"V\":\"");
+ espresponse->print (_hostname.c_str());
+ espresponse->print ("\",\"H\":\"Hostname\" ,\"S\":\"");
+ espresponse->print (String(MAX_HOSTNAME_LENGTH).c_str());
+ espresponse->print ("\", \"M\":\"");
+ espresponse->print (String(MIN_HOSTNAME_LENGTH).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ //2 - http protocol mode
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (HTTP_ENABLE_ENTRY);
+ espresponse->print ("\",\"T\":\"B\",\"V\":\"");
+ vi = prefs.getChar(HTTP_ENABLE_ENTRY, 1);
+ espresponse->print (String(vi).c_str());
+ espresponse->print ("\",\"H\":\"HTTP protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}");
+ espresponse->print (",");
+
+ //3 - http port
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (HTTP_PORT_ENTRY);
+ espresponse->print ("\",\"T\":\"I\",\"V\":\"");
+ espresponse->print (String(_port).c_str());
+ espresponse->print ("\",\"H\":\"HTTP Port\",\"S\":\"");
+ espresponse->print (String(MAX_HTTP_PORT).c_str());
+ espresponse->print ("\",\"M\":\"");
+ espresponse->print (String(MIN_HTTP_PORT).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ //TODO
+ //4 - telnet protocol mode
+ /* espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (TELNET_ENABLE_ENTRY);
+ espresponse->print ("\",\"T\":\"B\",\"V\":\"");
+ vi = prefs.getChar(TELNET_ENABLE_ENTRY, 0);
+ espresponse->print (String(vi).c_str());
+ espresponse->print ("\",\"H\":\"Telnet protocol\",\"O\":[{\"Enabled\":\"1\"},{\"Disabled\":\"0\"}]}");
+ espresponse->print (",");*/
+
+ //5 - telnet Port
+ /* espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (TELNET_PORT_ENTRY);
+ espresponse->print ("\",\"T\":\"I\",\"V\":\"");
+ espresponse->print (String(_data_port).c_str());
+ espresponse->print ("\",\"H\":\"Telnet Port\",\"S\":\"");
+ espresponse->print (String(MAX_TELNET_PORT).c_str());
+ espresponse->print ("\",\"M\":\"");
+ espresponse->print (String(MIN_TELNET_PORT).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");*/
+
+ //6 - wifi mode
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (ESP_WIFI_MODE);
+ espresponse->print ("\",\"T\":\"B\",\"V\":\"");
+ vi = prefs.getChar(ESP_WIFI_MODE, ESP_WIFI_OFF);
+ espresponse->print (String(vi).c_str());
+ espresponse->print ("\",\"H\":\"Wifi mode\",\"O\":[{\"STA\":\"1\"},{\"AP\":\"2\"},{\"None\":\"0\"}]}");
+ espresponse->print (",");
+
+ //7 - STA SSID
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_SSID_ENTRY);
+ espresponse->print ("\",\"T\":\"S\",\"V\":\"");
+ defV = DEFAULT_STA_SSID;
+ espresponse->print (prefs.getString(STA_SSID_ENTRY, defV).c_str());
+ espresponse->print ("\",\"S\":\"");
+ espresponse->print (String(MAX_SSID_LENGTH).c_str());
+ espresponse->print ("\",\"H\":\"Station SSID\",\"M\":\"");
+ espresponse->print (String(MIN_SSID_LENGTH).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ //8 - STA password
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_PWD_ENTRY);
+ espresponse->print ("\",\"T\":\"S\",\"V\":\"");
+ espresponse->print (HIDDEN_PASSWORD);
+ espresponse->print ("\",\"S\":\"");
+ espresponse->print (String(MAX_PASSWORD_LENGTH).c_str());
+ espresponse->print ("\",\"H\":\"Station Password\",\"M\":\"");
+ espresponse->print (String(MIN_PASSWORD_LENGTH).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ // 9 - STA IP mode
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_IP_MODE_ENTRY);
+ espresponse->print ("\",\"T\":\"B\",\"V\":\"");
+ espresponse->print (String(prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE)).c_str());
+ espresponse->print ("\",\"H\":\"Station IP Mode\",\"O\":[{\"DHCP\":\"0\"},{\"Static\":\"1\"}]}");
+ espresponse->print (",");
+
+ //10-STA static IP
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_IP_ENTRY);
+ espresponse->print ("\",\"T\":\"A\",\"V\":\"");
+ espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_IP_ENTRY, 0)).c_str());
+ espresponse->print ("\",\"H\":\"Station Static IP\"}");
+ espresponse->print (",");
+
+ //11-STA static Gateway
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_GW_ENTRY);
+ espresponse->print ("\",\"T\":\"A\",\"V\":\"");
+ espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_GW_ENTRY, 0)).c_str());
+ espresponse->print ("\",\"H\":\"Station Static Gateway\"}");
+ espresponse->print (",");
+
+ //12-STA static Mask
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (STA_MK_ENTRY);
+ espresponse->print ("\",\"T\":\"A\",\"V\":\"");
+ espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(STA_MK_ENTRY, 0)).c_str());
+ espresponse->print ("\",\"H\":\"Station Static Mask\"}");
+ espresponse->print (",");
+
+ //13 - AP SSID
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (AP_SSID_ENTRY);
+ espresponse->print ("\",\"T\":\"S\",\"V\":\"");
+ defV = DEFAULT_AP_SSID;
+ espresponse->print (prefs.getString(AP_SSID_ENTRY, defV).c_str());
+ espresponse->print ("\",\"S\":\"");
+ espresponse->print (String(MAX_SSID_LENGTH).c_str());
+ espresponse->print ("\",\"H\":\"AP SSID\",\"M\":\"");
+ espresponse->print (String(MIN_SSID_LENGTH).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ //14 - AP password
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (AP_PWD_ENTRY);
+ espresponse->print ("\",\"T\":\"S\",\"V\":\"");
+ espresponse->print (HIDDEN_PASSWORD);
+ espresponse->print ("\",\"S\":\"");
+ espresponse->print (String(MAX_PASSWORD_LENGTH).c_str());
+ espresponse->print ("\",\"H\":\"AP Password\",\"M\":\"");
+ espresponse->print (String(MIN_PASSWORD_LENGTH).c_str());
+ espresponse->print ("\"}");
+ espresponse->print (",");
+
+ //15 - AP static IP
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (AP_IP_ENTRY);
+ espresponse->print ("\",\"T\":\"A\",\"V\":\"");
+ defV = DEFAULT_AP_IP;
+ espresponse->print (wifi_config.IP_string_from_int(prefs.getInt(AP_IP_ENTRY, wifi_config.IP_int_from_string(defV))).c_str());
+ espresponse->print ("\",\"H\":\"AP Static IP\"}");
+ espresponse->print (",");
+
+ //16 - AP Channel
+ espresponse->print ("{\"F\":\"network\",\"P\":\"");
+ espresponse->print (AP_CHANNEL_ENTRY);
+ espresponse->print ("\",\"T\":\"B\",\"V\":\"");
+ espresponse->print (String(prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL)).c_str());
+ espresponse->print ("\",\"H\":\"AP Channel\",\"O\":[");
+ for (int i = MIN_CHANNEL; i <= MAX_CHANNEL ; i++) {
+ espresponse->print ("{\"");
+ espresponse->print (String(i).c_str());
+ espresponse->print ("\":\"");
+ espresponse->print (String(i).c_str());
+ espresponse->print ("\"}");
+ if (i < MAX_CHANNEL) {
+ espresponse->print (",");
+ }
+ }
+ espresponse->print ("]}");
+
+ espresponse->print ("]}");
+ prefs.end();
+ }
+ break;
+ //Set EEPROM setting
+ //[ESP401]P= T= V= pwd=
+ case 401:
+ {
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type != LEVEL_ADMIN) return false;
+#endif
+ //check validity of parameters
+ String spos = get_param (cmd_params, "P=", false);
+ String styp = get_param (cmd_params, "T=", false);
+ String sval = get_param (cmd_params, "V=", true);
+ spos.trim();
+ sval.trim();
+ if (spos.length() == 0) {
+ response = false;
+ }
+ if (! (styp == "B" || styp == "S" || styp == "A" || styp == "I" || styp == "F") ) {
+ response = false;
+ }
+ if ((sval.length() == 0) && !((spos==AP_PWD_ENTRY) || (spos==STA_PWD_ENTRY))){
+ response = false;
+ }
+
+ if (response) {
+ Preferences prefs;
+ prefs.begin(NAMESPACE, false);
+ //Byte value
+ if ((styp == "B") || (styp == "F")){
+ int8_t bbuf = sval.toInt();
+ if (prefs.putChar(spos.c_str(), bbuf) ==0 ) {
+ response = false;
+ } else {
+ //dynamique refresh is better than restart the board
+ if (spos == ESP_WIFI_MODE){
+ //TODO
+ }
+ if (spos == AP_CHANNEL_ENTRY) {
+ //TODO
+ }
+ if (spos == HTTP_ENABLE_ENTRY) {
+ //TODO
+ }
+ if (spos == TELNET_ENABLE_ENTRY) {
+ //TODO
+ }
+ }
+ }
+ //Integer value
+ if (styp == "I") {
+ int16_t ibuf = sval.toInt();
+ if (prefs.putUShort(spos.c_str(), ibuf) == 0) {
+ response = false;
+ } else {
+ if (spos == HTTP_PORT_ENTRY){
+ //TODO
+ }
+ if (spos == TELNET_PORT_ENTRY){
+ //TODO
+ //Serial.println(ibuf);
+ }
+ }
+
+ }
+ //String value
+ if (styp == "S") {
+ if (prefs.putString(spos.c_str(), sval) == 0) {
+ response = false;
+ } else {
+ if (spos == HOSTNAME_ENTRY){
+ //TODO
+ }
+ if (spos == STA_SSID_ENTRY){
+ //TODO
+ }
+ if (spos == STA_PWD_ENTRY){
+ //TODO
+ }
+ if (spos == AP_SSID_ENTRY){
+ //TODO
+ }
+ if (spos == AP_PWD_ENTRY){
+ //TODO
+ }
+ }
+
+ }
+ //IP address
+ if (styp == "A") {
+ if (prefs.putInt(spos.c_str(), wifi_config.IP_int_from_string(sval)) == 0) {
+ response = false;
+ } else {
+ if (spos == STA_IP_ENTRY){
+ //TODO
+ }
+ if (spos == STA_GW_ENTRY){
+ //TODO
+ }
+ if (spos == STA_MK_ENTRY){
+ //TODO
+ }
+ if (spos == AP_IP_ENTRY){
+ //TODO
+ }
+ }
+ }
+ prefs.end();
+ }
+ if (!response) {
+ if (espresponse) espresponse->println ("Error: Incorrect Command");
+ } else {
+ if (espresponse) espresponse->println ("ok");
+ }
+
+ }
+ break;
+ //Get available AP list (limited to 30)
+ //output is JSON
+ //[ESP410]
+ case 410: {
+ if (!espresponse)return false;
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type == LEVEL_GUEST) return false;
+#endif
+ espresponse->print("{\"AP_LIST\":[");
+ int n = WiFi.scanComplete();
+ if (n == -2) {
+ WiFi.scanNetworks (true);
+ } else if (n) {
+ for (int i = 0; i < n; ++i) {
+ if (i > 0) {
+ espresponse->print (",");
+ }
+ espresponse->print ("{\"SSID\":\"");
+ espresponse->print (WiFi.SSID (i).c_str());
+ espresponse->print ("\",\"SIGNAL\":\"");
+ espresponse->print (String(wifi_config.getSignal (WiFi.RSSI (i) )).c_str());
+ espresponse->print ("\",\"IS_PROTECTED\":\"");
+
+ if (WiFi.encryptionType (i) == WIFI_AUTH_OPEN) {
+ espresponse->print ("0");
+ } else {
+ espresponse->print ("1");
+ }
+ espresponse->print ("\"}");
+ }
+ }
+ WiFi.scanDelete();
+ if (WiFi.scanComplete() == -2) {
+ WiFi.scanNetworks (true);
+ }
+ espresponse->print ("]}");
+ }
+ break;
+ //Get ESP current status
+ case 420:
+ {
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type == LEVEL_GUEST) return false;
+#endif
+ if (!espresponse)return false;
+ espresponse->print ("Chip ID: ");
+ espresponse->print (String ( (uint16_t) (ESP.getEfuseMac() >> 32) ).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("CPU Frequency: ");
+ espresponse->print (String (ESP.getCpuFreqMHz() ).c_str());
+ espresponse->print ("Mhz");
+ espresponse->print ("\n");
+ espresponse->print ("CPU Temperature: ");
+ espresponse->print (String (temperatureRead(), 1).c_str());
+ espresponse->print ("°C");
+ espresponse->print ("\n");
+ espresponse->print ("Free memory: ");
+ espresponse->print (formatBytes (ESP.getFreeHeap()).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("SDK: ");
+ espresponse->print (ESP.getSdkVersion());
+ espresponse->print ("\n");
+ espresponse->print ("Flash Size: ");
+ espresponse->print (formatBytes (ESP.getFlashChipSize()).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Available Size for update: ");
+ //Not OTA on 2Mb board per spec
+ if (ESP.getFlashChipSize() > 0x20000) {
+ espresponse->print (formatBytes (0x140000).c_str());
+ } else {
+ espresponse->print (formatBytes (0x0).c_str());
+ }
+ espresponse->print ("\n");
+ espresponse->print ("Available Size for SPIFFS: ");
+ espresponse->print (formatBytes (SPIFFS.totalBytes()).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Baud rate: ");
+ long br = Serial.baudRate();
+ //workaround for ESP32
+ if (br == 115201) {
+ br = 115200;
+ }
+ if (br == 230423) {
+ br = 230400;
+ }
+ espresponse->print (String(br).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Sleep mode: ");
+ if (WiFi.getSleep())espresponse->print ("Modem");
+ else espresponse->print ("None");
+ espresponse->print ("\n");
+ espresponse->print ("Web port: ");
+ espresponse->print (String(_port).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Data port: ");
+ if (_data_port!=0)espresponse->print (String(_data_port).c_str());
+ else espresponse->print ("Disabled");
+ espresponse->print ("\n");
+ espresponse->print ("Hostname: ");
+ espresponse->print ( _hostname.c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Active Mode: ");
+ if (WiFi.getMode() == WIFI_STA) {
+ espresponse->print ("STA (");
+ espresponse->print ( WiFi.macAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+ espresponse->print ("Connected to: ");
+ if (WiFi.isConnected()){ //in theory no need but ...
+ espresponse->print (WiFi.SSID().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Signal: ");
+ espresponse->print ( String(wifi_config.getSignal (WiFi.RSSI())).c_str());
+ espresponse->print ("%");
+ espresponse->print ("\n");
+ uint8_t PhyMode;
+ esp_wifi_get_protocol (ESP_IF_WIFI_STA, &PhyMode);
+ espresponse->print ("Phy Mode: ");
+ if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)) espresponse->print ("11n");
+ else if (PhyMode == (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G)) espresponse->print ("11g");
+ else if (PhyMode == (WIFI_PROTOCOL_11B )) espresponse->print ("11b");
+ else espresponse->print ("???");
+ espresponse->print ("\n");
+ espresponse->print ("Channel: ");
+ espresponse->print (String (WiFi.channel()).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("IP Mode: ");
+ tcpip_adapter_dhcp_status_t dhcp_status;
+ tcpip_adapter_dhcpc_get_status (TCPIP_ADAPTER_IF_STA, &dhcp_status);
+ if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("DHCP");
+ else espresponse->print ("Static");
+ espresponse->print ("\n");
+ espresponse->print ("IP: ");
+ espresponse->print (WiFi.localIP().toString().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Gateway: ");
+ espresponse->print (WiFi.gatewayIP().toString().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Mask: ");
+ espresponse->print (WiFi.subnetMask().toString().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("DNS: ");
+ espresponse->print (WiFi.dnsIP().toString().c_str());
+ espresponse->print ("\n");
+ } //this is web command so connection => no command
+ espresponse->print ("Disabled Mode: ");
+ espresponse->print ("AP (");
+ espresponse->print (WiFi.softAPmacAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+ } else if (WiFi.getMode() == WIFI_AP) {
+ espresponse->print ("AP (");
+ espresponse->print (WiFi.softAPmacAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+ wifi_config_t conf;
+ esp_wifi_get_config (ESP_IF_WIFI_AP, &conf);
+ espresponse->print ("SSID: ");
+ espresponse->print ((const char*) conf.ap.ssid);
+ espresponse->print ("\n");
+ espresponse->print ("Visible: ");
+ espresponse->print ( (conf.ap.ssid_hidden == 0) ? "Yes" : "No");
+ espresponse->print ("\n");
+ espresponse->print ("Authentication: ");
+ if (conf.ap.authmode == WIFI_AUTH_OPEN) {
+ espresponse->print ("None");
+ } else if (conf.ap.authmode == WIFI_AUTH_WEP) {
+ espresponse->print ("WEP");
+ } else if (conf.ap.authmode == WIFI_AUTH_WPA_PSK) {
+ espresponse->print ("WPA");
+ } else if (conf.ap.authmode == WIFI_AUTH_WPA2_PSK) {
+ espresponse->print ("WPA2");
+ } else {
+ espresponse->print ("WPA/WPA2");
+ }
+ espresponse->print ("\n");
+ espresponse->print ("Max Connections: ");
+ espresponse->print (String(conf.ap.max_connection).c_str());
+ espresponse->print ("\n");
+ espresponse->print ("DHCP Server: ");
+ tcpip_adapter_dhcp_status_t dhcp_status;
+ tcpip_adapter_dhcps_get_status (TCPIP_ADAPTER_IF_AP, &dhcp_status);
+ if (dhcp_status == TCPIP_ADAPTER_DHCP_STARTED)espresponse->print ("Started");
+ else espresponse->print ("Stopped");
+ espresponse->print ("\n");
+ espresponse->print ("IP: ");
+ espresponse->print (WiFi.softAPIP().toString().c_str());
+ espresponse->print ("\n");
+ tcpip_adapter_ip_info_t ip_AP;
+ tcpip_adapter_get_ip_info (TCPIP_ADAPTER_IF_AP, &ip_AP);
+ espresponse->print ("Gateway: ");
+ espresponse->print (IPAddress (ip_AP.gw.addr).toString().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Mask: ");
+ espresponse->print (IPAddress (ip_AP.netmask.addr).toString().c_str());
+ espresponse->print ("\n");
+ espresponse->print ("Connected clients: ");
+ wifi_sta_list_t station;
+ tcpip_adapter_sta_list_t tcpip_sta_list;
+ esp_wifi_ap_get_sta_list (&station);
+ tcpip_adapter_get_sta_list (&station, &tcpip_sta_list);
+ espresponse->print (String(station.num).c_str());
+ espresponse->print ("\n");
+ for (int i = 0; i < station.num; i++) {
+ espresponse->print (mac2str(tcpip_sta_list.sta[i].mac));
+ espresponse->print (" ");
+ espresponse->print ( IPAddress (tcpip_sta_list.sta[i].ip.addr).toString().c_str());
+ espresponse->print ("\n");
+ }
+ espresponse->print ("Disabled Mode: ");
+ espresponse->print ("STA (");
+ espresponse->print (WiFi.macAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+ } else if (WiFi.getMode() == WIFI_AP_STA) //we should not be in this state but just in case ....
+ {
+ espresponse->print ("Mixed");
+ espresponse->print ("\n");
+ espresponse->print ("STA (");
+ espresponse->print (WiFi.macAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+ espresponse->print ("AP (");
+ espresponse->print (WiFi.softAPmacAddress().c_str());
+ espresponse->print (")");
+ espresponse->print ("\n");
+
+ } else { //we should not be there if no wifi ....
+ espresponse->print ("Wifi Off");
+ espresponse->print ("\n");
+ }
+ //TODO to complete
+ espresponse->print ("FW version: Marlin ");
+ espresponse->print (SHORT_BUILD_VERSION);
+ espresponse->print (" (ESP32)");
+ }
+ break;
+ //Set ESP mode
+ //cmd is RESTART
+ //[ESP444]
+ case 444:
+ parameter = get_param(cmd_params,"", true);
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type != LEVEL_ADMIN) {
+ response = false;
+ } else
+#endif
+ {
+ if (parameter=="RESTART") {
+ MYSERIAL0.println("Restart ongoing");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Restart ongoing");
+#endif
+ wifi_config.restart_ESP();
+ } else response = false;
+ }
+ if (!response) {
+ if (espresponse)espresponse->println ("Error: Incorrect Command");
+ } else {
+ if (espresponse)espresponse->println ("ok");
+ }
+ break;
+#ifdef ENABLE_AUTHENTICATION
+ //Change / Reset user password
+ //[ESP555]
+ case 555: {
+ if (auth_type == LEVEL_ADMIN) {
+ parameter = get_param (cmd_params, "", true);
+ if (parameter.length() == 0) {
+ Preferences prefs;
+ parameter = DEFAULT_USER_PWD;
+ prefs.begin(NAMESPACE, false);
+ if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()){
+ response = false;
+ espresponse->println ("error");
+ } else espresponse->println ("ok");
+ prefs.end();
+
+ } else {
+ if (isLocalPasswordValid (parameter.c_str() ) ) {
+ Preferences prefs;
+ prefs.begin(NAMESPACE, false);
+ if (prefs.putString(USER_PWD_ENTRY, parameter) != parameter.length()) {
+ response = false;
+ espresponse->println ("error");
+ } else espresponse->println ("ok");
+ prefs.end();
+ } else {
+ espresponse->println ("error");
+ response = false;
+ }
+ }
+ } else {
+ espresponse->println ("error");
+ response = false;
+ }
+ break;
+ }
+#endif
+ //[ESP700]
+ case 700: { //read local file
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type == LEVEL_GUEST) return false;
+#endif
+ cmd_params.trim() ;
+ if ( (cmd_params.length() > 0) && (cmd_params[0] != '/') ) {
+ cmd_params = "/" + cmd_params;
+ }
+ File currentfile = SPIFFS.open (cmd_params, FILE_READ);
+ if (currentfile) {//if file open success
+ //until no line in file
+ while (currentfile.available()) {
+ String currentline = currentfile.readStringUntil('\n');
+ currentline.replace("\n","");
+ currentline.replace("\r","");
+ if (currentline.length() > 0) {
+ int ESPpos = currentline.indexOf ("[ESP");
+ if (ESPpos > -1) {
+ //is there the second part?
+ int ESPpos2 = currentline.indexOf ("]", ESPpos);
+ if (ESPpos2 > -1) {
+ //Split in command and parameters
+ String cmd_part1 = currentline.substring (ESPpos + 4, ESPpos2);
+ String cmd_part2 = "";
+ //is there space for parameters?
+ if (ESPpos2 < currentline.length() ) {
+ cmd_part2 = currentline.substring (ESPpos2 + 1);
+ }
+ //if command is a valid number then execute command
+ if(cmd_part1.toInt()!=0) {
+ if (!execute_internal_command(cmd_part1.toInt(),cmd_part2, auth_type, espresponse)) response = false;
+ }
+ //if not is not a valid [ESPXXX] command ignore it
+ }
+ } else {
+ if (currentline.length() > 0){
+ currentline+="\n";
+ Serial2Socket.push(currentline.c_str());
+ //GCodeQueue::enqueue_one_now(currentline.c_str());
+ }
+ wifi_config.wait (1);
+ }
+ wifi_config.wait (1);
+ }
+ }
+ currentfile.close();
+ if (espresponse)espresponse->println ("ok");
+ } else {
+ if (espresponse)espresponse->println ("error");
+ response = false;
+ }
+ break;
+ }
+ //Format SPIFFS
+ //[ESP710]FORMAT pwd=
+ case 710:
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type != LEVEL_ADMIN) return false;
+#endif
+ parameter = get_param (cmd_params, "", true);
+#ifdef ENABLE_AUTHENTICATION
+ if (auth_type != LEVEL_ADMIN) {
+ espresponse->println ("error");
+ response = false;
+ break;
+ } else
+#endif
+ {
+ if (parameter == "FORMAT") {
+ if (espresponse)espresponse->print ("Formating");
+ SPIFFS.format();
+ if (espresponse)espresponse->println ("...Done");
+ } else {
+ if (espresponse)espresponse->println ("error");
+ response = false;
+ }
+ }
+ break;
+ //get fw version / fw target / hostname / authentication
+ //[ESP800]
+ case 800:
+ {
+ if (!espresponse)return false;
+ String resp;
+ resp = "FW version:";
+ resp += SHORT_BUILD_VERSION;
+ resp += " # FW target:marlin-embedded # FW HW:";
+ #if ENABLED(SDSUPPORT)
+ resp += "Direct SD";
+ #else
+ resp += "No SD";
+ #endif
+ resp += " # primary sd:/sd # secondary sd:none # authentication:";
+ #ifdef ENABLE_AUTHENTICATION
+ resp += "yes";
+ #else
+ resp += "no";
+ #endif
+ resp += " # webcommunication: Sync: ";
+ resp += String(_port + 1);
+ resp += "# hostname:";
+ resp += _hostname;
+ if (WiFi.getMode() == WIFI_AP)resp += "(AP mode)";
+ if (espresponse)espresponse->println (resp.c_str());
+ }
+ break;
+ default:
+ if (espresponse)espresponse->println ("Error: Incorrect Command");
+ response = false;
+ break;
+ }
+ return response;
+}
+
+//login status check
+void Web_Server::handle_login()
+{
+#ifdef ENABLE_AUTHENTICATION
+ String smsg;
+ String sUser,sPassword;
+ String auths;
+ int code = 200;
+ bool msg_alert_error=false;
+ //disconnect can be done anytime no need to check credential
+ if (_webserver->hasArg("DISCONNECT")) {
+ String cookie = _webserver->header("Cookie");
+ int pos = cookie.indexOf("ESPSESSIONID=");
+ String sessionID;
+ if (pos!= -1) {
+ int pos2 = cookie.indexOf(";",pos);
+ sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2);
+ }
+ ClearAuthIP(_webserver->client().remoteIP(), sessionID.c_str());
+ _webserver->sendHeader("Set-Cookie","ESPSESSIONID=0");
+ _webserver->sendHeader("Cache-Control","no-cache");
+ String buffer2send = "{\"status\":\"Ok\",\"authentication_lvl\":\"guest\"}";
+ _webserver->send(code, "application/json", buffer2send);
+ //_webserver->client().stop();
+ return;
+ }
+
+ level_authenticate_type auth_level = is_authenticated();
+ if (auth_level == LEVEL_GUEST) auths = "guest";
+ else if (auth_level == LEVEL_USER) auths = "user";
+ else if (auth_level == LEVEL_ADMIN) auths = "admin";
+ else auths = "???";
+
+ //check is it is a submission or a query
+ if (_webserver->hasArg("SUBMIT")) {
+ //is there a correct list of query?
+ if ( _webserver->hasArg("PASSWORD") && _webserver->hasArg("USER")) {
+ //USER
+ sUser = _webserver->arg("USER");
+ if ( !((sUser == DEFAULT_ADMIN_LOGIN) || (sUser == DEFAULT_USER_LOGIN))) {
+ msg_alert_error=true;
+ smsg = "Error : Incorrect User";
+ code = 401;
+ }
+ if (msg_alert_error == false) {
+ //Password
+ sPassword = _webserver->arg("PASSWORD");
+ String sadminPassword;
+
+ Preferences prefs;
+ prefs.begin(NAMESPACE, true);
+ String defV = DEFAULT_ADMIN_PWD;
+ sadminPassword = prefs.getString(ADMIN_PWD_ENTRY, defV);
+ String suserPassword;
+ defV = DEFAULT_USER_PWD;
+ suserPassword = prefs.getString(USER_PWD_ENTRY, defV);
+ prefs.end();
+
+ if(!(((sUser == DEFAULT_ADMIN_LOGIN) && (strcmp(sPassword.c_str(),sadminPassword.c_str()) == 0)) ||
+ ((sUser == DEFAULT_USER_LOGIN) && (strcmp(sPassword.c_str(),suserPassword.c_str()) == 0)))) {
+ msg_alert_error=true;
+ smsg = "Error: Incorrect password";
+ code = 401;
+ }
+ }
+ } else {
+ msg_alert_error=true;
+ smsg = "Error: Missing data";
+ code = 500;
+ }
+ //change password
+ if (_webserver->hasArg("PASSWORD") && _webserver->hasArg("USER") && _webserver->hasArg("NEWPASSWORD") && (msg_alert_error==false) ) {
+ String newpassword = _webserver->arg("NEWPASSWORD");
+ if (isLocalPasswordValid(newpassword.c_str())) {
+ String spos;
+ if(sUser == DEFAULT_ADMIN_LOGIN) spos = ADMIN_PWD_ENTRY;
+ else spos = USER_PWD_ENTRY;
+
+ Preferences prefs;
+ prefs.begin(NAMESPACE, false);
+ if (prefs.putString(spos.c_str(), newpassword) != newpassword.length()) {
+ msg_alert_error = true;
+ smsg = "Error: Cannot apply changes";
+ code = 500;
+ }
+ prefs.end();
+ } else {
+ msg_alert_error=true;
+ smsg = "Error: Incorrect password";
+ code = 500;
+ }
+ }
+ if ((code == 200) || (code == 500)) {
+ level_authenticate_type current_auth_level;
+ if(sUser == DEFAULT_ADMIN_LOGIN) {
+ current_auth_level = LEVEL_ADMIN;
+ } else if(sUser == DEFAULT_USER_LOGIN){
+ current_auth_level = LEVEL_USER;
+ } else {
+ current_auth_level = LEVEL_GUEST;
+ }
+ //create Session
+ if ((current_auth_level != auth_level) || (auth_level== LEVEL_GUEST)) {
+ auth_ip * current_auth = new auth_ip;
+ current_auth->level = current_auth_level;
+ current_auth->ip=_webserver->client().remoteIP();
+ strcpy(current_auth->sessionID,create_session_ID());
+ strcpy(current_auth->userID,sUser.c_str());
+ current_auth->last_time=millis();
+ if (AddAuthIP(current_auth)) {
+ String tmps ="ESPSESSIONID=";
+ tmps+=current_auth->sessionID;
+ _webserver->sendHeader("Set-Cookie",tmps);
+ _webserver->sendHeader("Cache-Control","no-cache");
+ switch(current_auth->level) {
+ case LEVEL_ADMIN:
+ auths = "admin";
+ break;
+ case LEVEL_USER:
+ auths = "user";
+ break;
+ default:
+ auths = "guest";
+ break;
+ }
+ } else {
+ delete current_auth;
+ msg_alert_error=true;
+ code = 500;
+ smsg = "Error: Too many connections";
+ }
+ }
+ }
+ if (code == 200) smsg = "Ok";
+
+ //build JSON
+ String buffer2send = "{\"status\":\"" + smsg + "\",\"authentication_lvl\":\"";
+ buffer2send += auths;
+ buffer2send += "\"}";
+ _webserver->send(code, "application/json", buffer2send);
+ } else {
+ if (auth_level != LEVEL_GUEST) {
+ String cookie = _webserver->header("Cookie");
+ int pos = cookie.indexOf("ESPSESSIONID=");
+ String sessionID;
+ if (pos!= -1) {
+ int pos2 = cookie.indexOf(";",pos);
+ sessionID = cookie.substring(pos+strlen("ESPSESSIONID="),pos2);
+ auth_ip * current_auth_info = GetAuth(_webserver->client().remoteIP(), sessionID.c_str());
+ if (current_auth_info != NULL){
+ sUser = current_auth_info->userID;
+ }
+ }
+ }
+ String buffer2send = "{\"status\":\"200\",\"authentication_lvl\":\"";
+ buffer2send += auths;
+ buffer2send += "\",\"user\":\"";
+ buffer2send += sUser;
+ buffer2send +="\"}";
+ _webserver->send(code, "application/json", buffer2send);
+ }
+#else
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send(200, "application/json", "{\"status\":\"Ok\",\"authentication_lvl\":\"admin\"}");
+#endif
+}
+//SPIFFS
+//SPIFFS files list and file commands
+void Web_Server::handleFileList ()
+{
+ level_authenticate_type auth_level = is_authenticated();
+ if (auth_level == LEVEL_GUEST) {
+ _upload_status = UPLOAD_STATUS_NONE;
+ _webserver->send (401, "text/plain", "Authentication failed!\n");
+ return;
+ }
+ String path ;
+ String status = "Ok";
+ if ( (_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_CANCELLED) ) {
+ status = "Upload failed";
+ }
+ //be sure root is correct according authentication
+ if (auth_level == LEVEL_ADMIN) {
+ path = "/";
+ } else {
+ path = "/user";
+ }
+ //get current path
+ if (_webserver->hasArg ("path") ) {
+ path += _webserver->arg ("path") ;
+ }
+ //to have a clean path
+ path.trim();
+ path.replace ("//", "/");
+ if (path[path.length() - 1] != '/') {
+ path += "/";
+ }
+ //check if query need some action
+ if (_webserver->hasArg ("action") ) {
+ //delete a file
+ if (_webserver->arg ("action") == "delete" && _webserver->hasArg ("filename") ) {
+ String filename;
+ String shortname = _webserver->arg ("filename");
+ shortname.replace ("/", "");
+ filename = path + _webserver->arg ("filename");
+ filename.replace ("//", "/");
+ if (!SPIFFS.exists (filename) ) {
+ status = shortname + " does not exists!";
+ } else {
+ if (SPIFFS.remove (filename) ) {
+ status = shortname + " deleted";
+ //what happen if no "/." and no other subfiles ?
+ String ptmp = path;
+ if ( (path != "/") && (path[path.length() - 1] = '/') ) {
+ ptmp = path.substring (0, path.length() - 1);
+ }
+ File dir = SPIFFS.open (ptmp);
+ File dircontent = dir.openNextFile();
+ if (!dircontent) {
+ //keep directory alive even empty
+ File r = SPIFFS.open (path + "/.", FILE_WRITE);
+ if (r) {
+ r.close();
+ }
+ }
+ } else {
+ status = "Cannot deleted " ;
+ status += shortname ;
+ }
+ }
+ }
+ //delete a directory
+ if (_webserver->arg ("action") == "deletedir" && _webserver->hasArg ("filename") ) {
+ String filename;
+ String shortname = _webserver->arg ("filename");
+ shortname.replace ("/", "");
+ filename = path + _webserver->arg ("filename");
+ filename += "/";
+ filename.replace ("//", "/");
+ if (filename != "/") {
+ bool delete_error = false;
+ File dir = SPIFFS.open (path + shortname);
+ {
+ File file2deleted = dir.openNextFile();
+ while (file2deleted) {
+ String fullpath = file2deleted.name();
+ if (!SPIFFS.remove (fullpath) ) {
+ delete_error = true;
+ status = "Cannot deleted " ;
+ status += fullpath;
+ }
+ file2deleted = dir.openNextFile();
+ }
+ }
+ if (!delete_error) {
+ status = shortname ;
+ status += " deleted";
+ }
+ }
+ }
+ //create a directory
+ if (_webserver->arg ("action") == "createdir" && _webserver->hasArg ("filename") ) {
+ String filename;
+ filename = path + _webserver->arg ("filename") + "/.";
+ String shortname = _webserver->arg ("filename");
+ shortname.replace ("/", "");
+ filename.replace ("//", "/");
+ if (SPIFFS.exists (filename) ) {
+ status = shortname + " already exists!";
+ } else {
+ File r = SPIFFS.open (filename, FILE_WRITE);
+ if (!r) {
+ status = "Cannot create ";
+ status += shortname ;
+ } else {
+ r.close();
+ status = shortname + " created";
+ }
+ }
+ }
+ }
+ String jsonfile = "{";
+ String ptmp = path;
+ if ( (path != "/") && (path[path.length() - 1] = '/') ) {
+ ptmp = path.substring (0, path.length() - 1);
+ }
+ File dir = SPIFFS.open (ptmp);
+ jsonfile += "\"files\":[";
+ bool firstentry = true;
+ String subdirlist = "";
+ File fileparsed = dir.openNextFile();
+ while (fileparsed) {
+ String filename = fileparsed.name();
+ String size = "";
+ bool addtolist = true;
+ //remove path from name
+ filename = filename.substring (path.length(), filename.length() );
+ //check if file or subfile
+ if (filename.indexOf ("/") > -1) {
+ //Do not rely on "/." to define directory as SPIFFS upload won't create it but directly files
+ //and no need to overload SPIFFS if not necessary to create "/." if no need
+ //it will reduce SPIFFS available space so limit it to creation
+ filename = filename.substring (0, filename.indexOf ("/") );
+ String tag = "*";
+ tag += filename + "*";
+ if (subdirlist.indexOf (tag) > -1 || filename.length() == 0) { //already in list
+ addtolist = false; //no need to add
+ } else {
+ size = "-1"; //it is subfile so display only directory, size will be -1 to describe it is directory
+ if (subdirlist.length() == 0) {
+ subdirlist += "*";
+ }
+ subdirlist += filename + "*"; //add to list
+ }
+ } else {
+ //do not add "." file
+ if (! ( (filename == ".") || (filename == "") ) ) {
+ size = formatBytes (fileparsed.size() );
+ } else {
+ addtolist = false;
+ }
+ }
+ if (addtolist) {
+ if (!firstentry) {
+ jsonfile += ",";
+ } else {
+ firstentry = false;
+ }
+ jsonfile += "{";
+ jsonfile += "\"name\":\"";
+ jsonfile += filename;
+ jsonfile += "\",\"size\":\"";
+ jsonfile += size;
+ jsonfile += "\"";
+ jsonfile += "}";
+ }
+ fileparsed = dir.openNextFile();
+ }
+ jsonfile += "],";
+ jsonfile += "\"path\":\"" + path + "\",";
+ jsonfile += "\"status\":\"" + status + "\",";
+ size_t totalBytes;
+ size_t usedBytes;
+ totalBytes = SPIFFS.totalBytes();
+ usedBytes = SPIFFS.usedBytes();
+ jsonfile += "\"total\":\"" + formatBytes (totalBytes) + "\",";
+ jsonfile += "\"used\":\"" + formatBytes (usedBytes) + "\",";
+ jsonfile.concat (F ("\"occupation\":\"") );
+ jsonfile += String (100 * usedBytes / totalBytes);
+ jsonfile += "\"";
+ jsonfile += "}";
+ path = "";
+ _webserver->sendHeader("Cache-Control", "no-cache");
+ _webserver->send(200, "application/json", jsonfile);
+ _upload_status = UPLOAD_STATUS_NONE;
+}
+
+//SPIFFS files uploader handle
+void Web_Server::SPIFFSFileupload ()
+{
+ //get authentication status
+ level_authenticate_type auth_level= is_authenticated();
+ //Guest cannot upload - only admin
+ if (auth_level == LEVEL_GUEST) {
+ _upload_status = UPLOAD_STATUS_CANCELLED;
+ MYSERIAL0.println("Upload rejected");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload rejected");
+#endif
+ _webserver->client().stop();
+ return;
+ }
+ static String filename;
+ static File fsUploadFile = (File)0;
+
+ HTTPUpload& upload = _webserver->upload();
+ //Upload start
+ //**************
+ if(upload.status == UPLOAD_FILE_START) {
+ String upload_filename = upload.filename;
+ if (upload_filename[0] != '/') filename = "/" + upload_filename;
+ else filename = upload.filename;
+ //according User or Admin the root is different as user is isolate to /user when admin has full access
+ if(auth_level != LEVEL_ADMIN) {
+ upload_filename = filename;
+ filename = "/user" + upload_filename;
+ }
+
+ if (SPIFFS.exists (filename) ) {
+ SPIFFS.remove (filename);
+ }
+ if (fsUploadFile ) {
+ fsUploadFile.close();
+ }
+ //create file
+ fsUploadFile = SPIFFS.open(filename, FILE_WRITE);
+ //check If creation succeed
+ if (fsUploadFile) {
+ //if yes upload is started
+ _upload_status= UPLOAD_STATUS_ONGOING;
+ } else {
+ //if no set cancel flag
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ MYSERIAL0.println("Upload error");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload error");
+#endif
+ _webserver->client().stop();
+ }
+ //Upload write
+ //**************
+ } else if(upload.status == UPLOAD_FILE_WRITE) {
+ //check if file is available and no error
+ if(fsUploadFile && _upload_status == UPLOAD_STATUS_ONGOING) {
+ //no error so write post date
+ fsUploadFile.write(upload.buf, upload.currentSize);
+ } else {
+ //we have a problem set flag UPLOAD_STATUS_CANCELLED
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ fsUploadFile.close();
+ if (SPIFFS.exists (filename) ) {
+ SPIFFS.remove (filename);
+ }
+ _webserver->client().stop();
+ MYSERIAL0.println("Upload error");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload error");
+#endif
+ }
+ //Upload end
+ //**************
+ } else if(upload.status == UPLOAD_FILE_END) {
+ //check if file is still open
+ if(fsUploadFile) {
+ //close it
+ fsUploadFile.close();
+ //check size
+ String sizeargname = upload.filename + "S";
+ fsUploadFile = SPIFFS.open (filename, FILE_READ);
+ uint32_t filesize = fsUploadFile.size();
+ fsUploadFile.close();
+ if (_webserver->hasArg (sizeargname.c_str()) ) {
+ if (_webserver->arg (sizeargname.c_str()) != String(filesize)) {
+ _upload_status = UPLOAD_STATUS_FAILED;
+ SPIFFS.remove (filename);
+ }
+ }
+ if (_upload_status == UPLOAD_STATUS_ONGOING) {
+ _upload_status = UPLOAD_STATUS_SUCCESSFUL;
+ } else {
+ MYSERIAL0.println("Upload error");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload error");
+#endif
+ }
+ } else {
+ //we have a problem set flag UPLOAD_STATUS_CANCELLED
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ _webserver->client().stop();
+ if (SPIFFS.exists (filename) ) {
+ SPIFFS.remove (filename);
+ }
+ MYSERIAL0.println("Upload error");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload error");
+#endif
+ }
+ //Upload cancelled
+ //**************
+ } else {
+ if (_upload_status == UPLOAD_STATUS_ONGOING) {
+ _upload_status = UPLOAD_STATUS_CANCELLED;
+ }
+ if(fsUploadFile)fsUploadFile.close();
+ if (SPIFFS.exists (filename) ) {
+ SPIFFS.remove (filename);
+ }
+ MYSERIAL0.println("Upload error");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload error");
+#endif
+ }
+ wifi_config.wait(0);
+}
+
+//Web Update handler
+void Web_Server::handleUpdate ()
+{
+ level_authenticate_type auth_level = is_authenticated();
+ if (auth_level != LEVEL_ADMIN) {
+ _upload_status = UPLOAD_STATUS_NONE;
+ _webserver->send (403, "text/plain", "Not allowed, log in first!\n");
+ return;
+ }
+ String jsonfile = "{\"status\":\"" ;
+ jsonfile += String(_upload_status);
+ jsonfile += "\"}";
+ //send status
+ _webserver->sendHeader("Cache-Control", "no-cache");
+ _webserver->send(200, "application/json", jsonfile);
+ //if success restart
+ if (_upload_status == UPLOAD_STATUS_SUCCESSFUL) {
+ wifi_config.wait(1000);
+ wifi_config.restart_ESP();
+ } else {
+ _upload_status = UPLOAD_STATUS_NONE;
+ }
+}
+
+//File upload for Web update
+void Web_Server::WebUpdateUpload ()
+{
+ static size_t last_upload_update;
+ static uint32_t maxSketchSpace ;
+ //only admin can update FW
+ if (is_authenticated() != LEVEL_ADMIN) {
+ _upload_status = UPLOAD_STATUS_CANCELLED;
+ _webserver->client().stop();
+ MYSERIAL0.println("Upload rejected");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload rejected");
+#endif
+ return;
+ }
+
+ //get current file ID
+ HTTPUpload& upload = _webserver->upload();
+ //Upload start
+ //**************
+ if(upload.status == UPLOAD_FILE_START) {
+ MYSERIAL0.println("Update Firmware");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Update Firmware");
+#endif
+ _upload_status= UPLOAD_STATUS_ONGOING;
+
+ //Not sure can do OTA on 2Mb board
+ maxSketchSpace = (ESP.getFlashChipSize() > 0x20000) ? 0x140000 : 0x140000 / 2;
+ last_upload_update = 0;
+ if(!Update.begin(maxSketchSpace)) { //start with max available size
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ MYSERIAL0.println("Update cancelled");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Update cancelled");
+#endif
+ _webserver->client().stop();
+ return;
+ } else {
+ MYSERIAL0.println("Update 0%");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Update 0%");
+#endif
+ }
+ //Upload write
+ //**************
+ } else if(upload.status == UPLOAD_FILE_WRITE) {
+ //check if no error
+ if (_upload_status == UPLOAD_STATUS_ONGOING) {
+ //we do not know the total file size yet but we know the available space so let's use it
+ if ( ((100 * upload.totalSize) / maxSketchSpace) !=last_upload_update) {
+ last_upload_update = (100 * upload.totalSize) / maxSketchSpace;
+ String s = "Update ";
+ s+= String(last_upload_update);
+ s+="%";
+ MYSERIAL0.println(s.c_str());
+#if NUM_SERIAL > 1
+ MYSERIAL1.println(s.c_str());
+#endif
+ }
+ if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ }
+ }
+ //Upload end
+ //**************
+ } else if(upload.status == UPLOAD_FILE_END) {
+ if(Update.end(true)) { //true to set the size to the current progress
+ //Now Reboot
+ MYSERIAL0.println("Update 100%");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Update 100%");
+#endif
+ _upload_status=UPLOAD_STATUS_SUCCESSFUL;
+ }
+ } else if(upload.status == UPLOAD_FILE_ABORTED) {
+ MYSERIAL0.println("Update failed");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Update failed");
+#endif
+ Update.end();
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ }
+ wifi_config.wait(0);
+}
+
+
+#if ENABLED(SDSUPPORT)
+
+//direct SD files list//////////////////////////////////////////////////
+void Web_Server::handle_direct_SDFileList()
+{
+ //this is only for admin an user
+ if (is_authenticated() == LEVEL_GUEST) {
+ _upload_status=UPLOAD_STATUS_NONE;
+ _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}");
+ return;
+ }
+
+
+ String path="/";
+ String sstatus="Ok";
+ if ((_upload_status == UPLOAD_STATUS_FAILED) || (_upload_status == UPLOAD_STATUS_CANCELLED)) {
+ sstatus = "Upload failed";
+ _upload_status = UPLOAD_STATUS_NONE;
+ }
+ bool list_files = true;
+ ESP_SD card;
+ int8_t state = card.card_status();
+ if (state != 1){
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send(200, "application/json", "{\"status\":\"No SD Card\"}");
+ return;
+ }
+
+ //get current path
+ if(_webserver->hasArg("path")) {
+ path += _webserver->arg("path") ;
+ }
+ //to have a clean path
+ path.trim();
+ path.replace("//","/");
+
+ //check if query need some action
+ if(_webserver->hasArg("action")) {
+ //delete a file
+ if(_webserver->arg("action") == "delete" && _webserver->hasArg("filename")) {
+ String filename;
+ String shortname = _webserver->arg("filename");
+ filename = path + shortname;
+ shortname.replace("/","");
+ filename.replace("//","/");
+
+ if(!card.exists(filename.c_str())) {
+ sstatus = shortname + " does not exist!";
+ } else {
+ if (card.remove(filename.c_str())) {
+ sstatus = shortname + " deleted";
+ } else {
+ sstatus = "Cannot deleted ";
+ sstatus+=shortname ;
+ }
+ }
+ }
+ //delete a directory
+ if( _webserver->arg("action") == "deletedir" && _webserver->hasArg("filename")) {
+ String filename;
+ String shortname = _webserver->arg("filename");
+ shortname.replace("/","");
+ filename = path + "/" + shortname;
+ filename.replace("//","/");
+ if (filename != "/") {
+ if(!card.dir_exists(filename.c_str())) {
+ sstatus = shortname + " does not exist!";
+ } else {
+ if (!card.rmdir(filename.c_str())) {
+ sstatus ="Error deleting: ";
+ sstatus += shortname ;
+ } else {
+ sstatus = shortname ;
+ sstatus+=" deleted";
+ }
+ }
+ } else {
+ sstatus ="Cannot delete root";
+ }
+ }
+ //create a directory
+ if( _webserver->arg("action")=="createdir" && _webserver->hasArg("filename")) {
+ String filename;
+ String shortname = _webserver->arg("filename");
+ filename = path + shortname;
+ shortname.replace("/","");
+ filename.replace("//","/");
+ if(card.exists(filename.c_str())) {
+ sstatus = shortname + " already exists!";
+ } else {
+ if (!card.mkdir(filename.c_str())) {
+ sstatus = "Cannot create ";
+ sstatus += shortname ;
+ } else {
+ sstatus = shortname + " created";
+ }
+ }
+ }
+ }
+
+ //check if no need build file list
+ if( _webserver->hasArg("dontlist")) {
+ if( _webserver->arg("dontlist") == "yes") {
+ list_files = false;
+ }
+ }
+ String jsonfile = "{" ;
+ jsonfile+="\"files\":[";
+ if (!card.openDir(path)){
+ String s = "{\"status\":\" ";
+ s += path;
+ s+= " does not exist on SD Card\"}";
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send(200, "application/json", s.c_str());
+ return;
+ }
+ if (list_files) {
+ char name[13];
+ uint32_t size;
+ bool isFile;
+ uint i = 0;
+ while (card.readDir(name,&size ,&isFile)) {
+ if (i>0) {
+ jsonfile+=",";
+ }
+ jsonfile+="{\"name\":\"";
+ jsonfile+=name;
+ jsonfile+="\",\"shortname\":\"";
+ jsonfile+=name;
+ jsonfile+="\",\"size\":\"";
+ if (isFile)jsonfile+=formatBytes(size);
+ else jsonfile+="-1";
+ jsonfile+="\",\"datetime\":\"";
+ //TODO datatime
+ jsonfile+="\"}";
+ i++;
+ wifi_config.wait(1);
+ }
+ jsonfile+="],\"path\":\"";
+ jsonfile+=path + "\",";
+ }
+ static uint32_t volTotal = card.card_total_space();
+ static uint32_t volUsed = card.card_used_space();;
+ //TODO
+ //Get right values
+ uint32_t occupedspace = (volUsed/volTotal)*100;
+ jsonfile+="\"total\":\"";
+ if ( (occupedspace <= 1) && (volTotal!=volUsed)) {
+ occupedspace=1;
+ }
+ jsonfile+= formatBytes(volTotal); ;
+
+ jsonfile+="\",\"used\":\"";
+ jsonfile+= formatBytes(volUsed); ;
+ jsonfile+="\",\"occupation\":\"";
+ jsonfile+=String(occupedspace);
+ jsonfile+= "\",";
+
+ jsonfile+= "\"mode\":\"direct\",";
+ jsonfile+= "\"status\":\"";
+ jsonfile+=sstatus + "\"";
+ jsonfile+= "}";
+
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send (200, "application/json", jsonfile.c_str());
+ _upload_status=UPLOAD_STATUS_NONE;
+}
+
+//SD File upload with direct access to SD///////////////////////////////
+void Web_Server::SDFile_direct_upload()
+{
+ static ESP_SD sdfile;
+ static String upload_filename;
+ //this is only for admin and user
+ if (is_authenticated() == LEVEL_GUEST) {
+ _upload_status=UPLOAD_STATUS_NONE;
+ _webserver->send(401, "application/json", "{\"status\":\"Authentication failed!\"}");
+ return;
+ }
+ //retrieve current file id
+ HTTPUpload& upload = _webserver->upload();
+
+ //Upload start
+ //**************
+ if(upload.status == UPLOAD_FILE_START) {
+ upload_filename = upload.filename;
+ if (upload_filename[0] != '/') {
+ upload_filename = "/" + upload.filename;
+ }
+ upload_filename= sdfile.makepath83(upload_filename);
+ if ( sdfile.card_status() != 1) {
+ _upload_status=UPLOAD_STATUS_CANCELLED;
+ MYSERIAL0.println("Upload cancelled");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload cancelled");
+#endif
+ _webserver->client().stop();
+ return;
+ }
+ if (sdfile.exists (upload_filename.c_str()) ) {
+ sdfile.remove (upload_filename.c_str());
+ }
+
+ if (sdfile.isopen())sdfile.close();
+ if (!sdfile.open (upload_filename.c_str(),false)) {
+ MYSERIAL0.println("Upload cancelled");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload cancelled");
+#endif
+ _webserver->client().stop();
+ _upload_status = UPLOAD_STATUS_FAILED;
+ return ;
+ } else {
+ _upload_status = UPLOAD_STATUS_ONGOING;
+ }
+ //Upload write
+ //**************
+ } else if(upload.status == UPLOAD_FILE_WRITE) {
+ //we need to check SD is inside
+ if ( sdfile.card_status() != 1) {
+ sdfile.close();
+ MYSERIAL0.println("Upload failed");
+ #if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload failed");
+ #endif
+ if (sdfile.exists (upload_filename.c_str()) ) {
+ sdfile.remove (upload_filename.c_str());
+ }
+ _webserver->client().stop();
+ return;
+ }
+ if (sdfile.isopen()) {
+ if ( (_upload_status = UPLOAD_STATUS_ONGOING) && (upload.currentSize > 0)) {
+ sdfile.write (upload.buf, upload.currentSize);
+ }
+ }
+ //Upload end
+ //**************
+ } else if(upload.status == UPLOAD_FILE_END) {
+ sdfile.close();
+ uint32_t filesize = sdfile.size();
+ String sizeargname = upload.filename + "S";
+ if (_webserver->hasArg (sizeargname.c_str()) ) {
+ if (_webserver->arg (sizeargname.c_str()) != String(filesize)) {
+ MYSERIAL0.println("Upload failed");
+ #if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload failed");
+ #endif
+ _upload_status = UPLOAD_STATUS_FAILED;
+ }
+ }
+ if (_upload_status == UPLOAD_STATUS_ONGOING) {
+ _upload_status = UPLOAD_STATUS_SUCCESSFUL;
+ }
+ } else {//Upload cancelled
+ _upload_status=UPLOAD_STATUS_FAILED;
+
+ MYSERIAL0.println("Upload failed");
+#if NUM_SERIAL > 1
+ MYSERIAL1.println("Upload failed");
+#endif
+ _webserver->client().stop();
+ sdfile.close();
+ if (sdfile.exists (upload_filename.c_str()) ) {
+ sdfile.remove (upload_filename.c_str());
+ }
+ }
+ wifi_config.wait(0);
+}
+#endif
+
+void Web_Server::handle(){
+static uint32_t timeout = millis();
+#ifdef ENABLE_CAPTIVE_PORTAL
+ if(WiFi.getMode() == WIFI_AP){
+ dnsServer.processNextRequest();
+ }
+#endif
+ if (_webserver)_webserver->handleClient();
+ if (_socket_server && _setupdone) {
+ Serial2Socket.handle_flush();
+ _socket_server->loop();
+ }
+ if ((millis() - timeout) > 10000) {
+ if (_socket_server){
+ String s = "PING:";
+ s+=String(_id_connection);
+ _socket_server->broadcastTXT(s);
+ timeout=millis();
+ }
+ }
+}
+
+
+void Web_Server::handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length) {
+
+ switch(type) {
+ case WStype_DISCONNECTED:
+ //USE_SERIAL.printf("[%u] Disconnected!\n", num);
+ break;
+ case WStype_CONNECTED:
+ {
+ IPAddress ip = _socket_server->remoteIP(num);
+ //USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+ String s = "CURRENT_ID:" + String(num);
+ // send message to client
+ _id_connection = num;
+ _socket_server->sendTXT(_id_connection, s);
+ s = "ACTIVE_ID:" + String(_id_connection);
+ _socket_server->broadcastTXT(s);
+ }
+ break;
+ case WStype_TEXT:
+ //USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+
+ // send message to client
+ // webSocket.sendTXT(num, "message here");
+
+ // send data to all connected clients
+ // webSocket.broadcastTXT("message here");
+ break;
+ case WStype_BIN:
+ //USE_SERIAL.printf("[%u] get binary length: %u\n", num, length);
+ //hexdump(payload, length);
+
+ // send message to client
+ // webSocket.sendBIN(num, payload, length);
+ break;
+ default:
+ break;
+ }
+
+}
+
+//just simple helper to convert mac address to string
+char * Web_Server::mac2str (uint8_t mac [8])
+{
+ static char macstr [18];
+ if (0 > sprintf (macstr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) ) {
+ strcpy (macstr, "00:00:00:00:00:00");
+ }
+ return macstr;
+}
+
+String Web_Server::get_Splited_Value(String data, char separator, int index)
+{
+ int found = 0;
+ int strIndex[] = {0, -1};
+ int maxIndex = data.length()-1;
+
+ for(int i=0; i<=maxIndex && found<=index; i++){
+ if(data.charAt(i)==separator || i==maxIndex){
+ found++;
+ strIndex[0] = strIndex[1]+1;
+ strIndex[1] = (i == maxIndex) ? i+1 : i;
+ }
+ }
+
+ return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
+}
+
+
+//helper to format size to readable string
+String Web_Server::formatBytes (uint32_t bytes)
+{
+ if (bytes < 1024) {
+ return String (bytes) + " B";
+ } else if (bytes < (1024 * 1024) ) {
+ return String (bytes / 1024.0) + " KB";
+ } else if (bytes < (1024 * 1024 * 1024) ) {
+ return String (bytes / 1024.0 / 1024.0) + " MB";
+ } else {
+ return String (bytes / 1024.0 / 1024.0 / 1024.0) + " GB";
+ }
+}
+
+//helper to extract content type from file extension
+//Check what is the content tye according extension file
+String Web_Server::getContentType (String filename)
+{
+ String file_name = filename;
+ file_name.toLowerCase();
+ if (filename.endsWith (".htm") ) {
+ return "text/html";
+ } else if (file_name.endsWith (".html") ) {
+ return "text/html";
+ } else if (file_name.endsWith (".css") ) {
+ return "text/css";
+ } else if (file_name.endsWith (".js") ) {
+ return "application/javascript";
+ } else if (file_name.endsWith (".png") ) {
+ return "image/png";
+ } else if (file_name.endsWith (".gif") ) {
+ return "image/gif";
+ } else if (file_name.endsWith (".jpeg") ) {
+ return "image/jpeg";
+ } else if (file_name.endsWith (".jpg") ) {
+ return "image/jpeg";
+ } else if (file_name.endsWith (".ico") ) {
+ return "image/x-icon";
+ } else if (file_name.endsWith (".xml") ) {
+ return "text/xml";
+ } else if (file_name.endsWith (".pdf") ) {
+ return "application/x-pdf";
+ } else if (file_name.endsWith (".zip") ) {
+ return "application/x-zip";
+ } else if (file_name.endsWith (".gz") ) {
+ return "application/x-gzip";
+ } else if (file_name.endsWith (".txt") ) {
+ return "text/plain";
+ }
+ return "application/octet-stream";
+}
+
+//check authentification
+level_authenticate_type Web_Server::is_authenticated()
+{
+#ifdef ENABLE_AUTHENTICATION
+ if (_webserver->hasHeader ("Cookie") ) {
+ String cookie = _webserver->header ("Cookie");
+ int pos = cookie.indexOf ("ESPSESSIONID=");
+ if (pos != -1) {
+ int pos2 = cookie.indexOf (";", pos);
+ String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2);
+ IPAddress ip = _webserver->client().remoteIP();
+ //check if cookie can be reset and clean table in same time
+ return ResetAuthIP (ip, sessionID.c_str() );
+ }
+ }
+ return LEVEL_GUEST;
+#else
+ return LEVEL_ADMIN;
+#endif
+}
+
+#ifdef ENABLE_AUTHENTICATION
+
+bool Web_Server::isLocalPasswordValid (const char * password)
+{
+ char c;
+ //limited size
+ if ( (strlen (password) > MAX_LOCAL_PASSWORD_LENGTH) || (strlen (password) < MIN_LOCAL_PASSWORD_LENGTH) ) {
+ return false;
+ }
+ //no space allowed
+ for (int i = 0; i < strlen (password); i++) {
+ c = password[i];
+ if (c == ' ') {
+ return false;
+ }
+ }
+ return true;
+}
+
+//add the information in the linked list if possible
+bool Web_Server::AddAuthIP (auth_ip * item)
+{
+ if (_nb_ip > MAX_AUTH_IP) {
+ return false;
+ }
+ item->_next = _head;
+ _head = item;
+ _nb_ip++;
+ return true;
+}
+
+//Session ID based on IP and time using 16 char
+char * Web_Server::create_session_ID()
+{
+ static char sessionID[17];
+//reset SESSIONID
+ for (int i = 0; i < 17; i++) {
+ sessionID[i] = '\0';
+ }
+//get time
+ uint32_t now = millis();
+//get remote IP
+ IPAddress remoteIP = _webserver->client().remoteIP();
+//generate SESSIONID
+ if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) {
+ strcpy (sessionID, "NONE");
+ }
+ return sessionID;
+}
+
+
+bool Web_Server::ClearAuthIP (IPAddress ip, const char * sessionID)
+{
+ auth_ip * current = _head;
+ auth_ip * previous = NULL;
+ bool done = false;
+ while (current) {
+ if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) {
+ //remove
+ done = true;
+ if (current == _head) {
+ _head = current->_next;
+ _nb_ip--;
+ delete current;
+ current = _head;
+ } else {
+ previous->_next = current->_next;
+ _nb_ip--;
+ delete current;
+ current = previous->_next;
+ }
+ } else {
+ previous = current;
+ current = current->_next;
+ }
+ }
+ return done;
+}
+
+//Get info
+auth_ip * Web_Server::GetAuth (IPAddress ip, const char * sessionID)
+{
+ auth_ip * current = _head;
+ //auth_ip * previous = NULL;
+ //get time
+ //uint32_t now = millis();
+ while (current) {
+ if (ip == current->ip) {
+ if (strcmp (sessionID, current->sessionID) == 0) {
+ //found
+ return current;
+ }
+ }
+ //previous = current;
+ current = current->_next;
+ }
+ return NULL;
+}
+
+//Review all IP to reset timers
+level_authenticate_type Web_Server::ResetAuthIP (IPAddress ip, const char * sessionID)
+{
+ auth_ip * current = _head;
+ auth_ip * previous = NULL;
+ //get time
+ //uint32_t now = millis();
+ while (current) {
+ if ( (millis() - current->last_time) > 360000) {
+ //remove
+ if (current == _head) {
+ _head = current->_next;
+ _nb_ip--;
+ delete current;
+ current = _head;
+ } else {
+ previous->_next = current->_next;
+ _nb_ip--;
+ delete current;
+ current = previous->_next;
+ }
+ } else {
+ if (ip == current->ip) {
+ if (strcmp (sessionID, current->sessionID) == 0) {
+ //reset time
+ current->last_time = millis();
+ return (level_authenticate_type) current->level;
+ }
+ }
+ previous = current;
+ current = current->_next;
+ }
+ }
+ return LEVEL_GUEST;
+}
+#endif
+
+String Web_Server::get_param (String & cmd_params, const char * id, bool withspace)
+{
+ static String parameter;
+ String sid = id;
+ int start;
+ int end = -1;
+ parameter = "";
+ //if no id it means it is first part of cmd
+ if (strlen (id) == 0) {
+ start = 0;
+ }
+ //else find id position
+ else {
+ start = cmd_params.indexOf (id);
+ }
+ //if no id found and not first part leave
+ if (start == -1 ) {
+ return parameter;
+ }
+ //password and SSID can have space so handle it
+ //if no space expected use space as delimiter
+ if (!withspace) {
+ end = cmd_params.indexOf (" ", start);
+ }
+ //if no end found - take all
+ if (end == -1) {
+ end = cmd_params.length();
+ }
+ //extract parameter
+ parameter = cmd_params.substring (start + strlen (id), end);
+ //be sure no extra space
+ parameter.trim();
+ return parameter;
+}
+
+ESPResponseStream::ESPResponseStream(WebServer * webserver){
+ _header_sent=false;
+ _webserver = webserver;
+}
+
+void ESPResponseStream::println(const char *data){
+ print(data);
+ print("\n");
+}
+
+void ESPResponseStream::print(const char *data){
+ if (!_header_sent) {
+ _webserver->setContentLength(CONTENT_LENGTH_UNKNOWN);
+ _webserver->sendHeader("Content-Type","text/html");
+ _webserver->sendHeader("Cache-Control","no-cache");
+ _webserver->send(200);
+ _header_sent = true;
+ }
+ _buffer+=data;
+ if (_buffer.length() > 1200) {
+ //send data
+ _webserver->sendContent(_buffer);
+ //reset buffer
+ _buffer = "";
+ }
+
+}
+
+void ESPResponseStream::flush(){
+ if(_header_sent) {
+ //send data
+ if(_buffer.length() > 0)_webserver->sendContent(_buffer);
+ //close connection
+ _webserver->sendContent("");
+ }
+ _header_sent = false;
+ _buffer = "";
+
+}
+
+#endif // Enable HTTP
+
+#endif // ENABLE_WIFI
+
+#endif // ARDUINO_ARCH_ESP32
diff --git a/src/web_server.h b/src/web_server.h
new file mode 100644
index 0000000..d96e32e
--- /dev/null
+++ b/src/web_server.h
@@ -0,0 +1,120 @@
+/*
+ web_server.h - wifi services functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+
+#ifndef _WEB_SERVER_H
+#define _WEB_SERVER_H
+
+
+#include "wificonfig.h"
+class WebSocketsServer;
+class WebServer;
+
+//Authentication level
+typedef enum {
+ LEVEL_GUEST = 0,
+ LEVEL_USER = 1,
+ LEVEL_ADMIN = 2
+} level_authenticate_type;
+
+#ifdef ENABLE_AUTHENTICATION
+struct auth_ip {
+ IPAddress ip;
+ level_authenticate_type level;
+ char userID[17];
+ char sessionID[17];
+ uint32_t last_time;
+ auth_ip * _next;
+};
+
+#endif
+
+
+
+
+class ESPResponseStream{
+ public:
+ void print(const char *data);
+ void println(const char *data);
+ void flush();
+ ESPResponseStream(WebServer * webserver);
+ private:
+ bool _header_sent;
+ WebServer * _webserver;
+ String _buffer;
+};
+
+class Web_Server {
+ public:
+ Web_Server();
+ ~Web_Server();
+ bool begin();
+ void end();
+ static void handle();
+ static long get_client_ID();
+ private:
+ static bool _setupdone;
+ static WebServer * _webserver;
+ static long _id_connection;
+ static WebSocketsServer * _socket_server;
+ static uint16_t _port;
+ static uint16_t _data_port;
+ static String _hostname;
+ static uint8_t _upload_status;
+ static char * mac2str (uint8_t mac [8]);
+ static String formatBytes (uint32_t bytes);
+ static String getContentType (String filename);
+ static String get_Splited_Value(String data, char separator, int index);
+ static level_authenticate_type is_authenticated();
+#ifdef ENABLE_AUTHENTICATION
+ static auth_ip * _head;
+ static uint8_t _nb_ip;
+ static bool AddAuthIP (auth_ip * item);
+ static char * create_session_ID();
+ static bool ClearAuthIP (IPAddress ip, const char * sessionID);
+ static auth_ip * GetAuth (IPAddress ip, const char * sessionID);
+ static level_authenticate_type ResetAuthIP (IPAddress ip, const char * sessionID);
+ static bool isLocalPasswordValid (const char * password);
+#endif
+ static String get_param (String & cmd_params, const char * id, bool withspace);
+ static bool execute_internal_command (int cmd, String cmd_params, level_authenticate_type auth_level, ESPResponseStream *espresponse);
+#ifdef ENABLE_SSDP
+ static void handle_SSDP ();
+#endif
+ static void handle_root();
+ static void handle_login();
+ static void handle_not_found ();
+ static void handle_web_command ();
+ static void handle_web_command_silent ();
+ static void handle_Websocket_Event(uint8_t num, uint8_t type, uint8_t * payload, size_t length);
+ static void SPIFFSFileupload ();
+ static void handleFileList ();
+ static void handleUpdate ();
+ static void WebUpdateUpload ();
+#if ENABLED(SDSUPPORT)
+ static void handle_direct_SDFileList();
+ static void SDFile_direct_upload();
+#endif
+};
+
+extern Web_Server web_server;
+
+#endif
+
diff --git a/src/wificonfig.cpp b/src/wificonfig.cpp
new file mode 100644
index 0000000..af0bfe7
--- /dev/null
+++ b/src/wificonfig.cpp
@@ -0,0 +1,429 @@
+/*
+ wificonfig.cpp - wifi functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WIFISUPPORT)
+#include "HAL.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include "wificonfig.h"
+#include "wifiservices.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+esp_err_t esp_task_wdt_reset();
+#ifdef __cplusplus
+}
+#endif
+
+WiFiConfig wifi_config;
+
+bool WiFiConfig::restart_ESP_module = false;
+
+WiFiConfig::WiFiConfig(){
+}
+
+WiFiConfig::~WiFiConfig(){
+ end();
+}
+
+/**
+ * Helper to convert IP string to int
+ */
+
+uint32_t WiFiConfig::IP_int_from_string(String & s){
+ uint32_t ip_int = 0;
+ IPAddress ipaddr;
+ if (ipaddr.fromString(s)) ip_int = ipaddr;
+ return ip_int;
+}
+
+/**
+ * Helper to convert int to IP string
+ */
+
+String WiFiConfig::IP_string_from_int(uint32_t ip_int){
+ IPAddress ipaddr(ip_int);
+ return ipaddr.toString();
+}
+
+/**
+ * Check if Hostname string is valid
+ */
+
+bool WiFiConfig::isHostnameValid (const char * hostname)
+{
+ //limited size
+ char c;
+ if (strlen (hostname) > MAX_HOSTNAME_LENGTH || strlen (hostname) < MIN_HOSTNAME_LENGTH) {
+ return false;
+ }
+ //only letter and digit
+ for (int i = 0; i < strlen (hostname); i++) {
+ c = hostname[i];
+ if (! (isdigit (c) || isalpha (c) || c == '_') ) {
+ return false;
+ }
+ if (c == ' ') {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Check if SSID string is valid
+ */
+
+bool WiFiConfig::isSSIDValid (const char * ssid)
+{
+ //limited size
+ //char c;
+ if (strlen (ssid) > MAX_SSID_LENGTH || strlen (ssid) < MIN_SSID_LENGTH) {
+ return false;
+ }
+ //only printable
+ for (int i = 0; i < strlen (ssid); i++) {
+ if (!isPrintable (ssid[i]) ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Check if password string is valid
+ */
+
+bool WiFiConfig::isPasswordValid (const char * password)
+{
+ if (strlen (password) == 0) return true; //open network
+ //limited size
+ if ((strlen (password) > MAX_PASSWORD_LENGTH) || (strlen (password) < MIN_PASSWORD_LENGTH)) {
+ return false;
+ }
+ //no space allowed ?
+ /* for (int i = 0; i < strlen (password); i++)
+ if (password[i] == ' ') {
+ return false;
+ }*/
+ return true;
+}
+
+/**
+ * Check if IP string is valid
+ */
+bool WiFiConfig::isValidIP(const char * string){
+ IPAddress ip;
+ return ip.fromString(string);
+}
+
+/*
+ * delay is to avoid with asyncwebserver and may need to wait sometimes
+ */
+void WiFiConfig::wait(uint32_t milliseconds){
+ uint32_t timeout = millis();
+ esp_task_wdt_reset(); //for a wait 0;
+ //wait feeding WDT
+ while ( (millis() - timeout) < milliseconds) {
+ esp_task_wdt_reset();
+ }
+}
+
+/**
+ * WiFi events
+ * SYSTEM_EVENT_WIFI_READY < ESP32 WiFi ready
+ * SYSTEM_EVENT_SCAN_DONE < ESP32 finish scanning AP
+ * SYSTEM_EVENT_STA_START < ESP32 station start
+ * SYSTEM_EVENT_STA_STOP < ESP32 station stop
+ * SYSTEM_EVENT_STA_CONNECTED < ESP32 station connected to AP
+ * SYSTEM_EVENT_STA_DISCONNECTED < ESP32 station disconnected from AP
+ * SYSTEM_EVENT_STA_AUTHMODE_CHANGE < the auth mode of AP connected by ESP32 station changed
+ * SYSTEM_EVENT_STA_GOT_IP < ESP32 station got IP from connected AP
+ * SYSTEM_EVENT_STA_LOST_IP < ESP32 station lost IP and the IP is reset to 0
+ * SYSTEM_EVENT_STA_WPS_ER_SUCCESS < ESP32 station wps succeeds in enrollee mode
+ * SYSTEM_EVENT_STA_WPS_ER_FAILED < ESP32 station wps fails in enrollee mode
+ * SYSTEM_EVENT_STA_WPS_ER_TIMEOUT < ESP32 station wps timeout in enrollee mode
+ * SYSTEM_EVENT_STA_WPS_ER_PIN < ESP32 station wps pin code in enrollee mode
+ * SYSTEM_EVENT_AP_START < ESP32 soft-AP start
+ * SYSTEM_EVENT_AP_STOP < ESP32 soft-AP stop
+ * SYSTEM_EVENT_AP_STACONNECTED < a station connected to ESP32 soft-AP
+ * SYSTEM_EVENT_AP_STADISCONNECTED < a station disconnected from ESP32 soft-AP
+ * SYSTEM_EVENT_AP_PROBEREQRECVED < Receive probe request packet in soft-AP interface
+ * SYSTEM_EVENT_GOT_IP6 < ESP32 station or ap or ethernet interface v6IP addr is preferred
+ * SYSTEM_EVENT_ETH_START < ESP32 ethernet start
+ * SYSTEM_EVENT_ETH_STOP < ESP32 ethernet stop
+ * SYSTEM_EVENT_ETH_CONNECTED < ESP32 ethernet phy link up
+ * SYSTEM_EVENT_ETH_DISCONNECTED < ESP32 ethernet phy link down
+ * SYSTEM_EVENT_ETH_GOT_IP < ESP32 ethernet got IP from connected AP
+ * SYSTEM_EVENT_MAX
+ */
+
+void WiFiConfig::WiFiEvent(WiFiEvent_t event)
+{
+ switch (event)
+ {
+ case SYSTEM_EVENT_STA_GOT_IP:
+ MYSERIAL0.println ("Connected");
+ MYSERIAL0.println(WiFi.localIP());
+ break;
+ case SYSTEM_EVENT_STA_DISCONNECTED:
+ MYSERIAL0.println("WiFi lost connection");
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Get WiFi signal strength
+ */
+int32_t WiFiConfig::getSignal (int32_t RSSI)
+{
+ if (RSSI <= -100) {
+ return 0;
+ }
+ if (RSSI >= -50) {
+ return 100;
+ }
+ return (2 * (RSSI + 100) );
+}
+
+/*
+ * Connect client to AP
+ */
+
+bool WiFiConfig::ConnectSTA2AP(){
+ String msg, msg_out;
+ uint8_t count = 0;
+ uint8_t dot = 0;
+ wl_status_t status = WiFi.status();
+ while (status != WL_CONNECTED && count < 40) {
+
+ switch (status) {
+ case WL_NO_SSID_AVAIL:
+ msg="No SSID";
+ break;
+ case WL_CONNECT_FAILED:
+ msg="Connection failed";
+ break;
+ case WL_CONNECTED:
+ break;
+ default:
+ if ((dot>3) || (dot==0) ){
+ dot=0;
+ msg_out = "Connecting";
+ }
+ msg_out+=".";
+ msg= msg_out;
+ dot++;
+ break;
+ }
+ MYSERIAL0.println(msg.c_str());
+ wait (500);
+ count++;
+ status = WiFi.status();
+ }
+ return (status == WL_CONNECTED);
+}
+
+/*
+ * Start client mode (Station)
+ */
+
+bool WiFiConfig::StartSTA(){
+ String defV;
+ Preferences prefs;
+ //stop active service
+ wifi_services.end();
+ //Sanity check
+ if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect();
+ if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect();
+ WiFi.enableAP (false);
+ WiFi.mode(WIFI_STA);
+ //Get parameters for STA
+ prefs.begin(NAMESPACE, true);
+ defV = DEFAULT_HOSTNAME;
+ String h = prefs.getString(HOSTNAME_ENTRY, defV);
+ WiFi.setHostname(h.c_str());
+ //SSID
+ defV = DEFAULT_STA_SSID;
+ String SSID = prefs.getString(STA_SSID_ENTRY, defV);
+ if (SSID.length() == 0)SSID = DEFAULT_STA_SSID;
+ //password
+ defV = DEFAULT_STA_PWD;
+ String password = prefs.getString(STA_PWD_ENTRY, defV);
+ int8_t IP_mode = prefs.getChar(STA_IP_MODE_ENTRY, DHCP_MODE);
+ //IP
+ defV = DEFAULT_STA_IP;
+ int32_t IP = prefs.getInt(STA_IP_ENTRY, IP_int_from_string(defV));
+ //GW
+ defV = DEFAULT_STA_GW;
+ int32_t GW = prefs.getInt(STA_GW_ENTRY, IP_int_from_string(defV));
+ //MK
+ defV = DEFAULT_STA_MK;
+ int32_t MK = prefs.getInt(STA_MK_ENTRY, IP_int_from_string(defV));
+ prefs.end();
+ //if not DHCP
+ if (IP_mode != DHCP_MODE) {
+ IPAddress ip(IP), mask(MK), gateway(GW);
+ WiFi.config(ip, gateway,mask);
+ }
+ if (WiFi.begin(SSID.c_str(), (password.length() > 0)?password.c_str():NULL)){
+ MYSERIAL0.print("\nClient Started\nConnecting ");
+ MYSERIAL0.println(SSID.c_str());
+ return ConnectSTA2AP();
+ } else {
+ MYSERIAL0.println("\nStarting client failed");
+ return false;
+ }
+}
+
+/**
+ * Setup and start Access point
+ */
+
+bool WiFiConfig::StartAP(){
+ String defV;
+ Preferences prefs;
+ //stop active services
+ wifi_services.end();
+ //Sanity check
+ if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect();
+ if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect();
+ WiFi.enableSTA (false);
+ WiFi.mode(WIFI_AP);
+ //Get parameters for AP
+ prefs.begin(NAMESPACE, true);
+ //SSID
+ defV = DEFAULT_AP_SSID;
+ String SSID = prefs.getString(AP_SSID_ENTRY, defV);
+ if (SSID.length() == 0)SSID = DEFAULT_AP_SSID;
+ //password
+ defV = DEFAULT_AP_PWD;
+ String password = prefs.getString(AP_PWD_ENTRY, defV);
+ //channel
+ int8_t channel = prefs.getChar(AP_CHANNEL_ENTRY, DEFAULT_AP_CHANNEL);
+ if (channel == 0)channel = DEFAULT_AP_CHANNEL;
+ //IP
+ defV = DEFAULT_AP_IP;
+ int32_t IP = prefs.getInt(AP_IP_ENTRY, IP_int_from_string(defV));
+ if (IP==0){
+ IP = IP_int_from_string(defV);
+ }
+ prefs.end();
+ IPAddress ip(IP);
+ IPAddress mask;
+ mask.fromString(DEFAULT_AP_MK);
+ //Set static IP
+ WiFi.softAPConfig(ip, ip, mask);
+ //Start AP
+ if(WiFi.softAP(SSID.c_str(), (password.length() > 0)?password.c_str():NULL, channel)) {
+ MYSERIAL0.print("\nAP Started ");
+ MYSERIAL0.println(WiFi.softAPIP().toString());
+ return true;
+ } else {
+ MYSERIAL0.println("\nStarting AP failed");
+ return false;
+ }
+}
+
+/**
+ * Stop WiFi
+ */
+
+void WiFiConfig::StopWiFi(){
+ //Sanity check
+ if((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))WiFi.disconnect(true);
+ if((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))WiFi.softAPdisconnect(true);
+ wifi_services.end();
+ WiFi.mode(WIFI_OFF);
+ MYSERIAL0.println("\nWiFi Off");
+}
+
+/**
+ * begin WiFi setup
+ */
+void WiFiConfig::begin() {
+ Preferences prefs;
+ //stop active services
+ wifi_services.end();
+ //setup events
+ WiFi.onEvent(WiFiConfig::WiFiEvent);
+ //open preferences as read-only
+ prefs.begin(NAMESPACE, true);
+ int8_t wifiMode = prefs.getChar(ESP_WIFI_MODE, DEFAULT_WIFI_MODE);
+ prefs.end();
+ if (wifiMode == ESP_WIFI_AP) {
+ StartAP();
+ //start services
+ wifi_services.begin();
+ } else if (wifiMode == ESP_WIFI_STA){
+ if(!StartSTA()){
+ MYSERIAL0.println("\nCannot connect to AP");
+ StartAP();
+ }
+ //start services
+ wifi_services.begin();
+ }else WiFi.mode(WIFI_OFF);
+}
+
+/**
+ * End WiFi
+ */
+void WiFiConfig::end() {
+ StopWiFi();
+}
+
+/**
+ * Restart ESP
+ */
+void WiFiConfig::restart_ESP(){
+ restart_ESP_module=true;
+}
+
+/**
+ * Handle not critical actions that must be done in sync environement
+ */
+void WiFiConfig::handle() {
+ //in case of restart requested
+ if (restart_ESP_module) {
+ end();
+ ESP.restart();
+ while (1) {};
+ }
+
+ //Services
+ wifi_services.handle();
+}
+
+
+#endif // ENABLE_WIFI
+
+#endif // ARDUINO_ARCH_ESP32
diff --git a/src/wificonfig.h b/src/wificonfig.h
new file mode 100644
index 0000000..3d31cab
--- /dev/null
+++ b/src/wificonfig.h
@@ -0,0 +1,133 @@
+/*
+ wificonfig.h - wifi functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+//Services that need to be used
+#include "../../inc/MarlinConfigPre.h"
+#define ENABLE_MDNS
+#define ENABLE_OTA
+#define ENABLE_HTTP
+#define ENABLE_SSDP
+#define ENABLE_CAPTIVE_PORTAL
+//Authentication flag is now in Configuration_adv.h
+//#define ENABLE_AUTHENTICATION
+#define ENABLE_SERIAL2SOCKET_OUT
+
+#define ENABLE_SERIAL2SOCKET_IN
+
+//Preferences entries
+#define NAMESPACE "MARLIN"
+#define HOSTNAME_ENTRY "ESP_HOSTNAME"
+#define STA_SSID_ENTRY "STA_SSID"
+#define STA_PWD_ENTRY "STA_PWD"
+#define STA_IP_ENTRY "STA_IP"
+#define STA_GW_ENTRY "STA_GW"
+#define STA_MK_ENTRY "STA_MK"
+#define ESP_WIFI_MODE "WIFI_MODE"
+#define AP_SSID_ENTRY "AP_SSID"
+#define AP_PWD_ENTRY "AP_PWD"
+#define AP_IP_ENTRY "AP_IP"
+#define AP_CHANNEL_ENTRY "AP_CHANNEL"
+#define HTTP_ENABLE_ENTRY "HTTP_ON"
+#define HTTP_PORT_ENTRY "HTTP_PORT"
+#define TELNET_ENABLE_ENTRY "TELNET_ON"
+#define TELNET_PORT_ENTRY "TELNET_PORT"
+#define STA_IP_MODE_ENTRY "STA_IP_MODE"
+
+//Wifi Mode
+#define ESP_WIFI_OFF 0
+#define ESP_WIFI_STA 1
+#define ESP_WIFI_AP 2
+
+#define DHCP_MODE 0
+#define STATIC_MODE 0
+
+//Switch
+#define ESP_SAVE_ONLY 0
+#define ESP_APPLY_NOW 1
+
+//defaults values
+#define DEFAULT_HOSTNAME "marlinesp"
+#define DEFAULT_STA_SSID "MARLIN_ESP"
+#define DEFAULT_STA_PWD "12345678"
+#define DEFAULT_STA_IP "0.0.0.0"
+#define DEFAULT_STA_GW "0.0.0.0"
+#define DEFAULT_STA_MK "0.0.0.0"
+#define DEFAULT_WIFI_MODE ESP_WIFI_AP
+#define DEFAULT_AP_SSID "MARLIN_ESP"
+#define DEFAULT_AP_PWD "12345678"
+#define DEFAULT_AP_IP "192.168.0.1"
+#define DEFAULT_AP_MK "255.255.255.0"
+#define DEFAULT_AP_CHANNEL 1
+#define DEFAULT_WEBSERVER_PORT 80
+#define DEFAULT_HTTP_STATE 1
+#define HIDDEN_PASSWORD "********"
+
+//boundaries
+#define MAX_SSID_LENGTH 32
+#define MIN_SSID_LENGTH 1
+#define MAX_PASSWORD_LENGTH 64
+//min size of password is 0 or upper than 8 char
+//so let set min is 8
+#define MIN_PASSWORD_LENGTH 8
+#define MAX_HOSTNAME_LENGTH 32
+#define MIN_HOSTNAME_LENGTH 1
+#define MAX_HTTP_PORT 65001
+#define MIN_HTTP_PORT 1
+#define MAX_TELNET_PORT 65001
+#define MIN_TELNET_PORT 1
+#define MIN_CHANNEL 1
+#define MAX_CHANNEL 14
+
+
+#ifndef _WIFI_CONFIG_H
+#define _WIFI_CONFIG_H
+
+#include
+
+class WiFiConfig {
+public:
+ WiFiConfig();
+ ~WiFiConfig();
+ static void wait(uint32_t milliseconds);
+ static bool isValidIP(const char * string);
+ static bool isPasswordValid (const char * password);
+ static bool isSSIDValid (const char * ssid);
+ static bool isHostnameValid (const char * hostname);
+ static uint32_t IP_int_from_string(String & s);
+ static String IP_string_from_int(uint32_t ip_int);
+
+ static bool StartAP();
+ static bool StartSTA();
+ static void StopWiFi();
+ static int32_t getSignal (int32_t RSSI);
+ static void begin();
+ static void end();
+ static void handle();
+ static void restart_ESP();
+
+ private :
+ static bool ConnectSTA2AP();
+ static void WiFiEvent(WiFiEvent_t event);
+ static bool restart_ESP_module;
+};
+
+extern WiFiConfig wifi_config;
+
+#endif
diff --git a/src/wifiservices.cpp b/src/wifiservices.cpp
new file mode 100644
index 0000000..59bb716
--- /dev/null
+++ b/src/wifiservices.cpp
@@ -0,0 +1,142 @@
+/*
+ wifiservices.cpp - wifi services functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifdef ARDUINO_ARCH_ESP32
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(WIFISUPPORT)
+
+#include "HAL.h"
+#include
+#include
+#include
+#include
+#include "wificonfig.h"
+#include "wifiservices.h"
+#ifdef ENABLE_MDNS
+#include
+#endif
+#ifdef ENABLE_OTA
+#include
+#endif
+#ifdef ENABLE_HTTP
+#include "web_server.h"
+#endif
+
+WiFiServices wifi_services;
+
+WiFiServices::WiFiServices(){
+}
+WiFiServices::~WiFiServices(){
+ end();
+}
+
+bool WiFiServices::begin(){
+ bool no_error = true;
+ //Sanity check
+ if(WiFi.getMode() == WIFI_OFF) return false;
+ String h;
+ Preferences prefs;
+ //Get hostname
+ String defV = DEFAULT_HOSTNAME;
+ prefs.begin(NAMESPACE, true);
+ h = prefs.getString(HOSTNAME_ENTRY, defV);
+ prefs.end();
+ WiFi.scanNetworks (true);
+ //Start SPIFFS
+ SPIFFS.begin(true);
+
+#ifdef ENABLE_OTA
+ ArduinoOTA
+ .onStart([]() {
+ String type;
+ if (ArduinoOTA.getCommand() == U_FLASH)
+ type = "sketch";
+ else {// U_SPIFFS
+ // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
+ type = "filesystem";
+ SPIFFS.end();
+ }
+ MYSERIAL0.printf("OTA:Start OTA updating %s]\r\n", type.c_str());
+ })
+ .onEnd([]() {
+ MYSERIAL0.println("OTA:End");
+
+ })
+ .onProgress([](unsigned int progress, unsigned int total) {
+ MYSERIAL0.printf("OTA:OTA Progress: %u%%]\r\n", (progress / (total / 100)));
+ })
+ .onError([](ota_error_t error) {
+ MYSERIAL0.printf("OTA: Error(%u)\r\n", error);
+ if (error == OTA_AUTH_ERROR) MYSERIAL0.println("OTA:Auth Failed]");
+ else if (error == OTA_BEGIN_ERROR) MYSERIAL0.println("OTA:Begin Failed");
+ else if (error == OTA_CONNECT_ERROR) MYSERIAL0.println("OTA:Connect Failed");
+ else if (error == OTA_RECEIVE_ERROR) MYSERIAL0.println("OTA:Receive Failed");
+ else if (error == OTA_END_ERROR) MYSERIAL0.println("OTA:End Failed]");
+ });
+ ArduinoOTA.begin();
+#endif
+#ifdef ENABLE_MDNS
+ //no need in AP mode
+ if(WiFi.getMode() == WIFI_STA){
+ //start mDns
+ if (!MDNS.begin(h.c_str())) {
+ MYSERIAL0.println("Cannot start mDNS");
+ no_error = false;
+ } else {
+ MYSERIAL0.printf("Start mDNS with hostname:%s\r\n",h.c_str());
+ }
+ }
+#endif
+#ifdef ENABLE_HTTP
+ web_server.begin();
+#endif
+ return no_error;
+}
+void WiFiServices::end(){
+#ifdef ENABLE_HTTP
+ web_server.end();
+#endif
+ //stop OTA
+#ifdef ENABLE_OTA
+ ArduinoOTA.end();
+#endif
+ //Stop SPIFFS
+ SPIFFS.end();
+
+#ifdef ENABLE_MDNS
+ //Stop mDNS
+ //MDNS.end();
+#endif
+}
+
+void WiFiServices::handle(){
+#ifdef ENABLE_OTA
+ ArduinoOTA.handle();
+#endif
+#ifdef ENABLE_HTTP
+ web_server.handle();
+#endif
+}
+
+#endif // ENABLE_WIFI
+
+#endif // ARDUINO_ARCH_ESP32
diff --git a/src/wifiservices.h b/src/wifiservices.h
new file mode 100644
index 0000000..790fd67
--- /dev/null
+++ b/src/wifiservices.h
@@ -0,0 +1,39 @@
+/*
+ wifiservices.h - wifi services functions class
+
+ Copyright (c) 2014 Luc Lebosse. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+
+
+#ifndef _WIFI_SERVICES_H
+#define _WIFI_SERVICES_H
+
+
+class WiFiServices {
+ public:
+ WiFiServices();
+ ~WiFiServices();
+ static bool begin();
+ static void end();
+ static void handle();
+};
+
+extern WiFiServices wifi_services;
+
+#endif
+