Skip to content

Commit 49b391b

Browse files
committed
Merge upstream changes
2 parents f0feb8a + 8b0a931 commit 49b391b

File tree

4 files changed

+409
-432
lines changed

4 files changed

+409
-432
lines changed

adafruit_is31fl3741/__init__.py

Lines changed: 162 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
2525
"""
2626

27+
from sys import implementation
2728
from adafruit_bus_device import i2c_device
2829
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
2930
from adafruit_register.i2c_bit import RWBit
@@ -33,6 +34,7 @@
3334
# Used only for typing
3435
from typing import Optional, Tuple, Union # pylint: disable=unused-import
3536
from PIL import Image
37+
from adafruit_framebuf import FrameBuffer
3638
except ImportError:
3739
pass
3840

@@ -59,10 +61,13 @@
5961

6062
class IS31FL3741:
6163
"""
62-
The IS31FL3741 is an abstract class contain the main function related to this chip.
63-
Each board needs to define width, height and pixel_addr.
64+
The IS31FL3741 is an abstract class containing the main function related
65+
to this chip. It focuses on lowest-level I2C operations and chip
66+
registers, and has no concept of a 2D graphics coordinate system, nor of
67+
RGB colors (subclasses provide these). It is linear and monochromatic.
6468
65-
:param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus i2c_device
69+
:param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus
70+
i2c_device
6671
:param address: the device address; defaults to 0x30
6772
:param allocate: buffer allocation strategy: NO_BUFFER = pixels are always
6873
sent to device as they're set. PREFER_BUFFER = RAM
@@ -72,9 +77,6 @@ class IS31FL3741:
7277
MemoryError if allocation fails.
7378
"""
7479

75-
width = 13
76-
height = 9
77-
7880
_page_reg = UnaryStruct(_IS3741_COMMANDREGISTER, "<B")
7981
_lock_reg = UnaryStruct(_IS3741_COMMANDREGISTERLOCK, "<B")
8082
_id_reg = UnaryStruct(_IS3741_IDREGISTER, "<B")
@@ -117,18 +119,18 @@ def unlock(self) -> None:
117119
self._lock_reg = 0xC5
118120

119121
def set_led_scaling(self, scale: int) -> None:
120-
"""Set LED scaling.
122+
"""Set scaling level for all LEDs.
121123
122-
param scale: The scale.
124+
:param scale: Scaling level from 0 (off) to 255 (brightest).
123125
"""
124-
scalebuf = [scale] * 181
125-
scalebuf[0] = 0
126+
scalebuf = bytearray([scale] * 181) # 180 bytes + 1 for reg addr
127+
scalebuf[0] = 0 # Initial register address
126128
self.page = 2
127129
with self.i2c_device as i2c:
128-
i2c.write(bytes(scalebuf))
130+
i2c.write(scalebuf)
129131
self.page = 3
130132
with self.i2c_device as i2c:
131-
i2c.write(bytes(scalebuf))
133+
i2c.write(scalebuf, end=172) # 2nd page is smaller
132134

133135
@property
134136
def global_current(self) -> int:
@@ -186,75 +188,26 @@ def __getitem__(self, led: int) -> int:
186188
return self._buf[1]
187189

