Tests + figuring out the weird eye crap for why adding MORE makes it work

This commit is contained in:
Aronnaxx 2025-08-16 14:57:38 -07:00
parent 0002523b0c
commit d86884306f
4 changed files with 157 additions and 33 deletions

View file

@ -14,6 +14,9 @@ class Eyes:
self.max_interval = max_interval
# Ensure eyes start ON to mimic previous behavior
self.ctrl.set_eyes_color("white")
self.ctrl.set_eyes(True)
self._stop_event = Event()
@ -27,10 +30,12 @@ class Eyes:
try:
while not self._stop_event.is_set():
self._set_eyes(False)
time.sleep(self.blink_duration)
if self._stop_event.wait(self.blink_duration):
break
self._set_eyes(True)
next_blink = random.uniform(self.min_interval, self.max_interval)
time.sleep(next_blink)
if self._stop_event.wait(next_blink):
break
except Exception as err:
print(f"Error in eye thread: {err}")
self._stop_event.set()
@ -39,7 +44,8 @@ class Eyes:
self._stop_event.set()
self._thread.join()
self._set_eyes(False)
# Do not deinit controller here; projector may also use it.
# deinit controller
self.ctrl.deinit()
if __name__ == "__main__":

View file

@ -10,7 +10,6 @@ from __future__ import annotations
import os
import atexit
import signal
from threading import Lock
from typing import Tuple, Optional, Union
@ -20,7 +19,7 @@ import neopixel
# Pin and pixel configuration
PIXEL_PIN = board.D10
NUM_PIXELS = 3
NUM_PIXELS = 10
# Allow configuration of pixel order.
# Default to GRBW (common on many RGBW strips). You can override via:
@ -31,12 +30,21 @@ try:
except Exception:
_CFG_LED_ORDER = None
_ORDER_NAME = os.getenv("ODUCK_LED_ORDER", _CFG_LED_ORDER or "GRBW").upper()
ORDER = getattr(neopixel, _ORDER_NAME, neopixel.GRBW)
_ORDER_NAME = os.getenv("ODUCK_LED_ORDER", _CFG_LED_ORDER or "RGBW").upper()
ORDER = getattr(neopixel, _ORDER_NAME, neopixel.RGBW)
# Brightness can be tuned via env
BRIGHTNESS = float(os.getenv("ODUCK_LED_BRIGHTNESS", "1.0"))
# White rendering mode: "W" uses the dedicated white channel (RGBW strips),
# "RGB" mixes white from RGB. Useful if a particular LED's W phosphor has tint.
try:
from open_duck_mini_runtime.duck_config import LED_WHITE_MODE as _CFG_WHITE_MODE # type: ignore
except Exception:
_CFG_WHITE_MODE = None
WHITE_MODE = os.getenv("ODUCK_LED_WHITE_MODE", _CFG_WHITE_MODE or "W").upper()
class LedController:
def __init__(self) -> None:
@ -46,14 +54,18 @@ class LedController:
# Lazily create the NeoPixel instance (avoid creating it at import-time)
self._pixels = neopixel.NeoPixel(
PIXEL_PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=False, pixel_order=ORDER
PIXEL_PIN, NUM_PIXELS, brightness=BRIGHTNESS, auto_write=True, pixel_order=ORDER
)
# Cache simple color tuples (use 4-tuple for RGBW strips)
# Colors are stored in logical (R, G, B, W) regardless of ORDER.
self.OFF = (0, 0, 0, 0)
# On RGBW strips, "white" uses the W channel for best white.
self.WHITE = (0, 0, 0, 255)
# White color can be sourced from W channel or mixed from RGB.
if WHITE_MODE == "RGB":
self.WHITE = (255, 255, 255, 0)
else:
# Default: use dedicated W channel on RGBW strips
self.WHITE = (0, 0, 0, 255)
self.RED = (255, 0, 0, 0)
self.GREEN = (0, 255, 0, 0)
self.BLUE = (0, 0, 255, 0)
@ -226,15 +238,6 @@ def get_controller() -> LedController:
return _controller
# Ensure graceful cleanup on Ctrl+C / SIGTERM without forcing controller creation.
def _shutdown_handler(signum, frame):
global _controller
if _controller is not None:
_controller.deinit()
try:
signal.signal(signal.SIGINT, _shutdown_handler)
signal.signal(signal.SIGTERM, _shutdown_handler)
except Exception:
# Not all environments allow setting signals (e.g., some threads)
pass
# Note: We intentionally avoid installing signal handlers here.
# Libraries should not override application-level signal behavior.
# atexit cleanup above is sufficient in most cases.

View file

@ -1,6 +1,3 @@
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
# Simple test for NeoPixels on Raspberry Pi
import time
@ -17,7 +14,7 @@ num_pixels = 3
# The order of the pixel colors - RGB or GRB. Some NeoPixels have red and green reversed!
# For RGBW NeoPixels, simply change the ORDER to RGBW or GRBW.
ORDER = neopixel.GRB
ORDER = neopixel.RGBW
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=0.2, auto_write=False, pixel_order=ORDER
@ -57,24 +54,29 @@ def rainbow_cycle(wait):
while True:
# Comment this line out if you have RGBW/GRBW NeoPixels
pixels.fill((255, 0, 0))
# pixels.fill((255, 0, 0))
# Uncomment this line if you have RGBW/GRBW NeoPixels
# pixels.fill((255, 0, 0, 0))
pixels.fill((255, 0, 0, 0))
pixels.show()
time.sleep(1)
# Comment this line out if you have RGBW/GRBW NeoPixels
pixels.fill((0, 255, 0))
# pixels.fill((0, 255, 0))
# Uncomment this line if you have RGBW/GRBW NeoPixels
# pixels.fill((0, 255, 0, 0))
pixels.fill((0, 255, 0, 0))
pixels.show()
time.sleep(1)
# Comment this line out if you have RGBW/GRBW NeoPixels
pixels.fill((0, 0, 255))
# pixels.fill((0, 0, 255))
# Uncomment this line if you have RGBW/GRBW NeoPixels
# pixels.fill((0, 0, 255, 0))
pixels.fill((0, 0, 255, 0))
pixels.show()
time.sleep(1)
rainbow_cycle(0.001) # rainbow cycle with 1ms delay per step
pixels.fill((0, 0, 255, 0))
pixels.show()
time.sleep(1)
# rainbow_cycle(0.001) # rainbow cycle with 1ms delay per step

113
tests/neopixel_numpad.py Normal file
View file

@ -0,0 +1,113 @@
import time
import board
import neopixel
import threading
import curses
import math
# --- NeoPixel setup ---
pixel_pin = board.D10
num_pixels = 10 # Ok so for some unknown reason this works with 10 but NOT with 3? TODO figure this out I guess
ORDER = neopixel.RGBW # <-- flip to GRBW if colors look wrong
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=0.3, auto_write=False, pixel_order=ORDER
)
# --- Colors (R, G, B, W) ---
colors = {
'1': (255, 0, 0, 0), # Red
'2': (0, 255, 0, 0), # Green
'3': (0, 0, 255, 0), # Blue
'4': (0, 0, 0, 255), # White
'5': (255, 255, 0, 0), # Yellow
'6': (0, 255, 255, 0), # Cyan
'7': (255, 0, 255, 0), # Magenta
'8': (128, 128, 128, 0), # Gray
'9': (0, 0, 0, 0), # Off
}
# --- Globals ---
rainbow_active = False
brightness = 0.3
# --- Gradient rainbow (wave across strip) ---
def smooth_rainbow(wait=0.02):
global rainbow_active
hue = 0
while rainbow_active:
for i in range(num_pixels):
# Offset hue per pixel for gradient effect
offset = (hue + (i * 360 / num_pixels)) % 360
r, g, b = hsv_to_rgb(offset / 360.0, 1, 1)
pixels[i] = (int(r * 255), int(g * 255), int(b * 255), 0)
pixels.show()
hue = (hue + 1) % 360
time.sleep(wait)
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h * 6.0)
f = (h * 6.0) - i
p = v * (1.0 - s)
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
i = i % 6
if i == 0: return v, t, p
if i == 1: return q, v, p
if i == 2: return p, v, t
if i == 3: return p, q, v
if i == 4: return t, p, v
if i == 5: return v, p, q
# --- Main with curses ---
def main(stdscr):
global rainbow_active, brightness
curses.curs_set(0) # Hide cursor
stdscr.nodelay(True)
stdscr.addstr(0, 0, "Press 19 for colors, 0 for rainbow, ←/→ for brightness, q to quit.")
while True:
key = stdscr.getch()
if key == -1:
time.sleep(0.05)
continue
if key in range(49, 58): # Keys '1''9'
ch = chr(key)
rainbow_active = False
pixels.fill(colors[ch])
pixels.show()
stdscr.addstr(2, 0, f"Set color {ch}: {colors[ch]} ")
elif key == ord('0'): # Rainbow toggle
if not rainbow_active:
rainbow_active = True
threading.Thread(target=smooth_rainbow, daemon=True).start()
stdscr.addstr(2, 0, "🌈 Gradient rainbow started ")
else:
rainbow_active = False
stdscr.addstr(2, 0, "Rainbow stopped ")
elif key == curses.KEY_RIGHT: # Brightness up
brightness = min(1.0, brightness + 0.1)
pixels.brightness = brightness
pixels.show()
stdscr.addstr(3, 0, f"Brightness: {brightness:.1f} ")
elif key == curses.KEY_LEFT: # Brightness down
brightness = max(0.0, brightness - 0.1)
pixels.brightness = brightness
pixels.show()
stdscr.addstr(3, 0, f"Brightness: {brightness:.1f} ")
elif key in (ord('q'), ord('Q')):
rainbow_active = False
pixels.fill((0, 0, 0, 0))
pixels.show()
stdscr.addstr(2, 0, "Goodbye! ")
time.sleep(0.5)
break
curses.wrapper(main)