/ CircuitPython_RPN_Calculator / code.py
code.py
1 # SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 # pylint: disable=redefined-outer-name,no-self-use,broad-except,try-except-raise,too-many-branches,too-many-statements,unused-import 6 7 import gc 8 import time 9 10 from adafruit_display_text.label import Label 11 from adafruit_hid.keyboard import Keyboard 12 from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS 13 from jepler_udecimal import Decimal, getcontext, localcontext 14 import jepler_udecimal.utrig # Needed for trig functions in Decimal 15 import board 16 import digitalio 17 import displayio 18 import framebufferio 19 import microcontroller 20 import sharpdisplay 21 import terminalio 22 23 try: 24 import usb_hid 25 except ImportError: 26 usb_hid = None 27 28 # Initialize the display, cleaning up after a display from the previous 29 # run if necessary 30 displayio.release_displays() 31 framebuffer = sharpdisplay.SharpMemoryFramebuffer(board.SPI(), board.RX, 400, 240) 32 display = framebufferio.FramebufferDisplay(framebuffer, auto_refresh=False) 33 34 def extraprec(add=8, num=0, den=1): 35 def inner(fn): 36 def wrapper(*args, **kw): 37 with localcontext() as ctx: 38 ctx.prec = ctx.prec + add + (ctx.prec * num + den - 1) // den 39 result = fn(*args, **kw) 40 return +result 41 return wrapper 42 return inner 43 44 class AngleConvert: 45 def __init__(self): 46 self.state = 0 47 48 def next_state(self): 49 self.state = (self.state + 1) % 3 50 51 def __str__(self): 52 return "DRG"[self.state] 53 54 @property 55 def factor(self): 56 return [360, None, 400][self.state] 57 58 def from_user(self, x): 59 factor = self.factor 60 if factor is None: 61 return x 62 x = x.remainder_near(factor) 63 pi_4 = Decimal("1.0").atan() 64 return x * pi_4 * 8 / factor 65 66 def to_user(self, x): 67 factor = self.factor 68 if factor is None: 69 return x 70 pi_4 = Decimal("1.0").atan() 71 return x * factor / pi_4 / 8 72 73 @extraprec(num=1) 74 def cos(self, x): 75 return self.from_user(x).cos() 76 77 @extraprec(num=1) 78 def sin(self, x): 79 return self.from_user(x).sin() 80 81 @extraprec(num=1) 82 def tan(self, x): 83 return self.from_user(x).tan() 84 85 @extraprec(num=1) 86 def acos(self, x): 87 return self.to_user(x.acos()) 88 89 @extraprec(num=1) 90 def asin(self, x): 91 return self.to_user(x.asin()) 92 93 @extraprec(num=1) 94 def atan(self, x): 95 return self.to_user(x.atan()) 96 97 getcontext().prec = 14 98 getcontext().Emax = 99 99 getcontext().Emin = -99 100 101 def get_pin(x): 102 if isinstance(x, microcontroller.Pin): 103 return digitalio.DigitalInOut(x) 104 return x 105 106 class MatrixKeypadBase: 107 def __init__(self, row_pins, col_pins): 108 self.row_pins = [get_pin(p) for p in row_pins] 109 self.col_pins = [get_pin(p) for p in col_pins] 110 self.old_state = set() 111 self.state = set() 112 113 for r in self.row_pins: 114 r.switch_to_input(digitalio.Pull.UP) 115 for c in self.col_pins: 116 c.switch_to_output(False) 117 118 def scan(self): 119 self.old_state = self.state 120 state = set() 121 for c, cp in enumerate(self.col_pins): 122 cp.switch_to_output(False) 123 for r, rp in enumerate(self.row_pins): 124 if not rp.value: 125 state.add((r, c)) 126 cp.switch_to_input() 127 self.state = state 128 return state 129 130 def rising(self): 131 old_state = self.old_state 132 new_state = self.state 133 134 return new_state - old_state 135 136 class LayerSelect: 137 def __init__(self, idx=1, next_layer=None): 138 self.idx = idx 139 self.next_layer = next_layer or self 140 141 LL0 = LayerSelect(0) 142 LL1 = LayerSelect(1) 143 LS1 = LayerSelect(1, LL0) 144 145 class MatrixKeypad: 146 def __init__(self, row_pins, col_pins, layers): 147 self.base = MatrixKeypadBase(row_pins, col_pins) 148 self.layers = layers 149 self.layer = LL0 150 self.pending = [] 151 152 def getch(self): 153 if not self.pending: 154 self.base.scan() 155 for r, c in self.base.rising(): 156 op = self.layers[self.layer.idx][r][c] 157 if isinstance(op, LayerSelect): 158 self.layer = op 159 else: 160 self.pending.extend(op) 161 self.layer = self.layer.next_layer 162 163 if self.pending: 164 return self.pending.pop(0) 165 166 return None 167 168 col_pins = (board.D10, board.D9, board.D6, board.TX) 169 row_pins = (board.A0, board.A1, board.A2, board.A3, board.A4, board.A5) 170 171 BS = '\x7f' 172 CR = '\n' 173 174 layers = ( 175 ( 176 ('^', 'l', 'r', LS1), 177 ('s', 'c', 't', '/'), 178 ('7', '8', '9', '*'), 179 ('4', '5', '6', '-'), 180 ('1', '2', '3', '+'), 181 ('0', '.', BS, CR) 182 ), 183 184 ( 185 ('v', 'L', 'R', LL0), 186 ('S', 'C', 'T', 'N'), 187 ( '', '', '', ''), 188 ( '', '', '', 'n'), 189 ( '', '', '', ''), 190 ('=', '@', BS, '~') 191 ), 192 ) 193 194 195 class Impl: 196 def __init__(self): 197 # incoming keypad 198 self.keypad = MatrixKeypad(row_pins, col_pins, layers) 199 200 # outgoing keypresses 201 self.keyboard = None 202 self.keyboard_layout = None 203 204 g = displayio.Group() 205 206 self.labels = labels = [] 207 labels.append(Label(terminalio.FONT, scale=2, color=0)) 208 labels.append(Label(terminalio.FONT, scale=3, color=0)) 209 labels.append(Label(terminalio.FONT, scale=3, color=0)) 210 labels.append(Label(terminalio.FONT, scale=3, color=0)) 211 labels.append(Label(terminalio.FONT, scale=3, color=0)) 212 labels.append(Label(terminalio.FONT, scale=3, color=0)) 213 214 for li in labels: 215 g.append(li) 216 217 bitmap = displayio.Bitmap((display.width + 126)//127, (display.height + 126)//127, 1) 218 palette = displayio.Palette(1) 219 palette[0] = 0xffffff 220 221 tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette) 222 bg = displayio.Group(scale=127) 223 bg.append(tile_grid) 224 225 g.insert(0, bg) 226 227 display.show(g) 228 229 def getch(self): 230 while True: 231 time.sleep(.02) 232 c = self.keypad.getch() 233 if c is not None: 234 return c 235 236 def setline(self, i, text): 237 li = self.labels[i] 238 text = text[:31] or " " 239 if text == li.text: 240 return 241 li.text = text 242 li.anchor_point = (0,0) 243 li.anchored_position = (1, max(1, 41 * i - 7) + 6) 244 245 def refresh(self): 246 pass 247 248 def paste(self, text): 249 if self.keyboard is None: 250 if usb_hid: 251 self.keyboard = Keyboard(usb_hid.devices) 252 self.keyboard_layout = KeyboardLayoutUS(self.keyboard) 253 else: 254 return 255 256 if self.keyboard_layout is None: 257 raise ValueError("USB HID not available") 258 text = str(text) 259 self.keyboard_layout.write(text) 260 raise RuntimeError("Pasted") 261 262 def start_redraw(self): 263 display.auto_refresh = False 264 265 def end_redraw(self): 266 display.auto_refresh = True 267 268 def end(self): 269 pass 270 impl = Impl() 271 272 stack = [] 273 entry = [] 274 275 def do_op(arity, fun): 276 if arity > len(stack): 277 return "underflow" 278 res = fun(*stack[-arity:][::-1]) 279 del stack[-arity:] 280 if isinstance(res, list): 281 stack.extend(res) 282 elif res is not None: 283 stack.append(res) 284 return None 285 angleconvert = AngleConvert() 286 287 def roll(): 288 stack[:] = stack[1:] + stack[:1] 289 290 def rroll(): 291 stack[:] = stack[-1:] + stack[:-1] 292 293 def swap(): 294 stack[-2:] = [stack[-1], stack[-2]] 295 296 ops = { 297 '\'': (1, lambda x: -x), 298 '\\': (2, lambda x, y: x/y), 299 '#': (2, lambda x, y: y**(1/x)), 300 '*': (2, lambda x, y: y*x), 301 '+': (2, lambda x, y: y+x), 302 '-': (2, lambda x, y: y-x), 303 '/': (2, lambda x, y: y/x), 304 '^': (2, lambda x, y: y**x), 305 'v': (2, lambda x, y: y**(1/x)), 306 '_': (2, lambda x, y: x-y), 307 '@': angleconvert.next_state, 308 'C': (1, angleconvert.acos), 309 'c': (1, angleconvert.cos), 310 'L': (1, Decimal.exp), 311 'l': (1, Decimal.ln), 312 'q': (1, lambda x: x**.5), 313 'r': roll, 314 'R': rroll, 315 'S': (1, angleconvert.asin), 316 's': (1, angleconvert.sin), 317 '~': swap, 318 'T': (1, angleconvert.atan), 319 't': (1, angleconvert.tan), 320 'n': (1, lambda x: -x), 321 'N': (1, lambda x: 1/x), 322 '=': (1, impl.paste) 323 } 324 325 def pstack(msg): 326 impl.setline(0, f'[{angleconvert}] {msg}') 327 328 for i, reg in enumerate("TZYX"): 329 if len(stack) > 3-i: 330 val = stack[-4+i] 331 else: 332 val = "" 333 impl.setline(1+i, f"{reg} {val}") 334 335 def loop(): 336 impl.start_redraw() 337 pstack(f'{gc.mem_free()} RPN bytes free') 338 impl.setline(5, "> " + "".join(entry) + "_") 339 impl.refresh() 340 impl.end_redraw() 341 342 while True: 343 do_pstack = False 344 do_pentry = False 345 message = '' 346 347 348 c = impl.getch() 349 if c in '\x7f\x08': 350 if entry: 351 entry.pop() 352 do_pentry = True 353 elif stack: 354 stack.pop() 355 do_pstack = True 356 if c == '\x1b': 357 del entry[:] 358 do_pentry = True 359 elif c in '0123456789.eE': 360 if c == '.' and '.' in entry: 361 c = 'e' 362 entry.append(c) 363 do_pentry = True 364 elif c == '\x04': 365 break 366 elif c in ' \n': 367 if entry: 368 try: 369 stack.append(Decimal("".join(entry))) 370 except Exception as e: 371 message = str(e) 372 del entry[:] 373 elif c == '\n' and stack: 374 stack.append(stack[-1]) 375 do_pstack = True 376 elif c in ops: 377 if entry: 378 try: 379 stack.append(Decimal("".join(entry))) 380 except Exception as e: 381 message = str(e) 382 del entry[:] 383 op = ops.get(c) 384 try: 385 if callable(op): 386 message = op() or '' 387 else: 388 message = do_op(*op) or '' 389 except (KeyboardInterrupt, SystemExit): 390 raise 391 except Exception as e: 392 message = str(e) 393 do_pstack = True 394 395 impl.start_redraw() 396 397 if do_pstack: 398 pstack(message) 399 do_pentry = True 400 401 if do_pentry: 402 impl.setline(5, "> " + "".join(entry) + "_") 403 404 if do_pentry or do_pstack: 405 impl.refresh() 406 407 impl.end_redraw() 408 409 try: 410 loop() 411 finally: 412 impl.end()