This commit is contained in:
caternuson 2024-01-12 11:59:49 -08:00
parent a55a80296d
commit 697c5b5469
5 changed files with 261 additions and 0 deletions

View file

@ -0,0 +1,104 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# modified from original
# 2024 Carter Nelson
"""
Modifed from original project code here:
https://learn.adafruit.com/adafruit-eyelights-led-glasses-and-driver/bmp-animation
This version only uses the matrix part of the glasses. The ring
LEDs are not used. BMP image files should be properly formatted
for the matrix (18x5 sprites) and placed in the /images folder.
Current animation can be changed by tilting head back. Brightness
can be changed by pressing the user button.
"""
import os
import time
import board
from busio import I2C
import digitalio
import adafruit_lis3dh
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
from eyelights_anim import EyeLightsAnim
# --| User Config |------------------------------
ANIM_DELAY = 0.07
BRIGHT_LEVELS = (0, 10, 20, 40)
# --| User Config |------------------------------
# use all BMPs found in /images dir
ANIM_FILES = [
"/images/" + f
for f in os.listdir("/images")
if f.endswith(".bmp") and not f.startswith("._")
]
# HARDWARE SETUP -----------------------
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)
button = digitalio.DigitalInOut(board.SWITCH)
button.switch_to_input(digitalio.Pull.UP)
# Initialize the IS31 LED driver, buffered for smoother animation
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
glasses.show() # Clear any residue on startup
glasses.global_current = 20 # Just middlin' bright, please
# ANIMATION SETUP ----------------------
# Two indexed-color BMP filenames are specified: first is for the LED matrix
# portion, second is for the LED rings -- or pass None for one or the other
# if not animating that part. The two elements, matrix and rings, share a
# few LEDs in common...by default the rings appear "on top" of the matrix,
# or you can optionally pass a third argument of False to have the rings
# underneath. There's that one odd unaligned pixel between the two though,
# so this may only rarely be desirable.
anim = EyeLightsAnim(glasses, ANIM_FILES[0], None)
# MAIN LOOP ----------------------------
# This example just runs through a repeating cycle. If you need something
# else, like ping-pong animation, or frames based on a specific time, the
# anim.frame() function can optionally accept two arguments: an index for
# the matrix animation, and an index for the rings.
_, filtered_y, _ = lis3dh.acceleration
looking_up = filtered_y < -5
anim_index = 0
bright_index = 0
while True:
# read accelo and check if looking up
_, y, _ = lis3dh.acceleration
filtered_y = filtered_y * 0.85 + y * 0.15
if looking_up:
if filtered_y > -3.5:
looking_up = False
else:
if filtered_y < -5:
looking_up = True
anim_index = (anim_index + 1) % len(ANIM_FILES)
print(ANIM_FILES[anim_index])
anim.matrix_filename = ANIM_FILES[anim_index]
# check for button press
if not button.value:
bright_index = (bright_index + 1) % len(BRIGHT_LEVELS)
print(BRIGHT_LEVELS[bright_index])
glasses.global_current = BRIGHT_LEVELS[bright_index]
while not button.value:
pass
anim.frame() # Advance matrix and rings by 1 frame and wrap around
glasses.show() # Update LED matrix
time.sleep(ANIM_DELAY) # Pause briefly

View file

@ -0,0 +1,157 @@
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# modified from original to allow changing matrix BMP file
# 2024 Carter Nelson
"""
EyeLightsAnim provides EyeLights LED glasses with pre-drawn frame-by-frame
animation from BMP images. Sort of a catch-all for modest projects that may
want to implement some animation without having to express that animation
entirely in code. The idea is based upon two prior projects:
https://learn.adafruit.com/32x32-square-pixel-display/overview
learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels
The 18x5 matrix and the LED rings are regarded as distinct things, fed from
two separate BMPs (or can use just one or the other). The former guide above
uses the vertical axis for time (like a strip of movie film), while the
latter uses the horizontal axis for time (as in audio or video editing).
Despite this contrast, the same conventions are maintained here to avoid
conflicting explanations...what worked in those guides is what works here,
only the resolutions are different."""
import displayio
import adafruit_imageload
def gamma_adjust(palette):
"""Given a color palette that was returned by adafruit_imageload, apply
gamma correction and place results back in original palette. This makes
LED brightness and colors more perceptually linear, to better match how
the source BMP might've appeared on screen."""
for index, entry in enumerate(palette):
palette[index] = sum(
[
int(((((entry >> shift) & 0xFF) / 255) ** 2.6) * 255 + 0.5) << shift
for shift in range(16, -1, -8)
]
)
class EyeLightsAnim:
"""Class encapsulating BMP image-based frame animation for the matrix
and rings of an LED_Glasses object."""
def __init__(self, glasses, matrix_filename, ring_filename, rings_on_top=True):
"""Constructor for EyeLightsAnim. Accepts an LED_Glasses object and
filenames for two indexed-color BMP images: first is a "sprite
sheet" for animating on the matrix portion of the glasses, second is
a pixels-over-time graph for the rings portion. Either filename may
be None if not used. Because the matrix and rings share some pixels
in common, the last argument determines the "stacking order" - which
of the two bitmaps is drawn later or "on top." Default of True
places the rings over the matrix, False gives the matrix priority.
It's possible to use transparent palette indices but that may be
more trouble than it's worth."""
self.glasses = glasses
self.matrix_bitmap = self.ring_bitmap = None
self.rings_on_top = rings_on_top
if matrix_filename:
self.matrix_filename = matrix_filename
if ring_filename:
self.ring_bitmap, self.ring_palette = adafruit_imageload.load(
ring_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
)
if self.ring_bitmap.height < 48:
raise ValueError("Ring bitmap must be at least 48 pixels tall")
gamma_adjust(self.ring_palette)
self.ring_frames = self.ring_bitmap.width
self.ring_frame = self.ring_frames - 1
def draw_matrix(self, matrix_frame=None):
"""Draw the matrix portion of EyeLights from one frame of the matrix
bitmap "sprite sheet." Can either request a specific frame index
(starting from 0), or pass None (or no arguments) to advance by one
frame, "wrapping around" to beginning if needed. For internal use by
library; user code should call frame(), not this function."""
if matrix_frame: # Go to specific frame
self.matrix_frame = matrix_frame
else: # Advance one frame forward
self.matrix_frame += 1
self.matrix_frame %= self.matrix_frames # Wrap to valid range
xoffset = self.matrix_frame % self.tiles_across * self.glasses.width
yoffset = self.matrix_frame // self.tiles_across * self.glasses.height
for y in range(self.glasses.height):
y1 = y + yoffset
for x in range(self.glasses.width):
idx = self.matrix_bitmap[x + xoffset, y1]
if not self.matrix_palette.is_transparent(idx):
self.glasses.pixel(x, y, self.matrix_palette[idx])
def draw_rings(self, ring_frame=None):
"""Draw the rings portion of EyeLights from one frame of the rings
bitmap graph. Can either request a specific frame index (starting
from 0), or pass None (or no arguments) to advance by one frame,
'wrapping around' to beginning if needed. For internal use by
library; user code should call frame(), not this function."""
if ring_frame: # Go to specific frame
self.ring_frame = ring_frame
else: # Advance one frame forward
self.ring_frame += 1
self.ring_frame %= self.ring_frames # Wrap to valid range
for y in range(24):
idx = self.ring_bitmap[self.ring_frame, y]
if not self.ring_palette.is_transparent(idx):
self.glasses.left_ring[y] = self.ring_palette[idx]
idx = self.ring_bitmap[self.ring_frame, y + 24]
if not self.ring_palette.is_transparent(idx):
self.glasses.right_ring[y] = self.ring_palette[idx]
def frame(self, matrix_frame=None, ring_frame=None):
"""Draw one frame of animation to the matrix and/or rings portions
of EyeLights. Frame index (starting from 0) for matrix and rings
respectively can be passed as arguments, or either/both may be None
to advance by one frame, 'wrapping around' to beginning if needed.
Because some pixels are shared in common between matrix and rings,
the "stacking order" -- which of the two appears "on top", is
specified as an argument to the constructor."""
if self.matrix_bitmap and self.rings_on_top:
self.draw_matrix(matrix_frame)
if self.ring_bitmap:
self.draw_rings(ring_frame)
if self.matrix_bitmap and not self.rings_on_top:
self.draw_matrix(matrix_frame)
@property
def matrix_filename(self):
return self._matrix_filename
@matrix_filename.setter
def matrix_filename(self, matrix_filename):
self._matrix_filename = matrix_filename
self.matrix_bitmap, self.matrix_palette = adafruit_imageload.load(
matrix_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
)
if (self.matrix_bitmap.width < self.glasses.width) or (
self.matrix_bitmap.height < self.glasses.height
):
raise ValueError("Matrix bitmap must be at least 18x5 pixels")
gamma_adjust(self.matrix_palette)
self.tiles_across = self.matrix_bitmap.width // self.glasses.width
self.tiles_down = self.matrix_bitmap.height // self.glasses.height
self.matrix_frames = self.tiles_across * self.tiles_down
self.matrix_frame = self.matrix_frames - 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B