Skip to content

Commit 9f1202c

Browse files
authored
Merge pull request #54 from DJDevon3/DJDevon3-IS31FL3731
Add Rotation and Typing
2 parents 6d364d1 + 9cf7264 commit 9f1202c

File tree

4 files changed

+211
-54
lines changed

4 files changed

+211
-54
lines changed

adafruit_is31fl3731/__init__.py

Lines changed: 143 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,36 @@
4747
https://github.com/adafruit/circuitpython/releases
4848
4949
"""
50-
5150
# imports
5251
import math
5352
import time
5453
from micropython import const
5554

5655
from adafruit_bus_device.i2c_device import I2CDevice
5756

57+
try:
58+
import typing
59+
import busio
60+
from circuitpython_typing import TypeAlias, Union
61+
from circuitpython_typing import (
62+
WriteableBuffer,
63+
ReadableBuffer,
64+
) # Import ReadableBuffer here
65+
66+
from typing import (
67+
TYPE_CHECKING,
68+
List,
69+
Tuple,
70+
Optional,
71+
Iterable,
72+
)
73+
74+
from PIL import Image
75+
76+
except ImportError as e:
77+
pass
78+
79+
5880
__version__ = "0.0.0+auto.0"
5981
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731.git"
6082

@@ -89,17 +111,25 @@ class IS31FL3731:
89111
90112
:param ~busio.I2C i2c: the connected i2c bus i2c_device
91113
:param int address: the device address; defaults to 0x74
114+
:param Iterable frames: list of frame indexes to use. int's 0-7.
92115
"""
93116

94-
width = 16
95-
height = 9
117+
width: int = 16
118+
height: int = 9
96119

97-
def __init__(self, i2c, address=0x74, frames=None):
120+
def __init__(
121+
self,
122+
i2c: busio.I2C,
123+
frames: Optional[Iterable] = None,
124+
address: int = 0x74,
125+
):
98126
self.i2c_device = I2CDevice(i2c, address)
99127
self._frame = None
100128
self._init(frames=frames)
101129

102-
def _i2c_read_reg(self, reg, result):
130+
def _i2c_read_reg(
131+
self, reg: Optional[int] = None, result: Optional[WriteableBuffer] = None
132+
) -> Optional[WriteableBuffer]:
103133
# Read a buffer of data from the specified 8-bit I2C register address.
104134
# The provided result parameter will be filled to capacity with bytes
105135
# of data read from the register.
@@ -108,36 +138,44 @@ def _i2c_read_reg(self, reg, result):
108138
return result
109139
return None
110140

111-
def _i2c_write_reg(self, reg, data):
141+
def _i2c_write_reg(
142+
self, reg: Optional[int] = None, data: Optional[ReadableBuffer] = None
143+
) -> None:
112144
# Write a contiguous block of data (bytearray) starting at the
113145
# specified I2C register address (register passed as argument).
114146
self._i2c_write_block(bytes([reg]) + data)
115147

116-
def _i2c_write_block(self, data):
148+
def _i2c_write_block(self, data: Optional[ReadableBuffer]) -> None:
117149
# Write a buffer of data (byte array) to the specified I2C register
118150
# address.
119151
with self.i2c_device as i2c:
120152
i2c.write(data)
121153

122-
def _bank(self, bank=None):
154+
def _bank(self, bank: Optional[int] = None) -> Optional[int]:
123155
if bank is None:
124156
result = bytearray(1)
125157
return self._i2c_read_reg(_BANK_ADDRESS, result)[0]
126158
self._i2c_write_reg(_BANK_ADDRESS, bytearray([bank]))
127159
return None
128160

129-
def _register(self, bank, register, value=None):
161+
def _register(
162+
self,
163+
bank: Optional[int] = None,
164+
register: Optional[int] = None,
165+
value: Optional[int] = None,
166+
) -> Optional[int]:
130167
self._bank(bank)
131168
if value is None:
132169
result = bytearray(1)
133170
return self._i2c_read_reg(register, result)[0]
134171
self._i2c_write_reg(register, bytearray([value]))
135172
return None
136173

137-
def _mode(self, mode=None):
174+
def _mode(self, mode: Optional[int] = None) -> int:
175+
"""Function for setting _register mode"""
138176
return self._register(_CONFIG_BANK, _MODE_REGISTER, mode)
139177

140-
def _init(self, frames=None):
178+
def _init(self, frames: Iterable) -> None:
141179
self.sleep(True)
142180
# Clear config; sets to Picture Mode, no audio sync, maintains sleep
143181
self._bank(_CONFIG_BANK)
@@ -154,21 +192,26 @@ def _init(self, frames=None):
154192
self._frame = 0 # To match config bytes above
155193
self.sleep(False)
156194

