2 | 2 |
from digitalio import DigitalInOut, Pull
|
3 | 3 |
|
4 | 4 |
|
5 | |
def range_rev(start, stop):
|
6 | |
return range(stop - 1, start - 1, -1)
|
7 | |
|
8 | |
|
9 | |
def mapping_up_down(cols, rows):
|
10 | |
'''
|
11 | |
Maps keys according to the following diagram:
|
12 | |
|
13 | |
1 2 3
|
14 | |
+------
|
15 | |
1| A A A
|
16 | |
2| A A A
|
17 | |
|
|
18 | |
1| B B B
|
19 | |
2| B B B
|
20 | |
'''
|
21 | |
|
22 | |
coord_mapping = list(range(2 * cols * rows))
|
23 | |
return coord_mapping
|
24 | |
|
25 | |
|
26 | |
def mapping_up_down_mirrored(cols, rows):
|
27 | |
'''
|
28 | |
Maps keys according to the following diagram:
|
29 | |
|
30 | |
1 2 3
|
31 | |
+------
|
32 | |
1| A A A
|
33 | |
2| A A A
|
34 | |
|
|
35 | |
2| B B B
|
36 | |
1| B B B
|
37 | |
'''
|
38 | |
|
39 | |
coord_mapping = []
|
40 | |
coord_mapping.extend(range(cols * rows))
|
41 | |
coord_mapping.extend(range_rev(cols * rows, 2 * cols * rows))
|
42 | |
return coord_mapping
|
43 | |
|
44 | |
|
45 | 5 |
def mapping_left_right(cols, rows):
|
46 | |
'''
|
|
6 |
"""
|
47 | 7 |
Maps keys according to the following diagram:
|
48 | 8 |
|
49 | 9 |
1 2 3 1 2 3
|
50 | 10 |
+-------------
|
51 | 11 |
1| A A A B B B
|
52 | 12 |
2| A A A B B B
|
53 | |
'''
|
|
13 |
"""
|
54 | 14 |
|
55 | 15 |
size = cols * rows
|
56 | 16 |
coord_mapping = []
|
|
61 | 21 |
return coord_mapping
|
62 | 22 |
|
63 | 23 |
|
64 | |
def mapping_left_right_mirrored(cols, rows):
|
65 | |
'''
|
66 | |
Maps keys according to the following diagram:
|
67 | |
|
68 | |
1 2 3 3 2 1
|
69 | |
+-------------
|
70 | |
1| A A A B B B
|
71 | |
2| A A A B B B
|
72 | |
'''
|
73 | |
|
74 | |
size = cols * rows
|
75 | |
coord_mapping = []
|
76 | |
for y in range(rows):
|
77 | |
yy = y * 2 * cols
|
78 | |
coord_mapping.extend(range(yy, yy + cols))
|
79 | |
coord_mapping.extend(range_rev(yy + size, yy + cols + size))
|
80 | |
return coord_mapping
|
81 | |
|
82 | |
|
83 | |
def mapping_interleave_cols(cols, rows):
|
84 | |
'''
|
85 | |
Maps keys according to the following diagram:
|
86 | |
|
87 | |
1 1 2 2 3 3
|
88 | |
+-------------
|
89 | |
1| A B A B A B
|
90 | |
2| A B A B A B
|
91 | |
'''
|
92 | |
|
93 | |
coord_mapping = []
|
94 | |
mat = cols * rows
|
95 | |
for i in range(mat):
|
96 | |
coord_mapping.append(i)
|
97 | |
coord_mapping.append(i + mat)
|
98 | |
return coord_mapping
|
99 | |
|
100 | |
|
101 | |
# these two are actually equivalent
|
102 | |
mapping_interleave_rows = mapping_left_right
|
103 | |
|
104 | |
|
105 | 24 |
class Matrix:
|
106 | |
'''
|
107 | |
A Scanner for Keyboard Matrices with Diodes in both directions.
|
108 | |
|
109 | |
In a bidirectional matrix, each (col, row) crossing can be used twice -
|
110 | |
once with a ROW2COL diode ("A"), and once with a COL2ROW diode ("B").
|
111 | |
|
112 | |
The raw key numbers returned by this scanner are based on this layout ("up_down"):
|
|
25 |
"""
|
|
26 |
A Scanner for Keyboard Matrices
|
|
27 |
The raw key numbers returned by this scanner are based on this layout:
|
113 | 28 |
|
114 | 29 |
C1 C2 C3
|
115 | 30 |
+-----------
|
116 | |
R1| A0 A1 A2
|
117 | |
R2| A3 A4 A5
|
118 | |
+-----------
|
119 | |
R1| B6 B7 B8
|
120 | |
R1| B9 B10 B11
|
121 | |
|
122 | |
If the physical layout of the matrix is different, you can pass a function
|
123 | |
for `mapping`. The function is passed `len_cols` and `len_rows` and should
|
124 | |
return a `coord_mapping` list.
|
125 | |
Various common mappings are provided in this module, see:
|
126 | |
- `kmk.scanners.bidirectional.mapping_left_right`
|
127 | |
- `kmk.scanners.bidirectional.mapping_left_right_mirrored`
|
128 | |
- `kmk.scanners.bidirectional.mapping_up_down`
|
129 | |
- `kmk.scanners.bidirectional.mapping_up_down_mirrored`
|
130 | |
- `kmk.scanners.bidirectional.mapping_interleave_rows`
|
131 | |
- `kmk.scanners.bidirectional.mapping_interleave_cols`
|
|
31 |
R1| 0 1 2
|
|
32 |
R2| 3 4 5
|
132 | 33 |
|
133 | 34 |
:param cols: A sequence of pins that are the columns for matrix A.
|
134 | 35 |
:param rows: A sequence of pins that are the rows for matrix A.
|
135 | |
:param mapping: A coord_mapping generator function, see above.
|
136 | |
'''
|
|
36 |
"""
|
137 | 37 |
|
138 | 38 |
def __init__(self, cols, rows, mapping=mapping_left_right):
|
139 | 39 |
self.len_cols = len(cols)
|
140 | 40 |
self.len_rows = len(rows)
|
141 | |
self.half_size = self.len_cols * self.len_rows
|
142 | |
self.keys = self.half_size * 2
|
143 | |
|
144 | |
self.coord_mapping = mapping(self.len_cols, self.len_rows)
|
|
41 |
self.keys = self.len_cols * self.len_rows
|
|
42 |
self.coord_mapping = mapping(self.len_cols, self.len_rows // 2)
|
|
43 |
self.coord_mapping.extend(range(max(self.coord_mapping) + 1, self.keys))
|
145 | 44 |
|
146 | 45 |
# A pin cannot be both a row and column, detect this by combining the
|
147 | 46 |
# two tuples into a set and validating that the length did not drop
|
|
150 | 49 |
unique_pins = {repr(c) for c in cols} | {repr(r) for r in rows}
|
151 | 50 |
assert (
|
152 | 51 |
len(unique_pins) == self.len_cols + self.len_rows
|
153 | |
), 'Cannot use a pin as both a column and row'
|
|
52 |
), "Cannot use a pin as both a column and row"
|
154 | 53 |
del unique_pins
|
155 | 54 |
|
156 | 55 |
# __class__.__name__ is used instead of isinstance as the MCP230xx lib
|
|
158 | 57 |
# https://github.com/adafruit/Adafruit_CircuitPython_MCP230xx/blob/3f04abbd65ba5fa938fcb04b99e92ae48a8c9406/adafruit_mcp230xx/digital_inout.py#L33
|
159 | 58 |
|
160 | 59 |
self.cols = [
|
161 | |
x if x.__class__.__name__ == 'DigitalInOut' else DigitalInOut(x)
|
|
60 |
x if x.__class__.__name__ == "DigitalInOut" else DigitalInOut(x)
|
162 | 61 |
for x in cols
|
163 | 62 |
]
|
164 | 63 |
self.rows = [
|
165 | |
x if x.__class__.__name__ == 'DigitalInOut' else DigitalInOut(x)
|
|
64 |
x if x.__class__.__name__ == "DigitalInOut" else DigitalInOut(x)
|
166 | 65 |
for x in rows
|
167 | 66 |
]
|
168 | 67 |
|
169 | 68 |
self.state = bytearray(self.keys)
|
170 | 69 |
|
171 | 70 |
def scan_for_changes(self):
|
172 | |
for (inputs, outputs, flip) in [
|
173 | |
(self.rows, self.cols, False),
|
174 | |
(self.cols, self.rows, True),
|
175 | |
]:
|
176 | |
for pin in outputs:
|
177 | |
pin.switch_to_input()
|
|
71 |
for row_pin in self.rows:
|
|
72 |
row_pin.switch_to_output(value=True)
|
178 | 73 |
|
179 | |
for pin in inputs:
|
180 | |
pin.switch_to_input(pull=Pull.DOWN)
|
|
74 |
for col_pin in self.cols:
|
|
75 |
col_pin.switch_to_input(pull=Pull.UP)
|
181 | 76 |
|
182 | |
for oidx, opin in enumerate(outputs):
|
183 | |
opin.switch_to_output(value=True)
|
|
77 |
for row, row_pin in enumerate(self.rows):
|
|
78 |
row_pin.value = False
|
184 | 79 |
|
185 | |
for iidx, ipin in enumerate(inputs):
|
186 | |
if flip:
|
187 | |
ba_idx = oidx * len(inputs) + iidx + self.half_size
|
188 | |
else:
|
189 | |
ba_idx = iidx * len(outputs) + oidx
|
|
80 |
for col, col_pin in enumerate(self.cols):
|
|
81 |
ba_idx = col + row * self.len_cols
|
190 | 82 |
|
191 | |
# cast to int to avoid
|
192 | |
#
|
193 | |
# >>> xyz = bytearray(3)
|
194 | |
# >>> xyz[2] = True
|
195 | |
# Traceback (most recent call last):
|
196 | |
# File "<stdin>", line 1, in <module>
|
197 | |
# OverflowError: value would overflow a 1 byte buffer
|
198 | |
#
|
199 | |
# I haven't dived too far into what causes this, but it's
|
200 | |
# almost certainly because bool types in Python aren't just
|
201 | |
# aliases to int values, but are proper pseudo-types
|
202 | |
new_val = int(ipin.value)
|
203 | |
old_val = self.state[ba_idx]
|
|
83 |
# cast to int to avoid
|
|
84 |
#
|
|
85 |
# >>> xyz = bytearray(3)
|
|
86 |
# >>> xyz[2] = True
|
|
87 |
# Traceback (most recent call last):
|
|
88 |
# File "<stdin>", line 1, in <module>
|
|
89 |
# OverflowError: value would overflow a 1 byte buffer
|
|
90 |
#
|
|
91 |
# I haven't dived too far into what causes this, but it's
|
|
92 |
# almost certainly because bool types in Python aren't just
|
|
93 |
# aliases to int values, but are proper pseudo-types
|
|
94 |
new_val = int(not col_pin.value)
|
|
95 |
old_val = self.state[ba_idx]
|
204 | 96 |
|
205 | |
if old_val != new_val:
|
206 | |
self.state[ba_idx] = new_val
|
207 | |
yield self.coord_mapping.index(ba_idx), new_val
|
|
97 |
if old_val != new_val:
|
|
98 |
map_idx = self.coord_mapping.index(ba_idx)
|
|
99 |
self.state[ba_idx] = new_val
|
|
100 |
yield map_idx, new_val
|
208 | 101 |
|
209 | |
opin.switch_to_input()
|
|
102 |
row_pin.value = True
|