From 009dd8dcb3af323ced22bb395cd288c3f657476a Mon Sep 17 00:00:00 2001 From: klemek Date: Mon, 2 Nov 2026 00:41:59 +0100 Subject: [PATCH] refactor: cleaner python project --- .gitignore | 2 + 2026-10-28_21-38.png | Bin 73469 -> 0 bytes Makefile | 4 +- __main__.py | 6 + host_generate.py | 106 ---------- i2c.py | 190 ------------------ pyproject.toml | 14 +- .../Px437_IBM_BIOS.ttf | Bin .../TinyAndChunkyRegular.ttf | Bin .../background.png | Bin i2c_loading.png => resources/loading.png | Bin resources/mock_data.txt | 130 ++++++++++++ .../append_to_file.sh | 0 line_in_file.sh => scripts/line_in_file.sh | 0 src/forge_invar/__init__.py | 40 ++++ src/forge_invar/constants.py | 37 ++++ src/forge_invar/display.py | 167 +++++++++++++++ src/forge_invar/enums.py | 8 + src/forge_invar/forge_data.py | 132 ++++++++++++ src/forge_invar/i2c.py | 49 +++++ uv.lock | 4 +- 21 files changed, 588 insertions(+), 301 deletions(-) delete mode 100644 2026-10-28_21-38.png create mode 100755 __main__.py delete mode 100644 host_generate.py delete mode 100755 i2c.py rename Px437_IBM_BIOS.ttf => resources/Px437_IBM_BIOS.ttf (100%) rename TinyAndChunkyRegular.ttf => resources/TinyAndChunkyRegular.ttf (100%) rename i2c_background.png => resources/background.png (100%) rename i2c_loading.png => resources/loading.png (100%) create mode 100644 resources/mock_data.txt rename append_to_file.sh => scripts/append_to_file.sh (100%) rename line_in_file.sh => scripts/line_in_file.sh (100%) create mode 100644 src/forge_invar/__init__.py create mode 100644 src/forge_invar/constants.py create mode 100644 src/forge_invar/display.py create mode 100644 src/forge_invar/enums.py create mode 100644 src/forge_invar/forge_data.py create mode 100644 src/forge_invar/i2c.py diff --git a/.gitignore b/.gitignore index fed370c..cea6c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ error.glsl forge_default_save.txt build tmp* +debug.png +forge_default_save.txt.bck diff --git a/2026-10-28_21-38.png b/2026-10-28_21-38.png deleted file mode 100644 index 87256a4684a766978d67ca2366600355173faa8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73469 zcma%j2|Sc*8@KjFIFVGg6xpfl*(zmU%0AXq$Zl+dF-8kQ_O0xWEygYkV<~Hh!9-!0 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࿇kFNg4xJvf}ht+4Fz)LS*gc9Ot##${9v+6zQbL(ZNpjs+V>pj-5eF-G# zWrUBj5+(T?$<-pVTt)DhP;(0%_KUCONLEksoG&>5q&xhdVeeYNCE-|a7pNJAxMgM1 zZ&{rSzl&>qYAjHxY3YvZto8rDxLsFByGKs_$Zwb+`LkV8$cri!Ypi6L9hIZPM3knq zj2As>lgjM4LE1r%wf0f?S~?xSUFLK{KXaFH+Au2-Tsymw0`~DXZelf2p48)09L&)5 zwM4=}1A26|TOmb&Le8BB{v{^zdl)jVC<69B`D+9MO!$(hmeH}q1%J#zVmZK*b+~5J1PxF0!3fSLzRf| zg)AlSBDQgx{Nc?~?{v-;#r6ms)IiLLI+V-X}arnT8HC#Cn zdCliS_OM81g#ngAxoe~uN8;*3z}`^TcC)%GG^&I`gC-lz+x9y@WVlB)N4D^H-X}MU zt17VdOGWjZpL8&j=l>HfgFwH77)fvNLQTh2y-^_~I9k>){GE;-h1ogUCfOU+ z9O0(e%bogcBZ-W9nap?P9~>UOB4hGF!*Fd z?`GUfF>i>GP3pZs_>~Pa5BHLzA{RF1yh2Z*PmLhznom&B#GU#v9D21+T`}QHfQCFB zgF|`;!2y8wb;o*TCRA+mE%~$RB@RhuT+xt#!uP#oJj$XkqH^Oi-_k_|4E_;az z@ujolg8%j&Q}QI>q08+xPosNNJ8Z&epzHfC-I(;X#q4DG8sF;k4zlEWxAGe$+xh2y zP5Qn_q-}4QP^Sn@>5yn*%<7pe{iA1v-BlPn8|`c&$`=f1EWQJ^B;0%jZO?6>5Yd^} zlHtoTXha48Xq zv$2dia$L~;ufJ4(FTT#+l69BWIl@1w;_DLEUbwmdJ}bK61peEp#@hSh?CYXc(Q5`D;_Ni{#c0eZ-9ZzxabE=Tpz*9Hz%t0fw0+8Dmq&sa9WJ$`- zbrgkLSS#Z-Mn89}t?IlpwO^dT`nQf`intg5?#Tpzo^C#sc*DxVRJ~55unPo5a<@2P z*u@&y+u+rgnyOYLN!oq@1!rAET$~p0l{jeC^;Mji_1%w&Yq+O$Qj z7joxT{V=m6T6#IR_I^0i&*CJ)ic4lhvt%nYjJcUCbkj;`h1%R}sQk_4swu?I7|L23 zLB=gUqCiw9D@ zs|U!u^$*?gsE05lE}@JIHf$~9#JlU`l&!4Is*n?s=Lmja@}8&w+mqKkq{{#=;VoOP z;xo`&yrh^y<;n;C!7 zP$ZL(dh|0G1Tbm>k9+tLPQ(ZJzgvB<1>qJxS~1|R>W5}yuo?D>pdgXVwD0A`1T=>7 z)kuVX%z{^*oa)~@?Ok9u@8kxicF>+lITs^aW^#|d4Tu?L_X_m8u1Ar;Gq+@5!(OHgG0XbKFo?uygXsDeb$CO zn;(1GGTk173lVeq_<|581}~wEw~}g#YU`vgA^WQM6I{XbK(JBs6$PpM&Dq*o7Mrs% z6x%xF>UeJda8N@Ott|PE18Ve7sKDHgIN-Kn@53y#LjTte4a7jYJeb8|{9m(7om#@A z;%MEmHR>m^Ee660H%t3Q(P~>NmlKALn~LW~4jW|lGhjv*FO8XRBeKtR2?Th{1PuaC zjpyq>zAas7&v(hzWqq}nTcWLXE@&e6KJcvL+NuDI?sb=-B37z6_`FCvi|gLggDR)u z%zan%{PaxlZ=9omn8bZZ3Sw^9<>e(Hpe_s15@;LEbG4WsJ!Y@`3D^TIc`SqR#)ufk zy0GNAa?*R#g%B|ZkIG#R(YqRf4P zU4S@m-AL1&r(rue1Es9)2NiiBr!gi@rEK9B96qJ1Z)qbuHqWYach{-CjEJ(yEu!qPh@K65pmRTtPCEdXW?v+mzX(;eH z>R{bHB(Nj)0g+MOdY(pZLcT3wwIO7dcYEbkAsp*C0#_f)b;zQR2dHYrs=rVwI*DNO z`tadys~=r??4_P}_PNi?xNsscUD4waf)O%8TamkXa5nybEws1H@~0%#K5Dx3WHdpn zs(UV7E7&w|BI6pex$8+gNMSv5)vmlYUS6dWFaBUYh z7QJcv43C#b*F`C9ZX_x|Fwu;_>R9m~6e$PL{aB;;l%*sg;W?_)X(wrr8r)m zGj0h+r{{0prq~BDwGAh}qkGIHmlb7GRU>y|&zMJHx}ajt)=$?Ck861qUg!BEqNshWwIz=-lz) z>{qoEJ-! z8@^8#(z)Ui7`0{&8bu4JCI?FDOPp7*eN8)C^0k7SN}URE&vF&Vp=wp_9Cr@nS?VNA zWuPGJ7aK#e4c0uGTQL4i54LBIke{GaAu2DO(2x((el5rIX!4m7?w2xR21th_xjvA= zy}e3H1A=|$L4^`?`*Fl(Bdp3*z_H}F!6t@o#NJtN5(sHwDXbrh#UlGq46Ll|q*0*}7ZqJb;GRDVvCSUMm{+ zkXjG@cP0Lak!R@WnU%l)lP*0yNrazFL5}d@5R`;0e~GSgts#fO3r&J@8U{K6`*wl0 zmsfhm6)x#vn$m6MX6k>_)!5l}qCQTUj-c&l(Iu^CCtb!wzEzMobYJkS!3cZ<(k$>h zt-XNARb9ST{KujJ3ikShMjn^V(o+viQ9edf(GUCrlz&<1Y8LEMmJMwnu0cUHYAMR; zK)XVpk}KW*h&&;i4QNv5wI8IL8S(PGxGqzCR$R=?Qe^E%m%(xtYl&4|>Ol8Lv^MwO zE+Fy8uV2xC?cxBbfaEd1S)6*pv!t1`j$%~c0%^;Fjv?CQ{EBxY2y$s>9V{NcKua*pj6m^?OJ!X8UvIdZzRlV`NRuWHN{n3 zUEP*4YYn{nyLtHda?JR#)7W?OmX&lbt4i$(02>@(Gq2&K3!mQ;g#!+y9_r!%F2vI~ zKyol=m5Ga=Cd673U>9pyEyem``s2XX{)%%K+w8=!e{a2kF7XBR4*5Q=u+Z%1s?Gb0 z(VGgI^UK3Sh2OFv|CCK@J!fdX=0@*sc`a{-(Cd~izW147>M)YP-46!OB?7YPA; z35G=Fm9H1S-+YtJ_C-SXqba65TKju7T3=xB)+Y^M3$bR*jZ7Lva+({l0r#cEfU;58 zq!_K7MwU<-4k$vLFLeIc?wdUAl5MWod_AuXziSOkL!y<{Wc|&ztOxR88!SgEwc5`1 zr`IU>8`I!C$LsAy468D^Vfqncu8;_Sw6QQe{%W+-=`vD}!RsYXKND>Hbj9pQI?i!$RQLf)}%{?S*rvk*_pDf*fXVCjmA+iYU-bX18<%1%weag<<>27;evgug)V zut@gAOSs`HVa)hg3p)5@=V)f1w5er#vRSN@hjFtR zNwvIiIztg{!Yyjowc(F^;nc#N)9+P5&zf27aTe_g@sYEp#s50GQONkRX}hbvz0uME zo0OZb)e2=Ymoz{nLSN$0bsK;9pIz{WLt5j_w`eW)sQF{%ZtnL#JsPq{-U#h|VsSzh zyRQ!-V0MQInKUHHUr~w8vPE_TcTo9m`eBt!E-g6}y4boWZS>18?7I=Nu>m zNYv@aDn)iz6prQj9+U#F+lS+{0B)`fB6IOyIXCojH^=DFBml>{*X zo4{AeKMHvtFsh#jZ^fVHzUHMr0~rp}JV)np3Rf0w%Am8^By>q6sbYQuIOg%QB8T!t z$+6%I+BTHl$s2~pQcfQyW5Hv`ZV{}a8VfPBHnEc*_Ek5XG7?}`4SCbl0rvc8ku5a5 zSW)(``{4Dj%q63hwdQRKNUze3KM#DR@q29$I^?_H?W(~qsZo5VP4Bf9$1~}2sBLdD zGx+%*F5?^@&#X_WL)sc``GZqhKHz(2Gz_!{p8FM zkuIna5`THvmJ-}AbZT()nehK&@|*X8D#;+wZe#QMuTRqrskG5F9;WJMzC9Heq&DjA zni})a@uaxh2ARzH9Ltv$RMrUmY#C1J>Fi|~q_?t*DXM=ZzGCAFJ zkJlZdU%(H8c{PZHd?$MUCYp^%FfP#BhF`W{K>63R>3W@oFP6N_i5ZZYQzr{sXAQ0F zPFW`yORVFsc14u$HgWTUA$5J9BpW(#OLbWp8m%fx`4=$s4rSvNt z?KJ`s^Y}Ic(==f0u9A4~KVb^z#f=EytQzGtgbil`j2nglYNe|J!a?TI#nh+jba2@- zBRmkNU_^X&^h>;HY@w51b#C51ilN=UQ7DEGs=SAgS> zVog?N2_JL;o6hG`IRI3qmGel4NA-$2Bed#U=K4ojsM)e4Chlfc zV|SNs?fIH{b3J(to?bX+lq3b*#bvYRpkhUu zi=wY0EURpz&6?@93M}$foaI-vB(6%~%!k~HLF}>(&6|uc$M4iS*(V8kN}>@js$_p} zn8p{*CzMQ8TJP3zrJaf40NjbRd?Z=nAOFM0siJ2tq?gx_beQ{GEorInH1r6cdux~Dkx381VMGG*Ffu%Wp8e)+(!p4>i@N?6R>H}CX$lfEfvTTXQ#DbEh!035cT~^^O#72JQ>-+x{HpZlt2#1f=I~ ztnAF`n6L@nC8~hsMJ-sbmhKBPz5?PYH3Vr}&0+OSUR%GZnO1Tvibx1uym{?oPdavkZmGY2_0gT~|% zA?`%}TXcOStsBXNGB5tGLIX-HIbXFFEV|y@q?UJRQizlZ;Hw!kxRnSdaFI?al*WZr ze%POVF_bH^v6K*hyY%N|nUsL&9DmlV87Chcv_xp|ZrGh5>`g+vbKj4~wW6(F!I?!jHZ~#&g}7G$ z`G@+N$i?Z8*#cm8=D_D=Dhf3in7IB=X40Q;!wH`I5Qy+ChD=cE@F91=37c(x5ug}6 zb--r*%)K7R=ucqU`S^Bp&I-sU=$`IhP7HbORL9K}YyRh$&}++(KoffCHJ`j2b8WcB z8bFUgPF> z51lZ&Av+6Uci`EG;(dN)tyd3>pYkf|kXFuVcJKdpM5`7wULEdGw9O z{}u)#XE~I8xs3EbW;L)X_}R?vce4+?Z^W*Mf~Jx?e2nu`r~AuPh>W=^#hRJW;iwu= zJZdW4T`0Bv1(0}Ilk7b!eh15LbVtwEl14Ti{6v%SM+D^dE$sh937l^%u3E!iPSF}^ zowZZvDviy&#E%kyDrsvA_=WdW{x##FsX??7Zp5X~LA;BwWq^4AD)Mc>Gg<2;SX#*XLzzVQ=NN zKlU&mr=v36?lyLX@!K^5@xHp966Wtkl2;45>A;>mtIU?U>CXy28}CcChLL%0qnrGB zhA*H*Qp0zm&6w+ojY2^%t;;4u@)xi=>`qp5)wwd#H)VkbImpVlI_OPOc zG(I)j+wW87Q^|UuTXJSx+Ydf(&hGptZ~|dFB?FD_1P@(~YAiPaazs~) zCjFs#5;e1ae^Y`M8;()K%#-|;GT7>8iDaEiU}gfAvtB3w8NeFqCQVO6>sAPgwHYc* z9496fY>4Gu#Z|AE#{Qx2b;4?xWHTurd;PEGCn{K&!7r&H)_=NZ!U=ogRzO~3Jn^PN zTXasmi=_tne*LHX)Y-&-3`%EzicD^bqWs!X21wP)VGtc!wAL(O<}Kv9>$>5Hmv#QE>@!?`{x$ri z6UlX9F~(Z9_oL>|Z${eb$ySqVfXH#(4=Vp844*&61K&Q{pE>y*%ZxW5Ea*NbF_(H9 zEY(w4KR6#W)p2;&ZgIfc@OzI!t`^sa&>TJfiXECKc*jWC^`*c3NFPO=AA7`H7F|s;AD@=K&K?T?OJJ{y? z=LiGmgCnM)i}>|@gMs#_?8e{XMxeMT1+?Ih_uC6*e|$m z$wmJuLM<_Aic3&Xsn}3&C1V%-@af;@dtBTsU7(y(E2H#qto*tG8zcYFSV|9 zm+<%%vOS(-@4(BwF^@F)o0L&*FLRGyo{vf*cV6U;ne#zSa*V6{?e|;|{GIKFY)Svt z%Y=jT8LF@~BH32=>r@KNo?bBYpaZdHB#Va0UEu&~w{7?48~daQSX~O#amFu2-Sr&w z%N5Ib_QQOlO!j>W%Y21(SIbbKbK<>maVQb*g#K9WBjA+rn7DNYCf*p?;Q7K?=m}tT z2hv6p$VG+B$lTBd_$>@8EFRh#H2vVXD0=G<;n^RUqny)QohA^lc{IJxeyBWTcdP2- z)s>S5?+khjY};fc}jGJq*7x_*RF{Tr~Ai9s--{igQ)~@(~Lpr zhrtm69^F7F2D^bzPxWta&H}NAk^2VI(lDne30Cf6W5OoGPqPOpCFb{cLgdKFvqR<;Pj=SQ zJVTao)7l>IvJL=Uuz0#c95V+xov|-mM_+jT^T&W4g7FxBqYM*rq9NlM9lVr%2Z!O@ z5*DS|lDT{B=rH1Yo8wI}Yr2O&Yl(1S%QS;5$VRVzH9u(~#BXJEif8@qn#}Nv-$qwZ z7gOj%4Zt((E73J_zmt!A9EAr?)ChyLV{-=0tuZU0oyoC=by_+q^lYLBnf%TXw;dIf z4d~9yRTV{UgD5d%AF4h%`m7qQ;DA7-15mqjGLs?BE%E`n0&()*Zc+^! z7<*gtX%TiDPXIqkXUG3L-%aE-kL}|k8c*dr!)0B!aas8jSeOO^RSfr7;kPkYFoadl z)NdtG`s@FH|CCbSLlyYP43=yLTaqUG{}pxB0Zp!Le-j5qN{7@)L15H(KRf5V-}&b~@6l~{T-WuB>wfO%$Q$vp zLv)X^K7HCebh`UtO=_K4?n&y-YsJIql`+vi-1U%a9Jjertp4I!%;D{h)5q-@rqhdj z^{B?k-)^oahsOu&dOyTN_j9Ws?|z+;q+tc$^mQm>Me*d1A~NG@+1EG2EN+qlE)!86R^Ghrzv-r2@4V zTW#Ar-}>CH_0lu{D4(VnBEI1&)uQ6M!zUvLnFI@)FD*P;OiDNKQ!A%q$Cq60&e@w{ zxR_L~P3(vN{hn*EIJ_-AucL>n(bHR7AE{(nfA+-h(SH~%v&18J06lozjSJ-=d3?vGI$~^S|cQdxFY@ z`e92*rA@Z0H#4Xy+GUYTyw%S2Za9vn@=nOM^yeXIE1I^UXyg=wj~hCtMv>X4mf-`kWV+7ij{tJLVgEM^vF-)j(WUJfudFaG`{dyu z;EMIH{BU{A0sidfo%@vnz6#?%I`(P|zbfRk_AdR+>APc!liSqH6F9fJGE!o7^WqA0 zE<6Bf#HKG(Q(`(+uIJ#Ew3Z?8K{bP0c$~)3{blU1$atqCx9=CPuZxMRlPktrNHJ!eVO%76Ol5{| zTN^;bZx$Lle_7I!@!rfc<4eB^?r6hA^msr1x&HRUw~Uj+h3$pq9OQ~h*k6m?_td*n z;1SxYCE>54>$*V6 z!h}qt0JuMG!dL6-G2iM+PjzE<&3T)%(7R$wMV#N)$W7FF(K*Z*Ki@nc?5r%&iPDxR zP1YZ8Z|_eXee=#x^na{TOB?rIA#V@dLC(#vwkG3gIWN$sn-j^QSX{xU)tH{Bz^A}{ zoF6LY{f?)BgXZ!a2J7tm2Qx5o4tn}aspnUvL#-~61uXtl=eb=Qow^RMorY*yt(j*z*3+-#)_KRg22@@vy zohgXb*RO02T9|8XdIn0%mx&K37!-yuqp5Ki5z0uGT+S52p1TK6-fmNx!ivQYc-rK5 z!SvCQIxb&oMHRmm#XE4)u-*EIZ@-dMEJQ;5Cx3RQCOTm(haQH-2CJ+NVkp9#7h(?5 z67Ei<3{5NuQ3`b~@1eo1BE@W{psPR~1Hrja1D4&HnoMqa-Zjz`k(;tKhHi!gIa71D zq+v>LGr|}XiT`Fa8CuTi=9{qly6H!RhHNSMS?eFUiBeBpH+ra79zN=%8(R3}(#}Ek z4yneZY9=LY**}?ES6HCD>^x`8>i|;h{e0Rm7gSqg#LZj<%OZfgACqI58Uw>70zH80b&_8dqGQIJ5r)20lHCH6Kka~Cj zUS&G?UvP%IQDI9gH@Y3}&0d^mtT1o8sc~a-q{Qst$_%*ABgq%9cr>Kw)_u4)8##V@ zWOdgMQ@FM}?VdP3HU4;I*YIS=&ybp9=kMw-&yF}})`-V#qPcjEmT=-8QSyGp8;lA_^7!x{?w*w>(B7=m&@xBlq>AK#3&V_X{>JCbHk%8(hCp^}WiY zIEQz!SckBDkyTxBcJQz266t#DpLrK8!TUoCMdHbdzJ%@LqXSR=7aP2<(0QQxT>jRc zw|yt+wFO-F>&*6x>WIZ^2LJZ79ltP!(Is4=1`iLtSYV4N6-;V8_Z_;#XE10)c9&*y zch;_E?u9p%PZx!3Rr(MP>}9y&{B6uksJ>l4=fW#Ro!*J{Xlf$E3a7 zJ@8a8V`H15{m{pk9_tfsUNeqK;IVc8wBo-<2fF~pPw0%a$fm)qH{F8;$!)j-n;q>0 z`(1xCZzkz^|3(LIc@BjaZ}7X|%`~454hEmGlME>y@69@X29M(u``{h<+g;_E1{c7K zm^S9L6>}{n&%ZwKZD*uegLvMvrCrm{LYZB8%uC4w!u2rA>DHCfk+AGtQ=6`Z9>EXZheo`ZPvtL(y zCxVgyUbS2KEut@G@Ia1$+>HYD4SJ2x(FigHt>|~iIC(Darh*n*H5)4H$(Ph?Y`k2p zY5$!CKo2ti(0sNN*Gue+Opy#7JX?*5DP~XYxY%g#v-@BiJ+a^`w8YJePpN3(pOCq+ z8k*gwUV)vLO8CqoAd%Cz`TF{`!_C?DWxd~etVx3nuCvX@>j&6{r2vuu8MeBQ`-Obr zatGH83l01JhgGE4trb^Px%&JzY>$jgM?VW>vd(ikOg4aL)=z$F{kb>NdewJi0?vaQ zARV`AZjnV5DOk4qMHWij2wTh8mIO&Nh}l*2t90zT%s9R+Y_;It&CO2YiygJ<@<0BQ zao|9FJboi@@#*CFfXm!V5@11^m{rSo2YgTo1FjR$MBVza+%{~n-jV+S#f zz6TY?RUGf=e@rAsCvCM~=UGb5B!W$ob&IP7f4awg^umDRuG`OClS}EencLIrw&%yp z9Kdbrs;8eLdFUEx@e`2@2%F~BaYq|pn z{?v2}!|xU&-{Dt=*OChf~rDJ{&%M*SV+}36jSa;Kxy_j2G zgo(;mbLqSKTXh4A85d=PltI$*lcU}g$>kDkx(1J& z7&v$_Gd#2TP-6Vj(jWX@M3Gi(%yDX>!gb$g<&z%k%{&Kdh`psDz9-w4uBLUXcdoZb za^zifIceou`ZGARYCIi8xqgB(Vw-B<3QC_Cb9UFZs8Gf)p+Z(_}WICynV#V$lw4 zFP^9ivGN8Sc@tbq`fd`wqm8xX%2FHB{q~_}mI|)>Rjt%oW*g^f-A5Qkv=6->=y>;J z7s5O(+!gU_zLz_a7cc8*$}T0SKwE^V4yRz%RY#1JS;qU%I)I+2NS!Kx2v$um^1imsbz2%P<^pKV2YX1 z8owsmYY=aL-8fcW|Q7`zD}D_8&LiuDwN-z+P1x$7{eP$*wJMlWWtUy`ufJE-+hW>^KC!a z)`EM-m{ivWJ(7hZY6Pp2a`9j%oQht|rYJ{nQO_%yOR$?J% zh1Os)#h0Az$XAFBBD_s?I7`RGw{`v}3O6|JN2aTosV-%$9%QP>V_O;ls@ zGPDwkHob;^*4#nun4?+GNnA0EQqY<_J#IJy?h`coWBUc)kkW8iYHgpNf%Yyq!>mT1 zbS{1$^rB(CU6;vn=i&s?ypJY5NP7h=Zp_fE9}h$jc#zdXE#eyfzI{mW`zAyJlsR6s~pZ<>i{cc#KtUd`}~1 zXLBa>N*?7JAAOf#CGmZ?M3XYg?PtI4!b!bGSZJ8c*y>WzTxt|?&&zmdeXu*baXK1% zX~5-MiL(OJg#=G)pLX(%5{in(wgLk(aAU+;*2Ka+%W8qE`=BN{8Y~SMRt)GSL-EE+ zp4>->+WFm^Qc&56FCL3k@h5D5QfymR7~XXCt=$A!?NQ0@zqj#3Lgg`mfTu`2THvy) zuU=W+8Y*7US^wksQhjLQ34Ahv%{Nv%wCLpXy+f`0UAB@({)^j}P8YZ;Rep+&xX0Gd z6^`GXIyzSW>+{l8N#$z<^xok~`54+Zbm#bf$0FZPnR3^5acae5VfpbNw^%nLO^p{` z^&Fd^pWyDuMohf>Yc0_#(UM*eMZM_RP-k(ry#YojPHTWawAfiOcDN@6_x@tIzH7sF znfBJjziB@-tMgY1ADEWkzA~OmvwHNk)vuIqAoqoPRjKKT>%`V`u;S%5w_63o->8?$ zgu4>P+5X;C$Qz3iy}#xkSN7r|YHUTraMXW!iGf{2oVn~`XQs+7r-??!tPpFciXA!w zHF@fAd(jJ}r(oQBAdkTSQ{ZQWhmwPg(}$^~LdRhz z^{Ay;>&cJ?ecvQxyfcO)L`dkLezxTA z%}+-^cwSTdVuxqdkIC3Du2&1f~E^B_1M`991e0fV#!M+FI{j`9N@&4wg z=<5(6@-3ZeT^C3l-|3V5SR2kerqqM-wgMcuP4BBtNRH<(jt`|9@*y2V!3S8zE?@)S zsAOfITM*7f{P+u)MbSaG<5XL2!Er*w2`)gkY?4cHY0-G|2Jd)`dV!jkUDoYfU!z-fbLF{ze(v?4QRP7NErBOUBpr_T+7&e|Ry0~7#8Rlo-t-hNF?a&QsPq-~ zIH1LgfDwWmFQj3N)UnlG7u~Lop@oX?ki$gX{#xyNE>ER;YQh9HI^n|^W_$OI7kh&( zD&t-1G+OIM3ckUhXwk8y)`3d|HP}Vm?eA=t$6eoiiKbC89K*;f{Ba!mDPff+usMm3 z7&mh{<-kwYdl}y zs%|bsxoJl_|_b;hgvJm(18PuwpU*?C^t=ihk~Mzu8fUh!y2YbaRnu}kB9 zMWh9Mb^4nTYl{24>vV}(UeauJj$T5#Q;#DiZxB+W=`Dc#}OxwU~mULKlT>It0yg-gX|3orG~K55!p;l*B* z%h;b>s$tUhgRJT4D-qyUTfe;`$Rce&?;~q3Bm*~gPMF9ax~lj{mx$9FG;fD8kUQNp zwfhyt+3SrfCt+h`&)yG&%?<|}CcNzv9Fuu&vDcK-F7oBkx@WQR-~)ES!4TGUtB69! z8tgs3oghM{NN*Sbu)H3Idl(nI` zSnQ+*RUU?ETQ@LON_Tb&lPBdwpFN;zB}!g`gnoeD3NY_&yR-uKz?**q;#@7R;qC( z6iG>eCXc_Y2@jX9iKrXMnIBh7NZy$-Qr#Wj7V3J<;JaH>vOUCZonQn2BVkyBf!Ek! zudm}m(6n&GY{)YfcDr%D_9co$kvc(N3b@Egio9KFtn>QuPlodzk;}JN{VEW0v)EJx zl@P-ogKpfiAflnokFdQ22r-}G|Pq=$?CTValRafv_fB&nc#L{ga_<|8xZ@OnE6+@1#gl@Y^{CU$R@#krymznl@%+?a>K`lyl=Jn!2bb`>V&gGC zKwkv^;_bF!RcvBdu?UCN)y+jO8=e$U7^@H7hMbX{Xz=~T!=?UYI|f(KD!JO;@iiJ? zrq&B}4ss;YDU4m}(8+J7z^tgDV*;i3CVh78r$Y2Xn&v{)nsRPmz*+(x`RwjC1<+|u z1p45+eM3bu$wiysHd%qKjbd7R=IOGUp*(+@#?eR7!Arxe9pW6c~bg@FMF>}_iO zw6&q3h&gXU)Huht>^|<$O76(pb6k}obwa+Hcr*5$TaoN;j;?LI1#GyM5ro3?xO7#b78;SR{pJ-)6W#i{LJWrMv!6Q3ng=`Cxs zM=o=tjJ`t3Gwup|XO0_Bc27VNyr7s~*^q4{4IEgl0j*-j#}c0jvtbhqEzt}^R=MqM z%Ll$YUI{Z>P<>vXY|JQj(SMZS*Ys^xx*;Z=8pYo|eAJrf!T-#Q1Qx)jRz3LQ8OxMg zk-xe|e?hNzwB3B$E7v89?{9-HPn#^gmyD2ddhj-0@QGS*&m>;%5=Un2@%a+aj{$pn zcT%_t6hj#7<{MI>O^O;lcDMMPTdYIt2)w;$UB{Z1`=(a#NccfO^ppFnZ^MRO+fXue z$KUEh^r+Llir*eutyB;_;#qy@>BfVe@v_+-i$;PkQxcL;?#r|6Y9D#;iD|CV0DWC{ zc9J`^e~SduudMsn$yU4gb6a+FGjC6~ydJKr(t2IXlkjzB{5@Rvamy`qDE!y;)P;yZ z0kG)PpSDL*7vupkW+9er_;g{<)1jfXvY)`P8h4MBXOp3oSNSodNnl-5pj?(vi}#Q# z^%%K!*6?{(vX-#fCWAPOP-MEt_Rc+?NkSl|KJP4~XS5p^a{JP1Ys|E&`yRi*gNLhY zJ}p+~krU3NFL>z%%Du4rJm7LYeeQSaQeAS$>(!yCzxBs!;4V=XHE?g0WXHzK=BYi$ zBln8vpde`mQWp~H-*Ri?*v5nLbrIiXaFgjtmFtnDx38~;$yQDH;nvpRQO?Y2>RQ&S z+}MDm{eqv_WbsT^^l@&(cW`6mFpG4#DV)IOyWWiFeFf9zWKuzWxki?0sq1I{Kzhlg z^}K4vAC$E3n$^Ai*@pVvrBkpgx8vzkH9Ef{Lf+(-3Cf7oI(oFDc51|_DoN3EoWPzR zy(7wO>0TO~bt(@fhgq>qY&;buqMKAJP6Y_e$M7&NGQ_7h(dRbk0|zpbK3}H(t-aHX zzki1-^yh#zIL27|+MJ$0JWEHu8TC?6XcGC$7a6j=7pBJU!)mzA?&ukj^5pu$*~v0oIf4k4}vkn z@~^6&FXIl_N@VW4X!`1d_^vJ%t`sMheJIds1in(~t)*%+E>`#~n$^%rR4|r1&$&%Z ze=N!PYf8~FM>~0Xvl$$SrN^1t{HV%LM&S^$>y7KlBtc+aD8@Plt|1Ljg|oT z8=341exylkb{|ld=3GN>Ld9#HM>~d5)#8#YEYE- zU{DS^^}?`6nLmPM{XTu!cm`J_6~}o}81@N|2+liHVNb#;4+ULC-)l%GXe@&D&J+&R zDZ3bU`<2t@qKjcMw<=^WR205imap-IRwynCcg~0whU>(O3@bBAY~`qR^_C5JiMxv_yIBx!Fz2+Q5$cXGdKB~AR?UglUuG*)2JPft}_ zvVI6KJLMO?btM#0bsW&>!DR@RM$#kkO?`(VDiUQh%(KUyB`rgTP$%96Et z;r!_B0kTk%)Vt!dXz3x*Irn78i){UlK}*upQHK{l`hQpENHQ=p-YqHs7i|bH`R^3E z8*1#|RMXTwp1SgSLH%&s+oJVP_wFr(WNz)7SjAWmTWyG=or}hu>hFC2;EUbpu`uQg zR;*}3CUg2zP2Ym%#A!<|Myg9k6E=qV8QrE_)6t(kd z`unkxy72Fq|F)IMJT1&BH+tMI9Ge@;!7DDM4aN<-z@U?`k)x|EHj13_NY{=-9b*@I6 zp`ihN;aCnFxWf4S;nTM)y?QALh-_p@atfyXnbCRQnjw_cNSkg1ZmI3Lb6Kec6X*Y%Df4uf-N*8z zff5~&nan^vGDJ&U^WtWe?am1a`l;067at!*XKQNfGJ!vKq2N1V0k5OCS_ICb-Gu6d z;`x3W!u{L`;HurS%}rVOkd(`HmTG1wGCV7yjjv!RawKr4TE$>B-qA)vU}s1$9O^aK zCyvP#w|<7$`=`P5dF3&Gp1Ddk^KX!~wOU6_FxIY*tZnF^TM>UsB2GvYkGl;;&l^I= z0puQrT+y;7EgfD(294%@EZDJfzm+2Wz)?*gsKJ}T81AanLRE1b`ND0V?&RJ2&}rEI zbG7{==5qmh+1ZB&eqZ+IDISEbEwRiajM&I5-y4<^3a;x z52M(zxNU3y396z)KfF`=@Q_9z{G<#>@=uT z^mhz~>B3H@j2XiX3MwzMM!nS~^x<2e^h|kKvfgStWz5ffYha2} zEJ8hQ=#uegFoANVfq)3Gk>(*y5q4+MQJPK-f!bwo5+xUHatGVISV5OkR3c3(kRw!a@hIYQ z7)dj~os-%Cz=)>z9WjBz^?fux;hHG2oJ7NzIcLcP6+z*C_46HK)<}0@P*~Lh zKTP@PlxF28S5P{qZh%VoH|z{H2X$8YjCm6-R@)6(EKWtQ4x_SS^3`UQ5ccqoP`KOc z4%bpd0jK_$9|o%mK1q1|;bWniD04nF;w9aD;RNy$Qj+s5?24q3ZhsO5z%Z)c4lu_s zut8HCXVJGr&l7hLMk_Vc|He-CCy2QtD>^=VOD?HQ`WCJ0(b6nOPYX^4I3n=(PV`IH zb(BcNA~HpqFNY$u;hCPdJ1IkxzYLykMu}>LkVBzk$-_t$s3RCNZEgUcqL(B=L*XUG z_O)TG?XjRk@&0U1G+DH4t=^*e6nMiK;2P{{9L|N|1HiBdNqY!=x^HNajZh zi5a!%-T)HV)cRSpk_~-$sGGd&HKb~NXpl}o<{9R$8+J--CmHgqYMPNGe}l=GDPLN5 zN(E@phif1Bvz-Hr(#6zOeP()?o~q2t#Oa?t36TDAh#To{{Nj8^$%1RXf0MNbK9iL@pn`mm*WI7=%UM*->MH4oXZZ&BG;0f( zt4;Oar!#w5NAC?xNhO)?q}rffv8CObTt4pa<^`FmR1G4iO@{)|2YzOV%>4X5On8+~ zW3Be5|KF{IDe;8-J{lX!6T&R`$n9P@mC6-I~GD9cIag;R?#tlqknfMI8Sv9065gOu36A z`*7oIS$+Xic}x zd3nYhI_X5MPZNOG@ka=g1Eh2=`*05T*STB3U&D6|;RtH#C8S;;)&#Jjs@obDN!~uf z;=S$j$mfX!x9~z3cz`L>Q&U1IPB78sDL7OVtK4>R?4>A!kuDo+Pjj=$J0BYB-Y`z} zwkNW$1uS@GI>>Jy>QX zqqFGYC|vMLB$97=bD?Jb_D+1DQE?L@lw@h_)dd*pU2MibAX1zj+vq*-+G@ZWcG*qi%t+^t z7qky>^v#RIU7bA7cw)I5DVsmen9xBzq+~sh__J~mu^9;cp`M3g zEKKLQ!I;j2(nADLC4Fy;!XE0un-fVZ$sq;9lKxy!pHeE#98QKfb(^d#{}cpJcSGB% zhmFCe!tX-h00kv|I0+d-8bmE+7t`M&RB0R-O@KH#9y|;hY>B=ZE_`>^bs7d^*Mi;= zkQlS|#{`;ZV2=o@EC|GFCd!dS!`)DbJK+_EpBX+gwtNkY>k(%u7fUmLwq9xp+N_Kf zaao%R8CM)% zQHTYeyCMrjjtJv5y%65T9#RY#;Tou|K%DT7AFyc0pXCEO?q=$=?X{j^ouoVA0*%ld zh5#Ca$h{)_U(|;;^Izmk*QEt%p#fqD;Nb@VO6c>IyXs&wdm@%mZhcdO%V~{b5O7(G zMO>K;_;-fQs1C-k&;pTRww}riyQ+j(4mDmd%_&xBcu?9dtL_Jx(^qK2Z$D9?~QZ6{|k_hyAW|XdBRxrsHd&B(;Ed1Q2Y>C}dooP$+ZpGjCjIF)zmnumAVW~?0Zk|rPGve~t$6l;SHFqpNenuFK z2?i_9F{7P&t0^!J29fEUjQ=N-$i6UylgPT{jW|`}k|>5h+DP3gX`Ye0DTvKmU#yiV z13zrfHB0sd$?7Nq4a#V=19m5H!12gPh(((Ih3$qXT{*O@XizMzNdesMKfqmo+ap4s ziu=-Jl191w5z6RHXxSKZt};Xy3JHd_tz8=5{lCS&bySAW#+(+Xq9$gIv@O)6M zU{e>6dI0CBrU1WoJP*pJ_BAHb(zjU`Z5!>fHibW>F#hsHiYO&rZLJP$UIU}5dKt#` zJ>99P8j4pKWvstMD}4mz)?5tskrTAyuz(9=nwV2eGx@{j`XWiU&KCI|vd_g6%5NLKXA@cUe6(P`=~s(|3n z_r3qrw8{dhI&css7WD~?o4t{ zTp8?YGFGkCfG;UM0DRP%v?+OLHlNShJx@p!$0rVat7NW>&fO3|cTd-g(k1 z;sXBsV)LX1P$spUiy>j~NX%2w_kPCr72z)!gCA+^Wi@U<8v74zK^N!vV2h9omlgvB6 zAOpTCfO)Z}C@5keYTmpet!3vI4&isVCWwfD zXXJ=efN7pXYq6d=stU(j-r>S8K9w&f@#K;qA9W+3QHG+e1 zX~F=`LM*v~Sp$&z`V}tnOdI|Ps>|lVO(HbEq{$E;o@=FkZKh6fFUJTRR29C=f_{;r zGX^$w@!JC!9^*}ncDcj|oiZnhbAtldawVilp|erTFk*rF-8E)tMo2mk*v%+yIP=D$ zxxR_pmxKg{@wGWaJJIB!;nwOoV)PeZoedzu87#`QGs#qf2~?Iel~f|wTmXIsup({` z1n2=?1zr}H>sm{Jh~(!>C_FIN{Jz&rEZfOf7^$$i7anMuobv+i!cOx?kj=5$^|P{% zz&gmq2@7<@z&bRo8GfeECu_{Ip-WY#mG6K*f=Rj%<=Wx>MoSW&ZS~Wu5KT&m;u*dr z%FNmWFy2Wvt(s}EC)hOxU7bLUGSd(Sux>{Ixo9Ta>W1MLRhV zO5z1Alu8T_($<}yDZgnL`0pk6r&CpytjX<<352kIh9#aHW*rfCQ2h+pjZKWJ+JS~ zV!C7L&S}w(lFfBkK5YKzHOWM8454OI3lLa6)FAPu=&7vT^{6DoPxJVwSwU73k~U12 z3WN~pCo#t7rDkF-@%q*n%Q#Z;G?K~h&y&|sASI#CzZ~Re9`;dtKb~_I&2Ul8Ky|?U z9<&3n{w%SS%$HrzqVWw_k^;NyY)dq=qcXDCL`;zl?KO(Ebm_LDvodf1-{i>5M4C+( zMxx9`!IGpRp2`_SJ0mD76Eqzmp~ZWK#S8k%rtANW)^@o4fefM(0V=AR2o_C*9M}{I zpuxkU#+OPpj^HIA(aRulL{`;8ZPzp0?z8nQ>my0)k+sK1ZP^0 z2oO(*4*ZpIj9BgKvgjnJG;Yv{)lS)+hc&vccbt||fBBvX1*NtpsA?C&etd1dFA<8^ zIg3usiZ&Sp|EE}kypc5%sy2{11&&>?3W^)TsB*oIy7ii)of7~01D??V@J?3Z;*(pd z!n8AP2kJOIAjxTcuwn8ugXr^b&|q_wWD+#Wl|)q8foSW3kd;lp1K0cjm=<7MG*@QH zh{;1X!axOv9s3^lMM-4eRf`D;>hKP}*fZ}%zuyU+9;tYc};6^hSjCK<6 z)`DEM?3jI7S)^n2lgu7J?n?Rqjrz*jBvZ0mqRGML^yVK^dx}#FmtE)W=2&)`QLVDc zr|UtMHZ(w5XAm&?9_j!*p{IDhDnHd45wHL)*K1(T1K9Sw(2Qbp{TLoVcCSlI0-Jjm3RdS% z-~n)$C?P)qNCq3j_d zhDz?ChHVK+W4a}RTX6Kmh#BR&gDf_WwUtWjY_pnp<%NWV1fM=Z z2y;=%SQA*sbso&nS>OI;gvf{Cw9DW_u=9|YDB}W32;Yq|7@#(R^dKF~#sAH}yN!wYO~HdI03YVq{0zfbq+ zXo?CZ$bAeHeyhTthsy3ho7Nmo)KK&)qRhoKi078JGoIAgTt}cNyzI^+=3|)fSe{E% zv{1r5w`7gd1Y1ls#q6wl6~HV#w6&LCvf-dioaxan(v#|O4rY-qfEPq$K~Vu?zc0$1 zs$tWFv93OS?IRbX{5>a4C}bhA9MF@V$g`AC_p`fOy=Z)GgG{Ti2lCmQNC1V-6PM&? zhFKbFSbKaMPOFx1_Ow3uW11HWEqL!mz)v8-(0FFmq5t>b!7W!ygC+SIsY4| z+w_kZNqwRs`pC>==bJSmH~Vn9lN2LP#Wbc%6do|x2yIvhA1OM@OoNC$*@Kp(ORm33 z0M~yS|LmL5QB2^#8$!*v^x}Z>tZ-->F9e_!pp+4TI)FAi)$T%A$eHFvFn)4AmiyBm zA+pLJ{Qo~s)o-an4z}SXG0mDfiNQ_9pKZ2v5&RBP?_MrB!FFFABIWn4s-H$hVFZ9T zOwzpQ(!pb+Vnu9C^cuc-JoXZ_c11+7c&XD-RERnIf?*MD7|939r)3r=J!-PX3}%iq!Sdrw^tpM|6E4{T{Zn8;9ceV&|A~#}RXAs_t;z6hYmZt= z79)`)mGQLd;E#S>fCC3Gfh^<0P(U3z(eq3BefUg>A;L0Ub&szARU#|kkDC2H1$2In zIB|l*zH9F z{%lbG%zwU(skZbHje}% zW{Mp#K)rIBK5ORhwG~&-q6Tc#@UmKbCQ!VPk=`^(jws+Hnr|v}ken4sO6n0|gi-Ze z1hKN;frnK%yGP9e^_b$dn)+bIHMH$5G{XncTkiqLXmZY0=~tMP&Br!2+wb}aFv~%G zosPSrY7$^ON9>&J^UTWT&0~AcqV>62noaDS{JgxRkBBlFL=y7ZT;U+FlngxFp$wTr z1A1}uSCVWt$|OI!Y8f&A4d`dkBP-CcL)xn>`9b-4Au#Mr1*jTev91CF5=@G|#=lTF zn4|xjC>xhS`+~@6GS4yj0iws!$ulinbcyH(XtOq>93^s2U4!Gmf4zHclF6Y1coo

