22# SPDX-FileCopyrightText: 2017 Ladyada for Adafruit Industries
33# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
44# SPDX-FileCopyrightText: 2018 Kevin J. Walters
5+ # SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
56#
67# SPDX-License-Identifier: MIT
78
89"""
910`adafruit_ws2801` - WS2801 LED pixel string driver
1011====================================================
1112
12- * Author(s): Damien P. George, Limor Fried & Scott Shawcroft, Kevin J Walters
13+ * Author(s): Damien P. George, Limor Fried & Scott Shawcroft, Kevin J Walters, Tim Cocks
1314"""
14- import math
1515
16+ import adafruit_pixelbuf
1617import busio
1718import digitalio
1819
1920try :
20- from typing import Any , Union , Tuple , List
21+ from typing import Type , Optional
22+ from circuitpython_typing import ReadableBuffer
23+ from types import TracebackType
2124 from microcontroller import Pin
2225except ImportError :
2326 pass
2730
2831# based on https://github.com/adafruit/Adafruit_CircuitPython_DotStar
2932
30-
31- class WS2801 :
33+ # Pixel color order constants
34+ RBG = "PRBG"
35+ """Red Blue Green"""
36+ RGB = "PRGB"
37+ """Red Green Blue"""
38+ GRB = "PGRB"
39+ """Green Red Blue"""
40+ GBR = "PGBR"
41+ """Green Blue Red"""
42+ BRG = "PBRG"
43+ """Blue Red Green"""
44+ BGR = "PBGR"
45+ """Blue Green Red"""
46+
47+
48+ class WS2801 (adafruit_pixelbuf .PixelBuf ):
3249 """
3350 A sequence of WS2801 controlled LEDs.
3451
@@ -38,7 +55,9 @@ class WS2801:
3855 :param float brightness: The brightness between 0.0 and (default) 1.0.
3956 :param bool auto_write: True if the dotstars should immediately change when
4057 set. If False, `show` must be called explicitly.
41-
58+ :param str pixel_order: Set the pixel order on the strip - different
59+ strips implement this differently. If you send red, and it looks blue
60+ or green on the strip, modify this! It should be one of the values above.
4261
4362 Example for Gemma M0:
4463
@@ -53,16 +72,34 @@ class WS2801:
5372 with adafruit_ws2801.WS2801(board.D2, board.D0, 25, brightness=1.0) as pixels:
5473 pixels[0] = darkred
5574 time.sleep(2)
75+
76+ .. py:method:: show()
77+
78+ Shows the new colors on the ws2801 LEDs themselves if they haven't already
79+ been autowritten.
80+
81+ The colors may or may not be showing after this function returns because
82+ it may be done asynchronously.
83+
84+ .. py:method:: fill(color)
85+
86+ Colors all ws2801 LEDs the given ***color***.
87+
88+ .. py:attribute:: brightness
89+
90+ Overall brightness of all ws2801 LEDs (0 to 1.0)
91+
5692 """
5793
58- def __init__ (
94+ def __init__ ( # pylint: disable=too-many-arguments
5995 self ,
6096 clock : Pin ,
6197 data : Pin ,
6298 n : int ,
6399 * ,
64100 brightness : float = 1.0 ,
65- auto_write : bool = True
101+ auto_write : bool = True ,
102+ pixel_order : str = "RGB" ,
66103 ) -> None :
67104 self ._spi = None
68105 try :
@@ -76,21 +113,31 @@ def __init__(
76113 self .dpin .direction = digitalio .Direction .OUTPUT
77114 self .cpin .direction = digitalio .Direction .OUTPUT
78115 self .cpin .value = False
79- self ._n = n
80- self ._buf = bytearray (n * 3 )
81- self ._brightness = 1.0 # keeps pylint happy
82- # Set auto_write to False temporarily so brightness setter does _not_
83- # call show() while in __init__.
84- self .auto_write = False
85- self .brightness = brightness
86- self .auto_write = auto_write
87- # TODO - review/consider adding GRB support like that in c++ version
116+
117+ # Supply one extra clock cycle for each two pixels in the strip.
118+ trailer_size = n // 16
119+ if n % 16 != 0 :
120+ trailer_size += 1
121+
122+ # Empty header.
123+ header = bytearray (0 )
124+ # Zero bits, not ones, for the trailer, to avoid lighting up
125+ # downstream pixels, if there are more physical pixels than
126+ # the length of this object.
127+ trailer = bytearray (trailer_size )
128+
129+ super ().__init__ (
130+ n ,
131+ byteorder = pixel_order ,
132+ brightness = brightness ,
133+ auto_write = auto_write ,
134+ header = header ,
135+ trailer = trailer ,
136+ )
88137
89138 def deinit (self ) -> None :
90- """Blank out the DotStars and release the resources."""
91- self .auto_write = False
92- black = (0 , 0 , 0 )
93- self .fill (black )
139+ """Blank out the ws2801 LEDs and release the resources."""
140+ self .fill (0 )
94141 self .show ()
95142 if self ._spi :
96143 self ._spi .deinit ()
@@ -102,81 +149,16 @@ def __enter__(self) -> "WS2801":
102149 return self
103150
104151 def __exit__ (
105- self , exception_type : Any , exception_value : Any , traceback : Any
152+ self ,
153+ exception_type : Optional [Type [type ]],
154+ exception_value : Optional [BaseException ],
155+ traceback : Optional [TracebackType ],
106156 ) -> None :
107157 self .deinit ()
108158
109159 def __repr__ (self ):
110160 return "[" + ", " .join ([str (x ) for x in self ]) + "]"
111161
112- def _set_item (self , index : int , value : Union [Tuple [int , ...], int ]):
113- offset = index * 3
114- if isinstance (value , int ):
115- r = value >> 16
116- g = (value >> 8 ) & 0xFF
117- b = value & 0xFF
118- else :
119- r , g , b = value
120- # red/green/blue order for WS2801
121- self ._buf [offset ] = r
122- self ._buf [offset + 1 ] = g
123- self ._buf [offset + 2 ] = b
124-
125- def __setitem__ (self , index : int , val : Union [Tuple [int , ...], int ]):
126- if isinstance (index , slice ):
127- start , stop , step = index .indices (self ._n )
128- length = stop - start
129- if step != 0 :
130- length = math .ceil (length / step )
131- if len (val ) != length :
132- raise ValueError ("Slice and input sequence size do not match." )
133- for val_i , in_i in enumerate (range (start , stop , step )):
134- self ._set_item (in_i , val [val_i ])
135- else :
136- self ._set_item (index , val )
137-
138- if self .auto_write :
139- self .show ()
140-
141- def __getitem__ (
142- self , index : Union [slice , int ]
143- ) -> Union [Tuple [int , ...], List [Tuple [int , ...]]]:
144- if isinstance (index , slice ):
145- out = []
146- for in_i in range (* index .indices (self ._n )):
147- out .append (tuple (self ._buf [in_i * 3 + i ] for i in range (3 )))
148- return out
149- if index < 0 :
150- index += len (self )
151- if index >= self ._n or index < 0 :
152- raise IndexError
153- offset = index * 3
154- return tuple (self ._buf [offset + i ] for i in range (3 ))
155-
156- def __len__ (self ) -> int :
157- return self ._n
158-
159- @property
160- def brightness (self ) -> float :
161- """Overall brightness of the pixel"""
162- return self ._brightness
163-
164- @brightness .setter
165- def brightness (self , brightness : float ) -> None :
166- self ._brightness = min (max (brightness , 0.0 ), 1.0 )
167- if self .auto_write :
168- self .show ()
169-
170- def fill (self , color : Union [Tuple [int , ...], int ]) -> None :
171- """Colors all pixels the given ***color***."""
172- auto_write = self .auto_write
173- self .auto_write = False
174- for i , _ in enumerate (self ):
175- self [i ] = color
176- if auto_write :
177- self .show ()
178- self .auto_write = auto_write
179-
180162 def _ds_writebytes (self , buf : bytearray ) -> None :
181163 for b in buf :
182164 for _ in range (8 ):
@@ -185,21 +167,8 @@ def _ds_writebytes(self, buf: bytearray) -> None:
185167 self .cpin .value = False
186168 b = b << 1
187169
188- def show (self ) -> None :
189- """Shows the new colors on the pixels themselves if they haven't already
190- been autowritten.
191-
192- The colors may or may not be showing after this function returns because
193- it may be done asynchronously."""
194- # Create a second output buffer if we need to compute brightness
195- buf = self ._buf
196- if self .brightness < 1.0 :
197- buf = bytearray (len (self ._buf ))
198- for i , val in enumerate (self ._buf ):
199- buf [i ] = int (val * self ._brightness )
200-
170+ def _transmit (self , buffer : ReadableBuffer ) -> None :
201171 if self ._spi :
202- self ._spi .write (buf )
172+ self ._spi .write (buffer )
203173 else :
204- self ._ds_writebytes (buf )
205- self .cpin .value = False
174+ self ._ds_writebytes (buffer )
0 commit comments