Categories
Classes Projects

Make a Color-Changing Silhouette Light

Final silhouette light code

This code uses:

  • An external 10-LED NeoPixel strip attached to pad A1
  • Capacitive touch controls on pads A2, A3, A4, A5, and A6 to change color palettes
  • Button inputs:
    • left/A button steps through the rainbow palette
    • right/B button starts a cycle of smooth gradients between random colors.
  • The FancyLED library for color palette manipulation and generating gradients between colors.
  • Bluetooth controls and sensor reports
import board
import neopixel
import touchio
import adafruit_fancyled.adafruit_fancyled as fancy
from adafruit_circuitplayground import cp
from adafruit_circuitplayground.bluefruit import cpb
import adafruit_ble
import adafruit_ble.advertising.standard
import adafruit_bluefruit_connect

from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket
# mobile app has all these controls, import so we don't crash if they're activated
# sends mobile device data to the CPX, we silently ignore it)
from adafruit_bluefruit_connect.accelerometer_packet import AccelerometerPacket
from adafruit_bluefruit_connect.gyro_packet import GyroPacket
from adafruit_bluefruit_connect.magnetometer_packet import MagnetometerPacket
from adafruit_bluefruit_connect.quaternion_packet import QuaternionPacket
from adafruit_bluefruit_connect.location_packet import LocationPacket

from adafruit_ble_adafruit.addressable_pixel_service import AddressablePixelService
from adafruit_ble_adafruit.light_sensor_service import LightSensorService
from adafruit_ble_adafruit.temperature_service import TemperatureService
from adafruit_ble_adafruit.accelerometer_service import AccelerometerService

import time
import random

######################### initialization

NUM_LEDS = 10
CPX_PIN = board.A1 # CPX Neopixels live on pin D8
LED_BRIGHTNESS = 1.0
LIGHT_BRIGHT = 100 # Light sensor "bright" threshhold
OFFSET_MAX = NUM_LEDS * 10
DELAY = 0.0

np = neopixel.NeoPixel(CPX_PIN, NUM_LEDS, brightness=LED_BRIGHTNESS, auto_write=False)
# np = cp.pixels

# set up service to get neopixel color from BLE app
NEOPIXEL_BUF_LENGTH = 3 # 3 RGB bytes
neopixel_buf = bytearray(NEOPIXEL_BUF_LENGTH)
neopixel_svc = AddressablePixelService()

sensor_measure_period = 100 # in milliseconds
sensor_last_update = 0

light_svc = LightSensorService()
temp_svc = TemperatureService()
accel_svc = AccelerometerService()

uart_svc = UARTService() # for mobile app inputs

ble = adafruit_ble.BLERadio()

# The Web Bluetooth dashboard identifies known boards by their advertised name,
# TODO: replace 'YOURNAMEHERE' with your name or initials
ble.name = "YOURNAMEHERE CPX"

# The Bluefruit Playground app looks in the manufacturer data
# in the advertisement. That data uses the USB PID as a unique ID.
# Adafruit Circuit Playground Bluefruit USB PID:
# Arduino: 0x8045, CircuitPython: 0x8046, app supports either
adv = adafruit_ble.advertising.standard.ProvideServicesAdvertisement()
adv.pid = 0x8046

