]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/commitdiff
Keep track of displays and buses in init
authorMelissa LeBlanc-Williams <melissa@adafruit.com>
Thu, 21 Sep 2023 02:12:22 +0000 (19:12 -0700)
committerMelissa LeBlanc-Williams <melissa@adafruit.com>
Thu, 21 Sep 2023 02:12:22 +0000 (19:12 -0700)
displayio/__init__.py
displayio/_constants.py
displayio/_display.py
displayio/_displaycore.py
displayio/_i2cdisplay.py
terminalio.py

index a908e1aa50bb54441ba43de0c821834af08d3d12..f357dfa2959987c6273e5cad73a26083cdddbfdb 100644 (file)
@@ -16,7 +16,7 @@ displayio for Blinka
 * Author(s): Melissa LeBlanc-Williams
 
 """
-
+import threading
 from typing import Union
 from ._fourwire import FourWire
 from ._i2cdisplay import I2CDisplay
@@ -30,19 +30,67 @@ from ._ondiskbitmap import OnDiskBitmap
 from ._palette import Palette
 from ._shape import Shape
 from ._tilegrid import TileGrid
-from ._display import displays
 from ._displaybus import _DisplayBus
+from ._constants import CIRCUITPY_DISPLAY_LIMIT
 
 __version__ = "0.0.0+auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 
+displays = []
+display_buses = []
+
+
+def _background():
+    """Main thread function to loop through all displays and update them"""
+    for display in displays:
+        display._background()  # pylint: disable=protected-access
+
+
 def release_displays() -> None:
     """Releases any actively used displays so their busses and pins can be used again.
 
     Use this once in your code.py if you initialize a display. Place it right before the
     initialization so the display is active as long as possible.
     """
-    for _disp in displays:
-        _disp._release()  # pylint: disable=protected-access
+    for display in displays:
+        display._release()  # pylint: disable=protected-access
     displays.clear()
+
+    for display_bus in display_buses:
+        display_bus._release()  # pylint: disable=protected-access
+    display_buses.clear()
+
+
+def allocate_display(new_display: Union[Display, EPaperDisplay]) -> None:
+    """Add a display to the displays pool and return the new display"""
+    if len(displays) >= CIRCUITPY_DISPLAY_LIMIT:
+        raise RuntimeError("Too many displays")
+    displays.append(new_display)
+
+
+def allocate_display_bus(new_display_bus: _DisplayBus) -> None:
+    """Add a display bus to the display_buses pool and return the new display bus"""
+    if len(display_buses) >= CIRCUITPY_DISPLAY_LIMIT:
+        raise RuntimeError(
+            "Too many display busses; forgot displayio.release_displays() ?"
+        )
+    display_buses.append(new_display_bus)
+
+
+background_thread = threading.Thread(target=_background, daemon=True)
+
+
+# Start the background thread
+def _start_background():
+    if not background_thread.is_alive():
+        background_thread.start()
+
+
+def _stop_background():
+    if background_thread.is_alive():
+        # Stop the thread
+        background_thread.join()
+
+
+_start_background()
index 938b49ceacd20382d6784e7567f2068f095fb087..d255dbf4c804cc516bd261ddc23f644131e24b3c 100644 (file)
@@ -18,3 +18,6 @@ CHIP_SELECT_TOGGLE_EVERY_BYTE = 1
 
 BACKLIGHT_IN_OUT = 1
 BACKLIGHT_PWM = 2
+
+NO_COMMAND = 0x100
+CIRCUITPY_DISPLAY_LIMIT = 1
index 241a0aa7ccb9fcc73ed4c5631aed8fbdefb40e05..eb3655d9ed2405598182f389c42a188d41cefc23 100644 (file)
@@ -19,7 +19,6 @@ displayio for Blinka
 
 import time
 import struct
-import threading
 from typing import Optional
 from dataclasses import astuple
 import digitalio
@@ -39,13 +38,12 @@ from ._constants import (
     DISPLAY_DATA,
     BACKLIGHT_IN_OUT,
     BACKLIGHT_PWM,
+    NO_COMMAND,
 )
 
 __version__ = "0.0.0+auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
-displays = []
-
 
 class Display:
     # pylint: disable=too-many-instance-attributes
@@ -118,11 +116,14 @@ class Display:
         The initialization sequence should always leave the display memory access inline with
         the scan of the display to minimize tearing artifacts.
         """
