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

Lines changed: 109761 additions & 0 deletions
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()
6.69 KB
Binary file not shown.
Binary file not shown.
1.84 MB
Binary file not shown.
Binary file not shown.
2.07 MB
Binary file not shown.
Binary file not shown.
675 KB
Binary file not shown.

0 commit comments

Comments
 (0)