Adafruit_Learning_System_Gu.../CircuitPython_RPN_Calculator/code.py
2020-10-16 14:30:22 -05:00

410 lines
10 KiB
Python

# pylint: disable=redefined-outer-name,no-self-use,broad-except,try-except-raise,too-many-branches,too-many-statements,unused-import
import gc
import time
from adafruit_display_text.label import Label
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
from jepler_udecimal import Decimal, getcontext, localcontext
import jepler_udecimal.utrig
import board
import digitalio
import displayio
import framebufferio
import microcontroller
import sharpdisplay
import supervisor
import terminalio
try:
import usb_hid
except ImportError:
usb_hid = None
# Initialize the display, cleaning up after a display from the previous
# run if necessary
displayio.release_displays()
framebuffer = sharpdisplay.SharpMemoryFramebuffer(board.SPI(), board.RX, 400, 240)
display = framebufferio.FramebufferDisplay(framebuffer, auto_refresh=False)
def extraprec(add=8, num=0, den=1):
def inner(fn):
def wrapper(*args, **kw):
with localcontext() as ctx:
ctx.prec = ctx.prec + add + (ctx.prec * num + den - 1) // den
result = fn(*args, **kw)
return +result
return wrapper
return inner
class AngleConvert:
def __init__(self):
self.state = 0
def next_state(self):
self.state = (self.state + 1) % 3
def __str__(self):
return "DRG"[self.state]
@property
def factor(self):
return [360, None, 400][self.state]
def from_user(self, x):
factor = self.factor
if factor is None:
return x
x = x.remainder_near(factor)
pi_4 = Decimal("1.0").atan()
return x * pi_4 * 8 / factor
def to_user(self, x):
factor = self.factor
if factor is None:
return x
pi_4 = Decimal("1.0").atan()
return x * factor / pi_4 / 8
@extraprec(num=1)
def cos(self, x):
return self.from_user(x).cos()
@extraprec(num=1)
def sin(self, x):
return self.from_user(x).sin()
@extraprec(num=1)
def tan(self, x):
return self.from_user(x).tan()
@extraprec(num=1)
def acos(self, x):
return self.to_user(x.acos())
@extraprec(num=1)
def asin(self, x):
return self.to_user(x.asin())
@extraprec(num=1)
def atan(self, x):
return self.to_user(x.atan())
getcontext().prec = 14
getcontext().Emax = 99
getcontext().Emin = -99
def get_pin(x):
if isinstance(x, microcontroller.Pin):
return digitalio.DigitalInOut(x)
return x
class MatrixKeypadBase:
def __init__(self, row_pins, col_pins):
self.row_pins = [get_pin(p) for p in row_pins]
self.col_pins = [get_pin(p) for p in col_pins]
self.old_state = set()
self.state = set()
for r in self.row_pins:
r.switch_to_input(digitalio.Pull.UP)
for c in self.col_pins:
c.switch_to_output(False)
def scan(self):
self.old_state = self.state
state = set()
for c, cp in enumerate(self.col_pins):
cp.switch_to_output(False)
for r, rp in enumerate(self.row_pins):
if not rp.value:
state.add((r, c))
cp.switch_to_input()
self.state = state
return state
def rising(self):
old_state = self.old_state
new_state = self.state
return new_state - old_state
class LayerSelect:
def __init__(self, idx=1, next_layer=None):
self.idx = idx
self.next_layer = next_layer or self
LL0 = LayerSelect(0)
LL1 = LayerSelect(1)
LS1 = LayerSelect(1, LL0)
class MatrixKeypad:
def __init__(self, row_pins, col_pins, layers):
self.base = MatrixKeypadBase(row_pins, col_pins)
self.layers = layers
self.layer = LL0
self.pending = []
def getch(self):
if not self.pending:
self.base.scan()
for r, c in self.base.rising():
op = self.layers[self.layer.idx][r][c]
if isinstance(op, LayerSelect):
self.layer = op
else:
self.pending.extend(op)
self.layer = self.layer.next_layer
if self.pending:
return self.pending.pop(0)
return None
col_pins = (board.D10, board.D9, board.D6, board.TX)
row_pins = (board.A0, board.A1, board.A2, board.A3, board.A4, board.A5)
BS = '\x7f'
CR = '\n'
layers = (
(
('^', 'l', 'r', LS1),
('s', 'c', 't', '/'),
('7', '8', '9', '*'),
('4', '5', '6', '-'),
('1', '2', '3', '+'),
('0', '.', BS, CR)
),
(
('v', 'L', 'R', LL0),
('S', 'C', 'T', 'N'),
( '', '', '', ''),
( '', '', '', 'n'),
( '', '', '', ''),
('=', '@', BS, '~')
),
)
class Impl:
def __init__(self):
# incoming keypad
self.keypad = MatrixKeypad(row_pins, col_pins, layers)
# outgoing keypresses
self.keyboard = None
self.keyboard_layout = None
g = displayio.Group(max_size=7)
self.labels = labels = []
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=2, color=0))
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=3, color=0))
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=3, color=0))
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=3, color=0))
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=3, color=0))
labels.append(Label(terminalio.FONT, max_glyphs=32, scale=3, color=0))
for li in labels:
g.append(li)
bitmap = displayio.Bitmap((display.width + 126)//127, (display.height + 126)//127, 1)
palette = displayio.Palette(1)
palette[0] = 0xffffff
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
bg = displayio.Group(scale=127)
bg.append(tile_grid)
g.insert(0, bg)
display.show(g)
def getch(self):
while True:
time.sleep(.02)
c = self.keypad.getch()
if c is not None:
return c
def setline(self, i, text):
li = self.labels[i]
text = text[:31] or " "
if text == li.text:
return
li.text = text
li.anchor_point = (0,0)
li.anchored_position = (1, max(1, 41 * i - 7) + 6)
def refresh(self):
pass
def paste(self, text):
if self.keyboard is None:
if usb_hid:
self.keyboard = Keyboard(usb_hid.devices)
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
else:
return
if self.keyboard_layout is None:
raise ValueError("USB HID not available")
text = str(text)
self.keyboard_layout.write(text)
raise RuntimeError("Pasted")
def start_redraw(self):
display.auto_refresh = False
def end_redraw(self):
display.auto_refresh = True
def end(self):
pass
impl = Impl()
stack = []
entry = []
def do_op(arity, fun):
if arity > len(stack):
return "underflow"
res = fun(*stack[-arity:][::-1])
del stack[-arity:]
if isinstance(res, list):
stack.extend(res)
elif res is not None:
stack.append(res)
return None
angleconvert = AngleConvert()
def roll():
stack[:] = stack[1:] + stack[:1]
def rroll():
stack[:] = stack[-1:] + stack[:-1]
def swap():
stack[-2:] = [stack[-1], stack[-2]]
ops = {
'\'': (1, lambda x: -x),
'\\': (2, lambda x, y: x/y),
'#': (2, lambda x, y: y**(1/x)),
'*': (2, lambda x, y: y*x),
'+': (2, lambda x, y: y+x),
'-': (2, lambda x, y: y-x),
'/': (2, lambda x, y: y/x),
'^': (2, lambda x, y: y**x),
'v': (2, lambda x, y: y**(1/x)),
'_': (2, lambda x, y: x-y),
'@': angleconvert.next_state,
'C': (1, angleconvert.acos),
'c': (1, angleconvert.cos),
'L': (1, Decimal.exp),
'l': (1, Decimal.ln),
'q': (1, lambda x: x**.5),
'r': roll,
'R': rroll,
'S': (1, angleconvert.asin),
's': (1, angleconvert.sin),
'~': swap,
'T': (1, angleconvert.atan),
't': (1, angleconvert.tan),
'n': (1, lambda x: -x),
'N': (1, lambda x: 1/x),
'=': (1, impl.paste)
}
def pstack(msg):
impl.setline(0, f'[{angleconvert}] {msg}')
for i, reg in enumerate("TZYX"):
if len(stack) > 3-i:
val = stack[-4+i]
else:
val = ""
impl.setline(1+i, f"{reg} {val}")
def loop():
impl.start_redraw()
pstack(f'{gc.mem_free()} RPN bytes free')
impl.setline(5, "> " + "".join(entry) + "_")
impl.refresh()
impl.end_redraw()
while True:
do_pstack = False
do_pentry = False
message = ''
c = impl.getch()
if c in '\x7f\x08':
if entry:
entry.pop()
do_pentry = True
elif stack:
stack.pop()
do_pstack = True
if c == '\x1b':
del entry[:]
do_pentry = True
elif c in '0123456789.eE':
if c == '.' and '.' in entry:
c = 'e'
entry.append(c)
do_pentry = True
elif c == '\x04':
break
elif c in ' \n':
if entry:
try:
stack.append(Decimal("".join(entry)))
except Exception as e:
message = str(e)
del entry[:]
elif c == '\n' and stack:
stack.append(stack[-1])
do_pstack = True
elif c in ops:
if entry:
try:
stack.append(Decimal("".join(entry)))
except Exception as e:
message = str(e)
del entry[:]
op = ops.get(c)
try:
if callable(op):
message = op() or ''
else:
message = do_op(*op) or ''
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
message = str(e)
do_pstack = True
impl.start_redraw()
if do_pstack:
pstack(message)
do_pentry = True
if do_pentry:
impl.setline(5, "> " + "".join(entry) + "_")
if do_pentry or do_pstack:
impl.refresh()
impl.end_redraw()
try:
loop()
finally:
impl.end()