|
| 1 | +""" |
| 2 | +ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly |
| 3 | +good clicky button for input, this code shows how one might instead use |
| 4 | +the onboard accelerometer for interactions*. |
| 5 | +
|
| 6 | +Worn normally, the LED rings are simply lit a solid color. |
| 7 | +TAP the eyeglass frames to cycle among a list of available colors. |
| 8 | +LOOK DOWN to light the LED rings bright white -- for navigating steps |
| 9 | +or finding the right key. LOOK BACK UP to return to solid color. |
| 10 | +This uses only the rings, not the matrix portion. |
| 11 | +
|
| 12 | +* Like, if you have big ol' monster hands, that little button can be |
| 13 | + hard to click, y'know? |
| 14 | +""" |
| 15 | + |
| 16 | +import time |
| 17 | +import board |
| 18 | +import digitalio |
| 19 | +import supervisor |
| 20 | +import adafruit_lis3dh |
| 21 | +import adafruit_is31fl3741 |
| 22 | +from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses |
| 23 | + |
| 24 | +i2c = board.I2C() # Shared by both the accelerometer and LED controller |
| 25 | + |
| 26 | +# Initialize the accelerometer and enable single-tap detection |
| 27 | +int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT) |
| 28 | +lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1) |
| 29 | +lis3dh.set_tap(1, 100) |
| 30 | +last_tap_time = 0 |
| 31 | + |
| 32 | +# Initialize the IS31 LED driver, buffered for smoother animation |
| 33 | +glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER) |
| 34 | + |
| 35 | +# Here's a list of colors that we cycle through when tapped, specified |
| 36 | +# as (R,G,B) tuples from 0-255. These are intentionally a bit dim -- |
| 37 | +# both to save battery and to make the "ground light" mode more dramatic. |
| 38 | +# Rather than primary color red/green/blue sequence which is just so |
| 39 | +# over-done at this point, let's use some HALLOWEEN colors! |
| 40 | +colors = ((27, 9, 0), (12, 0, 24), (5, 31, 0)) # Orange, purple, green |
| 41 | +color_index = 0 # Begin at first color in list |
| 42 | + |
| 43 | +# Check accelerometer to see if we've started in the looking-down state, |
| 44 | +# set the target color (what we're aiming for) appropriately. Only the |
| 45 | +# Y axis is needed for this. |
| 46 | +_, filtered_y, _ = lis3dh.acceleration |
| 47 | +looking_down = filtered_y > 5 |
| 48 | +target_color = (255, 255, 255) if looking_down else colors[color_index] |
| 49 | + |
| 50 | +interpolated_color = (0, 0, 0) # LEDs off at startup, they'll ramp up |
| 51 | + |
| 52 | + |
| 53 | +def fill_color(color): |
| 54 | + """Given an (R,G,B) tuple, fill both LED rings with this color.""" |
| 55 | + # Convert tuple to a 'packed' 24-bit value |
| 56 | + packed = (int(color[0]) << 16) | (int(color[1]) << 8) | int(color[2]) |
| 57 | + for i in range(24): |
| 58 | + glasses.left_ring[i] = glasses.right_ring[i] = packed |
| 59 | + |
| 60 | + |
| 61 | +while True: # Loop forever... |
| 62 | + |
| 63 | + # The try/except here is because VERY INFREQUENTLY the I2C bus will |
| 64 | + # encounter an error when accessing either the accelerometer or the |
| 65 | + # LED driver, whether from bumping around the wires or sometimes an |
| 66 | + # I2C device just gets wedged. To more robustly handle the latter, |
| 67 | + # the code will restart if that happens. |
| 68 | + try: |
| 69 | + |
| 70 | + # interpolated_color blends from the prior to the next ("target") |
| 71 | + # LED ring colors, with a pleasant ease-out effect. |
| 72 | + interpolated_color = ( |
| 73 | + interpolated_color[0] * 0.85 + target_color[0] * 0.15, |
| 74 | + interpolated_color[1] * 0.85 + target_color[1] * 0.15, |
| 75 | + interpolated_color[2] * 0.85 + target_color[2] * 0.15, |
| 76 | + ) |
| 77 | + # Fill both rings with interpolated_color, then refresh the LEDs. |
| 78 | + fill_color(interpolated_color) |
| 79 | + glasses.show() |
| 80 | + |
| 81 | + # The look-down detection only needs the accelerometer's Y axis. |
| 82 | + # This works with the Glasses Driver mounted on either temple and |
| 83 | + # with the glasses arms "open" (as when worn). |
| 84 | + _, y, _ = lis3dh.acceleration |
| 85 | + # Smooth the accelerometer reading the same way RGB colors are |
| 86 | + # interpolated. This avoids false triggers from jostling around. |
| 87 | + filtered_y = filtered_y * 0.85 + y * 0.15 |
| 88 | + # The threshold between "looking down" and "looking up" depends |
| 89 | + # on which of those states we're currently in. This is an example |
| 90 | + # of hysteresis in software...a change of direction requires a |
| 91 | + # little extra push before it takes, which avoids oscillating if |
| 92 | + # there was just a single threshold both ways. |
| 93 | + if looking_down: # Currently in the looking-down state |
| 94 | + _ = lis3dh.tapped # Discard any taps while looking down |
| 95 | + if filtered_y < 3.5: # Have we crossed the look-up threshold? |
| 96 | + target_color = colors[color_index] |
| 97 | + looking_down = False # We're looking up now! |
| 98 | + else: # Currently in looking-up state |
| 99 | + if filtered_y > 5: # Crossed the look-down threshold? |
| 100 | + target_color = (255, 255, 255) |
| 101 | + looking_down = True # We're looking down now! |
| 102 | + elif lis3dh.tapped: |
| 103 | + # No look up/down change, but the accelerometer registered |
| 104 | + # a tap. Compare this against the last time we sensed one, |
| 105 | + # and only do things if it's been more than half a second. |
| 106 | + # This avoids spurious double-taps that can occur no matter |
| 107 | + # how carefully the tap threshold was set. |
| 108 | + now = time.monotonic() |
| 109 | + elapsed = now - last_tap_time |
| 110 | + if elapsed > 0.5: |
| 111 | + # A good tap was detected. Cycle to the next color in |
| 112 | + # the list and note the time of this tap. |
| 113 | + color_index = (color_index + 1) % len(colors) |
| 114 | + target_color = colors[color_index] |
| 115 | + last_tap_time = now |
| 116 | + |
| 117 | + # See "try" notes above regarding rare I2C errors. |
| 118 | + except OSError: |
| 119 | + supervisor.reload() |
0 commit comments