working keyboard sim
This commit is contained in:
parent
9b6b676dd1
commit
a7c1cdf8ff
101
shared/charcodes.py
Normal file
101
shared/charcodes.py
Normal file
@ -0,0 +1,101 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Constants for Q1's keyboard.
|
||||
#
|
||||
# - using ascii internally
|
||||
# - but 'key numbers' useful sometimes for some as well
|
||||
# - power key is special, not a key (altho implemented in keyboard.py)
|
||||
#
|
||||
try:
|
||||
from micropython import const
|
||||
except ImportError:
|
||||
# this file also used by simulator.py
|
||||
const = int
|
||||
|
||||
NUM_ROWS = const(6)
|
||||
NUM_COLS = const(10)
|
||||
|
||||
# ascii key codes;
|
||||
KEY_NFC = '\x0e' # ctrl-N
|
||||
KEY_QR = '\x11' # ctrl-Q
|
||||
KEY_TAB = '\t' # tab = ctrl-I
|
||||
KEY_SELECT = '\r' # = CR
|
||||
KEY_CANCEL = '\x1b' # ESC = Cancel
|
||||
KEY_LEFT = '\x15' # ^U = left (incompatible)
|
||||
KEY_UP = '\x0b' # ^K = up on ADM-3A
|
||||
KEY_RIGHT = '\x0c' # ^L = right on ADM-3A
|
||||
KEY_DOWN = '\x0a' # ^J = LF = down on ADM-3A
|
||||
|
||||
KEY_PAGE_DOWN = '\x18' # ^x
|
||||
KEY_PAGE_UP = '\x19' # ^y
|
||||
KEY_END = '\x1a' # ^z
|
||||
KEY_HOME = '\x1c' # ^\
|
||||
|
||||
# these meta keys might not be visible to higher layers:
|
||||
KEY_LAMP = '\x07' # BELL = ^G
|
||||
KEY_SHIFT = '\x01'
|
||||
KEY_SPACE = ' '
|
||||
KEY_SYMBOL = '\x02'
|
||||
KEY_BS = '\x08' # ^H = backspace
|
||||
|
||||
# function keys, filling gaps, running out of space!
|
||||
KEY_F1 = '\x0f'
|
||||
KEY_F2 = '\x12'
|
||||
KEY_F3 = '\x13'
|
||||
KEY_F4 = '\x14'
|
||||
KEY_F5 = '\x16'
|
||||
KEY_F6 = '\x17'
|
||||
|
||||
# (row, col) => keycode
|
||||
# - unused spots are \0
|
||||
# - these are unshifted values
|
||||
# - ten per row, gaps with zero
|
||||
DECODER = (KEY_NFC + KEY_QR + KEY_TAB
|
||||
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
|
||||
+ '1234567890'
|
||||
+ 'qwertyuiop'
|
||||
+ 'asdfghjkl`'
|
||||
+ 'zxcvbnm,./'
|
||||
+ KEY_LAMP + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
|
||||
|
||||
# - same when shift is down
|
||||
# - make some unmarked combos dead (like shift+UP)
|
||||
# - but shift+' can be ", which really should be symb+'
|
||||
DECODER_SHIFT = (
|
||||
'\0\0\0\0\0\0\0\0\0\0'
|
||||
+ '!@#$%^&*()'
|
||||
+ 'QWERTYUIOP'
|
||||
+ 'ASDFGHJKL"'
|
||||
+ 'ZXCVBNM<>?'
|
||||
'\0\0\0\0\0\0\0\0\0\0' )
|
||||
|
||||
# - in caps mode: numbers unaffected, and also allow meta keys normally
|
||||
DECODER_CAPS = (KEY_NFC + KEY_QR + KEY_TAB
|
||||
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
|
||||
+ '1234567890'
|
||||
+ 'QWERTYUIOP'
|
||||
+ "ASDFGHJKL'"
|
||||
+ 'ZXCVBNM,./'
|
||||
+ KEY_LAMP + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
|
||||
|
||||
# - same w/ SYMBOL pressed
|
||||
# - be nice and allow number+symbol == number + shift
|
||||
DECODER_SYMBOL = (
|
||||
'\0\0\0\0\0\0\0\0\0\0'
|
||||
+ '!@#$%^&*()'
|
||||
+ '-_`\0\0\0[]{}'
|
||||
+ '+=\0\0:;~|\\"'
|
||||
+ KEY_F1 + KEY_F2 + KEY_F3 + KEY_F4 + KEY_F5 + KEY_F6 + '\0<>?'
|
||||
'\0\0\0\0\0\0\0\0\0\0' )
|
||||
|
||||
KEYNUM_LAMP = const(50)
|
||||
KEYNUM_SHIFT = const(51)
|
||||
KEYNUM_SYMBOL = const(53)
|
||||
|
||||
assert len(DECODER) == NUM_ROWS * NUM_COLS
|
||||
assert len(DECODER_SYMBOL) == NUM_ROWS * NUM_COLS
|
||||
assert len(DECODER_SHIFT) == NUM_ROWS * NUM_COLS
|
||||
assert DECODER[KEYNUM_SHIFT] == KEY_SHIFT
|
||||
assert DECODER[KEYNUM_SYMBOL] == KEY_SYMBOL
|
||||
assert DECODER[KEYNUM_LAMP] == KEY_LAMP
|
||||
|
||||
@ -8,41 +8,11 @@ from machine import Pin
|
||||
from random import shuffle
|
||||
from numpad import NumpadBase
|
||||
from utils import call_later_ms
|
||||
from charcodes import *
|
||||
|
||||
NUM_ROWS = const(6)
|
||||
NUM_COLS = const(10)
|
||||
SAMPLE_FREQ = const(60) # (Hz) how fast to do each scan
|
||||
NUM_SAMPLES = const(3) # this many matching samples required for debounce
|
||||
Q_CHECK_RATE = const(5) # (ms) how fast to check event Q
|
||||
|
||||
# ascii key codes;
|
||||
KEY_NFC = '\x0e' # ctrl-N
|
||||
KEY_QR = '\x11' # ctrl-Q
|
||||
KEY_TAB = '\t' # tab = ctrl-I
|
||||
KEY_SELECT = '\r' # = CR
|
||||
KEY_CANCEL = '\x1b' # ESC = Cancel
|
||||
KEY_LEFT = '\x15' # ^U = left (incompatible)
|
||||
KEY_UP = '\x0b' # ^K = up on ADM-3A
|
||||
KEY_RIGHT = '\x0c' # ^L = right on ADM-3A
|
||||
KEY_DOWN = '\x0a' # ^J = LF = down on ADM-3A
|
||||
|
||||
# these meta keys might not be visible to higher layers:
|
||||
KEY_LIGHT = '\x07' # BELL = ^G
|
||||
KEY_SHIFT = '\x01'
|
||||
KEY_SPACE = ' '
|
||||
KEY_SYMBOL = '\x01'
|
||||
KEY_BS = '\x08' # ^H = backspace
|
||||
|
||||
# (row, col) => keycode
|
||||
# - unused spots are \0
|
||||
# - these are unshifted values
|
||||
DECODER = (KEY_NFC + KEY_QR + KEY_TAB
|
||||
+ KEY_LEFT + KEY_UP + KEY_DOWN + KEY_RIGHT + KEY_SELECT + KEY_CANCEL + '\0'
|
||||
+ '1234567890'
|
||||
+ 'qwertyuiop'
|
||||
+ 'asdfghjkl`'
|
||||
+ 'zxcvbnm,./'
|
||||
+ KEY_LIGHT + KEY_SHIFT + KEY_SPACE + KEY_SYMBOL + KEY_BS + '\0\0\0\0\0')
|
||||
|
||||
class FullKeyboard(NumpadBase):
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import uasyncio
|
||||
from uasyncio import sleep_ms
|
||||
from graphics import Graphics
|
||||
import sram2
|
||||
from ckcc import lcd_blast
|
||||
from st7788 import ST7788
|
||||
|
||||
# we support 4 fonts
|
||||
from zevvpeep import FontSmall, FontLarge, FontTiny
|
||||
@ -20,99 +20,8 @@ del sram2.display_buf
|
||||
del sram2.display2_buf
|
||||
|
||||
# one byte per pixel; fixed palette maps to BGR565 in C code
|
||||
display_buf = bytearray(320 * 240)
|
||||
display2_buf = bytearray(320 * 240)
|
||||
|
||||
# few key commands for this display
|
||||
CASET = const(0x2a)
|
||||
RASET = const(0x2b)
|
||||
RAMWR = const(0x2c)
|
||||
|
||||
class ST7788(framebuf.FrameBuffer):
|
||||
def __init__(self):
|
||||
# assume the Bootrom setup the interface and LCD correctly already
|
||||
# - its fairly slow, complex and no need to change
|
||||
from machine import Pin
|
||||
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)
|
||||
|
||||
if 0:
|
||||
# BUST - just fades away
|
||||
# backlight control - will not see display with it off!
|
||||
self.bl_enable = Pin('BL_ENABLE', Pin.OUT, value=1)
|
||||
t = Timer(3, freq=100_000)
|
||||
# must be channel 3 because BL_ENABLE=>PE3?
|
||||
self.dimmer = t.channel(3, Timer.PWM, pin=self.bl_enable)
|
||||
|
||||
# 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
|
||||
self.cs(1)
|
||||
self.dc(0)
|
||||
self.cs(0)
|
||||
self.spi.write(bytes([cmd]))
|
||||
|
||||
if args:
|
||||
self.dc(1)
|
||||
self.spi.write(args)
|
||||
|
||||
self.cs(1)
|
||||
|
||||
def write_data(self, buf):
|
||||
# just send data bytes; lcd needs to be right mode already
|
||||
self.cs(1)
|
||||
self.dc(1)
|
||||
self.cs(0)
|
||||
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)
|
||||
a = struct.pack('>HH', x, x+w-1)
|
||||
self.write_cmd(CASET, a)
|
||||
|
||||
a = struct.pack('>HH', y, y+h-1)
|
||||
self.write_cmd(RASET, a)
|
||||
|
||||
self.write_cmd(RAMWR) # RAMWR - memory write
|
||||
|
||||
# .. 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 show(self):
|
||||
# send entire frame buffer
|
||||
self._set_window(0, 0)
|
||||
self.write_pixel_data(self.buffer)
|
||||
|
||||
class Display:
|
||||
|
||||
WIDTH = 320
|
||||
|
||||
@ -6,6 +6,7 @@ freeze_as_mpy('', [
|
||||
'keyboard.py',
|
||||
'scanner.py',
|
||||
'lcd_display.py',
|
||||
'st7788.py',
|
||||
'vdisk.py',
|
||||
'nfc.py',
|
||||
'ndef.py',
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
# DEBUG ONLY -- only installed for debug builds
|
||||
# Hack to monitor screen contents, as text.
|
||||
# Import this file to install the hacks.
|
||||
import ux
|
||||
import ux, version
|
||||
|
||||
global contents, full_contents, story
|
||||
|
||||
@ -16,8 +16,10 @@ full_contents = ''
|
||||
# copy of the story being shown
|
||||
story = None
|
||||
|
||||
|
||||
from display import Display
|
||||
if version.hw_label == 'q1':
|
||||
from lcd_display import Display
|
||||
else:
|
||||
from display import Display
|
||||
|
||||
orig_text = Display.text
|
||||
orig_clear = Display.clear
|
||||
|
||||
104
shared/st7788.py
Normal file
104
shared/st7788.py
Normal file
@ -0,0 +1,104 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# st7788.py - LCD communications for Q1's 320x240 pixel *colour* display!
|
||||
#
|
||||
import machine, uzlib, ckcc, utime, struct, array, sys
|
||||
from version import is_devmode
|
||||
import framebuf
|
||||
import uasyncio
|
||||
from uasyncio import sleep_ms
|
||||
from graphics import Graphics
|
||||
import sram2
|
||||
from ckcc import lcd_blast
|
||||
|
||||
# few key commands for this display
|
||||
CASET = const(0x2a)
|
||||
RASET = const(0x2b)
|
||||
RAMWR = const(0x2c)
|
||||
|
||||
class ST7788(framebuf.FrameBuffer):
|
||||
def __init__(self):
|
||||
# assume the Bootrom setup the interface and LCD correctly already
|
||||
# - its fairly slow, complex and no need to change
|
||||
from machine import Pin
|
||||
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)
|
||||
|
||||
if 0:
|
||||
# BUST - just fades away
|
||||
# backlight control - will not see display with it off!
|
||||
self.bl_enable = Pin('BL_ENABLE', Pin.OUT, value=1)
|
||||
t = Timer(3, freq=100_000)
|
||||
# must be channel 3 because BL_ENABLE=>PE3?
|
||||
self.dimmer = t.channel(3, Timer.PWM, pin=self.bl_enable)
|
||||
|
||||
# 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
|
||||
self.cs(1)
|
||||
self.dc(0)
|
||||
self.cs(0)
|
||||
self.spi.write(bytes([cmd]))
|
||||
|
||||
if args:
|
||||
self.dc(1)
|
||||
self.spi.write(args)
|
||||
|
||||
self.cs(1)
|
||||
|
||||
def write_data(self, buf):
|
||||
# just send data bytes; lcd needs to be right mode already
|
||||
self.cs(1)
|
||||
self.dc(1)
|
||||
self.cs(0)
|
||||
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)
|
||||
a = struct.pack('>HH', x, x+w-1)
|
||||
self.write_cmd(CASET, a)
|
||||
|
||||
a = struct.pack('>HH', y, y+h-1)
|
||||
self.write_cmd(RASET, a)
|
||||
|
||||
self.write_cmd(RAMWR) # RAMWR - memory write
|
||||
|
||||
# .. 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 show(self):
|
||||
# send entire frame buffer
|
||||
self._set_window(0, 0)
|
||||
self.write_pixel_data(self.buffer)
|
||||
|
||||
# EOF
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 254 KiB |
@ -12,14 +12,15 @@
|
||||
# Limitations:
|
||||
# - USB light not fully implemented, because happens at irq level on real product
|
||||
#
|
||||
import os, sys, tty, pty, termios, time, pdb, tempfile
|
||||
import os, sys, tty, pty, termios, time, pdb, tempfile, struct
|
||||
import subprocess
|
||||
import sdl2.ext
|
||||
from PIL import Image
|
||||
from PIL import Image, ImageSequence
|
||||
from select import select
|
||||
import fcntl
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
from bare import BareMetal
|
||||
from sdl2.scancode import * # SDL_SCANCODE_F1.. etc
|
||||
|
||||
MPY_UNIX = 'l-port/micropython'
|
||||
|
||||
@ -47,7 +48,6 @@ class SimulatedScreen:
|
||||
|
||||
def movie_end(self):
|
||||
fn = time.strftime('../movie-%j-%H%M%S.gif')
|
||||
from PIL import Image, ImageSequence
|
||||
|
||||
if not self.movie: return
|
||||
|
||||
@ -61,8 +61,6 @@ class SimulatedScreen:
|
||||
self.movie = None
|
||||
|
||||
def new_frame(self):
|
||||
from PIL import Image
|
||||
|
||||
dt = int((time.time() - self.last_frame) * 1000)
|
||||
self.last_frame = time.time()
|
||||
|
||||
@ -74,7 +72,82 @@ class SimulatedScreen:
|
||||
self.movie.append((dt, img))
|
||||
|
||||
class LCDSimulator(SimulatedScreen):
|
||||
pass
|
||||
# where the simulated screen is, relative to fixed background
|
||||
TOPLEFT = (65, 60)
|
||||
background_img = 'q1-images/background.png'
|
||||
|
||||
# see stm32/COLDCARD_Q1/modckcc.c where this pallet is defined.
|
||||
palette_colours = [
|
||||
'#000', '#fff', # black/white, must be 0/1
|
||||
'#f00', '#0f0', '#00f', # RGB demos
|
||||
# some greys: 5 .. 12
|
||||
'#555', '#999', '#ddd', '#111111', '#151515', '#191919', '#1d1d1d',
|
||||
# tbd/unused
|
||||
'#200', '#400', '#800',
|
||||
# #15: Coinkite brand
|
||||
'#f16422'
|
||||
]
|
||||
|
||||
def __init__(self, factory):
|
||||
self.movie = None
|
||||
|
||||
self.sprite = s = factory.create_software_sprite( (320,240), bpp=32)
|
||||
s.x, s.y = self.TOPLEFT
|
||||
s.depth = 100
|
||||
|
||||
self.palette = [sdl2.ext.prepare_color(code, s) for code in self.palette_colours]
|
||||
assert len(self.palette) == 16
|
||||
|
||||
sdl2.ext.fill(s, self.palette[0])
|
||||
|
||||
self.mv = sdl2.ext.PixelView(self.sprite)
|
||||
|
||||
# for any LED's .. no position implied
|
||||
self.led_red = factory.from_image("q1-images/led-red.png")
|
||||
self.led_green = factory.from_image("q1-images/led-green.png")
|
||||
|
||||
def new_contents(self, readable):
|
||||
# got bytes for new update. expect a header and packed pixels
|
||||
while 1:
|
||||
prefix = readable.read(8)
|
||||
if not prefix: return
|
||||
X,Y, w, h = struct.unpack('<4H', prefix)
|
||||
|
||||
assert X>=0 and Y>=0
|
||||
assert X+w <= 320
|
||||
assert Y+h <= 240
|
||||
|
||||
sz = w*h
|
||||
here = readable.read(sz)
|
||||
assert len(here) == sz
|
||||
|
||||
pos = 0
|
||||
for y in range(Y, Y+h):
|
||||
for x in range(X, X+w):
|
||||
val = here[pos]
|
||||
pos += 1
|
||||
self.mv[y][x] = self.palette[val & 0xf]
|
||||
|
||||
if self.movie is not None:
|
||||
self.new_frame()
|
||||
|
||||
def click_to_key(self, x, y):
|
||||
# take a click on image => keypad key if valid
|
||||
# - not planning to support, tedious
|
||||
return None
|
||||
|
||||
def draw_leds(self, spriterenderer, active_set=0):
|
||||
# always draw SE led, since one is always on
|
||||
GEN_LED = 0x1
|
||||
SD_LED = 0x2
|
||||
USB_LED = 0x4
|
||||
|
||||
spriterenderer.render(self.led_green if (active_set & GEN_LED) else self.led_red)
|
||||
|
||||
if active_set & SD_LED:
|
||||
spriterenderer.render(self.led_sdcard)
|
||||
if active_set & USB_LED:
|
||||
spriterenderer.render(self.led_usb)
|
||||
|
||||
class OLEDSimulator(SimulatedScreen):
|
||||
# top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
|
||||
@ -107,8 +180,14 @@ class OLEDSimulator(SimulatedScreen):
|
||||
self.led_sdcard = factory.from_image("mk4-images/led-sd.png")
|
||||
self.led_usb = factory.from_image("mk4-images/led-usb.png")
|
||||
|
||||
def new_contents(self, buf):
|
||||
def new_contents(self, readable):
|
||||
# got bytes for new update.
|
||||
|
||||
# Must be bigger than a full screen update.
|
||||
buf = readable.read(1024*1000)
|
||||
if not buf:
|
||||
return
|
||||
|
||||
buf = buf[-1024:] # ignore backlogs, get final state
|
||||
assert len(buf) == 1024, len(buf)
|
||||
|
||||
@ -148,6 +227,68 @@ class OLEDSimulator(SimulatedScreen):
|
||||
if active_set & USB_LED:
|
||||
spriterenderer.render(self.led_usb)
|
||||
|
||||
def shift_up(ch):
|
||||
# what ascii code for ascii key, ch, when shift also pressed?
|
||||
# IMPORTANT: this has nothing to do with Q1's keyboard layout
|
||||
if 'a' <= ch <= 'z':
|
||||
return ch.upper()
|
||||
|
||||
f,t = '1234567890-=\`[];\',./', \
|
||||
'!@#$%^&*()_+|~{}:"<>?'
|
||||
|
||||
idx = f.find(ch)
|
||||
return t[idx] if idx != -1 else ch
|
||||
|
||||
def load_shared_mod(name, path):
|
||||
# load indicated file.py as a module
|
||||
# from <https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path>
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
q1_charmap = load_shared_mod('charcodes', '../shared/charcodes.py')
|
||||
|
||||
def scancode_remap(sc):
|
||||
# return an ACSII (non standard) char to represent arrows and other similar
|
||||
# special keys on Q1 only.
|
||||
# - see ENV/lib/python3.10/site-packages/sdl2/scancode.py
|
||||
# - select/cancel/tab/bs all handled already
|
||||
# - NFC, lamp, QR buttons in alt_up()
|
||||
|
||||
m = {
|
||||
SDL_SCANCODE_RIGHT: q1_charmap.KEY_RIGHT,
|
||||
SDL_SCANCODE_LEFT: q1_charmap.KEY_LEFT,
|
||||
SDL_SCANCODE_DOWN: q1_charmap.KEY_DOWN,
|
||||
SDL_SCANCODE_UP: q1_charmap.KEY_UP,
|
||||
SDL_SCANCODE_HOME: q1_charmap.KEY_HOME,
|
||||
SDL_SCANCODE_END: q1_charmap.KEY_END,
|
||||
SDL_SCANCODE_PAGEDOWN: q1_charmap.KEY_PAGE_DOWN,
|
||||
SDL_SCANCODE_PAGEUP: q1_charmap.KEY_PAGE_UP,
|
||||
|
||||
SDL_SCANCODE_F1: q1_charmap.KEY_F1,
|
||||
SDL_SCANCODE_F2: q1_charmap.KEY_F2,
|
||||
SDL_SCANCODE_F3: q1_charmap.KEY_F3,
|
||||
SDL_SCANCODE_F4: q1_charmap.KEY_F4,
|
||||
SDL_SCANCODE_F5: q1_charmap.KEY_F5,
|
||||
SDL_SCANCODE_F6: q1_charmap.KEY_F6,
|
||||
}
|
||||
|
||||
return m[sc] if sc in m else None
|
||||
|
||||
def alt_up(ch):
|
||||
# ALT+(ch) => special needs of Q1
|
||||
print(f"Alt: {ch}")
|
||||
if ch == 'n':
|
||||
return q1_charmap.KEY_NFC
|
||||
if ch == 'q':
|
||||
return q1_charmap.KEY_QR
|
||||
if ch == 'l':
|
||||
return q1_charmap.KEY_LAMP
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def start():
|
||||
print('''\nColdcard Simulator: Commands (over simulated window):
|
||||
@ -162,7 +303,7 @@ def start():
|
||||
is_q1 = ('--q1' in sys.argv)
|
||||
|
||||
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
|
||||
simdis = OLEDSimulator(factory)
|
||||
simdis = (OLEDSimulator if not is_q1 else LCDSimulator)(factory)
|
||||
bg = factory.from_image(simdis.background_img)
|
||||
|
||||
window = sdl2.ext.Window("Coldcard Simulator", size=bg.size, position=(100, 100))
|
||||
@ -224,7 +365,7 @@ def start():
|
||||
str(display_w), str(numpad_r), str(led_w)] \
|
||||
+ metal_args + sys.argv[1:]
|
||||
xterm = subprocess.Popen(['xterm', '-title', 'Coldcard Simulator REPL',
|
||||
'-geom', '132x40+450+40', '-e'] + cc_cmd,
|
||||
'-geom', '132x40+650+40', '-e'] + cc_cmd,
|
||||
env=env,
|
||||
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
pass_fds=pass_fds, shell=False)
|
||||
@ -248,15 +389,15 @@ def start():
|
||||
pressed = set()
|
||||
|
||||
def send_event(ch, is_down):
|
||||
before = len(pressed)
|
||||
|
||||
#print(f'{ch} down={is_down}')
|
||||
if is_down:
|
||||
pressed.add(ch)
|
||||
if ch not in pressed:
|
||||
numpad_tx.write(ch.encode())
|
||||
pressed.add(ch)
|
||||
else:
|
||||
pressed.discard(ch)
|
||||
|
||||
if len(pressed) != before:
|
||||
numpad_tx.write(b''.join(pressed) + b'\n')
|
||||
if not pressed:
|
||||
numpad_tx.write(b'\0') # all up signal
|
||||
|
||||
|
||||
while running:
|
||||
@ -269,17 +410,27 @@ def start():
|
||||
if event.type == sdl2.SDL_KEYUP or event.type == sdl2.SDL_KEYDOWN:
|
||||
try:
|
||||
ch = chr(event.key.keysym.sym)
|
||||
#print('0x%0x => %s mod=0x%x'%(event.key.keysym.sym, ch, event.key.keysym.mod))
|
||||
#print('0x%0x => chr %s mod=0x%x'%(event.key.keysym.sym, ch, event.key.keysym.mod))
|
||||
if event.key.keysym.mod & 0x3: # left or right shift
|
||||
ch = shift_up(ch)
|
||||
if event.key.keysym.mod & 0x300: # left or right ALT
|
||||
ch = alt_up(ch)
|
||||
except:
|
||||
# things like 'shift' by itself
|
||||
#print('0x%0x' % event.key.keysym.sym)
|
||||
if 0x4000004f <= event.key.keysym.sym <= 0x40000052:
|
||||
# arrow keys
|
||||
ch = '9785'[event.key.keysym.sym - 0x4000004f]
|
||||
else:
|
||||
ch = '\0'
|
||||
# things like 'shift' by itself and anything not really ascii
|
||||
|
||||
# control+KEY
|
||||
scancode = event.key.keysym.sym & 0xffff
|
||||
#print(f'keysym=0x%0x => {scancode}' % event.key.keysym.sym)
|
||||
if is_q1:
|
||||
ch = scancode_remap(scancode)
|
||||
if not ch: continue
|
||||
elif SDL_SCANCODE_RIGHT <= scancode <= SDL_SCANCODE_UP:
|
||||
# arrow keys remap for Mk4
|
||||
ch = '9785'[scancode - SDL_SCANCODE_RIGHT]
|
||||
else:
|
||||
print('Ignore: 0x%0x' % event.key.keysym.sym)
|
||||
continue
|
||||
|
||||
# control+KEY => for our use
|
||||
if event.key.keysym.mod == 0x40 and event.type == sdl2.SDL_KEYDOWN:
|
||||
if ch == 'q':
|
||||
# control-Q
|
||||
@ -329,14 +480,13 @@ def start():
|
||||
continue
|
||||
|
||||
# need this to kill key-repeat
|
||||
ch = ch.encode('ascii')
|
||||
send_event(ch, event.type == sdl2.SDL_KEYDOWN)
|
||||
|
||||
if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
|
||||
#print('xy = %d, %d' % (event.button.x, event.button.y))
|
||||
ch = simdis.click_to_key(event.button.x, event.button.y)
|
||||
if ch is not None:
|
||||
send_event(ch.encode('ascii'), True)
|
||||
send_event(ch, True)
|
||||
|
||||
if event.type == sdl2.SDL_MOUSEBUTTONUP:
|
||||
for ch in list(pressed):
|
||||
@ -348,20 +498,19 @@ def start():
|
||||
if bare_metal and r == bare_metal.request:
|
||||
bare_metal.readable()
|
||||
continue
|
||||
|
||||
# Must be bigger than a full screen update.
|
||||
buf = r.read(1024*1000)
|
||||
if not buf:
|
||||
break
|
||||
|
||||
if r is display_rx:
|
||||
simdis.new_contents(buf)
|
||||
simdis.new_contents(r)
|
||||
spriterenderer.render(simdis.sprite)
|
||||
window.refresh()
|
||||
elif r is led_rx:
|
||||
|
||||
# XXX 8+8 bits
|
||||
for c in buf:
|
||||
c = r.read(1)
|
||||
if not c:
|
||||
break
|
||||
|
||||
c = c[0]
|
||||
if 1:
|
||||
#print("LED change: 0x%02x" % c[0])
|
||||
|
||||
mask = (c >> 4) & 0xf
|
||||
|
||||
@ -234,5 +234,8 @@ def get_cpi_id():
|
||||
#default mk4
|
||||
return 0x470 # STM32L4S5
|
||||
|
||||
def lcd_blast(buf):
|
||||
# sends to LCD
|
||||
return
|
||||
|
||||
# EOF
|
||||
|
||||
@ -24,6 +24,17 @@ class Pin:
|
||||
from ckcc import led_pipe
|
||||
led_pipe.write(bytes([(bm << 4) | (bm if n else 0)]))
|
||||
|
||||
def pin(self):
|
||||
# ? pin number
|
||||
return 99
|
||||
|
||||
def irq(self, *a, **k):
|
||||
# hack in the keyboard system
|
||||
if self.name == 'LCD_TEAR':
|
||||
from touch import Touch
|
||||
Touch()
|
||||
pass
|
||||
|
||||
ALT = None
|
||||
PULL_NONE = None
|
||||
PULL_UP = None
|
||||
@ -33,7 +44,6 @@ class Pin:
|
||||
IN = None
|
||||
IRQ_FALLING = 1
|
||||
IRQ_RISING = 2
|
||||
irq = lambda a,b,c: None
|
||||
|
||||
SPI = Mock
|
||||
UART = Mock
|
||||
|
||||
@ -16,6 +16,7 @@ freeze_as_mpy('', [
|
||||
'sim_settings.py',
|
||||
'sim_vdisk.py',
|
||||
'ssd1306.py',
|
||||
'st7788.py',
|
||||
'stm.py',
|
||||
'struct.py',
|
||||
'touch.py',
|
||||
|
||||
@ -145,7 +145,7 @@ class ExtInt:
|
||||
|
||||
class Timer:
|
||||
def __init__(self, n):
|
||||
# hack in the fake "touch" for mark 2 boards.
|
||||
# hack in the fake "touch" for mark 2-4 boards.
|
||||
assert n == 7
|
||||
from touch import Touch
|
||||
Touch()
|
||||
|
||||
36
unix/variant/st7788.py
Normal file
36
unix/variant/st7788.py
Normal file
@ -0,0 +1,36 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# st7788.py - LCD communications for Q1's 320x240 pixel *colour* display!
|
||||
#
|
||||
import struct, sys
|
||||
import framebuf
|
||||
import uasyncio
|
||||
|
||||
class ST7788(framebuf.FrameBuffer):
|
||||
def __init__(self):
|
||||
# for framebuf.FrameBuffer
|
||||
self.width = 320
|
||||
self.height = 240
|
||||
self.buffer = bytearray(320*240)
|
||||
|
||||
self.pipe = open(int(sys.argv[1]), 'wb')
|
||||
|
||||
super().__init__(self.buffer, self.width, self.height, framebuf.GS8)
|
||||
|
||||
def show_partial(self, y, h):
|
||||
# update just a few rows of the display
|
||||
assert h >= 1
|
||||
assert h < 120 # sim limitation
|
||||
hdr = struct.pack('<4H', 0, y, 320, h)
|
||||
rows = memoryview(self.buffer)[320*y:320*(y+h)]
|
||||
self.pipe.write(hdr + rows)
|
||||
|
||||
def show(self):
|
||||
# send entire frame buffer, but two packets
|
||||
hdr = struct.pack('<4H', 0, 0, 320, 120)
|
||||
self.pipe.write(hdr + self.buffer[0:320*120])
|
||||
|
||||
hdr = struct.pack('<4H', 0, 120, 320, 120)
|
||||
self.pipe.write(hdr + self.buffer[320*120:])
|
||||
|
||||
# EOF
|
||||
@ -23,14 +23,16 @@ class Touch:
|
||||
s = StreamReader(self.pipe)
|
||||
|
||||
# hack!
|
||||
from main import numpad
|
||||
from glob import numpad
|
||||
|
||||
while 1:
|
||||
ln = await s.readline()
|
||||
|
||||
key = ln[:-1].decode()
|
||||
#numpad.key_pressed = key if key else ''
|
||||
await numpad._changes.put(key)
|
||||
# rx's any key that is pressed and now released
|
||||
key = await s.read(1)
|
||||
print("Sim: %s" % key)
|
||||
if key == b'\0':
|
||||
await numpad._changes.put('') # all up
|
||||
else:
|
||||
await numpad._changes.put(key.decode())
|
||||
|
||||
def discharge(self):
|
||||
pass
|
||||
|
||||
@ -69,13 +69,13 @@ if '--mk3' in sys.argv:
|
||||
has_psram = False
|
||||
has_nfc = False
|
||||
|
||||
mk_num = int(hw_label[2:])
|
||||
|
||||
if '--q1' in sys.argv:
|
||||
hw_label = 'q1'
|
||||
has_qr = True
|
||||
num_sd_slots = 2
|
||||
|
||||
mk_num = int(hw_label[2:])
|
||||
|
||||
from public_constants import MAX_TXN_LEN, MAX_UPLOAD_LEN
|
||||
from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user