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