diff --git a/src/code.py b/src/code.py index be86a7f..aea4432 100644 --- a/src/code.py +++ b/src/code.py @@ -1,6 +1,6 @@ # UPDATE the settings.toml file before starting! -# Following are imported from circuitpython 8.x +# Following are imported from circuitpython 9.x import os import gc import board diff --git a/src/lib/adafruit_bitmap_font/bdf.py b/src/lib/adafruit_bitmap_font/bdf.py index c27175e..4ff14f0 100644 --- a/src/lib/adafruit_bitmap_font/bdf.py +++ b/src/lib/adafruit_bitmap_font/bdf.py @@ -33,7 +33,7 @@ from fontio import Glyph from .glyph_cache import GlyphCache -__version__ = "2.1.0" +__version__ = "2.1.3" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" diff --git a/src/lib/adafruit_bitmap_font/bitmap_font.py b/src/lib/adafruit_bitmap_font/bitmap_font.py index da11e5d..640b041 100644 --- a/src/lib/adafruit_bitmap_font/bitmap_font.py +++ b/src/lib/adafruit_bitmap_font/bitmap_font.py @@ -31,7 +31,7 @@ except ImportError: pass -__version__ = "2.1.0" +__version__ = "2.1.3" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" diff --git a/src/lib/adafruit_bitmap_font/glyph_cache.py b/src/lib/adafruit_bitmap_font/glyph_cache.py index 6a8f1a0..2933f89 100644 --- a/src/lib/adafruit_bitmap_font/glyph_cache.py +++ b/src/lib/adafruit_bitmap_font/glyph_cache.py @@ -30,7 +30,7 @@ import gc -__version__ = "2.1.0" +__version__ = "2.1.3" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font.git" diff --git a/src/lib/adafruit_bus_device/i2c_device.py b/src/lib/adafruit_bus_device/i2c_device.py index 8f6172b..c605290 100644 --- a/src/lib/adafruit_bus_device/i2c_device.py +++ b/src/lib/adafruit_bus_device/i2c_device.py @@ -20,7 +20,7 @@ pass -__version__ = "5.2.7" +__version__ = "5.2.10" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" diff --git a/src/lib/adafruit_bus_device/spi_device.py b/src/lib/adafruit_bus_device/spi_device.py index 1c599e0..60954e0 100644 --- a/src/lib/adafruit_bus_device/spi_device.py +++ b/src/lib/adafruit_bus_device/spi_device.py @@ -22,7 +22,7 @@ pass -__version__ = "5.2.7" +__version__ = "5.2.10" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git" @@ -89,7 +89,7 @@ def __init__( self.chip_select = chip_select self.cs_active_value = cs_active_value if self.chip_select: - self.chip_select.switch_to_output(value=True) + self.chip_select.switch_to_output(value=not self.cs_active_value) def __enter__(self) -> SPI: while not self.spi.try_lock(): diff --git a/src/lib/adafruit_connection_manager.mpy b/src/lib/adafruit_connection_manager.mpy new file mode 100644 index 0000000..002d5cd Binary files /dev/null and b/src/lib/adafruit_connection_manager.mpy differ diff --git a/src/lib/adafruit_display_text/__init__.py b/src/lib/adafruit_display_text/__init__.py index 39a83cf..7a46cba 100644 --- a/src/lib/adafruit_display_text/__init__.py +++ b/src/lib/adafruit_display_text/__init__.py @@ -7,7 +7,7 @@ ======================= """ -__version__ = "3.0.5" +__version__ = "3.2.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" from displayio import Group, Palette @@ -246,7 +246,6 @@ def __init__( tab_replacement: Tuple[int, str] = (4, " "), label_direction: str = "LTR", verbose: bool = False, - **kwargs, # pylint: disable=unused-argument ) -> None: # pylint: disable=too-many-arguments, too-many-locals @@ -271,9 +270,6 @@ def __init__( self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] self._verbose = verbose - if "max_glyphs" in kwargs: - print("Please update your code: 'max_glyphs' is not needed anymore.") - self._ascent, self._descent = self._get_ascent_descent() self._bounding_box = None @@ -428,12 +424,12 @@ def bounding_box(self) -> Tuple[int, int]: @property def height(self) -> int: """The height of the label determined from the bounding box.""" - return self._bounding_box[3] - self._bounding_box[1] + return self._bounding_box[3] @property def width(self) -> int: """The width of the label determined from the bounding box.""" - return self._bounding_box[2] - self._bounding_box[0] + return self._bounding_box[2] @property def line_spacing(self) -> float: diff --git a/src/lib/adafruit_display_text/bitmap_label.py b/src/lib/adafruit_display_text/bitmap_label.py index 8b78661..514ae69 100644 --- a/src/lib/adafruit_display_text/bitmap_label.py +++ b/src/lib/adafruit_display_text/bitmap_label.py @@ -23,7 +23,7 @@ """ -__version__ = "3.0.5" +__version__ = "3.2.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" import displayio @@ -183,6 +183,10 @@ def _reset_text( if self._background_tight: box_y = tight_box_y y_offset = tight_y_offset + self._padding_left = 0 + self._padding_right = 0 + self._padding_top = 0 + self._padding_bottom = 0 else: # calculate the box size for a loose background box_y = loose_box_y @@ -288,15 +292,18 @@ def _line_spacing_ypixels(font: FontProtocol, line_spacing: float) -> int: def _text_bounding_box( self, text: str, font: FontProtocol ) -> Tuple[int, int, int, int, int, int]: - # pylint: disable=too-many-locals + # pylint: disable=too-many-locals,too-many-branches - ascender_max, descender_max = self._ascent, self._descent + bbox = font.get_bounding_box() + if len(bbox) == 4: + ascender_max, descender_max = bbox[1], -bbox[3] + else: + ascender_max, descender_max = self._ascent, self._descent lines = 1 - xposition = ( - x_start - ) = yposition = y_start = 0 # starting x and y position (left margin) + # starting x and y position (left margin) + xposition = x_start = yposition = y_start = 0 left = None right = x_start diff --git a/src/lib/adafruit_display_text/label.py b/src/lib/adafruit_display_text/label.py index 09e6d22..7dd64a2 100644 --- a/src/lib/adafruit_display_text/label.py +++ b/src/lib/adafruit_display_text/label.py @@ -22,7 +22,7 @@ """ -__version__ = "3.0.5" +__version__ = "3.2.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" diff --git a/src/lib/adafruit_display_text/outlined_label.py b/src/lib/adafruit_display_text/outlined_label.py new file mode 100644 index 0000000..050ceb9 --- /dev/null +++ b/src/lib/adafruit_display_text/outlined_label.py @@ -0,0 +1,188 @@ +# SPDX-FileCopyrightText: 2023 Tim C +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.outlined_label` +==================================================== + +Subclass of BitmapLabel that adds outline color and stroke size +functionalities. + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import bitmaptools +from displayio import Palette, Bitmap +from adafruit_display_text import bitmap_label + +try: + from typing import Optional, Tuple, Union + from fontio import FontProtocol +except ImportError: + pass + + +class OutlinedLabel(bitmap_label.Label): + """ + OutlinedLabel - A BitmapLabel subclass that includes arguments and properties for specifying + outline_size and outline_color to get drawn as a stroke around the text. + + :param Union[Tuple, int] outline_color: The color of the outline stroke as RGB tuple, or hex. + :param int outline_size: The size in pixels of the outline stroke. + + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + font, + outline_color: Union[int, Tuple] = 0x999999, + outline_size: int = 1, + padding_top: Optional[int] = None, + padding_bottom: Optional[int] = None, + padding_left: Optional[int] = None, + padding_right: Optional[int] = None, + **kwargs + ): + if padding_top is None: + padding_top = outline_size + 0 + if padding_bottom is None: + padding_bottom = outline_size + 2 + if padding_left is None: + padding_left = outline_size + 0 + if padding_right is None: + padding_right = outline_size + 0 + + super().__init__( + font, + padding_top=padding_top, + padding_bottom=padding_bottom, + padding_left=padding_left, + padding_right=padding_right, + **kwargs + ) + + _background_color = self._palette[0] + _foreground_color = self._palette[1] + _background_is_transparent = self._palette.is_transparent(0) + self._palette = Palette(3) + self._palette[0] = _background_color + self._palette[1] = _foreground_color + self._palette[2] = outline_color + if _background_is_transparent: + self._palette.make_transparent(0) + + self._outline_size = outline_size + self._stamp_source = Bitmap((outline_size * 2) + 1, (outline_size * 2) + 1, 3) + self._stamp_source.fill(2) + + self._bitmap = None + + self._reset_text( + font=font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) + + def _add_outline(self): + """ + Blit the outline into the labels Bitmap. We will stamp self._stamp_source for each + pixel of the foreground color but skip the foreground color when we blit. + :return: None + """ + if hasattr(self, "_stamp_source"): + for y in range(self.bitmap.height): + for x in range(self.bitmap.width): + if self.bitmap[x, y] == 1: + try: + bitmaptools.blit( + self.bitmap, + self._stamp_source, + x - self._outline_size, + y - self._outline_size, + skip_dest_index=1, + ) + except ValueError as value_error: + raise ValueError( + "Padding must be big enough to fit outline_size " + "all the way around the text. " + "Try using either larger padding sizes, or smaller outline_size." + ) from value_error + + def _place_text( + self, + bitmap: Bitmap, + text: str, + font: FontProtocol, + xposition: int, + yposition: int, + skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index + # when copying glyph bitmaps (this is important for slanted text + # where rectangular glyph boxes overlap) + ) -> Tuple[int, int, int, int]: + """ + Copy the glpyphs that represent the value of the string into the labels Bitmap. + :param bitmap: The bitmap to place text into + :param text: The text to render + :param font: The font to render the text in + :param xposition: x location of the starting point within the bitmap + :param yposition: y location of the starting point within the bitmap + :param skip_index: Color index to skip during rendering instead of covering up + :return Tuple bounding_box: tuple with x, y, width, height values of the bitmap + """ + parent_result = super()._place_text( + bitmap, text, font, xposition, yposition, skip_index=skip_index + ) + + self._add_outline() + + return parent_result + + @property + def outline_color(self): + """Color of the outline to draw around the text.""" + return self._palette[2] + + @outline_color.setter + def outline_color(self, new_outline_color): + self._palette[2] = new_outline_color + + @property + def outline_size(self): + """Stroke size of the outline to draw around the text.""" + return self._outline_size + + @outline_size.setter + def outline_size(self, new_outline_size): + self._outline_size = new_outline_size + + self._padding_top = new_outline_size + 0 + self._padding_bottom = new_outline_size + 2 + self._padding_left = new_outline_size + 0 + self._padding_right = new_outline_size + 0 + + self._stamp_source = Bitmap( + (new_outline_size * 2) + 1, (new_outline_size * 2) + 1, 3 + ) + self._stamp_source.fill(2) + self._reset_text( + font=self._font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) diff --git a/src/lib/adafruit_display_text/scrolling_label.py b/src/lib/adafruit_display_text/scrolling_label.py index f65125d..b4e4e0a 100644 --- a/src/lib/adafruit_display_text/scrolling_label.py +++ b/src/lib/adafruit_display_text/scrolling_label.py @@ -23,10 +23,10 @@ """ -__version__ = "3.0.5" +__version__ = "3.2.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" -import time +import adafruit_ticks from adafruit_display_text import bitmap_label try: @@ -66,7 +66,7 @@ def __init__( self._last_animate_time = -1 self.max_characters = max_characters - if text[-1] != " ": + if text and text[-1] != " ": text = "{} ".format(text) self._full_text = text @@ -81,10 +81,13 @@ def update(self, force: bool = False) -> None: Default is False. :return: None """ - _now = time.monotonic() - if force or self._last_animate_time + self.animate_time <= _now: + _now = adafruit_ticks.ticks_ms() + if force or adafruit_ticks.ticks_less( + self._last_animate_time + int(self.animate_time * 1000), _now + ): if len(self.full_text) <= self.max_characters: - super()._set_text(self.full_text, self.scale) + if self._text != self.full_text: + super()._set_text(self.full_text, self.scale) self._last_animate_time = _now return @@ -120,10 +123,10 @@ def current_index(self) -> int: @current_index.setter def current_index(self, new_index: int) -> None: - if new_index < len(self.full_text): - self._current_index = new_index - else: + if self.full_text: self._current_index = new_index % len(self.full_text) + else: + self._current_index = 0 @property def full_text(self) -> str: @@ -136,11 +139,12 @@ def full_text(self) -> str: @full_text.setter def full_text(self, new_text: str) -> None: - if new_text[-1] != " ": + if new_text and new_text[-1] != " ": new_text = "{} ".format(new_text) - self._full_text = new_text - self.current_index = 0 - self.update() + if new_text != self._full_text: + self._full_text = new_text + self.current_index = 0 + self.update(True) @property def text(self): diff --git a/src/lib/adafruit_display_text/text_box.py b/src/lib/adafruit_display_text/text_box.py new file mode 100644 index 0000000..58f668b --- /dev/null +++ b/src/lib/adafruit_display_text/text_box.py @@ -0,0 +1,435 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_display_text.text_box` +================================================================================ + +Text graphics handling for CircuitPython, including text boxes + + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "3.2.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +import displayio +from micropython import const + +from adafruit_display_text import wrap_text_to_pixels +from adafruit_display_text import bitmap_label + +try: + from typing import Optional, Tuple + from fontio import FontProtocol +except ImportError: + pass + + +# pylint: disable=too-many-instance-attributes, duplicate-code +class TextBox(bitmap_label.Label): + """ + TextBox has a constrained width and optionally height. + You set the desired size when it's initialized it + will automatically wrap text to fit it within the allotted + size. + + Left, Right, and Center alignment of the text within the + box are supported. + + :param font: The font to use for the TextBox. + :param width: The width of the TextBox in pixels. + :param height: The height of the TextBox in pixels. + :param align: How to align the text within the box, + valid values are ``ALIGN_LEFT``, ``ALIGN_CENTER``, ``ALIGN_RIGHT``. + """ + + ALIGN_LEFT = const(0) + ALIGN_CENTER = const(1) + ALIGN_RIGHT = const(2) + + DYNAMIC_HEIGHT = const(-1) + + def __init__( + self, font: FontProtocol, width: int, height: int, align=ALIGN_LEFT, **kwargs + ) -> None: + self._bitmap = None + self._tilegrid = None + self._prev_label_direction = None + self._width = width + + if height != TextBox.DYNAMIC_HEIGHT: + self._height = height + self.dynamic_height = False + else: + self.dynamic_height = True + + if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): + raise ValueError( + "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" + ) + self._align = align + + self._padding_left = kwargs.get("padding_left", 0) + self._padding_right = kwargs.get("padding_right", 0) + + self.lines = wrap_text_to_pixels( + kwargs.get("text", ""), + self._width - self._padding_left - self._padding_right, + font, + ) + + super(bitmap_label.Label, self).__init__(font, **kwargs) + + print(f"before reset: {self._text}") + + self._text = "\n".join(self.lines) + self._text = self._replace_tabs(self._text) + self._original_text = self._text + + # call the text updater with all the arguments. + self._reset_text( + font=font, + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, + ) + print(f"after reset: {self._text}") + + def _place_text( + self, + bitmap: displayio.Bitmap, + text: str, + font: FontProtocol, + xposition: int, + yposition: int, + skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index + # when copying glyph bitmaps (this is important for slanted text + # where rectangular glyph boxes overlap) + ) -> Tuple[int, int, int, int]: + # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches + + # placeText - Writes text into a bitmap at the specified location. + # + # Note: scale is pushed up to Group level + original_xposition = xposition + cur_line_index = 0 + cur_line_width = self._text_bounding_box(self.lines[0], self.font)[0] + + if self.align == self.ALIGN_LEFT: + x_start = original_xposition # starting x position (left margin) + if self.align == self.ALIGN_CENTER: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space // 2 + if self.align == self.ALIGN_RIGHT: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space - self._padding_right + + xposition = x_start # pylint: disable=used-before-assignment + + y_start = yposition + # print(f"start loc {x_start}, {y_start}") + + left = None + right = x_start + top = bottom = y_start + line_spacing = self._line_spacing + + # print(f"cur_line width: {cur_line_width}") + for char in text: + if char == "\n": # newline + cur_line_index += 1 + cur_line_width = self._text_bounding_box( + self.lines[cur_line_index], self.font + )[0] + # print(f"cur_line width: {cur_line_width}") + if self.align == self.ALIGN_LEFT: + x_start = original_xposition # starting x position (left margin) + if self.align == self.ALIGN_CENTER: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space // 2 + if self.align == self.ALIGN_RIGHT: + unused_space = self._width - cur_line_width + x_start = original_xposition + unused_space - self._padding_right + xposition = x_start + + yposition = yposition + self._line_spacing_ypixels( + font, line_spacing + ) # Add a newline + + else: + my_glyph = font.get_glyph(ord(char)) + + if my_glyph is None: # Error checking: no glyph found + print("Glyph not found: {}".format(repr(char))) + else: + if xposition == x_start: + if left is None: + left = 0 + else: + left = min(left, my_glyph.dx) + + right = max( + right, + xposition + my_glyph.shift_x, + xposition + my_glyph.width + my_glyph.dx, + ) + if yposition == y_start: # first line, find the Ascender height + top = min(top, -my_glyph.height - my_glyph.dy) + bottom = max(bottom, yposition - my_glyph.dy) + + glyph_offset_x = ( + my_glyph.tile_index * my_glyph.width + ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap. + # for BDF loaded fonts, this should equal 0 + + y_blit_target = yposition - my_glyph.height - my_glyph.dy + + # Clip glyph y-direction if outside the font ascent/descent metrics. + # Note: bitmap.blit will automatically clip the bottom of the glyph. + y_clip = 0 + if y_blit_target < 0: + y_clip = -y_blit_target # clip this amount from top of bitmap + y_blit_target = 0 # draw the clipped bitmap at y=0 + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format( + char + ) + ) + + if (y_blit_target + my_glyph.height) > bitmap.height: + if self._verbose: + print( + 'Warning: Glyph clipped, exceeds descent property: "{}"'.format( + char + ) + ) + try: + self._blit( + bitmap, + max(xposition + my_glyph.dx, 0), + y_blit_target, + my_glyph.bitmap, + x_1=glyph_offset_x, + y_1=y_clip, + x_2=glyph_offset_x + my_glyph.width, + y_2=my_glyph.height, + skip_index=skip_index, # do not copy over any 0 background pixels + ) + except ValueError: + # ignore index out of bounds error + break + + xposition = xposition + my_glyph.shift_x + + # bounding_box + return left, top, right - left, bottom - top + + def _reset_text( + self, + font: Optional[FontProtocol] = None, + text: Optional[str] = None, + line_spacing: Optional[float] = None, + scale: Optional[int] = None, + ) -> None: + # pylint: disable=too-many-branches, too-many-statements, too-many-locals + + # Store all the instance variables + if font is not None: + self._font = font + if line_spacing is not None: + self._line_spacing = line_spacing + + # if text is not provided as a parameter (text is None), use the previous value. + if text is None: + text = self._text + + self._text = self._replace_tabs(text) + print(f"inside reset_text text: {text}") + + # Check for empty string + if (text == "") or ( + text is None + ): # If empty string, just create a zero-sized bounding box and that's it. + self._bounding_box = ( + 0, + 0, + 0, # zero width with text == "" + 0, # zero height with text == "" + ) + # Clear out any items in the self._local_group Group, in case this is an + # update to the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + + # Free the bitmap and tilegrid since they are removed + self._bitmap = None + self._tilegrid = None + + else: # The text string is not empty, so create the Bitmap and TileGrid and + # append to the self Group + + # Calculate the text bounding box + + # Calculate both "tight" and "loose" bounding box dimensions to match label for + # anchor_position calculations + ( + box_x, + tight_box_y, + x_offset, + tight_y_offset, + loose_box_y, + loose_y_offset, + ) = self._text_bounding_box( + text, + self._font, + ) # calculate the box size for a tight and loose backgrounds + + if self._background_tight: + box_y = tight_box_y + y_offset = tight_y_offset + self._padding_left = 0 + self._padding_right = 0 + self._padding_top = 0 + self._padding_bottom = 0 + + else: # calculate the box size for a loose background + box_y = loose_box_y + y_offset = loose_y_offset + + # Calculate the background size including padding + tight_box_x = box_x + box_x = box_x + self._padding_left + self._padding_right + box_y = box_y + self._padding_top + self._padding_bottom + + if self.dynamic_height: + print(f"dynamic height, box_y: {box_y}") + self._height = box_y + + # Create the Bitmap unless it can be reused + new_bitmap = None + if ( + self._bitmap is None + or self._bitmap.width != self._width + or self._bitmap.height != self._height + ): + new_bitmap = displayio.Bitmap( + self._width, self._height, len(self._palette) + ) + self._bitmap = new_bitmap + else: + self._bitmap.fill(0) + + # Place the text into the Bitmap + self._place_text( + self._bitmap, + text, + self._font, + self._padding_left - x_offset, + self._padding_top + y_offset, + ) + + if self._base_alignment: + label_position_yoffset = 0 + else: + label_position_yoffset = self._ascent // 2 + + # Create the TileGrid if not created bitmap unchanged + if self._tilegrid is None or new_bitmap: + self._tilegrid = displayio.TileGrid( + self._bitmap, + pixel_shader=self._palette, + width=1, + height=1, + tile_width=self._width, + tile_height=self._height, + default_tile=0, + x=-self._padding_left + x_offset, + y=label_position_yoffset - y_offset - self._padding_top, + ) + # Clear out any items in the local_group Group, in case this is an update to + # the bitmap_label + for _ in self._local_group: + self._local_group.pop(0) + self._local_group.append( + self._tilegrid + ) # add the bitmap's tilegrid to the group + + self._bounding_box = ( + self._tilegrid.x + self._padding_left, + self._tilegrid.y + self._padding_top, + tight_box_x, + tight_box_y, + ) + print(f"end of reset_text bounding box: {self._bounding_box}") + + if ( + scale is not None + ): # Scale will be defined in local_group (Note: self should have scale=1) + self.scale = scale # call the setter + + # set the anchored_position with setter after bitmap is created, sets the + # x,y positions of the label + self.anchored_position = self._anchored_position + + @property + def height(self) -> int: + """The height of the label determined from the bounding box.""" + return self._height + + @property + def width(self) -> int: + """The width of the label determined from the bounding box.""" + return self._width + + @width.setter + def width(self, width: int) -> None: + self._width = width + self.text = self._text + + @height.setter + def height(self, height: int) -> None: + if height != TextBox.DYNAMIC_HEIGHT: + self._height = height + self.dynamic_height = False + else: + self.dynamic_height = True + self.text = self._text + + @bitmap_label.Label.text.setter + def text(self, text: str) -> None: + self.lines = wrap_text_to_pixels( + text, self._width - self._padding_left - self._padding_right, self.font + ) + self._text = self._replace_tabs(text) + self._original_text = self._text + self._text = "\n".join(self.lines) + + self._set_text(self._text, self.scale) + + @property + def align(self): + """Alignment of the text within the TextBox""" + return self._align + + @align.setter + def align(self, align: int) -> None: + if align not in (TextBox.ALIGN_LEFT, TextBox.ALIGN_CENTER, TextBox.ALIGN_RIGHT): + raise ValueError( + "Align must be one of: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT" + ) + self._align = align diff --git a/src/lib/adafruit_ds3231.mpy b/src/lib/adafruit_ds3231.mpy index 2767c0f..215d53d 100644 Binary files a/src/lib/adafruit_ds3231.mpy and b/src/lib/adafruit_ds3231.mpy differ diff --git a/src/lib/adafruit_imageload/__init__.py b/src/lib/adafruit_imageload/__init__.py index d1e6d5f..1abb630 100644 --- a/src/lib/adafruit_imageload/__init__.py +++ b/src/lib/adafruit_imageload/__init__.py @@ -12,24 +12,25 @@ * Author(s): Scott Shawcroft, Matt Land """ -# pylint: disable=import-outside-toplevel try: + from io import BufferedReader from typing import ( - Tuple, + Iterable, Iterator, - Optional, List, - Iterable, + Optional, + Tuple, Union, ) - from io import BufferedReader - from displayio import Palette, Bitmap - from .displayio_types import PaletteConstructor, BitmapConstructor + + from displayio import Bitmap, ColorConverter, Palette + + from .displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" @@ -37,8 +38,8 @@ def load( file_or_filename: Union[str, BufferedReader], *, bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None -) -> Tuple[Bitmap, Optional[Palette]]: + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Bitmap, Optional[Union[Palette, ColorConverter]]]: """Load pixel values (indices or colors) into a bitmap and colors into a palette. bitmap is the desired type. It must take width, height and color_depth in the constructor. It @@ -89,4 +90,8 @@ def load( from . import png return png.load(file, bitmap=bitmap, palette=palette) + if header.startswith(b"\xff\xd8"): + from . import jpg + + return jpg.load(file, bitmap=bitmap) raise RuntimeError("Unsupported image format") diff --git a/src/lib/adafruit_imageload/bmp/__init__.py b/src/lib/adafruit_imageload/bmp/__init__.py index 096c5ed..00487b2 100644 --- a/src/lib/adafruit_imageload/bmp/__init__.py +++ b/src/lib/adafruit_imageload/bmp/__init__.py @@ -12,17 +12,18 @@ * Author(s): Scott Shawcroft, Matt Land """ -# pylint: disable=import-outside-toplevel try: - from typing import Tuple, Optional, Set, List from io import BufferedReader - from displayio import Palette, Bitmap - from ..displayio_types import PaletteConstructor, BitmapConstructor + from typing import List, Optional, Set, Tuple, Union + + from displayio import Bitmap, ColorConverter, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" @@ -30,11 +31,12 @@ def load( file: BufferedReader, *, bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None -) -> Tuple[Optional[Bitmap], Optional[Palette]]: + palette: Optional[PaletteConstructor] = None, +) -> Tuple[Optional[Bitmap], Optional[Union[Palette, ColorConverter]]]: """Loads a bmp image from the open ``file``. - Returns tuple of bitmap object and palette object. + Returns tuple of `displayio.Bitmap` object and + `displayio.Palette` object, or `displayio.ColorConverter` object. :param io.BufferedReader file: Open file handle or compatible (like `io.BytesIO`) with the data of a BMP file. diff --git a/src/lib/adafruit_imageload/bmp/indexed.py b/src/lib/adafruit_imageload/bmp/indexed.py index 7789924..044cd51 100644 --- a/src/lib/adafruit_imageload/bmp/indexed.py +++ b/src/lib/adafruit_imageload/bmp/indexed.py @@ -13,28 +13,29 @@ """ - import sys try: - from typing import Tuple, Optional from io import BufferedReader - from displayio import Palette, Bitmap - from ..displayio_types import PaletteConstructor, BitmapConstructor + from typing import Optional, Tuple + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass try: from bitmaptools import readinto as _bitmap_readinto except ImportError: - _bitmap_readinto = None # pylint: disable=invalid-name # type: Callable + _bitmap_readinto = None -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load( +def load( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches file: BufferedReader, width: int, height: int, @@ -58,7 +59,6 @@ def load( :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap :param PaletteConstructor palette: a function that returns a displayio.Palette """ - # pylint: disable=too-many-arguments,too-many-locals,too-many-branches palette_obj = None if palette: palette_obj = palette(colors) @@ -78,7 +78,6 @@ def load( minimum_color_depth *= 2 if sys.maxsize > 1073741823: - # pylint: disable=import-outside-toplevel, relative-beyond-top-level from .negative_height_check import negative_height_check # convert unsigned int to signed int when height is negative @@ -121,9 +120,7 @@ def load( for x in range(width): i = x // pixels_per_byte - pixel = ( - chunk[i] >> (8 - color_depth * (x % pixels_per_byte + 1)) - ) & mask + pixel = (chunk[i] >> (8 - color_depth * (x % pixels_per_byte + 1))) & mask bitmap_obj[offset + x] = pixel elif compression in (1, 2): decode_rle( @@ -137,7 +134,7 @@ def load( return bitmap_obj, palette_obj -def decode_rle( +def decode_rle( # noqa: PLR0912 Too many branches bitmap: Bitmap, file: BufferedReader, compression: int, @@ -145,7 +142,6 @@ def decode_rle( width: int, ) -> None: """Helper to decode RLE images""" - # pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches # RLE algorithm, either 8-bit (1) or 4-bit (2) # diff --git a/src/lib/adafruit_imageload/bmp/truecolor.py b/src/lib/adafruit_imageload/bmp/truecolor.py index 0d4af56..8f17b79 100644 --- a/src/lib/adafruit_imageload/bmp/truecolor.py +++ b/src/lib/adafruit_imageload/bmp/truecolor.py @@ -16,15 +16,16 @@ import sys try: - from typing import Union, Optional, Tuple from io import BufferedReader + from typing import Optional, Tuple, Union + from ..displayio_types import BitmapConstructor except ImportError: pass -from displayio import ColorConverter, Colorspace, Bitmap +from displayio import Bitmap, ColorConverter, Colorspace -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" bitfield_colorspaces = ( @@ -52,7 +53,7 @@ def bitfield_format(bitfield_mask): return None -def load( +def load( # noqa: PLR0912, PLR0913, Too many branches, Too many arguments in function definition file: BufferedReader, width: int, height: int, @@ -73,7 +74,6 @@ def load( :param dict bitfield_masks: The bitfield masks for each color if using bitfield compression :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap """ - # pylint: disable=too-many-arguments,too-many-locals,too-many-branches converter_obj = None bitmap_obj = None if bitmap: @@ -90,12 +90,11 @@ def load( input_colorspace = Colorspace.RGB555 converter_obj = ColorConverter(input_colorspace=input_colorspace) if sys.maxsize > 1073741823: - # pylint: disable=import-outside-toplevel, relative-beyond-top-level from .negative_height_check import negative_height_check # convert unsigned int to signed int when height is negative height = negative_height_check(height) - bitmap_obj = Bitmap(width, abs(height), 65535) + bitmap_obj = bitmap(width, abs(height), 65535) file.seek(data_start) line_size = width * (color_depth // 8) # Set the seek direction based on whether the height value is negative or positive @@ -119,19 +118,14 @@ def load( color = 0 for byte in range(bytes_per_pixel): color |= chunk[i + byte] << (8 * byte) - mask = ( - bitfield_masks["red"] - | bitfield_masks["green"] - | bitfield_masks["blue"] - ) + mask = bitfield_masks["red"] | bitfield_masks["green"] | bitfield_masks["blue"] if color_depth in (24, 32): mask = mask >> 8 pixel = color & mask + elif color_depth == 16: + pixel = chunk[i] | chunk[i + 1] << 8 else: - if color_depth == 16: - pixel = chunk[i] | chunk[i + 1] << 8 - else: - pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] + pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] bitmap_obj[offset + x] = converter_obj.convert(pixel) return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565) diff --git a/src/lib/adafruit_imageload/displayio_types.py b/src/lib/adafruit_imageload/displayio_types.py index 5a15243..bed5efe 100644 --- a/src/lib/adafruit_imageload/displayio_types.py +++ b/src/lib/adafruit_imageload/displayio_types.py @@ -13,14 +13,16 @@ * Author(s): Matt Land """ + try: from typing import Callable - from displayio import Palette, Bitmap + + from displayio import Bitmap, Palette PaletteConstructor = Callable[[int], Palette] BitmapConstructor = Callable[[int, int, int], Bitmap] except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/src/lib/adafruit_imageload/gif.py b/src/lib/adafruit_imageload/gif.py index f9a07c2..c3099fe 100644 --- a/src/lib/adafruit_imageload/gif.py +++ b/src/lib/adafruit_imageload/gif.py @@ -17,22 +17,21 @@ import struct try: - from typing import Tuple, Iterator, Optional, List from io import BufferedReader - from displayio import Palette, Bitmap - from .displayio_types import PaletteConstructor, BitmapConstructor + from typing import Iterator, List, Optional, Tuple + + from displayio import Bitmap, Palette + + from .displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" def load( - file: BufferedReader, - *, - bitmap: BitmapConstructor, - palette: Optional[PaletteConstructor] = None + file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None ) -> Tuple[Bitmap, Optional[Palette]]: """Loads a GIF image from the open ``file``. @@ -47,9 +46,7 @@ def load( header = file.read(6) if header not in {b"GIF87a", b"GIF89a"}: raise ValueError("Not a GIF file") - width, height, flags, _, _ = struct.unpack( # pylint: disable=no-member - "<HHBBB", file.read(7) - ) + width, height, flags, _, _ = struct.unpack("<HHBBB", file.read(7)) if (flags & 0x80) != 0: if not palette: raise RuntimeError("palette argument required") @@ -78,9 +75,7 @@ def load( def _read_frame(file: BufferedReader, bitmap: Bitmap) -> None: """Read a single frame and apply it to the bitmap.""" - ddx, ddy, width, _, flags = struct.unpack( # pylint: disable=no-member - "<HHHHB", file.read(9) - ) + ddx, ddy, width, _, flags = struct.unpack("<HHHHB", file.read(9)) if (flags & 0x40) != 0: raise NotImplementedError("Interlacing not supported") if (flags & 0x80) != 0: @@ -145,10 +140,7 @@ def decode(self, code: int) -> bytes: value = self.last + self.last[0:1] if self.last: self.codes.append(self.last + value[0:1]) - if ( - len(self.codes) + self.end_code + 1 >= 1 << self.code_len - and self.code_len < 12 - ): + if len(self.codes) + self.end_code + 1 >= 1 << self.code_len and self.code_len < 12: self.code_len += 1 self.last = value return value @@ -159,7 +151,7 @@ def lzw_decode(data: Iterator[int], code_size: int) -> Iterator[bytes]: dictionary = LZWDict(code_size) bit = 0 try: - byte = next(data) # pylint: disable=stop-iteration-return + byte = next(data) try: while True: code = 0 @@ -168,10 +160,10 @@ def lzw_decode(data: Iterator[int], code_size: int) -> Iterator[bytes]: bit += 1 if bit >= 8: bit = 0 - byte = next(data) # pylint: disable=stop-iteration-return + byte = next(data) yield dictionary.decode(code) except EndOfData: while True: - next(data) # pylint: disable=stop-iteration-return + next(data) except StopIteration: pass diff --git a/src/lib/adafruit_imageload/jpg.py b/src/lib/adafruit_imageload/jpg.py new file mode 100644 index 0000000..0f5d07d --- /dev/null +++ b/src/lib/adafruit_imageload/jpg.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2024 Channing Ramos +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_imageload.jpg` +==================================================== + +Load a JPG into a bitmap by calling the jpegio class. + +* Author(s): Channing Ramos + +""" + +# A separate try for jpegio. Not every board supports it and this import may fail. +# If that happens an ImportError with a proper message needs to be raised +try: + from jpegio import JpegDecoder +except ImportError: + print("jpegio not supported on this board.") + +try: + from io import BufferedReader + from typing import Optional, Tuple + + from .displayio_types import BitmapConstructor +except ImportError: + pass + +from displayio import Bitmap, ColorConverter, Colorspace + +__version__ = "1.23.5" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" + + +def load( + file: BufferedReader, + *, + bitmap: BitmapConstructor, +) -> Tuple[Bitmap, Optional[ColorConverter]]: + """ + Loads a JPG image from the open ''file''. + The JPG must be a Baseline JPG, Progressive and Lossless JPG formats are not supported. + + Returns tuple of bitmap object and ColorConverter object. + + :param io.BufferedReader file: Open file handle or compatible (like 'io.BytesIO') + :param object bitmap: Type to store bitmap data. + Must have API similar to 'displayio.Bitmap'. Will be skipped if None. + Will be skipped if None. + """ + decoder = JpegDecoder() + width, height = decoder.open(file) + bitmap_obj = bitmap(width, height, 65535) + decoder.decode(bitmap_obj) + + return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED) diff --git a/src/lib/adafruit_imageload/png.py b/src/lib/adafruit_imageload/png.py index 587bf9d..55f6226 100644 --- a/src/lib/adafruit_imageload/png.py +++ b/src/lib/adafruit_imageload/png.py @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2022 Radomir Dopieralski # SPDX-FileCopyrightText: 2023 Matt Land +# SPDX-FileCopyrightText: 2024 Channing Ramos # # SPDX-License-Identifier: MIT @@ -10,30 +11,29 @@ Load pixel values (indices or colors) into a bitmap and colors into a palette from a PNG file. -* Author(s): Radomir Dopieralski, Matt Land +* Author(s): Radomir Dopieralski, Matt Land, Channing Ramos """ try: from io import BufferedReader from typing import Optional, Tuple - from displayio import Palette, Bitmap - from .displayio_types import PaletteConstructor, BitmapConstructor + + from displayio import Bitmap, Palette + + from .displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass import struct import zlib -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load( - file: BufferedReader, - *, - bitmap: BitmapConstructor, - palette: Optional[PaletteConstructor] = None +def load( # noqa: PLR0912, PLR0915, Too many branches, Too many statements + file: BufferedReader, *, bitmap: BitmapConstructor, palette: Optional[PaletteConstructor] = None ) -> Tuple[Bitmap, Optional[Palette]]: """ Loads a PNG image from the open ``file``. @@ -48,7 +48,6 @@ def load( :param object palette: Type to store the palette. Must have API similar to `displayio.Palette`. Will be skipped if None. """ - # pylint: disable=too-many-locals,too-many-branches header = file.read(8) if header != b"\x89PNG\r\n\x1a\n": raise ValueError("Not a PNG file") @@ -86,6 +85,14 @@ def load( pal = palette(pal_size) for i in range(pal_size): pal[i] = file.read(3) + elif chunk == b"tRNS": + if size > len(pal): + raise ValueError("More transparency entries than palette entries") + trns_data = file.read(size) + for i in range(len(trns_data)): + if trns_data[i] == 0: + pal.make_transparent(i) + del trns_data elif chunk == b"IDAT": data.extend(file.read(size)) elif chunk == b"IEND": @@ -94,15 +101,84 @@ def load( file.seek(size, 1) # skip unknown chunks file.seek(4, 1) # skip CRC data_bytes = zlib.decompress(data) - bmp = bitmap(width, height, 1 << depth) - scanline = (width * depth + 7) // 8 - mem = memoryview(bmp) + unit = (1, 0, 3, 1, 2, 0, 4)[mode] + scanline = (width * depth * unit + 7) // 8 + if mode == 3: # indexed + bmp = bitmap(width, height, 1 << depth) + pixels_per_byte = 8 // depth + src = 1 + src_b = 1 + pixmask = (1 << depth) - 1 + for y in range(height): + for x in range(0, width, pixels_per_byte): + byte = data_bytes[src_b] + for pixel in range(pixels_per_byte): + bmp[x + pixel, y] = (byte >> ((pixels_per_byte - pixel - 1) * depth)) & pixmask + src_b += 1 + src += scanline + 1 + src_b = src + return bmp, pal + # RGB, RGBA or Grayscale + import displayio + + if depth != 8: + raise ValueError("Must be 8bit depth.") + pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB888) + bmp = bitmap(width, height, 65536) + prev = bytearray(scanline) + line = bytearray(scanline) for y in range(height): - dst = y * scanline - src = y * (scanline + 1) + 1 - filter_ = data_bytes[src - 1] + src = y * (scanline + 1) + filter_ = data_bytes[src] + src += 1 if filter_ == 0: - mem[dst : dst + scanline] = data_bytes[src : src + scanline] + line[0:scanline] = data_bytes[src : src + scanline] + elif filter_ == 1: # sub + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + line[i] = (data_bytes[src] + a) & 0xFF + src += 1 + elif filter_ == 2: # up + for i in range(scanline): + b = prev[i] + line[i] = (data_bytes[src] + b) & 0xFF + src += 1 + elif filter_ == 3: # average + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + b = prev[i] + line[i] = (data_bytes[src] + ((a + b) >> 1)) & 0xFF + src += 1 + elif filter_ == 4: # paeth + for i in range(scanline): + a = line[i - unit] if i >= unit else 0 + b = prev[i] + c = prev[i - unit] if i >= unit else 0 + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + p = a + elif pb <= pc: + p = b + else: + p = c + line[i] = (data_bytes[src] + p) & 0xFF + src += 1 + else: + raise ValueError("Wrong filter.") + prev, line = line, prev + if mode in (0, 4): # grayscale + for x in range(width): + c = line[x * unit] + bmp[x, y] = pal.convert((c << 16) | (c << 8) | c) + elif mode in {2, 6}: # rgb + for x in range(width): + bmp[x, y] = pal.convert( + (line[x * unit + 0] << 16) | (line[x * unit + 1] << 8) | line[x * unit + 2] + ) else: - raise NotImplementedError("Filters not supported") + raise ValueError("Unsupported color mode.") + pal = displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565) return bmp, pal diff --git a/src/lib/adafruit_imageload/pnm/__init__.py b/src/lib/adafruit_imageload/pnm/__init__.py index d78e9aa..971da14 100644 --- a/src/lib/adafruit_imageload/pnm/__init__.py +++ b/src/lib/adafruit_imageload/pnm/__init__.py @@ -14,34 +14,35 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ -# pylint: disable=import-outside-toplevel try: + from io import BufferedReader from typing import ( - Tuple, + Callable, + Iterable, Iterator, - Optional, List, - Iterable, + Optional, + Tuple, Union, - Callable, ) - from io import BufferedReader - from displayio import Palette, Bitmap - from ..displayio_types import PaletteConstructor, BitmapConstructor + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load( +def load( # noqa: PLR0912 Too many branches file: BufferedReader, header: bytes, *, bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None + palette: Optional[PaletteConstructor] = None, ) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ Scan for netpbm format info, skip over comments, and delegate to a submodule @@ -50,7 +51,6 @@ def load( All other formats have three: width, height, and max color value. This load function will move the file stream pointer to the start of data in all cases. """ - # pylint: disable=too-many-branches magic_number = header[:2] file.seek(2) pnm_header = [] # type: List[int] @@ -100,7 +100,7 @@ def load( palette_obj = None if palette: palette_obj = palette(1) - palette_obj[0] = b"\xFF\xFF\xFF" + palette_obj[0] = b"\xff\xff\xff" if magic_number.startswith(b"P1"): from . import pbm_ascii @@ -124,7 +124,8 @@ def load( next_byte = file.read(1) if next_byte == b"": - raise RuntimeError("Unsupported image format {!r}".format(magic_number)) + # mpy-cross does not support !r in f-string substitution, so ignore ruff rule + raise RuntimeError("Unsupported image format {!r}".format(magic_number)) # noqa: UP032, f-string if next_byte == b"#": # comment found, seek until a newline or EOF is found while file.read(1) not in [b"", b"\n"]: # EOF or NL pass diff --git a/src/lib/adafruit_imageload/pnm/pbm_ascii.py b/src/lib/adafruit_imageload/pnm/pbm_ascii.py index e777f1d..72eefd4 100644 --- a/src/lib/adafruit_imageload/pnm/pbm_ascii.py +++ b/src/lib/adafruit_imageload/pnm/pbm_ascii.py @@ -17,13 +17,14 @@ """ try: - from typing import Tuple, Optional from io import BufferedReader - from displayio import Palette, Bitmap + from typing import Optional, Tuple + + from displayio import Bitmap, Palette except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/src/lib/adafruit_imageload/pnm/pbm_binary.py b/src/lib/adafruit_imageload/pnm/pbm_binary.py index 18beec4..1d52a8e 100644 --- a/src/lib/adafruit_imageload/pnm/pbm_binary.py +++ b/src/lib/adafruit_imageload/pnm/pbm_binary.py @@ -15,14 +15,16 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ + try: - from typing import Tuple, Optional, Iterator from io import BufferedReader - from displayio import Palette, Bitmap + from typing import Iterator, Optional, Tuple + + from displayio import Bitmap, Palette except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" diff --git a/src/lib/adafruit_imageload/pnm/pgm/__init__.py b/src/lib/adafruit_imageload/pnm/pgm/__init__.py index 3ed43f7..8acefb5 100644 --- a/src/lib/adafruit_imageload/pnm/pgm/__init__.py +++ b/src/lib/adafruit_imageload/pnm/pgm/__init__.py @@ -14,12 +14,14 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ -# pylint: disable=import-outside-toplevel + try: - from typing import Tuple, Optional, Set, List from io import BufferedReader - from displayio import Palette, Bitmap - from ...displayio_types import PaletteConstructor, BitmapConstructor + from typing import List, Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass @@ -30,7 +32,7 @@ def load( header: List[int], *, bitmap: Optional[BitmapConstructor] = None, - palette: Optional[PaletteConstructor] = None + palette: Optional[PaletteConstructor] = None, ) -> Tuple[Optional[Bitmap], Optional[Palette]]: """ Perform the load of Netpbm greyscale images (P2, P5) diff --git a/src/lib/adafruit_imageload/pnm/pgm/ascii.py b/src/lib/adafruit_imageload/pnm/pgm/ascii.py index 097665f..7c597ca 100644 --- a/src/lib/adafruit_imageload/pnm/pgm/ascii.py +++ b/src/lib/adafruit_imageload/pnm/pgm/ascii.py @@ -14,11 +14,14 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ + try: - from typing import Tuple, Set, Optional from io import BufferedReader - from displayio import Palette, Bitmap - from ...displayio_types import PaletteConstructor, BitmapConstructor + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass @@ -66,9 +69,7 @@ def load( return bitmap_obj, palette_obj -def build_palette( - palette_class: PaletteConstructor, palette_colors: Set[int] -) -> Palette: # pylint: disable=duplicate-code +def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: """ construct the Palette, and populate it with the set of palette_colors """ diff --git a/src/lib/adafruit_imageload/pnm/pgm/binary.py b/src/lib/adafruit_imageload/pnm/pgm/binary.py index 7e8c56b..700b563 100644 --- a/src/lib/adafruit_imageload/pnm/pgm/binary.py +++ b/src/lib/adafruit_imageload/pnm/pgm/binary.py @@ -14,11 +14,14 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ + try: - from typing import Tuple, Optional, Set from io import BufferedReader - from displayio import Palette, Bitmap - from ...displayio_types import PaletteConstructor, BitmapConstructor + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ...displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass @@ -54,9 +57,7 @@ def load( return bitmap_obj, palette_obj -def build_palette( - palette_class: PaletteConstructor, palette_colors: Set[int] -) -> Palette: +def build_palette(palette_class: PaletteConstructor, palette_colors: Set[int]) -> Palette: """ construct the Palette, and populate it with the set of palette_colors """ diff --git a/src/lib/adafruit_imageload/pnm/ppm_ascii.py b/src/lib/adafruit_imageload/pnm/ppm_ascii.py index 2ba7e9f..ac8ea8a 100644 --- a/src/lib/adafruit_imageload/pnm/ppm_ascii.py +++ b/src/lib/adafruit_imageload/pnm/ppm_ascii.py @@ -16,20 +16,22 @@ """ -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" try: + from io import BufferedReader from typing import ( - Tuple, Iterator, - Optional, List, + Optional, Set, + Tuple, ) - from io import BufferedReader - from displayio import Palette, Bitmap - from ..displayio_types import PaletteConstructor, BitmapConstructor + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass diff --git a/src/lib/adafruit_imageload/pnm/ppm_binary.py b/src/lib/adafruit_imageload/pnm/ppm_binary.py index 44af03b..cada568 100644 --- a/src/lib/adafruit_imageload/pnm/ppm_binary.py +++ b/src/lib/adafruit_imageload/pnm/ppm_binary.py @@ -15,15 +15,18 @@ * Author(s): Matt Land, Brooke Storm, Sam McGahan """ + try: - from typing import Tuple, Optional, Set from io import BufferedReader - from displayio import Palette, Bitmap - from ..displayio_types import PaletteConstructor, BitmapConstructor + from typing import Optional, Set, Tuple + + from displayio import Bitmap, Palette + + from ..displayio_types import BitmapConstructor, PaletteConstructor except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" @@ -38,7 +41,6 @@ def load( Load pixel values (indices or colors) into a bitmap and for a binary ppm, return None for pallet. """ - # pylint: disable=too-many-locals data_start = file.tell() palette_colors = set() # type: Set[Tuple[int, int, int]] diff --git a/src/lib/adafruit_imageload/tilegrid_inflator.py b/src/lib/adafruit_imageload/tilegrid_inflator.py index ee6ce21..8df1a22 100644 --- a/src/lib/adafruit_imageload/tilegrid_inflator.py +++ b/src/lib/adafruit_imageload/tilegrid_inflator.py @@ -13,20 +13,23 @@ * Author(s): Tim Cocks, Matt Land """ + import displayio + import adafruit_imageload try: - from typing import Tuple, Optional, List, Union - from displayio import Palette, Bitmap, OnDiskBitmap, TileGrid + from typing import List, Optional, Tuple, Union + + from displayio import Bitmap, OnDiskBitmap, Palette, TileGrid except ImportError: pass -__version__ = "1.20.1" +__version__ = "1.23.5" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def inflate_tilegrid( +def inflate_tilegrid( # noqa: PLR0913, PLR0912, Too many arguments in function definition, Too many branches bmp_path: Optional[str] = None, target_size: Tuple[int, int] = (3, 3), tile_size: Optional[List[int]] = None, @@ -48,8 +51,6 @@ def inflate_tilegrid( :param Optional[Palette] bmp_palette: Already loaded spritesheet Palette """ - # pylint: disable=too-many-arguments, too-many-locals, too-many-branches - if bmp_path is None and (bmp_obj is None and bmp_palette is None): raise AttributeError("Must pass either bmp_path or bmp_obj and bmp_palette") diff --git a/src/lib/adafruit_irremote.mpy b/src/lib/adafruit_irremote.mpy index 8bfcc8f..4f1634a 100644 Binary files a/src/lib/adafruit_irremote.mpy and b/src/lib/adafruit_irremote.mpy differ diff --git a/src/lib/adafruit_ntp.mpy b/src/lib/adafruit_ntp.mpy index d7a9176..95130dc 100644 Binary files a/src/lib/adafruit_ntp.mpy and b/src/lib/adafruit_ntp.mpy differ diff --git a/src/lib/adafruit_register/i2c_bcd_alarm.py b/src/lib/adafruit_register/i2c_bcd_alarm.py index 292aa23..178d8e1 100644 --- a/src/lib/adafruit_register/i2c_bcd_alarm.py +++ b/src/lib/adafruit_register/i2c_bcd_alarm.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT # pylint: disable=too-few-public-methods +# pylint: disable=too-many-branches """ `adafruit_register.i2c_bcd_alarm` @@ -12,7 +13,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" import time @@ -23,7 +24,7 @@ from circuitpython_typing.device_drivers import I2CDeviceDriver FREQUENCY_T = Literal[ - "monthly", "weekly", "daily", "hourly", "secondly", "minutely" + "monthly", "weekly", "daily", "hourly", "minutely", "secondly" ] except ImportError: pass @@ -115,6 +116,9 @@ def __get__( frequency = "minutely" seconds = _bcd2bin(self.buffer[1] & 0x7F) i = 2 + else: + frequency = "minutely" + seconds = _bcd2bin(self.buffer[i] & 0x7F) minute = 0 if (self.buffer[i] & 0x80) == 0: frequency = "hourly" @@ -169,7 +173,7 @@ def __set__( raise ValueError(error_message) frequency = FREQUENCY.index(frequency_name) - if frequency <= 1 and not self.has_seconds: + if frequency < 1 and not self.has_seconds: raise ValueError(error_message) # i is the index of the minute byte diff --git a/src/lib/adafruit_register/i2c_bcd_datetime.py b/src/lib/adafruit_register/i2c_bcd_datetime.py index d9ab4cf..0a23ffe 100644 --- a/src/lib/adafruit_register/i2c_bcd_datetime.py +++ b/src/lib/adafruit_register/i2c_bcd_datetime.py @@ -12,7 +12,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" import time diff --git a/src/lib/adafruit_register/i2c_bit.py b/src/lib/adafruit_register/i2c_bit.py index b9c926d..e518957 100644 --- a/src/lib/adafruit_register/i2c_bit.py +++ b/src/lib/adafruit_register/i2c_bit.py @@ -12,7 +12,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" try: diff --git a/src/lib/adafruit_register/i2c_bits.py b/src/lib/adafruit_register/i2c_bits.py index 7ba9eae..08bf5be 100644 --- a/src/lib/adafruit_register/i2c_bits.py +++ b/src/lib/adafruit_register/i2c_bits.py @@ -12,7 +12,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" try: diff --git a/src/lib/adafruit_register/i2c_struct.py b/src/lib/adafruit_register/i2c_struct.py index ea4e42c..a097e3c 100644 --- a/src/lib/adafruit_register/i2c_struct.py +++ b/src/lib/adafruit_register/i2c_struct.py @@ -12,7 +12,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" import struct diff --git a/src/lib/adafruit_register/i2c_struct_array.py b/src/lib/adafruit_register/i2c_struct_array.py index 8822159..65470c6 100644 --- a/src/lib/adafruit_register/i2c_struct_array.py +++ b/src/lib/adafruit_register/i2c_struct_array.py @@ -12,7 +12,7 @@ * Author(s): Scott Shawcroft """ -__version__ = "1.9.17" +__version__ = "1.10.1" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" import struct diff --git a/src/lib/adafruit_requests.mpy b/src/lib/adafruit_requests.mpy index 0af3a76..c2a1cc2 100644 Binary files a/src/lib/adafruit_requests.mpy and b/src/lib/adafruit_requests.mpy differ diff --git a/src/lib/adafruit_ticks.mpy b/src/lib/adafruit_ticks.mpy new file mode 100644 index 0000000..6167b2f Binary files /dev/null and b/src/lib/adafruit_ticks.mpy differ diff --git a/src/network.py b/src/network.py index 1fbfbc8..01c92fe 100644 --- a/src/network.py +++ b/src/network.py @@ -1,11 +1,11 @@ ## Class for handling networking. import os import gc -import ssl +import time import wifi -import socketpool import adafruit_ntp import adafruit_requests +import adafruit_connection_manager class WifiNetwork: def __init__(self) -> None: @@ -28,6 +28,10 @@ def __init__(self) -> None: self._last_ntp_sync = None self.connect() + self._pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) + self._requests = adafruit_requests.Session(self._pool, adafruit_connection_manager.get_radio_socketpool(wifi.radio)) + self._connection_manager = adafruit_connection_manager.get_connection_manager(self._pool) + def connect(self) -> bool: """ If not connected connect to the network.""" @@ -41,18 +45,18 @@ def connect(self) -> bool: return True except Exception as e: print(e) - attempt += 1 + attempt += 1 + time.sleep(4) raise Exception('Unable to connect') def get_time(self): - pool = socketpool.SocketPool(wifi.radio) ntp_try = 0 while ntp_try < len(self.NTP_HOST): try: - ntp = adafruit_ntp.NTP(pool, tz_offset=self.TZ, server=self.NTP_HOST[ntp_try]) + ntp = adafruit_ntp.NTP(self._pool, tz_offset=self.TZ, server=self.NTP_HOST[ntp_try]) self._last_ntp_sync = ntp.datetime return ntp.datetime except Exception as ex: @@ -63,20 +67,15 @@ def get_time(self): def getJson(self, url): try: - pool = socketpool.SocketPool(wifi.radio) - context = ssl.create_default_context() - #requests = adafruit_requests.Session(pool, context) - requests = adafruit_requests.Session(pool, ssl.create_default_context()) print(f'getting url: {url}') gc.collect() print('free memory', gc.mem_free()) - - #response = requests.get(url, stream=True) - response = requests.get(url) - print('free memory after', gc.mem_free()) - return response.json() + with self._requests.get(url) as response: + print(f'free memory after: {gc.mem_free()} socket count: {self._connection_manager.managed_socket_count}: available: {self._connection_manager.available_socket_count}') + return response.json() except Exception as e: print('response.json Exception:', e) + print(f'free memory: {gc.mem_free()} socket count: {self._connection_manager.managed_socket_count}: available: {self._connection_manager.available_socket_count}') gc.collect() return {}