Required libraries
This project does not require any libraries on top of the standard libraries for Circuit Playground boards.
Installing the Project Code
To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory Circuit_Playground_Brake_Light/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
CIRCUITPY

# SPDX-FileCopyrightText: 2020 Eva Herrada for Adafruit Industries # # SPDX-License-Identifier: MIT import time import math from adafruit_circuitplayground import cp brightness = 0 # List that holds the last 10 z-axis acceleration values read from the accelerometer. # Used for the n=10 moving average last10 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # List that holds the last 50 z-axis acceleration values read from the accelerometer. # Used for the n=50 moving average last50 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] consecutive_triggers = 0 cp.pixels.fill((255, 0, 0)) light_on = False while True: x, y, z = cp.acceleration # moving average n=10, not super smooth, but it substantially lowers the amount of noise last10.append(z) last10.pop(0) avg10 = sum(last10)/10 # moving average n=50, very smooth last50.append(z) last50.pop(0) avg50 = sum(last50)/50 # If the difference between the moving average of the last 10 points and the moving average of # the last 50 points is greater than 1 m/s^2, this is true if avg10 - avg50 > 1: if consecutive_triggers > 3: # Is true when avg10-avg50 > 1m/s^2 at least 3 times in a row # Detects shake. Due to the very low shake threshold, this alone would have very low # specificity. This was mitigated by having it only run when the acceleration # difference is greater than 1 m/s^2 at least 3 times in a row. if not cp.shake(shake_threshold=10): # Set brightness to max, timestamp when it was set to max, and set light_on to true cp.pixels.brightness = 1 start = time.monotonic() light_on = True consecutive_triggers += 1 # increase it whether or not the light is turned on # light_on variable is for short circuiting. Not really necessary, just makes things run faster elif not light_on or time.monotonic() - start > 0.4: # Sine wave used for the color breathing effect. # Max brightness can be adjusted with the coefficient. cp.pixels.brightness = abs(math.sin(brightness)) * 0.5 brightness += 0.05 consecutive_triggers = 0 light_on = False time.sleep(0.02)
import time import math from adafruit_circuitplayground import cp
Then, it defines the variables it'll need in the main loop. brightness
is used to control the brightness for the breathing effect. last10
becomes a 10-point moving average of the z-axis acceleration, and last50
does the same but with 50 points instead. After that, consecutive_triggers
is defined. It is used to make sure the brake light only turns on when the acceleration threshold is met multiple times in a row to prevent false positives. Then, the NeoPixels are set to red. If you'd like to change the color the light uses, just change the RGB values there. light_on
is also defined. It's used to bypass an if
statement later on in the code to improve performance.
brightness = 0 last10 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] last50 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] consecutive_triggers = 0 cp.pixels.fill((255, 0, 0)) light_on = False
This next part just starts the while
loop and gets the values from the accelerometer every time it runs.
while True: x, y, z = cp.acceleration
These next few lines are used to take the moving average. For each moving average, the last value gathered is appended to the list, and then the first is removed. Then, the average of each list is taken. This decreases noise coming from the accelerometer, which makes the brake light function more reliably, and prevents false positives.
last10.append(z) last10.pop(0) avg10 = sum(last10)/10 last50.append(z) last50.pop(0) avg50 = sum(last50)/50
This block decides whether or not the brake light is to be activated. First, it determines if the Z acceleration has been increasing by comparing the two moving averages. If that increase is in excess of 1 m/s², it will then check to see how many times that has recently happened.
If that has happened 3 or more times in a row, it then goes to check if the accelerometer in the board has detected being shaken. If the accelerometer is detecting being shaken, it won't turn on the light.
If it hasn't been shaken, and the previous conditions have all been met, then it sets the brightness to maximum and makes a timestamp for when that happened. That timestamp will later be compared to the current time. This method was used instead of time.sleep
to preserve the validity of the moving averages, as sleeping would keep them from updating.
The variable light_on
is then set to true. This will disable the if
statement in the next block.
Regardless of what happens, as long as the initial condition (avg10 - avg50 > 1
) was satisfied, the variable consecutive_triggers
is increased by 1.
if avg10 - avg50 > 1: if consecutive_triggers > 3: if not cp.shake(shake_threshold=10): cp.pixels.brightness = 1 start = time.monotonic() light_on = True consecutive_triggers += 1
The first line here is true when light_on
is False, or when start
subtracted from the current time is greater than 0.4 seconds. light_on
uses short-circuiting to only have the second half of the if statement evaluated if light_on
is True
, which means that the brake light is currently on.
Then, if the statement evaluates to true, the breathing effect will continue. It uses a sine wave to generate the wave-shaped brightness curve. If you want to make it slower, decrease the number brightness is increased by, and if you want to adjust the max brightness, change the coefficient outside of the absolute value function.
Then, consecutive_triggers
is reset and light_on
is set to False
.
If the condition does not evaluate to True
, then the code repeats.
elif not light_on or time.monotonic() - start > 0.4: cp.pixels.brightness = abs(math.sin(brightness)) * 0.5 brightness += 0.05 consecutive_triggers = 0 light_on = False
After this, there's a time.sleep
. This was added in because when there wasn't one, something wacky happened in the serial console that kept crashing my computer.
time.sleep(0.02)
Page last edited January 20, 2025
Text editor powered by tinymce.