Adafruit_Spectro_Pi/selector.py
2020-01-25 22:20:34 -08:00

139 lines
5.6 KiB
Python
Executable file

#!/usr/bin/env python
"""
Selector program for Adafruit Spectro. Cycles among various programs/scripts
using the side button.
"""
# Gets code to pass both pylint & pylint3:
# pylint: disable=bad-option-value, useless-object-inheritance, no-member
import os
import subprocess
import time
import argparse
try:
import RPi.GPIO as gpio
except ImportError:
exit("This library requires the RPi.GPIO module\n"
"Install with: sudo pip install RPi.GPIO")
# List of programs to cycle through. First item is launched on startup,
# will run for 15 seconds before automatically advancing to the next.
# All subsequent advancements will only occur with a manual button press
# (even when cycling back to the first program). For each item, there is
# a program name (if ending in ".py", it will be run via the Python
# interpreter, else standalone program) and a True/False argument
# indicating whether the framebuffer-to-matrix utility should be run
# concurrently with that program/script.
PROGRAMS = (
("ip_address.py", False),
("cpu_load.py", False),
("bargraph.py", False),
("arcade_clock.py", False),
("life.py", False),
("gifplay.py", False),
("audio.py", False),
("accel.py", False),
("nextbus_matrix.py", False),
("idle.py", True)) # Nonsense idle script to test fb2matrix.py
# Python version to use with any .py scripts in above list, in case
# version 2 or 3 needs to be forced:
PYTHON = "python3"
# Name of framebuffer-to-matrix script:
FB_TO_MATRIX = "fb2matrix.py"
# Command-line flags passed to above program/scripts and the
# framebuffer-to-matrix utility:
FLAGS = ["--led-cols=64", "--led-rows=32", "--led-slowdown-gpio=4"]
# Additional commands not used here, but you might find useful:
# FLAGS = ["--led-rgb-sequence=rbg", "--led-brightness=50"]
class Selector(object):
"""Selector program for Spectro. Cycles among various programs/scripts
using the side button."""
def __init__(self):
self.mode = 0
self.process = []
self.timeout = 15.0 # First-program timeout (then advances modes)
self.time_start = time.time()
self.gpio = 25
self.parser = argparse.ArgumentParser()
self.args = None
self.parser.add_argument(
"-g", "--gpio", action="store", help="GPIO # for mode selector "
"button (to GND). Default: " + str(self.gpio), default=self.gpio,
type=int)
def run_one(self):
"""Monitor last-launched subprocess until either it exits,
the mode-change button is pressed or the subprocess timeout
has elapsed. Also check for extended button hold and halt
system if necessary."""
while True:
self.process[0].poll() # Sets returncode if process exited
if(self.process[0].returncode or # Signal or script error OR
gpio.input(self.gpio) == 0 or # Button press OR
(self.timeout > 0 and # Timeout
time.time() - self.time_start > self.timeout)):
time_pressed = time.time()
# Try killing process(es), assuming it hasn't choked
# on its own due to a signal or Python script error.
for process in self.process:
try:
process.terminate()
process.wait()
# process.wait() seems a little flaky about
# this, hence the extra while and sleep:
while process.returncode is None:
time.sleep(0.25)
except OSError:
pass # Process already exited (signal or error)
time.sleep(0.1) # Extra button debounce a little
while gpio.input(self.gpio) == 0: # Wait for button release
if time.time() - time_pressed >= 3: # Held a few sec?
os.system("shutdown -h now") # Halt system
while True: # Wait for it
pass
break
time.sleep(0.1)
def run(self):
"""Main loop of Selector program."""
# Handle script-specific command line argument(s):
self.args = self.parser.parse_args()
if self.args.gpio is not None:
self.gpio = self.args.gpio
# GPIO init
gpio.setwarnings(False)
gpio.setmode(gpio.BCM)
gpio.setup(self.gpio, gpio.IN, pull_up_down=gpio.PUD_UP)
while True: # Cycle between programs indefinitely
# Launch new process (from PROGRAMS list, based on self.mode).
# Run as Python script or standalone program, passing FLAGS:
if PROGRAMS[self.mode][0].endswith(".py"):
self.process.append(
subprocess.Popen([PYTHON, PROGRAMS[self.mode][0]] + FLAGS))
else:
self.process.append(
subprocess.Popen([PROGRAMS[self.mode][0]] + FLAGS))
# Optionally launch framebuffer-to-matrix util concurrently
if PROGRAMS[self.mode][1] is True:
self.process.append(
subprocess.Popen([PYTHON, FB_TO_MATRIX] + FLAGS))
self.run_one() # Until button press, timeout, etc.
self.mode += 1 # Advance to next mode
if self.mode >= len(PROGRAMS): # Wrap around to start
self.mode = 0
self.timeout = -1 # Only do timeout case once, at startup
self.process = []
if __name__ == "__main__":
SELECTOR = Selector()
SELECTOR.run()