188190
def __setitem__(self, led: int, pwm: int) -> None:
189-
if not 0 <= led <= 350:
190-
raise ValueError("LED must be 0 ~ 350")
191-
if not 0 <= pwm <= 255:
192-
raise ValueError("PWM must be 0 ~ 255")
193-
# print(led, pwm)
194191
if self._pixel_buffer:
192+
# Buffered version doesn't require range checks --
193+
# Python will throw its own IndexError/ValueError as needed.
195194
self._pixel_buffer[1 + led] = pwm
196-
else:
197-
if led < 180:
198-
self.page = 0
199-
self._buf[0] = led
195+
elif 0 <= led <= 350:
196+
if 0 <= pwm <= 255:
197+
# print(led, pwm)
198+
if led < 180:
199+
self.page = 0
200+
self._buf[0] = led
201+
else:
202+
self.page = 1
203+
self._buf[0] = led - 180
204+
self._buf[1] = pwm
205+
with self.i2c_device as i2c:
206+
i2c.write(self._buf)
200207
else:
201-
self.page = 1
202-
self._buf[0] = led - 180
203-
self._buf[1] = pwm
204-
with self.i2c_device as i2c:
205-
i2c.write(self._buf)
206-
207-
# This function must be replaced for each board
208-
@staticmethod
209-
def pixel_addrs(x: int, y: int) -> Tuple[int, ...]:
210-
"""Calulate the offset into the device array for x,y pixel"""
211-
raise NotImplementedError("Supported in subclasses only")
212-
213-
# pylint: disable-msg=too-many-arguments
214-
def pixel(self, x: int, y: int, color: Optional[int] = None) -> Union[int, None]:
215-
"""
216-
Color of for x-, y-pixel
217-
218-
:param x: horizontal pixel position
219-
:param y: vertical pixel position
220-
:param color: hex color value 0x000000 to 0xFFFFFF to set,
221-
or None to return current pixel value
222-
"""
223-
224-
if 0 <= x < self.width and 0 <= y < self.height: # Clip
225-
addrs = self.pixel_addrs(x, y) # LED indices
226-
# print(addrs)
227-
if color is None: # Return current pixel color if unspecified
228-
return (self[addrs[0]] << 16) | (self[addrs[1]] << 8) | self[addrs[2]]
229-
self[addrs[0]] = (color >> 16) & 0xFF
230-
self[addrs[1]] = (color >> 8) & 0xFF
231-
self[addrs[2]] = color & 0xFF
232-
return None
233-
234-
# pylint: enable-msg=too-many-arguments
235-
236-
def image(self, img: Image) -> None:
237-
"""Set buffer to value of Python Imaging Library image. The image should
238-
be in 8-bit mode (L) and a size equal to the display size.
239-
240-
:param img: Python Imaging Library image
241-
"""
242-
if img.mode != "RGB":
243-
raise ValueError("Image must be in mode RGB.")
244-
imwidth, imheight = img.size
245-
if imwidth != self.width or imheight != self.height:
246-
raise ValueError(
247-
"Image must be same dimensions as display ({0}x{1}).".format(
248-
self.width, self.height
249-
)
250-
)
251-
# Grab all the pixels from the image, faster than getpixel.
252-
pixels = img.load()
253-
254-
# Iterate through the pixels
255-
for x in range(self.width): # yes this double loop is slow,
256-
for y in range(self.height): # but these displays are small!
257-
self.pixel(x, y, pixels[(x, y)])
208+
raise ValueError("PWM must be 0 ~ 255")
209+
else:
210+
raise ValueError("LED must be 0 ~ 350")
258211

