249 lines
6.2 KiB
Python
249 lines
6.2 KiB
Python
# SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import board
|
|
import displayio
|
|
import keypad
|
|
import adafruit_displayio_sh1107
|
|
from adafruit_hid.keyboard import Keyboard
|
|
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
|
|
from adafruit_display_text import label
|
|
from adafruit_bitmap_font import bitmap_font
|
|
|
|
try:
|
|
import usb_hid
|
|
except ImportError:
|
|
usb_hid = None
|
|
|
|
K_SQ = "√"
|
|
K_CL = "<clear>"
|
|
K_FN = "<fn>"
|
|
K_PA = "<paste>"
|
|
|
|
KEYMAP0 = [
|
|
K_CL, K_FN, '%', '/',
|
|
'7', '8', '9', '*',
|
|
'4', '5', '6', '-',
|
|
'1', '2', '3', '+',
|
|
'0', '.', K_SQ, '='
|
|
]
|
|
|
|
KEYMAP1 = [
|
|
K_CL, None, '', '',
|
|
'', '', '', '',
|
|
'', '', '', '',
|
|
'', '', '', '',
|
|
'', '', '', K_PA,
|
|
]
|
|
|
|
keymaps = {
|
|
0: KEYMAP0,
|
|
1: KEYMAP1,
|
|
}
|
|
|
|
# pylint: disable=redefined-outer-name
|
|
def lookup(layer, key_number):
|
|
while layer >= 0:
|
|
key = keymaps[layer][key_number]
|
|
if key is not None:
|
|
return key
|
|
layer -= 1
|
|
return None
|
|
|
|
displayio.release_displays()
|
|
# oled_reset = board.D9
|
|
|
|
# Use for I2C
|
|
i2c = board.I2C() # uses board.SCL and board.SDA
|
|
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
|
|
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
|
|
|
|
# SH1107 is vertically oriented 64x128
|
|
WIDTH = 128
|
|
HEIGHT = 64
|
|
|
|
display = adafruit_displayio_sh1107.SH1107(display_bus, width=WIDTH, height=HEIGHT, rotation=180)
|
|
display.auto_refresh = False
|
|
|
|
font = bitmap_font.load_font("/digit-16px.pcf")
|
|
text_area = label.Label(font, text=" ", line_spacing=0.95)
|
|
text_area.y = 8
|
|
display.show(text_area)
|
|
|
|
N = float
|
|
|
|
unary = {
|
|
K_SQ: lambda a: a**.5,
|
|
}
|
|
|
|
binary = {
|
|
'+': (lambda a, b: a+b, lambda a, b: a * (1+b/100)),
|
|
'-': (lambda a, b: a-b, lambda a, b: a * (1-b/100)),
|
|
'*': (lambda a, b: a*b, lambda a, b: a * (b/100)),
|
|
'/': (lambda a, b: a/b, lambda a, b: a / (b/100)),
|
|
}
|
|
|
|
class Calculator:
|
|
def __init__(self):
|
|
self._number1 = N("0")
|
|
self._number2 = None
|
|
self.trail = ["Ready."]
|
|
self.entry = ""
|
|
self.op = None
|
|
self.keyboard = None
|
|
self.keyboard_layout = None
|
|
|
|
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:
|
|
self.add_trail("No USB")
|
|
else:
|
|
text = str(text)
|
|
self.keyboard_layout.write(text)
|
|
|
|
self.add_trail(f"Pasted {text}")
|
|
|
|
|
|
def add_trail(self, msg):
|
|
self.trail = self.trail[-3:] + [str(msg).upper()]
|
|
|
|
@property
|
|
def number1(self):
|
|
return self._number1
|
|
|
|
@number1.setter
|
|
def number1(self, number):
|
|
self._number1 = number
|
|
self.add_trail(number)
|
|
|
|
@property
|
|
def number2(self):
|
|
if self.entry == "":
|
|
if self._number2 is not None:
|
|
return self._number2
|
|
return None
|
|
return N(self.entry)
|
|
|
|
@number2.setter
|
|
def number2(self, number):
|
|
self._number2 = number
|
|
self.entry = ''
|
|
|
|
def clear(self):
|
|
self.number1 = N("0")
|
|
|
|
def clear_entry(self):
|
|
self.number2 = None
|
|
|
|
def key_pressed(self, k): # pylint: disable=too-many-branches
|
|
if k == K_CL:
|
|
if self.entry:
|
|
self.entry = self.entry[:-1]
|
|
elif self.op:
|
|
print("clear op")
|
|
self.op = None
|
|
elif self.number2 is None:
|
|
self.clear()
|
|
else:
|
|
print("clear entry - op = ", self.op)
|
|
self.clear_entry()
|
|
|
|
if len(k) == 1 and k in "0123456789":
|
|
self.entry = self.entry + k
|
|
|
|
if k == "." and not "." in self.entry:
|
|
if self.entry == "":
|
|
self.entry = "0"
|
|
self.entry = self.entry + k
|
|
|
|
if k == K_PA:
|
|
if self.number2 is not None:
|
|
self.paste(self.number2)
|
|
else:
|
|
self.paste(self.number1)
|
|
|
|
if k == "=":
|
|
self.do_binary_op(0)
|
|
|
|
if k == "%":
|
|
self.do_binary_op(1)
|
|
|
|
op = unary.get(k)
|
|
if op:
|
|
self.do_unary_op(op)
|
|
|
|
if k in binary:
|
|
if self.number2 is not None:
|
|
if self.op:
|
|
self.do_binary_op(0)
|
|
else:
|
|
self.number1 = self.number2
|
|
self.clear_entry()
|
|
self.op = k
|
|
|
|
def do_unary_op(self, op):
|
|
if self.number2 is not None:
|
|
self.number2 = op(self.number2)
|
|
else:
|
|
self.number1 = op(self.number1)
|
|
|
|
def do_binary_op(self, i):
|
|
if self.op and self.number2 is not None:
|
|
op = binary[self.op][i]
|
|
self.op = None
|
|
self.number1 = op(self.number1, self.number2)
|
|
self.clear_entry()
|
|
|
|
def show(self):
|
|
rows = [""] * 4
|
|
trail = self.trail
|
|
if len(trail) > 0:
|
|
rows[2] = trail[-1]
|
|
if len(trail) > 1:
|
|
rows[1] = trail[-2]
|
|
if len(trail) > 2:
|
|
rows[0] = trail[-3]
|
|
|
|
entry_or_number = self.entry or self.number2
|
|
cursor = ' :' if (self.number2 is None or self.entry != "") else ""
|
|
op = self.op or ''
|
|
op = 'd' if op == '/' else op
|
|
rows[-1] = f"{op}{entry_or_number or ''}{cursor}"
|
|
for r in rows:
|
|
print(r)
|
|
text_area.text = "\n".join(rows)
|
|
|
|
km=keypad.KeyMatrix(
|
|
row_pins=(board.A2, board.A1, board.A3, board.A0, board.D0),
|
|
column_pins=(board.D25, board.D11, board.D12, board.D24))
|
|
|
|
calculator=Calculator()
|
|
calculator.show()
|
|
|
|
layer = 0
|
|
while True:
|
|
ev = km.events.get()
|
|
if ev:
|
|
key = lookup(layer, ev.key_number)
|
|
if ev.pressed:
|
|
if key == K_FN:
|
|
layer = 1
|
|
try:
|
|
calculator.key_pressed(key)
|
|
except Exception as e: # pylint: disable=broad-except
|
|
calculator.add_trail(e)
|
|
calculator.show()
|
|
|
|
elif ev.released:
|
|
if key == K_FN:
|
|
layer = 0
|
|
|
|
else:
|
|
display.refresh()
|