feat(led-driver): add preset clear command and runtime debug
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
"""Minimal Presets-like stub for pattern smoke tests (no machine / NeoPixel)."""
|
||||
|
||||
|
||||
class _FakeNeo:
|
||||
def __init__(self, count):
|
||||
self._count = count
|
||||
self.pixels = [(0, 0, 0)] * count
|
||||
|
||||
def fill(self, color):
|
||||
self.pixels = [tuple(color) for _ in range(self._count)]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self.pixels[index] = tuple(value)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.pixels[index]
|
||||
|
||||
def write(self):
|
||||
pass
|
||||
|
||||
|
||||
class FakePresets:
|
||||
"""Subset of led-driver Presets API used by patterns/*.py."""
|
||||
|
||||
def __init__(self, num_leds=24):
|
||||
self.num_leds = num_leds
|
||||
self.n = _FakeNeo(num_leds)
|
||||
self.b = 255
|
||||
self.step = 0
|
||||
|
||||
def apply_brightness(self, color, brightness_override=None):
|
||||
local = brightness_override if brightness_override is not None else 255
|
||||
effective_brightness = int(local * self.b / 255)
|
||||
return tuple(int(c * effective_brightness / 255) for c in color)
|
||||
|
||||
def fill(self, color=None):
|
||||
fill_color = color if color is not None else (0, 0, 0)
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = fill_color
|
||||
self.n.write()
|
||||
|
||||
def off(self):
|
||||
self.fill((0, 0, 0))
|
||||
@@ -1,174 +0,0 @@
|
||||
"""
|
||||
Smoke-test pattern generators with a fake driver (no hardware).
|
||||
|
||||
Run from repo root (CPython or MicroPython):
|
||||
|
||||
python3 led-driver/tests/pattern_smoke.py
|
||||
|
||||
Requires only the stdlib; loads ``preset.py`` and ``patterns/*.py`` from
|
||||
``led-driver/src`` via import paths (MicroPython: copy ``src`` tree to the
|
||||
device and run the same command if ``importlib.util`` is available, or set
|
||||
``PYTHONPATH`` to ``led-driver/src`` and use ``python3`` on the host).
|
||||
|
||||
Exit code 0 on success, 1 on any failure.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Host-only ``utime`` so patterns import on CPython.
|
||||
# -----------------------------------------------------------------------------
|
||||
_UTIME_MS = [0]
|
||||
|
||||
|
||||
def utime_advance(ms):
|
||||
_UTIME_MS[0] += int(ms)
|
||||
|
||||
|
||||
def _install_utime_shim():
|
||||
if "utime" in sys.modules:
|
||||
return
|
||||
import types
|
||||
|
||||
m = types.ModuleType("utime")
|
||||
|
||||
def ticks_ms():
|
||||
return _UTIME_MS[0]
|
||||
|
||||
def ticks_diff(a, b):
|
||||
return a - b
|
||||
|
||||
def ticks_add(a, b):
|
||||
return a + b
|
||||
|
||||
m.ticks_ms = ticks_ms
|
||||
m.ticks_diff = ticks_diff
|
||||
m.ticks_add = ticks_add
|
||||
sys.modules["utime"] = m
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Load ``preset`` and pattern modules from ``led-driver/src`` (no sys.path).
|
||||
# -----------------------------------------------------------------------------
|
||||
_SRC = Path(__file__).resolve().parent.parent / "src"
|
||||
_TESTS = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def _load_module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("no spec for %s" % path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def _pattern_class_from_module(mod):
|
||||
for attr_name in dir(mod):
|
||||
attr = getattr(mod, attr_name)
|
||||
if isinstance(attr, type) and hasattr(attr, "run"):
|
||||
return attr
|
||||
return None
|
||||
|
||||
|
||||
def _load_preset_class():
|
||||
preset_mod = _load_module("preset", _SRC / "preset.py")
|
||||
return preset_mod.Preset
|
||||
|
||||
|
||||
def _list_pattern_basenames():
|
||||
pat_dir = _SRC / "patterns"
|
||||
out = []
|
||||
for p in sorted(pat_dir.iterdir()):
|
||||
if p.suffix != ".py":
|
||||
continue
|
||||
if p.name in ("__init__.py", "main.py"):
|
||||
continue
|
||||
out.append(p.stem)
|
||||
return out
|
||||
|
||||
|
||||
def _default_preset_dict(basename):
|
||||
"""Reasonable defaults so each pattern's ``run()`` can start."""
|
||||
base = {
|
||||
"p": basename,
|
||||
"d": 100,
|
||||
"b": 200,
|
||||
"a": True,
|
||||
"n1": 5,
|
||||
"n2": 8,
|
||||
"n3": 5,
|
||||
"n4": 4,
|
||||
}
|
||||
if basename in ("rainbow", "colour_cycle"):
|
||||
base["c"] = []
|
||||
base["n1"] = 2
|
||||
elif basename == "transition":
|
||||
base["c"] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
|
||||
base["d"] = 200
|
||||
elif basename in ("chase", "circle"):
|
||||
base["c"] = [(255, 0, 0), (0, 0, 255)]
|
||||
elif basename == "pulse":
|
||||
base["c"] = [(0, 200, 100)]
|
||||
base["n1"] = 50
|
||||
base["n2"] = 40
|
||||
base["n3"] = 50
|
||||
base["d"] = 30
|
||||
elif basename in ("blink", "flicker"):
|
||||
base["c"] = [(255, 100, 0)]
|
||||
base["d"] = 80
|
||||
elif basename == "flame":
|
||||
base["c"] = []
|
||||
base["d"] = 40
|
||||
base["n1"] = 40
|
||||
base["n2"] = 2000
|
||||
base["n3"] = -1
|
||||
base["n4"] = 0
|
||||
else:
|
||||
base["c"] = [(200, 200, 200)]
|
||||
return base
|
||||
|
||||
|
||||
def _run_pattern_ticks(Preset, driver, basename, steps, ms_per_tick):
|
||||
_install_utime_shim()
|
||||
mod = _load_module("patterns.%s" % basename, _SRC / "patterns" / (basename + ".py"))
|
||||
cls = _pattern_class_from_module(mod)
|
||||
if cls is None:
|
||||
raise RuntimeError("no pattern class in %s" % basename)
|
||||
|
||||
preset = Preset(_default_preset_dict(basename))
|
||||
gen = cls(driver).run(preset)
|
||||
for _ in range(steps):
|
||||
utime_advance(ms_per_tick)
|
||||
next(gen)
|
||||
|
||||
|
||||
def main():
|
||||
failures = []
|
||||
Preset = _load_preset_class()
|
||||
fake_mod = _load_module("fake_driver", _TESTS / "fake_driver.py")
|
||||
FakePresets = fake_mod.FakePresets
|
||||
|
||||
for basename in _list_pattern_basenames():
|
||||
d = FakePresets(16)
|
||||
try:
|
||||
_run_pattern_ticks(Preset, d, basename, steps=80, ms_per_tick=50)
|
||||
print("ok patterns.%s" % basename)
|
||||
except Exception as exc:
|
||||
print("FAIL patterns.%s: %r" % (basename, exc))
|
||||
failures.append((basename, exc))
|
||||
|
||||
if failures:
|
||||
print("%d pattern(s) failed" % len(failures))
|
||||
return 1
|
||||
print("all %d pattern smoke tests passed" % len(_list_pattern_basenames()))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(130)
|
||||
Reference in New Issue
Block a user