+        # Turn off auto-refresh as we init
+        self._auto_refresh = False
         ram_width = 0x100
         ram_height = 0x100
         if single_byte_bounds:
             ram_width = 0xFF
             ram_height = 0xFF
+
         self._core = _DisplayCore(
             bus=display_bus,
             width=width,
@@ -138,28 +139,34 @@ class Display:
             bytes_per_cell=bytes_per_cell,
             reverse_pixels_in_byte=reverse_pixels_in_byte,
             reverse_bytes_in_word=reverse_bytes_in_word,
+            column_command=set_column_command,
+            row_command=set_row_command,
+            set_current_column_command=NO_COMMAND,
+            set_current_row_command=NO_COMMAND,
+            data_as_commands=data_as_commands,
+            always_toggle_chip_select=False,
+            sh1107_addressing=(SH1107_addressing and color_depth == 1),
+            address_little_endian=False,
         )
 
-        self._set_column_command = set_column_command
-        self._set_row_command = set_row_command
         self._write_ram_command = write_ram_command
         self._brightness_command = brightness_command
-        self._data_as_commands = data_as_commands
-        self._single_byte_bounds = single_byte_bounds
-        self._width = width
-        self._height = height
-        self._colstart = colstart
-        self._rowstart = rowstart
-        self._rotation = rotation
+        self._first_manual_refresh = not auto_refresh
+        self._backlight_on_high = backlight_on_high
+
+        self._native_frames_per_second = native_frames_per_second
+        self._native_ms_per_frame = 1000 // native_frames_per_second
+
         self._auto_brightness = auto_brightness
-        self._brightness = 1.0
+        self._brightness = brightness
         self._auto_refresh = auto_refresh
+
         self._initialize(init_sequence)
         self._buffer = Image.new("RGB", (width, height))
         self._subrectangles = []
         self._bounds_encoding = ">BB" if single_byte_bounds else ">HH"
         self._current_group = None
-        displays.append(self)
+        self._last_refresh_call = 0
         self._refresh_thread = None
         if self._auto_refresh:
             self.auto_refresh = True
@@ -182,6 +189,14 @@ class Display:
                 self._backlight.switch_to_output()
             self.brightness = brightness
 
+    def __new__(cls, *args, **kwargs):
+        from . import (  # pylint: disable=import-outside-toplevel, cyclic-import
+            allocate_display,
+        )
+
+        allocate_display(cls)
+        return super().__new__(cls)
+
     def _initialize(self, init_sequence):
         i = 0
         while i < len(init_sequence):
@@ -190,7 +205,7 @@ class Display:
             delay = (data_size & 0x80) > 0
             data_size &= ~0x80
 
-            if self._data_as_commands:
+            if self._core.data_as_commands:
                 self._core.send(
                     DISPLAY_COMMAND,
                     CHIP_SELECT_TOGGLE_EVERY_BYTE,
@@ -216,7 +231,7 @@ class Display:
 
     def _send(self, command, data):
         self._core.begin_transaction()
-        if self._data_as_commands:
+        if self._core.data_as_commands:
             self._core.send(
                 DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) + data
             )
@@ -228,7 +243,7 @@ class Display:
         self._core.end_transaction()
 
     def _send_pixels(self, data):
-        if not self._data_as_commands:
+        if not self._core.data_as_commands:
             self._core.send(
                 DISPLAY_COMMAND,
                 CHIP_SELECT_TOGGLE_EVERY_BYTE,
@@ -248,7 +263,6 @@ class Display:
         target_frames_per_second: Optional[int] = None,
         minimum_frames_per_second: int = 0,
     ) -> bool:
-        # pylint: disable=unused-argument, protected-access
         """When auto refresh is off, waits for the target frame rate and then refreshes the
         display, returning True. If the call has taken too long since the last refresh call
         for the given target frame rate, then the refresh returns False immediately without
@@ -260,14 +274,47 @@ class Display:
         When auto refresh is on, updates the display immediately. (The display will also
         update without calls to this.)
         """
