From eded79487f42e7094a2bbc2f47bd33fd710bde66 Mon Sep 17 00:00:00 2001 From: Jason Jackson <jake1164@hotmail.com> Date: Sat, 30 Nov 2024 09:31:57 -0500 Subject: [PATCH] Updated to work with Circuitpython 9.2.x --- src/code.py | 2 +- src/lib/adafruit_bitmap_font/bdf.py | 2 +- src/lib/adafruit_bitmap_font/bitmap_font.py | 2 +- src/lib/adafruit_bitmap_font/glyph_cache.py | 2 +- src/lib/adafruit_bus_device/i2c_device.py | 2 +- src/lib/adafruit_bus_device/spi_device.py | 4 +- src/lib/adafruit_connection_manager.mpy | Bin 0 -> 3541 bytes src/lib/adafruit_display_text/__init__.py | 10 +- src/lib/adafruit_display_text/bitmap_label.py | 19 +- src/lib/adafruit_display_text/label.py | 2 +- .../adafruit_display_text/outlined_label.py | 188 ++++++++ .../adafruit_display_text/scrolling_label.py | 30 +- src/lib/adafruit_display_text/text_box.py | 435 ++++++++++++++++++ src/lib/adafruit_ds3231.mpy | Bin 1229 -> 1225 bytes src/lib/adafruit_imageload/__init__.py | 25 +- src/lib/adafruit_imageload/bmp/__init__.py | 18 +- src/lib/adafruit_imageload/bmp/indexed.py | 24 +- src/lib/adafruit_imageload/bmp/truecolor.py | 26 +- src/lib/adafruit_imageload/displayio_types.py | 6 +- src/lib/adafruit_imageload/gif.py | 34 +- src/lib/adafruit_imageload/jpg.py | 57 +++ src/lib/adafruit_imageload/png.py | 112 ++++- src/lib/adafruit_imageload/pnm/__init__.py | 29 +- src/lib/adafruit_imageload/pnm/pbm_ascii.py | 7 +- src/lib/adafruit_imageload/pnm/pbm_binary.py | 8 +- .../adafruit_imageload/pnm/pgm/__init__.py | 12 +- src/lib/adafruit_imageload/pnm/pgm/ascii.py | 13 +- src/lib/adafruit_imageload/pnm/pgm/binary.py | 13 +- src/lib/adafruit_imageload/pnm/ppm_ascii.py | 14 +- src/lib/adafruit_imageload/pnm/ppm_binary.py | 12 +- .../adafruit_imageload/tilegrid_inflator.py | 13 +- src/lib/adafruit_irremote.mpy | Bin 3069 -> 2977 bytes src/lib/adafruit_ntp.mpy | Bin 855 -> 1245 bytes src/lib/adafruit_register/i2c_bcd_alarm.py | 10 +- src/lib/adafruit_register/i2c_bcd_datetime.py | 2 +- src/lib/adafruit_register/i2c_bit.py | 2 +- src/lib/adafruit_register/i2c_bits.py | 2 +- src/lib/adafruit_register/i2c_struct.py | 2 +- src/lib/adafruit_register/i2c_struct_array.py | 2 +- src/lib/adafruit_requests.mpy | Bin 8578 -> 6944 bytes src/lib/adafruit_ticks.mpy | Bin 0 -> 694 bytes src/network.py | 27 +- 42 files changed, 969 insertions(+), 199 deletions(-) create mode 100644 src/lib/adafruit_connection_manager.mpy create mode 100644 src/lib/adafruit_display_text/outlined_label.py create mode 100644 src/lib/adafruit_display_text/text_box.py create mode 100644 src/lib/adafruit_imageload/jpg.py create mode 100644 src/lib/adafruit_ticks.mpy 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 0000000000000000000000000000000000000000..002d5cd8538342456ab2f6a200fa11ca3a85fb73 GIT binary patch literal 3541 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%0DWa1r0|7+=sc*&}i) zfU@~pN9?^m%Utn+(T+>n0o3pL;)71@I|9Ppje7>Bi>?P}ZU?<C=ZMP%M78sP5~Fy* z(WN$0LxTg5KA4Fo5YR||n1R6Xk8}*4<bpaXFc!f<Y!R^f?cO)RsF6#0<d?Vq^wpjm zjK_-~30rrd1`j-PkHcAXIb9>6!LoDE8FjnDgJrj)JnSsHLkILUUOs>l*Bko>p7!UD zfWwb;0iKW2-#^xf%xOd(1Chrp@V1cd{dyzN2K4u_#4#OOe)^D}Jqkxbp=ZFT)_J+! zkl{@@1MPD9hZ#yCV_(-BhYybe9p5YD%%jHvBY<gT#S5Oc7c>h4K1bhF$V*V&H%I4T z_<y(lZ`t6vc$0fjkj;+i=<vvJbk-4aJ4Rv(J*|uyo`j-!h8znZRZolGz<PW?5iovx zpv_pp(Fp_($V=Ge<-iWg*2AGtKfXfxKy=?7(#!ZN={tmQn4Q)l15VWvQa%OQRr`A< aZa(<pwrfPeGtkEvPQs|+86^Y1H2Xj2199X4 literal 0 HcmV?d00001 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 2767c0f9db0ce83e2d3ce1f82db6b912f7d615f4..215d53d28bcefae1fc179d71727e42e86fda0e11 100644 GIT binary patch literal 1225 zcmZXSTW{k;6vxMDnu{@w-Gm{e>DKEyVK=l1b-iK5f|PB71QnO0ELtLgCeGMxjM6x= zC%cNogA*;g6-X4s6HmPCGs?d4ZTJ|xa>h-nA{;sMJLml8T*lK(pq=*w-LkDyf8sJ? z2ZzStL3KPunaxj(X3KfvQU`r-9q&&9Vn@!ZYtSX44bFX+A?;e|J7;dd93RQcSbz!< zQJYvp%Rfiz%Cs%!F!$U+5}S_Q(HTlWWh6o=*F!j9{)93T2~t-wqiR--10=P+<jo@S z<na;IE*~{pElBkUQk|@--vQ@CUhY3$Jg@>1IMf^20g|Haum<kg$gxpz)jE0V+H8O} zhe4m5x_-c*(igmv6gZ5JwwvQ4Tpb6JugB0<L{^!QZo7fiA3DSfs5=~5%=1xh=mm_7 zy{C`~Gv|El_!gV^4$708*8Nk@r_N0mfxe$OKAg@7i3E;m3JdokOs%2YhcrBtCpWFn zFd@rLgdmwfZ(|Rrwtr8GzQ+MluU6uYU^ss=W~i|A1PnTZ6N*e@DK!2kG{{mYk9`lu zVN*2ECg6<F{G;^`2aJt__iME?mklQUD)r85tJ_k0xVnE$*Qam~+fz31M&#-PS78Ub z_<m0pdX2EM2hXQR<-M<(x~+zCtRif!6=OKo4dpoSdNGdUz=?<xI02lDIEj<MsfbfJ z)%{ovJ6ma-4rLL1uN6fsLR<oVy(nR+dn`Uy-u}DQ>>dl>${YC3F2Z*o8A#5UF`Vg8 z;qsgI=b4LUSgJHCjl1yU>puVQwI_|ru|4R0*BkVnr7uLKg5Qxdcu$Gv<jWWJ7~bLZ zGpAchDyM{X0dFhG+~x~_{U{bftQzS#)p+nHIoqz!1^jL#n1TBs5^yq<L@synOE{C? zIH}L$xXkSze|;|Fx%_6EJD;!N+aS+h&OyE#rT76zHxMo;hlXGZxTqAfZyB+O4vjTf zRU?iyh^_;dloCV}oH{f)Q#G+*-U}}`W3RMuV8#~ttq%P<Ef&r2x5gs7O=tHD8%47o z{;un<7WwV=B7Y0soemu+qH2bJfavzKf9PCRa+(>wRJD?>m5#u>+k9}c$m<;S=7U96 grM<LNGQ&T6y+yW6XKDegdiWPlTVCX}I_h@lzo&v#EdT%j literal 1229 zcmZuwU2hsk6rBaHU2Gifz-mKrs!%&PQEKC`LG(k3N(>Keq^g)&jV!gAU6!$#2-wxm z;0X1hW2?@$>{fm1Q>CiU6?yBw>CdS8);qh4i>iRcFz4QL=H7E>ntJMb>1?pIg_dr( zJ@+siGeLU%u6?&!2_{ZM-o68$cJtPqZ(5tQgrt%C+-|n$G4tqcZSCGAr#t4P7r_-~ zd&%ExX-hkh#ubHhnE=B2B_X6oj0+kjIYyGhAxTfLVuX1;;tt$!I2S0*0zFqy!G@}o znDp5PNbap=#Ux;(BTJLUB9sf!WvGw&iLS*(IQ9fdnD3}H3O!s!k1*Q&tF<5x9%|@Y zBWqV`mFmZqMaVG?L19G52C@u7LK^sl?Aw}=ZGENes--M#&(k!1zb{0<KiSziVxm9p zRy=>WGwXWi!OY_(3qA0<JrR8njU_6;cp=(fYwy2q%JOz&yRijdwN}-yv;SRV`wRNm z89I-hv&_q7gwa(Cs!3a@h~G1pJCAmmxOi4iS?0j!f&~5(8e-kl&lPr~$GF=aP~vls z4F<08Lm+MLHd`%laUVSBBqNb^&03#X3!TP;!e#RoBtKBIn9qI&+1Y49Rg6u1F5#?F zyeDh~6As6LP*q&0NHK|U;fgWGtLEw}!|J=7aO(M^9&guAXL7cRo}XOG+5eMMkyy?q ziQI=~HZp-f|52V8S<a3|wAZPhE?5QPxdYY>T>(SHOXkwcpPm}mX1$RotS9=XMsC2n z<dB72w2hTc{b?F_f(`=;ym3fz9!&mHnS2UoFB7;_6X@*f`B}7u299lHA&iY6^noNM zwpP<DW9B8+@>xy=cw(mjPh;Iq!)nbPVJ}!89r~e1=g%cdIlg~D(G_LB8!f+LFDkt~ z4=p+CR}g;7Tb36n_ut!nvo8C!)U;r#mlVHSAS3i*5rBIcBcG8!H_$ZyX#@tvoUY4X z%4PIsTF~3m!dvbVpmVn_7DK<au<>q2{;H(PKl~kuoz!q~O_#ryHg1+TZlYZzr}>TM tKi_wzh3nBqgUvL*5+ye>xwVY^!*M{M8X->6fLi{HOPQLhb#yR0>|cu8XjlLM 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 8bfcc8f22b3e2a8c1d6f7aba7b4ae6aa4d1784b9..4f1634a9635f3dc09084a3a11bdfc4c3184ee1ab 100644 GIT binary patch literal 2977 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 literal 3069 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 DXF$XM diff --git a/src/lib/adafruit_ntp.mpy b/src/lib/adafruit_ntp.mpy index d7a91766ec6b8005bc9bfd5acd0868bb383d6827..95130dc1f9f0aca3e0dc77ef5820dbd4088df172 100644 GIT binary patch literal 1245 zcmaJ<O;FoL6kf^3CdM(cqzE-Q#MlM`7+J9$)iCW0#D>(#@Z(^T(rIV0Wf|OJ$s?^Z zp<HB#&d^@lYi2srOOJw(^#73lH*L@Dq0?JWy+MYV-nue&NH5h$``$i%?|tukyJ;7= zc*QwTC>6?jy@H^MYC>%b^m+}X*JT8{QKhPYhQG1yNeBtyGH@;Amw?w$^gD_Ux@($_ z0FB;;TDfc}2m}mGeoH~n&eG}#oR<soh5`*m*3^;#!X8-Dv`yf{kyZhFk1Gtoc3z=W z(iOwNfLg&W1Xy@l{28iKJplh(ei&9YRYRIuk)eSKI+kMqT2<~MXl$u62*G--R6t6r zy^|2Pt{|JVqLwu*tJOX5&CJ2<!fYO}%Zuq7FmvtZ%sgPRz78>e%7Yp3G}IE(0Ie(X z9avGZ%hgp|04(cT6*vd40&jajuEA^;P<9io!P1+WT-a>6-a3-)emMy7XpM@dLI@}b zb)}|ZFNEg~^NVn0A^R$vU0O`P0=T7_^o`j(T+Y5W3t|&jsG6?T6di4WCc~acX-&I9 zG+uU93wJ@+)CNK|V=5L~ub_>3QINH2taJ3Sna&+ZS9BTQg{3XDft$d*0)_*(Cmt75 zlc`in6ubIFp_3=r9D;psG<_YTlJUvpr1WBZQrhuSJh2uu{ljyckxZmy=K4vF#H)jI zn3Qvj=<f<RIp-?n;x2^&HyS3mv5?4(=i`8KCrQqolZl-YA8_x6*QVv*ws~P^V%O)7 zaubxB6PBYOOj6y&khx8rDVmh0_#V}Rt+$u}+}Sw6@%~txOcLA>whnAZa<cQCOHX#q z-RbCA@pAVWPmkA(n>`Wyj0%xbFtcazzI}`DePHoa&f@)aJR~Lwi>K+tLFlEY__cS; zLyKo0S^PQE;yL{F<>dGI`_vRKn)7QTC^$S6OOB32E7xz1&nJxW`SI#>@J);H=l;y( zEyl;bU@^TGLrJ8$va^C&KC&3vyw$jR{q|!MyL-19f87p=zoo@h8+{%xOuxMZB=VZ@ z^*(#SVpxe-r5)^m1ism4FXALzULsfb*&z0x{rfie`WrOK4pJc;rNT|Ai7CIMlg)?! zSj@R3`TfD8(7iwY{s25Z#41|INDKKPMvCMQKRy@wxE1>8$tU#KQ9J3=whcRu4}bpb zH2(Q>@n61paP;ND(P8uW*RL#wIi=%r^1aj`JtUgj)6sHJKT%^beZObsES8r*Tc5>n ehbLLbk1=L=Pa@=I%1@1m=DTab=-mJ1$^QbB3W2-; literal 855 zcmY+B-%ry}6vuBn6=Vx^ZBr^6Ft&g{hFck<4A2A^6BZ*d83;zbY^8$+*4wmqMAXDG zU%>e2f8ak*e*FQW{tfcrt4}_d05S2+TLv0$?!(FVe9yg~b58m)-5%P+Oc2zy6_yGm zv!Y?BV<Tp4EQW0Vjft*|3hZ3KXJNCVV1XiWJ=V&qKrz1Fm=x>j5d;X=RnyXt4q*fc z41}g?AQDqTcrrf?^HXqkGCL15(^Kh*e1c}K5H(2)R8FBOYj6c)!y1gomo>anDaI63 zj$e?(M=p@knyC=9=?%O>z9IE7V%HiH2`M#@N~I*J8We+6)4TS?VI}m+7J(l$3xvfZ zfR>qV5CM$OQcZ$c4BIc6dIOobXfrH+0a2-Bskr!-30g>5RWWqCLls=?r3B#jGlq!_ z)x;YKI!Ou4QUR;3A*WEZ)hxv>NMi;<YcY3l$Xu>Y9NPBQa2WWT?A!L$&d$!Lq=qtJ z#}ON#Yut6jMp)z7t7#@?!EjhgQf|g5xF9TewjaK8O0KK&@Gt9uBeuc+VQohY!7|bj zM!Kdz3l~gGG*(xiV`y#YN=^X)Z1c4$7onY;?Rr&iZM8Ft()D=HP_%EgyI;E57YTJ_ z%Uytt^5VZ2bxQ^g#~qCw*&iG(V9&8&)#0clwK?jwAAPh*yfe4?eg5&+^v~A|3l7)8 z`)Z##oQG>1xd*e8nY<hGGgImNaBTeH$OD-km%-sK&yxk5Pu<}nGPSTvidB6HBZq5p zxG1@|XqhH*i>WH>B@>mQrn}iXVrBz;Kn9<3PoA<vyin2I=|R`ST<REgI4|R?t+*;z zX?ynbUUmO>_|3N;@4@M@BSu&+?<K~pYxJHq<w;UoCuiO-2M*VN%-%iOs}=k@al`?( anP=CKQdo0|Sr30r2IpCVb1YM5gZ=|@7!jcW 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 0af3a76356f94e76d99aefed3387c698e25afa92..c2a1cc28e66ec0892ff2b4d91388b3decc411988 100644 GIT binary patch literal 6944 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 S6>fh{Sf>d8pbB?Xh5rw4Y>r9* literal 8578 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 diff --git a/src/lib/adafruit_ticks.mpy b/src/lib/adafruit_ticks.mpy new file mode 100644 index 0000000000000000000000000000000000000000..6167b2f161ca616c28220d7ab71cab204e04c254 GIT binary patch literal 694 zcmYLEQE$>v7`-j)C<WPV$1XTXB;6hi7@9RrjEfp%NS294Vhq;BR94Dp1WIU`AwGDE z#3l33z}|h?FYrko_Y+JuV|>F)J23Yq_nhy1=iKkxTLpJgv{<Xx8oj>hXpX6Wuyfr3 zSaW&znA^#b1E5#c+BS&U{jSkFF>R{{SjTJ|AknrumSc5HUF+CDSTZ2^rdquYVm_>! zjm84E4BG}s(@u<@ZCV{o15nd?M%N<f`JpQb{v`81H??Kx{0lAip9A+1=Vb$~ZnT^l z&?6q*0sbok=3&!uy7sOl9h*+Gf0Wa$wzSw*da_u1&+O@B{g(r$Nrv$<a>M|bxQEkb z$02LiT4{?6+-O-J!ME)n^e**2jbr8QQBst$r59fXvHbLuzgIpJM!^w_A)TR;ffS8s z6|SILTL4A(qZl%zx|(7TqaGYp`86J9=*!|ytl~;Ch=M%N2O(2Y0#zgwOEN%AVXz;^ zkR`5Qibd-xzpemPrVw+G_UwEt6SyqCQZWp9Jt4HA$~2LwcjU?e#K|itEHfxl(O2CB z%Q5&djtYVTM0Ylueg37p`FZg*DUxXM`zZfssl0(iF^PhN4WgYke=F2N{Bl=>Ts(n4 zr5|L)8O9h)s)y53NOb3TS}43#i_gZBD{(mIe|6#Z7Nt<euq(pwW)ctc+56+k^-bEv dxo^9=;Xb>*IM-cPj6_KPSzZW_Ckl<H`aeUl*}wn* literal 0 HcmV?d00001 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 {}