]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/keypad.py
Add missing modules to the API documentation
[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
10 __version__ = "0.0.0-auto.0"
11 __repo__ = "https://github.com/adafruit/Adafruit_Blinka.git"
12
13
14 import time
15 import threading
16 from collections import deque
17 import digitalio
18
19
20 class Event:
21     """A key transition event."""
22
23     def __init__(self, key_number=0, pressed=True):
24         """
25         Create a key transition event, which reports a key-pressed or key-released transition.
26
27         :param int key_number: the key number
28         :param bool pressed: ``True`` if the key was pressed; ``False`` if it was released.
29         """
30         self._key_number = key_number
31         self._pressed = pressed
32
33     @property
34     def key_number(self):
35         """The key number."""
36         return self._key_number
37
38     @property
39     def pressed(self):
40         """
41         ``True`` if the event represents a key down (pressed) transition.
42         The opposite of `released`.
43         """
44         return self._pressed
45
46     @property
47     def released(self):
48         """
49         ``True`` if the event represents a key up (released) transition.
50         The opposite of `pressed`.
51         """
52         return not self._pressed
53
54     def __eq__(self, other):
55         """
56         Two `Event` objects are equal if their `key_number`
57         and `pressed`/`released` values are equal.
58         """
59         return self.key_number == other.key_number and self.pressed == other.pressed
60
61     def __hash__(self):
62         """Returns a hash for the `Event`, so it can be used in dictionaries, etc.."""
63         return hash(self._key_number)
64
65     def __repr__(self):
66         """Return a textual representation of the object"""
67         return "<Event: key_number {} {}>".format(
68             self.key_number, "pressed" if self._pressed else "released"
69         )
70
71
72 class _EventQueue:
73     """
74     A queue of `Event` objects, filled by a `keypad` scanner such as `Keys` or `KeyMatrix`.
75
76     You cannot create an instance of `_EventQueue` directly. Each scanner creates an
77     instance when it is created.
78     """
79
80     def __init__(self, max_events):
81         self._events = deque([], max_events)
82         self._overflowed = False
83
84     def get(self):
85         """
86         Return the next key transition event. Return ``None`` if no events are pending.
87
88         Note that the queue size is limited; see ``max_events`` in the constructor of
89         a scanner such as `Keys` or `KeyMatrix`.
90         If a new event arrives when the queue is full, the event is discarded, and
91         `overflowed` is set to ``True``.
92
93         :return: the next queued key transition `Event`
94         :rtype: Optional[Event]
95         """
96         if not self._events:
97             return None
98         return self._events.popleft()
99
100     def get_into(self, event):
101         """Store the next key transition event in the supplied event, if available,
102         and return ``True``.
103         If there are no queued events, do not touch ``event`` and return ``False``.
104
105         The advantage of this method over ``get()`` is that it does not allocate storage.
106         Instead you can reuse an existing ``Event`` object.
107
108         Note that the queue size is limited; see ``max_events`` in the constructor of
109         a scanner such as `Keys` or `KeyMatrix`.
110
111         :return ``True`` if an event was available and stored, ``False`` if not.
112         :rtype: bool
113         """
114         if not self._events:
115             return False
116         next_event = self._events.popleft()
117         # pylint: disable=protected-access
118         event._key_number = next_event._key_number
119         event._pressed = next_event._pressed
120         # pylint: enable=protected-access
121         return True
122
123     def clear(self):
124         """
125         Clear any queued key transition events. Also sets `overflowed` to ``False``.
126         """
127         self._events.clear()
128         self._overflowed = False
129
130     def __bool__(self):
131         """``True`` if `len()` is greater than zero.
132         This is an easy way to check if the queue is empty.
133         """
134         return len(self._events) > 0
135
136     def __len__(self):
137         """Return the number of events currently in the queue. Used to implement ``len()``."""
138         return len(self._events)
139
140     @property
141     def overflowed(self):
142         """
143         ``True`` if an event could not be added to the event queue because it was full. (read-only)
144         Set to ``False`` by  `clear()`.
145         """
146         return self._overflowed
147
148     def keypad_eventqueue_record(self, key_number, current):
149         """Record a new event"""
150         if len(self._events) == self._events.maxlen:
151             self._overflowed = True
152         else:
153             self._events.append(Event(key_number, current))
154
155
156 class _KeysBase:
157     def __init__(self, interval, max_events, scanning_function):
158         self._interval = interval
159         self._last_scan = time.monotonic()
160         self._events = _EventQueue(max_events)
161         self._scanning_function = scanning_function
162         self._scan_thread = threading.Thread(target=self._scanning_loop, daemon=True)
163         self._scan_thread.start()
164
165     @property
166     def events(self):
167         """The EventQueue associated with this Keys object. (read-only)"""
168         return self._events
169
170     def deinit(self):
171         """Stop scanning"""
172         if self._scan_thread.is_alive():
173             self._scan_thread.join()
174
175     def __enter__(self):
176         """No-op used by Context Managers."""
177         return self
178
179     def __exit__(self, exception_type, exception_value, traceback):
180         """
181         Automatically deinitializes when exiting a context. See
182         :ref:`lifetime-and-contextmanagers` for more info.
183         """
184         self.deinit()
185
186     def _scanning_loop(self):
187         while True:
188             remaining_delay = self._interval - (time.monotonic() - self._last_scan)
189             if remaining_delay > 0:
190                 time.sleep(remaining_delay)
191             self._last_scan = time.monotonic()
192             self._scanning_function()
193
194
195 class Keys(_KeysBase):
196     """Manage a set of independent keys."""
197
198     def __init__(
199         self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
200     ):
201         """
202         Create a `Keys` object that will scan keys attached to the given sequence of pins.
203         Each key is independent and attached to its own pin.
204
205         An `EventQueue` is created when this object is created and is available in the
206         `events` attribute.
207
208         :param Sequence[microcontroller.Pin] pins: The pins attached to the keys.
209           The key numbers correspond to indices into this sequence.
210         :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
211           ``False`` if the pin reads low (is grounded) when the key is pressed.
212           All the pins must be connected in the same way.
213         :param bool pull: ``True`` if an internal pull-up or pull-down should be
214           enabled on each pin. A pull-up will be used if ``value_when_pressed`` is ``False``;
215           a pull-down will be used if it is ``True``.
216           If an external pull is already provided for all the pins, you can set
217           ``pull`` to ``False``.
218           However, enabling an internal pull when an external one is already present is not
219           a problem;
220           it simply uses slightly more current.
221         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
222           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
223         :param int max_events: maximum size of `events` `EventQueue`:
224           maximum number of key transition events that are saved.
225           Must be >= 1.
226           If a new event arrives when the queue is full, the oldest event is discarded.
227         """
228         self._digitalinouts = []
229         for pin in pins:
230             dio = digitalio.DigitalInOut(pin)
231             if pull:
232                 dio.pull = (
233                     digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
234                 )
235             self._digitalinouts.append(dio)
236
237         self._currently_pressed = [False] * len(pins)
238         self._previously_pressed = [False] * len(pins)
239         self._value_when_pressed = value_when_pressed
240
241         super().__init__(interval, max_events, self._keypad_keys_scan)
242
243     def deinit(self):
244         """Stop scanning and release the pins."""
245         super().deinit()
246         for dio in self._digitalinouts:
247             dio.deinit()
248
249     def reset(self):
250         """Reset the internal state of the scanner to assume that all keys are now released.
251         Any key that is already pressed at the time of this call will therefore immediately cause
252         a new key-pressed event to occur.
253         """
254         self._currently_pressed = self._previously_pressed = [False] * self.key_count
255
256     @property
257     def key_count(self):
258         """The number of keys that are being scanned. (read-only)"""
259         return len(self._digitalinouts)
260
261     def _keypad_keys_scan(self):
262         for key_number, dio in enumerate(self._digitalinouts):
263             self._previously_pressed[key_number] = self._currently_pressed[key_number]
264             current = dio.value == self._value_when_pressed
265             self._currently_pressed[key_number] = current
266             if self._previously_pressed[key_number] != current:
267                 self._events.keypad_eventqueue_record(key_number, current)
268
269
270 class KeyMatrix(_KeysBase):
271     """Manage a 2D matrix of keys with row and column pins."""
272
273     # pylint: disable=too-many-arguments
274     def __init__(
275         self,
276         row_pins,
277         column_pins,
278         columns_to_anodes=True,
279         interval=0.02,
280         max_events=64,
281     ):
282         """
283         Create a `Keys` object that will scan the key matrix attached to the given row and
284         column pins.
285         There should not be any external pull-ups or pull-downs on the matrix:
286         ``KeyMatrix`` enables internal pull-ups or pull-downs on the pins as necessary.
287
288         The keys are numbered sequentially from zero. A key number can be computed
289         by ``row * len(column_pins) + column``.
290
291         An `EventQueue` is created when this object is created and is available in the `events`
292         attribute.
293
294         :param Sequence[microcontroller.Pin] row_pins: The pins attached to the rows.
295         :param Sequence[microcontroller.Pin] column_pins: The pins attached to the colums.
296         :param bool columns_to_anodes: Default ``True``.
297           If the matrix uses diodes, the diode anodes are typically connected to the column pins,
298           and the cathodes should be connected to the row pins. If your diodes are reversed,
299           set ``columns_to_anodes`` to ``False``.
300         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
301           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
302         :param int max_events: maximum size of `events` `EventQueue`:
303           maximum number of key transition events that are saved.
304           Must be >= 1.
305           If a new event arrives when the queue is full, the oldest event is discarded.
306         """
307         self._row_digitalinouts = []
308         for row_pin in row_pins:
309             row_dio = digitalio.DigitalInOut(row_pin)
310             row_dio.switch_to_input(
311                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
312             )
313             self._row_digitalinouts.append(row_dio)
314
315         self._column_digitalinouts = []
316         for column_pin in column_pins:
317             col_dio = digitalio.DigitalInOut(column_pin)
318             col_dio.switch_to_input(
319                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
320             )
321             self._column_digitalinouts.append(col_dio)
322         self._currently_pressed = [False] * len(column_pins) * len(row_pins)
323         self._previously_pressed = [False] * len(column_pins) * len(row_pins)
324         self._columns_to_anodes = columns_to_anodes
325
326         super().__init__(interval, max_events, self._keypad_keymatrix_scan)
327
328     # pylint: enable=too-many-arguments
329
330     @property
331     def key_count(self):
332         """The number of keys that are being scanned. (read-only)"""
333         return len(self._row_digitalinouts) * len(self._column_digitalinouts)
334
335     def deinit(self):
336         """Stop scanning and release the pins."""
337         super().deinit()
338         for row_dio in self._row_digitalinouts:
339             row_dio.deinit()
340         for col_dio in self._column_digitalinouts:
341             col_dio.deinit()
342
343     def reset(self):
344         """
345         Reset the internal state of the scanner to assume that all keys are now released.
346         Any key that is already pressed at the time of this call will therefore immediately cause
347         a new key-pressed event to occur.
348         """
349         self._previously_pressed = self._currently_pressed = [False] * self.key_count
350
351     def _row_column_to_key_number(self, row, column):
352         return row * len(self._column_digitalinouts) + column
353
354     def _keypad_keymatrix_scan(self):
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         self._latch.value = self._value_to_latch
475         for key_number in range(self._key_count):
476             self._clock.value = False
477             self._previously_pressed[key_number] = self._currently_pressed[key_number]
478             current = self._data.value == self._value_when_pressed
479             self._currently_pressed[key_number] = current
480             self._clock.value = True
481             if self._previously_pressed[key_number] != current:
482                 self._events.keypad_eventqueue_record(key_number, current)
483
484         self._latch.value = not self._value_to_latch