+        maximum_ms_per_real_frame = 0xFFFFFFFF
+        if minimum_frames_per_second > 0:
+            maximum_ms_per_real_frame = 1000 // minimum_frames_per_second
+
+        if target_frames_per_second is None:
+            target_ms_per_frame = 0xFFFFFFFF
+        else:
+            target_ms_per_frame = 1000 // target_frames_per_second
+
+        if (
+            not self._auto_refresh
+            and not self._first_manual_refresh
+            and target_ms_per_frame != 0xFFFFFFFF
+        ):
+            current_time = time.monotonic() * 1000
+            current_ms_since_real_refresh = current_time - self._core.last_refresh
+            if current_ms_since_real_refresh > maximum_ms_per_real_frame:
+                raise RuntimeError("Below minimum frame rate")
+            current_ms_since_last_call = current_time - self._last_refresh_call
+            self._last_refresh_call = current_time
+            if current_ms_since_last_call > target_ms_per_frame:
+                return False
+
+            remaining_time = target_ms_per_frame - (
+                current_ms_since_real_refresh % target_ms_per_frame
+            )
+            time.sleep(remaining_time / 1000)
+        self._first_manual_refresh = False
+        self._refresh_display()
+        return True
+
+    def _refresh_display(self):
+        # pylint: disable=protected-access
         if not self._core.start_refresh():
             return False
 
         # Go through groups and and add each to buffer
-        if self._core._current_group is not None:
-            buffer = Image.new("RGBA", (self._core._width, self._core._height))
+        if self._core.current_group is not None:
+            buffer = Image.new("RGBA", (self._core.width, self._core.height))
             # Recursively have everything draw to the image
-            self._core._current_group._fill_area(
+            self._core.current_group._fill_area(
                 buffer
             )  # pylint: disable=protected-access
             # save image to buffer (or probably refresh buffer so we can compare)
@@ -282,14 +329,18 @@ class Display:
 
         return True
 
-    def _refresh_loop(self):
-        while self._auto_refresh:
+    def _background(self):
+        if (
+            self._auto_refresh
+            and (time.monotonic() * 1000 - self._core.last_refresh)
+            > self._native_ms_per_frame
+        ):
             self.refresh()
 
     def _refresh_display_area(self, rectangle):
         """Loop through dirty rectangles and redraw that area."""
         img = self._buffer.convert("RGB").crop(astuple(rectangle))
-        img = img.rotate(360 - self._rotation, expand=True)
+        img = img.rotate(360 - self._core.rotation, expand=True)
 
         display_rectangle = self._apply_rotation(rectangle)
         img = img.crop(astuple(self._clip(display_rectangle)))
@@ -306,17 +357,17 @@ class Display:
         )
 
         self._send(
-            self._set_column_command,
+            self._core.column_command,
             self._encode_pos(
-                display_rectangle.x1 + self._colstart,
-                display_rectangle.x2 + self._colstart - 1,
+                display_rectangle.x1 + self._core.colstart,
+                display_rectangle.x2 + self._core.colstart - 1,
             ),
         )
         self._send(
-            self._set_row_command,
+            self._core.row_command,
             self._encode_pos(
-                display_rectangle.y1 + self._rowstart,
-                display_rectangle.y2 + self._rowstart - 1,
+                display_rectangle.y1 + self._core.rowstart,
+                display_rectangle.y2 + self._core.rowstart - 1,
             ),
         )
 
@@ -325,10 +376,10 @@ class Display:
         self._core.end_transaction()
 
     def _clip(self, rectangle):
-        if self._rotation in (90, 270):
-            width, height = self._height, self._width
+        if self._core.rotation in (90, 270):
+            width, height = self._core.height, self._core.width
         else:
-            width, height = self._width, self._height
+            width, height = self._core.width, self._core.height
 
         rectangle.x1 = max(rectangle.x1, 0)
         rectangle.y1 = max(rectangle.y1, 0)
@@ -339,26 +390,26 @@ class Display:
 
     def _apply_rotation(self, rectangle):
         """Adjust the rectangle coordinates based on rotation"""
-        if self._rotation == 90:
+        if self._core.rotation == 90:
             return RectangleStruct(
-                self._height - rectangle.y2,
+                self._core.height - rectangle.y2,
                 rectangle.x1,
-                self._height - rectangle.y1,
+                self._core.height - rectangle.y1,
                 rectangle.x2,
             )
-        if self._rotation == 180:
+        if self._core.rotation == 180:
             return RectangleStruct(
-                self._width - rectangle.x2,
-                self._height - rectangle.y2,
-                self._width - rectangle.x1,
-                self._height - rectangle.y1,
+                self._core.width - rectangle.x2,
+                self._core.height - rectangle.y2,
+                self._core.width - rectangle.x1,
+                self._core.height - rectangle.y1,
             )
