|
| 1 | +# pylint: disable=import-error |
| 2 | + |
| 3 | +""" |
| 4 | +NeoPixel goggles inspired by Rezz |
| 5 | +
|
| 6 | +No interactive controls; speed, color and directions randomize periodically. |
| 7 | +""" |
| 8 | + |
| 9 | +from random import random, randrange |
| 10 | +from time import monotonic |
| 11 | +import board |
| 12 | +import neopixel |
| 13 | +import adafruit_fancyled.adafruit_fancyled as fancy |
| 14 | + |
| 15 | +# Configurable defaults |
| 16 | + |
| 17 | +BRIGHTNESS = 0.15 # 0.0 (off) to 1.0 (max brightness) |
| 18 | + |
| 19 | +# Global variables |
| 20 | + |
| 21 | +# Declare NeoPixel object. Data from ItsyBitsy pin 5 because it has built-in |
| 22 | +# level shifting. Each LED eye has 44 pixels, so 88 total. Leave brightness |
| 23 | +# at 1.0 here (NeoPixel library runs faster at full brightness) and adjust |
| 24 | +# the global BRIGHTNESS above instead (used later when selecting HSV colors). |
| 25 | +PIXELS = neopixel.NeoPixel( |
| 26 | + board.D5, 88, auto_write=False, brightness=1.0, pixel_order=neopixel.RGB) |
| 27 | + |
| 28 | +# MODE indicates the current animation state through several bit fields. |
| 29 | +# Bit 0 indicates the second eye is x-axis mirrored (1) or an exact copy |
| 30 | +# of the first (0). Bit 1 indicates hue is slowly cycling (2) vs holding |
| 31 | +# steady (0). Bit 2 indicates the middle of the 3 LED rings moves the |
| 32 | +# opposite (4) or same (0) direction as the other 2. |
| 33 | +MODE = randrange(8) # 0 to 7 |
| 34 | +# Every few seconds, one of the above attributes is randomly toggled. |
| 35 | +# This keeps track of the last time. |
| 36 | +TIME_OF_LAST_MODE_SWITCH = 0 |
| 37 | +# HUE works around the color wheel, see FancyLED docs. |
| 38 | +HUE = random() |
| 39 | +# Relative position of MIDDLE of 3 rings, 0 to 143 |
| 40 | +MIDDLE_POS = randrange(144) |
| 41 | +# Relative position of INNER and OUTER rings, 0 to 143 |
| 42 | +POS = randrange(144) |
| 43 | +# Amount to increment POS and MIDDLE_POS each frame |
| 44 | +SPEED = 2 + random() * 5 |
| 45 | + |
| 46 | +# The MIRROR_X and OFFSET(OUTER,MIDDLE,INNER) arrays precompute some values |
| 47 | +# for each pixel so we don't need to repeat that math every frame or LED. |
| 48 | +MIRROR_X = [] |
| 49 | +OFFSET_OUTER = [] |
| 50 | +OFFSET_MIDDLE = [] |
| 51 | +OFFSET_INNER = [] |
| 52 | +for i in range(24): |
| 53 | + MIRROR_X.append(67 - ((i + 11) % 24)) |
| 54 | + OFFSET_OUTER.append(i * 6) |
| 55 | +for i in range(16): |
| 56 | + MIRROR_X.append(83 - ((i + 7) % 16)) |
| 57 | + OFFSET_MIDDLE.append(i * 9) |
| 58 | +for i in range(4): |
| 59 | + MIRROR_X.append(87 - ((i + 2) % 4)) |
| 60 | + OFFSET_INNER.append(i * 36) |
| 61 | + |
| 62 | + |
| 63 | +def set_pixel(index, color): |
| 64 | + """Set one pixel in both eyes. Pass in pixel index (0 to 43) and |
| 65 | + color (as a packed RGB value). If MODE bit 0 is set, second eye |
| 66 | + will be X-axis mirrored, else an exact duplicate.""" |
| 67 | + # Set pixel in first eye |
| 68 | + PIXELS[index] = color |
| 69 | + # Set pixel in second eye (mirrored or direct) |
| 70 | + if MODE & 1: |
| 71 | + PIXELS[MIRROR_X[index]] = color |
| 72 | + else: |
| 73 | + PIXELS[44 + index] = color |
| 74 | + |
| 75 | + |
| 76 | +# Main loop, repeat indefinitely... |
| 77 | +while True: |
| 78 | + |
| 79 | + # Check if 5 seconds have passed since last mode switch |
| 80 | + NOW = monotonic() |
| 81 | + if (NOW - TIME_OF_LAST_MODE_SWITCH) > 5: |
| 82 | + # Yes. Save the time, change ONE mode bit, randomize speed |
| 83 | + TIME_OF_LAST_MODE_SWITCH = NOW |
| 84 | + MODE ^= 1 << randrange(3) |
| 85 | + SPEED = 2 + random() * 5 |
| 86 | + |
| 87 | + # Generate packed RGB value based on current HUE value |
| 88 | + COLOR = fancy.CHSV(HUE, 1.0, BRIGHTNESS).pack() |
| 89 | + |
| 90 | + # Draw outer ring; 24 pixels, 8 lit |
| 91 | + for i in range(24): |
| 92 | + j = (POS + OFFSET_OUTER[i]) % 72 |
| 93 | + if j < 24: |
| 94 | + set_pixel(i, COLOR) |
| 95 | + else: |
| 96 | + set_pixel(i, 0) |
| 97 | + # Draw middle ring; 16 pixels, 6 lit |
| 98 | + for i in range(16): |
| 99 | + j = (OFFSET_MIDDLE[i] + MIDDLE_POS) % 72 |
| 100 | + if j < 27: |
| 101 | + set_pixel(24 + i, COLOR) |
| 102 | + else: |
| 103 | + set_pixel(24 + i, 0) |
| 104 | + # Draw inner ring; 4 pixels, 3 lit |
| 105 | + for i in range(4): |
| 106 | + j = (POS + OFFSET_INNER[i]) % 144 |
| 107 | + if j < 108: |
| 108 | + set_pixel(40 + i, COLOR) |
| 109 | + else: |
| 110 | + set_pixel(40 + i, 0) |
| 111 | + |
| 112 | + # Push new state to LEDs |
| 113 | + PIXELS.show() |
| 114 | + |
| 115 | + # If MODE bit 1 is set, advance hue (else holds steady) |
| 116 | + if MODE & 2: |
| 117 | + HUE += 0.003 |
| 118 | + |
| 119 | + # Increment position of inner & outer ring |
| 120 | + POS = (POS + SPEED) % 144 |
| 121 | + # Middle ring advances one way or other depending on MODE bit 2 |
| 122 | + if MODE & 4: |
| 123 | + MIDDLE_POS = (MIDDLE_POS - SPEED) % 144 |
| 124 | + else: |
| 125 | + MIDDLE_POS = (MIDDLE_POS + SPEED) % 144 |
0 commit comments