From 6292e73135fabc854a65b91e42da64074c42ea61 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Mon, 29 Jan 2024 00:49:52 +0100 Subject: [PATCH] Pending queue (#210) Pretty complex change here: pending images/commands are now queued. A command (like LED flasher) will not overwrite a pending image anymore. Also, sending multiple preloaded images is possible. Also works (at least, as far as I could test) in combination with Multi AP and mirroring tags ('display a copy' content type). It you want to test this: don't forget to upload the changed files in /www (the pending icon is now displaying the amount of queued messages). Timing improvements will follow later (only one message can be transmitted every checkin. If multiple messages are queued, at this moment, you have to wait until the next checkin which takes 40-60 sec). This comes also with the advantage of better stability if you upload multiple images to the same tag in succession. Before queuing, if was possible to replace the image between sending the pending message and the image transfer to the tag, causing md5 mismatches and instability. Solves #47 --- ESP32_AP-Flasher/data/tagtypes/31.json | 2 +- ESP32_AP-Flasher/data/www/edit.html.gz | Bin 6393 -> 6398 bytes ESP32_AP-Flasher/data/www/index.html.gz | Bin 5171 -> 5177 bytes ESP32_AP-Flasher/data/www/main.css.gz | Bin 3454 -> 3453 bytes ESP32_AP-Flasher/data/www/main.js.gz | Bin 13627 -> 13639 bytes ESP32_AP-Flasher/include/commstructs.h | 13 +- ESP32_AP-Flasher/include/newproto.h | 20 ++ ESP32_AP-Flasher/include/serialap.h | 4 +- ESP32_AP-Flasher/include/tag_db.h | 5 +- ESP32_AP-Flasher/include/tagdata.h | 6 +- ESP32_AP-Flasher/platformio.ini | 3 + ESP32_AP-Flasher/src/contentmanager.cpp | 10 +- ESP32_AP-Flasher/src/flasher.cpp | 4 +- ESP32_AP-Flasher/src/ips_display.cpp | 2 +- ESP32_AP-Flasher/src/main.cpp | 4 + ESP32_AP-Flasher/src/makeimage.cpp | 2 - ESP32_AP-Flasher/src/newproto.cpp | 328 ++++++++++++++++-------- ESP32_AP-Flasher/src/serialap.cpp | 30 +-- ESP32_AP-Flasher/src/storage.cpp | 1 - ESP32_AP-Flasher/src/tag_db.cpp | 9 +- ESP32_AP-Flasher/src/tagdata.cpp | 5 +- ESP32_AP-Flasher/src/web.cpp | 27 +- ESP32_AP-Flasher/wwwroot/edit.html | 2 +- ESP32_AP-Flasher/wwwroot/index.html | 5 +- ESP32_AP-Flasher/wwwroot/main.css | 3 +- ESP32_AP-Flasher/wwwroot/main.js | 87 +++---- 26 files changed, 357 insertions(+), 215 deletions(-) diff --git a/ESP32_AP-Flasher/data/tagtypes/31.json b/ESP32_AP-Flasher/data/tagtypes/31.json index 02e0a984..ca80ffa7 100644 --- a/ESP32_AP-Flasher/data/tagtypes/31.json +++ b/ESP32_AP-Flasher/data/tagtypes/31.json @@ -13,7 +13,7 @@ }, "shortlut": 0, "options": ["button", "led"], - "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20], + "contentids": [ 22, 23, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 26], "template": { "1": { "weekday": [148, 5, "Signika-SB.ttf", 60], diff --git a/ESP32_AP-Flasher/data/www/edit.html.gz b/ESP32_AP-Flasher/data/www/edit.html.gz index e397af05980958e862516a780d58a2f057900db2..4af04e1a1187ad92eed6ee735dbf796596c30ed7 100644 GIT binary patch delta 3115 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 F001se9>V|t delta 3119 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 J`1sRZ001EyA)5dI diff --git a/ESP32_AP-Flasher/data/www/index.html.gz b/ESP32_AP-Flasher/data/www/index.html.gz index 18e26bb42a9560282703c3ab6cd6a56d9c9d42d8..20623cacd5fe29044e8b182175fd51d8ec3e2f77 100644 GIT binary patch delta 5131 zcmV+m6!hz}D7h$*U4PaMg+igK0I>6ii<|!F{q2Cf8eLy?|NiefvvBUgCq6Oi!qc

~mO0{1?QTQK?vX7KM}AaV4eV(>Xh< zJaV=OgbXvd3+{%qGnYMbht>2;m2jVjoO(6oP>-E8t7J|;^M83XPa7h|q<*7gXlPVO zU5oy#6Y|hw-J5{l*hDwOR(BL(gMKKBrSw zfArlw(`x8V=zk-=Vq2wKt8M2GUpylg2}ent*6S0{tJ<3iG4&V?xY~2X9EEB9^Mua1 zw>-P1A(Noo=fmZEEIjr6CJKQ%b5BK}ey=X3v#@&k`s%9s@~(IBFRlIRexp%^|4eT> zb!!~(I2P_w3x`dhFchcb@<-x$RH?IyC#KMWf}sF-gMa8#^PowXC#^aX(y<~&4bSon z;F}uS5$K=EiUnAOK+P#IAk*ubGQF+|+iMtY8q!)wSM&7kbPKkfWpyQ{Q(#c(;@tnJd>#ZW z%yP>;qgB_}&AQuB0rd%Y&nhzVRS2R~x;ex=5DhepBRm1t|EE7Tn~nYb7vQ!4pi9=^ zDqT-d7o@Zh>y)YWC3WU<@NP+8c?g}HfIqX^ntyPQjiWFW{tkV0C&B}-@KM10@6_qK z9x`*kZYRI-F4~pgTV{ZA(|py@EV%EAMf{EV3Ok-m=5&RY& z2k^5jyyPDAuIIbMnOOYz!w)|tJ_bPDnCcFb(Y_jmhY>pcW2=2KX&qLyXm0FRhA=dd zdVh`XKOz3inK9?#qr;`+v4mqibaU)CklDr&UUA6JDy<9?yU20{Cb74-w}}rt%l{+g z936qMEF&KJ!fky|p}>u*avA~;+ZPcGeG%iv6Ey+$jQZ^2B0H!|FQ~bH zn2rq^iU&oD3o(_)%-nz&&XD>@J&3CeolT)t%-%rX1R)o`%B2SBSm4W4iYOrCWiB$z z!#(S8KZrt`vPlS`M8&4F8oNRssec#2n-N?3!pFD`GE?S}iI7RtTlm|l>-Ln>H{d%L zF7qsN9AnJQ^Zb)mSOkd%{~mH!YeM=~n#8_x(j={Y^YA@n(LTbNQE+pyYT~yQVdB=l zdDtn;TK=ERs&$A1cePkGk5 z<(fL*fvp}q@DZV6%(nt0uX!ob<|}w2`1rj9@*%a00v9tWJJF$Gb)xI0xA2!3(5IM_ z@nNOK*9I~cpW_}_-y8SA6Aq21f_Yxiq-oP1U#-Fd86=yFM1A%pFGbvFKEIb7afD?h zNnZ1^q|F!uxdfJ{?0zLzXEI8RK)ysa9GZOsJHrA>X~f(=U) zq@rOaIx+8?t2I*xOL9q6W9v(mTvjO+6}Nw_ z7v>!Rk}2Uu7^Cclau}GQy9*|}_U-`7c+sse=)<&$@Io-_4tL5WvwyYO{Fl~{ZAILc zDT&V(WDapOoif4|Sx}C(J2X5J&UC~{LO}uu_QA(ix_>fd5-O!wNIso~FY?mlD@9cL zs~kr&;DS_VU>m-G6&w_#R}#&W5>EhobOBgoJ2^f+ev#77cf5qfZw}4la$?EQe7qW( z>*yM+SrgOR0E!i}t$%@w*QwcQnDWVsF;(nvzk1j3Ufx`Ivq7)-OYio&*L(ZtUz@BQ z9=Y{N`{dPK!+jjL>aRW@yQBN&%@yB|u*WR1oj+*af(Ejk}2f6?5UH`p*^IJaF}<^ll{GB<7Dq} z@9?lw4>rQZ;pr`T;OJBQ9AJKy>T-2qW|$)63WU#WO?xvr)Zf(6d3Lwi-U+&uGXW?k zTLarr87Pt>$u(LBE08OGB%?yLc!czq=gPwq2FgUAsDH#H1eeB$g~^q1gRk;Lx&521 z7WYob2f}?4NHK*)OvQ{h)68tm6xeXOMMVr!rnb;YJCvEJpw-EAM;c_kfHm+KG_zBi zPQsv=5vUz(@bzFYAjgf?Uh`mYpEw&6#}1e$b25w#YBD@RmnLHO(*R$s;UU4JQ7Aff z^ZhpXCV%*=X0s6L0H6+vpjr(u$VMU5fic?xs6&7{EQC4&sH0UVm=anAUosvZbH{qo z_rY)~-Mg3PeSs}1R;2Nk~OeUrD-T22MuyzwwSqRnyHKkT@P)xUZ0)&r> z3AYZkoa+R?W(2}Xm{s8Da`Xk-UqRkFIm##>&40Lr@(=e*D*w1rPUo%CI!_1lTPfe8 z{xpI}A}OVjxTVnU6U-7GY4DZ|d`!n?h3cqu-(Hco41!bO;HpBkRJs=fa>1fdIfcYw z6f-8f(pDjr!88Z5V4^(6 z0yHFzj0p~tB!I|AxR7x{pazMMtzapF0S5aVr29obdi()GP25;y#N`SChUHItL?=@A zs0D;e%;6Kx%svQ;Vb5?`-k4AARkm0*27e&Fv&8yh24}ZTCpaYx8p3-CJqyW?`weJy zekx%g2V~Cu2(Sw0fObR{y3j}gxZNmA`VdJI+z9>(yLdez$+jP6&^RWT)9{d0i7Wgc zLOV1N6iI60xhtbovfz*ajbY3sMA0rWS`#qAw+>+-8O)AQQ`AWmDBLG@kcb;eG=Gvf z#@jWnc|zNl2){UJ%2>d1UD-QSXg&!^P8NkCQ0e>#iI0mIOfb-m&N)q;LZO$elM=cfNf--_tS3YjzJfUfenm>|0_iWDH<}Xq;B@kiS-4m)im&K6##>Q=&0p-+ zBt*_rzXkx4QwIiw$rqI_bU2^MY=0u$S|hy4JV%lA39R!32b{A7(IpENJ29}-Kv#q& zWGN!Dps?71cIVW`R`^(IjaQAk0ku=ehVh^hQA-6$%}V~Z!CHYQxal18V$0M>UuSQn zZ1@X|@}J0RRz{!+eYq#Fm^V$zXWYjbCFHWIn;<>c^IQ0yISGN)4{N_OFn@0J|J0L= zx=W;vC|Fa%Lek!|OCe#cSmY_G5dS`)y;5I6h78;_WvuP#VFJ5!Xr!3o;4|jx$uh<< z`wVl!^3hu|&?M)W=Gs}L*BQ+RIy$CfZ>e)IG!BC`i9(oI+gBG*N2PlOfAiWXSYI?& ziN&RiBf@Q#2j-qyze`=0#DA(O5i((Y56hURddPCv>4=Sm2=y+(7cUg=VBN+Vm#^kL zBmq`9wyAOfpcX)51?E8MEavcEI)&RM-6Rz!5uKSj2w+D{U z2S58#GUgN$e|bH6-hX%vw3`X-<|^%0O#33j)6!M)S~5>Z0N+&dSIkq~f8bSe$^BCF z-vRx`yFp3muecfukp31z(r8M(a=JM$OC6$Oqz-xqM|>_h9$U^7h|8)fX4Ou3fH(Mg z$seoa{z2kV==F(Mj}3XdY2Ocfq)z$}HMw-Vfxqg#!z|_%U4Kx{Nh#~>{X86<^nSh= zVHuXH2U-s8b~rk|MWkDb>7r8(>v}jkx<;&PEb4EC#UU3A3M;1hZ240 zx!{Q)V=7G?oEVi0?w1rlqs-<`L0&;XD#JpmZ$BW}*4qzA7pC~h-hPhkzG0J)&5Zlk z*^uHpru!RIN~Jpz4@*%z_^;1vFpJ*M2k0m*H!A)O`T>7HL4#f-FuHxe@r1^kyUan{HcJc^i@iX=RaooLlK2D(wjqS>|9Kld*c3$BRCnB9?#~ZX z&t>H;(SQ3g)#&X0x;c@XY^M)te7H(in=u)$~R_O{-=J+^djk7&mIFcz1T8;F=_K?lRzcB_m9C>G)o z6 z(_$iT+S938J@8za)fAq1a^-`mdJP=CzL_YNef8eXk@*vP}+$YJhN;1s|19K zMoZ2XWnF_lu3%>?04DbiGQ=`7+Bon#O|q}+V~M3_hEQkUG0AtPu9VzHv3onG`u@Vo z8M^FIn30GpaC%>XD$5ElyjqEBjKnOsCF@vCbp zSyjr)_~!&qVhHEZ%A6A~?-2T%V1EjimHwgbgK-hY``7x18>HTPt~%WSu(vj>#Boee z>BdibM@)tB+L-N1UM1^i%d$u5cHSz;oDo(c4lYbrI3S)TC7?Ca`% z&^(Ugr8j5v$#|07pV@^5vY2EL?;_#nZg2(bf2E6mcf+}i1@{!Wg#vw-Agiau-=%)dM9i4g zj%mD)Y1X)MMUk{5vVU0~JAibKR@>~rhjy#Hd;YH8L)yFl722zo*I9M&KL zj`Yt)t;Bf*1HBhS$Lzr2l+dxhnFMVZ#!dCHpHs4!aor@O%j79(2A>IlahFUbT>?6F tUA-;cctP-vK}~b9#LaJknVSyyJ5u)7qx4TonO`3JUq*eEj0BTR007Yc5-$J% delta 5124 zcmV+f6#MJBD6=S#U4Is;3WY+Us!(9(4;MH6(fiv0c{RGe>i+%Tb!Oq*gHL>7)P<*K zojD6BnT26cW4}fG@vPzqKV*Jb3zq?_5c_sk3EAhcj{RQ{XGW!B;aL<;YR8oni%;k5 ztn$d&A`mjn^j&Z_oSnJskvpuWU#f)rJml1?DTjLOtXU;<`hS_vqj}m8DJJzB9YaH- zLh73JXPuCT9_!u&%qN3e8Zb$&xc@+Uj>D89w}Sg&r*41{+`}&=^Uf+NTzX8+7z5^J zl1o_?%8z{n2;#Osl~)p^x~AZI$hW;frU)_CT?# z(|UaZf>L`^A%CVGqXAcYj+i4Ct$&`-Iro-l*ED1jr1gBboR5X4p5H_vU}x^B2-NS@ z#dH={FJE6>RbSrqF8-ypU)^sss_>r~MyGCP8y?5PU25jAiAj8X)^YhGaXhNjS;Z4m z;J!e%0B<__)I4Yu=IL4I549Qz=~$6kEpEyXfNyH37=IvmCMyNkej!8t#xTu?O|3o6}87r#pReJY`lPP24IbgC*{#XRrZ zk)T1ALRRcR$^lXzL$P2oPY@*jv~jU{-~nuIaJCJ|j-^;A78BEylc{~$Xh9JsJSWp3 z3q$T3xqq6cZzo&O?JTJ)F`WW|N*5RXN9FS%U}2V8?isbZzOL5YjtZzxxO-NSk*`8v zrP9rH%mdLt!#Khdp#6XPW3$=V-+$4mBS4p|!Bx7Rpe}G}A=WWdYfI|PK7eIe%oU> z6^r;A^A!#}SpE@CbWKS1QtDR#^@u^R} zk1khWZhYp1%q_9P_?!WSl8ej$7jjZ8BG@B0N$Vee{L8;)H{u_(lk>yB{pF`U4e{f* zvC$b*ocKw_qN2vd3bcz=PGG%YqftuPC=gWYK-4o^S-iF8x5L=w+`b#;Jff-Di+@wU zde-jtBPpSSw`sQrSlw?eQ?XD zvV7ZT?GrLL&%ODs+ry7>_Vm3N3S!4w^Ao@2 z*-2XM!}sFFI_HV1n737Bv45=@C%Uw%Z``n9i-J%z%y=i}eRH;E>R?JP@oH>-sgfIg zk~#pNh0jr%4)q_Y%3v|gYjTSrtaaTEV65Wyul2^f13)q*ya;`i-B1n#HFS5uWcS`3 zU>PgA6$WjXRuNtZX3gPFzGOBxoBz@(vQ3BEG$rxbg3Q5>rc*|^B7Y0YF?WZGN5Yx* zI7ui-0M0(x*h=?LCQU-66bs3xv+zZpn|!8-N`IB&Xa-ae>kM?m7qEkafb>qHX;R_| zAde;hlWZr)$Hy;H-1&i*(D==+d7Mrx>6(vMU32YSgEnhqS{i^6f(q%F&Fj=0Gfes9 z#h5B~xL>_%crR}*ynorC*ZZY+d)@23{qwI))((%{`lNmG>aO8Fj$8FtpO4+qee>pu z??>{t!}*8#hXDRe>h=1fm;cv)b#UGTY`sPA%C!8_JI9Z^^M`Bi&2LA|_cCaI`16C@ zfA_Bc-oN=Uoc{9ba&Y~A@uvS<(|+Z*G5m{`<}F zp+C4EUXJIZUvF=N@u$b>2+-dw&d!VkR#ec+W24PO2`PxPT{e!UiP3B34O|M)#h9E> z&m#*qRsviwKq={D!V}MDt8I%sP0cEEnmq~CB@cg>Uiy=LQN?{M$%uu~5D3 zp$?4N7Jon;0@Pt4)Db`(twO<)&?5Mf`S6%K)|Df-MZ! zK-^+7DJAd5KNf+tn<&acuqKEp)rx~+yv-9pd{m6Mb)flNNBA`}5Kh9Z07sXjFOdF9 z=dFvQjPOy7O9=mPzohVwE9GR~DlPMLGQXAZJ%8#?Bk&}WR2qp}3iUqeS;8U>-jacj z;n=KD9F^|dEAp0sa|#q(RVbE9_hLXUSQIL!5IM};5~iAU!D?LSI%0DE_P1^b$(9(WNf=-psQ2H+n2 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 diff --git a/ESP32_AP-Flasher/data/www/main.css.gz b/ESP32_AP-Flasher/data/www/main.css.gz index d643fbff8005c7b18de1334168703199bc52b87f..7b94706c12b57083c35a4af2d61449e8bc518a7e 100644 GIT binary patch delta 3375 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 F000F7YhVBX delta 3357 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 literal 13627 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 N{|^$b*c!Ih000gLiuC{h diff --git a/ESP32_AP-Flasher/include/commstructs.h b/ESP32_AP-Flasher/include/commstructs.h index 9193dbc3..d39b9db8 100644 --- a/ESP32_AP-Flasher/include/commstructs.h +++ b/ESP32_AP-Flasher/include/commstructs.h @@ -1,4 +1,7 @@ -#include +#ifndef NEWPROTO_H +#define NEWPROTO_H + +#include #pragma pack(push, 1) #include "../../oepl-definitions.h" @@ -25,7 +28,7 @@ struct APlist { #define SYNC_USERCFG 1 #define SYNC_TAGSTATUS 2 #define SYNC_DELETE 3 -#define SYNC_VERSION 0xAA00 +#define SYNC_VERSION 0xAA01 struct TagInfo { uint16_t structVersion = SYNC_VERSION; @@ -34,14 +37,16 @@ struct TagInfo { char alias[32]; uint32_t lastseen; uint32_t nextupdate; - bool pending; + uint16_t pendingCount; uint32_t expectedNextCheckin; uint8_t hwType; uint8_t wakeupReason; uint8_t capabilities; uint16_t pendingIdle; uint8_t contentMode; + uint8_t reserved[8]; } __packed; +#pragma pack(pop) -#pragma pack(pop) \ No newline at end of file +#endif // NEWPROTO_H \ No newline at end of file diff --git a/ESP32_AP-Flasher/include/newproto.h b/ESP32_AP-Flasher/include/newproto.h index 39c07f88..61dc3c95 100644 --- a/ESP32_AP-Flasher/include/newproto.h +++ b/ESP32_AP-Flasher/include/newproto.h @@ -1,4 +1,14 @@ #include +#include + +#include "commstructs.h" + +struct PendingItem { + struct pendingData pendingdata; + char filename[50]; + uint8_t* data; + uint32_t len; +}; extern void addCRC(void* p, uint8_t len); extern bool checkCRC(void* p, uint8_t len); @@ -23,3 +33,13 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending); void refreshAllPending(); void updateContent(const uint8_t* dst); void setAPchannel(); + +void enqueueItem(const PendingItem& item); +bool dequeueItem(const uint8_t* targetMac); +bool dequeueItem(const uint8_t* targetMac, const uint64_t dataVer); +uint16_t countQueueItem(const uint8_t* targetMac); +extern PendingItem* getQueueItem(const uint8_t* targetMac); +extern PendingItem* getQueueItem(const uint8_t* targetMac, const uint64_t dataVer); +void checkQueue(const uint8_t* targetMac); +bool queueDataAvail(struct pendingData* pending); +uint8_t* getDataForFile(fs::File& file); diff --git a/ESP32_AP-Flasher/include/serialap.h b/ESP32_AP-Flasher/include/serialap.h index d04e25d1..0c44e3ac 100644 --- a/ESP32_AP-Flasher/include/serialap.h +++ b/ESP32_AP-Flasher/include/serialap.h @@ -18,7 +18,7 @@ struct APInfoS { uint8_t channel; uint8_t mac[8]; uint8_t power; - uint8_t pending; + uint8_t pendingBuffer; uint8_t nop; }; @@ -34,4 +34,4 @@ bool sendChannelPower(struct espSetChannelPower* scp); void rxSerialTask2(void* parameter); void APTagReset(); bool bringAPOnline(); -void setAPstate(bool isOnline, uint8_t state); \ No newline at end of file +void setAPstate(bool isOnline, uint8_t state); diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h index 4dbf78b6..bc6f1422 100644 --- a/ESP32_AP-Flasher/include/tag_db.h +++ b/ESP32_AP-Flasher/include/tag_db.h @@ -14,7 +14,7 @@ class tagRecord { public: - tagRecord() : mac{0}, version(0), alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0), invert(0) {} + tagRecord() : mac{0}, version(0), alias(""), lastseen(0), nextupdate(0), contentMode(0), pendingCount(0), md5{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), apIp(IPAddress(0, 0, 0, 0)), pendingIdle(0), hasCustomLUT(false), rotate(0), lut(0), tagSoftwareVersion(0), currentChannel(0), dataType(0), filename(""), data(nullptr), len(0), invert(0) {} uint8_t mac[8]; uint8_t version; @@ -22,9 +22,8 @@ class tagRecord { uint32_t lastseen; uint32_t nextupdate; uint8_t contentMode; - bool pending; + uint16_t pendingCount; uint8_t md5[16]; - uint8_t md5pending[16]; uint32_t expectedNextCheckin; String modeConfigJson; uint8_t LQI; diff --git a/ESP32_AP-Flasher/include/tagdata.h b/ESP32_AP-Flasher/include/tagdata.h index 0fed8e90..feb69fc0 100644 --- a/ESP32_AP-Flasher/include/tagdata.h +++ b/ESP32_AP-Flasher/include/tagdata.h @@ -3,6 +3,8 @@ /// @brief Custom tag data parser and helpers #pragma once +#ifndef SAVE_SPACE + #include #include @@ -137,4 +139,6 @@ template , bool> = t inline T bytesTo(const uint8_t *data, int length) { return T(data, data + length); } -} // namespace TagData \ No newline at end of file +} // namespace TagData + +#endif \ No newline at end of file diff --git a/ESP32_AP-Flasher/platformio.ini b/ESP32_AP-Flasher/platformio.ini index 90b402dd..927c1b58 100644 --- a/ESP32_AP-Flasher/platformio.ini +++ b/ESP32_AP-Flasher/platformio.ini @@ -57,6 +57,7 @@ build_flags = -D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y -D HAS_RGB_LED -D BOARD_HAS_PSRAM + -D SAVE_SPACE -D POWER_NO_SOFT_POWER -D FLASHER_AP_SS=11 -D FLASHER_AP_CLK=9 @@ -93,6 +94,7 @@ build_flags = -D CONFIG_SPIRAM_USE_MALLOC=1 -D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y -D BOARD_HAS_PSRAM + -D SAVE_SPACE -D FLASHER_AP_SS=38 -D FLASHER_AP_CLK=40 -D FLASHER_AP_MOSI=39 @@ -184,6 +186,7 @@ build_flags = ${env.build_flags} -D CORE_DEBUG_LEVEL=0 -D SIMPLE_AP + -D SAVE_SPACE -D FLASHER_AP_SS=5 -D FLASHER_AP_CLK=18 -D FLASHER_AP_MOSI=23 diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index 7d3b3ac6..39134693 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -51,7 +51,7 @@ void contentRunner() { // taginfo->wakeupReason = 0; } - if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pending == false) { + if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pendingCount == 0) { int16_t minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60; if (minutesUntilNextUpdate > config.maxsleep) { minutesUntilNextUpdate = config.maxsleep; @@ -240,10 +240,7 @@ void drawNew(const uint8_t mac[8], tagRecord *&taginfo) { jpg2buffer(configFilename, filename, imageParams); } else { - filename = "/current/" + String(hexmac) + ".pending"; - if (!contentFS->exists(filename)) { - filename = "/current/" + String(hexmac) + ".raw"; - } + filename = "/current/" + String(hexmac) + ".raw"; if (contentFS->exists(filename)) { prepareDataAvail(filename, imageParams.dataType, imageParams.lut, mac, cfgobj["timetolive"].as(), true); wsLog("Resending image " + filename); @@ -944,8 +941,6 @@ void drawForecast(String &filename, JsonObject &cfgobj, const tagRecord *taginfo int getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams, String MAC) { // https://images.klari.net/kat-bw29.jpg - Storage.begin(); - HTTPClient http; logLine("http getImgURL " + URL); http.begin(URL); @@ -1363,7 +1358,6 @@ bool getCalFeed(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgPa void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) { #ifdef CONTENT_QR TFT_eSprite spr = TFT_eSprite(&tft); - Storage.begin(); const char *text = qrcontent.c_str(); QRCode qrcode; diff --git a/ESP32_AP-Flasher/src/flasher.cpp b/ESP32_AP-Flasher/src/flasher.cpp index 39256e90..e6562188 100644 --- a/ESP32_AP-Flasher/src/flasher.cpp +++ b/ESP32_AP-Flasher/src/flasher.cpp @@ -64,11 +64,11 @@ uint8_t pinsExt[] = {FLASHER_EXT_CLK, FLASHER_EXT_MISO, FLASHER_EXT_MOSI, FLASHE flasher::flasher() { zbs = new ZBS_interface; - Storage.end(); + // Storage.end(); } flasher::~flasher() { delete zbs; - Storage.begin(); + // Storage.begin(); } static uint8_t validatePowerPinCount(int8_t *powerPin, uint8_t pinCount) { diff --git a/ESP32_AP-Flasher/src/ips_display.cpp b/ESP32_AP-Flasher/src/ips_display.cpp index a9f987ee..380a64bb 100644 --- a/ESP32_AP-Flasher/src/ips_display.cpp +++ b/ESP32_AP-Flasher/src/ips_display.cpp @@ -112,7 +112,7 @@ void yellow_ap_display_loop(void) { } if (millis() - last_update >= 1000) { tagRecord* tag = tagDB.at(tftid); - if (tag->pending) { + if (tag->pendingCount > 0) { String filename = tag->filename; fs::File file = contentFS->open(filename); if (!file) { diff --git a/ESP32_AP-Flasher/src/main.cpp b/ESP32_AP-Flasher/src/main.cpp index b0344a56..a5a5c3fb 100644 --- a/ESP32_AP-Flasher/src/main.cpp +++ b/ESP32_AP-Flasher/src/main.cpp @@ -127,7 +127,11 @@ void setup() { #ifdef HAS_RGB_LED rgbIdle(); #endif + +#ifndef SAVE_SPACE TagData::loadParsers("/parsers.json"); +#endif + if (!loadDB("/current/tagDB.json")) { Serial.println("unable to load tagDB, reverting to backup"); loadDB("/current/tagDB.json.bak"); diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp index 3bd5af55..c80e5ec3 100644 --- a/ESP32_AP-Flasher/src/makeimage.cpp +++ b/ESP32_AP-Flasher/src/makeimage.cpp @@ -18,7 +18,6 @@ bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) } void jpg2buffer(String filein, String fileout, imgParam &imageParams) { - Storage.begin(); TJpgDec.setSwapBytes(true); TJpgDec.setJpgScale(1); TJpgDec.setCallback(spr_output); @@ -227,7 +226,6 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) { long t = millis(); - Storage.begin(); #ifdef YELLOW_IPS_AP extern uint8_t YellowSense; diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp index cc1fd57e..9f72495a 100644 --- a/ESP32_AP-Flasher/src/newproto.cpp +++ b/ESP32_AP-Flasher/src/newproto.cpp @@ -6,7 +6,11 @@ #include #include -#include "commstructs.h" +#include +#include +#include +#include + #include "serialap.h" #include "settings.h" #include "storage.h" @@ -19,6 +23,8 @@ extern uint16_t sendBlock(const void* data, const uint16_t len); extern UDPcomm udpsync; +std::vector pendingQueue; +std::mutex queueMutex; void addCRC(void* p, uint8_t len) { uint8_t total = 0; @@ -44,6 +50,7 @@ uint8_t* getDataForFile(fs::File& file) { file.readBytes((char*)ret, fileSize); } else { Serial.printf("malloc failed for file with size %d\n", fileSize); + wsErr("malloc failed while reading file"); util::printHeap(); } return ret; @@ -61,7 +68,9 @@ void prepareCancelPending(const uint8_t dst[8]) { return; } clearPending(taginfo); - + while (dequeueItem(dst)) { + }; + taginfo->pendingCount = countQueueItem(dst); wsSendTaginfo(dst, SYNC_TAGSTATUS); } @@ -74,7 +83,7 @@ void prepareIdleReq(const uint8_t* dst, uint16_t nextCheckin) { pending.attemptsLeft = 10 + config.maxsleep; Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X NOP\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); - sendDataAvail(&pending); + queueDataAvail(&pending); } } @@ -92,26 +101,35 @@ void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, const uint8 wsErr("no memory allocation for data"); return; } + + uint8_t md5bytes[16]; + { + MD5Builder md5; + md5.begin(); + md5.add(data, len); + md5.calculate(); + md5.getBytes(md5bytes); + } + memcpy(taginfo->data, data, len); - taginfo->pending = true; + taginfo->pendingCount++; taginfo->len = len; taginfo->pendingIdle = 0; taginfo->filename = String(); taginfo->dataType = dataType; - memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataSize = len; pending.availdatainfo.dataType = dataType; pending.availdatainfo.nextCheckIn = 0; - pending.availdatainfo.dataVer = millis(); + pending.availdatainfo.dataVer = *((uint64_t*)md5bytes); pending.attemptsLeft = 10; if (taginfo->isExternal) { udpsync.netSendDataAvail(&pending); } else { - sendDataAvail(&pending); + queueDataAvail(&pending); } wsSendTaginfo(dst, SYNC_TAGSTATUS); @@ -137,7 +155,6 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume } filename = "/" + filename; - Storage.begin(); if (!contentFS->exists(filename)) { wsErr("File not found. " + filename); @@ -164,49 +181,30 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume file.close(); uint16_t attempts = 60 * 24; - if (memcmp(md5bytes, taginfo->md5pending, 16) == 0) { - wsLog("new image is the same as current or already pending image. not updating tag."); - wsSendTaginfo(dst, SYNC_TAGSTATUS); - if (contentFS->exists(filename) && resend == false) { - contentFS->remove(filename); - } - return true; - } - if (dataType != DATATYPE_FW_UPDATE) { char dst_path[64]; - sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); + sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X_%lu.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], millis()); if (contentFS->exists(dst_path)) { contentFS->remove(dst_path); } if (resend == false) { contentFS->rename(filename, dst_path); filename = String(dst_path); - wsLog("new image: " + String(dst_path)); + wsLog("new image: " + filename); } time_t now; time(&now); taginfo->pendingIdle = nextCheckin * 60 + 60; clearPending(taginfo); - taginfo->filename = filename; - taginfo->len = filesize; - taginfo->dataType = dataType; - taginfo->pending = true; - memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes)); } else { - char dst_path[64]; - sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); - contentFS->remove(dst_path); - sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); - contentFS->remove(dst_path); wsLog("firmware upload pending"); clearPending(taginfo); - taginfo->filename = filename; - taginfo->len = filesize; - taginfo->dataType = dataType; - taginfo->pending = true; } + taginfo->filename = filename; + taginfo->len = filesize; + taginfo->dataType = dataType; + taginfo->pendingCount++; struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); @@ -219,7 +217,7 @@ bool prepareDataAvail(String& filename, uint8_t dataType, uint8_t dataTypeArgume checkMirror(taginfo, &pending); if (taginfo->isExternal == false) { Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X TYPE 0x%02X\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], pending.availdatainfo.dataType); - sendDataAvail(&pending); + queueDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); } @@ -238,13 +236,11 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { case DATATYPE_IMG_DIFF: case DATATYPE_IMG_RAW_1BPP: case DATATYPE_IMG_RAW_2BPP: { - Storage.begin(); - char hexmac[17]; mac2hex(pending->targetMac, hexmac); - String filename = "/current/" + String(hexmac) + ".pending"; - String imageUrl = "http://" + remoteIP.toString() + filename; - wsLog("GET " + imageUrl); + String filename = "/current/" + String(hexmac) + "_" + String(millis()) + ".pending"; + String imageUrl = "http://" + remoteIP.toString() + "/getdata?mac=" + String(hexmac); + wsLog("prepareExternalDataAvail GET " + imageUrl); HTTPClient http; logLine("http prepareExternalDataAvail " + imageUrl); http.begin(imageUrl); @@ -293,8 +289,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { taginfo->filename = filename; taginfo->len = filesize; taginfo->dataType = pending->availdatainfo.dataType; - taginfo->pending = true; - memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes)); + taginfo->pendingCount++; break; } case DATATYPE_NFC_RAW_CONTENT: @@ -316,7 +311,7 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { WiFiClient* stream = http.getStreamPtr(); stream->readBytes(taginfo->data, len); taginfo->dataType = pending->availdatainfo.dataType; - taginfo->pending = true; + taginfo->pendingCount++; taginfo->len = len; } } @@ -327,16 +322,15 @@ void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { return; } } - // taginfo->contentMode = 12; - // taginfo->nextupdate = 3216153600; checkMirror(taginfo, pending); - sendDataAvail(pending); + queueDataAvail(pending); wsSendTaginfo(pending->targetMac, SYNC_NOSYNC); } } void processBlockRequest(struct espBlockRequest* br) { + uint32_t t = millis(); if (config.runStatus == RUNSTATUS_STOP) { return; } @@ -345,40 +339,38 @@ void processBlockRequest(struct espBlockRequest* br) { return; } - tagRecord* taginfo = tagRecord::findByMAC(br->src); - if (taginfo == nullptr) { - if (config.lock) return; + PendingItem* queueItem = getQueueItem(br->src, br->ver); + if (queueItem == nullptr) { prepareCancelPending(br->src); Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\n", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0]); return; } - - if (taginfo->data == nullptr) { - // not cached. open file, cache the data - fs::File file = contentFS->open(taginfo->filename); + if (queueItem->data == nullptr) { + fs::File file = contentFS->open(queueItem->filename); if (!file) { Serial.print("No current file. Canceling request\n"); prepareCancelPending(br->src); return; } - taginfo->data = getDataForFile(file); + queueItem->data = getDataForFile(file); + Serial.println("Reading file " + String(queueItem->filename) + " in " + String(millis() - t) + "ms"); file.close(); } // check if we're not exceeding max blocks (to prevent sendBlock from exceeding its boundary) - uint8_t totalblocks = (taginfo->len / BLOCK_DATA_SIZE); - if (taginfo->len % BLOCK_DATA_SIZE) totalblocks++; + uint8_t totalblocks = (queueItem->len / BLOCK_DATA_SIZE); + if (queueItem->len % BLOCK_DATA_SIZE) totalblocks++; if (br->blockId >= totalblocks) { br->blockId = totalblocks - 1; } - uint32_t len = taginfo->len - (BLOCK_DATA_SIZE * br->blockId); + uint32_t len = queueItem->len - (BLOCK_DATA_SIZE * br->blockId); if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE; - uint16_t checksum = sendBlock(taginfo->data + (br->blockId * BLOCK_DATA_SIZE), len); + uint16_t checksum = sendBlock(queueItem->data + (br->blockId * BLOCK_DATA_SIZE), len); char buffer[150]; - sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X block request %s block %d, len %d checksum %u\0", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0], taginfo->filename.c_str(), br->blockId, len, checksum); + sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X block request %s block %d, len %d checksum %u\0", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0], queueItem->filename, br->blockId, len, checksum); wsLog((String)buffer); - Serial.printf("filename.c_str(), br->blockId, len, checksum); + Serial.printf("filename, br->blockId, len, checksum); } void processXferComplete(struct espXferComplete* xfc, bool local) { @@ -395,27 +387,34 @@ void processXferComplete(struct espXferComplete* xfc, bool local) { Serial.printf("src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); } - char src_path[64]; - char dst_path[64]; - sprintf(src_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); - sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); - if (contentFS->exists(dst_path) && contentFS->exists(src_path)) { - contentFS->remove(dst_path); - } - if (contentFS->exists(src_path)) { - if (config.preview) { - contentFS->rename(src_path, dst_path); - } else { - contentFS->remove(src_path); - } - } - time_t now; time(&now); + + char dst_path[64]; + sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); + + uint8_t md5bytes[16]; + PendingItem* queueItem = getQueueItem(xfc->src); + if (queueItem != nullptr) { + if (contentFS->exists(dst_path) && contentFS->exists(queueItem->filename)) { + contentFS->remove(dst_path); + } + if (contentFS->exists(queueItem->filename)) { + if (config.preview && (queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_2BPP || queueItem->pendingdata.availdatainfo.dataType == DATATYPE_IMG_RAW_1BPP)) { + contentFS->rename(queueItem->filename, String(dst_path)); + } else { + contentFS->remove(queueItem->filename); + } + } + memcpy(md5bytes, &queueItem->pendingdata.availdatainfo.dataVer, sizeof(uint64_t)); + dequeueItem(xfc->src); + } + tagRecord* taginfo = tagRecord::findByMAC(xfc->src); if (taginfo != nullptr) { - memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending)); clearPending(taginfo); + memcpy(taginfo->md5, md5bytes, sizeof(md5bytes)); + taginfo->pendingCount = countQueueItem(xfc->src); taginfo->wakeupReason = 0; if (taginfo->contentMode == 12 && local == false) { if (contentFS->exists(dst_path)) { @@ -428,6 +427,10 @@ void processXferComplete(struct espXferComplete* xfc, bool local) { taginfo->nextupdate = now; } } + + // more in the queue? + checkQueue(xfc->src); + wsSendTaginfo(xfc->src, SYNC_TAGSTATUS); if (local) udpsync.netProcessXferComplete(xfc); } @@ -451,9 +454,13 @@ void processXferTimeout(struct espXferComplete* xfc, bool local) { tagRecord* taginfo = tagRecord::findByMAC(xfc->src); if (taginfo != nullptr) { taginfo->pendingIdle = 60; - memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); clearPending(taginfo); + while (dequeueItem(xfc->src)) { + }; } + + checkQueue(xfc->src); + wsSendTaginfo(xfc->src, SYNC_TAGSTATUS); if (local) udpsync.netProcessXferTimeout(xfc); } @@ -469,7 +476,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP if (config.lock == 1 || (config.lock == 2 && eadr->adr.wakeupReason != WAKEUP_REASON_FIRSTBOOT)) return; taginfo = new tagRecord; memcpy(taginfo->mac, eadr->src, sizeof(taginfo->mac)); - taginfo->pending = false; + taginfo->pendingCount = 0; tagDB.push_back(taginfo); } time_t now; @@ -505,9 +512,7 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP if (eadr->adr.lastPacketRSSI != 0) { if (eadr->adr.wakeupReason >= 0xE0) { - if (!taginfo->pending) taginfo->nextupdate = 0; - memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); - memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); + if (taginfo->pendingCount == 0) taginfo->nextupdate = 0; if (local) { const char* reason = ""; @@ -523,13 +528,6 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP logLine(buffer); } } - - /* - if (local && taginfo->batteryMv != eadr->adr.batteryMv) { - sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X battery went from %.2fV to %.2fV", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], static_cast(taginfo->batteryMv) / 1000.0, static_cast(eadr->adr.batteryMv) / 1000.0); - logLine(buffer); - } - */ taginfo->LQI = eadr->adr.lastPacketLQI; taginfo->hwType = eadr->adr.hwType; @@ -545,8 +543,6 @@ void processDataReq(struct espAvailDataReq* eadr, bool local, IPAddress remoteIP if (local) { sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); Serial.print(buffer); - } else { - // sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); } if (local) { @@ -571,17 +567,17 @@ void processTagReturnData(struct espTagReturnData* trd, uint8_t len, bool local) sprintf(buffer, "TRD Data: len=%d, type=%d, ver=0x%08X\n", payloadLength, trd->returnData.dataType, trd->returnData.dataVer); wsLog((String)buffer); +#ifndef SAVE_SPACE TagData::parse(trd->src, trd->returnData.dataType, trd->returnData.data, payloadLength); +#endif } void refreshAllPending() { for (int16_t c = 0; c < tagDB.size(); c++) { tagRecord* taginfo = tagDB.at(c); - if (taginfo->pending && taginfo->version == 0) { + if (taginfo->pendingCount > 0 && taginfo->version == 0) { clearPending(taginfo); taginfo->nextupdate = 0; - memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); - memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS); } } @@ -592,8 +588,6 @@ void updateContent(const uint8_t* dst) { if (taginfo != nullptr) { clearPending(taginfo); taginfo->nextupdate = 0; - memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); - memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS); } } @@ -622,7 +616,7 @@ bool sendAPSegmentedData(const uint8_t* dst, String data, uint16_t icons, bool i pending.attemptsLeft = 120; Serial.printf(">AP Segmented Data %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); if (local) { - return sendDataAvail(&pending); + return queueDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); return true; @@ -640,7 +634,7 @@ bool showAPSegmentedInfo(const uint8_t* dst, bool local) { pending.attemptsLeft = 120; Serial.printf(">SDA %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); if (local) { - return sendDataAvail(&pending); + return queueDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); return true; @@ -659,8 +653,15 @@ bool sendTagCommand(const uint8_t* dst, uint8_t cmd, bool local, const uint8_t* } pending.attemptsLeft = 120; Serial.printf(">Tag CMD %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); + + tagRecord* taginfo = tagRecord::findByMAC(dst); + if (taginfo != nullptr) { + taginfo->pendingCount++; + wsSendTaginfo(taginfo->mac, SYNC_TAGSTATUS); + } + if (local) { - return sendDataAvail(&pending); + return queueDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); return true; @@ -674,7 +675,7 @@ void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP) { if (config.lock) return; taginfo = new tagRecord; memcpy(taginfo->mac, taginfoitem->mac, sizeof(taginfo->mac)); - taginfo->pending = false; + taginfo->pendingCount = 0; tagDB.push_back(taginfo); } tagRecord initialTagInfo = *taginfo; @@ -687,7 +688,7 @@ void updateTaginfoitem(struct TagInfo* taginfoitem, IPAddress remoteIP) { case SYNC_TAGSTATUS: taginfo->lastseen = taginfoitem->lastseen; taginfo->nextupdate = taginfoitem->nextupdate; - taginfo->pending = taginfoitem->pending; + taginfo->pendingCount = taginfoitem->pendingCount; taginfo->expectedNextCheckin = taginfoitem->expectedNextCheckin; taginfo->hwType = taginfoitem->hwType; taginfo->wakeupReason = taginfoitem->wakeupReason; @@ -739,24 +740,23 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) { taginfo2->len = taginfo->len; taginfo2->data = taginfo->data; // copy buffer pointer taginfo2->dataType = taginfo->dataType; - taginfo2->pending = true; + taginfo2->pendingCount++; taginfo2->nextupdate = 3216153600; - memcpy(taginfo2->md5pending, taginfo->md5pending, sizeof(taginfo->md5pending)); struct pendingData pending2 = {0}; memcpy(pending2.targetMac, taginfo2->mac, 8); pending2.availdatainfo.dataType = taginfo2->dataType; - pending2.availdatainfo.dataVer = *((uint64_t*)taginfo2->md5pending); + pending2.availdatainfo.dataVer = pending->availdatainfo.dataVer; pending2.availdatainfo.dataSize = taginfo2->len; pending2.availdatainfo.dataTypeArgument = pending->availdatainfo.dataTypeArgument; pending2.availdatainfo.nextCheckIn = pending->availdatainfo.nextCheckIn; pending2.attemptsLeft = pending->attemptsLeft; if (taginfo2->isExternal == false) { - sendDataAvail(&pending2); + queueDataAvail(&pending2); } else { char dst_path[64]; - sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", taginfo2->mac[7], taginfo2->mac[6], taginfo2->mac[5], taginfo2->mac[4], taginfo2->mac[3], taginfo2->mac[2], taginfo2->mac[1], taginfo2->mac[0]); + sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X_%lu.pending", taginfo2->mac[7], taginfo2->mac[6], taginfo2->mac[5], taginfo2->mac[4], taginfo2->mac[3], taginfo2->mac[2], taginfo2->mac[1], taginfo2->mac[0], millis()); xSemaphoreTake(fsMutex, portMAX_DELAY); File file = contentFS->open(dst_path, "w"); if (file) { @@ -775,3 +775,123 @@ bool checkMirror(struct tagRecord* taginfo, struct pendingData* pending) { } return false; } + +void enqueueItem(struct PendingItem& item) { + std::lock_guard lock(queueMutex); + pendingQueue.push_back(item); +} + +bool dequeueItem(const uint8_t* targetMac) { + return dequeueItem(targetMac, 0); +} + +bool dequeueItem(const uint8_t* targetMac, const uint64_t dataVer) { + std::lock_guard lock(queueMutex); + auto it = std::find_if(pendingQueue.begin(), pendingQueue.end(), + [targetMac, dataVer](const PendingItem& item) { + bool macMatches = memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0; + bool dataVerMatches = (dataVer == 0) || (dataVer == item.pendingdata.availdatainfo.dataVer); + return macMatches && dataVerMatches; + }); + if (it != pendingQueue.end()) { + if (it->data != nullptr) { + int datacount = 0; + for (const PendingItem& item : pendingQueue) { + if (item.data == it->data) { + datacount++; + } + } + if (datacount == 1) { + free(it->data); + } + it->data = nullptr; + } + pendingQueue.erase(it); + return true; + } + return false; +} + +uint16_t countQueueItem(const uint8_t* targetMac) { + std::unique_lock lock(queueMutex); + int count = std::count_if(pendingQueue.begin(), pendingQueue.end(), + [targetMac](const PendingItem& item) { + return memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0; + }); + return count; +} + +PendingItem* getQueueItem(const uint8_t* targetMac) { + return getQueueItem(targetMac, 0); +} + +PendingItem* getQueueItem(const uint8_t* targetMac, const uint64_t dataVer) { + auto it = std::find_if(pendingQueue.begin(), pendingQueue.end(), + [targetMac, dataVer](const PendingItem& item) { + bool macMatches = memcmp(item.pendingdata.targetMac, targetMac, sizeof(item.pendingdata.targetMac)) == 0; + bool dataVerMatches = (dataVer == 0) || (dataVer == item.pendingdata.availdatainfo.dataVer); + return macMatches && dataVerMatches; + }); + if (it != pendingQueue.end()) { + return &(*it); + } else { + return nullptr; + } +} + +void checkQueue(const uint8_t* targetMac) { + uint16_t queueCount; + queueCount = countQueueItem(targetMac); + if (queueCount > 0) { + Serial.printf("more from queue: total %d elements\n", pendingQueue.size()); + PendingItem* queueItem = getQueueItem(targetMac); + if (queueItem == nullptr) { + return; + } + if (queueCount > 1) queueItem->pendingdata.availdatainfo.nextCheckIn = 0; + sendDataAvail(&queueItem->pendingdata); + } +} + +bool queueDataAvail(struct pendingData* pending) { + PendingItem newPending; + newPending.pendingdata.availdatainfo = pending->availdatainfo; + newPending.pendingdata.attemptsLeft = pending->attemptsLeft; + std::copy(pending->targetMac, pending->targetMac + sizeof(pending->targetMac), newPending.pendingdata.targetMac); + + tagRecord* taginfo = tagRecord::findByMAC(pending->targetMac); + + if (taginfo == nullptr) { + return false; + } + + std::strcpy(newPending.filename, taginfo->filename.c_str()); + if (taginfo->data != nullptr) { + // move data pointer + newPending.data = taginfo->data; + taginfo->data = nullptr; + } else { + newPending.data = nullptr; + + // optional: read data early, don't wait for block request. + fs::File file = contentFS->open(newPending.filename); + if (file) { + newPending.data = getDataForFile(file); + Serial.println("Reading file " + String(newPending.filename)); + file.close(); + } + } + newPending.len = taginfo->len; + + if (countQueueItem(pending->targetMac) == 0) { + enqueueItem(newPending); + // first in line, send to tag + Serial.printf("queue item added, first in line, total %d elements\n", pendingQueue.size()); + sendDataAvail(pending); + } else { + enqueueItem(newPending); + Serial.printf("queue item added, total %d elements\n", pendingQueue.size()); + } + + return true; +} diff --git a/ESP32_AP-Flasher/src/serialap.cpp b/ESP32_AP-Flasher/src/serialap.cpp index c415c8ce..fcda1401 100644 --- a/ESP32_AP-Flasher/src/serialap.cpp +++ b/ESP32_AP-Flasher/src/serialap.cpp @@ -1,8 +1,8 @@ -#include "serialap.h" - #include #include +#include "serialap.h" + #include "commstructs.h" #include "contentmanager.h" #include "flasher.h" @@ -15,7 +15,6 @@ #include "zbs_interface.h" QueueHandle_t rxCmdQueue; - SemaphoreHandle_t txActive; // If a command is sent, it will wait for a reply here @@ -28,7 +27,7 @@ volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT; #define AP_SERIAL_PORT Serial1 volatile bool rxSerialStopTask2 = false; - uint8_t channelList[6]; +uint8_t channelList[6]; struct espSetChannelPower curChannel = {0, 11, 10}; #define RX_CMD_RQB 0x01 @@ -43,8 +42,6 @@ struct espSetChannelPower curChannel = {0, 11, 10}; volatile uint32_t lastAPActivity = 0; struct APInfoS apInfo; -extern uint8_t* getDataForFile(File& file); - struct rxCmd { uint8_t* data; uint8_t len; @@ -98,7 +95,7 @@ bool waitCmdReply() { break; case CMD_REPLY_ACK: lastAPActivity = millis(); - if(apInfo.isOnline == false) + if (apInfo.isOnline == false) setAPstate(true, AP_STATE_ONLINE); return true; break; @@ -150,8 +147,7 @@ void setAPstate(bool isOnline, uint8_t state) { CRGB::Yellow, CRGB::Aqua, CRGB::Red, - CRGB::YellowGreen - }; + CRGB::YellowGreen}; rgbIdleColor = colorMap[state]; rgbIdlePeriod = (isOnline ? 767 : 255); #endif @@ -222,6 +218,7 @@ blksend: txEnd(); return bd->checksum; } + bool sendDataAvail(struct pendingData* pending) { if (!apInfo.isOnline) return false; if (!txStart()) return false; @@ -462,7 +459,7 @@ void rxSerialTask(void* parameter) { packetp = (uint8_t*)calloc(sizeof(struct espBlockRequest) + 8, 1); memset(cmdbuffer, 0x00, 4); lastAPActivity = millis(); - if(apInfo.isOnline == false) + if (apInfo.isOnline == false) setAPstate(true, AP_STATE_ONLINE); } if (strncmp(cmdbuffer, "ADR>", 4) == 0) { @@ -472,7 +469,7 @@ void rxSerialTask(void* parameter) { packetp = (uint8_t*)calloc(sizeof(struct espAvailDataReq) + 8, 1); memset(cmdbuffer, 0x00, 4); lastAPActivity = millis(); - if(apInfo.isOnline == false) + if (apInfo.isOnline == false) setAPstate(true, AP_STATE_ONLINE); } if (strncmp(cmdbuffer, "XFC>", 4) == 0) { @@ -496,7 +493,7 @@ void rxSerialTask(void* parameter) { packetp = (uint8_t*)calloc(sizeof(struct espTagReturnData) + 8, 1); memset(cmdbuffer, 0x00, 4); lastAPActivity = millis(); - if(apInfo.isOnline == false) + if (apInfo.isOnline == false) setAPstate(true, AP_STATE_ONLINE); } break; @@ -535,7 +532,7 @@ void rxSerialTask(void* parameter) { case ZBS_RX_WAIT_TAG_RETURN_DATA: { packetp[pktindex] = lastchar; pktindex++; - if ((pktindex > 10) && (pktindex >= (packetp[9]+10))) { + if ((pktindex > 10) && (pktindex >= (packetp[9] + 10))) { addRXQueue(packetp, pktindex, RX_CMD_TRD); RXState = ZBS_RX_WAIT_HEADER; } @@ -582,7 +579,7 @@ void rxSerialTask(void* parameter) { charindex++; if (charindex == 2) { RXState = ZBS_RX_WAIT_HEADER; - apInfo.pending = (uint8_t)strtoul(cmdbuffer, NULL, 16); + apInfo.pendingBuffer = (uint8_t)strtoul(cmdbuffer, NULL, 16); } break; case ZBS_RX_WAIT_NOP: @@ -772,7 +769,6 @@ void APTask(void* parameter) { updateContent(apInfo.mac); } - uint16_t fsversion; if (FLASHER_AP_MOSI != -1) { fsversion = getAPUpdateVersion(apInfo.type); @@ -901,7 +897,7 @@ void APTask(void* parameter) { if (!reply) { attempts++; } else { - if(apInfo.isOnline == false) + if (apInfo.isOnline == false) setAPstate(true, AP_STATE_ONLINE); attempts = 0; } @@ -913,7 +909,7 @@ void APTask(void* parameter) { #ifdef HAS_RGB_LED showColorPattern(CRGB::Yellow, CRGB::Yellow, CRGB::Red); #endif - lastAPActivity = millis();// we set this to retrigger a recovery in AP_ACTIVITY_MAX_INTERVAL seconds + lastAPActivity = millis(); // we set this to retrigger a recovery in AP_ACTIVITY_MAX_INTERVAL seconds } else { setAPstate(true, AP_STATE_ONLINE); attempts = 0; diff --git a/ESP32_AP-Flasher/src/storage.cpp b/ESP32_AP-Flasher/src/storage.cpp index 545141ce..6ca786b3 100644 --- a/ESP32_AP-Flasher/src/storage.cpp +++ b/ESP32_AP-Flasher/src/storage.cpp @@ -176,7 +176,6 @@ void DynStorage::end() { } void listDir(fs::FS& fs, const char* dirname, uint8_t levels) { - Storage.begin(); Serial.printf(" \n "); Serial.printf("Listing directory: %s\n", dirname); diff --git a/ESP32_AP-Flasher/src/tag_db.cpp b/ESP32_AP-Flasher/src/tag_db.cpp index a19ea83a..c3c83979 100644 --- a/ESP32_AP-Flasher/src/tag_db.cpp +++ b/ESP32_AP-Flasher/src/tag_db.cpp @@ -103,7 +103,7 @@ void fillNode(JsonObject& tag, const tagRecord* taginfo) { tag["lastseen"] = taginfo->lastseen; tag["nextupdate"] = taginfo->nextupdate; tag["nextcheckin"] = taginfo->expectedNextCheckin; - tag["pending"] = taginfo->pending; + tag["pending"] = taginfo->pendingCount; tag["alias"] = taginfo->alias; tag["contentMode"] = taginfo->contentMode; tag["LQI"] = taginfo->LQI; @@ -128,7 +128,6 @@ void saveDB(const String& filename) { const long t = millis(); - Storage.begin(); xSemaphoreTake(fsMutex, portMAX_DELAY); fs::File existingFile = contentFS->open(filename, "r"); @@ -174,7 +173,6 @@ bool loadDB(const String& filename) { Serial.println("reading DB from " + String(filename)); const long t = millis(); - Storage.begin(); fs::File readfile = contentFS->open(filename, "r"); if (!readfile) { Serial.println("loadDB: Failed to open file"); @@ -206,14 +204,13 @@ bool loadDB(const String& filename) { taginfo->md5[i] = strtoul(md5.substring(i * 2, i * 2 + 2).c_str(), NULL, 16); } } - memcpy(taginfo->md5pending, taginfo->md5, sizeof(taginfo->md5)); taginfo->lastseen = (uint32_t)tag["lastseen"]; taginfo->nextupdate = (uint32_t)tag["nextupdate"]; taginfo->expectedNextCheckin = (uint32_t)tag["nextcheckin"]; if (taginfo->expectedNextCheckin < now) { taginfo->expectedNextCheckin = now + 1800; } - taginfo->pending = false; + taginfo->pendingCount = 0; taginfo->alias = tag["alias"].as(); taginfo->contentMode = tag["contentMode"]; taginfo->LQI = tag["LQI"]; @@ -303,11 +300,9 @@ void clearPending(tagRecord* taginfo) { } taginfo->data = nullptr; } - taginfo->pending = false; } void initAPconfig() { - Storage.begin(); DynamicJsonDocument APconfig(500); File configFile = contentFS->open("/current/apconfig.json", "r"); if (configFile) { diff --git a/ESP32_AP-Flasher/src/tagdata.cpp b/ESP32_AP-Flasher/src/tagdata.cpp index 41f8aef0..85806755 100644 --- a/ESP32_AP-Flasher/src/tagdata.cpp +++ b/ESP32_AP-Flasher/src/tagdata.cpp @@ -1,5 +1,7 @@ #include "tagdata.h" +#ifndef SAVE_SPACE + #include "tag_db.h" #include "util.h" @@ -8,7 +10,6 @@ std::unordered_map TagData::parsers = {}; void TagData::loadParsers(const String& filename) { const long start = millis(); - Storage.begin(); fs::File file = contentFS->open(filename, "r"); if (!file) { return; @@ -143,3 +144,5 @@ void TagData::parse(const uint8_t src[8], const size_t id, const uint8_t* data, Serial.printf("Set %s to %s\n", varName.c_str(), value.c_str()); } } + +#endif \ No newline at end of file diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp index 95f11dda..caff3023 100644 --- a/ESP32_AP-Flasher/src/web.cpp +++ b/ESP32_AP-Flasher/src/web.cpp @@ -169,7 +169,7 @@ void wsSendTaginfo(const uint8_t *mac, uint8_t syncMode) { if (syncMode == SYNC_TAGSTATUS) { taginfoitem.lastseen = taginfo->lastseen; taginfoitem.nextupdate = taginfo->nextupdate; - taginfoitem.pending = taginfo->pending; + taginfoitem.pendingCount = taginfo->pendingCount; taginfoitem.expectedNextCheckin = taginfo->expectedNextCheckin; taginfoitem.hwType = taginfo->hwType; taginfoitem.wakeupReason = taginfo->wakeupReason; @@ -215,7 +215,6 @@ uint8_t wsClientCount() { void init_web() { wsMutex = xSemaphoreCreateMutex(); - Storage.begin(); WiFi.mode(WIFI_STA); WiFi.setTxPower(static_cast(config.wifiPower)); @@ -240,7 +239,6 @@ void init_web() { server.serveStatic("/current", *contentFS, "/current/").setCacheControl("max-age=604800"); server.serveStatic("/tagtypes", *contentFS, "/tagtypes/").setCacheControl("max-age=600"); - server.serveStatic("/", *contentFS, "/www/").setDefaultFile("index.html"); server.on( "/imgupload", HTTP_POST, [](AsyncWebServerRequest *request) { @@ -274,12 +272,19 @@ void init_web() { String dst = request->getParam("mac")->value(); uint8_t mac[8]; if (hex2mac(dst, mac)) { - const tagRecord *taginfo = tagRecord::findByMAC(mac); + tagRecord *taginfo = tagRecord::findByMAC(mac); if (taginfo != nullptr) { - if (taginfo->pending == true) { - request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len); - return; + if (taginfo->data == nullptr) { + fs::File file = contentFS->open(taginfo->filename); + if (!file) { + request->send(404, "text/plain", "File not found"); + return; + } + taginfo->data = getDataForFile(file); + file.close(); } + request->send_P(200, "application/octet-stream", taginfo->data, taginfo->len); + return; } } } @@ -311,8 +316,6 @@ void init_web() { if (request->hasParam("invert", true)) { taginfo->invert = atoi(request->getParam("invert", true)->value().c_str()); } - // memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); - // memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); wsSendTaginfo(mac, SYNC_USERCFG); // saveDB("/current/tagDB.json"); request->send(200, "text/plain", "Ok, saved"); @@ -337,7 +340,9 @@ void init_web() { } if (strcmp(cmdValue, "clear") == 0) { clearPending(taginfo); - memcpy(taginfo->md5pending, taginfo->md5, sizeof(taginfo->md5pending)); + while (dequeueItem(mac)) { + }; + taginfo->pendingCount = countQueueItem(mac); wsSendTaginfo(mac, SYNC_TAGSTATUS); } if (strcmp(cmdValue, "refresh") == 0) { @@ -660,6 +665,8 @@ void init_web() { request->send(404); }); + server.serveStatic("/", *contentFS, "/www/").setDefaultFile("index.html"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "content-type"); diff --git a/ESP32_AP-Flasher/wwwroot/edit.html b/ESP32_AP-Flasher/wwwroot/edit.html index 90d2f95f..fcd15445 100644 --- a/ESP32_AP-Flasher/wwwroot/edit.html +++ b/ESP32_AP-Flasher/wwwroot/edit.html @@ -410,7 +410,7 @@ if (ext == "raw" || ext == "pending") { let storedTagTypes = localStorage.getItem("tagTypes"); let tagTypes = JSON.parse(storedTagTypes); - let mac = name.substring(name.lastIndexOf('/') + 1); + let mac = name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf('/') + 17); let targetDiv = null; if (window.opener && !window.opener.closed) { targetDiv = window.opener.document.querySelector(`div[data-mac="${mac}"]`); diff --git a/ESP32_AP-Flasher/wwwroot/index.html b/ESP32_AP-Flasher/wwwroot/index.html index 7e93d334..eafdc2d8 100644 --- a/ESP32_AP-Flasher/wwwroot/index.html +++ b/ESP32_AP-Flasher/wwwroot/index.html @@ -6,8 +6,9 @@ Open EPaper Link Access Point + - + @@ -525,8 +526,6 @@ style="display: none; position: absolute; background: white; border: 1px solid gray; padding: 0; list-style: none;"> - - diff --git a/ESP32_AP-Flasher/wwwroot/main.css b/ESP32_AP-Flasher/wwwroot/main.css index b700454c..886761f2 100644 --- a/ESP32_AP-Flasher/wwwroot/main.css +++ b/ESP32_AP-Flasher/wwwroot/main.css @@ -626,12 +626,13 @@ select { .pendingicon { width: 20px; height: 20px; - background-color: rgb(7, 174, 230); + background-color: rgb(1, 154, 201); font-size: 1.2em; text-align: center; font-weight: bold; display: none; vertical-align: top; + color: white; } .warningicon { diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js index 7990b67c..ee4a8599 100644 --- a/ESP32_AP-Flasher/wwwroot/main.js +++ b/ESP32_AP-Flasher/wwwroot/main.js @@ -40,7 +40,6 @@ let otamodule; let socket; let finishedInitialLoading = false; let getTagtypeBusy = false; -let webVersion = "0"; const loadConfig = new Event("loadConfig"); window.addEventListener("loadConfig", function () { @@ -57,14 +56,18 @@ window.addEventListener("loadConfig", function () { }); window.addEventListener("load", function () { - initVersionInfo(); + window.dispatchEvent(loadConfig); initTabs(); fetch('/content_cards.json') .then(response => response.json()) .then(data => { cardconfig = data; - loadTags(0); - connect(); + loadTags(0) + .then(() => { + finishedInitialLoading = true; + connect(); + }) + .catch(error => showMessage('loadTags error: ' + error)); setInterval(updatecards, 1000); }) .catch(error => { @@ -72,26 +75,17 @@ window.addEventListener("load", function () { alert("I can't load /www/content_cards.json.\r\nHave you upload it to the data partition?"); }); - window.dispatchEvent(loadConfig); - dropUpload(); populateTimes($('#apcnight1')); populateTimes($('#apcnight2')); -}); -function initVersionInfo() { - fetch('/version.txt') - .then(response => { - return response.text(); - }) - .then(data => { - webVersion = data; - console.log(webVersion); - }) - .catch(error => { - console.error('Fetch error:', error); - }); -} + document.addEventListener('DOMContentLoaded', function () { + var faviconLink = document.createElement('link'); + faviconLink.rel = 'icon'; + faviconLink.href = 'favicon.ico'; + document.head.appendChild(faviconLink); + }); +}); /* tabs */ let activeTab = ''; @@ -119,14 +113,14 @@ function initTabs() { }; function loadTags(pos) { - fetch("/get_db?pos=" + pos) + return fetch("/get_db?pos=" + pos) .then(response => response.json()) .then(data => { processTags(data.tags); - if (data.continu && data.continu > pos) loadTags(data.continu); - finishedInitialLoading = true; - }) - //.catch(error => showMessage('loadTags error: ' + error)); + if (data.continu && data.continu > pos) { + return loadTags(data.continu); + } + }); } function formatUptime(seconds) { @@ -280,7 +274,24 @@ function processTags(tagArray) { div.dataset.ver = element.ver; $('#tag' + localTagmac + ' .resolution').innerHTML += ` fw:${element.ver} 0x${element.ver.toString(16)}`; } + + if (!apConfig.preview || element.contentMode == 20) { + $('#tag' + tagmac + ' .tagimg').style.display = 'none' + } else if (div.dataset.hash != element.hash && div.dataset.hwtype > -1) { + let cachetag = element.hash; + if (element.hash != '00000000000000000000000000000000') { + if (element.isexternal && element.contentMode == 12) { + loadImage(tagmac, 'http://' + tagDB[tagmac].apip + '/current/' + tagmac + '.raw?' + cachetag); + } else { + loadImage(tagmac, '/current/' + tagmac + '.raw?' + cachetag); + } + } else { + $('#tag' + tagmac + ' .tagimg').style.display = 'none' + } + div.dataset.hash = element.hash; + } })(); + let statusline = ""; if (element.RSSI != 100) { if (element.ch > 0) statusline += `CH ${element.ch}, `; @@ -293,29 +304,13 @@ function processTags(tagArray) { } $('#tag' + tagmac + ' .received').innerHTML = statusline; $('#tag' + tagmac + ' .received').style.opacity = "1"; + } else { $('#tag' + tagmac + ' .model').innerHTML = "waiting for hardware type"; $('#tag' + tagmac + ' .received').style.opacity = "0"; $('#tag' + tagmac + ' .resolution').innerHTML = ""; } - if (!apConfig.preview || element.contentMode == 20) { - $('#tag' + tagmac + ' .tagimg').style.display = 'none' - } else if (div.dataset.hash != element.hash && div.dataset.hwtype > -1) { - let cachetag = element.hash; - if (element.hash != '00000000000000000000000000000000') { - //cachetag = Math.random(); - if (element.isexternal && element.contentMode == 12) { - loadImage(tagmac, 'http://' + tagDB[tagmac].apip + '/current/' + tagmac + '.raw?' + cachetag); - } else { - loadImage(tagmac, '/current/' + tagmac + '.raw?' + cachetag); - } - } else { - $('#tag' + tagmac + ' .tagimg').style.display = 'none' - } - div.dataset.hash = element.hash; - } - if (element.nextupdate > 1672531200 && element.nextupdate != 3216153600) { const date = new Date(element.nextupdate * 1000); const options = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }; @@ -379,6 +374,7 @@ function processTags(tagArray) { break; } $('#tag' + tagmac + ' .pendingicon').style.display = (element.pending ? 'inline-block' : 'none'); + $('#tag' + tagmac + ' .pendingicon').innerHTML = element.pending; div.classList.add("tagflash"); (function (tagmac) { setTimeout(function () { $('#tag' + tagmac).classList.remove("tagflash"); }, 1400); @@ -438,9 +434,9 @@ function updatecards() { $('#dashboardTimeout').innerHTML = timeoutcount; } -$('#clearlog').onclick = function () { +$('#clearlog').addEventListener("click", (event) => { $('#messages').innerHTML = ''; -} +}); document.querySelectorAll('.closebtn').forEach(button => { button.addEventListener('click', (event) => { @@ -1301,7 +1297,7 @@ async function getTagtype(hwtype) { try { getTagtypeBusy = true; tagTypes[hwtype] = { busy: true }; - const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json?' + webVersion); + const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json'); if (!response.ok) { let data = { name: 'unknown id ' + hwtype.toString(16), width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false }; tagTypes[hwtype] = data; @@ -1539,7 +1535,6 @@ function populateAPCard(msg) { } }); - // $('#ap' + apid + ' .apversion').innerHTML = msg.version; if (activeTab == 'aptab') { populateAPInfo(apip); }