q1 support: more leds, keyboard diff, etc

This commit is contained in:
Peter D. Gray 2023-05-30 10:07:59 -04:00 committed by scgbckbone
parent 3eb98562ef
commit bef465e2c7
12 changed files with 286 additions and 48 deletions

View File

@ -2,6 +2,7 @@
#
# Code for the simulator to run, to get it to the point where main.py is called
# on real system. Equivilent to a few lines of code found in stm32/COLDCARD/initfs.c
#
import machine, pyb, sys, os
@ -29,9 +30,10 @@ if '--sflash' not in sys.argv:
#glob.settings.current = dict(sim_defaults)
# Install Mk4 hacks and workarounds
# Install various hacks and workarounds
import mk4
import sim_mk4
import sim_q1
import sim_psram
import sim_vdisk

View File

@ -207,18 +207,32 @@ class LCDSimulator(SimulatedScreen):
# - not planning to support, tedious
return None
def draw_single_led(self, spriterenderer, x, y, red=False):
sp = self.led_red if red else self.led_green
sp.position = (x, y)
spriterenderer.render(sp)
def draw_leds(self, spriterenderer, active_set=0):
# always draw SE led, since one is always on
GEN_LED = 0x1
SD_LED = 0x2
# redraw all LED's in their current state, indicated
SE1_LED = 0x1
SD1_LED = 0x2
USB_LED = 0x4
SD2_LED = 0x8
NFC_LED = 0x10
spriterenderer.render(self.led_green if (active_set & GEN_LED) else self.led_red)
if active_set & SE1_LED:
self.draw_single_led(spriterenderer, 17, 0, red=False)
else:
self.draw_single_led(spriterenderer, 65, 0, red=True)
if active_set & SD_LED:
spriterenderer.render(self.led_green) # XXX reposition
if active_set & SD1_LED:
self.draw_single_led(spriterenderer, -10, 125)
if active_set & SD2_LED:
self.draw_single_led(spriterenderer, -10, 215)
if active_set & USB_LED:
spriterenderer.render(self.led_green) # XXX reposition
self.draw_single_led(spriterenderer, 195, 705)
if active_set & NFC_LED:
self.draw_single_led(spriterenderer, 400, 275)
class OLEDSimulator(SimulatedScreen):
# top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
@ -362,6 +376,67 @@ def alt_up(ch):
return None
q1_pressed = set()
def handle_q1_key_events(event, numpad_tx):
# Map SDL2 (unix, desktop) keyscan code into keynumber on Q1
# - allow Q1 to do shift logic
# - support up to 5 keys down at once
global q1_pressed
assert event.type in { sdl2.SDL_KEYUP, sdl2.SDL_KEYDOWN}
is_press = (event.type == sdl2.SDL_KEYDOWN)
# first, see if we can convert to ascii char
scancode = event.key.keysym.sym & 0xffff
try:
ch = chr(event.key.keysym.sym)
except:
ch = scancode_remap(scancode)
#print(f'scan 0x{scancode:04x} => char={ch}=0x{ord(ch) if ch else 0:02x}')
shift_down = bool(event.key.keysym.mod & 0x3) # left or right shift
symbol_down = bool(event.key.keysym.mod & 0x200) # right ALT
#print(f"modifier = 0x{event.key.keysym.mod:04x} => shift={shift_down} symb={symbol_down}")
# reverse char to a keynum, and perhaps the meta key too
kn = None
if ch:
if ch in q1_charmap.DECODER:
kn = q1_charmap.DECODER.find(ch)
elif ch in q1_charmap.DECODER_SHIFT:
kn = q1_charmap.DECODER_SHIFT.find(ch)
shift_down = is_press
elif ch in q1_charmap.DECODER_SYMBOL:
kn = q1_charmap.DECODER_SYMBOL.find(ch)
symbol_down = is_press
#print(f" .. => keynum={kn} => shift={shift_down} symb={symbol_down}")
if kn:
if is_press:
q1_pressed.add(kn)
else:
q1_pressed.discard(kn)
q1_pressed.discard(q1_charmap.KEYNUM_SHIFT)
q1_pressed.discard(q1_charmap.KEYNUM_SYMBOL)
if shift_down:
q1_pressed.add(q1_charmap.KEYNUM_SHIFT)
if symbol_down:
q1_pressed.add(q1_charmap.KEYNUM_SYMBOL)
#print(f" .. => pressed: {q1_pressed}")
# see variant/touch.py where this is decoded.
assert len(q1_pressed) <= 5
report = bytes(list(q1_pressed) + [ 255, 255, 255, 255, 255])[0:5]
numpad_tx.write(report)
def start():
is_q1 = ('--q1' in sys.argv)
@ -375,6 +450,7 @@ def start():
if is_q1:
print('''\
Q1 specials:
Right-Alt = AltGr - Symb (symbol key)
Alt-L - lamp button (but ignored because implemented lower level)
Alt-N - NFC button
Alt-Q - QR button
@ -488,6 +564,15 @@ Q1 specials:
running = False
break
if is_q1 and event.type in { sdl2.SDL_KEYUP, sdl2.SDL_KEYDOWN} :
if event.key.keysym.mod == 0x40:
# ctrl key down, not used on Q1, so process as simulator
# command, see lower.
pass
else:
handle_q1_key_events(event, numpad_tx)
continue
if event.type == sdl2.SDL_KEYUP or event.type == sdl2.SDL_KEYDOWN:
try:
ch = chr(event.key.keysym.sym)
@ -586,24 +671,18 @@ Q1 specials:
spriterenderer.render(simdis.sprite)
window.refresh()
elif r is led_rx:
# XXX 8+8 bits
c = r.read(1)
# XXX was 4+4 bits, now two bytes: [active, mask]
c = r.read(2)
if not c:
break
c = c[0]
if 1:
#print("LED change: 0x%02x" % c[0])
mask, lset = c
active_set = (mask & lset)
mask = (c >> 4) & 0xf
lset = c & 0xf
active_set = (mask & lset)
#print("Genuine LED: %r" % genuine_state)
spriterenderer.render(bg)
spriterenderer.render(simdis.sprite)
simdis.draw_leds(spriterenderer, active_set)
#print("Genuine LED: %r" % genuine_state)
spriterenderer.render(bg)
spriterenderer.render(simdis.sprite)
simdis.draw_leds(spriterenderer, active_set)
window.refresh()
else:

View File

@ -18,7 +18,7 @@ rng_fd = open('/dev/urandom', 'rb')
global genuine_led
led_pipe = open(int(sys.argv[3]), 'wb')
led_pipe.write(b'\xf1') # all off, except green
led_pipe.write(b'\xff\x01') # all off, except green
genuine_led = True
# HACK: reduce size of heap in Unix simulator to be more similar to
@ -84,11 +84,11 @@ def gate(method, buf_io, arg2):
if arg2 == 1:
# clear it
genuine_led = False
led_pipe.write(b'\x10')
led_pipe.write(b'\x01\x00')
if arg2 == 3:
# real code would do checksum then go green
genuine_led = True
led_pipe.write(b'\x11')
led_pipe.write(b'\x01\x01')
return 1 if genuine_led else 0
if method == 5:

View File

@ -1,5 +1,7 @@
from mock import Mock
UNSPEC = object()
class Pin:
def __init__(self, name, *a, **kw):
self.name = name
@ -15,14 +17,19 @@ class Pin:
self.cur_value = int(n)
bm = 0
# 0x01 => SE1 light
if self.name == 'SD_ACTIVE':
bm = 0x02
elif self.name == 'USB_ACTIVE':
bm = 0x04
elif self.name == 'SD_ACTIVE2':
bm = 0x08
elif self.name == 'NFC_ACTIVE':
bm = 0x10
if bm:
from ckcc import led_pipe
led_pipe.write(bytes([(bm << 4) | (bm if n else 0)]))
led_pipe.write(bytes([bm, (bm if n else 0)]))
def pin(self):
# ? pin number
@ -35,6 +42,12 @@ class Pin:
Touch()
pass
def __call__(self, new_val=UNSPEC):
if new_val==UNSPEC:
return self.cur_value
else:
self.value(new_val)
ALT = None
PULL_NONE = None
PULL_UP = None

View File

@ -8,6 +8,8 @@ freeze_as_mpy('', [
'os.py',
'pyb.py',
'sim_mk4.py',
'sim_q1.py',
'sim_scanner.py',
'sim_nfc.py',
'sim_psram.py',
'sim_quickstart.py',

View File

@ -1,7 +1,11 @@
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import utime as time
import uerrno as errno
import sys
from machine import Pin
class USB_VCP:
@staticmethod
def isconnected():
@ -117,7 +121,7 @@ class SDCard:
@classmethod
def power(cls, st=0):
from ckcc import led_pipe
led_pipe.write(bytes([0x20 | (0x2 if st else 0x0)]))
led_pipe.write(bytes([0x2, (0x2 if st else 0x0)]))
if st:
time.sleep(0.100) # drama
return False
@ -129,12 +133,6 @@ class SDCard:
b'2\x00^\x00\xd6\x81Y[\x8f\xff\xb7\xed\x94\x00@\x16',
b'APA\tDU F\xd9\x92\x11\x10\x9a\x1a\x01\xdf')
class Pin:
PULL_NONE =1
PULL_UP =2
def __init__(self, *a, **kw):
return
class ExtInt:
def __init__(self, *a, **kw):
@ -153,4 +151,4 @@ class Timer:
def deinit(self): pass
def init(self, **k): pass
# EOF

View File

@ -18,6 +18,10 @@ def _init0():
import sim_nfc
sys.modules['nfc'] = sim_nfc
# Q1: install (fake) QR scanner interface code
import sim_scanner
sys.modules['scanner'] = sim_scanner
mk4.rng_seeding()
mk4.init0 = _init0

16
unix/variant/sim_q1.py Normal file
View File

@ -0,0 +1,16 @@
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# sim_q14.py - Simulate Q1 specific code, not needed on other devices.
#
# - shared/q1.py calls mk4.init0 so no need to replace that
#
import q1
q1.setup_adc = lambda: None
def mock_get_batt_level():
return 3.69
q1.get_batt_level = mock_get_batt_level
# EOF

View File

@ -0,0 +1,72 @@
# Replace QR Scanner module interface.
# TODO: support (optional) local real scanner over USB serial.
#
import os
import uasyncio as asyncio
from scanner import QRScanner
# unix/working/...
DATA_FILE = 'qrdata.txt'
class SimulatedQRScanner(QRScanner):
def __init__(self):
self.q = None
def hw_scan(self):
# trigger a scan
pass
async def _read_results(self):
# be a task that reads incoming QR codes from scanner (already in operation)
# - will be canceled when done/stopping
try:
_, orig_mtime, _ = os.stat(DATA_FILE)[-3:]
except OSError:
orig_mtime = None
while 1:
await asyncio.sleep_ms(250)
try:
_, mtime, _ = os.stat(DATA_FILE)[-3:]
except OSError:
mtime = None
if mtime == orig_mtime:
continue
print("Got new QR scan data.")
got = open(DATA_FILE, 'rb').read(8196)
self.q.push(got)
orig_mtime = mtime
async def scan_start(self, test=15):
# returns a Q we append to as results come in
self.q = rv = Queue()
self._scan_task = asyncio.create_task(self._read_results())
return rv
async def scan_stop(self):
self._scan_task.cancel()
self._scan_task = None
self.q = None
async def wakeup(self):
return
async def sleep(self):
return
async def tx(self, msg):
return
async def torch(self, on):
print("Torch is: " + 'ON' if on else 'off')
# close door behind ourselves
QRScanner = SimulatedQRScanner
# EOF

View File

@ -256,7 +256,7 @@ def pin_stuff(submethod, buf_io):
# greenlight firmware
from ckcc import genuine_led, led_pipe
genuine_led = True
led_pipe.write(b'\x01')
led_pipe.write(b'\x00\x01')
elif submethod == 6:
if not version.has_608:

View File

@ -6,31 +6,67 @@ import struct, sys
import framebuf
import uasyncio
class ST7788(framebuf.FrameBuffer):
class ST7788:
def __init__(self):
# for framebuf.FrameBuffer
self.width = 320
self.height = 240
self.buffer = bytearray(320*240)
#self.buffer = bytearray(320*240)
self.pipe = open(int(sys.argv[1]), 'wb')
super().__init__(self.buffer, self.width, self.height, framebuf.GS8)
#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)]
hdr = struct.pack('<s5H', 'p', 0, y, 320, h, len(rows))
self.pipe.write(hdr + rows)
def show(self):
# send entire frame buffer, but two packets
hdr = struct.pack('<4H', 0, 0, 320, 120)
hdr = struct.pack('<s5H', 'p', 0, 0, 320, 120, 320*120)
self.pipe.write(hdr + self.buffer[0:320*120])
hdr = struct.pack('<4H', 0, 120, 320, 120)
hdr = struct.pack('<s5H', 'p', 0, 120, 320, 120, 320*120)
self.pipe.write(hdr + self.buffer[320*120:])
def show_zpixels(self, x, y, w, h, zpixels):
# display compressed pixel data
hdr = struct.pack('<s5H', 'z', x, y, w, h, len(zpixels))
self.pipe.write(hdr + zpixels)
def fill_screen(self, pixel=0x0000):
# clear screen to indicated pixel value
self.fill_rect(0,0, 320,240, pixel)
def fill_rect(self, x,y, w,h, pixel=0x0000):
msg = struct.pack('<s5HH', 'f', x, y, w, h, 2, pixel)
self.pipe.write(msg)
def show_pal_pixels(self, x, y, w, h, palette, pixels):
# show 4-bit packed paletted lookup pixels; used for fonts, icons
assert len(palette) == 2 * 16
assert len(pixels) == w * h // 2
if 0:
# do palette lookup now
tmp = bytearray(2 * w * h)
pos = 0
for px in pixels:
col = (px >> 4)
tmp[pos:pos+2] = palette[col:col+2]
col = px & 0xf
tmp[pos+2:pos+4] = palette[col:col+2]
pos += 4
hdr = struct.pack('<s5H', 'r', x, y, w, h, len(tmp))
self.pipe.write(hdr + tmp)
else:
# assumes simulator has same palette
hdr = struct.pack('<s5H', 't' if palette[0] == 0 else 'i', x, y, w, h, len(pixels))
self.pipe.write(hdr + pixels)
# EOF

View File

@ -1,7 +1,10 @@
import sys
import sys, version
from uasyncio.core import get_event_loop
from uasyncio import StreamReader
NUM_ROWS = const(6)
NUM_COLS = const(10)
class Touch:
# Misnomer: emulating a membrane now, not a touch interface
@ -26,13 +29,26 @@ class Touch:
from glob import numpad
while 1:
# 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
if version.has_qwerty:
# NOTE: numpad # will be FullKeyboard instance
# sends 2 keynums, might be few meta+specific keys
# - pad with -1
pressed = await s.read(5)
for kn in range(NUM_ROWS * NUM_COLS):
numpad.is_pressed[kn] = (0 if kn not in pressed else 1)
# Q1 simulator sends keynumbers, from shared/charcodes.py
numpad.process_chg_state()
else:
await numpad._changes.put(key.decode())
# 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