Matrix Festive Timer / CircuitPython BLE Tracker

by alsleben in Circuits > Wireless

617 Views, 6 Favorites, 0 Comments

Matrix Festive Timer / CircuitPython BLE Tracker

Cover.png

Hi! Welcome to my Instructable! I'm Leonard and together with my classmate Gabriel (@rodrialk on instructables), we developed a CircuitPython coded, BLE (Bluetooth Low Energy) Assistive Tech Tracking device for Behaviour Reinforcement Learning.

This project was created for a class at Boston College called "Physical Computing". Specifically this was for the "Campus School Project" in which we collaborate with the Campus School at Boston College to create assistive tech devices for their students.

In this project, you will be making a BLE tracker that interfaces with an S3 Matrix Portal powered, wall-mounted 64x32 Matrix. The longer you stay in range of the Matrix, the bigger an animated LED Christmas Tree grows!

However, if you just want a festive timer without the BLE Tracking, feel free to modify the code in this project and skip over the wearable part!

Note: For this project you need access to a 3D Printer, however various on Demand 3D printing services will also be able to print these files!

Supplies

Materials for Matrix

  1. 64x32 RGB LED Matrix - 4mm pitch
  2. Adafruit Matrix Portal S3
  3. 5V 4A (4000mA) switching power supply (Currently sold out on Adafruit, but any should do)
  4. Black LED Diffusion Acrylic Panel (Optional, but looks amazing)
  5. 3D Printed Chassis (File Linked Below)
  6. Six M3x8mm Screws


Materials for Wearable BLE Tracker

  1. Adafruit ItsyBitsy nRF52840 Express - Bluetooth LE
  2. Adafruit LiIon/LiPoly Backpack Add-On for Pro Trinket/ItsyBitsy
  3. Lithium Ion Polymer Battery - 3.7v 150mAh
  4. Small ON/OFF Switch with Mounting Holes
  5. We also used some Elastic Bands for Sewing for the Wearable, but feel free to modify the strap too!
  6. 3D Printed Wearable Chassis (File Linked Below)
  7. Two small M2 Screws and M2 Nuts


Equipment

  1. Soldering Supplies (Soldering Iron, Solder, Wire)
  2. Access to a 3D Printer (Or 3D Printing On Demand Service)

Assembling the Wearable

IMG_7218.png
IMG_7218.png
IMG_7402.jpg
IMG_7403.jpg

To begin, let's assemble the wearable! Grab the parts shown in the image above (i.e. all materials for the wearable)

I did this while my 3D print was going so if you have access to a 3D printer, I'd recommend getting those three files going first.

Soldering

Grab your ItsyBitsy nRF52840, and the LiOn Backpack. First you'll want to (as indicated in the image above) using a small hobby knife cut the lead between the two circles. This will allow us to wire in a switch later.

Now we want to solder the three pronged header to the backpack, and then stack the backpack onto the ItsyBitsy. Then solder those three pins on the ItsyBitsy too.

Assemble

Grab your 3D printed chassis for the wearable.

Grab the switch. Snip one of the three prings on the switch and insert it from the outside into the slot on the chassis. Screw it in with the two small M2 screws and nuts. Now (without the battery attached), insert the backpack into the chassis. The charging port should face the hole.

More Soldering

Using some small wire, solder each of the prongs on the switch to one of the holes between which we cut the lead. This may require some patience.

Insert the Battery

The battery should squeeze below the ItsyBitsy in the wearable. Refer to the image above. I also added some spacers out of spare acrylic to remove any rattling in the wearable.

I also printed the student's name in the wearble, but feel free to customize the wearable as you want!

Assemble the Matrix

IMG_7407.jpg

For this step have your 3D printed Matrix Chassis Ready!

Putting it Together

Slide the Matrix into the Chassis. This should be a snug fit. From there, screw the Matrix in with the M3 screws. If you have the Black Acrylic Diffusion Glass, slide that into the chassis from the side with the gap.

Wiring

This step is optional, but I decided to shorten the power cable that comes with the matrix as it neatens the build quite a bit. I just snipped a section of the black and red wire and soldered them back together.

Then put the matrix in the gap of the Matrix, makng sure the white arrows on the rear of the matrix are pointing up and to the right.

Than connect your matrix to power!

Code It Up!

Download the file linked and upload it to your CircuitPython Matrix Portal S3. To set up your Matrix Portal S3 with CircuitPython, follow this guide!

This will be a pretty extensive walkthrough so if you want to just download the file linked blow, scroll down!


Code Walkthrough (Wearable Code)


import time
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import Advertisement

