From f1b445a0dbf598097bb405c41dfc25e65ac556fe Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 30 Oct 2024 01:05:20 +0900 Subject: [PATCH 1/6] refactor code to run on modern frameworks - build with EmbUI 4.0 - build with ArduinoJson 7 - build with Arduino core 3.x --- .gitignore | 1 + data/css/menu-dark.webp | Bin 0 -> 112 bytes data/css/menu-light.webp | Bin 0 -> 3266 bytes data/css/pure.css.gz | Bin 5323 -> 4914 bytes data/css/style.css.gz | Bin 3449 -> 3344 bytes data/css/style_dark.css.gz | Bin 3418 -> 3301 bytes data/css/style_light.css.gz | Bin 3354 -> 3240 bytes data/index.html.gz | Bin 2822 -> 2796 bytes data/js/embui.js.gz | Bin 12403 -> 13120 bytes data/js/espem.js.gz | Bin 2927 -> 3024 bytes data/js/espem.ui.json.gz | Bin 1055 -> 1277 bytes data/js/lodash.custom.js.gz | Bin 4830 -> 4832 bytes data/js/ui_embui.i18n.json.gz | Bin 0 -> 2193 bytes data/js/ui_embui.json.gz | Bin 0 -> 2058 bytes data/js/ui_embui.lang.json.gz | Bin 0 -> 80 bytes data/js/ui_sys.json.gz | Bin 1857 -> 0 bytes espem/espem.cpp | 92 +++--- espem/interface.cpp | 311 ++++++++---------- espem/interface.h | 14 +- espem/log.h | 82 +++++ espem/main.cpp | 71 +++-- espem/uistrings.h | 79 ++--- platformio.ini | 36 +-- resources/html/index.html | 15 +- resources/html/js/espem.js | 18 +- resources/html/js/espem.ui.json | 543 ++++++++++++++++++-------------- 26 files changed, 694 insertions(+), 568 deletions(-) create mode 100644 data/css/menu-dark.webp create mode 100644 data/css/menu-light.webp create mode 100644 data/js/ui_embui.i18n.json.gz create mode 100644 data/js/ui_embui.json.gz create mode 100644 data/js/ui_embui.lang.json.gz delete mode 100644 data/js/ui_sys.json.gz create mode 100644 espem/log.h diff --git a/.gitignore b/.gitignore index 68189d1..1c7f74e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .vscode config.json lnk +data.* drafts *.bin *.orig diff --git a/data/css/menu-dark.webp b/data/css/menu-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..182e533647424ad3916c13bf9f5f3af9d84ead59 GIT binary patch literal 112 zcmWIYbaTsKU|Qr literal 0 HcmV?d00001 diff --git a/data/css/menu-light.webp b/data/css/menu-light.webp new file mode 100644 index 0000000000000000000000000000000000000000..167ed8321776b7e5c17a0c4b6eba64325e60a901 GIT binary patch literal 3266 zcmbW42UJtp7KYEgH@#4j5Q>!0n{;Jh+uXU2>@V%euH(Bab;lkS36G2g)0>WkQsZ z0F(=}MZz4EZ=h_DDoYcg{1#L1b~yCv0NfbOp>xp_@*oyCnq+`PneS`lu9jvdHfWfM8M*tr)BWO%K`Xu&vGw7 zljoL&CfUKp$-%+G))Gzs*YUTDUsZn(^0WP-cop*b%phjhjBUnu#+F_K0DA(>&6XKk z{7wK`)&rn+cgAM04*k&tgGPw3b2KLuZve+}R4 zXDQ$BYwlQH!UUcyRmzelm7kuPE|ajbGI)F;%i_O__#Zod6V^9z%nlVM2qi)>x>W>P zWg>ARdfZ}xNGeJfvqa**I^lol_Du|O{8HB_&}%;d#!^e5)WZPC?_B^xCj%sQ54r+< zJ+}a21du;@kvgMa>KiKf5x4+%;0@-3AP^3s!4kj+NgxeKKn}23x>(PzCmaT2KcX!7!A)=v41y8x42**}UKyrNR6MpSod3{^_qOs%6{pgyF& zRiG#=OORud1c$p(<3}q}r(3 zuR5tVOU+$Ps8*!bq;^y7y*f+XTRla6t9pz20}YIZnMR04j>aC1iyF^oF=n~U;?F9Y zb!^sMO<2=RGgNbh<^jzv%?T}SEnlq+t({sIw4Q4-wcWLowac~7Xpia8b>``abjo$k z>O5tsusm3)tV&h~Yg|`T*H2fbyI;3kcS_G#Z=qhkUX$JfeUd&~KS94-zg>UaK*u1^ zAkUz|;I1LjkZqV`xWlm1aMH-wDAH(yQH#+NV>M&0ajtQL@qH7jiJM8LNsY-3Q=BQ= zG{tncX|EYXWs-b*go(^*tM!&3v1Dn>L$2ZOv@?w$-*b?5K7=cB}1L?Ox6? zpCg>JXU=VVx_zMi2K#pVj}DFw5{E{IF-HT(ILBR%18fF6m|eoY?1XjlbXx6n%ITf6 zgR|86nDZYl7A`3+^)6558qXEZt(`mUs^`jc-S0X$k2NoD-oAN*Zn|zfw;H!c?)vTm z_k-@E9;P129!ET0cv^c(JWqH|a-2CUIp@3(FJG@BuWoOe_d@R~?}t8mK8ZdJKI6Xj zzAJps`C=0{$c)A{*MC80we)#fpFmbz-@u|7Z@%O zFK7t@LENBiK@WnBgENDF4#9;4hg63=4z&r*3+)V}hb;}O4|@~t8D1KGZ=uOT*~0b+ z#faF5x`@e0ugJ2V!l~LECHKIk)tugqRh?s*h6N|hTZC^AJYad$}duy@r;$@4k zEYVn!y5#gy#ihKZ$K$YZk#Y5LA9z8$8s0=aH-1x%G zzAU{j!#txfV=&V>b9?5v#9wkS3(8uY)hcD6%~Y4nM7CZwlszxII{R%-M9%SL%FD#d zx|W+Q-?V%**C+S;6_^$L743Ptd292AR(h=5zY1Cvzp8z;{_29&BWrxu99~OWo3i$o zeCzzO{MYLu*R>UB6|5;3UhliUp-`bvQaG@|Wy9W$n2n;1-J9$-?cDUKNKkaO*t)o) z_(KW5x_Zs}(gwIe z)-ZM?_DJs!UO%)pS~Tu&Vm1{Y1xIB^pZ*y4-<$J&pxk2n5g_|vWv^bD-PgS2*IbC{&bf)0U^x53A6X!C{JwKm#ez<*U z`@IWM7jATfbaY?zzj*nQ&!r2U9-U_|yIwwZ#rev~tL&>S*Bq`jciDHH_{IL06WtEo z%{`7it-VgYZGCh5&h)$WpTF*P{n8EYjcYd--0Zux@Yd~t*nz>@g4<8-q~3XXH~a3? zy*2l-_lq7VJ*ar7`LO0!(_fng9R^Pic@1?v3VU>Km_PjNx2)ejj1)YkKCT$m8LfL_ z`{dM^&sgu%MNdbbWjvdDUiiE6@4H@@y!h!4_dmMEqsB*GN?uO?S@KH#)uGpOUbjyK zPdt2+_GW6b=&kzO!|&MdI^RdWADznifd5eW(ez{6C;w0Pr_-jV(SH~$Gqeflg}`pe zjD)rV5YRNN1@P!Pn*D14K20+mB5&YA_qY$euF^(ZeBd3$_0d3o0|SBFkpLiw1`w2> Ie|I1M0`BSxHvj+t literal 0 HcmV?d00001 diff --git a/data/css/pure.css.gz b/data/css/pure.css.gz index 4076447f55b88b33001cd36eee4cc27a9b7082a2..4c98d30c4d12ecd0ac7ed8327a5712c6d25ca1e1 100644 GIT binary patch literal 4914 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 literal 5323 zcmchVB{e1t4-}}*d z@IDV-=N$4#RJi{g0^+HjZR}rOr&rx}-FmSC)-ek_gfME-JLV~;gZG#pC>MgJDF0On9!?5&4- zk7VTHuqvv^pHfV`KIxCGusv_L(bq4HQC4PO{blI*GW+a4a`mWDSk2gKvC6via#P0` z`#7RWgLSOr!C@E%0d|~~j}?2XE<9XXH75TNw;s!P;R$mv@Wo%K0_JENUh{8$+VT7o zYzI#La<${)0*p2H`QB`@_NKeZpA`Z5)@d_)np*NF2@898Id$Pr*=&JErJ36jxW3HS z_No{=XU~!WM(u>8sQyK3=nA-aI?vf^t+}$h=eXb*uM2G9%^UZ0F$ z)5bSO7xVYoouB9L)jV)DbFfaX)^^akvCv$NZRr_UKw*FKAoo!pFZ8r?=4?kiv$T9c z2&DVez*3v2niqQi>)I3dt?*VjwoxNomrFX1Q)E|dmVxju1`I}{wmh#@0mihkYW#q; zXWv`3mVHbi@uZhwMw|N5HYOE6lFee_kf`%GVOeMSCSq^!2U~e3ZCPbz2sWmR(s21P zNJNogf~j-+tNUcVMRjLU&fB9$4iA;3ghf%j%Fg!(nge}9{i=;8^K(U5D}8&>+S$Ne zB&pIlgH=ju4w=NDD{uBk5>@)Yu1fQtRj^n^kh4noKkcoE56BZ@EKXa)Vpm^&PD(5! z=^|DLPy_bt=%Q;s+&K6_8xeP{Nn#809u?Z$hHMz%;-WvXndF3l%14uJ@s^EY@s8a6@onOnQ(TuGm z8FszK6GyJM+E{5?n4)5H69j8{1Nh0L!1+!wQ8xLm?uFla*}Z@^d_!};LMczaBB)^$ z635|EOawH*&Em?&HhmI%f7>j^57?G5>cO#r$KXXkjL9>4Q>xf()2H1$yd?EIa)()A7N2Q#yJ(E6yynW>5y2$3d>MN+Z*T@xQJ#|$d6 zb4eQ1;0Lm7nAlIKNc4V!!s5889Zw8`i4{1v=m2;TO~Br0o&u;n!X0Q(ZSbl)sp)ae zx>eWFRS%1!-|aFz zuz5tb7}#BJW>OcSo)dfu&s{&!$JRkk)UkuIeHg7&^^MEOdZgK9aa7BeOh(?TbtkI~ zAIsK%U*1dj~GBN?l3zuaGbZ$EWY9ZsLET*-!r!8khqK}*sV=ppfs>LhH_ zi6=rLqhP-Z>w{A6BZdia7x~X2mPi~G3m`_4`XWXP|K{(5K2QAC{)UQKd?hf9aP4kp z=41$Wb@|A6ZeOj2B_*d#d}U?hjDTr0QNTTeqQ>z$JUG;q5U%;7=(fh5wG0AXL9Jw! z;^xm)|X5ir9Udv~Ab2YI?|#*%sb&*3YHiE3AmuRQvzJ|MIG zOfeHtl`sQSsrk>OCEF{#N)buN1!S%5c-k%tstWczF7uq$v7v2b9lC2%rd=F~0^bd@ zf~|05#pNR@nYBB4hi$9MuPBV2&i|ZX0Z1E58mjOE+mG5$I}4g{{%b!fa$NMF^{mQN z6h^-U%)8J9YRUPbB9@wPr0VzX6OyT{k-ogRwl_k$2U@U5O&`vEQp|yt7PFD}82zc_ zYqTYC2ND91De|}GA)iguQ`DAdHZ^L6$BqDP6r#zHiQEu;K{wcABbdTN6U^(OIWCu> zT=J2*I)zW<8dl0iq7v7;zeJ=@gzpaekNde)Iu$$72`_?IObiXe<+&ClFTs@~u6LbY5N6BQzmu+_%DplRS~( zdS4P9X~*85NH^KVdL$3 z+esHy14WQ?koSs&rx_1~zVVDVSHe{|6L%fnQFl!#Y_33nN(ucm${oK&!o=lFN1?%V zN8@6$Y{^O@u$)4#ysLQAL`;Oo7{5_<2drGP+}iw~tGc-^5hp&lp?ENAu}UDUO({n* z*GvQ?FO%x$`gVHY!(i2+NeoDR@dmvcD>S!Baaqq@Py(hXy|Q^lO$d~U?;S{@`Te7F{{cH^y{F5{+YS$J{>DXW`ka)eyj=0)h;O;xX8nUrFy!eaF?x)h zq~fMuGYj%rw8Pf^Mr{8UlWm%EwXD1Mn{9f{rl_*rX5PyNeeFT@qH0aF^S$-d0~mLK zyYk8O%`DsOdKo|H<+KPcQlty-7i%~#7N0KQdSiFiuPw=8ftu8GmxRk{V8R)dfV?rP7b65`&BNg4V0aZXit&x*av;7cpn zm&SAWVYffIp@XD~;GKCEKk{6x8&6)U=Ap1b zwMA)=&Q#fIP0lW=9g19zgWjNZE&v@NkJbP82D4p=HX{0xJ#oeQmR`uyg(9o28_Kt> zDCx6?w!dYP9<_W0@uE&O!b;i*IMhH>N8~-Ch*Iu=9nuJSijWpt4uV;)V006GHV6QX zCGBq@fJJ8~P(}kAhcARmCj?cQtM58OHpm@>ldY{_MnkKDBp*wXA$E$i49{vDxNx9? zj8${i#u{2~}*dx>eM=VH9o;xN!3cd&wwiFmNb_*t}zQHTFAO;0E5MW3+N5?i3k z)TP*IeK;Sw)t)M4FzX>8KZNHj78!)S`HdM7Se2oRanE=5i+Ib?V0p)3*v(OXh|$?C z62iKK$%3^a_^$=yqP5D&k$2eekOL0ZHdl1{U+dz4Siyl4AxdTyBZxG^oz;_g+-3dt zvym#o)Zxa`g8XuQks>s7+*%J!GAcJ}fH=t%n7@J);S6L6Alhjf(UCpJVF`2W#UP|< zF^WMaXfX;$|Dq};xl0sbL`)?(gF3}Xg}LJ)lL2kq|L+J=c)5?*+ zx%n^EE8w$XoaY5^(*O^}2@3{k@I-%HXpI#SFIon2Tx^ZY4SVqV@{|;mD~v37J$+OP z`iaClSkYis4$7ZR)VEL3F9StZ(GQ;R&k3r*yl6qqfKZYfAG@2s3syp7=jnY)8$@rG zzI}%{Ivd0|mcGUNK5TB3=r4Wy{&P~$cV@IzqpHXn6rH4=>kL#c;d-=o_qr9W|Bud5 z+tCqGj5*nNDQ!L@EA^>+ahqc3LTdO> zFL9dSKM@*pZXHnAAPW8uY*+@$4V~pf6`k0PGN#%&NP`zDuJ^=tCA_KP8(F<_aujv8E>tBR@prRU5K^nu}4wrtPw-xpjJ)syzGB3ic+|b+kc132KkvZ*w%mOoe=8*Rd0w<0!r%J zikcC`{n40Y>piZ}AVFgie=ttdAmMvTmN)O#%=d7h{qj&`XMewotBKy(Jq#mo?Y3$q>CAmyb)*{EgziGqliRyku zEOH~6b$qvkeFtgN>;*fX4}=lPG6RX^bd#_pop+a5L)@0rfr>HXra`A)#KuN0^ChkIXr zd9LeQ4t9MB@O(~0WZ#$q`EmVwF?zBPYSND65p;%Kis6}9AMgTt6^!Dt=v%s(Nzw{z z;9O)q3-2X6eitLr1fvPYRrrhI!}HUM5~H(~!6%Zijlkm>Dx|U1_Rye4`Ek-COyI~S zh}Izu77>@2;{s^ajXJ{jKhXT3SVrhI{LLB{9AJULWlq6|DEC=*QFIRZH_vo`fEgB- z%e_`BMCGbSx`fAPt8X+>)WI00Oex6MZ-m?18_XF^G)~R?>FOtLCZm@VnleXkP_jrC z(IlPDp^L>Q2t6Rym%w z9qn#7UxIOJ{|EH7fk5jX+OYU8sk3Q76r;-)!elBh+xCtUNt(_zEhfKKYi3@3DW|zn z{zftzIK_D%9V6Gh9x!OI-WrG{z@lm7IFtb-`sB2aM(WyDhlHp-NfRK&Y9EpN_xBjK z&YJT+3KQ*P9U@}-v-oNV*} diff --git a/data/css/style.css.gz b/data/css/style.css.gz index 437daee4d5413c73cac080d2f4fd09a4fab1c6e5..17c816bf11fc84c91c74a4f309be44da3831e55f 100644 GIT binary patch literal 3344 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 acN`;891<}ajR@UuPyY#Yv9)xuF#rJ77>}_4 literal 3449 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 diff --git a/data/css/style_light.css.gz b/data/css/style_light.css.gz index c480dbaad7a82bbee5ebb0c091ff65cbd923b014..c70ab2d89f476965ea0d08d22be891d8d46b05cd 100644 GIT binary patch literal 3240 zcmV;Z3|I3XiwFP!000021Jzn*bK^MD{%rjUyfd{m;T2^{wj6jl=bW=4vL)h75-b3+ ztabUn9~w#+HX=l2^Ww@s1c zvXUxeYO~^R)2aJ!wtV-sAuN}^d$NPoy)ahrW1>r5;JBjEU|7oL7VpCT^#snn!2h;G zB^pR#b=R^ni54G2#^$zfh&E#vR+yHRD40>a(JfbihI^hIZM|YoFYO>|zJctZ8V<}= zvO(Q#_qUhJ0ya~Jaj{%Z{hvj;e8(2ckEX#`(epC>=tEscs;>J|UOs!1#wy6K4tj6y zKnrr~CC2?_7_g)DUXB|<7$KXC?L=WpHd`z;1Fm68VH6Y`qUc!GNvq|@pPDHBX-Sfw zlAupT3iZrbN>3e&yk4)@-5~I25?+ao9VCawfC^^rn%AP*XY9?dT4>nsESxt}Z9fwY zO00Wycg7o0a{}w9uZGeaH5L*o;?9fkc-wGqoAhvLImG-RG9pQP7UB3efLqkG;BeG?{`nDvcBV$VlZ z^g#~FxHMynW$FodvXaHVtM}${PO??A zcB>!}^(IlETiIyw00+-JCLf%hd~2i=aQ=SsouUY`J7%OEr7>JIpfuBxnG*Ia=M8JO zx@4kZyeTjtm`&cFPhMAiZ+}lGuWM+w)5#3Ka3?_wF`Q%bs=}F1iD6>9#|iX!JAG0! zc}lC%Cf-E9FNJ18qbXRoGr9YEAs)VYXN$P;2BQXO0cRt8z#43BJMq9^-rvVeP(j6w z^&X+BU~LX*a?6WCG@Im5`0R>1Ni&{fKjs|Ji9L#V;OcD`RNdw&pzXNQsyiS$a_X4; zXEGS69$HFhwktt_GqV-14lOnkv@_1kmxSbERi=0ije&&=sK53y)NfLpH}NUJe0Bvj zO_>sS;b&zq26hT&*u@1-&#_GjJAZ!wvzRTRMnG1M)q@4(>(5aF0s}xdlNUXla_$j@ zpk^e}SA<6xmr%bb-*vjKWL0=yu-+I8_B7n(!mk*i3GzJmg`wSjS)cqUTb{PM(-|#Fo?+c zPUHq$U=qFcH0h>e&a3=wFbmkb_bjmoQ8qf*E7$8)w_mnm#^zqY1#9sLod>;bBU0$X z%0GUbh^Bx?$MvZjT+1UHNyUWGtlCoJne{Q8a^kvDlMm1d$rb02}~Qf{k)t-xa3S5UA@++(%S zGwkaTk~cVeiemQm_Lfi`XQ1EY#F31gz*7H>pFW5~yuXFo8|rA8bCRv6Op7K5wgaSi zW&^6)Tj8it&5R_ZA$AY`7>m>4Vz#Z=)2J#VNpq})UFB&?R5;B#d5BPh?Zgc*VW38j z*fNya1|}bdHyy5^)dHm@b|#7fibxNljDQh6+j_?Gxar^bN=&cF$9`&31TkEc-25#u z{LWnhMz{m)gD@+>c;j^KP%8G@W8(b_M3J2G&*Jv0*@{FN$z%)7d0t8F z2$7@-&1~=)Stm6wz@?RqG`Q9ooC&|S8qe5|LBSh6Ddmm{!*){L>uk~uad%AK?wEpB zfriGAJ(Y*vnbye7rUIe~ZKdh+QWSvGYocr9KbtJs&fT7H^6B8p7c8o@jJ_INvTy~AN_!VY*YDyRA(mpyA3;g5WyN8o_1578t=ZaBuF6VPzPBmRvs3bPfgrGnu)zBZr+;U|NWGTO}sHl)lO)by85-ODrp;S zXqU&eTZp!m!Q_*{qfy+uDcqQ4$8>}Jg5N_p77Y|M_&slrfomi)Hb6F@Tv=xmZMcEi zh|r3wjcvW*q6w0X$hPA=dWx|tzJiK9?Mh)F(JiNYnVsV8*PbUc?3pA_G_bfw9J2e6y!;wLMNjt!m^cag)UJBI^PTVf@Ao`s#0b|;IFZMgIZ#tiJem6e1^a&% zX-|aBj&F~RTv0ubi9}0|`!np1Bl}gI`DNX0KK+gpEzup0;d(^7kTCju@byiwOw*dK zHP9Hg&cD!ce4m3v(N3cWI>Yno-rF^#h1#s_hRTMY{a9S2?}8?w*XY$c{6p_8&~vxf z-<^N=ho~71N*ftFsI&|z%pOzRm#*Jp?7q0E_XsW#!7_cMnLeu+N+m+apM8(@)bV8` z+k--W?L`Ha^uq%4Sq!${L8GPek*FsWaL6C6P*AYdo|VS5Iy*n#2vcrXxSX6nh!zx> zbI|S0;_51C@ftO$Wh0DKE_<5!*tVdCu2s&W9c;{Y#3M0XppEA#S~+#$u*7hPFS;oN zuU@`7>RN~EseKF+A$)dV-NB{~AV*2ip$5Apn8sxcKW&N6Fh~o!Mm`$lo0NZjc-83N zhlTR+?MbNd3qR@G)_bb@;SYakpZXVK*kITR{kQGDC$~y~V!u|R2Amb)9TXWV^xDgp zNK<@=lx)4Q;g5g#y62qUAf<}=O**Cj=k#{yPXvwtxMQ=ia`(#N&^) a{Ee56t-Qj$M!tn*nEwFm;`tL8F8}}}zgy%0 literal 3354 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 k=i4Z9pF diff --git a/data/index.html.gz b/data/index.html.gz index f499ec76b1f1fda594de53244581d6459eba9fa4..1784c21c7ffe554c2deeea0f7108db7b3b542e7d 100644 GIT binary patch literal 2796 zcmVk;dxz*H7%Z#csA@@7*xO zb!Cw&1EQ7v$7tmTpE5AR!rK{VL9H=Je&hP`#&ym*)I)Wcz**bjRPrgzV>piNxsq!) zZ!U-Zm?mF~o$?`9M!|cHf?Qv>BBIBQSyP&&kFG-~ zeBa@CxY&nPBYCs3F?YN%zX`%66$5Zk6saSoOLSxKXm+~GBLBGM#5JoZYt%g5m4=n; zDf&kx>@;s0v^`Jdc=c|W%UP`NXS~NFN7o*EV$$HKqpI;-odDwHz-B*A@;30SvTgP;VLlMQe=6ku)|h%#d~hE&sjm&Ew-Cjc6%I z);KuFyKmB>#W!23R7j%)7#S@QPsFE0NvqBTnMQoP=VTO1lc%CE`J;-;Qwy#xa*O%* z%^4O=&7xcIJOwWwC@1w==?u#wS4*$*FGU9(6)rb>3f6KzY1x6Q@{XINtx8k`j@oRddRf;2kb*FMM*jp+qNAtaOayuMP{@^nrDIXo zPPl#w1wJjZC)7BR3zgJ$lPc)iL^sM!SvfqdlKHmdnP{SxWw=Q4h|>^ZTrLjs)RpaE z{_i{~LE0-yQ-X6I{5(u+rwdBx7L(6)%@`=d0gP@dW57_mjP6Dm-5t&7Q~sjNE}KuI zY(5>$hT7mRpD&|)zI>lL;-iep+^5xYT&?BN{5~2OPE;0oR9nK;C_4lgGWE3wWEu1h6Hmo&wt(D--$PWI-mV@_Wswu zd3YnhIA?@7z6h*!ara@!&&wp0k&chg$#n7gPCP4f0EbEg-0|6=P5MO|g-kpN@&dsr-Rw?^c~Dt|GE7y_8V~|{zIZy z>$0QrcSq$|BC#}29gPni4bs-Fw<`%eY4$j546!OIHFRYfevnLa;y^Z=W>?pEIjmAR zizow=%KE(DoMFeJ4B>fNbB%qOo3vYnwv^G6iiyeYNVK90oMK#Aq)w0fxzmD4yK#js zaVY4+vP=VuHQIfz)ebNi%~NX~#|qAN6sy8>vcjS&OQ4aOy>pXY=Py7<;npek5d5WO zCOQw0dd(EpiEu(Ph&OKp1Mc{;={X~`c=Bx|=&Q@58g_}!R9%zz8Un&yG5bu};k4K{ zl=p?Ffn~e0cgF*v7Y9Ga`OL%lm!zgC88seS6>iP1>YgSDp*fcy3XJm+3 zL(@q%TYK13iPrPU^XsSOd`tJz5`n>##67mF64Pj5f_@E^bl4 zU+Q{KA&J&9bFW}JZePQgxrfmCUP887q`Ewq&}=WEM+=K(l{w3g(ZE>ldL}qdBF4nE z)6>nSk>Qj-?leB?)9cqZx~4Q9dYRb;(ON}Mi~KA3S-@XELhXw#qcvAeYBM@3suH~q zL5Uv}W7Ma=o^~4&vt!Z^Ao}>oJLbrs9ADj=dTHSK`|sVs`Cu2|eK0&a2dx}I6>q0^ zp53}AIV6ZWhf#ZV_8poi5YXjYWq3*(suViBe)HjB(Cy*rOgm>ka3SBDp;4aMG8p!) z&Yt5i1+<_3mhvmatBxX1e4{`&*e^G#ZU}~L-)}e4_;Hp%{WxU(jD^N>>Vxd44WgXkQ4?+X=<_H>w^Ikn=D?k!E!cC+|+@-w_#jy91F%t6nP(NV_eA&iL%rCe zyXDO=ugW7lIz(La4K-_x184CdbBMKI9u zP}T6i8mx(X_f1u~&haDVKkt7Fgmn3R6HGT7*}hPzETW>DR!ONLFv=;ShOsQ=u!9jr z)65{;!;6wwzXYR=3lfp*Il^{^@zJtJ(6ak!oseAUY}&p~8sK`(6?S#qr_Uim*=ZU{8^ajvY$@>1BU!2jy2_e#d@m>bv zEmU8bMC3Vs{RR^7&iBs4vKJ3X2%mL55)s>R>Isp4KEF+MhTQRSgKwVi>KfJv4v$`k z7$V89h=dPDf;A$+V;_<&Re@OU$!NGiJbd~Qu`5SvohLDVWbSPct+(}K@A#cZ?X|*> zyjSA#m*VRXar7<99gV$45edfNXeI<+>zLq1(|`txS0nzK;0`Ylo)Gy#YWu$JUFQXM zM2`@}Z~MR1%{Bi&n#uQ7CyI05=RranH)|my)y^gBSz`=7?=xD@Mk!(POQR&y;{A?! zF;6JZZ-Uw7r%4`5yPiaDK%P8WB*)M@z$#r-dJ719u!pQ( zyhbdQPs(fog(1p_B!x^Nrc7zYqkNW(npY-LRv_4r9{!HZUQMS_oK0jJIm}6vrzptu zV#-IzuK%I8quohyBkf4nGjL2QYN{KmFX=fsm=})#}oCoP}`{iKNEAm4}&ND-0}Zxg~rncDF6U${bq6i literal 2822 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 Y)XiaE|M}9eum2kF{{i6GD5WO=0IoP|hyVZp diff --git a/data/js/embui.js.gz b/data/js/embui.js.gz index 78fe95e1009819fc2d4e3750f429a89633f96304..1b6c1f82a91388dfa05c3fb07dd78625fc752a24 100644 GIT binary patch literal 13120 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 a%IbvH$=X+oGQU literal 12403 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 Svlw8v+xis<2LVnwFaQ7;-R?C2 literal 2927 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 literal 1055 zcmV+)1mOE0iwFn?adc$@17&k?Wo<5XX)bDWZ*BnPSy^w}I1ql%uNY{7MFP~ZC2jK% zq*)YAP~;(7BL|vn5g4>Imf4j^fuvF!MgRMflBG!2k;9JbqAy8A4rjhOIh=go0kG6O zM(YqP$`pzT@z8o>mhVwW%9-AA(r+K7Ck)-Gj8D(u`V9E^D|$+1O{mqRV*vbq})YFH?dV6iq1x%Z!5 z{IXbpLg=z!Eozhyp$dLgiP!HhuHfVF?9-Celxj+SAD3mf$X~`#Gl0f03S!DDA~G

