Don’t be alarmed by the size of the code below…it’s actually more comments than code, to walk you through what it’s doing.

This example demonstrates some basics of using the LED rings and the driver board’s accelerometer.

Although the driver board has a clicky button that can be used for input (board.SWITCH in CircuitPython), this is one of those “make you think” projects: what if, instead explicitly interacting with devices, they anticipated our needs and did what we want? This is normally a pair of festive Halloween glasses…but when you look down (as when navigating steps or looking in your candy bag) it transitions into bright headlights. Look up again and it’s back to Halloween mode.

This also demonstrates the accelerometer’s tap-detect function (just tap the glasses to select among different color schemes) and smooth “easing” interpolation between colors that gives things a touch of luxury.

The driver board is normally mounted on an eyeglass frame’s temple, with the STEMMA QT connector toward the front. If the code’s look-down behavior seems wrong, it may be that the glasses are folded or the board isn’t mounted the right way.

Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.

Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory EyeLights_Accelerometer_Tap/EyeLights_Accelerometer_Tap_Circuitpython and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.

Your CIRCUITPY drive should now look similar to the following image:

CIRCUITPY
# SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
good clicky button for input, this code shows how one might instead use
the onboard accelerometer for interactions*.

Worn normally, the LED rings are simply lit a solid color.
TAP the eyeglass frames to cycle among a list of available colors.
LOOK DOWN to light the LED rings bright white -- for navigating steps
or finding the right key. LOOK BACK UP to return to solid color.
This uses only the rings, not the matrix portion.

* Like, if you have big ol' monster hands, that little button can be
  hard to click, y'know?
"""

import time
import board
import digitalio
import supervisor
import adafruit_lis3dh
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses

# Shared by both the accelerometer and LED controller
i2c = board.I2C()  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller

# Initialize the accelerometer and enable single-tap detection
int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1)
lis3dh.set_tap(1, 100)
last_tap_time = 0

# Initialize the IS31 LED driver, buffered for smoother animation
glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER)

# Here's a list of colors that we cycle through when tapped, specified
# as (R,G,B) tuples from 0-255. These are intentionally a bit dim --
# both to save battery and to make the "ground light" mode more dramatic.
# Rather than primary color red/green/blue sequence which is just so
# over-done at this point, let's use some HALLOWEEN colors!
colors = ((27, 9, 0), (12, 0, 24), (5, 31, 0))  # Orange, purple, green
color_index = 0  # Begin at first color in list

# Check accelerometer to see if we've started in the looking-down state,
# set the target color (what we're aiming for) appropriately. Only the
# Y axis is needed for this.
_, filtered_y, _ = lis3dh.acceleration
looking_down = filtered_y > 5
target_color = (255, 255, 255) if looking_down else colors[color_index]

interpolated_color = (0, 0, 0)  # LEDs off at startup, they'll ramp up


while True:  # Loop forever...

    # The try/except here is because VERY INFREQUENTLY the I2C bus will
    # encounter an error when accessing either the accelerometer or the
    # LED driver, whether from bumping around the wires or sometimes an
    # I2C device just gets wedged. To more robustly handle the latter,
    # the code will restart if that happens.
    try:

        # interpolated_color blends from the prior to the next ("target")
        # LED ring colors, with a pleasant ease-out effect.
        interpolated_color = (
            interpolated_color[0] * 0.85 + target_color[0] * 0.15,
            interpolated_color[1] * 0.85 + target_color[1] * 0.15,
            interpolated_color[2] * 0.85 + target_color[2] * 0.15,
        )
        # Fill both rings with interpolated_color, then refresh the LEDs.
        # fill_color(interpolated_color)
        packed = (
            (int(interpolated_color[0]) << 16)
            | (int(interpolated_color[1]) << 8)
            | int(interpolated_color[2])
        )
        glasses.left_ring.fill(packed)
        glasses.right_ring.fill(packed)
        glasses.show()

        # The look-down detection only needs the accelerometer's Y axis.
        # This works with the Glasses Driver mounted on either temple and
        # with the glasses arms "open" (as when worn).
        _, y, _ = lis3dh.acceleration
        # Smooth the accelerometer reading the same way RGB colors are
        # interpolated. This avoids false triggers from jostling around.
        filtered_y = filtered_y * 0.85 + y * 0.15
        # The threshold between "looking down" and "looking up" depends
        # on which of those states we're currently in. This is an example
        # of hysteresis in software...a change of direction requires a
        # little extra push before it takes, which avoids oscillating if
        # there was just a single threshold both ways.
        if looking_down:  #             Currently in the looking-down state
            _ = lis3dh.tapped  #        Discard any taps while looking down
            if filtered_y < 3.5:  #     Have we crossed the look-up threshold?
                target_color = colors[color_index]
                looking_down = False  # We're looking up now!
        else:  #                        Currently in looking-up state
            if filtered_y > 5:  #       Crossed the look-down threshold?
                target_color = (255, 255, 255)
                looking_down = True  #  We're looking down now!
            elif lis3dh.tapped:
                # No look up/down change, but the accelerometer registered
                # a tap. Compare this against the last time we sensed one,
                # and only do things if it's been more than half a second.
                # This avoids spurious double-taps that can occur no matter
                # how carefully the tap threshold was set.
                now = time.monotonic()
                elapsed = now - last_tap_time
                if elapsed > 0.5:
                    # A good tap was detected. Cycle to the next color in
                    # the list and note the time of this tap.
                    color_index = (color_index + 1) % len(colors)
                    target_color = colors[color_index]
                    last_tap_time = now

    # See "try" notes above regarding rare I2C errors.
    except OSError:
        supervisor.reload()

This guide was first published on Oct 12, 2021. It was last updated on Mar 19, 2024.

This page (Tap-and-Look Glasses) was last updated on Mar 18, 2024.

Text editor powered by tinymce.