- firmware/boot.py: WiFi connection on startup (MicroPython boot sequence) - firmware/config.json: default device config with all 9 fields (ssid, password, led_count, strip_type, led_pin, spi_id, spi_clk_pin, spi_dat_pin, udp_port) - firmware/led_driver.py: NeoPixelStrip (SK6812 RGBW) and WS2801Strip (RGB SPI) with unified interface (write_rgb, write_rgbw, fill, clear) - tests/test_firmware_protocol.py: 21 host-side tests for round-trip protocol parsing (DRGB, DRGBW, DNRGB, animation_cmd)
88 lines
2.8 KiB
Python
88 lines
2.8 KiB
Python
class NeoPixelStrip:
|
|
"""SK6812 RGBW driver using built-in neopixel module."""
|
|
|
|
def __init__(self, pin_num, led_count):
|
|
import neopixel
|
|
from machine import Pin
|
|
self.np = neopixel.NeoPixel(Pin(pin_num), led_count, bpp=4, timing=1)
|
|
self.led_count = led_count
|
|
|
|
def write_rgb(self, pixels):
|
|
"""Write list of (R,G,B) tuples. W channel set to 0."""
|
|
for i, (r, g, b) in enumerate(pixels):
|
|
if i >= self.led_count:
|
|
break
|
|
self.np[i] = (r, g, b, 0)
|
|
self.np.write()
|
|
|
|
def write_rgbw(self, pixels):
|
|
"""Write list of (R,G,B,W) tuples."""
|
|
for i, (r, g, b, w) in enumerate(pixels):
|
|
if i >= self.led_count:
|
|
break
|
|
self.np[i] = (r, g, b, w)
|
|
self.np.write()
|
|
|
|
def fill(self, r, g, b, w=0):
|
|
"""Fill all pixels with one color."""
|
|
self.np.fill((r, g, b, w))
|
|
self.np.write()
|
|
|
|
def clear(self):
|
|
self.fill(0, 0, 0, 0)
|
|
|
|
|
|
class WS2801Strip:
|
|
"""WS2801 RGB driver using SPI — no external library needed."""
|
|
|
|
def __init__(self, spi_id, clk_pin, dat_pin, led_count):
|
|
from machine import SPI, Pin
|
|
self.spi = SPI(spi_id, baudrate=1000000, polarity=0, phase=0,
|
|
sck=Pin(clk_pin), mosi=Pin(dat_pin))
|
|
self.led_count = led_count
|
|
self._buf = bytearray(led_count * 3)
|
|
|
|
def write_rgb(self, pixels):
|
|
"""Write list of (R,G,B) tuples."""
|
|
for i, (r, g, b) in enumerate(pixels):
|
|
if i >= self.led_count:
|
|
break
|
|
off = i * 3
|
|
self._buf[off] = r
|
|
self._buf[off + 1] = g
|
|
self._buf[off + 2] = b
|
|
self.spi.write(self._buf)
|
|
|
|
def write_rgbw(self, pixels):
|
|
"""Write RGBW tuples — W channel is discarded (WS2801 is RGB only)."""
|
|
for i, (r, g, b, _w) in enumerate(pixels):
|
|
if i >= self.led_count:
|
|
break
|
|
off = i * 3
|
|
self._buf[off] = r
|
|
self._buf[off + 1] = g
|
|
self._buf[off + 2] = b
|
|
self.spi.write(self._buf)
|
|
|
|
def fill(self, r, g, b, w=0):
|
|
for i in range(self.led_count):
|
|
off = i * 3
|
|
self._buf[off] = r
|
|
self._buf[off + 1] = g
|
|
self._buf[off + 2] = b
|
|
self.spi.write(self._buf)
|
|
|
|
def clear(self):
|
|
self.fill(0, 0, 0)
|
|
|
|
|
|
def create_strip(config):
|
|
"""Factory: create the right strip driver from config dict."""
|
|
if config['strip_type'] == 'sk6812':
|
|
return NeoPixelStrip(config['led_pin'], config['led_count'])
|
|
elif config['strip_type'] == 'ws2801':
|
|
return WS2801Strip(config['spi_id'], config.get('spi_clk_pin', 18),
|
|
config.get('spi_dat_pin', 23), config['led_count'])
|
|
else:
|
|
raise ValueError('Unknown strip_type: ' + config['strip_type'])
|