ball drop code
Ball drop code and audio wav files
This commit is contained in:
parent
d8e572da4b
commit
498388335c
4 changed files with 463 additions and 0 deletions
BIN
NY_Ball_Drop/Auld_Lang_Syne.wav
Executable file
BIN
NY_Ball_Drop/Auld_Lang_Syne.wav
Executable file
Binary file not shown.
371
NY_Ball_Drop/code.py
Normal file
371
NY_Ball_Drop/code.py
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
"""
|
||||
New Year's Eve ball drop robot friend.
|
||||
|
||||
Adafruit invests time and resources providing this open source code.
|
||||
Please support Adafruit and open source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Dave Astels for Adafruit Industries
|
||||
Copyright (c) 2018 Adafruit Industries
|
||||
Licensed under the MIT license.
|
||||
|
||||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
# pylint: disable=global-statement
|
||||
|
||||
import time
|
||||
import random
|
||||
import board
|
||||
from digitalio import DigitalInOut, Direction, Pull
|
||||
import busio
|
||||
import adafruit_ds3231
|
||||
import audioio
|
||||
import pulseio
|
||||
from adafruit_motor import servo
|
||||
import neopixel
|
||||
from debouncer import Debouncer
|
||||
|
||||
# Set to false to disable testing/tracing code
|
||||
TESTING = True
|
||||
|
||||
# Implementation dependant things to tweak
|
||||
NUM_PIXELS = 78 # number of neopixels in the striup
|
||||
DROP_THROTTLE = -0.03 # servo throttle during ball drop
|
||||
DROP_DURATION = 10.0 # how many seconds the ball takes to drop
|
||||
RAISE_THROTTLE = 0.1 # servo throttle while raising the ball
|
||||
FIREWORKS_DURATION = 30.0 # how many second the fireworks last
|
||||
|
||||
# Pins
|
||||
NEOPIXEL_PIN = board.D5
|
||||
POWER_PIN = board.D10
|
||||
SWITCH_PIN = board.D9
|
||||
SERVO_PIN = board.A1
|
||||
|
||||
# States
|
||||
WAITING_STATE = 0
|
||||
PAUSED_STATE = 1
|
||||
DROPPING_STATE = 2
|
||||
BURST_STATE = 3
|
||||
SHOWER_STATE = 4
|
||||
RAISING_STATE = 5
|
||||
IDLE_STATE = 6
|
||||
RESET_STATE = 7
|
||||
|
||||
|
||||
################################################################################
|
||||
# Setup hardware
|
||||
|
||||
# Power to the speaker and neopixels must be enabled using this pin
|
||||
|
||||
enable = DigitalInOut(POWER_PIN)
|
||||
enable.direction = Direction.OUTPUT
|
||||
enable.value = True
|
||||
|
||||
i2c = busio.I2C(board.SCL, board.SDA)
|
||||
rtc = adafruit_ds3231.DS3231(i2c)
|
||||
|
||||
audio = audioio.AudioOut(board.A0)
|
||||
|
||||
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
|
||||
strip.fill(0) # NeoPixels off ASAP on startup
|
||||
strip.show()
|
||||
|
||||
switch = Debouncer(SWITCH_PIN, Pull.UP, 0.01)
|
||||
|
||||
# create a PWMOut object on Pin A2.
|
||||
pwm = pulseio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50)
|
||||
|
||||
# Create a servo object, my_servo.
|
||||
servo = servo.ContinuousServo(pwm)
|
||||
servo.throttle = 0.0
|
||||
|
||||
# Set the time for testing
|
||||
# Once finished testing, the time can be set using the REPL using similar code
|
||||
if TESTING:
|
||||
# year, mon, date, hour, min, sec, wday, yday, isdst
|
||||
t = time.struct_time((2018, 12, 31, 23, 58, 55, 1, -1, -1))
|
||||
# you must set year, mon, date, hour, min, sec and weekday
|
||||
# yearday is not supported, isdst can be set but we don't do anything with it at this time
|
||||
print("Setting time to:", t)
|
||||
rtc.datetime = t
|
||||
print()
|
||||
|
||||
################################################################################
|
||||
# Variables
|
||||
|
||||
firework_color = 0
|
||||
firework_step_time = 0
|
||||
burst_count = 0
|
||||
shower_count = 0
|
||||
firework_stop_time = 0
|
||||
pixel_count = min([NUM_PIXELS // 2, 20])
|
||||
pixels = []
|
||||
pixel_index = 0
|
||||
|
||||
|
||||
################################################################################
|
||||
# Support functions
|
||||
|
||||
def log(s):
|
||||
"""Print the argument if testing/tracing is enabled."""
|
||||
if TESTING:
|
||||
print(s)
|
||||
|
||||
# Random color
|
||||
|
||||
def random_color_byte():
|
||||
""" Return one of 32 evenly spaced byte values.
|
||||
This provides random colors that are fairly distinctive."""
|
||||
return random.randrange(0, 256, 16)
|
||||
|
||||
def random_color():
|
||||
"""Return a random color"""
|
||||
red = random_color_byte()
|
||||
green = random_color_byte()
|
||||
blue = random_color_byte()
|
||||
return (red, green, blue)
|
||||
|
||||
# Color cycling. See https://learn.adafruit.com/hacking-ikea-lamps-with-circuit-playground-express/lamp-it-up
|
||||
|
||||
def wheel(pos):
|
||||
# Input a value 0 to 255 to get a color value.
|
||||
# The colours are a transition r - g - b - back to r.
|
||||
if pos < 0 or pos > 255:
|
||||
return 0, 0, 0
|
||||
if pos < 85:
|
||||
return int(255 - pos*3), int(pos*3), 0
|
||||
if pos < 170:
|
||||
pos -= 85
|
||||
return 0, int(255 - pos*3), int(pos*3)
|
||||
pos -= 170
|
||||
return int(pos * 3), 0, int(255 - (pos*3))
|
||||
|
||||
def cycle_sequence(seq):
|
||||
while True:
|
||||
for elem in seq:
|
||||
yield elem
|
||||
|
||||
def rainbow_lamp(seq):
|
||||
g = cycle_sequence(seq)
|
||||
while True:
|
||||
strip.fill(wheel(next(g)))
|
||||
strip.show()
|
||||
yield
|
||||
|
||||
# Fireworks effects
|
||||
|
||||
def reset_fireworks(time_now):
|
||||
"""As indicated, reset the fireworks system's variables."""
|
||||
global firework_color, burst_count, shower_count, firework_step_time
|
||||
firework_color = random_color()
|
||||
burst_count = 0
|
||||
shower_count = 0
|
||||
strip.fill(0)
|
||||
strip.show()
|
||||
firework_step_time = time_now + 0.05
|
||||
|
||||
|
||||
def burst(time_now):
|
||||
"""Show a burst of color on all pixels, fading in, holding briefly,
|
||||
then fading out. Each call to this does one step in that
|
||||
process. Return True once the sequence is finished."""
|
||||
global firework_step_time, burst_count, shower_count
|
||||
log("burst %d" % (burst_count))
|
||||
if burst_count == 0:
|
||||
strip.brightness = 0.0
|
||||
strip.fill(firework_color)
|
||||
elif burst_count == 22:
|
||||
shower_count = 0
|
||||
firework_step_time = time_now + 0.3
|
||||
return True
|
||||
if time_now < firework_step_time:
|
||||
return False
|
||||
elif burst_count < 11:
|
||||
strip.brightness = burst_count / 10.0
|
||||
firework_step_time = time_now + 0.08
|
||||
elif burst_count == 11:
|
||||
firework_step_time = time_now + 0.3
|
||||
elif burst_count > 11:
|
||||
strip.brightness = 1.0 - ((burst_count - 11) / 10.0)
|
||||
firework_step_time = time_now + 0.08
|
||||
strip.show()
|
||||
burst_count += 1
|
||||
return False
|
||||
|
||||
def shower(time_now):
|
||||
"""Show a shower of sparks effect.
|
||||
Each call to this does one step in the process. Return True once the
|
||||
sequence is finished."""
|
||||
global firework_step_time, pixels, pixel_index, shower_count
|
||||
log("Shower %d" % (shower_count))
|
||||
if shower_count == 0: # Initialize on the first step
|
||||
strip.fill(0)
|
||||
strip.brightness = 1.0
|
||||
pixels = [None] * pixel_count
|
||||
pixel_index = 0
|
||||
if time_now < firework_step_time:
|
||||
return False
|
||||
if shower_count == NUM_PIXELS:
|
||||
strip.fill(0)
|
||||
strip.show()
|
||||
return True
|
||||
if pixels[pixel_index]:
|
||||
strip[pixels[pixel_index]] = 0
|
||||
random_pixel = random.randrange(NUM_PIXELS)
|
||||
pixels[pixel_index] = random_pixel
|
||||
strip[random_pixel] = firework_color
|
||||
strip.show()
|
||||
pixel_index = (pixel_index + 1) % pixel_count
|
||||
shower_count += 1
|
||||
firework_step_time = time_now + 0.1
|
||||
return False
|
||||
|
||||
def start_playing(fname):
|
||||
sound_file = open(fname, 'rb')
|
||||
wav = audioio.WaveFile(sound_file)
|
||||
audio.play(wav, loop=False)
|
||||
|
||||
def stop_playing():
|
||||
if audio.playing:
|
||||
audio.stop()
|
||||
|
||||
|
||||
state = WAITING_STATE
|
||||
paused_state = None
|
||||
paused_servo = 0.0
|
||||
switch_pressed_at = 0
|
||||
drop_finish_time = 0
|
||||
|
||||
while True:
|
||||
test_trigger = False
|
||||
now = time.monotonic()
|
||||
t = rtc.datetime
|
||||
switch.update()
|
||||
fell = switch.fell # reading fell/rose will reset it, so we grab it here
|
||||
|
||||
# The machine sits in paused state until the switch is pressed again in
|
||||
# which case the machine goes back to the state it was in when paused (and
|
||||
# resumes the audio and servo as it was) or the switch is held for a second
|
||||
# in which case it goes to the reset state.
|
||||
if state == PAUSED_STATE:
|
||||
log("Paused")
|
||||
if fell:
|
||||
if audio.paused:
|
||||
audio.resume()
|
||||
servo.throttle = paused_servo
|
||||
paused_servo = 0.0
|
||||
state = paused_state
|
||||
elif not switch.value:
|
||||
if now - switch_pressed_at > 1.0:
|
||||
state = RESET_STATE
|
||||
continue
|
||||
|
||||
# There is a special check here for a switch press in any state other than
|
||||
# waiting. If there is a press, the current state, audio, and servo values
|
||||
# are saved and paused state is entered
|
||||
if fell and state != WAITING_STATE:
|
||||
switch_pressed_at = now
|
||||
paused_state = state
|
||||
if audio.playing:
|
||||
audio.pause()
|
||||
paused_servo = servo.throttle
|
||||
servo.throttle = 0.0
|
||||
state = PAUSED_STATE
|
||||
continue
|
||||
|
||||
# Waiting state handles waiting until 23:59 on NYE or until the switch is
|
||||
# pressed. When either happens, the song is played and the servo starts
|
||||
# droppping the ball. As well, a rainbow effect is started on the LEDs and
|
||||
# the machine moves to dropping state.
|
||||
if state == WAITING_STATE:
|
||||
log("Waiting")
|
||||
if fell:
|
||||
while not switch.rose:
|
||||
switch.update()
|
||||
test_trigger = True
|
||||
|
||||
if test_trigger or (t.tm_mday == 31 and
|
||||
t.tm_mon == 12 and
|
||||
t.tm_hour == 23 and
|
||||
t.tm_min == 59 and
|
||||
t.tm_sec == 50):
|
||||
test_trigger = False
|
||||
# Play the song
|
||||
start_playing('./countdown.wav')
|
||||
|
||||
# Drop the ball
|
||||
servo.throttle = DROP_THROTTLE
|
||||
|
||||
# color show in the ball
|
||||
rainbow = rainbow_lamp(range(0, 256, 2))
|
||||
log("1 minute to midnight")
|
||||
rainbow_time = now + 0.1
|
||||
|
||||
drop_finish_time = now + DROP_DURATION
|
||||
state = DROPPING_STATE
|
||||
|
||||
# In dropping the ball is dropping, colors are cycling, and the song is
|
||||
# playing. After the machine has been in this state long enough (set by
|
||||
# DROP_DURATION) it cleans up and switches to fireworks mode (burst to be
|
||||
# exact).
|
||||
elif state == DROPPING_STATE:
|
||||
log("Dropping")
|
||||
if now >= drop_finish_time:
|
||||
log("***Midnight")
|
||||
servo.throttle = 0.0
|
||||
stop_playing()
|
||||
start_playing('./Auld_Lang_Syne.wav')
|
||||
reset_fireworks(now)
|
||||
firework_stop_time = now + FIREWORKS_DURATION
|
||||
state = BURST_STATE
|
||||
continue
|
||||
if now >= rainbow_time:
|
||||
next(rainbow)
|
||||
rainbow_time = now + 0.1
|
||||
|
||||
# This state shows a burst of light (vi the burst function. It stays in
|
||||
# this mode until burst is finished, indicated by burst returning
|
||||
# True. When that happens the machine moves to the shower state.
|
||||
elif state == BURST_STATE:
|
||||
log("Burst")
|
||||
if burst(now):
|
||||
state = SHOWER_STATE
|
||||
shower_count = 0
|
||||
|
||||
# This state shows a shower-of-sparks effect until the shower function
|
||||
# returns True. If it's time to stop the fireworks effects the machine
|
||||
# moves to the idle state. Otherwise if loops back to the burst state.
|
||||
elif state == SHOWER_STATE:
|
||||
log("Shower")
|
||||
if shower(now):
|
||||
if now >= firework_stop_time:
|
||||
state = IDLE_STATE
|
||||
else:
|
||||
state = BURST_STATE
|
||||
reset_fireworks(now)
|
||||
|
||||
# The idle state currently just jumps into the waiting state.
|
||||
elif state == IDLE_STATE:
|
||||
log("Idle")
|
||||
state = WAITING_STATE
|
||||
|
||||
# This state resets the LEDs and audio, starts the servo raising the ball
|
||||
# and moves to the raising state.
|
||||
elif state == RESET_STATE:
|
||||
log("Reset")
|
||||
strip.fill(0)
|
||||
strip.brightness = 1.0
|
||||
strip.show()
|
||||
if audio.playing:
|
||||
audio.stop()
|
||||
servo.throttle = RAISE_THROTTLE
|
||||
state = RAISING_STATE
|
||||
|
||||
# This state simply waits until the switch is released at which time it
|
||||
# stops the servo and moves to the waiting state.
|
||||
elif state == RAISING_STATE:
|
||||
log("Raise")
|
||||
if switch.rose:
|
||||
servo.throttle = 0.0
|
||||
state = WAITING_STATE
|
||||
BIN
NY_Ball_Drop/countdown.wav
Normal file
BIN
NY_Ball_Drop/countdown.wav
Normal file
Binary file not shown.
92
NY_Ball_Drop/debouncer.py
Normal file
92
NY_Ball_Drop/debouncer.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
GPIO Pin Debouncer
|
||||
|
||||
Adafruit invests time and resources providing this open source code.
|
||||
Please support Adafruit and open source hardware by purchasing
|
||||
products from Adafruit!
|
||||
|
||||
Written by Dave Astels for Adafruit Industries
|
||||
Copyright (c) 2018 Adafruit Industries
|
||||
Licensed under the MIT license.
|
||||
|
||||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
import time
|
||||
import digitalio
|
||||
|
||||
class Debouncer(object):
|
||||
"""Debounce an input pin"""
|
||||
|
||||
DEBOUNCED_STATE = 0x01
|
||||
UNSTABLE_STATE = 0x02
|
||||
CHANGED_STATE = 0x04
|
||||
|
||||
|
||||
def __init__(self, pin, mode=None, interval=0.010):
|
||||
"""Make am instance.
|
||||
:param int pin: the pin (from board) to debounce
|
||||
:param int mode: digitalio.Pull.UP or .DOWN (default is no pull up/down)
|
||||
:param int interval: bounce threshold in seconds (default is 0.010, i.e. 10 milliseconds)
|
||||
"""
|
||||
self.state = 0x00
|
||||
self.pin = digitalio.DigitalInOut(pin)
|
||||
self.pin.direction = digitalio.Direction.INPUT
|
||||
if mode != None:
|
||||
self.pin.pull = mode
|
||||
if self.pin.value:
|
||||
self.__set_state(Debouncer.DEBOUNCED_STATE | Debouncer.UNSTABLE_STATE)
|
||||
self.previous_time = 0
|
||||
if interval is None:
|
||||
self.interval = 0.010
|
||||
else:
|
||||
self.interval = interval
|
||||
|
||||
|
||||
def __set_state(self, bits):
|
||||
self.state |= bits
|
||||
|
||||
|
||||
def __unset_state(self, bits):
|
||||
self.state &= ~bits
|
||||
|
||||
|
||||
def __toggle_state(self, bits):
|
||||
self.state ^= bits
|
||||
|
||||
|
||||
def __get_state(self, bits):
|
||||
return (self.state & bits) != 0
|
||||
|
||||
|
||||
def update(self):
|
||||
"""Update the debouncer state. Must be called before using any of the properties below"""
|
||||
self.__unset_state(Debouncer.CHANGED_STATE)
|
||||
current_state = self.pin.value
|
||||
if current_state != self.__get_state(Debouncer.UNSTABLE_STATE):
|
||||
self.previous_time = time.monotonic()
|
||||
self.__toggle_state(Debouncer.UNSTABLE_STATE)
|
||||
else:
|
||||
if time.monotonic() - self.previous_time >= self.interval:
|
||||
if current_state != self.__get_state(Debouncer.DEBOUNCED_STATE):
|
||||
self.previous_time = time.monotonic()
|
||||
self.__toggle_state(Debouncer.DEBOUNCED_STATE)
|
||||
self.__set_state(Debouncer.CHANGED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Return the current debounced value of the input."""
|
||||
return self.__get_state(Debouncer.DEBOUNCED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def rose(self):
|
||||
"""Return whether the debounced input went from low to high at the most recent update."""
|
||||
return self.__get_state(self.DEBOUNCED_STATE) and self.__get_state(self.CHANGED_STATE)
|
||||
|
||||
|
||||
@property
|
||||
def fell(self):
|
||||
"""Return whether the debounced input went from high to low at the most recent update."""
|
||||
return (not self.__get_state(self.DEBOUNCED_STATE)) and self.__get_state(self.CHANGED_STATE)
|
||||
Loading…
Reference in a new issue