Skip to content

Instantly share code, notes, and snippets.

@joshsucher
Created October 14, 2024 21:49
Show Gist options
  • Select an option

  • Save joshsucher/df0801072d5efe0d05192926ef551e6f to your computer and use it in GitHub Desktop.

Select an option

Save joshsucher/df0801072d5efe0d05192926ef551e6f to your computer and use it in GitHub Desktop.
Juggling Neopixels, Pirate Audio DAC hat w/ 1.3" Pi TFT, transistors and smoke generators on a Pi Zero
import time
import random
import subprocess
import os
import math
import board
import neopixel
import threading
import digitalio
import alsaaudio
import RPi.GPIO as GPIO
from adafruit_debouncer import Debouncer
import struct
from PIL import Image
#os.system("sudo /home/alsatest/fbcp-ili9341/build/fbcp-ili9341")
def start_display():
def run_fbcp_ili9341():
command = ["sudo", "/home/alsatest/fbcp-ili9341/build/fbcp-ili9341"]
try:
# Run the command and capture output
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
GPIO.output(13, GPIO.LOW)
# Optionally, you can process the output in real-time
for line in process.stdout:
print("fbcp-ili9341 output:", line.strip())
# Wait for the process to complete
process.wait()
except Exception as e:
print(f"Error running fbcp-ili9341: {e}")
# Create and start the thread
fbcp_thread = threading.Thread(target=run_fbcp_ili9341)
fbcp_thread.daemon = True # Set as daemon so it doesn't prevent program exit
fbcp_thread.start()
photo_folder = '/home/alsatest/family_photos/' # Change this to your folder
ceiling_fan_pin = 26
record_player_pin = 17
rope_lights_pin = 20
ashtray_smoke_pin = 24
van_smoke_pin = 23
lcd_backlight_pin = 13
# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(ceiling_fan_pin, GPIO.OUT)
GPIO.setup(record_player_pin, GPIO.OUT)
GPIO.setup(rope_lights_pin, GPIO.OUT)
GPIO.setup(lcd_backlight_pin, GPIO.OUT)
GPIO.setup(ashtray_smoke_pin, GPIO.OUT, initial=0)
GPIO.setup(van_smoke_pin, GPIO.OUT, initial=0)
# Create PWM objects
ceiling_fan_pwm = GPIO.PWM(ceiling_fan_pin, 100) # 100 Hz frequency
record_player_pwm = GPIO.PWM(record_player_pin, 100)
rope_lights_pwm = GPIO.PWM(rope_lights_pin, 800) # 800 Hz frequency
ashtray_smoke_pwm = GPIO.PWM(ashtray_smoke_pin, 500)
van_smoke_pwm = GPIO.PWM(van_smoke_pin, 1000)
# Start PWM with 0% duty cycle
ceiling_fan_pwm.start(0)
record_player_pwm.start(0)
rope_lights_pwm.start(0)
ashtray_smoke_pwm.start(0)
van_smoke_pwm.start(0)
projector_active = False
van_active = False
music_playing = False
# Define duty cycles (0-100 instead of 0-255)
record_player_duty_cycle = 6 # 15/255 * 100
ceiling_fan_duty_cycle = 9 # 18/255 * 100
rope_lights_duty_cycle = 24 # 60/255 * 100
current_rope_lights_duty_cycle = 0
ashtray_smoke_duty_cycle = 40
van_smoke_duty_cycle = 90
def set_alsa_volume(volume):
"""Sets the volume of the running mplayer process."""
#subprocess.run(['amixer', 'set', 'Master', f'{volume}%'], check=True)
mixer.setvolume(volume)
print(f"Volume set to {volume}%")
# Get the mixer for controlling volume
mixer = alsaaudio.Mixer()
# Get the current volume level
current_volume = mixer.getvolume()[0]
# Rotary Encoder Pins (CLK, DT, SW)
pinCLK = digitalio.DigitalInOut(board.D27) # CLK pin
pinDT = digitalio.DigitalInOut(board.D22) # DT pin
pinSW = digitalio.DigitalInOut(board.D5) # Switch pin
pinCLK.switch_to_input(pull=digitalio.Pull.UP)
pinDT.switch_to_input(pull=digitalio.Pull.UP)
pinSW.switch_to_input(pull=digitalio.Pull.UP)
buttonCLK = Debouncer(pinCLK)
buttonDT = Debouncer(pinDT)
buttonSW = Debouncer(pinSW)
def clamp(n, min, max):
if n < min:
return min
elif n > max:
return max
else:
return n
# Set up NeoPixels
pixel_pin = board.D12 # Choose the pin connected to the NeoPixels (use the correct pin for your setup)
num_pixels = 3 # Number of NeoPixels
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=1.0, auto_write=False)
def flame_colors():
# Generate a random color in the range of a flame (reds, oranges, yellows)
r = random.randint(220, 255)
g = random.randint(60, 90)
b = random.randint(0, 5)
return (r, g, b)
def projector_colors():
# Simulate higher color temperature for projector lens (cooler white/bluish hue)
r = random.randint(220, 255) # Bright white, slight warmth in red
g = random.randint(220, 255) # High green for white balance
b = random.randint(250, 255) # High blue for cool temperature effect
return (r, g, b)
def warm_white_color():
# A warm white color typically has a lower color temperature (~2700K to 3000K)
# This is a balance of red and green with a slight blue component to simulate warm white light
r = 255
g = 170
b = 87
return (r, g, b)
def flicker_van(pixel_index):
global van_active
# Record the start time
start_time = time.time()
while van_active:
# Get the current elapsed time
elapsed_time = time.time() - start_time
# Change the flicker range based on elapsed time
if elapsed_time < 10:
# First 10 seconds: flicker range from 0.1 to 0.2
flicker_brightness = random.uniform(0.1, 0.2)
elif 10 <= elapsed_time < 20:
# From 10 to 20 seconds: flicker range from 0.2 to 0.4
flicker_brightness = random.uniform(0.2, 0.4)
else:
# After 20 seconds: flicker range from 0.3 to 0.6
flicker_brightness = random.uniform(0.3, 0.6)
# Get the flame color
flame_color = flame_colors()
# Scale the color by brightness
r, g, b = [int(c * flicker_brightness) for c in flame_color]
pixels[pixel_index] = (r, g, b)
# Update the NeoPixel
pixels.show()
# Add a small delay to control the flicker rate
time.sleep(random.uniform(0.05, 0.15)) # Short delay to create randomness in the flicker rate
def toggle_van_smoke():
# Turn on the smoke generator
van_smoke_pwm.ChangeDutyCycle(van_smoke_duty_cycle)
print("Van smoke generator ON")
# Set up a timer to turn it off after 30 seconds
def turn_off_van_smoke():
# Turn off the smoke generator (duty cycle 0)
van_smoke_pwm.ChangeDutyCycle(0)
print("Van smoke generator OFF")
# Use threading to schedule the turn-off after 30 seconds
timer = threading.Timer(90.0, turn_off_van_smoke)
timer.start()
def toggle_ashtray_smoke():
# Turn on the smoke generator
ashtray_smoke_pwm.ChangeDutyCycle(ashtray_smoke_duty_cycle)
print("Ashtray smoke generator ON")
# Set up a timer to turn it off after 60 seconds
def turn_off_ashtray_smoke():
# Turn off the smoke generator (duty cycle 0)
ashtray_smoke_pwm.ChangeDutyCycle(0)
print("Ashtray smoke generator OFF")
# Use threading to schedule the turn-off after 60 seconds
timer = threading.Timer(60.0, turn_off_ashtray_smoke)
timer.start()
def flicker_projector(pixel_index):
global projector_active
#projector_active = True
while projector_active:
if random.random() > 0.5:
flicker_brightness = 0.2 # Full brightness
else:
flicker_brightness = 0.1 # Very dim or almost off
projector_color = projector_colors()
# Scale the color by brightness
r, g, b = [int(c * flicker_brightness) for c in projector_color]
pixels[pixel_index] = (r, g, b)
pixels.show()
# Add a small delay to control the flicker rate
time.sleep(0.05) # Adjust this value to change the flicker speed
# Turn off the LED when the flickering stops
pixels[pixel_index] = (0, 0, 0)
pixels.show()
def start_projector(pixel_index):
global projector_active
projector_active = not projector_active
thread = threading.Thread(target=flicker_projector, args=(pixel_index,))
thread.start()
return thread
def start_van(pixel_index):
global van_active
if van_active:
stop_van()
else:
toggle_van_smoke()
van_active = True
thread = threading.Thread(target=flicker_van, args=(pixel_index,))
thread.start()
return thread
def stop_projector():
global projector_active
projector_active = False
clear_pixels(1)
def stop_van():
global van_active
van_active = False
clear_pixels(2)
GPIO.output(van_smoke_pin, GPIO.LOW)
van_smoke_pwm.ChangeDutyCycle(0)
def ceiling_light(pixel_index):
# Set the ceiling light to a warm white color at 50% brightness
ceiling_brightness = 0.7
warm_white = warm_white_color()
# Scale the color by brightness
r, g, b = [int(c * ceiling_brightness) for c in warm_white]
pixels[pixel_index] = (r, g, b)
pixels.show()
def clear_pixels(pixel_index=-1):
# Turn off all NeoPixels
if pixel_index==-1:
for i in range(num_pixels):
pixels[i] = (0, 0, 0)
else:
pixels[pixel_index] = (0, 0, 0)
pixels.show()
def fade_rope_lights(toggle):
global current_rope_lights_duty_cycle
if toggle == 1:
for duty_cycle in range(0, rope_lights_duty_cycle + 1):
rope_lights_pwm.ChangeDutyCycle(duty_cycle)
current_rope_lights_duty_cycle = duty_cycle
time.sleep(0.01)
else:
for duty_cycle in range(current_rope_lights_duty_cycle, 0 - 1, -1):
rope_lights_pwm.ChangeDutyCycle(duty_cycle)
current_rope_lights_duty_cycle = duty_cycle
time.sleep(0.01)
def run_fim_command():
try:
os.system("sudo fim -o fb -a -q -d /dev/fb0 --slideshow 10 --random /home/alsatest/family_photos/*")
except Exception as ex:
print(f"An error occurred in the fim command: {ex}")
def start_slideshow():
try:
global music_playing
music_playing = False
stop_projector()
# Step 1: Kill all mplayer processes
print("Killing any running mplayer processes...")
os.system("sudo killall mplayer > /dev/null 2>&1")
os.system("sudo killall fim > /dev/null 2>&1")
os.system("sudo pkill fbcp-ili9341 > /dev/null 2>&1")
time.sleep(1)
start_display()
#os.system("sudo /home/alsatest/fbcp-ili9341/build/fbcp-ili9341 > /dev/null 2>&1")
time.sleep(1)
ceiling_fan_pwm.ChangeDutyCycle(ceiling_fan_duty_cycle)
record_player_pwm.ChangeDutyCycle(0)
#GPIO.output(record_player_pin, GPIO.LOW)
#GPIO.output(rope_lights_pin, GPIO.LOW)
fade_rope_lights(0)
GPIO.output(ashtray_smoke_pin, GPIO.LOW)
GPIO.output(van_smoke_pin, GPIO.LOW)
ceiling_light(0)
# Step 4: Start the fim command in a separate thread
fim_thread = threading.Thread(target=run_fim_command)
fim_thread.start()
time.sleep(11)
start_projector(1)
GPIO.output(lcd_backlight_pin, GPIO.HIGH)
print("Slideshow started in background.")
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
except Exception as ex:
print(f"An unexpected error occurred: {ex}")
def start_music():
def music_player():
try:
global music_playing
music_playing = True
GPIO.output(lcd_backlight_pin, GPIO.LOW)
clear_pixels(0)
# Step 1: Kill all mplayer processes
print("Killing any running mplayer processes...")
os.system("sudo killall mplayer > /dev/null 2>&1")
os.system("sudo killall fim > /dev/null 2>&1")
os.system("sudo pkill fbcp-ili9341 > /dev/null 2>&1")
#os.system("sudo /home/alsatest/fbcp-ili9341/build/fbcp-ili9341 > /dev/null 2>&1")
time.sleep(1)
start_display()
time.sleep(1)
GPIO.output(ceiling_fan_pin, GPIO.LOW)
record_player_pwm.ChangeDutyCycle(record_player_duty_cycle)
toggle_ashtray_smoke()
GPIO.output(ceiling_fan_pin, GPIO.LOW)
# Step 2: Wait for a moment (1 second)
time.sleep(0.5) # Wait for 1 second (or adjust as needed)
# Step 3: Define dictionaries for each artist and their songs
songs = {
"dead": {
"file": "dead_set2_240.mp4",
"duration": 6752, # 1h52m32s in seconds
"songs": [
("Promised Land", 0),
("Bertha", 196),
("Greatest Story Ever Told", 572),
("Row Jimmy", 878),
("WRS", 1440),
("Dark Star", 2438),
("Wharf Rat", 4674),
("Sugar Magnolia", 5237),
("Uncle John's Band", 5863)
]
},
"band": {
"file": "lastwaltz240.mp4",
"duration": 2545, # 42m25s in seconds
"songs": [
("Coyote", 0),
("Cripple Creek", 300),
#("Dixie", 630),
("Don't Do It", 885),
("The Shape I'm In", 1020),
("Such A Night", 1205)
]
},
"abb": {
"file": "allman240.mp4",
"duration": 3889, # 1h04m49s in seconds
"songs": [
("Done Somebody Wrong", 2580),
("Southbound", 2820),
("Midnight Rider", 3175),
("Ain't Wastin' Time", 3378),
("Statesboro Blues", 3617)
]
}
}
# Step 4: Randomly select an artist
artist = random.choice(list(songs.keys()))
artist_data = songs[artist]
video_file = "/home/alsatest/" + artist_data["file"]
artist_songs = artist_data["songs"]
video_duration = artist_data["duration"]
song_index = random.randint(0, len(artist_songs) - 1)
song, start_time = artist_songs[song_index]
# Calculate song duration
if song_index == len(artist_songs) - 1:
# If it's the last song, play until the end of the video
duration = video_duration - start_time
else:
# Otherwise, play until the start of the next song
_, next_start_time = artist_songs[song_index + 1]
duration = next_start_time - start_time
print(f"Starting '{artist}' video at the beginning of '{song}', at {start_time} seconds. Duration: {duration} seconds.")
# Step 6: Run the mplayer command with the random start time
command = [
'sudo', 'openvt', '-s', '-l', '--', 'mplayer',
'-vo', 'fbdev',
'-ao', 'alsa:device=softvol',
'-ss', str(start_time), # Seek to the randomly chosen start time
'-framedrop', # Enable frame dropping
'-af', 'equalizer=-5:-4:-2:0:1:2:3:3:2:1,volnorm=2:0.75',
video_file # Play the video file corresponding to the chosen artist
]
# Start mplayer process
process = subprocess.Popen(command)
#pi.set_PWM_dutycycle(rope_lights_pin, rope_lights_duty_cycle)
time.sleep(2)
fade_rope_lights(1)
time.sleep(3)
start_projector(1)
GPIO.output(lcd_backlight_pin, GPIO.HIGH)
print(f"Starting at '{song}'.")
# Wait for the duration of the song
time.sleep(duration+5)
# Send SIGTERM to mplayer process
process.terminate()
# Wait for the process to finish (give it up to 5 seconds)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
# If it doesn't terminate within 5 seconds, force kill it
process.kill()
print(f"Finished playing '{song}'.")
print("Starting slideshow...")
start_slideshow()
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
except Exception as ex:
print(f"An unexpected error occurred: {ex}")
# Start the music playback in a separate thread
music_thread = threading.Thread(target=music_player)
music_thread.start()
#ceiling_light(0)
start_slideshow()
#ceiling_fan_pwm.ChangeDutyCycle(ceiling_fan_duty_cycle)
try:
clk_last_state = buttonCLK.value
button_press_time = None
long_press_triggered = False
while True:
buttonCLK.update()
buttonDT.update()
buttonSW.update()
# Rotary encoder for volume adjustment
if buttonCLK.fell:
if buttonDT.value != buttonCLK.value:
current_volume = mixer.getvolume()[0]
volume = min(100, current_volume + 5) # Increase volume
set_alsa_volume(volume)
else:
current_volume = mixer.getvolume()[0]
volume = max(0, current_volume - 5) # Decrease volume
set_alsa_volume(volume)
# Rotary encoder button press handling
if buttonSW.fell:
button_press_time = time.monotonic()
long_press_triggered = False
elif buttonSW.rose:
if button_press_time is not None and not long_press_triggered:
# If released before long press and long press hasn't been triggered, it's a short press
stop_projector()
if music_playing == False:
start_music()
else:
start_slideshow()
button_press_time = None
long_press_triggered = False
elif buttonSW.value == 0 and button_press_time is not None: # Button is still pressed
if time.monotonic() - button_press_time >= 1 and not long_press_triggered:
start_van(2)
long_press_triggered = True
time.sleep(0.01) # Small delay to prevent CPU overuse
except KeyboardInterrupt:
print("Program exited.")
finally:
GPIO.output(lcd_backlight_pin, GPIO.LOW)
# Stop all PWM
ceiling_fan_pwm.stop()
record_player_pwm.stop()
rope_lights_pwm.stop()
stop_projector()
stop_van()
GPIO.output(record_player_pin, GPIO.LOW)
GPIO.output(ceiling_fan_pin, GPIO.LOW)
#GPIO.output(rope_lights_pin, GPIO.LOW)
GPIO.output(ashtray_smoke_pin, GPIO.LOW)
GPIO.output(van_smoke_pin, GPIO.LOW)
ceiling_fan_pwm.ChangeDutyCycle(0)
record_player_pwm.ChangeDutyCycle(0)
van_smoke_pwm.ChangeDutyCycle(0)
ashtray_smoke_pwm.ChangeDutyCycle(0)
fade_rope_lights(0)
#GPIO.cleanup()
# Ensure all pixels are turned off before exiting
clear_pixels()
os.system("sudo killall fim > /dev/null 2>&1")
os.system("sudo killall mplayer > /dev/null 2>&1")
os.system("sudo killall fbcp-ili9341 > /dev/null 2>&1")
# subprocess.run(['sudo', 'killall', 'mplayer'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment