Skip to content

Instantly share code, notes, and snippets.

@jkunkee
Last active April 26, 2023 06:22
Show Gist options
  • Save jkunkee/f1c9fc8767757f0e451ad72e5a18ebe5 to your computer and use it in GitHub Desktop.
Save jkunkee/f1c9fc8767757f0e451ad72e5a18ebe5 to your computer and use it in GitHub Desktop.
Adafruit LED Glasses firmware (originally for FRC 2023 Worlds) (MIT license)
import board
# Do what the board object should have already done
import neopixel
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
import adafruit_is31fl3741
import adafruit_lis3dh
from digitalio import DigitalInOut, Direction, Pull
from adafruit_debouncer import Debouncer
from busio import I2C
import audiobusio
board.I2C().deinit()
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
pin = DigitalInOut(board.SWITCH)
pin.direction = Direction.INPUT
pin.pull = Pull.UP
switch = Debouncer(pin, 0.05)
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
neo = neopixel.NeoPixel(board.NEOPIXEL, 1)
# https://learn.adafruit.com/adafruit-pdm-microphone-breakout/
# ZeroPDM and ZeroDMA are popular libraries used there
# See also https://learn.adafruit.com/bluefruit-dashboard-web-bluetooth-chrome/circuitpython
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16)
# N.B. LIS3DH is only rated for 400KHz I2C operation but appears to tolerate 1MHz
accel = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=DigitalInOut(board.ACCELEROMETER_INTERRUPT))
print(accel.acceleration)
#ble
from rainbowio import colorwheel
#import adafruit_led_animation
#from adafruit_bitmap_font import bitmap_font
import adafruit_fancyled.adafruit_fancyled as fancy
import time
# My animation objects have
# done
# def reset(this):
# def animate(this):
class MyAnimationSequence:
def __init__(this, animation_list) -> None:
this.done = False
this.list = animation_list
def reset(this):
for anim in this.list:
anim.reset()
this.done = False
def animate(this):
for anim in this.list:
if not anim.done:
anim.animate()
break
this.done = all([ a.done for a in this.list ])
class TextScroll:
done = False
last_update = 0
interval = 0.05
current_grid_pos = 0
font = {
'1': [
[0, 0, 0, 1, 0],
[1, 1, 1, 1, 1],
],
'3': [
[1, 0, 1, 0, 1],
[1, 0, 1, 0, 1],
[1, 1, 1, 1, 1],
],
'4': [
[0, 0, 1, 1, 1],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
],
'F': [
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 1],
[0, 0, 1, 0, 1],
],
'H': [
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
],
'I': [
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
],
'M': [
[1, 1, 1, 1, 1],
[0, 0, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 1, 1, 1],
],
'P': [
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 1],
[0, 0, 1, 1, 1],
],
'R': [
[1, 1, 1, 1, 1],
[0, 1, 1, 0, 1],
[1, 0, 1, 1, 0],
],
'S': [
[1, 0, 1, 1, 1],
[1, 0, 1, 0, 1],
[1, 1, 1, 0, 1],
],
'T': [
[0, 0, 0, 0, 1],
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 1],
],
'a': [
# A small capital A is also quite workable
[1, 1, 0, 1, 0],
[1, 0, 1, 1, 0],
[1, 1, 1, 0, 0],
],
'c': [
[1, 1, 1, 0, 0],
[1, 0, 1, 0, 0],
[1, 0, 1, 0, 0],
],
'g': [
[1, 1, 1, 1, 0],
[1, 0, 0, 1, 0],
[1, 1, 0, 1, 0],
],
'h': [
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[1, 1, 1, 0, 0],
],
'i': [
[1, 1, 0, 1, 0],
],
'k': [
[1, 1, 1, 1, 1],
[0, 1, 1, 0, 0],
[1, 0, 1, 0, 0],
],
'm': [
[1, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 0, 0, 0],
],
'n': [
[1, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 0, 0],
],
'o': [
[1, 1, 1, 0, 0],
[1, 0, 1, 0, 0],
[1, 1, 1, 0, 0],
],
'r': [
[1, 1, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
],
's': [
[1, 0, 1, 0, 0],
[1, 0, 1, 1, 0],
[0, 1, 0, 1, 0],
],
't': [
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
],
'w': [
[0, 1, 1, 0, 0],
[1, 0, 0, 0, 0],
[0, 1, 1, 0, 0],
[1, 0, 0, 0, 0],
[0, 1, 1, 0, 0],
],
'y': [
[1, 0, 1, 1, 0],
[1, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
],
' ': [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
'!': [
[1, 0, 1, 1, 1],
],
'unknown': [
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
],
}
def __init__(this, grid, message, update_interval, foreground_color, background_color):
this.interval = update_interval
this.foreground_color = foreground_color
this.background_color = background_color
this.grid = grid
this.message = message
def reset(this):
this.current_grid_pos = 0
this.last_update = 0
this.done = False
def animate(this):
# Check to see if the time step has passed
now = time.monotonic()
if now - this.last_update > this.interval:
# If it has, we can render the next frame.
this.last_update = now
# Scoot the text over by one column
this.current_grid_pos += 1
# Clear the canvas; assumes complete grid ownership
this.grid.fill(this.background_color)
# Render each letter in turn
cursor_pos = this.current_grid_pos
c = this.message[0]
# Skip so the first text has time to slide visibly
cursor_pos = this.grid.width
for c in this.message:
if c in this.font:
text_array = this.font[c]
else:
text_array = this.font['unknown']
for col_idx in range(len(text_array)):
col = text_array[col_idx]
for row_idx in range(len(col)):
if col[len(col)-1-row_idx]:
this.grid.pixel(cursor_pos + col_idx - this.current_grid_pos, row_idx, this.foreground_color)
cursor_pos += len(text_array)
cursor_pos += 1
if this.current_grid_pos > len(this.message) * 4 + this.grid.width:
this.done = True
class EyeColorWheelAnimation:
def __init__(this, left_ring, right_ring):
this.left_ring = left_ring
this.right_ring = right_ring
this.reset()
def reset(this):
# Start with the colorwheel not rotated at all around the eye
this.wheeloffset = 0
def animate(this):
for i in range(24):
hue = colorwheel(i * 256 // 24 + this.wheeloffset)
this.right_ring[i] = hue
this.left_ring[23 - i] = hue
this.wheeloffset += 5
team_4131_green = colorwheel(4*16 + 1*8 + 7)
off = 0
yellow = 0xffff00
white = 0xffffff
rat_fight_red = 0xff0000
animation_list = [
TextScroll(glasses.grid, '4131 Iron Patriots!', 0.05*3.0, team_4131_green, off),
TextScroll(glasses.grid, 'Rat Fight Rocks!', 0.05*3.0, rat_fight_red, off),
TextScroll(glasses.grid, 'Hi Mom!', 0.05*3.0, yellow, off),
TextScroll(glasses.grid, 'Say Taiwan', 0.05*3.0, white, off),
]
animation_sequence = MyAnimationSequence(animation_list)
current_animation_idx = 0
wheel = EyeColorWheelAnimation(glasses.left_ring, glasses.right_ring)
def RenderColorPicker(grid, left_edge_hsv, zoom_div):
hue_step = 255 / (zoom_div+1) / grid.width
for col_idx in range(grid.width):
for row_idx in range(grid.height):
grid.pixel(col_idx, row_idx, colorwheel(left_edge_hsv + col_idx * hue_step))
def RenderTransFlag(grid, start_col: int, width: int):
#trans_blue = fancy. # fancy.unpack(0x5bcefa). .pack() # CHSV(0.5, 1.0, 0.1).pack() #0x5bcefa
trans_blue = colorwheel(128)
#trans_pink = 0#0xf5a9b8
trans_pink = colorwheel(228)
trans_white = 0xcccccc
for col_offset in range(width):
col_idx = start_col + col_offset
glasses.grid.pixel(col_idx, 0, trans_blue)
glasses.grid.pixel(col_idx, 1, trans_pink)
glasses.grid.pixel(col_idx, 2, trans_white)
glasses.grid.pixel(col_idx, 3, trans_pink)
glasses.grid.pixel(col_idx, 4, trans_blue)
while True:
switch.update()
led.value = switch.value
if switch.fell:
current_animation_idx = (current_animation_idx + 1) % len(animation_list)
animation_list[current_animation_idx].reset()
wheel.animate() # First so the grid animation can overwrite shared pixels
neo[0] = colorwheel(wheel.wheeloffset)
anim = animation_list[current_animation_idx]
if anim.done:
anim.reset()
anim.animate()
#RenderColorPicker(glasses.grid, 228, 2)
#RenderTransFlag(glasses.grid, 1, 6)
# These two pixels decrease the grid's legibility
glasses.right_ring[24 - 4] = 0
glasses.left_ring[4] = 0
glasses.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment