From 869e238326404dcf57aad2801768fdf94d08058c Mon Sep 17 00:00:00 2001 From: mrsqr Date: Sun, 9 Aug 2026 22:42:19 +0100 Subject: [PATCH] Add hardware-verified startup splash --- README.md | 16 ++++++- assets/README.md | 12 +++++ assets/startup_splash.gif | Bin 0 -> 19615 bytes assets/startup_splash_preview.png | Bin 0 -> 35360 bytes splash.py | 31 ++++++++++++ startup_splash.rgb565 | Bin 0 -> 115200 bytes tests/test_splash.py | 66 +++++++++++++++++++++++++ tools/convert_splash.py | 77 ++++++++++++++++++++++++++++++ touch_drive.py | 24 ++++++++-- 9 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 assets/README.md create mode 100644 assets/startup_splash.gif create mode 100644 assets/startup_splash_preview.png create mode 100644 splash.py create mode 100644 startup_splash.rgb565 create mode 100644 tests/test_splash.py create mode 100644 tools/convert_splash.py diff --git a/README.md b/README.md index e414b10..de04d40 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,17 @@ python tools/generate_font.py /path/to/Montserrat-SemiBold.otf font_data.py The generated font data is distributed under the SIL Open Font License 1.1 in `FONT_LICENSE.txt`. +## Startup splash + +At startup, the timer displays the supplied Caterham artwork on a black background sized for the 240x240 round display. The image is stored as a native `startup_splash.rgb565` framebuffer and loaded directly into the LCD's existing buffer, avoiding a second full-screen allocation on the RP2040. If the asset is absent or has the wrong size, the original text splash is shown instead. + +The original artwork and a device-layout preview are kept under `assets/`. To regenerate the runtime asset after changing the source image, install Pillow and run: + +```sh +python tools/convert_splash.py assets/startup_splash.gif startup_splash.rgb565 \ + --preview assets/startup_splash_preview.png +``` + ## Supported hardware Version 3.3 supports the integrated [Waveshare RP2040-Touch-LCD-1.28](https://www.waveshare.com/product/rp2040-touch-lcd-1.28.htm). This board combines the RP2040, GC9A01A 240x240 LCD, CST816S touchscreen, and QMI8658 IMU used by the firmware. The standalone 1.28-inch Touch LCD connected to a separate Raspberry Pi Pico uses a different pin map and is not currently supported. @@ -96,7 +107,7 @@ The second command should identify an RP2040 MicroPython board. Run these commands from the repository root. Supporting files and font assets are copied first; `main.py` is installed last as the automatic entry point. ```sh -mpremote connect auto fs cp configuration.py font_data.py font_renderer.py launch.py lcd_1inch28.py params.json qmi8658.py settings.py timing.py touch_drive.py font_data*.bin : +mpremote connect auto fs cp configuration.py font_data.py font_renderer.py launch.py lcd_1inch28.py params.json qmi8658.py settings.py splash.py timing.py touch_drive.py font_data*.bin startup_splash.rgb565 : mpremote connect auto fs cp main.py : mpremote connect auto reset ``` @@ -111,11 +122,12 @@ When upgrading an existing device, omit that command so its saved track duration ### 4. Verify first boot -The display should show the v3.3 splash and then the green **Ready** screen. The serial console should report the loaded user parameters, `Success:Detected CST816T.`, and the touchscreen revision without a traceback. +The display should show the Caterham v3.3 splash and then the green **Ready** screen. The serial console should report the loaded user parameters, `Success:Detected CST816T.`, and the touchscreen revision without a traceback. If first boot fails: * `OSError: Font bitmap is missing or truncated` means one or more `font_data*.bin` files were not copied. +* The original text-only splash means `startup_splash.rgb565` is missing or has the wrong size; repeat the application upload command. * An import error generally means a `.py` support module was omitted; repeat the upload command and keep `main.py` last. * No serial device after flashing usually indicates a charge-only USB cable, an incorrect UF2, or a board still in BOOT mode. * A missing touchscreen or IMU error indicates unsupported hardware or a board-level connection problem. diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..4bd0b5f --- /dev/null +++ b/assets/README.md @@ -0,0 +1,12 @@ +# Startup splash source + +`startup_splash.gif` is the user-supplied source artwork from: + +https://static.wixstatic.com/media/467f72_bdf20d6c823c42a18bc41c04e17e7345~mv2.gif + +The runtime asset and preview are regenerated with: + +```sh +python tools/convert_splash.py assets/startup_splash.gif startup_splash.rgb565 \ + --preview assets/startup_splash_preview.png +``` diff --git a/assets/startup_splash.gif b/assets/startup_splash.gif new file mode 100644 index 0000000000000000000000000000000000000000..72926880218234cf4617550f687048b9065cf4bf GIT binary patch literal 19615 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᠂C7?2gd^fM*^ zc!3XY0Kx=t_Y`JP>~*y<;+cTZRKNaJBS?_S^{|%$3|L_gb@0YA9*2m~)6fpteX z1wP!t4cYtdQ&>Bc82V&O0I)%NDQw$LA%M5CVnLc9L5N+rBNoIhMACqO1bbmKd6eCN z0xU48TLGa0K-dEx_<#hpewSHzaxGGTVuCeP7|4K~f&!pQ-4u9M454_0G3pD6U4$ZX zz?7B^UH}3gk$^TaHGy-cP>4G40Sz^Yqv~EKL*DFb0Y(V&n*F*O#Zi~K8-_!OKfDY` zm?9Q?ut5qG5CcYXwFWVW!2wMmgc^jw5(~Nm3`B!HkBChH4%U*Y0MJ)9Upk{|z3@`x^vH<9YJa!f`VT@%MquVA|y2+6a^5TjX1{#+Y2#a{`qGTxo z64&|GrI*rtVGrmVXEY45KwP7TJk)2$cbv=C+Ofb;O zpaUHSFi3$1gdoyPgaXg8h=pr7Gyy|Kz1Z2Cn7OSk!%FKN^Mn(_zdn`mVjnpM8^%Bi zY5)XA)_25K)3$!z7KrhHJ-f>4aeLo zVPH^jV0-7=Y!5LJj-y4`X|cngtHT$%>*>U&q3oKMK|#Xv$*-8V4&L=Ve?;H>Wlf#0Io(2%`{5;NS@+=YZs{ zatOo#BESS<00^i+3d3+yyle`rzzS*r2`&VSfWQW@C=JCB4&zV`=a3HT&<-&oAOHY6 D|Jqpf literal 0 HcmV?d00001 diff --git a/assets/startup_splash_preview.png b/assets/startup_splash_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..90a2af24e33273f0cf9e528c85fffb921443f86b GIT binary patch literal 35360 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 J3as~|{{lJui{1bL literal 0 HcmV?d00001 diff --git a/splash.py b/splash.py new file mode 100644 index 0000000..f0421e3 --- /dev/null +++ b/splash.py @@ -0,0 +1,31 @@ +"""Memory-efficient loading of the startup splash framebuffer.""" + + +SPLASH_FILE = "startup_splash.rgb565" +READ_CHUNK_BYTES = 2048 + + +def load_splash(surface, path=SPLASH_FILE): + """Load an RGB565 image into an existing framebuffer without duplicating it.""" + buffer = surface.buffer + expected_size = len(buffer) + + try: + with open(path, "rb") as splash_file: + splash_file.seek(0, 2) + if splash_file.tell() != expected_size: + return False + splash_file.seek(0) + + target = memoryview(buffer) + offset = 0 + while offset < expected_size: + end = min(offset + READ_CHUNK_BYTES, expected_size) + bytes_read = splash_file.readinto(target[offset:end]) + if not bytes_read: + return False + offset += bytes_read + except OSError: + return False + + return True diff --git a/startup_splash.rgb565 b/startup_splash.rgb565 new file mode 100644 index 0000000000000000000000000000000000000000..d1b7bc6788b39218b75eb4cea26b53811d66910c GIT binary patch literal 115200 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