-        if self._rotation == 270:
+        if self._core.rotation == 270:
             return RectangleStruct(
                 rectangle.y1,
-                self._width - rectangle.x2,
+                self._core.width - rectangle.x2,
                 rectangle.y2,
-                self._width - rectangle.x1,
+                self._core.width - rectangle.x1,
             )
         return rectangle
 
@@ -370,7 +421,7 @@ class Display:
         self, y: int, buffer: circuitpython_typing.WriteableBuffer
     ) -> circuitpython_typing.WriteableBuffer:
         """Extract the pixels from a single row"""
-        for x in range(0, self._width):
+        for x in range(0, self._core.width):
             _rgb_565 = self._colorconverter.convert(self._buffer.getpixel((x, y)))
             buffer[x * 2] = (_rgb_565 >> 8) & 0xFF
             buffer[x * 2 + 1] = _rgb_565 & 0xFF
@@ -384,17 +435,6 @@ class Display:
     @auto_refresh.setter
     def auto_refresh(self, value: bool):
         self._auto_refresh = value
-        if self._refresh_thread is None:
-            self._refresh_thread = threading.Thread(
-                target=self._refresh_loop, daemon=True
-            )
-        if value and not self._refresh_thread.is_alive():
-            # Start the thread
-            self._refresh_thread.start()
-        elif not value and self._refresh_thread.is_alive():
-            # Stop the thread
-            self._refresh_thread.join()
-            self._refresh_thread = None
 
     @property
     def brightness(self) -> float:
index a50d094fb656a20bb4b75396490855fda378ffc2..ccda898e66a6c268f748921560817244307ac51b 100644 (file)
@@ -22,7 +22,7 @@ __version__ = "0.0.0+auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
 
 
-from typing import Union
+import time
 import circuitpython_typing
 from paralleldisplay import ParallelBus
 from ._fourwire import FourWire
@@ -30,12 +30,11 @@ from ._group import Group
 from ._i2cdisplay import I2CDisplay
 from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
 from ._area import Area
-
-displays = []
+from ._displaybus import _DisplayBus
 
 
 class _DisplayCore:
-    # pylint: disable=too-many-arguments, too-many-instance-attributes
+    # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-locals
 
     def __init__(
         self,
@@ -53,8 +52,16 @@ class _DisplayCore:
         bytes_per_cell: int,
         reverse_pixels_in_byte: bool,
         reverse_bytes_in_word: bool,
+        column_command: int,
+        row_command: int,
+        set_current_column_command: int,
+        set_current_row_command: int,
+        data_as_commands: bool,
+        always_toggle_chip_select: bool,
+        sh1107_addressing: bool,
+        address_little_endian: bool,
     ):
