2 `keypad` - Support for scanning keys and key matrices
3 ===========================================================
4 See `CircuitPython:keypad` in CircuitPython for more details.
6 * Author(s): Melissa LeBlanc-Williams
10 __version__ = "0.0.0-auto.0"
11 __repo__ = "https://github.com/adafruit/Adafruit_Blinka.git"
16 from collections import deque
21 """A key transition event."""
23 def __init__(self, key_number=0, pressed=True):
25 Create a key transition event, which reports a key-pressed or key-released transition.
27 :param int key_number: the key number
28 :param bool pressed: ``True`` if the key was pressed; ``False`` if it was released.
30 self._key_number = key_number
31 self._pressed = pressed
36 return self._key_number
41 ``True`` if the event represents a key down (pressed) transition.
42 The opposite of `released`.
49 ``True`` if the event represents a key up (released) transition.
50 The opposite of `pressed`.
52 return not self._pressed
54 def __eq__(self, other):
56 Two `Event` objects are equal if their `key_number`
57 and `pressed`/`released` values are equal.
59 return self.key_number == other.key_number and self.pressed == other.pressed
62 """Returns a hash for the `Event`, so it can be used in dictionaries, etc.."""
63 return hash(self._key_number)
66 """Return a textual representation of the object"""
67 return "<Event: key_number {} {}>".format(
68 self.key_number, "pressed" if self._pressed else "released"
74 A queue of `Event` objects, filled by a `keypad` scanner such as `Keys` or `KeyMatrix`.
76 You cannot create an instance of `_EventQueue` directly. Each scanner creates an
77 instance when it is created.
80 def __init__(self, max_events):
81 self._events = deque([], max_events)
82 self._overflowed = False
86 Return the next key transition event. Return ``None`` if no events are pending.
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``.
93 :return: the next queued key transition `Event`
94 :rtype: Optional[Event]
98 return self._events.popleft()
100 def get_into(self, event):
101 """Store the next key transition event in the supplied event, if available,
103 If there are no queued events, do not touch ``event`` and return ``False``.
105 The advantage of this method over ``get()`` is that it does not allocate storage.
106 Instead you can reuse an existing ``Event`` object.
108 Note that the queue size is limited; see ``max_events`` in the constructor of
109 a scanner such as `Keys` or `KeyMatrix`.
111 :return ``True`` if an event was available and stored, ``False`` if not.
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
125 Clear any queued key transition events. Also sets `overflowed` to ``False``.
128 self._overflowed = False
131 """``True`` if `len()` is greater than zero.
132 This is an easy way to check if the queue is empty.
134 return len(self._events) > 0
137 """Return the number of events currently in the queue. Used to implement ``len()``."""
138 return len(self._events)
141 def overflowed(self):
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()`.
146 return self._overflowed
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
153 self._events.append(Event(key_number, current))
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()
167 """The EventQueue associated with this Keys object. (read-only)"""
172 if self._scan_thread.is_alive():
173 self._scan_thread.join()
176 """No-op used by Context Managers."""
179 def __exit__(self, exception_type, exception_value, traceback):
181 Automatically deinitializes when exiting a context. See
182 :ref:`lifetime-and-contextmanagers` for more info.
186 def _scanning_loop(self):
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()
195 class Keys(_KeysBase):
196 """Manage a set of independent keys."""
199 self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
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.
205 An `EventQueue` is created when this object is created and is available in the
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
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.
226 If a new event arrives when the queue is full, the oldest event is discarded.
228 self._digitalinouts = []
230 dio = digitalio.DigitalInOut(pin)
233 digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
235 self._digitalinouts.append(dio)
237 self._currently_pressed = [False] * len(pins)
238 self._previously_pressed = [False] * len(pins)
239 self._value_when_pressed = value_when_pressed
241 super().__init__(interval, max_events, self._keypad_keys_scan)
244 """Stop scanning and release the pins."""
246 for dio in self._digitalinouts:
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.
254 self._currently_pressed = self._previously_pressed = [False] * self.key_count
258 """The number of keys that are being scanned. (read-only)"""
259 return len(self._digitalinouts)
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)
270 class KeyMatrix(_KeysBase):
271 """Manage a 2D matrix of keys with row and column pins."""
273 # pylint: disable=too-many-arguments
278 columns_to_anodes=True,
283 Create a `Keys` object that will scan the key matrix attached to the given row and
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.
288 The keys are numbered sequentially from zero. A key number can be computed
289 by ``row * len(column_pins) + column``.
291 An `EventQueue` is created when this object is created and is available in the `events`
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.
305 If a new event arrives when the queue is full, the oldest event is discarded.
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)
313 self._row_digitalinouts.append(row_dio)
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)
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
326 super().__init__(interval, max_events, self._keypad_keymatrix_scan)
328 # pylint: enable=too-many-arguments
332 """The number of keys that are being scanned. (read-only)"""
333 return len(self._row_digitalinouts) * len(self._column_digitalinouts)
336 """Stop scanning and release the pins."""
338 for row_dio in self._row_digitalinouts:
340 for col_dio in self._column_digitalinouts:
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.
349 self._previously_pressed = self._currently_pressed = [False] * self.key_count
351 def _row_column_to_key_number(self, row, column):
352 return row * len(self._column_digitalinouts) + column
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,
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[
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(
373 if self._columns_to_anodes
374 else digitalio.Pull.DOWN
379 class ShiftRegisterKeys(_KeysBase):
380 """Manage a set of keys attached to an incoming shift register."""
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.
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.
402 An `EventQueue` is created when this object is created and is available in the
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
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.
424 If a new event arrives when the queue is full, the oldest event is discarded.
426 clock_dio = digitalio.DigitalInOut(clock)
427 clock_dio.switch_to_output(
428 value=False, drive_mode=digitalio.DriveMode.PUSH_PULL
430 self._clock = clock_dio
432 data_dio = digitalio.DigitalInOut(data)
433 data_dio.switch_to_input()
434 self._data = data_dio
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
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
446 super().__init__(interval, max_events, self._keypad_shiftregisterkeys_scan)
449 """Stop scanning and release the pins."""
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.
461 self._currently_pressed = self._previously_pressed = [False] * self._key_count
465 """The number of keys that are being scanned. (read-only)"""
466 return self._key_count
470 """The ``EventQueue`` associated with this `Keys` object. (read-only)"""
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)
484 self._latch.value = not self._value_to_latch