palettes = {
'fire': {
'colors': [ # Reds and Yellows
fancy.CRGB(160, 30, 0),
fancy.CRGB(127, 65, 0),
fancy.CRGB(224, 30, 0),
fancy.CRGB(124, 30, 0),
fancy.CRGB(250, 80, 0),
fancy.CRGB(150, 80, 0),
fancy.CRGB(100, 40, 0),
fancy.CRGB(200, 140, 0),
fancy.CRGB(200, 40, 0),
],
'offset_interval': 7,
'offset_max': NUM_LEDS * 10,
'delay': 0.1,
},

'sunset': {
'colors': [ # Reds and Yellows - slow
fancy.CRGB(127, 65, 0),
fancy.CRGB(250, 180, 0),
fancy.CRGB(160, 30, 0),
fancy.CRGB(200, 140, 0),
fancy.CRGB(224, 30, 0),
fancy.CRGB(250, 80, 0),
],
'offset_interval': 1,
'offset_max': NUM_LEDS * 10000,
'delay': 0.2,
},

'ice': {
'colors': [ # blues
fancy.CRGB(0.0, 0.0, 0.8),
fancy.CRGB(0.0, 0.0, 0.8),
fancy.CRGB(0.3, 0.0, 0.8),
fancy.CRGB(0.2, 0.0, 1.0),
fancy.CRGB(0.0, 0.0, 0.9),
fancy.CRGB(0.1, 0.0, 0.6),
fancy.CRGB(0.2, 0.0, 0.7),
fancy.CRGB(0.0, 0.0, 0.8),
fancy.CRGB(0.1, 0.0, 0.5),
fancy.CRGB(0.2, 0.0, 0.6),
fancy.CRGB(0.1, 0.0, 0.9),
],
'offset_interval': 4,
'offset_max': NUM_LEDS * 10,
'delay': 0.2,

},

'rainbow': {
'colors': [
fancy.CRGB(1.0, 0.0, 0.0), # Red
fancy.CRGB(0.8, 0.3, 0.0), # Orange
fancy.CRGB(0.8, 0.3, 0.0), # Orange
fancy.CRGB(0.8, 0.6, 0.0), # Yellow
fancy.CRGB(0.8, 0.6, 0.0), # Yellow
fancy.CRGB(0.5, 0.7, 0.0), # Yellow Green
fancy.CRGB(0.0, 1.0, 0.0), # Green
fancy.CRGB(0.0, 0.5, 0.5), # Cyan
fancy.CRGB(0.0, 0.0, 1.0), # Blue
fancy.CRGB(0.2, 0.0, 0.8), # Violet
fancy.CRGB(0.2, 0.0, 0.8), # Violet
fancy.CRGB(0.5, 0.0, 0.5), # Purple
fancy.CRGB(0.5, 0.0, 0.5), # Purple
fancy.CRGB(0.7, 0.0, 0.3), # Magenta
],
'offset_interval': 1,
'offset_max': NUM_LEDS * 10,
'delay': 0.05,

},

'bright': {
'colors': [ # Reading Lamp mode - Warm Yellow
fancy.CRGB(255, 175, 80)
],
'offset_interval': 0,
'offset_max': NUM_LEDS * 10,
'delay': 0.1,
},

'dark': {
'colors': [ # dark = off
fancy.CRGB(0, 0, 0)
],
'offset_interval': 0,
'offset_max': NUM_LEDS * 10,
'delay': 0.1,
},
}


######################### function definitions

def set_palette(offset, palette):
palette_colors = palette["colors"]
palette_len = len(palette_colors)

for i in range(NUM_LEDS):
# Load each pixel's color from the palette using an offset, run it
# through the gamma function, pack RGB value and assign to pixel
palette_offset = (offset + i) / OFFSET_MAX
color = fancy.palette_lookup(palette_colors, palette_offset)
color = fancy.gamma_adjust(color, brightness=cur_brightness)

# print("LED " + str(i) + ", Offset " + str(palette_offset) + ", Color " + str(color))

np[i] = color.pack()
color = fancy.CRGB(0, 0, 0)
np.show()


def set_color(c):
# c = fancy.gamma_adjust(c, brightness=LED_BRIGHTNESS)
# print("New color:", c)
for i in range(NUM_LEDS):
np[i] = c.pack()
np.show()


def scale(value):
# scale analogin 0-65535 values to 0-100
return (value / 65535) * 100


######################### main

touch_A6 = touchio.TouchIn(board.A6)
touch_A5 = touchio.TouchIn(board.A5)
touch_A4 = touchio.TouchIn(board.A4)
touch_A3 = touchio.TouchIn(board.A3)
touch_A2 = touchio.TouchIn(board.A2)

# set initial palette to run on startup
palette_choice = "sunset"

o = 0 # Positional offset into color palette to get it to 'spin'
cur_brightness = LED_BRIGHTNESS

# set starting lights
set_palette(o, palettes[palette_choice])