157-
def reset(self):
195+
def reset(self) -> None:
158196
"""Kill the display for 10MS"""
159197
self.sleep(True)
160198
time.sleep(0.01) # 10 MS pause to reset.
161199
self.sleep(False)
162200

163-
def sleep(self, value):
201+
def sleep(self, value: bool) -> Optional[int]:
164202
"""
165203
Set the Software Shutdown Register bit
166204
167205
:param value: True to set software shutdown bit; False unset
168206
"""
169207
return self._register(_CONFIG_BANK, _SHUTDOWN_REGISTER, not value)
170208

171-
def autoplay(self, delay=0, loops=0, frames=0):
209+
def autoplay(
210+
self,
211+
delay: int = 0,
212+
loops: int = 0,
213+
frames: int = 0,
214+
) -> None:
172215
"""
173216
Start autoplay
174217
@@ -190,15 +233,20 @@ def autoplay(self, delay=0, loops=0, frames=0):
190233
self._register(_CONFIG_BANK, _AUTOPLAY2_REGISTER, delay % 64)
191234
self._mode(_AUTOPLAY_MODE | self._frame)
192235

193-
def fade(self, fade_in=None, fade_out=None, pause=0):
236+
def fade(
237+
self,
238+
fade_in: Optional[int] = None,
239+
fade_out: Optional[int] = None,
240+
pause: int = 0,
241+
) -> int:
194242
"""
195243
Start and stop the fade feature. If both fade_in and fade_out are None (the
196244
default), the breath feature is used for fading. if fade_in is None, then
197245
fade_in = fade_out. If fade_out is None, then fade_out = fade_in
198246
199-
:param fade_in: positive number; 0->100
200-
:param fade-out: positive number; 0->100
201-
:param pause: breath register 2 pause value
247+
:param fade_in: int positive number; 0->100
248+
:param fade-out: int positive number; 0->100
249+
:param pause: int breath register 2 pause value
202250
"""
203251
if fade_in is None and fade_out is None:
204252
self._register(_CONFIG_BANK, _BREATH2_REGISTER, 0)
@@ -223,12 +271,12 @@ def fade(self, fade_in=None, fade_out=None, pause=0):
223271
self._register(_CONFIG_BANK, _BREATH1_REGISTER, fade_out << 4 | fade_in)
224272
self._register(_CONFIG_BANK, _BREATH2_REGISTER, 1 << 4 | pause)
225273

226-
def frame(self, frame=None, show=True):
274+
def frame(self, frame: Optional[int] = None, show: bool = True) -> Optional[int]:
227275
"""
228276
Set the current frame
229277
230-
:param frame: frame number; 0-7 or None. If None function returns current frame
231-
:param show: True to show the frame; False to not show.
278+
:param frame: int frame number; 0-7 or None. If None function returns current frame
279+
:param show: bool True to show the frame; False to not show.
232280
"""
233281
if frame is None:
234282
return self._frame
@@ -239,11 +287,17 @@ def frame(self, frame=None, show=True):
239287
self._register(_CONFIG_BANK, _FRAME_REGISTER, frame)
240288
return None
241289

242-
def audio_sync(self, value=None):
290+
def audio_sync(self, value: Optional[int]) -> Optional[int]:
243291
"""Set the audio sync feature register"""
244292
return self._register(_CONFIG_BANK, _AUDIOSYNC_REGISTER, value)
245293

246-
def audio_play(self, sample_rate, audio_gain=0, agc_enable=False, agc_fast=False):
294+
def audio_play(
295+
self,
296+
sample_rate: int,
297+
audio_gain: int = 0,
298+
agc_enable: bool = False,
299+
agc_fast: bool = False,
300+
) -> None:
247301
"""Controls the audio play feature"""
248302
if sample_rate == 0:
249303
self._mode(_PICTURE_MODE)
@@ -262,7 +316,7 @@ def audio_play(self, sample_rate, audio_gain=0, agc_enable=False, agc_fast=False
262316
)
263317
self._mode(_AUDIOPLAY_MODE)
264318

265-
def blink(self, rate=None):
319+
def blink(self, rate: Optional[int] = None) -> Optional[int]:
266320
"""Updates the blink register"""
267321
# pylint: disable=no-else-return
268322
# This needs to be refactored when it can be tested
@@ -275,13 +329,18 @@ def blink(self, rate=None):
275329
self._register(_CONFIG_BANK, _BLINK_REGISTER, rate & 0x07 | 0x08)
276330
return None
277331

278-
def fill(self, color=None, blink=None, frame=None):
332+
def fill(
333+
self,
334+
color: Optional[int] = None,
335+
frame: Optional[int] = None,
336+
blink: bool = False,
337+
):
279338
"""
280339
Fill the display with a brightness level
281340
282341
:param color: brightness 0->255
283-
:param blink: True if blinking is required
284-
:param frame: which frame to fill 0->7
342+
:param blink: bool True to blink
343+
:param frame: int the frame to set the pixel, default 0
285344
"""
286345
if frame is None:
287346
frame = self._frame
@@ -301,35 +360,72 @@ def fill(self, color=None, blink=None, frame=None):
301360

302361
# This function must be replaced for each board
303362
@staticmethod
304-
def pixel_addr(x, y):
363+
def pixel_addr(x: int, y: int) -> int:
305364
"""Calulate the offset into the device array for x,y pixel"""
306365
return x + y * 16
307366

308367
# pylint: disable-msg=too-many-arguments
309-
def pixel(self, x, y, color=None, blink=None, frame=None):
368+
def pixel(
369+
self,
370+
x: int,
371+
y: int,
372+
color: Optional[int] = None,
373+
frame: Optional[int] = None,
374+
blink: bool = False,
375+
rotate: int = 0,
376+
) -> Optional[int]:
310377
"""
311-
Blink or brightness for x-, y-pixel
312-
313-
:param x: horizontal pixel position
314-
:param y: vertical pixel position
315-
:param color: brightness value 0->255
316-
:param blink: True to blink
317-
:param frame: the frame to set the pixel
378+
Matrix display configuration
379+
380+
:param x: int horizontal pixel position
381+
:param y: int vertical pixel position
382+
:param color: int brightness value 0->255
383+
:param blink: bool True to blink
384+
:param frame: int the frame to set the pixel, default 0
385+
:param rotate: int display rotation (0, 90, 180, 270)
318386
"""
319-
if not 0 <= x <= self.width:
320-
return None
321-
if not 0 <= y <= self.height:
322-
return None
323-
pixel = self.pixel_addr(x, y)
387+
# pylint: disable=too-many-branches
388+
389+
if rotate not in (0, 90, 180, 270):
390+
raise ValueError("Rotation must be 0, 90, 180, or 270 degrees")
391+
392+
if rotate == 0:
393+
check_x = 0 <= x <= self.width
394+
check_y = 0 <= y <= self.height
395+
if not (check_x and check_y):
396+
return None
397+
pixel = self.pixel_addr(x, y)
398+
elif rotate == 90:
399+
check_x = 0 <= y <= self.width
400+
check_y = 0 <= x <= self.height
401+
if not (check_x and check_y):
402+
return None
403+
pixel = self.pixel_addr(y, self.height - x - 1)
404+
elif rotate == 180:
405+
check_x = 0 <= x <= self.width
406+
check_y = 0 <= y <= self.height
407+
if not (check_x and check_y):
408+
return None
409+
pixel = self.pixel_addr(self.width - x - 1, self.height - y - 1)
410+
elif rotate == 270:
411+
check_x = 0 <= y <= self.width
412+
check_y = 0 <= x <= self.height
413+
if not (check_x and check_y):
414+
return None
415+
pixel = self.pixel_addr(self.width - y - 1, x)
416+
324417
if color is None and blink is None:
325418
return self._register(self._frame, pixel)
419+
# frames other than 0 only used in animation. allow None.
326420
if frame is None:
327421
frame = self._frame
422+
# Brightness
328423
if color is not None:
329424
if not 0 <= color <= 255:
330-
raise ValueError("Color out of range")
425+
raise ValueError("Brightness or Color out of range (0-255)")
331426
self._register(frame, _COLOR_OFFSET + pixel, color)
332-
if blink is not None:
427+
# Blink works but not well while animated
428+
if blink:
333429
addr, bit = divmod(pixel, 8)
334430
bits = self._register(frame, _BLINK_OFFSET + addr)
335431
if blink:
@@ -341,13 +437,15 @@ def pixel(self, x, y, color=None, blink=None, frame=None):
341437

342438
# pylint: enable-msg=too-many-arguments
343439

344-
def image(self, img, blink=None, frame=None):
440+
def image(
441+
self, img: Image, frame: Optional[int] = None, blink: bool = False
442+
) -> None:
345443
"""Set buffer to value of Python Imaging Library image. The image should
346444
be in 8-bit mode (L) and a size equal to the display size.
347445
348446
:param img: Python Imaging Library image
349447
:param blink: True to blink
350-
:param frame: the frame to set the image
448+
:param frame: the frame to set the image, default 0
351449
"""
352450
if img.mode != "L":
353451
raise ValueError("Image must be in mode L.")

0 commit comments

Comments
 (0)