diff --git a/adafruit_circuitplayground/circuit_playground_base.py b/adafruit_circuitplayground/circuit_playground_base.py index 69b5188..5bd525b 100755 --- a/adafruit_circuitplayground/circuit_playground_base.py +++ b/adafruit_circuitplayground/circuit_playground_base.py @@ -50,6 +50,95 @@ def light(self): return self._photocell.value * 330 // (2 ** 16) +class InterableInput: + """Wrapper class for iterable touchpad inputs + + :param name_list: A list of pin names to initialize as touchpad inputs + """ + + def __init__(self, name_list): + self._input_names = name_list + input_pins = [getattr(board, name) for name in name_list] + self._inputs = list(input_pins) + self._current_input = 0 + self._len_inputs = len(name_list) + + def __iter__(self): + for input_tio in self._inputs: + yield self._auto_convert_tio(input_tio) + + def __getitem__(self, index): + input_name = self._use_str_name(index) + for name, tio in zip(self._input_names, self._inputs): + if name == input_name: + return self._auto_convert_tio(tio) + raise ValueError( + "The given padname is either not a valid touchpad or was deinitialized" + ) + + def _auto_convert_tio(self, input_pad): + """Automagically turns an existing pin into a touchio.TouchIn as needed, and + also returns it + """ + + if not isinstance(input_pad, touchio.TouchIn): + name_index = self._inputs.index(input_pad) + self._inputs[name_index] = touchio.TouchIn(self._inputs[name_index]) + input_pad = self._inputs[name_index] + return input_pad + + @staticmethod + def _use_str_name(value): + """Converts an index into the pin name if needed""" + + if value in (7, "A7", "TX"): + return "TX" + if isinstance(value, int): + if value not in range(1, 7): + raise ValueError("Pins available as touchpads are 1-7") + return "A" + str(value) + if isinstance(value, str): + if not value.startswith("A") and int(value[1:]) not in range(1, 7): + raise ValueError("Pins available as touchpads are A1-A6 and A7/TX") + return value + raise TypeError( + "Iterable inputs can only be accessed by int index or analog string names" + ) + + def deinit_input(self, input_name): + """Deinitialize a given pin as a touchpad input, freeing up the memory and allowing the + pin to be used as a different type of input + + :param input_name: The name or pad number to be deinitialized + :type input_name: str|int + """ + + input_name = self._use_str_name(input_name) + if input_name in self._input_names: + input_index = self._input_names.index(input_name) + self._input_names.pop(input_index) + selected_tio = self._inputs.pop(input_index) + if isinstance(selected_tio, touchio.TouchIn): + selected_tio.deinit() + + def init_input(self, input_name): + """Initializes a given pin as a touchpad input, if not already + + :param input_name: The name or pad number to be initialized + :type input_name: str|int + """ + + input_name = self._use_str_name(input_name) + if input_name not in self._input_names: + self._inputs.append(getattr(board, input_name)) + self._input_names.append(input_name) + + @property + def names(self): + """Returns the names of all pins currently set up as touchpad inputs""" + return self._input_names + + class CircuitPlaygroundBase: # pylint: disable=too-many-public-methods """Circuit Playground base class.""" @@ -77,22 +166,21 @@ def __init__(self): self._light = Photocell(board.LIGHT) # Define touch: - # Initially, self._touches stores the pin used for a particular touch. When that touch is - # used for the first time, the pin is replaced with the corresponding TouchIn object. - # This saves a little RAM over using a separate read-only pin tuple. - # For example, after `cp.touch_A2`, self._touches is equivalent to: - # [None, board.A1, touchio.TouchIn(board.A2), board.A3, ...] + # Initially, IterableInput stores the pin used for a particular touch. When that touch is + # used for the first time, the stored pin is replaced with the corresponding TouchIn object. + # This saves a little RAM over initializing all pins as inputs immediately. # Slot 0 is not used (A0 is not allowed as a touch pin). - self._touches = [ - None, - board.A1, - board.A2, - board.A3, - board.A4, - board.A5, - board.A6, - board.TX, - ] + self._touches = InterableInput( + [ + "A1", + "A2", + "A3", + "A4", + "A5", + "A6", + "TX", + ] + ) self._touch_threshold_adjustment = 0 # Define acceleration: @@ -138,7 +226,7 @@ def detect_taps(self): @staticmethod def _default_tap_threshold(tap): if ( - "nRF52840" in os.uname().machine + "nRF52840" in os.uname().machine # pylint: disable=no-member ): # If we're on a CPB, use a higher tap threshold return 100 if tap == 1 else 70 @@ -351,13 +439,42 @@ def shake(self, shake_threshold=30): return self._lis3dh.shake(shake_threshold=shake_threshold) def _touch(self, i): - if not isinstance(self._touches[i], touchio.TouchIn): - # First time referenced. Get the pin from the slot for this touch - # and replace it with a TouchIn object for the pin. - self._touches[i] = touchio.TouchIn(self._touches[i]) - self._touches[i].threshold += self._touch_threshold_adjustment return self._touches[i].value + def deinit_touchpad(self, touchpad_pin): + """Deinitializes an input as a touchpad to free it up for use elsewhere + + :param touchpad_pin: The touchpad name or number to deinitialize + :type touchpad_pin: str|int + """ + + self._touches.deinit_input(touchpad_pin) + + def init_touchpad(self, touchpad_pin): + """Initializes a pin as a touchpad input + + :param touchpad_pin: The touchpad name or number to initialize + :type touchpad_pin: str|int + """ + + self._touches.init_input(touchpad_pin) + + @property + def touchpads(self): + """A list of all touchpad names currently set up as touchpad inputs""" + + return self._touches.names + + @property + def touched(self): + """A list of touchpad input names currently registering as being touched""" + + return [ + touch_name + for touch_pad, touch_name in zip(self._touches, self._touches.names) + if touch_pad.value + ] + # We chose these verbose touch_A# names so that beginners could use it without understanding # lists and the capital A to match the pin name. The capitalization is not strictly Python # style, so everywhere we use these names, we whitelist the errors using: diff --git a/examples/circuitplayground_touch_all.py b/examples/circuitplayground_touch_all.py index a8a3b3f..21a5e8a 100644 --- a/examples/circuitplayground_touch_all.py +++ b/examples/circuitplayground_touch_all.py @@ -4,18 +4,12 @@ """This example prints to the serial console when you touch the capacitive touch pads.""" from adafruit_circuitplayground import cp +print("Here are all the possible touchpads:") +print(cp.touchpads) + while True: - if cp.touch_A1: - print("Touched pad A1") - if cp.touch_A2: - print("Touched pad A2") - if cp.touch_A3: - print("Touched pad A3") - if cp.touch_A4: - print("Touched pad A4") - if cp.touch_A5: - print("Touched pad A5") - if cp.touch_A6: - print("Touched pad A6") - if cp.touch_TX: - print("Touched pad TX") + print("Touchpads currently registering a touch:") + print(cp.touched) + + if all(pad in cp.touched for pad in ("A2", "A3", "A4")): + print("This only prints when A2, A3, and A4 are being held at the same time!") diff --git a/examples/circuitplayground_touchpad_changing.py b/examples/circuitplayground_touchpad_changing.py new file mode 100644 index 0000000..dfce19f --- /dev/null +++ b/examples/circuitplayground_touchpad_changing.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021 Alec Delaney +# SPDX-License-Identifier: MIT + +"""This example prints to the serial console when you touch the capacitive touch pads.""" +from adafruit_circuitplayground import cp + +print("Here are all the initially registered touchpads:") +print(cp.touchpads) + +print("You can remove a few if you need those pins:") +cp.deinit_touchpad("A2") +cp.deinit_touchpad("A5") +print(cp.touchpads) + +print("You can also readd them later!") +cp.init_touchpad("A2") +print(cp.touchpads)