Z698@+adCmMV9Hh9)FF$AR0b@a>L0Vb{y!?9EeUeX}v8Za;v`f)-hDbTSDCW zwV$6^zo^Ww^Yrk@_5J?na|?o{;t4-KP8iDOKrJm;y2gL@8#sIMkQxDQ&ToAXUWyjb z=qADsj2;LtfZzcUI+^fmq-gopeO+kMz=^&BF2oM_EiW^#s6do1;gD$pyzZ>%;BZRO z?ZuuoVMCAK?6{6$O81fD7dN4a1c6m2>Xxhb!+JB5t3X!_AcYwCBS&&3l?!3I(UhbZ zaERnT2V$C>)P)=wm#WY+HAUQv4Rc*TScMFuAvvPuk?5n_{N(f6a1bs1SCiPXhmt5( zt7^iF=ZuPn`5P@_?ci<2`MIdkkzi<*Em@si+n*y+7NUv?nki4^KO^H(IOiph!˛RY(tenz}-; zNz9f!&%8fW-I}HA@6gLDEAL`-E6@?Z}&Ha*BuRa2{C;HQlwp8 z1mp9xac)*Z?7{+Q-Haok=Gh%fR?xa7fnD+(^Cin*42yR(uo;T*n?Z;)HxM=Nlox0p zW3A{u{LT6x?oru3A5-}&8W$I({`DIDLOO(2Rjg&EME`fDSmhHBYrbMNq~)pX0;zc|Ac?^Gm49t_fMCf!FDbZd>@?Wa z_c$waF4DODL_nL15=tR@oPPJe=26gt-^-SijGs%;gz|x;$tR^CsxI|f@ zU=VGH_~-yrEXW7Q<(;|#5EpccnsLoDZwmqlQnFG|rV*HoV6lh+vcLi1web$HkT&pJ z6o{Z_dTUoeb_e(amRKt;8*n%mQ~4fu4q5au0a~cc+t}O<`dmjPb}!vyV8b{pU;gH? zy+}N9F~vj_zAzjJCd6e}JG6a}R}C~?xCxaqmx;gJuMVJbg7t!Q^rVV&h2m+YQk*Hk z`sr&Dz1dp>0cJnh)RO1VTM7QXAC%=(3{dLnu3Hd7lvpK9IaXe*je zPC$SYA4!*qHS*OJMjo!_Ma<%dZY2?%7%<&X;*Ag)k-Gao(=Bnn>oHca+09xA>S5UC zc2a}n3NSLB71VI2;YsLGsyq-@<~qZ&j{w1Ju9#L032WC-myh)*(A8%FJNv9u3S1_d zo%#TxNvygaZKoDMzCk#{IR7TAW?Y=LXFo`%XpItx4;ai`8R+e}Zx7DJY@JCFCIXDU z`gz%$3DRLnN|s6o77=TI`-8xNmDnit z_&cJHz)}+Z={ew61rCsO#6*BdzQjy}pwjL#|KbhNq%`7AT)G(cRS%xdV|Ar?S>Fp; z$0;jw1{t>qg$*$L14sn0(_NDRGx?OdGlGEZ@iOIC>IHE=N`>5MdJUuqbnwjq|fI{DK1wLG)F4NG=VNuy@h4~jM+aItU5OjIYqATgb z2B|@efCF^3nVpb(1fA9Z-z_{Md=~wEp+-+F6tP$Ruc?+PAx3?VkC6%@h@eMuX>j$i z5O5BY-a*2SKl)z=#Wg3$?4e;D1(|D(0o5{+&|VS&38}{nV9_-vO{1D8!7d*K?%Q2- zcYrz+qr@Tr(?$;l4j5Zf1CiN(8VX%yuq>$>6%+&hFY^b?ku0@^8ZlaR#Jo4iJm2`@9&9 zeh`MBRSJDraK=0YKum}MFkpZC$|l~~f^5qG@4Y=LZRR+p0KlF;497M;1fK{fTm1ie zUMb2f71RT;z1Gp!8p%c4yg07EPMJ%D_w1N|>p3J!Z&|nR%*8N$hX>!Wnt;j^a4n!C zK^R~Y6u&C~{PB&Y>grsK2V2k0;Fu<1Fnd^&9#A&6isuSCMegkbZffyB7HTs-CdSY? zfH!h)03av_>S*+9OM(wXfv%^mLs^iE066Z`0XjCu=_b1E{Q7OUX37~BFJgC1`wT}T zCy@le=yPB3E>gZ{I*aDPqqZQ7h$R}H`rlUJ(wazn&PR!iLO}9>W>>2hB-vo)U_X#H zoWKb4Q+2M{*0{!a?e%h664=0p#KRyjzkck2XGLMhYG(6qONhw@#`=?j&&j0|aSO0u zo4X0P;WYKHKnw!0L~rGJ1f3?!BqN%0rP1KHClDeEznAsRpAiqi34sdt-_ZpCYrnaR zy=%S&;Ua!Z{juzV)Frfk^RKO$GRuv> $(PROJECT_DIR)/python.log& + @python3 $(PROJECT_DIR)/__main__.py &> $(PROJECT_DIR)/python.log & .PHONY: start-fg start-fg: diff --git a/__main__.py b/__main__.py new file mode 100755 index 0000000..7a95cc1 --- /dev/null +++ b/__main__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from src.forge_invar import main + +if __name__ == "__main__": + main() diff --git a/host_generate.py b/host_generate.py deleted file mode 100644 index 007b0b0..0000000 --- a/host_generate.py +++ /dev/null @@ -1,106 +0,0 @@ -from PIL import Image, ImageDraw, ImageFont, ImageMath - -# TODO clean - -font = ImageFont.truetype("./TinyAndChunkyRegular.ttf", 5) -font2 = ImageFont.truetype("./Px437_IBM_BIOS.ttf", 8) - -im0 = Image.new("1", (128, 64), "black") - -draw = ImageDraw.Draw(im0) - -draw.text((26, 24), "F.O.R.G.E.", fill="white", font=font2) -draw.rectangle((24, 35, 104, 40), outline="white", fill="black") - -im0.save("i2c_loading.png") - -im = Image.new("1", (128, 64), "black") - -draw = ImageDraw.Draw(im) - - -# tempo -draw.text((7, -2), "TEMPO:", fill="white", font=font) -draw.rectangle((6, 6, 60, 11), outline="white", fill="black") - -# time -draw.text((68, -2), "TIME:", fill="white", font=font) -draw.rectangle((67, 6, 121, 11), outline="white", fill="black") - -# pipes -draw.rectangle((26, 22, 78, 24), fill="white") -draw.rectangle((26, 46, 78, 48), fill="white") -draw.rectangle((78, 34, 128, 36), fill="white") -draw.rectangle((76, 23, 78, 46), fill="white") - -# squares -draw.rectangle((12, 16, 26, 30), outline="white", fill="black") -draw.rectangle((41, 16, 55, 30), outline="white", fill="black") - -draw.rectangle((12, 40, 26, 54), outline="white", fill="black") -draw.rectangle((41, 40, 55, 54), outline="white", fill="black") - -draw.rectangle((70, 28, 84, 42), outline="white", fill="black") -draw.rectangle((99, 28, 113, 42), outline="white", fill="black") - -selected_bounds = [ - (10, 14, 28, 32), - (10, 38, 28, 56), - (39, 14, 57, 32), - (39, 38, 57, 56), - (0, 0, 0, 0), - (97, 26, 115, 44), -] - -# page -draw.circle((55, 61), 2, outline="white", fill="black") -draw.circle((63, 61), 2, outline="white", fill="black") -draw.circle((71, 61), 2, outline="white", fill="black") - -im.save("i2c_background.png") - -# --- - -# tempo -draw.text((42, -2), "240", fill="white", font=font) -draw.rectangle((6, 7, 6 + 53 * 1, 10), fill="white", width=0) - -# time -draw.text((94, -2), "1234", fill="white", font=font) -draw.rectangle((67, 7, 67 + 53 * 1, 10), fill="white", width=0) - -draw.circle((55, 61), 2, fill="white") - -# selected - -for i in range(len(selected_bounds)): - if selected_bounds[i][0] > 0: - draw.rectangle(selected_bounds[i], outline="white", fill=None) - -im_overlay = Image.new("1", (128, 64), "black") -draw2 = ImageDraw.Draw(im_overlay) - - -# shaders -draw2.text((16, 20), "0", fill="white", font=font2) -draw2.text((45, 20), "2", fill="white", font=font2) -draw2.text((16, 44), "1", fill="white", font=font2) -draw2.text((45, 44), "3", fill="white", font=font2) -draw2.text((74, 32), "K", fill="white", font=font2) -draw2.text((103, 32), "6", fill="white", font=font2) - -im = ImageMath.lambda_eval(lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=im, b=im_overlay) - -# --- - -im = im.resize((1280, 640)).convert("RGB") - -draw2 = ImageDraw.Draw(im) - -for x in range(1, 128): - draw2.line((x * 10, 0, x * 10, 640), fill=(50, 50, 50)) -for y in range(1, 64): - draw2.line((0, y * 10, 1280, y * 10), fill=(50, 50, 50)) - - -im.resize((1280, 640)).save("tmp.png") diff --git a/i2c.py b/i2c.py deleted file mode 100755 index bbb612d..0000000 --- a/i2c.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 - -# see https://electricalflux.com/mcu-raspberry/i2c-oled-display-raspberry-pi-5-wiring-code - -import collections -import pathlib -import sys -import time - -from luma.core.error import DeviceNotFoundError -from luma.core.interface.serial import i2c -from luma.oled.device import device, sh1106 -from PIL import Image, ImageDraw, ImageFont, ImageMath - -TARGET_FILE = pathlib.Path(__file__).parent / "forge_default_save.txt" -TARGET_FILE_BACKUP = pathlib.Path(__file__).parent / "forge_default_save.txt.bck" - -ESTIMATED_BOOT_TIME_SECONDS = 18.0 - -data: dict[str, float] = collections.defaultdict(lambda: 0) - -font1 = ImageFont.truetype("./TinyAndChunkyRegular.ttf", 5) -font2 = ImageFont.truetype("./Px437_IBM_BIOS.ttf", 8) -loading_img = Image.open("i2c_loading.png").convert("1") -background_img = Image.open("i2c_background.png").convert("1") -blank_im = Image.new("1", (128, 64), "black") - -selected_bounds = [ - (10, 14, 28, 32), - (10, 38, 28, 56), - (39, 14, 57, 32), - (39, 38, 57, 56), - (0, 0, 0, 0), - (97, 26, 115, 44), -] - - -def try_restore_backup() -> bool: - if TARGET_FILE_BACKUP.exists() and readfile(TARGET_FILE_BACKUP): - TARGET_FILE.write_text(TARGET_FILE_BACKUP.read_text()) - return True - return False - - -def readfile(file: pathlib.Path = TARGET_FILE) -> bool: - try: - content = file.read_text() - except: # noqa: E722 - return False - for line in content.splitlines(): - key, value = line.split("=", maxsplit=2) - data[key] = float(value) - if "time" in data: - TARGET_FILE_BACKUP.write_text(content) - return "time" in data - - -def open_device() -> device: - # Pin definitions: I2C Bus 1 (SDA=GPIO2, SCL=GPIO3) - # Default SSD1306 I2C address is 0x3C - serial_interface = i2c(port=1, address=0x3C) - - try: - # Initialize the device. If using SH1106, change ssd1306 to sh1106 - return sh1106(serial_interface, width=128, height=64, rotate=2) - except DeviceNotFoundError: - print("Fatal: OLED not found at 0x3C. Check wiring and run i2cdetect -y 1") - sys.exit(1) - except OSError as e: - if e.errno == 121: - print( - "Fatal: Remote I/O error. I2C bus is locked or physically disconnected." - ) - else: - print(f"Fatal OS Error: {e}") - sys.exit(1) - - -def print_loading(restored: bool, t0: float) -> Image.Image: - im = loading_img.copy() - draw = ImageDraw.Draw(im) - - now = time.time() - - percent = min(1.0, (now - t0) / ESTIMATED_BOOT_TIME_SECONDS) - - draw.rectangle((24, 35, 24 + 80 * percent, 40), fill="white") - - if not restored: - draw.circle((4, 4), 1, fill="white") - - return im - - -def print_monitor() -> Image.Image: - im = background_img.copy() - draw = ImageDraw.Draw(im) - - im_overlay = blank_im.copy() - draw2 = ImageDraw.Draw(im_overlay) - - # tempo - draw.text((42, -2), f"{int(data['tempo'])}", fill="white", font=font1) - draw.rectangle( - (6, 7, 6 + 53 * (data["beats"] - int(data["beats"])), 10), fill="white", width=0 - ) - - # time - draw.text((94, -2), f"{int(data['time'])}", fill="white", font=font1) - draw.rectangle( - (67, 7, 67 + 53 * (data["time"] - int(data["time"])), 10), fill="white", width=0 - ) - - # page - draw.circle((55 + 8 * int(data["page"]), 61), 2, fill="white") - - # selected - selected = int(data["selected"]) - draw.rectangle(selected_bounds[selected], outline="white", fill=None) - - # TODO active fx/src - - # fx/mix values - # todo random and time based - fxa_value = 1 - data['value_18_x'] / 127 - draw.rectangle((41, 16 + round(14 * fxa_value), 55, 30), fill="white") - fxb_value = 1 - data['value_25_x'] / 127 - draw.rectangle((41, 40 + round(14 * fxb_value), 55, 54), fill="white") - mfx_value = 1 - data['value_32_x'] / 127 - draw.rectangle((99, 28 + round(14 * mfx_value), 113, 42), fill="white") - mix_value = 1 - data['value_34_x'] / 127 - draw.rectangle((70, 28 + round(14 * mix_value), 84, 42), fill="white") - - # active shaders - draw2.text((16, 20), hex(int(data["state_0"]))[2].upper(), fill="white", font=font2) - draw2.text((16, 44), hex(int(data["state_1"]))[2].upper(), fill="white", font=font2) - draw2.text((45, 20), hex(int(data["state_2"]))[2].upper(), fill="white", font=font2) - draw2.text((45, 44), hex(int(data["state_3"]))[2].upper(), fill="white", font=font2) - draw2.text( - (103, 32), hex(int(data["state_6"]))[2].upper(), fill="white", font=font2 - ) - mix_type = data['value_33_x'] == 0 - draw2.text((74, 32), "M" if mix_type else "K", fill="white", font=font2) # TODO - - return ImageMath.lambda_eval(lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=im, b=im_overlay) - - -def main(): - restored = try_restore_backup() or readfile() - - device = open_device() - - initial = True - started = False - - t0 = time.time() - - try: - device.display(print_loading(restored, t0)) - - while True: - if not readfile(): - time.sleep(0.01) - continue - - if initial or started: - if not started: - started = "time" in data and data["time"] == 0 - if started: - initial = False - device.display(print_loading(restored, t0)) - else: - device.display(print_monitor()) - - if started and data["time"] > 0: - initial = False - started = False - except KeyboardInterrupt: - print("Exiting gracefully...") - except OSError as e: - if e.errno == 121: - print("Runtime Error: I2C bus dropped. Check physical connections.") - device.cleanup() - sys.exit(1) - finally: - device.cleanup() - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index 70e8895..4625127 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,23 @@ [project] name = "forge-invar" -version = "0.0.0" +version = "0.1.0" +description = "FORGE invar background process for Raspberry Pi" +readme = "README.md" +authors = [ + { name = "klemek", email = "klemek.dev@proton.me" } +] requires-python = ">=3.14" dependencies = [ "luma-oled>=3.16.0", ] +[project.scripts] +forge-invar = "forge_invar:main" + +[build-system] +requires = ["uv_build>=0.13.0,<0.14.0"] +build-backend = "uv_build" + [dependency-groups] dev = [ "ruff>=0.17.3", diff --git a/Px437_IBM_BIOS.ttf b/resources/Px437_IBM_BIOS.ttf similarity index 100% rename from Px437_IBM_BIOS.ttf rename to resources/Px437_IBM_BIOS.ttf diff --git a/TinyAndChunkyRegular.ttf b/resources/TinyAndChunkyRegular.ttf similarity index 100% rename from TinyAndChunkyRegular.ttf rename to resources/TinyAndChunkyRegular.ttf diff --git a/i2c_background.png b/resources/background.png similarity index 100% rename from i2c_background.png rename to resources/background.png diff --git a/i2c_loading.png b/resources/loading.png similarity index 100% rename from i2c_loading.png rename to resources/loading.png diff --git a/resources/mock_data.txt b/resources/mock_data.txt new file mode 100644 index 0000000..69e0dd7 --- /dev/null +++ b/resources/mock_data.txt @@ -0,0 +1,130 @@ +time=226.380635 +beats=175.725006 +demo=0 +auto_random=0 +auto_random_cycle=4 +tempo=60 +page=0 +selected=0 +seed_0=898 +state_0=0 +seed_1=472 +state_1=9 +seed_2=243 +state_2=3 +seed_3=547 +state_3=14 +seed_4=524 +state_4=13 +seed_5=251 +state_5=10 +seed_6=808 +state_6=4 +active_0=0 +active_1=0 +active_2=0 +value_0_x=127 +value_0_y=0 +value_0_z=0 +value_1_x=111 +value_1_y=100 +value_1_z=71 +value_2_x=0 +value_2_y=127 +value_2_z=127 +value_3_x=56 +value_3_y=19 +value_3_z=71 +value_4_x=0 +value_4_y=0 +value_4_z=127 +value_5_x=38 +value_5_y=116 +value_5_z=82 +value_6_x=127 +value_6_y=127 +value_6_z=127 +value_7_x=32 +value_7_y=114 +value_7_z=113 +value_8_x=127 +value_8_y=0 +value_8_z=127 +value_9_x=83 +value_9_y=93 +value_9_z=62 +value_10_x=127 +value_10_y=127 +value_10_z=0 +value_11_x=92 +value_11_y=121 +value_11_z=1 +value_12_x=0 +value_12_y=127 +value_12_z=127 +value_13_x=20 +value_13_y=105 +value_13_z=64 +value_14_x=127 +value_14_y=0 +value_14_z=127 +value_15_x=78 +value_15_y=115 +value_15_z=28 +value_16_x=127 +value_16_y=127 +value_16_z=127 +value_17_x=89 +value_17_y=114 +value_17_z=115 +value_18_x=22 +value_18_y=112 +value_18_z=0 +value_19_x=0 +value_19_y=0 +value_19_z=127 +value_20_x=33 +value_20_y=102 +value_20_z=121 +value_21_x=0 +value_21_y=0 +value_21_z=127 +value_22_x=34 +value_22_y=117 +value_22_z=92 +value_23_x=127 +value_23_y=127 +value_23_z=127 +value_24_x=24 +value_24_y=1 +value_24_z=103 +value_25_x=62 +value_25_y=43 +value_25_z=127 +value_26_x=127 +value_26_y=127 +value_26_z=127 +value_27_x=62 +value_27_y=28 +value_27_z=88 +value_28_x=127 +value_28_y=0 +value_28_z=127 +value_29_x=76 +value_29_y=64 +value_29_z=8 +value_30_x=127 +value_30_y=0 +value_30_z=0 +value_31_x=74 +value_31_y=76 +value_31_z=10 +value_32_x=11 +value_32_y=67 +value_32_z=0 +value_33_x=127 +value_33_y=0 +value_33_z=0 +value_34_x=103 +value_34_y=57 +value_34_z=0 diff --git a/append_to_file.sh b/scripts/append_to_file.sh similarity index 100% rename from append_to_file.sh rename to scripts/append_to_file.sh diff --git a/line_in_file.sh b/scripts/line_in_file.sh similarity index 100% rename from line_in_file.sh rename to scripts/line_in_file.sh diff --git a/src/forge_invar/__init__.py b/src/forge_invar/__init__.py new file mode 100644 index 0000000..3db892f --- /dev/null +++ b/src/forge_invar/__init__.py @@ -0,0 +1,40 @@ +import argparse +import time + +from .display import ( + debug_image, + generate_background_image, + generate_loading_image, + get_display, +) +from .forge_data import ForgeData +from .i2c import I2C + + +def host_generate() -> None: + data = ForgeData().mock() + generate_loading_image() + generate_background_image() + image = get_display(data, 0) + debug_image(image) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--generate", action="store_true") + args = parser.parse_args() + + if args.generate: + host_generate() + return + + t0 = time.time() + with ForgeData() as data, I2C() as device: + device.update_display(get_display(data, t0)) + + while True: + if not data.update(): + time.sleep(0.01) + continue + + device.update_display(get_display(data, t0)) diff --git a/src/forge_invar/constants.py b/src/forge_invar/constants.py new file mode 100644 index 0000000..c552bec --- /dev/null +++ b/src/forge_invar/constants.py @@ -0,0 +1,37 @@ +import pathlib + +PROJECT_PATH = pathlib.Path(__file__).parent.parent.parent +RESOURCES_PATH = PROJECT_PATH / "resources" + +FORGE_SAVE_PATH = PROJECT_PATH / "forge_default_save.txt" +FORGE_SAVE_BACKUP_PATH = PROJECT_PATH / "forge_default_save.txt.bck" +MOCK_SAVE_PATH = RESOURCES_PATH / "mock_data.txt" + + +FONT_1_PATH = RESOURCES_PATH / "TinyAndChunkyRegular.ttf" +FONT_2_PATH = RESOURCES_PATH / "Px437_IBM_BIOS.ttf" + +LOADING_IMG_PATH = RESOURCES_PATH / "loading.png" +BACKGROUND_IMG_PATH = RESOURCES_PATH / "background.png" + +ESTIMATED_BOOT_TIME_SECONDS = 18.0 + +SELECTED_RECT_BOUNDS = [ + (10, 14, 28, 32), + (10, 38, 28, 56), + (39, 14, 57, 32), + (39, 38, 57, 56), + (0, 0, 0, 0), + (97, 26, 115, 44), +] + +ACTIVE_SRC_RECT_BOUNDS = [ + (10, 32, 28, 32), + (10, 56, 28, 56), +] + +ACTIVE_FX_RECT_BOUNDS = [ + (39, 32, 57, 32), + (39, 56, 57, 56), + (97, 44, 115, 44), +] diff --git a/src/forge_invar/display.py b/src/forge_invar/display.py new file mode 100644 index 0000000..7545608 --- /dev/null +++ b/src/forge_invar/display.py @@ -0,0 +1,167 @@ +import time + +from PIL import Image, ImageDraw, ImageFont, ImageMath + +from .constants import ( + ACTIVE_FX_RECT_BOUNDS, + ACTIVE_SRC_RECT_BOUNDS, + BACKGROUND_IMG_PATH, + ESTIMATED_BOOT_TIME_SECONDS, + FONT_1_PATH, + FONT_2_PATH, + LOADING_IMG_PATH, + SELECTED_RECT_BOUNDS, +) +from .enums import ForgeState +from .forge_data import ForgeData + +FONT_1 = ImageFont.truetype(FONT_1_PATH, 5) +FONT_2 = ImageFont.truetype(FONT_2_PATH, 8) +LOADING_IMG = Image.open(LOADING_IMG_PATH) +BACKGROUND_IMG = Image.open(BACKGROUND_IMG_PATH) +BLANK_IMG = Image.new("1", (128, 64), "black") + + +def __xor(img1: Image.Image, img2: Image.Image) -> Image.Image: + return ImageMath.lambda_eval( + lambda args: args["convert"](args["a"] ^ args["b"], "1"), a=img1, b=img2 + ) + + +def __get_loading_display(data: ForgeData, t0: float) -> Image.Image: + im = LOADING_IMG.copy() + draw = ImageDraw.Draw(im) + + now = time.time() + + percent = min(1.0, (now - t0) / ESTIMATED_BOOT_TIME_SECONDS) + + draw.rectangle((24, 35, 24 + 80 * percent, 40), fill="white") + + if data.state == ForgeState.INITIAL: + draw.circle((4, 4), 1, fill="white") + + return im + + +def __get_running_display(data: ForgeData) -> Image.Image: + im = BACKGROUND_IMG.copy() + draw = ImageDraw.Draw(im) + + im_overlay = BLANK_IMG.copy() + draw2 = ImageDraw.Draw(im_overlay) + + # tempo + draw.text((42, -2), f"{int(data.tempo)}", fill="white", font=FONT_1) + draw.rectangle((6, 7, 6 + int(53 * data.beat_progress), 10), fill="white", width=0) + + # time + draw.text((94, -2), f"{int(data.time)}", fill="white", font=FONT_1) + draw.rectangle( + (67, 7, 67 + int(53 * data.time_progress), 10), fill="white", width=0 + ) + + # page + draw.circle((55 + 8 * data.page, 61), 2, fill="white") + + # selected + draw.rectangle(SELECTED_RECT_BOUNDS[data.selected], outline="white", fill=None) + + # active + draw.rectangle(ACTIVE_SRC_RECT_BOUNDS[data.active(0)], fill="white") + draw.rectangle(ACTIVE_FX_RECT_BOUNDS[data.active(1)], fill="white") + + # fx/mix values + draw.rectangle((41, 16 + round(14 * (1 - data.fxa_value)), 55, 30), fill="white") + draw.rectangle((41, 40 + round(14 * (1 - data.fxb_value)), 55, 54), fill="white") + draw.rectangle((99, 28 + round(14 * (1 - data.mfx_value)), 113, 42), fill="white") + draw.rectangle((70, 28 + round(14 * (1 - data.mix_value)), 84, 42), fill="white") + + # active shaders + draw2.text((16, 20), hex(data.frag_state(0))[2].upper(), fill="white", font=FONT_2) + draw2.text((16, 44), hex(data.frag_state(1))[2].upper(), fill="white", font=FONT_2) + draw2.text((45, 20), hex(data.frag_state(2))[2].upper(), fill="white", font=FONT_2) + draw2.text((45, 44), hex(data.frag_state(3))[2].upper(), fill="white", font=FONT_2) + draw2.text((103, 32), hex(data.frag_state(5))[2].upper(), fill="white", font=FONT_2) + # TODO print when random + + draw2.text((74, 32), "K" if data.mix_mode_key else "M", fill="white", font=FONT_2) + + # TODO show feedback + + return __xor(im, im_overlay) + + +def get_display(data: ForgeData, t0: float) -> Image.Image: + if data.state == ForgeState.RUNNING: + return __get_running_display(data) + return __get_loading_display(data, t0) + + +def generate_loading_image() -> None: + global LOADING_IMG + + im = BLANK_IMG.copy() + + draw = ImageDraw.Draw(im) + + draw.text((26, 24), "F.O.R.G.E.", fill="white", font=FONT_2) + draw.rectangle((24, 35, 104, 40), outline="white", fill="black") + + im.save(LOADING_IMG_PATH) + + LOADING_IMG = im + + +def generate_background_image() -> None: + global BACKGROUND_IMG + + im = BLANK_IMG.copy() + + draw = ImageDraw.Draw(im) + + # tempo + draw.text((7, -2), "TEMPO:", fill="white", font=FONT_1) + draw.rectangle((6, 6, 60, 11), outline="white", fill="black") + + # time + draw.text((68, -2), "TIME:", fill="white", font=FONT_1) + draw.rectangle((67, 6, 121, 11), outline="white", fill="black") + + # pipes + draw.rectangle((26, 22, 78, 24), fill="white") + draw.rectangle((26, 46, 78, 48), fill="white") + draw.rectangle((78, 34, 128, 36), fill="white") + draw.rectangle((76, 23, 78, 46), fill="white") + + # squares + draw.rectangle((12, 16, 26, 30), outline="white", fill="black") + draw.rectangle((41, 16, 55, 30), outline="white", fill="black") + + draw.rectangle((12, 40, 26, 54), outline="white", fill="black") + draw.rectangle((41, 40, 55, 54), outline="white", fill="black") + + draw.rectangle((70, 28, 84, 42), outline="white", fill="black") + draw.rectangle((99, 28, 113, 42), outline="white", fill="black") + + # page + draw.circle((55, 61), 2, outline="white", fill="black") + draw.circle((63, 61), 2, outline="white", fill="black") + draw.circle((71, 61), 2, outline="white", fill="black") + + im.save(BACKGROUND_IMG_PATH) + + BACKGROUND_IMG = im + + +def debug_image(im: Image.Image) -> None: + im = im.resize((1280, 640)).convert("RGB") + + draw2 = ImageDraw.Draw(im) + + for x in range(1, 128): + draw2.line((x * 10, 0, x * 10, 640), fill=(50, 50, 50)) + for y in range(1, 64): + draw2.line((0, y * 10, 1280, y * 10), fill=(50, 50, 50)) + + im.resize((1280, 640)).save("debug.png") diff --git a/src/forge_invar/enums.py b/src/forge_invar/enums.py new file mode 100644 index 0000000..da0f500 --- /dev/null +++ b/src/forge_invar/enums.py @@ -0,0 +1,8 @@ +import enum + + +class ForgeState(enum.Enum): + INITIAL = enum.auto() + RESTORED = enum.auto() + STARTED = enum.auto() + RUNNING = enum.auto() diff --git a/src/forge_invar/forge_data.py b/src/forge_invar/forge_data.py new file mode 100644 index 0000000..da397cb --- /dev/null +++ b/src/forge_invar/forge_data.py @@ -0,0 +1,132 @@ +import collections +import pathlib +import typing + +from .constants import FORGE_SAVE_BACKUP_PATH, FORGE_SAVE_PATH, MOCK_SAVE_PATH +from .enums import ForgeState + + +class ForgeRawData: + def __init__(self) -> None: + self.__data: dict[str, float] = collections.defaultdict(lambda: 0) + + def try_restore_backup(self) -> bool: + if FORGE_SAVE_BACKUP_PATH.exists() and self.read(FORGE_SAVE_BACKUP_PATH): + FORGE_SAVE_PATH.write_text(FORGE_SAVE_BACKUP_PATH.read_text()) + return True + return False + + def read(self, file: pathlib.Path = FORGE_SAVE_PATH) -> bool: + try: + content = file.read_text() + except: # noqa: E722 + return False + for line in content.splitlines(): + key, value = line.split("=", maxsplit=2) + self.__data[key] = float(value) + if "time" in self.__data: + FORGE_SAVE_BACKUP_PATH.write_text(content) + return True + return False + + def __getitem__(self, key: str) -> float: + return self.__data[key] + + def __contains__(self, key: str) -> bool: + return key in self.__data + + +class ForgeData: + def __init__(self) -> None: + self.__data = ForgeRawData() + self.state = ForgeState.INITIAL + + def __enter__(self) -> typing.Self: + if self.__data.try_restore_backup() or self.__data.read(): + self.state = ForgeState.RESTORED + return self + + def __exit__(self, *_) -> None: + pass + + def __update_state(self) -> None: + if self.state == ForgeState.RUNNING: + return + elif self.state == ForgeState.STARTED: + if self.time > 0: + self.state = ForgeState.RUNNING + elif "time" in self.__data and self.time == 0: + self.state = ForgeState.STARTED + + def update(self) -> bool: + if self.__data.read(): + self.__update_state() + return True + return False + + def value(self, n: int) -> tuple[float, float, float]: + return ( + self.__data[f"value_{n}_x"] / 127, + self.__data[f"value_{n}_y"] / 127, + self.__data[f"value_{n}_z"] / 127, + ) + + def frag_state(self, n: int) -> int: + return int(self.__data[f"state_{n}"]) + + def active(self, n: int) -> int: + return int(self.__data[f"active_{n}"]) + + @property + def time(self) -> float: + return self.__data["time"] + + @property + def time_progress(self) -> float: + return self.time - int(self.time) + + @property + def tempo(self) -> float: + return self.__data["tempo"] + + @property + def beats(self) -> float: + return self.__data["beats"] + + @property + def beat_progress(self) -> float: + return self.beats - int(self.beats) + + @property + def page(self) -> int: + return int(self.__data["page"]) + + @property + def selected(self) -> int: + return int(self.__data["selected"]) + + @property + def fxa_value(self) -> float: + # todo random and time based + return self.value(18)[0] + + @property + def fxb_value(self) -> float: + return self.value(25)[0] + + @property + def mfx_value(self) -> float: + return self.value(32)[0] + + @property + def mix_value(self) -> float: + return self.value(34)[0] + + @property + def mix_mode_key(self) -> bool: + return self.value(33)[0] > 0. + + def mock(self) -> typing.Self: + self.__data.read(MOCK_SAVE_PATH) + self.state = ForgeState.RUNNING + return self diff --git a/src/forge_invar/i2c.py b/src/forge_invar/i2c.py new file mode 100644 index 0000000..c01f560 --- /dev/null +++ b/src/forge_invar/i2c.py @@ -0,0 +1,49 @@ +import sys +import typing + +from luma.core.error import DeviceNotFoundError +from luma.core.interface.serial import i2c +from luma.oled.device import device, sh1106 +from PIL import Image + + +class I2C: + def __init__(self) -> None: + self.__device: device | None = None + + def __enter__(self) -> typing.Self: + self.__open_device() + return self + + def __exit__(self, *_) -> None: + assert self.__device is not None + self.__device.cleanup() + + def __open_device(self) -> None: + # Pin definitions: I2C Bus 1 (SDA=GPIO2, SCL=GPIO3) + # Default SSD1306 I2C address is 0x3C + serial_interface = i2c(port=1, address=0x3C) + + try: + # Initialize the device. If using SH1106, change ssd1306 to sh1106 + self.__device = sh1106(serial_interface, width=128, height=64, rotate=0) + except DeviceNotFoundError: + print("Fatal: OLED not found at 0x3C. Check wiring and run i2cdetect -y 1") + sys.exit(1) + except OSError as e: + if e.errno == 121: + print( + "Fatal: Remote I/O error. I2C bus is locked or physically disconnected." + ) + else: + print(f"Fatal OS Error: {e}") + sys.exit(1) + + def update_display(self, image: Image.Image) -> None: + assert self.__device is not None + try: + self.__device.display(image) + except OSError as e: + if e.errno == 121: + print("Runtime Error: I2C bus dropped. Check physical connections.") + raise diff --git a/uv.lock b/uv.lock index 816593d..c966893 100644 --- a/uv.lock +++ b/uv.lock @@ -44,8 +44,8 @@ wheels = [ [[package]] name = "forge-invar" -version = "0.0.0" -source = { virtual = "." } +version = "0.1.0" +source = { editable = "." } dependencies = [ { name = "luma-oled" }, ]