Setup first approach on how to handle membran keypad.
Provides class with external readable buffer; pin inputs are IRQ FALLING based. Handler can be overwritten, keypad size is adjustable and character map can be provided.
This commit is contained in:
parent
b96c6b6974
commit
9d748f0ab8
256
src/input/ukeypad.py
Normal file
256
src/input/ukeypad.py
Normal file
@ -0,0 +1,256 @@
|
||||
import machine
|
||||
import utime
|
||||
|
||||
|
||||
class membrane:
|
||||
"""Class for interpreting input from a membran-like keypad,
|
||||
the default is a 4x4 matrix array membrane keypad.
|
||||
The layout of key bindings and size (i.e. 4x3 alternative)
|
||||
can be customized.
|
||||
Also the Callback/Handler can be overwritten if needed.
|
||||
|
||||
In default case, the handler pushes characters into an
|
||||
internal queue-like list/buffer. These characters can be
|
||||
returned; default interactions like len, indexing and string
|
||||
representation are done on this buffer.
|
||||
|
||||
Please keep in mind, that characters are just pushed onto that list.
|
||||
In order to prevent memory leaking on a pico by randomly pressing
|
||||
buttons, you should clear the buffer before waiting for sensible
|
||||
input and afterwards wipe your input characters.
|
||||
"""
|
||||
@classmethod
|
||||
def _validate_input(cls, characters, rows, cols):
|
||||
"""Validates input data to ensure, that all pins are related
|
||||
to characters, i.e. dimension of keymap fits the number of
|
||||
row pins and column pins.
|
||||
If there is a problem, a ValueError will be raised.
|
||||
|
||||
Keyword arguments:
|
||||
characters -- keypad character map, maps row/col pins
|
||||
to printable characters
|
||||
rows -- pins that correspond to the rows of the keypad
|
||||
cols -- pins that correspond to the columns of the keypad
|
||||
"""
|
||||
if not isinstance(rows, tuple) and not isinstance(rows, list):
|
||||
raise ValueError(
|
||||
"Row pins need to be either type LIST or TUPLE."
|
||||
)
|
||||
if not isinstance(cols, tuple) and not isinstance(cols, list):
|
||||
raise ValueError(
|
||||
"Column pins need to be either type LIST or TUPLE."
|
||||
)
|
||||
if (
|
||||
not isinstance(characters, tuple) and
|
||||
not isinstance(characters, list)
|
||||
):
|
||||
raise ValueError(
|
||||
"Keypad Characters need to be either type LIST or "
|
||||
"TUPLE of LIST or TUPLE."
|
||||
)
|
||||
if any(
|
||||
not isinstance(chrs, tuple) and not isinstance(chrs, list)
|
||||
for chrs in characters
|
||||
):
|
||||
raise ValueError(
|
||||
"Keypad Characters need to be either type LIST or "
|
||||
"TUPLE of LIST or TUPLE."
|
||||
)
|
||||
if len(characters) < len(rows):
|
||||
raise ValueError(
|
||||
f"Number of row pins ({len(rows)}) is beyond "
|
||||
"length of first dimension of "
|
||||
"character pad."
|
||||
)
|
||||
if any(len(chrs) < len(cols) for chrs in characters):
|
||||
raise ValueError(
|
||||
f"Number of column pins ({len(cols)}) is beyond "
|
||||
"length of second dimension "
|
||||
"of character pad."
|
||||
)
|
||||
|
||||
DEFAULT_KEYPAD = (
|
||||
('1', '2', '3', 'A'),
|
||||
('4', '5', '6', 'B'),
|
||||
('7', '8', '9', 'C'),
|
||||
('*', '0', '#', 'D')
|
||||
)
|
||||
DEFAULT_ROW_PINS = (2, 3, 4, 5)
|
||||
DEFAULT_COL_PINS = (6, 7, 8, 9)
|
||||
PUD_UP_ACTIVE_STATE = 0
|
||||
DEFAULT_BUFFER_TIME = 0.1
|
||||
|
||||
def __init__(
|
||||
self, callback=None,
|
||||
row_pins=DEFAULT_ROW_PINS, col_pins=DEFAULT_COL_PINS,
|
||||
characters=DEFAULT_KEYPAD, buffer_time=DEFAULT_BUFFER_TIME
|
||||
):
|
||||
"""Constructor to initialise the keypad membrane.
|
||||
|
||||
Keyword arguments:
|
||||
callback -- Function that is called when a butten is
|
||||
pressed (IRQ FALLING)
|
||||
row_pins -- list of pins corresponding to the rows of the keypad
|
||||
col_pins -- list of pins corresponding to the columns of the keypad
|
||||
characters -- keypad character map, maps row/col pins to
|
||||
printable characters
|
||||
buffer_time -- time to wait in order to validate an input
|
||||
and not a button issue.
|
||||
"""
|
||||
self._validate_input(characters, row_pins, col_pins)
|
||||
self._characters = characters
|
||||
self._callback = (
|
||||
callback
|
||||
if callback is not None
|
||||
else
|
||||
self._default_callback
|
||||
)
|
||||
self._row_pins = row_pins
|
||||
self._col_pins = col_pins
|
||||
self._size = (len(self._row_pins), len(self._col_pins))
|
||||
self._pins_row = [
|
||||
machine.Pin(pin, mode=machine.Pin.OUT)
|
||||
for pin in self._row_pins
|
||||
]
|
||||
self._pins_col = [
|
||||
machine.Pin(pin, mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
|
||||
for pin in self._col_pins
|
||||
]
|
||||
self._buffer = []
|
||||
self._buffer_time = buffer_time
|
||||
self._mutex = False
|
||||
self._reset()
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Direct access to an entry of the buffer, i.e. the n-th byte.
|
||||
|
||||
Keyword arguments:
|
||||
index -- Position to retrieve the input from the buffer
|
||||
"""
|
||||
return self._buffer[index]
|
||||
|
||||
def __delitem__(self, index):
|
||||
"""Removes data from the buffer at a certain position.
|
||||
|
||||
Keyword arguments:
|
||||
index -- Position to delete the data from the buffer
|
||||
"""
|
||||
del self._buffer[index]
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the current length/size of the buffer."""
|
||||
return len(self._buffer)
|
||||
|
||||
def __contains__(self, item):
|
||||
"""Allows checking if a certain character/item is withing the buffer
|
||||
at the moment.
|
||||
|
||||
Keyword arguments:
|
||||
item -- The item to be checked if it is part of the buffer
|
||||
|
||||
Returns boolean if the item is contained by the buffer
|
||||
"""
|
||||
return item in self._buffer
|
||||
|
||||
def __str__(self):
|
||||
"""Returns the current content of the buffer."""
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
"""Returns the current content of the buffer."""
|
||||
return "".join(map(str, self._buffer))
|
||||
|
||||
def _reset(self):
|
||||
"""Reset function to re-init row pins into Pin.OUT state
|
||||
and re-init the column pins into Pin.IN with interrup
|
||||
handling Pin.IRQ_FALLING and reattaching the handler.
|
||||
Resets the mutex to prevent multiple buttons together pressed.
|
||||
"""
|
||||
for pin in self._pins_row:
|
||||
pin.init(mode=machine.Pin.OUT, pull=None)
|
||||
for pin in self._pins_col:
|
||||
pin.init(mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
|
||||
pin.irq(trigger=machine.Pin.IRQ_FALLING, handler=self._callback)
|
||||
self._mutex = False
|
||||
|
||||
def _default_callback(self, cpin):
|
||||
"""Default callback function that is triggered as interrupt
|
||||
handler when pressing a button.
|
||||
Based on the column pin of the pressed button, this pin will
|
||||
be made to an OUT pin and all rows become temporarly to
|
||||
IN pins to identify the row and column of the pressed button
|
||||
on the keypad matrix membrane.
|
||||
With the identified row and column, the corresponding character
|
||||
is pushed onto the internal buffer/list, so that external
|
||||
components can read from the buffer.
|
||||
|
||||
Keyword arguments:
|
||||
cpin -- The machine.Pin object of the triggered pin.
|
||||
"""
|
||||
utime.sleep(self._buffer_time)
|
||||
if (
|
||||
cpin not in self._pins_col or
|
||||
cpin.value() != self.PUD_UP_ACTIVE_STATE
|
||||
):
|
||||
return
|
||||
col = self._pins_col.index(cpin)
|
||||
if not self._mutex:
|
||||
# Do not allow multiple key pressed together
|
||||
self._mutex = True
|
||||
|
||||
# switch function of rows and cols, first
|
||||
# we got the column, now we need to find where
|
||||
# the row is pressed.
|
||||
for rpin in self._pins_row:
|
||||
rpin.init(mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
|
||||
|
||||
# Make selected column to pin state as rows before.
|
||||
cpin.irq(handler=None)
|
||||
cpin.init(mode=machine.Pin.OUT, pull=None)
|
||||
|
||||
values = [rpin.value() for rpin in self._pins_row]
|
||||
if self.PUD_UP_ACTIVE_STATE not in values:
|
||||
self._reset()
|
||||
return
|
||||
row = values.index(self.PUD_UP_ACTIVE_STATE)
|
||||
self._buffer.append(self._characters[row][col])
|
||||
self._reset()
|
||||
|
||||
def clear(self):
|
||||
"""Flushes the buffer list in order to clean up.
|
||||
Usefull to prevent buffer overflow from time to time.
|
||||
"""
|
||||
self._buffer.clear()
|
||||
|
||||
def pop(self, index=0):
|
||||
"""Returns and removes the data from a certain position within
|
||||
the buffer.
|
||||
|
||||
Keyword arguments:
|
||||
index -- The position to return and remove data from (default 0)
|
||||
|
||||
Returns the data from the position.
|
||||
"""
|
||||
return self._buffer.pop(index)
|
||||
|
||||
def pop_all(self):
|
||||
"""Returns the entire buffer and cleans it.
|
||||
|
||||
Returns the entire buffer data.
|
||||
"""
|
||||
data = list(self._buffer)
|
||||
self.clear()
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
membrane_keypad = membrane()
|
||||
pin_size = 12
|
||||
|
||||
while len(membrane_keypad) < pin_size:
|
||||
utime.sleep(membrane_keypad.DEFAULT_BUFFER_TIME)
|
||||
print(f"\r{'*'*len(membrane_keypad)}\t", end="")
|
||||
print(f"\nInputData: {membrane_keypad}")
|
||||
print(f"'#' is contained in buffer: {'#' in membrane_keypad}")
|
||||
print(f"'*' is contained in buffer: {'*' in membrane_keypad}")
|
||||
print(f"Popped data is: {membrane_keypad.pop_all()}")
|
Loading…
Reference in New Issue
Block a user