Skip to content

Commit 697c5b5

Browse files
committed
add code
1 parent a55a802 commit 697c5b5

File tree

5 files changed

+261
-0
lines changed

5 files changed

+261
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# modified from original
6+
# 2024 Carter Nelson
7+
8+
"""
9+
Modifed from original project code here:
10+
https://learn.adafruit.com/adafruit-eyelights-led-glasses-and-driver/bmp-animation
11+
12+
This version only uses the matrix part of the glasses. The ring
13+
LEDs are not used. BMP image files should be properly formatted
14+
for the matrix (18x5 sprites) and placed in the /images folder.
15+
16+
Current animation can be changed by tilting head back. Brightness
17+
can be changed by pressing the user button.
18+
"""
19+
20+
import os
21+
import time
22+
import board
23+
from busio import I2C
24+
import digitalio
25+
import adafruit_lis3dh
26+
import adafruit_is31fl3741
27+
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
28+
from eyelights_anim import EyeLightsAnim
29+
30+
# --| User Config |------------------------------
31+
ANIM_DELAY = 0.07
32+
BRIGHT_LEVELS = (0, 10, 20, 40)
33+
# --| User Config |------------------------------
34+
35+
# use all BMPs found in /images dir
36+
ANIM_FILES = [
37+
"/images/" + f
38+
for f in os.listdir("/images")
39+
if f.endswith(".bmp") and not f.startswith("._")
40+
]
41+
42+
# HARDWARE SETUP -----------------------
43+
44+
i2c = I2C(board.SCL, board.SDA, frequency=1000000)
45+
46+
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)
47+
48+
button = digitalio.DigitalInOut(board.SWITCH)
49+
button.switch_to_input(digitalio.Pull.UP)
50+
51+
# Initialize the IS31 LED driver, buffered for smoother animation
52+
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)
53+
glasses.show() # Clear any residue on startup
54+
glasses.global_current = 20 # Just middlin' bright, please
55+
56+
57+
# ANIMATION SETUP ----------------------
58+
59+
# Two indexed-color BMP filenames are specified: first is for the LED matrix
60+
# portion, second is for the LED rings -- or pass None for one or the other
61+
# if not animating that part. The two elements, matrix and rings, share a
62+
# few LEDs in common...by default the rings appear "on top" of the matrix,
63+
# or you can optionally pass a third argument of False to have the rings
64+
# underneath. There's that one odd unaligned pixel between the two though,
65+
# so this may only rarely be desirable.
66+
anim = EyeLightsAnim(glasses, ANIM_FILES[0], None)
67+
68+
# MAIN LOOP ----------------------------
69+
70+
# This example just runs through a repeating cycle. If you need something
71+
# else, like ping-pong animation, or frames based on a specific time, the
72+
# anim.frame() function can optionally accept two arguments: an index for
73+
# the matrix animation, and an index for the rings.
74+
75+
_, filtered_y, _ = lis3dh.acceleration
76+
looking_up = filtered_y < -5
77+
anim_index = 0
78+
bright_index = 0
79+
80+
while True:
81+
# read accelo and check if looking up
82+
_, y, _ = lis3dh.acceleration
83+
filtered_y = filtered_y * 0.85 + y * 0.15
84+
if looking_up:
85+
if filtered_y > -3.5:
86+
looking_up = False
87+
else:
88+
if filtered_y < -5:
89+
looking_up = True
90+
anim_index = (anim_index + 1) % len(ANIM_FILES)
91+
print(ANIM_FILES[anim_index])
92+
anim.matrix_filename = ANIM_FILES[anim_index]
93+
94+
# check for button press
95+
if not button.value:
96+
bright_index = (bright_index + 1) % len(BRIGHT_LEVELS)
97+
print(BRIGHT_LEVELS[bright_index])
98+
glasses.global_current = BRIGHT_LEVELS[bright_index]
99+
while not button.value:
100+
pass
101+
102+
anim.frame() # Advance matrix and rings by 1 frame and wrap around
103+
glasses.show() # Update LED matrix
104+
time.sleep(ANIM_DELAY) # Pause briefly
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# modified from original to allow changing matrix BMP file
6+
# 2024 Carter Nelson
7+
8+
"""
9+
EyeLightsAnim provides EyeLights LED glasses with pre-drawn frame-by-frame
10+
animation from BMP images. Sort of a catch-all for modest projects that may
11+
want to implement some animation without having to express that animation
12+
entirely in code. The idea is based upon two prior projects:
13+
14+
https://learn.adafruit.com/32x32-square-pixel-display/overview
15+
learn.adafruit.com/circuit-playground-neoanim-using-bitmaps-to-animate-neopixels
16+
17+
The 18x5 matrix and the LED rings are regarded as distinct things, fed from
18+
two separate BMPs (or can use just one or the other). The former guide above
19+
uses the vertical axis for time (like a strip of movie film), while the
20+
latter uses the horizontal axis for time (as in audio or video editing).
21+
Despite this contrast, the same conventions are maintained here to avoid
22+
conflicting explanations...what worked in those guides is what works here,
23+
only the resolutions are different."""
24+
25+
import displayio
26+
import adafruit_imageload
27+
28+
29+
def gamma_adjust(palette):
30+
"""Given a color palette that was returned by adafruit_imageload, apply
31+
gamma correction and place results back in original palette. This makes
32+
LED brightness and colors more perceptually linear, to better match how
33+
the source BMP might've appeared on screen."""
34+
35+
for index, entry in enumerate(palette):
36+
palette[index] = sum(
37+
[
38+
int(((((entry >> shift) & 0xFF) / 255) ** 2.6) * 255 + 0.5) << shift
39+
for shift in range(16, -1, -8)
40+
]
41+
)
42+
43+
44+
class EyeLightsAnim:
45+
"""Class encapsulating BMP image-based frame animation for the matrix
46+
and rings of an LED_Glasses object."""
47+
48+
def __init__(self, glasses, matrix_filename, ring_filename, rings_on_top=True):
49+
"""Constructor for EyeLightsAnim. Accepts an LED_Glasses object and
50+
filenames for two indexed-color BMP images: first is a "sprite
51+
sheet" for animating on the matrix portion of the glasses, second is
52+
a pixels-over-time graph for the rings portion. Either filename may
53+
be None if not used. Because the matrix and rings share some pixels
54+
in common, the last argument determines the "stacking order" - which
55+
of the two bitmaps is drawn later or "on top." Default of True
56+
places the rings over the matrix, False gives the matrix priority.
57+
It's possible to use transparent palette indices but that may be
58+
more trouble than it's worth."""
59+
60+
self.glasses = glasses
61+
self.matrix_bitmap = self.ring_bitmap = None
62+
self.rings_on_top = rings_on_top
63+
64+
if matrix_filename:
65+
self.matrix_filename = matrix_filename
66+
67+
if ring_filename:
68+
self.ring_bitmap, self.ring_palette = adafruit_imageload.load(
69+
ring_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
70+
)
71+
if self.ring_bitmap.height < 48:
72+
raise ValueError("Ring bitmap must be at least 48 pixels tall")
73+
gamma_adjust(self.ring_palette)
74+
self.ring_frames = self.ring_bitmap.width
75+
self.ring_frame = self.ring_frames - 1
76+
77+
def draw_matrix(self, matrix_frame=None):
78+
"""Draw the matrix portion of EyeLights from one frame of the matrix
79+
bitmap "sprite sheet." Can either request a specific frame index
80+
(starting from 0), or pass None (or no arguments) to advance by one
81+
frame, "wrapping around" to beginning if needed. For internal use by
82+
library; user code should call frame(), not this function."""
83+
84+
if matrix_frame: # Go to specific frame
85+
self.matrix_frame = matrix_frame
86+
else: # Advance one frame forward
87+
self.matrix_frame += 1
88+
self.matrix_frame %= self.matrix_frames # Wrap to valid range
89+
90+
xoffset = self.matrix_frame % self.tiles_across * self.glasses.width
91+
yoffset = self.matrix_frame // self.tiles_across * self.glasses.height
92+
93+
for y in range(self.glasses.height):
94+
y1 = y + yoffset
95+
for x in range(self.glasses.width):
96+
idx = self.matrix_bitmap[x + xoffset, y1]
97+
if not self.matrix_palette.is_transparent(idx):
98+
self.glasses.pixel(x, y, self.matrix_palette[idx])
99+
100+
def draw_rings(self, ring_frame=None):
101+
"""Draw the rings portion of EyeLights from one frame of the rings
102+
bitmap graph. Can either request a specific frame index (starting
103+
from 0), or pass None (or no arguments) to advance by one frame,
104+
'wrapping around' to beginning if needed. For internal use by
105+
library; user code should call frame(), not this function."""
106+
107+
if ring_frame: # Go to specific frame
108+
self.ring_frame = ring_frame
109+
else: # Advance one frame forward
110+
self.ring_frame += 1
111+
self.ring_frame %= self.ring_frames # Wrap to valid range
112+
113+
for y in range(24):
114+
idx = self.ring_bitmap[self.ring_frame, y]
115+
if not self.ring_palette.is_transparent(idx):
116+
self.glasses.left_ring[y] = self.ring_palette[idx]
117+
idx = self.ring_bitmap[self.ring_frame, y + 24]
118+
if not self.ring_palette.is_transparent(idx):
119+
self.glasses.right_ring[y] = self.ring_palette[idx]
120+
121+
def frame(self, matrix_frame=None, ring_frame=None):
122+
"""Draw one frame of animation to the matrix and/or rings portions
123+
of EyeLights. Frame index (starting from 0) for matrix and rings
124+
respectively can be passed as arguments, or either/both may be None
125+
to advance by one frame, 'wrapping around' to beginning if needed.
126+
Because some pixels are shared in common between matrix and rings,
127+
the "stacking order" -- which of the two appears "on top", is
128+
specified as an argument to the constructor."""
129+
130+
if self.matrix_bitmap and self.rings_on_top:
131+
self.draw_matrix(matrix_frame)
132+
133+
if self.ring_bitmap:
134+
self.draw_rings(ring_frame)
135+
136+
if self.matrix_bitmap and not self.rings_on_top:
137+
self.draw_matrix(matrix_frame)
138+
139+
@property
140+
def matrix_filename(self):
141+
return self._matrix_filename
142+
143+
@matrix_filename.setter
144+
def matrix_filename(self, matrix_filename):
145+
self._matrix_filename = matrix_filename
146+
self.matrix_bitmap, self.matrix_palette = adafruit_imageload.load(
147+
matrix_filename, bitmap=displayio.Bitmap, palette=displayio.Palette
148+
)
149+
if (self.matrix_bitmap.width < self.glasses.width) or (
150+
self.matrix_bitmap.height < self.glasses.height
151+
):
152+
raise ValueError("Matrix bitmap must be at least 18x5 pixels")
153+
gamma_adjust(self.matrix_palette)
154+
self.tiles_across = self.matrix_bitmap.width // self.glasses.width
155+
self.tiles_down = self.matrix_bitmap.height // self.glasses.height
156+
self.matrix_frames = self.tiles_across * self.tiles_down
157+
self.matrix_frame = self.matrix_frames - 1
2.62 KB
Binary file not shown.
2.23 KB
Binary file not shown.
536 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)