K`{AAVm1BXjt#}-yZ%@q+cD3{OI~zNDldW4h6rC+B3eLKU zg7YgC1!q%5!TBwVfJf4I;j1e;U z=mTZ&RdNOsE+V4#kH+HoBQjlkJ-EI&GqcD56{snu3=ZLl98Z;s5BvQuD7Auj{Y7f; z8#aF{N&R;>e@b0n-re3F^~tf}_<4!~;RdLLt4p9h?Akqd&+OdZLJwG_Mi`j_J*4-T zyP=4Y2IXtM&pq!pnd5|iV<9I#n7PYh?DB_QK3$3f#0ua1HDEQUY;OpvvpQ5~Lr~q- Zp}Gas^!J_lVKQ*%$N%6T9{t!G004>004o3h diff --git a/data/js/lodash.custom.js.gz b/data/js/lodash.custom.js.gz index 342fda6e626926575c727e49e13a7e20f6cd2e49..3f3acec4ce6dfe72442ef32961f1d75dc073d39b 100644 GIT binary patch literal 4832 zcmV<65+Cg!iwFP!000021HC$VU)#E}|MMw`e>|Slh>)E9IH-Ot-RNzhd+)u$%`uJw zZjGGEaw#SG?r%o2B832Ne`g8ONHZGErqNit_wH?M-rM}QvdD$31-4&QldL}5B>PRR ztMko=O;JwhW@(_kygJ`)=Wfn7-^+_8%X< zeGH^6t$6dV(k@ZRylf_dN10})LbJ21X6Hhk3j6!9&}Xa$cq8FA`U@!RZVbt^k-07^ zN$8bpZfc^LV3Jb)grT8JZB{v>VGM$abT#5^`S!zZ-R_@G-NY455`TP~ZwE#l5w~6c+ zm;gK8)O0?dwzEBc=I{ePPpq-v2oQNi6qElsVix5Jg@NQGU&=@B>1sTVI2YU7TRPdYu5^`Ga$1}=Dmy7ftW_hJkj=RW`rop$ z5wT?54xZ%N`pf$;v0D^Xbp(w#Ios2SGu@YCC^1NM*o$;(~!V2ZybV#$&S+`7i zJ}Og-u8@aO)lP#EI^1xsT=|WiS%nBbA%bZ-^qT22#f4i14Z6cMaJ{F7RKrfb6ETwA zZc5Xk(-Mqwt`Z$T_w;9Iv#`t7Bb-Jxnqm@-s*}$m*Cqx}3n?aP6fc2?fRaWiuP3nZ z6OC4NWQKsIb2o2^Pc&r`7<;0@kJROrY4PX7)@)I)d4_Qn7+N5(;@=!H9V#$ikysoV zPeduS*fd?rG_Mv$raHuMJMnuR2eI%W(clrwy*yB-V0cp3X~a3o#A98qVZ9*Etf15B zNIgP~)T5k))~Gwi>|bh7pqoJomKm|XCk2HNV+|4`L_>{0N?w6It+-B>JO{JSQdY0S z1;WpG**g`Q)D(4cMlrBt?97_0QO69lugH|uEbB%QLnccaKR46)3{`H`Hs&9Y47=o= z@%Hvd(>^ur&gJ&@SJR*I8u7;S`Q?0mN}&ljQ%YxiY7HD9?+}0%JLBJ~VzSw1V|0{D zK6Q>Vo6lkJfl}Eexuoci4JR>yyFz0KAm`@$y)@lr<7?R6-@lr2AX!?n)3*@UC7gD8FPl# zh&1&Fkl^2#M4M4JoY|~uEk~i@n8;Vb*~Eo8qCglv3RJ+%!Z8UeB=0r+=VC=1MKKW^ zIc8Dc9#ex|&H2vXl4z$N#Uj|q#O~Pf>bW;z*)3?s5^2>H6Up7aVLKfX!7+CtRMg>M z6H$G6eo~cf=cKAikxAQIhV>3I4JJhX-qWH6+R*O2_H8>6*bbwxpp>!=nT&KAKhoq7 zN%(zy4&vIWPk4PQ!73Dd!KD*-!v}jsB1gr}AO-8l`A??%6LuQ@^PbZSaA7yw@W7xG zQta%orj0j>=de1*Ty|(rQOszECM4$V=}|hNWkba~UTU0|$pOj1k1j~Ya_5k-Gbk9} z-p)`ExygQVv~5ML{VS`I1zH#$D!};t0vw=$oen4y+zAhDs@f}tl;pr$l>#Y%vYb_{ zS=yCY1QI>Bmf;d7m-#W}_90p{;yHyhz;6pW&KPXR83Xi^z-947iLF0yK|uwMzIUp` zHR9+Du0c^6%iei58*X@Z9|Y`r|1z;a_@se%FnlQrK_2Yz)YzB85VjA%orq&L> z0Dv-?s4%}cvR3AO0>Y&I0}A~D6!E{&7o7%jyzq>9zgbGWWmF`8_WQpN{`~W&Uw`@i zm;RrB4r0MN9dLobP9Wo3@h+jA%fJqH279o0pL6{$17KoLcn*H}`vUS8#^_;?ArUWk zZ|AT|&nOB9=kr;ZOS~l}Em%rK@>klX{fSHEb_TIzed<#M9}Fv=xip2@{1(wGhiK69 zHY7I7+%|`#mRFDqLT&kJlO2gcqLgx5o7O z{LNZyv;nrIeRG)gq`w4tX@^4_Q^uCuS7bqkasZrSSiq0^!WQs4@k!Uwk^dL--iY5i-kkE*U;(8UA zwMe`$c}K-CDij!5ojBYCV4pnh?PD8e0HT-9D=VGAi(m>!2pvtppTGtVg4og8!S&>O zesb@lNNxnLxBj_u29$@T8rL7Xdw=FX2yx6e`n|x^2RP`cme%4Zc#< z_e%tZ?7Rrr^)BS1*YyB(J>1>r+LLR0d6!xk5)BuS zUa%bQ!g-r;V=1CZDN_kVK-RFF##!5Nfi7^V!(dM?R6$1jZH40x_D_qaDd9+4y-eHtC7>Ag7_MgN*o0Fb0}JfNdb z$H(dX__#;!CD1)iciG?h?x*96dt1BgAAEQ8sfT!7ON8ts$CEBJj(hNzCe)E@>HV}q zk3t_hB;hcOd;lftoAWJ7cTd?rfyZNVd`uJQXpM_I=kIlseXpA16j%~C0ciB+qruLf z>G9+q1*p{E>_e$X{uC*zO#l3ovEMXG`P$fTzuY)>AoU-&4Gey}ZRD1P-WNda_L2QE zJD-WktrPruYeT_;rcApLPVlv~YnTpa9r)0=+x4M`_V#lf{3z4eheBN302kL`CVz6k z^&@^2{xX|g2OXNT*h9lJ?K8E9N zl9m1h-rlwl_qS*H;+)%tgR^2uq88;eB!GSUyN--d)@WX7$l>6BCpHqfFaT*`L`O8e7L2ceaP{DqDa1#U|c~qZjiv<&HclD-wIb5>C{u+;W`<_eWq6z0ORx2 z{f#gr;}i`%6U_xug$QIHON`I(r*R+q@Z&&R5u~xpM+MU~LA3b!>e_(&UsxL{;MeFC zx~l~1a5KIlcr(eRWV)dwp<**&5|-loSY9yKn>%(J+alM3RsRL$kJ43zS9d7RKdtN*GxnF1EV4>$e$Dk0sj-(h>h8Uclr(pZiZ^2nX$in}xy=!vZ=@d|Ar z#)zO8l*zG0-f6t1GF#EKV;hTbg}WX4+?LHWZD+yYjs9EQrYld(lY$8V_efqNcObz8 zk94;-Jl9NdX;}5OJ-{(jnRm8!4(AG8t<^zurw-O1#jN^I^FQz(ag(gMXnn)ezC+e4 zlv{ZZH$1AB_}!>WsW<=SkcMUNu%K1B%C20Q(?J7;ir{_a3Zg9^Ds+)R4&AQRmH`bV zUspvz6kXh*Zypd>m_0`s5O6jwy}5?RCDK8DTC&hkx;8tJ5op?`&nQYJQ*rui}H>c8J=M)F^=~w$a}M z@k8g2et!=nD4dX&Rd%SS+d_}<)Lr5HIa^M{6md%@tJ-g;oA*$W%Xgyx;9(TXz`cz6 z3KG2jIBF3;rR>qF1T@F55;th-*M6V#)8I zqQFgNznX{#n)E5qwH6sgKzNzm|0CeXJ(g)Fs-#*@`zL1e5sl@14(>#m0M6QP3%oEq z9%?=&fkLlq2rPmoJR&wi1% z2z=5uVEp;|I8!*oH9_98mudb~Giht_nbiC%bf#sdwUGP?wDBr=Ao)jVo{RGegT-Uh zR;PkLL)7JaTmBrnYq5~Y@Pps>wk?{jJ$zFrn92*L;FW#pH?)||_I9_c_Nrw7!G~}v zrL5!Kj?jWVfD20?BHJ_1&<*cNvF?b7KPKC|r z>Q$K)vaO*!0!Slo3HHO_wVgEOGygzKaSeNuX3vV6AWwd}U%B}1o;LPfeH?x=UWG;P z{`~^>QShDOeg;Q!)-4Rf(8=Nk^122_n=a2NAQtx^@rK?euwi0YCVgH~;dY_jpr|l6k1O`yC&3q2Tz)TX! zo0vFvlOQYUR^5=i(<$(4WMQ76G*gJr_OE~{%aa_5G@0+guV8LIQPK)+F=osn`4Inh#mj+@hBm50? z!on8+ic6JQt@##uux?JCm;?D4^xW~q525qn@td~?M~6K6r8oG!_iN<7+u6I;wU2k< zP%k^5gq1%JO-Q4AV=h@c^M;5o7nH`CP6!DKfsz`m+#u7~5?N5IeoSQO@ZSNv+=kPk GFaQ8GzkEpm literal 4830 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 diff --git a/data/js/ui_embui.i18n.json.gz b/data/js/ui_embui.i18n.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..b5309da331b74fca7df5e1d25b28460e36a4380f GIT binary patch literal 2193 zcmV;C2yXWuiwFP!000021Jqe3bkV>^$Z07j%o$Kc-qjB;cQpZsS>%u;V% zkabc`yf^kC2!mMRnp7hk-N|zaSy(!G`OI1w;TQh)LI?ro6{rQ(QV@JGj<2ww05%Lb z3_ASy@RFWA!4Nnq7FStom&StTC#z|eEf=3huOd9XBK^H5dzuXSFksAA%4s7t!S30N z2^b4P5n5mamK?Ev(>#Th;?dhRW?0iJFrNlo3rhM52FvDDNzxpf=RWAE(V9dXh8926 z?%QM0!ug~pz*?bmjahjtRNvwE&C>UP)PkbQc(gj>70hnXq`oDv6N zsUe`B73`kO}5+Ba84d{mW>I*o#H;df2*oTj$=`;}rW3VpvuZLxJ4GZ5gKHU@erTLj&UQQQ_ zo89iXD2|=`J`FPW@PRO<$_P|3HU6a&*fq~0Xd@?jEh4fn>UZn$1}JftO?c%^@CqhISQwIs8LmvAb^iDfBGoFyROMv znNvT4srsNQJx7AnOtwZf0Y_q%*8M=nj`&{l-ag#7G&T7Ugd2>jgvK^4jXBUJzAQdXCMpQrr_S& zQhDg}yM$QTyB(EI+Z$(ha3GA{$Zcc+eWkTA{Ly!qy>CVaN;ke{O>0|Sc!kKD_y~67lUCdHx(qd%9$c6yP>C`Xk;+gW zVJG_Z0NUS{&DfyeM1pw(-OB9`t#!BW;U9xK&=QLKB#;9AW;>-mF%V^EFR*LOcM zcw1hP%k0r%1TbaNSQ+e|Pxd$4tF~(r<*1&FcmmB-GkAj{y7>wrNK+bS4 zo-J-q2T{V{CY?p*SiYa`~`oXd5%Fv*p?!Ot|{Wb+EXwc5waRTkr_9xHj@>21l^ zwxT%TE1e?sFnNVqZw`0*ypNV=LlD<6Vi_i^kyo%<6UUX8ufRj?M@pxEj+M&s0W1TC z-fG+r&*sW5m$*oRBRY!e$1SCgpqgVRlH(WJ@pf;ys8)wEZKSs-yvsB}LN3`6ouc{d zL`m?vl{GKI2?d$sZJQBIQs?e2MkooOgS|E2`u+>*KCe$_z zY$&C6#!d^w@CHrFUH)f7=WuyXWC~zmQ5m$jiAK#*#hs|Oy`!LlGQ&xOv(7Z(iONH$V8RK9@j6!6OP=5O)UNk2`2 zF9HcJ*Nt)f$e;13#eg1iUJhLhC_&@Ql=5mRmN589gE$QOy9r1}fBeol_~V Tj6Ae)*NcAvcvy#=P(1(ue;72n literal 0 HcmV?d00001 diff --git a/data/js/ui_embui.json.gz b/data/js/ui_embui.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..725e61b652e05f42d9992f8d04b71b6c6127704b GIT binary patch literal 2058 zcmV+l2=(_LiwFP!000021MON@bK^J?exF~_r3;&EYjg0^2F=vw^_{$z?^F;8Nf?s= zj{q%er#9!D!^`FLI&t%Q{{s1suA4L_NWj=4rJ5W~5bXXM{dJ=oiqkm9VvasTN@Kyzb+9BN=Pgh0uG7-lImI3Mt?>L2BS zfqtZdEnP{o546@vB@Re##wy*i*X5Le1fY&g^b$7YJv(Q`PKj8bM>yfv;)sB*6?yh{ z%mhAe(u|)SA`GbH_j}g>R^60J#Mjdk-5V)wB#XHfdZFG9-xdLfhjG%AGm}Ol4EdCP z2L=a@DJ%{Q7Kf00R)JfITam?TVzMVtam=NeQZ!|>wH9uSJ5P2?;|X2+fsNH}_mR#` zt(k|PT{##E5uRsl4{4+=6dzov_W^tCSqGe{kv|^tgS9|CAW69snvh9G&^)ky$wNL6 zl7khr0xE?d;w%NDAmy4H`T=LLESeQ$W7O~+)0!W?o81mG`k66G`qba%wXe&z9YO;Y zDaskclyf-+`;f~YWX%3?D$O|E>W#%fSe1_K@K7YUG>eQ-yGA-;0Z;wDen?5lgVLQP z5}U>5ciTA7T7-2uS1P9nT{N4^#)6nj3&$ThUKeMi=VV^ zXKk#K7DO#OkZT%?!h`hX?LvK=o;NmIXfgl7GSLsg})#E5N<2{bhjn)w%1{+!_LDy z!q#@y2y1{v(+Zj4M#P+&aBoLU6I4QjdbSbIKgo|5FXhLf4f#jK3;CJ+`{I4N_#ckH znD6E%^W!*>e}zB(kRM-2s~%)&&mt#*{%H3J_S28#lFa=#IPc*AiD(nH^%|v6XhiMq z?Dm=QK_>SW4N7-f;hxCg2KuQQYo6Yqf#%i^1V^%QXxu~{zf+Qf9*Kczhinpw;mCky z+ZYFsW)auGk|Yv51eO^UGa~{PMUxef3M9v2qkx4+Gi^W^h?$fbizaKWIV$a)NU^Y0 zmocETZ?oiA8<>y`^zdW9gF-+;V^9a;R9-PZ@10s09)_)Yn`j9 zqKW@(jHjBhbtO%iEo7ef#pY(+>cm=2l6zdTc_ZhrEOQ1ya2{KFv&H^7fgtI0yJ-@3 zT1rO?b@OCtYn3JzdOB&bB`Td8+ct|bBHz9D-r<8Jv$)UGeas@=i^=Gb8Vb1--%*Zl zPz^?iN@TdJ>%mx^~F|B-sH;+6dC{M+Ki zDwAMawwwP*Bh-ei!3{qZ;SGR~9mhE@D;hVj*wCm8&B{?8-=%TScCkpBognPEZ)KY&zz^Bc$r|nOaplN z4HU1_52W>NKr>!Mth@Pl#XG3GY#UvM(VSI8`F%O{b<{&E@Lmn7opoQ`Zts`~pIr!( zi1kZ}aYN?SjijeAGp8cl+m%XDkfeMdc9s(KTdZcbzT?(LxjIXzScCG@2b8!1=$5r6 zZWW=s*tizUxLa$NaRZa=Q^nDYnIi58c3t^D@-vt-PSU?k3%bGlT~Pi}{w=1a_lno| zQuC3s&1l1-&c8Wa-xz!wgKJgYe#VL@o!8goT3e#I)8QQYlW{mV*Zzi!@j|sYG{gUL zbKbOtfEJRqFc^1>R}tfJ%gFjUY^l9v{!#IE@hUFm>HSiZo@m2kcXpZ(nAs)xny5`j z;}|t>=V!_oIZOXm^aQbn(hVHx)~G7%Vt619M8w^NjQ1wzEWl(5Ro%y z$~S6XT^KF(p;hC1u-x!-E?Hmr4UtZ)}@PrRAoB?{E~vQ_&hXLY}sL9Z0OIa i5wp74)jLbBP3-!fFLTa3;@Kd!E`mYj+@aD8pg90bjU6Na literal 0 HcmV?d00001 diff --git a/data/js/ui_sys.json.gz b/data/js/ui_sys.json.gz deleted file mode 100644 index deda8fd3d30d085459a990768aa986438bbf4c0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1857 zcmV-H2fp|piwFP!000021MOOEZ`4K*{+#@Z)u~c~beB;0g7PiR5fQ0@YZFwpRdw34 z_i{`8R(pMyYZNJgA}WDEMU~o5Af!tD8d4&lknk7mf7F?^edkx~yO5SjMTp#a*YnKI zJTvpyVRv?>CWaQPE!DUonBAoctJUFRpVoMoI7|p`Y&nD) zDp5?M%eP#c3fN9y)$H72iXa`$RJI*yE81UM+MS)5sToWROnW={hO3H@NHv$YExN_p z(T2KQ*Jy`nu=*8WN+#T*iy{v$Xzn8 zH?MC>Lh_iKBN~S4|exr7}2NiW>cW!5G4R( z(FuXLVYwn6v=HV?rm`*skVLoEiD?r>W7HqARhY8Z8OtKye)SP&vavkppR`%iTb+sN+wDmu7nXj54un*pD@d<^C3%MTv3?gG+HJM791I_RTj(TmNl4wD=pg;NXg|E z8F0}fQOd~t6{7U0?GT+C7p6+>IFQS&6oJp9fd`=?L8y@ZWIpOX6`MD;l5SRN+K4n^ z;~ocwTBe~5&k~q0=See#X^)4i)xev`x~a0nhA_cOk15+7bEh$n z^L9v=BbDqVq;QeLDR)QCUftp0G?gx)LlUf}Qo#fV*^`mykubtl(+c>Tl<3|s-l2cn zJ0y$5d+hIf$KG525q|jtt{-@(-VywKM7)>q@x(ivPQ)v&-5xnPI##*@iiejBA{xl% zI@K%Fj|F(sBEl|}O)@p^Tq&0>jW2XvBRnUO@lM$pQ4Mr%$2JEJv*#QlH&rKCs>()2 zH^Iy|Ca7zt$Gc)tr9+Kx^KJW}GPh-&TEDO4rN~kML9iI6VD8peLN)wtMTn2ea z)myG32p2@BhT&3exKK=Hbo~dwOp`d#f)XB*#P*CfEL22>u-d>{XBx^BwWpC|*Ed`6 zXlmm_a~U7jR2O+2aSYk7b>T;gvB>PgJf;v25Dl&B5Ug8YxvVxUvD&6RWA~>XOVVwW{?!|VbULV}>7JP-rUe?V9J5a~7t?*^ z%H<4{;p&~-he7F}v_DaA(#$PxoGp;pn2%YgHK z1JU*t1l@`E%D?SDAl^IFcm9vwEAKD=ASfM>ey83$yn<41fy{lv)j0yU9m1Y{@055) z#6N)B;WO+xCf;-Zj`sr0+(u1!7y$S`dnW|tA(>MH-L=-7tII103TE{oEG&ZY#kmFI zJwcPOcH}>RpT{u-{|>r&N3NfGuZe#TKOpLT0>?N2Ur)U^{sXB;QJUr)4EHp^%eQjt zJ;1>TJS-zaCL84hKkvymHW=+ zixYMo*TShxMjiU3vQQ<)sb|CSRQeAO>5G+zbQkp8duDVul6xads~}FjXHYf! zP)6|Y#N)bKRY7`>p>e$NA3`MteQ0&0yeAsjB_i_wc9+-g@~8-zgE?zg%N`~xIoHd? z{s<%XX1IhQPvK zEpm7U5lqji^I_(#&NE~Wk_~8a;@?LmpWq9fka(|w+^?p4mlHJTY!07z&jA8i`X(qs z#C+;KIiFuwc)vof{`B{O_b=frhe1)C`ac2g5gu+4TzM`t3S~=HH_8TE@>pXJx?E$~ zQJK0*^Mh05Z`hw=!GWFdV2OqYAEBead(X0tIui|Le0)~ATZ(2+H~3cMFO0!T_@5Ut v^=UJ;`~lu(9n%!kc>w7;mhVSjCYe{+lYdkw6nBzY_`CNvr>p(rL?QqHMazGj diff --git a/espem/espem.cpp b/espem/espem.cpp index ca73bea..759e04e 100644 --- a/espem/espem.cpp +++ b/espem/espem.cpp @@ -2,26 +2,23 @@ * A code for ESP32 based boards to interface with PeaceFair PZEM PowerMeters * It can poll/collect PowerMeter data and provide it for futher processing in text/json format * - * (c) Emil Muratov 2018-2022 https://github.com/vortigont/espem + * (c) Emil Muratov 2018-2024 https://github.com/vortigont/espem * */ #include "espem.h" #include "EmbUI.h" // EmbUI framework +#include "log.h" + -#define MAX_FREE_MEM_BLK ESP.getMaxAllocHeap() -#define PUB_JSSIZE 1024 // sprintf template for json sampling data #define JSON_SMPL_LEN 85 // {"t":1615496537000,"U":229.50,"I":1.47,"P":1216,"W":5811338,"hz":50.0,"pF":0.64}, -static const char PGsmpljsontpl[] PROGMEM = "{\"t\":%u000,\"U\":%.2f,\"I\":%.2f,\"P\":%.0f,\"W\":%.0f,\"hz\":%.1f,\"pF\":%.2f},"; -static const char PGdatajsontpl[] PROGMEM = "{\"age\":%llu,\"U\":%.1f,\"I\":%.2f,\"P\":%.0f,\"W\":%.0f,\"hz\":%.1f,\"pF\":%.2f}"; +static constexpr const char* PGsmpljsontpl = "{\"t\":%u000,\"U\":%.2f,\"I\":%.2f,\"P\":%.0f,\"W\":%.0f,\"hz\":%.1f,\"pF\":%.2f},"; +static constexpr const char* PGdatajsontpl = "{\"age\":%llu,\"U\":%.1f,\"I\":%.2f,\"P\":%.0f,\"W\":%.0f,\"hz\":%.1f,\"pF\":%.2f}"; // HTTP responce messages -static const char PGsmpld[] = "Metrics collector disabled"; -static const char PGdre[] = "Data read error"; -static const char PGacao[] = "Access-Control-Allow-Origin"; -static const char* PGmimetxt = "text/plain"; -//static const char* PGmimehtml = "text/html; charset=utf-8"; +static constexpr const char* PGsmpld = "Metrics collector disabled"; +static constexpr const char* PGdre = "Data read error"; using namespace pzmbus; // use general pzem abstractions @@ -37,7 +34,7 @@ class FrameSendMQTTRaw : public FrameSendMQTT { }; bool Espem::begin(const uart_port_t p, int rx, int tx){ - LOG(printf, "espem.begin: port: %d, rx_pin: %d, tx_pin:%d\n", p, rx, tx); + LOGI(C_espem, printf, "espem.begin: port: %d, rx_pin: %d, tx_pin:%d\n", p, rx, tx); // let's make our begin idempotent ) if (qport){ @@ -74,9 +71,9 @@ bool Espem::begin(const uart_port_t p, int rx, int tx){ if (pz->autopoll(true)){ t_uiupdater.restartDelayed(); - LOG(println, "Autopolling enabled"); + LOGI(C_espem, println, "Autopolling enabled"); } else { - LOG(println, "Sorry, can't autopoll somehow :("); + LOGE(C_espem, println, "Error on enabling autopolling!"); } embui.server.on(PSTR("/getdata"), HTTP_GET, [this](AsyncWebServerRequest *request){ @@ -123,12 +120,12 @@ String& Espem::mktxtdata ( String& txtdata) { // compat method for v 1.x cacti scripts void Espem::wpmdata(AsyncWebServerRequest *request) { if ( !ds.getTSsize(1) ) { - request->send(503, PGmimetxt, PGdre ); + request->send(503, asyncsrv::T_text_plain, PGdre ); return; } String data; - request->send(200, PGmimetxt, mktxtdata(data) ); + request->send(200, asyncsrv::T_text_plain, mktxtdata(data) ); } @@ -147,7 +144,7 @@ void Espem::wdatareply(AsyncWebServerRequest *request){ m->asFloat(meter_t::frq), m->asFloat(meter_t::pf) ); - request->send(200, PGmimejson, buffer ); + request->send(200, asyncsrv::T_application_json, buffer ); } @@ -162,7 +159,7 @@ void DataStorage::wsamples(AsyncWebServerRequest *request) { // check if there is any sampled data if ( !getTSsize(id) ) { - request->send(503, PGmimejson, "[]"); + request->send(503, asyncsrv::T_application_json, "[]"); return; } @@ -180,7 +177,7 @@ void DataStorage::wsamples(AsyncWebServerRequest *request) { const auto ts = getTS(id); if (!ts) - request->send(503, PGmimejson, "[]"); + request->send(503, asyncsrv::T_application_json, "[]"); auto iter = ts->cbegin(); // get const iterator @@ -188,9 +185,9 @@ void DataStorage::wsamples(AsyncWebServerRequest *request) { if (cnt > 0 && cnt < ts->getSize()) iter += ts->getSize() - cnt; // offset iterator to the last cnt elements - LOG(printf, "TimeSeries buffer has %d items, scntr: %d\n", ts->getSize(), cnt); + LOGV(C_espem, printf, "TimeSeries buffer has %d items, scntr: %d\n", ts->getSize(), cnt); - AsyncWebServerResponse* response = request->beginChunkedResponse(PGmimejson, + AsyncWebServerResponse* response = request->beginChunkedResponse(asyncsrv::T_application_json, [this, iter, ts](uint8_t* buffer, size_t buffsize, size_t index) mutable -> size_t { // If provided bufer is not large enough to fit 1 sample chunk, than I'm just sending @@ -224,18 +221,18 @@ void DataStorage::wsamples(AsyncWebServerRequest *request) { m.asFloat(meter_t::pf) ); } else { - LOG(println, "SMLP pointer is null"); + LOGW(C_espem, println, "SMLP pointer is null"); } if (++iter == ts->cend()) buffer[len-1] = 0x5d; // ASCII ']' implaced over last comma } - LOG(printf, "Sending timeseries JSON, buffer %d/%d, items left: %d\n", len, buffsize, ts->cend() - iter); + LOGV(C_espem, printf, "Sending timeseries JSON, buffer %d/%d, items left: %d\n", len, buffsize, ts->cend() - iter); return len; }); - response->addHeader(PGacao, "*"); // CORS header + response->addHeader(asyncsrv::T_CORS_ACAO, "*"); // CORS header request->send(response); } @@ -246,20 +243,19 @@ void Espem::wspublish(){ const auto m = pz->getMetricsPZ004(); - DynamicJsonDocument doc(PUB_JSSIZE); - JsonObject obj = doc.to(); - doc["stale"] = pz->getState()->dataStale(); - doc["age"] = pz->getState()->dataAge(); - doc["U"] = m->voltage; - doc["I"] = m->current; - doc["P"] = m->power; - doc["W"] = m->energy + ds.getEnergyOffset(); - doc["Pf"] = m->pf; - doc["freq"] = m->freq; - - Interface interf(&embui.feeders, 128); - interf.json_frame(C_espem); - interf.jobject(doc, true); + Interface interf(&embui.feeders); + interf.json_frame(C_espem, C_sample); + + JsonObject obj = interf.json_object_create(); + obj["stale"] = pz->getState()->dataStale(); + obj["age"] = pz->getState()->dataAge(); + obj["U"] = m->voltage; + obj["I"] = m->current; + obj["P"] = m->power; + obj["W"] = m->energy + ds.getEnergyOffset(); + obj["Pf"] = m->pf; + obj["freq"] = m->freq; + interf.json_frame_flush(); } @@ -285,30 +281,30 @@ void DataStorage::reset(){ tsids.clear(); uint8_t a; - a = addTS(embui.paramVariant(V_TS_T1_CNT), time(nullptr), embui.paramVariant(V_TS_T1_INT), "Tier 1", 1); + a = addTS(embui.getConfig()[V_TS_T1_CNT] | TS_T1_CNT, time(nullptr), embui.getConfig()[V_TS_T1_INT] | TS_T1_INTERVAL, "Tier 1", 1); tsids.push_back(a); - //LOG(printf, "Add TS: %d\n", a); + LOGD(C_espem, printf, "Add TS: %d\n", a); - a = addTS(embui.paramVariant(V_TS_T2_CNT), time(nullptr), embui.paramVariant(V_TS_T2_INT), "Tier 2", 2); + a = addTS(embui.getConfig()[V_TS_T2_CNT] | TS_T2_CNT, time(nullptr), embui.getConfig()[V_TS_T2_INT] | TS_T2_INTERVAL, "Tier 2", 2); tsids.push_back(a); - //LOG(printf, "Add TS: %d\n", a); + LOGD(C_espem, printf, "Add TS: %d\n", a); - a = addTS(embui.paramVariant(V_TS_T3_CNT), time(nullptr), embui.paramVariant(V_TS_T3_INT), "Tier 3", 3); + a = addTS(embui.getConfig()[V_TS_T3_CNT] | TS_T3_CNT, time(nullptr), embui.getConfig()[V_TS_T3_INT] | TS_T3_INTERVAL, "Tier 3", 3); tsids.push_back(a); - //LOG(printf, "Add TS: %d\n", a); + LOG(printf, "Add TS: %d\n", a); - LOG(println, "Setup TimeSeries DB:"); + LOGI(C_espem, println, "Setup TimeSeries DB:"); LOG_CALL( for ( auto i : tsids ){ auto t = getTS(i); if (t){ - LOG(printf, "%s: size:%d, interval:%u, mem:%u\n", t->getDescr(), t->capacity, t->getInterval(), t->capacity * sizeof(pz004::metrics)); + LOGI(C_espem, printf, "%s: size:%d, interval:%u, mem:%u\n", t->getDescr(), t->capacity, t->getInterval(), t->capacity * sizeof(pz004::metrics)); } } ) - LOG(printf, "SRAM: heap %u, free %u\n", ESP.getHeapSize(), ESP.getFreeHeap()); - LOG(printf, "SPI-RAM: size %u, free %u\n", ESP.getPsramSize(), ESP.getFreePsram()); + LOGI(C_espem, printf, "SRAM: heap %u, free %u\n", ESP.getHeapSize(), ESP.getFreeHeap()); + LOGI(C_espem, printf, "SPI-RAM: size %u, free %u\n", ESP.getPsramSize(), ESP.getFreePsram()); } mcstate_t Espem::set_collector_state(mcstate_t state){ @@ -351,7 +347,7 @@ mcstate_t Espem::set_collector_state(mcstate_t state){ void msgdebug(uint8_t id, const RX_msg* m){ - Serial.printf("\nCallback triggered for PZEM ID: %d\n", id); + LOGD("DBG", printf, "\nCallback triggered for PZEM ID: %d\n", id); /* It is also possible to work directly on a raw data from PZEM diff --git a/espem/interface.cpp b/espem/interface.cpp index 621909d..f909085 100644 --- a/espem/interface.cpp +++ b/espem/interface.cpp @@ -3,49 +3,46 @@ #include "interface.h" #include "ui_i18n.h" // localized GUI text-strings -// статический класс с готовыми формами для базовых системных натсроек +// EmbUI's basic interface #include "basicui.h" #define MAX_UI_UPDATE_RATE 30 +#define DS_ENTRY_SIZE 28 +// our espem object extern Espem *espem; -static const char* chart_css = "graphwide"; +static constexpr const char* WebUI = "WebUI"; +static constexpr const char* chart_css = "graphwide"; // variable that holds TS id which is currently displayed at Web UI unsigned power_chart_id{1}; // forward declarations -void ui_page_espem(Interface *interf, const JsonObject *data, const char* action); +void ui_page_espem(Interface *interf, JsonObjectConst data, const char* action); /** - * Headlile section - * this is an overriden weak method that builds our WebUI from the top + * Main ESPEM WebUI page * == * Головная секция - * переопределенный метод фреймфорка, который начинает строить корень нашего WebUI * */ -void ui_page_main(Interface *interf, const JsonObject *data, const char* action){ +void ui_page_main(Interface *interf, JsonObjectConst data, const char* action){ interf->json_frame_interface(); // application manifest - interf->json_section_manifest(C_DICT[lang][CD::ESPEM_H], embui.macid(), ESPEM_JSAPI_VERSION, FW_VERSION_STRING); // HEADLINE for WebUI - interf->json_section_end(); // json_section_manifest - - // load uidata objects - interf->json_section_uidata(); - interf->uidata_xload(C_espem_ui, "js/espem.ui.json", false, ESPEM_UI_VERSION); + interf->json_section_manifest(C_EspEM, embui.macid(), ESPEM_JSAPI_VERSION, FW_VERSION_STRING); // HEADLINE for WebUI interf->json_section_end(); - block_menu(interf); // Строим UI блок с меню выбора других секций + block_menu(interf); // Build UI Menu block interf->json_frame_flush(); // send frame if((WiFi.getMode() & WIFI_MODE_STA)){ // если контроллер не подключен к внешней AP, сразу открываем вкладку с настройками WiFi - ui_page_espem(interf, nullptr, action); // construct main page + ui_page_espem(interf, {}, action); // construct main page } else { - basicui::page_settings_netw(interf, nullptr, NULL); + // Open WiFi setup page + basicui::page_settings_netw(interf, {}, NULL); } } @@ -58,7 +55,7 @@ void block_menu(Interface *interf){ interf->json_section_menu(); // открываем секцию "меню" interf->option(A_ui_page_espem, C_DICT[lang][CD::ESPEM_DB]); // пункт меню "ESPEM Info" interf->option(A_ui_page_espem_setup, C_DICT[lang][CD::ESPEMSet]); // пункт меню "ESPEM Setup" - interf->option(A_ui_page_dataexport, "ESPEM DataExport"); // пункт меню "ESPEM Data Export" + interf->option(A_ui_page_dataexport, "DataExport"); // пункт меню "ESPEM Data Export" /** * добавляем в меню пункт - настройки, @@ -75,16 +72,14 @@ void block_menu(Interface *interf){ */ void ui_frame_mkchart(Interface *interf){ interf->json_frame_jscall(C_mkchart); - StaticJsonDocument<128> doc; - JsonObject params = doc.to(); // parameters for charts - params[P_id] = C_gsmini; - params[C_tier] = power_chart_id; - auto ts = espem->ds.getTS(power_chart_id); - // check if requested TimeSeries exist - if (ts) - params["interval"] = ts->getInterval(); - params[C_scnt] = embui.paramVariant(V_SMPLCNT).as(); // espem->ds.getTScap(power_chart_id); // samples counter - interf->jobject(params, true); + JsonObject params(interf->json_object_create()); + params[P_id] = C_smpchart; + params[C_tier] = power_chart_id; + auto ts = espem->ds.getTS(power_chart_id); + // check if requested TimeSeries exist + if (ts) + params["interval"] = ts->getInterval(); + params[C_scnt] = embui.getConfig()[V_SMPLCNT].as() | espem->ds.getTScap(power_chart_id); // espem->ds.getTScap(power_chart_id); // samples counter interf->json_frame_flush(); // flush frame } @@ -104,80 +99,96 @@ void ui_block_chart_ctrls(Interface *interf){ interf->json_section_end(); // end select drop-down // slider for the amount of metric samples to be plotted on a chart - interf->range(A_SMPLCNT, embui.paramVariant(V_SMPLCNT).as(), 0, (int)espem->ds.getTScap(power_chart_id), 10, C_DICT[lang][CD::MScale], true); + //espem->ds.getTS(i+1) + interf->range( + A_SMPLCNT, // id + embui.getConfig()[V_SMPLCNT].as() | 50, // value + 0, // min + espem->ds.getTScap(power_chart_id), // max + espem->ds.getTScap(power_chart_id)/100, // step + C_DICT[lang][CD::MScale], true // label + ); interf->json_section_end(); // end of line } /** - * This code builds UI section with dashboard + * @brief send values for power chart controls + * + * @param interf + */ +void ui_block_chart_ctrls_values(Interface *interf){ + interf->json_frame_value(); + interf->value(A_TS_TIER, power_chart_id); + interf->value(A_SMPLCNT, embui.getConfig()[V_SMPLCNT].as() | 50); + interf->json_frame_flush(); +} + +/** + * This code builds UI page with dashboard + * @note called from a ui_page_main() * */ -void ui_page_espem(Interface *interf, const JsonObject *data, const char* action){ +void ui_page_espem(Interface *interf, JsonObjectConst data, const char* action){ interf->json_frame_interface(); - interf->json_section_main(A_ui_page_espem, C_DICT[lang][CD::ESPEM_H]); - - interf->json_section_line(); // "Live controls" - interf->checkbox(A_EPOLLENA, (bool)espem->get_uirate(), "Live update", true); // Meter poller status - // UI update rate range slider - interf->range(A_UI_UPDRT, embui.paramVariant(V_UI_UPDRT).as(), 0, MAX_UI_UPDATE_RATE, 1, "UI update rate, sec", true); - interf->json_section_end(); // end of line - - // Plain values display - interf->json_section_line(); // "Live controls" - auto *m = espem->pz->getMetricsPZ004(); - // Widgets & left side menu - // id, type, value, label, param - interf->display("pwr", m->power/10 ); // Power - interf->display("cur", m->asFloat(pzmbus::meter_t::cur)); // Current - interf->display("enrg", m->energy/1000); // Energy - interf->json_section_end(); // end of line + interf->json_section_uidata(); + interf->uidata_pick("espem.ui.pages.main"); + interf->json_section_end(); - interf->json_section_line(); - // id, value, label, param - interf->jscall("gaugeV", C_mkgauge, C_DICT[lang][CD::Voltage], chart_css); // Voltage gauge - interf->jscall("gaugePF", C_mkgauge, C_DICT[lang][CD::PowerF], chart_css); // Power Factor - interf->json_section_end(); // end of line + //interf->spacer("Power chart spacer"); - interf->spacer("Power chart"); + // draw block with chart controls ui_block_chart_ctrls(interf); - // empty div placeholder for TimeSeries Power chart - interf->jscall(C_gsmini, P_EMPTY, P_EMPTY, chart_css); + // div placeholder for TimeSeries Power chart + //interf->jscall(C_gsmini, P_EMPTY, P_EMPTY, chart_css); + //interf->div(C_gsmini, P_html, P_EMPTY, P_EMPTY, chart_css); + interf->json_section_begin(C_minichart); + interf->div(C_smpchart, "chart", P_EMPTY, "Power Sampling", chart_css); + interf->spacer("Power Chart"); - interf->json_frame_flush(); // flush frame + // send values for controls + auto m = espem->pz->getMetricsPZ004(); + interf->json_frame_value(); + interf->value(A_EPOLLENA, (bool)espem->get_uirate()); + interf->value(A_UI_UPDRT, embui.getConfig()[V_UI_UPDRT].as() | DEFAULT_WS_UPD_RATE); // call js function to build power chart ui_frame_mkchart(interf); + } -// Create Additional buttons on "Settings" page -void user_settings_frame(Interface *interf, const JsonObject *data, const char* action){ + +/** + * @brief A callback function + * Creates Additional buttons on EmbUI's "Settings" page + */ +void block_user_settings(Interface *interf, JsonObjectConst data, const char* action){ interf->button(button_t::generic, A_ui_page_espem_setup, "ESPEM"); } /** - * ESPEM options setup + * ESPEM configuration options page * */ -void block_page_espemset(Interface *interf, const JsonObject *data, const char* action){ +void block_page_espemset(Interface *interf, JsonObjectConst data, const char* action){ interf->json_frame_interface(); interf->json_section_uidata(); - interf->uidata_pick("espem.ui.settings.cfg"); + interf->uidata_pick("espem.ui.pages.settings"); interf->json_frame_flush(); interf->json_frame_value(); - interf->value(V_UART, embui.paramVariant(V_UART).as()); // Uart port - interf->value(V_RX, embui.paramVariant(V_RX).as()); - interf->value(V_TX, embui.paramVariant(V_TX).as()); + interf->value(V_UART, embui.getConfig()[V_UART].as() | UART_NUM_1); // Uart port + interf->value(V_RX, embui.getConfig()[V_RX].as()); + interf->value(V_TX, embui.getConfig()[V_TX].as()); interf->value(V_EOFFSET, espem->ds.getEnergyOffset()); // TimeSeries capacity - interf->value(V_TS_T1_CNT, embui.paramVariant(V_TS_T1_CNT).as()); - interf->value(V_TS_T1_INT, embui.paramVariant(V_TS_T1_INT).as()); - interf->value(V_TS_T2_CNT, embui.paramVariant(V_TS_T2_CNT).as()); - interf->value(V_TS_T2_INT, embui.paramVariant(V_TS_T2_INT).as()); - interf->value(V_TS_T3_CNT, embui.paramVariant(V_TS_T3_CNT).as()); - interf->value(V_TS_T3_INT, embui.paramVariant(V_TS_T3_INT).as()); + interf->value(V_TS_T1_CNT, embui.getConfig()[V_TS_T1_CNT].as()); + interf->value(V_TS_T1_INT, embui.getConfig()[V_TS_T1_INT].as()); + interf->value(V_TS_T2_CNT, embui.getConfig()[V_TS_T2_CNT].as()); + interf->value(V_TS_T2_INT, embui.getConfig()[V_TS_T2_INT].as()); + interf->value(V_TS_T3_CNT, embui.getConfig()[V_TS_T3_CNT].as()); + interf->value(V_TS_T3_INT, embui.getConfig()[V_TS_T3_INT].as()); // collector state interf->value(A_ECOLLECTORSTATE, (uint8_t)espem->get_collector_state()); interf->json_frame_flush(); @@ -189,73 +200,22 @@ void block_page_espemset(Interface *interf, const JsonObject *data, const char* for ( unsigned i=1; i!=4; ++i ){ char buff[64]; char key[8]; - std::snprintf(buff, 64, "Used: %hu/%hu, %u kib", espem->ds.getTSsize(i), espem->ds.getTScap(i), espem->ds.getTScap(i) * 28 / 1024); // sizeof(pz004::metric) + std::snprintf(buff, 64, "Used: %hu/%hu, %u KiB", espem->ds.getTSsize(i), espem->ds.getTScap(i), espem->ds.getTScap(i) * DS_ENTRY_SIZE / 1024); // sizeof(pz004::metric) std::snprintf(key,8, "t%umem", i); interf->constant(std::string_view(key), std::string_view(buff)); // capacity and memory usage } interf->json_frame_flush(); - - -/* - // replacing page with a new one with settings - interf->json_section_main(A_ui_page_espem_setup, C_DICT[lang][CD::ESPEMSet]); - - interf->json_section_begin(A_SET_UART); - interf->json_section_line(); - interf->number_constrained(V_UART, embui.paramVariant(V_UART).as(), "Uart port", 1, 0, SOC_UART_NUM); - interf->number_constrained(V_RX, embui.paramVariant(V_RX).as(), "RX pin (-1 default)", 1, -1, NUM_OUPUT_PINS); - interf->number_constrained(V_TX, embui.paramVariant(V_TX).as(), "TX pin (-1 default)", 1, -1, NUM_OUPUT_PINS); - interf->json_section_end(); // end of line - - interf->button(button_t::submit, A_SET_UART, T_DICT[lang][TD::D_Apply]); - interf->json_section_end(); // end of "uart" - - // counter opts - interf->spacer("Energy counter options"); - interf->json_section_begin(A_SET_PZOPTS); - interf->number(V_EOFFSET, espem->ds.getEnergyOffset(), "Energy counter offset"); - interf->button(button_t::submit, A_SET_PZOPTS, T_DICT[lang][TD::D_Apply]); - interf->json_section_end(); // end of "energy" - - - - interf->spacer("Metrics collector options"); - String _msg("Metrics pool capacity: "); - _msg += espem->ds.getMetricsSize(); - _msg += "/"; - _msg += espem->ds.getMetricsCap(); // current number of metrics samples - _msg += " samples"; - - interf->constant("mcap", _msg); - - // Button "Apply Metrics pool settings" - interf->button(button_t::submit, A_set_espem_pool, T_DICT[lang][TD::D_Apply]); - - // - // Define metrics collector state - // 0: Disabled, memory released - // 1: Running and storing metrics in RAM - // 2: Paused, collecting but not storing, memory reserved - // - interf->select(A_ECOLLECTORSTATE, (uint8_t)espem->get_collector_state(), "Metrics collector status", true); - interf->option(0, "Disabled"); - interf->option(1, "Running"); - interf->option(2, "Paused"); - interf->json_section_end(); // select - - interf->json_frame_flush(); // flush frame -*/ } /** * ESPEM data export page * */ -void ui_page_dataexport(Interface *interf, const JsonObject *data, const char* action){ +void ui_page_dataexport(Interface *interf, JsonObjectConst data, const char* action){ interf->json_frame_interface(); interf->json_section_uidata(); - interf->uidata_pick("espem.ui.export"); + interf->uidata_pick("espem.ui.pages.export"); interf->json_frame_flush(); } @@ -264,19 +224,19 @@ void ui_page_dataexport(Interface *interf, const JsonObject *data, const char* a /** * Apply espem options values */ -void set_sampler_opts(Interface *interf, const JsonObject *data, const char* action){ - if (!data) return; +void set_sampler_opts(Interface *interf, JsonObjectConst data, const char* action){ + if (!data.size()) return; // save sampling storage capacity values - SETPARAM(V_TS_T1_CNT); - SETPARAM(V_TS_T1_INT); - SETPARAM(V_TS_T2_CNT); - SETPARAM(V_TS_T2_INT); - SETPARAM(V_TS_T3_CNT); - SETPARAM(V_TS_T3_INT); + embui.getConfig()[V_TS_T1_CNT] = data[V_TS_T1_CNT]; + embui.getConfig()[V_TS_T1_INT] = data[V_TS_T1_INT]; + embui.getConfig()[V_TS_T2_CNT] = data[V_TS_T2_CNT]; + embui.getConfig()[V_TS_T2_INT] = data[V_TS_T2_INT]; + embui.getConfig()[V_TS_T3_CNT] = data[V_TS_T3_CNT]; + embui.getConfig()[V_TS_T3_INT] = data[V_TS_T3_INT]; espem->ds.reset(); // display main page - if (interf) ui_page_espem(interf, nullptr, NULL); + if (interf) ui_page_espem(interf, {}, NULL); } @@ -284,7 +244,7 @@ void set_sampler_opts(Interface *interf, const JsonObject *data, const char* act * обработка "живых" переключателей * */ -void set_directctrls(Interface *interf, const JsonObject *data, const char* action){ +void set_directctrls(Interface *interf, JsonObjectConst data, const char* action){ if (!data) return; std::string_view sv(action); @@ -292,55 +252,53 @@ void set_directctrls(Interface *interf, const JsonObject *data, const char* acti // ena/disable polling if (sv.compare(V_EPOLLENA) == 0){ - espem->set_uirate( (*data)[action] ? embui.paramVariant(V_UI_UPDRT) : 0); - LOG(printf, "ESPEM: UI refresh state: %d\n", (*data)[A_EPOLLENA].as() ); + espem->set_uirate( data[action] ? embui.getConfig()[V_UI_UPDRT] : 0); + LOGI(WebUI, printf, "UI refresh state: %d\n", data[A_EPOLLENA].as() ); return; } // UI update rate if (sv.compare(V_UI_UPDRT) == 0){ - espem->set_uirate((*data)[action]); - embui.var(V_UI_UPDRT, espem->get_uirate()); - LOG( printf, "ESPEM: Set UI update rate to: %d\n", espem->get_uirate() ); + espem->set_uirate(data[action]); + embui.getConfig()[V_UI_UPDRT] = espem->get_uirate(); + LOGI(WebUI, printf, "Set UI update rate to: %d\n", espem->get_uirate() ); return; } // Metrics collector run/pause if (sv.compare(V_ECOLLECTORSTATE) == 0){ - uint8_t new_state = (*data)[action]; + uint8_t new_state = data[action]; // reset TS Container if empty and we need to start it //if (espem->get_collector_state() == mcstate_t::MC_DISABLE && new_state >0) - // espem->ds.tsSet(embui.paramVariant(V_EPOOLSIZE), embui.paramVariant(V_SMPL_PERIOD)); + // espem->ds.tsSet(embui.getConfig()[V_EPOOLSIZE], embui.getConfig()[V_SMPL_PERIOD]); espem->set_collector_state((mcstate_t)new_state); - LOG(printf, "UI: Set TS Collector state to: %d\n", (int)espem->get_collector_state() ); + LOGD(WebUI, printf, "UI: Set TS Collector state to: %d\n", (int)espem->get_collector_state() ); return; } // Metrics graph - number of samples to draw in a small power chart if (sv.compare(C_scnt) == 0){ - embui.var(V_SMPLCNT, (*data)[action]); + embui.getConfig()[V_SMPLCNT] = data[action]; // send update command to AmCharts block if (interf){ - interf->json_frame("espem"); - interf->value(C_scnt, (*data)[action]); + interf->json_frame(C_espem); + interf->value(C_scnt, data[action]); interf->json_frame_flush(); } return; } + // select another tier on minichart if (sv.compare(C_tier) == 0){ // save new TS id - power_chart_id = (*data)[action]; + power_chart_id = data[action]; + LOGI(WebUI, printf, "Switch TS tier to:%d\n", power_chart_id ); if (!interf) return; - - // call js function to build power chart + // publish control values + ui_block_chart_ctrls_values(interf); + // call js function to re-build power chart ui_frame_mkchart(interf); - - // send update command to AmCharts block - interf->json_frame_interface(); - ui_block_chart_ctrls(interf); - interf->json_frame_flush(); return; } } @@ -348,27 +306,27 @@ void set_directctrls(Interface *interf, const JsonObject *data, const char* acti /** * Apply uart options values */ -void set_uart_opts(Interface *interf, const JsonObject *data, const char* action){ +void set_uart_opts(Interface *interf, JsonObjectConst data, const char* action){ if (!data) return; - uint8_t p = (*data)[V_UART].as(); + uint8_t p = data[V_UART].as(); if ( p <= SOC_UART_NUM ){ - SETPARAM(V_UART); + embui.getConfig()[V_UART] = data[V_UART]; } else return; - int r = (*data)[V_RX].as(); + int r = data[V_RX].as(); if (r <= NUM_OUPUT_PINS && r >0) { - SETPARAM(V_RX); + embui.getConfig()[V_RX] = data[V_RX]; } else return; - int t = (*data)[V_TX].as(); + int t = data[V_TX].as(); if (t <= NUM_OUPUT_PINS && r >0){ - SETPARAM(V_TX); + embui.getConfig()[V_TX] = data[V_TX]; } else return; - espem->begin(embui.paramVariant(V_UART), embui.paramVariant(V_RX), embui.paramVariant(V_TX)); + espem->begin(embui.getConfig()[V_UART], embui.getConfig()[V_RX], embui.getConfig()[V_TX]); // display main page - if (interf) ui_page_espem(interf, nullptr, NULL); + if (interf) ui_page_espem(interf, {}, NULL); } /** @@ -377,14 +335,14 @@ void set_uart_opts(Interface *interf, const JsonObject *data, const char* action * @param interf * @param data */ -void set_pzopts(Interface *interf, const JsonObject *data, const char* action){ +void set_pzopts(Interface *interf, JsonObjectConst data, const char* action){ if (!data) return; - SETPARAM(V_EOFFSET); - espem->ds.setEnergyOffset(embui.paramVariant(V_EOFFSET)); + embui.getConfig()[V_EOFFSET] = data[V_EOFFSET]; + espem->ds.setEnergyOffset(embui.getConfig()[V_EOFFSET]); // display main page - if (interf) ui_page_espem(interf, nullptr, NULL); + if (interf) ui_page_espem(interf, {}, NULL); } @@ -397,24 +355,7 @@ void set_pzopts(Interface *interf, const JsonObject *data, const char* action){ * */ void embui_actions_register(){ - LOG(println, "UI: Creating application vars"); - - /** - * регистрируем свои переменные - */ - embui.var_create(V_UI_UPDRT, DEFAULT_WS_UPD_RATE); // WebUI update rate - // Time Series values - embui.var_create(V_TS_T1_CNT, TS_T1_CNT); - embui.var_create(V_TS_T1_INT, TS_T1_INTERVAL); - embui.var_create(V_TS_T2_CNT, TS_T2_CNT); - embui.var_create(V_TS_T2_INT, TS_T2_INTERVAL); - embui.var_create(V_TS_T3_CNT, TS_T3_CNT); - embui.var_create(V_TS_T3_INT, TS_T3_INTERVAL); - embui.var_create(V_UART, 0x1); // default UART port UART_NUM_1 - embui.var_create(V_RX, -1); // RX pin (default) - embui.var_create(V_TX, -1); // TX pin (default) - embui.var_create(V_TX, -1); // TX pin (default) - embui.var_create(V_EOFFSET, 0); // Energy counter offset + LOGD(WebUI, println, "UI: Creating application vars"); /** * обработчики действий @@ -422,7 +363,7 @@ void embui_actions_register(){ // UI page callback handlers embui.action.set_mainpage_cb(ui_page_main); // index page callback - embui.action.set_settings_cb(user_settings_frame); // "settings" page options callback + embui.action.set_settings_cb(block_user_settings); // "settings" page options callback //embui.action.set_publish_cb(pubCallback); // Publish callback // вывод WebUI секций diff --git a/espem/interface.h b/espem/interface.h index 1a469bc..464f883 100644 --- a/espem/interface.h +++ b/espem/interface.h @@ -5,15 +5,15 @@ void embui_actions_register(); // Interface blocks void block_menu(Interface *interf); -void block_page_main(Interface *interf, const JsonObject *data, const char* action); -void block_page_espemset(Interface *interf, const JsonObject *data, const char* action); +void block_page_main(Interface *interf, JsonObjectConst data, const char* action); +void block_page_espemset(Interface *interf, JsonObjectConst data, const char* action); // ACTIONS -void action_demopage(Interface *interf, const JsonObject *data, const char* action); -void set_sampler_opts(Interface *interf, const JsonObject *data, const char* action); -void set_directctrls(Interface *interf, const JsonObject *data, const char* action); -void set_uart_opts(Interface *interf, const JsonObject *data, const char* action); -void set_pzopts(Interface *interf, const JsonObject *data, const char* action); +void action_demopage(Interface *interf, JsonObjectConst data, const char* action); +void set_sampler_opts(Interface *interf, JsonObjectConst data, const char* action); +void set_directctrls(Interface *interf, JsonObjectConst data, const char* action); +void set_uart_opts(Interface *interf, JsonObjectConst data, const char* action); +void set_pzopts(Interface *interf, JsonObjectConst data, const char* action); // Callbacks void pubCallback(Interface *interf); diff --git a/espem/log.h b/espem/log.h new file mode 100644 index 0000000..1649e48 --- /dev/null +++ b/espem/log.h @@ -0,0 +1,82 @@ +/* +LOG macro will enable/disable logs to serial depending on ESPEM_DEBUG build-time flag +*/ +#pragma once + +#ifndef ESPEM_DEBUG_PORT +#define ESPEM_DEBUG_PORT Serial +#endif + +#ifndef ESPEM_DEBUG_LEVEL +#define ESPEM_DEBUG_LEVEL 2 +#endif + +// undef possible LOG macros +#ifdef LOG + #undef LOG +#endif +#ifdef LOGV + #undef LOGV +#endif +#ifdef LOGD + #undef LOGD +#endif +#ifdef LOGI + #undef LOGI +#endif +#ifdef LOGW + #undef LOGW +#endif +#ifdef LOGE + #undef LOGE +#endif + +static constexpr const char* S_V = "V: "; +static constexpr const char* S_D = "D: "; +static constexpr const char* S_I = "I: "; +static constexpr const char* S_W = "W: "; +static constexpr const char* S_E = "E: "; + +#if defined(ESPEM_DEBUG_LEVEL) && ESPEM_DEBUG_LEVEL == 5 + #define LOGV(tag, func, ...) ESPEM_DEBUG_PORT.print(S_V); ESPEM_DEBUG_PORT.print(tag); ESPEM_DEBUG_PORT.print((char)0x9); ESPEM_DEBUG_PORT.func(__VA_ARGS__) +#else + #define LOGV(...) +#endif + +#if defined(ESPEM_DEBUG_LEVEL) && ESPEM_DEBUG_LEVEL > 3 + #define LOGD(tag, func, ...) ESPEM_DEBUG_PORT.print(S_D); ESPEM_DEBUG_PORT.print(tag); ESPEM_DEBUG_PORT.print((char)0x9); ESPEM_DEBUG_PORT.func(__VA_ARGS__) +#else + #define LOGD(...) +#endif + +#if defined(ESPEM_DEBUG_LEVEL) && ESPEM_DEBUG_LEVEL > 2 + #define LOGI(tag, func, ...) ESPEM_DEBUG_PORT.print(S_I); ESPEM_DEBUG_PORT.print(tag); ESPEM_DEBUG_PORT.print((char)0x9); ESPEM_DEBUG_PORT.func(__VA_ARGS__) + // compat macro + #define LOG(func, ...) ESPEM_DEBUG_PORT.func(__VA_ARGS__) +#else + #define LOGI(...) + // compat macro + #define LOG(...) +#endif + +#if defined(ESPEM_DEBUG_LEVEL) && ESPEM_DEBUG_LEVEL > 1 + #define LOGW(tag, func, ...) ESPEM_DEBUG_PORT.print(S_W); ESPEM_DEBUG_PORT.print(tag); ESPEM_DEBUG_PORT.print((char)0x9); ESPEM_DEBUG_PORT.func(__VA_ARGS__) +#else + #define LOGW(...) +#endif + +#if defined(ESPEM_DEBUG_LEVEL) && ESPEM_DEBUG_LEVEL > 0 + #define LOGE(tag, func, ...) ESPEM_DEBUG_PORT.print(S_E); ESPEM_DEBUG_PORT.print(tag); ESPEM_DEBUG_PORT.print((char)0x9); ESPEM_DEBUG_PORT.func(__VA_ARGS__) +#else + #define LOGE(...) +#endif + + +// LOG tags +static constexpr const char* T_Effect = "Effect"; +static constexpr const char* T_EffCfg = "EffCfg"; +static constexpr const char* T_EffWrkr = "EffWrkr"; +static constexpr const char* T_Fade = "Fade"; +static constexpr const char* T_Module = "Module"; +static constexpr const char* T_WebUI = "WebUI"; +static constexpr const char* T_ModMGR = "ModMGR"; diff --git a/espem/main.cpp b/espem/main.cpp index bef7c1d..172b457 100644 --- a/espem/main.cpp +++ b/espem/main.cpp @@ -10,60 +10,80 @@ #include "main.h" #include "espem.h" -#include +#include "EmbUI.h" #include "interface.h" - -extern "C" int clock_gettime(clockid_t unused, struct timespec *tp); +#include "log.h" // PROGMEM strings // sprintf template for json version data -static const char PGverjson[] = "{\"ChipID\":\"%s\",\"Flash\":%u,\"SDK\":\"%s\",\"firmware\":\"" FW_NAME "\",\"version\":\"" FW_VERSION_STRING "\",\"git\":\"%s\",\"CPUMHz\":%u,\"RAM Heap size\":%u,\"RAM Heap free\":%u,\"PSRAM size\":%u,\"PSRAM free\":%u,\"Uptime\":%u}"; +static constexpr const char* PGverjson = "{\"ChipID\":\"%s\",\"Flash\":%u,\"SDK\":\"%s\",\"firmware\":\"" FW_NAME "\",\"version\":\"" FW_VERSION_STRING "\",\"git\":\"%s\",\"CPUMHz\":%u,\"RAM Heap size\":%u,\"RAM Heap free\":%u,\"PSRAM size\":%u,\"PSRAM free\":%u,\"Uptime\":%u}"; // Our instance of espem Espem *espem = nullptr; +// load configuration for espem and start it +void setup_espem(); + // ---- // MAIN Setup void setup() { -#ifdef ESPEM_DEBUG +#ifdef ESPEM_DEBUG_PORT ESPEM_DEBUG.begin(BAUD_RATE); // start hw serial for debugging #endif - LOG(println, F("Starting EspEM...")); + LOGI(C_EspEM, println, "Starting EspEM..."); // Start framework, load config and connect WiFi embui.begin(); + + // start EspEM object + setup_espem(); + + // attach EmbUI callback actions embui_actions_register(); - // create and run ESPEM object - espem = new Espem(); + // status call + embui.server.on("/fw", HTTP_GET, [](AsyncWebServerRequest *request){ wver(request); }); + + // adjust EmbUI's publish rate + embui.setPubInterval(WEBUI_PUBLISH_INTERVAL); +} + +// MAIN loop +void loop() { + embui.handle(); +} + +// load configuration for espem and start it +void setup_espem(){ + if (!espem){ + // create and run ESPEM object + espem = new Espem(); + } + + if (!espem) return; + + if (espem->begin( + embui.getConfig()[V_UART] | UART_NUM_1, // by default UART_NUM_1 uses SECOND uart port on ESP32, not the one that outputs console serial + embui.getConfig()[V_RX] | -1, + embui.getConfig()[V_TX] | -1) + ){ + LOGI(C_EspEM, printf, "attaching to UART:%d\n",embui.getConfig()[V_UART] | UART_NUM_1 ); - if (espem && espem->begin( embui.paramVariant(V_UART), - embui.paramVariant(V_RX), - embui.paramVariant(V_TX)) - ) - { - espem->ds.setEnergyOffset(embui.paramVariant(V_EOFFSET)); + espem->ds.setEnergyOffset(embui.getConfig()[V_EOFFSET]); + LOGI(C_EspEM, printf, "Configured energy offset is:%d\n", embui.getConfig()[V_EOFFSET] | 0 ); // postpone TimeSeries setup until NTP aquires valid time TimeProcessor::getInstance().attach_callback([](){ + LOGI(C_EspEM, println, "Aquired time from NTP, running TimeSeries collector..." ); espem->set_collector_state(mcstate_t::MC_RUN); // we only need that setup once TimeProcessor::getInstance().dettach_callback(); }); } - - embui.server.on("/fw", HTTP_GET, [](AsyncWebServerRequest *request){ wver(request); }); - - embui.setPubInterval(WEBUI_PUBLISH_INTERVAL); -} - -// MAIN loop -void loop() { - embui.handle(); } // send HTTP responce, json with controller/fw versions and status info @@ -72,7 +92,7 @@ void wver(AsyncWebServerRequest *request) { timespec tp; clock_gettime(0, &tp); - snprintf_P(buff, sizeof(buff), PGverjson, + snprintf(buff, sizeof(buff), PGverjson, ESP.getChipModel(), ESP.getFlashChipSize(), ESP.getSdkVersion(), @@ -87,6 +107,5 @@ void wver(AsyncWebServerRequest *request) { (uint32_t)tp.tv_sec); - request->send(200, FPSTR(PGmimejson), buff ); + request->send(200, asyncsrv::T_application_json, buff ); } -// \ No newline at end of file diff --git a/espem/uistrings.h b/espem/uistrings.h index 1a5ef73..e7538e9 100644 --- a/espem/uistrings.h +++ b/espem/uistrings.h @@ -3,57 +3,60 @@ // Set of flash-strings that might be reused multiple times within the code // General -static constexpr const char C_espem[] = "espem"; -static constexpr const char C_espem_ui[] = "espem.ui"; -static constexpr const char C_mkchart[] = "mkchart"; -static constexpr const char C_mkgauge[] = "mkgauge"; -static constexpr const char C_gsmini[] = "gsmini"; -static constexpr const char C_mqtt_pzem_jmetrics[] = "pub/pzem/jmetrics"; -static constexpr const char C_scnt[] = "scnt"; // samle counter -static constexpr const char C_tier[] = "tier"; -static constexpr const char C_lchart[] = "lchart"; +static constexpr const char* C_EspEM = "EspEM"; +static constexpr const char* C_espem = "espem"; +static constexpr const char* C_espem_ui = "espem.ui"; +static constexpr const char* C_mkchart = "mkchart"; +static constexpr const char* C_mkgauge = "mkgauge"; +static constexpr const char* C_minichart = "minichart"; +static constexpr const char* C_smpchart = "smpchart"; +static constexpr const char* C_mqtt_pzem_jmetrics = "pub/pzem/jmetrics"; +static constexpr const char* C_sample = "sample"; +static constexpr const char* C_scnt = "scnt"; // samle counter +static constexpr const char* C_tier = "tier"; +static constexpr const char* C_lchart = "lchart"; ////////////////////// // Configuration variables names - V_ prefix for 'Variable' -static constexpr const char V_TS_T1_CNT[] = "t1cnt"; // default Tier 1 TimeSeries count -static constexpr const char V_TS_T1_INT[] = "t1int"; // default Tier 1 TimeSeries interval -static constexpr const char V_TS_T2_CNT[] = "t2cnt"; // default Tier 2 TimeSeries count -static constexpr const char V_TS_T2_INT[] = "t2int"; // default Tier 2 TimeSeries interval -static constexpr const char V_TS_T3_CNT[] = "t3cnt"; // default Tier 3 TimeSeries count -static constexpr const char V_TS_T3_INT[] = "t3int"; // default Tier 3 TimeSeries interval -static constexpr const char V_RX[] = "rx"; // rx pin -static constexpr const char V_TX[] = "tx"; // tx ping -static constexpr const char V_UART[] = "uart"; // uart interface -static constexpr const char V_EOFFSET[] = "eoffset"; // energy offset +static constexpr const char* V_TS_T1_CNT = "t1cnt"; // default Tier 1 TimeSeries count +static constexpr const char* V_TS_T1_INT = "t1int"; // default Tier 1 TimeSeries interval +static constexpr const char* V_TS_T2_CNT = "t2cnt"; // default Tier 2 TimeSeries count +static constexpr const char* V_TS_T2_INT = "t2int"; // default Tier 2 TimeSeries interval +static constexpr const char* V_TS_T3_CNT = "t3cnt"; // default Tier 3 TimeSeries count +static constexpr const char* V_TS_T3_INT = "t3int"; // default Tier 3 TimeSeries interval +static constexpr const char* V_RX = "rx"; // rx pin +static constexpr const char* V_TX = "tx"; // tx ping +static constexpr const char* V_UART = "uart"; // uart interface +static constexpr const char* V_EOFFSET = "eoffset"; // energy offset // directly changed vars, must match actions with prefixed "dctl_" -static constexpr const char V_EPOLLENA[] = "poll"; // Enable/disable poller -static constexpr const char V_EPFFIX[] = "pffix"; // PowerFactor value correction -static constexpr const char V_UI_UPDRT[] = "updaterate"; // UI update rate -static constexpr const char V_ECOLLECTORSTATE[] = "collector"; // Metrics collector run/pause -static constexpr const char V_SMPLCNT[] = "smplcnt"; // Metrics graph - number of samples to draw in a small power chart +static constexpr const char* V_EPOLLENA = "poll"; // Enable/disable poller +static constexpr const char* V_EPFFIX = "pffix"; // PowerFactor value correction +static constexpr const char* V_UI_UPDRT = "updaterate"; // UI update rate +static constexpr const char* V_ECOLLECTORSTATE = "collector"; // Metrics collector run/pause +static constexpr const char* V_SMPLCNT = "smplcnt"; // Metrics graph - number of samples to draw in a small power chart // UI blocks - B_ prefix for 'web Block' -static constexpr const char A_ui_page_espem[] = "ui_page_espem"; -static constexpr const char A_ui_page_espem_setup[] = "ui_page_espem_setup"; -static constexpr const char A_ui_page_dataexport[] = "ui_page_dataexport"; +static constexpr const char* A_ui_page_espem = "ui_page_espem"; +static constexpr const char* A_ui_page_espem_setup = "ui_page_espem_setup"; +static constexpr const char* A_ui_page_dataexport = "ui_page_dataexport"; // direct control elements -static constexpr const char A_DIRECT_CTL[] = "dctl_*"; // checkboxes/controls that should be processed onChange +static constexpr const char* A_DIRECT_CTL = "dctl_*"; // checkboxes/controls that should be processed onChange // UI handlers - A_ prefix for 'Action' -static constexpr const char A_set_espem_pool[] = "set_espem_pool"; // ESPEM settings update -static constexpr const char A_SET_UART[] = "set_uart"; -static constexpr const char A_SET_PZOPTS[] = "set_nrgoffset"; -static constexpr const char A_SET_MCOLLECTOR[] = "set_mcollector"; // apply metrics collector settings +static constexpr const char* A_set_espem_pool = "set_espem_pool"; // ESPEM settings update +static constexpr const char* A_SET_UART = "set_uart"; +static constexpr const char* A_SET_PZOPTS = "set_nrgoffset"; +static constexpr const char* A_SET_MCOLLECTOR = "set_mcollector"; // apply metrics collector settings // onChange controls actions -static constexpr const char A_EPOLLENA[] = "dctl_poll"; // Enable/disable poller -static constexpr const char A_EPFFIX[] = "dctl_pffix"; // PowerFactor value correction -static constexpr const char A_UI_UPDRT[] = "dctl_updaterate"; // UI update rate -static constexpr const char A_ECOLLECTORSTATE[] = "dctl_collector"; // Metrics collector run/pause -static constexpr const char A_SMPLCNT[] = "dctl_scnt"; // Metrics graph - number of samples to draw in a small power chart -static constexpr const char A_TS_TIER[] = "dctl_tier"; // drop-down selector for power chart TS id +static constexpr const char* A_EPOLLENA = "dctl_poll"; // Enable/disable poller +static constexpr const char* A_EPFFIX = "dctl_pffix"; // PowerFactor value correction +static constexpr const char* A_UI_UPDRT = "dctl_updaterate"; // UI update rate +static constexpr const char* A_ECOLLECTORSTATE = "dctl_collector"; // Metrics collector run/pause +static constexpr const char* A_SMPLCNT = "dctl_scnt"; // Metrics graph - number of samples to draw in a small power chart +static constexpr const char* A_TS_TIER = "dctl_tier"; // drop-down selector for power chart TS id // other constants diff --git a/platformio.ini b/platformio.ini index 4d7624b..ee9bd9d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,8 +9,8 @@ extra_configs = board_build.filesystem = littlefs framework = arduino build_flags = - -DFZ_WITH_ASYNCSRV - -DNO_GLOBAL_UPDATE + -DFZ_WITH_ASYNCSRV -DFZ_NOHTTPCLIENT -DNO_GLOBAL_UPDATE + -DEMBUI_IDPREFIX='"EspEM"' ; -DCOUNTRY="ru" build_src_flags = !python flags.py @@ -18,7 +18,9 @@ build_src_flags = src_build_unflags = -std=gnu++11 lib_deps = - https://github.com/vortigont/EmbUI.git#v3.1 + vortigont/pzem-edl @ ~1.2 + vortigont/EmbUI @ ~4.0.1 +; https://github.com/vortigont/EmbUI.git#v3.1 lib_ignore = ESPAsyncTCP monitor_speed = 115200 @@ -28,9 +30,10 @@ monitor_speed = 115200 [debug] espem_serial = -DESPEM_DEBUG=Serial + -DESPEM_DEBUG_LEVEL=4 app_serial = ${debug.espem_serial} - -DEMBUI_DEBUG + -DEMBUI_DEBUG_LEVEL=3 -DEMBUI_DEBUG_PORT=Serial all_serial = ${debug.app_serial} @@ -42,7 +45,7 @@ espem_serial1 = -DESPEM_DEBUG=Serial1 app_serial1 = ${debug.espem_serial1} - -DEMBUI_DEBUG + -DEMBUI_DEBUG_LEVEL=3 -DEMBUI_DEBUG_PORT=Serial1 all_serial1 = ${debug.espem_serial1} @@ -50,33 +53,23 @@ all_serial1 = [esp32_base] extends = common -platform = espressif32 @ 6.9.0 +; Tasmota's platform, based on Arduino Core v3.0.4 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.08.11/platform-espressif32.zip +;platform = espressif32 @ 6.9.0 board = wemos_d1_mini32 ;upload_speed = 460800 monitor_filters = esp32_exception_decoder ;build_flags = -lib_ignore = - ESPAsyncTCP - LITTLEFS - LittleFS_esp32 ; ===== Build ENVs ====== [env] extends = common -;build_flags = -; -DCOUNTRY="ru" // Country double-letter code, used for NTP pool selection -; -DNO_GLOBAL_SERIAL -; -DNO_GLOBAL_SERIAL1 ; ESP32 with PZEM EDL [env:espem] extends = esp32_base -lib_deps = - ${esp32_base.lib_deps} - vortigont/pzem-edl @ ~1.2 -; https://github.com/vortigont/pzem-edl ;build_flags = ; ${esp32_base.build_flags} ; -DCOUNTRY="ru" @@ -88,10 +81,6 @@ lib_deps = ; ESP32 with PZEM EDL, debug enabled [env:espem_debug] extends = esp32_base -lib_deps = - ${esp32_base.lib_deps} - https://github.com/vortigont/pzem-edl -; vortigont/pzem-edl @ ~1.0.0 build_flags = ${esp32_base.build_flags} ${debug.app_serial} @@ -100,9 +89,6 @@ build_flags = ; build pzem emulator [env:espem_dummy] extends = esp32_base -lib_deps = - ${esp32_base.lib_deps} - https://github.com/vortigont/pzem-edl build_flags = ${esp32_base.build_flags} -DESPEM_DUMMY diff --git a/resources/html/index.html b/resources/html/index.html index 59b1cb3..3537474 100644 --- a/resources/html/index.html +++ b/resources/html/index.html @@ -104,13 +104,16 @@ {{/if}} {{#if type == "checkbox"}}

