-
Notifications
You must be signed in to change notification settings - Fork 0
/
trellism4_extended.py
316 lines (246 loc) · 9.9 KB
/
trellism4_extended.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 Arofarn
#
# SPDX-License-Identifier: MIT
"""
`trellism4_extended`
================================================================================
CircuitPython library to extended Adafruit NeotrellisM4 board with two Neotrellis seesaw
boards (or more !).
* Author(s): arofarn
Implementation Notes
--------------------
**Hardware:**
* Adafruit NeoTrellis M4 Express: https://www.adafruit.com/product/3938
* Adafruit NeoTrellis RGB Driver PCB for 4x4 Keypad :https://www.adafruit.com/product/3954
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit CircuitPython bus_devices library (from Adafruit_CircuitPython_Bundle):
https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases
"""
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/arofarn/CircuitPython_TrellisM4_extended.git"
import board
import digitalio
from micropython import const
from adafruit_seesaw.keypad import KeyEvent
from adafruit_matrixkeypad import Matrix_Keypad
from neopixel import NeoPixel
_NEO_TRELLIS_NUM_ROWS = const(4)
_NEO_TRELLIS_NUM_COLS = const(4)
_NEO_TRELLIS_NUM_KEYS = const(16)
# _NEO_TRELLIS_MAX_CALLBACKS = const(32)
_TRELLISM4_LEFT_PART = const(0)
_TRELLISM4_RIGHT_PART = const(4)
def _key(xval):
return int(int(xval / 4) * 8 + (xval % 4))
def _seesaw_key(xval):
return int(int(xval / 8) * 4 + (xval % 8))
def _to_seesaw_key(xval):
return int(xval + (xval // 4) * 4)
class _TrellisNeoPixel:
"""Neopixel driver"""
# Lots of stuff come from Adafruit_CircuitPython_seesaw/neopixel.py
def __init__(
self, auto_write=True, brightness=1.0, part=_TRELLISM4_LEFT_PART, left_part=None
):
if part == _TRELLISM4_LEFT_PART:
self.pix = NeoPixel(
board.NEOPIXEL, 32, auto_write=False, brightness=brightness
)
elif part == _TRELLISM4_RIGHT_PART:
self.pix = left_part.pix
self.auto_write = auto_write
self._offset = part
def __setitem__(self, key, color):
self.pix[_key(key) + self._offset] = color
if self.auto_write:
self.show()
def __getitem__(self, key):
return self.pix[_key(key) + self._offset]
def fill(self, color):
"""Fill method wrapper"""
# Suppress auto_write while filling.
current_auto_write = self.auto_write
self.auto_write = False
for i in range(16):
self[i] = color
if current_auto_write:
self.show()
self.auto_write = current_auto_write
def show(self):
"""Fill method wrapper"""
self.pix.show()
class _TrellisKeypad:
"""Simple Keypad object for Trellis M4
No pixel, no rotation
Key numbers : 0 - 15"""
def __init__(self, part=_TRELLISM4_LEFT_PART, row_pins=None):
self._offset = part
col_pins = []
for x in range(self._offset, self._offset + _NEO_TRELLIS_NUM_COLS):
col_pin = digitalio.DigitalInOut(getattr(board, "COL{}".format(x)))
col_pins.append(col_pin)
if part == _TRELLISM4_LEFT_PART:
self.row_pins = []
for y in range(_NEO_TRELLIS_NUM_ROWS):
row_pin = digitalio.DigitalInOut(getattr(board, "ROW{}".format(y)))
self.row_pins.append(row_pin)
elif part == _TRELLISM4_RIGHT_PART:
if row_pins is None:
raise ValueError("Missing row_pins list for the right part")
self.row_pins = row_pins
else:
raise ValueError("part arg should be 0 (left part) or 4 (right part)")
key_names = []
for y in range(4):
row = []
for x in range(4):
row.append(4 * x + y)
key_names.append(row) # Keys of each halves is numbered from 0-15
self._matrix = Matrix_Keypad(col_pins, self.row_pins, key_names)
@property
def pressed_keys(self):
"""A list of tuples of currently pressed button coordinates.
.. code-block:: python
import time
import adafruit_trellism4
trellis = adafruit_trellism4.TrellisM4Express()
current_press = set()
while True:
pressed = set(trellis.pressed_keys)
for press in pressed - current_press:
print("Pressed:", press)
for release in current_press - pressed:
print("Released:", release)
time.sleep(0.08)
current_press = pressed
"""
return self._matrix.pressed_keys
class NeoTrellisM4:
"""
Driver for the Adafruit NeoTrellis.
:param left_part: None (default) or left part object
.. note:: if None (or ommitted) the class create a
neotrellis.multitrellis-compatible object for the left half of the
TrellisM4 board. Else the right part is created and the arguement should
be the left part object.
Example:
.. code-block:: python
from neotrellism4 import NeoTrellisM4
trellis_left = NeoTrellisM4()
trellis_right = NeoTrellisM4(left_part=trellis_left)
"""
EDGE_HIGH = const(0)
EDGE_LOW = const(1)
EDGE_FALLING = const(2)
EDGE_RISING = const(3)
def __init__(self, left_part=None):
if left_part is None:
self._offset = _TRELLISM4_LEFT_PART
self.pixels = _TrellisNeoPixel()
self.keypad = _TrellisKeypad()
else:
self._offset = _TRELLISM4_RIGHT_PART
self.pixels = _TrellisNeoPixel(
32, part=_TRELLISM4_RIGHT_PART, left_part=left_part.pixels
)
self.keypad = _TrellisKeypad(
part=_TRELLISM4_RIGHT_PART, row_pins=left_part.keypad.row_pins
)
self._events = [0] * _NEO_TRELLIS_NUM_KEYS
self._current_press = set()
self._key_edges = [self.EDGE_HIGH] * _NEO_TRELLIS_NUM_KEYS # Keys edges
self._current_events = bytearray()
self.callbacks = [None] * 16
@property
def interrupt_enabled(self):
"""Only for compatibility with neotrellis module:
Interrupts are disable on trellis M4 keypad"""
return False
# pylint: disable=unused-argument, no-self-use
@interrupt_enabled.setter
def interrupt_enabled(self, value):
"""Only for compatibility with neotrellis module:
Interrupts are disable on trellis M4 keypad
"""
print("Warning: no interrupt with Trellis M4 keypad (method does nothing)")
# pylint: enable=unused-argument, no-self-use
@property
def count(self):
"""Return the pressed keys count"""
self._read_keypad()
return len(self._current_events)
# pylint: disable=unused-argument, no-self-use
@count.setter
def count(self, value):
"""Only for compatibility with neotrellis module"""
raise AttributeError("count is read only")
# pylint: enable=unused-argument, no-self-use
def set_event(self, key, edge, enable):
"""
Control which kinds of events are set
:param int key: the key number
:param int edge: the type of event
:param bool enable: True to enable the event, False to disable it
"""
if enable not in (True, False):
raise ValueError("event enable must be True or False")
if edge > 3 or edge < 0:
raise ValueError("invalid edge")
# Pas besoin de l'écriture sur I2C mais de l'enregistrer dans self._events
if enable:
self._events[key] = self._events[key] | (1 << edge)
else:
self._events[key] = self._events[key] & (0xF ^ (1 << edge))
def read_keypad(self, num):
"""
Read data from the keypad
:param int num: The number of bytes to read
"""
while num > len(self._current_events):
self._current_events.append(0xFF)
return self._current_events[:num]
def _read_keypad(self):
"""Read keypad and update _key_edges and _current_events"""
pressed = set(self.keypad.pressed_keys)
# default : not pressed => EDGE_HIGH
self._key_edges = [self.EDGE_HIGH] * _NEO_TRELLIS_NUM_KEYS
for k in pressed:
self._key_edges[k] = self.EDGE_LOW
for k in pressed - self._current_press:
self._key_edges[k] = self.EDGE_RISING
for k in self._current_press - pressed:
self._key_edges[k] = self.EDGE_FALLING
self._current_press = pressed
self._current_events = bytearray()
for k in range(_NEO_TRELLIS_NUM_KEYS):
if (self._events[k] >> self._key_edges[k]) & 0x1:
raw_evt = (_to_seesaw_key(k) << 2) | self._key_edges[k]
self._current_events.append(raw_evt)
def activate_key(self, key, edge, enable=True):
"""
Activate or deactivate a key on the trellis
:param int key : key number from 0 to 16.
:param int edge : specifies what edge to register an event on and can be
NeoTrellis.EDGE_FALLING or NeoTrellis.EDGE_RISING.
:param bool enable : should be set to True if the event is to be enabled,
or False if the event is to be disabled.
"""
self.set_event(key, edge, enable)
def sync(self):
"""
Read any events from the Trellis hardware and call associated callbacks.
"""
available = self.count
if available > 0:
available = available + 2
buf = self.read_keypad(available)
for raw in buf:
evt = KeyEvent(_seesaw_key((raw >> 2) & 0x3F), raw & 0x3)
if (
evt.number < _NEO_TRELLIS_NUM_KEYS
and self.callbacks[evt.number] is not None
):
self.callbacks[evt.number](evt)