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.
257 lines
9.1 KiB
Python
257 lines
9.1 KiB
Python
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()}")
|