Skip to content

Commit

Permalink
Merge pull request #25 from FoamyGuy/use_pixelbuf
Browse files Browse the repository at this point in the history
Use adafruit_pixelbuf
  • Loading branch information
FoamyGuy authored Jan 7, 2025
2 parents 6604532 + a961df7 commit 171998b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 106 deletions.
181 changes: 75 additions & 106 deletions adafruit_ws2801.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@
# SPDX-FileCopyrightText: 2017 Ladyada for Adafruit Industries
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2018 Kevin J. Walters
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`adafruit_ws2801` - WS2801 LED pixel string driver
====================================================
* Author(s): Damien P. George, Limor Fried & Scott Shawcroft, Kevin J Walters
* Author(s): Damien P. George, Limor Fried & Scott Shawcroft, Kevin J Walters, Tim Cocks
"""
import math

import adafruit_pixelbuf
import busio
import digitalio

try:
from typing import Any, Union, Tuple, List
from typing import Type, Optional
from circuitpython_typing import ReadableBuffer
from types import TracebackType
from microcontroller import Pin
except ImportError:
pass
Expand All @@ -27,8 +30,22 @@

# based on https://github.com/adafruit/Adafruit_CircuitPython_DotStar


class WS2801:
# Pixel color order constants
RBG = "PRBG"
"""Red Blue Green"""
RGB = "PRGB"
"""Red Green Blue"""
GRB = "PGRB"
"""Green Red Blue"""
GBR = "PGBR"
"""Green Blue Red"""
BRG = "PBRG"
"""Blue Red Green"""
BGR = "PBGR"
"""Blue Green Red"""


class WS2801(adafruit_pixelbuf.PixelBuf):
"""
A sequence of WS2801 controlled LEDs.
Expand All @@ -38,7 +55,9 @@ class WS2801:
:param float brightness: The brightness between 0.0 and (default) 1.0.
:param bool auto_write: True if the dotstars should immediately change when
set. If False, `show` must be called explicitly.
:param str pixel_order: Set the pixel order on the strip - different
strips implement this differently. If you send red, and it looks blue
or green on the strip, modify this! It should be one of the values above.
Example for Gemma M0:
Expand All @@ -53,16 +72,34 @@ class WS2801:
with adafruit_ws2801.WS2801(board.D2, board.D0, 25, brightness=1.0) as pixels:
pixels[0] = darkred
time.sleep(2)
.. py:method:: show()
Shows the new colors on the ws2801 LEDs themselves if they haven't already
been autowritten.
The colors may or may not be showing after this function returns because
it may be done asynchronously.
.. py:method:: fill(color)
Colors all ws2801 LEDs the given ***color***.
.. py:attribute:: brightness
Overall brightness of all ws2801 LEDs (0 to 1.0)
"""

def __init__(
def __init__( # pylint: disable=too-many-arguments
self,
clock: Pin,
data: Pin,
n: int,
*,
brightness: float = 1.0,
auto_write: bool = True
auto_write: bool = True,
pixel_order: str = "RGB",
) -> None:
self._spi = None
try:
Expand All @@ -76,21 +113,31 @@ def __init__(
self.dpin.direction = digitalio.Direction.OUTPUT
self.cpin.direction = digitalio.Direction.OUTPUT
self.cpin.value = False
self._n = n
self._buf = bytearray(n * 3)
self._brightness = 1.0 # keeps pylint happy
# Set auto_write to False temporarily so brightness setter does _not_
# call show() while in __init__.
self.auto_write = False
self.brightness = brightness
self.auto_write = auto_write
# TODO - review/consider adding GRB support like that in c++ version

# Supply one extra clock cycle for each two pixels in the strip.
trailer_size = n // 16
if n % 16 != 0:
trailer_size += 1

# Empty header.
header = bytearray(0)
# Zero bits, not ones, for the trailer, to avoid lighting up
# downstream pixels, if there are more physical pixels than
# the length of this object.
trailer = bytearray(trailer_size)

super().__init__(
n,
byteorder=pixel_order,
brightness=brightness,
auto_write=auto_write,
header=header,
trailer=trailer,
)

def deinit(self) -> None:
"""Blank out the DotStars and release the resources."""
self.auto_write = False
black = (0, 0, 0)
self.fill(black)
"""Blank out the ws2801 LEDs and release the resources."""
self.fill(0)
self.show()
if self._spi:
self._spi.deinit()
Expand All @@ -102,81 +149,16 @@ def __enter__(self) -> "WS2801":
return self

def __exit__(
self, exception_type: Any, exception_value: Any, traceback: Any
self,
exception_type: Optional[Type[type]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self.deinit()

def __repr__(self):
return "[" + ", ".join([str(x) for x in self]) + "]"

def _set_item(self, index: int, value: Union[Tuple[int, ...], int]):
offset = index * 3
if isinstance(value, int):
r = value >> 16
g = (value >> 8) & 0xFF
b = value & 0xFF
else:
r, g, b = value
# red/green/blue order for WS2801
self._buf[offset] = r
self._buf[offset + 1] = g
self._buf[offset + 2] = b

def __setitem__(self, index: int, val: Union[Tuple[int, ...], int]):
if isinstance(index, slice):
start, stop, step = index.indices(self._n)
length = stop - start
if step != 0:
length = math.ceil(length / step)
if len(val) != length:
raise ValueError("Slice and input sequence size do not match.")
for val_i, in_i in enumerate(range(start, stop, step)):
self._set_item(in_i, val[val_i])
else:
self._set_item(index, val)

if self.auto_write:
self.show()

def __getitem__(
self, index: Union[slice, int]
) -> Union[Tuple[int, ...], List[Tuple[int, ...]]]:
if isinstance(index, slice):
out = []
for in_i in range(*index.indices(self._n)):
out.append(tuple(self._buf[in_i * 3 + i] for i in range(3)))
return out
if index < 0:
index += len(self)
if index >= self._n or index < 0:
raise IndexError
offset = index * 3
return tuple(self._buf[offset + i] for i in range(3))

def __len__(self) -> int:
return self._n

@property
def brightness(self) -> float:
"""Overall brightness of the pixel"""
return self._brightness

@brightness.setter
def brightness(self, brightness: float) -> None:
self._brightness = min(max(brightness, 0.0), 1.0)
if self.auto_write:
self.show()

def fill(self, color: Union[Tuple[int, ...], int]) -> None:
"""Colors all pixels the given ***color***."""
auto_write = self.auto_write
self.auto_write = False
for i, _ in enumerate(self):
self[i] = color
if auto_write:
self.show()
self.auto_write = auto_write

def _ds_writebytes(self, buf: bytearray) -> None:
for b in buf:
for _ in range(8):
Expand All @@ -185,21 +167,8 @@ def _ds_writebytes(self, buf: bytearray) -> None:
self.cpin.value = False
b = b << 1

def show(self) -> None:
"""Shows the new colors on the pixels themselves if they haven't already
been autowritten.
The colors may or may not be showing after this function returns because
it may be done asynchronously."""
# Create a second output buffer if we need to compute brightness
buf = self._buf
if self.brightness < 1.0:
buf = bytearray(len(self._buf))
for i, val in enumerate(self._buf):
buf[i] = int(val * self._brightness)

def _transmit(self, buffer: ReadableBuffer) -> None:
if self._spi:
self._spi.write(buf)
self._spi.write(buffer)
else:
self._ds_writebytes(buf)
self.cpin.value = False
self._ds_writebytes(buffer)
3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py)
.. use this format as the module name: "adafruit_foo.foo"
API Reference
=============

.. automodule:: adafruit_ws2801
:members:
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

Adafruit-Blinka
adafruit-circuitpython-busdevice
adafruit-circuitpython-pixelbuf

0 comments on commit 171998b

Please sign in to comment.