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
11 from collections import deque
16 """A key transition event."""
18 def __init__(self, key_number=0, pressed=True):
20 Create a key transition event, which reports a key-pressed or key-released transition.
22 :param int key_number: the key number
23 :param bool pressed: ``True`` if the key was pressed; ``False`` if it was released.
25 self._key_number = key_number
26 self._pressed = pressed
31 return self._key_number
36 ``True`` if the event represents a key down (pressed) transition.
37 The opposite of `released`.
44 ``True`` if the event represents a key up (released) transition.
45 The opposite of `pressed`.
47 return not self._pressed
49 def __eq__(self, other):
51 Two `Event` objects are equal if their `key_number`
52 and `pressed`/`released` values are equal.
54 return self.key_number == other.key_number and self.pressed == other.pressed
57 """Returns a hash for the `Event`, so it can be used in dictionaries, etc.."""
58 return hash(self._key_number)
61 """Return a textual representation of the object"""
62 return "<Event: key_number {} {}>".format(
63 self.key_number, "pressed" if self._pressed else "released"
69 A queue of `Event` objects, filled by a `keypad` scanner such as `Keys` or `KeyMatrix`.
71 You cannot create an instance of `_EventQueue` directly. Each scanner creates an
72 instance when it is created.
75 def __init__(self, max_events):
76 self._events = deque([], max_events)
77 self._overflowed = False
81 Return the next key transition event. Return ``None`` if no events are pending.
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``.
88 :return: the next queued key transition `Event`
89 :rtype: Optional[Event]
93 return self._events.popleft()
95 def get_into(self, event):
96 """Store the next key transition event in the supplied event, if available,
98 If there are no queued events, do not touch ``event`` and return ``False``.
100 The advantage of this method over ``get()`` is that it does not allocate storage.
101 Instead you can reuse an existing ``Event`` object.
103 Note that the queue size is limited; see ``max_events`` in the constructor of
104 a scanner such as `Keys` or `KeyMatrix`.
106 :return ``True`` if an event was available and stored, ``False`` if not.
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
120 Clear any queued key transition events. Also sets `overflowed` to ``False``.
123 self._overflowed = False
126 """``True`` if `len()` is greater than zero.
127 This is an easy way to check if the queue is empty.
129 return len(self._events) > 0
132 """Return the number of events currently in the queue. Used to implement ``len()``."""
133 return len(self._events)
136 def overflowed(self):
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()`.
141 return self._overflowed
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
148 self._events.append(Event(key_number, current))
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()
162 """The EventQueue associated with this Keys object. (read-only)"""
167 if self._scan_thread.is_alive():
168 self._scan_thread.join()
171 """No-op used by Context Managers."""
174 def __exit__(self, exception_type, exception_value, traceback):
176 Automatically deinitializes when exiting a context. See
177 :ref:`lifetime-and-contextmanagers` for more info.
181 def _scanning_loop(self):
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()
190 class Keys(_KeysBase):
191 """Manage a set of independent keys."""
194 self, pins, *, value_when_pressed, pull=True, interval=0.02, max_events=64
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.
200 An `EventQueue` is created when this object is created and is available in the
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
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.
221 If a new event arrives when the queue is full, the oldest event is discarded.
223 self._digitalinouts = []
225 dio = digitalio.DigitalInOut(pin)
228 digitalio.Pull.DOWN if value_when_pressed else digitalio.Pull.UP
230 self._digitalinouts.append(dio)
232 self._currently_pressed = [False] * len(pins)
233 self._previously_pressed = [False] * len(pins)
234 self._value_when_pressed = value_when_pressed
236 super().__init__(interval, max_events, self._keypad_keys_scan)
239 """Stop scanning and release the pins."""
241 for dio in self._digitalinouts:
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.
249 self._currently_pressed = self._previously_pressed = [False] * self.key_count
253 """The number of keys that are being scanned. (read-only)"""
254 return len(self._digitalinouts)
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)
265 class KeyMatrix(_KeysBase):
266 """Manage a 2D matrix of keys with row and column pins."""
268 # pylint: disable=too-many-arguments
273 columns_to_anodes=True,
278 Create a `Keys` object that will scan the key matrix attached to the given row and
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.
283 The keys are numbered sequentially from zero. A key number can be computed
284 by ``row * len(column_pins) + column``.
286 An `EventQueue` is created when this object is created and is available in the `events`
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.
300 If a new event arrives when the queue is full, the oldest event is discarded.
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)
308 self._row_digitalinouts.append(row_dio)
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)
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
321 super().__init__(interval, max_events, self._keypad_keymatrix_scan)
323 # pylint: enable=too-many-arguments
327 """The number of keys that are being scanned. (read-only)"""
328 return len(self._row_digitalinouts) * len(self._column_digitalinouts)
331 """Stop scanning and release the pins."""
333 for row_dio in self._row_digitalinouts:
335 for col_dio in self._column_digitalinouts:
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.
344 self._previously_pressed = self._currently_pressed = [False] * self.key_count
346 def _row_column_to_key_number(self, row, column):
347 return row * len(self._column_digitalinouts) + column
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,
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[
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(
368 if self._columns_to_anodes
369 else digitalio.Pull.DOWN
374 class ShiftRegisterKeys(_KeysBase):
375 """Manage a set of keys attached to an incoming shift register."""
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.
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.
397 An `EventQueue` is created when this object is created and is available in the
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
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.
419 If a new event arrives when the queue is full, the oldest event is discarded.
421 clock_dio = digitalio.DigitalInOut(clock)
422 clock_dio.switch_to_output(
423 value=False, drive_mode=digitalio.DriveMode.PUSH_PULL
425 self._clock = clock_dio
427 data_dio = digitalio.DigitalInOut(data)
428 data_dio.switch_to_input()
429 self._data = data_dio
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
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
441 super().__init__(interval, max_events, self._keypad_shiftregisterkeys_scan)
444 """Stop scanning and release the pins."""
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.
456 self._currently_pressed = self._previously_pressed = [False] * self._key_count
460 """The number of keys that are being scanned. (read-only)"""
461 return self._key_count
465 """The ``EventQueue`` associated with this `Keys` object. (read-only)"""
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)
479 self._latch.value = not self._value_to_latch