## Required libraries

This project does not require any libraries on top of the standard libraries for Circuit Playground boards.

## Installing the Project Code

Copy code.py to the CIRCUITPY drive which appears when the Circuit Playground is connected to your computer via a USB cable.

```import time
import math

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)
```

## Code Run Through

First, the code imports the required libraries.

```import time
import math

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)`

This guide was first published on Jun 16, 2020. It was last updated on Jun 16, 2020.

This page (CircuitPython Setup and Code) was last updated on Jan 26, 2022.