Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, .wav files, and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries # SPDX-License-Identifier: MIT # BLE Ouija Board import time import random import board import digitalio import neopixel from adafruit_motorkit import MotorKit from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.button_packet import ButtonPacket import audiocore import audiomixer import audiobusio # Prep the status LEDs on the Feather blue_led = digitalio.DigitalInOut(board.BLUE_LED) red_led = digitalio.DigitalInOut(board.RED_LED) blue_led.direction = digitalio.Direction.OUTPUT red_led.direction = digitalio.Direction.OUTPUT ble = BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) num_motors = 1 # up to 4 motors depending on the prop you are driving i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller motorwing = MotorKit(i2c=i2c) motorwing.frequency = 122 # tune this 50 - 200 range max_throttle = 0.65 # tune this 0.2 - 1 range # # make arrays for all the things we care about motors = [None] * num_motors motors[0] = motorwing.motor1 # # set motors to "off" for i in range(num_motors): motors[i].throttle = None # - Audio setup audio = audiobusio.I2SOut(bit_clock=board.TX, word_select=board.MISO, data=board.RX) mixer = audiomixer.Mixer(voice_count=2, sample_rate=11025, channel_count=1, bits_per_sample=16, samples_signed=True) audio.play(mixer) # attach mixer to audio playback wav_files = (('spooky_ouija.wav', 0.07, True), ('lars_ouija.wav', 0.09, False)) # open samples for i in range(len(wav_files)): wave = audiocore.WaveFile(open(wav_files[i][0], "rb")) mixer.voice[i].level = 0 # start up with level down mixer.voice[i].play(wave, loop=wav_files[i][2]) # - NeoPixels fire_color = 0xcc6600 fade_by = -1 num_leds = 7 max_bright = 0.5 led_pin = board.D6 leds = neopixel.NeoPixel(led_pin, num_leds, brightness=0.0, auto_write=False) leds.fill(fire_color) leds.show() last_time = 0 next_duration = 0.2 print("BLE Ouija board") print("Use Adafruit Bluefruit app to connect") while True: blue_led.value = False ble.name = 'Ouija' ble.start_advertising(advertisement) while not ble.connected: # Wait for a connection. pass blue_led.value = True # turn on blue LED when connected while ble.connected: if uart_service.in_waiting: # Packet is arriving. red_led.value = False # turn off red LED packet = Packet.from_stream(uart_service) if isinstance(packet, ButtonPacket) and packet.pressed: red_led.value = True # blink to show a packet has been received if packet.button == ButtonPacket.RIGHT: # > button pressed print("forward") motors[0].throttle = max_throttle time.sleep(0.1) # wait a moment elif packet.button == ButtonPacket.LEFT: # < button print("reverse") motors[0].throttle = max_throttle * -1 time.sleep(0.1) elif packet.button == ButtonPacket.DOWN: # v button print("stop") motors[0].throttle = None time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_1: # 1 button print("on BG music") mixer.voice[0].level = wav_files[0][1] time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_3: # 3 button print("off BG music") mixer.voice[0].level = 0.0 time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_2: # 2 button print("on led & Lars") leds.brightness = max_bright mixer.voice[1].play(wave, loop=False) mixer.voice[1].level = wav_files[1][1] time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_4: # 4 button print("off led & Lars") leds.brightness = 0 mixer.voice[1].level = 0.0 time.sleep(0.1) # fade down all LEDs leds[:] = [[min(max(i+fade_by, 0), 255) for i in l] for l in leds] leds.show() # add new fire to leds if time.monotonic() - last_time > next_duration: last_time = time.monotonic() next_duration = random.uniform(0.95, 1.95) # tune these nums # for i in range( 1): c = fire_color # get our color c = (c >> 16 & 0xff, c >> 8 & 0xff, c & 0xff) # make it a tuple leds[random.randint(0, num_leds-1)] = c
How It Works
The code imports a number of libraries needed to use the board pins, NeoPixels, BLE, Motor FeatherWing, and audio.
import time import random import board import digitalio import neopixel from adafruit_motorkit import MotorKit from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService from adafruit_bluefruit_connect.packet import Packet from adafruit_bluefruit_connect.button_packet import ButtonPacket import audiocore import audiomixer import audiobusio
Setup
Next, the code does the setup for LED indicators, BLE, motor driving, audio, and NeoPixel animation.
blue_led = digitalio.DigitalInOut(board.BLUE_LED) red_led = digitalio.DigitalInOut(board.RED_LED) blue_led.direction = digitalio.Direction.OUTPUT red_led.direction = digitalio.Direction.OUTPUT ble = BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) num_motors = 1 # up to 4 motors depending on the prop you are driving motorwing = MotorKit(i2c=board.I2C()) motorwing.frequency = 122 # tune this 50 - 200 range max_throttle = 0.65 # tune this 0.2 - 1 range # # make arrays for all the things we care about motors = [None] * num_motors motors[0] = motorwing.motor1 # # set motors to "off" for i in range(num_motors): motors[i].throttle = None # - Audio setup audio = audiobusio.I2SOut(bit_clock=board.TX, word_select=board.MISO, data=board.RX) mixer = audiomixer.Mixer(voice_count=2, sample_rate=11025, channel_count=1, bits_per_sample=16, samples_signed=True) audio.play(mixer) # attach mixer to audio playback wav_files = (('spooky_ouija.wav', 0.07, True), ('lars_ouija.wav', 0.09, False)) # open samples for i in range(len(wav_files)): wave = audiocore.WaveFile(open(wav_files[i][0], "rb")) mixer.voice[i].level = 0 # start up with level down mixer.voice[i].play(wave, loop=wav_files[i][2]) # - NeoPixels fire_color = 0xcc6600 fade_by = -1 num_leds = 7 max_bright = 0.5 led_pin = board.D6 leds = neopixel.NeoPixel(led_pin, num_leds, brightness=0.0, auto_write=False) leds.fill(fire_color) leds.show()
Main Loop
The main loop of the program establishes the BLE advertisement an waits for a connection with the Bluefruit program.
Once connected, the UART service waits to receive packets of button presses. These are translated into motor movement in both directions, motor stop, and enabling or disabling the audio tracks and NeoPixel animations.
while True: blue_led.value = False ble.name = 'Ouija' ble.start_advertising(advertisement) while not ble.connected: # Wait for a connection. pass blue_led.value = True # turn on blue LED when connected while ble.connected: if uart_service.in_waiting: # Packet is arriving. red_led.value = False # turn off red LED packet = Packet.from_stream(uart_service) if isinstance(packet, ButtonPacket) and packet.pressed: red_led.value = True # blink to show a packet has been received if packet.button == ButtonPacket.RIGHT: # > button pressed print("forward") motors[0].throttle = max_throttle time.sleep(0.1) # wait a moment elif packet.button == ButtonPacket.LEFT: # < button print("reverse") motors[0].throttle = max_throttle * -1 time.sleep(0.1) elif packet.button == ButtonPacket.DOWN: # v button print("stop") motors[0].throttle = None time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_1: # 1 button print("on BG music") mixer.voice[0].level = wav_files[0][1] time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_3: # 3 button print("off BG music") mixer.voice[0].level = 0.0 time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_2: # 2 button print("on led & Lars") leds.brightness = max_bright mixer.voice[1].play(wave, loop=False) mixer.voice[1].level = wav_files[1][1] time.sleep(0.1) elif packet.button == ButtonPacket.BUTTON_4: # 4 button print("off led & Lars") leds.brightness = 0 mixer.voice[1].level = 0.0 time.sleep(0.1)
Animation
The fade animation on the NeoPixels is run at all times, with the BLE packets only controlling the brightness.
# fade down all LEDs leds[:] = [[min(max(i+fade_by, 0), 255) for i in l] for l in leds] leds.show() # add new fire to leds if time.monotonic() - last_time > next_duration: last_time = time.monotonic() next_duration = random.uniform(0.95, 1.95) # tune these nums # for i in range( 1): c = fire_color # get our color c = (c >> 16 & 0xff, c >> 8 & 0xff, c & 0xff) # make it a tuple leds[random.randint(0, num_leds-1)] = c
Text editor powered by tinymce.