Setup first approach on how to handle membran keypad. #2
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