From d0bb72c67dad91b99674ec713630f64ba6ef6755 Mon Sep 17 00:00:00 2001 From: Martin Ger Date: Thu, 30 Nov 2017 15:28:48 +0100 Subject: [PATCH] cli.c new and csvstr() --- SCRIPTING.md | 7 +- firmware/0x00000.bin | Bin 44864 -> 44880 bytes firmware/0x10000.bin | Bin 301404 -> 302140 bytes firmware/sha1sums | 4 +- user/cli.c | 997 +++++++++++++++++++++++++++++++++++++++++ user/global.h | 45 ++ user/lang.c | 76 ++++ user/user_main.c | 1015 +----------------------------------------- 8 files changed, 1132 insertions(+), 1012 deletions(-) create mode 100644 user/cli.c create mode 100644 user/global.h diff --git a/SCRIPTING.md b/SCRIPTING.md index eac47aa..072f3a5 100644 --- a/SCRIPTING.md +++ b/SCRIPTING.md @@ -56,7 +56,7 @@ In general, scripts conform to the following BNF: ::= | | () | not () | retained_topic() | substr(,,) | - json_parse (,) + csvstr(,,) | json_parse (,) := '=' | '>' | gte | str_ge | str_gte | '+' | '-' | '*' | '|' | div @@ -211,6 +211,11 @@ substr(,,) ``` Extracts characters from a string. The two constant numbers give the starting position (first is postion 0) and the length. If the starting position is negative (write it with colons as e.g. "-2"), it counts backwards from the end of the string. +``` +csvstr(,,) +``` +Extracts strings from a CSV-list (correctly a string with a delimiter character). The constant number gives the position (first is postion 1) and the char is the delimiter. Examples: csvstr("one,two,three", 2, ",") is "two", csvstr("system/test/1", 2, "/") is "test" + ``` json_parse (,) ``` diff --git a/firmware/0x00000.bin b/firmware/0x00000.bin index 6c15bd297f21f7eb2159a005ed70c775c472b66a..c0504214d054190fdd9ab164ea819ab5c23dfcb8 100644 GIT binary patch delta 5447 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 K%=EqJo&N(C^5+Zy diff --git a/firmware/0x10000.bin b/firmware/0x10000.bin index 2f92aaf6fe2406b1e0488133eefaa1ce34a76559..0692a0892fedc6e25d5956daff8efc5b46f2ed82 100644 GIT binary patch delta 45874 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 z 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 delta 45006 zcmZU53tUvy_W#*?<_w@`c!}c!%{c?Y@EGCX3sVFp4`PWT8fs+%8Cppy+1naKhcHQz zvBruL&Aiq%Uv#B+Kpxb>LMpSamYQ{~v=m>!%$)Q8?iuTL|35yT@9eYp*=y~!*4}&V z=i!5QB2Vm#+*3rz3Rf{nB6D2N;rBq}yW|uZ;Eb>bOOY+%#*18J%f%4MrZdgtlwj8$ zT@-oErRZ+qop;*hCtODTFzT&!jnJ3&ACjnLT7gtx`Cc_M8DXsJ4gG{5B}%pl{9f02 zeFjzLyOQ{sI^`R?Yb75Y!M{5xt${b4;o5kvIoT|~<9dr9oFE^VB+y{#kQk7F<4*8m z7UZDCI{Bpul2^05cFx|w@wd4y@}_ypN0}_CVF8kqT$zfxKgnVZf0FUV<5`|9SCGe< z^Y=VOzjEBv|K^)A-g$hy=+PvP#@OBBy2qeVCB1P$&>WIzeP(!8IFJs^YEm<^5GuRW z%v6N6z#?ENQ1ndW&%xhODo=B@_8mjajXiyzC6r&~avJNY{I)A5G<~4+;Z)kNz;IaO z@VAZ^7FNu5W$;S2>!Hwi%9p$9L$j!|)b)L+Wr(uQX0lusB87hPxKWZ_v)0>RJC|jt zyxHQ6aSaKxQ)Q8>C~VX{&h;aTBeW93{c&-$?IuoHZ8H@Ftvsa(k&gun{h}XzJlHv6 zlz380uOBCpFtwTER(`8h2M3Gs=|+L_4WonWw|uP$AuslkLYaxpW>$o0L|b3Z(m*Uz zttUm5!!wN|!k?s3&dMaAK+vV#b=guDA>1Nf(BxIF4FJTRM(sK%3nbU{?EcAYWbPftDLnkJe; znqkIpQOw%tDig0#=ZMAymWhNq2fMb#R8u9wH6nH@<&CZtv9qW%$kiFUkn+Seet1I2 zV${o~n>jAlRW|$xRc>}oh#P&AoR}Cao!Da)+P9{QHX*yNTZtc*Ajo4ft%7s3Ykgc| zAGu$0d!#%pSw8IgB<@|xC%K-EPmGZD$tDXY2PKO~G)pAMf9`rWJ~~KANfaWLLf60J z$A$CZi2@b+33_=ziV(>UbH!TosdG!?Bi2qzf*YqKr5NbNXBwZ7>=57N`Y3HygmYE0 zAp|DXrSC>bP0`VcYFEx}chiRI#_hM=N^g>zpwOX~<44b( z?!FYfH?k$)cu_DKs?ftYXK`cbG$Wz%*v6sLFF_%xF?_}@L%6do$+9?zAEYyNUa$$y zL5-i!E+gT}E|w}2o$1W)HbEhcx6S>9==dKK8pq!0)c0wIRvo~$HH~faO(ev*Z@f(q z`YD55KNa0em6fik#luD@Z;XduKx#y?A(*R64yj8pG@H4V4Zl`eD{8Bozeyi$736>T zDV<@i7mGI<`I>}a5^w<(&$@0dIYt|vZH&C9524M^Hr}+Lm-M;#?C>mZ?eMJNwJuBP zNUFT%nq8VgmFHd6rAu}EPsuJ%=`;Q0Pvb8+G*h`wJ`{icj1cKO6YqL{VNze^V7yaj zEwpfo-1ynTAymhATV222S5Cv%y6(RJ;S~OSVjD&6a%l}5TnPVLBFQ)>ex??150r>} z(7jQmVVm)1MIVTx{8U%s1KAPE+W1zHbc*zx)SX=uDc3fxc)&~hnIWp* z`w?p4gvb!Wce>U-nB6B8>A&>VGCQ7aJoeyr9nD|c_;h)i-mnzuMQa=PKT@Yd&zthZ zchpH6hd-HTh~~exiDqAyeIVSeT^yX&FnRK8p=gNCFKvy_tu(`9l{fBQHJ6fdS4h>Y zAZJpnK;?ATf~winIlA$~s!U4iUB2p1sWZ}bc+JU3<#+4fbyY4{UGF^mj86W^+Gt-p zkjg`+?h@ln|?OEIS=DH^cJ+-zmt~Q27@H?$Sx$;k|Q0c@^9ske9JL*D$llkiw zn^E?Ttu&4syC~FVRKn8mlf-_$mbx0at}@b9Mtd@BVa_Di`j-lH{BQBDvoC!`xf<7d zFHfZA8dvYjy;M2Uc&+|9qH~^$cU8Umk)fz&cveG=>(RhI`f2JLEjq2MYD0RN)8qsu*Ren3ol+0U_`v(grX??{>&fMw3(D|Eof!1slbbRGl zTPT86u6LU@N1m!f-@!n;2e^pma988~?>K0La=(NwqqJ&<>ss>!vvQxS_r2%)D?`VX z(%_Dx*`<_Tv;EYbhJIS=+V=jEQSyYb=$*^QTBgMv7tLAAYc|>h=|@eud2CJ}HYf`^ z=gr6==-}hV`ycq5U6Vf$X{yV0{|9+g8R>fWgNOPob-_siwJz6~4~Hc=2aOFLBIr(p z+d}1EN9P#06Za}-wH!Sg?kuk)q?PqG*~zI9X6L!lu8I$nbjnYoUAsP9LzU>paUbm@ zeVx<(ER%Cx{CKKi4_Kdax$Mo8`YBh&K*Dnxf0c!YRQ}4C#uuB%653Jg>TJoHC|^sp zhyYSxHv1)+bgO)!)!?r)ExbB87jaqdHoTp@$x#?JUrvqy2x zsjmE13@x{~UTuA9l=2x$koEt+Y2;7G{An8gQ{-T#Q34On!>;L{h%|hotNfGS4XGPp zlZ~#@1Hj)X%hqDi6DM9S|ZwqWOj~He-e$=%;+hw`)0FT560L9tQREs>|5+8(q55)!Sz2C$D8@ zxuWqkEISdR_+VGc!AX>_ay@*|O!-$`?t|||DUY#SFRIF;EZm_9vHat%dk+=soTtXP z4j&TxD3d{+)}SfVT~`hb@8f)6jFm_yHD$3Y=I~ST-se%KP^+%8!olzP|8hl~5v zLfeY9YG%X6M#HBn<*)2hGr7i)BYzND|5jt~=TC&v=8cVyoU98X$*z!}mXHaJkN*^k z5$EnPMp{byL{Dg*(3sjH#?b`0!NPpbVlz0mH@^O#%)U1B+ZYCc4wNyqQOzs@mIKv5 zJ@5|j6>uKVyCN=czV$eAo&qicOP@zQynqmB#&bBp;W-~+1A7Ki5idfx2ROyzKm}0y zd}HtBmq^-U?7bUlKo$!T-ve|2r+@~4L%JDA1!`YtY`eOTr?u|JCp;y5WCPMp0o*3= z0nD5J^*ec*B;-P?QU6ji&42)$Wm*Cs0Na3#z;a+w)AN+vGmchlYU&+Neh;Fxo0@*T zg+!7*&ER$lIJc?EAd{O2y|}3!|s~rp|f9L__XE2@8OwO_%1A z$wAb-q3Py@q(nzkH#EI^Kbb~o)&}?Q50I%;IqIIUm{idG4epN?le=~NFa6!a%E){j z|L^|pm1X2Qt=Q1?#DnBNeW;w)bkF0&30*fd{rWU{B8cwU&{SGQChBR&hNkV$l8+4Z z6jC;j&j%Vf=Bn!4H@`{VeviSniBD#RAO`N{qi$ zXRjeirOMY4?qhF}Y1G-_j@w0^r}7c^hr7u2!Kq-kaknMwYLu24S)pd;BVL3#;L^IL z&v%oXG0kan_t{H^2gwz2nCz`}kJ(F}qKeD?uf5=+yw)^zAF)F6x~4~($eY1rfm>`Q z(Uc$Np4d#{OiG?fJXL40eB-?M3;%vNyn&;?{D}LBW^$8B4vkdSM7A_;aORreLD!u> z6v3b9-?XclBx7on(R8AP+>R0GN~C+#C#0ySBi}#@s+>b_tcn6;M0JXqU zARkBt!hws6@E+I$)B;O^d>|DF2QGsC6wnMb02M&d!lvF&h)PJlyGJ1(-@f{;;aMwS zjr>wHxOXv51Z$c3D0?YT3+w@!fezpla1qF_Mq_+nc-9c)pQY!rnrV2}I}zbor-pM` z4MW1w0gMO9om7r+-+z$I=bhVQGQGM$K7C|A@pw&o;c|8vb8 z_vcg&127hp)o%U>nMoSlbB~ZO$sV`ubMje42NVR6wGzX$f24MYd_mU7M!lnEF7B{o zrIroPYR5B}wieR_5)$ED9nl`iAB<3P+#h^FM0k7m4_}aYD&Of2?jW;_X5`L?_L~qU zx$o;BmOgTNM0MlA;`crdML0g8~T^Y`hDk@oWBXnlt48rpfj09jC}@ve9ijO`b6{K(dY{ zP4AtC^Ba5$5!PZ?!>Msg(aHg?Si+o*`G6`fHEsKm{D?KnKKGiR2-aC`?!!Nk zQtDjpmVSn9`7-x?KNBfPxo;>salfW5Ka=@bGu;$(DB?!j`+zkEcf>g|o)#^2Pdi5@ zQ@+33eU6L}_{bQ;WsO^e92=XCo+B}&pRy{vG}0(ooJYgt-@-)hy0Y9I^eefyPcbdjJvjnM1?MTPtEMx zm*c{K@Uo_TJ>--gTLeuTRkA6FI>)<580dRcNoo4kK=0O3`Cj*}eQ5FsXS{Gjfv=`< ztSV_SM|>Lkl$;>^RTqJD`q%R#qop$#>`qB&|e>8lH&mn}=-Fm+g z@4nQR&gGTZ2t{;H7w9>veB3n7NJmkf^Kw|z(lEM_7>XgR_Z@ei2%2MPhPJ1cx(g%d zcVt166iFu-f)!gR+vpkRes~~F4VDvGOd0KdYaoqD%r76FRo@ev#bt|G6+c_DDh9@6 z)%3<@y@N0UzP$+VD}X)a?%sj)ZJpdb%)NIIofz)CK5W@XT6qv>8o*-~b-!C1MBf?4 z|2C}cN4CEb(6P0c*ez-1R`dBND`lj|2GfKP{-ME^WRAZ&%pDv}7ljnXXqmlX9Jkc{ zcr+c!Z-h20+{M!(s+C60m9p^- zo@G9r^o#cGhUnOndO?49%P%WK_Rb|248-FE%OiB>){L+`Be|bEiRgDL1oTbIWG*k1 zl^{RFOW%RNpeMgPj@QmAK6vk;Eyp*UTiLzr#&U+sf5_jz#SgU*N$8hm=lI1uHonL< zMsZ{FZ@_RZQ*dE+wS0KiST`9;&E}Pd@md)A0ma-MQ*81Si`S=#m$Zz~yfCuPojsKH zb1Y}6h5LNL%WTF&#m8ry%kIW@qM3PTt`;`+TMB0KY2QX1r#1nmdT|9v@-XJxjN~EK zN|`~9M+6q~6Ci{K)v_32JISuk8(}R5g`xP)y_*RwTmfiGEH7!<~>PIP%ME!Udza@ zXodmEcWR=!d8*mcswWu#Iqp*%_P*@;i&6gBx5x9{bo9Nv==&Y8!^oV>KgXcMl`n;C(`1i&f

lnAj;MycFTDnJko?yV3pmE}B&MgvPq*_gb)o-Jrlm>|J>6 z?@Bt1qjn2-L{|{0FQI*;Ue=Z$#gF{&zvo}>&%dX&II^=iqUET1^h?$K$xs?|$AheV zt5MRyRHPqkZvi{q$I^2JtkqzmrTB4vrAZ|LJKaJ)V>^~q?AIzrc8X{h*q7q;=#*AV zwyIgv?6Ko#%yDPkM57GL(Ok2ZyYIM(jvKu1F;LlzSZ<{asTLw@KDpl52Q7i@e9ZmU zO?1FeGrGZ9bm!HvFNJR z?p`ysIHp}%qhie$hGt4$T@6mB(Jg60=)pRpcu^yFFnJAqIF2prJF>2jGpuyW2o7!9 zc@sTC+gq)na4P~?!*U)D4c==Lj&C@$Ny#*Bh%QX6hJ#=u;zCPul)HK^I?x8o)Up063 z9-3&%F>$t_aS>C*x#T`+&5Reb-zX;EY3`MK=paW`A8z6Ep_WjxGt!dBMgJasGAD>D zb%q=qI-u{#D%SF6j|;BEo0B<&;|kbAK`4p-z3}zRh}igpLz7Dcu0Afcydk=z{p-U) zlT8segV4VUl=BSH?FV1^A%}7{y@7BAcH|Gfbm%npLR-4`Mz&-bFPe}oDmgqW+!mg- zBAzJ*m)~-q`(u%&glB3NfjiO3R^54_eD7elD$+y;vPh}S$?EO5aC|pkjiF?RK9-A8wFta#$KDnZmlqpS(8=V#R`^ zWJ+REVQ&3pj;aV-$pQh_3A@vzq7o0P|Vf-V`2gDhGJ z@|J5__B9P|o;Zi#j3o=(q;OQEn7zxs#N1Ve!0V$mvC$nh9Bo?uhmVTG>KZCq#hU_t zY8$#|A`01o@B`og&;dj}uVz|-WMC993787Jj8SY3!n;^}C#Dey-+2=Q2*Ra61+W^Z z1vUW<0QZ8L84S#F;AaK!0ibsy0ay;~08Ri`fYeRM1FQyWfIbjz8^R9Y9H4&@0s*Um zZNLeDdr8fV1(w6lOh7mXLgpiU65;#6zkv?K9a;7;&Wnfem!Tby1#tMwMYsrf3|MW^ zGVcSUjd-qk585H@04@StJuJh3;bj6QQL%X56pum?P6GA}i^(cNcoE@Q&@RVw1yBua zs#hJE4R{zIr)4GsJMjEA&`jstoSo{x2fzX3wvvnL8iG4m-kuz)}ckMmPil zj6yg8h`K2zE1$gs7DM>u@V*sz-T~|ZK8F<#z2Z}jI~@2q1)K-C1~dx5 z3|N5)z*L|JSOio9HNZw-8;ir7%?J+wUjbJDFEC^acmP?zRA2$H6nF>d08Rl{0AVZY z--@3}zye@7Pz%%pZvzK_6To?ZdkrQ9l7XL4@C1ajfTch+uo2h+ybZJiCxC7scpKUT zkP2i0Q-QmHMZja*)cKjK@vsrt2D}3_10BFw;39D7LoO>E^RR7J<_=HQy>{_%amy-i z$g*!|{F2?nyoaT@+bWRfnYXz>(ZM@Ab?HJLHuuQ})xGO&YIdyCeDp+#c*QHcZl!50 zW+AE|+e{XFL1CD{pRpxK{WVND$@k0w6`tuMMY8FM^J|r@9>!@>7#n)OQZeo#Vr1j5 zKa#FVeOcs7yphLhQV1e)CnC-#R=YvnP$?apqm1_1J74sT#wklPq`F04Yyn%A~P)&SFWfE;=5`9o$ z4}C<+SH&6XR^#EU7?<>zx?!riEE5X8mtC%oBgGG?(lQl0E)9>bVlDi2FmWg$TFVl~ z1u3MKF)|fUsZ6!lo+i5 zIdC7#81ODuX{}$eI=QBbwO$K7w39m1eJ62<=_|hKo8j>EEthO8Z%3YaX`xG5ueK3R zN<>-wdKLBC`ZX*2va0k1nJD4E50YZlJO>l;In{uYB$3Hp!|I+j*NbYKQ7;Wuags~+ zGWS+!4;3Fmxra?%{#HKF-^$mZ6g|hUHAwwc%)!8|nz1>rijitJ-Km|9P>+7$D@aKy zp6#);nC&T%`DHHnfQ-<22IO-%kz4si?Mg{1-0Z{b6ceR94<@JxKk)@>SRzb^Rrrzr zql%g@Ly$iVl2jiES2F6KRME>4AK}F$PxgKvYpeTx(lH;}D$F4}kjeR@Dqiu;*x@Uo zT=wfesmq5qMe-_#^C`ySv}*WL_j3xZdWd6w>o*_MdbwZg$_Q}`)4Fiq#nuK=n%UPP zUM3~WVGwM$2!4k?iL7Sc{P@cDp@xrj(om1hh{azToAp_Wd)qrvbJB4idYtlz+O}Vp zCiV)HEm0ozHoLpuqvLO$>$&aY6%Sos z1Swyt(I>3}mh1KgLJs!2%igC$9BnV^(q8-RdgH&UUS{hSsl`x~Ys>URht^WdT>I$_zRpg{)(Mt7ljl%-`z4q$BUC9tU*iv| zt?lOP^`~-v)40}la&f*HN?_N(s1&i+irV!>R%y-Is=Ha?QiTs=3SmNe3yyx`u^0~* zMt&7HETS|e-XeBMnLg6X)(nM}1j;zN*)y~9a=(@DR1LR*i?oa}x#-VVK%^x;XNnj7 zKzh&zKj)mQwq@%$=>Z=bd;jCl)JmLtf{svrM-Vedsc#<56PQT~Rl``uuY~bC;m;Zw zbo5mwRgK*ub~_5oKP^qkW`f=6BVVx$N2r(ij4#Rxd&%_65kV z?9mp6#B=P4w`$^TJ~ES472mg)X*9*J(KNNq1YvXh!hYbFpprnB>|1=&Ej|bIDQF-C zlQ?;*+9n`lfbPp0pUwGKn+AaL#U3TK}X#$@~8H)j-l5(BvH!VFJha1-?Z!{Dzw% z0`_D=EUg{!jl*Z`>MaqwrAfWn(X5#Jc;##`tBpZEyDgDbNS2!OJ!Jws{t#H9bD7Oo z3gHluC&DlAMAdK%4ubb^;%1NZhu7Y~VVeO9Pm2lnnEp^t+gJ%RHuj=n3Hl+eCHHb< z3Ukm$sn5OTBkY>lSuEF>kYbH(ZoVS*dJjk1^vamQR-<$BU?%)^FM26<82GWO;T`4^ zFMCnt^ZXX+G#nkqs=S^uv48h6nMbKPyO_@^9X6!pH;lnVWrM?c3lsXBmsvSYZF?Oz zVp6E#Rn5_I#w$(s+QlyTGYPFe4aUnmOmY~1R{V(@#mgyb)g~%^%LZ)98A|@`ZQ5h2}i0bvAIN^J7}YtI|haRvS05oWjtWE+*i6Ou#%r9YURasKsCW&ANKSf_DHa^a zf4*FNvj?80zAHe7H#f$8af>$tQ|Yp>r;N_UdhGl$WnN;+FCc(KRki8eqYvDnXPL4wK!01%K-5jCe@FO z_I#cAx}UVJ2BcM~8vnaGV`EQtsF#UQ;Wf)HX43v@TMFo&@)!CwBSb^YiKjkMaOyYwJaM2NEIl{&-Jwn1Qg zzrO-L4roK9_dFmGzw#y6K$>Ccs6 zOoChe66n>o-!ajs^GoOQC^|NSkS4KuI+J%$p{n6Oj8~S|<`78DekMLp?Q)j!1G~j|=sXqDy}qh; zzrRRgh07ja+aXN#51C3R?szIO9*5;5K}##OP`=3D-7S8T>p|=6_O*S8Z83;*lfO+| z_qDwXt)iJQ^O-Q`JDDsqm@FAMD_EY|e1%`rn_c6nv1dQ)X`Q!{X&u1|RxbP6-UMwZ zqs{lzMlxEvgAtnx%uMNER&>5>E-14r7k!4!%tU%dFv(9)#|Xx-{9tR&U!Hxx2b_x+ zc_go=@+8U6aTVkS$rpTWwJ50D!#e61Mrb}`F07*@zGU9J0uRhMMdAx_s{emCOu$7!wj|N$-0sj;|>b5rW>3a-#gL&ydZm@V1}u=l>Eq`!YhN`EDl5 z|I%eDU;7Lr8Ql(Mz2l7RyuXe=U53Ybeo0?r6~!t!Yk5H~r*!(N?v5kUtNw!88HKl! zRq{uS0-9sAv&^e3?DDt47d&iq`NC%yO#GeL#bm&?ru?}tr@vp*^{n9c{PGqsMaj-( z?v)D0xM!Gw_|JTXU?$&cmhCN;?S#KcZDyr0b2vPfc~z{CL%u2>C#_)lUi)vp=UG0c z{iDeD3<~Gld{vh@X*tXH;(zn4W%=yLw*>jBQL@ANiLdGxPFl<|u4fr9(DP|&;$`uP zZhM27JisnKFahrMo9AnKCO1uP_O+eh;KTg3sSFSmTJ$Kt^_agkF777RT0q!d$84HA zGKc3d`&`tWmYkHVCxwhBUYyA-usgtdhTjD5``QkI=`U*!Q=^cf`y!WeQjIV)Tp$xBs!T)U|ITh?tdnlQ!``+)v>K$@1iyu^$>tzd z-#oK%o}g^=wLOS3sNZ5yo@NK^Y|%k?$O2bF^sF#xVQ*?$CAx81+0%skv-s=1X!fl5 z2A|<>PP)>|yqJ!aU%1uZq^~Z;GiQk~r{W`xw0Ww0!2qt(Z@u>syreC0rKx318Sz|lO1c%wAQ@w$zI+Od5@}jTJ4x$r`$Uo~2^o;LVVNA1cdM)$b zgL1OlSC!65U$K1OGv??0*6U#Tz>_&;eJ{i>roB=-*_Za`I&wql-ivLG1l*D$AQtbn0*i$+0vy0t0 zM77|oIQcqC#C|7^L6-f`DL2l;UhEY%k{Fi3u)vG>3%$Z9GRz+@a(Jt8YXGPGhqts0 zx*oE3j_~bMXe)U7{i59G;uW9Cy4OU$Prd3B5UC$Z79X;P;v2n`! zK~|&u34Tr+uUjn{p;&*AXx5mm#z15sizq|Xqjz54SuPLNo&HJdHbVMqY(e8!H;i;J z32;8Vyhn&7M{Yz?yM;;QQx-Wi#CE^Hr>jQ~_SDo^j9d-wGUIxYKye_5nzIgZu49`u zVuwZ!u!bn#rlVp|t7zyH`M+I6fc-=E9^N8P?QQwv`e%|Qd3=H-L|U@LauH0Kk)3mT zO1ASOuY1XGoB=xr4U{MKGOGmcPwd1Fdj)U>8{nG|1}gKjo^LmV?1kGp`pxy7T}roR zf!k!0sbLueUySVWN?ls^qdjxT6y215Hl6(+cg`JYpQ)D*G2y3ja;~oBH=K!)f@H2F zndBEV&rU!#vU@E&EuvrDdTq3%eQ%JF}lrDm?fC zh`-q5PE*d0YvxMC^Snj?QNBd35Xl9~GdF(qH z5UmQFkFt9^=pnzX>{%AF_uCB#1gFY6ab(Y#Ez?_4dwjaY-|g5%iS2B^WF;9!;Ve0{ z#C>}_9T^#ggR18DaZ-}!zxE_X^|)V+r%w>8dk`-E&>y?qMOG>a@z({N#p2*c1>t7X zD^{A6!0z&d!zNKcGcGR=L^uR61F^tejX3u~xY~WmO8e2S@+Lz9eV15!uL<-tu3*S$ zwCn~B8MqY+9btVp40m^otxz!gE7s{NTHJ_;;gnb<%${ z4^u-LuNH{U=IZ*UeL5*uZ!fN>8JU`;uh}Ra)NEnOf3&s;QocnS8y{98Rz#PU6j%P- zFIBu;v&~F^y_9bR0%hYuJSnN@}%t=nLf<*iZEssK)om)lJKE=#Ss=yohYpv=aCpxrr zDn5-1JL5tVj6!4rpP16t!f{;j-Ky0PkMp00Iq5NEw--CA!AyQsdy3k{YViA@M10WC zZ>zM7@$1mq8Znhy0*2#4t<+x3@(1Qk0fGeM6h1no>P;>?SGDTFI|qaEf`E|a#cQ0) zG;l2!*X%qa&c7k(-FT~fs#U$hiSuy$b(@w;#VJ}=+NyD(kk$A&llBEjD~(jGIwtKM zoHUDFl0wnW#f;z1N-_Tiv*}>Qw`o<+a-yASo&BfQGy!t8t>%g?0iAQkagrR6OJ2m7 zd=M#SY7W@0uocT+#^2eZwLJ!Hv$U2B&2HhO3GAqkS$jWQl^+f@lyTXiel^32*ZWXc zs5pF3)i#<5`=^8@jJc_k$Tu0-!#+-?2FrsSo~!QS#0V`RwCWDz_xda`T)YF@MlqZ; z{06adw`MLkBWrz~JeZN!)F}o-LMS87<3t@JzKtvX-Dip8;&&HH18>m5(zwKfT(ETe z8R;O4qtmFSRg zem3LD?Bi8oAKCq>gfls=3?zL^0z~_(`brnnq!js0tt}e1JFH6WYJ$Ha)A4PJ{a$Wz zzrt{dO_WQDt04w4T?KiGSlv?Rg|rQ0B5s!Qf-pfi@*vXvLTzR;B^YtLhAXz-=oHpq z*6=prjr*ZAI^6LNI%_j<2>1#(1zZ8Rf8cx>o}j2Aa6->6mPH|L6p#HhAA4TJe zWQ$fJPAV63YW7)!e8#u}+IEq{iLLaI7HyNC#G!rLd9GzJ&Ry*GWNc%TlHy5x86S|W z!)G=Xj1!0Zg=;|_}q7orUM=PAk9=6yzH_s z`Uh)Jd!1G2#MOKoZ5z%>^Sq~yMBM09#W}>Z7%jA~1>)_u3(^z~CnQUmNFQ@)@m%Rc z6QvwBc#N$ZEqbo98YsuN*SwVGmqESIakge&k3%v8#hYBv~=Crx*G9lv|rRS*OfPTDL7!RIm|qXzT%nM2Dqw)Fo9sS=V*Em+Ew8_5ku%RY5m1q?;N_l|IHgFdo3Yc{H6gp!4OdDR&9FcT&7wn zXVG)<1Kn|BX}=ujY?gF4tdlf7K30JDvFYVPmNu7@enliuSY@${LtK=C@+w6ct4Or& zZ5_D9kQHlocDo&8Y3xw>ni_a*id2Q0Te>|~VS8Gbgi9M?@^p7IvN+^3JU$c^<*nkR z;)ANWpfPCqRnKgHo$m@CORd&=& z)bBcTG0){Qn2AFJ)jnP`4Dwg|?pYG<3WUkOsxwV>dNCcg4s`Ep zsWz81$%kgn4pz_y%lC?TfqL8HtmdWF{-TtzU@C|Bqy`mjY78qO7<(rwC$S@X#l1hB z-sCtA$8`cY4|stE_LwZ;4R}i+7Dxutfm~n$Fo}h@V6`chUAC!hEglSeohcsJRXl(e zM|Tzv@{tR)sA#!W7`J)K$8&ezw?2LZHB2Vb&zdb%KB1aggN|}OtMNoD{GsJ@hYk)g zX9Tf)PS`^eC!O)|->D|MEq!zD$8&ZsTAwt+*@MqdOQ~UnYRJWM544f!6C-F9T+}xn zTPXjHYU-MjzB&4PtB&+%jVt@CS32u8k8`1vNpSpJ zHKk>XZ+e*gtkCq`33?$icYT5`@q5!oyEPNvI_8DReRQVQY-^aq-r&F`=GmhRd3+n1 zJ2y{%dQES7r1j&u6pK=Yc^HVl*{TzCi6heN+%Ni^9%Kt>(S^elXn|u}Q|o}E|MU*} z0`hdF3r~(<#@xt7Pa7M{I=b|n$EKg6IS;70nrWY3leL+*O`jLWJI>xv z&!#5~oASedwZdGpf|-{?w$1TQTLCq7^I&C-2=42@6ufbG39HyFj;& zOxKysQi+aB_#@d8|3XxB4B5-FupJ_^q9i54RPM9#x7w|-h;`VC%Zaf8Vay{_QyrO-d1UywKl062(i$At z_K$C*sWZ)|6MA!hu>O&se1pH?zkK0W@$1<(%57r9o1}D_Ggu> z^ZV&gewS*B?0a-F+SU1jbYVQJ5X`ru5r#5v9jX{0aL)4%w*MRNk^iZh%2n~r-lV&& z)I7YwN?C~un67zr2t}!r_X*NwpN*GwOwnfu_`>^-hXDksA2R@9=_`p81?}xi!uaj1 zt}Uu)@uqLS?fV2>`p1}7>(Y0#MXYtb$vzkkkc2xl6k)0yBo0Ql1YOlVftJ;F7l+I7 zka7#^J^ZN2-?F^r>B5~*pf678d5;S5F-c@iEKIrthpY4;scIyLKdGx1+#!YX3X|fD zkM*6jFQV*Ugnu@l{}7VmBk&w6m%H9kcqQNR#h@wE6Mn$%4fbQu7F?L zFx)`K!KM5nGti>-(IWy4S9u3a;%HI&`od49jT>hFVqm)Q{4K0kgh{w67s_8^!i4*U z=>R>m$n&Z_B@Dt?Qi3}_C#Ewb{bP%xq2l!Jo&?>TkJEp!>GMLB!-m|=>AQc?GcyhR z!eHiBr~|*qNk%n+`L6lW2Vmx`g+JiMqaG_gk>+<}lvyRwhdVMCs6iy$pUx|KguF|` z?ljbGJF4DLZU>X^KTJLoaY%14hHII2(ck0xcz_bxUsX@~t#m*|!I*x85O#uVRI1hb zI@rO+?11|j@(!&GhM;XDxT<$mzmzsw1t0$$GV@hxVWclW-@{4I_Ts9CvRW46-#ukQVZ*P1Brid(lhKrkzA=37W4$te&wXcSw4Ox7~(D^BmU_|n&JS%Yk{;= zNMp3lr@`-PAno6r^e~Rm<)_rbk}Fo>f)T`3$2n;!yHVm;0fv_iU*p7o{RL_|Fe-O9 zwhwjV^vGoRf|KS$3%CO3<0`yB{l0WK!t@jp`yN_tc9}Wh!O3yz~$sjMved3k-qtY0i{07h>oyQ%l)jTR<=a- zNOyx={?L=V8CUBL>q!h-Nii?d2LHv{2#j0HNpZ}OjOYC-S`cH~rORb(^<+L{=2vDO zd%_h+BjMP<*W&AzCVTu>+L|x|VI8F-eHnXC6VP$7Sh4tfasrX$tq$dNJTzH+n8T)k?3{!MN1p`*TH>%6gR>(=`dIFVvClloZ zi@(0&{r1AvB1|94o~6sZczbXwl`i%EIjmnW!1Yf4i&@((ob(+oPN7&_2SV(qeHoz%Hfn)6t!SZ;(IXCqK}eeW2Gos?oanPs*G@oOM{b`=?1XENRdW ze}F~a4*pLf~MbPy=*8fSm|0g618B2Y|1DD}aEHCI$k@Ksw;a$Ik+QeO19edKiV-Xx-MO{^j>e zEi?5fin2;KN)1m!iHu{rJ`iM>bMyi%2)irhEoxN6CU)hG` zM&SKPOw8GP3BF0q3Fp!UHf{$Q^4U$S=n-jK9r~Ly?LSc5mN7b~2Szi)1zr2lsw235 zvC^6EEl;U#A7J=YCvL~Tukf}VyixE$o%9+TRg+Xxr$L;EV)6f={LBr8abV7P2iv#m zFOICpnqE=x0|oynH~4?e`2R~MPVKT#2Zw8=hx9l&MpqA09`#_6FvTl9r`b~~5r2fm zbG_NYJ(iTp3_aiFtNKSkg0}5CX*H7|Mm1Fq6>sW|ej0mMORC$q`iC(#_caXA1%yZ% z@s}8KjfxS>FpZ>bjk0eIFK}!XZqB@Q`W^Gjj7sJW`6d$~2h8{vC<+A6il>YC6S*+L2Jwwg^qv(5T}eo^ye|u$+yIFj;&&&B^1WLeJ88voZ*i z`rtwvXLwvEe(^h2QSFgAkHEaEyi{}uFalORy!?(H=fKPe&V@|rab78(8KEG>iC8I; z_;fG+eHbTdml#C>mH(xe#(1z@g7*v9`!QZ=Qh;PNV)r0P8Rbn&skEm!3sP2>r>qg* zx+EBs-NY~(H53rk!6e0oosCH&A)UMG98F!$>N;pDkU;*-4g6vt{4UrIw>6W&GVSYg^I8BL_df&w+~Bo)}V75Nev&AFF@-?bT>oQf9?Q>7lQv7b)7FHkdW z;W}{#Q(|YNyjd^((Ce=foa%?kW-n%kP=W2N<}n3^G3$zRE(v<)R#HXTU07BCD*n6P zqByhM>oe6w;*u`v+2vTPeQfy8FMHd00*qY{kVAXeM9sK{Hc1Q|uTOlW!>YB^4hC zD8|?7rL6%eGFhP!UM$}Oipdj^poIAqi@qekL=0`XR|&ng%hIcUrGA4Ly7s$<8k(s% z`-WC)_0qZkznd99!7FXKq0~4Q@5_`@2I+GeQA@}fJwnM=QMEq=G;G^NC0t|=RetiB zx&|(b>v6kpqwx~f2W9?wG0JTKU2K$89)X z9J3Hz(z0YNOdt%XOj&Dc)r;b#=Ym+LiPV=bSzo?n9siVGDhvpm%mh?D;se+1DQo7i zPXY30HRSSh`lBNz;+^dEB>jG7$r`XLPg!4?vMxFJ{H9&S3Tlw*U<|g z!gMvi&@Tep5VbJ*u6V!_8w(jh{7Wb{j*j+Qq$biZ6DI6CEj~6>va{W#1l4hb8lGlm z&SX1BOV&A$`0Od`@IgW@t-7YI@U}e?FuEa!O6jawedaUaru~QA=uLI$g5`580BBni z5#z68g-p${Siyk}Ib8~~_AR$8Spz2vj{7HLwbqNBDvOGmET(^YTE~sfS7vnnj24QH zhmzZe<|I(G0rCaoVp&one#k7bvp`v(m#*~qU4K6)>io$gMWN-aDM+bdy)4qhOmG(3 zSBqil(%B8>+Z$|r0mNCtCO;)9H3cc_+D(Q?YEP+_f>fOEJG^YEiR84D8XGU@aZ#Dv z%Q726=#4=!n6lgaq5KDE$$dq~|7!j?5r4V%KbpT*3Tmc#HM~F5e4!wH+k;hNB-1?R zhUPGwe~@uIrcr}KITp~}&3hs0oxs-$aM9Ih13H%U zHMCtLe&cJN9g%oD{0o%W$TTjigc#6ENJ|)PI2KsNji7B`5$On&!5tn`dE&CE-RVK7 zpWEA4$3ev#KFgA4S)MK)V+5XYKC^?FlY=>Ojzcii z%d-MskQ0`n$LGLjnebz72w$G^tmL~^FgeuH5N^kPhN0}LLR!kR2{VV#gj?8l`14E| zj}Mkrh`*70OJI3?p&<-_V$ze2YiJwTZ@hHv4vRICyt^L^>g%L6zN8U+H^>a{5Pwdb zEpP&}MWA6?KG*#<2PiY*E2}`~eVLey^D&-dsEhRX&2nBDr@s>7hUs ztw9z2=XRF%t_LHr$onZu@ZUG}(u5w2b!e>0Pwah@M_LG)m1xO`okfzopEY9BWIior zK6o4jvM z0j#&}n)Pc^@?eT4HC#h9+Co~`r37S!@uNp26pqU&#Es+(U(pydd6MOoUSJuA zP}0OQq9TrjCd`J_C$|+7)PQv++8TL}IZt=h3>t5YM@2YS`AnTU@s;cD_h!(WCOfwT z%$75nm|l@t;pNw|2l|wU_^QajZX}M3r6@-Dw9gb7BCfd}xuhCf3z0V*OCRZUxAPnK zumULQd>PE*Xnx^`lkt6YTFUA?W4LJUT(XKdnGk4OXmcbt-Y-S{wOqPAYRnAXHN3a* zT)fF()3vcHg3{aFXuXHr{bte;1xiS&Qvx; z4ED=t(K)Z{q)bT1ocVSp!ef}&Rgws1U))gP%`;tSl+qF}MBK*SKf)${!Y#PC!{4IE zRxr!W`*TMLI_!|(Ex|o?bdNdyX$j^>vpK0~0Yq1(qs{eS_Br!*tcp)yz7Xvn8slJa zWe0N(Pr}CHsH=Y)*M46BW;ml}4k=~bqu85FV7XsswqJr)Se;J2m$R2#ow4Mqd)n=E zxB;ggq5M|&&UX;JDgIa zAVX2=a=up(QIT^~y) z*=G0{#M!ReuHDiJ79Z`y#D#bb_@^qzS$q`!-+&MWyWjkIRRnh=6iZ0vWLBZOS`Peu zT~F>UZT5FsZ0?KL+(+My9`V=GlFwKBvd&K1zE^Y3sxQo@18-k}MQ;sI3;6%1Mc_YL zgk17ba{4PUJ38T7LUv31Ra}6Zcnvl!o^-9Cfh)-2q{E(K`5LTMFpWi>R&;EAbV>%{ ze|0aLP5TYi@i=OSRw;ehf1U|*R`}SRgwuHBhvUvb!KE_v5K3=mLG3KD(9%I>7 z_zY1T?h@@_vkvE@J|CMY?WTs~8hM)uWHpvrqR3wMn42xVej&@!oS?IXwHdgUtrx6< zx$`1<^DFm&In+kCUvTHlpdo)k7cGFH!p9%8ah76@k4$9Pfd%#iTAf z=6Z;fwtyoqmonZ98SnZt+4Waue0;SZ*zq%3uG$b}e{dD!;oDc;#dB%DygM$0NF@gD zDO!K6c;{7N#~MTN+gAmO!ari!-n#m@4nGC=aL(g@{cnyeTrk+_uj7m2j0@jJlx&%W z>sLpgzT$3~OD&V{xh&7<&91vDhItO_vtPKH)=>1VK6}$u{J5^RLcrJ9THHI}efCv%;vIBw!IM{8ez}}8gv$-98I@$i%4Mu2So-oBKg-uP1bZK+ z{$E#L0@vixJpAnL6ApPm!X+YT60QUTlAs6*nnVJqpnw zeYN6Mk7_+y8*SC16{;<^ifujWfw$J%dJ8WA$Vx`#FXRDV0kth9>8~q?`=IQdwjI7uCATpaejY-L1NQUW+IEtN?eF7$ zUJJJK%?KDc6z5`}Q(zlPi)iL;_}?o2j1Hr1Z^MCMR7L%6!=+)g z2hAD@A;W2&`^KwINdo?%s!OGxeerPWMd*fG@XK-L7b^(Uf=xx}Oj8}Y$`A{V79d(9 z&4D;^R#|=3*YE&hQ)0*Yi!(D((4ISz79cA~(M{^7bfz;1M*;KmMPJ{51MjIAGs9Qe&|F5!5Fs zil=IDn2Y>1`qtRcSG-c~KV@2L^Z~1=;Y&xE;Q!`DRd!LB7q$6{B&Y!Pji4XU{Wrln zl8&QmZ^EvTbUZD0{02bWDB6#Xy9oXiSaHkw!*BL=$Gh9%2r}P z)4^|yAv-g#pcMDMZTInNJ%yJVit65YLs|I>@|L3)6I&&Ekjc$OT4iVEW}#rTRoJ#2 zVe86E+;f!B944foIlsRGp&PRj?iJE@zJqvN-3CGTF|YUR4lI>b0P4}yUsdp~rgCpY z2U{Cr{z_KwlBHnD*XIAyfs8H5^Ba&en#T5VbM${_(|eBoze9+T*NA7HqShqN0hPY> zbRxB!QSvy2kC)7+u3^K!`NJGg6$Kzr`jv2DG!6GieFrs*;jAmgSy%rKc#T2IQ{vfh zkll4sAWcV&T{@4mZS|Bm@pxU7R=TCJ9`H?-bZxT}v>_;@n?sL{MCy99ui-kMuU9IU zRgdxg>u#h+T~S0`QG0Tf7c^W#UL#JeY~BmWhu@N;p@{w}a)_tKNm_ke@Xs-d_3aF& zz1re5J?-Ub2ya5w_5nCgZR{#P4&a>k@hwN*Fv@f3X=bf#sr6Ls^@W>b=*SkXS8Ftw z!#c$m28^X8p6n`4T;oa6;w>N{6W|M{$5OpV+6`)|kb0u7@3^Wgmn zbUC%Tf!|y7QyNzeyWgT?skQ)uCek(@0|0B#w&XIYq-_BtPo$k`LLSVThzzf1`EYY0 zzQ?~Z9Ij8ovYBJ-WyLtj>5*;l<=eD7RSky+Z=>}5l{|=@Oxv|S#|IwYYqUg}-0C0X zH1;zD^qAcLN$5Nc^Z95 zuMP*}bXvp5_0@D5;Q9MdtXJori0`I)M=CySYY%yc4koO6W?Ps&gZ|1CP36MH z#;9`K5D)9y4=id#U3p&rrFI4NxNBJ5G4)Y;R*Ci0RaR_EJE1Q{UH(1;3kcp3#d(K~ z@Ze*E@7>FX()%8CfRb*F4FUIX$z9>%VH zg0g8VgH8KC4K|50LQQf@)aUU|s3~t*uqhB5MbKRh$JA$)w+Xf9SI24 z6-J;27+-BO0w77EO{+NUSbTy9!^T;(oxTlE(o|v>yfk`mv}WW;_5G)Sw?^;tX1Vel z3=gq|(FzT*p2USbprQ_~Js92eyQY5OkjC9!?82 zokv%NZX3G!=!(!~qw`1Xio;(OItksw`S@yND%OW?GrDC{LtB|9AsQB8APrp{I)8NH zROmJv&pKUmpt=W}TEms)1E`SQ7bm5AL@1fe2iw?AsqNdPwkrCGak$cUzeyGS z%3N@rLyfc|2NLJdWI7`UY;%xAznTL-%%QQ&cElSR z=FqS}6t{ejYBqEX&}7q!(Uw5hAv9&TmwE&~OUr?7b7||q13B0(zN?z3k%G%x;G&@E z(B{0CdXP-)Iq>#e8X5FA2Y4YhZqPd_3W&+3)7kk{buudI&4B}RX%xMj3-{(CVfS7R z4<7b;3xLi&< z`Y5*l3-fuZ{;G+%=74SG`J53@UXFi4`zu7c8~)#qM)AMM1IZ& zv@T`vI>;;1bnwM#F4gx@k-xK{a6WBo*xlTKZLVDYhoJdYt2ow_wIL*6KpeGrQQaC% zD4Ixw3#dl@?whVmmCM%%Le~K}!YB+GP?+O{!bXJb&w;W9)Q8w$`2uRqsy_FB;Ls|p zPf%dMMJ+Xv0lXrE8%iLjx3ncT8QXaxPA88Q1mW4+O0`oHNq+;}c)6yS&jBeTRHax5 zksr`@Ua0cvfBT2^!mtmhznfxO6M}oMk;G&}*#|Tzd@{!dKDeZpS#pYOS&$?sCSYZr zoxANQua|KPy3j~8*-(wOx?4+{EP-y0e&prAqYr3nZ`+in5+dlW3+W(Gs*g4jDI0=5 zq$=!Sw-0HXo~U@E8S-*(gd9g6HUA-Go&!qpJ%nTI5w<@aeg3kMJQ{@1@TuF*v)tM7 z9j_Q>H6T?Pk`3!Vq>1#83vl^E+RpXs<-w-8xS=fq*9saqOOYbfS|nPI=YGDBK5z7+ z@}ofwS1N_fb@iIcK-7%uaa%!LcxR8(n_j2zB0{CDHj25a&^QTT}ZwT^FJ26IF6Z%m`(7ij>?LSz}(N5EV7 z8}K_W#xtjLv2n+{*XDbcYILqGUyNwj+x6bF$H58D= z5t_zhYImdlF5?g9qYuxi>5Zs)5!1+|dKmCAO$!Uap3HAJZ_`bY5LLHGB)VKBiGEyYpb8yWbLgzC;qx(#j&5;2$q*&60>D zK6yM4leL+Sjq$-icxMr9>!DdEct* zmVeuPf!fwI6u*)my@3BLr4gF1FPgu+i1_9~2J`Qe+A?#;#fT8|mWxP2qe3C#w;66O z#pBAgZQ!wtwiz^JL)BrWN?*nQ*H%^4Dtpx;kCKo&-f-X?vVosF9E$t!Ef|kOWqxaT z+yzwThqHqGhX)SbknuqQp7^neq3+e2pF!y|8e;CcL9;}tvM5!kIm+=rm7vUGFAe#M z*Cf5&RYT_T;`n&jr8ruHm-Jo^o$#e)17?n)W}iy9zKo{K8gMZ)=i*oi=^5g*7PGq; z;sgKDGOWe%#Q`~-?{w%xxh`LkDro~jDatkJ@ocYadA403 zeTn){d32nl>+m92aTzZck(U^3seHF5)dovRi5@>|$U?_}BWDVicoiS0%h7V+SCJk@ zvp@zox^O{Gm2hs5>yBmpvmxp3k}nVU<*9fqMw^&khTpM!)_K^uM3Nv@_wj<0$C)?m zSWXqaChc%={_66{$r>5)($#PrJEwCG4^$&bzCVx?wzxta3ba0yapDQ;zvaxoQCV}k zyX}nE|M2DGW%c>mJfofJG>2s8=lYZK{EO2#zDqp<+7S&c$7S|AT;CPaOTgo?R84EmI+-7|i` z3h*{~{ZIG{t5;G3eDf)d%IU}}(A2i5*C;vGoRBey?6yE!r4nv462*&6a7S(CPXE^m zLbd~&@YI4#$HLw2IAKYPH_%hGt402>C;o@}GpYR`&LY&{t@jtlApbMk!KL?MtWq5? z2v&TCmKUbO$&F*RpVBCm4WT~fyp(D1T(-Fd`MFBfQS9iHCEjw}pBx+AkiKz?z<2aeGHd=5bn zqOrc(Z-Y(0?MF`2_wY+4ex3hh!M#e{*pBn!+Ga-fPY!>ggW3F}!|yODMvgfA>5lkA z+#l(VoN&G-9pItzw%Ep_8e}XcwnP*^$e}w<0~Hd@;hP=R1tRxqACJ}P8>vsEbKiRaTuciRyq%vp!%Rr3_Kt)mO04m?dq&5Sq+ zaJ%wA$v#`rB`2H&*dS`l!*KO%rAE&TJ$OU0SulD%j`6kkpnN@5DYJ6UT|B)m2)`C` zbd=m3U~489x)U@w?d+UwVnFKA*%ON#55@XC&(Rq14Ed4w%x_!k0Vpt%PN$*4A=X7M+XkNu^ zHA$4TsR-=|9vq{TM6DBI>~J2^Djmo*#Njt6>Xh>hT5ZK_N93U9NQb|FCfxg+#O4z&!8LU?_uy(wjQ2!>AOBrxs6K&(g ziI@F2Do41(QTqBlXt9}^y?XP!XanQ|mFDv8EF}YmZl+-odmMFyBic*)ze(~-%!QM0 z9bXNJr8$I*gvu_YJ8a)f1N>U>{P(Cu9gf z9RB+F)<& z>^8It^y5KTyNyP-`r?$|ZW5PpGuY?!()-Vs+=)Zl)_Uv|+}cJ9=!R2}ww>nD{qx|< z?X_&=^KIVr zwu9K_O++*oC4H+EnZvUoUkdGaT60BG%7cirrj9C9aA#d8k_j0a)fJ=as^2=Nu;9^> zyZS4cOy{{SkIaCu<1{$`lgSQ6E)#g<8FOJ^04e0&F@$j~nLU#tCKTe~=3>^9zDp=3R z+{j5354tEFI$0E_Wwf1w>~w9P<-gF8q|`p@7wSpq?8h+eSGt`fLFftky;ee-#g%jM zEx{3F)1c#2Fd@2wF2Z?E0plg3Wo;~(xgVsHv~8Gen!9e0 zoX^pD_E?*yvXZ=g1T#9JC8+#avc}1n*`qIkD=u{ zLGqIn=3k(D9gXnm$;&E?_^O|5cR-}6?g{VJA!EX)FKn!%F>bE=dF?XN^?l)L9gQZ( z!RHk1&__WL?s9z6Z*D6~-l<0$)e zHXKu)RUfA(TgsQ`96%kps8AOcpQ65Ar}?)~-$yf2S?NDzrPq>gVecv0&Tn@b#)k#= z3)5#DPXF1-UEnlfHQYNzTMe$_WzaghA{OVu;q;$SB@kK1^Q(Wy32{b!%RoNk!8h~4 z=>AjZz;lIxU+^k+^0Meb$tKhwbVN41pJvQJBFF*8yJ;}|H`=lNl0GNus&EJo>q`?< zr*}@np;9$}JX;w5bU{3T>4y1z;NWl6Kj8yj9GS9JkyV<*zmS>f7(}{)O1^l9GnXfl zubNz{g!quK;1K6Lwfb3;;aQVqT>^RcJMj7)SzeP$!GgcDM)J&BBCVIi;`#w&j!V9i z3RPo|li@s`+t@yyp;MAv9@0d5W0NH9e;QMg0X)U!IBzK?L|reZOQf@7ERG8cLc-#d z>LX2tBiM2=N&Sx3D9dUmS*7wko##vbkOg?~J8}~i{RvTj(B7V{`(P*QB1_E|o3yot zk%h3}4?JTk`@rXa(1bD0yea&W3jT|bSoB`mX~-=;F`z0}W71TSC%ob2VZdyS98TmM zpt0|l+>24qn}3a!V;#%D6CP~-fNwsXfVQHwEoO>IWB;OSb6${{ej`ad!`49`zfv+)F?qBa;;?PiobraW}9(UrK=3httx zO2T4fi=#xTOg3?k;W~^vg9O9sbol5DRr$~4&1-fv8Ncja>^5Lbt@#q7Y*~J9s5wKE zT71S+r53w__k+(_WKc{>2h&-!RXC1U^=73J9F`pGvHe9jRfIC+{g%Ouvov^KCg>kt$|)N~9Sfvn^|%XzGaOa4AWcj#(;7_(>#^=PthU zU4g#KSZ5^bs*f;DK0ah|Uc9gWY`vtekr&NZEvO}39lbj(r`8&!pK#iecDx|=KRyIH zoTKfPxg3b;wUU47G}Sw``9bM0{v3^L(fV7AMl3Ov!Mby_8+HB`uAHNtTVLsg@kb43 z%(pUMy>eM*WE#m|eX=I!|xVIcf0ZJUucii?{Lyy-OR)j`th$ zqvTtP82%$k<4NS*1}8Tm63sVpG*9MH>ZuKgTpEpc^T@oJR!U-WPxbvdi&9kJ1HQNTJ60$2`&K9=j^$be>YZq`|PH!Ll)cw0cwh%{;DY z=Xo9~)_Bu`aZb_rb~1oIRd|2qA9qx`PCy0@^McXSx@tQKHA$1 zp;~M(D6rD|WWioITThedJH4UZWvX&MkP+q;MXDhAGW~@1>nhcfV{q;&4Q*M)zn7LIyzQ822f^bS z?o-jH@Y)M^vm;2Obi5dBdV(&y9<7C;3%rb6#6R$Q>;`0ALzP?6d}zx2vX<<)gygq+ zd@8KEMtgN_?f67P8}NvZ6oj86L}bG$9`bU8oEr@%c&Mc#gh$vO=3iu~6S)fN>vXDT zt^=_AH-KgLf=$;^qRG0TPLuC~eDg!E42NG?HjN|6_U6ITXo#`WU+bmIBs2NJHqe$@ zHotgwf48juEesO}Wlk8B>VvD6R?N_NMr-(iIf|;c8Gll%(e(>Q9 zx}bF)Vo|ekS0vNOr4)om9sbHtk_nMFk&GYn-((~fPC&s;+ME0aU*4phNDW-ONsDn8 z9C!=ab*p;AJGW?L)Y6{3hL>`Lo}%^lMPv~VF4dqV%)v|`pGgk>kX6X@#E;l6xK=}M5c zykWmPlb9SWU$X};m3(kxOmqbQmeG6_UMD0)5f$$niu7^%DTw>S0%@VX04>59Sybo1 zjKaVW^>20(S^x?zfM-DP6}N18hiJ%orF4KkJ*itt#KdbPeU|4Vb)*N z*PS_r@HgDXiI4(o|Dyez9wM-l%HY*sG*bO66GByJs6Fvitz+7-`=C{^xA^DACO}o z!i+zZqFAtH-DEPKzoy9VVu!tVX=sXdznc_ZlBRcRi-cfnP6&GNHC0pTVyqNw-?||^ z(Sk>mos-GS7qeZ2b*Nb0-*-jrc~?CCq3D%mZAUV74}{#KT{>F7b|a&BPq5Sjz5-Rx z6^v43p?8JkmnrY5QVEt&TV4{!X3HoCsW|HZEV)Nh+6DF+*in(uNo5Hpn26Y{(WJyN z+@@{q+!n{D!i#$}hC26x$oqIqh};9c@6!Zr(3{?(eiC)nHza_!tUmaS;V|VLjb4ihyzp*ER zF*__}b`dH55vm>_{aM`^ZatuFb>F7^b@$NZCN%RWMS3WnwjjGwFmIje4LzG2(@f9y z+XEdQQl0KNCs!5y@M94J9}-DZJ)cPg!68}(zgL$&qv&kh%$O#GaRjYS4p>7__ zx*nw8*RcB$HMTb)_VQV(#*N9sb02!yn4NZdlp;eH66U2;uWUkN_m*`-Ny69A@-g*Q z#lGp8AP8A{@#gis=huhwE$N65=GC8sr9jeSIo4chzx+x|vb zy(OtYp3uM^Ym+bo8Z6u|3!V=g>|9O0K=X;U;DMsx!78e5(S#R%mUYj__OCD};*_Gj zyvnFnG#L~a{hVz23i6)dRqC>2Sonm-bkD${n}n{kc?h|MY;Fj)gtvnfdG|)V?0dmi zHd6;Ofkz@|82MbNQXV75CV~8f2DLmfl;14jIWMd7?fleKX#bR|6-CLOrk#-aly+!i zOs!H0ni18=Ko?4b^r9oLvGQ%RJ{A7+lqS=FRJi(-4va7+V+;S1u9XPXPIT($c$tEH ze^QrEZyRuj&?du(XLKZGRyg(yi71*34bP}P>MstV3*P#KSJLG3vL&tcv&a^S?&`xb zIo$)3>Z2!r6HLqhkktoe-1Y}Pr+U9jR!mTDk&a@9-0?dY}7dZL{V9^Ua6Lx+UY!XsDP3IGvhXWNb$W(~ekwVYT zWFaqHjNCkzCC*(6oP{jsu8YRZDZp#TjW{^Fa#Pk-zP)aIwOW!Tfm-6pY%Bczg7%_s zS)uz&Bv!^+Vc<)ur9-VyieXC*uMdx9_`m$VO#1U+95Ek2U1GgrGz@j4X?h5xuOpW_ ziK^c7GriP$`aOP8MTO)mGD)?<>6f(gK#zYKu4-y-IF=VFKQ5CP?!oPH)&Jctqa5kg zGHK5}1!|#qnOqPiOe!qRauVDIpo}L8wZh0(G^BMKo?|oFHCNu;;@hoDS_p0{m-G^d zpB2`=qMK>Qq0sv^ZB6>asMj<-HSMc0ib;i@c|~TSuqrR_Yg3^TN2Zu%r79*Rc{VTA zJr!0bp$3h91_qKN{9_v~parx9$9)nx&j$l(z*n!SQR#zxh$_ULCt04RZGAjV(;=~e zCd94Xg?VsVAb;ixXFYopw-UebmSm*^;b+FJYPQfymh&+3|E}>m%RWqkT@A<_`XC98 zHy{J6Gznfe&>Yj4H@Rl>T!-*dd=!sZCndUVU6+<9dEv8^LZe;19f#Y331rBd{8Jr} z<|M(gMmnG5K~NJN;nUG!EA`=7bu+b5VIlsQp^1J(opvvW>pFn zy{Ha$BwCsi72lk8`coDJe>hHP3YJM1rhw9kh0t&_barClRAqrYC+0&7Q(&qS>)UluieOYmM;maI zG)aPSsG!~~lSkcoAJu|!wSZ4;ZQd+DH6?4cP}V!8q>_}R3RwdMbFY-;&P*Tl)Qnk? z;klF_No*XG9MW`-Q_%zH>C99C*Sp(;+86^-M7~vPY(MP6qKU7Gx2FE_RUFY;x@fcoI=oFV zgL4a}qXsh=TQE(4t{ET~ahzjWbo~kJ zZoyX5*ks6bW4SC$O*Bp)VXbfToLlS%gk^tz}yyExMQMgAGHV@<#d4HBOK2Ys2JlNRfZ zo&)E6S)zxA=SHqIvLH#j1PJwG@l=rtd48;jI-B4ZKQ@dix;`$lsyHpM}vG z32@(^MUYfz-I`hH>Ryo7n!OYGelLmFwj!@;C8w0pyE3nAaooIoc0c-wP1E~E@5|GxfFrkCE*vF zD_%1CVwrm{_^1uDhMqE)FqXwg0TW6GD6u02jFwI#LZK3yD?Zs3WAgXGi#BXl(8`1c zZ>+|s`B;eYf6Qgem|xfrqHeKqrwvbv@oL4YiUb8ywKRI~Y>H7)~)Ap zaLy!1E%q+lm#}3anajhetH~vJ7{dHw-sM5Vf8;Yv60{3-%yl7|{%;4)W|BnSm^RZQ zNylids7phJS$rt#H|za49={4jh}P~S?>Yi$Jn$`<#smBk)3WwEGMNX^>_%a~VC^c$ znEUYxNiW_=<~Vsw=-7&5p(KR|ozyrsvD!dhO)V+>w}D?s!M_dsN`_-#)|(bikRd#& z+zXZO{?kGuN2`fJjHk6P}G|d3;%_ydrgF#j#Wp z%7dK4{bs3s1N3c2-XNa1Hc{T&VxLpRu@)qti+xcT+ezu@c(CYLFxdx%I$TO;UWcWq z`;*R1wxf!-&IE6go=33uYKJuWLgowgzx7ifM%s*`&Pe#>MjkgsQU1*;tR(Mr{^{#*Ad;ogMcwRfQEWS-8FS!U0}G<765*_Y zwWAT8;FW{?of#uU z0f4LuUo<19NEDL^ZuMfdhOFDsRP4S9?#5wTFQHpJo9MlLK=I>s4(gBJH{CXz)O3cb zc&0@@+wFK3N>}s*&n~zQwC@DbU2vl=?h3Xptbl&>x&3w*wv3>>-1`YE$oYvyDmkS3 z9M&bUG+a?0B;Yz-8UwM3EQQ{@46_nhcVe}FpU7qri|sw9%w$=8{xNxN)X*YhrQCT5 zK4!3qIrMJ}+`Ekgi`OTC%C55@6AHPrDn9zZ-a&h5hDD942P(WRG+#RHN zc-5Un;Ry>OOzg0SYA!OW`9_GpRdejGOw60mPy@8Iu>Nivx)2WoVvV{i1>Ul-;Yd^e zVqsU2nBLukeM(E`LZ2kIolY@8V-jmcdvpOmE6b&6(NJP#rPQs9{fU*mMd^-M7@5kR zkeQIvlTGp2Jp=31WyYJc)v1O=MWjf+oB>yQvJO7$94U+A%d+!RiybMeXFzB#_K{Z$ z1NMsQvwLQ?st<`|B)>CM_hQSb>jnGJ-s}@XNgO;$!w$aH0Rq#Rf#!FBzUfE^4eVf_ zkqd=R1>7PZx;0(5l7Ds_cCerWYqaZAk4W<2}z?R861f@hFkP$7V ztEDQU(i*B_s!nLCqBXNoaL^QGKbeV$PA8|?-TSk*nYUM0gl@L(Xytr9CuWa5dZ-ST z4q|z@%{&=|6iyRN&1PGpcH$tvha!CS@kHGyb0YSv;ke?0_>P(6w8!RL%vf6M{^OG8 zkplT1`sA>Uw6h+b<{)lG>LDaK@1xooo6175KFvaG3XQU%YJ`XMzH zI+*E^|D83Mb)d!=m@}9~k_6Z_m?cGj*b#HzSQ5bHb;wt|sLEsZ2?2jAAF7nP{A3qF z)u-ylvQ~}^U$lqNA*@HMw|FV4_DX%^j9P>Gp)4y3r+(5NW({FeeS0@2dY;zM?_i?KqShe;S_yW@u+7(21-2UfCHh|Ew4qzfqBcEZSJ{e_|K#Q1T!0z&w1Ajao8zxw(!U|I}QngN8BeF!>R_ zAIo;*Rb$CGBo|}5L)|zw5{bXg&4KeGiVpmD{>=E| z_)no*5esPDz%d$WQM}qFFS%dh1j#P_r%+tPeEc8rD2y<7Sste}jTm5Yi_o+Rt<=HMi8%HXte~63ekXMxPh$Oo6l#W5)D@XHh5Uv#=4NS- z%~dVJ#9~~C&cLB!L`**&JT7KQv{Ny}m9Q?YX7MSsB-r$ZgdAZiyD1ls><$Y`Sl`ym z#(SEOCi~~`(!ej4n>}|vZfUezh<(QgcnF~W;c(|e)}69& z&{yCGRdj-(6*zmYEwF!3!E#9plP%QrcT1E8w88y_tSzawyMM&$sk7VhnKuk>i{Z&4 zwicJE<%`)o#i2!IstV97VdLn=MexxQ{8WXZ@YNEwl1^C!eU@U&5uvbtDK2cX{lrq7 zj81vw$bc=kYd*mlLMzMR#&UL$>O$w|SFGN%siZ27=4LY+!IBhTN zTZxnU^Fkn>GJjfG2yH%PsjU|Bj9F@>MeTI~5j^gZM(s)F7usihiVGKw53#pd#fC8Q zGfZE@^a*j}F?YE;iV4hoS$1H)q?i}uJZ~ut-$x|RRRW3T$(~6jRZN*lqbz6t`iXA5 zy?zbzAxOhMtz<{M1)*hr6WUWKq@kI$8gqlJz5^%LvQ(|@vWyfT2|GuHQH3gxp^{{g znE7v6Q7_j};Z5+P4)_FZ9ShM`pLgW;{x>(p-1Ra(5NOWLgYR~*LB%@cu=Izm>sWi$ zk+`xg^AR@+wB_n$mzxPXjV8JHr5s+8nORW*PuH=+Agzo?iL8NyGoq2zPYX32GJ|D} zLcCj`R>Ac3tfQEJIo}I*u4k3STUKC6@(t0J4F32d=wh z&Wz;v7`7U(P)&|2R88UPUBj=h&4=g>tX2E^a7@k$TaA+i8|MDP)#h_NQWK2B$!Cye zL>WS3UIHZ>Se$qF>ln2}zZ$Q8xTk1!*>pIt0axm8;qnI7IiiNk2cm`Xm-5Lm7ancr zK+(v#cqd)Eh}YHOiAvvb6-9*!`@yo2h2iLo-N@RJRj^z zdEm8~^(W6^^k$YBf3h)5Lkqo>1MdkMK^!<+FbYlsgN5n^4Kuq)J@a+*%Dg_r+Df!1 zDyMlaa@<3gj5(2+&>0?WM#8l%1Z-hO=Y5ShLc0{uZws?{ZsLL6m5-Ke;BiOiw zg+zSHW39oByLQVuH+)}f-GUER?-8m$dGA@aujS1RHj+70CWd z+{psTyU?(U`9sN0)|))CAKb|j3Gw1_BkBC%P_UczL0Zm{^P>Hi-7KEakDox}E*1d6 zU$Y@(8I*pF``U-F@oUW42v5FdeFLmduS81HdTX)#Kj!RKq4}WAFmw-_OE(X*U){s( zxXcZMRd&{uMhvr`wKF9l*Wu1y7DdiMz&>U~LA~^SxToxcqx)D-x@xH1`&-t6(2*8< zyYKKIMOzJpko{~6p0a=0&rVU@V-8p=CJ1S8-~f9~yTn1aove-L`A7WY4{^t5N>>zz zO3ZNmAfDsegYWk&mVCb{?R%`NVmI`uVkP9Y{ZJL#PN?FzebQm(LW1M*j-tXJ4XU9_ zIvRp^X!R%^jPi!J&924#-H&xM{O1Q;Rt55$>M z9^5pcV#T#MQW{L6v!|#AsMOnY;n!;R6|GK$xiw79tm-SUxrUu#Ht#Di=SLRhQ62H7 zVo|7Wc?G`zk+tLds=DWvOIZ@ckuWDO59;BcozmV{e zz3tB|Q>01RQ2Z;>8@BuQ#1rgCLQOw|QytULwFwYWhurgJ3HIT2>^(|@yTHZYkWzR8 zD}HC4A{0F@<19rm7|72#On5&h$udr`CkJbA= z{tK(c8+wO5EsaLYuek3);3Yg)T3^^dyTk@kT67zpUB*LT+F$U|6;|G2*~Ji(>28QA z^$N7TiX7Hyu~2c9op%kyxM7Gk?VoL=_XQRfaPs6xSw- z({SPun~uvu&&TXL58JLQ_i#2?cU{qCR!_Ej{mqUO55=-8Iwk5YS$ajMwhn@OPuN{# z;9_&@2M1MeMB;>j_c&4%O@*Q!lC-wo*%>y2~pVlQgIwXwbl0TnD{60u+6_59@y@vG%%q_ zo%IfEP>5P2qK+%XnE0;mq)MwoG-AASfjde!7%OXtdp&;jY{|tCsrcssn;__Qa`zB) zLJ~6V@HZOboy50@&A!=5{8pqXDDUPbCLzCQi<>x*e7#%}{~|Knk;Ex@37zjQen8^k zuDf`&(|PR9Ep&n!5nL^Ie~kXcJJ41TR=Q0kcYlK6z4*Hm-EALSqA+a;-1ZQMQEe>r zY$^8fXxJ53rczDP7_(iI>*B__0;tPewA&ME$$6I`ltbtl@QAIk!J#TT2dwXndOIx|S9)D40+DAN3 zDj?TaT@s4c6$7>j5rnV=tlp6iH|JS#A5eY1KL> zT)eAWUntZllb`cdfvcJr1}FW+R*KVD!2r1DFSd_O`x6^?LglSqXp{ASluI>W7MGn( z-P@w;fUY~bOmxNQ{FU}TtwkMa`Q#iHHn=sacj7HPc?9JFqB_O)`h20$gTISysXzlF z77mA?cy%6c1Mt}Z!m&n9_7W5?F;_F-)?^NNOAffg*0f4fPBH&;xE~<;dykFKL<#1n z4Ymm)+BH-!l<`iWbsKRW39+wjBZd(A!Q1d-pm>nxzHOfuBz}RLO@_T=JF(uGejNgS z;o@U=pAfu*L>&szQv@Loe6`|cg!G_R44}WQg>za_FFwm4CD1}APN&tiFjptW&60NE ziz;;25WDc%h`8@oyZj^B@C`3R+#MOevg(K~60?5BvnKq2x)hyxf-WfG2h|m>pq8w73SA?_ z8?@L8!R^H;?*o5e`UurLZ7M z93GHy9?79pCTNuZCG;K7tP?_kb`XmctKK)PdLQ2IAP%JQq40AD983}gH#>+qNOPN` z#UQE}4|&ld8bPvGM~kDWle9S0Y-F*wPr-2h_53~G&s+cDy7vJ#3qiW;)W{&7bU z@6)Tj4%mmsiX92H$?!>M@lRS210`|dS9GSv9v&~c5n6rR-m#15%7_KJC5rt~&U2Bqp^Lvfooq8 z-?7BmPxlpM!!=(cAiq+sIFlGe88oAr>AF5Vz2GVqx__A`@u-DDh{G-~$SA z<+sA^LNSN#83akAMHS5(35BCYleB6OM@3?oHwl;D-J?Z4{b&%>j}{l;jx%lyer0Pt zl#dZnRnDb-wHC!4*?M1gAr+ENdUHp;^KFMH5Q;9*d?ES=)hh4m%W!@S&Q#Q^GgfTX zqWaH$TqfMp{h>W^U<&Kxw_7A6v7hLT#XN!4C_YmCT R^?aL07j3`2RP5*S{{X4KU1tCQ diff --git a/firmware/sha1sums b/firmware/sha1sums index 690205d..e83aaef 100644 --- a/firmware/sha1sums +++ b/firmware/sha1sums @@ -1,2 +1,2 @@ -2d114ceba82ee7e63b16726534888665fe1d7b22 0x00000.bin -bdb01713a737a6f328b6a5953825758622e9e894 0x10000.bin +65c1e77051ecf1d65eb38017df1096f84b5daa4a 0x00000.bin +a4ce86573e9a5a60ae6665a47d624522e054a0b2 0x10000.bin diff --git a/user/cli.c b/user/cli.c new file mode 100644 index 0000000..d43d3dc --- /dev/null +++ b/user/cli.c @@ -0,0 +1,997 @@ +#include "c_types.h" +#include "mem.h" +#include "ets_sys.h" +#include "osapi.h" +#include "os_type.h" + +#include "global.h" + +static char INVALID_LOCKED[] = "Invalid command. Config locked\r\n"; +static char INVALID_NUMARGS[] = "Invalid number of arguments\r\n"; +static char INVALID_ARG[] = "Invalid argument\r\n"; + +bool ICACHE_FLASH_ATTR printf_topic(topic_entry * topic, void *user_data) { + uint8_t *response = (uint8_t *) user_data; + + os_sprintf(response, "%s: \"%s\" (QoS %d)\r\n", + topic->clientcon != + LOCAL_MQTT_CLIENT ? topic->clientcon->connect_info.client_id : "local", topic->topic, topic->qos); + to_console(response); + return false; +} + +bool ICACHE_FLASH_ATTR printf_retainedtopic(retained_entry * entry, void *user_data) { + uint8_t *response = (uint8_t *) user_data; + + os_sprintf(response, "\"%s\" len: %d (QoS %d)\r\n", entry->topic, entry->data_len, entry->qos); + to_console(response); + return false; +} + +#ifdef ALLOW_SCANNING +void ICACHE_FLASH_ATTR scan_done(void *arg, STATUS status) { + char response[128]; + + if (status == OK) { + struct bss_info *bss_link = (struct bss_info *)arg; + + ringbuf_memcpy_into(console_tx_buffer, "\r", 1); + while (bss_link != NULL) { + os_sprintf(response, "%d,\"%s\",%d,\"" MACSTR "\",%d\r\n", + bss_link->authmode, bss_link->ssid, bss_link->rssi, MAC2STR(bss_link->bssid), bss_link->channel); + to_console(response); + bss_link = bss_link->next.stqe_next; + } + } else { + os_sprintf(response, "scan fail !!!\r\n"); + to_console(response); + } + system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) console_conn); +} +#endif + +int ICACHE_FLASH_ATTR parse_str_into_tokens(char *str, char **tokens, int max_tokens) +{ +char *p, *q, *end; +int token_count = 0; +bool in_token = false; + + // preprocessing + for (p = q = str; *p != 0; p++) { + if (*(p) == '%' && *(p+1) != 0 && *(p+2) != 0) { + // quoted hex + uint8_t a; + p++; + if (*p <= '9') + a = *p - '0'; + else + a = toupper(*p) - 'A' + 10; + a <<= 4; + p++; + if (*p <= '9') + a += *p - '0'; + else + a += toupper(*p) - 'A' + 10; + *q++ = a; + } else if (*p == '\\' && *(p+1) != 0) { + // next char is quoted - just copy it, skip this one + *q++ = *++p; + } else if (*p == 8) { + // backspace - delete previous char + if (q != str) q--; + } else if (*p <= ' ') { + // mark this as whitespace + *q++ = 0; + } else { + *q++ = *p; + } + } + + end = q; + *q = 0; + + // cut into tokens + for (p = str; p != end; p++) { + if (*p == 0) { + if (in_token) { + in_token = false; + } + } else { + if (!in_token) { + tokens[token_count++] = p; + if (token_count == max_tokens) + return token_count; + in_token = true; + } + } + } + return token_count; +} + +void ICACHE_FLASH_ATTR console_handle_command(struct espconn *pespconn) { +#define MAX_CMD_TOKENS 6 + + char cmd_line[MAX_CON_CMD_SIZE + 1]; + char response[256]; + char *tokens[MAX_CMD_TOKENS]; + + int bytes_count, nTokens; + + bytes_count = ringbuf_bytes_used(console_rx_buffer); + ringbuf_memcpy_from(cmd_line, console_rx_buffer, bytes_count); + + cmd_line[bytes_count] = 0; + response[0] = 0; + + nTokens = parse_str_into_tokens(cmd_line, tokens, MAX_CMD_TOKENS); + + if (nTokens == 0) { + char c = '\n'; + ringbuf_memcpy_into(console_tx_buffer, &c, 1); + goto command_handled_2; + } + + if (strcmp(tokens[0], "help") == 0) { + os_sprintf(response, "show [config|stats|mqtt]\r\nsave\r\nreset [factory]\r\nlock []\r\nunlock \r\nquit\r\n"); + to_console(response); +#ifdef ALLOW_SCANNING + os_sprintf(response, "scan\r\n"); + to_console(response); +#endif + os_sprintf(response, "set [ssid|password|auto_connect|ap_ssid|ap_password|ap_on|ap_open] \r\n"); + to_console(response); + os_sprintf(response, "set [network|dns|ip|netmask|gw] \r\n"); + to_console(response); + os_sprintf(response, "set [config_port|config_access|bitrate|system_output] \r\n"); + to_console(response); + os_sprintf(response, "set [broker_user|broker_password|broker_access|broker_clients] \r\n"); + to_console(response); + os_sprintf(response, "set [broker_subscriptions|broker_retained_messages|broker_autoretain] \r\n"); + to_console(response); + os_sprintf(response, "delete_retained|save_retained\r\n"); + to_console(response); + os_sprintf(response, "publish [local|remote] [retained]\r\n"); + to_console(response); +#ifdef SCRIPTED + os_sprintf(response, "script ||delete\r\nshow [script|vars]\r\n"); + to_console(response); +#ifdef GPIO +#ifdef GPIO_PWM + os_sprintf(response, "set pwm_period \r\n"); + to_console(response); +#endif +#endif +#endif +#ifdef NTP + os_sprintf(response, "time\r\nset [ntp_server|ntp_interval| \r\n"); + to_console(response); +#endif +#ifdef MQTT_CLIENT + os_sprintf(response, "set [mqtt_host|mqtt_port|mqtt_ssl|mqtt_user|mqtt_password|mqtt_id] \r\n"); + to_console(response); +#endif + + goto command_handled_2; + } + + if (strcmp(tokens[0], "show") == 0) { + int16_t i; + ip_addr_t i_ip; + + if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { + os_sprintf(response, "STA: SSID:%s PW:%s%s\r\n", + config.ssid, + config.locked ? "***" : (char *)config.password, config.auto_connect ? "" : " [AutoConnect:0]"); + to_console(response); + + os_sprintf(response, "AP: SSID:%s PW:%s%s%s IP:%d.%d.%d.%d/24\r\n", + config.ap_ssid, + config.locked ? "***" : (char *)config.ap_password, + config.ap_open ? " [open]" : "", + config.ap_on ? "" : " [disabled]", IP2STR(&config.network_addr)); + to_console(response); + + // if static IP, add it + os_sprintf(response, + config.my_addr.addr ? + "Static IP: %d.%d.%d.%d Netmask: %d.%d.%d.%d Gateway: %d.%d.%d.%d\r\n" + : "", IP2STR(&config.my_addr), IP2STR(&config.my_netmask), IP2STR(&config.my_gw)); + to_console(response); + // if static DNS, add it + os_sprintf(response, config.dns_addr.addr ? "DNS: %d.%d.%d.%d\r\n" : "", IP2STR(&config.dns_addr)); + to_console(response); +#ifdef MDNS + if (config.mdns_mode) { + os_sprintf(response, "mDNS: %s interface\r\n", config.mdns_mode==1 ? "STA": "SoftAP"); + to_console(response); + } +#endif +#ifdef REMOTE_CONFIG + if (config.config_port == 0 || config.config_access == 0) { + os_sprintf(response, "No network console access\r\n"); + } else { + os_sprintf(response, "Network console access on port %d (mode %d)\r\n", config.config_port, config.config_access); + } + to_console(response); +#endif + + os_sprintf(response, "MQTT broker max. subscription: %d\r\nMQTT broker max. retained messages: %d%s\r\n", + config.max_subscriptions, config.max_retained_messages, config.auto_retained?" (auto saved)":""); + to_console(response); + + os_sprintf(response, "MQTT broker max. clients: %d\r\n", config.max_clients); + to_console(response); + + if (os_strcmp(config.mqtt_broker_user, "none") != 0) { + os_sprintf(response, + "MQTT broker username: %s\r\nMQTT broker password: %s\r\n", + config.mqtt_broker_user, + config.locked ? "***" : (char *)config.mqtt_broker_password); + to_console(response); + } + response[0] = '\0'; + if (config.mqtt_broker_access == LOCAL_ACCESS) + os_sprintf(response, "MQTT broker: local access only\r\n"); + if (config.mqtt_broker_access == REMOTE_ACCESS) + os_sprintf(response, "MQTT broker: remote access only\r\n"); + if (config.mqtt_broker_access == 0) + os_sprintf(response, "MQTT broker: disabled\r\n"); + to_console(response); +#ifdef MQTT_CLIENT + os_sprintf(response, "MQTT client %s\r\n", mqtt_enabled ? "enabled" : "disabled"); + to_console(response); + + if (os_strcmp(config.mqtt_host, "none") != 0) { + os_sprintf(response, + "MQTT client host: %s\r\nMQTT client port: %d\r\nMQTT client user: %s\r\nMQTT client password: %s\r\nMQTT client id: %s\r\nMQTT SSL: %s\r\n", + config.mqtt_host, config.mqtt_port, config.mqtt_user, + config.locked ? "***" : (char *)config.mqtt_password, config.mqtt_id, + config.mqtt_ssl ? "on" : "off"); + to_console(response); + } +#endif +#ifdef NTP + if (os_strcmp(config.ntp_server, "none") != 0) { + os_sprintf(response, + "NTP server: %s (interval: %d s, tz: %d)\r\n", + config.ntp_server, config.ntp_interval / 1000000, config.ntp_timezone); + to_console(response); + } +#endif + os_sprintf(response, "Clock speed: %d\r\n", config.clock_speed); + to_console(response); + + os_sprintf(response, "Serial bitrate: %d\r\n", config.bit_rate); + to_console(response); + if (!config.system_output) + to_console("System output: off\r\n"); + + goto command_handled_2; + } + + if (nTokens == 2 && strcmp(tokens[1], "stats") == 0) { + uint32_t time = (uint32_t) (get_long_systime() / 1000000); + int16_t i; + + os_sprintf(response, "System uptime: %d:%02d:%02d\r\n", time / 3600, (time % 3600) / 60, time % 60); + to_console(response); + + os_sprintf(response, "Free mem: %d\r\n", system_get_free_heap_size()); + to_console(response); +#ifdef SCRIPTED + os_sprintf(response, "Interpreter loop: %d us\r\n", loop_time); + to_console(response); +#endif + if (connected) { + os_sprintf(response, "External IP-address: " IPSTR "\r\n", IP2STR(&my_ip)); + } else { + os_sprintf(response, "Not connected to AP\r\n"); + } + to_console(response); + if (config.ap_on) + os_sprintf(response, "%d Station%s connected to AP\r\n", + wifi_softap_get_station_num(), wifi_softap_get_station_num() == 1 ? "" : "s"); + else + os_sprintf(response, "AP disabled\r\n"); + to_console(response); +#ifdef NTP + if (ntp_sync_done()) { + os_sprintf(response, "NTP synced: %s \r\n", get_timestr()); + } else { + os_sprintf(response, "NTP no sync\r\n"); + } + to_console(response); +#endif + goto command_handled_2; + } + + if (nTokens == 2 && strcmp(tokens[1], "mqtt") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + MQTT_ClientCon *clientcon; + int ccnt = 0; + + os_sprintf(response, "Current clients: %d\r\n", MQTT_server_countClientCon()); + to_console(response); + for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next, ccnt++) { + os_sprintf(response, "%s%s", clientcon->connect_info.client_id, clientcon->next != NULL ? ", " : ""); + to_console(response); + } + os_sprintf(response, "%sCurrent subsriptions:\r\n", ccnt ? "\r\n" : ""); + to_console(response); + iterate_topics(printf_topic, response); + os_sprintf(response, "Retained topics:\r\n"); + to_console(response); + iterate_retainedtopics(printf_retainedtopic, response); +#ifdef MQTT_CLIENT + os_sprintf(response, "MQTT client %s\r\n", mqtt_connected ? "connected" : "disconnected"); + to_console(response); +#endif +#ifdef SCRIPTED + os_sprintf(response, "Script %s\r\n", script_enabled ? "enabled" : "disabled"); + to_console(response); +#endif + goto command_handled_2; + } +#ifdef SCRIPTED + if (nTokens >= 2 && strcmp(tokens[1], "script") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + uint32_t line_count, char_count, start_line = 1; + if (nTokens == 3) + start_line = atoi(tokens[2]); + + uint32_t size = get_script_size(); + if (size == 0) + goto command_handled; + + uint8_t *script = (uint8_t *) os_malloc(size); + uint8_t *p; + bool nl; + + if (script == 0) { + os_sprintf(response, "Out of memory"); + goto command_handled; + } + + blob_load(SCRIPT_SLOT, (uint32_t *) script, size); + + p = script + 4; + for (line_count = 1; line_count < start_line && *p != 0; p++) { + if (*p == '\n') + line_count++; + } + nl = true; + for (char_count = 0; *p != 0 && char_count < MAX_CON_SEND_SIZE - 20; p++, char_count++) { + if (nl) { + os_sprintf(response, "\r%4d: ", line_count); + char_count += 7; + to_console(response); + line_count++; + nl = false; + } + ringbuf_memcpy_into(console_tx_buffer, p, 1); + if (*p == '\n') + nl = true; + } + if (*p == 0) { + ringbuf_memcpy_into(console_tx_buffer, "\r\n--end--", 9); + } else { + ringbuf_memcpy_into(console_tx_buffer, "...", 3); + } + ringbuf_memcpy_into(console_tx_buffer, "\r\n", 2); + + os_free(script); + goto command_handled_2; + } + + if (nTokens >= 2 && strcmp(tokens[1], "vars") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + int i; + + if (script_enabled) { + for (i = 0; i < MAX_VARS; i++) { + if (!vars[i].free) { + os_sprintf(response, "%s: %s\r\n", vars[i].name, vars[i].data); + to_console(response); + } + } + } + + uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN]; + blob_load(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); + + for (i = 0; i < MAX_FLASH_SLOTS; i++) { + os_sprintf(response, "@%d: %s\r\n", i+1, &slots[i*FLASH_SLOT_LEN]); + to_console(response); + } + goto command_handled_2; + } +#endif + } + + if (strcmp(tokens[0], "save") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { + config_save(&config); + os_sprintf(response, "Config saved\r\n"); + goto command_handled; + } + } +#ifdef ALLOW_SCANNING + if (strcmp(tokens[0], "scan") == 0) { + wifi_station_scan(NULL, scan_done); + os_sprintf(response, "Scanning...\r\n"); + goto command_handled; + } +#endif +#ifdef NTP + if (strcmp(tokens[0], "time") == 0) { + os_sprintf(response, "%s %s\r\n", get_weekday(), get_timestr()); + goto command_handled; + } +#endif + if (strcmp(tokens[0], "reset") == 0) { + if (config.locked && pespconn != NULL) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + if (nTokens == 2 && strcmp(tokens[1], "factory") == 0) { + config_load_default(&config); + config_save(&config); +#ifdef SCRIPTED + // Clear script, vars, and retained topics + blob_zero(SCRIPT_SLOT, MAX_SCRIPT_SIZE); + blob_zero(VARS_SLOT, MAX_FLASH_SLOTS * FLASH_SLOT_LEN); + blob_zero(RETAINED_SLOT, MAX_RETAINED_LEN); +#endif + } + + save_retainedtopics(); + + os_printf("Restarting ... \r\n"); + system_restart(); // if it works this will not return + + os_sprintf(response, "Reset failed\r\n"); + goto command_handled; + } + + if (strcmp(tokens[0], "quit") == 0) { + remote_console_disconnect = 1; + os_sprintf(response, "Quitting console\r\n"); + goto command_handled; + } +#ifdef SCRIPTED + if (strcmp(tokens[0], "script") == 0) { + uint16_t port; + + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens != 2) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + + if (strcmp(tokens[1], "delete") == 0) { +#ifdef GPIO + stop_gpios(); +#endif + script_enabled = false; + if (my_script != NULL) + free_script(); + blob_zero(0, MAX_SCRIPT_SIZE); + blob_zero(1, MAX_FLASH_SLOTS * FLASH_SLOT_LEN); + os_sprintf(response, "Script deleted\r\n"); + goto command_handled; + } + + if (!isdigit(tokens[1][0])) { + scriptcon = pespconn; + http_get(tokens[1], "", http_script_cb); + os_sprintf(response, "HTTP request to %s started\r\n", tokens[1]); + goto command_handled; + } + + port = atoi(tokens[1]); + if (port == 0) { + os_sprintf(response, "Invalid port\r\n"); + goto command_handled; + } + // delete and disable existing script +#ifdef GPIO + stop_gpios(); +#endif + script_enabled = false; + if (my_script != NULL) + free_script(); + + scriptcon = pespconn; + downloadCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + + /* Equivalent to bind */ + downloadCon->type = ESPCONN_TCP; + downloadCon->state = ESPCONN_NONE; + downloadCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); + downloadCon->proto.tcp->local_port = port; + + /* Register callback when clients connect to the server */ + espconn_regist_connectcb(downloadCon, script_connected_cb); + + /* Put the connection in accept mode */ + espconn_accept(downloadCon); + + os_sprintf(response, "Waiting for script upload on port %d\r\n", port); + goto command_handled; + } +#endif + if (strcmp(tokens[0], "lock") == 0) { + if (config.locked) { + os_sprintf(response, "Config already locked\r\n"); + goto command_handled; + } + if (nTokens == 1) { + if (os_strlen(config.lock_password) == 0) { + os_sprintf(response, "No password defined\r\n"); + goto command_handled; + } + } + else if (nTokens == 2) { + os_sprintf(config.lock_password, "%s", tokens[1]); + } + else { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + config.locked = 1; + config_save(&config); + os_sprintf(response, "Config locked (pw: %s)\r\n", config.lock_password); + goto command_handled; + } + + if (strcmp(tokens[0], "unlock") == 0) { + if (nTokens != 2) { + os_sprintf(response, INVALID_NUMARGS); + } else if (os_strcmp(tokens[1], config.lock_password) == 0) { + config.locked = 0; + config_save(&config); + os_sprintf(response, "Config unlocked\r\n"); + } else { + os_sprintf(response, "Unlock failed. Invalid password\r\n"); + } + goto command_handled; + } + + if (strcmp(tokens[0], "publish") == 0) + { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + uint8_t retained = 0; + + if (nTokens < 4 || nTokens > 5) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + if (nTokens == 5) { + if (strcmp(tokens[4], "retained")==0) { + retained = 1; + } else { + os_sprintf(response, "Invalid arg %s\r\n", tokens[4]); + goto command_handled; + } + } + if (strcmp(tokens[1], "local") == 0) { + MQTT_local_publish(tokens[2], tokens[3], os_strlen(tokens[3]), 0, retained); + } +#ifdef MQTT_CLIENT + else if (strcmp(tokens[1], "remote") == 0 && mqtt_connected) { + MQTT_Publish(&mqttClient, tokens[2], tokens[3], os_strlen(tokens[3]), 0, retained); + } +#endif + else { + os_sprintf(response, "Invalid arg %s\r\n", tokens[1]); + goto command_handled; + } + os_sprintf(response, "Published topic\r\n"); + goto command_handled; + } + + if (strcmp(tokens[0], "delete_retained") == 0) + { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens != 1) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + + delete_retainedtopics(); + + os_sprintf(response, "Deleted retained topics\r\n"); + goto command_handled; + } + + if (strcmp(tokens[0], "save_retained") == 0) + { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + if (nTokens != 1) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + + bool success = save_retainedtopics(); + + os_sprintf(response, "Saved retained topics %ssuccessfully\r\n", success?"":"un"); + goto command_handled; + } +/* + if (strcmp(tokens[0], "load_retained") == 0) + { + if (nTokens != 1) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } + + bool success = load_retainedtopics(); + + os_sprintf(response, "Loaded retained topics %ssuccessfully\r\n", success?"":"un"); + goto command_handled; + } +*/ + if (strcmp(tokens[0], "set") == 0) { + if (config.locked) { + os_sprintf(response, INVALID_LOCKED); + goto command_handled; + } + + /* + * For set commands atleast 2 tokens "set" "parameter" "value" is needed + * hence the check + */ + if (nTokens < 3) { + os_sprintf(response, INVALID_NUMARGS); + goto command_handled; + } else { + // atleast 3 tokens, proceed + if (strcmp(tokens[1], "ssid") == 0) { + os_sprintf(config.ssid, "%s", tokens[2]); + config.auto_connect = 1; + os_sprintf(response, "SSID set (auto_connect = 1)\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "password") == 0) { + os_sprintf(config.password, "%s", tokens[2]); + os_sprintf(response, "Password set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "auto_connect") == 0) { + config.auto_connect = atoi(tokens[2]); + os_sprintf(response, "Auto Connect set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_ssid") == 0) { + os_sprintf(config.ap_ssid, "%s", tokens[2]); + os_sprintf(response, "AP SSID set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_password") == 0) { + if (os_strlen(tokens[2]) < 8) { + os_sprintf(response, "Password to short (min. 8)\r\n"); + } else { + os_sprintf(config.ap_password, "%s", tokens[2]); + config.ap_open = 0; + os_sprintf(response, "AP Password set\r\n"); + } + goto command_handled; + } + + if (strcmp(tokens[1], "ap_open") == 0) { + config.ap_open = atoi(tokens[2]); + os_sprintf(response, "Open Auth set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "ap_on") == 0) { + if (atoi(tokens[2])) { + if (!config.ap_on) { + wifi_set_opmode(STATIONAP_MODE); + user_set_softap_wifi_config(); + do_ip_config = true; + config.ap_on = true; + os_sprintf(response, "AP on\r\n"); + } else { + os_sprintf(response, "AP already on\r\n"); + } + + } else { + if (config.ap_on) { + wifi_set_opmode(STATION_MODE); +#ifdef MDNS + if (config.mdns_mode == 2) { + espconn_mdns_close(); + } +#endif + config.ap_on = false; + os_sprintf(response, "AP off\r\n"); + } else { + os_sprintf(response, "AP already off\r\n"); + } + } + goto command_handled; + } + + if (strcmp(tokens[1], "speed") == 0) { + uint16_t speed = atoi(tokens[2]); + bool succ = system_update_cpu_freq(speed); + if (succ) + config.clock_speed = speed; + os_sprintf(response, "Clock speed update %s\r\n", succ ? "successful" : "failed"); + goto command_handled; + } + + if (strcmp(tokens[1],"bitrate") == 0) + { + config.bit_rate = atoi(tokens[2]); + os_sprintf(response, "Bitrate set to %d\r\n", config.bit_rate); + goto command_handled; + } + + if (strcmp(tokens[1],"system_output") == 0) + { + config.system_output = atoi(tokens[2]); + os_sprintf(response, "System output %s\r\n", config.system_output?"on":"off"); + goto command_handled; + } + + if (strcmp(tokens[1], "network") == 0) { + config.network_addr.addr = ipaddr_addr(tokens[2]); + ip4_addr4(&config.network_addr) = 0; + os_sprintf(response, "Network set to %d.%d.%d.%d\r\n", IP2STR(&config.network_addr)); + goto command_handled; + } + + if (strcmp(tokens[1], "dns") == 0) { + if (os_strcmp(tokens[2], "dhcp") == 0) { + config.dns_addr.addr = 0; + os_sprintf(response, "DNS from DHCP\r\n"); + } else { + config.dns_addr.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "DNS set to %d.%d.%d.%d\r\n", IP2STR(&config.dns_addr)); + if (config.dns_addr.addr) { + dns_ip.addr = config.dns_addr.addr; + } + } + goto command_handled; + } + + if (strcmp(tokens[1], "ip") == 0) { + if (os_strcmp(tokens[2], "dhcp") == 0) { + config.my_addr.addr = 0; + os_sprintf(response, "IP from DHCP\r\n"); + } else { + config.my_addr.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "IP address set to %d.%d.%d.%d\r\n", IP2STR(&config.my_addr)); + } + goto command_handled; + } + + if (strcmp(tokens[1], "netmask") == 0) { + config.my_netmask.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "IP netmask set to %d.%d.%d.%d\r\n", IP2STR(&config.my_netmask)); + goto command_handled; + } + + if (strcmp(tokens[1], "gw") == 0) { + config.my_gw.addr = ipaddr_addr(tokens[2]); + os_sprintf(response, "Gateway set to %d.%d.%d.%d\r\n", IP2STR(&config.my_gw)); + goto command_handled; + } +#ifdef MDNS + if (strcmp(tokens[1], "mdns_mode") == 0) { + config.mdns_mode = atoi(tokens[2]); + os_sprintf(response, "mDNS mode set to %d\r\n", config.mdns_mode); + goto command_handled; + } +#endif +#ifdef REMOTE_CONFIG + if (strcmp(tokens[1], "config_port") == 0) { + config.config_port = atoi(tokens[2]); + if (config.config_port == 0) + os_sprintf(response, "WARNING: if you save this, remote console access will be disabled!\r\n"); + else + os_sprintf(response, "Config port set to %d\r\n", config.config_port); + goto command_handled; + } + + if (strcmp(tokens[1], "config_access") == 0) { + config.config_access = atoi(tokens[2]) & (LOCAL_ACCESS | REMOTE_ACCESS); + if (config.config_access == 0) + os_sprintf(response, "WARNING: if you save this, remote console access will be disabled!\r\n"); + else + os_sprintf(response, "Config access set\r\n"); + goto command_handled; + } +#endif + if (strcmp(tokens[1], "broker_subscriptions") == 0) { + config.max_subscriptions = atoi(tokens[2]); + os_sprintf(response, "Broker subscriptions set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_retained_messages") == 0) { + config.max_retained_messages = atoi(tokens[2]); + os_sprintf(response, "Broker retained messages set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_clients") == 0) { + config.max_clients = atoi(tokens[2]); + os_sprintf(response, "Broker max clients set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_user") == 0) { + os_strncpy(config.mqtt_broker_user, tokens[2], 32); + config.mqtt_broker_user[31] = '\0'; + os_sprintf(response, "Broker username set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_password") == 0) { + if (os_strcmp(tokens[2], "none") == 0) { + config.mqtt_broker_password[0] = '\0'; + } else { + os_strncpy(config.mqtt_broker_password, tokens[2], 32); + config.mqtt_broker_password[31] = '\0'; + } + os_sprintf(response, "Broker password set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_access") == 0) { + config.mqtt_broker_access = atoi(tokens[2]) & (LOCAL_ACCESS | REMOTE_ACCESS); + os_sprintf(response, "Broker access set\r\n", config.config_port); + goto command_handled; + } + + if (strcmp(tokens[1], "broker_autoretain") == 0) { + config.auto_retained = atoi(tokens[2]) != 0; + os_sprintf(response, "Broker autoretain set\r\n", config.config_port); + goto command_handled; + } +#ifdef SCRIPTED + if (strcmp(tokens[1], "script_logging") == 0) { + lang_logging = atoi(tokens[2]); + os_sprintf(response, "Script logging set\r\n"); + goto command_handled; + } + + if (tokens[1][0] == '@') { + uint32_t slot_no = atoi(&tokens[1][1]); + if (slot_no == 0 || slot_no > MAX_FLASH_SLOTS) { + os_sprintf(response, "Invalid flash slot number"); + } else { + slot_no--; + uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN]; + blob_load(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); + os_strcpy(&slots[slot_no*FLASH_SLOT_LEN], tokens[2]); + blob_save(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); + os_sprintf(response, "%s written to flash\r\n", tokens[1]); + } + goto command_handled; + } +#ifdef GPIO +#ifdef GPIO_PWM + if (strcmp(tokens[1], "pwm_period") == 0) { + config.pwm_period = atoi(tokens[2]); + os_sprintf(response, "PWM period set\r\n"); + goto command_handled; + } +#endif +#endif +#endif +#ifdef NTP + if (strcmp(tokens[1], "ntp_server") == 0) { + os_strncpy(config.ntp_server, tokens[2], 32); + config.ntp_server[31] = 0; + ntp_set_server(config.ntp_server); + os_sprintf(response, "NTP server set to %s\r\n", config.ntp_server); + goto command_handled; + } + + if (strcmp(tokens[1], "ntp_interval") == 0) { + config.ntp_interval = atoi(tokens[2]) * 1000000; + os_sprintf(response, "NTP interval set to %d s\r\n", atoi(tokens[2])); + goto command_handled; + } + + if (strcmp(tokens[1], "ntp_timezone") == 0) { + config.ntp_timezone = atoi(tokens[2]); + set_timezone(config.ntp_timezone); + os_sprintf(response, "NTP timezone set to %d h\r\n", config.ntp_timezone); + goto command_handled; + } +#endif +#ifdef MQTT_CLIENT + if (strcmp(tokens[1], "mqtt_host") == 0) { + os_strncpy(config.mqtt_host, tokens[2], 32); + config.mqtt_host[31] = 0; + os_sprintf(response, "MQTT host set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "mqtt_port") == 0) { + config.mqtt_port = atoi(tokens[2]); + os_sprintf(response, "MQTT port set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "mqtt_ssl") == 0) { + config.mqtt_ssl = atoi(tokens[2]); + os_sprintf(response, "MQTT ssl %s\r\n", config.mqtt_ssl?"on":"off"); + goto command_handled; + } + + if (strcmp(tokens[1], "mqtt_user") == 0) { + os_strncpy(config.mqtt_user, tokens[2], 32); + config.mqtt_user[31] = 0; + os_sprintf(response, "MQTT user set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "mqtt_password") == 0) { + os_strncpy(config.mqtt_password, tokens[2], 32); + config.mqtt_password[31] = 0; + os_sprintf(response, "MQTT password set\r\n"); + goto command_handled; + } + + if (strcmp(tokens[1], "mqtt_id") == 0) { + os_strncpy(config.mqtt_id, tokens[2], 32); + config.mqtt_id[31] = 0; + os_sprintf(response, "MQTT id set\r\n"); + goto command_handled; + } +#endif /* MQTT_CLIENT */ + } + + } + + /* Control comes here only if the tokens[0] command is not handled */ + os_sprintf(response, "\r\nInvalid Command\r\n"); + + command_handled: + to_console(response); + command_handled_2: + system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) pespconn); + return; +} diff --git a/user/global.h b/user/global.h new file mode 100644 index 0000000..75792f2 --- /dev/null +++ b/user/global.h @@ -0,0 +1,45 @@ +#include "user_interface.h" + +#include "ringbuf.h" +#include "user_config.h" +#include "config_flash.h" + +#include "mqtt/mqtt_server.h" +#include "mqtt/mqtt_topiclist.h" +#include "mqtt/mqtt_retainedlist.h" + +#ifdef SCRIPTED +#include "lang.h" +#include "pub_list.h" +#endif + +/* Hold the system wide configuration */ +#define user_procTaskPrio 0 + +extern sysconfig_t config; +extern ringbuf_t console_rx_buffer, console_tx_buffer; +extern struct espconn *console_conn; +extern uint8_t remote_console_disconnect; +extern bool mqtt_enabled, mqtt_connected; +extern ip_addr_t my_ip; +extern ip_addr_t dns_ip; +extern bool connected; +extern uint8_t my_channel; +extern bool do_ip_config; + +#ifdef MQTT_CLIENT +extern MQTT_Client mqttClient; +extern bool mqtt_enabled, mqtt_connected; +#endif + +#ifdef SCRIPTED +extern uint8_t *my_script; +extern struct espconn *downloadCon; +extern struct espconn *scriptcon; + +void http_script_cb(char *response_body, int http_status, char *response_headers, int body_size); +void script_connected_cb(void *arg); +#endif + +void console_handle_command(struct espconn *pespconn); +void to_console(char *str); diff --git a/user/lang.c b/user/lang.c index b9a226a..6ef412b 100644 --- a/user/lang.c +++ b/user/lang.c @@ -1189,6 +1189,82 @@ int ICACHE_FLASH_ATTR parse_expression(int next_token, char **data, int *data_le *data_type = STRING_T; } } + + else if (is_token(next_token, "csvstr")) { + lang_debug("val csvstr\r\n"); + + len_check(7); + if (syn_chk && !is_token(next_token+1, "(")) + return syntax_error(next_token+1, "expected '('"); + + char *str_data; + int str_data_len; + Value_Type str_data_type; + // parse path string + if ((next_token = parse_expression(next_token + 2, &str_data, &str_data_len, &str_data_type, doit)) == -1) + return -1; + if (!doit) + str_data_len = 0; + char str[str_data_len+1]; + if (doit) + os_strcpy(str, str_data); + + if (syn_chk && !is_token(next_token, ",")) + return syntax_error(next_token, "expected ','"); + next_token++; + + int16_t num = atoi(my_token[next_token]); + if (num == 0) + return syntax_error(next_token, "value > 0 expected"); + num--; + next_token++; + + if (syn_chk && !is_token(next_token, ",")) + return syntax_error(next_token, "expected ','"); + next_token++; + + uint8_t ch = my_token[next_token][0]; + // if as string const + if (my_token[next_token][0] == '"') + ch = my_token[next_token][1]; + next_token++; + + if (syn_chk && !is_token(next_token, ")")) + return syntax_error(next_token, "expected ')'"); + next_token++; + + if (doit) { + int i; + char *p, *q; + + for (i=0, p=q=str_data; i<=num; p++) { + if (*p == ch || *p == '\0') { + i++; + if (i > num) { + break; + } + q=p+1; + } + if (*p == '\0') + break; + } + + if (i<=num) { + *data_len = 0; + *data = ""; + } else { + uint16_t len = p-q; + if (len > sizeof(tmp_buffer)-1) + len = sizeof(tmp_buffer)-1; + os_strncpy(tmp_buffer, q, len); + tmp_buffer[len] = '\0'; + + *data_len = len; + *data = tmp_buffer; + } + *data_type = STRING_T; + } + } #ifdef GPIO else if (is_token(next_token, "gpio_in")) { lang_debug("val gpio_in\r\n"); diff --git a/user/user_main.c b/user/user_main.c index d069538..bb18b08 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -3,19 +3,12 @@ #include "ets_sys.h" #include "osapi.h" #include "os_type.h" +#include "sys_time.h" -#include "user_interface.h" #include "string.h" #include "driver/uart.h" -#include "ringbuf.h" -#include "user_config.h" -#include "config_flash.h" -#include "sys_time.h" - -#include "mqtt/mqtt_server.h" -#include "mqtt/mqtt_topiclist.h" -#include "mqtt/mqtt_retainedlist.h" +#include "global.h" #ifdef GPIO //#include "easygpio.h" @@ -57,10 +50,10 @@ uint64_t t_old; /* Hold the system wide configuration */ sysconfig_t config; -static ringbuf_t console_rx_buffer, console_tx_buffer; +ringbuf_t console_rx_buffer, console_tx_buffer; -static ip_addr_t my_ip; -static ip_addr_t dns_ip; +ip_addr_t my_ip; +ip_addr_t dns_ip; bool connected; uint8_t my_channel; bool do_ip_config; @@ -96,7 +89,6 @@ bool ICACHE_FLASH_ATTR check_connection_access(struct espconn *pesp_conn, uint8_ } #ifdef MQTT_CLIENT - MQTT_Client mqttClient; bool mqtt_enabled, mqtt_connected; @@ -209,7 +201,7 @@ static void ICACHE_FLASH_ATTR script_discon_cb(void *arg) { } /* Called when a client connects to the script server */ -static void ICACHE_FLASH_ATTR script_connected_cb(void *arg) { +void ICACHE_FLASH_ATTR script_connected_cb(void *arg) { char response[64]; struct espconn *pespconn = (struct espconn *)arg; @@ -262,64 +254,6 @@ void ICACHE_FLASH_ATTR free_script(void) { } #endif /* SCRIPTED */ -int ICACHE_FLASH_ATTR parse_str_into_tokens(char *str, char **tokens, int max_tokens) -{ -char *p, *q, *end; -int token_count = 0; -bool in_token = false; - - // preprocessing - for (p = q = str; *p != 0; p++) { - if (*(p) == '%' && *(p+1) != 0 && *(p+2) != 0) { - // quoted hex - uint8_t a; - p++; - if (*p <= '9') - a = *p - '0'; - else - a = toupper(*p) - 'A' + 10; - a <<= 4; - p++; - if (*p <= '9') - a += *p - '0'; - else - a += toupper(*p) - 'A' + 10; - *q++ = a; - } else if (*p == '\\' && *(p+1) != 0) { - // next char is quoted - just copy it, skip this one - *q++ = *++p; - } else if (*p == 8) { - // backspace - delete previous char - if (q != str) q--; - } else if (*p <= ' ') { - // mark this as whitespace - *q++ = 0; - } else { - *q++ = *p; - } - } - - end = q; - *q = 0; - - // cut into tokens - for (p = str; p != end; p++) { - if (*p == 0) { - if (in_token) { - in_token = false; - } - } else { - if (!in_token) { - tokens[token_count++] = p; - if (token_count == max_tokens) - return token_count; - in_token = true; - } - } - } - return token_count; -} - void ICACHE_FLASH_ATTR console_send_response(struct espconn *pespconn) { char payload[MAX_CON_SEND_SIZE]; uint16_t len = ringbuf_bytes_used(console_tx_buffer); @@ -338,51 +272,11 @@ void ICACHE_FLASH_ATTR console_send_response(struct espconn *pespconn) { } } -#ifdef ALLOW_SCANNING -void ICACHE_FLASH_ATTR scan_done(void *arg, STATUS status) { - char response[128]; - - if (status == OK) { - struct bss_info *bss_link = (struct bss_info *)arg; - - ringbuf_memcpy_into(console_tx_buffer, "\r", 1); - while (bss_link != NULL) { - os_sprintf(response, "%d,\"%s\",%d,\"" MACSTR "\",%d\r\n", - bss_link->authmode, bss_link->ssid, bss_link->rssi, MAC2STR(bss_link->bssid), bss_link->channel); - to_console(response); - bss_link = bss_link->next.stqe_next; - } - } else { - os_sprintf(response, "scan fail !!!\r\n"); - to_console(response); - } - system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) console_conn); -} -#endif - void ICACHE_FLASH_ATTR con_print(uint8_t *str) { ringbuf_memcpy_into(console_tx_buffer, str, os_strlen(str)); system_os_post(user_procTaskPrio, SIG_CONSOLE_TX_RAW, (ETSParam) console_conn); } -bool ICACHE_FLASH_ATTR printf_topic(topic_entry * topic, void *user_data) { - uint8_t *response = (uint8_t *) user_data; - - os_sprintf(response, "%s: \"%s\" (QoS %d)\r\n", - topic->clientcon != - LOCAL_MQTT_CLIENT ? topic->clientcon->connect_info.client_id : "local", topic->topic, topic->qos); - to_console(response); - return false; -} - -bool ICACHE_FLASH_ATTR printf_retainedtopic(retained_entry * entry, void *user_data) { - uint8_t *response = (uint8_t *) user_data; - - os_sprintf(response, "\"%s\" len: %d (QoS %d)\r\n", entry->topic, entry->data_len, entry->qos); - to_console(response); - return false; -} - bool ICACHE_FLASH_ATTR delete_retainedtopics() { clear_retainedtopics(); blob_zero(RETAINED_SLOT, MAX_RETAINED_LEN); @@ -417,903 +311,6 @@ void MQTT_local_DataCallback(uint32_t * args, const char *topic, uint32_t topic_ #endif } - -static char INVALID_LOCKED[] = "Invalid command. Config locked\r\n"; -static char INVALID_NUMARGS[] = "Invalid number of arguments\r\n"; -static char INVALID_ARG[] = "Invalid argument\r\n"; - -void ICACHE_FLASH_ATTR console_handle_command(struct espconn *pespconn) { -#define MAX_CMD_TOKENS 6 - - char cmd_line[MAX_CON_CMD_SIZE + 1]; - char response[256]; - char *tokens[MAX_CMD_TOKENS]; - - int bytes_count, nTokens; - - bytes_count = ringbuf_bytes_used(console_rx_buffer); - ringbuf_memcpy_from(cmd_line, console_rx_buffer, bytes_count); - - cmd_line[bytes_count] = 0; - response[0] = 0; - - nTokens = parse_str_into_tokens(cmd_line, tokens, MAX_CMD_TOKENS); - - if (nTokens == 0) { - char c = '\n'; - ringbuf_memcpy_into(console_tx_buffer, &c, 1); - goto command_handled_2; - } - - if (strcmp(tokens[0], "help") == 0) { - os_sprintf(response, "show [config|stats|mqtt]\r\nsave\r\nreset [factory]\r\nlock []\r\nunlock \r\nquit\r\n"); - to_console(response); -#ifdef ALLOW_SCANNING - os_sprintf(response, "scan\r\n"); - to_console(response); -#endif - os_sprintf(response, "set [ssid|password|auto_connect|ap_ssid|ap_password|ap_on|ap_open] \r\n"); - to_console(response); - os_sprintf(response, "set [network|dns|ip|netmask|gw] \r\n"); - to_console(response); - os_sprintf(response, "set [config_port|config_access|bitrate|system_output] \r\n"); - to_console(response); - os_sprintf(response, "set [broker_user|broker_password|broker_access|broker_clients] \r\n"); - to_console(response); - os_sprintf(response, "set [broker_subscriptions|broker_retained_messages|broker_autoretain] \r\n"); - to_console(response); - os_sprintf(response, "delete_retained|save_retained\r\n"); - to_console(response); - os_sprintf(response, "publish [local|remote] [retained]\r\n"); - to_console(response); -#ifdef SCRIPTED - os_sprintf(response, "script ||delete\r\nshow [script|vars]\r\n"); - to_console(response); -#ifdef GPIO -#ifdef GPIO_PWM - os_sprintf(response, "set pwm_period \r\n"); - to_console(response); -#endif -#endif -#endif -#ifdef NTP - os_sprintf(response, "time\r\nset [ntp_server|ntp_interval| \r\n"); - to_console(response); -#endif -#ifdef MQTT_CLIENT - os_sprintf(response, "set [mqtt_host|mqtt_port|mqtt_ssl|mqtt_user|mqtt_password|mqtt_id] \r\n"); - to_console(response); -#endif - - goto command_handled_2; - } - - if (strcmp(tokens[0], "show") == 0) { - int16_t i; - ip_addr_t i_ip; - - if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { - os_sprintf(response, "STA: SSID:%s PW:%s%s\r\n", - config.ssid, - config.locked ? "***" : (char *)config.password, config.auto_connect ? "" : " [AutoConnect:0]"); - to_console(response); - - os_sprintf(response, "AP: SSID:%s PW:%s%s%s IP:%d.%d.%d.%d/24\r\n", - config.ap_ssid, - config.locked ? "***" : (char *)config.ap_password, - config.ap_open ? " [open]" : "", - config.ap_on ? "" : " [disabled]", IP2STR(&config.network_addr)); - to_console(response); - - // if static IP, add it - os_sprintf(response, - config.my_addr.addr ? - "Static IP: %d.%d.%d.%d Netmask: %d.%d.%d.%d Gateway: %d.%d.%d.%d\r\n" - : "", IP2STR(&config.my_addr), IP2STR(&config.my_netmask), IP2STR(&config.my_gw)); - to_console(response); - // if static DNS, add it - os_sprintf(response, config.dns_addr.addr ? "DNS: %d.%d.%d.%d\r\n" : "", IP2STR(&config.dns_addr)); - to_console(response); -#ifdef MDNS - if (config.mdns_mode) { - os_sprintf(response, "mDNS: %s interface\r\n", config.mdns_mode==1 ? "STA": "SoftAP"); - to_console(response); - } -#endif -#ifdef REMOTE_CONFIG - if (config.config_port == 0 || config.config_access == 0) { - os_sprintf(response, "No network console access\r\n"); - } else { - os_sprintf(response, "Network console access on port %d (mode %d)\r\n", config.config_port, config.config_access); - } - to_console(response); -#endif - - os_sprintf(response, "MQTT broker max. subscription: %d\r\nMQTT broker max. retained messages: %d%s\r\n", - config.max_subscriptions, config.max_retained_messages, config.auto_retained?" (auto saved)":""); - to_console(response); - - os_sprintf(response, "MQTT broker max. clients: %d\r\n", config.max_clients); - to_console(response); - - if (os_strcmp(config.mqtt_broker_user, "none") != 0) { - os_sprintf(response, - "MQTT broker username: %s\r\nMQTT broker password: %s\r\n", - config.mqtt_broker_user, - config.locked ? "***" : (char *)config.mqtt_broker_password); - to_console(response); - } - response[0] = '\0'; - if (config.mqtt_broker_access == LOCAL_ACCESS) - os_sprintf(response, "MQTT broker: local access only\r\n"); - if (config.mqtt_broker_access == REMOTE_ACCESS) - os_sprintf(response, "MQTT broker: remote access only\r\n"); - if (config.mqtt_broker_access == 0) - os_sprintf(response, "MQTT broker: disabled\r\n"); - to_console(response); -#ifdef MQTT_CLIENT - os_sprintf(response, "MQTT client %s\r\n", mqtt_enabled ? "enabled" : "disabled"); - to_console(response); - - if (os_strcmp(config.mqtt_host, "none") != 0) { - os_sprintf(response, - "MQTT client host: %s\r\nMQTT client port: %d\r\nMQTT client user: %s\r\nMQTT client password: %s\r\nMQTT client id: %s\r\nMQTT SSL: %s\r\n", - config.mqtt_host, config.mqtt_port, config.mqtt_user, - config.locked ? "***" : (char *)config.mqtt_password, config.mqtt_id, - config.mqtt_ssl ? "on" : "off"); - to_console(response); - } -#endif -#ifdef NTP - if (os_strcmp(config.ntp_server, "none") != 0) { - os_sprintf(response, - "NTP server: %s (interval: %d s, tz: %d)\r\n", - config.ntp_server, config.ntp_interval / 1000000, config.ntp_timezone); - to_console(response); - } -#endif - os_sprintf(response, "Clock speed: %d\r\n", config.clock_speed); - to_console(response); - - os_sprintf(response, "Serial bitrate: %d\r\n", config.bit_rate); - to_console(response); - if (!config.system_output) - to_console("System output: off\r\n"); - - goto command_handled_2; - } - - if (nTokens == 2 && strcmp(tokens[1], "stats") == 0) { - uint32_t time = (uint32_t) (get_long_systime() / 1000000); - int16_t i; - - os_sprintf(response, "System uptime: %d:%02d:%02d\r\n", time / 3600, (time % 3600) / 60, time % 60); - to_console(response); - - os_sprintf(response, "Free mem: %d\r\n", system_get_free_heap_size()); - to_console(response); -#ifdef SCRIPTED - os_sprintf(response, "Interpreter loop: %d us\r\n", loop_time); - to_console(response); -#endif - if (connected) { - os_sprintf(response, "External IP-address: " IPSTR "\r\n", IP2STR(&my_ip)); - } else { - os_sprintf(response, "Not connected to AP\r\n"); - } - to_console(response); - if (config.ap_on) - os_sprintf(response, "%d Station%s connected to AP\r\n", - wifi_softap_get_station_num(), wifi_softap_get_station_num() == 1 ? "" : "s"); - else - os_sprintf(response, "AP disabled\r\n"); - to_console(response); -#ifdef NTP - if (ntp_sync_done()) { - os_sprintf(response, "NTP synced: %s \r\n", get_timestr()); - } else { - os_sprintf(response, "NTP no sync\r\n"); - } - to_console(response); -#endif - goto command_handled_2; - } - - if (nTokens == 2 && strcmp(tokens[1], "mqtt") == 0) { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - MQTT_ClientCon *clientcon; - int ccnt = 0; - - os_sprintf(response, "Current clients: %d\r\n", MQTT_server_countClientCon()); - to_console(response); - for (clientcon = clientcon_list; clientcon != NULL; clientcon = clientcon->next, ccnt++) { - os_sprintf(response, "%s%s", clientcon->connect_info.client_id, clientcon->next != NULL ? ", " : ""); - to_console(response); - } - os_sprintf(response, "%sCurrent subsriptions:\r\n", ccnt ? "\r\n" : ""); - to_console(response); - iterate_topics(printf_topic, response); - os_sprintf(response, "Retained topics:\r\n"); - to_console(response); - iterate_retainedtopics(printf_retainedtopic, response); -#ifdef MQTT_CLIENT - os_sprintf(response, "MQTT client %s\r\n", mqtt_connected ? "connected" : "disconnected"); - to_console(response); -#endif -#ifdef SCRIPTED - os_sprintf(response, "Script %s\r\n", script_enabled ? "enabled" : "disabled"); - to_console(response); -#endif - goto command_handled_2; - } -#ifdef SCRIPTED - if (nTokens >= 2 && strcmp(tokens[1], "script") == 0) { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - uint32_t line_count, char_count, start_line = 1; - if (nTokens == 3) - start_line = atoi(tokens[2]); - - uint32_t size = get_script_size(); - if (size == 0) - goto command_handled; - - uint8_t *script = (uint8_t *) os_malloc(size); - uint8_t *p; - bool nl; - - if (script == 0) { - os_sprintf(response, "Out of memory"); - goto command_handled; - } - - blob_load(SCRIPT_SLOT, (uint32_t *) script, size); - - p = script + 4; - for (line_count = 1; line_count < start_line && *p != 0; p++) { - if (*p == '\n') - line_count++; - } - nl = true; - for (char_count = 0; *p != 0 && char_count < MAX_CON_SEND_SIZE - 20; p++, char_count++) { - if (nl) { - os_sprintf(response, "\r%4d: ", line_count); - char_count += 7; - to_console(response); - line_count++; - nl = false; - } - ringbuf_memcpy_into(console_tx_buffer, p, 1); - if (*p == '\n') - nl = true; - } - if (*p == 0) { - ringbuf_memcpy_into(console_tx_buffer, "\r\n--end--", 9); - } else { - ringbuf_memcpy_into(console_tx_buffer, "...", 3); - } - ringbuf_memcpy_into(console_tx_buffer, "\r\n", 2); - - os_free(script); - goto command_handled_2; - } - - if (nTokens >= 2 && strcmp(tokens[1], "vars") == 0) { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - int i; - - if (script_enabled) { - for (i = 0; i < MAX_VARS; i++) { - if (!vars[i].free) { - os_sprintf(response, "%s: %s\r\n", vars[i].name, vars[i].data); - to_console(response); - } - } - } - - uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN]; - blob_load(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); - - for (i = 0; i < MAX_FLASH_SLOTS; i++) { - os_sprintf(response, "@%d: %s\r\n", i+1, &slots[i*FLASH_SLOT_LEN]); - to_console(response); - } - goto command_handled_2; - } -#endif - } - - if (strcmp(tokens[0], "save") == 0) { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - if (nTokens == 1 || (nTokens == 2 && strcmp(tokens[1], "config") == 0)) { - config_save(&config); - os_sprintf(response, "Config saved\r\n"); - goto command_handled; - } - } -#ifdef ALLOW_SCANNING - if (strcmp(tokens[0], "scan") == 0) { - wifi_station_scan(NULL, scan_done); - os_sprintf(response, "Scanning...\r\n"); - goto command_handled; - } -#endif -#ifdef NTP - if (strcmp(tokens[0], "time") == 0) { - os_sprintf(response, "%s %s\r\n", get_weekday(), get_timestr()); - goto command_handled; - } -#endif - if (strcmp(tokens[0], "reset") == 0) { - if (config.locked && pespconn != NULL) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - if (nTokens == 2 && strcmp(tokens[1], "factory") == 0) { - config_load_default(&config); - config_save(&config); -#ifdef SCRIPTED - // Clear script, vars, and retained topics - blob_zero(SCRIPT_SLOT, MAX_SCRIPT_SIZE); - blob_zero(VARS_SLOT, MAX_FLASH_SLOTS * FLASH_SLOT_LEN); - blob_zero(RETAINED_SLOT, MAX_RETAINED_LEN); -#endif - } - - save_retainedtopics(); - - os_printf("Restarting ... \r\n"); - system_restart(); // if it works this will not return - - os_sprintf(response, "Reset failed\r\n"); - goto command_handled; - } - - if (strcmp(tokens[0], "quit") == 0) { - remote_console_disconnect = 1; - os_sprintf(response, "Quitting console\r\n"); - goto command_handled; - } -#ifdef SCRIPTED - if (strcmp(tokens[0], "script") == 0) { - uint16_t port; - - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - if (nTokens != 2) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - - if (strcmp(tokens[1], "delete") == 0) { -#ifdef SCRIPTED -#ifdef GPIO - stop_gpios(); -#endif -#endif - script_enabled = false; - if (my_script != NULL) - free_script(); - blob_zero(0, MAX_SCRIPT_SIZE); - blob_zero(1, MAX_FLASH_SLOTS * FLASH_SLOT_LEN); - os_sprintf(response, "Script deleted\r\n"); - goto command_handled; - } - - if (!isdigit(tokens[1][0])) { - scriptcon = pespconn; - http_get(tokens[1], "", http_script_cb); - os_sprintf(response, "HTTP request to %s started\r\n", tokens[1]); - goto command_handled; - } - - port = atoi(tokens[1]); - if (port == 0) { - os_sprintf(response, "Invalid port\r\n"); - goto command_handled; - } - // delete and disable existing script -#ifdef SCRIPTED -#ifdef GPIO - stop_gpios(); -#endif -#endif - script_enabled = false; - if (my_script != NULL) - free_script(); - - scriptcon = pespconn; - downloadCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); - - /* Equivalent to bind */ - downloadCon->type = ESPCONN_TCP; - downloadCon->state = ESPCONN_NONE; - downloadCon->proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); - downloadCon->proto.tcp->local_port = port; - - /* Register callback when clients connect to the server */ - espconn_regist_connectcb(downloadCon, script_connected_cb); - - /* Put the connection in accept mode */ - espconn_accept(downloadCon); - - os_sprintf(response, "Waiting for script upload on port %d\r\n", port); - goto command_handled; - } -#endif - if (strcmp(tokens[0], "lock") == 0) { - if (config.locked) { - os_sprintf(response, "Config already locked\r\n"); - goto command_handled; - } - if (nTokens == 1) { - if (os_strlen(config.lock_password) == 0) { - os_sprintf(response, "No password defined\r\n"); - goto command_handled; - } - } - else if (nTokens == 2) { - os_sprintf(config.lock_password, "%s", tokens[1]); - } - else { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - config.locked = 1; - config_save(&config); - os_sprintf(response, "Config locked (pw: %s)\r\n", config.lock_password); - goto command_handled; - } - - if (strcmp(tokens[0], "unlock") == 0) { - if (nTokens != 2) { - os_sprintf(response, INVALID_NUMARGS); - } else if (os_strcmp(tokens[1], config.lock_password) == 0) { - config.locked = 0; - config_save(&config); - os_sprintf(response, "Config unlocked\r\n"); - } else { - os_sprintf(response, "Unlock failed. Invalid password\r\n"); - } - goto command_handled; - } - - if (strcmp(tokens[0], "publish") == 0) - { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - uint8_t retained = 0; - - if (nTokens < 4 || nTokens > 5) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - if (nTokens == 5) { - if (strcmp(tokens[4], "retained")==0) { - retained = 1; - } else { - os_sprintf(response, "Invalid arg %s\r\n", tokens[4]); - goto command_handled; - } - } - if (strcmp(tokens[1], "local") == 0) { - MQTT_local_publish(tokens[2], tokens[3], os_strlen(tokens[3]), 0, retained); - } -#ifdef MQTT_CLIENT - else if (strcmp(tokens[1], "remote") == 0 && mqtt_connected) { - MQTT_Publish(&mqttClient, tokens[2], tokens[3], os_strlen(tokens[3]), 0, retained); - } -#endif - else { - os_sprintf(response, "Invalid arg %s\r\n", tokens[1]); - goto command_handled; - } - os_sprintf(response, "Published topic\r\n"); - goto command_handled; - } - - if (strcmp(tokens[0], "delete_retained") == 0) - { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - if (nTokens != 1) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - - delete_retainedtopics(); - - os_sprintf(response, "Deleted retained topics\r\n"); - goto command_handled; - } - - if (strcmp(tokens[0], "save_retained") == 0) - { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - if (nTokens != 1) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - - bool success = save_retainedtopics(); - - os_sprintf(response, "Saved retained topics %ssuccessfully\r\n", success?"":"un"); - goto command_handled; - } -/* - if (strcmp(tokens[0], "load_retained") == 0) - { - if (nTokens != 1) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } - - bool success = load_retainedtopics(); - - os_sprintf(response, "Loaded retained topics %ssuccessfully\r\n", success?"":"un"); - goto command_handled; - } -*/ - if (strcmp(tokens[0], "set") == 0) { - if (config.locked) { - os_sprintf(response, INVALID_LOCKED); - goto command_handled; - } - - /* - * For set commands atleast 2 tokens "set" "parameter" "value" is needed - * hence the check - */ - if (nTokens < 3) { - os_sprintf(response, INVALID_NUMARGS); - goto command_handled; - } else { - // atleast 3 tokens, proceed - if (strcmp(tokens[1], "ssid") == 0) { - os_sprintf(config.ssid, "%s", tokens[2]); - config.auto_connect = 1; - os_sprintf(response, "SSID set (auto_connect = 1)\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "password") == 0) { - os_sprintf(config.password, "%s", tokens[2]); - os_sprintf(response, "Password set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "auto_connect") == 0) { - config.auto_connect = atoi(tokens[2]); - os_sprintf(response, "Auto Connect set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "ap_ssid") == 0) { - os_sprintf(config.ap_ssid, "%s", tokens[2]); - os_sprintf(response, "AP SSID set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "ap_password") == 0) { - if (os_strlen(tokens[2]) < 8) { - os_sprintf(response, "Password to short (min. 8)\r\n"); - } else { - os_sprintf(config.ap_password, "%s", tokens[2]); - config.ap_open = 0; - os_sprintf(response, "AP Password set\r\n"); - } - goto command_handled; - } - - if (strcmp(tokens[1], "ap_open") == 0) { - config.ap_open = atoi(tokens[2]); - os_sprintf(response, "Open Auth set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "ap_on") == 0) { - if (atoi(tokens[2])) { - if (!config.ap_on) { - wifi_set_opmode(STATIONAP_MODE); - user_set_softap_wifi_config(); - do_ip_config = true; - config.ap_on = true; - os_sprintf(response, "AP on\r\n"); - } else { - os_sprintf(response, "AP already on\r\n"); - } - - } else { - if (config.ap_on) { - wifi_set_opmode(STATION_MODE); -#ifdef MDNS - if (config.mdns_mode == 2) { - espconn_mdns_close(); - } -#endif - config.ap_on = false; - os_sprintf(response, "AP off\r\n"); - } else { - os_sprintf(response, "AP already off\r\n"); - } - } - goto command_handled; - } - - if (strcmp(tokens[1], "speed") == 0) { - uint16_t speed = atoi(tokens[2]); - bool succ = system_update_cpu_freq(speed); - if (succ) - config.clock_speed = speed; - os_sprintf(response, "Clock speed update %s\r\n", succ ? "successful" : "failed"); - goto command_handled; - } - - if (strcmp(tokens[1],"bitrate") == 0) - { - config.bit_rate = atoi(tokens[2]); - os_sprintf(response, "Bitrate set to %d\r\n", config.bit_rate); - goto command_handled; - } - - if (strcmp(tokens[1],"system_output") == 0) - { - config.system_output = atoi(tokens[2]); - os_sprintf(response, "System output %s\r\n", config.system_output?"on":"off"); - goto command_handled; - } - - if (strcmp(tokens[1], "network") == 0) { - config.network_addr.addr = ipaddr_addr(tokens[2]); - ip4_addr4(&config.network_addr) = 0; - os_sprintf(response, "Network set to %d.%d.%d.%d\r\n", IP2STR(&config.network_addr)); - goto command_handled; - } - - if (strcmp(tokens[1], "dns") == 0) { - if (os_strcmp(tokens[2], "dhcp") == 0) { - config.dns_addr.addr = 0; - os_sprintf(response, "DNS from DHCP\r\n"); - } else { - config.dns_addr.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "DNS set to %d.%d.%d.%d\r\n", IP2STR(&config.dns_addr)); - if (config.dns_addr.addr) { - dns_ip.addr = config.dns_addr.addr; - } - } - goto command_handled; - } - - if (strcmp(tokens[1], "ip") == 0) { - if (os_strcmp(tokens[2], "dhcp") == 0) { - config.my_addr.addr = 0; - os_sprintf(response, "IP from DHCP\r\n"); - } else { - config.my_addr.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "IP address set to %d.%d.%d.%d\r\n", IP2STR(&config.my_addr)); - } - goto command_handled; - } - - if (strcmp(tokens[1], "netmask") == 0) { - config.my_netmask.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "IP netmask set to %d.%d.%d.%d\r\n", IP2STR(&config.my_netmask)); - goto command_handled; - } - - if (strcmp(tokens[1], "gw") == 0) { - config.my_gw.addr = ipaddr_addr(tokens[2]); - os_sprintf(response, "Gateway set to %d.%d.%d.%d\r\n", IP2STR(&config.my_gw)); - goto command_handled; - } -#ifdef MDNS - if (strcmp(tokens[1], "mdns_mode") == 0) { - config.mdns_mode = atoi(tokens[2]); - os_sprintf(response, "mDNS mode set to %d\r\n", config.mdns_mode); - goto command_handled; - } -#endif -#ifdef REMOTE_CONFIG - if (strcmp(tokens[1], "config_port") == 0) { - config.config_port = atoi(tokens[2]); - if (config.config_port == 0) - os_sprintf(response, "WARNING: if you save this, remote console access will be disabled!\r\n"); - else - os_sprintf(response, "Config port set to %d\r\n", config.config_port); - goto command_handled; - } - - if (strcmp(tokens[1], "config_access") == 0) { - config.config_access = atoi(tokens[2]) & (LOCAL_ACCESS | REMOTE_ACCESS); - if (config.config_access == 0) - os_sprintf(response, "WARNING: if you save this, remote console access will be disabled!\r\n"); - else - os_sprintf(response, "Config access set\r\n"); - goto command_handled; - } -#endif - if (strcmp(tokens[1], "broker_subscriptions") == 0) { - config.max_subscriptions = atoi(tokens[2]); - os_sprintf(response, "Broker subscriptions set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_retained_messages") == 0) { - config.max_retained_messages = atoi(tokens[2]); - os_sprintf(response, "Broker retained messages set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_clients") == 0) { - config.max_clients = atoi(tokens[2]); - os_sprintf(response, "Broker max clients set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_user") == 0) { - os_strncpy(config.mqtt_broker_user, tokens[2], 32); - config.mqtt_broker_user[31] = '\0'; - os_sprintf(response, "Broker username set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_password") == 0) { - if (os_strcmp(tokens[2], "none") == 0) { - config.mqtt_broker_password[0] = '\0'; - } else { - os_strncpy(config.mqtt_broker_password, tokens[2], 32); - config.mqtt_broker_password[31] = '\0'; - } - os_sprintf(response, "Broker password set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_access") == 0) { - config.mqtt_broker_access = atoi(tokens[2]) & (LOCAL_ACCESS | REMOTE_ACCESS); - os_sprintf(response, "Broker access set\r\n", config.config_port); - goto command_handled; - } - - if (strcmp(tokens[1], "broker_autoretain") == 0) { - config.auto_retained = atoi(tokens[2]) != 0; - os_sprintf(response, "Broker autoretain set\r\n", config.config_port); - goto command_handled; - } -#ifdef SCRIPTED - if (strcmp(tokens[1], "script_logging") == 0) { - lang_logging = atoi(tokens[2]); - os_sprintf(response, "Script logging set\r\n"); - goto command_handled; - } - - if (tokens[1][0] == '@') { - uint32_t slot_no = atoi(&tokens[1][1]); - if (slot_no == 0 || slot_no > MAX_FLASH_SLOTS) { - os_sprintf(response, "Invalid flash slot number"); - } else { - slot_no--; - uint8_t slots[MAX_FLASH_SLOTS*FLASH_SLOT_LEN]; - blob_load(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); - os_strcpy(&slots[slot_no*FLASH_SLOT_LEN], tokens[2]); - blob_save(VARS_SLOT, (uint32_t *)slots, sizeof(slots)); - os_sprintf(response, "%s written to flash\r\n", tokens[1]); - } - goto command_handled; - } -#ifdef GPIO -#ifdef GPIO_PWM - if (strcmp(tokens[1], "pwm_period") == 0) { - config.pwm_period = atoi(tokens[2]); - os_sprintf(response, "PWM period set\r\n"); - goto command_handled; - } -#endif -#endif -#endif -#ifdef NTP - if (strcmp(tokens[1], "ntp_server") == 0) { - os_strncpy(config.ntp_server, tokens[2], 32); - config.ntp_server[31] = 0; - ntp_set_server(config.ntp_server); - os_sprintf(response, "NTP server set to %s\r\n", config.ntp_server); - goto command_handled; - } - - if (strcmp(tokens[1], "ntp_interval") == 0) { - config.ntp_interval = atoi(tokens[2]) * 1000000; - os_sprintf(response, "NTP interval set to %d s\r\n", atoi(tokens[2])); - goto command_handled; - } - - if (strcmp(tokens[1], "ntp_timezone") == 0) { - config.ntp_timezone = atoi(tokens[2]); - set_timezone(config.ntp_timezone); - os_sprintf(response, "NTP timezone set to %d h\r\n", config.ntp_timezone); - goto command_handled; - } -#endif -#ifdef MQTT_CLIENT - if (strcmp(tokens[1], "mqtt_host") == 0) { - os_strncpy(config.mqtt_host, tokens[2], 32); - config.mqtt_host[31] = 0; - os_sprintf(response, "MQTT host set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "mqtt_port") == 0) { - config.mqtt_port = atoi(tokens[2]); - os_sprintf(response, "MQTT port set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "mqtt_ssl") == 0) { - config.mqtt_ssl = atoi(tokens[2]); - os_sprintf(response, "MQTT ssl %s\r\n", config.mqtt_ssl?"on":"off"); - goto command_handled; - } - - if (strcmp(tokens[1], "mqtt_user") == 0) { - os_strncpy(config.mqtt_user, tokens[2], 32); - config.mqtt_user[31] = 0; - os_sprintf(response, "MQTT user set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "mqtt_password") == 0) { - os_strncpy(config.mqtt_password, tokens[2], 32); - config.mqtt_password[31] = 0; - os_sprintf(response, "MQTT password set\r\n"); - goto command_handled; - } - - if (strcmp(tokens[1], "mqtt_id") == 0) { - os_strncpy(config.mqtt_id, tokens[2], 32); - config.mqtt_id[31] = 0; - os_sprintf(response, "MQTT id set\r\n"); - goto command_handled; - } -#endif /* MQTT_CLIENT */ - } - - } - - /* Control comes here only if the tokens[0] command is not handled */ - os_sprintf(response, "\r\nInvalid Command\r\n"); - - command_handled: - to_console(response); - command_handled_2: - system_os_post(user_procTaskPrio, SIG_CONSOLE_TX, (ETSParam) pespconn); - return; -} - #ifdef SCRIPTED void ICACHE_FLASH_ATTR do_command(char *t1, char *t2, char *t3) { ringbuf_memcpy_into(console_rx_buffer, t1, os_strlen(t1));