Setup first approach on how to handle membran keypad. #2

Merged
lhahn merged 1 commits from keypad into main 2024-11-11 11:52:36 +01:00
Showing only changes of commit 9d748f0ab8 - Show all commits

256
src/input/ukeypad.py Normal file
View 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()}")