Skip to content

Commit d5aeaa3

Browse files
authored
Merge pull request #1596 from jepler/circuitpython-keypad-calculator
CircuitPython Custom Keypad Calculator
2 parents f5528a9 + fdb9174 commit d5aeaa3

26 files changed

+109761
-0
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import board
2+
import displayio
3+
import keypad
4+
import adafruit_displayio_sh1107
5+
from adafruit_hid.keyboard import Keyboard
6+
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
7+
from adafruit_display_text import label
8+
from adafruit_bitmap_font import bitmap_font
9+
10+
try:
11+
import usb_hid
12+
except ImportError:
13+
usb_hid = None
14+
15+
K_SQ = "√"
16+
K_CL = "<clear>"
17+
K_FN = "<fn>"
18+
K_PA = "<paste>"
19+
20+
KEYMAP0 = [
21+
K_CL, K_FN, '%', '/',
22+
'7', '8', '9', '*',
23+
'4', '5', '6', '-',
24+
'1', '2', '3', '+',
25+
'0', '.', K_SQ, '='
26+
]
27+
28+
KEYMAP1 = [
29+
K_CL, None, '', '',
30+
'', '', '', '',
31+
'', '', '', '',
32+
'', '', '', '',
33+
'', '', '', K_PA,
34+
]
35+
36+
keymaps = {
37+
0: KEYMAP0,
38+
1: KEYMAP1,
39+
}
40+
41+
# pylint: disable=redefined-outer-name
42+
def lookup(layer, key_number):
43+
while layer >= 0:
44+
key = keymaps[layer][key_number]
45+
if key is not None:
46+
return key
47+
layer -= 1
48+
return None
49+
50+
displayio.release_displays()
51+
# oled_reset = board.D9
52+
53+
# Use for I2C
54+
i2c = board.I2C()
55+
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
56+
57+
# SH1107 is vertically oriented 64x128
58+
WIDTH = 128
59+
HEIGHT = 64
60+
61+
display = adafruit_displayio_sh1107.SH1107(display_bus, width=WIDTH, height=HEIGHT, rotation=180)
62+
display.auto_refresh = False
63+
64+
font = bitmap_font.load_font("/digit-16px.pcf")
65+
text_area = label.Label(font, text=" ", line_spacing=0.95)
66+
text_area.y = 8
67+
display.show(text_area)
68+
69+
N = float
70+
71+
unary = {
72+
K_SQ: lambda a: a**.5,
73+
}
74+
75+
binary = {
76+
'+': (lambda a, b: a+b, lambda a, b: a * (1+b/100)),
77+
'-': (lambda a, b: a-b, lambda a, b: a * (1-b/100)),
78+
'*': (lambda a, b: a*b, lambda a, b: a * (b/100)),
79+
'/': (lambda a, b: a/b, lambda a, b: a / (b/100)),
80+
}
81+
82+
class Calculator:
83+
def __init__(self):
84+
self._number1 = N("0")
85+
self._number2 = None
86+
self.trail = ["Ready."]
87+
self.entry = ""
88+
self.op = None
89+
self.keyboard = None
90+
self.keyboard_layout = None
91+
92+
def paste(self, text):
93+
if self.keyboard is None:
94+
if usb_hid:
95+
self.keyboard = Keyboard(usb_hid.devices)
96+
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
97+
else:
98+
return
99+
100+
if self.keyboard_layout is None:
101+
self.add_trail("No USB")
102+
else:
103+
text = str(text)
104+
self.keyboard_layout.write(text)
105+
106+
self.add_trail(f"Pasted {text}")
107+
108+
109+
def add_trail(self, msg):
110+
self.trail = self.trail[-3:] + [str(msg).upper()]
111+
112+
@property
113+
def number1(self):
114+
return self._number1
115+
116+
@number1.setter
117+
def number1(self, number):
118+
self._number1 = number
119+
self.add_trail(number)
120+
121+
@property
122+
def number2(self):
123+
if self.entry == "":
124+
if self._number2 is not None:
125+
return self._number2
126+
return None
127+
return N(self.entry)
128+
129+
@number2.setter
130+
def number2(self, number):
131+
self._number2 = number
132+
self.entry = ''
133+
134+
def clear(self):
135+
self.number1 = N("0")
136+
137+
def clear_entry(self):
138+
self.number2 = None
139+
140+
def key_pressed(self, k): # pylint: disable=too-many-branches
141+
if k == K_CL:
142+
if self.entry:
143+
self.entry = self.entry[:-1]
144+
elif self.op:
145+
print("clear op")
146+
self.op = None
147+
elif self.number2 is None:
148+
self.clear()
149+
else:
150+
print("clear entry - op = ", self.op)
151+
self.clear_entry()
152+
153+
if len(k) == 1 and k in "0123456789":
154+
self.entry = self.entry + k
155+
156+
if k == "." and not "." in self.entry:
157+
if self.entry == "":
158+
self.entry = "0"
159+
self.entry = self.entry + k
160+
161+
if k == K_PA:
162+
if self.number2 is not None:
163+
self.paste(self.number2)
164+
else:
165+
self.paste(self.number1)
166+
167+
if k == "=":
168+
self.do_binary_op(0)
169+
170+
if k == "%":
171+
self.do_binary_op(1)
172+
173+
op = unary.get(k)
174+
if op:
175+
self.do_unary_op(op)
176+
177+
if k in binary:
178+
if self.number2 is not None:
179+
if self.op:
180+
self.do_binary_op(0)
181+
else:
182+
self.number1 = self.number2
183+
self.clear_entry()
184+
self.op = k
185+
186+
def do_unary_op(self, op):
187+
if self.number2 is not None:
188+
self.number2 = op(self.number2)
189+
else:
190+
self.number1 = op(self.number1)
191+
192+
def do_binary_op(self, i):
193+
if self.op and self.number2 is not None:
194+
op = binary[self.op][i]
195+
self.op = None
196+
self.number1 = op(self.number1, self.number2)
197+
self.clear_entry()
198+
199+
def show(self):
200+
rows = [""] * 4
201+
trail = self.trail
202+
if len(trail) > 0:
203+
rows[2] = trail[-1]
204+
if len(trail) > 1:
205+
rows[1] = trail[-2]
206+
if len(trail) > 2:
207+
rows[0] = trail[-3]
208+
209+
entry_or_number = self.entry or self.number2
210+
cursor = ' :' if (self.number2 is None or self.entry != "") else ""
211+
op = self.op or ''
212+
op = 'd' if op == '/' else op
213+
rows[-1] = f"{op}{entry_or_number or ''}{cursor}"
214+
for r in rows:
215+
print(r)
216+
text_area.text = "\n".join(rows)
217+
218+
km=keypad.KeyMatrix(
219+
row_pins=(board.A2, board.A1, board.A3, board.A0, board.D0),
220+
column_pins=(board.D25, board.D11, board.D12, board.D24))
221+
222+
calculator=Calculator()
223+
calculator.show()
224+
225+
layer = 0
226+
while True:
227+
ev = km.events.get()
228+
if ev:
229+
key = lookup(layer, ev.key_number)
230+
if ev.pressed:
231+
if key == K_FN:
232+
layer = 1
233+
try:
234+
calculator.key_pressed(key)
235+
except Exception as e: # pylint: disable=broad-except
236+
calculator.add_trail(e)
237+
calculator.show()
238+
239+
elif ev.released:
240+
if key == K_FN:
241+
layer = 0
242+
243+
else:
244+
display.refresh()
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)