Skip to content

keypad.Keys and keypad.KeyMatrix are absent #13

Description

@begeistert

No hardware needed: polling over digitalio with debounce. The expensive part is the event queue, which upstream returns as Event objects; with a fixed event buffer, about 120 lines. Without a heap the queue depth has to be fixed at compile time, and the API should say so.

From the read-only audit of the layer against CircuitPython (2026-09-14), Uno-class boards first.


Fixed (Keys; KeyMatrix is not done)

The design, and why the queue holds nothing

A key's stored state moves only when its change is reported, so what is "waiting to be read"
is exactly the set of keys whose pins disagree with it. That is what a queue is for, and it
costs one bit a key instead of a buffer. overflowed is therefore always false and cannot be
otherwise, and max_events is refused rather than accepted and dropped: there is nothing to
size.

CircuitPython scans in the background on a tick. There is no background here, so the scan
happens inside events.get_into() -- the loop a program already writes reads the pins every
time round. interval is refused for the same reason: it is how often the background scan
runs, and here that is how often the program calls.

Measured on an Arduino Uno

The test holds keys down by pulling their pins low and drains the queue three times:

pressed events, in order still waiting after the first
none none 0
key 1 (1, pressed), then none 0
keys 0 and 2 (0, pressed), (2, pressed), then none 1
keys 0, 1, 2 (0), (1), (2) 2

Two deviations, both stated in the module

Keys takes a list of digitalio.DigitalInOut the caller has already made inputs, not a
list of pin names. A list of names has no storage behind it here: the names are compile-time
values and the list cannot be indexed at run time (PyMCU#308).

events.get() returns a new Event and there is no heap to build one on. It is refused
naming get_into(event), which is upstream's own allocation-free call.

Tests

  • pymcu-circuitpython c7cda74 — 10 tests in tests/test_keypad.py.
  • pymcu-avr ea9f73fcompat-cp-keypad, 5 tests. Red before: the module did not exist.

Still open

keypad.KeyMatrix is not written. It scans rows against columns, which needs the row pins to
switch between output and input on every pass, and the same list-of-instances iteration this
one uses would have to drive as well as read. It is a second sitting, not a variation.

One thing the change ran into

The queue owns the pins rather than asking the Keys for them. A list of instances loses its
iterability across one instance hop too many: the identical for p in ... loop was refused
as "not a compile-time iterable" when it reached through self._keys._pins, and compiled
when the list was one hop closer.

2026-09-15 follow-up: Event.timestamp, EventQueue, bool closed; Keys.events stays open

Event.key_number and Event.pressed are now real properties over backing fields (same
shape as Event.released already was), so inspect.getattr_static sees them.
Event.timestamp is a new property, stamped from supervisor.ticks_ms() by
EventQueue.get_into() on every reported transition. The private _EventQueue is renamed
to the public EventQueue, and it gained __bool__ (len(queue) > 0, matching PulseIn's)
to complete its surface against upstream's EventQueue. All four removed from
tests/parity/allowlist.toml.

Keys.events stays a plain instance attribute, tried and reverted as a @property: once
events became a property, keys.events.get_into(event) was refused by the compiler with
"its receiver is not a name bound to an object" -- a property that hands back a ZCA instance
loses it for further method dispatch. That is a compiler limit, not a layer decision, so
keypad.Keys.events stays tracked here rather than moving to a language-side issue of its
own; keypad.KeyMatrix is still the only genuinely unwritten piece of this issue.

pymcu-circuitpython 5ffcc89.

Filed as PyMCU/PyMCU#445 (milestone Classes): a @Property returning a ZCA instance loses it
for further method dispatch, with the minimal reproduction (Owner.q as a property over
Queue, owner.q.bump() refused the same way keys.events.get_into(event) was).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions