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