b|AD6MNoD9*La|y{NZESFmD&!#5$YTU`S^x*4?= zHd@;7dpVFT61b9gg$1rTH7Dc5-d)hLD^at7N-Sm;Jf6VM6A$&?hA}CXN*zyjqy~C# z@0)|;+MVi1hLau1SaLJaemnhC`ulxPrrt@tnp~6IoTy4H!HlR%Rwb&$=dY*UNOfZT z7$eftd*b)$d(vO&`E~NKp663fr~Wxs9oy3Ra_8%v?<8T0<(#(d$T?uc$X0I;gEeD; z2<<)W?Z2%}Tdf}IwZ)F;-SKhB5NJ9P7*FcGuXk2vIDxjY&G%>ZI{TI;cO}-x7ld9= zH^6RXP8RElFi;tZd-jpfN#K#OhS4=dwVUXnw>(G`hj5*w2*rC#b8z%dM9@HmW_ z^TNn)e60`bC)tZ6ei z6}8V7$+5;G)`N0g=-}X|s7Rlo%;4v&O&8ovU+A3L7%xe^l6nm?H$+@}28=uK&0?ID zCzK$=@vatpPF@cHxkth6cS5e;Prgn+iPy`3`7+?^OugN6MW7Ab1?IrLZ+6eSK>X{# zyA(+O{=~0O{7=a7e@%Wf^{*#>cj9+Fy*Y>?%`WH17gp!xjR!reM`ftJcVY?}Hwj$}8GAVe z?!A79IX98KJ#6+7b!ZUqx542{!J|vT+Y!OvhWs`jp9JsT4|~iDu6`Txp4LW&vxOE| ziqAOaV0^JJ2b}!7^yhnimHOAzx08>h{yF(Y$nwsf&Ys`){06$)n|v<0G_kbjmx=qO zDU97BdOPJBNDXOz&0E*K7?Stdt>ItDzW0~+GyGTAZ6`yagVs9BWJU}uZi^Hw?y$zk z8wkV7!L8?V-xqp~F){TPsL!z*y6`)(Bhf`zRYY9*i1kH5VtWPALIYJGD{uv}0^23Z z%+gDYcL8J6$Fkx^XyJ3tR$WbXz&=`&SOP5^#y@=2?Xu9ZklvUB;B!72{GO5L!z_nE znNG~B`>KKY*1`Leu*1RG>8+6Wcl5lIdK;D-WVsmcaK?b+!R0;g^xh?;d1=~p;txRk z+sWL$?NExGf-c{h?U6YH#nYg<*99wRh}9J!m&%R^De|^ zAdq1gwyJRoqUHYdMx z4m=4=vzFWUpv9l;y{qRJ(C*aou*iY;_Xob8E=~K=UpevX)VKHkHWtzE-}X-Qj_B>% zuEe&)Z%;fFI-+fi+!mc^EwZ|a5mtG)L*dwK(c6+=>wBcvncg$dIq=JY-(J0n`)`up ziY~&KsNauV&C{u;laK9vzq2=4xc92E=;L_g`f5e7NVWsyUSN#>x9vw&#=O{@@#*B1 z-rJ%xku!RO)p4wg69domY1ArmAFvb_w6K&K1&<;V%(|*!m=asq$ix*HB1DtOwaE7( z0_p9KOtWFNcd#2Z1r^!^R&&G7gQl$VmnQ{?75z|G`!gYUV;IYB!wU*5>8J8^ue-pQ+iK&3;v0b29Dd|%gx5VA5BLFdeYCOB75K0mmyl< z)S&H<13ixQT`N2*hIso%w<6@RYE$0e)Cn`{-skW~0=ngAl2I16OotKa`-^1J< z@6@&Y-_nbLE1DBLSP2xtks|w$!BqG?u!uRBr@`kLUMJd%1{d7_!x$(@xI_#axJUaStH4AE0)PirHDIHu69{QNV3)vk+lQb{?MuFDY+wT zJAC<(TcR!D$>EW>V?=nDBT8gmv_kN@5(Up^h8A59{B^w>F`C0fKYlBQ(Bs2+o%R~g zHXgIwJ*c^38H|S`{ubiii)opsQW<_WB0l$Jd@{;$gR2dhJtws_aW`!1p4fBxBZ)hp zvmZpv>???nB1S64->1Kd*xA3Q;;Ai(nehp+V^J+q8!gxMSU0Smm$b#o64VINlY}ox zo5q>;EM$eS_s`gBe?ndZ-=Kh5#uyQ{ghz;NZrQ^YpgtEv7Q-L$N9rQw zQ6*Zh?-ZD~BL8%&knhv4#aeK=kngb4IgqoU)iGRh3N4jIoScm@XrpEAa>3K7&cxrv zo?;H1xlq=G79I{&A;MA(jr&blVfUo}FYxnMz|Dw~f}8&UpSn6RD}GzYxY#+&SUKX? zb$LF!)EL!C#_yo0QMMH?&%NhWv{)fT&6om2I$nPz%wHju_q13OyMgmkP&c zsA2lF$g)x+z*<*OHG|b9#J9`V#k@Ler^G0hDn#Epav;M-c8)i3^=kW zPUx>IusUBXJ)(@0t>ED%!QlqVp9j7M#sktz*AsWLBu_4+DQx`tL@7SBEK(l7kCqMpe|9SN2XxR1@gW|EhE6>jTZa7h z7VQzv{s3wMy%)SMcvg6is*6M4LNv{=#3;ufMI@B+nwpJyRs6WuJ?DS3X)H!OcJFK-iC$$~gL9I(W5FVvH<9WRBEauNa^vIH( zr^A!0-HH}05Bh_3p#%8d{$coLasKf0{?O^rDSY>&wmoz@JTfv*e*tln#fgUy1x2JA z<2A&s7`ei$Hd^l;w{p;e&%|GVcb2DN#)J-qy0yoH^R%-XYgX})7Z4a&sbw#Fv3%?7?_kK`$t&o=FB_m#G#4o-tvW-8X zciMYLqAGS4Ry$;R=qxzDJUgQ0;P@6udh+}^;mJY5NAJ_T$Y(>ckptM@4y|9tDU3`6vWz|NC90z9tBb((3Xavl*pFvFEPhx*7(}~1y29{e zfGs4Oh^)6`m16-acQ2ra2-dGgXZEcf^s0=w33-__jnewd1OE@kKcv6hSJ;;euKvG! zewOscZ|xYPX@yu@Q=^`tyo8N6f*fBkcOOvR)q##Zx=e)(82=U zYtSaPJAm!{KcBN1mz=0UHu#(`~8aH@8hCMt>@Yoz7Mem2sM7+6gPU4|R zv9=EwPo~z+_-WMWQ!;j(UqeSl-wf9DLZ`u&q$~uwE7;8mE964Tz0nzrLmF8wk+*Q- zpRy5fYUD41bMGN116^Va?VS2%=RmAqKN!sSw!yj`XuA3MGw@Y6dUjf|#uFXF6m=6U z=t@+fmZRTMH)0&EjuqY{q)~ZPi=CB=t@W_K#tdp!%4XH!@ z6VBN1mDzuh~Y-!!0bMxzS2>moE>pdTjU>AU`&AR*W;De(iuE z$%Tz|5|4(}MQry6w}(f^X8>Dg&*fxz&!2?Wdk>dhz{-l4a&N>T zA|oQ!J%sYGs5i0x9P`esDmn3cR2R75A^$A-#l%DL1+nwN34so*^#Wcub;I`+kD~?s zZ}QRU^NbpD6)@^xW46%Fl_Su?QxQL^izxab?;)!OdmXw|$mM?WrL{SOF3(ADEUTDM z$0Ty0u>QoihdoZto$RRfGzaSiy7RJ{EjmoYiBE(-l;IUc-RSLy-OSj=+>9*@c@7Sr z7Ae4;h#v}_0oDf?8^PG2?a&V+qU%J>R#jqA?0i&@Y8cC*1uBG>B(x{vT_UapF9>ms z)G*K1IfyYUG7%N-c{_Fl*lx$%p$4F}&e=qJaKC3i?9(aEosOOG=SUHYt(LK3T1DWz zl-jO0auSJ{Co#Tn2xH7ZufREg+|}Kv3;drznK=IrvFzUy?bK7qmu`-(_H4WEl9@E&87l53*jBY~T9&W7FuzH{}Rs%~q^;!C@V+$v;nhJ!hCt!_tM zu9Q1MDwB#F>l{0r`yKn7`yn(G#>NY^$+RYUj%ui`vn8tH_v)?q4YX#Y2%pTw444{S0Ifa^_onkbbi!g}tov7?s)_Yg%qP8!%4nA5L?*8hc>LP82&ySq>I!loa9-c)a z23CLJS=TL)(HoTMO|KzyUZS@m#?0Ez80ADpTHCMeQ!Nt9#W*P^ToZFdX^wk^$IY^T42XhuQ4Z>9jNH4 zPKGm)G-v_xFVHS?G?QjSH>=;X0+!V)fmgJzHNC1o9GQlHP!Q`~LP=rpG3B*^bBc8+ zZ>27!jwM$nD&rOL0#XOVQvz6j{qr)gpRHgud0; z*>pTuukGNm>xYpwsuCD;8eS&G$zAbzVl2nwTwq%PjLTyeIy-^ykHNcpVjZX$_zl)W zel1>*x)i*1@PBT2!?8Z+EaIE51a`CEk*$lJA)Vbd1Fo^Tw>YpK1lntI80++G2ZaVE z4>NMs4@>HF?qq>6aUN;)J9bptA&rY5jmfo>v)nqwuLiDoq~@)Q{%K6FeV5i3@Wj3l zVI1OL@y^gU;a$P|*&RDi&W$b)rM=xiRss>qa4+Iue@sZ4%)mQ#d@@k zQ7K$sB(zCA8FT|-=X@^g}*L*Ws~%FdM(%4hO0b+9y# zvKo90Ynj;EP9p>4mi*Q3V69dgI)FJb3KBU(|3OqUQdztvzIEAea0XIv(t9saN<&_6hSuDjx+475rM-711FTLA16a%RGHRl#qU=av(;kVUIv}|_I01D&IO-d{F&m2*SQr^CtfNH6!oKZYWroG> zDnKVtz4{hpWr3Cp(I-SW@l1ZxRIYk zlvE7NU&i2S@omY1D-5{(-#+C(RXSo}QO#WB6W}KU4iBi!tFxXR0oZ?@4KzGM; zazOz2qT`X9Gdi@zvl94XEw^o_=Mb!`9a@(wFLw;+w44|N=}N?*>I4VWAr{#$wwkSq z6*(f`81Gr7vU-em5R|Wttcy15J4MWvk1aalx4^pjT5S1>n12MW2HpZ)!L{Vw!8s6? z^n=MNplgTDeGw}`?TFjjan^{uf@6&+OQndd;+2TO;ZY!rULRaP3~qPh`*zF7oYU?d zkL`D_(++|5uORC6t=Mx)ww+R* zR*z`nDC9I+1a;6kkpD$|nOz_sgQ9h!N}yTJUpl&a3fAuwaJ{bD7%T{#jn3Db^#l4r zj6=x4>cF^J-@)-GFwP)3gx=6r#-jFx1bMnasz64o85r9ktC5+WJt|nBEeaw_kb4Vz{#*UfoW;q;UHpo%~`vLHTA4~1UTRsZ7bhjR@NcT2=m@-+}M!P?N4(ok_>~+)>y!Zpar}eCQ#>?Hj#0 z*0aZvHVZXi4>HRy#N5E7bp$|z2>w@f*c&ax8~c|rkLIawBHjsYnphN7OAjMQvje_mvAzM` z^%TTG%9QO(r3}K@CceVNeUVJUZ=XX84Tx zPrT_pvo8ZAKsT~VdB|opsAp6?JgQoji-Q%x<=!{7XOzd)#+pmoeW7r4W~?K2v12@< zgN}}|9b=hO7a3RZH87?w^+(EZ#}Kas)`L7uxeU4t8}4XbVg+iix-|H9pm`C_88!sF zIsmaQ#>mATh(eFF@Es9-Lv&SO3|^+TVdj#SBe+;1)gW!lpuxE}%5A^3EO0n*#8%h2 zHTArT4(5p1ud~>5M?|IoTTkGuwnmwWUkeoE`N}HwNcC8?Uu{v=k%u9p-M%u&XzJl2 z@Gzp-p;JOm>#&K}pvrDcJg;M<;M@Jt9nl?Osd|LVjauaq;t2TQ=7V*+^YqQHjF7ET zW#BmRjowAA3tOSiQ4zFR-J}uyTZ?Q##T)w_VdRxs=70DNIMj)@Z|;PX$^SY7TVM_5WO8^mi~`0P-C%6$eKG)2{#TI zv0Gz@^~2z7#*3E&!)uWoM0Ge1hoz1&9k=Ms(Gqw=#01k5S{M425|jo7dq zgLv5)rNf!wGZCjTEHFqeRxIEf*Wc_wZZi0@F7{;VY1aFagAjK{R1UR(qnsOHli(^6 zvIBLYqru&<*QGHYTw%aBb*Tk%y3FSXzT3&eL|EI7Xz4y6JO#739x>7?%=Xb8qXpmY z2-~ZRWIwQ#co@Q~qn*#=w&Yr5&9jj2-^`8CDo^8yKla^|`Zjtx#=K z1S_gX2bTwngVQ3g4MWLDL3DAbLtyQU-mZU3-yDmf%IpX$WU!6_6|nk?`eBiI{Ror= z56j@*O1&$FcwkoV5?#T$#FhiknEMhCZGm<{e=XdL@jkL5Uo~)o}2cR9%Gcsdogev5-1;d()X$3OyYNukErl&($VQ^;2)r|KbLx} z=Ra7zoyb9F_OHjD>ih}1)IGSj7gyHU?R~*F)i+;l3nJ4KoT{wniX5dPIA4Jm5IV4Q zq<$b;QEiRf8=VlHxa~^Q>rGemhxD+%nyXZ>c1??{!=S7-ts#uzJz+2K z67;3c^`ru5?v^Bmhe*FP&|KC9H#IN^WjYX1QI`R8~e;D||)x^~w4Ezh~ z5I%q6SE=Vu{Nd^kJ~)y7YBC31u43vgbz>kMIv<@LIvTm9IxlcGSnSGERs>PK?iz*2 z-6>Cfus%AY>BsC!+4NlKyP@4$Rd8XbJM<>vBr?4$4C-Y{=`7B8j_1dVLut!o!)CTX zpBO&vV0KH&IUuhPsG^d>H?H(T=>_ydL%&(nyu{QnI1MvEXk}4pLVt=@84zxPPLeXi zg0*+T_I4(j1;k9pC*IT5_G&xU3wk{0GC{46*+kio8+td5UV7`n3qL=$C$HV!ECnpe zK7)Uae2dV+87Xb#!0zVWJ#r5Drp^xf0* zQ~d#T4SKY8peE}K<~Axbc&)LT0fFoP>iPHBqruzI#bAM>A`nBz?mLhJ`ao6i zt_qZi^wlWG2PgQOtN-tbKe9Wp_HgjFPz>C;sVN;utGe31bhOs0wg!u5acXDlqR zhq=lW_4{vYw`;fK(H5K-oEe-RTmY;OL0`#0xlC|1aE{g|DmtvrgVuG1Y@xrKsVk|c zY_>pNdXECv>FAG!PFujJmgfTPIG=sHl*L>lK#v*NK#sFMLHK|`7}gSgkKu=GMx6?y z?y$gcwM49J<$Af2((}G)3VO>s+u*&RMv;BigpFkf`R$ewYo{Dr-`w@=$!34Me{E5Q zf42+%7154hSyoCzP9sK$T$GzAiW_H^!NHI))R_FlvCezux;4g`um7bdTOE9)27S)J zzx4bXeSGiM=O{~Xy*OH+JWD?)mj|x;ma@p%1!}awr)WiQ$BLXTbQeVjP~WY}y@8Z| zfAYZ-e;oLiEWR0(#l!&4KR}O@oMcYVzn=I*=a2MueS`M6+8QZ_M|B8QS6z{L+R6}m zB55nNRocv&w~+C`>PX;hLv`+}@blS4bXVYfaGKT->PM`~t#>6`RrvZicL|af9xslV z_7n$nXyIA%tM^4m173mR;D9ciO_6Ids&i{|&Y+LlQ3buC(Q6~Ci_Z)pY|Ico8a6lf z4c)|kdAts;t=_uay4;eN^CtH0x7ZOeslZmfVWH&|SuJ%VUp}yL-NsY3+n+rNdBF)eEFCa$CQ%lvDLSPM}-cnYtS|H=it^7p!|%sL0cJg z1gFEGaZn+3YK{xIdSYb+!X z(f8vK)eE*dAN*}(o0@%-@p`@Y2s)Q`c%h46Y0%MbCFWCc@>*-66b;kEz4XyPwvc8$tB`gcz zYe6@DWeIhV7qO7+F5yMBB5q$SI#?0qi$M7Zt{He~Ou zQvd9l=BRa^L=`}@T<-0LWjz+_z{e{k7`$Evd+skry)`xDvFclc1;K^Eg<2c19*1rx zo6z6kHmu8;;Ee^xC0l)14-M`Gx`=29X@j}m1>Gq*TZt|r>fmd9p1A>iw^EPXmK3aa zo~E9()fOgw(-D0)5XKn`6yafU7?$v(eap3(gRWy$U; zlge@@^V-LcSlWx)i?&$4UDn*J?(nS>`uC&}#}ZbwQ72GqWRjuD%d$r)o++VmUKoBM zoh3xBrg#^E-a+3rkwkBODM z4$mr=l@_ES7eE7YfFC<^8*=%Y-P)mpOPmd&2Rkeb)JIsk9tzxiG{enDX@_<8_WY{z zm%j0(M@rA>Ydbqtm36(!!r&y)(RUfLo#+`Hy&IWqbeiRwa_tFqb8r%@vPsC7xcQ%u z9!JqJaZ}((AYDDCW2`R^wg94cs33v8O3MmI%%k>uR`M!Xh88A{yq1s1szJIjGG$#S zk?(Nb2>P{%!s+No945CL5?N^Be8Iz+j3Q?bqZs@ocZ{P&hP2F#GMC-44_sRWT(>*6 z6JOMvS)?-Osd9gLnfD|kScCS}zH9I$50?eq)kQu-(hnys@hykmfW6KkyF;6<<*wmh zzL9Nq+vnO8UcZy`7;+ArA^4Y44%+LZuVw&T9jv;9odJ0bj~XZo&3w^&#Pf{20D5k^ zJk7D(x!$v)>ATt&VolJ*f1msY)?+=3p4@xTBj63L;bCvzZSQfd7W=SA?~dM$TqQKU z?2zW$>K)H`+26F$v#9i9>|)L-u1jNm5V$vw$1Av3ax~ClX4U}u1?cjI<7&W@+hXS{ z#OWskX}=`XTH-s-R@iS78&@m=F9~= zXImWmP*Hdi2$wle&bG*9_thHq$W3!Kma4!jJasH!=u37ToOO5mYDl zT8b=EuH9FzZpt|z-v8JSf{u(?Q6|&@S;;aYOCd)Ee8oR=!_`AubNYP~kawzJOjmTr zLF9zI?7ir_6*XtKICaD~*XNFs=63$##P6^ETjKA~oBMdp6=bhxaRm=7tlgqlfap}o zE5BpK$7RvK7Dp_S>G4c4bT~ozd*Q_5h&t|(Tp0SjlSu;Iukh4 zo}m;dhI-I5v{N#tz?hBQb{bsGn;p((39eXrk!Xr>hLr%=M;?Dg+Z+g^@(_dX=gG3G z2nZwM0CbC7MYAo$lmimFNZ?x}`{i|z;U|eOV$gnrhYbx}4H*sTEYPjCtNUD;nJ110 z(~-~kUvRIm4K3be#NpI4pMHWTR!#yRG50Cocy93QDp5(>btAi_#N#zEn1|rt>V9xC zWInX=My~nf8nfJys19xneGBW<&aoSs=zBI3SmG*^xY~CL>+pa%R*|Q0{a8rb=pEh{ zR9&glX7v0dor9&SPoH=nt4ix~_S-lPFc)O-FXBqYK3q>0_2!jEELk83I~4uBx`}D_ zEOPU>@60RMGPY(&e<7o}FXqaKa5hsx`lA3UBobWutE+(3(Q00{FvB};9(xCKoc?<)}=eE zfAJo?UE4`Xo;?CmxJ)L(z}VnmYGcQCybENB?x2MBf0%`L7{iyG# zGjRpHd7ZdY16D)ZkT<7Z(Hcy+iJC4k+I6h=$|vR z!@u4Zte8CXcrQRs5wZZ3gM|xO>Sw%1*gWMTHV8XL(4Tq{l*6_J%D3PwEe=HN5ix@H zx7JE|g-KL2%!A!*hyT6asTU>@N6X*=9vj7YBxFYeEkfYvX3r+h97iQ)!f0}Ok!W@8JH}ExhSnx3<^g7@RyBV67 z!!9hWLH=c)kXkr1_9@`<>tY*%e{udhXze48yHjnIM*a0L`lNfd!BSuy5HnSf!)bK# zF2KC3^Q?pii_bu3AteQ+X;FZSg>(W>N%Q1s_;#K5#cl6+k4jS<%L~tXU*wwh!n4jp zT&YL?6^Qg(WXF0{<+UY&CZsO+%1W}*OSpbKd>^?a_9%3X8}katihpJHtOQyDv15GO z&R85|y}*v#Yj803-cKHf?9qC!>Eekq+Vu zZ%NtiTPG98;Wx~3%pUsacDXiKsWf{J5C8bnybXQc2zVc`nhg=gW-Ad=xI2DLN#*HZ2ptG-lv!HDz= z-=>ZdfOb8j%%zt?&(b1=B!Fzq9gX}EyxP)>QYEcNN(-m1EC%lrOK|64t(JHN8Bjqj z03N0t4IP71fE;yf&&X#3VLRlteYVAW5;iji@MZl*Cc~fggN;ZB(Nz$cg!Np6ePwTu zI+XqvDd(hbvbNsm51jk+zDxFppVsFbc$Usq;4}qTg03R}N}S2nkngxM=?v)HDUNyS zYV_m&?|r%G-TV8!J=@-C`Yx_BNU2XWkki9;zc=x!R=j#1>K#U5{wHf(g7r485HB*r%5Zacu0<++Z&C+I|c0uj;DAs?A6eXsDf{SEp2CFX^eftw%2h6T!(KFEDMWu-E2$ssOm9@N*?em$oO$QslU;i zt&Ug9g2kPLo=&o6kKPF~eCYK-iz}y}z3_l>vqO>QIiYzKPt7aQ+dF^3NR`2F);iBd ztYk(m3=v?7grF9{9BC%6fkqg3ZHFBOOFIXZf!r%TVGO@q8VyUo&5J0y*c)VWRzeFq z05X%W`CmjA^Q0Mn00|0j=o)1lQq)u2}qKm6Oj0RhM+=$sPxLuq9K;$^TOht3O~p^*7N6*lUyQJvk4 ztn4X^o%Rnn3_8i)>l8f$Yk(E%y~HP8k$&MN5m18tR=!(bRms1R#g^l zfi5Ju?8jLWTimQ=j#}U3>ip_4-hM<#%2Zr$%3|jypM@LZu!wv@4|ajV^KdPz0?!6& zTS#aOYdt#oZNinZAnUK82JjUbCd4p0PA=RjBHa15LZ~vE%cP2OZG{SqJ#L_Gj^7p9+MNkk-&D4cdxaXVOROVyTXT( zYst0Nb-5OoTWPM&SKkD-buLtQ1%57b)!=99CXL1#$K&c6_(lEnlPSxMJ}$O;&ql;3 zfUmf+bC!dFEuxH&1CHhFH?Kbs=z#XL=kz0*fow6buFIfHo2DPvz}skrMJN2E%p>ET zl;PH9F_R$QVVNQSYe;5jTro2EavL0sHSKn;fi}2x&>s?ZGv0$;CGg$OIJRrDr(UT? zEz4+Sx%aeOj^Xzr^5 z%7TiLAfLpj3aSle5Z%mZF%Tub;C4hpj5rx0Yqds>VG!MGP<8>er54~TEGvV1nKdHL z0$(~w#o0=k8!}$;&Hnq%b;-H8)l5j)I72kX|-^QgW?1swfb>Q#8E z^I%0cH@&PN59Mu--W9YVPC|6Sil?!Px#GeAgN8xu9D!nP0>)h%IB1MB3n9&q;DFj3(MFS%+eV+EnQ-Fw70UM`_D7Q3~J$8vNEL!}AOV-0$N5h3OE<)L@&qVqMn zkKh;hw$+`%7d8~p9m1Ddnx47A&*0{MN4>LOmYF5`Qz*ke+71c5PQ)V_Z{`5L%09?u zN@l^i%o?LEU_b7)axwZYKZE)o^EQkEtnQ^hBf3Y7M)r5SXCr&-0xjVeiHMa1Z0jIn z{rI7sku%8G3P{^>d59zK!Uvy_mY3 z{EL2Iu&~MR@P8@4>jeJ-UvReHQpCA}PPaQ*3*bz;@=$wg;9Lg1TI3RuNg|4TkzHhq zuh%IC@I8U?DLd9!T?Xf~#cA$GbDults zs3b+F)viobqgik&J>NRm*2qC|hKPZj6ML7C>v_EiSaPgP8BLEGU90Ok1CS#afmJ4M z#}3A>gx-}r+g(KtR$2O;2hnGCtb~ZkXC1-6bpFt#!omWLkt_79RHkoxSFLJ#8Bq>D zcx@fzC~nl6pf`A!RunAgX4uSbX&&N#Kn@+vy~<2v9uPZWK2rG9;84nEjB@7A?x%*Pi%XS*Cb|!5tdaMr7sWzF4Uvw!f*ySp&}HktWZfr z8O`6LHn6hvlDfGtO?-v^r7b1wDSSVRFKV?c_8Y5Nvih--5cV)^>3xh^%JaO3IRidf zrRxol5?>P$>2^AZl?0u~kOS z%-hXTz>fO*22{D{5nrs4vIaVuUQZT=o{K$&EMo&!beB2SbC>XV%PqLRfn&MweckA< zQh~R@zpNNQ?T$m6<(t6l0W~`0aG{bu+IE_{ef-oqGI=)r%^&RbnZy-YB{uTE!GHe3w6!V+^~lc>B8vhZnOT%fh<(%Mf4QAjW!x*0OY)-23q``eeTSQ!gjYCocaI~@BZ1#4^fIrdx2#M*>ntcUr> zn#-IU#8<3Kp>!VPUISsSdj4d`Y5w}-QGi#DuwWf4>*6_Z)mfXqth2Z8p4e6nZ+q@& zS3zzbYH=1gS`c442rIfScO)u(aP23oe|k9dFneh*W+9|DkQFfs+Rm_?87F}!mi%}} zXa4GwQy_N~VikU_9uWvX<7fyy5!<@={q#MF9Q}dVQ@nC9aYwfQ9BKd%g}}N;)MTrh zx4nz&kJ*$NVr~Fma4&3UL>|jABpeyzk8a5G{2RBMKl%BXd$^VYtfI%NGw4s^3k1uO=T%7NU2|^Ss`9>?y1)#x>=z z!uamk)2JVLcj{PDm2Vs8kuSfwPjJgaWMTBw3Y9vRiGDDvOE`AXGfeQD&kLSmLGI|B=?M1gBtZ)?4 z9JV)XF6I;r?j^b+Zbk3uuXxDju5XG04(5iiZgh}`yNNUVI`LX>`ZDNY#655|=9dE< zyml()K;|1lHaBN@*NAEWVXU8NxTym$zx-FF0M>yXV|F*Ihk-p$y}%qyDNp1&$wA7kgzJhh6okfMT;nRi%Dz!?4zbMZtP z0~WM+1wYB~nv9I#FwKj9<_g7+aRBB|{>l_^RN%^N&7$WhPzbOi!etil2U-t27K)vL8c5uU0H`Y7baQ!wD)xRk$&4c|+ zQo!WW&m`>TNt+ZfDPU5-q<~2QlL96MObVD3FezYCz@&gl0h0nI1xyN<6fh}ZQoy8u zNdc1rCIw6im=rK6U{b)OfJp(90wx7a3YZiyDPU5-q<~2QlL96MObVD3FezYCz@&gl z0h0nI1xyN<6fh}ZQoy8uNdc1rCIw6im=rK6U{b)OfJp(90wx7a3YZiyDPU5-q<~2Q zlL96MObVD3FezYCz@&gl0h0nI1xyN<6fh}ZQoy8uNdc1rCIw6im=rK6U{b)OfJp(9 z0wx7a3YZiyDPU5-q<~2QlL96MObVD3FezYCz@&gl0h0nI1xyN<6fh}ZQoy8uNdc1r NCIw6i{OwTS{|7J3T;c!# literal 0 HcmV?d00001 diff --git a/tests/test_splash.py b/tests/test_splash.py new file mode 100644 index 0000000..6c2bb82 --- /dev/null +++ b/tests/test_splash.py @@ -0,0 +1,66 @@ +import tempfile +import unittest +from pathlib import Path + +from splash import load_splash +from tools.convert_splash import to_framebuffer_bytes + + +class FakeSurface: + def __init__(self, size=32, value=0): + self.buffer = bytearray([value] * size) + + +class FakeImage: + width = 3 + height = 1 + + def getdata(self): + return [(255, 0, 0), (0, 255, 0), (0, 0, 255)] + + +class SplashTests(unittest.TestCase): + def test_converter_uses_gc9a01_rgb565_byte_order(self): + self.assertEqual( + bytes((0xF8, 0x00, 0x07, 0xE0, 0x00, 0x1F)), + to_framebuffer_bytes(FakeImage()), + ) + + def test_loads_exact_asset_into_existing_buffer(self): + expected = bytes(range(32)) + surface = FakeSurface() + + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "splash.rgb565" + path.write_bytes(expected) + + self.assertTrue(load_splash(surface, str(path))) + + self.assertEqual(expected, surface.buffer) + + def test_missing_asset_returns_false_without_changing_buffer(self): + surface = FakeSurface(value=7) + + self.assertFalse(load_splash(surface, "/missing/splash.rgb565")) + + self.assertEqual(bytes([7] * 32), surface.buffer) + + def test_wrong_sized_asset_returns_false_without_changing_buffer(self): + surface = FakeSurface(value=9) + + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "splash.rgb565" + path.write_bytes(bytes(range(31))) + + self.assertFalse(load_splash(surface, str(path))) + + self.assertEqual(bytes([9] * 32), surface.buffer) + + def test_generated_device_asset_matches_framebuffer_size(self): + asset = Path(__file__).parents[1] / "startup_splash.rgb565" + + self.assertEqual(240 * 240 * 2, asset.stat().st_size) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/convert_splash.py b/tools/convert_splash.py new file mode 100644 index 0000000..57ee59d --- /dev/null +++ b/tools/convert_splash.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Convert the supplied transparent artwork into the device splash format.""" + +import argparse +from pathlib import Path + +DISPLAY_SIZE = 240 +ARTWORK_MAX_WIDTH = 224 +ARTWORK_MAX_HEIGHT = 150 +ARTWORK_CENTER_Y = 115 +BACKGROUND = (0, 0, 0, 255) + + +def compose_splash(source): + """Center the source artwork inside the round display's safe area.""" + from PIL import Image + + artwork = source.convert("RGBA") + scale = min( + ARTWORK_MAX_WIDTH / artwork.width, + ARTWORK_MAX_HEIGHT / artwork.height, + ) + dimensions = ( + max(1, round(artwork.width * scale)), + max(1, round(artwork.height * scale)), + ) + artwork = artwork.resize(dimensions, Image.Resampling.LANCZOS) + + canvas = Image.new("RGBA", (DISPLAY_SIZE, DISPLAY_SIZE), BACKGROUND) + position = ( + (DISPLAY_SIZE - artwork.width) // 2, + ARTWORK_CENTER_Y - (artwork.height // 2), + ) + canvas.alpha_composite(artwork, position) + return canvas.convert("RGB") + + +def to_framebuffer_bytes(image): + """Encode the big-endian RGB565 byte stream expected by the GC9A01.""" + output = bytearray(image.width * image.height * 2) + offset = 0 + for red, green, blue in image.getdata(): + value = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3) + # FrameBuffer stores 16-bit values little-endian, while show() sends + # the bytes directly over SPI. Writing the high byte first therefore + # matches the byte-swapped values used by MicroPython's RGB565 API. + output[offset] = value >> 8 + output[offset + 1] = value & 0xFF + offset += 2 + return output + + +def main(): + from PIL import Image + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("source", type=Path, help="source GIF, PNG, or JPEG") + parser.add_argument("output", type=Path, help="output RGB565 framebuffer file") + parser.add_argument( + "--preview", + type=Path, + help="optional PNG preview of the composed 240x240 splash", + ) + args = parser.parse_args() + + with Image.open(args.source) as source: + splash = compose_splash(source) + + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_bytes(to_framebuffer_bytes(splash)) + if args.preview: + args.preview.parent.mkdir(parents=True, exist_ok=True) + splash.save(args.preview) + + +if __name__ == "__main__": + main() diff --git a/touch_drive.py b/touch_drive.py index 6d0a4a7..fdd83bd 100644 --- a/touch_drive.py +++ b/touch_drive.py @@ -109,12 +109,26 @@ def Timer_callback(self,t): def BootScreen(self, LCD, sleep=4, version_number="0.0"): self.mode = 0 self.Set_Mode(self.Mode) - LCD.fill(LCD.red) - LCD.write_centered('Track',55,3,LCD.green) - LCD.write_centered('Session',90,3,LCD.green) - LCD.write_centered('Timer',125,3,LCD.green) - LCD.write_centered(('Version ' + version_number),195,1,LCD.green) + + splash_loaded = False + try: + # Import after LCD construction so the 115,200-byte framebuffer is + # allocated before this optional startup feature uses any heap. + from splash import load_splash + splash_loaded = load_splash(LCD) + except (ImportError, OSError): + pass + + if splash_loaded: + LCD.write_centered(('Version ' + version_number),205,1,LCD.white) + else: + LCD.fill(LCD.red) + LCD.write_centered('Track',55,3,LCD.green) + LCD.write_centered('Session',90,3,LCD.green) + LCD.write_centered('Timer',125,3,LCD.green) + LCD.write_centered(('Version ' + version_number),195,1,LCD.green) LCD.show() + return splash_loaded def SetBackColour(self, LCD, backColour): if backColour == 'green':