# initialize random gradient
gradient_steps = 20
random_sleep = 0.1
random_color = fancy.CRGB(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
next_random = fancy.CRGB(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
gradient = fancy.expand_gradient([
(0.0, random_color),
(1.0, next_random)
], gradient_steps)

palette_selected = True
cycle_rainbow = False
cycle_random = False
random_color = fancy.CRGB(random.randint(128, 255), random.randint(128, 255), random.randint(128, 255))

ble.start_advertising(adv)

while True:
now_msecs = time.monotonic_ns() // 1000000 # pylint: disable=no-member

if not ble.connected and not ble.advertising:
ble.start_advertising(adv)

# measure & send sensor values
if now_msecs - sensor_last_update >= sensor_measure_period:
light_svc.light_level = cp.light
uart_svc.write("{},{}\n".format(cp.light, cp.temperature))
temp_svc.temperature = cp.temperature
accel_svc.acceleration = cp.acceleration
sensor_last_update = now_msecs

# get neopixel inut from BLE app/dashboard
neopixel_values = neopixel_svc.values

if neopixel_values is not None:
start = neopixel_values.start
if start > NEOPIXEL_BUF_LENGTH:
continue
cycle_rainbow = False
cycle_random = False
palette_selected = False
data = neopixel_values.data
data_len = min(len(data), NEOPIXEL_BUF_LENGTH - start)
neopixel_buf[start: start + data_len] = data[:data_len]
print("Got neopixel color:", neopixel_buf[1], neopixel_buf[0], neopixel_buf[2])
# data comes in GRB order
np.fill([neopixel_buf[1], neopixel_buf[0], neopixel_buf[2]])
np.show()



# get any waiting UART inputs (color or button packet)
elif uart_svc.in_waiting:
pkt = Packet.from_stream(uart_svc)

if isinstance(pkt, ButtonPacket):
if pkt.button == ButtonPacket.LEFT:
cycle_rainbow = True
cycle_random = False
palette_selected = False
print("Got UART button LEFT")
elif pkt.button == ButtonPacket.RIGHT:
cycle_rainbow = False
cycle_random = True
palette_selected = False
print("Got UART button RIGHT")

elif isinstance(pkt, ColorPacket):
cycle_rainbow = False
cycle_random = False
palette_selected = False
np.fill(pkt.color)
np.show()
print("Got UART neopixel color:", pkt.color)


else:
print("Got unused packet:", pkt.to_bytes())

elif cp.button_a:
cycle_rainbow = True
cycle_random = False
palette_selected = False

elif cp.button_b:
cycle_rainbow = False
cycle_random = True
palette_selected = False

elif touch_A2.value:
palette_choice = "sunset"
palette_selected = True
cycle_random = False

elif touch_A3.value:
palette_choice = "rainbow"
palette_selected = True
cycle_random = False

elif touch_A4.value:
palette_choice = "fire"
palette_selected = True
cycle_random = False

elif touch_A5.value:
palette_choice = "ice"
palette_selected = True
cycle_random = False

elif touch_A6.value:
palette_choice = "bright"
palette_selected = True
cycle_random = False

if cycle_random:
palette_selected = False
cycle_random = True
cycle_rainbow = True
palette_choice = "rainbow"

if (o % gradient_steps) == 0:
random_color = next_random
next_random = fancy.CRGB(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
gradient = fancy.expand_gradient([
(0.0, random_color),
(1.0, next_random)
], gradient_steps)

# for i in range(gradient_steps):
i = o % gradient_steps
o += 1
set_color(gradient[i])
time.sleep(random_sleep)


elif cycle_rainbow:
palette_selected = False
cycle_random = False
cycle_rainbow = False # just switch color once
palette_choice = "rainbow"
o = (o + 1) % len(palettes["rainbow"]["colors"])
set_color(palettes["rainbow"]["colors"][o])

elif palette_selected:
o = (o + palettes[palette_choice]["offset_interval"]) % palettes[palette_choice]['offset_max']
set_palette(o, palettes[palette_choice])
time.sleep(palettes[palette_choice]['delay'])

# print("Palette (", palette_choice , "):", palette_selected, "Rainbow:", cycle_rainbow, "Random", cycle_random)