- +
{{/if}} {{#if html == "const"}}
{{label}}{{#if2 value}}{{/if2}}
{{/if}} + {{#if html == "constbtn"}} + + {{/if}} {{#if html == "comment"}}
{{label}}
{{/if}} @@ -132,7 +135,11 @@ param.* - reserved --> {{#if2 label}}{{label}}{{/if2}} -
{{value}}
+
{{value}}
+ {{/if}} + {{#if html == "div" && type == "chart"}} + {{#if2 label}}{{label}}{{/if2}} +
{{/if}} {{#if html == "div" && type == "pbar"}} @@ -191,10 +198,6 @@

{{label}}

- diff --git a/resources/html/js/espem.js b/resources/html/js/espem.js index 71d3e8a..ca05dd9 100644 --- a/resources/html/js/espem.js +++ b/resources/html/js/espem.js @@ -242,7 +242,8 @@ function mkgauge(id, param){ } } -function mkchart(obj){ +function mkcharts(obj){ + console.log('mkcharts:', obj); let id = obj.block[0].id; minichart.tier = obj.block[0].tier; minichart.scnt = obj.block[0].scnt; @@ -349,3 +350,18 @@ function mkchart(obj){ console.log("created gsmini"); } + +// register user functions +customFuncs["mkchart"] = mkcharts; +customFuncs["mkgauge"] = mkgauge; + + +// load EspEM's App UIData +window.addEventListener("load", async function(ev){ + let response = await fetch("/js/espem.ui.json", {method: 'GET'}); + if (response.ok){ + response = await response.json(); + uiblocks['espem'] = {"ui": response}; + } + +}.bind(window)) diff --git a/resources/html/js/espem.ui.json b/resources/html/js/espem.ui.json index f071e90..d6f0451 100644 --- a/resources/html/js/espem.ui.json +++ b/resources/html/js/espem.ui.json @@ -2,265 +2,344 @@ "type": "interface", "version": 2, "descr": "EspEM UI objects", - "settings": { - "cfg": { - "section": "ui_page_empem_setup", - "label": "ESPEM Setup", + "pages":{ + "main":{ + "section": "ui_page_espem", + "label": "EspEnergy Meter", "main": true, "block": [ { - "section": "set_uart", - "label": "UART GPIO Setup", - "hidden": true, - "block": [ + "section": "live_update_ctrls", + "line": true, + "block":[ { - "section": "uart_gpio", - "line": true, - "block": [ - { - "id": "uart", - "html": "input", - "value": 1, - "type": "number", - "label": "Uart port", - "max": 3, - "step": 1 - }, - { - "id": "rx", - "html": "input", - "value": -1, - "type": "number", - "label": "RX pin (-1 to disable)", - "min": -1, - "max": 46, - "step": 1 - }, - { - "id": "tx", - "html": "input", - "value": -1, - "type": "number", - "label": "TX pin (-1 to disable)", - "min": -1, - "max": 46, - "step": 1 - } - ] + "id":"dctl_poll", + "html":"input", + "label":"Live-update", + "type":"checkbox", + "onChange": true }, { - "id": "set_uart", - "html": "button", - "type": 1, - "label": "Apply" + "id": "dctl_updaterate", + "html": "input", + "type": "range", + "min": 0, + "max": 30, + "step": 1, + "onChange": true, + "label": "Update rate, sec" } ] }, { - "section": "set_nrgoffset", - "label": "PZEM Options", - "hidden": true, - "block": [ + "section": "displays", + "line": true, + "block":[ { - "html": "spacer", - "label": "Energy counter options" + "id":"pwr", + "html":"div", + "type":"html", + "label":"Power", + "class": "display pwr" }, { - "id": "eoffset", - "html": "input", - "value": 0, - "type": "number", - "label": "Energy counter offset (Wh)" + "id":"cur", + "html":"div", + "type":"html", + "label":"Current", + "class": "display cur" }, { - "id": "set_nrgoffset", - "html": "button", - "type": 1, - "label": "Apply" + "id":"enrg", + "html":"div", + "type":"html", + "label":"Energy", + "class": "display enrg" } ] }, { - "section": "set_mcollector", - "label": "Time Series Collector", - "hidden": true, - "block": [ - { - "html": "spacer", - "label": "Pool capacity setup" - }, - { - "id": "dctl_collector", - "html": "select", - "label": "Metrics collector state", - "onChange": true, - "section": "options", - "block": [ - { "value": 0, "label": "Disabled" }, - { "value": 1, "label": "Running" }, - { "value": 2, "label": "Paused" } - ] - }, - { - "section": "t1cmt", - "line": true, - "block": [ - { - "html": "comment", - "label": "Tier 1 series" - }, - { - "id": "t1mem", - "html": "const", - "label": "Memory: -/-" - } - ] - }, - { - "section": "t1opts", - "line": true, - "block": [ - { - "id": "t1cnt", - "html": "input", - "type": "number", - "label": "Num of samples", - "min": 100, - "step": 100 - }, - { - "id": "t1int", - "html": "input", - "value": 1, - "type": "number", - "label": "interval (sec.)", - "min": 1, - "step": 1 - } - ] - }, - { - "section": "t2cmt", - "line": true, - "block": [ - { - "html": "comment", - "label": "Tier 2 series" - }, - { - "id": "t2mem", - "html": "const", - "label": "Memory: -/-" - } - ] - }, + "section": "gauges", + "line": true, + "block":[ { - "section": "t2opts", - "line": true, - "block": [ - { - "id": "t2cnt", - "html": "input", - "type": "number", - "label": "Num of samples", - "min": 100, - "step": 100 - }, - { - "id": "t2int", - "html": "input", - "type": "number", - "label": "interval (sec.)", - "min": 5, - "step": 5 - } - ] + "id":"gaugeV", + "html":"div", + "type":"js", + "label":"Voltage", + "value":"mkgauge", + "class":"graphwide" }, { - "section": "t3cmt", - "line": true, - "block": [ - { - "html": "comment", - "label": "Tier 3 series" - }, - { - "id": "t3mem", - "html": "const", - "label": "Memory: -/-" - } - ] - }, - { - "section": "t3opts", - "line": true, - "block": [ - { - "id": "t3cnt", - "html": "input", - "type": "number", - "label": "Num of samples", - "min": 100, - "step": 100 - }, - { - "id": "t3int", - "html": "input", - "type": "number", - "label": "interval (sec.)", - "min": 60, - "step": 60 - } - ] - }, - { - "id": "set_mcollector", - "html": "button", - "type": 1, - "label": "Apply" + "id":"gaugePF", + "html":"div", + "type":"js", + "label":"Power Factor", + "value":"mkgauge", + "class":"graphwide" } ] + } + ] + }, + "settings":{ + "section": "ui_page_espem_setup", + "label": "ESPEM Setup", + "main": true, + "block": [ + { + "section": "set_uart", + "label": "UART GPIO Setup", + "hidden": true, + "block": [ + { + "section": "uart_gpio", + "line": true, + "block": [ + { + "id": "uart", + "html": "input", + "value": 1, + "type": "number", + "label": "Uart port", + "max": 3, + "step": 1 + }, + { + "id": "rx", + "html": "input", + "value": -1, + "type": "number", + "label": "RX pin (-1 to disable)", + "min": -1, + "max": 46, + "step": 1 + }, + { + "id": "tx", + "html": "input", + "value": -1, + "type": "number", + "label": "TX pin (-1 to disable)", + "min": -1, + "max": 46, + "step": 1 + } + ] + }, + { + "id": "set_uart", + "html": "button", + "type": 1, + "label": "Apply" + } + ] + }, + { + "section": "set_nrgoffset", + "label": "PZEM Options", + "hidden": true, + "block": [ + { + "html": "spacer", + "label": "Energy counter options" + }, + { + "id": "eoffset", + "html": "input", + "value": 0, + "type": "number", + "label": "Energy counter offset (Wh)" + }, + { + "id": "set_nrgoffset", + "html": "button", + "type": 1, + "label": "Apply" + } + ] + }, + { + "section": "set_mcollector", + "label": "Time Series Collector", + "hidden": true, + "block": [ + { + "html": "spacer", + "label": "Pool capacity setup" + }, + { + "id": "dctl_collector", + "html": "select", + "label": "Metrics collector state", + "onChange": true, + "section": "options", + "block": [ + { "value": 0, "label": "Disabled" }, + { "value": 1, "label": "Running" }, + { "value": 2, "label": "Paused" } + ] + }, + { + "section": "t1cmt", + "line": true, + "block": [ + { + "html": "comment", + "label": "Tier 1 series" + }, + { + "id": "t1mem", + "html": "const", + "label": "Memory: -/-" + } + ] + }, + { + "section": "t1opts", + "line": true, + "block": [ + { + "id": "t1cnt", + "html": "input", + "type": "number", + "label": "Num of samples", + "min": 100, + "step": 100 + }, + { + "id": "t1int", + "html": "input", + "value": 1, + "type": "number", + "label": "interval (sec.)", + "min": 1, + "step": 1 + } + ] + }, + { + "section": "t2cmt", + "line": true, + "block": [ + { + "html": "comment", + "label": "Tier 2 series" + }, + { + "id": "t2mem", + "html": "const", + "label": "Memory: -/-" + } + ] + }, + { + "section": "t2opts", + "line": true, + "block": [ + { + "id": "t2cnt", + "html": "input", + "type": "number", + "label": "Num of samples", + "min": 100, + "step": 100 + }, + { + "id": "t2int", + "html": "input", + "type": "number", + "label": "interval (sec.)", + "min": 5, + "step": 5 + } + ] + }, + { + "section": "t3cmt", + "line": true, + "block": [ + { + "html": "comment", + "label": "Tier 3 series" + }, + { + "id": "t3mem", + "html": "const", + "label": "Memory: -/-" + } + ] + }, + { + "section": "t3opts", + "line": true, + "block": [ + { + "id": "t3cnt", + "html": "input", + "type": "number", + "label": "Num of samples", + "min": 100, + "step": 100 + }, + { + "id": "t3int", + "html": "input", + "type": "number", + "label": "interval (sec.)", + "min": 60, + "step": 60 + } + ] + }, + { + "id": "set_mcollector", + "html": "button", + "type": 1, + "label": "Apply" + } + ] + }, + { + "id": "ui_page_settings", + "html": "button", + "type": 1, + "label": "Exit", + "color": "gray" + } + ] + }, + "export":{ + "section": "ui_page_espem_setup", + "label": "ESPEM Data Export", + "main": true, + "block": [ + { + "html": "comment", + "label": "Sampling data could be exported in json format.
Generic URI to get the data: http://[espem]/samples.json?tsid=X&scnt=YY
Where X is TS id (1-3)
YY - num of samples to receive" + }, + { + "html": "button", + "type": 3, + "label": "Download TimeSeries #1", + "color": "green", + "value": "/samples.json?tsid=1" + }, + { + "html": "button", + "type": 3, + "label": "Download TimeSeries #2", + "color": "green", + "value": "/samples.json?tsid=2" }, { - "id": "ui_page_settings", "html": "button", - "type": 1, - "label": "Exit", - "color": "gray" + "type": 3, + "label": "Download TimeSeries #3", + "color": "green", + "value": "/samples.json?tsid=3" } - ] - } - }, - "export":{ - "section": "ui_page_empem_setup", - "label": "ESPEM Data Export", - "main": true, - "block": [ - { - "html": "comment", - "label": "Sampling data could be exported in json format.
Generic URI to get the data: http://[espem]/samples.json?tsid=X&scnt=YY
Where X is TS id (1-3)
YY - num of samples to receive" - }, - { - "html": "button", - "type": 3, - "label": "Download TimeSeries #1", - "color": "green", - "value": "/samples.json?tsid=1" - }, - { - "html": "button", - "type": 3, - "label": "Download TimeSeries #2", - "color": "green", - "value": "/samples.json?tsid=2" - }, - { - "html": "button", - "type": 3, - "label": "Download TimeSeries #3", - "color": "green", - "value": "/samples.json?tsid=3" - } - ] + ] + } } } \ No newline at end of file From 85ceb06fe01d4857a2f82a2cad6df864569321b0 Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 30 Oct 2024 01:17:08 +0900 Subject: [PATCH 2/6] CI: add patch to platformio an ugly bug with LDF needs to be patched --- .github/workflows/pio_build.yml | 24 +++++++-------- ...ib-dependency-after-recursive-search.patch | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 extra/0001-LDF-refresh-lib-dependency-after-recursive-search.patch diff --git a/.github/workflows/pio_build.yml b/.github/workflows/pio_build.yml index 9abb8d6..402829e 100644 --- a/.github/workflows/pio_build.yml +++ b/.github/workflows/pio_build.yml @@ -37,18 +37,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: PIP Cache - id: cache-pip - uses: actions/cache@v4 - env: - cache-name: cache-pip-pkgs - with: - path: | - ~/.cache/pip - key: ${{ runner.os }}-pip-${{env.cache-name}} - restore-keys: | - ${{ runner.os }}-pio- - ${{ runner.os }}- - name: Set up Python uses: actions/setup-python@v5 with: @@ -57,7 +45,19 @@ jobs: run: | python -m pip install --upgrade pip pip install --upgrade platformio + #platformio run -t buildfs #platformio pkg update + - name: Patch Platformio + run: | + pwd + echo "REPO_NAME=$(basename ${{ github.repository }})" >> $GITHUB_ENV + cd $(dirname $(which pio) ) + cd ../ + echo $(find -type d -name site-packages) + cd $(find -type d -name site-packages) + pwd + git apply --verbose $GITHUB_WORKSPACE/extra/0001-LDF-refresh-lib-dependency-after-recursive-search.patch + cd $GITHUB_WORKSPACE - name: Run PlatformIO run: | pio run -e espem -e espem_debug diff --git a/extra/0001-LDF-refresh-lib-dependency-after-recursive-search.patch b/extra/0001-LDF-refresh-lib-dependency-after-recursive-search.patch new file mode 100644 index 0000000..ca83002 --- /dev/null +++ b/extra/0001-LDF-refresh-lib-dependency-after-recursive-search.patch @@ -0,0 +1,29 @@ +From 7d12a010a2a71428e6945c8c9c2d05073df379a2 Mon Sep 17 00:00:00 2001 +From: Emil Muratov +Date: Fri, 21 Jun 2024 17:01:38 +0900 +Subject: [PATCH] LDF: refresh lib dependency after recursive search + +LDF might mistakenly remove recursive dependency libs from a graph +usually platform bundled ones + +Closes #4940 +--- + platformio/builder/tools/piolib.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py +index ca9c9f1..36b72d2 100644 +--- a/platformio/builder/tools/piolib.py ++++ b/platformio/builder/tools/piolib.py +@@ -1159,6 +1159,8 @@ def ConfigureProjectLibBuilder(env): + for lb in lib_builders: + if lb in found_lbs: + lb.search_deps_recursive(lb.get_search_files()) ++ # refill found libs after recursive search ++ found_lbs = [lb for lb in lib_builders if lb.is_dependent] + for lb in lib_builders: + for deplb in lb.depbuilders[:]: + if deplb not in found_lbs: +-- +2.34.1 + From af210c1bf22e3400553fb8ea5300a70abfe3854b Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 30 Oct 2024 01:35:14 +0900 Subject: [PATCH 3/6] remove old config.h trash --- espem/config.h | 24 ------------------------ espem/espem.h | 2 +- espem/main.cpp | 10 +++++++--- espem/main.h | 19 +------------------ 4 files changed, 9 insertions(+), 46 deletions(-) delete mode 100644 espem/config.h diff --git a/espem/config.h b/espem/config.h deleted file mode 100644 index 76f561b..0000000 --- a/espem/config.h +++ /dev/null @@ -1,24 +0,0 @@ -// Default config options -// do NOT change anything here, copy 'config.h' into 'user_config.h' and change you options there - -#pragma once - -#if defined __has_include -# if __has_include("user_config.h") -# include "user_config.h" -# endif -#endif - - -#define FW_NAME "espem" - -// LOG macro's -#if defined(LOG) -#undef LOG -#endif - -#if defined(ESPEM_DEBUG) - #define LOG(func, ...) ESPEM_DEBUG.func(__VA_ARGS__) -#else - #define LOG(func, ...) ; -#endif diff --git a/espem/espem.h b/espem/espem.h index 4d2ff3f..190a35c 100644 --- a/espem/espem.h +++ b/espem/espem.h @@ -10,9 +10,9 @@ #include "main.h" #include "pzem_edl.hpp" #include "timeseries.hpp" - // Tasker object from EmbUI #include "ts.h" +#include "ESPAsyncWebServer.h" // Defaults #ifndef DEFAULT_WS_UPD_RATE diff --git a/espem/main.cpp b/espem/main.cpp index 172b457..368154b 100644 --- a/espem/main.cpp +++ b/espem/main.cpp @@ -2,7 +2,7 @@ * A code for ESP32 based boards to interface with PeaceFair PZEM PowerMeters * It can poll/collect PowerMeter data and provide it for futher processing in text/json format * - * (c) Emil Muratov 2017-2022 + * (c) Emil Muratov 2017-2024 * */ @@ -14,14 +14,18 @@ #include "interface.h" #include "log.h" +#define BAUD_RATE 115200 // serial debug port baud rate +#define HTTP_VER_BUFSIZE 256 -// PROGMEM strings // sprintf template for json version data static constexpr const char* PGverjson = "{\"ChipID\":\"%s\",\"Flash\":%u,\"SDK\":\"%s\",\"firmware\":\"" FW_NAME "\",\"version\":\"" FW_VERSION_STRING "\",\"git\":\"%s\",\"CPUMHz\":%u,\"RAM Heap size\":%u,\"RAM Heap free\":%u,\"PSRAM size\":%u,\"PSRAM free\":%u,\"Uptime\":%u}"; // Our instance of espem Espem *espem = nullptr; +// forward declaration +void wver(AsyncWebServerRequest *request); + // load configuration for espem and start it void setup_espem(); @@ -30,7 +34,7 @@ void setup_espem(); void setup() { #ifdef ESPEM_DEBUG_PORT - ESPEM_DEBUG.begin(BAUD_RATE); // start hw serial for debugging + ESPEM_DEBUG_PORT.begin(BAUD_RATE); // start hw serial for debugging #endif LOGI(C_EspEM, println, "Starting EspEM..."); diff --git a/espem/main.h b/espem/main.h index 49227e0..64ee145 100644 --- a/espem/main.h +++ b/espem/main.h @@ -2,7 +2,7 @@ * A code for ESP32 based boards to interface with PeaceFair PZEM PowerMeters * It can poll/collect PowerMeter data and provide it for futher processing in text/json format * - * (c) Emil Muratov 2018 - 2023 + * (c) Emil Muratov 2018 - 2024 * */ @@ -24,24 +24,7 @@ #define ESPEM_UI_VERSION 2 -#define BAUD_RATE 115200 // serial debug port baud rate -#define HTTP_VER_BUFSIZE 256 - #define WEBUI_PUBLISH_INTERVAL 20 // Sketch configuration -#include "globals.h" // EmbUI macro's for LOG -#include "config.h" #include "uistrings.h" // non-localized text-strings -#include - -// EMBUI -void create_parameters(); // декларируем для переопределения weak метода из фреймворка для WebUI -void sync_parameters(); - -// WiFi connection callback -void onSTAGotIP(); -// Manage network disconnection -void onSTADisconnected(); - -void wver(AsyncWebServerRequest *request); \ No newline at end of file From adde7ce09a805bb2432e83115ef548028a8a0acf Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 30 Oct 2024 01:47:04 +0900 Subject: [PATCH 4/6] try EmbUI from testing --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index ee9bd9d..4d4a7ee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,8 @@ src_build_unflags = -std=gnu++11 lib_deps = vortigont/pzem-edl @ ~1.2 - vortigont/EmbUI @ ~4.0.1 +; vortigont/EmbUI @ ~4.0.1 + https://github.com/vortigont/EmbUI.git#hdr ; https://github.com/vortigont/EmbUI.git#v3.1 lib_ignore = ESPAsyncTCP From 910457be0320de52fe17a0b10e2badb7bd96fd2e Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Wed, 30 Oct 2024 01:58:13 +0900 Subject: [PATCH 5/6] bump EmbUI dependency --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4d4a7ee..ffd7c89 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,8 +19,7 @@ src_build_unflags = -std=gnu++11 lib_deps = vortigont/pzem-edl @ ~1.2 -; vortigont/EmbUI @ ~4.0.1 - https://github.com/vortigont/EmbUI.git#hdr + vortigont/EmbUI @ ~4.0.2 ; https://github.com/vortigont/EmbUI.git#v3.1 lib_ignore = ESPAsyncTCP From fde77216d3bae4fec69ead3c82f5d442421df860 Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Fri, 8 Nov 2024 13:59:08 +0900 Subject: [PATCH 6/6] switch to IDF 5.3 core --- platformio.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index ffd7c89..e677ca3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,8 +19,8 @@ src_build_unflags = -std=gnu++11 lib_deps = vortigont/pzem-edl @ ~1.2 - vortigont/EmbUI @ ~4.0.2 -; https://github.com/vortigont/EmbUI.git#v3.1 + https://github.com/vortigont/EmbUI.git#idf5.3 +; vortigont/EmbUI @ ~4.0.2 lib_ignore = ESPAsyncTCP monitor_speed = 115200 @@ -53,11 +53,12 @@ all_serial1 = [esp32_base] extends = common +; Tasmota's platform, 2024.11.30 Tasmota Arduino Core 3.1.0.241030 based on IDF 5.3.1+ +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.11.30/platform-espressif32.zip ; Tasmota's platform, based on Arduino Core v3.0.4 -platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.08.11/platform-espressif32.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.08.11/platform-espressif32.zip ;platform = espressif32 @ 6.9.0 board = wemos_d1_mini32 -;upload_speed = 460800 monitor_filters = esp32_exception_decoder ;build_flags =