We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries, there is a full getting started guide here.
For more info on the Feather nRF52840 Express, check out this guide.
Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and its installation in this tutorial.
Libraries
You'll also need to add the following libraries for this project. Follow this guide on adding libraries.
Plug your nRF Feather board into your computer via a USB cable. Please be sure the cable is a good power+data cable so the computer can talk to the board.
A new disk should appear in your computer's file explorer/finder called CIRCUITPY. This is the place we'll copy the code and code library. If you can only get a drive named FTHR840BOOT, load CircuitPython per the previous page.
Download the latest CircuitPython libraries to your computer using the green button below. Match the library you get to the version of CircuitPython you are using. Save to your computer's hard drive where you can find it.
With your file explorer/finder, browse to the bundle and open it up. Copy the following folders and files from the library bundle to your CIRCUITPY lib directory you made earlier:
The ones you'll need are:
-
adafruit_ble (folder)
- adafruit_bus_device (folder)
- adafruit_led_animation (folder)
- neopixel.mpy (file)
All of the other necessary code is baked into CircuitPython!
CircuitPython Code
Copy the program below and paste it into a new document in Mu. Then, save it from Mu onto your CIRCUITPY flash drive as code.py.
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""Bluetooth Key Tracker."""
from adafruit_ble import BLERadio
from adafruit_led_animation.animation import Pulse, Solid
import adafruit_led_animation.color as color
from analogio import AnalogIn
from array import array
from audiobusio import I2SOut
from audiocore import RawSample, WaveFile
from board import BATTERY, D5, D6, D9, NEOPIXEL, RX, TX
from digitalio import DigitalInOut, Direction, Pull
from math import pi, sin
from neopixel import NeoPixel
from time import sleep
battery = AnalogIn(BATTERY)
ble = BLERadio()
hit_status = [color.RED, color.ORANGE, color.AMBER, color.GREEN]
pixel = NeoPixel(NEOPIXEL, 1)
pulse = Pulse(pixel,
speed=0.01,
color=color.PURPLE, # Use CYAN for Male Key
period=3,
min_intensity=0.0,
max_intensity=0.5)
solid = Solid(pixel, color.GREEN)
reed_switch = DigitalInOut(D5)
reed_switch.direction = Direction.INPUT
reed_switch.pull = Pull.UP
amp_enable = DigitalInOut(D6)
amp_enable.direction = Direction.OUTPUT
amp_enable.value = False
def play_tone():
"""Generate tone and transmit to I2S amp."""
length = 4000 // 440
sine_wave = array("H", [0] * length)
for i in range(length):
sine_wave[i] = int(sin(pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
sample = RawSample(sine_wave, sample_rate=8000)
i2s = I2SOut(TX, RX, D9)
i2s.play(sample, loop=True)
sleep(1)
i2s.stop()
sample.deinit()
i2s.deinit()
def play_message():
"""Play recorded WAV message and transmit to I2S amp."""
with open("d1.wav", "rb") as file:
wave = WaveFile(file)
i2s = I2SOut(TX, RX, D9)
i2s.play(wave)
while i2s.playing:
pass
wave.deinit()
i2s.deinit()
boundary_violations = 0
while True:
if reed_switch.value: # Not Docked
hits = 0
try:
advertisements = ble.start_scan(timeout=3)
for advertisement in advertisements:
addr = advertisement.address
if (advertisement.scan_response and
addr.type == addr.RANDOM_STATIC):
if advertisement.complete_name == '<Your 1st beacon name here>':
hits |= 0b001
elif advertisement.complete_name == '<Your 2nd beacon name here>':
hits |= 0b010
elif advertisement.complete_name == '<Your 3rd beacon name here>':
hits |= 0b100
except Exception as e:
print(repr(e))
hit_count = len([ones for ones in bin(hits) if ones == '1'])
solid.color = hit_status[hit_count]
solid.animate()
sleep(1)
if hit_count == 0:
if boundary_violations % 60 == 0: # Play message every 60 cycles
amp_enable.value = True
sleep(1)
play_tone()
sleep(1)
play_message()
sleep(1)
amp_enable.value = False
boundary_violations += 1
else:
boundary_violations = 0
else: # Docked
boundary_violations = 0
voltage = battery.value * 3.3 / 65535 * 2
if voltage < 3.7:
pulse.period = 1 # Speed up LED pulse for low battery
else:
pulse.period = 3
pulse.animate()
Page last edited January 19, 2025
Text editor powered by tinymce.