firmware/shared/calc.py
Peter D. Gray 6cb1173671 nit
2025-10-02 21:29:06 +02:00

109 lines
3.5 KiB
Python

# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# calc.py - Simple TOY calculator, before login. Not meant to be useful, just fun!
#
# Test with: ./simulator.py --q1 --eff -g --set calc=1
#
import utime, ngu, re
from utils import B2A, word_wrap
from ux_q1 import ux_input_text
async def login_repl():
from glob import dis
from pincodes import pa
NUM_LINES = 7 # 10 - title - 2 for prompt
# recognise 12-12 / 12- but also accept underscore, or even space in pin: "12 12"
re_prefix = re.compile(r'^(\d\d+)[-_]$')
re_pin = re.compile(r'^(\d\d+)[-_ ](\d\d+)$')
# in decreasing order of hazard...
# - find these with: import builtins; help(builtins)
blacklist = ['import', '__', 'exec', 'locals', 'globals', 'eval', 'input',
'getattr', 'setattr', 'delattr', 'open', 'execfile', 'compile' ]
lines = '''\
Example Commands:
>> 23 + 55 / 22
>> 1.020 * 45.88
>> sha256('some message')
>> cls # clear screen
>> help\
'''.split('\n')
state = dict()
state['sha256'] = lambda x: B2A(ngu.hash.sha256s(x))
state['sha512'] = lambda x: B2A(ngu.hash.sha512(x).digest())
state['ripemd'] = lambda x: B2A(ngu.hash.ripemd160(x))
state['rand'] = lambda x=32: B2A(ngu.random.bytes(x))
state['cls'] = lambda: lines.clear()
state['help'] = lambda: 'Commands: ' + (', '.join(state))
while 1:
dis.clear()
dis.text(0, 0, ' ECC Calculator ', invert=True)
for i,ln in enumerate(lines):
dis.text(0, i+1, ln, dark=ln.startswith('>> '))
dis.text(0, -2, ''*34, dark=True)
dis.text(0, -1, '>> ')
# prompt always a bottom of screen
ln = await ux_input_text('', max_len=34-3, force_xy=(3, 9),
prompt='', min_len=1, scan_ok=True, placeholder=None)
lines.append('>> ' + (ln or ''))
ans = None
try:
dis.busy_bar(1)
if ln is None :
# Cancel key - do nothing
ans = None
elif ln in ('help', 'cls', 'rand'):
# no need for () for these commands
ans = state[ln]()
elif pa.attempts_left and re_pin.match(ln) and (len(ln) <= 13):
# try login
m = re_pin.match(ln)
ln = m.group(1)+ '-' + m.group(2)
try:
pa.setup(ln)
ok = pa.login()
if ok: return
except RuntimeError as exc:
# I'm a brick and other stuff can happen here
# - especially AUTH_FAIL when pin is just wrong.
if exc.args[0] == 'AUTH_FAIL':
pa.attempts_left -= 1
ans = '%-7d # %d tries remain' % (eval(ln), pa.attempts_left)
else:
ans = 'Error: ' + repr(exc.args)
elif re_prefix.match(ln) and (len(ln) <= 7):
# show words
ans = pa.prefix_words(ln[:-1].encode())
else:
if any((b in ln) for b in blacklist):
ans = None
else:
ans = eval(ln, state.copy())
except Exception as exc:
lines.extend(word_wrap(str(exc), 34))
finally:
dis.busy_bar(0)
if ans is not None:
here = repr(ans) if not isinstance(ans, str) else ans
lines.extend(word_wrap(here, 34))
# trim lines to fit (scroll)
lines = lines[-NUM_LINES:]
# EOF