refactor
@ -9,6 +9,10 @@
|
||||
|
||||
make && ./simulator.py
|
||||
|
||||
OR
|
||||
|
||||
make && ./simulator.py --q1
|
||||
|
||||
|
||||
## Other Startup Flags
|
||||
|
||||
@ -28,6 +32,7 @@ wallet (on testnet, always with the same seed). But there are other options:
|
||||
- `--mk2` => emulate mark2 hardware (older micro, etc), default is current-gen (mark4)
|
||||
- `--mk3` => emulate mark3 hardware
|
||||
- `--mk4` => emulate mark4 hardware
|
||||
- `--q1` => emulate Q1 hardware
|
||||
- `-g` => don't skip login sequence
|
||||
- `--addr` => go to the address explorer at startup
|
||||
- `--xw` => go to the wallet export submenu
|
||||
|
||||
199
unix/bare.py
Normal file
@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Connect to a real device, and allow simulator to use it's hardware for SE access.
|
||||
#
|
||||
# This is a normal python3 program, not micropython.
|
||||
#
|
||||
import os, sys, tty, pty, termios, time, pdb, tempfile
|
||||
from PIL import Image
|
||||
from select import select
|
||||
import fcntl
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
|
||||
|
||||
class BareMetal:
|
||||
#
|
||||
# Use a real Coldcard device's bootrom and Secure Elements
|
||||
#
|
||||
def __init__(self, req_r, resp_w):
|
||||
self.open()
|
||||
self.request = open(req_r, 'rt', closefd=0)
|
||||
self.response = open(resp_w, 'wb', closefd=0, buffering=0)
|
||||
|
||||
def open(self, name='usbserial-AQ00T1RR'):
|
||||
# return a file-descriptor ready to be used for access to a real Coldcard's console I/O.
|
||||
# - assume only one coldcard
|
||||
import sys, serial
|
||||
from serial.tools.list_ports import comports
|
||||
|
||||
for d in comports():
|
||||
if not name:
|
||||
if d.pid != 0xcc10: continue
|
||||
else:
|
||||
if name not in d.name: continue
|
||||
|
||||
sio = serial.Serial(d.device, write_timeout=1, baudrate=115200)
|
||||
|
||||
print("Connecting to: %s" % d.device)
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("Can't find usb serial port for real Coldcard")
|
||||
|
||||
self.sio = sio
|
||||
sio.timeout = 0.250
|
||||
|
||||
if d.pid == 0xcc10:
|
||||
# USB mode a litte easier
|
||||
greet = sio.readlines()
|
||||
if greet and b'Welcome to Coldcard!' in greet[1]:
|
||||
sio.write(b'\x03') # ctrl-C
|
||||
while 1:
|
||||
sio.timeout = 1
|
||||
lns = sio.readlines()
|
||||
if not lns: break
|
||||
else:
|
||||
# real serial port
|
||||
sio.write(b'\x03') # ctrl-C
|
||||
while 1:
|
||||
sio.timeout = 3
|
||||
lns = sio.readlines()
|
||||
print("ECHO: " + repr(lns))
|
||||
if not lns: break
|
||||
|
||||
# hit enter, expect prompt
|
||||
sio.timeout = 0.100
|
||||
sio.write(b'\r')
|
||||
ln = sio.readlines()
|
||||
#assert ln[-1] == b'>>> ', ln
|
||||
#assert ln[-1] == b'=== ', ln
|
||||
assert ln[-1] in {b'>>> ', b'=== '}, ln #ok if in paste mode
|
||||
|
||||
print(" Connected to: %s" % d.device)
|
||||
|
||||
sio.write(b'''\x05\
|
||||
from glob import dis
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
import ckcc
|
||||
dis.fullscreen("BareMetal")
|
||||
try:
|
||||
busy = dis.busy_bar
|
||||
except:
|
||||
busy = lambda x: None
|
||||
\x04'''.replace(b'\n', b'\r'))
|
||||
|
||||
# above is quick but will be echoed, so clear it out
|
||||
lns = self.wait_done()
|
||||
#print(f"setup: {lns}")
|
||||
|
||||
|
||||
def read_sflash(self):
|
||||
# capture contents of SPI flash (settings area only: last 128k)
|
||||
# XXX not working, and not for Mk4
|
||||
self.sio.write(b'''\x05\
|
||||
busy(1)
|
||||
from main import sf
|
||||
dis.fullscreen("SPI Flash")
|
||||
buf = bytearray(256)
|
||||
addr = 0xe0000
|
||||
for i in range(0, 0x20000, 256):
|
||||
sf.read(addr+i, buf)
|
||||
print(b2a_hex(buf).decode())
|
||||
busy(0)
|
||||
dis.fullscreen("BareMetal")
|
||||
\x04\r'''.replace(b'\n', b'\r'))
|
||||
|
||||
count = 0
|
||||
self.sio.timeout = 0.5
|
||||
for ln in self.sio.readlines():
|
||||
ln = ln.decode('ascii')
|
||||
if len(ln) == 512 + 2:
|
||||
self.response.write(ln[:-2].encode('ascii') + b'\n')
|
||||
count += 1
|
||||
elif ln.startswith('>>> '):
|
||||
break
|
||||
elif not ln or not ln.strip() or ln.startswith('=== ') or 'paste mode' in ln:
|
||||
pass
|
||||
else:
|
||||
print(f'junk: {ln}')
|
||||
|
||||
assert count == (128*1024)//256, count
|
||||
|
||||
print("Sent real SPI Flash contents to simulated Coldcard.")
|
||||
|
||||
def wait_done(self, timeout=1):
|
||||
sio = self.sio
|
||||
sio.timeout = timeout
|
||||
rv = sio.read_until('>>> ')
|
||||
return [str(i, 'ascii') for i in rv.split(b'\r\n')]
|
||||
|
||||
def readable(self):
|
||||
# expects (method, hex, arg2) as string on one line
|
||||
ln = self.request.readline()
|
||||
|
||||
arg1, bb, arg2 = ln.split(', ')
|
||||
|
||||
method = int(arg1)
|
||||
arg2 = int(arg2)
|
||||
buf_io = a2b_hex(bb) if bb != 'None' else None
|
||||
|
||||
if method == -99:
|
||||
# internal to us: read SPI flash contents
|
||||
return self.read_sflash()
|
||||
elif method in {2, 3}:
|
||||
# these methods always die; not helpful for testing
|
||||
print(f"FATAL Callgate(method={method}, arg2={arg2}) => execution would stop")
|
||||
self.response.write(b'0,\n')
|
||||
return
|
||||
|
||||
sio = self.sio
|
||||
|
||||
sio.timeout = 0.1
|
||||
sio.read_all()
|
||||
|
||||
sio.write(b'\r\x05') # CTRL-E => paste mode
|
||||
|
||||
if buf_io is None:
|
||||
sio.write(b'bb = None\r')
|
||||
else:
|
||||
sio.write(b'bb = bytearray(a2b_hex("%s"))\r' % b2a_hex(buf_io))
|
||||
|
||||
sio.write(b'busy(1)\r')
|
||||
sio.write(b'rv = ckcc.gate(%d, bb, %d)\r' % (method, arg2))
|
||||
sio.write(b'busy(0)\r')
|
||||
if buf_io is None:
|
||||
sio.write(b'print("%d," % rv)\r')
|
||||
else:
|
||||
sio.write(b'print("%d, %s" % (rv, b2a_hex(bb).decode()))\r')
|
||||
sio.write(b'\x04\r') # CTRL-D, end paste; start exec
|
||||
|
||||
lines = []
|
||||
for retries in range(10):
|
||||
lines.extend(self.wait_done())
|
||||
#print('back: \n' + '\n'.join( f'[{n}] {l}' for n,l in enumerate(lines)))
|
||||
if len(lines) >= 2 and lines[-1] == lines[-2] == '>>> ':
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("timed out")
|
||||
|
||||
# result is in lines between final === and first >>> ... typically a single
|
||||
# line, but might overflow into next 'line'
|
||||
assert '=== ' in lines and '>>> ' in lines
|
||||
a = -list(reversed(lines)).index('=== ')
|
||||
b = lines[a:].index('>>> ')
|
||||
rv = ''.join(lines[a:a+b]).strip()
|
||||
|
||||
assert rv
|
||||
assert ',' in rv
|
||||
assert not rv.startswith('===')
|
||||
|
||||
if 1:
|
||||
# trace output
|
||||
print(f"Callgate(method={method}, {len(buf_io) if buf_io else 0} bytes, "\
|
||||
f"arg2={arg2}) => rv={rv}")
|
||||
|
||||
self.response.write(rv.encode('ascii') + b'\n')
|
||||
|
||||
# EOF
|
||||
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
BIN
unix/q1-images/background.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
unix/q1-images/led-green.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
unix/q1-images/led-red.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
@ -19,51 +19,15 @@ from PIL import Image
|
||||
from select import select
|
||||
import fcntl
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
from bare import BareMetal
|
||||
|
||||
MPY_UNIX = 'l-port/micropython'
|
||||
|
||||
UNIX_SOCKET_PATH = '/tmp/ckcc-simulator.sock'
|
||||
|
||||
# top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
|
||||
OLED_ACTIVE = (46, 85)
|
||||
|
||||
# keypad touch buttons
|
||||
KEYPAD_LEFT = 52
|
||||
KEYPAD_TOP = 216
|
||||
KEYPAD_PITCH = 73
|
||||
|
||||
|
||||
class OLEDSimulator:
|
||||
|
||||
def __init__(self, factory):
|
||||
self.movie = None
|
||||
|
||||
s = factory.create_software_sprite( (128,64), bpp=32)
|
||||
self.sprite = s
|
||||
s.x, s.y = OLED_ACTIVE
|
||||
s.depth = 100
|
||||
|
||||
self.fg = sdl2.ext.prepare_color('#ccf', s)
|
||||
self.bg = sdl2.ext.prepare_color('#111', s)
|
||||
sdl2.ext.fill(s, self.bg)
|
||||
|
||||
self.mv = sdl2.ext.PixelView(self.sprite)
|
||||
|
||||
def render(self, window, buf):
|
||||
# do a full-screen update of the OLED contents and display
|
||||
assert len(buf) == 1024, len(buf)
|
||||
|
||||
for y in range(0, 64, 8):
|
||||
line = buf[y*128//8:]
|
||||
for x in range(128):
|
||||
val = buf[(y*128//8) + x]
|
||||
mask = 0x01
|
||||
for i in range(8):
|
||||
self.mv[y+i][x] = self.fg if (val & mask) else self.bg
|
||||
mask <<= 1
|
||||
|
||||
if self.movie is not None:
|
||||
self.new_frame()
|
||||
class SimulatedScreen:
|
||||
# a base class
|
||||
|
||||
def snapshot(self):
|
||||
fn = time.strftime('../snapshot-%j-%H%M%S.png')
|
||||
@ -109,209 +73,97 @@ class OLEDSimulator:
|
||||
img = img.convert('P')
|
||||
self.movie.append((dt, img))
|
||||
|
||||
class BareMetal:
|
||||
#
|
||||
# Use a real Coldcard device's bootrom and Secure Elements
|
||||
#
|
||||
def __init__(self, req_r, resp_w):
|
||||
self.open()
|
||||
self.request = open(req_r, 'rt', closefd=0)
|
||||
self.response = open(resp_w, 'wb', closefd=0, buffering=0)
|
||||
class LCDSimulator(SimulatedScreen):
|
||||
pass
|
||||
|
||||
def open(self, name='usbserial-AQ00T1RR'):
|
||||
# return a file-descriptor ready to be used for access to a real Coldcard's console I/O.
|
||||
# - assume only one coldcard
|
||||
import sys, serial
|
||||
from serial.tools.list_ports import comports
|
||||
class OLEDSimulator(SimulatedScreen):
|
||||
# top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
|
||||
OLED_ACTIVE = (46, 85)
|
||||
|
||||
for d in comports():
|
||||
if not name:
|
||||
if d.pid != 0xcc10: continue
|
||||
else:
|
||||
if name not in d.name: continue
|
||||
# keypad touch buttons
|
||||
KEYPAD_LEFT = 52
|
||||
KEYPAD_TOP = 216
|
||||
KEYPAD_PITCH = 73
|
||||
|
||||
sio = serial.Serial(d.device, write_timeout=1, baudrate=115200)
|
||||
background_img = 'mk4-images/background.png'
|
||||
|
||||
print("Connecting to: %s" % d.device)
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("Can't find usb serial port for real Coldcard")
|
||||
def __init__(self, factory):
|
||||
self.movie = None
|
||||
|
||||
self.sio = sio
|
||||
sio.timeout = 0.250
|
||||
s = factory.create_software_sprite( (128,64), bpp=32)
|
||||
self.sprite = s
|
||||
s.x, s.y = self.OLED_ACTIVE
|
||||
s.depth = 100
|
||||
|
||||
if d.pid == 0xcc10:
|
||||
# USB mode a litte easier
|
||||
greet = sio.readlines()
|
||||
if greet and b'Welcome to Coldcard!' in greet[1]:
|
||||
sio.write(b'\x03') # ctrl-C
|
||||
while 1:
|
||||
sio.timeout = 1
|
||||
lns = sio.readlines()
|
||||
if not lns: break
|
||||
else:
|
||||
# real serial port
|
||||
sio.write(b'\x03') # ctrl-C
|
||||
while 1:
|
||||
sio.timeout = 3
|
||||
lns = sio.readlines()
|
||||
print("ECHO: " + repr(lns))
|
||||
if not lns: break
|
||||
self.fg = sdl2.ext.prepare_color('#ccf', s)
|
||||
self.bg = sdl2.ext.prepare_color('#111', s)
|
||||
sdl2.ext.fill(s, self.bg)
|
||||
|
||||
# hit enter, expect prompt
|
||||
sio.timeout = 0.100
|
||||
sio.write(b'\r')
|
||||
ln = sio.readlines()
|
||||
#assert ln[-1] == b'>>> ', ln
|
||||
#assert ln[-1] == b'=== ', ln
|
||||
assert ln[-1] in {b'>>> ', b'=== '}, ln #ok if in paste mode
|
||||
|
||||
print(" Connected to: %s" % d.device)
|
||||
|
||||
sio.write(b'''\x05\
|
||||
from glob import dis
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from ubinascii import unhexlify as a2b_hex
|
||||
import ckcc
|
||||
dis.fullscreen("BareMetal")
|
||||
try:
|
||||
busy = dis.busy_bar
|
||||
except:
|
||||
busy = lambda x: None
|
||||
\x04'''.replace(b'\n', b'\r'))
|
||||
|
||||
# above is quick but will be echoed, so clear it out
|
||||
lns = self.wait_done()
|
||||
#print(f"setup: {lns}")
|
||||
|
||||
|
||||
def read_sflash(self):
|
||||
# capture contents of SPI flash (settings area only: last 128k)
|
||||
# XXX not working, and not for Mk4
|
||||
self.sio.write(b'''\x05\
|
||||
busy(1)
|
||||
from main import sf
|
||||
dis.fullscreen("SPI Flash")
|
||||
buf = bytearray(256)
|
||||
addr = 0xe0000
|
||||
for i in range(0, 0x20000, 256):
|
||||
sf.read(addr+i, buf)
|
||||
print(b2a_hex(buf).decode())
|
||||
busy(0)
|
||||
dis.fullscreen("BareMetal")
|
||||
\x04\r'''.replace(b'\n', b'\r'))
|
||||
|
||||
count = 0
|
||||
self.sio.timeout = 0.5
|
||||
for ln in self.sio.readlines():
|
||||
ln = ln.decode('ascii')
|
||||
if len(ln) == 512 + 2:
|
||||
self.response.write(ln[:-2].encode('ascii') + b'\n')
|
||||
count += 1
|
||||
elif ln.startswith('>>> '):
|
||||
break
|
||||
elif not ln or not ln.strip() or ln.startswith('=== ') or 'paste mode' in ln:
|
||||
pass
|
||||
else:
|
||||
print(f'junk: {ln}')
|
||||
|
||||
assert count == (128*1024)//256, count
|
||||
|
||||
print("Sent real SPI Flash contents to simulated Coldcard.")
|
||||
|
||||
def wait_done(self, timeout=1):
|
||||
sio = self.sio
|
||||
sio.timeout = timeout
|
||||
rv = sio.read_until('>>> ')
|
||||
return [str(i, 'ascii') for i in rv.split(b'\r\n')]
|
||||
|
||||
def readable(self):
|
||||
# expects (method, hex, arg2) as string on one line
|
||||
ln = self.request.readline()
|
||||
|
||||
arg1, bb, arg2 = ln.split(', ')
|
||||
|
||||
method = int(arg1)
|
||||
arg2 = int(arg2)
|
||||
buf_io = a2b_hex(bb) if bb != 'None' else None
|
||||
|
||||
if method == -99:
|
||||
# internal to us: read SPI flash contents
|
||||
return self.read_sflash()
|
||||
elif method in {2, 3}:
|
||||
# these methods always die; not helpful for testing
|
||||
print(f"FATAL Callgate(method={method}, arg2={arg2}) => execution would stop")
|
||||
self.response.write(b'0,\n')
|
||||
return
|
||||
|
||||
sio = self.sio
|
||||
|
||||
sio.timeout = 0.1
|
||||
sio.read_all()
|
||||
|
||||
sio.write(b'\r\x05') # CTRL-E => paste mode
|
||||
|
||||
if buf_io is None:
|
||||
sio.write(b'bb = None\r')
|
||||
else:
|
||||
sio.write(b'bb = bytearray(a2b_hex("%s"))\r' % b2a_hex(buf_io))
|
||||
|
||||
sio.write(b'busy(1)\r')
|
||||
sio.write(b'rv = ckcc.gate(%d, bb, %d)\r' % (method, arg2))
|
||||
sio.write(b'busy(0)\r')
|
||||
if buf_io is None:
|
||||
sio.write(b'print("%d," % rv)\r')
|
||||
else:
|
||||
sio.write(b'print("%d, %s" % (rv, b2a_hex(bb).decode()))\r')
|
||||
sio.write(b'\x04\r') # CTRL-D, end paste; start exec
|
||||
|
||||
lines = []
|
||||
for retries in range(10):
|
||||
lines.extend(self.wait_done())
|
||||
#print('back: \n' + '\n'.join( f'[{n}] {l}' for n,l in enumerate(lines)))
|
||||
if len(lines) >= 2 and lines[-1] == lines[-2] == '>>> ':
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("timed out")
|
||||
|
||||
# result is in lines between final === and first >>> ... typically a single
|
||||
# line, but might overflow into next 'line'
|
||||
assert '=== ' in lines and '>>> ' in lines
|
||||
a = -list(reversed(lines)).index('=== ')
|
||||
b = lines[a:].index('>>> ')
|
||||
rv = ''.join(lines[a:a+b]).strip()
|
||||
|
||||
assert rv
|
||||
assert ',' in rv
|
||||
assert not rv.startswith('===')
|
||||
|
||||
if 1:
|
||||
# trace output
|
||||
print(f"Callgate(method={method}, {len(buf_io) if buf_io else 0} bytes, "\
|
||||
f"arg2={arg2}) => rv={rv}")
|
||||
|
||||
self.response.write(rv.encode('ascii') + b'\n')
|
||||
self.mv = sdl2.ext.PixelView(self.sprite)
|
||||
|
||||
# for genuine/caution lights and other LED's
|
||||
self.led_red = factory.from_image("mk4-images/led-red.png")
|
||||
self.led_green = factory.from_image("mk4-images/led-green.png")
|
||||
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):
|
||||
# got bytes for new update.
|
||||
buf = buf[-1024:] # ignore backlogs, get final state
|
||||
assert len(buf) == 1024, len(buf)
|
||||
|
||||
for y in range(0, 64, 8):
|
||||
line = buf[y*128//8:]
|
||||
for x in range(128):
|
||||
val = buf[(y*128//8) + x]
|
||||
mask = 0x01
|
||||
for i in range(8):
|
||||
self.mv[y+i][x] = self.fg if (val & mask) else self.bg
|
||||
mask <<= 1
|
||||
|
||||
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
|
||||
col = ((x - self.KEYPAD_LEFT) // self.KEYPAD_PITCH)
|
||||
row = ((y - self.KEYPAD_TOP) // self.KEYPAD_PITCH)
|
||||
|
||||
#print('rc= %d,%d' % (row,col))
|
||||
if not (0 <= row < 4): return None
|
||||
if not (0 <= col < 3): return None
|
||||
|
||||
return '123456789x0y'[(row*3) + col]
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def start():
|
||||
print('''\nColdcard Simulator: Commands (over simulated window):
|
||||
- Control-Q to quit
|
||||
- Z to snapshot screen.
|
||||
- S/E to start/end movie recording
|
||||
- N to capture NFC data (tap it)
|
||||
- ^Z to snapshot screen.
|
||||
- ^S/^E to start/end movie recording
|
||||
- ^N to capture NFC data (tap it)
|
||||
''')
|
||||
sdl2.ext.init()
|
||||
sdl2.SDL_EnableScreenSaver()
|
||||
|
||||
is_q1 = ('--q1' in sys.argv)
|
||||
|
||||
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
|
||||
bg = factory.from_image("background.png")
|
||||
oled = OLEDSimulator(factory)
|
||||
|
||||
# for genuine/caution lights and other LED's
|
||||
led_red = factory.from_image("led-red.png")
|
||||
led_green = factory.from_image("led-green.png")
|
||||
led_sdcard = factory.from_image("led-sd.png")
|
||||
led_usb = factory.from_image("led-usb.png")
|
||||
simdis = OLEDSimulator(factory)
|
||||
bg = factory.from_image(simdis.background_img)
|
||||
|
||||
window = sdl2.ext.Window("Coldcard Simulator", size=bg.size, position=(100, 100))
|
||||
window.show()
|
||||
@ -321,17 +173,17 @@ def start():
|
||||
|
||||
spriterenderer = factory.create_sprite_render_system(window)
|
||||
|
||||
# initial state
|
||||
spriterenderer.render(bg)
|
||||
spriterenderer.render(oled.sprite)
|
||||
spriterenderer.render(led_red)
|
||||
spriterenderer.render(simdis.sprite)
|
||||
genuine_state = False
|
||||
sd_active = False
|
||||
simdis.draw_leds(spriterenderer)
|
||||
|
||||
# capture exec path and move into intended working directory
|
||||
env = os.environ.copy()
|
||||
env['MICROPYPATH'] = ':' + os.path.realpath('../shared')
|
||||
|
||||
oled_r, oled_w = os.pipe() # fancy OLED display
|
||||
display_r, display_w = os.pipe() # fancy OLED display
|
||||
led_r, led_w = os.pipe() # genuine LED
|
||||
numpad_r, numpad_w = os.pipe() # keys
|
||||
|
||||
@ -349,7 +201,7 @@ def start():
|
||||
# - open the serial device
|
||||
# - get buffering/non-blocking right
|
||||
# - pass in open fd numbers
|
||||
pass_fds = [oled_w, numpad_r, led_w]
|
||||
pass_fds = [display_w, numpad_r, led_w]
|
||||
|
||||
if '--metal' in sys.argv:
|
||||
# bare-metal access: use a real Coldcard's bootrom+SE.
|
||||
@ -369,7 +221,7 @@ def start():
|
||||
cc_cmd = ['../coldcard-mpy',
|
||||
'-X', 'heapsize=9m',
|
||||
'-i', '../sim_boot.py',
|
||||
str(oled_w), str(numpad_r), str(led_w)] \
|
||||
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,
|
||||
@ -379,16 +231,16 @@ def start():
|
||||
|
||||
|
||||
# reopen as binary streams
|
||||
oled_rx = open(oled_r, 'rb', closefd=0, buffering=0)
|
||||
display_rx = open(display_r, 'rb', closefd=0, buffering=0)
|
||||
led_rx = open(led_r, 'rb', closefd=0, buffering=0)
|
||||
numpad_tx = open(numpad_w, 'wb', closefd=0, buffering=0)
|
||||
|
||||
# setup no blocking
|
||||
for r in [oled_rx, led_rx]:
|
||||
for r in [display_rx, led_rx]:
|
||||
fl = fcntl.fcntl(r, fcntl.F_GETFL)
|
||||
fcntl.fcntl(r, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
|
||||
readables = [oled_rx, led_rx]
|
||||
readables = [display_rx, led_rx]
|
||||
if bare_metal:
|
||||
readables.append(bare_metal.request)
|
||||
|
||||
@ -427,19 +279,14 @@ def start():
|
||||
else:
|
||||
ch = '\0'
|
||||
|
||||
# remap ESC/Enter
|
||||
if ch == '\x1b':
|
||||
ch = 'x'
|
||||
elif ch == '\x0d':
|
||||
ch = 'y'
|
||||
# control+KEY
|
||||
if event.key.keysym.mod == 0x40 and event.type == sdl2.SDL_KEYDOWN:
|
||||
if ch == 'q':
|
||||
# control-Q
|
||||
running = False
|
||||
break
|
||||
|
||||
if ch == 'q' and event.key.keysym.mod == 0x40:
|
||||
# control-Q
|
||||
running = False
|
||||
break
|
||||
|
||||
if ch == 'n':
|
||||
if event.type == sdl2.SDL_KEYDOWN:
|
||||
if ch == 'n':
|
||||
# see sim_nfc.py
|
||||
try:
|
||||
nfc = open('nfc-dump.ndef', 'rb').read()
|
||||
@ -448,44 +295,48 @@ def start():
|
||||
print(f"Simulated NFC read: {len(nfc)} bytes into {fn}")
|
||||
except FileNotFoundError:
|
||||
print("NFC not ready")
|
||||
continue
|
||||
|
||||
if ch in 'zse':
|
||||
if event.type == sdl2.SDL_KEYDOWN:
|
||||
if ch in 'zse':
|
||||
if ch == 'z':
|
||||
oled.snapshot()
|
||||
simdis.snapshot()
|
||||
if ch == 's':
|
||||
oled.movie_start()
|
||||
simdis.movie_start()
|
||||
if ch == 'e':
|
||||
oled.movie_end()
|
||||
simdis.movie_end()
|
||||
continue
|
||||
|
||||
if ch == 'm':
|
||||
# do many OK's in a row ... for word nest menu
|
||||
for i in range(30):
|
||||
numpad_tx.write(b'y\n')
|
||||
numpad_tx.write(b'\n')
|
||||
continue
|
||||
|
||||
if event.key.keysym.mod == 0x40:
|
||||
# control key releases: ignore
|
||||
continue
|
||||
|
||||
if ch == 'm':
|
||||
# do many OK's in a row ... for word nest menu
|
||||
for i in range(30):
|
||||
numpad_tx.write(b'y\n')
|
||||
numpad_tx.write(b'\n')
|
||||
continue
|
||||
# remap ESC/Enter
|
||||
if not is_q1:
|
||||
if ch == '\x1b':
|
||||
ch = 'x'
|
||||
elif ch == '\x0d':
|
||||
ch = 'y'
|
||||
|
||||
if ch not in '0123456789xy':
|
||||
if ch.isprintable():
|
||||
print("Invalid key: '%s'" % ch)
|
||||
continue
|
||||
|
||||
if ch not in '0123456789xy':
|
||||
if ch.isprintable():
|
||||
print("Invalid key: '%s'" % ch)
|
||||
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))
|
||||
col = ((event.button.x - KEYPAD_LEFT) // KEYPAD_PITCH)
|
||||
row = ((event.button.y - KEYPAD_TOP) // KEYPAD_PITCH)
|
||||
#print('rc= %d,%d' % (row,col))
|
||||
if not (0 <= row < 4): continue
|
||||
if not (0 <= col < 3): continue
|
||||
ch = '123456789x0y'[(row*3) + col]
|
||||
send_event(ch.encode('ascii'), True)
|
||||
ch = simdis.click_to_key(event.button.x, event.button.y)
|
||||
if ch is not None:
|
||||
send_event(ch.encode('ascii'), True)
|
||||
|
||||
if event.type == sdl2.SDL_MOUSEBUTTONUP:
|
||||
for ch in list(pressed):
|
||||
@ -498,44 +349,30 @@ def start():
|
||||
bare_metal.readable()
|
||||
continue
|
||||
|
||||
# Cheating: 1024 is size of OLED update, don't change.
|
||||
# Must be bigger than a full screen update.
|
||||
buf = r.read(1024*1000)
|
||||
if not buf:
|
||||
break
|
||||
|
||||
if r is oled_rx:
|
||||
buf = buf[-1024:]
|
||||
oled.render(window, buf)
|
||||
spriterenderer.render(oled.sprite)
|
||||
if r is display_rx:
|
||||
simdis.new_contents(buf)
|
||||
spriterenderer.render(simdis.sprite)
|
||||
window.refresh()
|
||||
elif r is led_rx:
|
||||
|
||||
# XXX 8+8 bits
|
||||
for c in buf:
|
||||
#print("LED change: 0x%02x" % c[0])
|
||||
|
||||
mask = (c >> 4) & 0xf
|
||||
lset = c & 0xf
|
||||
GEN_LED = 0x1
|
||||
SD_LED = 0x2
|
||||
USB_LED = 0x4
|
||||
|
||||
sd_active = usb_active = False
|
||||
|
||||
if mask & GEN_LED:
|
||||
genuine_state = ((mask & lset) == GEN_LED)
|
||||
if mask & SD_LED:
|
||||
sd_active = ((mask & lset) == SD_LED)
|
||||
if mask & USB_LED:
|
||||
usb_active = ((mask & lset) == USB_LED)
|
||||
active_set = (mask & lset)
|
||||
|
||||
#print("Genuine LED: %r" % genuine_state)
|
||||
spriterenderer.render(bg)
|
||||
spriterenderer.render(oled.sprite)
|
||||
spriterenderer.render(led_green if genuine_state else led_red)
|
||||
if sd_active:
|
||||
spriterenderer.render(led_sdcard)
|
||||
if usb_active:
|
||||
spriterenderer.render(led_usb)
|
||||
spriterenderer.render(simdis.sprite)
|
||||
simdis.draw_leds(spriterenderer, active_set)
|
||||
|
||||
window.refresh()
|
||||
else:
|
||||
|
||||
@ -228,6 +228,8 @@ def get_cpi_id():
|
||||
return 0x461 # STM32L496RG6
|
||||
if ('--mk4' in sys.argv):
|
||||
return 0x470 # STM32L4S5
|
||||
if ('--q1' in sys.argv):
|
||||
return 0x470 # STM32L4S5
|
||||
|
||||
#default mk4
|
||||
return 0x470 # STM32L4S5
|
||||
|
||||
@ -32,7 +32,7 @@ def get_header_value(fld_name):
|
||||
return b'\x18\x07\x11\x19S\x08\x00\x00'
|
||||
return 0
|
||||
|
||||
# default is latest hardware
|
||||
# default is Mk4 hardware
|
||||
hw_label = 'mk4'
|
||||
has_608 = True
|
||||
has_membrane = True
|
||||
@ -40,6 +40,8 @@ has_fatram = True
|
||||
has_se2 = True
|
||||
has_psram = True
|
||||
has_nfc = True
|
||||
has_qr = False
|
||||
num_sd_slots = 1
|
||||
|
||||
if '--mk1' in sys.argv:
|
||||
# doubt this works still
|
||||
@ -67,6 +69,11 @@ if '--mk3' in sys.argv:
|
||||
has_psram = False
|
||||
has_nfc = False
|
||||
|
||||
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
|
||||
|
||||