mirror of
https://github.com/apirrone/Open_Duck_Mini_Runtime.git
synced 2025-09-03 19:53:56 +00:00
Tests + figuring out the weird eye crap for why adding MORE makes it work
This commit is contained in:
parent
0002523b0c
commit
d86884306f
4 changed files with 157 additions and 33 deletions
|
@ -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__":
|
||||
|
|
|
@ -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.
|
|
@ -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
113
tests/neopixel_numpad.py
Normal 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 1–9 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)
|
Loading…
Reference in a new issue