ble = BLERadio()
advertisement = Advertisement()
advertisement.complete_name = "WearableFeather"

ble.start_advertising(advertisement)

while True:
print("Advertising as WearableFeather")
time.sleep(5) # Adjust for preferred interval


The wearable code uses the adafruit_ble library to send out the BLE beacon. From there, we initialize the wearable as "WearableFeather" and begin advertising. That's it!


Code Walkthrough (Matrix Code)

Imports

The script begins by importing various modules:


import time, random, board, terminalio, displayio, math
from digitalio import DigitalInOut, Direction, Pull
from adafruit_matrixportal.matrixportal import MatrixPortal
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import Advertisement
from adafruit_display_text import label


- time and random: Manage delays and randomness, like snowflake positions.

- board: Access board pin mappings (e.g., buttons, NeoPixels).

- terminalio: Provides font rendering for text labels.

- displayio: Handles graphical bitmap operations for animations.

- math: Used for geometric calculations (e.g., determining ornament spacing).

- digitalio: Configures button inputs with pull-ups and debouncing.

- MatrixPortal: Drives the matrix display.

- BLERadio: Enables Bluetooth communication for detecting nearby devices.


Core Class: ChristmasPresenceDisplay

The main logic is encapsulated in the `ChristmasPresenceDisplay` class. Upon instantiation, it sets up display layers, buttons, BLE, and animation states.


Key Initialization Components:

Color Palette

Defines a range of colors used throughout the display:

self.COLORS = {
'GREEN': 0x00FF00,
'BROWN': 0x964B00,
'RED': 0xFF0000,
'WHITE': 0xFFFFFF,
...
}


Button Debouncing:

Configures hardware buttons for controlling animation states and timing modes:

self.button_up = DigitalInOut(board.BUTTON_UP)
self.button_up.direction = Direction.INPUT
self.button_up.pull = Pull.UP


BLE Initialization:

Sets up BLE for detecting nearby devices:


self.ble = BLERadio()
self.TARGET_NAME = "WearableFeather"


Animation States

Defines a state machine for the display's lifecycle:



self.animation_states = {
"WAITING": 0,
"GROWING": 1,
"DECORATING": 2,
"STAR": 3,
"SNOWING": 4
}


Display Layers

The display comprises multiple layers: the timer, tree bitmap, and mode text. These are grouped together using `displayio.Group` and rendered on the matrix portal.


self.tree_bitmap = displayio.Bitmap(32, 64, 9)
self.tree_palette = displayio.Palette(9)
self.tree_tile_grid = displayio.TileGrid(self.tree_bitmap, pixel_shader=self.tree_palette)


Animation States

The animation progresses through states based on elapsed time:


1. WAITING: Clears the display.

2. GROWING: Animates the tree by gradually adding rows.

3. DECORATING: Adds ornaments randomly to the tree.

4. STAR: Animates a star falling to the top of the tree.

5. SNOWING: Animates falling snowflakes.


def determine_animation_state(self):
if self.elapsed_time < self.time_settings["GROWING"]:
return "GROWING"
elif self.elapsed_time < self.time_settings["DECORATING"]:
return "DECORATING"
elif self.elapsed_time < self.time_settings["STAR"]:
return "STAR"
else:
return "SNOWING"


Bluetooth Integration

The script uses BLE to track the presence of a specific device (e.g., a wearable). When the device enters or exits the range, the display adjusts accordingly.


def check_bluetooth_presence(self):
for advertisement in self.ble.start_scan(Advertisement, timeout=2.0):
if advertisement.complete_name == self.TARGET_NAME:
print(f"Detected {self.TARGET_NAME}")


Tree Drawing and Decorations

Tree construction is achieved using a `displayio.Bitmap`. Layers are added iteratively, including ornaments and a star.


Drawing Ornaments

Ornaments are randomly placed but ensure a minimum distance between each to maintain a visually pleasing layout.


for x in range(16 - i, 16 + i + 1):
for y in range(start_y + i * 2, start_y + i * 2 + 2):
self.tree_bitmap[x, y] = 1 # Green for leaves


Interactive Controls

Pause: Toggled by pressing the UP button.

Timing Mode: Cycles between short, medium, and long durations using the DOWN button.


Main Loop

The loop continuously checks for button presses, updates BLE presence, and transitions between animation states based on elapsed time.

def main_loop(self):
while True:
self.check_button()
if not self.paused:
self.update_timer_display()
new_state = self.determine_animation_state()
self.handle_animation_state()
time.sleep(0.1)

Done!

IMG_7416.jpg

There it is! Thanks for following along. Check out this video to see a demo!