Last active
April 26, 2023 06:22
-
-
Save jkunkee/f1c9fc8767757f0e451ad72e5a18ebe5 to your computer and use it in GitHub Desktop.
Adafruit LED Glasses firmware (originally for FRC 2023 Worlds) (MIT license)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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