Created
October 14, 2024 21:49
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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