From 1e6f65807e72803a1cca8483c3477ac930762420 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Sun, 1 Feb 2026 20:49:09 +0100 Subject: [PATCH] Fixes --- .../ButtonWidget.cpp.6932614AE5FC71F9.idx | Bin 9394 -> 11336 bytes .../PowerNodeWidget.cpp.D068C7972720D9A3.idx | Bin 8602 -> 14662 bytes .../PowerNodeWidget.hpp.EFF78DEAFB845F91.idx | Bin 1382 -> 2176 bytes .../RoomCardWidget.cpp.FA613078500AD19C.idx | Bin 0 -> 11326 bytes .../RoomCardWidget.hpp.DE0E3E44BD9BC30A.idx | Bin 0 -> 2316 bytes .../index/Widget.cpp.63DB7B9186B85891.idx | Bin 5162 -> 7784 bytes .../index/Widget.hpp.63E5D98A9B23E60F.idx | Bin 3008 -> 3790 bytes .../WidgetConfig.cpp.FD56F9F36C29A5DA.idx | Bin 10020 -> 12862 bytes .../WidgetConfig.hpp.CAEFE2EEEB2A6996.idx | Bin 10946 -> 17280 bytes .../WidgetManager.cpp.D8CE609DC911F13E.idx | Bin 50468 -> 55998 bytes .../WidgetManager.hpp.8559171B7B8A010C.idx | Bin 10524 -> 10766 bytes .../index/main.cpp.7C677863E2582AB3.idx | Bin 7996 -> 7996 bytes main/CMakeLists.txt | 1 + main/WidgetConfig.cpp | 187 ++++++ main/WidgetConfig.hpp | 112 +++- main/WidgetManager.cpp | 597 +++++++++++++++--- main/WidgetManager.hpp | 13 +- main/main.cpp | 2 +- main/webserver/StaticFileHandlers.cpp | 11 +- main/widgets/PowerNodeWidget.cpp | 302 ++++++++- main/widgets/PowerNodeWidget.hpp | 19 +- main/widgets/RoomCardWidget.cpp | 335 ++++++++++ main/widgets/RoomCardWidget.hpp | 49 ++ main/widgets/Widget.cpp | 119 +++- main/widgets/Widget.hpp | 26 + main/widgets/WidgetFactory.cpp | 3 + sdkconfig | 9 +- web-interface/src/App.vue | 5 +- web-interface/src/components/CanvasArea.vue | 38 +- web-interface/src/components/FileManager.vue | 477 ++++++++++++++ .../src/components/ScreenSettingsModal.vue | 58 +- web-interface/src/components/SidebarLeft.vue | 4 + web-interface/src/components/SidebarRight.vue | 374 ++++++++++- web-interface/src/components/TopBar.vue | 3 +- .../src/components/WidgetElement.vue | 134 ++++ web-interface/src/constants.js | 76 ++- web-interface/src/stores/editor.js | 32 +- web-interface/src/utils.js | 27 +- web-interface/vite.config.js | 4 + 39 files changed, 2850 insertions(+), 167 deletions(-) create mode 100644 .cache/clangd/index/RoomCardWidget.cpp.FA613078500AD19C.idx create mode 100644 .cache/clangd/index/RoomCardWidget.hpp.DE0E3E44BD9BC30A.idx create mode 100644 main/widgets/RoomCardWidget.cpp create mode 100644 main/widgets/RoomCardWidget.hpp create mode 100644 web-interface/src/components/FileManager.vue diff --git a/.cache/clangd/index/ButtonWidget.cpp.6932614AE5FC71F9.idx b/.cache/clangd/index/ButtonWidget.cpp.6932614AE5FC71F9.idx index 5bc490ebb5f14b5116255c75ec373229963cc358..842133a8481f32df783ebb63707d75c3a962ac3e 100644 GIT binary patch literal 11336 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 RyHF+Lt!e-O literal 8602 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 diff --git a/.cache/clangd/index/PowerNodeWidget.hpp.EFF78DEAFB845F91.idx b/.cache/clangd/index/PowerNodeWidget.hpp.EFF78DEAFB845F91.idx index 8da357beafd863e1e3b103b7400dbb5c5197b537..f85231c3335091e89af99f3f5045f1bc0d3ca92d 100644 GIT binary patch literal 2176 zcmYk74Nw$i7{_;C*IV}3TlenvJ^_IPEdxazBO9Ty1V>=7QecEIBg7BHQcePt8cZ3A z9LLNQ(b3EyP!Es+N3(`x3>8cT{1__3k5R_PbYvu(61DeT`(NJ7J;(k3&-3iN&ptnH zPSVVoB>@~aV_{Os^1M|!0>^Py`u46WT>1z7{FUdplAX1mw*f&016z|I59pS)d@F}|hfOYhaTHI6r9;=&L?w=hnF>*DnmXzcCqWsxvdK(B{u|Tm0rEE^yZ7#8YU@gM(1~2 z8@HjdJ^B3jxX=khvl~wu-j6Li;HlkUnf>6xqed-r@jpvu-RoO1Z)IghSLfFcU;b{e zH@oaHH=JhAjQWB9+1`}zEnWWZ^CLK}?_Tfs>jq+pLV^e znnG-}d5{!j(*tZd#W^@PsUkxmGPT{3ThjyVW7SIAzMi%Wl~~a{Pzv;)y!l~8_DkmQ z9EI3vwN0_P^nmKUxi`rDSmQ#4j+GNFip8l1*!4-n)+WmYuSyJPu1HD{?5c#g{dMIV zN>yTkZIq1uYY%*rSUat&ZIeQbw7FR^yYztS!SupWcN?edu@fHE29qHe_V1*;_|Ra@ zK9!7s9Uui*VQ&qKe`|Aob*oCuuq8?I+wb%@mlhRdA6AJ9+a#I%Gyi)>^qh{VM~{ny zk|YWS!KMdJ(szRwM`lNC&Yj=w=u(LT_2E*uzkT@SVD?L< z4zHTL#v9Kg539`~vud6;x%}9Xg*={kZC{EjkiTh5<69u(&-%=J3h^Z4(_ll>`X P`l?>omh;tgK7jid>*tI( literal 1382 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¶@+_67?Y*qq^l?m_&|h41t*;e6>_vfbHtwVFtN_vI#hRN&(8gybH4k1=bYb{ zoERUk;20)0B{456HFueUVHh3!+_^a!dX{0*3B%;=Ih=HHc5|fA6>;uPalTy?32WVG z*?;_4R@b4%U5)c^o=(V$y|MSU=Na4O2fkC{roMM->0m*urat!zX69zU!Q|=Ix z+2M)2K*hFuW7w^U3r|c8G1k`fr@jBjx8>*aBH48lzr1>-r7L{Z-Q>rup4+aBgEO|A zFF)y8y`($)!mYNl7w)XxF+VA@u;|p45BoF=?a@)2ySc_1$CT#D^xBL2Iy-u5S5BJw za=~ElcWHx$1r5IM*5Ay`4=kLnnjRFB$R9|LepL6_>8G#T!q$l8dNK7{C0D@o2K>wQ)8h z2Am7H5tG(Er%)h)5HQ3_L+m(e=j>(-7IRq?9f@4KE`(4lKy zTQrKW;Pw@KEmWyh24MCrKR)}YscR!obWmbFuQzxNx>skG-c8(2_VXkF+!8MtyawIs zNJn<={fv4Q0g9+XsnB~31FXTr(h*ndsy@k^l+nr)J>;7?v*l_gYMaGx~HmXTDM3va5PCTS!je`#Bk*zTb3jr zN*(-OAU@!3XQg&_G*xqI9p)e0Hoc@_V%7tJC?OzTijSa9&N)0i>ZtxV)^|QXAP_%r zmk_ChjHVpN4d*9>|DNmZAX)|l<-X1tm9MO5i(@HOSQK`Y5gAxS1_fmV1!Nfylo1b* zWhhWyJoD_ulV^4ME-cQ%C{ z$W~LTQm817I=o3L^S^JRLZmn=lO5FPN}4jD9$vc28DrQ{U|jmj1+5+ zSi*i}7;!%cBgis>DC15*meE5Q(F0jV4&_Y)=YO1=)L-O7sWw1s#VMkcH}iHZ8>M!O z-GSf313;5yL~$kcBj?!rOG|e`BSP(=;n-YC!^Vb<$L3vG)%)6~8%y@VBq*)QaNHDD z0hW}p#wCY#ANS-fI_80Br``E1b`#x1^EcpLz5)6F9SmWIC1uhv2O$SpCLCpK7|1f& zC~wMZk*DvgcXxZ9bfwfZ@qC3YH8M)N8?#2wMdpAJOY*jmXvP_ToP{zkmdUUW$Y}-@~wb! zPU^~K^PqsrY4y1i-f_pUI6lZMgORnZ6a2#pW7e}sjs2~vz4n%e9v06Q_xXh*m5KA( gSlj~R&ROC1m)oQLr$_KdTepmRue&*YGOK0&0xqPlI{*Lx literal 0 HcmV?d00001 diff --git a/.cache/clangd/index/Widget.cpp.63DB7B9186B85891.idx b/.cache/clangd/index/Widget.cpp.63DB7B9186B85891.idx index fcdc0f4d2600795f638dabc504a2087c46c53d2d..a38a563d484ade28b35b9dc1d8cc54560dd05ce4 100644 GIT binary patch literal 7784 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 LDsTmW27voNZDKo% diff --git a/.cache/clangd/index/Widget.hpp.63E5D98A9B23E60F.idx b/.cache/clangd/index/Widget.hpp.63E5D98A9B23E60F.idx index 47cb7c485c9b135ab5b484964eb4a46e3783b68c..58387ac38194ccf99c79dc349da6bc206eb1b667 100644 GIT binary patch literal 3790 zcmY*c3sh5A7Ja!yB;h5zJOV_LKmwvb1mqVg6;M7AmI8wDQ^AfXSWyuLoaJxor+(Im zqgG~?OB5X)e^tw9rGBav>9mZWDyXzYMQc?owq>fd{zd1!=WKMc76-ETJ?Gwg?!9|2 zax)x`MlHvs6lTmWDXJ(?avbMLKjjr=#XA)oSMS1cvp3d^7(D$-f8o}9Wh*D22~it5 zwrGd*KpdT}`$`>%van z?HY6C$Eyl`V&?j#yS0S%%j`GQWv=Yv1!D(YhFeBh4!t_>+-`B>yQ2z7MFD?%Vl(2 z`|TZVcYl~ud$R8C_u=zTxEvE^EKVQ#;>Nklsij3tdBrAw@krB6pT9ivzxP(r>e=_6 zuBohwoxEk=gN_q9w;qnkvtC$VaDHFQ?W4^@X8Aqa)wKA%@80ozx@?vGPem^ZyK;8k zKUGsdrDnp017%sG|D5%3r|aBn=G;qPtPlNV@#!6bXIul%x?Z{FGPQ8y*&s)I{KeFH zHADKhnUyJz#s?I1ESOtk_qkg2_UOJTyL-);+umcl*Qv>my#xF8o&8mF%9K>oS2|a@RtMUMrTSN*M>?{MEtvMS{6uj zwkq3K4xqiqMFyuUKUQg3PojsZhneL7dPwm%Q!6?<`~{|Euj+|{N)SLdcQISy4SR^d zyeKnHJV7^AzP~2q(1$Tv=0@3>>P&w*fF4kJ_*7`& z*RfirB05W*94-`C zKhROSsP4wp&iu$HbFhArz&t?5>*51Z+>pPmHD~g@Qh}LiZcnYJQ4XND|2xX3-=c(t z0t-fVj4r0Tc#>O7+^p>17745`=r~=R1=$OapKjiIqviuG>rKUntA?B80D53vr`InF zrY;khKhYKsi%AZkXZ`kkMPtd9l>!SVTElDfasWMc)F+)47j}H?!y+)ZidShsYc`*W zvG+PsE3h!o(YomFm2dCa`gn7b*M5O1L8qzGy6LL6J7JlE|$^mrTJN4^IV_WJ4=0~(%tJli` zwD10Nimz`TYZRCd(ZPmbs~kY5?QZ;Mb;ih(0<&S|k-Et4mHRbj?_IrlSd+kvlx@L7^zFX6wx6v1ByFLju6mE>6JHkoqzkTz$}z) z^{|@d0Q%dD8|HKC3LXe71lh5=*zQN9(EL8?@yx4_v`j5Q=nJyqCMCiipTEo(mUD>DLzVG zv?wiFTy|cxDy>@Vl@|k)0b0lh6^OPcpg`bfitG<6H~3OrgUt|z=0d_1Dp|U*ikV|q zZM)ZeCy+)vbPiwi8j4OEljIzuf1UWHdFhC?Av8Lue^3k*2m(*FC1J;C2s^bQF*-3F zHPy1bV-Vz>1|>x$IdF^BvdCi)M4px-DLi0^JIyPJI^g7|_W8G}t_*hJMWfQl<3ga7*kPrxjtfzj0a- z0ruCY6%-s4f!6V2aCC4yo|js7AMB%LY_Z3drLJjGx0OL!q=4?Jeq~0l44LEqulKyD z(x|#mf|q3oiS$j!`#`IBQKQtTp_=r~qzS+`*c)9&&oIwOs6H?1`{+X<8NBFa_OjxL z)Up9$Di|Q0kYs#-(F;0H$@Ku^3e#xG=m2BmG+J^wz%V!(Eg2hN7#qQop#g@W5iA)Q z;KXHZmi$CVG4(htFf9`Lj-Aq!lDT2@lDbb@kK1<5=t2EZdh!@W{ZQ)p*Zr{ZHa+ep zFWStupx56rlo>qQ`qT4gE5;vNJ%=W@##xhbUncaj4!Tns`98rGyJZkA6-79=g|^+ZU&{n{q;q6ggPSe`Y|KrO7@Ce+zJJg9To7; z*MvUPrZ%pn!oRw7v{;DwzLm$maM&QW(w zv8F)~CH$r$lFec?j+tP|UIBM0oi#_CPui$RKwLmFUZr~P-LnI$U)(0xsdVzNNZbxu ziR8a1f?G?qCG!Og-=)!#-vUN48ZDVE;Ig7=1(J@HmrX2>kp2+uvG-dKu6*9&qVp6u Sodmu5NdKe$?<3$3j{7gum6qiI literal 3008 zcmY*b4^&fS6n}dR+~zlUld(NEHdzo5IM^f@Off)N2!cw8DQbdXf`B?eMah3@p2$1~ zi5?~ziuk90g8xtvk10r6Hj)akLfFqNG$BP%uK1f@V9kmZP}B;VqE_#zOuCkM$-oSMH{711j*Q(IW{(pk zS8J1Qvu7Uptmt2@U;9)vs!xI} ztnDlH&36%{%9hnvrC3@Fe{V2W zNOreaL_S9^B}{7Wy|N@AwrGv*UPtvezE6HBFrRt;$FC++V#j&Qj044!uKs4|jjGKW z>Iy9WL_f%GG(MEC9IQ?lPFZP>>t5J?b^kv9r41LFyW7`ZK0bTphaZ%M+)tX>|8-Tj z=+TUub<=9M&hc8;Ij#47kMGWXRh*t4WNtb*AtJjnbZ>jpx7j^EEVg~Gnm*4y>F|P6 zgA*m4Iq^}09aC!T=Zh+@$K~}UHnkqhcG|P~?T;0|99Wh0mtV`G9n@ z(5~yZ!x`F%rjVQ1jpYr{PE$@7j9;4{!O$Xz_Eq?L09`rti%&IchN7M6Q8+rB4Ij%J zpdYozSG=*PC1wsJcc(AUp6Ia$2O`b zfuV))_E2%CJJ9R4>+^2Jo|(naDxeKAg9_-HO#!xf+Zs|B+6^byx#$$U0eaWDiu@Z% z&u25V14M_2Lr8LVUUmD-B}2;?dJK+sb#_(r2BHsM8Lu3-F=7Klvk)C93nUlwnyAfG zIQ`*9hE@V?lo>rCdSdE;+k^E>-(zSwPOf#)dhiD5!iWEqH0M@rV`vXdOOaI08=&W= z)D0ATU-P+=)1N@vcy9t7+QvttEFlcZ-DkL z+_%@eX%N$nklbJ9uLjyQ=fbXKA#E2J+7(C3o#k@g0IjOMF8HRqp_!qbG40KI>v#in z%+cmAE2hqBVQ4iZ*UR)Id3e9FG4blAR)%J9G>cdnZy>s}V3sVpBdB)_?E=vz57R3^ z|9HbzOzlehlcC*#4w40taj<4C&fkm0=yY<%Pt7ya=bN>04J}rg6Z#mU{m2(Hh|Sby|97;F~52+_ABOYuO71Zd0}sp%%p}C|strn^g^cwaQ zFCgp1`a|8=X`DJ3obbT|wIL%1*ZJv!pg4rQgT2krT?xPC@XofmSuRyuoMO212zAa7 z0`w@!i=}@dM-=+s=@6bKH25|-fdLrWj$uw%0EQFdmT;;9FbE3v=7a=bs0Mp; zDgyB46DQjn0}d~D#5D_D5qRO6g>p1fvoG?ML$49?)%dab!~qQIh7ZoE8o+Sh*qf6yfT0i!b5aH{s2EPdsTaU- zUD%tGE`Z_Qus5ex0E1d#m{Tc$t(iG#(OABq%L3iC$6?1bk&XmVo-dCs@cc+ZfX+-+ zQN+=fi3ITKiMsq*(S#BLpl4kXb!_GcpzHc{z|&*a9pW*(yIvHhm@*7yQ2IlT0k;do z!w67rYtuehUtUUp;N+LiSO;IKXD<<8TU%6vDP-ey0@#O?c?7j};`OR7fCJ@+?=S9L-bjGs8Sg~emh9;n z87Sph`-ILr%L!mEAK(3!D(oNu$~_}eOS5;MAmbAwSI5Ne2>h4;3vbmH73R!4oo`)~ hAH<0+I#>2%t}RzF-(j>=Lg9br@R7m~ijs`{{0}{w{<#1E diff --git a/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx b/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx index ed3b598ab791daa0f7d2bbc2e9fb3d6dd9e72bb9..d42d31fb8422e3d7147e67c08d33cd86958eb645 100644 GIT binary patch literal 12862 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 l(P^p!s=%sXQS|!)> diff --git a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx index f50850c036c08259d2a2108bb23ceb7ea038f4ac..374d28d7157ae16aa850f5f5f377b8c89da7e504 100644 GIT binary patch literal 17280 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%0A6o%z}3d*uVJo{Mg=tm^6Rfian3$6BoY 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 diff --git a/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx b/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx index 416328fcab48f17a17ca093236437fcaf1b803d6..6a7ec3f95421e3ba57d1b1a0be02a5cb90c45805 100644 GIT binary patch literal 55998 zcmeFacU%<5|M<`D?e1;8JC39GE>bMmvG)>VtTCEcVoah*j7DSB7)3!T3W9(lDk3OV z5DO{_f{G|8C>9j4q9TYDMNv_|vs{Lo`TYHRJbv##;^PtWxaYp6?3CBcyyi8t<3hnt2~4^bj89Z+)h`@Lavo((3K|x`m(bf4I{(;SD|4e&3CLo?};aSTQbf z-orn>uJN>TyRc8)IKF1uw1tZwx<}1ld(y@7{EnoLFAjb`?f8p>ZqKJ|3p@3$W2k%l z9s6>>y2p#VJWG06*Isr1^MuY<)+L{+=oyw`h3mP>BZ9~{8F7Z_^+RP zsG_W+*B@6O-E3~Z@|Rtg*)@k>{j4n?)@Mj=dVzba1~WEII$>Z!7&bR{YSd+f}!jji*y1)+f$+`~BZ9Kg5jn z3R(7Z=iDI&UfhWO*tf3R^GAcP>+-e+&FZpVxiYb&?Cp0a@-M#nP`9l2(Z{!L?cDmJ z!Kf_phebgfPq=KmHTdP-=6Am}w%o7x9+_BuWd8vVjr!y#hoN_BDwb=K7F_wTamlB4 zlV6f#ehocofDj##p!<&b^DXt`ys`Vmansmx|2d{tUUhPCaM?9kv2W+aFRi}XRO?Du zudkhbbRZvFbBX&sAk9=6-9L8Mu-{8KRrHcO#+$#KJkP@Ffi~sQ_;J&8+rIG}|JM4X zm~>Ec$#lcrE2iyDqL0m*GbOg_KJ$|wH|DES)AmYbz`>cOY1egGGstr{zndN!J?AH{ zRp0IW;ocXXLvH4K583u-lGBiTzxsP5QhzcFgDw@j_X@~;5h`UAU8*wjyh?gR=pNNg zyMNwFST_5@45hyL7uCqS*v$a!H>q)tRzYb zeI_h1o;y@K?Lpa*7QgE2n-Blu_3+mbPZLj>Po8JGsCh+$$rt06_C6*x@{hi33AlH? z(!6KoC?Da6{8dv3vo}WDGafF@DJ%YZMq2;)iJzw}Fx#~w>gQ#XnzmJ!tzAB7jc@-M zm5aJZ_wVFgK5SyYkvF!-O^i78Z1%B#tf%vo1eOm=kegZa}3&^-qdSo%YHA^SzrF4_c|X zzT9%kp!m-ZdXE~uLanwUrICjNl!sg=gxK_B)@`ia#ko(L`b$drv@wC_7gcT`$!T-P z9`9j4-Oc{Y%Vo0%_6cj3^xo^FqPnm7?*++O?>>KSFpu3oL$U6C;QO4Wb|w8Yu3h=Q zhhnvAO7)dK&x(hnHJwx1S6(qMk1K!J?aY|eqp$BysW<6S`ufb`52NEIJCh#n9ez*n zuJ5qdckx}XVX-r2PP%`%=JWXj)rIRwAuxvg==MCh*NTX*=1YX@s2|K;7988|zyI&y zJ948Q>=|(Q&6AI3`rbRTN?C0FW$e|ndx}$>zv!RP{BrW$Z!LaZ;P}V<;=g08|6y`Y z{PLBj*S+k-+fg6GE)Lo-c*~=er~@7jtv-a~wA zt*iI_>f-wMjOC}6J1&@Xcj(UKtU|4M!ssIr%T^5SHreOns0$Z5?|;$qaox;s=Xf=3 zoV2~P&-B$byT3Sb)8grIU5~ltujaOVm=pfZq}?~{r#_ve{L`nGz0d8#e#6F;y-E2p zr{A*Umv(L4*Ldq#`NCRYq?A9$=3JULz2(!OE%fX$vybPNwZySk{vN%g zDU;vpcJ*R(<^=ZnZ+Quv)n60C9?eaT9O1O>@_N(4MZY|B&0o?nHgT-|=%o6>twGmr zhlHl&j@i+B+uWmTU1E&RpQ`o!L7n31Fnz(a)p4(m=FPRcYWz>kr;~?D);?a8bD?}@ zhT3vUVc+p59^YIr>d$58FDo6U&5i5R|F0_k=mm>9pYek(Z}W_uzxq|u1C` zR>o=bxA9vtcC0S^cynpoot__FUn{@5cY8)cmi?}gCZ(?n2B|;o{mMCI-O2#F+J(>h zWep?f%^N-+u@bwj+NnIDs89a_6BZNx(Ia^cz@U4O6jy593c?NR${ zehYqiI6L7N^Of@tok=Xc7tgN0+{f%n__g8f)$hMqP&>PYPf?dmzrW(^*vPyE0W)nM zObVJ8?Gg43Z#VJsexniNFaJ31^me<6KZh+G`BmEHl}mb7M(0GL5Ztq+>A?=2$e4gp>q&snshwXiDwOCPo}C-lFt)MQ?}G55L#t`Bs3oz{8bZHvT7%g56`qjilg zcV4W2J@_krb=NETGp3EYYLe42ZQgW?sedi|q#j!+^fOwx^tbOVIU}R7b^f>1CkGp8 z$P(ftVN^%lRqF049qS2yg7YWLjKHKv{`bk_!_u3dUQ-(Kxn8W4)0J|r^+Yhi1rt_K z?CYEhX(lT(lp6A^UMzyuMX(O_L;%4B5T;P9ucrSG_3vb{&w8;`MwiOi)e~K~E+$Z@ zSFgRZCakt(HDrZe=z~J{LE%zQY$mwPge4RU?s(*+Mf`SS4f# zg_d_TdJwiW-%LY3(+f3ox@HcFCzZOBO80sqoZ!L<8yK6;A@9iZ`yYW=qdvAON>@dJ*kTDT))3p= z_ikzVy%gOwzgqn9!ypZLS}zt)>f%ZJdcvBs)Q(2J{^!8dVWSpn=U7=iBN3>YJ94QSI>?$GWWEJxVmX)+(VBb+&bc{1-}`Al^3r zs0|#gAUPok2M3?6|vunMps+26vIm~Jjk~G|1evzb#IPqbBt$d z$cy^)niRSw1!%{c2yPS6){<|{7}}SaHsX5?c||W)LOPd_9qWny7X4jewtiYu(dWC> zKm8yYeP~T_vTky_Izs*nQ~PsD{;u7p7J_JN~Ea!;^p4kZbhC zluhcgN#}aPopZNL*Wx^w~*RRqCB7`jaVSn<{#hirchd0sE}kkvh8L0oGHZVk~ETW!t0qM>FtLNsKE zKFKhJE==K3Ps9*h3}FXzc6@SUs$lXwR70-Ui=9>K&MH0YiH=-H2PiaP#4yd0!eQ$) zWT`&MGE!G2_p3;PizM25*3R1<-+%KkKU^{@Y^_9%iN>sskpIF;lol-AXA^%ST0?%) zM^wV-N@V5Oo$KDF9N+n+`K342ZPJjz`YZ)0bU_MG)6oPMO|;c@;D`-@?qjEI){qzU zaTT+=ViweOAi)J1Mv7XEo!zJ7AGWmB&@s~X7zxTDj?l&#${}QORp%KAMY}ZQJAG`I z3EgD^lkeX+QKsZ+F`&Thm6V`4S7i)*8^JjfCh0b=a#n(Ce=g3-MdE*JS-ZmB4u3_ z!D}LTpo{eXhoySsL}Xy$_QWC$S*(w$MX761g1oOHxK)N05uq{q^NxAm84dYLFSdu$ z?V*4Y?Zx$KQ=!&R1`HW>>^Km5sTW#J>sHfN^@Jbir-pe?+Vo=JK--WC4Y^kTg6X6# zodoL7g>z{emlp0kxN)fKh^rd1QXkWGT6diWL-gO=-)#eQ<}7yViX z65LutT=^NtjYdT0)rdyTt*vyJILxY!kpIH+oaxE;Oxqn>FBuiIN*G%jyVnu&Unud* z`TC}Pv;KLaA&J{qP^WtKEv>aZfemYrR+zp(`SQ3Nh!*+f;~= zGCcoDMZh}^8K^JJT%|5o>0M8R5nLG2HtrhzG5g1b`-%-SBvPy+Y8g!}V+%(R9?q7E zPd1oVBxM9|?3S|Xc7+?k0ntke`%4N3s1*RHqwVWx2iOb&DB#-_@E%Zw0#L&{*6>aU zc*NU3;vEps#Je={t_a8#y5$Pp5l|p>DG<6Mpi<~kX#gg~uc?(w?vNBn?lMVTCM}_^ z0U%UCg(@teIsl+qK{YEZ;V1{d8k$-|Tf#OwvBj;)FbEU9sh2J z{5+=cJf`T36viWk$0J2Y1U#obp3@zX(g|d|1DP%exWIc};5#F4wnlVaBYK@lxV`6F z>t&0NmwqfgTz*wSfXp-rzD+_es1}BKHKzY`#)wjZCm$8UM};wLRse)jLMVlTjg+tv z16h=ig@OH)dOu}~#Bq!=IYzY&oFLLul*uU!+@yq?SR8jL^<6BE`;_`V23}F>R~Yz2 zsXt+07u{_a-5beA2Hhot?gfF%kiTp=X&t2m8QM=$`$;Vv(g8@Ns8mV|M`pPkXf~8K zgOg1FB55j;)*_&gW(#REI7$PFGMX);&EQZAKsn8p8vtA~$W3j#Qny{{3TMs$q$)M3 zN=F3jS8Dbf0O(oz1qSR{%^mG}9ez2FmOEiH-=mrDk2Hi@v3;%B8CG{da`a8xU@xVd zZm!^!YiRtfU~sN58VQ>#e48uGMnHw+Tp@YE$T|wce(U#Owvqv14++kPgm#E%p5UB^ zfs=ysNeo<)oG(f3U}PYUD#^JD19v3nI|i^SdDm|fU01f|{Qz%zfcHd_E8{K8_znn& z5nN(~UI-`?9Lj`l5WvNlTxQQ1&2z>EQC#|k87yA7AR|D*>(^rN30#HIAd>qed!Mv` zx)^{B3U-6S0_nwX71UdW4k`=~nSRX%BLhM1QS3d+0xm-ityiRM3-sdUP8+YF;uR8% z?9}NP>gjL21o?@rm2G>tMtI)eXwIOU|FrUKwqrKi9lqwIfevOHN7S`GF=Nf_VF

|>{#BG?vNpB$gnZ^!N<1)Rt0WiCJq2}7p@C@w5Jpj9lm@>1%+m*I)4N}m6yV80 zk~&CQAzdkgrXpx7q@!G*tuN4>V81x^^@^mw7hF`4R2`9_5Hl3EFc|>$DX4u4QwXMx zFn7B9`*#{ONaieIe3r0=0~!Dcq&9)Hfh#Tmc9Nz$NqYq3lNR};6P$kmi2_nrK-wXo zh_omoo!}fANEDO0V$u!)RisN5>4Q{pEaehQ`5=noA!YiIvWFE2;;5%A>nUdhyrxWF zV-jJsXBgcTDWPp@-8Quy(k&9yx&#C0nWC*sSi4iB1F>g`Zdqa{M6^&GS}2Z1z-e*# zX>mLP?uldWiBk{|F0~7nd=ao!YPS^wS(0a#)D4lymO5ukeGqV5az8G$=@_|iC9eXh zJ0ejic@<*dgyeMs11BY~lNdN9d7Z*Qh4giW^gR;kb!q%{X&M67s-~`0{fK}RRnHXF zU<4dijXA7BU;mD3)E(6%D3NmYWsn!0%%&1wSwNN z0y}catOoZ^qu&_IYH<_qvWf4~wqUL!w(`bXc}E0X5S%Uu=*WFR7Cn|>5hr{#fYBF`rB8YJv-o;{9%d%Sp$*C7(MyjY8Y*Sz=|14jgl zBLX_nIxbio$G}xVcU7=KBCQkHIswf`mP(VQvVg_hrB4xmed!dP4al;Sms97q1$Q0c zOgZz&&tYD}I=9V{!Nhpju+hlR5!47C`8kpssYZUjAgC7v57$y4sVI_)B6(=X0^lg4 zI?Cu^O#*O?Q60m;UB>h-V-1G}AaReO?lC+9N?EFu<>C4dNStH2bF3Nx*IDX1CQ;8) z^(+sU&LGHRmV3;q5%89!-eM9#92Laza8Ut*1an+4r$#^=N5x?hdpT+^$HO%Z2$Icl z*_;{y`5cvxNz`$J>$s6{VF`l7@b)phE7Dqb@ZLLkU!s3{hNB->pFh+T2uK+q|!T5=?63N`k%FI zMtFSdlWf)`o3(=J9q6{Q+da=zQ+dU5Sz&(}Td~yB_Vxc~#WJvfAA}T54d1bb?}UIy zyw@Y%8v#vxwm1_}7yvc3ve>cW*S@U4JW(wlLi zRth+Jsv+L|shKPqsB{Zdc7~z=9AbMMG8BBy{Fo#4Ti%;>1W%HADw(%NGLX(w=@`i1 zsSFHc@l+ND_VUzT3}o|EHU{?b)IJR4@@y_|k0g7HXOCgvG%ud!oe_y*UM$AI8D2bt zfwR1L76T=`Sb~8|ym$!%mwE9r2Cnep6%1VE#j6;o3q$_^GZ!59b^G~pQ7AZRvV zAW_gI3h0s{ThL`=;Gke}P(arZ=LOAqERIWp?h+Q{vY@+c07Ff6LmyfUGH(w$&J6Pz zj%{XZu*f9X{e%`;Rl)Okrhhy$5}K_6NMjt+7*Dv^0AQ7pU8U3_y);1S9H8_@K%mkp z(9q>UkTpuHH5gc{v|5XSV5MEKvKv+?37+BAnuE#)x=pKR1 z{?o<5>Ef4gk_D0tm4=2&Um;+#G;}itK1(*AB{w*HgCL)!o}Z;b5TtHC@B27+y}3MU z#;MxJsk*|*Oj8f`;Uj4a`N?L&Z8Omg?&_rswk_J0G1@{NXz~QVJmCwZ-&RSztEAB| zl9~9FqOD%hHi#YX4Q=^`wnjh@qYGlpFyI};bU?sH z#%&|h4goQYYYgL#fJ=OrOMIK<1LPw{bc+$&EH7>$A>Z5nPT$5amlRL~9;v^O+c$Qk2%C1Z{8 znabpu$^pI|kl3bfzfFyu3|W88kOt=#qlSmOZJ1@4@*f3#$TfhAGkdl7`?AzluKsEKG!vvpO|zZmhWxz6``zLPBb6U2szOC`m^~0a zSM<&m+l&ZZ*#T2OeeY{0SMe5t-9l)fVHSWSf=$A}1A={kfd+zYz`%QgeUAZulJzIi zNY|3=S`37fY&ZtuNHz`w7fAL32I@dvQ92~2F9r6cfTr?RVBZ=5TxpVEGOl-)fRl0X zmKh|KL2BUURs6QnKE~yz?BxoqR8W-$J(4-5aeC-Ce>%udw$kEO+6nefI7G`;`;nwR zl6Ht`>V<-Op|FFkKsGkEI%2>Kz#b(V-XaOhNWvESTL4kLI|dZ3IPmoB;LJ5nax#~N z(U*m9k$R30wGpBtQqQYYPODUHCaFtLb}z|{ALJ~rpb7}7fUrRt+dA5K9qoWvzQX8! zVf2>>I6)hqpdH}*f;iIo9_d)Gd#|#7uWFB^-=eZ^!9aq#eS*3tB9W*zNmRFu8f0nr zSJtKIC>(PXUa)upILmiG%MV0K z=8USt8CBahON5R4q%wQLRqpNk3Eh6e0V#+y#ygGaiOl?hdB|oK>n6ABn+o@vh7GSzAKeuN9m88ANf+9uV_SG9r0-5hb74u0oA+l&^JzC*MK=q+ zn}t3|4WvlkDN=VhmFeqnC5I~4y31M9o0^f1tT#17V3XeM=NY4&-Q|jIV9gp>SHv{a z!fIPsM+DsBY;SR1&?zXT*V)ITQ#-lKuVuknI6#AXGu7o?>CnmUviW+x{d`YE3uh8k zCZR#v!!gq67%3vA>60XLlGGxW=n|4CA+^wT21Hp!QdOh|>Hk5B_Cbnn$aZCzqGOn% zH_~EbDJquIAja!3nhK*eNcVd{dp@AMBNK^3yy_5dg%t5e!Rw>YHl%}mq^ZO-gPjk6 z8!GmON(bx9{6h?v*HF`12^9+03Vhp>c6&to!sIqOt{{UPy*vn@p{_E*RmK#y%jdI- zY}bt6=pi@qMXE(wq$roGmZLv2sp*;2EM&fwO^Ml*1_77(VV8N-zEUb^O9eCJ6=TJA zv0~enSG$Rk58D?ed&;)m>HKtbNStmv9UT&Dc=H-;d%A{qtHE}tYWS`-d|xD~LQz{N zTEns$o^Pk5zq;xvtBg;A^hvOSPXO2{X?9As2zV{&UQ4JV-mGFbt4yK9@t1*hTyB%6 zyc?as7$-0`$jUQ|G0tLapj!iwILsIyW^53<=sCvt9Aksn@*XnA4-H#pAV?i!T!(=M z#<&3k&luxp1^}CxJT~f`_Mi>}k)5I0Gqeus{bQkrHsAVZlegTZ5(p{*xYPXmPaJO5 zDL^s7M}AUH*_Kn@uxcxo{u8-Oo9-j`_AJIOi)n|vUL9js$Fzew%l-G@^7%7*c90{< zW}UMQD(dl>CYFt05ldOL(ux1xnYhU99J`EJ?#aHL^Js=G$2hr!71o?Epnu5xl`xo%DhekTQV z;&?~ueMf4W*LK{I7PxB8->ssD8N0)b8`3Bn7`q0>4OZi}_diNCop@hk5bFWK_JDx; ziX0S%9mLk5hXmV0m_)ANmTNHNfH-o6zPSQw&dn7jE^)C$SALTa0?fQ(&|Y%gKpx@3DD167i36$b7| zws#C*%!PfAS3I2TD^Key1^Y_ONb5GiUS4XbVt$6A%$T9ps1 z-Nh+)P0o%_YK=XU`ZAN60Q)2mJwj=YP-X~t&yRS|qXzF_Q57tU>|~2`oF$%vM9km!N5y`dWr1-MUzytVFzg4`_8l140+)z_p=be zFGN87Cc-7FaLFE~3nVs3+6_|MlIFXb$@{MN9oR4D9z^H#v)S3bQ)Hq7Z;a+N_awhJ!5tk&IKM!L9(aDrYfDsf$v&!PSPGkyd|a zn30+hHzUWi^zQBs25L*zQUhl^;O81bTtn#K_zZx4tp}%|0O;3x2)Ia47YPxr-hsqb zg1t&;U@ZesO|aD%&@T@W3H|aA0nG&4j0MrJ7ZC~ldJ!(zKq~t6A_Da5MFhl>Y%Hlk zfPMjqfJ~CgBt^Ky1CbsfsUu)w_pkFX0Qw~+^fCkB8cAIvML3TGpqgZ>4HG*6YDl&Q z1I;AWj0I^SsTK?bDyTpO8fl|~YE+1DE(jvsL{Xb45zYkxh@;pz!{iWvc#4Y0BrZ~# zihj8rVF6L={a{(IQ-50dR{JZ_zpg+@q;`n1p`G z4Z9@>qF-_&AVp23s6_}y&kJu{UGcoTJdI3HI3y^1;cKhkI-P6^ZR##J@l>IGs?Y^d zas|?N1=3G2602ED0_uu-_mH(tslv5X;Rz!FpiI%FOwkJg#eDZ-9$ko>P<1$Au$=x} z&>`vx zMPMM2GfU*G5Q!vCmBeWgu$xou#=tYq{u$>CcYxoHHy=KwU3L!&6y9o)T`jgvK>^q$ zX?7U~6}cuP-CM~GmKgwBRO}X2Tad%Ig7$9NYuWSvlgNzIe(ZLq{BBQKw>=|mo{^rg z&j1juu!+X1D3G!Vq&%U+Qb}O%0n>{&^peNFBZPQ_FhSN{k9hM(ydBaK9`P=ZFi*2b ze5Xfz4@Ba$s6A~kbdJltoEw?k+)K_`IoqwA?Tt*{?y&qFR)c^G+<*(*FgSV3{C>{E z1F;G}5ft7==DA@d3MBkF%AYfZ!wvu&In74S5!N>VsT`Hc znIa&E)8ufDa6cVLoZ+Z5oGAjXa@1AM6y^{}Jm9DYoGBb20C>$&uQ^lLW&jA_sQ}&- z?oBW2UE;TAdbOWi+{FY{OlY8X2jC1rox#8klG;IP5ZmlQnmuS(3xFVRmF!!k4(|Fe zgAXt2{P)5>a&bRa=pHMqke>LO*1e{!kU=hrafo8vAUO8-ubb|jO70`im1{|=)}U7Z z(XQ^|Cz|z@pX?)?_Ys|76QNznqT}P${p8+TMX0I>6F7k8Qbu>ZCQG{+6#!WoO1b%TcVm$i9; z;8h@?{wxK;zyfUkTOdp>5T+yD{kr6QU1~Eq4t(Ej`GctH0j40bc*Z`SaYq)`sf>Lp z->=Qgvteg0M0Rh z^MJYpfDFztgY$r@1AtP_t(5bDssn&3&b^B3096M7b)0J*=M7Z{0L`3hGv^Ie2LOTU zE`jRaa8OykNmW0mWX?dj_mwkzIb(v9)N`iGbEXem4=Pe_b)Rx{6&`RQ zJTlScRKss!16iP|&a%{5)(pNT0FPMe5o-ot6M!`wwT3f;uL(dVM`dzm@HGJ_z&XO_ z9JXt=i|o7$+a#+|*wh$y6hIt}iaw2sen`Jdq3lv92RMra5*umPjkG&7^#G8|_sr$} zkj}JS-E+Iz4^}U;_vONhSDAz5I(w^he5>?^jrhuxIqIEjm?5&=ZIRg`2e_sHKbM;= zM}KBhx@^i0&P&1bJf&lv(i0wR0iaZ{DivIiOvH+Qv0|Ie;l~8)4*@BuL*zDAOIp^F z9iaLI6{l}W@SRU&jh$sP%W0UbNzPHabCffZZUyC9L3KgEJHGEb9(Av;7dzG)torbk zXi&BCF;LiCLEOg(>KI{$)D}3Sa)&g7`2rGGRN@sCYEgQmVjmgiUh5+}2b`W+IZOp! zL4V2vYHtAar#uj#Kjnb{{V5OlDjdr#uj#Kjnb{ z{V5Lw=udedK!3^u4yzzi{V5L!cI42G&&IwUF1M$AM$Bil(0nv+v9k0<|6U{H5jcww zvM~F5y2>OSGh$b()YS$f_KTRJ;xxyG{5!+ueSW!U6 zuZiuiVc>@7b;EE@1W4QxeQt?eVAllTo@jPYw1HsQ!`EG=sNRk622Wzd4lxED1wgDg zI#!$rMFH3U7U%41LA-K;s^vB5(gZ@ zK(07ASNsBzC=myjVBows;5-)OirDXp*k-E)S-&RsyM}=qV*eZBa3s=O;?P@|-%Y*v zRlPVFk$56bd?J2_fal`q=U4%~5l6qlz&mlkJ1k=@V&4{VC=!RiH>GsKzV$SdVi2mpZd?zsL#@P zL}I7PVW+Ciy#xd~pc;1o^SVE->U|vZ%Riy&eF6hTs{TbPw2;rJ2Aol$m0hYDR;n6< zwpQ6y#&I+J3bfl~||#Vx1a&+i>+4;TVWiPmWa2Kq8G+kB?SQ zg@7F2De%{%t0U#p7*9!yr=$a-l%o_DQJBy06NUIhp@X9b2ogY91W*o8(*jUM`&H3{ zpaBGcYTBuqZjXSQw0P5CA^{S&Y4J7&-q7M3S_c<7Kq8272*Nys!Wh3WW)LE=fpOcw z_#hyLv4~;N^O!A+MGNDAOhb<MRz^w$JZVI>bm^(@V6#El}?GwZ71AsTQ?Hk$-W*LAO#v_Jl z4*^_b$P*>>vILL~Th`VRK0d9;^zt42e3ZO2Ng>Qqh<4Bx3BU!y>;lmaS*;dOHU*Rq z9Mpis8Or7i26obBJLz_C`~ecDXzeN51p()2v-5O2#FMB*sVh;s!E%FzAGCM4KRL6Fz97T6YG zTLYkp);3{a1Jip0(-)D5V(ftR0RhpBT{H&#m3IEhwk`%DU8QtdWpLgFV1v?XgVF|e zLI9$bR?!&PqO{tAfq11=JO*|t?ROc@l7cvPE1h>6j-LXMrZh`aS|F)pDebecAbXY0 zd$Aw~lx7F8AbCn{o>B+31Bf(Vsm;g0HKqMEr6X+iK%!cytyby~@KC9JXfX5uiCU$$ z76Xrz+D8~@08Ymkc%sxk!N60c_9+HlDzz^$(5TclV&Iih`w9b}ls2D~c2JLjjD1#W zKPz9pS6Ih4}jyW*>Mbf5Tp-+8jd_b zB2Hx)r)u*T0AQP{-8O^s698!{uQbEfTr238rW%06QK>SmRJHAD0f{P=ah1vhk+`91 zcLR&`naUVAJ0cRVRbH>LRNkvBfnydT(W0^hj#&r@RGS8>(PY=DJFHW;`Hg{mgsVG* zV<1xP3-*zbIHJ`ZffpnKHmi*{tI>>YQ)|I#NuSofY)|)+h!)n zM}k@l4qze@3F>yhrwjo*)y6xqI1<&yz{w0sxU|o{;rH~{uVgjdAW#k1xwvM5Z5G;M z2NDld;sb2$5vyin4e0}kWHptn*1*_LFqb{drWuWsmpGwfuTaAj`p;3F#+G>u9Vahw z3OG%H!OROlA;%VSmYM%vc+m=M3ppPMPNXIt??o1jZ8)t2z*(vLS*gvDP3S%$XpYU&iLxkAD0`K@Q0o8yI$@*I z2~HpWdDh3~)0WhUvd8EYehTVdvB-N-SGafvPZ#xHG#vT)r_G%Zh!Ju3L09vscJNE$8!g1CSstPy?ww5%PVjTI>UJf$_%`$ZmK->&+(^lQ24zaouak+yIg1|Uvh9A`N14Zv=N z{cddACR^c>ZEy<&5^E{rwUjL!hv&A0e8Dbx@O5j`=N&_NANV%m7dt;1(r(!|^4o0V zG}{b&AOLLV*zKGpH0ZSg+wGhW1kDL4AC~MGJf*eUP+|dPiOu)KGZ;vuwTZMnEO2;G zQl50;mtlcqBE@!*Vw)qX@AOAY!W!;=C)e6qQCur(VUGqNRnnyz8i#A&pV=4Uk27T3 z*G`(aPjcFmoIL_sINKJk zJ=BALtXn&7_p(9X%VEV~4 zq>!2v?23*8(!PN7L3T5XN&8~b2ieU`rYw>vcVvShg`!d@69lADW@(fw0;(zJYN|6d z8H0?~P`zrX5eRrlIXt8~AfT4AtEIdVP)CifqrOGJV@mTFyTqe`a%!MDArddC!7r)t z2xy|jCdwKCZz;RCSSk^;Z3OLwY?f@Ksg1M=0-|WMDB2YQXm*wd8}ueb@Q$kq+toxT zI0*`!tn2Jwc;pAU1%ZWKn$j7WWr4-eQ>8teWz|jVd$shBldYm9lywQVkrFJr1{+R| z8Q;55a;NWwALPb%U1@(^*&YrcMC1!)%6AukkjJY$(mIdyKb5geTwuz zD!GVsE+RW2phiK}D6~jl+CWhoC@q`@^cph5p(3NIHO~(f)I)4<_#sU_qy^Z^E?$~G z;>SI|&zAEXMVdyD)=;}IbdVG;eqTOYE~6@fsv=lyTc27_vQQM-Hd?pf82OS(*i}K; zG=@rJRB*?4{JK@=On*M~Z>+j1!XArNx1O|zv1*IxQGZ?}f0VPfjds~agUj^w|6UzE zwDNe;Pd~|%s0*Uz0_HeeCu!;=bl)dWrOCrKf*Mt9qpHod7{n2xW<%6%;M#QCLCNVC ze)?~;|8KOXbWlHm$4Q&c_3XlNC^&rLr0l>H(#tcRPFs^SfIM;$8z_=d6I3g0~S-117JK~6Ni*>ri zIwJraLuz2%5YGf~45@{6LO=-T9KyN4g$zh#2WP*7b3g!?&+Ow|V8H+hFrPWeIY7{N z?M;Vo`wpEWYwkmW=OF>zxy%zh^DuBy@H~luOOn?msTZ7cfHl4NHhE|VOFg1#ugMci)a~6;K@B@qDI(3`13GSfFISM7I zP|_Fb=1;8-OGQm1JD!aiEZBtLl)KgR(Rzh7EK3kJnbss5&hhs9rPp8A7M*X6wT$7* z44Y6u^f@!+IWrpCv|iTE_U`=ixIg52$X7V!V~&znc;72L>a@L8blobp?f3To#nb3> z!jwN`f8$2dxRJDm#wP&Ylcw)Udjzajn5|Z{ZQcTjO$y^p1~*Jq-1KdI)xttKBfAvp zT?$7y5CKricQ579Gc#o>pE6aOFN6P;Plp^QdbNf0nrq<&Lq-xRt# ztbzK|xekM;TQ8CuQzqe(Nq8b&K<5bOb3}V&owkc~-$nW$i>^bw&mrs%4X_)KW^hFZ zkpk2H4a0eD0D$#-yTQ!<>U-+Nps(&N`fqyp-}LZ*Z+d8Zf3KHOb;zHxe!4<%R|qw- zzWPjXp9wWo@vuY5V~qape5jwG6`lB3;&Y$ zI{t9}1HC$WX}hJqa9t0cz9XIAVK zwCTCO=W!A25Iw9z|YBhc5jk zkAW!!m4aEjE)diO!UR#8MYMGh-5$0N5ac{fou^IUgy$1+{@izUy_fu_0sqs0|9ct` zwo+LG9-^p2lon1UfSS>N*8{4VmCgq#M&M*NPN4XhT6Vh(o)&&Jcd`s@FLt517H(x zyoonMK%rQEtdL4sVDysu-o53f~qTdVRz zBo3=Q4jUX4Kpb~eu6GPR4UbzsgwDv@uv9L@U|~$KFa<6Z00@=5LJijt!`YWS0_-NJ z-Gn(@p@H(&eI?mdnX3cUq2q0w9}m$fn$2)qQX2*uVbw zPAlX@X7Dp~(9#QjR`DaN_z6h%(nZg7vCS*ucHYNBI*a)$h5*sc1hpA>HvjvrB>-B# zVx&Pp9KpsBY6NT}q-}%_0m+0}GGU8=41&ra1Xv0n(me#V2LqXebtd72NaPb#J|=OT zppIjpfS?L6P)JaP7$_z>7ZW`ZrG1uQ&k|}3*qkMt5l}){l@N{yC?(XTgc$G(Rit$l=>(TR^2<@yv6K^B$N}(>5+70~&?X2#J*BBP9P4TYF7=crBJr4V zdQ5pB;58+_#^MN}xe)A@nGo6~g!Y8H8X(ejG`o&gBOr`+3Zp$R&@GJagMb2>D=?fa z25}V7E(O>g_X(Okfo+_hroB(oU65hFf;O+9?Vv&haRjhz0IP;xU9F&306PEyQLJke z>xHPoD0W;F`war3S=VSxBAOi+jY-6?9x-f3BuET9C5D}efMmW?GIroJjrU2zR(0uo zhji?MzYM-(26nM<7VnY8cSQ8YUfyFb2C{kYY-}~Xk8i&ZyS_Mwx5>e7XgtKb9^$=_ zNO!C0-G-yrpu|$tol?|ow^^M2Bg%hA`SKO=tZ#w&0w-i5yJ*y+NytxsFhj#GTMPl` zEHMxux&;_6zx?O>&JwvTxm9C+&wp=*Xv_w_M+0^>-FdP7dBaU~v56&}H~!vlrF=u# zTz0M+2^q%e!Z>SaJq6DbIW`fybNd-5KI2T_xh^2_KwuvTYNSN6L`@cU4_>&W2{-sk zfgoEY%~lNTRcZDbJn~}BykB$m+pR0*u^@yq114T1LpwMvF!3Vb31|C+YY#KD`%GtZ zuT_2gm^L2Im+ zl+-1q8gBf_Z^N=%SrgcJwYDp#n>7O`R`4W6^i44wb^;(z^ve^`gBBHH-wJUgGHln1 zU2DZQXGIX?w&Zi$U_J(*Of{$sI|J}q)$g@x6pUj?U}JHg_^GSpw@PIAL`DZU?*OP} z2G%lPKuc3U_Dc6Z$26{zXGg$MHDBor&6Q8*Mzbcts@1X=I(`vVEAY9>bb+4O@`91+ zbf4)CV~sS`bZwq(yjqS`9~SQFEnA!1(DTTM)pEV&Q(``414n-VPE+D(3?$Qz$#f^E zta|M8Vfy~_2;AualqF}j4r>Al9G}3MAZ_9)=lImnCQM)MApY9?Do`$xB!*97)L4;p ztz~+_%m+0W)lV4vHqZ-*8XFs1z%NmOpE_?{7sRgV%DSqdVHiAhb#Qe@etL;sCg@LB zFE8Y$ujq?D^>y_{pLP+upijHFc0r%|iGJu)KUY8WX>YMN`n0!eZ}e#&u@CyRk82E~wb&rgIe$^~;ozg-keFJcm*qFbogc5f6&Wxd#L zz1Zf60zjgqNtD{Y7y!TvspAX73>$zZN!KJuwhi9s` z>qUV?i>haf!34V}>g~-amCIUrs# zKWDI6^q%_E^vTL!*2?QA;IO|+*$L6Jz{&lZ(g#tIz=ge5=?Fb8KoH>eoo`rBJsuHY zKkwu2AUU#f(z%>$JGTr#u);amaF7{*Hwx!B7zm)91L(FOgNZFc1A1o!$-R9CtKDJn zCvOFgJ6Lz9t2fUcSXbn6zE$+1lDep5kvwl<*)6OHMKey5aWnp5f@S@4K}lUu@`z|G z%f@1NaA&b>mH{+%8(CWasaLRkRz!dJ8fruU^oOqzpg(+#0R7=>1n3W6BS3%n8Ugyl z*9g!bzJ_4@wVN9XCaegSi|8sNYjgMn0KGPc0FHn?z>^5VD1wmSO`NSj4Ze$sfJj1! zBrFgRLm0&n5~2w3dsaal=>(mQT@qhJ&_#q6S&>{O=*xr_S&@7q=udNG+zr z=t@$H=&&FK9i-4=7f{k+m>W-%f^Jf1p+g^t^n-%_plI{!0U(B=W3Ugg?V;#B1``L6 zsG{g9N{g&FR@3xqT8rF?6-?8?*r(by(ex&Rk2Hwm98I62wTSAxPSe+EEp!0@5)Wwl z0j))JX*@&6W5=yh89J5GLcbpnWDi5{F-)QW$YJOl?EF;;LziG%UzZvBGNXla2@vEu zLtkgKa5x6w14Dmc&@TeyD(PIM7S2?F#7QN6QmKU(i~(>~NuR~;*}to#?<%#3`e{+p zElMq%2!bHNEFH{h;aL>`B3L?t)xzEnKru@fvs%Q|{g9;}vRXKa1QG!p9e`adl)=#% zoEF*NFXiY`P7AH$AV?KQS8-Z+YXSgu99_q0;bazoW{z&=w9o?pfFPa@; zy@S^xttF%g&4l)2xhtZPtw9Z4kAmW*hI<}j`Y47(RhfA+xY7hRb7Y$<)bj*d_C4+xc2S~kOHGioHVdZt@F z;|E2d;+92^($rDxDmwkwcVUUx?DO9Et3zX`TmV1wKN}+}rJt>Tc0zux0$w55iIEo3 zv&C?>{8dwqE~zK@v^a?9rh>Yuu!r^Xup|9isWxwY(j;(ALfavl+`4ZY?R|!>lk<5< z@H-@YiB$3>srMym44m{sr6h}{kW>n(hRP6Bvi@7a@RfT0Qx%%-+;zPy8qaFuvFnQC zS*Lizl_)V^{n- zOzkkW3-U8x(Jo)%4I2XpUCH}aVwR7cqQ_3L?Q|CW#Irl7SoFirAmkdtbPZvFOv<+q z##=DcW)fkXgn^5MbP>CX>MCJ;mCzw$Z8c$Bje!S*@dFGr5XKD{XeNxCG4P%+evbiv z(%7HWAp`hY(s(Tf!b#(B48)Sgu^5OWjpHzoNlKZ9%jH3N93iD6qy{?L0C0gczCh|= zUjg76DP1#MfCWG`X9oJE&b%xJof*|iGvv-swGAoFqjpMLO#Hwhi ziq;@Q{4Lt_7Hxrmd$e>9lXxi@zr>E}y%mh#Vjx8=r5H};9c>x3v|VJ%2HCQdOG&xd zxuHDDGLLdami#9u=>#TGL|GPL5+#&Wf=TSA?RH}umnpPsior?%k_`r?Cv11+iP-On z*k&GaRwc$hSgVMThr^ltO!PAA$GqQT%xYF5dY2fiX1~2Vap1-B-y{Cp6#Z{g^nY(t zwBfeK?blz5H_F3$HQ%!u`zqf7(er>|!|YwZlCS!_w~LwuL=$)_f#;BUMJi9FVjz>J zGBI#~rw(A?FwY+5B_xg_UM%8`5m3gnWthZ8Uc87&Jmtlw7>E%xF@h-)N1ULG6D$#M zPSBjgB+3L$83rl@T?H28xj;P^I5=qnum4f7`6!?_$gdLFRicELVWLGbS~Nz$W>MUX zfjCi&!@zb?+>U_+QB1%puJ+`+&@QGAFcTL-+NG0-524H$SPiqA06D2k03XcEOH3^a>kGX_40;s*?T z62(s#SSyKZCA7hWNMZ;E)=T1g3`9v{6b3d);wB7ik;E++NRY$?3?xaKBnd6`J(6yZ zWQkP65lMFh19_4z4+Cc;-5Cs=lXT}Wa6!^tz(A#>tHi)9Np}kak0kLCmeYDkQ;+5J zm85xvfj5%w4Hm~c$>N=4i@eK6$>Jjh0#z1)DpX>VirS1NkaVzRDDdv{0of#K0+)<`f2sRhnW9l&CZ%7&x!e zoX5aLm3R?L_Lhphg}q#jimk!GV-@unE032d>LmtVsn}OokT)v!4F>$xtiKv9!eBKO ztVRntRLzEB5@BjK3c2_Wf0L~Ly-C)NAoVD_nNy-< zD^4KG2C{7?#DEP`$_t#1Mak2zr_#Wu(g;KcJd*}KGXQA9YCZPM=t>w{Xqs_L|2yK` zO4k^9ZBj0@D;Llkx5GqL7-n8sr?OgSuycGfx+2f^#JCtaYv&Z~Im7v`gJ+k#3y=My zHP&3pIu~=r&!gP)FjxHDwAXIBBiwa`rffN{K?+@v!WrguZdlc!E~yTiQJO=^Ws%~G;SU}t1wp1;trwD4q*r~o!uw- z?K4VKXLo^pH14Saq|7q^4Bw9OyH;fQy2))e+mNu`cGjXK>wi&1n56f zfdKs-76{OPlL7(yPf#F0|HTOe=)X9D0R0yy5TO6!1OoJ5oIrs7ixUXYe_#Ru`VUMX zK>zs&1n55>fdKv2A`qbe76b%ldvBJ-HT)1KSGE2`5PSlF{zMQ0^e2K4pg$1=L3rk$ z?-UO#x5^#o9jkfAIwRX3?^*UeYmEW>_iSefzGye=z?7JNTjfemAS@CH7id)l;4Eu# z7VCulWLz$4b;5$ghD2jDsD{G9E80DsQhpF?xFhO<~>I4cT* zY~-9bavc!AkyOq+74tUA9L_n1>wxU(oZ-yRaOjO0Wt>GB=YnkV{J-|D z`!A|v?R$1-cTbsfc7a8DZx#>~JBmHAUXz=qSG_SY8e__h#+znxuMrgiJ58|wiXs68 zMWiXk6;uQTQLvx{tbk%cumbNK@|is|?_Y3#h(Gu_eD|4hX68(Lp6B~q=4>u=9t2Rw z+17EMgx2RaXLFnLAb=LmwuSQ~)IiTTn`cUl3RI8Rob7AQlh6x=@HQd52L{^R*UxmE z6}(fvk1k@wBF2HRP7Txeg=vB)Q$oK&O%O3%lxT*QXeJUsDnBBX3POpP%vS5Y@FMqk z($KLpA8+3&&jE*7cv#W73z>hs{P@J4-SUvxAXsh?NK)U8Lhwe4cs@$>jZ(}ae&83K z{r>ek@p7&WjG=+C#RG9b*2b&9H(!dESD!qsZ=N=Q&=j4}+MUok5kP^~vOwFL0E)B$ zMcTm>Fri5MHUXHll8Mq>mT28eDAM`|+QARB!?8{&{a#wd#1jvCs#>jKt2Opmx2pOm zXKw1+cN657IK)~VQkJl3IZHpCl{Y&7SFI6l7m)GtZ$ znmv!7N7A*;bDZZ%l%2^)nH0Bq9Y3;;illlB`W{oLqVfC5Z(PEF62&|Mt*zCJv6^wm zYwK^dX}zC}dVk-ajbm|%vBQZ`+HS_8o3X=YU=r=Q7|?N{qPoMUFHhor@kG@va+yK4La34CM!We zH~`^*1hxXOl>!a{a0u|kFgXI)BS0d6d;s$)I|vhiCJLwnpbqea3gi#K{sANcxCPi- z6wn6PHVWthY!?M2Y5bEkL&&L6DrQehboozfOV1yW zXTArl(1q7J{$8SitRxWk{11*N%C zUWz>Go~po8N*C3p0&Nr!qXsdQE-Fq9;wWG(0BeB(_uZO<{Ucru^G=mtFPm}8X8iDM zFe!X+c-sg##lXLeDLRmI;K@HwhDc4$3#yqn;LPe3`M#{ z<@Q9S#3uzw+ws-)GuscznWPB)Qm9~rMdI8d(aiWR#-AHJ)GoJ26tT@_Qanb8p|wrn zF>=}wk~}bEa=P3h^^BojSuKw~k2lO**4Xn)EHgEhnSUxtCNjlPj6`@FamGUNGZcFw=1hAYhv+c$;bpo?sBLSLL{ua{S2Ry|O49 ze8lD&M-i4IHrJh$yEY5GbvxY&uGY$J8!nn>_K?32y`lF zdto{5sEpqg*t-J6rDc9sq!u(+lYcz8kH)P38bzjBaS9udk_?2pQrP>Ts zhX=lQ@X5t2xk>&s!oXF-#%gjVuT|i+qPRyN=WUJgw#GbbsurH!5X60UOwMFI3|TbC>anu#%_6Y%NrAce0zvGz<0ol^d{2cW4r;+c ztw30=9n*qiT7fv2lxabkR={_NC`q-pZ?!gnu%N8hf_klhH*5~7+`*+CF$MB)E>L?E zs6C0!yUYh(ru?6FL$BQmn@E1|$6MSx$_nK{yOkHV@|Jk84x3#V`>!FFdVWEa@0%3j zr_NQSfvfyvFl?rP1i_e~Fz=vx91@I&C?HR;&7)Ug!M2D3Y6WAhlH~oDFQPs!Eh&;)HB<1& z6i7F-qr!}%lsj#?IHFuMM=@C*UH?Y+^mllE z!=MpvZpCtsghT&uWh$8N6;Qfv(!651M&X=gIA=p#Yb0}gGUdeekh6Tq+2CDf#$wgK zGv8TUET5)}pjVL+`W*o!(4&O%MQ(t;4OE<+DsgC)IGUJGu84!Lh@>y_T50rJD$+@| z6p$^M1AL)U>LuTLg`3H}&x(whUw>OHUs{Ff`h@9xi5Vb@b&674uaQI|+b5CoH_T?; zvsrJvlpX9g9UQYSvsgX@zEoMiRM`w^#rfBQb z>^ijrzHdho9cs2i?Lb(HhiKRkjRUsw?*8hr)AFwt_GDH7U;(fpj!LBfmQp~1hE32| z;WG=$eXj=Y)!1O*k?*oWLl&z~$un*eYnh~6-}C@(Ns7xv-Y?h32RD9jN^Z>pMlAS$ z7fY658dvIK$+A!(K|)DV_+cqLdF_oz^q)!QSa(PQ(aI<4%_bSxL|d*|lh)9rb;c7L zS}xz{jBj-2lkd;W(W~Qsi!Av|b@-R+@PAKrxP0KStt)n1Es>{%KO;WlZQkCgC&X7_ zlTHVqU!tKQno{y@gNAJsumc))P{3|z*i8Y6(2z(0Nzjl)0l!1T?-Y;;4Ve^h3>uD6 zKrS@oQovbgI7}mh zxs;*$8PWC(1(b`nLz;-ue8)8nRma7JF7P{5(xI&Wf|f`atiQ#7ts za~+#UZH(>_Ez=pwbUxS%1*NUffeM`i26|Px#`-V6)KkS$!B|S&S6vZ|S16!PfOUc^ zv0ted*m}h~_1&yF{~b}e?W|nIb6V$fRG8gXt#hl^8!xENcjtc`V)0FxJVRBe*$Q=U zd{Wr-X5N=wy>6Y8_m*pQ-fMM3iT4cG4Gh|}cHq=0t8*e*B`8`XAURJ&k4_zpYLXxEvSS|Om~E~@nxshf>zwZ57HE~)jGDB!+E zf1kShXwvALD4E4k-nNh@k1mD$phC)kppc(l$j>3VH(RpFrmoBCC98VnQ~ahR z|L2S!s(PAiu70jHzDGi@G}mpe5BcZ7xxu3?fIL zym5nnd|tlg$^(P)KrsI55pV){oB;j=@R|>P%}*s-D-23u6pPy<81P6Lg3hHg(>BFV zzaUox1qe%`ZW>Tftrq2`!Dsd)3vS2yN?G(VKk_lvOSN!dt#TB@Zl^Nq-R~;n?r8EaOd;L12txBGbB6NKsbOFSN6wNwCD?ZXlVn5qwzhdx$fE?C6M^Q*E z`Bm%q#RtzW%54=7yyHPX;#`vm42hJBNj^V4pMQsFD-&;J;z>XEOvx`(p~=Kp;O&p_ks=-dh;s{AZ01yGJu@(U}#1)P4ipC2!M1}CptszgRU6x1B0*eK<#6WE2 zgIlQ+LKPfb1x_GbYTrEcZhKYnebDRT5aY1m&oLmGrXc1ueLsHQ;L#RG(DN zn97+uX406T_y^n8T$XQX_6u(NDWySyIH^D+NjnO~X@%kpVjd|HrxsBmMvKL1#gxQJ zaq3A*!X!>JDH2z`1}te=RCoEWv&&y+m;b%9i*5D8qSxnd^?a>0>V7rqAiOg`pUYW( z)LAORcbVj0rm#>POjO1Gr#hlW-c{slUGfzhioK=!HwXIesFB0Sma#TvtQVnkImbGj zWBU?7HS1c<4j_Q5ti@GIZ*!d;b)B6`NHnoFO{^E8V{K*~n%TYt@RW6Z$_^la7p%n# z)`b9Gu_IrxlL;V{vkB$Ah`Y6QoWnYI z?FV@7LqMp`5UO(^R+>PBgw7#?0yeWVHnX$vE)QiI$r>VA2Lgy< z4N(*j%^IR9U>9rL#kvql5?O~t<>C%yx}SB}PXWhR!!at4Y}PuPbtUr1VXbp0Ae9@E z%8kVhgED==jd;OL#FkR{vc2aNp*5yyUGO4xHSVG1A@^f7@*))_SVmFX>=?l}Mi@yf zpSMNJ+oD;MZM9?0lC>k-Yvi>jS?HH63?M`c#E%LT0{p7ABYru^!deTY6l+z%R)tXl z0a0ogr4UFUph*pzC}1^!tCheoNP-TQjDR)`Y*UyV5J1O9MgScf839|hwp$gG90YV~ z#ZIji9w7+G*NOQ$E6l=xfEt}xqqD+?7z8xx#73PJHaSH=mkxF*qzecLXJNQPx`2S= ztl>D@8+RrGIs~pmp)xq#{{5=%2?uKBxn#NfavyBTG~BEDzoC(7wQ^(c7KZN@#u1I3 zE(WD5p}Rlmz2M8%%#2#OPg~jEt(3uH7t421Mj7>-T|MWFjWXUiEIwrF8-7hLW}D!= zO(5^-Q$)KIrTeS4yzr<9p&hY(cr5mQESj%8J?F)S6nHdT zmzAXZ^!j~@cdfm4O7gc$GwS5`kJ0(X=tkfUnKk{nvFY2Vb@Jw{UCXvB5tI?|T+2SE zfEPOUh0YQ843aRhz@%)XI>Gz9a^HVhC%@+=Vdf@Emk}%a#wt5`Jcr4mC0bCTHDFa0 z+G2%p#t>zTRh%J&)$OXNmqqsollM@1^IUOwt~d@i$>_oXacNtl8|3%Ap$0b;;`u$c z*C)&i3~7|#a}{J)L3@0QhJYl|kR+P*)ChPk8lQ{agv54<-7cAvcrJHdvN&v0qufbr zblz)pgK%>qV4betI_jL8&5q7yr{J-vVTblxmz&sAkutTujN1HGsJ$wv=m{nKuo8YW zF$4-E{{qFv;m4NA`-ZBHH_A=(lpphy+Wy{vLvASBUrfj)pDo^oZ=2r*r7Y6IBCS1c z|KfrlU-m0j-;&!9(YUQro44!pcKCl3@Y$B0Uoyp@OmRGJ_aVcLN5($3ye+rx=ltiS z0o&(}pOXe`D}|9OsaT&cq3=t@UTs>R{QkvTAKZ}(Pf)uir~`2)d^q;*m%GUu7zyV^5BlM>u=V(Fv8LJUuDRswY zUbmUoW9=nM&<1C;K{BvOx%ff3I1jf(l-EVUJL1~?9)?8kC1!IBl=4ffFI|Y^O03R4 zmRcA$a051QBk_lU5^UsLHgf$5U=QcMhuR?Q<*fEn8-znJ@DRltm=0~y6{mJgN~cGI zz{1YL2RAM%?5N7=sH#7~d61>D%~E+2z;Tt|aa9mD&PPd1DnFAdh#)@N1gtj!4}5k* z5+%T;1o#o>>?gqK2}Ob00bD!40K(Aj6>xh+1(=J_ct&Uj5vrkLjc2iD5Y|AUQYtmJ zl^SnsHj98)lHV(e(s+;FYmeR>z~v{On=wnLEo_ko*-?$#QH>YQEM`zrs*?d z8E}0@MK){)&h6A_?f|fZYO9wZ=p~p=H(4|$i|*JEdf~8(dki*{p2&LpL!#p$N>z{{dSr-w zal<2t0@1O6k|-423Kh#nBvB-~6;VL3=vGVtCq=iD6krnFObYPBmMRlhqIxFZKqc^& z3A`sU60&*6Y@XZ(SMrXP6c8tP$5B3SRf0>E5`U+rddDxlVn1n@8*#hNaXS?-8bz9q zV4bi%TWU#1`Phq?n6kNf`mDLDU&sUhJQ#jn34DY%*YaA~ zp=CR?ZuqJmeLcB4W3J8*e?3T|LQDHM+hHB_e&%h)r;4qVoGK~chGcg`@+B(PDA_eq zK(l1mOaV_M>nBuuv`KbtlthPQ+d(zZO1<4mJz2^+y~R2`>2MIPw-48oO}IgCzkvca z>zz02&1T=I8zS`%k$N*{TX0_qB>fTpMm|?9;un!j?TcI&`4XpzZa%o1Qebt!fDT1p z_2a^`yS<77I^|j23Rqd;UF?bvXH5=Va;#G>;JDiIxY`cy%@A;fA9jTg#w!y7LZK82 zNsfwS*f&|x?2R;>+?BQchc4MC;Efu*p&WU_00^V}cD4Ypg#r!&aFBB3ISkmtlp{|b zfO(YP&Pf1IQouC;u2GIW^?>D98*7d z-o@N*IoAr-vVyfCE~pz=zJb*fzy;3o0%wCg4es6k*Rj2;E_BPix{zOJLyVJ!ZVLwx ze|GU>y7=it7q`KIZ7>*Duh+7LUwtBvmG71<`0l3s(89?7|M~A0@qC+jZX}-X63;h@ z=Q`r~590X-@qCYXzN0)di1zNM%@6G-ZVS{mG0n_f<{s0+++iA-Tg+_~tfzsw!PKG6 z6DqD*N!6=~cy1t`>y_sPUw`%$gZ?~@3>5PjwaQ>&X=Svvvv+WGa&~cbbB{no##@+` z%$Hw%z4(hI-@Ntq|7(IW!JgfW8#j}6|%Eck3Uvy=Ia`Dg<(Vfn&EpNBL5UdR0Xz4zaZVWOGsOe7P< z^zGNjCl-aQ*?}CS<4~xb!zhy10p<{sj=~_OGAYPI=LnO52$PR8S;!e{KaiJ>8jEo7#5PT>s2J$9Rqv=NEf5Ni)0uA>+}< z9z`wtoGt^Yx6=NJeg3nA@^NSz`BD1jXZ+>o7HjS7-_=#k`1;`Ey-wTTvXfm7-R|r+Xi?JzgX5-DO#i;z z&))m;A&c4}<)cT>m{sAkZfaPTr(OQG_|Gqnjvt-z;;8rYky|3perUGBC-$C8LHnx5 zvx1(*zpQF({^0A-7S~qpI(wz%ieFw03hEisX4Uxh`46tVZkHr0<+hi~&rF?pvt?V~ z$Vcybf8H@^-^Yt}-Jf(%eqh!3&4Z?~_rA7>*dJRwRa-MAXY|nDOOks0^IIG9bq?#p zGb~a!+PEzIJ>jab*ltG7;!J~zO;7QikUNv`h^-LJS9NJO%t^Ic!D!nL&@XCp) z-K6O9BJr<~Bx`JlFf89Z9Q(yn~tQ%XcPn$4lWb~~E+^_A$f#2m>H|F_}qvNcTiuL=)(iiXiG-k#6Nx%9p`f10{ z_rLM$c_%%f=a#?Y-Fx2uBiJ_%{LRf+T9o6{F688k6`JJiqFZKu*Jmf&`9nDJ@eX@@i4egVf zW4~s|mz)!EgO`1}GV*x`FKW;=F;{bT*1hqKT8tX;`;g+?Kqcs>kwd_Sn8}aFHuZiw zVD0*eD~s+nx*$Jc>z3Ugye-{o+Kz4eMn4`jX=+&8mywr>9!)^Zw*i>)w#*?l zVTx-dKhAAvX0t%i@2T#|PDvPMv3``<`fmazAK3W#)NNgt$@>@Y0LvZ?Dql!l$ZRrn zw&mnry3r3Wo%qzgqzVHtJhA#_@SF~5np)}6x1U1p7vHdH zd!v67`RDXSBPqjMv#rS$bB7|Wd!H6udb;tSkAXjxcx;WY+y7ed{CVi4HD@M=+;A=VmD#7)UF&LdVB@Ur3z_2i zb|bsTetp=Xf1d>w7WTBJ-|-MW#cSwt$Ijf!wUr5?&*)LVCl-tz7@9xx#%h`#J$X<@ z8<#QOF1at~PVCk(qEUREe-=~H$>y)qyY_$h`t^xT^x?70%DT|HV{aN==#qT%+W0oi zV)KzD*E&AS>6!HABJXnJnoU7WK}Drxn(wA+D6jrM8(yAH+Zud$UY~6z z*FD_Z_4wPTpL09iKe33k;!MMBYK1URGU)^KVqlIVZJo|93OTQ**1Lh`sz5Vo8_{Yq=tbuJug$!MPq}Pb3 zjz2beB__1paH8F%Hch50dw`dpwRYj4$zvA1y*Xm=jJ>bFiJW+0@Y#8lS0)AY46Jl0 zIrN98*SxXw&(3$7HoUCYj$QjRbvD}voLDn=L9bRLntbkm`ErZHFFt)g3&f6}dYKHTFH)-e6w&5qb?3l{Cm)`qA6oOed)f5oj7n}#>q>cVuWk+2F!l5 zPdeazy>R`$p~CY&Pi+_N{}~qXX!5SezV2JDhFfRO{Jp{}eRi|xxIr!h;;S<^FTHtp z`HIAo1Gjb9GC4KOGs-OgY-Qlj7KwhYW2TK>9P=vm)MV%Dmj6b5$x68p_IT#8%LU_- zE$l{Sb{cZ#@ttY?|DKzFm3JLIIi_Qme{M;smu;(>4C#J#i(mBA#joNYn)_0}3$Nz? z+9ROqtZw7UDdBzl`!1^P)wHO?sBua2Iy@@!*nafk>fZXn20CCyv3o|d1>eod{4%PW zczovAXYsQ$hx*>%BYl%Qn>y*cqpP=l;NjV?XN}r28NBg+Plf$LbAsK#-xC75Rji(n ze9q=w?55;xi!(ppnGDl%zTQ5#%KYJzCpI1C zQSWS~ybNo6sbWIh$%*!MYpLQ2?{q`ougtC?YKTK-k(U)=U%Wva`~ z_S1f^n7I9Sn}t(Ta^v#u#|q(BI~uO7y4k0(Mcoh6DkpxD5-l!`d9dL7=*UykLdH2g z9KLkQdf$j2BNdzweSZE}Y%=#`;_RXtDm(?xkTH zXEZrQcXmv>eK#+3>K3Q)59Xx)2x^a7#%X6%zG#YrnWoUS)_v@qv``*`0F0uNsC)vOCLMB|8=Wl&61{!u^si#+%Fb`GUd)@Gv@p`-cB?#8&nlsYLV5$ zOiN#&vM94EDxcTq^KR8tFeL_41~WM6QU87M{W#~&m)Af`KR1dciuy#+tD0IyiOVQ^ zB=-HW%Sl!XlX)$D-Y8bX>uY$|YAS>hLnvz`)=Ast=jsot*jJ<2Hcr2dbFQX>#2_mq z)UMru2}2j#30nG!QK**D*D{{f)J96&NZBE=Wz9}x*~V_Q)Y6}gVtYCLUe2kSY9+SP zA))!r%pOL}NjGTeXGWpxqW-!F<623HD@}2+F|)$|-jwI8rI*t6h4F^czoB6A=ZbS1 z6943Sx400`cdlAG`+w=;^tD`*YHFdl&;z}2fBruO7hc%98R#=cp(x5CigK)`x`XbP zNGMX$wZ8GGm6wiwV-#9LIj^C-t0}#`-W>^DS~=|6`iU+4we)SH&{v@U3SgQxQQ{_3 zk;=c&*V?~X*HTM|7?ZxA*YD?js;O0!xQcQ_vFTk;op}D>GZd>e##RXQg=%cklo)M_ zZE~G=QhGb4wU&Nr6iX5HDI!eSa!OoIIiT1orgr|rcKXlVwe&foSQM>~qFt&f2hl-~ zgzUyg2lor<)LSvTMVY|{HdUGGZ2W6%T4rXI8Mo7d7Vqq@nBA)4 zPm6#3Suy)q|Hcve5sj)S^)Gtc-y_p|KR&f?VneTq;`C8!uL%?b8{WnB{ibMk|J8r! z=(omqJrwm1Mc59)DQ!5_&^~jfr@uOt@_LG5_OAYI?uxrz6{Y?~Z>yXb`|H8uBmUCT zON_;oNb3`6k7~+C^sz;WUX7Pj+k4zv*wYv~-;@t_W5i!P-_+jm5?q^unk)OOJhwp7oEG zK5G;@Nqd~6n^sesDcjAIClX8V_)FBR!Y;G5bg=Q2S*$*bg>@Vt1~k<1;_tnFZd%f6 zu9m)PjB5v_-$B8mT0@CzOkJi+v{Jg)^3HmGn#%XWqy`B=+Qw?<;+IMxR&!Ev8 zwe)3UT=9ZFUVwESN{OMSk)l%T?EEF;M>tHs#u*$GlagMmd|I#Xc3d%A zM60^0M%31bP!}2hk4p9SnaI%0t#R2}I>#8*Ena_%hiP9#iHl4vVvW}9?|U|+KOR;4`^Yb6``dIp&DWzKal2NEk z(3c4?t}sdrGsTsjoMF~?{i$-r?0tPJ?XC2-ucFkys65B{No|vMMptXhPS;CV+FANk zQR-hL@q2#tn?n=+eX6A|8eesZ)?cDw-qup$TGR9+xJN>x1$|;_wDfDESQ@2Iqo4}0 z5p5b&h#9{s{pppE4_Z3ZSeQF`{Z2lhnu?&r2&!S+HJd*1ml-yPOfw`}siI0bZ7Ju3 zMi2?jmU6P5SYL@xUbePX;-b4(yeS@vUSM18 zfo1hB<9nBBhJi}fw~}p!<^3ZY@R1F|z+TC3uhar%1io^s?6p<)AHIM8*q=6v-WhqH z^Ntr>XDIk2LJo|O+o4Y1+rM_tueoNx96ou?$gdeo)My}B1mr~k2g`xHoB%t4ypsS) zz#<7)V{se?R);~uum~ef1y-p9xC-Q}L>xDP#Z4lP+rZ*B0UiU3#{_r-EZz`cGuvu2 z+W|{Q92*qJwnM?_KH|u8_T<)c`@u2#>D~Z74Mrlw>$oF z3ag4Zh#G{I7LsX@Oxt5^Ybgtsvi4ZZ$zdIG z*ygCAoc(@5{Jd#}<}|3H;us~4aYCTBa(_Sa8Z4SdaMN_(2JNBSFr zjkM)P+5s&vAc&^*(X=xLj?y+qX*V?8fD$RRRSNBdfkN7&kPg5qa5eB)4Fa%!aR=z{ z0B2M^Fpe@{TL#=Q@C4|e5E7wm<4`sT%YTtZU!-wHEf~geL!-Z80&Np@H@1iE(5h-Q zNA}KBoK2k?-vpS}Hiw`iCG!Ff;b-UG0oeL-Id z``J(IXMz2EM1e^yRV!$)f|ih(0)j)F`5{h^>Is6wocUn_+~llpat=rpLWyDy z6mt>=5(SVbNN9QwC3XwqZovWr#{_VUkjN20jv%3x5e$+mh`E9V2CfO<8X@se01pKT z%^6{k3PG$8EHLm+0PhHia1n%y5}LBYAQ7S%AzEM{Mg%d0#5u9YIk6vFH^3mZa63)( z!rI~|G2oLJh}HBi*>aa`gC-ishyHvsXV5skI<97Noin+vXblCy65eVFZ;b{z2-0~u zo!6i>9RxLew;H|&($NsS<1OCtmKazj4qYY=$MSGp9DH3IiY!#;C;zjdpii=1Ro{;& z-6P5dRsV)|_gfB`7-p?L3FqCz`DQ2~um24bl2^snKZy{mA_RN%z9X#`wz}`P+gi;{ z9^;ZnOi0UEm$Lt7LfUPM)Ez6D(^9k3QgaMsOa9qX00xSrRz*^43_O#*eI^Y=0Od;6 zHDSCqjCVz8h8o3agF_l6B=zi*SeqJyD*B0Y|HL&xq7bYT+N={AiuKsks1wzj>I}Z{ zNr(hOBnR|92$o7N~cN?;`c!X*$+fCvdh5MY%ARuN!>By5meu-8UQLNo#5 zBqdJrz$D@&C7u8Yl9E7xoszPX0J|h*7Xc1P$^il#l$3)6I3y{D2yj?Z4in&rq#Pl@ zQAs&UfD}ndA;1YqIYEGvl5&y&sgjaPfLuw*CBS(}IZuFmNy#U`B}utNfC5P=Ai!lw zxlDi?l5&FpHznmJ0g5H1m;fb`QbK?`l5&RtrIJ!gfV+}%mjGpwQbvG>lJ=qGgH`rp zN&lFbygrfiPfVbfwZ2z{ZFd__*kH%njCE@;{=vySB_vTgWSfG|qqr_nTt8&|gJ3)7 zww?1sb65yIaKZ;phjo`a&QQlSbTugPi8Fj6z-P|znE+on!xsVs^Y+2KCl={q-fc1O zhkugn@Is)j0y>@K!kl$mhG~^8?T}1$@g| z0jw3wk%p@Wwrd4<46GCU*9omKuwH1sUTBYjD4}JP&=~_81>Hu$5d-l;t9YRcHU=h& z?GnWvXxRf_`%rB3P;7zhtq@d*))k@)1}a7CN&-9*tsfDfO0=#bK(%OHO@Q69&u(JM zm?ihflD|ds5ct|-isLcG8;xJ}pzSfGI|92m<_A7L8E&Hv8-+^aLZu}NTfTZ#g3~__ z?Nn<)k>p%NwCEzKS&_-GP%qJ~Na~F>@ei`o2iYA(=8;Mb3|YC^UI#@-I*xS4T68dF z9!zO5dqFVOBA5!oz!IwO5~@E2mQ&`-i6AQ|#}$+_CQ-)NmobiLq6%O7fU$c(Ol1>5 z*96cV8R4MBdEj&&xM1KhYyX&a#EcL%tX&Q3fPpWp{tIg$K)@HaDF&8v-pjd07zpRQ z!Z{xd9FT$zm{zGU9s6YOeR3mIaNf(8|0vBo?4Z`qI#yZ7dZ0!4yd%{v3GKf+sB6&$ z(gJHN_hFQI7-ffnG^u@>)C;Sfc-cH&wnHz0F2pLkfW&fz-j_O;laKaVN z8BGYCX^-fmFFHG^tFo<>^H$0a?PWo5lv0jTc9?QHO?jNAnqljO3=8X+(C&V zP2(a>YYbe~SY0(qIG#9r&$97kC$;UD@!B%p0~vMeLDw>VCFk=}dmhnLC@<87ilYgv;bW&H+A(TT1<%!i+ zw}epwPzO?NB9W9`B;|y5exaywV;G5EIZ|`J$G#=*>T8}z1D;4fV%54^*6o(vP%T1< ztg$NAGz=1SUh7v5cT+voIesDAqmb=` zHZ341V*QHPrU;DQu%cPv;^!V}LF{GR_A>sccp=y+wcaUp!%F6}()6>^U=m*A=rjD1 z3+1JD?j%Z|M7d%Gv7HOp&b7s6PZgqDg&2Sh+q>n!-EwD?2~<_84aB%VkLqe@*Mkm0 zoAbTY#Z@!~qA3McGz9g#^OhL6$cYy@OSD4`CGt5#KIe>qLQX8?ERi0D5?45vE1VAo zZgY0GIX47mMMbSUl40hpmgZtXw^*>l<}Zr{_r-!Q0%$Uzw!oztr==$I6a>Za=ms86zF`^_ZdxlSno8N_7HH~)9Sae13Y}x+B+{8 z&I?|cY2cQiyCt|`AXRiq75&kfgT!{{9}n2w+(%7-tzhel44Cgh(1*NUBYZ3%pXxHz z57VRx6iA@7SS25(%?{HFW(qt>b0=vX+C7FbWYSzFtwTHW5ERm&kk(?I`U~Co3*8Fa zF9~Iug)$wmX1E%F)j*4x6GK@L%4)GbRl@p}u&uGlz&go%on()-+$h;U$}}v)blg*v zdnP**1ZytLDp=L8yYzTdlda11y`n*6ycI$jlyQU46PDRzVTBn zjvG14jhrL40^7-1?&KWNCMlHI&spy095FLo7H65oIbx=xJDlYm(~blTa*wmTM}Tt9 zvYY@FoMnXxpjM@hZ^k1)NDsltGFTyl)g#@VwjyO?>AxES)E=~v0vqAx{Qv$bee9cu zFhiTDPtF0Sb07fKJu*w4v^$@bXK-_3u=pB|T59hcf+^_nBw z=g7E?Jy+J{$_7+fbM-a*Yr}^(uOBP~!(PDwTezhQQaWMH+$Y-Y6P?jY?%h?Fv!0D3 zo2$9mz_@H+TA;$ZH@k-G_siD$SRYFr9uuuRQPw3AWAP!y`H&KTQdD{B4*&xOZc2S`noNOEV!Ldxuom$>|Sz#~hfO;0}d&YBhsPCag3Im@QkJ6zh zFziPFJ_0S;PlsR|tKG&rqR6HWt2@)J^G~hRB;I2T_e{$M2&!2_HS2*?IRwi%_hnqe z$`d73P2wtEyNY*56I7TG<6%9Nkn+BFA_soz(Apc8mz`jzMdK;_xr9=dP6wnHq z6hKf!3q__`0tB~c;T8d&)8IJ~YJ`$js0o9CxCKfQ#2mm60f+iLaL;@kw zG}C|}3V5$kOhUTf@a_lTw|4M ztR4f!EGQ-Te&`7V*-TJ2(+&giQtNoBBc{3DDNWy*Ok2O5Zh9&$c|aSrd*n;)@};hr z=u^4*Q`3~IWK;JiHdofRQOB&ug8O5^8{3k4BG^9>oH0-%*w+x?t6=yl*kcmGqIs~W z!@y$Ed@%toiY^yL541=1Zir2vk&O406^|36_ZlYVJ+ub`~0s(mVG#|qjHHFF47GL9>W3i!x4eq{X60f7sl9lDOm zS=~+@We!lv0m=%SduK~F*^)C>-r168w#oDjgJet1v!yneM5(MRH5sc0pS*f9a@YHI zYRZy@R>?vKY&1V5NT&oX2KI_w_lmvIXufa!q>3ZaOnXHwJVCopa6_UHoEE@o!5Rbk zf;Qi@5`hw>0w@)%(ZB$~b3yxDa6`2T!4eTH5v?(>R@AN)-Ozp>lt>Ukf@qC_!y-5= zTB8&~i8CTNBU&RB4#5=>ToJ8Ni-4d^1ZAQ%+C7`w;X?bpV@leq#hp%pbV`d9G6WeE z$RNN58f>7on0+mo6_QPpLm1>5FI?mG$fVBoI6kw*ycr$U;=a%5?=$vTKYPOJpRo4W zFtmbmUBP)HkT&m+4foIP>ZnehN@-AP(uDu&76-Lg44u>`yD5*|RCClsST8y|V~9m( zwQm+u=7p3MQv48v(B>hu6;j&H?~eGa{nf9t8p$TsV-wpHX{&$xp0{2e5!P8v^bSVZ zL5!XG49GXxbf82z3(8G)-65$*>e>t_=`6t~M`+;)ZG$ZoQ)wZUw!s#P#k5dN+hBcT z2_r0FY_KxA$$*=tGZQep-{; zY!G0k&uicIY-MM4HR9C4sXH3O;m-{W*ucnGayBwxBLR{bkW7HR4A@J6gN$&HvB2Uu z%4m-gc|68wkC{NXx>oZat}E$k1>YaVxkPb3*y3m#=dz9S!3-?%oNGL>oY>1b?p3!Mj!PzUZO=l&Im|YWM~V z2n6whd%VyXb$B6LLYIiutN#{5#tQ;!2 zph~VIn%pyqrL9M%F6^duhbYDn#rUG7O20VIvrqnv=q5nju~Ps$1p~?^1la<}77Qqx z5R?m`Tri+)La;&vD?|gzCIov#utzkYY(j8S1SdrU$|eNoMQ~m;plm`=EP`UufU@~< z+^z)+rO5hR6-XTmhyl1(4y=_MZ1YG{sS8`<{Sh>Ch1ra!K|F1N=4?0H%CSv`{oF(C zxko9Fqm&m?DiEBYoK8?K7|5amvWTsS&2;n4#LmMl#_^WPZVTh6W;#|gow1G(3!Gzt zE1HQxiRG-jnzmOp?NRk|2VTy&@@iiXwa%{bZrAt#)QGQ* zoMf?M3D;9Kgv>O|bVbu}_;bEtKK?Ti=o5i6n%lwWyLq?WydOHq3_-bMUoLrKi8v^? zKPWeBQ2esp;bTbR?w)EJE2Zs9>841Z!HP4sB$Vef`v*;MoZ#MDorYxreHQS*-g_Q+ zod-b}xFL1AAq~LZyGUuaNNKR0qAannYSn8$sJX(p4^!YUWx#6d1`Te|29z!+u|lJ) zFxe>~*s2k>n(UO}ku5^bjl0p?9A?3I?iZ<32#n``FeS-V!(Lr7X!v~zZfu{`$gj%jMRAU7lCcZShr_`UiVSk z(@{=2%IT0TV9G2$??sn(ebpgxCnfJB45DR1>ZxLau zD3aP25lW+44yYzh2tJh?Kb0Gd2QcB!<;KqmP%Hb_n$8wOi8peSH*yf_nh<=D4IgAj z1bSDz4jO6xp>HGjWS`u0pGikSa8Mp_P#%UvAvhxYA0Z-3k^NH$a8hn_QtpBUIVHD0 zC3nX_mi$AOJPrdF<*payz8ENwyA}}OirnLh{0$~?U+!_A01xG^4~Za8<<3v#25S;b z`g6JSa{|=LU25e%Sfp>{UT=s~28)#M7AYeziBM%&sPYpAmMR05DtG~{QU4n7+t+bC;@B-SQ`0r4H(DnsP`XQymA>y>_F{S@8We6s5Q*phiG;FWK zAl1s?YT`WXYo)_$B93=Thj#?1Q@YeCczJwQx_%~-9<1pdtQm+UJx=2ir}4qSevR{f zO~cv^rsIUh98GybPlAOhAJ2&mjk(+4o!7o?8d9QDE1E7$-lDt=x^18Y-gHmC-&Wn z7~dkMHEPH!52ddu{WfEuT8Pn{6wO&-3E2ftI&)T7LJn}!0nQ4InJ~y%PC84>xl1|E zQZ4|^xuHZE=T*iv!N3E~;{oT7flAJ!(gf<3*RFcjT>vGiAAb85^o)RF8>k!6u!X#F3&T!?9 zft8%|N&>v)oZp(d7>x7-=l+3fH~yij?mUlVJyP*aNC31OPF0aQx0qoKrNaNT4&W(Xx>1VfBqi_`@Kn+3yW z0<4oY>tqY0t)WDrVppg%>`y{aq%{;D4JJ{fX#|gQ zVc@F95+3Cufd5q@FE=zgcm)TOxS?qT52j$?rpEFn5l6Ac5}s5+5}u6@-fmy>$9Jk~ z3z0zxaSvpTEUY0mv0^kzjHcmY2n}R;EzJ}3egVh;)oZK$X zG(-HmfAc|?e0vR6XYgA@?G}?+5rVCvuoZ4x{_p;7J#gA8HbLOuXB{2CY}!3cJ+?Pe z8j0_w&J38@60OAG)0tgn_Q8Js?f5r-DD4reeZ<-#MFGVZar#9Zen>Ku(}(^)z&Dg@ zjs*$j`h;@dVIYhP4CC5kpiF94MjYUYR&>!OyEc4njG~J%fd#7qrd;iFa+q2&PzNnF z-8O^(YKL6n$S~}Ln*}%2F_E5Dhmu4VB(gd*9>P(_==%YUIv4aEf5}bv9IjSPwdhtY zwnE2(7WQiO$GaztiiSwh0Tnz1aiSDQTuUht?Mg&vWDD7~*l+WX;iu|jh01R? z?|}4Qw(s|MZ~d0{z1q+o)0U5ECp1#1uVE}BO}FPD*uuDMA&l~gjAx?hcn*~K%vgSA zoX|)y`P1@mgxL?jS99}7bbBN=LAhB~*y2&oMst5qbMryaelS_MA*d6CI>8QEhw6b- zozMh<_uCUc&feBzq#8B_C@H{>n2{(M1lYvtHkl5Up=&tmw21Vi0V7M28zspNXU77^ zJenQxr0gd(@v*WJE9+4I@#^%O@Iv`ztZFxjW`$_h0@Wu3sVqokWelXTAdLX0Sa9n9 z0iLH=KP<>G9vtIkG~nC@#~%+0zc=>3?bH9ZPyf%iPtjFlRhR9i!EV|d?OwwkWpqA+ zbeaD99xHY|`DdKkfht9}O0gO0{ravOoii*q)IYh-+Foa!&;oDZ$PYg82cpNReS4K? zSS5O3T5g-D+a|hT;FjogOKgm^-1L=UgLlsDK3)yGol&+kUT97W!FdLpCv@3e7Tjf> z(1?JJ;i+dVjlOYEF6Ppj=@thf$Ez8RW4z;tb--@MX}4(udE$Vs>5C5S8n0$JTJeom znj(#{vRC&L)?=L}IKU@cDX^8Y!j@SlC~$(mp3pEzs^3=!=0nz~v5Tf`L-tTnYj(a1RW)2Y$rBeW1M$ zTrf}$+{;083{-(0RbU7Po&x15aKOMb;QWku%Tm^9DeI5f)t9qiIctT16|7+e>xBTa zP^v=-?j#B3aWUnzm}-uuI8~!NUC*1I^|RW7GJ!)TVMyODdu=ye#jw18`NF+Umw#4U zP&V(9%{NAa02TRyPyDIyXLZauLOUFxeX)uD;i_R?{55}~n(;yk6jB1Q&k90l0g0jobNv=fy;CXF|6thd9N5m8qy4-gD;Hg~ z{w?KyvFZyc7a~@D2VUaMY_&#ghmEZ}L}`a;j)B`^*V|%GG}w-_ zvTJ{S`PSdGaM=xQx(`fFH6VZn+N-QJ8q*C4KpSQ{eh7m=18q3xhDjs|-bsQFw(p!OxTgvp7=YIR&I{hyMl`$za7%E zJlE<;%J4Vg7Mc7CII zeW$4himv+1KqOrvuols%gt=+5uT3Ab3ezzocC-@PRgbFzq)%i3r9r!gT7& zJZ8+6P8RYEH6fcBi_MH1(t8jjO05%#gXkfeCLx-JozmcIUsBwL`PYX%0US;cmv+;D zPa5#UDlL``jAdJ+vZ_9J(zVAJhnZ>%NuWFusK(eCgDlEDi}J;$_M2(%&2#`Z3tuM% ztRspD8e{L7&XvMQp=EWA$;t@Obl@VRXET)X^9I*Y(2XJ+suFoFo|FFpq|G2vP+d z9Of~QEIK8N0qEoyTJoq$&giKX#R|ioq2)8Q4O;t$uBdri{a5BcssVet$8_A?;G9Nt z0?~2~K7B!Zyr7#PRRqBy#^(@m53ZE)C}o;p3a5hgs9^CO<~r7=j%|Ty`eex`nQ&1R zEn7sJ&SJy2Y?f^{6L({_%0XM@&e+L=9K|Y!Sg0(68z!dPuM2#${o_-L{!xdISPH}v zW{4aLt)pMqaJ2HjGn%HwmQkaNoj9V)<`8y-}Gq7rg(mi+C9E<{$IHU|5gyZ z;GJIZ4O==8)bVb0ycae>T_-rLGj00(IISFPoi==qdMKb)8Zb~S zI~U6yXvPjDN@d4V;&$H$+3SPcu!RUEPARQUDebW!HHvSIY2y}3L}|RDOuLR~7NTm= zEfm;7*`UP*EFz=hX;ef|Vjm6mnHCDihYdQQNxCp!E#CR=^KsX!iNG}xc%#xDZ|Byf z`mg2-)R&Bv#_Exg4E{`!`lU!ivAJKC?3X1sY|h?2^*KdvJ9R-fD{U(5a2ikjuRlAYMxHD!BpaDN;pkf z5WwLy<&J?&%082F!@wEJ;tXZLKo(`6MM&gQopY&Pn8b6c&2y?VYLzgztLc`j=?-Y1 zgkS^Rd;{GEn>B8v#f`+3i)dPjrmZlEXxb&3_Qt>_+IbW0g@G8_Dh3`F`JYF9_?FGI z^JYTgC~a|+Hehzf6k1M!eqaCRa2p0mqb<^C0~&N8NT=m=;@W#5?NCU&V`JxP;IJCF zqs0geatA1PfE8w{Ed$yz(@g{@Q3gE9Kx0heK5)Med@=9@C{Ktuf>|+`xELSIdIYnL zkueuWx|kIfvlbW#W!*zrUjnoYWjkOXg%wjw_t#(?DXd2dF=U=#g%iZ~;3+oX6dQyM zs^?jo^Q<${Uux!fp^Udc2Se&X+cLfj243)9FL-}U^Ss~(z2HY+;3e<%l8|`G4|+*R z)bhTyd^0RaE&oF;KNbTaQu7evj#QY`B#dy#v{Y)kl<+IGOlr1_@Wr=M@?9x4!}@%< zBkyan3$fMMVB!h7BdHBFtfJe%bcn@S0tC_rBuv%umSUt%}I>M+2 zP4c-Qbea)@+g$V8Tx%3-q_wu?`-zr|)mV*T(I&>+uw75up6I(+t;VB3ISL%nFbu&d zpqwJWR@QAR+Z<`0Hiw#Uo&J3ks)nr+3{^ype-@zKK!W<&TTt5`;eu@(RMAwpZHYPuqL7$DejkI^a(`cy+*^ zc2qjzPdj>b#Gm#T`s1#2`@8n{!943u7pLPM>E=jtEHO<4Kljt{6t;+H$|9O_z$Qf5 zv}-o)gYJpL^14KOUm`qBA7V6zOouq3L@6Vd5-zvHfmb;2#|GXUR>)y3u%W1e6)RXR zwnnXEHFc(Ao-oq2lDL-ee4H$4k_o@Ym6EnnvcYsym1JE-I697#z2oEtzZEdj1i4Xy z+^}~J!7ioQE~VkR76i$PK3Q?V7QRJ_QlvB-pMw&&6rWqfWH>?7G(pqgfDB3;*0eor zviR*?_wLR*bI&k!kXRyFFOl#8W`$(ALTZHNVTa_fgIKHQNsf6E?$h^y7oz%|E|COm9qSrT;1n-lX@1KKZ+QiYCZD5Y#{SDKQ?*MP57KxE6Me%AZ+P5& z=1M_WY4WG}rd7YZ>M!k$2{B;2Nr(aCO+pM9ZxUj_ zc#{x;Avf==K0S27GPQ{EIaODqClDBQH3DcjYXhIGq0H7$8sxmI9$3JS`d}cEk|QZw z3`9|8QIrPj=A>&j7{?BZ-9h+fO{3T}N{1~T@+dZs(qW5-Hx&DZ(qW5-D4LC;b=W+A z2hHxFb?AyCd`lwDCcVEgnKJClsJ>{lwqGTI&|a+Mq10TwM@ey1qi|c8%}&@A|9~uro}&$ zC5}JmOPHl<8)}I0E1lS z*vp&_4Ym-}a%?T(h<7K??&Ni79s(uy^Xz_JC;#tbZ4eyg*`tJu+X0E921yaw6j6t+07Gz2WY38@G&6?cu&sq!$H4Kp3>L>60BaRoCV*z~y?IAt|{PYd;ND6t6$n}8DBzg!(c zHgeKN&K#Q-?c}7LoH=$4WIreE=giT5Dh!gvNm-mZx{m@u31=wb>`~=*^iLfU7aX!e z)k2A!l*n0Nk(F_+%DDDO6zN)3^biXU5#EE0U;9Fta6A;S_7B(E73x^>oAjF{whsBt z;Wu~e=Uu7oT?zkg%6i3bz0z=${?(gf`uMi+Q-(02s|>ixxS*n}XvV(gEo|zaM95-< z+%QZd<)EfBmIkr31yV||u#F!aL+Nb$@2wSkJX(gUq91wPM;`Yy_>p)2Xu4+~_1)jC zqh{?4SI3WYvi2NtwCAR*y-9#Ovi1%E-pl&;gpoZ>aZEE^kbsevD%w)Q$o^E(J|)0< zjk4Z!Pk5~R<4)%feH&5lai-oG8q)BB^|EtMgzWk!8K#==TWB7Q6>PDJq&$B3OqtA<4?-IC*_8l7w8La-mu8=@3z5^ODO9l zlr1(XjiD@KC_PF%l*pwtxrFDBe9AJP(qn^95oK9KfD+2GgaGA~WjO(!Qfhg0GDPmXSb@)DQ$wKodnsEFi8NrBMo44=O(r3+g>~LS*ymzduUONf7`}G9Y_pv(#D^-K zLzM<=fQNZp^usV_jXFMzlg8n`BXgzpxrD{%z8rAhWbyg)!g2wuNwqKKa1eAC}NYRB@$R7 z;b4UXRuEu~1lAB>y(FxcG*}!vBxQ$WiGe+mu!oS?Cn@^~i9AWkBftYm`#`eB;;4|c z6$Gf3^wp9bCb3ioOJxyR`C#UvWXC9C$8D1=Y?3vYkzuc_?3FDsa6ncL5a6(^945dq zSvf|46S8uG0BN$4Mt}@i$soWPSvf<1Y+1=BK%T7R5#W-nTp~cBtP~R9s;pckzztcs zL4XojDIq|atdtSpfvh|rK&7ly5}-=fR>{^_i9M0EPYCc#);=S^OIiDp0Iy~3YXZEJ zweJY{wkg_e3SJ&biZ+P=dlmg&#SSa61B&ed#R&sP6x$;NNL6f836QUVdTlK6 zfA<@$=e_sLTXS)tSCl%Bu9O;85-Zf5viVM8O@2nPKVw>g|1jXnDW@}oqtv8jF+!H< z>cP?Tvp=kgo?aj8LEvzZIJSEP_#7dQ?QUWHx3JC7h8VI#s%bTTejKHBa>T8apxv$; zRfED;UiX!-J$&WeznW|hP$F0`1Pe}>DJ)p<4;BJ3utYE{AtaUv{!0jn<$`WG@hyer zg70#nVYeQc)zli;%z@3EB{DR@v>U&Pj?$i0x`-f#&L}|Tr+vx`1bC%F2!pWZBngPMi0`+01tuD zK{5u6u8T2X^h1n5kmm8p`;&HUQk#>Zm7xdPlSI~TwLuuaAdd?5%-y?xztN3~v4$a3 zf7U1__|xdE8Usde)fg~(tHyxQ7c~ZqexxyAbQ_HUquXc<7`;Sez~~ej14gIN7%)18 z#(>c&GzN@Lp)p`|3XK7y4`>V+eL!Qt=;0XyMi0*zFgkR`fYDtu0)_*3F2y|gIYzB& z;g{`~LpGJha=a_J$jR_J-hr8KiFt!cD<}04_I$76^RP zXu^?^QJpudmA;X(-AH+2hOnK2?M|X=?Grrq2~AN?f2ITbT(nq9ndpJQ8~5;mjw3p4QSG0{Ddjk2j~(p`X8nTMw!}FszZAAD zrY=sgEl;uCF|bT(vyAwX5cDLIs_{be$`?Ibbs6|#(-u_+tP+$}CLcQgNWWZ|_3^uH z>XTwhTTI!a#?<84s#|*=U)!b*3x_!WLtHb=n17VBJIXm>;25Vn#u+ehoNIQR3nD=G z<6LhHq;lq|#D35z&ixeOwe|rQ^nh!F%>D-+?Y-0^{b_wt^H?E|wMRQv^!F!zOb#2g zUCqQw!D^*xZ0Ubu+V~NvKWtZrl4#%=4SX>(;&#bryVS4`UO27hr^F?zc4(pK2z~@^ z`Wn%6M0?Bz{R8*`_uu$~-4D3IdWQK7d(5ajPng%>{~yM@m=YG7J_rE8ZpwN$<%ZS% zet2q~@UnWC(H%CqMuS1_GUj&)AJXw4ARe^EOox@Mc_rag`Xj6RX!528y3jP=C| zs*ZE7;~HT{0oDqhYlX(xTw$Z&ywT(kF0efH?*Z1LZKWcbp28R03QD;3|+XEBQ?z+yv$rxDABc1b7UD#{_r-gf|4(%r@Q3 zw#1ZG92*eF1|nd+F2C>f7RPt1g_y&;5V!I-e0g(jw%z)2~7S-L7eSRxHwDwL`v$vme+fN0czTR_1(2AS4e%q@)F?bo8 zqluZ#&rLV9z1DJ{@c^Lqw8?!c6uc&DuMwbH)>q4}NYO)yFS7OvArYd{hnT+02qpGt zlszVYS}3SmQcozWCzKCr@35r8WPO<22$fV+pS*r6ALQ**clwN9@Lmd2GD|xOZJl#TvaD}FCJis8(AzTr0vbaqY=cXD*7Mx6qiTUb?~CO`rl4=d zr^N6CJtwX-Is2UR+dsokk6#9FIKZoOvP*=?p-5|oCG3yo5y#Y`xM_1f z335Y)cgsw5pKiA_Ma|y>#`6Kw2=xsJYFN)2!b58~*DRb1LNA`4E59+jc&0wqbG*wr zlUo$u{k{7K6j;D1B0LPkfXgPwmLUTZEug z_A51gkp+Sea?l6jnD8m3=P6|{7NkaLQ=>FEIEO)^G(l0ChLgoUg)Nt#uI_$ZEwp^b zBcExC<+q&mEN7dc?45UZugxP1yZd-EdBT1*6(uoh<{(tSA z>0cB_-oWjdn(peZY8hZ~IEGUJF9h!!O`=btF;9{wYc|>F?q-cgG%<;B*POc?E*eo1 zg#qFbK}paE5kXLds3>3rQPF@05CKIwM2?_FBC^%yQ`1$?Kd^7&3qL(ye$`!FRbA)r zNHlmS8p5c`MBz0!B6il^d`+Xg+~`}*Io@qB`8Jpw&Nqp~c4fhKWg%_z1RPUl9OKkG zij~R5Tm-ZW$}<-@lj%FkvOCI~^d28oZQFaYtm}yOz6Azr0jC}Iy}|W+gEv(d^Iz%u zd^?UW(6X8=OiUKSnWa>s2ogm^H#CSunh4TF#3;LDiXc-&%tX0J1VtjEn@uE1nK-OW z3}f`9szp#OB09zJGUSgdy_a}YYl^wX!MVmEOx0v7W3xGfggg|Or<q*aIfr` z*5Ep1)FHdjhBoolV@dCf|MBr(i0jiB-FZb{RO#5L5b3z~zDWv9QhaHl5wJsnJ2-Gi z!G}0|hvN!9&VdpImvG>sf-iEQL$P$|9vD{JuRWByYmRI0n+)8OIcu8~#VbV#WHQu( zXSQI6#(ZS#nlY38{fo5s-GxGT=^x3wz_8NwvlkX=cfAE&Zb2)v5M3)PYh@<}l*uk- zvX#y{vX&YCp7X-8B5er2i~=v~!5ImtLxbx$OScDT7`bZ9 z_+`dotuKg#E|L0j`Z4g2gbtOz_FFx)36UUfRxhdkwBM#mj9Sj>MRUC#b({k(^@YT5Mj+fpyrtjsvmS9Ls??Y>wl=W^CTffdp($ z;6M^KCvhMJTT-wi205vyENsit53~q4h;0WsP>s#i+&x;bxrGBg*wVw@^gg!S=RhyE z^m3pNTlzQGcl&QE( z2Xvs(4qA2t%We=v*VA-x(*E)QSRMdR`j?8s!y`jK8uNoT_H{`iU7YKvF6q@S&UMsA zW%NdF5GG#2i*?sgv%f9fzje<1AGAS%IE&w@hcYFAxN$$PpFCAAu(eK$vp*KilgxQi zFjclDqWKcYm%Jz#aNaK|boGxvX!D~^#nQ^gnna%_J>IU*&AmzaN&wa1Fn~A8F(ZHJQBw+ zE7>S1Bubjj06Tob4qv2`oZxZz&#RtHJENsdL!;A>BU%^%yYcwl*x@TllfdKCbntoD zqQ+^3D=W0jZ$g$$$eWJ7kHNG}dRVB5*^M53NA?kQC9;EMbUMu~ddrMXWgI9sI+b(a zn#t)Jw=AwVIn{HZ!Q|Ayf$fU>b|sjZ?rvAcY**OeoZFRo+m+WCiFRyl=e9@es;gc9 zYF3sMe3Z4OvO=4|EO1)jMz(0n zy2AIXxyA+(4pwuBfOiSb%uW&U@*l5j*2YgiQCFvQp4B!n4uCNSKsf!#2sj7^9|WNc zxU7U)^6uuGZA8PN(fWxvNn(cq zc75x7`KO}o&G|hSv;s&3L5W~EGbc|5=44J^I$4>Qth~S!K#Jm;!Wk{L;E)#V2sJ=; zp0%vD8$i3kjadT38$rCTl0YP)0Ehx^bib1n#5t4YoGFkNLw5xPllW~w3SpBH1RwUIGf^v%|fPIs3&J=`;FTeTLoDjizWVIZ^Dya#&i zfx{S(1^u$%Cx1fO9fZb0h|7|86YG(oQ>E?8(c^y}8+B#@W;1v>x2S@!qB3B7O@bB~R(b)y_e zy9bhFlkByL3pi?*{p@;_4yhNx<01EmhF;P7rVjz}&>RohrzAkP1n9@y!w%i-99S=pUoX$3 z1w-ypB~PxBXHg{wx~KVg;wxHqi@tPBsEnEXNhZXXoXLiMlL0sBcC!S;8gZ;{CQCrQ z5!Z9zS0nyaHAkC8?IA0v%`&qVjnL^j}9kEr&DuC(fiM7pG= zORm(`lzJ3hDBn=Hr(<6lwiEr?rZ?I#7X~@E)=%a%hYP6%68g*ce>IlB% zDb7iM_r0uIEvE@mK!P-i7TqiJ?ps>k?IwPYNC*WH-WF|ir<#B}BD}+aDhXCeHs%Z} z1%ecPhmC&Q+DKn0f#uj^nmZKky;78p<>Mr-1rxuQl zZq!QPw~+93yMch;Lc%lPw~+7@3|Z~7eA#E8KTg{x2K0%J_atc}q@W0iU56yV=?QQS ztzHwHFnoP}YNJ*XMMkG0ZXQ)^3@qkc_3lz8?&1Q2_TbPS{oL|{wr9ScU?^19+g%{KikR zWY6q$xuIo!sj`&S%~)z%%Iap+DO2h=RgBGe$L;O5%Tl}SM2|U0f>``qtR7Y~A@BkUioM#b8SO0(TJAVvB|lfVuJC21 zvvz5ao$JY4;iy(Pg|=9dpbh%A!4VAThXMWEh!!EcM#!vpqEa-rlyd=e3RzF-7JSs< zj1#F@XAkFKT9)Kt`wZUu3?mo=&kTcmh9QUnIfjrN!&IsWN|GEggd8zUrRP`#!~nM# zFqqEm2`B`4kTr~slW-x-$5NQW~?OeQzF5uqpqK7wY0etKS-mq(HIE(n-s=N8wM~)?1IRGAF->#_06VzMUjXAS zfZ0r5n*nU*EPI)@-gkDVG>}wBE5#cIlJZP~i!REkPN@EORpzTP6ou6yFxzDO~l%ZJ!KCdi}1} zv6EBhURPgaO&e znvI4tAQxG4IdB+x9Y&)Vi2`IRK%opcioB1aFa{JM?;;KqBhO;)v1Q1+jFYH9J{4#* zbC2`L_dFWIfQ!igB4R^1S0mqQPND`4u0a!+B=yL*o|CwaY}dK0-$b^X9B4-V&D>Mk zkgbiAxPxqWIM9WBy11En__;D87`o{@;gwrI>2atscL z;UqTT!5c99wwrLkCQjlD?DGX?%QPN)$78ml60uJrC$R&2?Z9kh@5FvPG5aoivEN<} z?8m|T@f0TOIXEZ>PiDXoJoE^j%78Q2?F<)Uy9&Ek;StP6b1n9)#jIDcX6)I_fi~>j zhS~7#UD&G&J3M2Mw*E7={j3M^BH$jj-Q&OmY4#1 za3Egwh*w84?~<%~By%7|^+@4By6Tp$vLVO!svdhei44^}LuCsnPxZ*-B#x@iN7W(B zV~?wz$5plnPpY0LIZ&$ll&X$xQBpvsRj<=}DAe#_13_B-&WBpBzeHKW`o~=2w`3$U zHLO*_Yq>4xI2;zI?>K+3`1DtSMPYqfx8@35UFnqM$u04h2C;%We^Ic4E10AArh3xF*g=mD^Y1KUh^ z8|ThymkICE!JpKZ7T3;9dZd+YEM5|e-=j?+al;(@FKf2;Yk#?51Q+xvN!*NU;}%}X z@7IQ?Z=uV#(8_Fn9fitK=)`~w*(F1^Qti!KH{QwH9d)i>tFy(*Vk^@M7W*$A#r&*M zX4EM2m&*2H=DLx&zR6tw#9Y@h z*Y(VG9dmt)xo*<0g=S%P_=ZIT{I0&})FQMBH-%e7Yq&{h6s`+5gnFSts1s_1YXW&( ztDb5=3v=DTTwl|#7rp()TLSr6NL1+y1f#+1?BZ&1_we+xdHeYK`3J-jk8_)a2w}xr zZ!ddu`O5!!`kC2tX3d@V!gK%o{Ijdx|HnFEEwPo05#D>}-G#3$_|u3#zy8<1L_hl{=`^Eo&3!N<A@4zY0g zY}Jx+Qb0&-Y|9}1w6OaFJAEGz%X$6$=M!VD702d#+->`ILsem(b69|@-0*0{ z%scf}6?e`(T9@^uWlTe;|HQj9e`?h_clOJ^2R$mcli+)Icqg08!y#364Fj$%&uFqCCfU#5Fs8D3&5YkaeQq^z*V(a0Ke#d>U}}%2 zo1Shc-rw}lZ1L4aZbI9a&)43cE){+?v*P?82AfVTE0=Amq`$V@I`&)-d#yR)nJpL$n6Xs6AgU)-bS_iKCN#oWxzrwuhBZ-Tz{>7{cD+Ef#v z8~^zlkIi%A9E+?S(r@?%eE#yc%h}~G)1T|pv#M{L$j$uA!oxRo|J2S~pFTJj7c+d% zDaV&K>AhdK4EMgZwV8h7jhxnxw%*+{ICcAem%HKnw|(_w$-WCk()`rEi`@6M>9yC% zee2*}Z5H)vQ`|Sx>q=Pp<=3UZUdZs8p7X5Xx7|&0k~?*|o>6Om#eUw68?9|REjw}f z#`x^M-Jjfge0|vbvNa!MxBo`J%*`z$>#(jU@w*jsTjY)Gn3?!VNWGZV`c^e}4ThDi;Z<~nwgBmnb zrcD~n*=aNbew}^Wwyu{(;{2Q_2+%)y0pk}iCyh@1`6zJvTOX_WT1!rF(#X@Sr@0#o z$iC52-Fcg0t;kIyc@N|FFlSQ*leew^o_6c)pIV8F#t_RGzl^!UV91fLcG}O{-BjXy zVX&C-i*M{PyY8!EjcTChDp|wJdFk9h=~0ugXT8V zNn9{`uVef=)(i%QDy>i*U1cM3BdGsLCZ1%jrmE58T=o53;a`(UKAG%d!Y<}*s$jA( z_>(%9b!+V;ZXAXQ>!s~>BF!66XvJq5+FOc zI(S1K)Z>6#NYxj9A~%oTzFfofHM9(B1snHWVQI+;KCMJ9kxXh?NGJ_rhQK zV=n?MG!A4k&~~7!v4DKB`2BBIjOh?4ap8D~4UFGlp5^6fGfMOIyV}`mEIc&wAl)Fn zvC#0e9o<_q*&^TJ$-d`H2DcZv12obqHE*d%AF5T5wpuhE4?wc+z%0neC9Z$HD@hh1-3>>^pe)8K-|B^u* z4AwJ#y?F`~-n86V6VW7G;-X-%obk(<8$5J8oHzJ^Lsq!)Mag}&eeH||m+hBn`z`6n;bi10eRrDuaA{(dleyac{Kkt`?O}p0&38^T!8Gz*^<1Kd2D5^O%+TMk5|9xlyFlPx#47Tf{7=XhpB=|!AIhQ@t=*4 z5;b#ZUa~=Na5EOr?`6mnU9wVpN?Z%9fgA7U0GUf{UpR5|)jlG(PtnD!tYN}wf zbIQ?bDZ(b+k<*h&fA{`vjRoX0?B%Pm`oaW>^T7y<7{AE;8ZI!y^MbKn?@3%33EWiEUseYoS4^3g1-7r^U0XZkW{eX+DV+M-cU>d)HSvN3uQw5W4amU+_ zE3p_Xag#7a5#x)@L(GZ^oA~mP?_g!Kx@zPCeSx#FfP<1t9e#=S&;LN=2GJPjnDsdt zL#<%)YQy%w^VauD5;+5z9A?5{*34AFB)!hL?1Q0yC0lYlnT)j_+uT?{uK4w@Q~D!z z28j#8Q?6zFTGkjq`z5_@wRwMqLF9%||2o#8j>)D9K5i$+L|TrF8!d4m@Db}0>j$|w zrspS)$sQ>p*PJ};V8RaOW~yMqN}n~|KH^fU$c-eE-Avd`;YF=r5)qy3b$Ryr%8^5?8$pJ9_YCv*A@6r zkINLfQ8d6IW_O5Qm0H1rep0Z-!P?qPiJJ-!s~NxAd{)UHMohh){Y9pznXP>N$qvcx z#sUu79E)lfAC#6Wal_!C4d2EF^4q54R_MOkl`C=s=n+n{(9~(&b%gs>j9+CwD3))lS<+DXaE8RCz(D5Z#;oSf$$yVJxMa)}x%M>vc4oVsIhiUv z!^-n-imnc?nk91YlgSy@;S7D4Y6X+-6~gU3UI%7NTpJ9sgz-zvAt<`=c|*5#!{&&Z zS@f!g+Yk3J7U-Wpf7_$ZpTx}-xzRL4H5007zo`{WIvs3h89C#ZLOL+%Jj%Aqb~P4| z9e-cXrmp&U z#W==z8VmGazrZ%$(~wjya$`t7$oPZI-c-TFCUe?B_jPAhNL(6*2IcO zmVBfR5N#XnA}a-YzwWZL%dQu{lAMFyRgAAU+f#p6{^yI^$n+b{Z=qyw&g&*WH>(o4f#jo} zS=Td9Qw1M={Zp!wR?1}3k4(<9X6Koose(znBPBO>J^W_9sF_LcYLv|=J7WQP|1Wzk z_NsZjUgQeM!#*bLqe0XPCX0sN=#adjev8B zhTd4fL7&)m??!)a*eP*6;h>c9rRMOqu(w5SSC8_&5?9!`VOsDlJmA2vVsu&ZHp@fw zK~M%Z&TgEmv4A|pt4B|--PevtTnG4{$M|_>|1Hw(`#llLPgrW~X_6mUf1o!O=+F5N z{2_Vfy^|7`js8(QrIMz1+1D?)VPW)$Gs^Vnd(5@T)f)@w-u}}M^qZ7%##X~o;1ac> z&RCcO*Sm?&JhCT_IV*C>H2i+X?`ICC3O>F&Q(9QNc2m8?dE*flGJc_Xjh9QBoV(5^ z&r4i93?lf5#thf(agXTu6Q@Y-4Y>>7#TM=_4|jjqwPfX|BKHA3!BHk0Wj>|~CIgNP zo7MSJ#b>sfrj*7H)(w`7g*lDi{L!Z(8TQDY{|LexFAMfiNgxe2F>1+}vRM&yLnr z*Bm)5%_7x3wT-br|Al+(f;QgYdCfv&Lvc^A7MzTQIqo^E^UrPjYVS3P8;b!#`OwCw z^S)Echfk{yTvu{O+QyS@ChLs_{15H>(&0hI^IIa@-c zi3`98OBuh^JcSpd`)Xr?gYHONDhwj|$i|!T;O_UYPRQ$WpEf$>I{9|_uEqlHKm6js zi-RdM9!p#+^sZ!lrFjWw_uAq=v-j#J64w_7&b)JD9ITbO$pDVZ4ce;M@WM;jE101I+?u$hLLN3&}G`2(vQr4;X@f zkYkm5Kmhk3LFF9~z&l7#I0u@+Ip{$Y$bn{oZs4}H%o~?;@DA#&at;`RbI=p2kfTvB z(G3z*v;hH3gIp;r1I;1b>aD(=mgK+-vZuGFKN3zs?&sLg4GFLy_jl~?hD)3vr#hy( z;foREQr5YY$%w=PokiNS%nHYpK}3@5$H~&Jx9My*Cm1OVcG^}g)7>}gHo?I z3@|uY3UYWzco~K{Ti86MvS9F%srQ$&N@Nv7}`xS!*dO{>7al(xou!))#(gbTDCTdpr5$i3K}ARqfRJbeg&(0A=!lvXZiFiiwi-1nya-PZRKz$VA%YVS zBC=NT5Cm``5>zMzA?i(7!O!J4dpOfK;Ly?C{0)RSgt#M`338}os0R)`K@PJIb3=fk z@76xt&HUX)IY%`|8YIZWbi*XT8bKbX8z&(H6XbMVx`gEx{`Fx%n$w$XpgOFJwA`Ot@?j^8{8>;)By-=4p-(#mu>wH8+2iOPI?N=7sy0ATMQ3 zOPME*TqT}3l`v0pJXyxvmN6gmSG=6LFK51Z!%9eWEoI*3kXXiC%a}Lf4x$*Xy#m{i zLV~l=22uDLUdUhH#;;CH`81kh!ZFtT81n;gLrrc}=!PB$)b!OTs1416JW4;x2T3~? zhelIb8_dAkNKhFY1iUsHPXTOrYy@s2zVLDa~RO41-8Zc`71qTx`+i9pRNHiJE| z83`&ggWwmLrh6ugP1XvuyB)VOcWifo47G=uHF^q!6SPN|HAsU%8LIXuvqtiYuZUJm zL2mFb5XrDTO)uI6IY8fNMk;lKArc3ALKV3|KwzXE3S&bv7#rEEfDOX0BO|X~$f`@F zmwku@9%4a2JV0*LLj`T<0nkQ*O4%Ub%cY?etcGS_HL_Qk8U(;JiXaL}Lo*;6*((4I zFAMC9niXz_W@HT1tne~8!TCndq9QT~KxAaEf-wjHW7IZLI#8frFpB&4EBiGG`7mhARt|% z9ttSKn*bxDW|fS=9!Cuc3Kqk{frwGFLc`!@!K?ZQg9f}T6a-mlCU}__EYdZy1#V)3 zJkVpHFA@nsu3(N8%pHhIkSm#6CG*D3OQ5afx|+4X-V!J@Xm>Dc#0r6~Q`()(8aFM0 z5`670W^FKK`~n3A?QUj`NFfkw(e7i`z%T+$SX<4ku>zR5&Pru?DoxV}vV*mQ3lc@) z|I$Z6j<<+!ygCT~m#)w+m%e_;Wdu1vn_!2KLce?xg7FK5Y5N`={e26 zbo(++3B}q&OhPnJetRBbZScFFO>K+*H}@>35o?&XhS?#V223Q!DoKQEBuDfp3OYnH zz7VokkRh6pJd?GG3LzkWAwfli5ODV*M=BqLfIORes6Y?`ZfDd(rGXHD0ZC99AOz$Q zBq-z$Zw0%JnpM;X`%9TmMr{b2;d+C_f>VC%dNTc@634aCHrS;U$E{-Q@$m>0$FAEEw~_15p@OxS}ZvQy31H!}}mm2y(J6 z*&fLS9m~4u_BfX5m)rD~=3k3h)MA!kzU!2*HYF?+(^6PSyE4`pcM;s-Xu1lp#H;`; zY1I^1iO&q2lA2XI342_X$c;)RA>b?|L7|ao##N3SDJT-@;|jkk)-8*_`;}sD|KR>z zagh<^9DR-_F2p!6$)$=Y;S`9H1O-i^83!hfr2{f=CDek<>$>koXXQAE{YE zj|duBV=XI`0C|HPChdz^i;W>@-2tL{3dp8MbMboY#ig)6UxRt$P__Gv(a>%~EB zE?u46CH?xK#~C|5yIf9rJ1qCq<;Dq9-VAn~8k2Y7X?VcPkY?3!g;{aAr8%d@TkPmC zHZ$e5cj>U?fC96c!6|Hgmx=yKe&>g#92~#)`k#02+wO}gvQ4U7^JnsbZciR{Juvf0 z!hx5KoYN9hzRf%O?uCsUv%~Dbuvsq-mt0JH=Xz+ty=DEk{WZB)>bJRj;-VZ|#icg? z`0!}ztnwdkmITh(IV0aq6U;Sf&o}w`^W%StqZjr`?B!8%b7)M(mOJLvKkv&6IOO?X zF6`IY=2s@Ze9-#rdoQb%{{(*j=$Pl|F3sx>X+eK=sn%85t}O_kEADOAU*i5(r^N@& z0JD(4J<8^ei002%6-;W{H(a`XBj;Y(Xfq+H^wFp|Zj8-<_y;$77xp=sd$d6h<}^E8 ztYL4frd!{7s4Ym;r*v*p_f-7Dz~VK2m(3#o?stb@_^E zP0xG%67_QOpO;g@TCKnQ*OLb)PJBGyFl^h7-fgyNBR(H{^Yqx8#@4??pK*EjPx-Hp z-#woc-J{*=vxl6@YnFf8ZQZ?X4!4V*t+`_DaBKKTjWELPu1jv=n_d~&x~TT|KDO?$ zIOS_$=r5DM&aCL^mU6yTzV*t9+0V>>i<)|DQtF#Q+oOvl|A7&cvZPB3TeXfdpWoQp z(r@|BnD~W%?74WS!5_h|_1!X(pC`mGpW5Cs^6ayOZW|{}95c^uQN^KIf9ujaeT+?y z*xRv%&(R^*7i?ZMrLuqN4>f~xemSsoQu?sWxPbgXpXAQJkB+N1qUu+_D({{#K0W)T z%rYz8y`&^yPDGy>W;ZMH#!VUIvBB)v&FUFuE9S0nIdiUC>fs4@W}oX;p3IGSH1yED z=4HR1S@ZMih2!ts$emvv>9_pSItSs%UrT4Yl@~|&FR}BvzR#y%85g@{ZjAX1i=3v# zYs~(6(|fb{nx}y^*YJ_|@;K7X9OCd1}R7mX19)7 z9zMMx{Kpwn22Xl?y3Ma0rnN0-cfs*u+ob_n$4-20+p=D7dymZ>rul8miOw&-|LI8G zMeW{=y1a1f&}xU5{aETt^Uhy9zYh6g)^Bj#=&=(<4W2J)G(8^Y-nBg%p^-RWCpz@> zPtIk0E_2dYsXw3jum4itdWP156Pz@1hAzY1P(bYyInJH8Db|WyHmQGP{Ey7pSi$5= zqrWHI`SM09agpevi19_t6$X8(&Tq7zT45=1J}}5*d>(UzLAcZ|<;h3IU>FR7_#k_z z6Vr+-KTrJC(t@+5F$~oW^)wVvhlOoR?mfMZmBa<2^&-YEVs&7Uv`i~hMlQ1vxdduo z#l$M+YOHEaPE2Y+QHSq8|vQex4Q)`JL4;I%9odSGyQH_0=0sT z`=*fLiCybA6uDkxQq6*@Sw~|9lbS#CAM@33o0w}H$RyS_*40ozeYQC6O4*R+{t_38 zK`deX64Q6SIAL<>ME#}!TaCGgM(%3WRc|OXJZ(kyMoc!J=yr3pBA3scHS%C_ zu)U!`4}mK;=1)tV)ZA8MK@PfEb(0K*$w9*XzB_J&cMTG`&Mq2xnB6c}LxJ|Xq1=_$ zZ|4U~TpC6=hw*cmI}9AW4($Eow|~eW1_p~6zt}W|5uX}tJQ!vcDsdfPFq82!nHxN` z*fl=>i9<%H;X}!h+DJP?0rl-A9iKU0oYzw1Ce_i%={D(hh63uI)t9SF*L?^Rxh$H0 zl6_JgLjiTYA-NWp_c@0_YHx$r&zOQUJZ&(xAk+UWB3g)qbH8NH(>9w!& zQ_HTmWs&=qOm;DG7xOSyFv(dcK1n^5+CkJzans2C_5Ixp1=J5l%V{ndDIF!Q9+tq3 zcXNQ6>(wkjt^8_dkta5si zJM3&Z)cYZ(JH*_L6-?H}>}xh`j(NPqjYJ1i_$jp=OpOXjd;iQQUfHLv8aYd! ze9ndDk0Sjh_7l0@)I$x^)ld&=1(Oe}*8e?zWki3GOCpmkOxVKe7%P~hA9bGB zukX7=3yvp~LApV84F%MTAHS>7SJ@>=+&38GLdGv-wQ;k1|BnsJw=GT*xjxi>KWo09 z$;Jvk?j}ZsTMUdDEO9~b(axovFVw|R9rrpWdL)ZnUGlJ+39Ff#v4ROJePy{m;X;bY z4J4DbOjt{iMXg{G7MbXEvEn9~j3JXsCRQ>>V+E6}{s*I<9+*GmUpGJtzD4a!dKFLm z*>U*pA~rG8-beqjE_3LXk`N|K|_4je9VsbmsmALV8!pmusXH{1E#h~XkPl|ERe zL#B(Nfcnta5bs?N#-@r~p7KGnZL;--0{Vz1Z&SAi-yGq{*^&27?wtY*1=JQXT`rA} z{XR|4b;B~2FsX#qhVQeyeKVVKyTjKpQa z!+gfiH{DS3$gpwOGtZ0>HIwPs^mFLvZYa>cadn4)*l&hsOWXjoZ_GEgf%=Q(urjOj zo3cf&2aRAa3*O5jjTKB5Oen1SdvW$SiKBrV{>jUj%QEHzgDH)U`!S z*h;fdE0`Q?J9I(zg?8g5Za922<(t-?Oy5eee#84;##?YY@{p!Wb2k)dow4Lm!IE;z z3AP$Z)W|kk8!JO$xIk?D=mwhJ$2aAh+aVJrt_9rBW&B*z4RPsEW$v)P6ADuh3f=Ro@!rjeY+jAwZF}f*Wd;wj0#GG1}#dv!|0sYCN&m1q7R}@)tj+8U%E%f$=!mv^r z4XxC}`#X+DIo+Bmabq!s1&m+7JmH~y-lb1V{6EYTHB+gBUJkun3l2W;u=6*%J@=Ndk<}E zGl; zZTi|73aGa|+I%+R;PaItH-S8?XTo~wM6F;lv+vF3iK~vUmAL+x>;!&7?RFnwx&QU` zljk-l@s|>}RGU=2p+NJ_(E(ov|D3c@;yR-FEXL0=#jc`G=GkpL=5LX>X|>y>9$(J` z4wA|S&r4iqv6D^%C02=ciLQnM>ONlWJ9<@ItCG0p@Snry)V?6?_-UqyF;~)L{o;J!Cy9%NK^Pxa zo0qyhZyy=EzlPMEpl;2#wuSqP-?~3-GiSknMXn!>U>g&*vHHdeCOxVKOl@^x@hMx4 zB_-e8t-4Ew!jycMFMQRZgU8H^689at@Zo)GUpP<0pZZSj`qxEUjTxStbVxhnz_b?BATERoD792qB*Sa%WnE%%QcBhK?lKnaBV<|b4nihvU2-% zCF!F*I^1Ts-cZ1Q-!AVRo{WBdN8|#i`;Cm>NSU=-!Nh;io2rAG((g)K6Z9~H@iRWJ~JAJR6ZG-RyZSXV=V_9M?cdAlQd@^gu6i1zasKhN~k&W%{>H>K0k7ZTS6 z2F|>5?H#>ZYT-X7N%KnNM$iz~FkuZ1QLWI0#&XWV=NI!nO57-Tn9cavtoAMGU2)pO zs%`I2v~_43bg}AUXDCeDV2=DtP{O6^ugYnlvzBa=tTz<3J!A|SI`$(WNU#aU8<_;| z954ya+Q1?p7ASeO-Os#`*MdzjYa@|>A&3MyR!IaDAQ7Zc2?P`%5TsDZ1H>Q?G!O+l zKrGO^*tVK^&N>Z>GQH~B#4_rDrTM2TD;1)98 z4uTwK6{knyB*;ltNqW4e>6d#_1Jkcu)-soMzy=iLeAXnN1>;5*Xxn^K#9Cn=333VZ zDPaLf^EWIy)8c7$i8G@$Zk-T?H3s3K0V`w&!v)zPu|jkp2GJq0LUJGm$sw^qa3BW3 zp$(&A8z`^`Nue?uC_ZR|KAw*1+mOCcJL`5%c5OtW- z5Ci3qSm8K$d0>-~Sm8GqJ@Buj!k%xai~ z1s3E{R-+_5D+GD0)mRDll|T{PIhWNnov3NdWg7EB)*{H1#ZPCRAUjGdaLQwzrdUA0 zFQ3&loxlR-Qoy|MP7-7SUNe{{E>IBP=t?lYMWzNr%aTxT(F zQ}`=puEoq7F$NKfR$Jj+$oGJ{XiF%F3!mWKjo78VlK&e?=YJ=wyOa5Xd7&mZD)&MQ z1Z6rW3hjbeko)TU)<<@Y#i8C*+yyh>E>b9%3u1gU>P=x>7&JmOiB+}*dq6F6qrfbP z5ueFk0a>_w5&221k}KGQT#-V>R#5nc54UIRX7C&;1sn` zpcKT&LrAQ^D2VfSRo$p*vSGBjAcqBqwZ+SXuAY#baMRUO$+VSBz={bJ=d`<+fWRkE z%A~Dg0&=L@rMEV6E%XMiMT1az78KxFG#dqG;e3E&k-fsNV2xcx)+)S$0(TwRtF#IV zY+q`jGAby*sHla)rXa=*LEBmZQxFTphHUpSFJL1uDl$_56`n1EoavY8kM9poMO~b=rU@;P&|pL~K^0%Y9$}e! zSFsfo$iAqB!mP05z*QtxAr79&?+=yzzDtXqULoXZ5gc1UmED)l7#VAkc$IyN2oTRuU*f z*OoI~k}+i$=)`N+G95yIK%_&vp6LKV1e&6DGt*(tn5vbv3eZ%Rr4eKYor4RqJ>mc5 zfPx%h9#Nb73;!?cpkFSLzDP*~IZ7L4hgdPH3Q7jiiINGh1aNRXG&>MB$Ys|z4U zVui**3=l_d6#fP$4+72KSlT)l8}2}doy-$=AJ(1hRUn5J*nH$)p>YuJ``mGYU+Mbq zIVHwwE0~0XuKf0_V2$y+yUpQx|GB++F7>#ZX?HU_^2gspbhpH#=J;aUoeO6F*(dTEC8{0V(Gku2_=jkn{tw0QUH=vU^yD7g5%%> zIF7t4C=NL_vL~`u=^N}p-$ zfq>e`rGnWIFhJNytl%|725>bJt56M2k(bgCREh=#){7JhJ%bqZjKm5xgBS@5`B#w{ z6kuhfP(T@C)AxbN_uCvuKdS_1ZMY3KA_Zq_*&gRZpx~@s#cUACZ?ri!vTf3s^K_f; zWc*G>|ASurKdeeV#P4MyZY~8&@YPJjlTf)>_tK!4oWv@og95jUxwQe$L9EQicQ0%4 z@7efNv*!PvjrTql`0v?x?`MIS4TeK;R$+U_FiUYZR1WQoNFd1ltoqp_F`z5eD$^cU zD*bZHY+(A8$2#P(uBI1CA!}U7f-x_O07IN4utE~6 zgb?<4;E)@Y3_^jI7PU~YATqo%-%Gj%#kU?)^o$9NX@hr$AgAloJ@G=t)kjWMTnMMY zg``k0A;h@)s4o=`LIF5P_9_&F0#J}zC`1tZ9Vn2*3I;>~&}0lrOHw}2*o@?{txSLZ EAD?Yjj{pDw diff --git a/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx b/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx index 40ef825e235c95107c52b063019ea3c5b51d7873..4344402b4707ba6263540139dd0c01504f192285 100644 GIT binary patch delta 21 dcmdmEx5sY7Us;asm6pH4J_<^1W|cE%1ORV!2(17B delta 21 dcmdmEx5sY7Us;Y*$~-0oS&uw7v&xw>0sv+{2nPTF diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 339fd49..b3ca56b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -12,6 +12,7 @@ idf_component_register(SRCS "HistoryStore.cpp" "KnxWorker.cpp" "Nvs.cpp" "main.c "widgets/PowerLinkWidget.cpp" "widgets/ChartWidget.cpp" "widgets/ClockWidget.cpp" + "widgets/RoomCardWidget.cpp" "webserver/WebServer.cpp" "webserver/StaticFileHandlers.cpp" "webserver/ConfigHandlers.cpp" diff --git a/main/WidgetConfig.cpp b/main/WidgetConfig.cpp index bd2ac4a..173bc58 100644 --- a/main/WidgetConfig.cpp +++ b/main/WidgetConfig.cpp @@ -66,6 +66,71 @@ void WidgetConfig::serialize(uint8_t* buf) const { buf[pos++] = chartSeriesColor[i].g; buf[pos++] = chartSeriesColor[i].b; } + + // Secondary KNX address (left value) + buf[pos++] = knxAddress2 & 0xFF; + buf[pos++] = (knxAddress2 >> 8) & 0xFF; + buf[pos++] = static_cast(textSource2); + memcpy(&buf[pos], text2, MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; + + // Tertiary KNX address (right value) + buf[pos++] = knxAddress3 & 0xFF; + buf[pos++] = (knxAddress3 >> 8) & 0xFF; + buf[pos++] = static_cast(textSource3); + memcpy(&buf[pos], text3, MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; + + // Conditions + buf[pos++] = conditionCount; + for (size_t i = 0; i < MAX_CONDITIONS; ++i) { + const StyleCondition& cond = conditions[i]; + // threshold (4 bytes as float) + memcpy(&buf[pos], &cond.threshold, sizeof(float)); pos += sizeof(float); + buf[pos++] = static_cast(cond.op); + buf[pos++] = static_cast(cond.source); + buf[pos++] = cond.priority; + // ConditionStyle + buf[pos++] = cond.style.iconCodepoint & 0xFF; + buf[pos++] = (cond.style.iconCodepoint >> 8) & 0xFF; + buf[pos++] = (cond.style.iconCodepoint >> 16) & 0xFF; + buf[pos++] = (cond.style.iconCodepoint >> 24) & 0xFF; + buf[pos++] = cond.style.textColor.r; + buf[pos++] = cond.style.textColor.g; + buf[pos++] = cond.style.textColor.b; + buf[pos++] = cond.style.bgColor.r; + buf[pos++] = cond.style.bgColor.g; + buf[pos++] = cond.style.bgColor.b; + buf[pos++] = cond.style.bgOpacity; + buf[pos++] = cond.style.flags; + buf[pos++] = cond.enabled ? 1 : 0; + } + + // RoomCard sub-buttons + buf[pos++] = subButtonCount; + buf[pos++] = subButtonSize; + buf[pos++] = subButtonDistance; + for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { + const SubButtonConfig& sb = subButtons[i]; + buf[pos++] = sb.iconCodepoint & 0xFF; + buf[pos++] = (sb.iconCodepoint >> 8) & 0xFF; + buf[pos++] = (sb.iconCodepoint >> 16) & 0xFF; + buf[pos++] = (sb.iconCodepoint >> 24) & 0xFF; + buf[pos++] = sb.knxAddrRead & 0xFF; + buf[pos++] = (sb.knxAddrRead >> 8) & 0xFF; + buf[pos++] = sb.knxAddrWrite & 0xFF; + buf[pos++] = (sb.knxAddrWrite >> 8) & 0xFF; + buf[pos++] = sb.colorOn.r; + buf[pos++] = sb.colorOn.g; + buf[pos++] = sb.colorOn.b; + buf[pos++] = sb.colorOff.r; + buf[pos++] = sb.colorOff.g; + buf[pos++] = sb.colorOff.b; + buf[pos++] = static_cast(sb.position); + buf[pos++] = static_cast(sb.action); + buf[pos++] = sb.targetScreen; + buf[pos++] = sb.enabled ? 1 : 0; + buf[pos++] = 0; // padding + buf[pos++] = 0; // padding + } } void WidgetConfig::deserialize(const uint8_t* buf) { @@ -130,6 +195,100 @@ void WidgetConfig::deserialize(const uint8_t* buf) { chartSeriesColor[i].g = buf[pos++]; chartSeriesColor[i].b = buf[pos++]; } + + // Secondary KNX address (left value) - check bounds for backward compatibility + if (pos + 19 <= SERIALIZED_SIZE) { + knxAddress2 = buf[pos] | (buf[pos + 1] << 8); pos += 2; + textSource2 = static_cast(buf[pos++]); + memcpy(text2, &buf[pos], MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; + text2[MAX_FORMAT_LEN - 1] = '\0'; + } else { + knxAddress2 = 0; + textSource2 = TextSource::STATIC; + text2[0] = '\0'; + } + + // Tertiary KNX address (right value) + if (pos + 19 <= SERIALIZED_SIZE) { + knxAddress3 = buf[pos] | (buf[pos + 1] << 8); pos += 2; + textSource3 = static_cast(buf[pos++]); + memcpy(text3, &buf[pos], MAX_FORMAT_LEN); pos += MAX_FORMAT_LEN; + text3[MAX_FORMAT_LEN - 1] = '\0'; + } else { + knxAddress3 = 0; + textSource3 = TextSource::STATIC; + text3[0] = '\0'; + } + + // Conditions + if (pos + 1 <= SERIALIZED_SIZE) { + conditionCount = buf[pos++]; + if (conditionCount > MAX_CONDITIONS) conditionCount = MAX_CONDITIONS; + } else { + conditionCount = 0; + } + + for (size_t i = 0; i < MAX_CONDITIONS; ++i) { + StyleCondition& cond = conditions[i]; + if (pos + 20 <= SERIALIZED_SIZE) { + memcpy(&cond.threshold, &buf[pos], sizeof(float)); pos += sizeof(float); + cond.op = static_cast(buf[pos++]); + cond.source = static_cast(buf[pos++]); + cond.priority = buf[pos++]; + cond.style.iconCodepoint = buf[pos] | (buf[pos + 1] << 8) | + (buf[pos + 2] << 16) | (buf[pos + 3] << 24); + pos += 4; + cond.style.textColor.r = buf[pos++]; + cond.style.textColor.g = buf[pos++]; + cond.style.textColor.b = buf[pos++]; + cond.style.bgColor.r = buf[pos++]; + cond.style.bgColor.g = buf[pos++]; + cond.style.bgColor.b = buf[pos++]; + cond.style.bgOpacity = buf[pos++]; + cond.style.flags = buf[pos++]; + cond.enabled = buf[pos++] != 0; + } else { + cond = StyleCondition{}; + } + } + + // RoomCard sub-buttons + if (pos + 3 <= SERIALIZED_SIZE) { + subButtonCount = buf[pos++]; + if (subButtonCount > MAX_SUBBUTTONS) subButtonCount = MAX_SUBBUTTONS; + subButtonSize = buf[pos++]; + if (subButtonSize == 0) subButtonSize = 40; // Default + subButtonDistance = buf[pos++]; + if (subButtonDistance == 0) subButtonDistance = 80; // Default 80px + } else { + subButtonCount = 0; + subButtonSize = 40; + subButtonDistance = 80; + } + + for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { + SubButtonConfig& sb = subButtons[i]; + if (pos + 20 <= SERIALIZED_SIZE) { + sb.iconCodepoint = buf[pos] | (buf[pos + 1] << 8) | + (buf[pos + 2] << 16) | (buf[pos + 3] << 24); + pos += 4; + sb.knxAddrRead = buf[pos] | (buf[pos + 1] << 8); pos += 2; + sb.knxAddrWrite = buf[pos] | (buf[pos + 1] << 8); pos += 2; + sb.colorOn.r = buf[pos++]; + sb.colorOn.g = buf[pos++]; + sb.colorOn.b = buf[pos++]; + sb.colorOff.r = buf[pos++]; + sb.colorOff.g = buf[pos++]; + sb.colorOff.b = buf[pos++]; + sb.position = static_cast(buf[pos++]); + sb.action = static_cast(buf[pos++]); + sb.targetScreen = buf[pos++]; + sb.enabled = buf[pos++] != 0; + pos += 2; // padding + } else { + sb = SubButtonConfig{}; + } + } } WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) { @@ -157,6 +316,19 @@ WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const c cfg.iconPosition = 0; cfg.iconSize = 1; cfg.iconGap = 8; + // Secondary/tertiary address defaults + cfg.knxAddress2 = 0; + cfg.textSource2 = TextSource::STATIC; + cfg.text2[0] = '\0'; + cfg.knxAddress3 = 0; + cfg.textSource3 = TextSource::STATIC; + cfg.text3[0] = '\0'; + // Conditions + cfg.conditionCount = 0; + // Sub-buttons + cfg.subButtonCount = 0; + cfg.subButtonSize = 40; + cfg.subButtonDistance = 80; return cfg; } @@ -203,6 +375,19 @@ WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y, cfg.iconPosition = 0; cfg.iconSize = 1; cfg.iconGap = 8; + // Secondary/tertiary address defaults + cfg.knxAddress2 = 0; + cfg.textSource2 = TextSource::STATIC; + cfg.text2[0] = '\0'; + cfg.knxAddress3 = 0; + cfg.textSource3 = TextSource::STATIC; + cfg.text3[0] = '\0'; + // Conditions + cfg.conditionCount = 0; + // Sub-buttons + cfg.subButtonCount = 0; + cfg.subButtonSize = 40; + cfg.subButtonDistance = 80; return cfg; } @@ -211,6 +396,8 @@ void ScreenConfig::clear(uint8_t newId, const char* newName) { id = newId; mode = ScreenMode::FULLSCREEN; backgroundColor = {26, 26, 46}; // Dark blue background + bgImagePath[0] = '\0'; // No background image + bgImageMode = BgImageMode::STRETCH; widgetCount = 0; memset(widgets, 0, sizeof(widgets)); memset(name, 0, sizeof(name)); diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index 6c0c7ee..0bf58b4 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -9,7 +9,11 @@ static constexpr size_t MAX_WIDGETS = 64; static constexpr size_t MAX_SCREENS = 8; static constexpr size_t MAX_TEXT_LEN = 32; static constexpr size_t MAX_SCREEN_NAME_LEN = 24; +static constexpr size_t MAX_BG_IMAGE_PATH_LEN = 48; static constexpr size_t CHART_MAX_SERIES = 3; +static constexpr size_t MAX_CONDITIONS = 3; +static constexpr size_t MAX_FORMAT_LEN = 16; // Shorter format strings for left/right values +static constexpr size_t MAX_SUBBUTTONS = 6; // Sub-buttons for RoomCard enum class WidgetType : uint8_t { LABEL = 0, @@ -23,6 +27,7 @@ enum class WidgetType : uint8_t { POWERLINK = 8, CHART = 9, CLOCK = 10, + ROOMCARD = 11, }; enum class IconPosition : uint8_t { @@ -37,6 +42,13 @@ enum class ScreenMode : uint8_t { MODAL = 1, }; +enum class BgImageMode : uint8_t { + NONE = 0, + STRETCH = 1, + CENTER = 2, + TILE = 3, +}; + enum class ButtonAction : uint8_t { KNX = 0, JUMP = 1, @@ -93,6 +105,81 @@ struct Color { } }; +// Condition operator for conditional styling +enum class ConditionOp : uint8_t { + LESS = 0, // value < threshold + LESS_EQUAL = 1, // value <= threshold + EQUAL = 2, // value == threshold + GREATER_EQUAL = 3, // value >= threshold + GREATER = 4, // value > threshold + NOT_EQUAL = 5, // value != threshold +}; + +// Which KNX address to use for condition evaluation +enum class ConditionSource : uint8_t { + PRIMARY = 0, // knxAddress (bottom value) + SECONDARY = 1, // knxAddress2 (left value) + TERTIARY = 2, // knxAddress3 (right value) +}; + +// Style to apply when condition is met +struct ConditionStyle { + uint32_t iconCodepoint; // 0 = don't change icon + Color textColor; + Color bgColor; + uint8_t bgOpacity; + uint8_t flags; // Bits: 0=useTextColor, 1=useBgColor, 2=useBgOpacity, 3=hide + + static constexpr uint8_t FLAG_USE_TEXT_COLOR = 0x01; + static constexpr uint8_t FLAG_USE_BG_COLOR = 0x02; + static constexpr uint8_t FLAG_USE_BG_OPACITY = 0x04; + static constexpr uint8_t FLAG_HIDE = 0x08; +}; + +// A single style condition +struct StyleCondition { + float threshold; // 4 bytes + ConditionOp op; // 1 byte + ConditionSource source; // 1 byte - which value to check + uint8_t priority; // 1 byte (lower = higher priority) + ConditionStyle style; // 12 bytes + bool enabled; // 1 byte + // Total: 20 bytes per condition +}; + +// Sub-button position around RoomCard bubble (8 positions, 45° apart) +enum class SubButtonPosition : uint8_t { + TOP = 0, + TOP_RIGHT = 1, + RIGHT = 2, + BOTTOM_RIGHT = 3, + BOTTOM = 4, + BOTTOM_LEFT = 5, + LEFT = 6, + TOP_LEFT = 7, +}; + +// Sub-button action type +enum class SubButtonAction : uint8_t { + TOGGLE_KNX = 0, // Toggle KNX switch + NAVIGATE = 1, // Navigate to screen +}; + +// Sub-button configuration for RoomCard (20 bytes) +struct SubButtonConfig { + uint32_t iconCodepoint; // 4 bytes - Icon codepoint + uint16_t knxAddrRead; // 2 bytes - KNX address to read status + uint16_t knxAddrWrite; // 2 bytes - KNX address to write on click + Color colorOn; // 3 bytes - Color when ON + Color colorOff; // 3 bytes - Color when OFF + SubButtonPosition position; // 1 byte - Position around bubble + SubButtonAction action; // 1 byte - Action type + uint8_t targetScreen; // 1 byte - Target screen for navigate + bool enabled; // 1 byte - Is this sub-button active? + uint8_t _padding[2]; // 2 bytes - Alignment padding + // Total: 20 bytes per SubButton +}; + // Shadow configuration struct ShadowConfig { int8_t offsetX; @@ -151,8 +238,29 @@ struct WidgetConfig { TextSource chartTextSource[CHART_MAX_SERIES]; Color chartSeriesColor[CHART_MAX_SERIES]; + // Secondary KNX address (for PowerNode LEFT value) + uint16_t knxAddress2; + TextSource textSource2; + char text2[MAX_FORMAT_LEN]; // Format string for left value (short) + + // Tertiary KNX address (for PowerNode RIGHT value) + uint16_t knxAddress3; + TextSource textSource3; + char text3[MAX_FORMAT_LEN]; // Format string for right value (short) + + // Conditional styling + uint8_t conditionCount; + StyleCondition conditions[MAX_CONDITIONS]; + + // RoomCard sub-buttons + uint8_t subButtonCount; + uint8_t subButtonSize; // Sub-button size in pixels (default 40) + uint8_t subButtonDistance; // Distance from center in pixels (default 80) + SubButtonConfig subButtons[MAX_SUBBUTTONS]; + // Serialization size (fixed for NVS storage) - static constexpr size_t SERIALIZED_SIZE = 98; + // 197 + 1 (subButtonCount) + 1 (subButtonSize) + 1 (subButtonDistance) + 120 (6 subButtons * 20) = 320 + static constexpr size_t SERIALIZED_SIZE = 320; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); @@ -175,6 +283,8 @@ struct ScreenConfig { char name[MAX_SCREEN_NAME_LEN]; ScreenMode mode; Color backgroundColor; + char bgImagePath[MAX_BG_IMAGE_PATH_LEN]; // Background image path (e.g., "/images/bg.png") + BgImageMode bgImageMode; // 0=none, 1=stretch, 2=center, 3=tile uint8_t widgetCount; WidgetConfig widgets[MAX_WIDGETS]; diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index 7d83f36..cb7ce5a 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -1,5 +1,6 @@ #include "WidgetManager.hpp" #include "widgets/WidgetFactory.hpp" +#include "widgets/RoomCardWidget.hpp" #include "HistoryStore.hpp" #include "SdCard.hpp" #include "esp_lv_adapter.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include static const char* TAG = "WidgetMgr"; @@ -148,6 +150,15 @@ WidgetManager& WidgetManager::instance() { } WidgetManager::WidgetManager() { + // Allocate GuiConfig in PSRAM to save internal RAM + config_ = static_cast(heap_caps_malloc(sizeof(GuiConfig), MALLOC_CAP_SPIRAM)); + if (!config_) { + ESP_LOGE(TAG, "Failed to allocate GuiConfig in PSRAM, trying internal RAM"); + config_ = new GuiConfig(); + } else { + new (config_) GuiConfig(); // Placement new to call constructor + } + // widgets_ is default-initialized to nullptr portMUX_INITIALIZE(&knxCacheMux_); uiQueue_ = xQueueCreate(UI_EVENT_QUEUE_LEN, sizeof(UiEvent)); @@ -155,14 +166,14 @@ WidgetManager::WidgetManager() { ESP_LOGE(TAG, "Failed to create UI event queue"); } createDefaultConfig(); - activeScreenId_ = config_.startScreenId; + activeScreenId_ = config_->startScreenId; lastActivityUs_ = esp_timer_get_time(); } void WidgetManager::createDefaultConfig() { - config_.clear(); - config_.screenCount = 1; - ScreenConfig& screen = config_.screens[0]; + config_->clear(); + config_->screenCount = 1; + ScreenConfig& screen = config_->screens[0]; screen.clear(0, "Screen 1"); // Default: Temperature label @@ -178,26 +189,26 @@ void WidgetManager::createDefaultConfig() { ensureButtonLabels(screen); - config_.startScreenId = screen.id; - config_.standbyEnabled = false; - config_.standbyScreenId = 0xFF; - config_.standbyMinutes = 0; + config_->startScreenId = screen.id; + config_->standbyEnabled = false; + config_->standbyScreenId = 0xFF; + config_->standbyMinutes = 0; activeScreenId_ = screen.id; } void WidgetManager::init() { loadFromSdCard(); - HistoryStore::instance().configureFromConfig(config_); + HistoryStore::instance().configureFromConfig(*config_); HistoryStore::instance().loadFromSdCard(); - if (config_.findScreen(config_.startScreenId)) { - activeScreenId_ = config_.startScreenId; - } else if (config_.screenCount > 0) { - activeScreenId_ = config_.screens[0].id; + if (config_->findScreen(config_->startScreenId)) { + activeScreenId_ = config_->startScreenId; + } else if (config_->screenCount > 0) { + activeScreenId_ = config_->screens[0].id; } else { activeScreenId_ = 0; } lastActivityUs_ = esp_timer_get_time(); - ESP_LOGI(TAG, "WidgetManager initialized with %d screens", config_.screenCount); + ESP_LOGI(TAG, "WidgetManager initialized with %d screens", config_->screenCount); } void WidgetManager::loadFromSdCard() { @@ -237,7 +248,7 @@ void WidgetManager::loadFromSdCard() { delete[] json; if (success) { - ESP_LOGI(TAG, "Loaded %d screens from SD card", config_.screenCount); + ESP_LOGI(TAG, "Loaded %d screens from SD card", config_->screenCount); } else { ESP_LOGE(TAG, "Failed to parse config file"); } @@ -269,19 +280,19 @@ void WidgetManager::saveToSdCard() { delete[] json; if (written > 0) { - ESP_LOGI(TAG, "Saved %d screens to SD card", config_.screenCount); + ESP_LOGI(TAG, "Saved %d screens to SD card", config_->screenCount); } else { ESP_LOGE(TAG, "Failed to write config file"); } } void WidgetManager::applyConfig() { - HistoryStore::instance().configureFromConfig(config_); - if (!config_.findScreen(activeScreenId_)) { - if (config_.findScreen(config_.startScreenId)) { - activeScreenId_ = config_.startScreenId; - } else if (config_.screenCount > 0) { - activeScreenId_ = config_.screens[0].id; + HistoryStore::instance().configureFromConfig(*config_); + if (!config_->findScreen(activeScreenId_)) { + if (config_->findScreen(config_->startScreenId)) { + activeScreenId_ = config_->startScreenId; + } else if (config_->screenCount > 0) { + activeScreenId_ = config_->screens[0].id; } } applyScreen(activeScreenId_); @@ -306,16 +317,16 @@ void WidgetManager::resetToDefaults() { ScreenConfig* WidgetManager::activeScreen() { if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) { - return config_.findScreen(modalScreenId_); + return config_->findScreen(modalScreenId_); } - return config_.findScreen(activeScreenId_); + return config_->findScreen(activeScreenId_); } const ScreenConfig* WidgetManager::activeScreen() const { if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) { - return config_.findScreen(modalScreenId_); + return config_->findScreen(modalScreenId_); } - return config_.findScreen(activeScreenId_); + return config_->findScreen(activeScreenId_); } const ScreenConfig* WidgetManager::currentScreen() const { @@ -334,7 +345,7 @@ void WidgetManager::applyScreen(uint8_t screenId) { void WidgetManager::applyScreenLocked(uint8_t screenId) { ESP_LOGI(TAG, "applyScreen(%d) - start", screenId); - ScreenConfig* screen = config_.findScreen(screenId); + ScreenConfig* screen = config_->findScreen(screenId); if (!screen) { ESP_LOGW(TAG, "Screen %d not found", screenId); return; @@ -525,7 +536,7 @@ void WidgetManager::closeModalLocked() { void WidgetManager::showScreenLocked(uint8_t screenId) { ESP_LOGI(TAG, "showScreen(%d) called", screenId); - ScreenConfig* screen = config_.findScreen(screenId); + ScreenConfig* screen = config_->findScreen(screenId); if (!screen) { ESP_LOGW(TAG, "Screen %d not found", screenId); return; @@ -602,12 +613,12 @@ void WidgetManager::goBackLocked() { printf("WM: Modal closed. Restoring screen %d\n", activeScreenId_); fflush(stdout); // Restore the active screen (which was in background) - if (config_.findScreen(activeScreenId_)) { + if (config_->findScreen(activeScreenId_)) { applyScreenLocked(activeScreenId_); } else { ESP_LOGE(TAG, "Active screen %d not found after closing modal!", activeScreenId_); - if (config_.findScreen(config_.startScreenId)) { - activeScreenId_ = config_.startScreenId; + if (config_->findScreen(config_->startScreenId)) { + activeScreenId_ = config_->startScreenId; applyScreenLocked(activeScreenId_); } } @@ -617,7 +628,7 @@ void WidgetManager::goBackLocked() { if (previousScreenId_ != SCREEN_ID_NONE && previousScreenId_ != activeScreenId_) { printf("WM: Going back to screen %d\n", previousScreenId_); fflush(stdout); - if (config_.findScreen(previousScreenId_)) { + if (config_->findScreen(previousScreenId_)) { activeScreenId_ = previousScreenId_; previousScreenId_ = SCREEN_ID_NONE; applyScreenLocked(activeScreenId_); @@ -637,12 +648,33 @@ void WidgetManager::goBack() { esp_lv_adapter_unlock(); } -void WidgetManager::enterStandby() { - if (!config_.standbyEnabled || config_.standbyMinutes == 0) return; - if (standbyActive_) return; - if (config_.standbyScreenId == SCREEN_ID_NONE) return; +void WidgetManager::navigateToScreen(uint8_t screenId) { + navAction_ = ButtonAction::JUMP; + navTargetScreen_ = screenId; + navPending_ = true; + navRequestUs_ = esp_timer_get_time(); +} - ScreenConfig* standbyScreen = config_.findScreen(config_.standbyScreenId); +void WidgetManager::navigateBack() { + navAction_ = ButtonAction::BACK; + navTargetScreen_ = SCREEN_ID_NONE; + navPending_ = true; + navRequestUs_ = esp_timer_get_time(); +} + +void WidgetManager::sendKnxSwitch(uint16_t groupAddr, bool value) { + ESP_LOGI(TAG, "sendKnxSwitch: GA=%d, value=%d", groupAddr, value); + // TODO: Send actual KNX telegram via KnxWorker + // For now, just log and update cache so UI reflects the change + cacheKnxSwitch(groupAddr, value); +} + +void WidgetManager::enterStandby() { + if (!config_->standbyEnabled || config_->standbyMinutes == 0) return; + if (standbyActive_) return; + if (config_->standbyScreenId == SCREEN_ID_NONE) return; + + ScreenConfig* standbyScreen = config_->findScreen(config_->standbyScreenId); if (!standbyScreen) return; standbyReturnScreenId_ = activeScreenId_; @@ -692,13 +724,13 @@ void WidgetManager::loop() { if (didUiNav) return; - if (!config_.standbyEnabled || config_.standbyMinutes == 0) return; + if (!config_->standbyEnabled || config_->standbyMinutes == 0) return; if (standbyActive_) return; - if (config_.standbyScreenId == SCREEN_ID_NONE) return; + if (config_->standbyScreenId == SCREEN_ID_NONE) return; int64_t now = esp_timer_get_time(); int64_t idleUs = now - lastActivityUs_; - int64_t timeoutUs = static_cast(config_.standbyMinutes) * 60 * 1000000LL; + int64_t timeoutUs = static_cast(config_->standbyMinutes) * 60 * 1000000LL; if (idleUs >= timeoutUs) { enterStandby(); } @@ -710,7 +742,7 @@ void WidgetManager::onUserActivity() { standbyActive_ = false; uint8_t returnId = standbyReturnScreenId_; if (returnId == SCREEN_ID_NONE) { - returnId = config_.startScreenId; + returnId = config_->startScreenId; } standbyWakeTarget_ = returnId; standbyWakePending_ = true; @@ -738,6 +770,52 @@ void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* paren screen.backgroundColor.b), 0); lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0); + // Background image (if set) + // Note: Requires LV_USE_FS_POSIX=y in sdkconfig with LV_FS_POSIX_LETTER='S' (83) + if (screen.bgImagePath[0] != '\0') { + char fullPath[64]; + snprintf(fullPath, sizeof(fullPath), "S:/sdcard%s", screen.bgImagePath); + + // Check if file exists, try uppercase IMAGES as fallback + struct stat st; + char checkPath[64]; + snprintf(checkPath, sizeof(checkPath), "/sdcard%s", screen.bgImagePath); + if (stat(checkPath, &st) != 0) { + // Try uppercase /IMAGES/ instead of /images/ + if (strncmp(screen.bgImagePath, "/images/", 8) == 0) { + snprintf(fullPath, sizeof(fullPath), "S:/sdcard/IMAGES%s", screen.bgImagePath + 7); + ESP_LOGI(TAG, "Trying uppercase path: %s", fullPath); + } + } + ESP_LOGI(TAG, "Loading background image: %s", fullPath); + + lv_obj_t* bgImg = lv_image_create(parent); + lv_image_set_src(bgImg, fullPath); + + // Position at top-left + lv_obj_set_pos(bgImg, 0, 0); + + // Apply scaling mode + switch (screen.bgImageMode) { + case BgImageMode::STRETCH: + lv_obj_set_size(bgImg, lv_pct(100), lv_pct(100)); + lv_image_set_inner_align(bgImg, LV_IMAGE_ALIGN_STRETCH); + break; + case BgImageMode::CENTER: + lv_obj_center(bgImg); + break; + case BgImageMode::TILE: + lv_image_set_inner_align(bgImg, LV_IMAGE_ALIGN_TILE); + lv_obj_set_size(bgImg, lv_pct(100), lv_pct(100)); + break; + default: + break; + } + + // Send to background (behind all widgets) + lv_obj_move_to_index(bgImg, 0); + } + // Pass 1: Create root widgets (parentId == -1) for (uint8_t i = 0; i < screen.widgetCount; i++) { const WidgetConfig& cfg = screen.widgets[i]; @@ -884,48 +962,59 @@ void WidgetManager::applyCachedValuesToWidgets() { for (auto& widget : widgets_) { if (!widget) continue; + // Primary address uint16_t addr = widget->getKnxAddress(); - if (addr == 0) continue; - TextSource source = widget->getTextSource(); - if (source == TextSource::STATIC) continue; - if (source == TextSource::KNX_DPT_TIME || - source == TextSource::KNX_DPT_DATE || - source == TextSource::KNX_DPT_DATETIME) { - KnxTimeType type = KnxTimeType::TIME; - if (source == TextSource::KNX_DPT_DATE) { - type = KnxTimeType::DATE; - } else if (source == TextSource::KNX_DPT_DATETIME) { - type = KnxTimeType::DATETIME; + if (addr != 0 && source != TextSource::STATIC) { + if (source == TextSource::KNX_DPT_TIME || + source == TextSource::KNX_DPT_DATE || + source == TextSource::KNX_DPT_DATETIME) { + KnxTimeType type = KnxTimeType::TIME; + if (source == TextSource::KNX_DPT_DATE) { + type = KnxTimeType::DATE; + } else if (source == TextSource::KNX_DPT_DATETIME) { + type = KnxTimeType::DATETIME; + } + struct tm tmValue = {}; + if (getCachedKnxTime(addr, type, &tmValue)) { + widget->onKnxTime(tmValue, source); + } + } else if (source == TextSource::KNX_DPT_SWITCH) { + bool state = false; + if (getCachedKnxSwitch(addr, &state)) { + widget->onKnxSwitch(state); + } + } else if (source == TextSource::KNX_DPT_TEXT) { + char text[MAX_TEXT_LEN] = {}; + if (getCachedKnxText(addr, text, sizeof(text))) { + widget->onKnxText(text); + } + } else if (isNumericTextSource(source)) { + float value = 0.0f; + if (getCachedKnxValue(addr, source, &value)) { + widget->onKnxValue(value); + } } - struct tm tmValue = {}; - if (getCachedKnxTime(addr, type, &tmValue)) { - widget->onKnxTime(tmValue, source); - } - continue; } - if (source == TextSource::KNX_DPT_SWITCH) { - bool state = false; - if (getCachedKnxSwitch(addr, &state)) { - widget->onKnxSwitch(state); - } - continue; - } - - if (source == TextSource::KNX_DPT_TEXT) { - char text[MAX_TEXT_LEN] = {}; - if (getCachedKnxText(addr, text, sizeof(text))) { - widget->onKnxText(text); - } - continue; - } - - if (isNumericTextSource(source)) { + // Secondary address (left value) + uint16_t addr2 = widget->getKnxAddress2(); + TextSource source2 = widget->getTextSource2(); + if (addr2 != 0 && source2 != TextSource::STATIC && isNumericTextSource(source2)) { float value = 0.0f; - if (getCachedKnxValue(addr, source, &value)) { - widget->onKnxValue(value); + if (getCachedKnxValue(addr2, source2, &value)) { + widget->onKnxValue2(value); + } + } + + // Tertiary address (right value) + uint16_t addr3 = widget->getKnxAddress3(); + TextSource source3 = widget->getTextSource3(); + if (addr3 != 0 && source3 != TextSource::STATIC && isNumericTextSource(source3)) { + float value = 0.0f; + if (getCachedKnxValue(addr3, source3, &value)) { + widget->onKnxValue3(value); } } } @@ -972,10 +1061,25 @@ void WidgetManager::updateSystemTimeWidgets() { void WidgetManager::applyKnxValue(uint16_t groupAddr, float value, TextSource source) { for (auto& widget : widgets_) { - if (widget && widget->getKnxAddress() == groupAddr && + if (!widget) continue; + + // Primary address (bottom value) + if (widget->getKnxAddress() == groupAddr && widget->getTextSource() == source) { widget->onKnxValue(value); } + + // Secondary address (left value) + if (widget->getKnxAddress2() == groupAddr && + widget->getTextSource2() == source) { + widget->onKnxValue2(value); + } + + // Tertiary address (right value) + if (widget->getKnxAddress3() == groupAddr && + widget->getTextSource3() == source) { + widget->onKnxValue3(value); + } } if (HistoryStore::instance().updateLatest(groupAddr, source, value)) { @@ -985,12 +1089,24 @@ void WidgetManager::applyKnxValue(uint16_t groupAddr, float value, TextSource so void WidgetManager::applyKnxSwitch(uint16_t groupAddr, bool value) { for (auto& widget : widgets_) { - if (widget && widget->getKnxAddress() == groupAddr) { + if (!widget) continue; + + if (widget->getKnxAddress() == groupAddr) { widget->onKnxSwitch(value); } + + // RoomCard sub-button status updates + if (widget->getType() == WidgetType::ROOMCARD) { + const WidgetConfig& cfg = widget->getConfig(); + for (uint8_t i = 0; i < cfg.subButtonCount && i < MAX_SUBBUTTONS; ++i) { + if (cfg.subButtons[i].enabled && cfg.subButtons[i].knxAddrRead == groupAddr) { + static_cast(widget.get())->onSubButtonStatus(i, value); + } + } + } } - if (config_.knxNightModeAddress != 0 && groupAddr == config_.knxNightModeAddress) { + if (config_->knxNightModeAddress != 0 && groupAddr == config_->knxNightModeAddress) { nightMode_ = value; } } @@ -1006,9 +1122,9 @@ void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) { void WidgetManager::applyKnxTime(uint16_t groupAddr, const struct tm& value, KnxTimeType type) { // Simplified system time synchronization bool isGlobalTime = false; - if (type == KnxTimeType::TIME && config_.knxTimeAddress != 0 && groupAddr == config_.knxTimeAddress) isGlobalTime = true; - if (type == KnxTimeType::DATE && config_.knxDateAddress != 0 && groupAddr == config_.knxDateAddress) isGlobalTime = true; - if (type == KnxTimeType::DATETIME && config_.knxDateTimeAddress != 0 && groupAddr == config_.knxDateTimeAddress) isGlobalTime = true; + if (type == KnxTimeType::TIME && config_->knxTimeAddress != 0 && groupAddr == config_->knxTimeAddress) isGlobalTime = true; + if (type == KnxTimeType::DATE && config_->knxDateAddress != 0 && groupAddr == config_->knxDateAddress) isGlobalTime = true; + if (type == KnxTimeType::DATETIME && config_->knxDateTimeAddress != 0 && groupAddr == config_->knxDateTimeAddress) isGlobalTime = true; if (isGlobalTime) { time_t now; @@ -1281,23 +1397,23 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { return; } - cJSON_AddNumberToObject(root, "startScreen", config_.startScreenId); + cJSON_AddNumberToObject(root, "startScreen", config_->startScreenId); cJSON* standby = cJSON_AddObjectToObject(root, "standby"); - cJSON_AddBoolToObject(standby, "enabled", config_.standbyEnabled); - cJSON_AddNumberToObject(standby, "screen", config_.standbyScreenId); - cJSON_AddNumberToObject(standby, "minutes", config_.standbyMinutes); + cJSON_AddBoolToObject(standby, "enabled", config_->standbyEnabled); + cJSON_AddNumberToObject(standby, "screen", config_->standbyScreenId); + cJSON_AddNumberToObject(standby, "minutes", config_->standbyMinutes); cJSON* knx = cJSON_AddObjectToObject(root, "knx"); - cJSON_AddNumberToObject(knx, "time", config_.knxTimeAddress); - cJSON_AddNumberToObject(knx, "date", config_.knxDateAddress); - cJSON_AddNumberToObject(knx, "dateTime", config_.knxDateTimeAddress); - cJSON_AddNumberToObject(knx, "night", config_.knxNightModeAddress); + cJSON_AddNumberToObject(knx, "time", config_->knxTimeAddress); + cJSON_AddNumberToObject(knx, "date", config_->knxDateAddress); + cJSON_AddNumberToObject(knx, "dateTime", config_->knxDateTimeAddress); + cJSON_AddNumberToObject(knx, "night", config_->knxNightModeAddress); cJSON* screens = cJSON_AddArrayToObject(root, "screens"); - for (uint8_t s = 0; s < config_.screenCount; s++) { - const ScreenConfig& screen = config_.screens[s]; + for (uint8_t s = 0; s < config_->screenCount; s++) { + const ScreenConfig& screen = config_->screens[s]; cJSON* screenJson = cJSON_CreateObject(); cJSON_AddNumberToObject(screenJson, "id", screen.id); @@ -1309,6 +1425,12 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b); cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr); + // Background image + if (screen.bgImagePath[0] != '\0') { + cJSON_AddStringToObject(screenJson, "bgImage", screen.bgImagePath); + cJSON_AddNumberToObject(screenJson, "bgImageMode", static_cast(screen.bgImageMode)); + } + // Modal-specific properties if (screen.mode == ScreenMode::MODAL) { cJSON* modal = cJSON_AddObjectToObject(screenJson, "modal"); @@ -1373,9 +1495,78 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { cJSON_AddNumberToObject(widget, "iconPosition", w.iconPosition); cJSON_AddNumberToObject(widget, "iconSize", w.iconSize); cJSON_AddNumberToObject(widget, "iconGap", w.iconGap); - + cJSON_AddNumberToObject(widget, "parentId", w.parentId); + // Secondary KNX address (left value) + if (w.knxAddress2 > 0) { + cJSON_AddNumberToObject(widget, "knxAddr2", w.knxAddress2); + cJSON_AddNumberToObject(widget, "textSrc2", static_cast(w.textSource2)); + cJSON_AddStringToObject(widget, "text2", w.text2); + } + + // Tertiary KNX address (right value) + if (w.knxAddress3 > 0) { + cJSON_AddNumberToObject(widget, "knxAddr3", w.knxAddress3); + cJSON_AddNumberToObject(widget, "textSrc3", static_cast(w.textSource3)); + cJSON_AddStringToObject(widget, "text3", w.text3); + } + + // Conditions + if (w.conditionCount > 0) { + cJSON* conditions = cJSON_AddArrayToObject(widget, "conditions"); + for (uint8_t ci = 0; ci < w.conditionCount && ci < MAX_CONDITIONS; ++ci) { + const StyleCondition& cond = w.conditions[ci]; + if (!cond.enabled) continue; + + cJSON* condJson = cJSON_CreateObject(); + + // Source + const char* sourceStr = "primary"; + if (cond.source == ConditionSource::SECONDARY) sourceStr = "secondary"; + else if (cond.source == ConditionSource::TERTIARY) sourceStr = "tertiary"; + cJSON_AddStringToObject(condJson, "source", sourceStr); + + cJSON_AddNumberToObject(condJson, "threshold", cond.threshold); + + // Operator + const char* opStr = "lt"; + switch (cond.op) { + case ConditionOp::LESS: opStr = "lt"; break; + case ConditionOp::LESS_EQUAL: opStr = "lte"; break; + case ConditionOp::EQUAL: opStr = "eq"; break; + case ConditionOp::GREATER_EQUAL: opStr = "gte"; break; + case ConditionOp::GREATER: opStr = "gt"; break; + case ConditionOp::NOT_EQUAL: opStr = "neq"; break; + } + cJSON_AddStringToObject(condJson, "op", opStr); + + cJSON_AddNumberToObject(condJson, "priority", cond.priority); + + // Style + if (cond.style.iconCodepoint != 0) { + cJSON_AddNumberToObject(condJson, "icon", cond.style.iconCodepoint); + } + if (cond.style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) { + char colorStr[8]; + snprintf(colorStr, sizeof(colorStr), "#%02X%02X%02X", + cond.style.textColor.r, cond.style.textColor.g, cond.style.textColor.b); + cJSON_AddStringToObject(condJson, "textColor", colorStr); + } + if (cond.style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { + char colorStr[8]; + snprintf(colorStr, sizeof(colorStr), "#%02X%02X%02X", + cond.style.bgColor.r, cond.style.bgColor.g, cond.style.bgColor.b); + cJSON_AddStringToObject(condJson, "bgColor", colorStr); + } + if (cond.style.flags & ConditionStyle::FLAG_HIDE) { + cJSON_AddBoolToObject(condJson, "hide", true); + } + + cJSON_AddItemToArray(conditions, condJson); + } + } + if (w.type == WidgetType::CHART) { cJSON* chart = cJSON_AddObjectToObject(widget, "chart"); cJSON_AddNumberToObject(chart, "period", w.chartPeriod); @@ -1394,6 +1585,37 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { } } + // RoomCard sub-buttons + if (w.type == WidgetType::ROOMCARD) { + cJSON_AddNumberToObject(widget, "subButtonSize", w.subButtonSize); + cJSON_AddNumberToObject(widget, "subButtonDistance", w.subButtonDistance); + } + if (w.type == WidgetType::ROOMCARD && w.subButtonCount > 0) { + cJSON* subButtons = cJSON_AddArrayToObject(widget, "subButtons"); + for (uint8_t si = 0; si < w.subButtonCount && si < MAX_SUBBUTTONS; ++si) { + const SubButtonConfig& sb = w.subButtons[si]; + if (!sb.enabled) continue; + + cJSON* sbJson = cJSON_CreateObject(); + cJSON_AddNumberToObject(sbJson, "pos", static_cast(sb.position)); + cJSON_AddNumberToObject(sbJson, "icon", sb.iconCodepoint); + cJSON_AddNumberToObject(sbJson, "knxRead", sb.knxAddrRead); + cJSON_AddNumberToObject(sbJson, "knxWrite", sb.knxAddrWrite); + cJSON_AddNumberToObject(sbJson, "action", static_cast(sb.action)); + cJSON_AddNumberToObject(sbJson, "target", sb.targetScreen); + + char colorOnStr[8], colorOffStr[8]; + snprintf(colorOnStr, sizeof(colorOnStr), "#%02X%02X%02X", + sb.colorOn.r, sb.colorOn.g, sb.colorOn.b); + snprintf(colorOffStr, sizeof(colorOffStr), "#%02X%02X%02X", + sb.colorOff.r, sb.colorOff.g, sb.colorOff.b); + cJSON_AddStringToObject(sbJson, "colorOn", colorOnStr); + cJSON_AddStringToObject(sbJson, "colorOff", colorOffStr); + + cJSON_AddItemToArray(subButtons, sbJson); + } + } + cJSON_AddItemToArray(widgets, widget); } @@ -1563,6 +1785,104 @@ bool WidgetManager::updateConfigFromJson(const char* json) { w.parentId = -1; // Default to root } + // Secondary KNX address (left value) + cJSON* knxAddr2 = cJSON_GetObjectItem(widget, "knxAddr2"); + if (cJSON_IsNumber(knxAddr2)) w.knxAddress2 = knxAddr2->valueint; + + cJSON* textSrc2 = cJSON_GetObjectItem(widget, "textSrc2"); + if (cJSON_IsNumber(textSrc2)) w.textSource2 = static_cast(textSrc2->valueint); + + cJSON* text2 = cJSON_GetObjectItem(widget, "text2"); + if (cJSON_IsString(text2)) { + strncpy(w.text2, text2->valuestring, MAX_FORMAT_LEN - 1); + w.text2[MAX_FORMAT_LEN - 1] = '\0'; + } + + // Tertiary KNX address (right value) + cJSON* knxAddr3 = cJSON_GetObjectItem(widget, "knxAddr3"); + if (cJSON_IsNumber(knxAddr3)) w.knxAddress3 = knxAddr3->valueint; + + cJSON* textSrc3 = cJSON_GetObjectItem(widget, "textSrc3"); + if (cJSON_IsNumber(textSrc3)) w.textSource3 = static_cast(textSrc3->valueint); + + cJSON* text3 = cJSON_GetObjectItem(widget, "text3"); + if (cJSON_IsString(text3)) { + strncpy(w.text3, text3->valuestring, MAX_FORMAT_LEN - 1); + w.text3[MAX_FORMAT_LEN - 1] = '\0'; + } + + // Conditions + cJSON* conditions = cJSON_GetObjectItem(widget, "conditions"); + if (cJSON_IsArray(conditions)) { + uint8_t condIdx = 0; + cJSON* condItem = nullptr; + cJSON_ArrayForEach(condItem, conditions) { + if (condIdx >= MAX_CONDITIONS) break; + + StyleCondition& cond = w.conditions[condIdx]; + memset(&cond, 0, sizeof(cond)); + cond.enabled = true; + + // Source + cJSON* source = cJSON_GetObjectItem(condItem, "source"); + if (cJSON_IsString(source)) { + if (strcmp(source->valuestring, "secondary") == 0) { + cond.source = ConditionSource::SECONDARY; + } else if (strcmp(source->valuestring, "tertiary") == 0) { + cond.source = ConditionSource::TERTIARY; + } else { + cond.source = ConditionSource::PRIMARY; + } + } + + // Threshold + cJSON* threshold = cJSON_GetObjectItem(condItem, "threshold"); + if (cJSON_IsNumber(threshold)) cond.threshold = static_cast(threshold->valuedouble); + + // Operator + cJSON* op = cJSON_GetObjectItem(condItem, "op"); + if (cJSON_IsString(op)) { + if (strcmp(op->valuestring, "lt") == 0) cond.op = ConditionOp::LESS; + else if (strcmp(op->valuestring, "lte") == 0) cond.op = ConditionOp::LESS_EQUAL; + else if (strcmp(op->valuestring, "eq") == 0) cond.op = ConditionOp::EQUAL; + else if (strcmp(op->valuestring, "gte") == 0) cond.op = ConditionOp::GREATER_EQUAL; + else if (strcmp(op->valuestring, "gt") == 0) cond.op = ConditionOp::GREATER; + else if (strcmp(op->valuestring, "neq") == 0) cond.op = ConditionOp::NOT_EQUAL; + } + + // Priority + cJSON* priority = cJSON_GetObjectItem(condItem, "priority"); + if (cJSON_IsNumber(priority)) cond.priority = priority->valueint; + + // Icon + cJSON* icon = cJSON_GetObjectItem(condItem, "icon"); + if (cJSON_IsNumber(icon)) cond.style.iconCodepoint = static_cast(icon->valuedouble); + + // Text color + cJSON* textColor = cJSON_GetObjectItem(condItem, "textColor"); + if (cJSON_IsString(textColor)) { + cond.style.textColor = Color::fromHex(parseHexColor(textColor->valuestring)); + cond.style.flags |= ConditionStyle::FLAG_USE_TEXT_COLOR; + } + + // Background color + cJSON* bgColor = cJSON_GetObjectItem(condItem, "bgColor"); + if (cJSON_IsString(bgColor)) { + cond.style.bgColor = Color::fromHex(parseHexColor(bgColor->valuestring)); + cond.style.flags |= ConditionStyle::FLAG_USE_BG_COLOR; + } + + // Hide + cJSON* hide = cJSON_GetObjectItem(condItem, "hide"); + if (cJSON_IsBool(hide) && cJSON_IsTrue(hide)) { + cond.style.flags |= ConditionStyle::FLAG_HIDE; + } + + condIdx++; + } + w.conditionCount = condIdx; + } + cJSON* chart = cJSON_GetObjectItem(widget, "chart"); if (cJSON_IsObject(chart)) { cJSON* period = cJSON_GetObjectItem(chart, "period"); @@ -1604,6 +1924,77 @@ bool WidgetManager::updateConfigFromJson(const char* json) { } } + // RoomCard sub-button size and distance + cJSON* subButtonSize = cJSON_GetObjectItem(widget, "subButtonSize"); + if (cJSON_IsNumber(subButtonSize)) { + w.subButtonSize = subButtonSize->valueint; + } else { + w.subButtonSize = 40; // Default + } + cJSON* subButtonDistance = cJSON_GetObjectItem(widget, "subButtonDistance"); + if (cJSON_IsNumber(subButtonDistance)) { + w.subButtonDistance = subButtonDistance->valueint; + } else { + w.subButtonDistance = 80; // Default 80px + } + + // RoomCard sub-buttons + cJSON* subButtons = cJSON_GetObjectItem(widget, "subButtons"); + if (cJSON_IsArray(subButtons)) { + uint8_t sbIdx = 0; + cJSON* sbItem = nullptr; + cJSON_ArrayForEach(sbItem, subButtons) { + if (sbIdx >= MAX_SUBBUTTONS) break; + + SubButtonConfig& sb = w.subButtons[sbIdx]; + memset(&sb, 0, sizeof(sb)); + sb.enabled = true; + + cJSON* pos = cJSON_GetObjectItem(sbItem, "pos"); + if (cJSON_IsNumber(pos)) { + sb.position = static_cast(pos->valueint); + } + + cJSON* icon = cJSON_GetObjectItem(sbItem, "icon"); + if (cJSON_IsNumber(icon)) { + sb.iconCodepoint = static_cast(icon->valuedouble); + } + + cJSON* knxRead = cJSON_GetObjectItem(sbItem, "knxRead"); + if (cJSON_IsNumber(knxRead)) { + sb.knxAddrRead = knxRead->valueint; + } + + cJSON* knxWrite = cJSON_GetObjectItem(sbItem, "knxWrite"); + if (cJSON_IsNumber(knxWrite)) { + sb.knxAddrWrite = knxWrite->valueint; + } + + cJSON* action = cJSON_GetObjectItem(sbItem, "action"); + if (cJSON_IsNumber(action)) { + sb.action = static_cast(action->valueint); + } + + cJSON* target = cJSON_GetObjectItem(sbItem, "target"); + if (cJSON_IsNumber(target)) { + sb.targetScreen = target->valueint; + } + + cJSON* colorOn = cJSON_GetObjectItem(sbItem, "colorOn"); + if (cJSON_IsString(colorOn)) { + sb.colorOn = Color::fromHex(parseHexColor(colorOn->valuestring)); + } + + cJSON* colorOff = cJSON_GetObjectItem(sbItem, "colorOff"); + if (cJSON_IsString(colorOff)) { + sb.colorOff = Color::fromHex(parseHexColor(colorOff->valuestring)); + } + + sbIdx++; + } + w.subButtonCount = sbIdx; + } + screen.widgetCount++; } @@ -1646,6 +2037,22 @@ bool WidgetManager::updateConfigFromJson(const char* json) { screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring)); } + // Parse background image + cJSON* bgImage = cJSON_GetObjectItem(screenJson, "bgImage"); + if (cJSON_IsString(bgImage)) { + strncpy(screen.bgImagePath, bgImage->valuestring, MAX_BG_IMAGE_PATH_LEN - 1); + screen.bgImagePath[MAX_BG_IMAGE_PATH_LEN - 1] = '\0'; + } else { + screen.bgImagePath[0] = '\0'; + } + + cJSON* bgImageMode = cJSON_GetObjectItem(screenJson, "bgImageMode"); + if (cJSON_IsNumber(bgImageMode)) { + screen.bgImageMode = static_cast(bgImageMode->valueint); + } else { + screen.bgImageMode = BgImageMode::STRETCH; + } + // Parse modal-specific properties cJSON* modal = cJSON_GetObjectItem(screenJson, "modal"); if (cJSON_IsObject(modal)) { @@ -1753,8 +2160,8 @@ bool WidgetManager::updateConfigFromJson(const char* json) { newConfig->standbyScreenId = SCREEN_ID_NONE; } - config_ = *newConfig; + *config_ = *newConfig; cJSON_Delete(root); - ESP_LOGI(TAG, "Parsed %d screens from JSON", config_.screenCount); + ESP_LOGI(TAG, "Parsed %d screens from JSON", config_->screenCount); return true; } \ No newline at end of file diff --git a/main/WidgetManager.hpp b/main/WidgetManager.hpp index 4973b21..99625f6 100644 --- a/main/WidgetManager.hpp +++ b/main/WidgetManager.hpp @@ -59,9 +59,16 @@ public: void handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target); void goBack(); + // Navigation (for RoomCard sub-buttons etc.) + void navigateToScreen(uint8_t screenId); + void navigateBack(); + + // KNX write (for RoomCard sub-buttons etc.) + void sendKnxSwitch(uint16_t groupAddr, bool value); + // Direct config access - GuiConfig& getConfig() { return config_; } - const GuiConfig& getConfig() const { return config_; } + GuiConfig& getConfig() { return *config_; } + const GuiConfig& getConfig() const { return *config_; } const ScreenConfig* currentScreen() const; private: @@ -158,7 +165,7 @@ private: static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json"; static constexpr int64_t NAV_DELAY_US = 10 * 1000; // 10ms delay (almost immediate) - GuiConfig config_; + GuiConfig* config_ = nullptr; // Allocated in PSRAM uint8_t activeScreenId_ = 0; uint8_t previousScreenId_ = 0xFF; uint8_t standbyReturnScreenId_ = 0xFF; diff --git a/main/main.cpp b/main/main.cpp index 7be4f9e..3c1aee6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -52,7 +52,7 @@ public: 1280, // Vertical resolution ESP_LV_ADAPTER_ROTATE_90 // Rotation ); - disp_cfg.profile.buffer_height = 34; // Reduced to 10 (~25KB) to fit in Internal RAM + disp_cfg.profile.buffer_height = 40; // Reduced to 10 (~25KB) to fit in Internal RAM disp_cfg.profile.use_psram = true; lv_disp_t* lv_display = esp_lv_adapter_register_display(&disp_cfg); assert(lv_display != NULL); diff --git a/main/webserver/StaticFileHandlers.cpp b/main/webserver/StaticFileHandlers.cpp index e87ff67..ea21fff 100644 --- a/main/webserver/StaticFileHandlers.cpp +++ b/main/webserver/StaticFileHandlers.cpp @@ -31,5 +31,14 @@ esp_err_t WebServer::imagesHandler(httpd_req_t* req) { snprintf(filepath, sizeof(filepath), "/sdcard%.*s", (int)(sizeof(filepath) - 8), req->uri); - return sendFile(req, filepath); + // Try lowercase first + esp_err_t result = sendFile(req, filepath); + if (result != ESP_OK) { + // Try uppercase IMAGES folder as fallback + char altpath[CONFIG_HTTPD_MAX_URI_LEN + 8]; + snprintf(altpath, sizeof(altpath), "/sdcard/IMAGES%s", + req->uri + 7); // Skip "/images" + result = sendFile(req, altpath); + } + return result; } diff --git a/main/widgets/PowerNodeWidget.cpp b/main/widgets/PowerNodeWidget.cpp index 1da075c..0ab5212 100644 --- a/main/widgets/PowerNodeWidget.cpp +++ b/main/widgets/PowerNodeWidget.cpp @@ -8,6 +8,8 @@ PowerNodeWidget::PowerNodeWidget(const WidgetConfig& config) { labelText_[0] = '\0'; valueFormat_[0] = '\0'; + leftFormat_[0] = '\0'; + rightFormat_[0] = '\0'; } static bool set_label_text_if_changed(lv_obj_t* label, const char* text) { @@ -67,21 +69,36 @@ int PowerNodeWidget::encodeUtf8(uint32_t codepoint, char* buf) { void PowerNodeWidget::parseText() { labelText_[0] = '\0'; valueFormat_[0] = '\0'; + leftFormat_[0] = '\0'; + rightFormat_[0] = '\0'; + // Parse primary text (label\nformat) const char* text = config_.text; - if (!text || text[0] == '\0') return; + if (text && text[0] != '\0') { + const char* newline = strchr(text, '\n'); + if (newline) { + size_t labelLen = static_cast(newline - text); + if (labelLen >= MAX_TEXT_LEN) labelLen = MAX_TEXT_LEN - 1; + memcpy(labelText_, text, labelLen); + labelText_[labelLen] = '\0'; + strncpy(valueFormat_, newline + 1, MAX_TEXT_LEN - 1); + valueFormat_[MAX_TEXT_LEN - 1] = '\0'; + } else { + strncpy(labelText_, text, MAX_TEXT_LEN - 1); + labelText_[MAX_TEXT_LEN - 1] = '\0'; + } + } - const char* newline = strchr(text, '\n'); - if (newline) { - size_t labelLen = static_cast(newline - text); - if (labelLen >= MAX_TEXT_LEN) labelLen = MAX_TEXT_LEN - 1; - memcpy(labelText_, text, labelLen); - labelText_[labelLen] = '\0'; - strncpy(valueFormat_, newline + 1, MAX_TEXT_LEN - 1); - valueFormat_[MAX_TEXT_LEN - 1] = '\0'; - } else { - strncpy(labelText_, text, MAX_TEXT_LEN - 1); - labelText_[MAX_TEXT_LEN - 1] = '\0'; + // Copy left format from text2 + if (config_.text2[0] != '\0') { + strncpy(leftFormat_, config_.text2, MAX_FORMAT_LEN - 1); + leftFormat_[MAX_FORMAT_LEN - 1] = '\0'; + } + + // Copy right format from text3 + if (config_.text3[0] != '\0') { + strncpy(rightFormat_, config_.text3, MAX_FORMAT_LEN - 1); + rightFormat_[MAX_FORMAT_LEN - 1] = '\0'; } } @@ -104,6 +121,8 @@ lv_obj_t* PowerNodeWidget::create(lv_obj_t* parent) { lv_obj_set_flex_align(obj_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(obj_, 6, 0); lv_obj_set_style_pad_gap(obj_, 2, 0); + + // Top label (title) if (labelText_[0] != '\0') { labelLabel_ = lv_label_create(obj_); set_obj_name(labelLabel_, "PowerNode", config_.id, "label"); @@ -111,15 +130,58 @@ lv_obj_t* PowerNodeWidget::create(lv_obj_t* parent) { lv_obj_clear_flag(labelLabel_, LV_OBJ_FLAG_CLICKABLE); } - if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { - iconLabel_ = lv_label_create(obj_); - set_obj_name(iconLabel_, "PowerNode", config_.id, "icon"); - char iconText[5]; - encodeUtf8(config_.iconCodepoint, iconText); - lv_label_set_text(iconLabel_, iconText); - lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE); + // Check if we have left/right values (dual-value mode) + bool hasDualValues = (config_.knxAddress2 > 0 || config_.knxAddress3 > 0); + + if (hasDualValues) { + // Create middle row container for: left | icon | right + middleRow_ = lv_obj_create(obj_); + lv_obj_remove_style_all(middleRow_); + set_obj_name(middleRow_, "PowerNode", config_.id, "middleRow"); + lv_obj_set_size(middleRow_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_clear_flag(middleRow_, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_flex_flow(middleRow_, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(middleRow_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_gap(middleRow_, 8, 0); + + // Left value label (secondary) + if (config_.knxAddress2 > 0) { + leftLabel_ = lv_label_create(middleRow_); + set_obj_name(leftLabel_, "PowerNode", config_.id, "left"); + lv_label_set_text(leftLabel_, leftFormat_[0] != '\0' ? leftFormat_ : ""); + lv_obj_clear_flag(leftLabel_, LV_OBJ_FLAG_CLICKABLE); + } + + // Center icon + if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { + iconLabel_ = lv_label_create(middleRow_); + set_obj_name(iconLabel_, "PowerNode", config_.id, "icon"); + char iconText[5]; + encodeUtf8(config_.iconCodepoint, iconText); + lv_label_set_text(iconLabel_, iconText); + lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE); + } + + // Right value label (tertiary) + if (config_.knxAddress3 > 0) { + rightLabel_ = lv_label_create(middleRow_); + set_obj_name(rightLabel_, "PowerNode", config_.id, "right"); + lv_label_set_text(rightLabel_, rightFormat_[0] != '\0' ? rightFormat_ : ""); + lv_obj_clear_flag(rightLabel_, LV_OBJ_FLAG_CLICKABLE); + } + } else { + // Original layout: icon in column flow + if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { + iconLabel_ = lv_label_create(obj_); + set_obj_name(iconLabel_, "PowerNode", config_.id, "icon"); + char iconText[5]; + encodeUtf8(config_.iconCodepoint, iconText); + lv_label_set_text(iconLabel_, iconText); + lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE); + } } + // Bottom value label (primary) valueLabel_ = lv_label_create(obj_); set_obj_name(valueLabel_, "PowerNode", config_.id, "value"); if (valueFormat_[0] != '\0') { @@ -153,6 +215,7 @@ void PowerNodeWidget::applyStyle() { uint8_t valueSizeIdx = config_.fontSize; uint8_t labelSizeIdx = valueSizeIdx > 0 ? static_cast(valueSizeIdx - 1) : valueSizeIdx; + uint8_t sideValueSizeIdx = labelSizeIdx; // Left/right values use smaller font if (labelLabel_ != nullptr) { lv_obj_set_style_text_color(labelLabel_, lv_color_make( @@ -170,13 +233,26 @@ void PowerNodeWidget::applyStyle() { lv_obj_set_style_text_align(iconLabel_, LV_TEXT_ALIGN_CENTER, 0); } + if (leftLabel_ != nullptr) { + lv_obj_set_style_text_color(leftLabel_, lv_color_make( + config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); + lv_obj_set_style_text_font(leftLabel_, Fonts::bySizeIndex(sideValueSizeIdx), 0); + lv_obj_set_style_text_align(leftLabel_, LV_TEXT_ALIGN_RIGHT, 0); + } + + if (rightLabel_ != nullptr) { + lv_obj_set_style_text_color(rightLabel_, lv_color_make( + config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); + lv_obj_set_style_text_font(rightLabel_, Fonts::bySizeIndex(sideValueSizeIdx), 0); + lv_obj_set_style_text_align(rightLabel_, LV_TEXT_ALIGN_LEFT, 0); + } + if (valueLabel_ != nullptr) { lv_obj_set_style_text_color(valueLabel_, lv_color_make( config_.textColor.r, config_.textColor.g, config_.textColor.b), 0); lv_obj_set_style_text_font(valueLabel_, Fonts::bySizeIndex(valueSizeIdx), 0); lv_obj_set_style_text_align(valueLabel_, LV_TEXT_ALIGN_CENTER, 0); } - } void PowerNodeWidget::updateValueText(const char* text) { @@ -184,26 +260,86 @@ void PowerNodeWidget::updateValueText(const char* text) { set_label_text_if_changed(valueLabel_, text); } +void PowerNodeWidget::updateLeftText(const char* text) { + if (leftLabel_ == nullptr || text == nullptr) return; + set_label_text_if_changed(leftLabel_, text); +} + +void PowerNodeWidget::updateRightText(const char* text) { + if (rightLabel_ == nullptr || text == nullptr) return; + set_label_text_if_changed(rightLabel_, text); +} + +void PowerNodeWidget::updateIcon(uint32_t codepoint) { + if (iconLabel_ == nullptr || codepoint == 0) return; + char iconText[5]; + encodeUtf8(codepoint, iconText); + set_label_text_if_changed(iconLabel_, iconText); +} + +static bool isNumericSource(TextSource source) { + return source == TextSource::KNX_DPT_TEMP || + source == TextSource::KNX_DPT_PERCENT || + source == TextSource::KNX_DPT_POWER || + source == TextSource::KNX_DPT_ENERGY || + source == TextSource::KNX_DPT_DECIMALFACTOR; +} + +static void formatValue(char* buf, size_t bufSize, float value, const char* fmt, TextSource source) { + const char* useFmt = (fmt && fmt[0] != '\0') ? fmt : "%0.1f"; + if (source == TextSource::KNX_DPT_PERCENT || + source == TextSource::KNX_DPT_DECIMALFACTOR) { + int intVal = static_cast(value + 0.5f); + snprintf(buf, bufSize, useFmt, intVal); + } else { + snprintf(buf, bufSize, useFmt, value); + } +} + void PowerNodeWidget::onKnxValue(float value) { + // Cache and call base for condition evaluation + cachedPrimaryValue_ = value; + hasCachedPrimary_ = true; + if (valueLabel_ == nullptr) return; - if (config_.textSource != TextSource::KNX_DPT_TEMP && - config_.textSource != TextSource::KNX_DPT_PERCENT && - config_.textSource != TextSource::KNX_DPT_POWER && - config_.textSource != TextSource::KNX_DPT_ENERGY && - config_.textSource != TextSource::KNX_DPT_DECIMALFACTOR) return; + if (!isNumericSource(config_.textSource)) return; char buf[32]; - const char* fmt = valueFormat_[0] != '\0' ? valueFormat_ : "%0.1f"; - - if (config_.textSource == TextSource::KNX_DPT_PERCENT || - config_.textSource == TextSource::KNX_DPT_DECIMALFACTOR) { - int percent = static_cast(value + 0.5f); - snprintf(buf, sizeof(buf), fmt, percent); - } else { - snprintf(buf, sizeof(buf), fmt, value); - } - + formatValue(buf, sizeof(buf), value, valueFormat_, config_.textSource); updateValueText(buf); + + // Evaluate conditions after updating value + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); +} + +void PowerNodeWidget::onKnxValue2(float value) { + cachedSecondaryValue_ = value; + hasCachedSecondary_ = true; + + if (leftLabel_ == nullptr) return; + if (!isNumericSource(config_.textSource2)) return; + + char buf[32]; + formatValue(buf, sizeof(buf), value, leftFormat_, config_.textSource2); + updateLeftText(buf); + + // Evaluate conditions after updating value + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); +} + +void PowerNodeWidget::onKnxValue3(float value) { + cachedTertiaryValue_ = value; + hasCachedTertiary_ = true; + + if (rightLabel_ == nullptr) return; + if (!isNumericSource(config_.textSource3)) return; + + char buf[32]; + formatValue(buf, sizeof(buf), value, rightFormat_, config_.textSource3); + updateRightText(buf); + + // Evaluate conditions after updating value + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); } void PowerNodeWidget::onKnxSwitch(bool value) { @@ -217,3 +353,99 @@ void PowerNodeWidget::onKnxText(const char* text) { if (config_.textSource != TextSource::KNX_DPT_TEXT) return; updateValueText(text); } + +bool PowerNodeWidget::evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) { + if (config_.conditionCount == 0) return false; + + const StyleCondition* bestMatch = nullptr; + uint8_t bestPriority = 255; + + for (uint8_t i = 0; i < config_.conditionCount && i < MAX_CONDITIONS; ++i) { + const StyleCondition& cond = config_.conditions[i]; + if (!cond.enabled) continue; + + // Get the value to check based on source + float checkValue = 0.0f; + bool hasValue = false; + switch (cond.source) { + case ConditionSource::PRIMARY: + checkValue = primaryValue; + hasValue = hasCachedPrimary_; + break; + case ConditionSource::SECONDARY: + checkValue = secondaryValue; + hasValue = hasCachedSecondary_; + break; + case ConditionSource::TERTIARY: + checkValue = tertiaryValue; + hasValue = hasCachedTertiary_; + break; + } + + if (!hasValue) continue; + + // Evaluate condition + bool matches = false; + switch (cond.op) { + case ConditionOp::LESS: + matches = checkValue < cond.threshold; + break; + case ConditionOp::LESS_EQUAL: + matches = checkValue <= cond.threshold; + break; + case ConditionOp::EQUAL: + matches = checkValue == cond.threshold; + break; + case ConditionOp::GREATER_EQUAL: + matches = checkValue >= cond.threshold; + break; + case ConditionOp::GREATER: + matches = checkValue > cond.threshold; + break; + case ConditionOp::NOT_EQUAL: + matches = checkValue != cond.threshold; + break; + } + + if (matches && cond.priority < bestPriority) { + bestMatch = &cond; + bestPriority = cond.priority; + } + } + + if (bestMatch) { + // Apply icon change + if (bestMatch->style.iconCodepoint != 0 && + bestMatch->style.iconCodepoint != currentConditionIcon_) { + updateIcon(bestMatch->style.iconCodepoint); + currentConditionIcon_ = bestMatch->style.iconCodepoint; + } + + // Apply text color to icon + if ((bestMatch->style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) && iconLabel_) { + lv_obj_set_style_text_color(iconLabel_, lv_color_make( + bestMatch->style.textColor.r, + bestMatch->style.textColor.g, + bestMatch->style.textColor.b), 0); + } + + // Apply border color change (bgColor affects border in PowerNode) + if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { + lv_obj_set_style_border_color(obj_, lv_color_make( + bestMatch->style.bgColor.r, + bestMatch->style.bgColor.g, + bestMatch->style.bgColor.b), 0); + } + + // Handle hide flag + if (bestMatch->style.flags & ConditionStyle::FLAG_HIDE) { + lv_obj_add_flag(obj_, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_HIDDEN); + } + + return true; + } + + return false; +} diff --git a/main/widgets/PowerNodeWidget.hpp b/main/widgets/PowerNodeWidget.hpp index 6cbcb48..eb20e38 100644 --- a/main/widgets/PowerNodeWidget.hpp +++ b/main/widgets/PowerNodeWidget.hpp @@ -8,18 +8,29 @@ public: lv_obj_t* create(lv_obj_t* parent) override; void applyStyle() override; void onKnxValue(float value) override; + void onKnxValue2(float value) override; + void onKnxValue3(float value) override; void onKnxSwitch(bool value) override; void onKnxText(const char* text) override; + bool evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) override; private: - lv_obj_t* labelLabel_ = nullptr; - lv_obj_t* valueLabel_ = nullptr; - lv_obj_t* iconLabel_ = nullptr; + lv_obj_t* labelLabel_ = nullptr; // Top label (title) + lv_obj_t* middleRow_ = nullptr; // Horizontal container for left-icon-right + lv_obj_t* leftLabel_ = nullptr; // Left value (secondary) + lv_obj_t* iconLabel_ = nullptr; // Center icon + lv_obj_t* rightLabel_ = nullptr; // Right value (tertiary) + lv_obj_t* valueLabel_ = nullptr; // Bottom value (primary) char labelText_[MAX_TEXT_LEN] = {0}; - char valueFormat_[MAX_TEXT_LEN] = {0}; + char valueFormat_[MAX_TEXT_LEN] = {0}; // Format for primary/bottom value + char leftFormat_[MAX_FORMAT_LEN] = {0}; // Format for left value + char rightFormat_[MAX_FORMAT_LEN] = {0}; // Format for right value void parseText(); void updateValueText(const char* text); + void updateLeftText(const char* text); + void updateRightText(const char* text); + void updateIcon(uint32_t codepoint); static int encodeUtf8(uint32_t codepoint, char* buf); }; diff --git a/main/widgets/RoomCardWidget.cpp b/main/widgets/RoomCardWidget.cpp new file mode 100644 index 0000000..72ef22d --- /dev/null +++ b/main/widgets/RoomCardWidget.cpp @@ -0,0 +1,335 @@ +#include "RoomCardWidget.hpp" +#include "../Fonts.hpp" +#include "../WidgetManager.hpp" +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +RoomCardWidget::RoomCardWidget(const WidgetConfig& config) + : Widget(config) +{ + roomName_[0] = '\0'; + tempFormat_[0] = '\0'; + for (size_t i = 0; i < MAX_SUBBUTTONS; ++i) { + subButtonStates_[i] = false; + } +} + +int RoomCardWidget::encodeUtf8(uint32_t codepoint, char* buf) { + if (codepoint < 0x80) { + buf[0] = static_cast(codepoint); + buf[1] = '\0'; + return 1; + } else if (codepoint < 0x800) { + buf[0] = static_cast(0xC0 | (codepoint >> 6)); + buf[1] = static_cast(0x80 | (codepoint & 0x3F)); + buf[2] = '\0'; + return 2; + } else if (codepoint < 0x10000) { + buf[0] = static_cast(0xE0 | (codepoint >> 12)); + buf[1] = static_cast(0x80 | ((codepoint >> 6) & 0x3F)); + buf[2] = static_cast(0x80 | (codepoint & 0x3F)); + buf[3] = '\0'; + return 3; + } else if (codepoint < 0x110000) { + buf[0] = static_cast(0xF0 | (codepoint >> 18)); + buf[1] = static_cast(0x80 | ((codepoint >> 12) & 0x3F)); + buf[2] = static_cast(0x80 | ((codepoint >> 6) & 0x3F)); + buf[3] = static_cast(0x80 | (codepoint & 0x3F)); + buf[4] = '\0'; + return 4; + } + buf[0] = '\0'; + return 0; +} + +void RoomCardWidget::parseText() { + roomName_[0] = '\0'; + tempFormat_[0] = '\0'; + + const char* text = config_.text; + if (text && text[0] != '\0') { + const char* newline = strchr(text, '\n'); + if (newline) { + size_t nameLen = static_cast(newline - text); + if (nameLen >= MAX_TEXT_LEN) nameLen = MAX_TEXT_LEN - 1; + memcpy(roomName_, text, nameLen); + roomName_[nameLen] = '\0'; + strncpy(tempFormat_, newline + 1, MAX_TEXT_LEN - 1); + tempFormat_[MAX_TEXT_LEN - 1] = '\0'; + } else { + strncpy(roomName_, text, MAX_TEXT_LEN - 1); + roomName_[MAX_TEXT_LEN - 1] = '\0'; + } + } +} + +void RoomCardWidget::calculateSubButtonPosition(SubButtonPosition pos, int16_t& x, int16_t& y) { + int16_t centerX = config_.width / 2; + int16_t centerY = config_.height / 2; + + // Sub-button size (configurable, default 40) + int16_t subBtnSize = config_.subButtonSize > 0 ? config_.subButtonSize : 40; + + // Sub-buttons orbit radius in pixels (default 80) + int16_t orbitRadius = config_.subButtonDistance > 0 ? config_.subButtonDistance : 80; + + // Calculate angle: 0=TOP, going clockwise, 8 positions = 45 degrees each + float angle = static_cast(static_cast(pos)) * (M_PI / 4.0f) - (M_PI / 2.0f); + + // Calculate position (center of sub-button) + x = centerX + static_cast(orbitRadius * cosf(angle)) - subBtnSize / 2; + y = centerY + static_cast(orbitRadius * sinf(angle)) - subBtnSize / 2; +} + +lv_obj_t* RoomCardWidget::create(lv_obj_t* parent) { + parseText(); + + // Create main container + obj_ = lv_obj_create(parent); + lv_obj_remove_style_all(obj_); + lv_obj_set_pos(obj_, config_.x, config_.y); + lv_obj_set_size(obj_, config_.width, config_.height); + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE); + + // Create central bubble first + createCentralBubble(); + + // Create sub-buttons + createSubButtons(); + + return obj_; +} + +void RoomCardWidget::createCentralBubble() { + // Calculate bubble size (80% of widget size, circular) + int16_t minSide = config_.width < config_.height ? config_.width : config_.height; + int16_t bubbleSize = (minSide * 80) / 100; + + // Create bubble container (centered) + bubble_ = lv_obj_create(obj_); + lv_obj_remove_style_all(bubble_); + lv_obj_set_size(bubble_, bubbleSize, bubbleSize); + lv_obj_center(bubble_); + lv_obj_clear_flag(bubble_, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(bubble_, LV_OBJ_FLAG_CLICKABLE); + + // Circular shape + lv_obj_set_style_radius(bubble_, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_bg_opa(bubble_, config_.bgOpacity, 0); + lv_obj_set_style_bg_color(bubble_, lv_color_hex(config_.bgColor.toLvColor()), 0); + + // Set up flex layout for bubble content + lv_obj_set_flex_flow(bubble_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(bubble_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(bubble_, 8, 0); + lv_obj_set_style_pad_gap(bubble_, 2, 0); + + // Room icon + if (config_.iconCodepoint > 0 && Fonts::hasIconFont()) { + roomIcon_ = lv_label_create(bubble_); + lv_obj_clear_flag(roomIcon_, LV_OBJ_FLAG_CLICKABLE); + char iconText[5]; + encodeUtf8(config_.iconCodepoint, iconText); + lv_label_set_text(roomIcon_, iconText); + } + + // Temperature label + tempLabel_ = lv_label_create(bubble_); + lv_obj_clear_flag(tempLabel_, LV_OBJ_FLAG_CLICKABLE); + lv_label_set_text(tempLabel_, tempFormat_[0] != '\0' ? "--" : ""); + + // Room name label + if (roomName_[0] != '\0') { + roomLabel_ = lv_label_create(bubble_); + lv_obj_clear_flag(roomLabel_, LV_OBJ_FLAG_CLICKABLE); + lv_label_set_text(roomLabel_, roomName_); + } + + // Click handler for navigation + lv_obj_add_event_cb(bubble_, bubbleClickCallback, LV_EVENT_CLICKED, this); +} + +void RoomCardWidget::createSubButtons() { + // Sub-button size (configurable, default 40) + int16_t subBtnSize = config_.subButtonSize > 0 ? config_.subButtonSize : 40; + + for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) { + const SubButtonConfig& cfg = config_.subButtons[i]; + if (!cfg.enabled) continue; + + // Create sub-button object + lv_obj_t* btn = lv_obj_create(obj_); + lv_obj_remove_style_all(btn); + lv_obj_set_size(btn, subBtnSize, subBtnSize); + lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); + + // Circular shape + lv_obj_set_style_radius(btn, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_bg_opa(btn, 255, 0); + + // Position using circle geometry + int16_t x, y; + calculateSubButtonPosition(cfg.position, x, y); + lv_obj_set_pos(btn, x, y); + + // Create icon + if (cfg.iconCodepoint > 0 && Fonts::hasIconFont()) { + lv_obj_t* icon = lv_label_create(btn); + lv_obj_clear_flag(icon, LV_OBJ_FLAG_CLICKABLE); + char iconText[5]; + encodeUtf8(cfg.iconCodepoint, iconText); + lv_label_set_text(icon, iconText); + lv_obj_center(icon); + subButtonIcons_[i] = icon; + } + + // Store index in user_data for click handler + lv_obj_set_user_data(btn, reinterpret_cast(static_cast(i))); + lv_obj_add_event_cb(btn, subButtonClickCallback, LV_EVENT_CLICKED, this); + + subButtonObjs_[i] = btn; + subButtonStates_[i] = false; + + // Apply initial color (OFF state) + updateSubButtonColor(i); + } +} + +void RoomCardWidget::applyStyle() { + if (!obj_) return; + + // Apply shadow to bubble if enabled + if (bubble_ && config_.shadow.enabled) { + lv_obj_set_style_shadow_width(bubble_, config_.shadow.blur, 0); + lv_obj_set_style_shadow_ofs_x(bubble_, config_.shadow.offsetX, 0); + lv_obj_set_style_shadow_ofs_y(bubble_, config_.shadow.offsetY, 0); + lv_obj_set_style_shadow_color(bubble_, lv_color_hex(config_.shadow.color.toLvColor()), 0); + lv_obj_set_style_shadow_opa(bubble_, 255, 0); + } + + // Font sizes + const lv_font_t* iconFont = Fonts::iconFont(config_.iconSize); + const lv_font_t* textFont = Fonts::bySizeIndex(config_.fontSize); + const lv_font_t* labelFont = Fonts::bySizeIndex(config_.fontSize > 0 ? config_.fontSize - 1 : 0); + lv_color_t textColor = lv_color_hex(config_.textColor.toLvColor()); + + // Style room icon + if (roomIcon_ && iconFont) { + lv_obj_set_style_text_font(roomIcon_, iconFont, 0); + lv_obj_set_style_text_color(roomIcon_, textColor, 0); + } + + // Style temperature + if (tempLabel_ && textFont) { + lv_obj_set_style_text_font(tempLabel_, textFont, 0); + lv_obj_set_style_text_color(tempLabel_, textColor, 0); + } + + // Style room label + if (roomLabel_ && labelFont) { + lv_obj_set_style_text_font(roomLabel_, labelFont, 0); + lv_obj_set_style_text_color(roomLabel_, lv_color_hex(config_.textColor.toLvColor()), 0); + lv_obj_set_style_text_opa(roomLabel_, 180, 0); // Slightly dimmed + } + + // Style sub-buttons - adjust icon size based on button size + // subButtonSize: 30-40 -> font 1, 41-55 -> font 2, 56+ -> font 3 + uint8_t subBtnFontIdx = 1; // Default small + if (config_.subButtonSize > 55) { + subBtnFontIdx = 3; + } else if (config_.subButtonSize > 40) { + subBtnFontIdx = 2; + } + const lv_font_t* subBtnIconFont = Fonts::iconFont(subBtnFontIdx); + for (uint8_t i = 0; i < config_.subButtonCount && i < MAX_SUBBUTTONS; ++i) { + if (subButtonIcons_[i] && subBtnIconFont) { + lv_obj_set_style_text_font(subButtonIcons_[i], subBtnIconFont, 0); + lv_obj_set_style_text_color(subButtonIcons_[i], lv_color_white(), 0); + } + } +} + +void RoomCardWidget::updateSubButtonColor(uint8_t index) { + if (index >= MAX_SUBBUTTONS || !subButtonObjs_[index]) return; + + const SubButtonConfig& cfg = config_.subButtons[index]; + const Color& color = subButtonStates_[index] ? cfg.colorOn : cfg.colorOff; + + lv_obj_set_style_bg_color(subButtonObjs_[index], lv_color_hex(color.toLvColor()), 0); +} + +void RoomCardWidget::updateTemperature(float value) { + if (!tempLabel_ || tempFormat_[0] == '\0') return; + + char buf[32]; + snprintf(buf, sizeof(buf), tempFormat_, value); + lv_label_set_text(tempLabel_, buf); +} + +void RoomCardWidget::onKnxValue(float value) { + cachedPrimaryValue_ = value; + hasCachedPrimary_ = true; + updateTemperature(value); +} + +void RoomCardWidget::onKnxSwitch(bool value) { + // Not used directly - sub-button status is handled via onSubButtonStatus + (void)value; +} + +void RoomCardWidget::onSubButtonStatus(uint8_t index, bool value) { + if (index >= MAX_SUBBUTTONS) return; + + subButtonStates_[index] = value; + updateSubButtonColor(index); +} + +void RoomCardWidget::sendSubButtonToggle(uint8_t index) { + if (index >= config_.subButtonCount || index >= MAX_SUBBUTTONS) return; + + const SubButtonConfig& cfg = config_.subButtons[index]; + if (cfg.action == SubButtonAction::TOGGLE_KNX && cfg.knxAddrWrite > 0) { + bool newState = !subButtonStates_[index]; + // Send KNX toggle via WidgetManager + WidgetManager::instance().sendKnxSwitch(cfg.knxAddrWrite, newState); + // Optimistically update local state + subButtonStates_[index] = newState; + updateSubButtonColor(index); + } +} + +void RoomCardWidget::bubbleClickCallback(lv_event_t* e) { + RoomCardWidget* widget = static_cast(lv_event_get_user_data(e)); + if (!widget) return; + + // Handle navigation based on action + if (widget->config_.action == ButtonAction::JUMP) { + WidgetManager::instance().navigateToScreen(widget->config_.targetScreen); + } else if (widget->config_.action == ButtonAction::BACK) { + WidgetManager::instance().navigateBack(); + } +} + +void RoomCardWidget::subButtonClickCallback(lv_event_t* e) { + RoomCardWidget* widget = static_cast(lv_event_get_user_data(e)); + lv_obj_t* target = static_cast(lv_event_get_target(e)); + if (!widget || !target) return; + + uint8_t index = static_cast(reinterpret_cast(lv_obj_get_user_data(target))); + + if (index < widget->config_.subButtonCount && index < MAX_SUBBUTTONS) { + const SubButtonConfig& cfg = widget->config_.subButtons[index]; + + if (cfg.action == SubButtonAction::TOGGLE_KNX) { + widget->sendSubButtonToggle(index); + } else if (cfg.action == SubButtonAction::NAVIGATE) { + WidgetManager::instance().navigateToScreen(cfg.targetScreen); + } + } +} diff --git a/main/widgets/RoomCardWidget.hpp b/main/widgets/RoomCardWidget.hpp new file mode 100644 index 0000000..cb737fd --- /dev/null +++ b/main/widgets/RoomCardWidget.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "Widget.hpp" + +class RoomCardWidget : public Widget { +public: + explicit RoomCardWidget(const WidgetConfig& config); + lv_obj_t* create(lv_obj_t* parent) override; + void applyStyle() override; + void onKnxValue(float value) override; // Temperature update + void onKnxSwitch(bool value) override; // Not used directly + + // Sub-button specific handling + void onSubButtonStatus(uint8_t index, bool value); + void sendSubButtonToggle(uint8_t index); + +private: + // Central bubble elements + lv_obj_t* bubble_ = nullptr; + lv_obj_t* roomIcon_ = nullptr; + lv_obj_t* roomLabel_ = nullptr; + lv_obj_t* tempLabel_ = nullptr; + + // Sub-button elements + lv_obj_t* subButtonObjs_[MAX_SUBBUTTONS] = {}; + lv_obj_t* subButtonIcons_[MAX_SUBBUTTONS] = {}; + bool subButtonStates_[MAX_SUBBUTTONS] = {}; + + // Cached config values + char roomName_[MAX_TEXT_LEN] = {0}; + char tempFormat_[MAX_TEXT_LEN] = {0}; + + // Layout helpers + void parseText(); + void createCentralBubble(); + void createSubButtons(); + void updateSubButtonColor(uint8_t index); + void updateTemperature(float value); + + // Geometry calculations + void calculateSubButtonPosition(SubButtonPosition pos, int16_t& x, int16_t& y); + + // Event handlers + static void bubbleClickCallback(lv_event_t* e); + static void subButtonClickCallback(lv_event_t* e); + + // UTF-8 encoding helper + static int encodeUtf8(uint32_t codepoint, char* buf); +}; diff --git a/main/widgets/Widget.cpp b/main/widgets/Widget.cpp index b5853c7..0d02131 100644 --- a/main/widgets/Widget.cpp +++ b/main/widgets/Widget.cpp @@ -26,8 +26,22 @@ void Widget::applyStyle() { applyCommonStyle(); } -void Widget::onKnxValue(float /*value*/) { - // Default: do nothing +void Widget::onKnxValue(float value) { + cachedPrimaryValue_ = value; + hasCachedPrimary_ = true; + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); +} + +void Widget::onKnxValue2(float value) { + cachedSecondaryValue_ = value; + hasCachedSecondary_ = true; + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); +} + +void Widget::onKnxValue3(float value) { + cachedTertiaryValue_ = value; + hasCachedTertiary_ = true; + evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_); } void Widget::onKnxSwitch(bool /*value*/) { @@ -46,6 +60,107 @@ void Widget::onHistoryUpdate() { // Default: do nothing } +bool Widget::evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) { + if (config_.conditionCount == 0) return false; + + const StyleCondition* bestMatch = nullptr; + uint8_t bestPriority = 255; + + for (uint8_t i = 0; i < config_.conditionCount && i < MAX_CONDITIONS; ++i) { + const StyleCondition& cond = config_.conditions[i]; + if (!cond.enabled) continue; + + // Get the value to check based on source + float checkValue = 0.0f; + bool hasValue = false; + switch (cond.source) { + case ConditionSource::PRIMARY: + checkValue = primaryValue; + hasValue = hasCachedPrimary_; + break; + case ConditionSource::SECONDARY: + checkValue = secondaryValue; + hasValue = hasCachedSecondary_; + break; + case ConditionSource::TERTIARY: + checkValue = tertiaryValue; + hasValue = hasCachedTertiary_; + break; + } + + if (!hasValue) continue; + + // Evaluate condition + bool matches = false; + switch (cond.op) { + case ConditionOp::LESS: + matches = checkValue < cond.threshold; + break; + case ConditionOp::LESS_EQUAL: + matches = checkValue <= cond.threshold; + break; + case ConditionOp::EQUAL: + matches = checkValue == cond.threshold; + break; + case ConditionOp::GREATER_EQUAL: + matches = checkValue >= cond.threshold; + break; + case ConditionOp::GREATER: + matches = checkValue > cond.threshold; + break; + case ConditionOp::NOT_EQUAL: + matches = checkValue != cond.threshold; + break; + } + + if (matches && cond.priority < bestPriority) { + bestMatch = &cond; + bestPriority = cond.priority; + } + } + + if (bestMatch) { + applyConditionStyle(bestMatch->style); + return true; + } + + return false; +} + +void Widget::applyConditionStyle(const ConditionStyle& style) { + if (obj_ == nullptr) return; + + // Handle hide flag + if (style.flags & ConditionStyle::FLAG_HIDE) { + lv_obj_add_flag(obj_, LV_OBJ_FLAG_HIDDEN); + return; + } else { + lv_obj_clear_flag(obj_, LV_OBJ_FLAG_HIDDEN); + } + + // Apply text color if flag set + if (style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) { + lv_obj_set_style_text_color(obj_, lv_color_make( + style.textColor.r, style.textColor.g, style.textColor.b), 0); + } + + // Apply background color if flag set + if (style.flags & ConditionStyle::FLAG_USE_BG_COLOR) { + lv_obj_set_style_bg_color(obj_, lv_color_make( + style.bgColor.r, style.bgColor.g, style.bgColor.b), 0); + } + + // Apply background opacity if flag set + if (style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) { + lv_obj_set_style_bg_opa(obj_, style.bgOpacity, 0); + } + + // Icon changes are handled by subclasses that have icons + if (style.iconCodepoint != 0) { + currentConditionIcon_ = style.iconCodepoint; + } +} + void Widget::applyCommonStyle() { if (obj_ == nullptr) return; diff --git a/main/widgets/Widget.hpp b/main/widgets/Widget.hpp index 8d8b706..abbb1ea 100644 --- a/main/widgets/Widget.hpp +++ b/main/widgets/Widget.hpp @@ -26,9 +26,13 @@ public: // KNX group address for read binding uint16_t getKnxAddress() const { return config_.knxAddress; } + uint16_t getKnxAddress2() const { return config_.knxAddress2; } + uint16_t getKnxAddress3() const { return config_.knxAddress3; } // TextSource for KNX callback filtering TextSource getTextSource() const { return config_.textSource; } + TextSource getTextSource2() const { return config_.textSource2; } + TextSource getTextSource3() const { return config_.textSource3; } // Widget type WidgetType getType() const { return config_.type; } @@ -44,17 +48,39 @@ public: // KNX callbacks - default implementations do nothing virtual void onKnxValue(float value); + virtual void onKnxValue2(float value); // For secondary address (left) + virtual void onKnxValue3(float value); // For tertiary address (right) virtual void onKnxSwitch(bool value); virtual void onKnxText(const char* text); virtual void onKnxTime(const struct tm& value, TextSource source); virtual void onHistoryUpdate(); + // Condition evaluation - returns true if style was changed + virtual bool evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue); + + // Get cached values for condition evaluation + float getCachedPrimaryValue() const { return cachedPrimaryValue_; } + float getCachedSecondaryValue() const { return cachedSecondaryValue_; } + float getCachedTertiaryValue() const { return cachedTertiaryValue_; } + protected: // Common style helper functions void applyCommonStyle(); void applyShadowStyle(); + void applyConditionStyle(const ConditionStyle& style); static const lv_font_t* getFontBySize(uint8_t sizeIndex); const WidgetConfig& config_; lv_obj_t* obj_ = nullptr; + + // Cached values for condition evaluation + float cachedPrimaryValue_ = 0.0f; + float cachedSecondaryValue_ = 0.0f; + float cachedTertiaryValue_ = 0.0f; + bool hasCachedPrimary_ = false; + bool hasCachedSecondary_ = false; + bool hasCachedTertiary_ = false; + + // Current applied condition (for detecting changes) + uint32_t currentConditionIcon_ = 0; }; diff --git a/main/widgets/WidgetFactory.cpp b/main/widgets/WidgetFactory.cpp index ccb4945..9ded5dd 100644 --- a/main/widgets/WidgetFactory.cpp +++ b/main/widgets/WidgetFactory.cpp @@ -10,6 +10,7 @@ #include "PowerLinkWidget.hpp" #include "ChartWidget.hpp" #include "ClockWidget.hpp" +#include "RoomCardWidget.hpp" std::unique_ptr WidgetFactory::create(const WidgetConfig& config) { if (!config.visible) return nullptr; @@ -37,6 +38,8 @@ std::unique_ptr WidgetFactory::create(const WidgetConfig& config) { return std::make_unique(config); case WidgetType::CLOCK: return std::make_unique(config); + case WidgetType::ROOMCARD: + return std::make_unique(config); default: return nullptr; } diff --git a/sdkconfig b/sdkconfig index 310db79..bfb155c 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1485,7 +1485,7 @@ CONFIG_SPIRAM_USE_MALLOC=y CONFIG_SPIRAM_MEMTEST=y CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 # CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set -CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=100000 +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=90000 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y # CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set # end of PSRAM config @@ -3169,7 +3169,10 @@ CONFIG_LV_USE_GRID=y # CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=0 # CONFIG_LV_USE_FS_STDIO is not set -# CONFIG_LV_USE_FS_POSIX is not set +CONFIG_LV_USE_FS_POSIX=y +CONFIG_LV_FS_POSIX_LETTER=83 +CONFIG_LV_FS_POSIX_PATH="" +CONFIG_LV_FS_POSIX_CACHE_SIZE=0 # CONFIG_LV_USE_FS_WIN32 is not set # CONFIG_LV_USE_FS_FATFS is not set # CONFIG_LV_USE_FS_MEMFS is not set @@ -3181,7 +3184,7 @@ CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=0 # CONFIG_LV_USE_LODEPNG is not set # CONFIG_LV_USE_LIBPNG is not set # CONFIG_LV_USE_BMP is not set -# CONFIG_LV_USE_TJPGD is not set +CONFIG_LV_USE_TJPGD=y # CONFIG_LV_USE_LIBJPEG_TURBO is not set # CONFIG_LV_USE_GIF is not set # CONFIG_LV_BIN_DECODER_RAM_LOAD is not set diff --git a/web-interface/src/App.vue b/web-interface/src/App.vue index 50d0b35..122096e 100644 --- a/web-interface/src/App.vue +++ b/web-interface/src/App.vue @@ -1,6 +1,6 @@ @@ -19,11 +20,13 @@ import SidebarRight from './components/SidebarRight.vue'; import CanvasArea from './components/CanvasArea.vue'; import SettingsModal from './components/SettingsModal.vue'; import ScreenSettingsModal from './components/ScreenSettingsModal.vue'; +import FileManager from './components/FileManager.vue'; import { useEditorStore } from './stores/editor'; const store = useEditorStore(); const showSettings = ref(false); const showScreenSettings = ref(false); +const showFiles = ref(false); onMounted(() => { store.loadConfig(); diff --git a/web-interface/src/components/CanvasArea.vue b/web-interface/src/components/CanvasArea.vue index 59ece4b..87eb246 100644 --- a/web-interface/src/components/CanvasArea.vue +++ b/web-interface/src/components/CanvasArea.vue @@ -90,11 +90,37 @@ const canvasH = computed(() => { return DISPLAY_H; }); -const canvasStyle = computed(() => ({ - width: `${canvasW.value * store.canvasScale}px`, - height: `${canvasH.value * store.canvasScale}px`, - backgroundColor: store.activeScreen?.bgColor || '#1A1A2E' -})); +const canvasStyle = computed(() => { + const style = { + width: `${canvasW.value * store.canvasScale}px`, + height: `${canvasH.value * store.canvasScale}px`, + backgroundColor: store.activeScreen?.bgColor || '#1A1A2E' + }; + + // Add background image if set + const bgImage = store.activeScreen?.bgImage; + if (bgImage) { + style.backgroundImage = `url(${bgImage})`; + style.backgroundPosition = 'center'; + + const mode = store.activeScreen?.bgImageMode || 1; + if (mode === 1) { + // Stretch + style.backgroundSize = '100% 100%'; + style.backgroundRepeat = 'no-repeat'; + } else if (mode === 2) { + // Center (original size) + style.backgroundSize = 'auto'; + style.backgroundRepeat = 'no-repeat'; + } else if (mode === 3) { + // Tile + style.backgroundSize = 'auto'; + style.backgroundRepeat = 'repeat'; + } + } + + return style; +}); const gridStyle = computed(() => ({ backgroundImage: 'linear-gradient(to right, rgba(255, 255, 255, 0.1) 1px, transparent 1px), linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 1px, transparent 1px)', @@ -256,7 +282,7 @@ function resizeDrag(e) { let newW = Math.round(rawW); let newH = Math.round(rawH); - if (w.type === WIDGET_TYPES.LED || w.type === WIDGET_TYPES.POWERNODE) { + if (w.type === WIDGET_TYPES.LED || w.type === WIDGET_TYPES.POWERNODE || w.type === WIDGET_TYPES.ROOMCARD) { const maxSize = Math.min(maxW, maxH); const size = clamp(Math.max(newW, newH), minSize.w, maxSize); newW = size; diff --git a/web-interface/src/components/FileManager.vue b/web-interface/src/components/FileManager.vue new file mode 100644 index 0000000..f3ae236 --- /dev/null +++ b/web-interface/src/components/FileManager.vue @@ -0,0 +1,477 @@ + + + diff --git a/web-interface/src/components/ScreenSettingsModal.vue b/web-interface/src/components/ScreenSettingsModal.vue index f111747..95982c8 100644 --- a/web-interface/src/components/ScreenSettingsModal.vue +++ b/web-interface/src/components/ScreenSettingsModal.vue @@ -25,6 +25,42 @@ + +
+
Hintergrundbild
+
+
+ Kein Bild +
+
+ + +
+
+
+ + +
+
+
Modal
@@ -46,18 +82,38 @@
+ + +