]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/keypad.py
Added keypad module
[Adafruit_Blinka-hackapet.git] / src / keypad.py
1 """
2 `keypad` - Support for scanning keys and key matrices
3 ===========================================================
4 See `CircuitPython:keypad` in CircuitPython for more details.
5
6 * Author(s): Melissa LeBlanc-Williams
7 """
8
9 import time
10 import threading
11 from collections import deque
12 import digitalio
13
14
15 class Event:
16     """A key transition event."""
17
18     def __init__(self, key_number=0, pressed=True):
19         """
20         Create a key transition event, which reports a key-pressed or key-released transition.
21
22         :param int key_number: the key number
23         :param bool pressed: ``True`` if the key was pressed; ``False`` if it was released.
24         """
25         self._key_number = key_number
26         self._pressed = pressed
27
28     @property
29     def key_number(self):
30         """The key number."""
31         return self._key_number
32
33     @property
34     def pressed(self):
35         """
36         ``True`` if the event represents a key down (pressed) transition.
37         The opposite of `released`.
38         """
39         return self._pressed
40
41     @property
42     def released(self):
43         """
44         ``True`` if the event represents a key up (released) transition.
45         The opposite of `pressed`.
46         """
47         return not self._pressed
48
49     def __eq__(self, other):
50         """
51         Two `Event` objects are equal if their `key_number`
52         and `pressed`/`released` values are equal.
53         """
54         return self.key_number == other.key_number and self.pressed == other.pressed
55
56     def __hash__(self):
57         """Returns a hash for the `Event`, so it can be used in dictionaries, etc.."""
58         return hash(self._key_number)
59
60     def __repr__(self):
61         """Return a textual representation of the object"""
62         return "<Event: key_number {} {}>".format(
63             self.key_number, "pressed" if self._pressed else "released"
64         )
65
66
67 class _EventQueue:
68     """
69     A queue of `Event` objects, filled by a `keypad` scanner such as `Keys` or `KeyMatrix`.
70
71     You cannot create an instance of `_EventQueue` directly. Each scanner creates an
72     instance when it is created.
73     """
74
75     def __init__(self, max_events):
76         self._events = deque([], max_events)
77         self._overflowed = False
78
79     def get(self):
80         """
81         Return the next key transition event. Return ``None`` if no events are pending.
82
83         Note that the queue size is limited; see ``max_events`` in the constructor of
84         a scanner such as `Keys` or `KeyMatrix`.
85         If a new event arrives when the queue is full, the event is discarded, and
86         `overflowed` is set to ``True``.
87
88         :return: the next queued key transition `Event`
89         :rtype: Optional[Event]
90         """
91         if not self._events:
92             return None
93         return self._events.popleft()
94
95     def get_into(self, event):
96         """Store the next key transition event in the supplied event, if available,
97         and return ``True``.
98         If there are no queued events, do not touch ``event`` and return ``False``.
99
100         The advantage of this method over ``get()`` is that it does not allocate storage.
101         Instead you can reuse an existing ``Event`` object.
102
103         Note that the queue size is limited; see ``max_events`` in the constructor of
104         a scanner such as `Keys` or `KeyMatrix`.
105
106         :return ``True`` if an event was available and stored, ``False`` if not.
107         :rtype: bool
108         """
109         if not self._events:
110             return False
111         next_event = self._events.popleft()
112         # pylint: disable=protected-access
113         event._key_number = next_event._key_number
114         event._pressed = next_event._pressed
115         # pylint: enable=protected-access
116         return True
117
118     def clear(self):
119         """
120         Clear any queued key transition events. Also sets `overflowed` to ``False``.
121         """
122         self._events.clear()
123         self._overflowed = False
124
125     def __bool__(self):
126         """``True`` if `len()` is greater than zero.
127         This is an easy way to check if the queue is empty.
128         """
129         return len(self._events) > 0
130
131     def __len__(self):
132         """Return the number of events currently in the queue. Used to implement ``len()``."""
133         return len(self._events)
134
135     @property
136     def overflowed(self):
137         """
138         ``True`` if an event could not be added to the event queue because it was full. (read-only)
139         Set to ``False`` by  `clear()`.
140         """
141         return self._overflowed
142
143     def keypad_eventqueue_record(self, key_number, current):
144         """Record a new event"""
145         if len(self._events) == self._events.maxlen:
146             self._overflowed = True
147         else:
148             self._events.append(Event(key_number, current))
149
150
151 class _KeysBase:
152     def __init__(self, interval, max_events, scanning_function):
153         self._interval = interval
154         self._last_scan = time.monotonic()
155         self._events = _EventQueue(max_events)
156         self._scanning_function = scanning_function
157         self._scan_thread = threading.Thread(target=self._scanning_loop, daemon=True)
158         self._scan_thread.start()
159
160     @property
161     def events(self):
162         """The EventQueue associated with this Keys object. (read-only)"""
163         return self._events
164
165     def deinit(self):
166         """Stop scanning"""
167         if self._scan_thread.is_alive():
168             self._scan_thread.join()
169
170     def __enter__(self):
171         """No-op used by Context Managers."""
172         return self
173
174     def __exit__(self, exception_type, exception_value, traceback):
175         """
176         Automatically deinitializes when exiting a context. See
177         :ref:`lifetime-and-contextmanagers` for more info.
178         """
179         self.deinit()
180
181     def _scanning_loop(self):
182         while True:
183             self._scanning_function()
184             time.sleep(0.001)
185
186
187 class Keys(_KeysBase):
188     """Manage a set of independent keys."""
189
190     def __init__(
191         self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
192     ):
193         """
194         Create a `Keys` object that will scan keys attached to the given sequence of pins.
195         Each key is independent and attached to its own pin.
196
197         An `EventQueue` is created when this object is created and is available in the
198         `events` attribute.
199
200         :param Sequence[microcontroller.Pin] pins: The pins attached to the keys.
201           The key numbers correspond to indices into this sequence.
202         :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
203           ``False`` if the pin reads low (is grounded) when the key is pressed.
204           All the pins must be connected in the same way.
205         :param bool pull: ``True`` if an internal pull-up or pull-down should be
206           enabled on each pin. A pull-up will be used if ``value_when_pressed`` is ``False``;
207           a pull-down will be used if it is ``True``.
208           If an external pull is already provided for all the pins, you can set
209           ``pull`` to ``False``.
210           However, enabling an internal pull when an external one is already present is not
211           a problem;
212           it simply uses slightly more current.
213         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
214           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
215         :param int max_events: maximum size of `events` `EventQueue`:
216           maximum number of key transition events that are saved.
217           Must be >= 1.
218           If a new event arrives when the queue is full, the oldest event is discarded.
219         """
220         self._digitalinouts = []
221         for pin in pins:
222             dio = digitalio.DigitalInOut(pin)
223             if pull:
224                 dio.pull = (
225                     digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
226                 )
227             self._digitalinouts.append(dio)
228
229         self._currently_pressed = [False] * len(pins)
230         self._previously_pressed = [False] * len(pins)
231         self._value_when_pressed = value_when_pressed
232
233         super().__init__(interval, max_events, self._keypad_keys_scan)
234
235     def deinit(self):
236         """Stop scanning and release the pins."""
237         super().deinit()
238         for dio in self._digitalinouts:
239             dio.deinit()
240
241     def reset(self):
242         """Reset the internal state of the scanner to assume that all keys are now released.
243         Any key that is already pressed at the time of this call will therefore immediately cause
244         a new key-pressed event to occur.
245         """
246         self._currently_pressed = self._previously_pressed = [False] * self.key_count
247
248     @property
249     def key_count(self):
250         """The number of keys that are being scanned. (read-only)"""
251         return len(self._digitalinouts)
252
253     def _keypad_keys_scan(self):
254         if time.monotonic() - self._last_scan < self._interval:
255             return
256         self._last_scan = time.monotonic()
257
258         for key_number, dio in enumerate(self._digitalinouts):
259             self._previously_pressed[key_number] = self._currently_pressed[key_number]
260             current = dio.value == self._value_when_pressed
261             self._currently_pressed[key_number] = current
262             if self._previously_pressed[key_number] != current:
263                 self._events.keypad_eventqueue_record(key_number, current)
264
265
266 class KeyMatrix(_KeysBase):
267     """Manage a 2D matrix of keys with row and column pins."""
268
269     # pylint: disable=too-many-arguments
270     def __init__(
271         self,
272         row_pins,
273         column_pins,
274         columns_to_anodes=True,
275         interval=0.02,
276         max_events=64,
277     ):
278         """
279         Create a `Keys` object that will scan the key matrix attached to the given row and
280         column pins.
281         There should not be any external pull-ups or pull-downs on the matrix:
282         ``KeyMatrix`` enables internal pull-ups or pull-downs on the pins as necessary.
283
284         The keys are numbered sequentially from zero. A key number can be computed
285         by ``row * len(column_pins) + column``.
286
287         An `EventQueue` is created when this object is created and is available in the `events`
288         attribute.
289
290         :param Sequence[microcontroller.Pin] row_pins: The pins attached to the rows.
291         :param Sequence[microcontroller.Pin] column_pins: The pins attached to the colums.
292         :param bool columns_to_anodes: Default ``True``.
293           If the matrix uses diodes, the diode anodes are typically connected to the column pins,
294           and the cathodes should be connected to the row pins. If your diodes are reversed,
295           set ``columns_to_anodes`` to ``False``.
296         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
297           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
298         :param int max_events: maximum size of `events` `EventQueue`:
299           maximum number of key transition events that are saved.
300           Must be >= 1.
301           If a new event arrives when the queue is full, the oldest event is discarded.
302         """
303         self._row_digitalinouts = []
304         for row_pin in row_pins:
305             row_dio = digitalio.DigitalInOut(row_pin)
306             row_dio.switch_to_input(
307                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
308             )
309             self._row_digitalinouts.append(row_dio)
310
311         self._column_digitalinouts = []
312         for column_pin in column_pins:
313             col_dio = digitalio.DigitalInOut(column_pin)
314             col_dio.switch_to_input(
315                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
316             )
317             self._column_digitalinouts.append(col_dio)
318         self._currently_pressed = [False] * len(column_pins) * len(row_pins)
319         self._previously_pressed = [False] * len(column_pins) * len(row_pins)
320         self._columns_to_anodes = columns_to_anodes
321
322         super().__init__(interval, max_events, self._keypad_keymatrix_scan)
323
324     # pylint: enable=too-many-arguments
325
326     @property
327     def key_count(self):
328         """The number of keys that are being scanned. (read-only)"""
329         return len(self._row_digitalinouts) * len(self._column_digitalinouts)
330
331     def deinit(self):
332         """Stop scanning and release the pins."""
333         super().deinit()
334         for row_dio in self._row_digitalinouts:
335             row_dio.deinit()
336         for col_dio in self._column_digitalinouts:
337             col_dio.deinit()
338
339     def reset(self):
340         """
341         Reset the internal state of the scanner to assume that all keys are now released.
342         Any key that is already pressed at the time of this call will therefore immediately cause
343         a new key-pressed event to occur.
344         """
345         self._previously_pressed = self._currently_pressed = [False] * self.key_count
346
347     def _row_column_to_key_number(self, row, column):
348         return row * len(self._column_digitalinouts) + column
349
350     def _keypad_keymatrix_scan(self):
351         if time.monotonic() - self._last_scan < self._interval:
352             return
353         self._last_scan = time.monotonic()
354
355         for row, row_dio in enumerate(self._row_digitalinouts):
356             row_dio.switch_to_output(
357                 value=(not self._columns_to_anodes),
358                 drive_mode=digitalio.DriveMode.PUSH_PULL,
359             )
360             for col, col_dio in enumerate(self._column_digitalinouts):
361                 key_number = self._row_column_to_key_number(row, col)
362                 self._previously_pressed[key_number] = self._currently_pressed[
363                     key_number
364                 ]
365                 current = col_dio.value != self._columns_to_anodes
366                 self._currently_pressed[key_number] = current
367                 if self._previously_pressed[key_number] != current:
368                     self._events.keypad_eventqueue_record(key_number, current)
369             row_dio.value = self._columns_to_anodes
370             row_dio.switch_to_input(
371                 pull=(
372                     digitalio.Pull.UP
373                     if self._columns_to_anodes
374                     else digitalio.Pull.DOWN
375                 )
376             )
377
378
379 class ShiftRegisterKeys(_KeysBase):
380     """Manage a set of keys attached to an incoming shift register."""
381
382     def __init__(
383         self,
384         *,
385         clock,
386         data,
387         latch,
388         value_to_latch=True,
389         key_count,
390         value_when_pressed,
391         interval=0.02,
392         max_events=64
393     ):
394         """
395         Create a `Keys` object that will scan keys attached to a parallel-in serial-out
396         shift register like the 74HC165 or CD4021.
397         Note that you may chain shift registers to load in as many values as you need.
398
399         Key number 0 is the first (or more properly, the zero-th) bit read. In the
400         74HC165, this bit is labeled ``Q7``. Key number 1 will be the value of ``Q6``, etc.
401
402         An `EventQueue` is created when this object is created and is available in the
403         `events` attribute.
404
405         :param microcontroller.Pin clock: The shift register clock pin.
406           The shift register should clock on a low-to-high transition.
407         :param microcontroller.Pin data: the incoming shift register data pin
408         :param microcontroller.Pin latch:
409           Pin used to latch parallel data going into the shift register.
410         :param bool value_to_latch: Pin state to latch data being read.
411           ``True`` if the data is latched when ``latch`` goes high
412           ``False`` if the data is latched when ``latch goes low.
413           The default is ``True``, which is how the 74HC165 operates. The CD4021 latch is
414           the opposite. Once the data is latched, it will be shifted out by toggling the
415           clock pin.
416         :param int key_count: number of data lines to clock in
417         :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
418           ``False`` if the pin reads low (is grounded) when the key is pressed.
419         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
420           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
421         :param int max_events: maximum size of `events` `EventQueue`:
422           maximum number of key transition events that are saved.
423           Must be >= 1.
424           If a new event arrives when the queue is full, the oldest event is discarded.
425         """
426         clock_dio = digitalio.DigitalInOut(clock)
427         clock_dio.switch_to_output(
428             value=False, drive_mode=digitalio.DriveMode.PUSH_PULL
429         )
430         self._clock = clock_dio
431
432         data_dio = digitalio.DigitalInOut(data)
433         data_dio.switch_to_input()
434         self._data = data_dio
435
436         latch_dio = digitalio.DigitalInOut(latch)
437         latch_dio.switch_to_output(value=True, drive_mode=digitalio.DriveMode.PUSH_PULL)
438         self._latch = latch_dio
439         self._value_to_latch = value_to_latch
440
441         self._currently_pressed = [False] * key_count
442         self._previously_pressed = [False] * key_count
443         self._value_when_pressed = value_when_pressed
444         self._key_count = key_count
445
446         super().__init__(interval, max_events, self._keypad_shiftregisterkeys_scan)
447
448     def deinit(self):
449         """Stop scanning and release the pins."""
450         super().deinit()
451         self._clock.deinit()
452         self._data.deinit()
453         self._latch.deinit()
454
455     def reset(self):
456         """
457         Reset the internal state of the scanner to assume that all keys are now released.
458         Any key that is already pressed at the time of this call will therefore immediately cause
459         a new key-pressed event to occur.
460         """
461         self._currently_pressed = self._previously_pressed = [False] * self._key_count
462
463     @property
464     def key_count(self):
465         """The number of keys that are being scanned. (read-only)"""
466         return self._key_count
467
468     @property
469     def events(self):
470         """The `EventQueue` associated with this `Keys` object. (read-only)"""
471         return self._events
472
473     def _keypad_shiftregisterkeys_scan(self):
474         if time.monotonic() - self._last_scan < self._interval:
475             return
476         self._last_scan = time.monotonic()
477
478         self._latch.value = self._value_to_latch
479         for key_number in range(self._key_count):
480             self._clock.value = False
481             self._previously_pressed[key_number] = self._currently_pressed[key_number]
482             current = self._data.value == self._value_when_pressed
483             self._currently_pressed[key_number] = current
484             self._clock.value = True
485             if self._previously_pressed[key_number] != current:
486                 self._events.keypad_eventqueue_record(key_number, current)
487
488         self._latch.value = not self._value_to_latch