diff --git a/NS.py b/NS.py index ccc6a6e..722bc72 100644 --- a/NS.py +++ b/NS.py @@ -32,6 +32,7 @@ from pygame.draw import aalines, lines from pygame.gfxdraw import aapolygon, arc, polygon, aaellipse, ellipse, filled_ellipse, filled_circle from pygame.locals import * +import gpio from lib.pgfw.pgfw.Game import Game from lib.pgfw.pgfw.GameChild import GameChild from lib.pgfw.pgfw.Sprite import Sprite, RainbowSprite, BlinkingSprite @@ -146,11 +147,14 @@ class NS(Game, Animation): # Specify possible arguments and parse the command line. If the -h flag is passed, the argparse library will print a # help message and end the program. parser = argparse.ArgumentParser() - parser.add_argument("--minimize-load-time", action="store_true") + parser.add_argument("--minimize-load-time", action="store_true", help="Disable some graphics loading and effects generation") parser.add_argument("--serial-port") parser.add_argument("--audio-buffer-size", type=int, default=1024) parser.add_argument("--list-serial-ports", action="store_true") - parser.add_argument("--no-serial", action="store_true") + parser.add_argument("--no-serial", action="store_true", help="Force serial (Arduino) mode off.") + parser.add_argument( + "--pi", action="store_true", + help="Force to read input from the GPIO pins of a Raspberry Pi. Must be running on Raspberry Pi. Forces --no-serial.") parser.add_argument("--show-config", action="store_true") arguments = parser.parse_known_args()[0] @@ -221,19 +225,37 @@ class NS(Game, Animation): if arguments.no_serial: self.get_configuration().set("input", "serial", False) + # Apply the pi flag from the command line if requested. This takes precedence over Arduino, so even if serial is enabled, + # force to no serial mode. + if arguments.pi: + self.get_configuration().set("input", "pi", True) + if get_configuration("input", "serial"): + print("Pi mode was requested, so forcing serial (Arduino) mode to off") + self.get_configuration().set("input", "serial", False) + # Print the configuration if requested on the command line if arguments.show_config: print(self.get_configuration()) - # Initialize the serial reader and launch a thread for reading from the serial port - if self.serial_enabled(): + # init Pi + if self.pi_enabled(): + + # Initialize GPIO interface + gpio.initialize_gpio() + + # init Arduino + elif self.serial_enabled(): + + # Initialize the serial reader and launch a thread for reading from the serial port from serial import Serial, SerialException from serial.tools import list_ports + # If a list of serial ports was requested, print detected ports and exit. if arguments.list_serial_ports: for port in list_ports.comports(): print(f"Detected serial port: {port.device}") exit() + # Open the port specified by the configuration or command line if it is found. If the specified port is not # found, open the first found serial port. If no serial ports are found, raise an exception. requested_port = self.get_configuration("input", "arduino-port") @@ -247,6 +269,8 @@ class NS(Game, Animation): self.serial_kill = False self.serial_data = 0 self.reset_arduino() + + # Launch a separate thread for reading serial data self.serial_thread = Thread(target=self.read_serial) self.serial_thread.start() @@ -299,6 +323,9 @@ class NS(Game, Animation): clear() + def pi_enabled(self): + return self.get_configuration("input", "pi") + def serial_enabled(self): return self.get_configuration("input", "serial") @@ -351,6 +378,21 @@ class NS(Game, Animation): if light.pressed: self.idle_elapsed = 0 + def apply_gpio(self): + """ + Check the connection status of the GPIO pins and turn on the appropriate light objects (the pads). + """ + # Get the active (a.k.a. pressed) state of each GPIO pin (a.k.a. pad) + activity = gpio.activity() + + for light_id in activity.keys(): + # The pressed state is set to the activity state + self.platform.lights[light_id].pressed = activity[light_id] + + # Reset idle timer if a light is detected as pressed + if activity[light_id]: + self.idle_elapsed = 0 + def reset(self, leave_wipe_running=False): self.idle_elapsed = 0 self.suppressing_input = False @@ -450,10 +492,22 @@ class NS(Game, Animation): def update(self): Animation.update(self) last_frame_duration = self.time_filter.get_last_frame_duration() - if self.serial_enabled(): + + # Apply controller input to light (pad) states from either Pi or Arduino if applicable + if self.pi_enabled(): + + # Translate Raspberry Pi GPIO state into pad states + self.apply_gpio() + + elif self.serial_enabled(): + + # Translate the most recent serial data, being provided by serial/serial2/serial2.ino, into pad states self.apply_serial() + + # Handle auto reset of the Arduino for stablizing serial data if self.title.active or self.ending.active or self.dialogue.active: self.no_reset_elapsed += last_frame_duration + # If we received good input, reset the auto reset timer if 0b11 <= self.serial_data <= 0b1100: self.no_reset_elapsed = 0 @@ -461,6 +515,7 @@ class NS(Game, Animation): print("auto arduino reset triggered") self.reset_arduino() self.no_reset_elapsed = 0 + self.title.update() self.level_select.update() self.ending.update() diff --git a/config b/config index 923d2d8..209f6e0 100644 --- a/config +++ b/config @@ -60,7 +60,8 @@ volume = 1.0 [input] buffer = 0 arduino-port = /dev/ttyACM0 -serial = True +serial = False +pi = True confirm-quit = False [time] diff --git a/gpio_test.py b/gpio.py similarity index 57% rename from gpio_test.py rename to gpio.py index e8d01c2..55add18 100644 --- a/gpio_test.py +++ b/gpio.py @@ -6,9 +6,10 @@ # # Full open source code is available at . # -# This is a utility script for testing if the GPIO interface of a Raspberry Pi is working -# with the four input wires, which correspond to the four metal floor pads in the standard -# Scrapeboard build. +# This module can be imported and used to check on the status of connections between four GPIO +# inputs on the Raspberry Pi for detecting the pads in Scrapeboard. +# +# When run as a script, it prints connections detected between the input GPIO pins. import time, itertools import RPi.GPIO as GPIO @@ -22,6 +23,17 @@ pins = { LSW: 23 } +def initialize_gpio(): + """ + Set pin numbering mode to GPIO and initialize all pins to input pullup. + """ + # Use GPIO numbering + GPIO.setmode(GPIO.BCM) + + # Set all pins to pullup + for pin_id, pin in pins.items(): + GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) + def connection2(pin_a, pin_b): """ Set `pin_a` to output a low signal, and set every other pin to pullup. If `pin_b` reads a low signal even though @@ -55,21 +67,46 @@ def connection(pin_a, pin_b): def connections(): """ - Look at all six possible connections between the four pins and print a message if one is found connected. + Look at all six possible connections between the four pins and return the state of each as a single dict. + + @return A dict with one entry per combination of pins, the key is a tuple of pin IDs, and the value is the state of connection """ + state = {} for pin_id_a, pin_id_b in itertools.combinations(pins, 2): if connection(pins[pin_id_a], pins[pin_id_b]): - print(f"{pin_id_a} ({pins[pin_id_a]}) <-> {pin_id_b} ({pins[pin_id_b]})") + state[(pin_id_a, pin_id_b)] = True + else: + state[(pin_id_a, pin_id_b)] = False + return state + +def activity(): + """ + Look at all six possible connections between the four pins, and if a pin appears in any active connection, consider it active. + Return the active state of all four pins as a dict. + + @return A dict with one entry per pin. The key is the pin ID, and the value is the state of whether it's active (a.k.a. pressed) + """ + # Create a dict of pins all at False + state = pins.copy() + for pin in state.keys(): + state[pin] = False + + # Check all six combinations. If a pin appears in a connection, update its state to True. + for pin_id_a, pin_id_b in itertools.combinations(pins, 2): + if connection(pins[pin_id_a], pins[pin_id_b]): + state[pin_id_a] = True + state[pin_id_b] = True + + return state if __name__ == "__main__": - # Use GPIO numbering - GPIO.setmode(GPIO.BCM) - - # Set all pins to pullup - for pin_id, pin in pins.items(): - GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) - + initialize_gpio() + while True: # Try all connections once each frame - connections() + for connection_ids, connection_state in connections(): + # Only print connected combinations of pins + if connection_state: + pin_id_a, pin_id_b = connection_ids + print(f"{pin_id_a} ({pins[pin_id_a]}) <-> {pin_id_b} ({pins[pin_id_b]})") time.sleep(0.1)