259212
def show(self) -> None:
260213
"""Issue in-RAM pixel data to device. No effect if pixels are
@@ -279,3 +232,134 @@ def show(self) -> None:
279232
self._pixel_buffer[180] = 0
280233
i2c.write(self._pixel_buffer, start=180, end=352)
281234
self._pixel_buffer[180] = save
235+
236+
237+
IS3741_RGB = (0 << 4) | (1 << 2) | (2) # Encode as R,G,B
238+
IS3741_RBG = (0 << 4) | (2 << 2) | (1) # Encode as R,B,G
239+
IS3741_GRB = (1 << 4) | (0 << 2) | (2) # Encode as G,R,B
240+
IS3741_GBR = (2 << 4) | (0 << 2) | (1) # Encode as G,B,R
241+
IS3741_BRG = (1 << 4) | (2 << 2) | (0) # Encode as B,R,G
242+
IS3741_BGR = (2 << 4) | (1 << 2) | (0) # Encode as B,G,R
243+
244+
245+
class IS31FL3741_colorXY(IS31FL3741):
246+
"""
247+
Class encompassing IS31FL3741 and a minimal layer for RGB color 2D
248+
pixel operations (base class is hardware- and register-centric and
249+
lacks these concepts). Specific boards like the QT matrix or EyeLights
250+
glasses then subclass this. In theory, a companion monochrome XY class
251+
could be separately implemented in the future if required for anything.
252+
Mostly though, this is about providing a place for common RGB matrix
253+
functions like fill() that then work across all such devices.
254+
255+
:param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus
256+
i2c_device
257+
:param width: Matrix width in pixels.
258+
:param height: Matrix height in pixels.
259+
:param address: the device address; defaults to 0x30
260+
:param allocate: buffer allocation strategy: NO_BUFFER = pixels are always
261+
sent to device as they're set. PREFER_BUFFER = RAM
262+
permitting, buffer pixels in RAM, updating device only
263+
when show() is called, but fall back on NO_BUFFER
264+
behavior. MUST_BUFFER = buffer pixels in RAM, throw
265+
MemoryError if allocation fails.
266+
:param order: Pixel RGB color order, one of the IS3741_* color types
267+
above. Default is IS3741_BGR.
268+
"""
269+
270+
# pylint: disable-msg=too-many-arguments
271+
def __init__(
272+
self,
273+
i2c: busio.I2C,
274+
width: int,
275+
height: int,
276+
address: int = _IS3741_ADDR_DEFAULT,
277+
allocate: int = NO_BUFFER,
278+
order: int = IS3741_BGR,
279+
):
280+
super().__init__(i2c, address=address, allocate=allocate)
281+
self.order = order
282+
self.width = width
283+
self.height = height
284+
self.r_offset = (order >> 4) & 3
285+
self.g_offset = (order >> 2) & 3
286+
self.b_offset = order & 3
287+
288+
# pylint: enable-msg=too-many-arguments
289+
290+
# This function must be replaced for each board
291+
@staticmethod
292+
def pixel_addrs(x: int, y: int) -> Tuple[int, ...]:
293+
"""Calculate a device-specific LED offset for an X,Y 2D pixel."""
294+
raise NotImplementedError("Supported in subclasses only")
295+
296+
def fill(self, color: int = 0) -> None:
297+
"""Set all pixels to a given RGB color.
298+
299+
:param color: Packed 24-bit color value (0xRRGGBB).
300+
"""
301+
red = (color >> 16) & 0xFF
302+
green = (color >> 8) & 0xFF
303+
blue = color & 0xFF
304+
for y in range(self.height):
305+
for x in range(self.width):
306+
addrs = self.pixel_addrs(x, y)
307+
self[addrs[self.r_offset]] = red
308+
self[addrs[self.g_offset]] = green
309+
self[addrs[self.b_offset]] = blue
310+
311+
def pixel(self, x: int, y: int, color: int = None) -> Union[int, None]:
312+
"""
313+
Set or retrieve RGB color of pixel at position (X,Y).
314+
315+
:param x: Horizontal pixel position.
316+
:param y: Vertical pixel position.
317+
:param color: If setting, a packed 24-bit color value (0xRRGGBB).
318+
If getting, either None or leave off this argument.
319+
:returns: If setting, returns None. If getting, returns a packed
320+
24-bit color value (0xRRGGBB).
321+
"""
322+
323+
if 0 <= x < self.width and 0 <= y < self.height: # Clip
324+
addrs = self.pixel_addrs(x, y) # LED indices
325+
# print(addrs)
326+
if color is not None:
327+
self[addrs[self.r_offset]] = (color >> 16) & 0xFF
328+
self[addrs[self.g_offset]] = (color >> 8) & 0xFF
329+
self[addrs[self.b_offset]] = color & 0xFF
330+
else: # Return current pixel color if unspecified
331+
return (
332+
(self[addrs[self.r_offset]] << 16)
333+
| (self[addrs[self.g_offset]] << 8)
334+
| self[addrs[self.b_offset]]
335+
)
336+
return None
337+
338+
def image(self, img: Union[FrameBuffer, Image]) -> None:
339+
"""Copy an in-memory image to the LED matrix. Image should be in
340+
24-bit format (e.g. "RGB888") and dimensions should match matrix,
341+
this isn't super robust yet or anything.
342+
343+
:param img: Source image -- either a FrameBuffer object if running
344+
CircuitPython, or PIL image if running CPython w/Python
345+
Imaging Lib.
346+
"""
347+
if implementation.name == "circuitpython":
348+
for y in range(self.height):
349+
for x in range(self.width):
350+
self.pixel(x, y, img.pixel(x, y))
351+
else:
352+
if img.mode != "RGB":
353+
raise ValueError("Image must be in mode RGB.")
354+
if img.size[0] != self.width or img.size[1] != self.height:
355+
raise ValueError(
356+
"Image must be same dimensions as display ({0}x{1}).".format(
357+
self.width, self.height
358+
)
359+
)
360+
361+
# Iterate X/Y through all image pixels
362+
pixels = img.load() # Grab all pixels, faster than getpixel on each
363+
for y in range(self.height):
364+
for x in range(self.width):
365+
self.pixel(x, y, pixels[(x, y)])

0 commit comments

Comments
 (0)