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.

Note: This project uses the Bluetooth functionality available in CircuitPython 5.0.0-beta.0 and later, on nRF52840 boards such as the Feather nRF52840 Express and Circuit Playground Bluefruit. Make sure you are using that version or later.


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

"""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,
              color=color.PURPLE,  # Use CYAN for Male Key

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)

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)
        while i2s.playing:

boundary_violations = 0

while True:
    if reed_switch.value:  # Not Docked
        hits = 0
            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:
        hit_count = len([ones for ones in bin(hits) if ones == '1'])
        solid.color = hit_status[hit_count]
        if hit_count == 0:
            if boundary_violations % 60 == 0:  # Play message every 60 cycles
                amp_enable.value = True
                amp_enable.value = False
            boundary_violations += 1
            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
            pulse.period = 3

This guide was first published on Jul 22, 2020. It was last updated on Jul 22, 2020.

This page (CircuitPython Code) was last updated on Mar 16, 2021.

Text editor powered by tinymce.