]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/keypad.py
correct handling of active devices
[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             remaining_delay = self._interval - (time.monotonic() - self._last_scan)
184             if remaining_delay > 0:
185                 time.sleep(remaining_delay)
186             self._last_scan = time.monotonic()
187             self._scanning_function()
188
189
190 class Keys(_KeysBase):
191     """Manage a set of independent keys."""
192
193     def __init__(
194         self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
195     ):
196         """
197         Create a `Keys` object that will scan keys attached to the given sequence of pins.
198         Each key is independent and attached to its own pin.
199
200         An `EventQueue` is created when this object is created and is available in the
201         `events` attribute.
202
203         :param Sequence[microcontroller.Pin] pins: The pins attached to the keys.
204           The key numbers correspond to indices into this sequence.
205         :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
206           ``False`` if the pin reads low (is grounded) when the key is pressed.
207           All the pins must be connected in the same way.
208         :param bool pull: ``True`` if an internal pull-up or pull-down should be
209           enabled on each pin. A pull-up will be used if ``value_when_pressed`` is ``False``;
210           a pull-down will be used if it is ``True``.
211           If an external pull is already provided for all the pins, you can set
212           ``pull`` to ``False``.
213           However, enabling an internal pull when an external one is already present is not
214           a problem;
215           it simply uses slightly more current.
216         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
217           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
218         :param int max_events: maximum size of `events` `EventQueue`:
219           maximum number of key transition events that are saved.
220           Must be >= 1.
221           If a new event arrives when the queue is full, the oldest event is discarded.
222         """
223         self._digitalinouts = []
224         for pin in pins:
225             dio = digitalio.DigitalInOut(pin)
226             if pull:
227                 dio.pull = (
228                     digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
229                 )
230             self._digitalinouts.append(dio)
231
232         self._currently_pressed = [False] * len(pins)
233         self._previously_pressed = [False] * len(pins)
234         self._value_when_pressed = value_when_pressed
235
236         super().__init__(interval, max_events, self._keypad_keys_scan)
237
238     def deinit(self):
239         """Stop scanning and release the pins."""
240         super().deinit()
241         for dio in self._digitalinouts:
242             dio.deinit()
243
244     def reset(self):
245         """Reset the internal state of the scanner to assume that all keys are now released.
246         Any key that is already pressed at the time of this call will therefore immediately cause
247         a new key-pressed event to occur.
248         """
249         self._currently_pressed = self._previously_pressed = [False] * self.key_count
250
251     @property
252     def key_count(self):
253         """The number of keys that are being scanned. (read-only)"""
254         return len(self._digitalinouts)
255
256     def _keypad_keys_scan(self):
257         for key_number, dio in enumerate(self._digitalinouts):
258             self._previously_pressed[key_number] = self._currently_pressed[key_number]
259             current = dio.value == self._value_when_pressed
260             self._currently_pressed[key_number] = current
261             if self._previously_pressed[key_number] != current:
262                 self._events.keypad_eventqueue_record(key_number, current)
263
264
265 class KeyMatrix(_KeysBase):
266     """Manage a 2D matrix of keys with row and column pins."""
267
268     # pylint: disable=too-many-arguments
269     def __init__(
270         self,
271         row_pins,
272         column_pins,
273         columns_to_anodes=True,
274         interval=0.02,
275         max_events=64,
276     ):
277         """
278         Create a `Keys` object that will scan the key matrix attached to the given row and
279         column pins.
280         There should not be any external pull-ups or pull-downs on the matrix:
281         ``KeyMatrix`` enables internal pull-ups or pull-downs on the pins as necessary.
282
283         The keys are numbered sequentially from zero. A key number can be computed
284         by ``row * len(column_pins) + column``.
285
286         An `EventQueue` is created when this object is created and is available in the `events`
287         attribute.
288
289         :param Sequence[microcontroller.Pin] row_pins: The pins attached to the rows.
290         :param Sequence[microcontroller.Pin] column_pins: The pins attached to the colums.
291         :param bool columns_to_anodes: Default ``True``.
292           If the matrix uses diodes, the diode anodes are typically connected to the column pins,
293           and the cathodes should be connected to the row pins. If your diodes are reversed,
294           set ``columns_to_anodes`` to ``False``.
295         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
296           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
297         :param int max_events: maximum size of `events` `EventQueue`:
298           maximum number of key transition events that are saved.
299           Must be >= 1.
300           If a new event arrives when the queue is full, the oldest event is discarded.
301         """
302         self._row_digitalinouts = []
303         for row_pin in row_pins:
304             row_dio = digitalio.DigitalInOut(row_pin)
305             row_dio.switch_to_input(
306                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
307             )
308             self._row_digitalinouts.append(row_dio)
309
310         self._column_digitalinouts = []
311         for column_pin in column_pins:
312             col_dio = digitalio.DigitalInOut(column_pin)
313             col_dio.switch_to_input(
314                 pull=(digitalio.Pull.UP if columns_to_anodes else digitalio.Pull.DOWN)
315             )
316             self._column_digitalinouts.append(col_dio)
317         self._currently_pressed = [False] * len(column_pins) * len(row_pins)
318         self._previously_pressed = [False] * len(column_pins) * len(row_pins)
319         self._columns_to_anodes = columns_to_anodes
320
321         super().__init__(interval, max_events, self._keypad_keymatrix_scan)
322
323     # pylint: enable=too-many-arguments
324
325     @property
326     def key_count(self):
327         """The number of keys that are being scanned. (read-only)"""
328         return len(self._row_digitalinouts) * len(self._column_digitalinouts)
329
330     def deinit(self):
331         """Stop scanning and release the pins."""
332         super().deinit()
333         for row_dio in self._row_digitalinouts:
334             row_dio.deinit()
335         for col_dio in self._column_digitalinouts:
336             col_dio.deinit()
337
338     def reset(self):
339         """
340         Reset the internal state of the scanner to assume that all keys are now released.
341         Any key that is already pressed at the time of this call will therefore immediately cause
342         a new key-pressed event to occur.
343         """
344         self._previously_pressed = self._currently_pressed = [False] * self.key_count
345
346     def _row_column_to_key_number(self, row, column):
347         return row * len(self._column_digitalinouts) + column
348
349     def _keypad_keymatrix_scan(self):
350         for row, row_dio in enumerate(self._row_digitalinouts):
351             row_dio.switch_to_output(
352                 value=(not self._columns_to_anodes),
353                 drive_mode=digitalio.DriveMode.PUSH_PULL,
354             )
355             for col, col_dio in enumerate(self._column_digitalinouts):
356                 key_number = self._row_column_to_key_number(row, col)
357                 self._previously_pressed[key_number] = self._currently_pressed[
358                     key_number
359                 ]
360                 current = col_dio.value != self._columns_to_anodes
361                 self._currently_pressed[key_number] = current
362                 if self._previously_pressed[key_number] != current:
363                     self._events.keypad_eventqueue_record(key_number, current)
364             row_dio.value = self._columns_to_anodes
365             row_dio.switch_to_input(
366                 pull=(
367                     digitalio.Pull.UP
368                     if self._columns_to_anodes
369                     else digitalio.Pull.DOWN
370                 )
371             )
372
373
374 class ShiftRegisterKeys(_KeysBase):
375     """Manage a set of keys attached to an incoming shift register."""
376
377     def __init__(
378         self,
379         *,
380         clock,
381         data,
382         latch,
383         value_to_latch=True,
384         key_count,
385         value_when_pressed,
386         interval=0.02,
387         max_events=64,
388     ):
389         """
390         Create a `Keys` object that will scan keys attached to a parallel-in serial-out
391         shift register like the 74HC165 or CD4021.
392         Note that you may chain shift registers to load in as many values as you need.
393
394         Key number 0 is the first (or more properly, the zero-th) bit read. In the
395         74HC165, this bit is labeled ``Q7``. Key number 1 will be the value of ``Q6``, etc.
396
397         An `EventQueue` is created when this object is created and is available in the
398         `events` attribute.
399
400         :param microcontroller.Pin clock: The shift register clock pin.
401           The shift register should clock on a low-to-high transition.
402         :param microcontroller.Pin data: the incoming shift register data pin
403         :param microcontroller.Pin latch:
404           Pin used to latch parallel data going into the shift register.
405         :param bool value_to_latch: Pin state to latch data being read.
406           ``True`` if the data is latched when ``latch`` goes high
407           ``False`` if the data is latched when ``latch goes low.
408           The default is ``True``, which is how the 74HC165 operates. The CD4021 latch is
409           the opposite. Once the data is latched, it will be shifted out by toggling the
410           clock pin.
411         :param int key_count: number of data lines to clock in
412         :param bool value_when_pressed: ``True`` if the pin reads high when the key is pressed.
413           ``False`` if the pin reads low (is grounded) when the key is pressed.
414         :param float interval: Scan keys no more often than ``interval`` to allow for debouncing.
415           ``interval`` is in float seconds. The default is 0.020 (20 msecs).
416         :param int max_events: maximum size of `events` `EventQueue`:
417           maximum number of key transition events that are saved.
418           Must be >= 1.
419           If a new event arrives when the queue is full, the oldest event is discarded.
420         """
421         clock_dio = digitalio.DigitalInOut(clock)
422         clock_dio.switch_to_output(
423             value=False, drive_mode=digitalio.DriveMode.PUSH_PULL
424         )
425         self._clock = clock_dio
426
427         data_dio = digitalio.DigitalInOut(data)
428         data_dio.switch_to_input()
429         self._data = data_dio
430
431         latch_dio = digitalio.DigitalInOut(latch)
432         latch_dio.switch_to_output(value=True, drive_mode=digitalio.DriveMode.PUSH_PULL)
433         self._latch = latch_dio
434         self._value_to_latch = value_to_latch
435
436         self._currently_pressed = [False] * key_count
437         self._previously_pressed = [False] * key_count
438         self._value_when_pressed = value_when_pressed
439         self._key_count = key_count
440
441         super().__init__(interval, max_events, self._keypad_shiftregisterkeys_scan)
442
443     def deinit(self):
444         """Stop scanning and release the pins."""
445         super().deinit()
446         self._clock.deinit()
447         self._data.deinit()
448         self._latch.deinit()
449
450     def reset(self):
451         """
452         Reset the internal state of the scanner to assume that all keys are now released.
453         Any key that is already pressed at the time of this call will therefore immediately cause
454         a new key-pressed event to occur.
455         """
456         self._currently_pressed = self._previously_pressed = [False] * self._key_count
457
458     @property
459     def key_count(self):
460         """The number of keys that are being scanned. (read-only)"""
461         return self._key_count
462
463     @property
464     def events(self):
465         """The ``EventQueue`` associated with this `Keys` object. (read-only)"""
466         return self._events
467
468     def _keypad_shiftregisterkeys_scan(self):
469         self._latch.value = self._value_to_latch
470         for key_number in range(self._key_count):
471             self._clock.value = False
472             self._previously_pressed[key_number] = self._currently_pressed[key_number]
473             current = self._data.value == self._value_when_pressed
474             self._currently_pressed[key_number] = current
475             self._clock.value = True
476             if self._previously_pressed[key_number] != current:
477                 self._events.keypad_eventqueue_record(key_number, current)
478
479         self._latch.value = not self._value_to_latch