""" Adafruit LED Glasses - Bluefruit Connect App Controller - Color Picker packet: changes text color - UART text: scrolls across the matrix - Rings: continuous rotating rainbow """ import board import busio import time import adafruit_is31fl3741 from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses from rainbowio import colorwheel import adafruit_ble from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTService # ── Hardware ─────────────────────────────────────────────────────────────────── i2c = busio.I2C(board.SCL, board.SDA, frequency=1_000_000) # MUST_BUFFER is critical: all pixel writes go to RAM, only flushed on show() glasses = LED_Glasses(i2c, allocate=adafruit_is31fl3741.MUST_BUFFER) glasses.show() glasses.global_current = 10 # ── BLE ──────────────────────────────────────────────────────────────────────── ble = adafruit_ble.BLERadio() uart_service = UARTService() advertisement = ProvideServicesAdvertisement(uart_service) # ── Config ───────────────────────────────────────────────────────────────────── MATRIX_W = 18 MATRIX_H = 5 CHAR_WIDTH = 4 NUM_LEDS = 24 FRAME_DELAY = 0.02 # ~50fps SCROLL_TICKS = 3 # scroll advances every N frames # ── Font ─────────────────────────────────────────────────────────────────────── FONT = { ' ': (0x00, 0x00, 0x00, 0x00), '!': (0x00, 0x17, 0x00, 0x00), '"': (0x03, 0x00, 0x03, 0x00), '#': (0x0A, 0x1F, 0x0A, 0x1F), '$': (0x12, 0x15, 0x1F, 0x15), '%': (0x09, 0x04, 0x12, 0x00), '&': (0x0A, 0x15, 0x0A, 0x10), "'": (0x00, 0x03, 0x00, 0x00), '(': (0x00, 0x0E, 0x11, 0x00), ')': (0x00, 0x11, 0x0E, 0x00), '*': (0x05, 0x02, 0x05, 0x00), '+': (0x04, 0x0E, 0x04, 0x00), ',': (0x00, 0x10, 0x08, 0x00), '-': (0x04, 0x04, 0x04, 0x00), '.': (0x00, 0x10, 0x00, 0x00), '/': (0x08, 0x04, 0x02, 0x00), '0': (0x0E, 0x11, 0x0E, 0x00), '1': (0x12, 0x1F, 0x10, 0x00), '2': (0x19, 0x15, 0x12, 0x00), '3': (0x11, 0x15, 0x0E, 0x00), '4': (0x07, 0x04, 0x1C, 0x00), '5': (0x17, 0x15, 0x09, 0x00), '6': (0x0E, 0x15, 0x08, 0x00), '7': (0x01, 0x19, 0x07, 0x00), '8': (0x0A, 0x15, 0x0A, 0x00), '9': (0x02, 0x15, 0x0E, 0x00), ':': (0x00, 0x0A, 0x00, 0x00), ';': (0x00, 0x14, 0x08, 0x00), '<': (0x04, 0x0A, 0x11, 0x00), '=': (0x0A, 0x0A, 0x0A, 0x00), '>': (0x11, 0x0A, 0x04, 0x00), '?': (0x01, 0x15, 0x03, 0x00), '@': (0x0E, 0x15, 0x16, 0x00), 'A': (0x1E, 0x05, 0x1E, 0x00), 'B': (0x1F, 0x15, 0x0A, 0x00), 'C': (0x0E, 0x11, 0x11, 0x00), 'D': (0x1F, 0x11, 0x0E, 0x00), 'E': (0x1F, 0x15, 0x11, 0x00), 'F': (0x1F, 0x05, 0x01, 0x00), 'G': (0x0E, 0x15, 0x1D, 0x00), 'H': (0x1F, 0x04, 0x1F, 0x00), 'I': (0x11, 0x1F, 0x11, 0x00), 'J': (0x08, 0x10, 0x0F, 0x00), 'K': (0x1F, 0x04, 0x1B, 0x00), 'L': (0x1F, 0x10, 0x10, 0x00), 'M': (0x1F, 0x02, 0x1F, 0x00), 'N': (0x1F, 0x06, 0x1F, 0x00), 'O': (0x0E, 0x11, 0x0E, 0x00), 'P': (0x1F, 0x05, 0x02, 0x00), 'Q': (0x0E, 0x19, 0x1E, 0x00), 'R': (0x1F, 0x05, 0x1A, 0x00), 'S': (0x12, 0x15, 0x09, 0x00), 'T': (0x01, 0x1F, 0x01, 0x00), 'U': (0x0F, 0x10, 0x0F, 0x00), 'V': (0x07, 0x18, 0x07, 0x00), 'W': (0x1F, 0x08, 0x1F, 0x00), 'X': (0x1B, 0x04, 0x1B, 0x00), 'Y': (0x03, 0x1C, 0x03, 0x00), 'Z': (0x19, 0x15, 0x13, 0x00), '[': (0x00, 0x1F, 0x11, 0x00), '\\': (0x02, 0x04, 0x08, 0x00), ']': (0x00, 0x11, 0x1F, 0x00), '^': (0x02, 0x01, 0x02, 0x00), '_': (0x10, 0x10, 0x10, 0x00), '`': (0x00, 0x01, 0x02, 0x00), 'a': (0x0C, 0x12, 0x1E, 0x00), 'b': (0x1F, 0x14, 0x08, 0x00), 'c': (0x0C, 0x12, 0x12, 0x00), 'd': (0x08, 0x14, 0x1F, 0x00), 'e': (0x0C, 0x1A, 0x12, 0x00), 'f': (0x04, 0x1E, 0x05, 0x00), 'g': (0x04, 0x14, 0x1C, 0x00), 'h': (0x1F, 0x04, 0x18, 0x00), 'i': (0x00, 0x1D, 0x00, 0x00), 'j': (0x08, 0x10, 0x0D, 0x00), 'k': (0x1F, 0x04, 0x1A, 0x00), 'l': (0x11, 0x1F, 0x10, 0x00), 'm': (0x1E, 0x02, 0x1E, 0x00), 'n': (0x1E, 0x02, 0x1C, 0x00), 'o': (0x0C, 0x12, 0x0C, 0x00), 'p': (0x1E, 0x0A, 0x04, 0x00), 'q': (0x04, 0x0A, 0x1E, 0x00), 'r': (0x1C, 0x02, 0x02, 0x00), 's': (0x14, 0x1A, 0x0A, 0x00), 't': (0x02, 0x1F, 0x12, 0x00), 'u': (0x0E, 0x10, 0x1E, 0x00), 'v': (0x06, 0x18, 0x06, 0x00), 'w': (0x0E, 0x18, 0x0E, 0x00), 'x': (0x12, 0x0C, 0x12, 0x00), 'y': (0x02, 0x14, 0x0E, 0x00), 'z': (0x1A, 0x1E, 0x16, 0x00), } # ── Helpers ──────────────────────────────────────────────────────────────────── def pack_color(r, g, b): return (int(r) << 16) | (int(g) << 8) | int(b) def build_bitmap(text): bitmap = [0x00] * MATRIX_W # leading blanks so text enters from right for ch in text.upper(): cols = FONT.get(ch, FONT[' ']) bitmap.extend(cols[:CHAR_WIDTH]) bitmap.append(0x00) bitmap.extend([0x00] * 5) # trailing blanks so text exits left return bitmap def parse_color_packet(data): if len(data) >= 6 and data[0:2] == b'!C': return (data[2], data[3], data[4]) return None def parse_uart_text(data): try: text = data.decode('utf-8').strip() return text if text else None except Exception: return None # ── Main ─────────────────────────────────────────────────────────────────────── text_color = (0, 80, 200) scroll_bitmap = [] scroll_pos = 0 rainbow_off = 0 tick = 0 print("Advertising...") while True: ble.start_advertising(advertisement) while not ble.connected: for i in range(NUM_LEDS): c = colorwheel((i * 256 // NUM_LEDS - rainbow_off) & 0xFF) glasses.left_ring[i] = c glasses.right_ring[i] = c glasses.show() rainbow_off = (rainbow_off + 10) & 0xFF time.sleep(FRAME_DELAY) ble.stop_advertising() print("Connected!") tick = 0 while ble.connected: # ── BLE input ───────────────────────────────────────────────────────── if uart_service.in_waiting: raw = uart_service.read(uart_service.in_waiting) color = parse_color_packet(raw) if color: text_color = color print("Color ->", text_color) else: text = parse_uart_text(raw) if text: scroll_bitmap = build_bitmap(text) scroll_pos = 0 tick = 0 print("Text ->", text) # ── Matrix: scroll (writes to buffer only) ──────────────────────────── packed = pack_color(*text_color) for x in range(MATRIX_W): src = scroll_pos + x col_bits = scroll_bitmap[src] if (scroll_bitmap and src < len(scroll_bitmap)) else 0 for y in range(MATRIX_H): glasses.pixel(x, y, packed if (col_bits & (1 << y)) else 0) # ── Rings: rainbow (writes to buffer only, after matrix) ────────────── for i in range(NUM_LEDS): c = colorwheel((i * 256 // NUM_LEDS - rainbow_off) & 0xFF) glasses.left_ring[i] = c glasses.right_ring[i] = c # ── Single flush of entire buffer to hardware ───────────────────────── glasses.show() # ── Advance counters ────────────────────────────────────────────────── rainbow_off = (rainbow_off + 10) & 0xFF tick += 1 if scroll_bitmap and tick >= SCROLL_TICKS: tick = 0 scroll_pos += 1 if scroll_pos >= len(scroll_bitmap): scroll_pos = 0 time.sleep(FRAME_DELAY) print("Disconnected - re-advertising")