diff --git a/graphics/compress.py b/graphics/compress.py index c55ba8ad..bf577da3 100755 --- a/graphics/compress.py +++ b/graphics/compress.py @@ -46,20 +46,20 @@ def crunch(n): return a[0] -# LCD Display wants RGB565 values, but wrong endian from us, so green gets split weird. +# LCD Display wants RGB565 values, but big endian, so green gets split weird. def swizzle(r,g,b): # from 0-255 per component => two bytes b = (b >> 3) g = (g >> 3) # should be >> 2 for 6 bits; but looks trash? r = (r >> 3) - return pack('H', ((r<<11) | (g<<6) | b)) # these values tested on real hardware -assert swizzle(255, 0, 0) == b'\x00\xf8' # red -#assert swizzle(0, 255, 0) == b'\xc0\x0f' # green (6 bits) -assert swizzle(0, 255, 0) == b'\xc0\x07' # green (5 bits) -assert swizzle(0, 0, 255) == b'\x1f\x00' # blue +assert swizzle(255, 0, 0) == b'\xf8\x00' # red +##assert swizzle(0, 255, 0) == b'\xc0\x0f' # green (6 bits) +assert swizzle(0, 255, 0) == b'\x07\xc0' # green (5 bits) +assert swizzle(0, 0, 255) == b'\x00\x1f' # blue def into_bgr565(img): diff --git a/graphics/graphics_q1.py b/graphics/graphics_q1.py index 6269a772..11e41cb2 100644 --- a/graphics/graphics_q1.py +++ b/graphics/graphics_q1.py @@ -6,39 +6,39 @@ class Graphics: # (w,h, data) splash = (264, 88, - b'\xed\x92\xb1k\xf2\\\x14\xc6\x03\xde!\xa3\xa3\xa3\xdd\x1c;:\x1ay\x87.\x82\x0e\x82\x08\x82c\xc1\xa5c\xa1K\x9b\xa9\x83CG\x11\x11\xdf\xd1\xc1\xa1\xa3C\x07\x1b(\x88\x83\x88`y\x11\xf4Opt\xf4\xcb1Ms\x93\x9c\xe4\xdeDk\xed\xd7\xf3\xdc\xc16\xb99\xe7<\xcf\xf9)\xcayI\xcd\x17\x1aj^\xf9\xb5\x9aT\'U\xff\xd3B\xa3\xd0\xc0\x9e\x9f\xbbJ\xcd\xf6\x02Ny0\xbd\x8bWa\xdeY\xef\xe6\x1dw\xcdR3Qy\x80Z\xef/\xd6\xb7\x90\xbas\x03\xfa\x17\x1a\xf6\x9cv}\xab3\xbcsN\xb4]@-\x8b+v\xe1\xb8\x9d\xde-W\x96S\x879\xd8r\xa9\xc9\xf7\xb5\xdd\xf2\x1d\xbd\x93\xf0U\x81>1q\xe7\xc3\xc3\xf4n\xbdS\xf3\xf0Wm\xb3\\\xc1_\xf0\xb6\xd4\xb43\xefn\xed\x1d\xc1\xaf%5?\xef8\x99\xc1\xbdB\x03\\\xc3\xaf\xbd\xbf(\xa6w\xd3;k\x93\xd0\x0b\xb6\x07\xbf\xcel2\x9aTm\x87P\xd7\xfa\x16r\x98w\n\x8dR\x93\xdf_{\xd1\xddZ\xa9\xa8y\xab\xaf\xed\x96\xe7\x81\xf7oU{\x7f\xe9n\xed\xac\x1c\x92~\x02\x0fv\xe6\xf0k;\xb6\xdd\xc23\xcb\x0b\x10\x1f\xee\xaa\xbd\xe0\x13\x8a\xc6Cm\x83\xdfpx\x80g<\x81\xce\xb7\xf1\xb2\x06\x02\xdd{\xabm\xecJ\xb0A\x8b\x16p\xb5\xde\x01\rb\xb7\xee\'@\x97U\xa3<\xc0\'?w\x1e\xf8g\xf6\xdf\xce>\xc4\xc9\x1f\xc2Cy \xe2\x01\xf2m/\xe6\x1d{\x9aCy\xb09\xb0\xb9\x98T\xd7\xbb\xe5\n<\xc0q\xaa\xb6\x17\xcb\x95\x9c[\xef\x93\xda&\xf5\xc6\xff\xfe$\x1e&\xd5\xef\xe5\xc1]\x19\xe3\x01\x9e\xd66\xeb\xddz\xd7\xdd\xc2\xb4\x87\xf2\x00|\x15\x1aP\x7fzgwz\x7f\x99w\xeccw\rr%\xe6\x01\\\xa8y\xe8cu\xf8)<\xd8\x7f\x9f;\x0f 5_jv\xb7\xb5M\x18\x0fj\x1e\xf6\xcc.\xc2\xa7`\x17\xeb]yP\x1e\xacw\xd6Mo\'\x91+1\x0fj\x1ef{\x7f\xe9n\x15\xe5\xe7\xf0\xc0.j\x9b\xf7\x17;\x11\x9b\xe4\xe5\xca\xf2\xf6}\xe1Iu\xb9\x82\xa7\xce.dyP\xf3pO\xcd\xbb\x89\xb6s\xb1\x9f\xfb\xb7\xdc^\xd8\xb7\xdc}\xfd7\xc1Ym\xa3\xfchM\xaa\x85\x86C\xc8\xf7\xce\xe1}Vh\x14\x1a<\x0b\xc7\x17\xbb\x80\x1e\xc7\xf2\x0f\xc4yI\'\xfd^\xcd;\xeb\x9d\x9a\xa7\x1cH\x8aRj\xb6\x17\xeb]y@I\x90@\x85\xc6\xbcSjR\x0e$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H$\x12\x89D"\x91H\xbfAZ.\xd1g\xb3?\xff\xfc\x87\x8d\x8d\x96\x96\x91\xa8\x901Zl\x8cV\x98%\xfaZ.\xfc;62\xee\x95d\xdc\xe9\xf5:\x1b\xfeA\xa7w\xfb\xc8\xa5\xa5\xca\xa5\x8d\xa7 \'\xec\xf9\xa1\x18q\xb8\xa4q\xcfFf\xf7\x9ev)\xb5\x89l\xd0&\\s\x84$\x1a\xb6M\xdfV\x92\xec\x99\r\xf5\x1b\xa3\x97\xf8\xcb\xe5Y\x11}\xabeE.D\xfd\xf5\nN\x83\xf3\x1d\x1b\xc6#\xc2\xb8\x0f\xef\xcc3!&\xdb\x9ch,pr\x13a\xb8\x94I\xaa\x93bNt\xfd\xe1J\xd6\x8b9G=\xa0F1B\x8d\x8a^\xd4\xaf\xf5\xbaI\xc5\xb3,\rb"\xc44\x04\x11\x91\xe8\xc7N\xfas\x7f\xf2\xee\xf7Dd\x0f\xa3!\xd2\x9c\x1c\rrD\xb0Q\x1478\x112\x0ex/ZV\xbb|(>\\\xc9\xd3\x10\xeeE\x8e\x06\x9c\x08\xf7\xfbD?:\x0fz=\x8a\xfbp\xb2\xe5h\x90&\xc2C\x83\x98\x88\\:\x9a\x17\x8c\x08-\x13\xb9\x06\xb7\x15Y\x1a\x82\xbdh9Y\x1a\x90\xf9UO\x87\xe7\x18<\\G\xf5\x1fD\x84<\rRD 4\x88\x88\x88\xbeK?\x11\xdae\x8c\x1a\x1fD\xe8\xf5\xc8I\xe6\x0e\xa1\xc17\xff\xb7\xf0\x80\x13\x11\x8d\x06!\x11\x014X\xe7\xe1\xeaxk\x0b7\xe4d\xa8$\x11\'#\xee\xfd^\xc6#\xe2\xe4:\x1e\r8\x11\xdeD\x8d\'\xcc1\x9ah%\x88\x07\xe3\x11)\xa1\x1a=\xf1|F\xcbCC\x00\x11\xda%\xde\x19\xa1!`~\xbd~b\x1e\xd0-\xd9\\\xc3[1\r8\x11\xfa\x8d\x0c\rl\xe6O\x11#B\x8e\x074\xd1\xebH<\x00\x11-!\r\xb8\xed\xdf\xca\x83^g#\xd8\xc5\xeb-O\xc5\xe9y\xc0h8\t\x0fI\xf6\x8c\xdd\xfb\x9d<\x18\x8f\xf8\xbd\xd3\xf3`\xdcc\xd5\x8f\xc5\x83\xd1\x92\xa1\xc1\xdc\xd9LQ\xf1\xcel\xa8$\x032l\x9d\x01\x0f)6\x92\xe5\x81\x8d\x95\x94\x98\x86}\x8d\xecW\xf2\xc0F\xc6\xe3\xc7\xe9a;\xd3o\x0e\xe1\xc1\xbb\x15\x84\x08UD\xc3>\x83K\xbc\xf3~\x9fI\x99\xbe\xdf\xc0C\x8a\r}>\x02y\x80M`Dxi\xe07\xf2\x15<\xf05\xfc\xbd\xe1\xbc\xde\xc6\xe4A\x15\xd1\xb0\xff\xb6\xe7!\x02\xa1\xc1\xc9\xd1;=J\x04\xda\xf7sg\xa7\xe2\x01\xa1\xe1\xcf?n\xe3I\xc4\x89\x8f\x08l#r\xbb<\x06\x0fAD\x18\xf71xP\x8d\x1e\xe2x\x86<\x1b\xb3!w\xc6\xc8\x8d\xa1S\x96\x8dbU\x98}&\xed\xe5a\xe6\xfa\x16=\x89\xbe^\t\xe3\xc1\xd3?x\n\x17{\x89\xbe\xd0\xc9\x08\xdb\x86\x969\x1d\x0frDxy@3E\xb7\xa2W\xb0\xea\xe1\x87\xcd\xb4,\xd7;\x8b1%:\xdc>\xd5\xe8_{s\xf2\xf2 \xed#\xe3J1\x83e$tr\xcdW\xf8z\x1e\x82\x88\xd0o\x82y\x88\xb2\x15\xbd\x1e9\xc5\x9c{>-\x17\x95\x08\xbd\xce}\x1e\x93\x87?\xff\x9c9\xe2\xf0\xe0\xa6:.\x11\xfc\x16N\xc5C\x10\x11J\xfa0\x1e\xec\xad\xe8\x95Ch\x88N\x84\x8b\x86\x03xx\xbd\x8d\xcf\x03FCt"\xdc4\x9c\x8e\x07\x93\x88{d\x9a\xca!<8_\xcb\x13\x81\xd3\xb0\x9f +K\x04\xdf\xd7\xd2\x11x\xa8\x1f\x87\x86hDxi0\x95\x92\xe5A\xbb\xf2\xd4\xaa\xdboriO\x8dG\xbc\x82\x9f\x08\x8e\x87\xcc!4\xc8\x12\x11\x96\xa2,\x11~\x1a\x14%\xd1\x8f\xc7\x83v\x15\xcf?\x1b\x87\xf9\x90%\x02\xa1\xc1\x14\x1b\x89\xdc~\x92\xe3JK\xcb\x04\xd6(\x06\x95\xf0\x12\xa1\xa5\xb9\x1a\xe3Ch\x90!"\x9c\x069"\xf0|\xcc\xfcg\xd1i0Za\xd9\x84\xd2\x90Q\x04\x12\x13\x81\xd3`~\x99\xe3:\xf5\xc3z\xe8\xd7\x9c\x97G\xfe\xcd\xc3\x95\xf3&\x11Z\x83wm\xdc\xbbj\x14\x0f\xa3\xc1\xf2\x92\xe8\xe3\x9bac\xa3%N\x11r4Zx\x92l\x96\xe8k\xb9\xe8\xdf\x05\xect\xe8\xdf\x87^g\xc3?3\x11\x0bF+\x97Vd\x946\x9e\x82\x9c\xb0\xe7\x87bH\x06Y\xa3g\xde\x19\xbd\xde*jx\x0b\xbd\xc8\x9e\xcd\x9bC\xbd\xee\xaf\x01\x9b`#]\\\xa3\xf2Q\xa3"\xbf\xcd\xb0\xad\xfc\x07' + b'\xed\x92\xbfk:\xcd\x13\xc7\x17\xdc\xe2JKK\xd3Y\xa6\xb4\xf4\xc4"\x8d\xa0\x85 \x82`)\xd8\xa4\xfc@\x9ad\xab\x14\x16)ED|J\x0b\x0bK\x8b\x14\xe6 \x16r\x08\x06\x11\xf4O\xb0\xb4\xf4\xf9\x0c\xf7\xdd\xe7~\xed\xde\xed\x9d\xc6\x98\xaf\xf3\x9e\xc2dowf\xde3/B\xaeKy\xadU\xcck\xe4fU\x9b\xd7\xe6\xfe\xd3V\xb1U\x14\x9d_\xbb\xda\xe5U\x17bTyZ\xc4\xcb\xd0[\x1ew\xbd\xa5;g\xbb\x1cS\xfc\xffC\x7fT9\'\x0fPa_?\xee\xac\xe9^?\x0f\xadb\xab\xd8[\xee\xeb\x87\xbe5{B\xf6\xf5\xed&\xafY_\xdb\xe5\xa8<\x8c*\xf0\n\xb2\x9e\xcaCm~\xdc\xc1njs\x98\xa7\x95\xefi\xd1.C\x9fy\x8d\xb3\xca\xc9\xdd\xd7\x9f\x16\xadbm\x1e\x95\x07\xeb\x9e]\x19\xfe\xb7\xfe\xab\xcd\xdf\xbf\xf8{\xff\x96ks\xf0\x08,\xc1\xaf]\xd7\x7f\xf3i\x01\x15\xec\x1b\xd7\xcd\x03\xc4v3\xaap\x1a\xf8\xfc!`\x1bQy\xb8\xa3\x9f)\xeb\xf5\xa9<\x10\xf2\xfe\x05y\x0e}\x8b[8i\x97\xad\xdc\xc7\xdd\xbe\xee\x9cpm\xbe\xdd\xc0\xa9\xbd\x0bU\x1e\xf2\x1a\xdc\xcbkn\xa2\xf9\\\xf8\xb9\x7f\xcb\xab.\xbf\xe5\xae\xeb\xbf\t\xce\xf6u\xf2\xabU\x9b\xb7\x8a6!?\xdb\x87\xf7\xacUl\x15\x9d,\x9c_w\x14j\x9c\xcb?\x10\xe7%\x1du\xbb\xea-\x8f\xbb\xbc\x86s@\x11\xd2.\xaf\xba\xc7\xdd\xa8\x82\x93@\x81Z\xc5\xde\xb2]\xc69\xa0P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xa1P(\x14\n\x85B\xdd\x82r\xfa0a\xd2u\xc1\x1f3\xda12zx\x86\x8c\xde1f\xc2\x0c&\x1d&rz\xf0\xbb)}6\x92\xb1\xbbo\xb0\t5\x0b\xeb\xc0\x00\x1f\xe9\x9cJ\xb64y\x93:\x19\xd3\xd2K\xb4\xde\x92\xe4\xd9\x98\xd2\x19\x1d\x18\xf7\xba\xca\xfd\xact\x13\xae\x89\xd2\x9c\x1eg\x9b\xde\xad$\xc9\x98N\xe8#\x1b\x18\xff$\xec\xf7U\x16\xf66\xab\x87\xb9\x08\xab_eb\x1a\xecw\x13\x1a\x8f\x88gc]P\x8b\x19\r\';\xa3\xcfB\x9c<2\xf5\xdeRdB\xed)\xe6B\xab?\xbc\xa8zY\x17\x1a\x92>J\x11rTY\x895Y\x83\x8d\xff\x92\xaeJC8\x11\xe14\xc8\x88\x18&\xe2N\xda\xde\x9f\xba{ "\x98\xecp\x1a\xa2\xf4\xe9\xa4A\x8d\x88)\x8d\xe2FL\xc4,R\x8eG\x96\xd5\xef\xf5\xd2\xcb\xc3\x8b:\r\xc1^\xd4h\x10\x13\xe1\xfe>LD\xe7\xa1\xc1\xa2\xb8\x0f&[\x8d\x06U"\xbc4\x84\x13\x91\xceE\xf3"""\xa3G\xcd\xe1\xdcJ\x95E\x99\xa4\xc8KN\x99\x06\x7f\xff\x9a\x87\x871\x8d\xceC\x93E\xf5/#B\x9d\x06\x15"D4\x84\x11\x11}\x97~"\xeec\xe4\xe0D4X\xd4Iz\xbdD\xa3\xc1\xdb\xff\xcf\xf0 &"\x1a\raD\xc8h\xb0\xe2\xe1\xe5|\x1e\xd2\xfa\x8c"\x0f\xc8\x83\x8c\x06\xe4\xe16x\xf8\'q\xaf{#\'\xa0a]x\xd0\x91\x87\xff\x7f\x1eTcJ5\x82< \x0fV\xcchZ\'\xc8\xc3\x95\xf30\xa6\r&\x8eW\xe3\x9c<\xb8i@\x1e\xae\x95\x877i\x8e\x12;\x1f\x0f^\x1a.\xc9\x83\x86\x93Ju/\xcfC\x8aL|>\xe4<\xc0&R\n487\xf2\x1d<8s\xbc\n\xb7\xf7\xe7#\x1e\x0fZ(\r\x10\x03\x0f\x11"\x1a\xec9ft\xff7/\x11\xe2\xba\xa5\x0b\xf3 \xa2a]H9|\xfa\xbf\xfa\x89\x10m\xa4y1\x1edD<\x1b\xd1y\xd0\xc8@\x90\xcb\x14\xcchF\'\x8e\x98\tnL\x1c;\x9b\xc6\xca`\xfe7i/\x0f\xa6\xeb\xad8\x86\x89*\x0b\xe2a&x#\xea\xc2\xcd\xde0\x11\xe6D\xe4u]\xc8\xe8\x97\xe3A\x8d\x08/\x0f\xa6\xe2\x9a\'S\x1d\x97\x08\xe7\x16.\xc5\x83\x8c\x88\xf4\x89<\xf0\xadT\xd9)4D\'\xa2\xe1\x9aa|\x1e\xfe|\xc4\xe7ADCt"\xdc4\\\x8e\x07B\x9e\x05DT\xd9)\')\xa2\xca\xc3\x83gc\x8d\xffr\xa5s\xee/\xaf\x92\x1c~"\xaaR\xa6\xa2oE\x85\x88\xa0)\xaa\x12\xe1\xa7\x81\x90a"\x1e\x0f\x0fz<\xff\xb3@\x1f\xaaD<2\xd1\xdb)\rs\xcb\xc91]\xd92\xba,GI\x9a\xc3KD\xda\x91C\x95iy\x9faD\x98!ST!B<\x9f\x8c\x12I\xde\xe8\x18A\xb3\t\xa2!\x13\xe2C\x85\x88G\xc9\x96r\x0e2\x874\xa8F\xd31\xefW\x97\x97\x87\x17G\x8eDP\x0e\xa7\xebgW\x8e\xd2\xcbi4X^\x86\t\xf1ff\xb4c\x84O\x11\xe6\xd81\xc4\x934\xe90\x91\xd3\xa3\xbf\x13\xc7\x84\xfa\xf7\xd1`\x13j\x86\xb2\xd01\xd29\xa2\xa04y\x93:\x19\xd3\xd2\x8b\xfceV\x1f\x18&\x9d\xd2?\x1fZH\x8d\x12\x1bS\x93Nh\x83\xf9s\xc0&\xfe\xe6`a9\xaa\xff\xcb\xe1\xdf\xa9|\x9bA[\xf9\x17' ) shift_0 = (25, 11, - b'\xcd\x90\xa1\x0e\xc0 \x0cD+&N"\xb1|\x02\x12I?\r\xc1\x8f!\xf6[\xcb\xe5B60\xd8AH/\xa5\x8f\xf60\xfb\xe7\xea\x19\xde\xb3\xd9\x88p3\xf8\x88-\xc0\xb9[\xa8\x90\x1a\xf1JRWb\x9d\xceK\x88f$\xc1\x17**\x94ST\xaf/\xc1\xca\n\xc5\x16t?sfw\xe9\x99\x8a\x84&X\t\xf8]V\x82N\x98#A\x07;\xc1Iv\x82}V\x1f_bVM}v\xbe\x13\xe7\xdf\xdd\x89\x07' + b'\xcd\x90\xa1\x0e\xc0 \x0cD+&N"\xb1|\x02\x12I?\r\xc1\x8f!\xf6[\xcb\xe5B60\xd8AH/\xa5\x8f\xf60\xfb\xe7\xca\xdd\x91\xbbY\x1c\x0e3G\x1c\xa19\xb8CC\x95\x8a#]R\xe9b\x9d\xceK\x88f$\xc1\x17PQ\x95ST\xaf/\xc1JT\xc5\xd0t?sf\xe5\xce\x9d\x8a\x84&X\tG\xb9W\x82N\x98#A\x07;\xc1Iv\x82}V\x1f_bVM}v\xbe\x13\xe7\xdf\xdd\x89\x07' ) shift_1 = (25, 11, - b'\xcd\xd0\xa1\x0e\x830\x10\x06\xe0\x13\x15\'\x10\x95\x88\x99\xcaI$\xb2$\x13[\xc2\x03\x8cd\n\x89\xec\x1b4\x04\xc9#\xf0&\xa8% Hf\x11$TNN"\xbb\\\x9a\xa5\x83\x05\xbf\xd64\xd7\xff\xcb\xf5\n\xf0\x8fK\xe2\xd8\x1a;\xb6\x12k\xd5,\x00\xcdR+L\x8c\xa5\x8d\xc9\xe9\xe8N\xb5\xe2\xa5;\xf1r\xc8\x8c\rrc\x87\xcc\x0b\xcd\xd3\xc2\xd8\xb4\xd0\x9cDZ\x9c/L0\xe1jL\x90\xd0\\"\x00\t\x89$\x00\xaa\xc8\xd8*\x02 \xd1\xc7t\xebk\x12\xa7\xf9\xfa`\x82\xc4\xe7\x05ka\xec4\x7f\x0b\x80{8\xb6\x87\xa7\xe6\xd4\xa3\x8f\x7f{\x04\xb9\xcby!Q\xe2\xedU+?\xc7Z\xb8\x94\xafQn\xc8(\xb77\xf9Vh\x1ev\xc6\x86\xdd\xfe\xefn\xc5\x1b' + b'\xcd\xd0\xa1\x0e\x830\x10\x06\xe0\x13\x15\'\x10\x95\x88\x99\xcaI$\xb2$\x13[\xc2\x03\x8cd\n\x89\xec\x1b4\x04\xc9#\xf0&\xa8% Hf\x11$TNN"\xbb\\\x9a\xa5\x83\x05\xbf\xd64\xd7\xff\xcb\xf5\n\xf0\x8f\x0be;Z\xd3\x8e(U\xbd4\x00K\xa3\xea\x04\xad\xa1\x9d\xe0\xf1\xe4N\xaa.\xb9;\x95<\x1b\xac\xc9\x03k\xb2\xc1\x0b\xae\x8b\xd4\x9a"\xe5\x9aD\x91^\xce\x82\t\xe6j\x82\x91\xe0\x1a%\x00\t\x94$\x00\xa2\xca\x9a\xa8\x02 \x11\xf7t\xebk(\xe7\xe9q\x15\x8c\xc4\xe7\x05ka\xcd<}\x0b\x80\xf0\xde\x8e\xcf\x03\xd7\xd4#\xee\x7f{\xe4\x81\xcby\x81\x12\xe5\xeb\xa6j?\xc7Z\xb8\x94\xafQ.\x1b(\xb77\xf9Vp\xdd\x85\xd6t\xe1\xfe\xefn\xc5\x1b' ) symbol_0 = (20, 11, - b'\xc5P!\x0e\xc40\x0c\x0b\x080\x1c\x1c\r,,,l\xa4~l\xa0\x1f\x1b\xe8\xb7N\x96U\xdd4\xe9\xf0\xb5J\x94&\xb6\x93\xd4\xec_gV\xe4\xacf\x1e\xab\x99\xad\xe61\xca(f\xf4\xf7\x89D\xf2\xc5H\xd6\x81\xbc\x0ed\xc7(b\x08G\xbb\x0e\xfa\xad\xbc\x1a/\x95\xc4\x10N|Vg\xed \xce\x03\xe9A\x1cs[\x0fI\xae&TD\r2G\xd1\xcc\xc21{\x9f\xbb##v\xa027\xd06\x9ajV\x8f\xef\x1eO\x1c\xf7\x7f\xeb\xed\x7f\xf9\x85S\xf5\x03' + b"\xc5P!\x0e\xc40\x0c\x0b\x080\x1c\x1c\r,,,l\xa4~l\xa0\x1f\x1b\xe8\xb7N\x96U\xdd4\xe9\xf0\xb5J\x94&\xb6\x93\xd4\xec_\xa7\xceD\x9df\xe1m\x99\xb5\x15^F\x19f\xf4\xe7\x9dH\xf0\xc5H\x86\x9e8\xae\x04z\x19b\x08G;.\xfa\xad\xdc\x16/\x95\xc4\x10N|V\xebD'.<\x11N\x1cs[/A\xae&TD\r2\xcb\xd0\xcc\xc21{\xde\xbb##v\xa027\xd06\x9a\xaa\xce\xf0\xef\x1eO\x1c\xf7\x7f\xeb\xed\x7f\xf9\x85S\xf5\x03" ) symbol_1 = (20, 11, - b'\xc5\x8f\xa1k\xc3P\x10\xc6OL\x86\xe0c\x08\xbeu\xd9l\x9a\xde\x0f\xae$\x84v\x87nA\xe4\xaf\xd8Mm7\x9cNQ;Ede5\xaf\xb9JMme\x91T\xd5R%\xc7\xba\xab\xa7\xa4z\xbb\xc8P\xde\xc5"\x87,U0t%\xc7\xb8\x9d\x19\xb8\xa1\xc8P\xdb\r]\xbe\x1c\xdcq\x9f\xf3\xdd\xee. \xc2\x97*X\x9c0o\xf8\xc4\xa4+\x9fr\xfb\xca\xa8\xb1k\xfc\x00\x7f\xc1_>\x1f\xec\xbb\xcd\'\xb8P_' + b'\xc5\x90!\x0e\x00!\x0c\x04+\x10\x95H,\xf2$\x12I\x13>\x86\xe0c\x08\xbeu\xd9l\x9a\xde\x0f\xae$\x84v\x87nA\xe4\xafh\xdb\xb4m\x9c\xca1-G$/S\xaf\xb9J\xcd4/\x91\x9aLk"\xc7\xba\xab\xe5\xd4\xd4\xaf\x88\x0e\xde\xc5"\x87\xac&0t%\xc7\xe8\x97\x198\x1d\xc8Pk\x1b]\xbe\x1c\xdcq\x9f\xf3\xf5\xeb. \xc2\x97*X\x9c0o\xf8\xc4\xa4y\x95\xd3o^\xa8\xb1k\xfc\x00\x7f\xc1_\xfeL\xecm?3\xb8P_' ) caps_1 = (20, 11, - b"\xc5\x90\xa1j\xc4@\x10\x86G\x9cXqb\xe5\x8a\x98\x93}\x84\xc8-T\xb4\x10\x91\xba\x04V\x9d\x8c\xcc\x1b,\xa7J\x88\x08\xa1\xf2\xde\xa2rU!\x11\x81\xd8\x15\x0772r\xe5\xc9)\xc3P\xd27\xe8\xa8\x85\xef[\xfe\xf9\x07\xe0\x7f\xc6\xaa\x18\x90>\xbf\xac\x028\x9e\x91\x8eg\x80\xd77$\xa4\x18\xac\x12\xca\xaf\xa2a\x86\xb4\xd4\x00e\x87Tv\xe2-\xf5\xf5\xd1\xb7K\xfdK\xf5\xe5v\x07xy\xf2\x1a \x86j\x8dA<\xaf\xabu\x18\xd9\xf3\x9a\xb3\xc4\x93\xc9\xb6\xb2\xcb6\xf1\x0e'\x97\xf4\xc5\xaa\xdb\xbdZ\x0f\xa7\xbf\x9e\xd7H\x1f\xef\xfc_\xf6s\x89\xf9\xb7\x89!\xdb\xbc\xdes\xe7\x9c)\xd2\x9c\xb3\xa7\x9e9\x8d{Z\xe5R\xdf\xee=\x8a\xc6\xa59w\xa9hd?\xb6\xfa\xf6\xfa\x90F\xfb]\x86\xd1L\x00f\x1a\xc6\xdd\xf3\xdaLHf\xb2\xea\x07" + b'\xc5\x90\xa1j\xc4@\x10\x86G\x9cXqb\xe5\x8a\x98\x93}\x84\xc8-T\xb4\x10\x91\xba\x04V\x9d\x8c\xcc\x1b,\xa7J\x88\x08\xa1\xf2\xde\xa2rU!\x11\x81\xd8\x15\x0772r\xe5\xc9)\xc3P\xd27\xe8\xa8\x85\xef[\xfe\xf9\x07\xe0\x7fF\xd9\x10\t\xbf>\x95\x058\x1f\t\xcfG\x80\xb7WB\xc2\x10\x95\x15\xca\xaf\xa6`FX/\x00]I\xd8\x95\xe2\xd5\xcb\xe3\xda\xf6\xf5\xf2K/\xfa~\x03xz\xd1\x1e \xc4\xb5\nQ<\xed\xd7j\x1c\xd8\xd3\x9e\xb3\xc4\x93\xd9\xb2\xae\xdc2\xf1N\x87\xe4.Z\xd9\xfbm\xadN\x87\xbf\x9e\xf6\x84\xef\x1f\xfc_\xf6K\x8e\xb9\xf9\x0eq\xcb\xb4\xdfs\xf3\x99)a>\xb3\xf7\xac8\x8d{*\x9b\\\xdb\xef=\x9a"\xb9|N\xae)d?\xb6\xda\xfeq\x95F\xfb]\xc6a2\x00\x93\x19\x87\xdd\xd3~2\x84\x93Q\xf6\x07' ) bip39_0 = (50, 11, - b'\xedR\xb1\x0e\x85 \x0cd`\xe8\xc8\xe8\xea\xe8\xc8\xc8\x08\x89?\xc6\xc0\x8f9\xf0[\xe6ri\x8a/5\xf1\x03\x1eFs\xb6w\xb4\xf4\x08\xe1\xbf\xbe\xae\x9e\xa4I\x1b\x19\xf8\xda\xa4]\x1b\xd0\xc8\x1aSd<\xb0B\x00\x13\xe8]\xcb,\xa2\xd0j>\xee\xd2\xe2\xae|\xdd\x85\xaf\xf1\x9e5\x18\x7f\xd3\xf6\xd4S\x15pP\x1b\xf9\x91\xf1h\xa7\xda\x9d\x9d\x97\xbcg\x8d\x10f\xf9\xa2\xadB\x1e\xbe\xb3 :\xcb\xc8UVd<\x9b\x81U\xf3\xb4\xe4!\xc69\xcf\x82(O\xce\xda#S\xa1\xc8x6!\xdb\xc5\xd3\x92\xa7\xfd\xf5d\xee\xeb\x1f\xfa\xa1\x87D\xc6{\xcej\x96\xf3\xf0\xb5\xbfs\xa6v\x96\x9e4\xeby\xbe\xcey\xcd\xbekW\xcf\xb1\xce\x03\x9e\x8d\x8c\xbe\xfc\xbb\xeb\xd5\x00\x8a\xbb\xa75\xdfn' + b'\xedR\xb1\x0e\x85 \x0cd`\xe8\xc8\xe8\xea\xe8\xc8\xc8\x08\x89?\xc6\xc0\x8f9\xf0[\xe6ri\x8a/5\xf1\x03\x1eFs\xb6w\xb4\xf4\x08\xe1\xbf\xbe\xae\xd4\x9b4\xc9\x03x\xbb\x9al\x17P\x1e\x1aSd<\xb0B\x00\x13\xe8]\xcb,\xa2\xd0j~\x8fM\xf6\xa8|\xdd\x85\xaf\xf1\x9e5\x18\x7f\xd3\xa6\x9e\xbaTpP\x1b\xf9<\xf0h\xa7\xda\x9d\x9d\x97\xbcg\x8d\x10\xca\xfc\xa2\x95J\x1e\xbee"Zf\x1eRWd<\x9b\x81U\xf3\xb4\xe4!\xc69\x97\x89(O\xce\xdayP\xa1\xc8x6!\xdb\xc5\xd3\x92\xa7\xfd\xa5n\xee\xeb\x1f\xfa\xa1\x87D\xc6{\xce\xaa\xcc\xe3\xf4\xb5\xbfs\xa6\xb6\xcc\xd45\xeby\xbe\xcey\xcd\xbekW\xcf\xb1\x8e\x13\x9e\xe5\x81\xbe\xfc\xbb\xeb\xd5\x00\xda\xa3\xa75\xdfn' ) bip39_1 = (50, 11, - b'\xed\x92!o\x84@\x10\x85G \x10\'VT 0\xfc\x84J$u\\\x82\xa0\x8eKP\'\x91\xc8:\xd2T4\x04AP\x15\xb8\xfe\x0c\xd4% H\xae\xae\x88KX\x89D"\xb7y\xd9\\\xe6\xae\xe1\xda\xfe\x80\xaez\xd9y\xdf\xbe\xd9\x9d%\xfa_\x7f]\xfeV*\xa9\x86\xc63\x896{\xa96{"\xcf\x1c\x1a\xbd\xc7\x8a}EZ/D\xf5R\xa4E\xfa\x13\xdb\xefP\x95\xaaH\xc1\xf6;\x10Da.U\x98\x13\xa1\n\xa6\xdf\xb1b\xdfeF\xbd\xbc>b\xff\x16\xebo\xfd\xad\xe1\x80\xcdDt,+\xa2\xa1\x89\x8eC\xa3}\x99@w\xac\xd8\x87\x93=\x93\xd3N\xa3x\xfe\x9d5\x9cx\x86\xcf\x9e\xc2\xdc\x9ep\xdf\xd3\x18\x1d\r\xe7R\xb1O\xbf\x10\xde\x00\x19\x07\xab^\x82d\x8d\xd5ou\x1a\xcf\xf3\x88g\xc3\xc9\x84T\xb8y&\x88\x0e\xd6\xd0\xd8\xd3\xa5b\x1fN\xee\\\x9e\x07\xaak,2:\xf7\xe5^g\x98\x0f\xb8Q\xe7\xea\xfe:\x17\xddxf:\xf2h<\x1aw\xfa \x1f\xe7\xe0\xb5\xc9\xacJ\xdd\xbb\x10\xf5.L\xe6\x1a\xf6\xbd=\xa3\x8bh\xb24v\xb2\xbc\'+\x81\xbf\x82\xac\xc4\xec\xb4Z\xa3c\xe092\xabR\x1b\xeb\r\x8de2\xd7\xf8\x04e%R\xa2\xf3av:\x1f\xee\xc9\xc6\x96\x15\x93;\xfd\xa8\x16)\xbb\x84\xdfF\x95\xba\xac\xfc\xbc\xac\xdcm\xa0\x06\xf2\x87\xbf\xbd\x861\xdcL\x96p\xd5&\x17\x01\xbe\xf9\x9e\xa3\x9a]\x16\x81;3\xabR\xfb\xb9\xb1~\xced\xae\xf1\tz\x17%\x940\x16\xf7\xa2D\x9b\xccY\x82\xec\r\x1b\x97\x8e\xcc*\x12\x14\xa9K\x90k\xd8\xf7\xfe%5Q\x11\xb0\xcb"\xf8+A%\\\xc7\xcf\x047{\xa2\xcd\xfe>A\xcc\x84\x12a\x1c\xd5E\x10\xd5a\x0cr\x18CmO\xddod7u\xb7d7uH:\xaa?_Vk?\'\xf2\xf3\xd5\x1ad,\x91~\x03' + b'\xed\x92\xa1o\xc2@\x14\xc6\x9f@T N\x9c\xa8\xc0 \'\'+!\x99\x80\xa4\x82%\x08H\xaa\x90\x95\x95s\x97fb!\x15\x84 &p\'\x91H\xd4\x92V\x9007D\x93\x9eDV"o\xf9\xf2r)e\xdb\x7f\xb0\x9eh\xf3\xbe{\xbf\xdew\xdf#\xfa\x7f\x9ag~\xb4\x06+\xc9\xc6#\xbc\x0fgo\x90d\xd7-\xd1u\x9bd\x8d\xaa\xe5nJ\xb4\x9bj\xd9\xd4\x92\xccu\x10-\xba\xd6,\xbaD\x0fO\xd6<\xbe:\xf2x4\x1e\xf5; \xcf\x8f\xe0\xb5\xc9\xacjy\xe9\x11]zL\xe6\x1a\xf6=\xbf\xa1\x8bh9\xb1f9\xb9\'\x0b\x85\xbf\x82,\xd4i\xb6^\xa1\xc3\x1b82\xabZZ3\xf4\xaca2\xd7\xf8\x04U\x99\n\xa2\xc3\xf94;\x9c\xef\xc9\xd6T%\x93\xfb\x9d:J\x05\xbb\x84\xdfF\xd5\xb2*s\xbf*\xddm\xa0\x06\xb2\xffq\xdd\xc6!\xdc,\'p\xd5&\x07\x05\xbe\xf9\x9e\xeb\x88]\x06\x85;3\xabZ\xe6\xbe5\xb9\xcfd\xae\xf1\t.=\xa1\x84\xb2\x06\xf7"T\x9b\xccY\x82<\xf4\x1a\x97\x8e\xcc*\x12L\x85K\x90k\xd8\xf7\xf5\xae%QP\xb0\xcb\xa0\xf8+A\xa1\\\xc7\xcf\x04\xf7\x1b\xa2\xfd\xe6>A\xcc\x84PqXGAQGq\x08r\x1cBmO\xddod7u\xb7d7uH\xba\x8e^>\xd7\xab\xdc\'\xca\xfd\xf5\nd\xacT|\x03' ) devmode = (15, 11, - b'c`\xa0\x15h(>\xf0\xbf\xe1\xfb\x819\x0c\x0c\x07\xa2\x0e\xfc\x07\xc1\x86\xe2\x86)\x0e\xe7\x18\x18\x1c\xce5L\x01\xb2\xbf;d\x03\xc5\x82@\xb2\x0e^\x0e^\x0c\x8a\x0e\xd9\x0e\xef\x81\xb2\xef\x1d\xb2A\xb2@}\xf7\x18Z@\xb2\r\x02\x0e\x1c@\x9e%\x88\x054\xc7\x12U\x16\x08\xef\x01m\x13\x00\x9b\x05T\x81&k\xd9`\x08v\xce\xf3\x86)\x0c\xcfA\xaeB\x96\x85\xb9\xf5\xc0\xde\x86k\x07\xf6Bd\xd1]\x05tQ\x0f\x90\xdd\x83\xddG@\xcd\xc9@s\x92\x19\x18\x00' + b"\xb5\x8f!\x0e\xc3@\x0c\x04\x17\x16\xf6\x0b}Ba\x98q``i\xe0\xc1\xfb\xc0\x82\xc2\xc0\x83\xfdJ\xa4}A`ah``\xa0{wQ\xa5\xa6*\xadM\xac\x9d\xb5\xbc\x06\xfeU\x91\xae\x8d\x0f\x017y\xed\xc8\xc4\xc9\x80\xc9\x12#7\x06su,\xb4\xb5\xd6.\x08\xb6f\xbaZ\xb0B\x81Y\xf7\xba{\xe6)\xebM\x9d\\\x8d\x8e\xd45\xe7\x1b\x85t,\x8e#mteI\xb3 q\xa9\xa9>\xe9;\xeb\xa8'G\xed\xf4;\x150de\xb0\xdf\x1f\x01=\\}v\xbd\x00" ) edge = (20, 11, - b'c`\x18\x18p \xea\xc0\x7f\x10l(n(\x06\x92\xdf\x0f\xcca`p\xe08\xb0\x17(\xb6\x17H\xc3eA,\x07/\x07/\x06E\xa0\xca\xef\x0e\xd9@\xb1 \x10\xc9\x90\xec\xf0\x1e(\x07\x97\x05\xb1\x1a\x04\x1c8\x18\x18@\xea\x80\xe6\xdfchah\x01\x92\x0c 1\x84,\xd4\xe4{\x18\xeaZ`\xb6Ad\xc1,\xcb\x06Ctu\x07$\x1a\xbeC\xd5\x81eA,\x88\x8f\xd0\xed\x85\xa9C\xf8\x17\x9b?\x1czP}\x89-\\\x0eH0%dH' % NUM_GREYS, *vals) def doit(out_fname='font_iosevka.py', cls_name='FontIosevka'): font = ImageFont.truetype(FONT, FONT_SIZE) diff --git a/shared/actions.py b/shared/actions.py index 6fd97eb4..e2cfbf9c 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -363,39 +363,6 @@ Press 6 to prove you read to the end of this message.''', title='WARNING', escap from flow import EmptyWallet return MenuSystem(EmptyWallet) -async def login_countdown(sec): - # Show a countdown, which may need to - # run for multiple **days** - from glob import dis - from display import FontSmall, FontLarge - from utime import ticks_ms, ticks_diff - - # pre-render fixed parts - dis.clear() - y = 0 - dis.text(None, y, 'Login countdown in', font=FontSmall); y += 14 - dis.text(None, y, 'effect. Must wait:', font=FontSmall); y += 14 - y += 5 - dis.save() - - st = ticks_ms() - while sec > 0: - dis.restore() - dis.text(None, y, pretty_short_delay(sec), font=FontLarge) - - dis.show() - dis.busy_bar(1) - - # this should be more accurate, errors were accumulating - now = ticks_ms() - dt = 1000 - ticks_diff(now, st) - await sleep_ms(dt) - st = ticks_ms() - - sec -= 1 - - dis.busy_bar(0) - async def block_until_login(): # # Force user to enter a valid PIN. @@ -428,17 +395,22 @@ async def show_nickname(nick): # Show a nickname for this coldcard (as a personalization) # - no keys here, just show it until they press anything from glob import dis - from display import FontLarge, FontTiny, FontSmall from ux import ux_wait_keyup dis.clear() - if dis.width(nick, FontLarge) <= dis.WIDTH: - dis.text(None, 21, nick, font=FontLarge) + if dis.has_lcd: + from lcd_display import CHARS_H + dis.text(None, CHARS_H//3, nick) else: - dis.text(None, 27, nick, font=FontSmall) + from display import FontLarge, FontSmall - dis.show() + if dis.width(nick, FontLarge) <= dis.WIDTH: + dis.text(None, 21, nick, font=FontLarge) + else: + dis.text(None, 27, nick, font=FontSmall) + + dis.show() await ux_wait_keyup() @@ -788,7 +760,7 @@ async def start_login_sequence(): # # - easy to brick units here, so catch and ignore errors where possible/appropriate # - from ux import idle_logout + from ux import idle_logout, ux_login_countdown from glob import dis import callgate @@ -841,7 +813,7 @@ async def start_login_sequence(): if delay: # kill some time, with countdown, and get "the" PIN again for real login pa.reset() - await login_countdown(delay * (60 if not version.is_devmode else 1)) + await ux_login_countdown(delay * (60 if not version.is_devmode else 1)) # keep it simple for Mk4+: just challenge again for any PIN # - if it's the same countdown pin, it will be accepted and they diff --git a/shared/flow.py b/shared/flow.py index 54bc2c36..0932e574 100644 --- a/shared/flow.py +++ b/shared/flow.py @@ -12,7 +12,6 @@ from mk4 import dev_enable_repl from multisig import make_multisig_menu, import_multisig_nfc from seed import make_ephemeral_seed_menu, make_seed_vault_menu from address_explorer import address_explore -from users import make_users_menu from drv_entro import drv_entro_start, password_entry from backups import clone_start, clone_write_data from xor_seed import xor_split_start, xor_restore_start @@ -23,13 +22,15 @@ from trick_pins import TrickPinMenu # Optional feature: HSM, depends on hardware -# - code for HSM support wont exist on other version, so dont call it +# - code for HSM support wont exist on some platforms, so dont call it if version.supports_hsm: from hsm import hsm_policy_available + from users import make_users_menu hsm_feature = lambda: True else: hsm_policy_available = lambda: False hsm_feature = lambda: False + make_users_menu = lambda: [] trick_pin_menu = TrickPinMenu.make_menu diff --git a/shared/lcd_display.py b/shared/lcd_display.py index d76e3f99..ef977175 100644 --- a/shared/lcd_display.py +++ b/shared/lcd_display.py @@ -11,19 +11,16 @@ from graphics import Graphics as obsoleteGraphics import sram2 from st7788 import ST7788 -# we support 4 fonts -from zevvpeep import FontSmall, FontLarge, FontTiny -FontFixed = object() # ugly 8x8 PET font - +# the one font: fixed-width (except for a few double-width chars) from font_iosevka import CELL_W, CELL_H, TEXT_PALETTE, TEXT_PALETTE_INV from font_iosevka import FontIosevka -# free unused screen buffers, we will make bigger ones +# free unused screen buffers, we don't work that way del sram2.display_buf del sram2.display2_buf # one byte per pixel; fixed palette maps to BGR565 in C code -display2_buf = bytearray(320 * 240) +#display2_buf = bytearray(320 * 240) #WIDTH = const(320) #HEIGHT = const(240) @@ -106,7 +103,7 @@ class Display: def draw_status(self, full=False, **kws): if full: y = TOP_MARGIN - self.dis.fill_rect(0, 0, WIDTH, y-2, 0x0) + self.dis.fill_rect(0, 0, WIDTH, y-1, 0x0) self.dis.fill_rect(0, y-1, WIDTH, 1, grey_level(0.25)) kws = get_sys_status() @@ -132,10 +129,7 @@ class Display: self.image(x, 0, '%s_%d' % (meta, kws[meta])) def width(self, msg, font): - if font == FontFixed: - return len(msg) * 8 - else: - return sum(font.lookup(ord(ch)).w for ch in msg) + return sum(font.lookup(ord(ch)).w for ch in msg) def image(self, x, y, name): # display a graphics image, immediately @@ -148,45 +142,6 @@ class Display: # XXX plan is these are chars or images return 10, 10 - def XXX_text(self, x,y, msg, font=FontSmall, invert=0): - # Draw at x,y (top left corner of first letter) - # using font. Use invert=1 to get reverse video - - if x is None or x < 0: - # center/rjust - w = self.width(msg, font) - if x == None: - x = max(0, (WIDTH - w) // 2) - else: - # measure from right edge (right justify) - x = max(0, WIDTH - w + 1 + x) - - if y < 0: - # measure up from bottom edge - y = HEIGHT - font.height + 1 + y - - if font == FontFixed: - # use font provided by Micropython: 8x8 - self.dis.text(msg, x, y) - - return x + (len(msg) * 8) - - for ch in msg: - fn = font.lookup(ord(ch)) - if fn is None: - # use last char in font as error char for junk we don't - # know how to render - fn = font.lookup(font.code_range.stop) - bits = bytearray(fn.w * fn.h) - bits[0:len(fn.bits)] = fn.bits - if invert: - bits = bytearray(i^0xff for i in bits) - gly = framebuf.FrameBuffer(bits, fn.w, fn.h, framebuf.MONO_HLSB) - self.dis.blit(gly, x, y, invert) - x += fn.w - - return x - def text(self, x,y, msg, font=None, invert=0): # Draw at x,y (in cell positions, not pixels) # Use invert=1 to get reverse video @@ -228,8 +183,7 @@ class Display: if x >= WIDTH: break def clear(self): - # fill to black, but only text area - # - not status bar + # fill to black, but only text area, not status bar self.dis.fill_rect(0, TOP_MARGIN, WIDTH, HEIGHT-TOP_MARGIN, 0x0) def clear_rect(self, x,y, w,h): @@ -242,10 +196,11 @@ class Display: pass # rather than clearing and redrawing, use this buffer w/ fixed parts of screen + # - obsolete concept def save(self): - display2_buf[:] = self.dis.buffer + pass def restore(self): - self.dis.buffer[:] = display2_buf + pass def hline(self, y): self.dis.fill_rect(0,y, WIDTH, 1, 0xffff) @@ -435,7 +390,7 @@ class Display: # centered text under that y = CHARS_H - num_lines for line in parts: - self.text(None, y, line, FontTiny) + self.text(None, y, line) y += 1 if idx_hint: diff --git a/shared/login.py b/shared/login.py index 0ab8bb04..79c0759e 100644 --- a/shared/login.py +++ b/shared/login.py @@ -4,7 +4,6 @@ # import pincodes, version, random from glob import dis -from display import FontLarge, FontTiny from ux import PressRelease, ux_wait_keyup, ux_show_story, ux_show_pin from callgate import show_logout from pincodes import pa @@ -70,6 +69,7 @@ class LoginUX: dis.text(None, -1, "CANCEL or SELECT to continue") else: # Old style + from display import FontLarge, FontTiny y = 15 x = 18 dis.text(x, y, words[0], FontLarge) diff --git a/shared/main.py b/shared/main.py index 4f9438d3..7672e54b 100644 --- a/shared/main.py +++ b/shared/main.py @@ -16,7 +16,7 @@ assert not glob.dis, "main reimport" # this makes the GC run when larger objects are free in an attempt to reduce fragmentation. gc.threshold(4096) -if 0: +if 1: #XXX # useful for debug: keep this stub! import ckcc ckcc.vcp_enabled(True) diff --git a/shared/menu.py b/shared/menu.py index 4f2cc8e2..4bb11506 100644 --- a/shared/menu.py +++ b/shared/menu.py @@ -3,7 +3,6 @@ # menu.py - Implement an interactive menu system. # import gc -from display import FontLarge, FontTiny, Display from ux import PressRelease, the_ux from uasyncio import sleep_ms from charcodes import (KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, diff --git a/shared/seed.py b/shared/seed.py index e18620c9..d710bb6b 100644 --- a/shared/seed.py +++ b/shared/seed.py @@ -253,9 +253,9 @@ individual words if you wish.''') def late_draw(self, dis): # add an overlay with "word N" in small text, top right. - from display import FontTiny + if dis.has_lcd: return # unreachable anyway? - if dis.has_lcd: return # unreachable? + from display import FontTiny count = len(self.words) if count >= self.target_words: @@ -303,6 +303,7 @@ async def show_words(words, prompt=None, escape=None, extra='', ephemeral=False) async def add_dice_rolls(count, seed, judge_them, nwords=None, enforce=False): from glob import dis + # XXX q1 support from display import FontTiny, FontLarge low_entropy_msg = "You only provided %d dice rolls, and each roll adds only 2.585 bits of entropy." diff --git a/shared/selftest.py b/shared/selftest.py index 6be7c89e..90b46755 100644 --- a/shared/selftest.py +++ b/shared/selftest.py @@ -5,7 +5,6 @@ import ckcc from uasyncio import sleep_ms from glob import dis -from display import FontLarge from ux import ux_wait_keyup, ux_clear_keys, ux_poll_key from ux import ux_show_story from callgate import get_is_bricked, get_genuine, clear_genuine @@ -14,6 +13,11 @@ import version from glob import settings from charcodes import KEY_SELECT, KEY_CANCEL +try: + from display import FontLarge +except ImportError: + FontLarge = None + async def wait_ok(): k = await ux_wait_keyup('xy' + KEY_SELECT + KEY_CANCEL) if k not in 'y' + KEY_SELECT: @@ -51,6 +55,12 @@ async def test_keyboard(): # XXX pass +async def test_qr_scanner(): + # QR Scanner module: assume pretested, just testing connection + from glob import SCAN + assert SCAN + assert glob.SCAN.version.startswith('V2.3.') + def set_genuine(): # PIN must be blank for this to work # - or logged in already as main @@ -309,6 +319,8 @@ async def start_selftest(): await test_keyboard() else: await test_numpad() + if version.has_qr: + await test_qr_scanner() await test_secure_element() await test_sd_active() await test_usb_light() diff --git a/shared/st7788.py b/shared/st7788.py index 5c1b943f..99b6b1a0 100644 --- a/shared/st7788.py +++ b/shared/st7788.py @@ -22,7 +22,8 @@ RAMWR = const(0x2c) # - maybe: with QR module expansion? # - clear to pixel value # - palette + xy/wh + nible-packed palette lookup (for font) -from ckcc import lcd_blast +# - see stm32/COLDCARD_Q1/modlcd.c for code +import lcd class ST7788(): def __init__(self): @@ -32,9 +33,9 @@ class ST7788(): from pyb import Timer # not from machine self.spi = machine.SPI(1, baudrate=60_000_000, polarity=0, phase=0) - #reset_pin = Pin('PA6', Pin.OUT) # not using - self.dc = Pin('PA8', Pin.OUT, value=0) - self.cs = Pin('PA4', Pin.OUT, value=1) + #reset_pin = Pin('LCD_RESET', Pin.OUT) # not using + self.dc = Pin('LCD_DATA_CMD', Pin.OUT, value=0) + self.cs = Pin('LCD_CS', Pin.OUT, value=1) if 0: # BUST - just fades away @@ -47,9 +48,6 @@ class ST7788(): # for framebuf.FrameBuffer self.width = 320 self.height = 240 - #self.buffer = bytearray(320*240) - - #super().__init__(self.buffer, self.width, self.height, framebuf.GS8) def write_cmd(self, cmd, args=None): # send a command byte and a number of arguments @@ -72,18 +70,6 @@ class ST7788(): self.spi.write(buf) self.cs(1) - def write_pixel_data(self, buf): - # lcd_blast expands 1-byte per pixel to BGR565 - self.cs(1) - self.dc(1) - self.cs(0) - try: - lcd_blast(self.spi, buf) - except: - print('lcd_blast fail') - self.cs(1) - - def _set_window(self, x, y, w=320, h=240): #self.write_cmd(0x2a, 0, LCD_WIDTH-1) # CASET - Column address set range (x) #self.write_cmd(0x2b, y, LCD_HEIGHT-1) # RASET - Row address set range (y) @@ -97,33 +83,52 @@ class ST7788(): # .. follow with w*h*2 bytes of pixel data - def show_partial(self, y, h): - # update just a few rows of the display - assert h >= 1 - self._set_window(0, y, h=h) - rows = memoryview(self.buffer)[320*y:320*(y+h)] - self.write_pixel_data(rows) + def fill_screen(self, pixel=0x0000): + # clear ENTIRE screen to indicated pixel value + self.fill_rect(0,0, 320, 240, pixel) def show_zpixels(self, x, y, w, h, zpixels): - # display compressed pixel data - print('st7788.show_zpixels ... write me') + # display compressed pixel data, used for images/icons + # - keeping in mpy since C version would be same speed + data = uzlib.decompress(zpixels, -10) + self._set_window(x, y, w, h) + self.write_data(data) def show_pal_pixels(self, x, y, w, h, palette, pixels): - # show 4-bit packed paletted lookup pixels; used for fonts, icons + # show 4-bit packed paletted lookup pixels; used for fonts assert len(palette) == 2 * 16 + if 0: + buf = bytearray() + for here in pixels: + px1 = (here >> 4) * 2 + px2 = (here & 0xf) * 2 + buf.extend(palette[px1:px1+2]) + buf.extend(palette[px2:px2+2]) - def show(self): - # send entire frame buffer - self._set_window(0, 0) - self.write_pixel_data(self.buffer) + if (w*h) % 2 == 1: + buf = memoryview(buf[0:-2]) + + self._set_window(x, y, w, h) + self.write_data(buf) + else: + lcd.send_packed(self.spi, x, y, w, h, palette, pixels) + + def show_qr_data(self, x, y, w, expand, scan_w, packed_data): + # 8-bit packed QR data, and where to draw it, expanded by 'expand' + assert len(packed_data) == (scan_w*w) // 8 + # XXX write me def fill_rect(self, x,y, w,h, pixel=0x0000): - # need C code - pass - - def fill_screen(self, pixel=0x0000): - # clear screen to indicated pixel value - # XXX need C code - pass + # set a rectangle to a single colour + if not w or not h: return + if 0: + assert h >= 1 and w >= 1 + pixel = struct.pack('>H', pixel) + ln = pixel * w + self._set_window(x, y, w, h) + for y in range(h): + self.write_data(ln) + else: + lcd.fill_rect(self.spi, x, y, w, h, pixel) # EOF diff --git a/shared/ux.py b/shared/ux.py index f83e043c..fa040883 100644 --- a/shared/ux.py +++ b/shared/ux.py @@ -18,12 +18,14 @@ if version.has_qwerty: CH_PER_W = CHARS_W STORY_H = CHARS_H from ux_q1 import PressRelease, ux_enter_number, ux_input_numbers, ux_input_text, ux_show_pin + from ux_q1 import ux_login_countdown else: # How many characters can we fit on each line? How many lines? # (using FontSmall) CH_PER_W = 17 STORY_H = 5 from ux_mk4 import PressRelease, ux_enter_number, ux_input_numbers, ux_input_text, ux_show_pin + from ux_mk4 import ux_login_countdown class UserInteraction: def __init__(self): diff --git a/shared/ux_mk4.py b/shared/ux_mk4.py index a74a0745..f2640cad 100644 --- a/shared/ux_mk4.py +++ b/shared/ux_mk4.py @@ -425,4 +425,37 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw, dis.show() +async def ux_login_countdown(sec): + # Show a countdown, which may need to + # run for multiple **days** + from glob import dis + from display import FontSmall, FontLarge + from utime import ticks_ms, ticks_diff + + # pre-render fixed parts + dis.clear() + y = 0 + dis.text(None, y, 'Login countdown in', font=FontSmall); y += 14 + dis.text(None, y, 'effect. Must wait:', font=FontSmall); y += 14 + y += 5 + dis.save() + + st = ticks_ms() + while sec > 0: + dis.restore() + dis.text(None, y, pretty_short_delay(sec), font=FontLarge) + + dis.show() + dis.busy_bar(1) + + # this should be more accurate, errors were accumulating + now = ticks_ms() + dt = 1000 - ticks_diff(now, st) + await sleep_ms(dt) + st = ticks_ms() + + sec -= 1 + + dis.busy_bar(0) + # EOF diff --git a/shared/ux_q1.py b/shared/ux_q1.py index d58dc528..d70f6f27 100644 --- a/shared/ux_q1.py +++ b/shared/ux_q1.py @@ -219,5 +219,31 @@ def ux_show_pin(dis, pin, subtitle, is_first_part, is_confirmation, force_draw, dis.text(10, y+2, msg) dis.show() +async def ux_login_countdown(sec): + # Show a countdown, which may need to + # run for multiple **days** + # XXX untested + from glob import dis + from utime import ticks_ms, ticks_diff + + y = 4 + dis.clear() + dis.text(None, y-2, "Login countdown in effect.", invert=1) + dis.text(None, y-1, "Must wait:") + + st = ticks_ms() + while sec > 0: + dis.text(None, y, pretty_short_delay(sec)) + dis.busy_bar(1) + + # this should be more accurate, errors were accumulating + now = ticks_ms() + dt = 1000 - ticks_diff(now, st) + await sleep_ms(dt) + st = ticks_ms() + + sec -= 1 + + dis.busy_bar(0) # EOF diff --git a/stm32/COLDCARD/file_time.c b/stm32/COLDCARD/file_time.c index cc40b067..f40c515a 100644 --- a/stm32/COLDCARD/file_time.c +++ b/stm32/COLDCARD/file_time.c @@ -2,12 +2,12 @@ // // AUTO-generated. // -// built: 2023-09-08 -// version: 5.1.4 +// built: 2023-06-05 +// version: 6.0.1 // #include // this overrides ports/stm32/fatfs_port.c uint32_t get_fattime(void) { - return 0x57282820UL; + return 0x56c53000UL; } diff --git a/stm32/COLDCARD_Q1/modckcc.c b/stm32/COLDCARD_Q1/modckcc.c index c7eb8ff9..1c4469d5 100644 --- a/stm32/COLDCARD_Q1/modckcc.c +++ b/stm32/COLDCARD_Q1/modckcc.c @@ -249,68 +249,6 @@ STATIC mp_obj_t watchpoint(volatile mp_obj_t arg1) } MP_DEFINE_CONST_FUN_OBJ_1(watchpoint_obj, watchpoint); -#define SWAB16(n) (( ((n)>>8) | ((n) << 8) )&0xffff) -#define GREY(n) SWAB16( (n<<11) | (n<<5) | n) - -// BGR565 values, but wrong endian, so green split weird -static uint16_t palette[16] = { - 0x0000, // 0 => black - 0xffff, // 1 => white - - SWAB16(0xf800), // 2 => red - SWAB16(0x07e0), // 3 => green - SWAB16(0x001f), // 4 => blue - - // some greys: 5 .. 12 - GREY(5), GREY(9), GREY(13), GREY(17), GREY(21), GREY(25), GREY(29), - - 0x2000, // tbd - 0x4000, // tbd - 0x8000, // tbd - - // Coinkite brand colour? ie. orange rgb(241, 100, 34) => rgb(.945, .392, .133) - // XXX needs work -- too red - SWAB16((29 << 11) | (25<<5) | 4), -}; - -STATIC mp_obj_t lcd_blast(mp_obj_t spi_arg, mp_obj_t buf_arg) -{ - // Just send a bunch of bytes, expanded via fixed palette to the LCD - // over SPI. CS/CMD_vs_DATA must already be set correctly, and cleared - // at end... we are just reformatting and sending the pixel data. - mp_buffer_info_t buf; - mp_get_buffer_raise(buf_arg, &buf, MP_BUFFER_READ); - - const spi_t *spi = spi_from_mp_obj(spi_arg); - - int len = buf.len; - if((len < 1) || (len > 320*240)) { - mp_raise_ValueError(NULL); - } - - const uint8_t *pixels = buf.buf; - - // working buffer - const int max_rows = 30; - uint16_t fb[max_rows * 320]; // largish: 19.2k - - while(len) { - uint32_t here = 0; - - for(; len && here < (sizeof(fb)/2); len--, here++, pixels++) { - fb[here] = palette[(*pixels) & 0xf]; - } - - if(!here) break; - - // send what we have - spi_transfer(spi, here*2, (const uint8_t *)fb, NULL, SPI_TRANSFER_TIMEOUT(here*2)); - } - - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(lcd_blast_obj, lcd_blast); - // See psram.c extern const mp_obj_type_t psram_type; @@ -330,7 +268,6 @@ STATIC const mp_rom_map_elem_t ckcc_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_stack_limit), MP_ROM_PTR(&stack_limit_obj) }, { MP_ROM_QSTR(MP_QSTR_usb_active), MP_ROM_PTR(&usb_active_obj) }, { MP_ROM_QSTR(MP_QSTR_PSRAM), MP_ROM_PTR(&psram_type) }, - { MP_ROM_QSTR(MP_QSTR_lcd_blast), MP_ROM_PTR(&lcd_blast_obj) }, }; STATIC MP_DEFINE_CONST_DICT(ckcc_module_globals, ckcc_module_globals_table); diff --git a/stm32/COLDCARD_Q1/modlcd.c b/stm32/COLDCARD_Q1/modlcd.c new file mode 100644 index 00000000..5c58a50a --- /dev/null +++ b/stm32/COLDCARD_Q1/modlcd.c @@ -0,0 +1,173 @@ +// +// (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC. +// +// modlcd.c - module for driving the Q1 LCD fastly. +// +#include +#include + +#include "py/obj.h" +#include "bufhelper.h" +#include "py/gc.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mpstate.h" +#include "py/stackctrl.h" +#include "boardctrl.h" +#include "spi.h" + +#include "extint.h" + +#define PIN_LCD_TEAR pin_B11 +#define PIN_LCD_CS pin_A4 +#define PIN_LCD_SCLK pin_A5 +#define PIN_LCD_RESET pin_A6 +#define PIN_LCD_MOSI pin_A7 +#define PIN_LCD_DATA_CMD pin_A8 + +// few key commands for this display +#define CASET 0x2a +#define RASET 0x2b +#define RAMWR 0x2c + + +#define SWAB16(n) (( ((n)>>8) | ((n) << 8) )&0xffff) + +static inline void write_cmd(const spi_t *spi, uint8_t cmd) +{ + // write a command byte + mp_hal_pin_write(PIN_LCD_CS, 1); + mp_hal_pin_write(PIN_LCD_DATA_CMD, 0); + mp_hal_pin_write(PIN_LCD_CS, 0); + + spi_transfer(spi, 1, (const uint8_t *)&cmd, NULL, SPI_TRANSFER_TIMEOUT(1)); + + mp_hal_pin_write(PIN_LCD_CS, 1); +} + +static inline void write_cmd2(const spi_t *spi, uint8_t cmd, uint16_t arg1, uint16_t arg2) +{ + // Write a command byte, followed by 2 big-endian 16 bit arguments. + uint16_t args[2] = { SWAB16(arg1), SWAB16(arg2)}; + + mp_hal_pin_write(PIN_LCD_CS, 1); + mp_hal_pin_write(PIN_LCD_DATA_CMD, 0); + mp_hal_pin_write(PIN_LCD_CS, 0); + + //spi_transfer(spi, 1, (const uint8_t *)&cmd, NULL, SPI_TRANSFER_TIMEOUT(1)); + HAL_SPI_Transmit(spi->spi, (uint8_t *)&cmd, 1, SPI_TRANSFER_TIMEOUT(1)); + + mp_hal_pin_write(PIN_LCD_DATA_CMD, 1); + + // faster to avoid DMA for little transfers, so do that + //spi_transfer(spi, 4, (const uint8_t *)&args, NULL, SPI_TRANSFER_TIMEOUT(4)); + HAL_SPI_Transmit(spi->spi, (uint8_t *)&args, 4, SPI_TRANSFER_TIMEOUT(4)); + + mp_hal_pin_write(PIN_LCD_CS, 1); +} + + +static void write_data(const spi_t *spi, int len, const uint8_t *data) +{ + // Send a bunch of data, like pixel data. + mp_hal_pin_write(PIN_LCD_CS, 1); + mp_hal_pin_write(PIN_LCD_DATA_CMD, 1); + mp_hal_pin_write(PIN_LCD_CS, 0); + + spi_transfer(spi, len, data, NULL, SPI_TRANSFER_TIMEOUT(len)); + + mp_hal_pin_write(PIN_LCD_CS, 1); +} + +static void set_window(const spi_t *spi, int x, int y, int w, int h) +{ + // set active window; controls where pixel data will show up on screen + write_cmd2(spi, CASET, x, x+w-1); + write_cmd2(spi, RASET, y, y+h-1); + write_cmd(spi, RAMWR); // RAMWR - memory write, implies data to follow +} + + +STATIC mp_obj_t send_packed(size_t n_args, const mp_obj_t *args) +{ + // take 4-bit packed palette-ized data, unpack and send + // signature: spi, x, y, w, h, pal, pixels + + const spi_t *spi = spi_from_mp_obj(args[0]); + + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t h = mp_obj_get_int(args[4]); + + mp_buffer_info_t palette; + mp_get_buffer_raise(args[5], &palette, MP_BUFFER_READ); + mp_buffer_info_t pixels; + mp_get_buffer_raise(args[6], &pixels, MP_BUFFER_READ); + + if(palette.len != 16*2) mp_raise_ValueError(NULL); + const uint8_t *pal = palette.buf; + + // working buffer + uint8_t fb[(w * h * 2) + 4]; // may write one extra bogus pixel for odd w*h cases + const uint8_t *p = pixels.buf; + uint8_t *o = fb; + for(int i=0; i> 4) * 2; + uint8_t px2 = (*p & 0xf) * 2; + o[0] = pal[px1]; + o[1] = pal[px1+1]; + o[2] = pal[px2]; + o[3] = pal[px2+1]; + } + + set_window(spi, x, y, w, h); + write_data(spi, w*h*2, (const uint8_t *)fb); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(send_packed_obj, 7, 7, send_packed); + +STATIC mp_obj_t fill_rect(size_t n_args, const mp_obj_t *args) +{ + // write the same pixel value to a region + // signature: spi, x, y, w, h, pixel_value + const spi_t *spi = spi_from_mp_obj(args[0]); + + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t h = mp_obj_get_int(args[4]); + mp_int_t pixel = mp_obj_get_int(args[5]); + + uint16_t line[w]; + for(int i=0; iH', raw[pos:pos+2]) self.mv[x][y] = val pos += 2