-        self._colorspace = ColorspaceStruct(
+        self.colorspace = ColorspaceStruct(
             depth=color_depth,
             grayscale=grayscale,
             grayscale_bit=8 - color_depth,
@@ -64,12 +71,23 @@ class _DisplayCore:
             reverse_bytes_in_word=reverse_bytes_in_word,
             dither=False,
         )
-        self._current_group = None
-        self._colstart = colstart
-        self._rowstart = rowstart
-        self._last_refresh = 0
-        self._refresh_in_progress = False
-        self._full_refresh = False
+        self.current_group = None
+        self.colstart = colstart
+        self.rowstart = rowstart
+        self.last_refresh = 0
+
+        self.column_command = column_command
+        self.row_command = row_command
+        self.set_current_column_command = set_current_column_command
+        self.set_current_row_command = set_current_row_command
+        self.data_as_commands = data_as_commands
+        self.always_toggle_chip_select = always_toggle_chip_select
+        self.sh1107_addressing = sh1107_addressing
+        self.address_little_endian = address_little_endian
+
+        self.refresh_in_progress = False
+        self.full_refresh = False
+        self.last_refresh = 0
 
         if bus:
             if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
@@ -81,75 +99,75 @@ class _DisplayCore:
                 raise ValueError("Unsupported display bus type")
 
         self._bus = bus
-        self._area = Area(0, 0, width, height)
+        self.area = Area(0, 0, width, height)
 
-        self._width = width
-        self._height = height
-        self._ram_width = ram_width
-        self._ram_height = ram_height
-        self._rotation = rotation
-        self._transform = TransformStruct()
+        self.width = width
+        self.height = height
+        self.ram_width = ram_width
+        self.ram_height = ram_height
+        self.rotation = rotation
+        self.transform = TransformStruct()
 
     def set_rotation(self, rotation: int) -> None:
         """
         Sets the rotation of the display as an int in degrees.
         """
         # pylint: disable=protected-access, too-many-branches
-        transposed = self._rotation in (90, 270)
+        transposed = self.rotation in (90, 270)
         will_be_transposed = rotation in (90, 270)
         if transposed != will_be_transposed:
-            self._width, self._height = self._height, self._width
+            self.width, self.height = self.height, self.width
 
-        height = self._height
-        width = self._width
+        height = self.height
+        width = self.width
 
         rotation %= 360
-        self._rotation = rotation
-        self._transform.x = 0
-        self._transform.y = 0
-        self._transform.scale = 1
-        self._transform.mirror_x = False
-        self._transform.mirror_y = False
-        self._transform.transpose_xy = False
+        self.rotation = rotation
+        self.transform.x = 0
+        self.transform.y = 0
+        self.transform.scale = 1
+        self.transform.mirror_x = False
+        self.transform.mirror_y = False
+        self.transform.transpose_xy = False
 
         if rotation in (0, 180):
             if rotation == 180:
-                self._transform.mirror_x = True
-                self._transform.mirror_y = True
+                self.transform.mirror_x = True
+                self.transform.mirror_y = True
         else:
-            self._transform.transpose_xy = True
+            self.transform.transpose_xy = True
             if rotation == 270:
-                self._transform.mirror_y = True
+                self.transform.mirror_y = True
             else:
-                self._transform.mirror_x = True
-
-        self._area.x1 = 0
-        self._area.y1 = 0
-        self._area.next = None
-
-        self._transform.dx = 1
-        self._transform.dy = 1
-        if self._transform.transpose_xy:
-            self._area.x2 = height
-            self._area.y2 = width
-            if self._transform.mirror_x:
-                self._transform.x = height
-                self._transform.dx = -1
-            if self._transform.mirror_y:
-                self._transform.y = width
-                self._transform.dy = -1
+                self.transform.mirror_x = True
+
+        self.area.x1 = 0
+        self.area.y1 = 0
+        self.area.next = None
+
+        self.transform.dx = 1
+        self.transform.dy = 1
+        if self.transform.transpose_xy:
+            self.area.x2 = height
+            self.area.y2 = width
+            if self.transform.mirror_x:
+                self.transform.x = height
+                self.transform.dx = -1
+            if self.transform.mirror_y:
+                self.transform.y = width
+                self.transform.dy = -1
         else:
-            self._area.x2 = width
-            self._area.y2 = height
-            if self._transform.mirror_x:
-                self._transform.x = width
-                self._transform.dx = -1
-            if self._transform.mirror_y:
-                self._transform.y = height
-                self._transform.dy = -1
-
-        if self._current_group is not None:
-            self._current_group._update_transform(self._transform)
+            self.area.x2 = width
+            self.area.y2 = height
+            if self.transform.mirror_x:
+                self.transform.x = width
+                self.transform.dx = -1
+            if self.transform.mirror_y:
+                self.transform.y = height
+                self.transform.dy = -1
+
+        if self.current_group is not None:
+            self.current_group._update_transform(self.transform)
 
     def show(self, root_group: Group) -> bool:
         # pylint: disable=protected-access
@@ -167,25 +185,25 @@ class _DisplayCore:
             circuitpython_splash = _Supervisor().circuitpython_splash
             if not circuitpython_splash._in_group:
                 root_group = circuitpython_splash
-            elif self._current_group == circuitpython_splash:
+            elif self.current_group == circuitpython_splash:
                 return True
         """
 
-        if root_group == self._current_group:
+        if root_group == self.current_group:
             return True
 
         if root_group is not None and root_group._in_group:
             return False
 
-        if self._current_group is not None:
-            self._current_group._in_group = False
+        if self.current_group is not None:
+            self.current_group._in_group = False
 
         if root_group is not None:
-            root_group._update_transform(self._transform)
+            root_group._update_transform(self.transform)
             root_group._in_group = True
 
-        self._current_group = root_group
-        self._full_refresh = True
+        self.current_group = root_group
+        self.full_refresh = True
 
         return True
 
@@ -193,38 +211,38 @@ class _DisplayCore:
         # pylint: disable=protected-access
         """Mark the display core as currently being refreshed"""
 
-        if self._refresh_in_progress:
+        if self.refresh_in_progress:
             return False
 
-        self._refresh_in_progress = True
-        # self._last_refresh = _Supervisor()._ticks_ms64()
+        self.refresh_in_progress = True
+        self.last_refresh = time.monotonic() * 1000
         return True
 
     def finish_refresh(self) -> None:
         # pylint: disable=protected-access
         """Unmark the display core as currently being refreshed"""
 
-        if self._current_group is not None:
-            self._current_group._finish_refresh()
+        if self.current_group is not None:
+            self.current_group._finish_refresh()
 
-        self._full_refresh = False
-        self._refresh_in_progress = False
-        # self._last_refresh = _Supervisor()._ticks_ms64()
+        self.full_refresh = False
+        self.refresh_in_progress = False
+        self.last_refresh = time.monotonic() * 1000
 
     def get_refresh_areas(self) -> list:
         """Get a list of areas to be refreshed"""
         subrectangles = []
-        if self._current_group is not None:
+        if self.current_group is not None:
             # Eventually calculate dirty rectangles here
-            subrectangles.append(RectangleStruct(0, 0, self._width, self._height))
+            subrectangles.append(RectangleStruct(0, 0, self.width, self.height))
         return subrectangles
 
     def release(self) -> None:
         """Release the display from the current group"""
         # pylint: disable=protected-access
 
-        if self._current_group is not None:
-            self._current_group._in_group = False
+        if self.current_group is not None:
+            self.current_group._in_group = False
 
     def fill_area(
         self,
@@ -235,22 +253,22 @@ class _DisplayCore:
         # pylint: disable=protected-access
         """Call the current group's fill area function"""
 
-        return self._current_group._fill_area(self._colorspace, area, mask, buffer)
+        return self.current_group._fill_area(self.colorspace, area, mask, buffer)
 
     def clip_area(self, area: Area, clipped: Area) -> bool:
         """Shrink the area to the region shared by the two areas"""
         # pylint: disable=protected-access
 
-        overlaps = self._area._compute_overlap(area, clipped)
+        overlaps = self.area._compute_overlap(area, clipped)
         if not overlaps:
             return False
 
         # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
-        if self._colorspace.depth < 8:
+        if self.colorspace.depth < 8:
             pixels_per_byte = (
-                8 // self._colorspace.depth * self._colorspace.bytes_per_cell
+                8 // self.colorspace.depth * self.colorspace.bytes_per_cell
             )
-            if self._colorspace.pixels_in_byte_share_row:
+            if self.colorspace.pixels_in_byte_share_row:
                 if clipped.x1 % pixels_per_byte != 0:
                     clipped.x1 -= clipped.x1 % pixels_per_byte
                 if clipped.x2 % pixels_per_byte != 0:
@@ -290,21 +308,21 @@ class _DisplayCore:
         """
         Gets the width of the display in pixels.
         """
-        return self._width
+        return self.width
 
     def get_height(self) -> int:
         """
         Gets the height of the display in pixels.
         """
-        return self._height
+        return self.height
 
     def get_rotation(self) -> int:
         """
         Gets the rotation of the display as an int in degrees.
         """
-        return self._rotation
+        return self.rotation
 
-    def get_bus(self) -> Union[FourWire, ParallelBus, I2CDisplay]:
+    def get_bus(self) -> _DisplayBus:
         """
         The bus being used by the display. [readonly]
         """
index 42f85ecac625e38f15885d392293c537db8e2acc..b5de381edced2f74620a106204a2ffa6352bdfd8 100644 (file)
@@ -53,6 +53,14 @@ class I2CDisplay:
         self._i2c = i2c_bus
         self._dev_addr = device_address
 
+    def __new__(cls, *args, **kwargs):
+        from . import (  # pylint: disable=import-outside-toplevel, cyclic-import
+            allocate_display_bus,
+        )
+
+        allocate_display_bus(cls)
+        return super().__new__(cls)
+
     def _release(self):
         self.reset()
         self._i2c.deinit()
index ce177e80840bbd25cf89271459ee68cb65272f93..c346ceb7a97f7a704b70aa5430cc076a57319a52 100644 (file)
@@ -17,7 +17,6 @@ terminalio for Blinka
 
 """
 
-import sys  # pylint: disable=unused-import
 import fontio
 
 __version__ = "0.0.0+auto.0"
@@ -26,5 +25,7 @@ __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 FONT = fontio.BuiltinFont()
 
 # TODO: Tap into stdout to get the REPL
+# Look at how Adafruit_Python_Shell's run_command works as an option
+# Additionally, adding supervisor to Blinka may be helpful to keep track of REPL output
 # sys.stdout = open('out.dat', 'w')
 # sys.stdout.close()