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
zcmZuxTTmO<89pl{5aQxxCDt;=c)hwHkRpT-#7&$SR%>yyL6Rk5#ghtM7Elqps<ett
z{lKp5COGM|ZeQwmzhtHlmAD9e%h-VNwVlr7!I#b?dEvPkhVk^VXIH|eok}z3{Qvh~
z&wp8WH8_5*!4geGr!u)DA4z61%pAuv_VheBghyGH<D-0%W3xaPiA?4AT!x86fF_#B
zMCXByjm|L%K9^200FQI26caD&s`=y`1Jq}eSsv)dB&C3?gpQ{rg=h+B%0N9opH8yV
z0QKflS!O)PgT}N(B{`t!wL9!iKo0w9Ce9_8GneCxYz-{K{;@M|Ka*yn{CgA|_Mc<2
z+2}L_jPJznu~ttsnSyz6WpBVS#<8&!7oUaU2N{;hB;yrH%YU<N6*iEGve~&L571bW
zjU=Lc6wKoZIkiYE$!9@*hKXi57GMF6BeFAGh6idXae&Q5FGu8OLEB15pqGKjr6il+
zE`ip4Jd$8iu(Fy26U$9YE~Q6Gtc<d9Al<1HENq;cOMeOs^|GHVAR;+79nEB!L^)Iz
zh*uiZ9Bj=L53Yy}&In9zUlw6GHnJZQXtF7WNdx?V0;r#X+Dryib1ZD+Z<q{h2%mu^
z1Sq`9g-{HEeDgUx68VD3WMNz3V4x8gCe1-=*b*$~6OE;q_igC<pciA<+#HjM@(jp-
zEQZC2>bWEfs$r2pU0y4?^+`6J<M&^I%J1XgWT@qui<eZVXLvrH{iv&JI?2!EVs==G
zuKfY-8rYv?cQO-)6YZVnXJBf>et&tcp$*U|RMnNTsg!by4kFc+K5CG6T56@k{$a0L
z*==|Bst%2DJi)MBZhD50Q&JtJIDUp;zF^ocE|DP0u8Bl)Y6@0RrSnP?oh1?+0k`QL
zd}N$W%@cFc%q(Fhvgv4?A!ed%0?sH<9jTbWXJ?ZMM%L<*b%46cVegg>$L~`%4>H+*
z)@<{Tp9bARw=pqG%o5iQU%SxR5C4<1#C6y8kbo*Wk)u_u3S1Eud`FeIQb1J@scu!_
zDha8<YldtBszz&34O-W!JX+tX#?`I0cx|f&*R<B*bzuQ*fEJCd4R}L%L^IKJ@<-Yo
z9zi}w58=Ns0Q{qX6QF3*$F3?I4LA|BNhX_|3S953!dseDC}u!h3LKLu1*A?vN^7Ws
zls4d0qB_|?7jUZ3W{5QhoYg1}2|VDeK@UUXurIE@464z^!KP2}Pf-*<?TbUcX>nBM
zn<Rb_{Ub{1EyDODF>Ir3VOu9@F`O$1LBXo9A`h|tf(wa^UUVonHoaR|e^KH-aVQ>b
zmxQ1}xwkxV@r|ceqE&luDC)B?{Y%I!@8;hYSA#+b9^!?dxN|Y&-wMMYMN!C#d!(p4
zvMgE^eMp$PwNQ-T3KY>REf!&Fa?(6SboTWIJss95D@|Eu%$^Rj$87a<kj4&@hPTz+
zVfA#4(=&vv*WBx|Chazx!PbZkz&syf6g(ebZg^gOO>Yb2cm41sQi?UC@OQMFV4D1c
zw;;-BHTmb0W$+?-l`7;b;BQZrK@X;d?~Tj;zJ5vjuv10!DB9}EBO91*$%QboB>!*W
zHBB#UQj~AAATH<aRl%)7UcMh;`V|+ljp<ii$YUoct?jyy_C_aCf}m5e2Eh{TUUQcn
z8>$aFhAQG~+Ozs#?FluBxuLGat~@9`p(^S%2i4bQc}1fBA4&dIC3i#Z0SUbhn;cvh
z;o(v|#m*dLpP3rzq3?()M6PFf2F4tU=P&NSp5K!TdgoooJ^sSo5S6<9PIUXd$Sa8|
zX=&aErwR*#6_LyL)$Zcky}woF?6?%;{@um5LGfql3f-eeq#MrAPZvYfaSX!-U`x<r
z*uZ@k0{z96;^ctZ>=CY8K_N1F=wVR|!b0^Gg<zMC>WYhAS^BjsZIGp}pcKlw_hsMw
zKUw~=Ut{)ogm0~&7#TgWVFL2FFG)|=$V_G4-Q*H<c1+qU-Qp8@uXNI0<#p=D=EWC+
zcD7#zoAJ}q2zDapdjQ_<MDR{uTp@aIA!z%crA_$BXe<`=mZgHe4W^^-@WsCzdP)(U
zgTjuWHdxwcNis`}Td3zkL2J=j`@q9uLEH9*9xZ4)bVrKPCjHvCF{Hs-AmBnaM)VVy
z?nm0UIb5@>wF$uupKsGr^*Q|dNCqGDf$-9F7`k%*b-@?Eme9B1H-S)-Ag*bQ*H5sM
zCOKyuKkWqYYlPq}QtpPjPR02i1#UlY0}_w6#r){;n4u0Oq~k=arwVuY;>vVOy?)Sg
zLrq|MX??C<ex|@%AW%<`K3j)rNR72Xd}E|pPH}8=Sc##_Q~UX0Lra*}rJ&DXO)kJJ
zX^+Lf^#0=-(q|tWZX<5!1r14(1Zg&!OeASA8Ajmc+Y%mmx2azhrNTU36?Y!3Exn?L
zSA0Lv6czAqm)zqL?Y4rg72T0FSgfL3y9&3KT8Rsfuo_+0vQ)tPuoPEMz7d4Qe!>Bx
z9VbrQ=ojjUq{TuOboS!XN?+CXGCf{`zD6XgRmqAf#i<j}(5S}_-wX-0-yE8rl&g0b
zKMf1zMah`daYzcNA!evYjWHi;iuE9PFPwn<(Cyy|LVrv9V7If!Mc$Y(!XKQ3Za4|f
z<$}IvrJ#4M7WAYq{wMEWZY~P_-rm8HQG?NR<C0Kou~Htx75j+AY$0uCtL3bv(`Yn}
znLO=gg9UTLPt@Yx_NwoP@9DwS(o33J-NuwUCE8csJ#DuODdqQe3tE>{9f2AUclY`f
zkKry#qR42YL-FJ(q}PLM>mkY^>wQmq;JvmGg3BvuJrkZ|{V-5uG~`gcc={E!B}_b7
zx)&7JfBu*7iVhUCP9eB<Cj@U1-r{bE@)xu{6|AR%-O2AfyDRQIzgzkzY}wGl&cwni
zcWEI^eY;T5lC%dq3qOG2qV7YfdCLM7ns~ZG4@o^(k-AyN#hnYEy>y$xVPE_|jFYv^

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
zcmZu#Yj6`+mcA`pLKr_<QoAj{m`2@tShoBSLLmkcBejie1Ga1na6(3wTWwoGuaRWH
z!;q+jlNu*8wf?)iwY${rR?Y1G*_LFRM<5w6WI_U@cD802=ed8#ul<#5ZEbCB?e4kV
zl0(RZO6%Tx&OP_sbH4MP3w|BiEA?9)k!WN#vk=RLGvZGd#B46xm0m<mb)K%?u7jvf
z%w&=&q<=k|N}}3S7U{B!S=2cv=E6cMnG}UwER_r=BFV^{m_gnxTYo~>8cU?(VnR&j
zB6t(hCnE_F?V8L)1aT%JoSR%si>NLK-z=&f8b!LJ$wj2+V?qv*qiMV)5=VMPs~)ss
z$vNbhND1e}TsoDCpGZX);-Vs@)Kz8NIx%tFpGxM$i#cWP6=E3yE}LG=&4c5rovy56
z2ARX-kqhHuHl0dlMPwdb$c@g9i@8h;JR-N&8z;nU7GHohLdYu0(xpbYat<zs>MPdt
z8BsWoq{~<<5{)HuDOB*2Vp2%KWDYfkg@`aOMiCjF2f3KZA_g=CF?L>zhT~##E;o<b
zsw?4{h1pq%Fa-;VNGyphkYQndA$bl=G%82H0k%Jt6|)deWyNsCEjkPFU&w-DR7CaQ
z8qREMI33A=PSq2AI1{-52V^T(-ML$8!bc0|!^%aB-w!3bML9h@n@J_AZqni0d?s}v
z9Jvr#L<P1!IkT99AkwoLam$fHuM}>q-hj%AIhT-1or{U6wv$0z_<STDi$-!{IGM_Y
z=YAGTqsA@ol&KUn$8ur@-&lz|rPvB*V?Ps-9w!{><Ecm#vYO4tRV`Sft9J^=qHyha
zB#VJx$)W@NkHw?mnbblu8p$k%!GQ?se10_xkbwA0Cl;ml+J-trUb@`JT)GS+CX6d%
zHWnAF86;h<OP5_PkTou3BFShffpqiYMK~IgFP!^6;#pBVhy9MnQ62c0jG{tw{lL*<
zM~6-#-2l`>tFju7WW#DOJJtD?uc?{Wp&YXS5xiS*0^c2v)xe=$>a0@d=;gZTLL!|-
zHQ>MP(&ZzUE+hSXDhqK7;4ih16*DfVYrr%W!sDaLxUsswp-{73XLa|Wy7^o#-G}P&
zPqq(f`VfV~PyrmIR;d>p@Wxy$A*L1}4bSF;jHd*Z6AE@cHi8OGx~{Hn)R<E0K8qSh
z$0mnHPfmb?P%Gfzz)>DaF5R(#Nu*CJK97w~Od?$xZ0p8Clc+8o$qDnQZtUo!KY;3^
zVqDAt-uZ#!1Cs;Dco^^|q!Q_u01)BxP<)ui!hm=HLplW`8Z%xvp|A=fCk6&a!UHFH
zG}P@nK&CUPw3x{OvfB$Latl3r1$sKDRHVmuM~&6;kqA*bB56tSqJSw!L`FOUgfGA_
zS0zAHa7m}BgqTQW7SG4T3#ibYlKj$?)Y2Liak+*Uk};)h!?YUPFlIhfhw+w0(*8u@
z2(4gm9)K1H1`DbB1DQ-JgY*iRBg~HgQ(u8cVW2)C0xd#(A$aphw~&bgo8^Eb5~wW_
zk3&Ic#Apm^A7Hja`?8wv?(Vr*Zhm2=3*y;bZ2;Xzt6jyf67HBvxZ~9x1ACCR;RkGt
zbOeL6dqT;`Ok8AgDF*W<6PTPFgOOUHT}Y>aQquOF6fcMwX1ub@C=3^6a`Uk)lS+zR
z4B&N3nq^{%1o#bP5?=%h#3A^<4{586?W&Ni)&QwUW`Sm1TP&?5EA;ZrQe0^$$=ucw
z1E*q2>SS<;86lDc&SGXnCL#!ceKE>JvJA!(lgcodnN<HOmYFJ?@Rija46rYwRCyQD
z_V`tM6;X9pWERMefjY|p7b#Y;b)`@1(2sWEQ<)!bsT!*8dj25A;U8{Mtbl%HE7CSs
z5XRsOXBX1xR3-=gNZXBBuc6MbQnQPXLHe>W+{62r*;FRs!ht%(;1|FV%caW}q-{FH
zVCX)xVyy1Qjl06H+O~k&E1ASXRta$|$wc9apjeFnq>iWx4D_=c<G6IWoxyzx*m-M5
zYL>w@$N;c$#FQ|1Di;6()Ut!Pn7&s|i%G~IoH7$jLPubL5kvrCA0U;Cq|@=3%3R$_
z;I;dnUApMHaN&YWxh_<a0x29DupQ6Euz%gpgrZuNn1yv3rW@&YDA4cj?drvdr>h0U
z{Ggzaw!V@KmqIMs`e&mC7_Ux`s}Bk-9ZGKH2_r9;5>j!vjgAN)ttpvOA*{r#!rxl+
zxR@59c%qED!pufu@dYSJ^vkHjLrnMQ_gM#QcH7`_zO!@hz{P21*%W8~oFp~0)W9NI
zYRm_B*U&Zat)-<V_|ei@`08j~p4{dncI#WW(c7j9v_u+ortPLWme}-vl+TkpKvv(n
zgWfTfCmYB{vWX-~gHyZP*xEohv^LU>txa@OD@l{B2HF5RCXynXoi)4ZRugS%rD&?P
znQoq%bR)9GqoG?S-2`biNCr<0ZJu;%$epmbbJATy?t;Otpio=r)YrGX`%?55+YOSD
zmP}*|Em3Yn?|#!sOzbD<J#G!XFDTSp+|{^SYLU$Omy5EwH7FFAzXc(z?9BV78LO@M
z<|aQVn_5k60{-r$n4*^uB(sIw2THs0BmJ;US(HT*>CEp2E9aTvyr8h$st@dZH`LEE
z`KG0v(Ix%-(w=40l5MGBseZ}4q+ixA?^teJGFCS2#G7ai`{^d={otb~kNvHoz3y7N
zBN%jQCiZLT7u`DA;@(F0x%Kn`_jbC+T}S&3QnP!9hoE^psE0ut9yGvU$lXZSN@I9$
z6MZZwkjr}iL451oQfofOSvis6+Bx3Ab~rqYld*9SioGN&o4KHDwss=fY|G1LyHVP6
z)kDZ;2duV_$!4cab+8&Qu~GI<luVtJOE&HG5>rO0jjizzqEXswl$eT+hrm0#@T}Gg
zqi&h%@uDfP)LW9x?w}CZUbkOr$rofn>iJDA?E&xhNfwFCzavuzuqLdR38NPW9dh$4
zIE#9rSn_IwchZ@I9k9&hS2&JoAFvN`o!kuL?CP}ey;jaHn;ds#lYOH^9W3U{-kN)>
z`Oo>;QgF3P6S}`@l&nP$@$HA@_3wUHS}(&|DR`|*bLtvgcugpSxa1)=`DwVkI==d?
zhxn*;J*dt=^ZH%5fkUReM#%=c<sjJb5)W?VH~A49wo(vV70NfBZutKDjiB&$`jb52
zd=X#daQ>g{7S3vEXFKh-o<R=Wgk8QoKn?sYKjD3)4X_F~@DTTk{P9)AR1tqql|knT
ze-^&w@9wYS8Jq%|!8H)B!MQ#jpI#Pz85n-af=|o;)*2WbwA;NdD`&IV=6pW;ao%Bd
z3=Baq{Q;aeE#yr$wfi^ovdPJR1mAXW=$K6P6_m^>>4XG&2<i~4lf0UI=&qV)xcQ1l
zvrz{1<0>|Ae)B>Zu;Lp5JHfAbpAW942K*kZ@FT16{UguA_nu6>SX}jL?yrG?00dK+
zV5K4Xw5fgVn^(;3Yr@;+nYKQu<#7I+<DA`Lu^i+qMc7ClE_w^-n>#quYFajm<^Rn4
zPM^M4zV91e19PP{ujU&t{zreW^z_UtPyF1}RI&VDKb8iCD?K~u0|3SE7^}}Z8o;sf
z1tBADeiS}{^CK$GeORPo6Urqhywh{Evj_J5-!#KGShm*|aJAdoZQZWX3hM6^{eIv7
z=AkG6aS*?45WgPH-HkH!k{4~1_<zfn`7c0hFOjTAL%r-pa2tCWPc}-dev==9P?vEX
zwU^lKoBXJ<5$lotn^2A=^5`aiswA6iUt(8nr86PYSN`_ttEYT}fDc>gl<z+Pe}#hv
z(l52Fv@rWwwoW$jU-A?1W$QxZ0L0!XIar_(F(}M$Z{$IbnPy<Ku*CGzZ7_8(4i^`2
z%O-2^tAF94+S_3Wck*a(BCU|$HWt`NWE-eKnL0)Wu0n2b$y>2>>EKTD5LnsYkF5-L
zJ#WVTteHlPM#>L3f;h;gHrd2^G!;W{rQ4ZWFx1~~b#o2?c83)o&;O+Akkcr2I5p3j
zYf`}73rKH~aTI&2wwtJ7+0^SL06<nh59DVp3U4!?{FZPWlUf2&=gN3L8?ZBWtHnBD
zx0Y~>Tr3IJfD|B=8!}}ndkNqbs9j%lwfKaefPg^ZLb4Q^@eucY$6yKO_t)m8e2rC3
zdnej+a@Svq{mVO+Y%ylJe%Z84N0+uOZHMkK@n?%sM%?;S=@7echfvyp+9OUHB-*V3
zk{`tb;9YtO4{Bi$!UL#i`Yg1IX=oP_XcsfkE(B;7(O@t*W;r|_bZ(p2zk^P=>*+V#
z4YY)yS4bLn6D`%cNm|mm4YZ_pLkF!33Z_EOZ<Yu%?i^hg&#xpL9B14H0g%m|K!GDr
z>c@f5PXvXP^k*c064bg^vNns~#qsQj&E;@%E+^O76L1dMM^0EfMr3LfFrMG&K=+E&
zm=}ddeogdf?ti$R5B+Xk3VpOn1z7_%j%yd#=NiV_3A|>c#;<t^Sb_;Tp%9aG-9udY
zoDZ%SLJzQti5kD5r~n^;Xz6o)di_f1<D2R+RFCG9hj{ZOHU8c$#k$Y=y|JF_<Q@Rd
zP+6uz*S*Bk4G_Wa)^WaneFH0>0@qYG!Xqt@0C%?mrpi7z^tK1~`Rt|gs<TFr^rUWd
z<u8m{>hy}2pw4)a@Kzv3Pw^{%-4W<KU}0?-wE$R;2J>()ODV`#Lekx%xdS}VUIOC)
zAq;8%KuKX!7OOUc7e%Aw1_JitwhIkLe}V}~`&a(aVmW4?9AE=Zn|<s~V3$n2l80N!
z)Se|TQ85W+duO=ev{CYW%@0hC-5DlcD`FQMEUmck3h)u>)xf4#fukxG%EGew_Zsf(
zUg^NfZ3}BTGt6i9TkQkR{9WI_=L3N5*NP>z4Q>n{0ps4U`KfzDO{=CFV~y!2n>^ot
zKd*@f?>TB;8{Px*wy*iny`deT2=cIpSe;d6#A{`^C{zya(MoR_-qI0E<)?QcC|LBw
zzx*aV5CVR3T57>w{a&WdLQoimN#Tuf#r!HA!J-#d{$TAGE`>*!4TveUcbu?cnVK$^
z;g&$@)Qmc6gGSK*YU>Q%w14Fbi&Fl751aNt5#K9O`&PYB%Sy!qzn?9x;=UWgHrSx>
z7IVGUBH*nDr59J8S}l(Dj$sGB3+^Ov7`$5`q-Tq43k+08FoA+2XSW<tT~f4aK`SWy
zRp;hPEyNA%#-u*!5HB65g$nzhxfz?o!N$6v=~z$Bb;T?eOIL@>X&JJz#~kgB*qq&I
z?{GR@qqgoCdtzLsrq%!k!3Rn~om~%p<@Xy!@p|Q#H9s)IKM>ABg?+frXTiC31(^K&
zmd4Hp<lM*W;gP0K$a!k~=6mb!g}gN%`?_yRIO(u!Qr)E#17u+xx3CZM562z_A795$
zsrKut$&gH)gu?2n`Sjsqb{94N0lb@hbL)J_DD|u>H8?W<2#RpsOFZ<Af~UNnZTL_v
z>4(*%DG}PO1aum|8q~nH!oZEzJcNxTV;uRVA7U#M$=6HXnkn)eUI6-mg*dwhR&MY?
zwrxv`-FpMp*8TqL{3%wCmm$SMkp)V(-@;`!wH6*R*bI1)Gpun2xc-#SwUw&dIFU2O
zb#TEKUWZ<+f)RKKubg-vizCJN@uWW}5K`c1y;Lu~e6_=c<nTz{og(h(-L%gm{CfVK
zzk7?2H3{I-KIt$doR^MVkY19yuZ~Vnvwq9TNtX@&8yvQE9<w@@^cI%oUS|RU)^e6}
ziPM&j5l8zme@8dxbPjPt?c849(6DW2VZ_pLdenN-y5{$HqWgheCF&5FyaLaW+K)eZ
z_~<s3BGcqgy_!eEyP-NAU-J`Fiber6YCwaWdjv#rlgz;1jV$@G$G%fH$t)gUIrP{E
zCF)@J-GpbdNak+A<Kh+=&na)6hmYX}^S+08ESpa7$Kg|bKAp&a8nltV+tLTp(@$l1
zV=dfz|0a1}HXSD~Tq7^arV;PkH8#Vz%@}~FM>)f-;_bEa>J6`cYPeM;m`R&yNY9yU
z<e1GE4BAW%(`(=(XXHQ62Mz(AI0K1GY=kpVjN~P3ln<Wpdn5n|1xqh*6x0Mqv6fBV
z-~V$0zhYRp!EhV44l}3bYaXl)Y4H)?+sD4TI0LRHo8g1P6aVcuID>!4Xl6MN!I87Z
zot#1TXl%{b`G1heMXnh?xBf=56gpwpb^6uM26dJzO`rLEuT1g3kg%9s4B2RorhZ0V
z@)G<YN8j~*k>}`qh)izs@Sz58zIThGl~U`d1tRyoahv)%nN%L*k9=pInShl;<Yf>@
zRg;&!guks~<2FZkXkkCs3_V+>5=H1$YwOp%+icCs64wlj`f<LWd;`FD`08zV`zOdZ
zixZ{%3k8iJodJ<iLW=B;B9n;cgc?P7UlrCoCxpivSbd-hwa*E4itv#t-1eMMuL%EH
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 {}