From 0b5ff8354871ec3ece4d0bc47ae29f0d8221780f Mon Sep 17 00:00:00 2001 From: GuanYuankai Date: Thu, 8 Jan 2026 09:58:49 +0800 Subject: [PATCH] Update .gitignore and remove ignored files from tracking --- .gitignore | 2 +- captures/20260107_083403_id20.jpg | Bin 7032 -> 0 bytes captures/20260107_083403_id7.jpg | Bin 12227 -> 0 bytes captures/20260107_083404_id19.jpg | Bin 10060 -> 0 bytes captures/20260107_083405_id27.jpg | Bin 8922 -> 0 bytes captures/20260107_083405_id31.jpg | Bin 8415 -> 0 bytes captures/20260107_083407_id38.jpg | Bin 8952 -> 0 bytes captures/20260107_083410_id45.jpg | Bin 10268 -> 0 bytes captures/20260107_083411_id50.jpg | Bin 9075 -> 0 bytes captures/20260107_083412_id54.jpg | Bin 9919 -> 0 bytes captures/20260107_083413_id60.jpg | Bin 8304 -> 0 bytes captures/20260107_083414_id52.jpg | Bin 9149 -> 0 bytes captures/20260107_083416_id58.jpg | Bin 9012 -> 0 bytes src/config/config_manager.cc | 25 + src/config/config_manager.h | 2 + src/videoService/video_pipeline.cpp | 50 +- src/videoService/video_pipeline.hpp | 4 +- src/web/web_server.cc | 753 ++++------------------------ src/web/web_server.cc.bak | 690 +++++++++++++++++++++++++ src/web/web_server.h | 59 ++- 20 files changed, 867 insertions(+), 718 deletions(-) delete mode 100644 captures/20260107_083403_id20.jpg delete mode 100644 captures/20260107_083403_id7.jpg delete mode 100644 captures/20260107_083404_id19.jpg delete mode 100644 captures/20260107_083405_id27.jpg delete mode 100644 captures/20260107_083405_id31.jpg delete mode 100644 captures/20260107_083407_id38.jpg delete mode 100644 captures/20260107_083410_id45.jpg delete mode 100644 captures/20260107_083411_id50.jpg delete mode 100644 captures/20260107_083412_id54.jpg delete mode 100644 captures/20260107_083413_id60.jpg delete mode 100644 captures/20260107_083414_id52.jpg delete mode 100644 captures/20260107_083416_id58.jpg create mode 100644 src/web/web_server.cc.bak diff --git a/.gitignore b/.gitignore index d7379af..06aea89 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,7 @@ hls_streams/live *.db *.log .runner -capture/ +captures/ # 特例列表 !rknn_sdk/librknn_api/aarch64/*.so diff --git a/captures/20260107_083403_id20.jpg b/captures/20260107_083403_id20.jpg deleted file mode 100644 index 4a7cee4b34a6656c64d18e35405fbb79199d15d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7032 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 UFVkPlF~2sl(@8v-=W5}<02w@cmjD0& diff --git a/captures/20260107_083404_id19.jpg b/captures/20260107_083404_id19.jpg deleted file mode 100644 index 69510cc73ec1e9509288fdc3b01cc80f25dc2c7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10060 zcmbWdXH*kk)IJ&nrAu#8BGROTbSY7kE}%$n5mBo24uL2L2m+xfC@msQq)V@%_uc{o zfdr(Ngc=~gKfm`~>)tQ-!@c*+n!RS$oZ0g{>+G}3IdeUGy$qn&)zr}hkdOcXB*X)7 zjRHIY+#va{{U>ky*T_i!lQ+r8NXc&9ymjk;PI5|$TjZ4Fw{B5TQ&3X<*N9g%)KoP8 z-TbHIe_y?Ela%x(75Odl|LOAoNnE!97$^YAfcK;%_W(B-NJtq-uDbw0BF>xt1CI#y ze;dgSQnH)3$cZqhi5+U_iSS8DiJ)&15fgid5}yOe7;ZB1N~=$EjHPWzhn- z|ET>Bv;TL*!vB9U`@e|&-@N7lG^8ZN!6Rh=fB@%KotDJ}7ktZa$h)b(d4DP*Wf7EG z^NM3vCt&a1IArS8ERfkCXXP_6;+*xERV1ykQ>>n*P*I&6*%G4JQi9i7j_&v_2W_zX zv6*d?&J-c)1|YqfRN%ircBT7QmIYRI*DXpQqO_W(_Eb=*{v(ts_fJS!4i8R$%Z7O6 zWyuw7@G#dVCE5B{41Lru5|O}6S7yi!%Ma?F=%Cj5 zI}&_hmLClh=L?5&3NLW)e`OJM@MTy}e9U#q6>&D@*>RxMrpUcnEc<%7cWpV&@u%$S0>zwocn=P$emU5JXGw3 z;F!LP6NC#d{hMHc>uS{MG-PTigSj!JV-7I)qUNXmS!8iwyAs>%XOZKJvbd!V;2c|9 zB_4>@{Osyevbe(b8YRXJ_^Ue9^jUjO$mt>%=7PdU;z-fgfE{YAH|h~~WV^-vK;G(U z!yBtCi_YJ&LtdxM?uw|smrLnqbX~Um(-6B7Lkx`BrE0-;OZQOsZ~*KolLp}Ol=38h zXUZNJBK&(IplPqm*pVw)U>o<;WGalB;WLbF%ybxtpR+)8BTTVDhQ3?QEnZ} z2W~}1CT7+N6|=Ih0JDPkJK{r5Jn2%?0|8}DYGr>)c`-}}l(#g^!E4M>Qct{xZLGL)CV~!@=ljYO{w$y0Y+zdjGFZWjF9Gd z&2;ie`)>=jfg{3_{L0v00rOp;$UZb<_j=;B};qqdfG}BA|M6+%%Qce8k=8JV*c)uq|nxR-v4W%waWwb zp}QaQM4QTPd4aR_R7WSK5zS3qgb{qsHGu5!vOtYveqqr9ZvuHzh~jFwf_vvwJOasD zH)3Z;l*S8H*k`h+IR#x1zpIswKP+TCPi0)u` z4=kSN=4ECpy$+7Edp<7Madu~ zzO76JKk2zFDZ9Gok_IKe0uEsV0}LAv^3sKS!bZv#!pC(A^SxNf=xeiA%+Rw%%} za0Wt0eR(x`C{Qk@x!~Vh8vK-A%!{NBukjqa_ua8@6GOo}x0I4HQysU_2iX45?e}FB zKr)%2q36b^63_Lak7B5u$?Go7;qF$bh=6s$UfX18>AswgAgsW;+0C0#xKGIW^N>3F zJ-KKcBj|~R+3#QDUzVNWg~)^*$PW}t!|78Z4cxb2+&q3QFpDs#RDXT2(h(|-PJy*KFx`|qy-x54T& zDj@70^>O-ATRK@l`p2K+FNCJ{`S|H6cy+Z>0X!=0wxjm1#CXl^UO(#ROfXz?#b)|J zcWK&@S(7Q(fC-xfk>YuS)7h#!JLJ@(Ee?Sw^Xf4bmJ-*1G%;}Vz^Ej?#IZ6s=VPvPtRq_2>9sK4z z&a4-R)$w2egd|wy>x%K zSCDi}*Mo>jQzOBLDs+TW4JeaO%Q?>))DM za2qh3m#Kzs&w`x)ic9cTA`>%hZHD}_bK`vWx0Jb%$r?_~Wm=EHCLLc@Ygzh_-!0}# z`mwH@8Iu^P&#I);e&>%TB-F?iMiKNvBv5p8OT!y>)!rZ#v$2?gm6a6X_?x0!~4m!nlo0;xz_z$8!yu0 z4k*|$tvPrBI}DKQs*f!~=ZSL(Wo!D9ougv9C2Pt)YqvWV6~AP^ z-(%mj{a$#r0Hu($7k5Pi@5qx2G%Ic)zRRJ5S1lF7o6Do2RBmr3#bq~6w;n{&tu@wt zX)Xh7p84-v$~PiEOD<^m6&wCB?iy^)TH!J?CGXr3Fz?CNDn%^AxnHWB$T;VsFqdV% zs*XCX$YueoRrh>;ZTd!HIn7|~@(Pqw?KWvwR zgIWn7JVM#?xce1n46n7OKW@xS8kgRAVTG1&r^yOTur?(P& zx!&dbnqpA=5;GKC@M=PB&<7(KxhXdl(k=`Y+oVM$-y&5>;-I(&I2~`BfR-AH279P`k&O1ctf}2GX2)&Vu&I9sRw%}RtO`Op70Z47u`iKnB|^G z^2d<(NyWQU5gluL{_JCX*~{3JW!EQx)0yC}tMdd{Q=IYY_l;`+^YKxlYp}*}V_%wr zi+CI?NBO3t~eaUMa8A48&IwM;m3Id#LSQbsj5t>7G`C1PQG zfqdww=n~cntk6f=6+3@et<8^EL2KW8EVK!^LFF$Rk-l{FgfN^!$I{I$)9?ve-Jbc~ z-M2JJUE?j|fZG5K?M6?LG6T}eeXV3pgVwNvu2YY|{Eu=mL z<`H}bb~p@^>a??Lb{00gT3jid2@Q4Ah*!OgCfRCd+fAYxb)^;UkTD zZrtKyLDF0wi~>Up?)X*I^4Bs+3`wV0N=qtnUy7b^><^u)-LRgP7^;uaiTPU@{K8RVBkrp z;;`?-PYL6XC^!>jtk(+t(|0xtWp{D{Um-d~cm+&lKJWOC{U{oc=q1{0yCq5=PfT;k zzToXw+)U-&^mLK3d5mNZRHln-)9rkF|H2PKRk!u7D9~8G?%-QPTVN7=lQfR11}*OGoT$Uw(HIW8bEzO6c_&c2hm*x%Z_f(u1fRvb%C!CjIge6N ztbeOWes{wnLAxh-71IK0hgK}L?~0=L@`+XSXV#gkyNxZGL6*}kgI2QAXF*F0&vg%0 z{myD(mS;Z8+6$Td>7e_X>y-QZbR-Ya%oBHxH#JiOax=ww2+<@yhW(K;nQ7mmZ*lR^2>!;A!@DWl{z3@?oKz#j7h|pg@eoSPuD$8R zD=apl=hQ4?#Fc~RL0oLFz&JCZVbvWqW+se{I2pqGV7J@+ZRuvN=s=K5M8~lG(g7Rk3po@G~$DGr>pG1j1AQEoi`sKP|Fwv0XOhA()3{ z#At9+_h4xdCSrsOzKkGftc1X#u6S`_O*vbiD!)ZlqV+P&^CTVtWFjV$4S$r|O(0w( z=Tb~We9PnKxdsLsjQ`Sm9d-Lv5N%l$`lhLmwl$wEX75X8QcyX^W1+dk4|wC+YXDXU zI$Jvp4M6|3qrAF*>{fdAaX748tJ}JmJtgiPEZc&rpkzeoAS49%z^s?}@VMvUxmk55 z(fC6y<6hOfHtB(x+eH&>>F5)+k1n#O$2WjjeefJi{X;e8N~SpixFxA#=V^ZpXnpa; z>={9QAiEO!4e^JMrSbql%pjM4ga3?nL20`&`_zG?nAsW#Md?0)?Ig(IOw(NKJ$J)? z+lZIBts-#}loPnH=buVjsRRE^>qRE7+qTwLKRvy>3d;{2E(~i&{1J$=wMN_$mrMvd zDGK%S_PPd;CeD0??f6{qJ1bBI6@-o0((B}2BxfLw0z7Qs~;am!4KN|GPkQE||IupiKVFZ@q5gskz&PJOmfhbt66>3zakT>5`Ff3Sj70{f1PTIy+ zCu+yIASFgmFFnyLA1Y%o19bwixu!~m=XM!+aC+0UKL^=mf`5_+a&|T0PgHONUW zFFt`9VyC-@B^fPFv8BDBvZN!Y43iIR?XfWcGvLFmLW%w`Z? z^#;;|GQLL#M>bF1@%rmj7Yma~@r6s{&Agb{;^d`7@sbf<6+;5H>C5!Ly**px(26uvQ?d z>&`WxTQY`+EkxAymx{8_@5>Zz_vbcNTxWF`x2d8N^IoKkjPv&Jub_c=QBb!llnQdE zY3#=|bkeV!Q83Hd#bE&>H6cAZ>9l%|3?RjuWO@F5$LeP5ak@L8SF;HJz7gv0owKuF zdA5%oHHz9cPU~`h&6j4HwEtGQj{czba5Su*Ai4md^J$d6bJwO1_RZ_QQ+I#h@RT}uKLFx#Pi*l|7Za(ySq>2L6$4$)5 zr)b!XNih-L_>J_3%39e~f3_s{8qhHs&T6UE760&HxZIC&mz6`Do%w5?Bgd2W*KzF9 zsJ0Uh;`SBbWa_f8IkdzI@3P;+%J}(D?RZ3iNRX3t)y?_9eidW`!4a#!)Kwmt&5sWv zOYW4fs2Fs3hd_3FR{oOF96U8%A<>dMu-FUv`tMshJH$6RUdPI|_332iAwe1b1TTu= zx1?>h<+)QDKa-x^=wyjmC`t$*6?c;+`}B`vnWtuNkrnB7JS-^sh1a}%|DMnncMO7? zpaAN*2F%%`^Y8Kd@^-6JPs2v^#0)g=})N<{zeZX>w8 z#1U99cp1|yEex_Q)9`#bs2H2c6sWT_{{2F6P!IlN*o(`4-Q79rT;dTQ$dYi;cnye% zBSw+$4t7+K=eYZdqiXD5=!VkY))Bazi7^Ttp=3jVs*Y=&y3V8N~7$EaqUl0M!Zl;}GxWDz_u`#G?rR!!Uq#jk?B^A5i5O zmx5Ys1m5s6PRT5it$_vWq8je+_~bjm zx1Q=H@fW=>r*=sF)sN)1@FDFSEMo5tAqOtF3F)vLQx+G^F|DaArQU?_`B zg$c196fD8ef5gU*T!3N?z*H!3;EFZQwD6rkLZ?=&V#J%9N&mQD5Ypb3 zup-?&&+ow*(kV?-2MD^SGgqK7k3IXEz?Ux$aRE0LWsxKtUNA~Yd9zQMewV_LH^+FF&-a}E`z7+*i0ID-t_&TVfh-oYL9rO zTI_*_zHjAOaGr;MJo4ZysLJ2+Z`JSlT0%Qwf7d}JJGXot9A&yxy|gQY1$Cz=bbw;h zq~&xOQ~x> zRNrMX)Wwj-#_1YREnXH`OtErMw(CdPrNH{9vWI?EerNfD>HtPhAjYfkXPX1vYLo<< zMp7z4Eb`O4MPZ9ZXsJ=B(u&-99Su$u(n+UBvk`V69UE+Wq61z>44pNHF&ew*TAy2= zt)D33*gi<832mi9IZcig(c^b)9!}aIKG=n1;WbR+!+hdarQ2CLA+FQ=C1Ijl{pc18 z-pRO&(o)$=s9W`;_qxBR6MwAQs}^rv@}2k*;n;4+ggDy~7g;X;ZkiR$qve2i=B#AB z9i_ty7)J^d7jW6r#lQ|YUWn+Ta_cV0cLV}dQQEKT;FkD8F;7M|&qG!Ubwcky(s2E- zuX?24bc8#imxB(6?)Rv)j)!jGMV@B7OqO*%T2Bi)laTO`U_KoQGy%foPCN*V#V72@ zRuO_}1#w$et7GP5@%C6j!oiH)o7b?H4GHRGo(TaW4+ve^hAi7W?0EDAPuOU>d)7*e z{fR0*U^&A^bJuw5G>xuE@e8nV*wawu8UVQa>J5!4qWckph?^dt|~@Rf`X86-TQwX@uVAL_;S2qVz(0-K`WcVAUhFX58gzM}3Xy zj4U`ZohSddmhj&q*O_a8tGw4SCHuR`b)j_J(!9^fAzlw#Fi-2aV%DQA$JqeT)2AB1 z0jXpr1Ukwd+Or%g^deXw?#L@@8-fNdx{DJ=U6Sf>wf=O1@7}+kz6(uxfM2h43EzZD zA>`PL<@IjFyr}U5yjh2u0M7jftPAlb`SI-otK?3E5ML}I`W`ya(GayF;@u;$%eUE5 z_s3LgG5bH0>L$Fz*Wr-r__tVUv|v2CiL>;{Kct+~=Y>Ba$z4A^xIAule%6q|V*UTk^E_ zFw>Fc2oEV1>2zeUcqhp|r?9@z%k&9;FF=k!nr}7g#o6=Y)!_$l4!A-n{NF??@ecrf z7q$y4)7QzTzA-nT8&9uHunL`S51#rx$Aj*ZE+R~>0gn5(ECNNB7eD-X>@==abD>vE zBmeg7jHmt{LTgwwgp13$tdMqZbO_9WgEDYKaA4!B0qry%_R4hhlZFQARIikZqJht43D~^D#D`BGx0c}Rl_)e^yj?xKa17ciJbUvUpe@H$E6>_ z@dEQDVRvvwJvSn!@T75`PCx4_5eIj3HtEI|qc4tH9Y=_nsxl`x+ez#cN~#Pg7}=>5|&D^M%g@_u>!GTrGsX!#nz_gNM% zbg1fqsT96Y4?KRpEEy}_Mp_#@lbyv3M)}Z*d-B7$ppz^=W3Q5O2r>sdW*6-T>$~GV z!~&EmnHIE5cj7<2SNFJi2h>cyceFk11I6m@^65;0+ze@E!JDav?P5&TiNiPkC{v73 zlD$fr9oGn>#vm^8!C3fDZ0|*$;S4Y;*gXp)$%ZfJ?P8^0dVDg6>~H?0o8dD_L(y9k zDvlG?o(SW<8als=hvuU;)b&}-QZ9II)-0f+g$7B57Gp)vI>m@<6h#}MJVF?)RoHG7 z4umnsL(2!$c(-%rCqjoW%jFFR$Ze(%kUJbSMmEDi{JN8J?;XCTiRmQ1SudA;0S;(i z+B-M~r$~1Zq}Ar}zQvdM@1#I07|@sBuYwd2iAobGdFY^! z2mvA>LzZVbhA#NUC9&YYK{D*k?{x z=CO_aPvbbi$#syMhiwKwTcA#eZ9XR_+vtOAi`mjo*!KZ0;e#S4)Gl(1UiaiV8FWPb z*~|BbFS3XS~YH%FcP6`=Ow)sJP@~>8I+N+PeCN z#-`70xb}`t{I{;|p96!xeh&?gj82ngX6NP?{wyw0Ha54msXMgYy}!6P0M7rwV)y?? zT*7Qz2mbni=Pxdf1L5q8Q<&@E2{mq!i`RKPgG5iNKRa~f(#!W%t-Nv?wq&te!To&V z@|x2Ml)upaMfQIO?D_u}vi}9_f8!bj1UNa^&f^pYpujX;VgF`ipF0nQyM<(P7!K{Y z?gmexxh+}AUu#tDYKww)>lmhPrzNfOmHkK^TOiU{!0Ui^ER83C1>l!IFw$I@YU?;0 zcyosZ6oohx4|UJ!8t&D*nADxUaYC}gCiA+zD|2(-uPkE3mHA?Zd4gPWZpIa-ioflc zXLVJ}X?ntGUVT0nhxHO4z?q}Ngv~=NGvzB+L)vE;SO`e&0SkB$(mwSceT#&BBwgAw zm1b&a#R8t%vjBQzO9f=xTJ8~Wh!^r}5r2-Phy4B!Nw#4D4iI`lK*Lb$zyL1t6btxjfMi0gGT6>G#HR|7Ob*1Uv2+E5vrZna)YVEB3gAwS{%AcE5cUpf5;#5CV zCsEn20&JgWQXh7)s3|_~J`aCdqjJ=`6J8L7-A#>U^1^VUL zkWqb+FhO|6Q5JyuJC^RtGR^Ie@?E@)f+k-FSEQJn*PG963lhk!*H>i{(M(71GBnCU zm8q66MGHdGyWudpPDz`97gLxZf+WgdSpfLs9HSX?M4VgoFfCPP)A#QjD;s~hoa@$Y zYSUf27b}ANaUV&xV4D~NTZTnxvw-wl4NPga6V&s~$qP(AHF9DrQmEEO(v1ddC&<1j zy=>SeScIV_y9V6+PP|=7R1fNXqRIk5{frdkpEn3I7SL)G%K{V~-V(aNiWYPdI)jwA z9Ixv)%mQjnN^0j>K=wWDxjE)$%la(xB3yJD9VM&90`9V%YK2hzWt2+q)Nf`Ejk17& zTtqX5(6_VbA34TIdxgXq(cmm#Xo{KH>z;Qc{{mW_kroW~L4&-oZ0W7ad)vlWkKb|K z8ycOpo%g3caSHKR|>c+uV;+OpDfXCHyE0nke-IBhOI7m!py96j17RuxET(2ke_+7?gG(%|NDZ zDN>r|P2R`-{xE=J0{!<3mg@zz)d+Jg5a}AzA1m|9JUGGy0sEl=)FEk5Ea5aytvSML2R#-}GtSI+vf_ z?vA!Y#izwE+tatQ(sgv@JrL6bbg%m_O2|dXakA|DfCKZ69vWA8_rK?nFo6Txl1!Op z)sJ`g@Ko>bd-`L|tsDwBb5`jt(99KDYt`+eO!a|mmR8D-UOK~hOX#aGy zJnj9?!pWv}QG^y58t0!&aH1g##tW3}oiu5CvW>dm%8-W)T``(8q*sOJvi!5FcKVg- zJ*_hhmC6Ny$s43*=^YN+!a1v(LA!aq!Cl3OBFYX`hNcqgSBT>NGvp5bGFbPb?s2mI zO5Cl>qG`_BebY^*oHAAjBt>Hy8HYIvCtM?2e+dr=QAjGXf9$n0^qiL`Q(njnQn%+x zS>mlgDEg8TwXdIwEgzkFI)Xd?Bwc9_FE=z#SnW}VOELMLJt9x~qkSlsarWWIt|TjJ z4|PczdmqL3U4ON?=onb+o76gwv@1R5{a#F|xK?)~+U4cdsk+bMWfDp;w?zHQgqHJP zNDJRIoIPhc2dgd#4e5Ccv3yZpl6Q2k5y^f#z z4Bvf!%m5_|-)mFGt9w}aiSG*B_DU0YTs4lmw*{h|*ohs&!DbrVojmaYQ5IK+sFVU9t0|S)_UXLn$dIc} zavAObuKLG2$mZFa1@o3d9`s!AF1+mzeOjR=@@lc{a_(9c z36>OrjizgS{8OJ5A(82+L;X_lC?h6wD)qeAx|tp;}_BXOgV zX!6=)o?oi168bm9uRlBT4v^tIaES`Z-0FST7?|#U+*Enfq~tgl9nsUT+ALgPax2QV zf7K%0(Nxnss?WTYRAD&-;tV*0@GC5v(ba$DB+CLKN(3#%-duP5^wPKIeWvq2AsS4j z#XZP-qM$v_eQ>6_F2DD~0SQeIi6Agk#wL>5@PRjDtrYnPmK?m}%q5r-tZ66PPJzP{j9`M8x7u7frH(C{8nr;3$t++5vSx+>;E`HHP+dss-s9XB z*L&QjRhWpkyFjSC0O}`(1t<)~o?oIezC*WOAZ2M)Z2I#uVgdiGI51JKf3g|z5%bg( z;yfc}RaLW%vLq|llxf9OCiikImqMWF4qg2f3X6swGSu?ON0A-?Mf3dir-~qh{(Sxg3^c^!$b3z{g>g2AxO{3#j?_h5>?wVe4fum7=<<*j9K+Ms+zZ5EHzAUI#cwlP0O3>&L%IW7@2?(p@BOg z(%2t!8aK9$&K*jw-yDy&_4lSVQ!u%w~=gn^W#)!*|Y<7GjFuJIZ>{#c!#}pl0 z5xII%yYx`n*!Xz>F(((g{+toi5t-()R5eDBep9pGaeX(6p-f4go{F1gWI_YnOi2>4 zb-adeyiTn5)z1vc^&nhrX|qdZPGiw35ATkol|4yp0^%>u|* zC%oDhsQR?JeP!C(sJRw63ZuPw;(%-IHS68Lt`zMq=|mJYwo>_>z|9zIJ+yV|H1lXu<;EJ7tP2Ovo#wmau#udQXLS#XZZUc z82^`v!}6*pk}YE5>Lc>$hd*FA899Z#WYFcaO`NOHy_+f#sj-oUkw1~#b^HOsOv0o_ z%ukYK&P>xlz%U~}@H@9PH12H^RcyO4{AOUM#7O7*$p0{5vZ{E^G7I?efSn20eBYj; zDPvo|s2-#HGx9OK6H%Js7%i059UeeF*)RAab@ZJBmNsA?AjBM)H1zX6Id!FG;9;Ek zf`W&W6bq0I*>&h+lN&@HfnZcZi3L+2y4d~=#MuV&S_^r`?w8?>UT<}RpP`DEZ@_`F z!#a-^`a)uTFip%*oQ3wYWSN&pcxS8x1f8j zMZYOe3FfSp;n!D)ns@+fr;X3NzLxyCpfKzEQ%j}Jh+INTOG-VdM2;Ar$&)2X%}tRn z(sS1u=G<;zB!rcA8-yYbYB?|9oKxB)tQsLj9$xr4`#GM$Xp^!(k2(uNKQZ3zT7*SS zRyg|Z)yHCxePaZcbLplOR2;$+x~hYQ8#qfRO6;N&N0u^*72hzO|tKH8hox-i+^1C zX{Zy}Cvg<4ts}4}N~7G7?eZ*=bh#0s_fWp1YVRhw!8wuSgM$q(PzwgvxSaE&f=Uru zlo#biB8C?=@480BMxWy)QNx&ZbN#PlIWSpu}>luj;S{HHP+xs2nd=i@Sc1zVSRN=H^7wLtXU3I9H~% z3i|nb`|;%a`>6Y+o1W+T*)v$G*^ zps%5;5PG5a)8gL8-{5E~0UYo*CrDO$wkg6o$W-=z25n+S@%QOvl9eF!UTor6D(r1bARW;@qIHuXV1im!+ z3o@paIqRUKA~bcfNB%)UxGSe59bW`DT?Mn3lwCCqOOj%QYGS$IF;Y$QY9(>egA&4> z8{&L5R~&a1iq$%vxRCdLXlE8zKbgLV#_wBzw$V5Kx%C6lyN#Yl@z#vH^lBK}IrQaj zxen0@V66g)E~$r5DMMoxz`a6S8*`0yrG>nsMYRpMSjc#j@f~^};y99yo5xR<Z-+wGl9jW8zH+QP=l~_PC^QU4 zHb&7-7iRT4yCK|lXRL$Y#!BMyPDF7{;f3MJ(K@3vp;)@>BmfVNtoh&I05XOJZUN zN8M8642?=<6Ywtwv^IV{IWaxSQH^VNmmTmEyHC+|d-u;F@k1g&jMDrr^6wMxBp zKQYe5d!kIbR`n0E6Pri#i%}Z^fd+a%mF$mo(I>EFD5Ip0Mc0inoon0Mg?)pw#6cmq zj^H;R^!98uKUI6^aE@Y%WAt@DSrjUSj<*gdI_b@oUQN)``&4|(+A{LhP$M8@PA^lM zX%e3g^LrmaNZlVC5qQMOUBes=U!4(CYPt3%UJPf*e0yYfOEXFOv#7bI`R3sQEy84Z zZb~7qyIR7VYeUzq#Xd*ehzNTxo#2=UF0r7a6X0d4Bmt|lbymR>P3Nd1GPUG2{+w=# z!au^h4+^6tk4HH4V!?FpX>=k)o`U+?V|0#cHgF|n@UQnm*rO7yd-q_P0tw zBPf(-)bOj}^e^*qxYi;W8JkHNsWgZS68vbF80a>^0z|`;ZSN)5`ONM_^N(4{+nqb6 zmw(czpPjZ_kk9P#yN(niEGV+s58m9WrwGmlJ?l5kwT3$HQn3#y+~q-%k#08wPO~GD z-4l$8E$Nb#r63V3;{dyyux+|R#xswk#9(%ydv+?J?xKTsS7;s6MqwsW{)p>da-3g} zU*+whD`ztDLs$Tx!DYz5g3gq4rHt4zr{y2jQJ#+laXyS#ZmemK@8d|}BAeZh#>m+? zrUAvgjx_(61$Yjl8Bh)OJe!FuAz3LBW!6(LqO=%YgwJ$~_jW31amU)Yz91>K{>@Qh zQs%kgL{!X0i5sz%{T7gs;GKguiDI2|wngglM#nQTaITVe{- zQ52%{oH5y-oaG68$&07?_*d~4GlnXH*=Zgxh8zB=lZiKPZu)U&kaTxVli|fswQiIT zJgTD>4auZ$<0du9E5UPV3pd8alv^!E4V)rT@g2$C3(mi$f5|OQ2_gc?Tam_DO{K#f z@=V9))b1daqVA?Pv-HyA+`7Bgd>I7ib+6CnacbtdG&>^bD>4qrrCQ$15klm_lFNr` z%nj>5zxz^U=IC?gd&hucTFb#m{lAg+5rhlv^8PJaGd#7^dH#Jsse4!1pAgrn$qy>8 zDmFUH3oC~)3m3nxyPKH4B5LF-m1WH}*8bV}^lGmn-2Ss=$u1-TBYxw2cVSkG&EjTo zbeEQ`OR=ORdgeJaey`?{5KgB$%tR7^;Z-4S#L3(7|vlm|oU90ea0J5pI zE=7GG0+EoTvD6^)+ZuCHz$mCy{EU>}lY$QWXV0W(WbZtI>N9OHYQv6nL$=E55=#Bs z(q2Q0wnOyGA*XZ1B+ufD^@Rtt7DDbFGkG6)f69hDUc@VVtJiR4GVdsULu{7Rd<5@x zJCD*_3cI_r?QIG+F(}ykMA4xi*X-1otChFo_qicS#(;So{=+n<|^qTbHvN&8+vYsdWQxj1*BpnH9^lYLRC?O zp>4DBOQXJJK(Z@+%~~Vc3a|JR`vIDWs-8@(q^pw&Z8;dFF`8W~=9uN<>j7Mm{&TaTk!)jc++%Y4j$WH|(2j`-@9H4Ueu_BM=6@QFBr4+Y%%3-IBSqlR#vStm zJPPkkdN z+l_Qj92BV6y!WZ*F_>fNbHW7Od}vN1{)gx5x4L4$1J_9T?sj;$Xr3)WDz^r%{;`v&O{uDi6Y|L`BUQ%U}%D~~Pb0ge1$d&R~9+m8j2yW1ef32+Bf zSqCg0GQ_TvG_p4nbxB*9ap%f}sigpR&fR5bJqJnY9qO=QPb06c_bT^dA6 z9d4LOL20$LzsYEWoc^L5#4nw7QHcks_$+0@CIF{40zJ??igA1EISA7T%2vUst}A@n z9iEHP5dVW8c?9;3?1hrABS*$&I$!f7GKJ{Y+Lz2NT8fKAFPQVZx)s<&xQC)HFmf=y(nPXEzP~tl**#;~FI)XStD=l4OpTlkQ zcDMRfI4bP@X#l*5*%4X1>E}llQZ49xvD_XT7`?X;$xah7PakXBj2atwh3vQW(&XT_ zF=46KND(YC1`BT12^{!?stv$N4a@Y6Xc^f{9k_wYWBE3xtP}ec-CXn7NNqvpF`WBmJQvp8SQDu2x{*syn4CF>wQlpt>^gY z_dnlxzb;^`B1+<+_dp}z=Co(iXc1rMspxfLY}>}-Xst>p_!al>GupB`vmeKKCdNU| z%n73VG16I_QY>}yRBpvDW9O+9Y%o-_cT+63bU#Pqn-5#35%U+dAy~VNvM{Y_BX!5} z3nA{e=iE`|pUW@t?<({i9r}|QuUP;QHCi{+Aq5s)ij=1qhNw5us!{tGi_l%m;FgRhZ5*ntxr z<_Ifu1?-fEeKtV+fH_~^k1(b5;8ln<`VDdPt5v;XuY$6qNU3~9g!gxDwcsUiQglSaWzwr)uNMr&bwW1*QN|FI_zX*=emS?C}azE^;BI$ z4)37fBMjKXL}xqw0<$A{483Z+Z-(uVdC+!Q`H!rOC--a7g)<5s=Ak?vW&FC30bttc z*tV$@eowLl75?Mw;bi&W%jK{Um!rpPYoe>`!+M_O<%y@I1i{KZlmfR-2s9|*pgtv< zP4_D>`z@dAAL-_~=AjG*zYzIzI-1R!t2Sa>$YR|?m&qGJWiXsf?FBiP>^pX8Y6EoT k{7SUwlW_>Q_y{|g9QWDb`4QndKw{^e9;p3lBx~fq0D^cBmH+?% diff --git a/captures/20260107_083405_id31.jpg b/captures/20260107_083405_id31.jpg deleted file mode 100644 index ce65e18d4508f7247b543755b6433986893ed4ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8415 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 E0F(;N*Z=?k diff --git a/captures/20260107_083411_id50.jpg b/captures/20260107_083411_id50.jpg deleted file mode 100644 index e0652335ceb8a460a02821361019e841252ea3cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9075 zcmbVxcT|(X*JdzOMXG?*h&1WaL~0a}4gpbmk)ntZ5Re`c73n1u0qN33N~9O*y+{|3 zUIi(^gc2eNVdL-H^X>kzf9&pk&&+w>^Uln9o|${+-aB*g^I{diq_3@~4WOU^04T^0 z;9>!w3AjY@PyW@H{s|@UUrj|x38bW^qNe^&p`oLtrlF&url!3@OLzI7kl*O9T&Dl` z@o$j->UxO^2&B49LrwFaCjU>>MF)VH7LWsY1EjbKxWr5WWTv?20f5Ljss00x4EDc- z;u4UOikgND;|jS!9TOQo5J(11MMg|+9YX#Opk$_E;g`8j&1&jMbJOdZ?AydVS^U**UIratR5G+!7U&SGc2iS4mk@>w&h8uAaWxqsQhJmR8ozE>EAi zK6iWJ^V-+XKOitD^nF-(L}XNSQu2qC)U=Q38Tkc;Ma3nbOTSdt)YjEEG&VJNb@%l4 z^$!dVO-}uoo|*kQH@~*NvH5Fj`}fW+_VDQV1b2%6d-jhP1pxS8w8-`Ukry+W*QI}J zK=Y3m#U+1o0Wwok@yk%N+&86h^kTg!`_2M% zVfKGV?A`wtv;QXczj)09=z$dE#RD<}zyRDmU8QR*dV_`Li{Gk@?OM`QDeQtgyi!~V z+q-Tb{%UUxolUwwzA-HC6-s<~*96RJLOH=9K!@z%3S>Q9IGY}$f392-NN-jGP@D*(lqHS*kELS`O+F0>F__b1`HCLzcV{0)ecD(Z0{NeoQnStJV z^qN+b*yoo_0ZL}MrjS=kceG!w`V~GBG@p&#H|vP+?t!ySqtOc#(of=qqB=PVYxSY% zoav!qtU$*d$$Fgo!#OMLwFW;i0f8oo!qwR0BFEZ^;!YMi!ROP$ZzNJ{^krlC2gW#+ z0B8Ovlk)@xg9=t$*t?PI`qJ)F?mYT$wJ%A83KScBT15o2{bGmnkDm zd;JDeQi8RSs-;(C^Ip0Fa!QH<5&L0S*DgMp^BR~kA8yJFnwhyIw)~`tdC|yStdd6i z@<6Dc?%x`;Mru89XY6MQE)t(2-Ce=mtqkgLP3xL<^N>>2I-RioTk7Ls@RnVX-C~)V zZmHq(NCYNaOcHkm?2VHTz0EEM_)Nz%&9C6^# zIshd!ID*l+bg@%F`%-*@?G5y-`t(U{`&Cuu^J-PYd6&a)eJCD(i^*a3%TIvZGR6^8 zD7C=Kj}u!KE<@%&f3*pa#@ne#R}{9xV6NEU?mH|i+8id63V=z2+Ru=5zyK8w#nd0* z2yD(78?lDiU%`B{_o7BtKL5>3H*!)vmwKL#?M z7Z(;D<_K*%ZSHjB3Rk7iO&h;lwn4Ur0L^vR&}SHnI8`OLkfI@fgXfNxr+i97tDzm6 zL1s8THja_J?OcHR%>dBGY*%nhoQNHS)oSsk6%;p{5xgMwy?*TqrN_O2p|3!i61Kw& z0NQ``0zi$wvi`xI8-x2e6VXHUtZCweW8s;I!&3S9Kw+w1tT1|sPW3gaoWx0#!E0hD zJdUNq&wx9kWwtt!OBstTky_ALQO7==yMx=prQ|ue3Zudfubs@kCu;h;BmtgHN}ZMl zi>Ih0JrE2%eKWAitQq?Iby0D|7AZRz1-pe^E?Bda&!cKf_vgWxM13zGBHUkG&g+h4 zG~J6n6EK|JKN+L8gf>eKl|wG;7ujsH)LXqB9O$QH48pF&@X`Dr%9ZbA{6e>Q+l)!h zB*L+aVqq=M@5M4NWjwav8$$*~tbux$PVZKT?lb=3a0@VYGX?$ztv(l6jZe?Mz~p>Q&b=MB;5$)+;b;vN>-43J;HlC{@$;a~`GZWp))40_ypX=HZV9!PLgunH5I)^e3Yk(hh6C}8Y2WP$ zlCaGKrv+lnq0R7aeD%|Nrvq$~bE!M~@NyzGW|ISPU0sGK)0hl;`9nch%ebft_l=uV zb0ZchW7QlFU64>Ul`8wR(=h6t{8M8SIKB@c<+o_AJ^NN+h3LFTG^H93s732^DZ8&L zi!MmytGs{GU$yb-1Z9k=EGXHT7iteXi8G!NZ@uG&j@5Xj_c*0X(c=p+_*v^^$;PZB zFKgc0?J~se~x)*>>QIfu5h#Hw~N7z-v^o)TIcE9G#_n0Y(`7c+a12l{>1)%m$=DMs%) z1DN_DcmS1+aFm{J=w6{vmw{|ZM^ziSf7(mXD+(BNj7_Oe*bmmj#U>TYM+k;2HnfJQ z5L{t87@D3|&+hrL?(>&RA@Or)k0WMV?f#K?TfSZg;EwHx<-p+`6Gm${F-Cc0;s6Y3 zYNUEvoWNyD@r`YOG}(6d8%D9aF6Kd_uC%Xq@GU8>xo6LTmGocYy07hpTBx%JWI)&h zZ1<{Pm@W%uv=#8y{ovMDWnLcRvC+qL4{K3Gn8+m=LqDFEONuntV2H!l18yd5e;*)J zn!E&$HvKjaznLZ0?It)|V4+f@`i=R+Ghd7XN!jd!5Og7f#mlc5;yW#}B(Q~yV8>O% z*fCq5lcK^Gf1cpy9Xg8_``d{V%!Jh_|@=5nPfl9jT0q*7pKlHe^w-E>(K% z9VS7Y^=6945zb%^y^?ol@!43JylUHx?WTr+*_}aET<>d8jpzGvm-Ej=j>7q$)b$er ziUY#jZ`kK;^eY#A(%FKJC6w2jvqgmRI(7^HLT2r_?J!Q3Zp#9VO;h>Ph?@5X3L}$c zGInPGubYA*c(e z9*S-0+@f=m>4PW~=Tg8yy~<d_AvTRlBz_2Hcr(R#kSo+nQM9HA-B74oyZbTL5d^a9g^?(*O{31uH3;M!+~-^Vuhf{_vw_}Re~j8#l};d$)V3=GU)m1%&5r{5ynmC29MXCy^%|@ zn({;M_>x9b(-DEPeZcWn!4Z$Y=V*1ab_q8Dge9m21Zo2XA2d!?1yUn00Q^W{J*^3b=HRp(zUbLu3h>*|13`+E&SG zg5&vXbukQtU~wx!O1^KJOQG>u&UGOQSM5u-*_@|AdAJOP7CqkV=3CK%w0SfDiZ@g* zy4Rk&MBa_r^)mQ0m+m6$p)@#9vENH?V{*)wvNPpz>+jIW(92EKu_?u^sJHV~b>h4e zwJc1J-%vh^=Z_-!@7cXXWNiCr=F#CCwfxEy#e>sJkM?ecR;3&&jamO)AzkiEG$PRB zcZK61DObZf1_(tS8=QO*$$w`jW-z+kSNp`t(!=--%L_Ff2BTJ zev~MYoSpbrlE5*a?YX{7lxlLv?w&sEclThFY<~s*4u5>I-IdE_MHIqkuKkHbFrQpC zl0O3CU2VSAUGk4D{}>hvV_~Yds)h`A2(Gs&pT~L@LKZkWAdXU84%d3wH(#o&yZ=;Q zb;hDfRZV8FL_4f~cD{yl>jV)Z;P&+n2P|R0<*|L!bxEXM|6~i-3aff1BNXJk9MIm~ zEcvr;R@I@v`V!bja=D0$?#C-jPcg4t#{(MuGzeBCypG`8I)3s^<#&cG>1p=l0Dy*5 zMXz5CsqohUi^RQ;=xdX2ZfdHYI=K?3A3Unc6n-OOk;Ct9- z;-s6!WP99IWV-;A?1JP-y=~VJZ&&IJ`S|eJSn6)$iNc2VuQNA(I0ZWu>+&l0>CW+A zZ76_0Yu5s=CMxsAtswyc+I>os2fP|!rd{?^!C%=ft;_<7JPDGJ)q7|MY8VXf$dE_S zT83r}7Y5nK1^99w-55*1!p}=;4JkuQ}+~dRuRA4Qkg8bMzdW|{y2h?sNH(7}$Yqo#> z&;A0zB7l!3ZIy#~ji&E*-{%$ADOGn$>y)*9M+X6X2`+*R zvw0;X|EOc7dMw=iu?bbE$+T;eTGIj#Eydn+TS`ivB{?edYVi#^4cc#`#QZ9_q{U8q zIHc4^jROQ7LOowpKvQ_CVI@C3^HoJ#pZK3i1}GA*iFEtf^PeZd0t@VR6D-dgwAjP- zwrH8>A3aqPb(m1YAE+Qm;Kr}xEJO#5nfZtZCw z@(QBs8~)q+3eccv`Gey8$LUHcbT{=7qYx86f8)RtnGv5yrQd56yH|zu{lW4G7gX2< zfF5>*;2OYab^&;wYW1`h%hY&XCGn?3T;gBSyJp5v{EHRVwp&)|)D>%Gbg%~{ zi}&f`+@IF9-bkmWz0ohO(@-)5MN3EIVszW*R6199+>gJ3xdKpqa0Se--Q~sKXS`iS zIjPPnK3=c(3EFFi?_}rrK3PmJOOW&GYXJ^8YkVALq2mI;aExMXk5m=?oW5cZS^IsT zexyzN*Wqw3;0T1XIL~amws-hq<(j%EUaE`G;{)|xwCz>}{nisKRBqG~ z7@=f6tr_g-qY6=5+e^%M!@8N19=+<>>}G4xkiS3fXz@mT3pvNV?i_&XoO-S(Ey$IM zV(I3z{d_`V@NAXnI<;vtxCZvNC->Kg$p~H?xZB2w}dH`@s&eV_3HZ zZlKOapok39FZT_jaW(gYKNd*cd-pgco?1op%ok;No-OnU&ygSc#>;1x)b_N}Y@@eO z`A^u1BufCW-`Dk~ybH#=^eslqt2OG9q)fxF| z9zw0J)|yh26g+cU5hPkDR`D?=7Y*EKf5-J>C=s95josx6sjiptdTIJ}LzNiY2pm5_ zWwk@eCPi(e@?i4yw>}Q9=dUyzpIl0{jQFlYSU#uGKMTcao@d{znuN2EmU6C7UmkQU zDNn0U>@!RQJ--0xq^h2YyJRsMo}daKReyU5p20sXT3fD24LX`@v;zK^=y>rZR_Nuo z9AJl8#yog1jr`Z)Wq&6vUoa0cecZnKPB!GhBkZ%E9a zj{-A=AL{<*FpYY3mneDxc=7J>=)H2mZ}WO0@)v-H^}6xChlbOslUL$lQ^(Ywpf0h_ zG{6!CdaeYz+6HTXTc&qp`P0*HWX-tDXWh#p zX@pkHcbMyOA2e?tv3ZRb{u07j*KcJYfIot&fEiekZ=zGw&QIeR)`%ju3A-V~$aYZcbt@rXw`Gg{2U zo_9?+1NAs^iaN9_^&f27VE)HGg0%8=kL`XS^<6`SE><1+YZNvZyL_S?f zWM?I{PF(;dO;6#H8YHpp6&yEtcb+;@zkB}0H&6s0Q`~#)iBO9;HKUGu)Ak7{44y>Z z{Xhyk741(i0E~F>y1Y0j!gs_QgJ*o4o9Un6<5f7+;lv&0xOG$UtXIjb7w!t5DZ7Oa zU*m|>l4XI|=7A>LD#@FyHlkA-&I}()%}DERpEv6oU%_+qVmcLmO)uvP=anqTyHrY} z6K(eQt6_ryq23#mT&Jb=$6)oEAkY`uw6AyHh-)*k1dT8$u(md^6Y}j1?>+Vi; zT|t2JYCoD{6@fQGIOWD$KaKUje6=mT{6+BvKrbDeWwX@x{Wqi1AmwYZ#(5=1Hhe=b znDStK3hai6P$aRq$=;l)n3Fzk>fn^dMHzu`k(bCi&ea&NCI1s^ER)x!WZ%9cY8~m$tSQ#2aK_FTstk|DRgE24=yZKnr6hm zwA@-c+*hFHAy%m8t)3Srs5xAI+uvN(Yo=uzCo)s9F$FWqw;sB)@Ip^)`yC1@`zXo@dY&{98SLB1> zZca;~Dq;6ajoH`(og@l)^pYD9%4;46TT2iWoZCEfF9{kT@)ifjg+@MpAE!TEkYxY}d?Ik*RLPUIV@VWAw!rffJbqg%MyhH# zWpK6_tU(wu%Tx%Gg-Ys=q9>{}?q6XK+`M!ZE?Zw(V0-Py>$2^4hireJH6#>-p3o>v z1|T*{dfgPRh^pRS&qZq%>TU1HEiV)p@nmnf0yvrbhcsO|N1)$hz{bww?W^;QvGXG{ zt$8qq86vl@mtc~-wL_2iuu$CML4WdRYxF%e;A$3$`%Hw$T0o+6z%uGW$KE?&%s@Op zX&%nF1axb(NWanJHV^oiBNK`oLv4EY#E}EE`Pk;1YpO_`K|Tw^K6H$!$_E} zAN$-C-Y3gKH1LyKn)$GVwf)Cy473e;w;78WL1CIbb@^JQ!tk-ZT#P556SAdZGjimO92Q`%%62 z1>ocfMWVwkw|T$t8kTy$MC2b`HiY#~i@^FAPk`TgF!}C*U}NN0mGqKbG5LF3jHuby zhEpBNkQL2npD!~#N4+_-YmoVG8@?BSDC4(lzqz|D>ofkC+^HWf7VAj-2FE&f&g-KA z*nzuh2jsw{_SrYgFK2vOVE*s3h@LXcZ&j<$np-0!b5-BHY!-5tG;i>_@Mlg@&eE+_ zfK16rx55fXz_v+-%K0l=_tloZ1CR9(ypC}7(TTDEkrA^R#;)<{NxVvex!bzj+{l+-m}Fy;=6TrddpS1(k6 zp}*|6U1;tg~_fsOh4{v*mA3{!fcn zi+}vSR&SJ!%^4Tu)PAW}LrvKiL-<_@KAT96>*Vd?Rp-8bN>p67uhk;K;eVCVY%5CS5zo@*`w1bdEAr9mag(Y`C zl}kP^XiyA$U<6xXjBQa_x0IC6jaFw0efR(()o)Vce092B*_#1`MS0NTsW_koqViVd zl`-Tc{x)Vh#a0G&-@01WNQqL#yg=~41ZH3|s{}}-Jrnjr&5oihn~kLLZQ=V_&fc4R zK6nc`>H-8e?Ll-I+fc2J(%Gh$&EXQu)|mZ-x-33ddvd`T+t7`q#5YgB6Kg{i(jGWJ z22fe8QS<{sj3H(ZdpD4C$rkPJW-ZQ>PJ;EGx;J4O;)UaW=B}G638*i^VY44>@R>b+ zSb4AIJoDeYa?w+GtFjl0AaeDe)HE+e5m@ILTDL`8!zUBBrsZF|3pv-B z2ZnOh2cCST^39#tlhiu)GEb(kSo+5WbuW4LOrera0c7WP>lQwAhUi{hEB+E#rN@w_n%i@rM!3OP?L4SB$RCpvkdy`x`#%weff!g;@z z9d^AJzsnsNsc+1F8oQ6S@^8TCs?I%obpLb^o|3&fMjH^*XQ9ojyfqDq8&Qp=SqoE` zhdMB{uwqF3(y_&DKMt-XZ_dBvF*dpt=dS)FT9^yhH`-!54G?1O5Zy8UO$Q diff --git a/captures/20260107_083412_id54.jpg b/captures/20260107_083412_id54.jpg deleted file mode 100644 index b68d49c68125be41c2b8ca2e73594809dfcf21e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9919 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 diff --git a/captures/20260107_083413_id60.jpg b/captures/20260107_083413_id60.jpg deleted file mode 100644 index 10ec780b0ffd3a82d11937d7482c6c0964616a69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8304 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 VE}B3~9Zr9FDrzR^6D_FI{{!Sfj7$Ik diff --git a/captures/20260107_083414_id52.jpg b/captures/20260107_083414_id52.jpg deleted file mode 100644 index ef18880ea3f7aaf787579a0aec35e69b98e32ffb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9149 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 GTKFF-gX=i} diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc index 770496c..473e19f 100644 --- a/src/config/config_manager.cc +++ b/src/config/config_manager.cc @@ -266,4 +266,29 @@ TripwireConfig ConfigManager::getTripwireConfig() { } return config; +} +bool ConfigManager::updateTripwireLine(float p1_x, float p1_y, float p2_x, float p2_y) { + std::unique_lock lock(m_mutex); // 获取写锁,确保线程安全 + + // 1. 确保 tripwire 对象存在 + if (!m_config_json.contains("tripwire") || !m_config_json["tripwire"].is_object()) { + m_config_json["tripwire"] = json::object(); + } + + // 2. 确保 line 对象存在 + if (!m_config_json["tripwire"].contains("line") || + !m_config_json["tripwire"]["line"].is_object()) { + m_config_json["tripwire"]["line"] = json::object(); + } + + // 3. 更新坐标值 + // 注意:JSON 结构根据 config.json 文件构建 + m_config_json["tripwire"]["line"]["p1"] = {{"x", p1_x}, {"y", p1_y}}; + m_config_json["tripwire"]["line"]["p2"] = {{"x", p2_x}, {"y", p2_y}}; + + spdlog::info("Updating Tripwire: P1({:.2f}, {:.2f}), P2({:.2f}, {:.2f})", p1_x, p1_y, p2_x, + p2_y); + + // 4. 保存到文件 (save_unlocked 内部不加锁,适合在这里调用) + return save_unlocked(); } \ No newline at end of file diff --git a/src/config/config_manager.h b/src/config/config_manager.h index c2093f7..72ba12d 100644 --- a/src/config/config_manager.h +++ b/src/config/config_manager.h @@ -89,6 +89,8 @@ public: TripwireConfig getTripwireConfig(); + bool updateTripwireLine(float p1_x, float p1_y, float p2_x, float p2_y); + private: ConfigManager() = default; ~ConfigManager() = default; diff --git a/src/videoService/video_pipeline.cpp b/src/videoService/video_pipeline.cpp index 11a2435..3cda195 100644 --- a/src/videoService/video_pipeline.cpp +++ b/src/videoService/video_pipeline.cpp @@ -25,20 +25,29 @@ static cv::Point getBottomCenter(const cv::Rect& box) { VideoPipeline::VideoPipeline() : running_(false), next_track_id_(0) { detector_ = std::make_unique(); - // 模型路径 if (detector_->init("../models/vehicle_model.rknn") != 0) { spdlog::error("Failed to initialize YoloDetector"); } else { spdlog::info("YoloDetector initialized successfully."); } + + // === [新增] 注册配置热重载回调 === + // 当 "tripwire" 键发生变化时,ConfigManager 会调用这个 lambda + ConfigManager::getInstance().monitorKey("tripwire", [this](const json& /*new_val*/) { + spdlog::info("Hot-Reload: Tripwire config detected change!"); + + TripwireConfig newConfig = ConfigManager::getInstance().getTripwireConfig(); + + this->setTripwire(newConfig); + }); + + // 初始加载 try { TripwireConfig config = ConfigManager::getInstance().getTripwireConfig(); setTripwire(config); - spdlog::info("Tripwire loaded via ConfigManager: {}, Enabled: {}", config.name, - config.enabled); - + spdlog::info("Tripwire loaded: {}, Enabled: {}", config.name, config.enabled); } catch (const std::exception& e) { - spdlog::warn("Failed to load tripwire config via manager: {}", e.what()); + spdlog::warn("Failed to load tripwire config: {}", e.what()); } } @@ -70,12 +79,16 @@ void VideoPipeline::Stop() { } void VideoPipeline::setTripwire(const TripwireConfig& config) { - // 可以在运行时更新配置 + // [新增] 加锁,防止与 updateTracker 或 drawOverlay 冲突 + std::lock_guard lock(config_mtx_); + tripwire_config_ = config; + // 重置宽高,强制下一帧重新计算像素坐标 current_frame_width_ = 0; current_frame_height_ = 0; - spdlog::info("Tripwire config set: {}", config.name); + + spdlog::info("Tripwire config updated safely: {}", config.name); } void VideoPipeline::inferenceWorker() { @@ -156,9 +169,10 @@ bool VideoPipeline::isLineCrossed(const cv::Point& A, const cv::Point& B, const } // [业务逻辑] 处理跨线车辆 (异步截图入库) -void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat& frame) { +void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat& frame, + const std::string& locationName) { // 启动分离线程,避免阻塞主视频流 - std::thread([this, vehicle, frame]() { + std::thread([this, vehicle, frame, locationName]() { // === 1. 准备目录与文件名 === std::string saveDir = "../captures"; try { @@ -224,8 +238,7 @@ void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat // 3.2 插入业务主表 // 假设 SystemID 为 1,实际项目可能需配置 int64_t systemId = 1; - std::string location = - tripwire_config_.name.empty() ? "Unkown_Line" : tripwire_config_.name; + std::string location = locationName.empty() ? "Unkown_Line" : locationName; int64_t dataId = deviceDao.ReportIdentification(systemId, location, cColor, cType); @@ -297,20 +310,20 @@ void VideoPipeline::updateTracker(const FrameData& frameData) { float current_is_ev = (det.class_id == 1) ? 1.0f : 0.0f; if (best_match_id != -1) { - // === 匹配成功 === TrackedVehicle& track = tracks_[best_match_id]; - // [新增] 跨线检测 if (tripwire_config_.enabled && track.prev_bottom_center.x != -1) { - bool crossed = isLineCrossed(tripwire_p1_pixel_, tripwire_p2_pixel_, // 线 - track.prev_bottom_center, current_bottom // 轨迹 - ); + bool crossed = isLineCrossed(tripwire_p1_pixel_, tripwire_p2_pixel_, + track.prev_bottom_center, current_bottom); if (crossed) { spdlog::info(">>> Vehicle {} Crossed Line: {} <<<", track.id, tripwire_config_.name); - // 触发业务逻辑 - processCrossing(track, frame); + + // [修改] 传入 tripwire_config_.name 的副本 + // 因为 processCrossing 是异步的,this->tripwire_config_ + // 可能会在线程运行期间被修改 + processCrossing(track, frame, tripwire_config_.name); } } @@ -351,6 +364,7 @@ void VideoPipeline::updateTracker(const FrameData& frameData) { } void VideoPipeline::drawOverlay(cv::Mat& frame, const std::vector& trackedObjects) { + std::lock_guard lock(config_mtx_); // 1. 画绊线 (如果启用) if (tripwire_config_.enabled) { cv::line(frame, tripwire_p1_pixel_, tripwire_p2_pixel_, cv::Scalar(0, 255, 255), 2); diff --git a/src/videoService/video_pipeline.hpp b/src/videoService/video_pipeline.hpp index 3b59190..76b2360 100644 --- a/src/videoService/video_pipeline.hpp +++ b/src/videoService/video_pipeline.hpp @@ -40,7 +40,8 @@ private: bool isLineCrossed(const cv::Point& p1, const cv::Point& p2, const cv::Point& line_start, const cv::Point& line_end); - void processCrossing(const TrackedVehicle& vehicle, const cv::Mat& frame); + void processCrossing(const TrackedVehicle& vehicle, const cv::Mat& frame, + const std::string& locationName); private: std::atomic running_; @@ -66,6 +67,7 @@ private: std::mutex output_mtx_; std::condition_variable output_cv_; + std::mutex config_mtx_; TripwireConfig tripwire_config_; // 配置数据 cv::Point tripwire_p1_pixel_; // 缓存:转换后的起点像素坐标 cv::Point tripwire_p2_pixel_; // 缓存:转换后的终点像素坐标 diff --git a/src/web/web_server.cc b/src/web/web_server.cc index d048773..96cc5f9 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -3,688 +3,107 @@ #include #include +#include "DTOs/common_types.hpp" #include "config/config_manager.h" #include "nlohmann/json.hpp" #include "spdlog/spdlog.h" -WebServer::WebServer(SystemMonitor::SystemMonitor &monitor, - DeviceManager &deviceManager, LiveDataCache &liveDataCache, - AlarmService &alarm_service, uint16_t port) - : crow::Crow(), m_monitor(monitor), - m_device_manager(deviceManager), m_live_data_cache(liveDataCache), - m_alarm_service(alarm_service), m_port(port) { - auto &cors = this->get_middleware(); - cors.global() - .origin("*") - .headers("Content-Type", "Authorization") - .methods("GET"_method, "POST"_method, "OPTIONS"_method); +WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, + LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port) + : crow::Crow(), + m_monitor(monitor), + m_device_manager(deviceManager), + m_live_data_cache(liveDataCache), + m_alarm_service(alarm_service), + m_port(port) { + auto& cors = this->get_middleware(); + cors.global() + .origin("*") + .headers("Content-Type", "Authorization") + .methods("GET"_method, "POST"_method, "OPTIONS"_method); - this->loglevel(crow::LogLevel::Warning); - setup_routes(); + this->loglevel(crow::LogLevel::Warning); + setup_routes(); } -WebServer::~WebServer() { stop(); } +WebServer::~WebServer() { + stop(); +} void WebServer::start() { - if (m_thread.joinable()) { - spdlog::warn("Web server is already running."); - return; - } - m_thread = std::thread([this]() { - spdlog::info("Starting Web server on port {}", m_port); - this->bindaddr("0.0.0.0").port(m_port).run(); - spdlog::info("Web server has stopped."); - }); + if (m_thread.joinable()) { + spdlog::warn("Web server is already running."); + return; + } + m_thread = std::thread([this]() { + spdlog::info("Starting Web server on port {}", m_port); + this->bindaddr("0.0.0.0").port(m_port).run(); + spdlog::info("Web server has stopped."); + }); } void WebServer::stop() { - crow::Crow::stop(); - if (m_thread.joinable()) { - m_thread.join(); - } + crow::Crow::stop(); + if (m_thread.joinable()) { + m_thread.join(); + } } void WebServer::set_shutdown_handler(std::function handler) { - m_shutdown_handler = handler; + m_shutdown_handler = handler; } - -bool WebServer::validate_video_config(const std::string &json_string, - std::string &error_message) { - try { - auto config = nlohmann::json::parse(json_string); - - if (!config.is_object()) { - error_message = "Root is not an object."; - return false; - } - - // 1. 验证 'video_service' - if (!config.contains("video_service") || - !config["video_service"].is_object()) { - error_message = "Missing 'video_service' object."; - return false; - } - if (!config["video_service"].contains("enabled") || - !config["video_service"]["enabled"].is_boolean()) { - error_message = "'video_service' must have 'enabled' (boolean)."; - return false; - } - - // 2. 验证 'video_streams' - if (!config.contains("video_streams") || - !config["video_streams"].is_array()) { - error_message = "Missing 'video_streams' array."; - return false; - } - - // 3. 验证 'video_streams' 中的每个元素 - for (const auto &stream : config["video_streams"]) { - if (!stream.is_object()) { - error_message = "Item in 'video_streams' is not an object."; - return false; - } - // 检查必需的键 - for (const char *key : {"id", "input_url", "module_type"}) { - if (!stream.contains(key) || !stream[key].is_string() || - stream[key].get().empty()) { - error_message = - "Stream missing or invalid key: '" + std::string(key) + "'."; - return false; - } - } - if (!stream.contains("enabled") || !stream["enabled"].is_boolean()) { - error_message = "Stream missing or invalid key: 'enabled' (boolean)."; - return false; - } - if (!stream.contains("module_config") || - !stream["module_config"].is_object()) { - error_message = "Stream missing 'module_config' (object)."; - return false; - } - - // 4. 验证 'module_config' 的关键字段 - const auto &mod_cfg = stream["module_config"]; - for (const char *key : {"model_path", "label_path"}) { - if (!mod_cfg.contains(key) || !mod_cfg[key].is_string() || - mod_cfg[key].get().empty()) { - error_message = - "module_config missing or empty key: '" + std::string(key) + "'."; - return false; - } - } - if (!mod_cfg.contains("class_num") || - !mod_cfg["class_num"].is_number_integer() || - mod_cfg["class_num"].get() <= 0) { - error_message = "module_config missing or invalid 'class_num' (must be " - "integer > 0)."; - return false; - } - if (!mod_cfg.contains("rknn_thread_num") || - !mod_cfg["rknn_thread_num"].is_number_integer()) { - error_message = "module_config missing 'rknn_thread_num' (integer)."; - return false; - } - } - - return true; // 所有检查通过 - - } catch (const nlohmann::json::parse_error &e) { - error_message = "Invalid JSON: " + std::string(e.what()); - return false; - } catch (const std::exception &e) { - error_message = "Validation error: " + std::string(e.what()); - return false; - } -} - -bool WebServer::validate_main_config(const std::string &json_string, - std::string &error_message) { - try { - auto config = nlohmann::json::parse(json_string); - - if (!config.is_object()) { - error_message = "Root is not an object."; - return false; - } - - // 1. 必需的字符串类型 - for (const char *key : - {"device_id", "config_base_path", "mqtt_broker", - "mqtt_client_id_prefix", "data_storage_db_path", "data_cache_db_path", - "log_level", "alarm_rules_path", "piper_executable_path", - "piper_model_path", "video_config_path"}) { - if (!config.contains(key) || !config[key].is_string() || - config[key].get().empty()) { - error_message = - "Missing or invalid/empty string key: '" + std::string(key) + "'."; - return false; - } - } - - // 2. 必需的整数类型 - if (!config.contains("web_server_port") || - !config["web_server_port"].is_number_integer()) { - error_message = "Missing or invalid 'web_server_port' (must be integer)."; - return false; - } - - // 3. 必需的数组类型 - if (!config.contains("tcp_server_ports") || - !config["tcp_server_ports"].is_array()) { - error_message = "Missing or invalid 'tcp_server_ports' (must be array)."; - return false; - } - for (const auto &port : config["tcp_server_ports"]) { - if (!port.is_number_integer()) { - error_message = "Item in 'tcp_server_ports' is not an integer."; - return false; - } - } - - return true; // 所有检查通过 - - } catch (const nlohmann::json::parse_error &e) { - error_message = "Invalid JSON: " + std::string(e.what()); - return false; - } catch (const std::exception &e) { - error_message = "Validation error: " + std::string(e.what()); - return false; - } -} - void WebServer::setup_routes() { - - CROW_ROUTE((*this), "/api/system/id").methods("GET"_method)([this] { - auto deviceID = ConfigManager::getInstance().getDeviceID(); - crow::json::wvalue response; - response["deviceID"] = deviceID; - return response; - }); - - CROW_ROUTE((*this), "/api/system/status").methods("GET"_method)([this] { - auto cpu_util = m_monitor.getCpuUtilization(); - auto mem_info = m_monitor.getMemoryInfo(); - - crow::json::wvalue response; - response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; - response["memory_total_kb"] = mem_info.total_kb; - response["memory_free_kb"] = mem_info.available_kb; - response["memory_usage_percentage"] = - (mem_info.total_kb > 0) - ? (1.0 - - static_cast(mem_info.available_kb) / mem_info.total_kb) * - 100.0 - : 0.0; - - return response; - }); - - CROW_ROUTE((*this), "/api/devices").methods("GET"_method)([this] { - auto devices_info = m_device_manager.get_all_device_info(); - - std::vector devices_json; - for (const auto &info : devices_info) { - crow::json::wvalue device_obj; - - device_obj["id"] = info.id; - device_obj["type"] = info.type; - device_obj["is_running"] = info.is_running; - - crow::json::wvalue details_obj; - for (const auto &pair : info.connection_details) { - details_obj[pair.first] = pair.second; - } - device_obj["connection_details"] = std::move(details_obj); - - devices_json.push_back(std::move(device_obj)); - } - auto res = crow::response(crow::json::wvalue(devices_json)); - res.set_header("Content-Type", "application/json"); - return res; - }); - - CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method)([this] { - auto latest_data_map = m_live_data_cache.get_all_data(); - - crow::json::wvalue response; - for (const auto &pair : latest_data_map) { - response[pair.first] = crow::json::load(pair.second); - } - auto res = crow::response(response); - res.set_header("Content-Type", "application/json"); - return res; - }); - - CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method)([this] { - try { - auto json_string = m_alarm_service.getActiveAlarmsJson().dump(); - - auto res = crow::response(200, json_string); - res.set_header("Content-Type", "application/json"); - return res; - - } catch (const std::exception &e) { - spdlog::error("Error processing /api/alarms/active: {}", e.what()); - crow::json::wvalue error_resp; - error_resp["error"] = "Failed to retrieve active alarms."; - - auto res = crow::response(500, error_resp.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - CROW_ROUTE((*this), "/api/alarms/history") - .methods("GET"_method)([this](const crow::request &req) { - int limit = 100; - if (req.url_params.get("limit")) { - try { - limit = std::stoi(req.url_params.get("limit")); - } catch (const std::exception &) { /* ignore invalid */ - } - } - if (limit <= 0) - limit = 100; - - try { - auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); - - auto res = crow::response(200, json_string); - res.set_header("Content-Type", "application/json"); - return res; - - } catch (const std::exception &e) { - spdlog::error("Error processing /api/alarms/history: {}", e.what()); - crow::json::wvalue error_resp; - error_resp["error"] = "Failed to retrieve alarm history."; - - auto res = crow::response(500, error_resp.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - CROW_ROUTE((*this), "/api/alarms/reload").methods("POST"_method)([this]() { - spdlog::info("Web API: Received request to reload alarm rules..."); - - bool success = m_alarm_service.reload_rules(); - - if (success) { - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = "Alarm rules reloaded successfully."; - - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - - } else { - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = - "Failed to reload alarm rules. Check service logs for details."; - auto res = crow::response(500, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - CROW_ROUTE((*this), "/api/alarms/clear") - .methods("POST"_method)([this](const crow::request &req) { - crow::json::rvalue j_body; - try { - j_body = crow::json::load(req.body); - } catch (const std::exception &e) { - spdlog::warn("Failed to parse request body for /api/alarms/clear: {}", - e.what()); - auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - if (!j_body.has("rule_id") || !j_body.has("device_id")) { - auto res = crow::response( - 400, "{\"error\":\"Missing 'rule_id' or 'device_id'.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::string rule_id = j_body["rule_id"].s(); - std::string device_id = j_body["device_id"].s(); - - bool success = m_alarm_service.manually_clear_alarm(rule_id, device_id); - - if (success) { - auto res = crow::response( - 200, "{\"status\":\"success\", \"message\":\"Alarm cleared.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } else { - auto res = crow::response( - 404, "{\"status\":\"error\", \"message\":\"Alarm not " - "found or not active.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - CROW_ROUTE((*this), "/api/alarms/config").methods("GET"_method)([this]() { - std::string rules_json_string = m_alarm_service.get_rules_as_json_string(); - auto res = crow::response(200, rules_json_string); - res.set_header("Content-Type", "application/json"); - return res; - }); - - CROW_ROUTE((*this), "/api/alarms/config") - .methods("POST"_method)([this](const crow::request &req) { - const std::string &new_rules_content = req.body; - - bool success = - m_alarm_service.save_rules_from_json_string(new_rules_content); - - if (success) { - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = - "Rules saved successfully. A reload is required to apply."; - - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - - } else { - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = - "Failed to save rules. Invalid JSON format or server error."; - - auto res = crow::response(400, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - /** - * @brief GET /api/devices/config - * 获取设备配置 (devices.json) 的原始 JSON 字符串 - */ - CROW_ROUTE((*this), "/api/devices/config").methods("GET"_method)([this]() { - std::string rules_json_string = - m_device_manager.get_config_as_json_string(); - auto res = crow::response(200, rules_json_string); - res.set_header("Content-Type", "application/json"); - return res; - }); - - /** - * @brief POST /api/devices/config - * 保存设备配置 (devices.json) 的原始 JSON 字符串 - */ - CROW_ROUTE((*this), "/api/devices/config") - .methods("POST"_method)([this](const crow::request &req) { - const std::string &new_rules_content = req.body; - - // save_config_from_json_string 内部包含 JSON 格式和 Schema 校验 - bool success = - m_device_manager.save_config_from_json_string(new_rules_content); - - if (success) { - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = "Device config saved successfully. A " - "reload is required to apply."; - - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - - } else { - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = "Failed to save rules. Invalid JSON format " - "or schema. Check service logs."; - - auto res = crow::response(400, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - /** - * @brief POST /api/devices/reload - * 通知后端从磁盘重载 devices.json 并应用 - */ - CROW_ROUTE((*this), "/api/devices/reload").methods("POST"_method)([this]() { - spdlog::info("Web API: Received request to reload device rules..."); - - bool success = m_device_manager.reload_config_from_file(); - - if (success) { - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = "Device rules reload posted successfully."; - - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - - } else { - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = - "Failed to post device rules reload. Check service logs."; - auto res = crow::response(500, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - }); - - /** - * @brief GET /api/video_config - * 获取 video_config.json 的原始 JSON 字符串 - */ - CROW_ROUTE((*this), "/api/video_config").methods("GET"_method)([this]() { - std::string config_path; - try { - - config_path = ConfigManager::getInstance().getVideoConfigPath(); - } catch (const std::exception &e) { - spdlog::error("Failed to get video config path from ConfigManager: {}", - e.what()); - auto res = crow::response(500, "{\"error\":\"Server configuration error: " - "cannot determine config path.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::ifstream ifs(config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open video config file for reading: {}", - config_path); - auto res = - crow::response(404, "{\"error\":\"Video config file not found.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::string content((std::istreambuf_iterator(ifs)), - (std::istreambuf_iterator())); - - auto res = crow::response(200, content); - res.set_header("Content-Type", "application/json"); - return res; - }); - - /** - * @brief POST /api/video_config - * 验证并保存 video_config.json - */ - CROW_ROUTE((*this), "/api/video_config") - .methods("POST"_method)([this](const crow::request &req) { - const std::string &new_config_content = req.body; - std::string error_msg; - - if (!validate_video_config(new_config_content, error_msg)) { - spdlog::warn("Web API: Failed to save video_config: {}", error_msg); - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = "Invalid JSON format or schema: " + error_msg; - auto res = crow::response(400, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::string config_path = - ConfigManager::getInstance().getVideoConfigPath(); - - std::ofstream ofs(config_path); - if (!ofs.is_open()) { - spdlog::error("Failed to open video config file for writing: {}", - config_path); - auto res = crow::response( - 500, "{\"error\":\"Failed to write config file on server.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - auto json_data = nlohmann::json::parse(new_config_content); - ofs << json_data.dump(4); // 格式化写入 - ofs.close(); - - // 4. 成功响应 - spdlog::info("Video config successfully updated via API at {}", - config_path); - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = - "Video config saved. Send POST to /api/service/reload to apply."; - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - }); - - CROW_ROUTE((*this), "/api/service/reload").methods("POST"_method)([this]() { - if (m_shutdown_handler) { - spdlog::info("Web API: Received request to reload service..."); - m_shutdown_handler(); - - auto res = - crow::response(202, "{\"status\":\"restarting\", " - "\"message\":\"Service is restarting...\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - spdlog::error("Web API: /api/service/reload called, but shutdown handler " - "is not set!"); - auto res = crow::response( - 500, "{\"error\":\"Shutdown handler not configured on server.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - }); - - - /** - * @brief GET /api/config - * 获取主 config.json 的原始 JSON 字符串 - */ - CROW_ROUTE((*this), "/api/config").methods("GET"_method)([this]() { - std::string config_path; - try { - // 1. 从 ConfigManager 获取路径 - config_path = ConfigManager::getInstance().getConfigFilePath(); - if (config_path.empty()) { - throw std::runtime_error("ConfigManager returned an empty path."); - } - } catch (const std::exception &e) { - spdlog::error("Failed to get main config path from ConfigManager: {}", - e.what()); - auto res = crow::response(500, "{\"error\":\"Server configuration error: " - "cannot determine config path.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - // 2. 读取文件 - std::ifstream ifs(config_path); - if (!ifs.is_open()) { - spdlog::error("Failed to open main config file for reading: {}", - config_path); - auto res = - crow::response(404, "{\"error\":\"Main config file not found.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - // 3. 返回内容 - std::string content((std::istreambuf_iterator(ifs)), - (std::istreambuf_iterator())); - - auto res = crow::response(200, content); - res.set_header("Content-Type", "application/json"); - return res; - }); - - /** - * @brief POST /api/config - * 验证并保存主 config.json - */ - CROW_ROUTE((*this), "/api/config") - .methods("POST"_method)([this](const crow::request &req) { - const std::string &new_config_content = req.body; - std::string error_msg; - - if (!validate_main_config(new_config_content, error_msg)) { - spdlog::warn("Web API: Failed to save main config: {}", error_msg); - crow::json::wvalue error_json; - error_json["status"] = "error"; - error_json["message"] = "Invalid JSON format or schema: " + error_msg; - auto res = crow::response(400, error_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::string config_path = - ConfigManager::getInstance().getConfigFilePath(); - if (config_path.empty()) { - auto res = crow::response( - 500, "{\"error\":\"Failed to get config file path on server.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - std::ofstream ofs(config_path); - if (!ofs.is_open()) { - spdlog::error("Failed to open main config file for writing: {}", - config_path); - auto res = crow::response( - 500, "{\"error\":\"Failed to write config file on server.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - try { - auto json_data = nlohmann::json::parse(new_config_content); - ofs << json_data.dump(4); - ofs.close(); - } catch (const std::exception &e) { - spdlog::error("Failed to re-parse and dump main config: {}", - e.what()); - auto res = crow::response( - 500, "{\"error\":\"Failed to serialize config for writing.\"}"); - res.set_header("Content-Type", "application/json"); - return res; - } - - spdlog::info("Main config successfully updated via API at {}", - config_path); - crow::json::wvalue response_json; - response_json["status"] = "success"; - response_json["message"] = - "Main config saved. A full service restart (e.g., via " - "/api/service/reload) is required to apply changes."; - auto res = crow::response(200, response_json.dump()); - res.set_header("Content-Type", "application/json"); - return res; - }); + CROW_ROUTE((*this), "/api/v1/video/setTripwire") + .methods("POST"_method)([](const crow::request& req) { + // 1. 解析请求体为 JSON + auto json_body = crow::json::load(req.body); + + // 2. 校验 JSON 格式是否合法 + if (!json_body) { + return crow::response(400, "Invalid JSON format"); + } + + // 3. 校验必要字段是否存在 + if (!json_body.has("x1") || !json_body.has("y1") || !json_body.has("x2") || + !json_body.has("y2")) { + return crow::response(400, "Missing coordinates (x1, y1, x2, y2)"); + } + + try { + // 4. 提取数据 (假设前端传的是 0-100 的数值) + // 使用 .d() 获取 double 类型 + double x1_pct = json_body["x1"].d(); + double y1_pct = json_body["y1"].d(); + double x2_pct = json_body["x2"].d(); + double y2_pct = json_body["y2"].d(); + + // 5. 简单的范围校验 (可选,防止异常数据) + if (x1_pct < 0 || x1_pct > 100 || y1_pct < 0 || y1_pct > 100 || x2_pct < 0 || + x2_pct > 100 || y2_pct < 0 || y2_pct > 100) { + return crow::response(400, "Values must be between 0 and 100"); + } + + // 6. 归一化:除以 100 转换为 0.0 - 1.0 + float p1_x = static_cast(x1_pct / 100.0); + float p1_y = static_cast(y1_pct / 100.0); + float p2_x = static_cast(x2_pct / 100.0); + float p2_y = static_cast(y2_pct / 100.0); + + // 7. 调用 ConfigManager 更新并保存 + bool success = + ConfigManager::getInstance().updateTripwireLine(p1_x, p1_y, p2_x, p2_y); + + if (success) { + json response_json = {{"status", "success"}, + {"message", "Tripwire updated successfully"}}; + return crow::response(200, response_json.dump()); + } else { + return crow::response(500, "Failed to save configuration"); + } + + } catch (const std::exception& e) { + spdlog::error("Error parsing tripwire coordinates: {}", e.what()); + return crow::response(400, "Invalid data types"); + } + }); } \ No newline at end of file diff --git a/src/web/web_server.cc.bak b/src/web/web_server.cc.bak new file mode 100644 index 0000000..7891e82 --- /dev/null +++ b/src/web/web_server.cc.bak @@ -0,0 +1,690 @@ +#include "web_server.h" + +#include +#include + +#include "config/config_manager.h" +#include "nlohmann/json.hpp" +#include "spdlog/spdlog.h" + +WebServer::WebServer(SystemMonitor::SystemMonitor &monitor, + DeviceManager &deviceManager, LiveDataCache &liveDataCache, + AlarmService &alarm_service, uint16_t port) + : crow::Crow(), m_monitor(monitor), + m_device_manager(deviceManager), m_live_data_cache(liveDataCache), + m_alarm_service(alarm_service), m_port(port) { + auto &cors = this->get_middleware(); + cors.global() + .origin("*") + .headers("Content-Type", "Authorization") + .methods("GET"_method, "POST"_method, "OPTIONS"_method); + + this->loglevel(crow::LogLevel::Warning); + setup_routes(); +} + +WebServer::~WebServer() { stop(); } + +void WebServer::start() { + if (m_thread.joinable()) { + spdlog::warn("Web server is already running."); + return; + } + m_thread = std::thread([this]() { + spdlog::info("Starting Web server on port {}", m_port); + this->bindaddr("0.0.0.0").port(m_port).run(); + spdlog::info("Web server has stopped."); + }); +} + +void WebServer::stop() { + crow::Crow::stop(); + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void WebServer::set_shutdown_handler(std::function handler) { + m_shutdown_handler = handler; +} + +bool WebServer::validate_video_config(const std::string &json_string, + std::string &error_message) { + try { + auto config = nlohmann::json::parse(json_string); + + if (!config.is_object()) { + error_message = "Root is not an object."; + return false; + } + + // 1. 验证 'video_service' + if (!config.contains("video_service") || + !config["video_service"].is_object()) { + error_message = "Missing 'video_service' object."; + return false; + } + if (!config["video_service"].contains("enabled") || + !config["video_service"]["enabled"].is_boolean()) { + error_message = "'video_service' must have 'enabled' (boolean)."; + return false; + } + + // 2. 验证 'video_streams' + if (!config.contains("video_streams") || + !config["video_streams"].is_array()) { + error_message = "Missing 'video_streams' array."; + return false; + } + + // 3. 验证 'video_streams' 中的每个元素 + for (const auto &stream : config["video_streams"]) { + if (!stream.is_object()) { + error_message = "Item in 'video_streams' is not an object."; + return false; + } + // 检查必需的键 + for (const char *key : {"id", "input_url", "module_type"}) { + if (!stream.contains(key) || !stream[key].is_string() || + stream[key].get().empty()) { + error_message = + "Stream missing or invalid key: '" + std::string(key) + "'."; + return false; + } + } + if (!stream.contains("enabled") || !stream["enabled"].is_boolean()) { + error_message = "Stream missing or invalid key: 'enabled' (boolean)."; + return false; + } + if (!stream.contains("module_config") || + !stream["module_config"].is_object()) { + error_message = "Stream missing 'module_config' (object)."; + return false; + } + + // 4. 验证 'module_config' 的关键字段 + const auto &mod_cfg = stream["module_config"]; + for (const char *key : {"model_path", "label_path"}) { + if (!mod_cfg.contains(key) || !mod_cfg[key].is_string() || + mod_cfg[key].get().empty()) { + error_message = + "module_config missing or empty key: '" + std::string(key) + "'."; + return false; + } + } + if (!mod_cfg.contains("class_num") || + !mod_cfg["class_num"].is_number_integer() || + mod_cfg["class_num"].get() <= 0) { + error_message = "module_config missing or invalid 'class_num' (must be " + "integer > 0)."; + return false; + } + if (!mod_cfg.contains("rknn_thread_num") || + !mod_cfg["rknn_thread_num"].is_number_integer()) { + error_message = "module_config missing 'rknn_thread_num' (integer)."; + return false; + } + } + + return true; // 所有检查通过 + + } catch (const nlohmann::json::parse_error &e) { + error_message = "Invalid JSON: " + std::string(e.what()); + return false; + } catch (const std::exception &e) { + error_message = "Validation error: " + std::string(e.what()); + return false; + } +} + +bool WebServer::validate_main_config(const std::string &json_string, + std::string &error_message) { + try { + auto config = nlohmann::json::parse(json_string); + + if (!config.is_object()) { + error_message = "Root is not an object."; + return false; + } + + // 1. 必需的字符串类型 + for (const char *key : + {"device_id", "config_base_path", "mqtt_broker", + "mqtt_client_id_prefix", "data_storage_db_path", "data_cache_db_path", + "log_level", "alarm_rules_path", "piper_executable_path", + "piper_model_path", "video_config_path"}) { + if (!config.contains(key) || !config[key].is_string() || + config[key].get().empty()) { + error_message = + "Missing or invalid/empty string key: '" + std::string(key) + "'."; + return false; + } + } + + // 2. 必需的整数类型 + if (!config.contains("web_server_port") || + !config["web_server_port"].is_number_integer()) { + error_message = "Missing or invalid 'web_server_port' (must be integer)."; + return false; + } + + // 3. 必需的数组类型 + if (!config.contains("tcp_server_ports") || + !config["tcp_server_ports"].is_array()) { + error_message = "Missing or invalid 'tcp_server_ports' (must be array)."; + return false; + } + for (const auto &port : config["tcp_server_ports"]) { + if (!port.is_number_integer()) { + error_message = "Item in 'tcp_server_ports' is not an integer."; + return false; + } + } + + return true; // 所有检查通过 + + } catch (const nlohmann::json::parse_error &e) { + error_message = "Invalid JSON: " + std::string(e.what()); + return false; + } catch (const std::exception &e) { + error_message = "Validation error: " + std::string(e.what()); + return false; + } +} + +void WebServer::setup_routes() { + + CROW_ROUTE((*this), "/api/v1/system/id").methods("GET"_method)([this] { + auto deviceID = ConfigManager::getInstance().getDeviceID(); + crow::json::wvalue response; + response["deviceID"] = deviceID; + return response; + }); + + CROW_ROUTE((*this), "/api/v1/system/status").methods("GET"_method)([this] { + auto cpu_util = m_monitor.getCpuUtilization(); + auto mem_info = m_monitor.getMemoryInfo(); + + crow::json::wvalue response; + response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; + response["memory_total_kb"] = mem_info.total_kb; + response["memory_free_kb"] = mem_info.available_kb; + response["memory_usage_percentage"] = + (mem_info.total_kb > 0) + ? (1.0 - + static_cast(mem_info.available_kb) / mem_info.total_kb) * + 100.0 + : 0.0; + + return response; + }); + + CROW_ROUTE((*this), "/api/v1/devices").methods("GET"_method)([this] { + auto devices_info = m_device_manager.get_all_device_info(); + + std::vector devices_json; + for (const auto &info : devices_info) { + crow::json::wvalue device_obj; + + device_obj["id"] = info.id; + device_obj["type"] = info.type; + device_obj["is_running"] = info.is_running; + + crow::json::wvalue details_obj; + for (const auto &pair : info.connection_details) { + details_obj[pair.first] = pair.second; + } + device_obj["connection_details"] = std::move(details_obj); + + devices_json.push_back(std::move(device_obj)); + } + auto res = crow::response(crow::json::wvalue(devices_json)); + res.set_header("Content-Type", "application/json"); + return res; + }); + + CROW_ROUTE((*this), "/api/v1/data/latest").methods("GET"_method)([this] { + auto latest_data_map = m_live_data_cache.get_all_data(); + + crow::json::wvalue response; + for (const auto &pair : latest_data_map) { + response[pair.first] = crow::json::load(pair.second); + } + auto res = crow::response(response); + res.set_header("Content-Type", "application/json"); + return res; + }); + + CROW_ROUTE((*this), "/api/v1/alarms/active").methods("GET"_method)([this] { + try { + auto json_string = m_alarm_service.getActiveAlarmsJson().dump(); + + auto res = crow::response(200, json_string); + res.set_header("Content-Type", "application/json"); + return res; + + } catch (const std::exception &e) { + spdlog::error("Error processing /api/v1/alarms/active: {}", e.what()); + crow::json::wvalue error_resp; + error_resp["error"] = "Failed to retrieve active alarms."; + + auto res = crow::response(500, error_resp.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/v1/alarms/history") + .methods("GET"_method)([this](const crow::request &req) { + int limit = 100; + if (req.url_params.get("limit")) { + try { + limit = std::stoi(req.url_params.get("limit")); + } catch (const std::exception &) { /* ignore invalid */ + } + } + if (limit <= 0) + limit = 100; + + try { + auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); + + auto res = crow::response(200, json_string); + res.set_header("Content-Type", "application/json"); + return res; + + } catch (const std::exception &e) { + spdlog::error("Error processing /api/v1/alarms/history: {}", e.what()); + crow::json::wvalue error_resp; + error_resp["error"] = "Failed to retrieve alarm history."; + + auto res = crow::response(500, error_resp.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/v1/alarms/reload").methods("POST"_method)([this]() { + spdlog::info("Web API: Received request to reload alarm rules..."); + + bool success = m_alarm_service.reload_rules(); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = "Alarm rules reloaded successfully."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = + "Failed to reload alarm rules. Check service logs for details."; + auto res = crow::response(500, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/v1/alarms/clear") + .methods("POST"_method)([this](const crow::request &req) { + crow::json::rvalue j_body; + try { + j_body = crow::json::load(req.body); + } catch (const std::exception &e) { + spdlog::warn("Failed to parse request body for /api/v1/alarms/clear: {}", + e.what()); + auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + if (!j_body.has("rule_id") || !j_body.has("device_id")) { + auto res = crow::response( + 400, "{\"error\":\"Missing 'rule_id' or 'device_id'.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string rule_id = j_body["rule_id"].s(); + std::string device_id = j_body["device_id"].s(); + + bool success = m_alarm_service.manually_clear_alarm(rule_id, device_id); + + if (success) { + auto res = crow::response( + 200, "{\"status\":\"success\", \"message\":\"Alarm cleared.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } else { + auto res = crow::response( + 404, "{\"status\":\"error\", \"message\":\"Alarm not " + "found or not active.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/v1/alarms/config").methods("GET"_method)([this]() { + std::string rules_json_string = m_alarm_service.get_rules_as_json_string(); + auto res = crow::response(200, rules_json_string); + res.set_header("Content-Type", "application/json"); + return res; + }); + + CROW_ROUTE((*this), "/api/v1/alarms/config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_rules_content = req.body; + + bool success = + m_alarm_service.save_rules_from_json_string(new_rules_content); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = + "Rules saved successfully. A reload is required to apply."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = + "Failed to save rules. Invalid JSON format or server error."; + + auto res = crow::response(400, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + /** + * @brief GET /api/v1/devices/config + * 获取设备配置 (devices.json) 的原始 JSON 字符串 + */ + CROW_ROUTE((*this), "/api/v1/devices/config").methods("GET"_method)([this]() { + std::string rules_json_string = + m_device_manager.get_config_as_json_string(); + auto res = crow::response(200, rules_json_string); + res.set_header("Content-Type", "application/json"); + return res; + }); + + /** + * @brief POST /api/v1/devices/config + * 保存设备配置 (devices.json) 的原始 JSON 字符串 + */ + CROW_ROUTE((*this), "/api/v1/devices/config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_rules_content = req.body; + + // save_config_from_json_string 内部包含 JSON 格式和 Schema 校验 + bool success = + m_device_manager.save_config_from_json_string(new_rules_content); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = "Device config saved successfully. A " + "reload is required to apply."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = "Failed to save rules. Invalid JSON format " + "or schema. Check service logs."; + + auto res = crow::response(400, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + /** + * @brief POST /api/v1/devices/reload + * 通知后端从磁盘重载 devices.json 并应用 + */ + CROW_ROUTE((*this), "/api/v1/devices/reload").methods("POST"_method)([this]() { + spdlog::info("Web API: Received request to reload device rules..."); + + bool success = m_device_manager.reload_config_from_file(); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = "Device rules reload posted successfully."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = + "Failed to post device rules reload. Check service logs."; + auto res = crow::response(500, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + /** + * @brief GET /api/v1/video_config + * 获取 video_config.json 的原始 JSON 字符串 + */ + CROW_ROUTE((*this), "/api/v1/video_config").methods("GET"_method)([this]() { + std::string config_path; + try { + + config_path = ConfigManager::getInstance().getVideoConfigPath(); + } catch (const std::exception &e) { + spdlog::error("Failed to get video config path from ConfigManager: {}", + e.what()); + auto res = crow::response(500, "{\"error\":\"Server configuration error: " + "cannot determine config path.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::ifstream ifs(config_path); + if (!ifs.is_open()) { + spdlog::error("Failed to open video config file for reading: {}", + config_path); + auto res = + crow::response(404, "{\"error\":\"Video config file not found.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + + auto res = crow::response(200, content); + res.set_header("Content-Type", "application/json"); + return res; + }); + + /** + * @brief POST /api/v1/video_config + * 验证并保存 video_config.json + */ + CROW_ROUTE((*this), "/api/v1/video_config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_config_content = req.body; + std::string error_msg; + + if (!validate_video_config(new_config_content, error_msg)) { + spdlog::warn("Web API: Failed to save video_config: {}", error_msg); + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = "Invalid JSON format or schema: " + error_msg; + auto res = crow::response(400, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string config_path = + ConfigManager::getInstance().getVideoConfigPath(); + + std::ofstream ofs(config_path); + if (!ofs.is_open()) { + spdlog::error("Failed to open video config file for writing: {}", + config_path); + auto res = crow::response( + 500, "{\"error\":\"Failed to write config file on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + auto json_data = nlohmann::json::parse(new_config_content); + ofs << json_data.dump(4); // 格式化写入 + ofs.close(); + + // 4. 成功响应 + spdlog::info("Video config successfully updated via API at {}", + config_path); + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = + "Video config saved. Send POST to /api/v1/service/reload to apply."; + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + }); + + CROW_ROUTE((*this), "/api/v1/service/reload").methods("POST"_method)([this]() { + if (m_shutdown_handler) { + spdlog::info("Web API: Received request to reload service..."); + m_shutdown_handler(); + + auto res = + crow::response(202, "{\"status\":\"restarting\", " + "\"message\":\"Service is restarting...\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + spdlog::error("Web API: /api/v1/service/reload called, but shutdown handler " + "is not set!"); + auto res = crow::response( + 500, "{\"error\":\"Shutdown handler not configured on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + }); + + + /** + * @brief GET /api/v1/config + * 获取主 config.json 的原始 JSON 字符串 + */ + CROW_ROUTE((*this), "/api/v1/config").methods("GET"_method)([this]() { + std::string config_path; + try { + // 1. 从 ConfigManager 获取路径 + config_path = ConfigManager::getInstance().getConfigFilePath(); + if (config_path.empty()) { + throw std::runtime_error("ConfigManager returned an empty path."); + } + } catch (const std::exception &e) { + spdlog::error("Failed to get main config path from ConfigManager: {}", + e.what()); + auto res = crow::response(500, "{\"error\":\"Server configuration error: " + "cannot determine config path.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + // 2. 读取文件 + std::ifstream ifs(config_path); + if (!ifs.is_open()) { + spdlog::error("Failed to open main config file for reading: {}", + config_path); + auto res = + crow::response(404, "{\"error\":\"Main config file not found.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + // 3. 返回内容 + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + + auto res = crow::response(200, content); + res.set_header("Content-Type", "application/json"); + return res; + }); + + /** + * @brief POST /api/v1/config + * 验证并保存主 config.json + */ + CROW_ROUTE((*this), "/api/v1/config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_config_content = req.body; + std::string error_msg; + + if (!validate_main_config(new_config_content, error_msg)) { + spdlog::warn("Web API: Failed to save main config: {}", error_msg); + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = "Invalid JSON format or schema: " + error_msg; + auto res = crow::response(400, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string config_path = + ConfigManager::getInstance().getConfigFilePath(); + if (config_path.empty()) { + auto res = crow::response( + 500, "{\"error\":\"Failed to get config file path on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::ofstream ofs(config_path); + if (!ofs.is_open()) { + spdlog::error("Failed to open main config file for writing: {}", + config_path); + auto res = crow::response( + 500, "{\"error\":\"Failed to write config file on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + try { + auto json_data = nlohmann::json::parse(new_config_content); + ofs << json_data.dump(4); + ofs.close(); + } catch (const std::exception &e) { + spdlog::error("Failed to re-parse and dump main config: {}", + e.what()); + auto res = crow::response( + 500, "{\"error\":\"Failed to serialize config for writing.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + spdlog::info("Main config successfully updated via API at {}", + config_path); + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = + "Main config saved. A full service restart (e.g., via " + "/api/v1/service/reload) is required to apply changes."; + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + }); +} \ No newline at end of file diff --git a/src/web/web_server.h b/src/web/web_server.h index fe4baf6..e91f5f0 100644 --- a/src/web/web_server.h +++ b/src/web/web_server.h @@ -1,50 +1,47 @@ #pragma once -#include "crow.h" -#include "crow/middlewares/cors.h" - -#include "alarm/alarm_service.h" -#include "dataCache/live_data_cache.h" -#include "deviceManager/device_manager.h" -#include "systemMonitor/system_monitor.h" - -#include "nlohmann/json.hpp" #include #include +#include "alarm/alarm_service.h" +#include "crow.h" +#include "crow/middlewares/cors.h" +#include "dataCache/live_data_cache.h" +#include "deviceManager/device_manager.h" +#include "nlohmann/json.hpp" +#include "systemMonitor/system_monitor.h" + class WebServer : public crow::Crow { - public: - WebServer(SystemMonitor::SystemMonitor &monitor, DeviceManager &deviceManager, - LiveDataCache &liveDataCache, AlarmService &alarm_service, - uint16_t port = 8080); - ~WebServer(); + WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager, + LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port = 8080); + ~WebServer(); - WebServer(const WebServer &) = delete; - WebServer &operator=(const WebServer &) = delete; + WebServer(const WebServer&) = delete; + WebServer& operator=(const WebServer&) = delete; - void start(); - void stop(); + void start(); + void stop(); - void set_shutdown_handler(std::function handler); + void set_shutdown_handler(std::function handler); private: - void setup_routes(); + void setup_routes(); - bool validate_video_config(const std::string &json_string, - std::string &error_message); + // bool validate_video_config(const std::string &json_string, + // std::string &error_message); - bool validate_main_config(const std::string &json_string, - std::string &error_message); + // bool validate_main_config(const std::string &json_string, + // std::string &error_message); - SystemMonitor::SystemMonitor &m_monitor; - DeviceManager &m_device_manager; - LiveDataCache &m_live_data_cache; - AlarmService &m_alarm_service; + SystemMonitor::SystemMonitor& m_monitor; + DeviceManager& m_device_manager; + LiveDataCache& m_live_data_cache; + AlarmService& m_alarm_service; - uint16_t m_port; + uint16_t m_port; - std::thread m_thread; + std::thread m_thread; - std::function m_shutdown_handler; + std::function m_shutdown_handler; };