Skip to content

Commit 4879082

Browse files
authored
Merge pull request #1406 from adafruit/darksaber
All set
2 parents 75dabd7 + 57f8036 commit 4879082

File tree

11 files changed

+238
-0
lines changed

11 files changed

+238
-0
lines changed

CircuitPython_Darksaber/code.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
"""
2+
Prop-Maker based Darksaber
3+
Adapted from the Prop-Maker based Master Sword code
4+
by Kattni Rembor & Limor Fried
5+
Adafruit invests time and resources providing this open source code.
6+
Please support Adafruit and open source hardware by purchasing
7+
products from Adafruit!
8+
Written by Liz Clark for Adafruit Industries
9+
Copyright (c) 2021 Adafruit Industries
10+
Licensed under the MIT license.
11+
All text above must be included in any redistribution.
12+
"""
13+
14+
import time
15+
import random
16+
import board
17+
from digitalio import DigitalInOut, Direction
18+
import neopixel
19+
import adafruit_lis3dh
20+
from adafruit_led_animation.animation.solid import Solid
21+
from adafruit_led_animation.animation.pulse import Pulse
22+
from adafruit_led_animation.animation.comet import Comet
23+
24+
from adafruit_bluefruit_connect.packet import Packet
25+
from adafruit_bluefruit_connect.color_packet import ColorPacket
26+
27+
from adafruit_ble import BLERadio
28+
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
29+
from adafruit_ble.services.nordic import UARTService
30+
31+
# BLE setup
32+
ble = BLERadio()
33+
uart_service = UARTService()
34+
advertisement = ProvideServicesAdvertisement(uart_service)
35+
36+
# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion
37+
HIT_THRESHOLD = 250
38+
SWING_THRESHOLD = 150
39+
40+
# Set to the length in seconds of the "on.wav" file
41+
POWER_ON_SOUND_DURATION = 1.7
42+
43+
# NeoPixel setup
44+
NUM_PIXELS = 34 # Number of pixels used in project
45+
NEOPIXEL_PIN = board.D5
46+
POWER_PIN = board.D10
47+
48+
enable = DigitalInOut(POWER_PIN)
49+
enable.direction = Direction.OUTPUT
50+
enable.value = False
51+
52+
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=.5, auto_write=False)
53+
strip.fill(0) # NeoPixels off ASAP on startup
54+
strip.show()
55+
56+
# default NeoPixel color is white
57+
COLOR = (255, 255, 255)
58+
59+
# NeoPixel animations
60+
pulse = Pulse(strip, speed=0.05, color=COLOR, period=3)
61+
solid = Solid(strip, color=COLOR)
62+
comet = Comet(strip, speed=0.05, color=COLOR, tail_length=40)
63+
64+
#audio
65+
try:
66+
from audiocore import WaveFile
67+
except ImportError:
68+
from audioio import WaveFile
69+
70+
try:
71+
from audioio import AudioOut
72+
except ImportError:
73+
try:
74+
from audiopwmio import PWMAudioOut as AudioOut
75+
except ImportError:
76+
pass # not always supported by every board!
77+
78+
audio = AudioOut(board.A0) # Speaker
79+
wave_file = None
80+
81+
# Set up accelerometer on I2C bus, 4G range:
82+
i2c = board.I2C()
83+
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
84+
accel.range = adafruit_lis3dh.RANGE_4_G
85+
86+
def play_wav(name, loop=False):
87+
"""
88+
Play a WAV file in the 'sounds' directory.
89+
:param name: partial file name string, complete name will be built around
90+
this, e.g. passing 'foo' will play file 'sounds/foo.wav'.
91+
:param loop: if True, sound will repeat indefinitely (until interrupted
92+
by another sound).
93+
"""
94+
global wave_file # pylint: disable=global-statement
95+
print("playing", name)
96+
if wave_file:
97+
wave_file.close()
98+
try:
99+
wave_file = open('sounds/' + name + '.wav', 'rb')
100+
wave = WaveFile(wave_file)
101+
audio.play(wave, loop=loop)
102+
except OSError:
103+
pass # we'll just skip playing then
104+
105+
106+
def power_on(sound, duration):
107+
"""
108+
Animate NeoPixels with accompanying sound effect for power on.
109+
:param sound: sound name (similar format to play_wav() above)
110+
:param duration: estimated duration of sound, in seconds (>0.0)
111+
"""
112+
start_time = time.monotonic() # Save audio start time
113+
play_wav(sound)
114+
while True:
115+
elapsed = time.monotonic() - start_time # Time spent playing sound
116+
if elapsed > duration: # Past sound duration?
117+
break # Stop animating
118+
comet.animate()
119+
120+
# List of swing wav files without the .wav in the name for use with play_wav()
121+
swing_sounds = [
122+
'swing1',
123+
'swing2',
124+
'swing3',
125+
'swing4',
126+
]
127+
128+
# List of hit wav files without the .wav in the name for use with play_wav()
129+
hit_sounds = [
130+
'hit1',
131+
'hit2',
132+
'hit3',
133+
'hit4',
134+
]
135+
136+
mode = 0 # Initial mode = OFF
137+
138+
#RGB LED
139+
red_led = DigitalInOut(board.D11)
140+
green_led = DigitalInOut(board.D12)
141+
blue_led = DigitalInOut(board.D13)
142+
143+
red_led.direction = Direction.OUTPUT
144+
green_led.direction = Direction.OUTPUT
145+
blue_led.direction = Direction.OUTPUT
146+
147+
blue_led.value = True
148+
red_led.value = True
149+
green_led.value = True
150+
151+
# Darksaber start-up before loop
152+
if mode == 0: # If currently off...
153+
enable.value = True
154+
power_on('on', POWER_ON_SOUND_DURATION) # Power up!
155+
play_wav('idle', loop=True) # Play idle sound now
156+
mode = 1 # Idle mode
157+
158+
while True:
159+
# begin advertising BLE
160+
ble.start_advertising(advertisement)
161+
# if no BLE connection...
162+
# allows it to be used without the bluefruit app connection
163+
while not ble.connected:
164+
if mode >= 1: # If not OFF mode...
165+
x, y, z = accel.acceleration # Read accelerometer
166+
accel_total = x * x + z * z
167+
# (Y axis isn't needed, due to the orientation that the Prop-Maker
168+
# Wing is mounted. Also, square root isn't needed, since we're
169+
# comparing thresholds...use squared values instead.)
170+
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
171+
TRIGGER_TIME = time.monotonic() # Save initial time of hit
172+
play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound
173+
# NeoPixels are solid on with a hit
174+
solid.animate()
175+
mode = 3 # HIT mode
176+
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
177+
TRIGGER_TIME = time.monotonic() # Save initial time of swing
178+
play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds
179+
while audio.playing:
180+
pass # wait till we're done
181+
mode = 2 # we'll go back to idle mode
182+
elif mode == 1:
183+
# pulse animation when idling or swinging
184+
pulse.animate()
185+
elif mode > 1: # If in SWING or HIT mode...
186+
if audio.playing: # And sound currently playing...
187+
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
188+
if mode == 2: # If SWING,
189+
blend = abs(0.5 - blend) * 2.0 # ramp up, down
190+
else: # No sound now, but still SWING or HIT modes
191+
play_wav('idle', loop=True) # Resume idle sound
192+
mode = 1 # Return to idle mode
193+
ble.stop_advertising()
194+
195+
# if BLE is connected...
196+
while ble.connected:
197+
# color picker from bluefruit app
198+
if uart_service.in_waiting:
199+
packet = Packet.from_stream(uart_service)
200+
# if a color packet is recieved...
201+
if isinstance(packet, ColorPacket):
202+
print(packet.color)
203+
# color for the different animations are updated
204+
comet.color = packet.color
205+
solid.color = packet.color
206+
pulse.color = packet.color
207+
solid.animate()
208+
# repeat of the above code
209+
if mode >= 1: # If not OFF mode...
210+
x, y, z = accel.acceleration # Read accelerometer
211+
accel_total = x * x + z * z
212+
# (Y axis isn't needed, due to the orientation that the Prop-Maker
213+
# Wing is mounted. Also, square root isn't needed, since we're
214+
# comparing thresholds...use squared values instead.)
215+
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
216+
TRIGGER_TIME = time.monotonic() # Save initial time of hit
217+
play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound
218+
# NeoPixels are solid on with a hit
219+
solid.animate()
220+
mode = 3 # HIT mode
221+
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
222+
TRIGGER_TIME = time.monotonic() # Save initial time of swing
223+
play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds
224+
while audio.playing:
225+
pass # wait till we're done
226+
mode = 2 # we'll go back to idle mode
227+
228+
elif mode == 1:
229+
# pulse animation when idling or swinging
230+
pulse.animate()
231+
elif mode > 1: # If in SWING or HIT mode...
232+
if audio.playing: # And sound currently playing...
233+
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
234+
if mode == 2: # If SWING,
235+
blend = abs(0.5 - blend) * 2.0 # ramp up, down
236+
else: # No sound now, but still SWING or HIT modes
237+
play_wav('idle', loop=True) # Resume idle sound
238+
mode = 1 # Return to idle mode
43.2 KB
Binary file not shown.
43.2 KB
Binary file not shown.
43.2 KB
Binary file not shown.
28.7 KB
Binary file not shown.
87 KB
Binary file not shown.

CircuitPython_Darksaber/sounds/on.wav

67.3 KB
Binary file not shown.
30.4 KB
Binary file not shown.
30.4 KB
Binary file not shown.
28.2 KB
Binary file not shown.
30.4 KB
Binary file not shown.

0 commit comments

Comments
 (0)