From 95d33cba27d6db87dfb2e7d131455c746eb6432d Mon Sep 17 00:00:00 2001 From: bdbarnett Date: Fri, 22 Nov 2024 15:54:58 -0600 Subject: [PATCH 01/35] pydisplay initial config --- .../displaybuf/displaybuf/__init__.py | 262 +++++ .../pydisplay/displaybuf/displaybuf/_viper.py | 39 + .../displaybuf/examples/displaybuf_blit.py | 14 + .../examples/displaybuf_simpletest.py | 67 ++ micropython/pydisplay/displaybuf/manifest.py | 0 micropython/pydisplay/displaysys/README.md | 0 .../displaysys/busdisplay.py | 607 ++++++++++++ .../examples/board_config.py | 59 ++ .../displaysys-busdisplay/manifest.py | 0 .../displaysys/fbdisplay.py | 121 +++ .../examples/board_config.py | 90 ++ .../displaysys-fbdisplay/manifest.py | 0 .../displaysys/pgdisplay.py | 260 +++++ .../examples/board_config.py | 34 + .../displaysys-pgdisplay/manifest.py | 0 .../displaysys/psdisplay.py | 178 ++++ .../examples/board_config.py | 17 + .../displaysys-psdisplay/manifest.py | 0 .../displaysys/sdldisplay/__init__.py | 456 +++++++++ .../sdldisplay/_sdl2_lib/__init__.py | 17 + .../sdldisplay/_sdl2_lib/_constants.py | 246 +++++ .../sdldisplay/_sdl2_lib/_cpython.py | 304 ++++++ .../sdldisplay/_sdl2_lib/_micropython.py | 188 ++++ .../displaysys-sdldisplay/manifest.py | 0 .../displaysys/displaysys/__init__.py | 544 +++++++++++ .../displaysys/displaysys/_byteswap.py | 48 + .../displaysys/displaysys/_byteswap_viper.py | 19 + .../displaysys/displaysys/manifest.py | 2 + .../examples/displaysys_block_test.py | 46 + .../examples/displaysys_fill_rect_test.py | 38 + .../examples/displaysys_simpletest.py | 11 + .../pydisplay/eventsys/eventsys/__init__.py | 100 ++ .../pydisplay/eventsys/eventsys/device.py | 692 +++++++++++++ .../pydisplay/eventsys/eventsys/keys.py | 571 +++++++++++ .../examples/eventsys_encoder_test.py | 48 + .../eventsys/examples/eventsys_simpletest.py | 17 + .../eventsys/examples/eventsys_touch_test.py | 134 +++ micropython/pydisplay/eventsys/manifest.py | 0 micropython/pydisplay/gpio_pin/gpio_pin.py | 219 +++++ micropython/pydisplay/gpio_pin/manifest.py | 0 .../graphics/examples/graphics_area_test.py | 23 + .../graphics/examples/graphics_simpletest.py | 72 ++ .../pydisplay/graphics/graphics/__init__.py | 79 ++ .../pydisplay/graphics/graphics/_area.py | 272 ++++++ .../pydisplay/graphics/graphics/_draw.py | 97 ++ .../pydisplay/graphics/graphics/_files.py | 86 ++ .../pydisplay/graphics/graphics/_font.py | 373 ++++++++ .../pydisplay/graphics/graphics/_framebuf.py | 602 ++++++++++++ .../graphics/graphics/_framebuf_plus.py | 627 ++++++++++++ .../pydisplay/graphics/graphics/_shapes.py | 905 ++++++++++++++++++ .../pydisplay/graphics/graphics/font_8x14.py | 259 +++++ .../pydisplay/graphics/graphics/font_8x16.py | 259 +++++ .../pydisplay/graphics/graphics/font_8x8.py | 259 +++++ micropython/pydisplay/graphics/manifest.py | 0 micropython/pydisplay/i80bus/manifest.py | 0 .../palettes/examples/palette_cube.py | 47 + .../palettes/examples/palette_material.py | 22 + .../palettes/examples/palette_wheel.py | 31 + micropython/pydisplay/palettes/manifest.py | 0 .../pydisplay/palettes/palettes/__init__.py | 178 ++++ .../pydisplay/palettes/palettes/_cube125.py | 131 +++ .../pydisplay/palettes/palettes/_cube27.py | 35 + .../pydisplay/palettes/palettes/_cube64.py | 70 ++ .../pydisplay/palettes/palettes/_cube8.py | 12 + .../palettes/palettes/_material_design.py | 329 +++++++ .../pydisplay/palettes/palettes/cube.py | 54 ++ .../palettes/palettes/material_design.py | 88 ++ .../pydisplay/palettes/palettes/wheel.py | 107 +++ micropython/pydisplay/spibus/manifest.py | 0 micropython/pydisplay/spibus/spibus.py | 149 +++ .../timer/examples/timer_simpletest.py | 32 + micropython/pydisplay/timer/manifest.py | 0 micropython/pydisplay/timer/timer/__init__.py | 59 ++ micropython/pydisplay/timer/timer/_librt.py | 155 +++ micropython/pydisplay/timer/timer/_sdl2.py | 49 + .../pydisplay/timer/timer/_timerbase.py | 121 +++ 76 files changed, 11030 insertions(+) create mode 100644 micropython/pydisplay/displaybuf/displaybuf/__init__.py create mode 100644 micropython/pydisplay/displaybuf/displaybuf/_viper.py create mode 100644 micropython/pydisplay/displaybuf/examples/displaybuf_blit.py create mode 100644 micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py create mode 100644 micropython/pydisplay/displaybuf/manifest.py create mode 100644 micropython/pydisplay/displaysys/README.md create mode 100644 micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py create mode 100644 micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py create mode 100644 micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py create mode 100644 micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py create mode 100644 micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py create mode 100644 micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py create mode 100755 micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py create mode 100644 micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py create mode 100644 micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py create mode 100644 micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py create mode 100644 micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py create mode 100644 micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py create mode 100755 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py create mode 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/__init__.py create mode 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py create mode 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py create mode 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py create mode 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py create mode 100644 micropython/pydisplay/displaysys/displaysys/manifest.py create mode 100644 micropython/pydisplay/displaysys/examples/displaysys_block_test.py create mode 100644 micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py create mode 100644 micropython/pydisplay/displaysys/examples/displaysys_simpletest.py create mode 100644 micropython/pydisplay/eventsys/eventsys/__init__.py create mode 100644 micropython/pydisplay/eventsys/eventsys/device.py create mode 100755 micropython/pydisplay/eventsys/eventsys/keys.py create mode 100644 micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py create mode 100644 micropython/pydisplay/eventsys/examples/eventsys_simpletest.py create mode 100644 micropython/pydisplay/eventsys/examples/eventsys_touch_test.py create mode 100644 micropython/pydisplay/eventsys/manifest.py create mode 100644 micropython/pydisplay/gpio_pin/gpio_pin.py create mode 100644 micropython/pydisplay/gpio_pin/manifest.py create mode 100644 micropython/pydisplay/graphics/examples/graphics_area_test.py create mode 100644 micropython/pydisplay/graphics/examples/graphics_simpletest.py create mode 100644 micropython/pydisplay/graphics/graphics/__init__.py create mode 100644 micropython/pydisplay/graphics/graphics/_area.py create mode 100644 micropython/pydisplay/graphics/graphics/_draw.py create mode 100644 micropython/pydisplay/graphics/graphics/_files.py create mode 100644 micropython/pydisplay/graphics/graphics/_font.py create mode 100644 micropython/pydisplay/graphics/graphics/_framebuf.py create mode 100644 micropython/pydisplay/graphics/graphics/_framebuf_plus.py create mode 100644 micropython/pydisplay/graphics/graphics/_shapes.py create mode 100644 micropython/pydisplay/graphics/graphics/font_8x14.py create mode 100644 micropython/pydisplay/graphics/graphics/font_8x16.py create mode 100644 micropython/pydisplay/graphics/graphics/font_8x8.py create mode 100644 micropython/pydisplay/graphics/manifest.py create mode 100644 micropython/pydisplay/i80bus/manifest.py create mode 100644 micropython/pydisplay/palettes/examples/palette_cube.py create mode 100644 micropython/pydisplay/palettes/examples/palette_material.py create mode 100644 micropython/pydisplay/palettes/examples/palette_wheel.py create mode 100644 micropython/pydisplay/palettes/manifest.py create mode 100644 micropython/pydisplay/palettes/palettes/__init__.py create mode 100644 micropython/pydisplay/palettes/palettes/_cube125.py create mode 100644 micropython/pydisplay/palettes/palettes/_cube27.py create mode 100644 micropython/pydisplay/palettes/palettes/_cube64.py create mode 100644 micropython/pydisplay/palettes/palettes/_cube8.py create mode 100644 micropython/pydisplay/palettes/palettes/_material_design.py create mode 100644 micropython/pydisplay/palettes/palettes/cube.py create mode 100644 micropython/pydisplay/palettes/palettes/material_design.py create mode 100644 micropython/pydisplay/palettes/palettes/wheel.py create mode 100644 micropython/pydisplay/spibus/manifest.py create mode 100644 micropython/pydisplay/spibus/spibus.py create mode 100644 micropython/pydisplay/timer/examples/timer_simpletest.py create mode 100644 micropython/pydisplay/timer/manifest.py create mode 100644 micropython/pydisplay/timer/timer/__init__.py create mode 100644 micropython/pydisplay/timer/timer/_librt.py create mode 100755 micropython/pydisplay/timer/timer/_sdl2.py create mode 100644 micropython/pydisplay/timer/timer/_timerbase.py diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py new file mode 100644 index 000000000..d7550f1b7 --- /dev/null +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -0,0 +1,262 @@ +# SPDX-FileCopyrightText: 2020 Peter Hinch, 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`displaybuf` +==================================================== + +FrameBuffer wrapper for using framebuf based GUIs with pydisplay. +Works with MicroPython Nano-GUI, Micro-GUI and MicroPython-Touch from Peter Hinch, +but may also be used without them. + +Usage: + 'color_setup.py' + from displaybuf import DisplayBuffer as SSD + from board_config import display_drv + + format = SSD.RGB565 # or .GS8 or .GS4_HMSB + + ssd = SSD(display_drv, format) + + 'main.py' + from color_setup import ssd + +""" + +import gc +import sys +from displaysys import color565, color565_swapped, color332 + +try: + import graphics as framebuf +except ImportError: + import framebuf # type: ignore + +if sys.implementation.name == "micropython": + from ._viper import _bounce8, _bounce4 +else: + + def _bounce8(*args, **kwargs): + raise NotImplementedError( + ".GS8 and .GS4_HMSB DisplayBuffer formats are only implemented for MicroPython." + ) + + _bounce4 = _bounce8 + + +gc.collect() + + +def alloc_buffer(size): + return memoryview(bytearray(size)) + + +_display_drv_get_attrs = {"set_vscroll", "tfa", "bfa", "vsa", "vscroll", "translate_point"} +_display_drv_set_attrs = {"vscroll"} + + +class DisplayBuffer(framebuf.FrameBuffer): + """ + DisplayBuffer: A class to wrap an pydisplay driver and provide a framebuf + compatible interface to it. It provides a show() method to copy the framebuf + to the display. The show() method is optimized for the format. + The format must be one of the following: + DisplayBuffer.RGB565 + DisplayBuffer.GS8 + DisplayBuffer.GS4_HMSB + """ + + rgb = None # Function to convert r, g, b to a color value; used by Nano-GUI and Micro-GUI. + colors_registered = 0 # For .color(). Not used in Nano-GUI or Micro-GUI. + + RGB565 = framebuf.RGB565 + GS8 = framebuf.GS8 + GS4_HMSB = framebuf.GS4_HMSB + + def __init__(self, display_drv, format=framebuf.RGB565, stride=8): + gc.collect() + self.display_drv = display_drv + self.vscrdef = display_drv.vscrdef + self.vscsad = display_drv.vscsad + height = display_drv.height + width = display_drv.width + BPP = display_drv.color_depth // 8 + self.palette = BoolPalette( + format + ) # a 2-value color palette for rendering monochrome glyphs + # with ssd.blit(glyph_buf, x, y, key=-1, palette=ssd.palette) + + # If byte swapping is required and the display bus is capable of having byte swapping disabled, + # disable it and set a flag so we can swap the color bytes as they are created. + if self.display_drv.requires_byteswap: + # self.display_drv.disable_auto_byteswap(True) returns True if it was successful, False if not. + self.needs_swap = self.display_drv.disable_auto_byteswap(True) + else: + self.needs_swap = False + + # Set the DisplayBuffer.rgb function to the appropriate one for the format and byte swapping + if format == DisplayBuffer.GS8: + DisplayBuffer.rgb = color332 + else: + if self.needs_swap: + DisplayBuffer.rgb = color565_swapped + else: + DisplayBuffer.rgb = color565 + + # Set the show function to the appropriate one for the format and + # allocate the buffer. Also create the line buffer and lut if needed. + gc.collect() + if format == DisplayBuffer.RGB565: + buffer = bytearray(width * height * BPP) + self.show = self._show16 + elif format == DisplayBuffer.GS8: + self._stride = stride + self._bounce_buf = alloc_buffer(width * self._stride * BPP) + buffer = bytearray(width * height) + self.show = self._show8 + elif format == DisplayBuffer.GS4_HMSB: + DisplayBuffer.lut = bytearray(0x00 for _ in range(32)) + self._stride = stride + self._bounce_buf = alloc_buffer(width * self._stride * BPP) + buffer = bytearray(width * height // 2) + self.show = self._show4 + else: + raise ValueError(f"Unsupported format: {format}") + + super().__init__(buffer, width, height, format) + self._mvb = memoryview(self.buffer) + self.show() # Clear the display + gc.collect() + + def __getattr__(self, name): + if name in _display_drv_get_attrs: + return getattr(self.display_drv, name) + raise AttributeError(f"{self.__class__.__name__} object has no attribute '{name}'") + + def __setattr__(self, name, value): + if name in _display_drv_set_attrs: + return setattr(self.display_drv, name, value) + super().__setattr__(name, value) + + @property + def color_palette(self): + return self._color_palette + + @color_palette.setter + def color_palette(self, palette): + if len(palette) > 16: + raise ValueError("Palette must be 16 colors or less") + self._color_palette = palette + for index, color in enumerate(palette): + r, g, b = color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF + self.color(r, g, b, index) + + @staticmethod + def color(r, g, b, idx=None): + """ + Get an RGB565 or RGB332 value for a color and optionally register it in the display's LUT. + This is a convenience function for using this framework WITHOUT Nano-GUI or Micro-GUI. + Those packages have their own methods of registering colors. + + Args: + r (int): Red component (0-255) + g (int): Green component (0-255) + b (int): Blue component (0-255) + idx (int): Optional index to register the color in the display's LUT (0-15); + ignored if the display doesn't use a LUT in its current format + + Raises: + ValueError: If 16 colors have already been registered or if the index is out of range + + Returns: + (int): RGB565 color value in RG565 format; + RGB332 color value in GS8 format; + the index of the registered color in the LUT in GS4_HMSB format + """ + c = DisplayBuffer.rgb(r, g, b) # Convert the color to RGB565 or RGB332 + if not hasattr(DisplayBuffer, "lut"): # If the ssd doesn't use a LUT in its current format + return c # Return the color as-is + if idx is None: # If no index was provided + if ( + DisplayBuffer.colors_registered < 16 + ): # If there are fewer than 16 colors registered + idx = DisplayBuffer.colors_registered # Set the index to the next index + DisplayBuffer.colors_registered += 1 # Increment the number of colors registered + else: # If there are already 16 colors registered + raise ValueError("16 colors have already been registered") + if not 0 <= idx <= 15: # If the index is out of range + raise ValueError("Color numbers must be 0..15") + offset = idx << 1 # Multiply by 2 (2 bytes per 16-bit color) + DisplayBuffer.lut[offset] = c & 0xFF # Set the lower 8 bits of the color + DisplayBuffer.lut[offset + 1] = c >> 8 # Set the upper 8 bits of the color + return idx # Return the index of the registered color + + def _show16(self, area=None): + if area is not None: + x, y, w, h = area + for row in range(y, y + h): + buffer_begin = (row * self.width + x) * 2 + buffer_end = buffer_begin + w * 2 + self.display_drv.blit_rect(self.buffer[buffer_begin:buffer_end], x, row, w, 1) + else: + self.display_drv.blit_rect(self.buffer, 0, 0, self.width, self.height) + + def _show8(self, area=None): + # Note: area is ignored for now in _show8 + # Convert the 8 bit RGB332 values to 16 bit RGB565 values and then copy the line + # to the display, line by line. + swap = self.needs_swap + buf = self._mvb + bb = self._bounce_buf + wd = self.width + ht = self.height + lines = self._stride + stride = lines * wd + chunks, remainder = divmod(ht, lines) + for chunk in range(chunks): + start = chunk * stride + _bounce8(bb, buf[start:], stride, swap) + self.display_drv.blit_rect(bb, 0, chunk * lines, wd, lines) + if remainder: + start = chunks * stride + _bounce8(bb, buf[start:], remainder * wd, swap) + self.display_drv.blit_rect(bb, 0, chunks * lines, wd, remainder) + + def _show4(self, area=None): + # Note: area is ignored for now in _show4 + # Convert the 4 bit index values to 16 bit RGB565 values using a lookup table + # and then copy the line to the display, line by line. + clut = DisplayBuffer.lut + buf = self._mvb + bb = self._bounce_buf + wd = self.width + ht = self.height + lines = self._stride + stride = lines * wd // 2 # 2 pixels per byte + chunks, remainder = divmod(ht, lines) + for chunk in range(chunks): + start = chunk * stride + _bounce4(bb, buf[start:], stride, clut) + self.display_drv.blit_rect(bb, 0, chunk * lines, wd, lines) + if remainder: + start = chunks * stride + _bounce4(bb, buf[start:], remainder * wd // 2, clut) + self.display_drv.blit_rect(bb, 0, chunks * lines, wd, remainder) + + +class BoolPalette(framebuf.FrameBuffer): + # This is a 2-value color palette for rendering monochrome glyphs to color + # FrameBuffer instances. Supports destinations with up to 16 bit color. + + # Copyright (c) Peter Hinch 2021 + # Released under the MIT license see LICENSE + def __init__(self, format): + buf = bytearray(4) # OK for <= 16 bit color + super().__init__(buf, 2, 1, format) + + def fg(self, color): # Set foreground color + self.pixel(1, 0, color) + + def bg(self, color): + self.pixel(0, 0, color) diff --git a/micropython/pydisplay/displaybuf/displaybuf/_viper.py b/micropython/pydisplay/displaybuf/displaybuf/_viper.py new file mode 100644 index 000000000..5da3c6bcb --- /dev/null +++ b/micropython/pydisplay/displaybuf/displaybuf/_viper.py @@ -0,0 +1,39 @@ +import micropython + + +@micropython.viper +def _bounce8(dest: ptr8, source: ptr8, length: int, swap: bool): # noqa: F821 + # Convert a line in 8 bit RGB332 format to 16 bit RGB565 format. + # Each byte becomes 2 in destination. Source format: + # + # dest: + # swap==False: <00 00 00 B1 B0 00 00 00> + # swap==True: <00 00 00 B1 B0 00 00 00> + + if swap: + lsb = 0 + msb = 1 + else: + lsb = 1 + msb = 0 + n = 0 + for x in range(length): + c = source[x] + dest[n + lsb] = (c & 0xE0) | ((c & 0x1C) >> 2) # Red Green + dest[n + msb] = (c & 0x03) << 3 # Blue + n += 2 + + +@micropython.viper +def _bounce4(dest: ptr16, source: ptr8, length: int, lut: ptr16): # noqa: F821 + # Convert a line in 4+4 bit index format to two * 16 bit RGB565 format + # using a color lookup table. Each byte becomes 4 in destination. + # Source format: + # Dest format: the same as self.rgb * 2 + n = 0 + for x in range(length): + c = source[x] # Get the indices of the 2 pixels + dest[n] = lut[c >> 4] # lookup top 4 bits for even pixels + n += 1 + dest[n] = lut[c & 0x0F] # lookup bottom 4 bits for odd pixels + n += 1 diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py new file mode 100644 index 000000000..27c1a0194 --- /dev/null +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py @@ -0,0 +1,14 @@ +from color_setup import ssd +from framebuf import FrameBuffer, RGB565 + + +ssd.fill(0xF800) +ssd.show() + +ba = bytearray(100*100*2) +mv = memoryview(ba) +fb = FrameBuffer(mv, 100, 100, RGB565) +fb.fill(0x000F) + +ssd.blit(fb, 100, 100) +ssd.show() \ No newline at end of file diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py new file mode 100644 index 000000000..29b858875 --- /dev/null +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py @@ -0,0 +1,67 @@ +""" +displaybuf_simpletest.py - Simple test program for displaybuf.py +""" + +from color_setup import ssd +from array import array # for defining a polygon + + +FONT_WIDTH = 8 + +# Define colors (max 16 colors if using lookup tables / GS4_HMSB mode) +# Note: ssd.color and ssd.colors_registered should not be used with Nano-GUI or +# Micro-GUI because those packages have their own mechanisms for managing colors. +WHITE = ssd.color(255, 255, 255) +RED = ssd.color(255, 0, 0) +GREEN = ssd.color(0, 255, 0) +BLUE = ssd.color(0, 0, 255) +CYAN = ssd.color(0, 255, 255) +MAGENTA = ssd.color(255, 0, 255) +YELLOW = ssd.color(255, 255, 0) +BLACK = ssd.color(0, 0, 0) +LIGHT_GREY = ssd.color(192, 192, 192) +GREY = ssd.color(96, 96, 96) +DARK_GREY = ssd.color(64, 64, 64) +GREY = ssd.color( + 128, 128, 128, GREY +) # Example of how to redefine a color in the lookup table +if ssd.colors_registered: # Will be 0 if not using lookup tables / GS4_HMSB mode. + print(f"{ssd.colors_registered} colors registered.") + + +# Main loop +def main(scroll=False, animate=False, text1="displaybuf", text2="simpletest"): + WIDTH = ssd.width + HEIGHT = ssd.height + poly = array("h", [0, 0, WIDTH // 2, -HEIGHT // 4, WIDTH - 1, 0]) + y_range = range(HEIGHT - 1, -1, -1) if animate else [HEIGHT - 1] + for y in y_range: + ssd.fill(BLACK) + ssd.poly(0, y, poly, YELLOW, True) + ssd.fill_rect(WIDTH // 6, HEIGHT // 3, WIDTH * 2 // 3, HEIGHT // 3, GREY) + ssd.line(0, 0, WIDTH - 1, HEIGHT - 1, GREEN) + ssd.rect(0, 0, 15, 15, RED, True) + ssd.rect(WIDTH - 15, HEIGHT - 15, 15, 15, BLUE, True) + ssd.hline(WIDTH // 8, HEIGHT // 2, WIDTH * 3 // 4, MAGENTA) + ssd.vline(WIDTH // 2, HEIGHT // 4, HEIGHT // 2, CYAN) + ssd.pixel(WIDTH // 2, HEIGHT * 1 // 8, WHITE) + ssd.ellipse( + WIDTH // 2, HEIGHT // 2, WIDTH // 4, HEIGHT // 8, BLACK, True, 0b1111 + ) + ssd.text(text1, (WIDTH - FONT_WIDTH * len(text1)) // 2, HEIGHT // 2 - 8, WHITE) + ssd.text(text2, (WIDTH - FONT_WIDTH * len(text2)) // 2, HEIGHT // 2, WHITE) + ssd.show() + + ssd.hline(0, 0, WIDTH, BLACK) + ssd.vline(0, 0, HEIGHT, BLACK) + + scroll_range = range(min(WIDTH, HEIGHT)) if scroll else [] + for _ in scroll_range: + ssd.scroll(1, 1) + ssd.show() + + +launch = lambda: main(animate=True) +wipe = lambda: main(scroll=True) + +main() diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/README.md b/micropython/pydisplay/displaysys/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py new file mode 100644 index 000000000..908514dbe --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py @@ -0,0 +1,607 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett and Kevin Schlosser +# +# SPDX-License-Identifier: MIT + +""" +pydisplay busdisplay +""" + +from displaycore import DisplayDriver +from micropython import const +import struct +import sys +import gc + +try: + from typing import Optional +except ImportError: + pass + +if sys.implementation.name == "micropython": + from machine import Pin + from time import sleep_ms + from micropython import alloc_emergency_exception_buf + + alloc_emergency_exception_buf(256) +elif sys.implementation.name == "circuitpython": + import digitalio + from time import sleep + + def sleep_ms(ms): + return sleep(ms / 1000) +else: + raise ImportError("BusDisplay is not supported on this platform.") + + +gc.collect() + +# MIPI DCS (Display Command Set) Command Constants +_INVOFF = const(0x20) +_INVON = const(0x21) +_CASET = const(0x2A) +_RASET = const(0x2B) +_RAMWR = const(0x2C) +_COLMOD = const(0x3A) +_MADCTL = const(0x36) +_RAMCONT = const(0x3C) +_SWRESET = const(0x01) +_SLPIN = const(0x10) +_SLPOUT = const(0x11) +_VSCRDEF = const(0x33) +_VSCSAD = const(0x37) + +# fmt: off + +# MIPI DCS MADCTL bits +# Bits 0 (Flip Vertical) and 1 (Flip Horizontal) affect how the display is refreshed, not how frame memory is written. +# Instead of using them, we only change Bits 6 (column/horizontal) and 7 (page/vertical). +_RGB = const(0x00) # (Bit 3: 0=RGB order, 1=BGR order) +_BGR = const(0x08) # (Bit 3: 0=RGB order, 1=BGR order) +_MADCTL_MH = const(0x04) # Refresh 0=Left to Right, 1=Right to Left (Bit 2: Display Data Latch Order) +_MADCTL_ML = const(0x10) # Refresh 0=Top to Bottom, 1=Bottom to Top (Bit 4: Line Refresh Order) +_MADCTL_MV = const(0x20) # 0=Normal, 1=Row/column exchange (Bit 5: Page/Column Addressing Order) +_MADCTL_MX = const(0x40) # 0=Left to Right, 1=Right to Left (Bit 6: Column Address Order) +_MADCTL_MY = const(0x80) # 0=Top to Bottom, 1=Bottom to Top (Bit 7: Page Address Order) + +# MADCTL values for each of the rotation constants. +_DEFAULT_ROTATION_TABLE = ( + _MADCTL_MX, # mirrored = False, rotation = 0 + _MADCTL_MV, # mirrored = False, rotation = 90 + _MADCTL_MY, # mirrored = False, rotation = 180 + _MADCTL_MY | _MADCTL_MX | _MADCTL_MV, # mirrored = False, rotation = 270 +) + +_MIRRORED_ROTATION_TABLE = ( + 0, # mirrored = True, rotation = 0 + _MADCTL_MV | _MADCTL_MX, # mirrored = True, rotation = 90 + _MADCTL_MX | _MADCTL_MY, # mirrored = True, rotation = 180 + _MADCTL_MV | _MADCTL_MY, # mirrored = True, rotation = 270 +) +# fmt: on + + +class BusDisplay(DisplayDriver): + """ + Base class for displays connected via a bus. + + Args: + display_bus (SPIBus, I80Bus): The bus the display is connected to. + init_sequence (bytes, list): The initialization sequence for the display. + width (int): The width of the display in pixels. + height (int): The height of the display in pixels. + colstart (int): The column start address for the display. + rowstart (int): The row start address for the display. + rotation (int): The rotation of the display in degrees. + mirrored (bool): If True, the display is mirrored. + color_depth (int): The color depth of the display in bits. + bgr (bool): If True, the display uses BGR color order. + invert (bool): If True, the display colors are inverted. + reverse_bytes_in_word (bool): If True, the bytes in 16-bit colors are reversed. + brightness (float): The brightness of the display as a float between 0.0 and 1.0. + backlight_pin (int, Pin): The pin the display backlight is connected to. + backlight_on_high (bool): If True, the backlight is on when the pin is high. + reset_pin (int, Pin): The pin the display reset is connected to. + reset_high (bool): If True, the reset pin is high. + power_pin (int, Pin): The pin the display power is connected to. + power_on_high (bool): If True, the power pin is high. + set_column_command (int): The command to set the column address. + set_row_command (int): The command to set the row address. + write_ram_command (int): The command to write to the display RAM. + brightness_command (int): The command to set the display brightness. + data_as_commands (bool): If True, data is sent as commands. + single_byte_bounds (bool): If True, single byte bounds are used. + + Attributes: + display_bus (SPIBus, I80Bus): The bus the display is connected to. + color_depth (int): The color depth of the display in bits. + bgr (bool): If True, the display uses BGR color order. + rotation_table (tuple): The rotation table for the display. + """ + + def __init__( + self, + display_bus, + init_sequence=None, + *, + width=0, + height=0, + colstart=0, + rowstart=0, + rotation=0, + mirrored=False, + color_depth=16, + bgr=False, + invert=False, + reverse_bytes_in_word=False, + brightness=1.0, + backlight_pin=None, + backlight_on_high=True, + reset_pin=None, + reset_high=True, + power_pin=None, + power_on_high=True, + set_column_command=_CASET, + set_row_command=_RASET, + write_ram_command=_RAMWR, + brightness_command=None, # For color OLEDs + data_as_commands=False, # For color OLEDs + single_byte_bounds=False, # For color OLEDs + ): + print(f"Started BusDisplay") + gc.collect() + self.display_bus = display_bus + self._width = width + self._height = height + self._colstart = colstart + self._rowstart = rowstart + self._rotation = rotation + self.color_depth = color_depth + self.bgr = bgr + self._invert = invert + self._requires_byteswap = reverse_bytes_in_word + 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 # not implemented + self._single_byte_bounds = single_byte_bounds # not implemented + + self.send = display_bus.send + self.send_color = ( + display_bus.send if not hasattr(display_bus, "send_color") else display_bus.send_color + ) + + self.rotation_table = _DEFAULT_ROTATION_TABLE if not mirrored else _MIRRORED_ROTATION_TABLE + + self._param_buf = bytearray(4) + self._param_mv = memoryview(self._param_buf) + + self._reset_pin = self._config_output_pin(reset_pin, value=not reset_high) + self._reset_high = reset_high + + self._power_pin = self._config_output_pin(power_pin, value=power_on_high) + self._power_on_high = power_on_high + + self._backlight_pin = self._config_output_pin(backlight_pin, value=backlight_on_high) + self._backlight_on_high = backlight_on_high + + if self._backlight_pin is not None: + try: + from machine import PWM + + self._backlight_pin = PWM(self._backlight_pin, freq=1000, duty_u16=0) + self._backlight_is_pwm = True + except ImportError: + # PWM not implemented on this platform or Pin + self._backlight_is_pwm = False + + # Run the display driver init_sequence. + if type(init_sequence) is bytes: + self._init_bytes(init_sequence) + elif type(init_sequence) is list or type(init_sequence) is tuple: + self._init_list(init_sequence) + + # Run the display driver init() method, which also gets called by rotation.setter + # This should run immediately after _init_bytes() or _init_list() but before + # sending other commands such as _INVON, _INVOFF, _COLMOD, brightness, etc. + self._initialized = False + super().__init__() + if not self._initialized: + raise RuntimeError("Display driver init() must call super().init()") + + # Set COLMOD (color mode) based on color_depth + pixel_formats = {3: 0x11, 8: 0x22, 12: 0x33, 16: 0x55, 18: 0x66, 24: 0x77} + self._param_buf[0] = pixel_formats[self.color_depth] + self.send(_COLMOD, self._param_mv[:1]) + + self.brightness = brightness + + gc.collect() + print(f"Finished BusDisplay") + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Post initialization tasks. + + This method may be overridden by subclasses to perform any post initialization. + If it is overridden, it must call super().init() or set self._initialized = True. + """ + self._initialized = True + + # Convert from degrees to one quarter rotations. Wrap at the number of entries in the rotations table. + # For example, rotation = 90 -> index = 1. With 4 entries in the rotation table, rotation = 540 -> index = 2 + index = (self._rotation // 90) % len(self.rotation_table) + + # Set the display MADCTL bits for the given rotation. + self._param_buf[0] = self.rotation_table[index] | _BGR if self.bgr else _RGB + self.send(_MADCTL, self._param_mv[:1]) + + # Set the display inversion mode + self.invert_colors(self._invert) + + def blit_rect(self, buf: memoryview, x: int, y: int, w: int, h: int): + """ + Blit a buffer to the display. + + This method takes a buffer of pixel data and writes it to a specified + rectangular area of the display. The top-left corner of the rectangle is + specified by the x and y parameters, and the size of the rectangle is + specified by the width and height parameters. + + Args: + buf (memoryview): The buffer containing the pixel data. + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle in pixels. + h (int): The height of the rectangle in pixels. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the rectangle. + """ + if self._auto_byteswap: + self.byteswap(buf) + + x1 = x + self.colstart + x2 = x1 + w - 1 + y1 = y + self.rowstart + y2 = y1 + h - 1 + + self._set_window(x1, y1, x2, y2) + self.send_color(self._write_ram_command, buf) + return (x, y, w, h) + + def fill_rect(self, x: int, y: int, w: int, h: int, c: int): + """ + Draw a rectangle at the given location, size and filled with color. + + This method draws a filled rectangle on the display. The top-left corner of + the rectangle is specified by the x and y parameters, and the size of the + rectangle is specified by the width and height parameters. The rectangle is + filled with the specified color. + + Args: + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle in pixels. + h (int): The height of the rectangle in pixels. + c (int): The color of the rectangle. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the rectangle. + """ + color_bytes = ( + (c & 0xFFFF).to_bytes(2, "big") + if self._auto_byteswap + else (c & 0xFFFF).to_bytes(2, "little") + ) + x1 = x + self.colstart + x2 = x1 + w - 1 + y1 = y + self.rowstart + y2 = y1 + h - 1 + + if h > w: + buf = memoryview(bytearray(color_bytes * h)) + passes = w + else: + buf = memoryview(bytearray(color_bytes * w)) + passes = h + + self._set_window(x1, y1, x2, y2) + self.send(_RAMWR) + for _ in range(passes): + self.send_color(_RAMCONT, buf) + return (x, y, w, h) + + def pixel(self, x: int, y: int, c: int): + """ + Set a pixel on the display. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the pixel. + """ + color_bytes = ( + (c & 0xFFFF).to_bytes(2, "big") + if self._auto_byteswap + else (c & 0xFFFF).to_bytes(2, "little") + ) + if self._auto_byteswap: + c = c >> 8 | c << 8 + xpos = x + self.colstart + ypos = y + self.rowstart + self._set_window(xpos, ypos, xpos, ypos) + self.send(_RAMWR, color_bytes) + return (x, y, 1, 1) + + ############### API Method Overrides ################ + + def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: + """ + Set Vertical Scrolling Definition. + + To scroll a 135x240 display these values should be 40, 240, 40. + There are 40 lines above the display that are not shown followed by + 240 lines that are shown followed by 40 more lines that are not shown. + You could write to these areas off display and scroll them into view by + changing the TFA, VSA and BFA values. + + Args: + tfa (int): Top Fixed Area. + vsa (int): Vertical Scrolling Area. + bfa (int): Bottom Fixed Area. + """ + super().vscrdef(tfa, vsa, bfa) + self.send(_VSCRDEF, struct.pack(">HHH", tfa, vsa, bfa)) + + def vscsad(self, vssa: Optional[int] = None) -> int: + """ + Set the vertical scroll start address. + + Args: + vssa (int, None): The vertical scroll start address. + + Returns: + int: The vertical scroll start address. + """ + if vssa is not None: + super().vscsad(vssa) + self.send(_VSCSAD, struct.pack(">H", self._vssa)) + return self._vssa + + ############### Optional API Methods ################ + + @property + def colstart(self): + """ + The offset in pixels to the first column of the visible display. + """ + rot = self.rotation % 360 + if rot == 0 or rot == 180: + return self._colstart + return self._rowstart + + @property + def rowstart(self): + """ + The offset in pixels to the first row of the visible display. + """ + rot = self.rotation % 360 + if rot == 0 or rot == 180: + return self._rowstart + return self._colstart + + @property + def power(self) -> bool: + """ + The power state of the display. + + Returns: + bool: The power state of the display. + """ + if self._power_pin is None: + return -1 + + state = self._power_pin.value() + if self._power_on_high: + return state + + return not state + + @power.setter + def power(self, value: bool) -> None: + """ + Set the power state of the display. + + Args: + value (bool): The power state to set, True for on, False for off. + """ + if self._power_pin is None: + return + + if self._power_on_high: + self._power_pin.value(value) + else: + self._power_pin.value(not value) + + @property + def brightness(self) -> float: + """ + The brightness of the display. + """ + if self._backlight_pin is None and self._brightness_command is None: + return -1 + + return self._brightness + + @brightness.setter + def brightness(self, value: float) -> None: + """ + Set the brightness of the display. + + Args: + value (float): The brightness of the display as a float between 0.0 and 1.0. + """ + if 0 <= float(value) <= 1.0: + self._brightness = value + if self._backlight_pin: + if not self._backlight_on_high: + value = 1.0 - value + if self._backlight_is_pwm: + if sys.implementation.name == "micropython": + self._backlight_pin.duty_u16(int(value * 0xFFFF)) + elif sys.implementation.name == "circuitpython": + self._backlight_pin.duty_cycle = int(value * 0xFFFF) + else: + if sys.implementation.name == "micropython": + self._backlight_pin.value(value > 0.5) + elif sys.implementation.name == "circuitpython": + self._backlight_pin.value = value > 0.5 + elif self._brightness_command is not None: + self._param_buf[0] = int(value * 255) + self.send(self._brightness_command, self._param_mv[:1]) + + def invert_colors(self, value: bool) -> None: + """ + Invert the colors of the display. + + Args: + value (bool): If True, invert the colors of the display. + """ + if value: + self.send(_INVON) + else: + self.send(_INVOFF) + + def reset(self) -> None: + """ + Reset display. + + This method resets the display. If the display has a reset pin, it is + reset using the reset pin. Otherwise, the display is reset using the + software reset command. + """ + if self._reset_pin is not None: + self.hard_reset() + else: + self.soft_reset() + + def hard_reset(self) -> None: + """ + Hard reset display. + """ + self._reset_pin.value(self._reset_high) + sleep_ms(120) + self._reset_pin.value(not self._reset_high) + + def soft_reset(self) -> None: + """ + Soft reset display. + """ + self.send(_SWRESET) + sleep_ms(150) + + def sleep_mode(self, value: bool) -> None: + """ + Enable or disable display sleep mode. + + Args: + value (bool): If True, enable sleep mode. If False, disable sleep mode. + """ + self.send(_SLPIN if value else _SLPOUT) + + ############### Class Specific Methods ############## + + def _set_window(self, x1, y1, x2, y2): + # See https://github.com/adafruit/Adafruit_Blinka_Displayio/blob/main/displayio/_displaycore.py#L271-L363 + # TODO: Add `if self._single_byte_bounds is True:` for Column and Row _param_buf packing + + # Column addresses + self._param_buf[0] = (x1 >> 8) & 0xFF + self._param_buf[1] = x1 & 0xFF + self._param_buf[2] = (x2 >> 8) & 0xFF + self._param_buf[3] = x2 & 0xFF + self.send(self._set_column_command, self._param_mv[:4]) + + # Row addresses + self._param_buf[0] = (y1 >> 8) & 0xFF + self._param_buf[1] = y1 & 0xFF + self._param_buf[2] = (y2 >> 8) & 0xFF + self._param_buf[3] = y2 & 0xFF + self.send(self._set_row_command, self._param_mv[:4]) + + def _init_bytes(self, init_sequence): + """ + Send an initialization sequence to the display. + + Used by display driver subclass if init_sequence is a CircuitPython displayIO compatible bytes object. + The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins + with a command byte followed by a byte to determine the parameter count and if a + delay is need after. When the top bit of the second byte is 1, the next byte will be + the delay time in milliseconds. The remaining 7 bits are the parameter count + excluding any delay byte. The third through final bytes are the remaining command + parameters. The next byte will begin a new command definition. + + Args: + init_sequence (bytes): The initialization sequence to send to the display. + """ + DELAY = 0x80 + + i = 0 + while i < len(init_sequence): + command = init_sequence[i] + data_size = init_sequence[i + 1] + delay = (data_size & DELAY) != 0 + data_size &= ~DELAY + + self.send(command, init_sequence[i + 2 : i + 2 + data_size]) + + delay_time_ms = 10 + if delay: + data_size += 1 + delay_time_ms = init_sequence[i + 1 + data_size] + if delay_time_ms == 255: + delay_time_ms = 500 + + sleep_ms(delay_time_ms) + i += 2 + data_size + + def _init_list(self, init_sequence): + """ + Send an initialization sequence to the display. + + Used by display driver subclass if init_sequence is a list of tuples. + As a list, it can be modified in .init(), for example: + self._INIT_SEQUENCE[-1] = (0x29, b"\x00", 100) + Each tuple contains the following: + - The first element is the register address (command) + - The second element is the register value (data) + - The third element is the delay in milliseconds after the register is set + + Args: + init_sequence (list): The initialization sequence to send to the display + """ + for line in init_sequence: + self.send(line[0], line[1]) + if line[2] != 0: + sleep_ms(line[2]) + + def _config_output_pin(self, pin, value=None): + if pin is None: + return None + + if sys.implementation.name == "micropython": + p = Pin(pin, Pin.OUT) + if value is not None: + p.value(value) + elif sys.implementation.name == "circuitpython": + p = digitalio.DigitalInOut(pin) + p.direction = digitalio.Direction.OUTPUT + if value is not None: + p.value = value + return p diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py new file mode 100644 index 000000000..63530ced5 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py @@ -0,0 +1,59 @@ +"""WT32-SC01 Plus 320x480 ST7796 display""" + +from i80bus import I80Bus +from st7796 import ST7796 +from machine import I2C, Pin # See the note about reset below +from ft6x36 import FT6x36 +from machine import freq +import eventsys.device as device + + +freq(240_000_000) +# The WT32-SC01 Plus has the reset pins of the display IC and the touch IC both +# tied to pin 4. Controlling this pin with the display driver can lead to an +# unresponsive touchscreen. This case is uncommon. If they aren't tied +# together on your board, define reset in ST7796 instead, like: +# ST7796(reset=4) +reset = Pin(4, Pin.OUT, value=1) + +display_bus = I80Bus( + dc=0, + cs=6, + wr=47, + data=[9, 46, 3, 8, 18, 17, 16, 15], +) + +display_drv = ST7796( + display_bus, + width=320, + height=480, + colstart=0, + rowstart=0, + rotation=0, + mirrored=False, + color_depth=16, + bgr=True, + reverse_bytes_in_word=True, + invert=True, + brightness=1.0, + backlight_pin=45, + backlight_on_high=True, + reset_pin=None, + reset_high=True, + power_pin=None, + power_on_high=True, +) + +i2c = I2C(0, sda=Pin(6), scl=Pin(5), freq=100000) +touch_drv = FT6x36(i2c) +touch_read_func = touch_drv.get_positions +touch_rotation_table = None + +broker = device.Broker() + +touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_read_func, + data=display_drv, + data2=touch_rotation_table, +) diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py new file mode 100644 index 000000000..1a784d27a --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +pydisplay fbdisplay +""" + +from displaycore import DisplayDriver + + +class FBDisplay(DisplayDriver): + """ + A class to interface with CircuitPython FrameBuffer objects. + + Args: + buffer (FrameBuffer): The CircuitPython FrameBuffer object. + width (int, optional): The width of the display. Defaults to None. + height (int, optional): The height of the display. Defaults to None. + reverse_bytes_in_word (bool, optional): Whether to reverse the bytes in a word. Defaults to False. + + Attributes: + color_depth (int): The color depth of the display + """ + + def __init__(self, buffer, width=None, height=None, reverse_bytes_in_word=False): + self._raw_buffer = buffer + self._buffer = memoryview(buffer) + self._width = width if width else buffer.width + self._height = height if height else buffer.height + self._requires_byteswap = reverse_bytes_in_word + self._rotation = 0 + self.color_depth = 16 + + super().__init__() + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Initializes the display instance. Called by __init__ and rotation setter. + """ + pass + + def fill_rect(self, x, y, w, h, c): + """ + Fills a rectangle with the given color. + + Args: + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color to fill the rectangle with. + + Returns: + (tuple): A tuple containing the x, y, w, h values + """ + BPP = self.color_depth // 8 + if self._auto_byteswap: + color_bytes = (c & 0xFFFF).to_bytes(2, "big") + else: + color_bytes = (c & 0xFFFF).to_bytes(2, "little") + + for _y in range(y, y + h): + begin = (_y * self.width + x) * BPP + end = begin + w * BPP + self._buffer[begin:end] = color_bytes * w + return (x, y, w, h) + + def blit_rect(self, buf, x, y, w, h): + """ + Blits a buffer to the display at the given coordinates. + + Args: + buf (memoryview): The buffer to blit. + x (int): The x-coordinate of the buffer. + y (int): The y-coordinate of the buffer. + w (int): The width of the buffer. + h (int): The height of the buffer. + + Returns: + (tuple): A tuple containing the x, y, w, h values. + """ + if self._auto_byteswap: + self.byteswap(buf) + + BPP = self.color_depth // 8 + if x < 0 or y < 0 or x + w > self.width or y + h > self.height: + raise ValueError("The provided x, y, w, h values are out of range") + if len(buf) != w * h * BPP: + raise ValueError("The source buffer is not the correct size") + for row in range(h): + source_begin = row * w * BPP + source_end = source_begin + w * BPP + dest_begin = ((y + row) * self.width + x) * BPP + dest_end = dest_begin + w * BPP + self._buffer[dest_begin:dest_end] = buf[source_begin:source_end] + return (x, y, w, h) + + def pixel(self, x, y, c): + """ + Sets the color of the pixel at the given coordinates. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. + + Returns: + (tuple): A tuple containing the x, y values. + """ + return self.fill_rect(x, y, 1, 1, c) + + ############### Optional API Methods ################ + + def show(self) -> None: + """ + Refreshes the display. + """ + self._raw_buffer.refresh() diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py new file mode 100644 index 000000000..085ec53e9 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -0,0 +1,90 @@ +"""Qualia S3 RGB-666 with TL040HDS20 4.0" 720x720 Square Display""" +# Similar configs may be available for RGBMatrix, is31fl3741 and picodvi + +from rgbframebuffer import RGBFrameBuffer # type: ignore +# from machine import I2C, Pin # type: ignore +# from pca9554 import PCA9554 # type: ignore +# from ft6x36 import FT6x36 # type: ignore +# from fbdisplay import FBDisplay +# import eventsys.device as device + + +# def send_init_sequence(init_sequence, mosi, sck, cs): +# cs(0) +# for byte in init_sequence: +# for _ in range(8): +# mosi(byte & 0x80) +# sck(1) +# byte <<= 1 +# sck(0) +# cs(1) + + +tft_pins = { + "de": 17, + "vsync": 3, + "hsync": 46, + "dclk": 9, + "red": (1, 2, 42, 41, 40), + "green": (21, 47, 48, 45, 38, 39), + "blue": (10, 11, 12, 13, 14), +} + +tft_timings = { + "frequency": 16_000_000, + "width": 720, + "height": 720, + "hsync_pulse_width": 2, + "hsync_front_porch": 46, + "hsync_back_porch": 44, + "vsync_pulse_width": 2, + "vsync_front_porch": 16, + "vsync_back_porch": 18, + "hsync_idle_low": False, + "vsync_idle_low": False, + "de_idle_high": False, + "pclk_active_high": False, + "pclk_idle_high": False, +} + +# init_sequence = bytes() +# +# i2c = I2C(0, sda=Pin(8), scl=Pin(18), freq=100000) +# iox = PCA9554(i2c, address=0x38) +# btn_down = iox.Pin(6, Pin.IN) +# btn_up = iox.Pin(5, Pin.IN) +# reset = iox.Pin(2, Pin.OUT, value=1) +# backlight = iox.Pin(4, Pin.OUT, value=1) +# +# send_init_sequence(init_sequence, mosi=iox.Pin(7, Pin.OUT), +# sck=iox.Pin(0, Pin.OUT, value=0), cs=iox.Pin(1, Pin.OUT, value=1)) + + +fb = RGBFrameBuffer(**tft_pins, **tft_timings) +# mv = memoryview(fb) +# mv[:] = b'\xFF' * len(mv) +# fb.refresh() +# +# touch_drv = FT6x36(i2c, address=0x48) #, irq = iox.Pin(3, Pin.OUT)) +# +# def touch_read_func(): +# touches = touch_drv.touches +# if len(touches): +# return touches[0]['x'], touches[0]['y'] +# return None +# +# +# # Typical board_config.py setup from here on out +# +# display_drv = FBDisplay(fb) +# +# touch_rotation_table=(0, 0, 0, 0) +# +# broker = device.Broker() +# +# touch_dev = broker.create_device( +# type=device.Types.TOUCH, +# read=touch_read_func, +# data=display_drv, +# data2=touch_rotation_table, +# ) diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py new file mode 100755 index 000000000..1c3a09d58 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py @@ -0,0 +1,260 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +displaysys.pgdisplay +""" + +from displaysys import DisplayDriver, color_rgb +import pygame as pg + +try: + from typing import Optional +except ImportError: + pass + + +def poll() -> Optional[pg.event.Event]: + """ + Polls for an event and returns the event type and data. + + Returns: + Optional[pg.event.Event]: The event type and data. + """ + return pg.event.poll() + + +class PGDisplay(DisplayDriver): + """ + A class to emulate an LCD using pygame. + Provides scrolling and rotation functions similar to an LCD. The .texture + object functions as the LCD's internal memory. + + Args: + width (int, optional): The width of the display. Defaults to 320. + height (int, optional): The height of the display. Defaults to 240. + rotation (int, optional): The rotation of the display. Defaults to 0. + color_depth (int, optional): The color depth of the display. Defaults to 16. + title (str, optional): The title of the display window. Defaults to "pydisplay". + scale (float, optional): The scale of the display. Defaults to 1.0. + window_flags (int, optional): The flags for creating the display window. Defaults to pg.SHOWN + + Attributes: + color_depth (int): The color depth of the display. + touch_scale (float): The touch scale of the display. + """ + + def __init__( + self, + width=320, + height=240, + rotation=0, + color_depth=16, + title="pydisplay", + scale=1.0, + window_flags=pg.SHOWN, + ): + self._width = width + self._height = height + self._rotation = rotation + self.color_depth = color_depth + self._title = title + self._window_flags = window_flags + self._scale = scale + self.touch_scale = scale + self._buffer = None + self._requires_byteswap = False + + self._bytes_per_pixel = color_depth // 8 + + if self._scale != 1 and not hasattr(pg.transform, "scale_by"): + print( + f"PGDisplay: Scaling is set to {self._scale}, but pygame {pg.ver} does not support it." + ) + self._scale = 1 + + pg.init() + + self._buffer = pg.Surface(size=(self._width, self._height), depth=self.color_depth) + self._buffer.fill((0, 0, 0)) + + super().__init__(auto_refresh=True) + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Initializes the display instance. Called by __init__ and rotation setter. + """ + self._window = pg.display.set_mode( + size=(int(self.width * self._scale), int(self.height * self._scale)), + flags=self._window_flags, + depth=self.color_depth, + display=0, + vsync=0, + ) + pg.display.set_caption(self._title) + + super().vscrdef( + 0, self.height, 0 + ) # Set the vertical scroll definition without calling show + self.vscsad(False) # Scroll offset; set to False to disable scrolling + + def blit_rect(self, buffer: memoryview, x: int, y: int, w: int, h: int): + """ + Blits a buffer to the display. + + Args: + buffer (memoryview): The buffer to blit. + x (int): The x-coordinate of the buffer. + y (int): The y-coordinate of the buffer. + w (int): The width to blit. + h (int): The height to blit. + + Returns: + (tuple): A tuple containing the x, y, w, h values. + """ + + blitRect = pg.Rect(x, y, w, h) + for i in range(h): + for j in range(w): + pixel_index = (i * w + j) * self._bytes_per_pixel + color = color_rgb(buffer[pixel_index : pixel_index + self._bytes_per_pixel]) + self._buffer.set_at((x + j, y + i), color) + self.render(blitRect) + return (x, y, w, h) + + def fill_rect(self, x: int, y: int, w: int, h: int, c: int): + """ + Fill a rectangle with a color. + + Renders to the texture instead of directly to the window + to facilitate scrolling and scaling. + + Args: + x (int): The x-coordinate of the rectangle. + y (int): The y-coordinate of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color of the rectangle. + + Returns: + (tuple): A tuple containing the x, y, w, h values. + """ + fillRect = pg.Rect(x, y, w, h) + self._buffer.fill(color_rgb(c), fillRect) + self.render(fillRect) + return (x, y, w, h) + + def pixel(self, x: int, y: int, c: int): + """ + Set a pixel on the display. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. + + Returns: + (tuple): A tuple containing the x, y, w & h values. + """ + return self.blit_rect(bytearray(c.to_bytes(2, "little")), x, y, 1, 1) + + ############### API Method Overrides ################ + + def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: + """ + Set the vertical scroll definition. + + Args: + tfa (int): The top fixed area. + vsa (int): The vertical scroll area. + bfa (int): The bottom fixed area. + """ + super().vscrdef(tfa, vsa, bfa) + self.render() + + def vscsad(self, vssa: Optional[int] = None) -> int: + """ + Set the vertical scroll start address. + + Args: + vssa (Optional[int], optional): The vertical scroll start address. Defaults to None. + + Returns: + int: The vertical scroll start address. + """ + if vssa is not None: + super().vscsad(vssa) + self.render() + return self._vssa + + def _rotation_helper(self, value): + """ + Helper function for the rotation setter. + """ + if (angle := (value % 360) - (self._rotation % 360)) != 0: + tempBuffer = pg.transform.rotate(self._buffer, -angle) + self._buffer = tempBuffer + + ############### Class Specific Methods ############## + + def render(self, renderRect: Optional[pg.Rect] = None) -> None: + """ + Render the display. Automatically called after blitting or filling the display. + + Args: + renderRect (Optional[pg.Rect], optional): The rectangle to render. Defaults to None. + """ + s = self._scale + if s != 1: + buffer = pg.transform.scale_by(self._buffer, s) + else: + buffer = self._buffer + if not (y_start := self.vscsad()): + if renderRect is not None: + x, y, w, h = renderRect + renderRect = pg.Rect(x * s, y * s, w * s, h * s) + dest = renderRect + else: + dest = (0, 0) + self._window.blit(buffer, dest, renderRect) + else: + # Ignore renderRect and render the entire buffer to the window in four steps + y_start *= s + tfa = self._tfa * s + vsa = self._vsa * s + bfa = self._bfa * s + width = self.width * s + + if tfa > 0: + tfaRect = pg.Rect(0, 0, width, tfa) + self._window.blit(buffer, tfaRect, tfaRect) + + vsaTopHeight = vsa + tfa - y_start + vsaTopSrcRect = pg.Rect(0, y_start, width, vsaTopHeight) + vsaTopDestRect = pg.Rect(0, tfa, width, vsaTopHeight) + self._window.blit(buffer, vsaTopDestRect, vsaTopSrcRect) + + vsaBtmHeight = vsa - vsaTopHeight + vsaBtmSrcRect = pg.Rect(0, tfa, width, vsaBtmHeight) + vsaBtmDestRect = pg.Rect(0, tfa + vsaTopHeight, width, vsaBtmHeight) + self._window.blit(buffer, vsaBtmDestRect, vsaBtmSrcRect) + + if bfa > 0: + bfaRect = pg.Rect(0, tfa + vsa, width, bfa) + self._window.blit(buffer, bfaRect, bfaRect) + + def show(self) -> None: + """ + Show the display. + """ + pg.display.flip() + + def deinit(self) -> None: + """ + Deinitializes the pygame instance. + """ + pg.display.quit() + pg.quit() diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py new file mode 100644 index 000000000..d1ec2de58 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -0,0 +1,34 @@ +""" +Combination board configuration for desktop platforms. + +Tested with CPython on Linux, Windows and ChromeOS. +Tested with MicroPython on Linux. +Should work on MacOS, but not tested. +""" + +import eventsys.device as device +import sys + +try: + from pgdisplay import PGDisplay as DTDisplay, poll +except ImportError: + from sdldisplay import SDLDisplay as DTDisplay, poll + + +display_drv = DTDisplay( + width=320, + height=480, + rotation=0, + color_depth=16, + title=f"{sys.implementation.name} on {sys.platform}", + scale=1.0, +) + +broker = device.Broker() + +events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, +) diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py new file mode 100644 index 000000000..fd6fb1ad0 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +pydisplay psdisplay +""" + +from displaycore import DisplayDriver, color_rgb +from pyscript.ffi import create_proxy # type: ignore +from js import document, console # type: ignore + + +def log(*args): + console.log(*args) + + +class PSDevices: + """ + A class to emulate a display on PyScript. + + Args: + id (str): The id of the canvas element. + """ + + def __init__(self, id): + self.canvas = document.getElementById(id) + self._mouse_pos = None + + # self.canvas.oncontextmenu = self._no_context + + # Proxy functions are required for javascript + self.on_down = create_proxy(self._on_down) + self.on_up = create_proxy(self._on_up) + self.on_move = create_proxy(self._on_move) + self.on_enter = create_proxy(self._on_enter) + self.on_leave = create_proxy(self._on_leave) + + self.canvas.addEventListener("mousedown", self.on_down) + self.canvas.addEventListener("mouseup", self.on_up) + self.canvas.addEventListener("mousemove", self.on_move) + self.canvas.addEventListener("mouseenter", self.on_enter) + self.canvas.addEventListener("mouseleave", self.on_leave) + + def get_mouse_pos(self) -> tuple | None: + """ + Returns the current mouse position. + + Returns: + tuple or None: The x, y coordinates of the mouse position. + """ + return self._mouse_pos + + def _on_down(self, e): + if e.button == 0: # left mouse button + log(f"Mouse down {e.offsetX}, {e.offsetY}") + self._mouse_pos = (e.offsetX, e.offsetY) + else: + return False + + def _on_up(self, e): + if e.button == 0: # left mouse button + log(f"Mouse up {e.offsetX}, {e.offsetY}") + self._mouse_pos = None + else: + return False + + def _on_move(self, e): + if e.buttons & 1: + log(f"Mouse move {e.offsetX}, {e.offsetY}") + self._mouse_pos = (e.offsetX, e.offsetY) + + def _on_enter(self, e): + log("Mouse enter") + + def _on_leave(self, e): + log("Mouse leave") + self._mouse_pos = None + + def _no_context(self, e): + e.preventDefault() + e.stopPropagation() + return False + + +class PSDisplay(DisplayDriver): + """ + A class to emulate a display on PyScript. + + Args: + id (str): The id of the canvas element. + width (int, optional): The width of the display. Defaults to None. + height (int, optional): The height of the display. Defaults to None. + """ + + def __init__(self, id, width=None, height=None): + self._canvas = document.getElementById(id) + self._ctx = self._canvas.getContext("2d") + self._width = width or self._canvas.width + self._height = height or self._canvas.height + self._requires_byteswap = False + self._rotation = 0 + self.color_depth = 16 + self._draw = self._ctx + + super().__init__() + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Initializes the display instance. Called by __init__ and rotation setter. + """ + self._canvas.width = self.width + self._canvas.height = self.height + + def fill_rect(self, x, y, w, h, c): + """ + Fills a rectangle with the given color. + + Args: + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color to fill the rectangle with. + + Returns: + (tuple): A tuple containing the x, y, w, h values + """ + r, g, b = color_rgb(c) + self._ctx.fillStyle = f"rgb({r},{g},{b})" + self._ctx.fillRect(x, y, w, h) + return (x, y, w, h) + + def blit_rect(self, buf, x, y, w, h): + """ + Blits a buffer to the display. + + Args: + buf (bytearray): The buffer to blit. + x (int): The x-coordinate of the top-left corner of the buffer. + y (int): The y-coordinate of the top-left corner of the buffer. + w (int): The width of the buffer. + h (int): The height of the buffer. + + Returns: + (tuple): A tuple containing the x, y, w, h values + """ + BPP = self.color_depth // 8 + if x < 0 or y < 0 or x + w > self.width or y + h > self.height: + raise ValueError("The provided x, y, w, h values are out of range") + if len(buf) != w * h * BPP: + raise ValueError("The source buffer is not the correct size") + img_data = self._ctx.createImageData(w, h) + for i in range(0, len(buf), BPP): + r, g, b = color_rgb(buf[i : i + BPP]) + j = i * 2 + img_data.data[j] = r + img_data.data[j + 1] = g + img_data.data[j + 2] = b + img_data.data[j + 3] = 255 + self._ctx.putImageData(img_data, x, y) + return (x, y, w, h) + + def pixel(self, x, y, c): + """ + Sets a pixel to the given color. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color to set the pixel to. + + Returns: + (tuple): A tuple containing the x, y, w & h values. + """ + return self.fill_rect(x, y, 1, 1, c) diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py new file mode 100644 index 000000000..1cfb995bb --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -0,0 +1,17 @@ +"""board_config.py - board configuration for PyScript""" + +from psdisplay import PSDisplay, PSDevices +import eventsys.device as device + + +display_drv = PSDisplay("display_canvas", 480, 320) + +broker = device.Broker() + +touch_drv = PSDevices("display_canvas") + +touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, +) diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py new file mode 100755 index 000000000..3b2ddcc8e --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py @@ -0,0 +1,456 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +pydisplay sdldisplay +""" + +from displaycore import DisplayDriver, color_rgb +from eventsys import Events +from sys import implementation +from ._sdl2_lib import ( + SDL_Init, + SDL_Quit, + SDL_GetError, + SDL_CreateWindow, + SDL_CreateRenderer, + SDL_DestroyWindow, + SDL_DestroyRenderer, + SDL_DestroyTexture, + SDL_SetRenderDrawColor, + SDL_RenderPresent, + SDL_RenderSetLogicalSize, + SDL_SetWindowSize, + SDL_RenderCopyEx, + SDL_SetRenderTarget, + SDL_SetTextureBlendMode, + SDL_RenderFillRect, + SDL_RenderCopy, + SDL_UpdateTexture, + SDL_CreateTexture, + SDL_PIXELFORMAT_ARGB8888, + SDL_PIXELFORMAT_RGB888, + SDL_PIXELFORMAT_RGB565, + SDL_TEXTUREACCESS_TARGET, + SDL_BLENDMODE_NONE, + SDL_RENDERER_ACCELERATED, + SDL_RENDERER_PRESENTVSYNC, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOW_SHOWN, + SDL_INIT_EVERYTHING, + SDL_Rect, + SDL_PollEvent, + SDL_GetKeyName, + SDL_Event, + SDL_QUIT, + SDL_BUTTON_LMASK, + SDL_BUTTON_MMASK, + SDL_BUTTON_RMASK, + SDL_MOUSEBUTTONDOWN, + SDL_MOUSEBUTTONUP, + SDL_MOUSEMOTION, + SDL_MOUSEWHEEL, + SDL_KEYDOWN, + SDL_KEYUP, +) + +try: + from typing import Optional +except ImportError: + pass + +if implementation.name == "cpython": + import ctypes + + is_cpython = True +else: + is_cpython = False + + +_event = SDL_Event() + + +def poll() -> Optional[Events]: + """ + Polls for an event and returns the event type and data. + + Returns: + Optional[Events]: The event type and data. + """ + global _event + if SDL_PollEvent(_event): + if is_cpython: + if _event.type in Events.filter: + return _convert(SDL_Event(_event)) + else: + if int.from_bytes(_event[:4], "little") in Events.filter: + return _convert(SDL_Event(_event)) + return None + + +def _convert(e): + # Convert an SDL event to a Pygame event + if e.type == SDL_MOUSEMOTION: + l = 1 if e.motion.state & SDL_BUTTON_LMASK else 0 # noqa: E741 + m = 1 if e.motion.state & SDL_BUTTON_MMASK else 0 + r = 1 if e.motion.state & SDL_BUTTON_RMASK else 0 + evt = Events.Motion( + e.type, + (e.motion.x, e.motion.y), + (e.motion.xrel, e.motion.yrel), + (l, m, r), + e.motion.which != 0, + e.motion.windowID, + ) + elif e.type in (SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP): + evt = Events.Button( + e.type, + (e.button.x, e.button.y), + e.button.button, + e.button.which != 0, + e.button.windowID, + ) + elif e.type == SDL_MOUSEWHEEL: + evt = Events.Wheel( + e.type, + e.wheel.direction != 0, + e.wheel.x, + e.wheel.y, + e.wheel.preciseX, + e.wheel.preciseY, + e.wheel.which != 0, + e.wheel.windowID, + ) + elif e.type in (SDL_KEYDOWN, SDL_KEYUP): + name = SDL_GetKeyName(e.key.keysym.sym) + evt = Events.Key( + e.type, + name, + e.key.keysym.sym, + e.key.keysym.mod, + e.key.keysym.scancode, + e.key.windowID, + ) + elif e.type == SDL_QUIT: + evt = Events.Quit(e.type) + else: + evt = Events.Unknown(e.type) + return evt + + +def retcheck(retvalue): + # Check the return value of an SDL function and raise an exception if it's not 0 + if retvalue: + raise RuntimeError(SDL_GetError()) + + +class SDLDisplay(DisplayDriver): + """ + A class to emulate an LCD using SDL2. + Provides scrolling and rotation functions similar to an LCD. The .texture + object functions as the LCD's internal memory. + + Args: + width (int, optional): The width of the display. Defaults to 320. + height (int, optional): The height of the display. Defaults to 240. + rotation (int, optional): The rotation of the display. Defaults to 0. + color_depth (int, optional): The color depth of the display. Defaults to 16. + title (str, optional): The title of the display window. Defaults to "SDL2 Display". + scale (float, optional): The scale of the display. Defaults to 1.0. + window_flags (int, optional): The flags for creating the display window. Defaults to SDL_WINDOW_SHOWN. + render_flags (int, optional): The flags for creating the renderer. Defaults to SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC. + x (int, optional): The x-coordinate of the display window's position. Defaults to SDL_WINDOWPOS_CENTERED. + y (int, optional): The y-coordinate of the display window's position. Defaults to SDL_WINDOWPOS_CENTERED. + """ + + def __init__( + self, + width=320, + height=240, + rotation=0, + color_depth=16, + title="SDL2 Display", + scale=1.0, + window_flags=SDL_WINDOW_SHOWN, + render_flags=SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC, + x=SDL_WINDOWPOS_CENTERED, + y=SDL_WINDOWPOS_CENTERED, + ): + self._width = width + self._height = height + self._rotation = rotation + self.color_depth = color_depth + self._title = title + self._window_flags = window_flags + self._scale = scale + self._buffer = None + self._requires_byteswap = False + + # Determine the pixel format + if color_depth == 32: + self._px_format = SDL_PIXELFORMAT_ARGB8888 + elif color_depth == 24: + self._px_format = SDL_PIXELFORMAT_RGB888 + elif color_depth == 16: + self._px_format = SDL_PIXELFORMAT_RGB565 + else: + raise ValueError("Unsupported color_depth") + + retcheck(SDL_Init(SDL_INIT_EVERYTHING)) + self._window = SDL_CreateWindow( + self._title.encode(), + x, + y, + int(self.width * self._scale), + int(self.height * self._scale), + self._window_flags, + ) + if not self._window: + raise RuntimeError(f"{SDL_GetError()}") + self._renderer = SDL_CreateRenderer(self._window, -1, render_flags) + if not self._renderer: + raise RuntimeError(f"{SDL_GetError()}") + + self._buffer = SDL_CreateTexture( + self._renderer, + self._px_format, + SDL_TEXTUREACCESS_TARGET, + self.width, + self.height, + ) + if not self._buffer: + raise RuntimeError(f"{SDL_GetError()}") + retcheck(SDL_SetTextureBlendMode(self._buffer, SDL_BLENDMODE_NONE)) + + super().__init__(auto_refresh=True) + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Initializes the display instance. Called by __init__ and rotation setter. + """ + retcheck( + SDL_SetWindowSize( + self._window, + int(self.width * self._scale), + int(self.height * self._scale), + ) + ) + retcheck(SDL_RenderSetLogicalSize(self._renderer, self.width, self.height)) + + super().vscrdef( + 0, self.height, 0 + ) # Set the vertical scroll definition without calling .render() + self.vscsad(False) # Scroll offset; set to False to disable scrolling + + def blit_rect(self, buffer: memoryview, x: int, y: int, w: int, h: int): + """ + Blits a buffer to the display. + + Args: + buffer (memoryview): The buffer to blit. + x (int): The x-coordinate of the buffer. + y (int): The y-coordinate of the buffer. + w (int): The width to blit. + h (int): The height to blit. + + Returns: + (tuple): A tuple containing the x, y, w, h values. + """ + pitch = int(w * self.color_depth // 8) + if len(buffer) != pitch * h: + raise ValueError("Buffer size does not match dimensions") + blitRect = SDL_Rect(x, y, w, h) + if is_cpython: + if isinstance(buffer, memoryview): + buffer_array = (ctypes.c_ubyte * len(buffer.obj)).from_buffer(buffer.obj) + elif type(buffer) is bytearray: + buffer_array = (ctypes.c_ubyte * len(buffer)).from_buffer(buffer) + else: + raise ValueError( + f"Buffer is of type {type(buffer)} instead of memoryview or bytearray" + ) + buffer_ptr = ctypes.c_void_p(ctypes.addressof(buffer_array)) + retcheck(SDL_UpdateTexture(self._buffer, blitRect, buffer_ptr, pitch)) + else: + retcheck(SDL_UpdateTexture(self._buffer, blitRect, buffer, pitch)) + self.render(blitRect) + return (x, y, w, h) + + def fill_rect(self, x: int, y: int, w: int, h: int, c: int): + """ + Fill a rectangle with a color. + + Renders to the texture instead of directly to the window + to facilitate scrolling and scaling. + + Args: + x (int): The x-coordinate of the rectangle. + y (int): The y-coordinate of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color of the rectangle. + + Returns: + (tuple): A tuple containing the x, y, w, h values + """ + fillRect = SDL_Rect(x, y, w, h) + r, g, b = color_rgb(c) + + retcheck( + SDL_SetRenderTarget(self._renderer, self._buffer) + ) # Set the render target to the texture + retcheck( + SDL_SetRenderDrawColor(self._renderer, r, g, b, 255) + ) # Set the color to fill the rectangle + retcheck(SDL_RenderFillRect(self._renderer, fillRect)) # Fill the rectangle on the texture + retcheck( + SDL_SetRenderTarget(self._renderer, None) + ) # Reset the render target back to the window + self.render(fillRect) + return (x, y, w, h) + + def pixel(self, x: int, y: int, c: int): + """ + Set a pixel on the display. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. + + Returns: + (tuple): A tuple containing the x, y values. + """ + return self.blit_rect(bytearray(c.to_bytes(2, "little")), x, y, 1, 1) + + ############### API Method Overrides ################ + + def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: + """ + Set the vertical scroll definition. + + Args: + tfa (int): The top fixed area. + vsa (int): The vertical scroll area. + bfa (int): The bottom fixed area. + """ + super().vscrdef(tfa, vsa, bfa) + self.render() + + def vscsad(self, vssa: Optional[int] = None) -> int: + """ + Set or get the vertical scroll start address. + + Args: + vssa (int): The vertical scroll start address. Defaults to None. + + Returns: + int: The vertical scroll start address. + """ + if vssa is not None: + super().vscsad(vssa) + self.render() + return self._vssa + + def _rotation_helper(self, value): + """ + Creates a new texture to use as the buffer and copies the old one, + applying rotation with SDL_RenderCopyEx. Destroys the old buffer. + + Args: + value (int): The new rotation value. + """ + + if (angle := (value % 360) - (self._rotation % 360)) != 0: + if implementation.name == "cpython": + tempBuffer = SDL_CreateTexture( + self._renderer, + self._px_format, + SDL_TEXTUREACCESS_TARGET, + self.height, + self.width, + ) + if not tempBuffer: + raise RuntimeError(f"{SDL_GetError()}") + + retcheck(SDL_SetTextureBlendMode(tempBuffer, SDL_BLENDMODE_NONE)) + retcheck(SDL_SetRenderTarget(self._renderer, tempBuffer)) + if abs(angle) != 180: + dstrect = SDL_Rect( + (self.height - self.width) // 2, + (self.width - self.height) // 2, + self.width, + self.height, + ) + else: + dstrect = None + retcheck( + SDL_RenderCopyEx(self._renderer, self._buffer, None, dstrect, angle, None, 0) + ) + retcheck(SDL_SetRenderTarget(self._renderer, None)) + retcheck(SDL_DestroyTexture(self._buffer)) + self._buffer = tempBuffer + else: + retcheck(SDL_DestroyTexture(self._buffer)) + self._buffer = SDL_CreateTexture( + self._renderer, + self._px_format, + SDL_TEXTUREACCESS_TARGET, + self.height, + self.width, + ) + if not self._buffer: + raise RuntimeError(f"{SDL_GetError()}") + retcheck(SDL_SetTextureBlendMode(self._buffer, SDL_BLENDMODE_NONE)) + + ############### Class Specific Methods ############## + + def render(self, renderRect: Optional[SDL_Rect] = None): + """ + Render the display. Automatically called after blitting or filling the display. + + Args: + renderRect (Optional[SDL_Rect], optional): The rectangle to render. Defaults to None. + """ + # if (y_start := self.vscsad()) == False: + if False: + # The following line is not working on Chromebooks, Ubuntu and Raspberry Pi OS + retcheck(SDL_RenderCopy(self._renderer, self._buffer, renderRect, renderRect)) + else: + # Ignore renderRect and render the entire texture to the window in four steps + y_start = self.vscsad() + if self._tfa > 0: + tfaRect = SDL_Rect(0, 0, self.width, self._tfa) + retcheck(SDL_RenderCopy(self._renderer, self._buffer, tfaRect, tfaRect)) + + vsaTopHeight = self._vsa + self._tfa - y_start + vsaTopSrcRect = SDL_Rect(0, y_start, self.width, vsaTopHeight) + vsaTopDestRect = SDL_Rect(0, self._tfa, self.width, vsaTopHeight) + retcheck(SDL_RenderCopy(self._renderer, self._buffer, vsaTopSrcRect, vsaTopDestRect)) + + vsaBtmHeight = self._vsa - vsaTopHeight + vsaBtmSrcRect = SDL_Rect(0, self._tfa, self.width, vsaBtmHeight) + vsaBtmDestRect = SDL_Rect(0, self._tfa + vsaTopHeight, self.width, vsaBtmHeight) + retcheck(SDL_RenderCopy(self._renderer, self._buffer, vsaBtmSrcRect, vsaBtmDestRect)) + + if self._bfa > 0: + bfaRect = SDL_Rect(0, self._tfa + self._vsa, self.width, self._bfa) + retcheck(SDL_RenderCopy(self._renderer, self._buffer, bfaRect, bfaRect)) + + def show(self) -> None: + """ + Show the display. + """ + SDL_RenderPresent(self._renderer) + + def deinit(self) -> None: + """ + Deinitializes the sdl2lcd instance. + """ + retcheck(SDL_DestroyTexture(self._buffer)) + retcheck(SDL_DestroyRenderer(self._renderer)) + retcheck(SDL_DestroyWindow(self._window)) + retcheck(SDL_Quit()) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/__init__.py new file mode 100644 index 000000000..65a9e29d3 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/__init__.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +This module provides the SDL2 library implementation for MicroPython and CPython. + +The module checks the implementation name and imports the appropriate module +based on whether the current Python implementation is MicroPython or CPython. +""" + +from sys import implementation + +if implementation.name == "micropython": + from ._micropython import * # noqa: F403 +else: + from ._cpython import * # noqa: F403 diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py new file mode 100644 index 000000000..a7200073d --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py @@ -0,0 +1,246 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +from micropython import const + + +############################################################################### +# SDL2 Constants # +############################################################################### + +# SDL_WindowPos values +SDL_WINDOWPOS_UNDEFINED = const(0x1FFF0000) +SDL_WINDOWPOS_CENTERED = const(0x2FFF0000) + +# SDL_Window flags +SDL_WINDOW_FULLSCREEN = const(0x00000001) +SDL_WINDOW_OPENGL = const(0x00000002) +SDL_WINDOW_SHOWN = const(0x00000004) +SDL_WINDOW_HIDDEN = const(0x00000008) +SDL_WINDOW_BORDERLESS = const(0x00000010) +SDL_WINDOW_RESIZABLE = const(0x00000020) +SDL_WINDOW_MINIMIZED = const(0x00000040) +SDL_WINDOW_MAXIMIZED = const(0x00000080) +SDL_WINDOW_INPUT_GRABBED = const(0x00000100) +SDL_WINDOW_INPUT_FOCUS = const(0x00000200) +SDL_WINDOW_MOUSE_FOCUS = const(0x00000400) +SDL_WINDOW_FULLSCREEN_DESKTOP = const(0x00001001) +SDL_WINDOW_ALLOW_HIGHDPI = const(0x00002000) +SDL_WINDOW_MOUSE_CAPTURE = const(0x00004000) +SDL_WINDOW_ALWAYS_ON_TOP = const(0x00008000) +SDL_WINDOW_SKIP_TASKBAR = const(0x00010000) +SDL_WINDOW_UTILITY = const(0x00020000) +SDL_WINDOW_TOOLTIP = const(0x00040000) +SDL_WINDOW_POPUP_MENU = const(0x00080000) +SDL_WINDOW_VULKAN = const(0x10000000) + +# SDL_Renderer flags +SDL_RENDERER_SOFTWARE = const(0x00000001) +SDL_RENDERER_ACCELERATED = const(0x00000002) +SDL_RENDERER_PRESENTVSYNC = const(0x00000004) +SDL_RENDERER_TARGETTEXTURE = const(0x00000008) + +# SDL_Init flags +SDL_INIT_TIMER = const(0x00000001) +SDL_INIT_AUDIO = const(0x00000010) +SDL_INIT_VIDEO = const(0x00000020) +SDL_INIT_JOYSTICK = const(0x00000200) +SDL_INIT_HAPTIC = const(0x00001000) +SDL_INIT_GAMECONTROLLER = const(0x00002000) +SDL_INIT_EVENTS = const(0x00004000) +SDL_INIT_EVERYTHING = const(0x0000000F) +SDL_INIT_NOPARACHUTE = const(0x00100000) + +# SDL_Texture values +SDL_TEXTUREACCESS_STATIC = const(0) +SDL_TEXTUREACCESS_STREAMING = const(1) +SDL_TEXTUREACCESS_TARGET = const(2) + +# SDL_BlendMode values +SDL_BLENDMODE_NONE = const(1) +SDL_BLENDMODE_BLEND = const(2) +SDL_BLENDMODE_ADD = const(3) +SDL_BLENDMODE_MOD = const(4) +SDL_BLENDMODE_MUL = const(5) + +# SDL_Event types (not complete) +SDL_QUIT = const(0x100) # User clicked the window close button +SDL_KEYDOWN = const(0x300) # Key pressed +SDL_KEYUP = const(0x301) # Key released +SDL_MOUSEMOTION = const(0x400) # Mouse moved +SDL_MOUSEBUTTONDOWN = const(0x401) # Mouse button pressed +SDL_MOUSEBUTTONUP = const(0x402) # Mouse button released +SDL_MOUSEWHEEL = const(0x403) # Mouse wheel motion +SDL_POLLSENTINEL = const(0x7F00) # Signals the end of an event poll cycle + +# SDL_MouseMotionEvent button masks +SDL_BUTTON_LMASK = const(1 << 0) # Left mouse button +SDL_BUTTON_MMASK = const(1 << 1) # Middle mouse button +SDL_BUTTON_RMASK = const(1 << 2) # Right mouse button + + +############################################################################### +# SDL2 Pixel Formats # +############################################################################### + + +def SDL_DEFINE_PIXELFORMAT(type, order, layout, bits, bytes): + """ + Define a pixel format. + """ + return ( + (1 << 28) + | ((type) << 24) + | ((order) << 20) + | ((layout) << 16) + | ((bits) << 8) + | ((bytes) << 0) + ) + + +# SDL_PIXELTYPE values +SDL_PIXELTYPE_UNKNOWN = const(0) +SDL_PIXELTYPE_INDEX1 = const(1) +SDL_PIXELTYPE_INDEX4 = const(2) +SDL_PIXELTYPE_INDEX8 = const(3) +SDL_PIXELTYPE_PACKED8 = const(4) +SDL_PIXELTYPE_PACKED16 = const(5) +SDL_PIXELTYPE_PACKED32 = const(6) +SDL_PIXELTYPE_ARRAYU8 = const(7) +SDL_PIXELTYPE_ARRAYU16 = const(8) +SDL_PIXELTYPE_ARRAYU32 = const(9) +SDL_PIXELTYPE_ARRAYF16 = const(10) +SDL_PIXELTYPE_ARRAYF32 = const(11) + +# SDL_PACKEDORDER values +SDL_PACKEDORDER_NONE = const(0) +SDL_PACKEDORDER_XRGB = const(1) +SDL_PACKEDORDER_RGBX = const(2) +SDL_PACKEDORDER_ARGB = const(3) +SDL_PACKEDORDER_RGBA = const(4) +SDL_PACKEDORDER_XBGR = const(5) +SDL_PACKEDORDER_BGRX = const(6) +SDL_PACKEDORDER_ABGR = const(7) +SDL_PACKEDORDER_BGRA = const(8) + +# SDL_ARRAYORDER values +SDL_ARRAYORDER_NONE = const(0) +SDL_ARRAYORDER_RGB = const(1) +SDL_ARRAYORDER_RGBA = const(2) +SDL_ARRAYORDER_ARGB = const(3) +SDL_ARRAYORDER_BGR = const(4) +SDL_ARRAYORDER_BGRA = const(5) +SDL_ARRAYORDER_ABGR = const(6) + +# SDL_PACKEDLAYOUT values +SDL_PACKEDLAYOUT_NONE = const(0) +SDL_PACKEDLAYOUT_332 = const(1) +SDL_PACKEDLAYOUT_4444 = const(2) +SDL_PACKEDLAYOUT_1555 = const(3) +SDL_PACKEDLAYOUT_5551 = const(4) +SDL_PACKEDLAYOUT_565 = const(5) +SDL_PACKEDLAYOUT_8888 = const(6) +SDL_PACKEDLAYOUT_2101010 = const(7) +SDL_PACKEDLAYOUT_1010102 = const(8) + +# SDL_BITMAPORDER values +SDL_BITMAPORDER_NONE = const(0) +SDL_BITMAPORDER_4321 = const(1) +SDL_BITMAPORDER_1234 = const(2) + +# SDL_PIXELFORMAT values +SDL_PIXELFORMAT_UNKNOWN = const(0) +SDL_PIXELFORMAT_INDEX1LSB = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_4321, 0, 1, 0 +) +SDL_PIXELFORMAT_INDEX1MSB = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, 1, 0 +) +SDL_PIXELFORMAT_INDEX4LSB = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, 4, 0 +) +SDL_PIXELFORMAT_INDEX4MSB = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_1234, 0, 4, 0 +) +SDL_PIXELFORMAT_INDEX8 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1) +SDL_PIXELFORMAT_RGB332 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_332, 8, 1 +) +SDL_PIXELFORMAT_XRGB4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_4444, 12, 2 +) +SDL_PIXELFORMAT_RGB444 = SDL_PIXELFORMAT_XRGB4444 +SDL_PIXELFORMAT_XBGR4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_4444, 12, 2 +) +SDL_PIXELFORMAT_BGR444 = SDL_PIXELFORMAT_XBGR4444 +SDL_PIXELFORMAT_XRGB1555 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_1555, 15, 2 +) +SDL_PIXELFORMAT_RGB555 = SDL_PIXELFORMAT_XRGB1555 +SDL_PIXELFORMAT_XBGR1555 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_1555, 15, 2 +) +SDL_PIXELFORMAT_BGR555 = SDL_PIXELFORMAT_XBGR1555 +SDL_PIXELFORMAT_ARGB4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_4444, 16, 2 +) +SDL_PIXELFORMAT_RGBA4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_4444, 16, 2 +) +SDL_PIXELFORMAT_ABGR4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_4444, 16, 2 +) +SDL_PIXELFORMAT_BGRA4444 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_4444, 16, 2 +) +SDL_PIXELFORMAT_ARGB1555 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_1555, 16, 2 +) +SDL_PIXELFORMAT_RGBA5551 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_5551, 16, 2 +) +SDL_PIXELFORMAT_ABGR1555 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_1555, 16, 2 +) +SDL_PIXELFORMAT_BGRA5551 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_5551, 16, 2 +) +SDL_PIXELFORMAT_RGB565 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_565, 16, 2 +) +SDL_PIXELFORMAT_BGR565 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_565, 16, 2 +) +SDL_PIXELFORMAT_RGB24 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_RGB, 0, 24, 3) +SDL_PIXELFORMAT_BGR24 = SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0, 24, 3) +SDL_PIXELFORMAT_XRGB8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, SDL_PACKEDLAYOUT_8888, 24, 4 +) +SDL_PIXELFORMAT_RGB888 = SDL_PIXELFORMAT_XRGB8888 +SDL_PIXELFORMAT_RGBX8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX, SDL_PACKEDLAYOUT_8888, 24, 4 +) +SDL_PIXELFORMAT_XBGR8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, SDL_PACKEDLAYOUT_8888, 24, 4 +) +SDL_PIXELFORMAT_BGR888 = SDL_PIXELFORMAT_XBGR8888 +SDL_PIXELFORMAT_BGRX8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX, SDL_PACKEDLAYOUT_8888, 24, 4 +) +SDL_PIXELFORMAT_ARGB8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_8888, 32, 4 +) +SDL_PIXELFORMAT_RGBA8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBA, SDL_PACKEDLAYOUT_8888, 32, 4 +) +SDL_PIXELFORMAT_ABGR8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, SDL_PACKEDLAYOUT_8888, 32, 4 +) +SDL_PIXELFORMAT_BGRA8888 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, SDL_PACKEDLAYOUT_8888, 32, 4 +) +SDL_PIXELFORMAT_ARGB2101010 = SDL_DEFINE_PIXELFORMAT( + SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, SDL_PACKEDLAYOUT_2101010, 32, 4 +) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py new file mode 100644 index 000000000..f6187c26f --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py @@ -0,0 +1,304 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +An implementation of SDL2 written in CPython using ctypes. +""" + +import ctypes +from ._constants import * # noqa: F403 +from sys import platform + +# Load the SDL2 shared library using ctypes +if platform == "win32": + _libSDL2 = ctypes.CDLL("SDL2.dll") +else: + _libSDL2 = ctypes.CDLL("libSDL2-2.0.so.0") + + +############################################################################### +# SDL2 structs # +############################################################################### + + +class SDL_Rect(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("w", ctypes.c_int), + ("h", ctypes.c_int), + ] + + +class SDL_Point(ctypes.Structure): + _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)] + + +class SDL_CommonEvent(ctypes.Structure): + _fields_ = [ + ("type", ctypes.c_uint), + ("timestamp", ctypes.c_uint), + ("unused", ctypes.c_uint * 12), + ] + + +class SDL_KeyboardEvent(ctypes.Structure): + class Key(ctypes.Structure): + class SDL_Keysym(ctypes.Structure): + _fields_ = [ + ("scancode", ctypes.c_int), + ("sym", ctypes.c_int), + ("mod", ctypes.c_uint16), + ("unused", ctypes.c_uint), + ] + + _fields_ = [ + ("windowID", ctypes.c_uint), + ("state", ctypes.c_uint8), + ("repeat", ctypes.c_uint8), + ("padding2", ctypes.c_uint8), + ("padding3", ctypes.c_uint8), + ("keysym", SDL_Keysym), + ] + + _fields_ = [("type", ctypes.c_uint), ("timestamp", ctypes.c_uint), ("key", Key)] + + +class SDL_MouseMotionEvent(ctypes.Structure): + class Motion(ctypes.Structure): + _fields_ = [ + ("windowID", ctypes.c_uint), + ("which", ctypes.c_uint), + ("state", ctypes.c_uint), + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("xrel", ctypes.c_int), + ("yrel", ctypes.c_int), + ] + + _fields_ = [ + ("type", ctypes.c_uint), + ("timestamp", ctypes.c_uint), + ("motion", Motion), + ] + + +class SDL_MouseButtonEvent(ctypes.Structure): + class Button(ctypes.Structure): + _fields_ = [ + ("windowID", ctypes.c_uint), + ("which", ctypes.c_uint), + ("button", ctypes.c_uint8), + ("state", ctypes.c_uint8), + ("clicks", ctypes.c_uint8), + ("padding", ctypes.c_uint8), + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ] + + _fields_ = [ + ("type", ctypes.c_uint), + ("timestamp", ctypes.c_uint), + ("button", Button), + ] + + +class SDL_MouseWheelEvent(ctypes.Structure): + class Wheel(ctypes.Structure): + _fields_ = [ + ("windowID", ctypes.c_uint), + ("which", ctypes.c_uint), + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("direction", ctypes.c_uint), + ("preciseX", ctypes.c_float), + ("preciseY", ctypes.c_float), + ] + + _fields_ = [("type", ctypes.c_uint), ("timestamp", ctypes.c_uint), ("wheel", Wheel)] + + +############################################################################### +# SDL2 functions # +############################################################################### + +# SDL misc functions +_libSDL2.SDL_Init.argtypes = [ctypes.c_uint] +_libSDL2.SDL_Init.restype = ctypes.c_int +SDL_Init = _libSDL2.SDL_Init + +_libSDL2.SDL_Quit.argtypes = [] +_libSDL2.SDL_Quit.restype = None +SDL_Quit = _libSDL2.SDL_Quit + +_libSDL2.SDL_GetError.argtypes = [] +_libSDL2.SDL_GetError.restype = ctypes.c_char_p +SDL_GetError = _libSDL2.SDL_GetError + +_libSDL2.SDL_PollEvent.argtypes = [ctypes.POINTER(SDL_CommonEvent)] +_libSDL2.SDL_PollEvent.restype = ctypes.c_int +SDL_PollEvent = _libSDL2.SDL_PollEvent + +# SDL key functions +_libSDL2.SDL_GetKeyName.argtypes = [ctypes.c_int] +_libSDL2.SDL_GetKeyName.restype = ctypes.c_char_p +SDL_GetKeyName = _libSDL2.SDL_GetKeyName + +_libSDL2.SDL_GetKeyFromName.argtypes = [ctypes.c_char_p] +_libSDL2.SDL_GetKeyFromName.restype = ctypes.c_int +SDL_GetKeyFromName = _libSDL2.SDL_GetKeyFromName + +# SDL window functions +_libSDL2.SDL_CreateWindow.argtypes = [ + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_uint, +] +_libSDL2.SDL_CreateWindow.restype = ctypes.c_void_p +SDL_CreateWindow = _libSDL2.SDL_CreateWindow + +_libSDL2.SDL_DestroyWindow.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_DestroyWindow.restype = None +SDL_DestroyWindow = _libSDL2.SDL_DestroyWindow + +_libSDL2.SDL_SetWindowSize.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int] +_libSDL2.SDL_SetWindowSize.restype = None +SDL_SetWindowSize = _libSDL2.SDL_SetWindowSize + +# SDL renderer functions +_libSDL2.SDL_CreateRenderer.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_uint] +_libSDL2.SDL_CreateRenderer.restype = ctypes.c_void_p +SDL_CreateRenderer = _libSDL2.SDL_CreateRenderer + +_libSDL2.SDL_DestroyRenderer.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_DestroyRenderer.restype = None +SDL_DestroyRenderer = _libSDL2.SDL_DestroyRenderer + +_libSDL2.SDL_SetRenderDrawColor.argtypes = [ + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, +] +_libSDL2.SDL_SetRenderDrawColor.restype = ctypes.c_int +SDL_SetRenderDrawColor = _libSDL2.SDL_SetRenderDrawColor + +_libSDL2.SDL_SetRenderTarget.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +_libSDL2.SDL_SetRenderTarget.restype = ctypes.c_int +SDL_SetRenderTarget = _libSDL2.SDL_SetRenderTarget + +_libSDL2.SDL_RenderClear.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_RenderClear.restype = ctypes.c_int +SDL_RenderClear = _libSDL2.SDL_RenderClear + +_libSDL2.SDL_RenderCopy.argtypes = [ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.POINTER(SDL_Rect), + ctypes.POINTER(SDL_Rect), +] +_libSDL2.SDL_RenderCopy.restype = ctypes.c_int +SDL_RenderCopy = _libSDL2.SDL_RenderCopy + +_libSDL2.SDL_RenderCopyEx.argtypes = [ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.POINTER(SDL_Rect), + ctypes.POINTER(SDL_Rect), + ctypes.c_double, + ctypes.POINTER(SDL_Point), + ctypes.c_int, +] +_libSDL2.SDL_RenderCopyEx.restype = ctypes.c_int +SDL_RenderCopyEx = _libSDL2.SDL_RenderCopyEx + +_libSDL2.SDL_RenderPresent.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_RenderPresent.restype = None +SDL_RenderPresent = _libSDL2.SDL_RenderPresent + +_libSDL2.SDL_RenderFillRect.argtypes = [ctypes.c_void_p, ctypes.POINTER(SDL_Rect)] +_libSDL2.SDL_RenderFillRect.restype = ctypes.c_int +SDL_RenderFillRect = _libSDL2.SDL_RenderFillRect + +_libSDL2.SDL_RenderSetLogicalSize.argtypes = [ + ctypes.c_void_p, + ctypes.c_int, + ctypes.c_int, +] +_libSDL2.SDL_RenderSetLogicalSize.restype = ctypes.c_int +SDL_RenderSetLogicalSize = _libSDL2.SDL_RenderSetLogicalSize + +# SDL texture functions +_libSDL2.SDL_CreateTexture.argtypes = [ + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, +] +_libSDL2.SDL_CreateTexture.restype = ctypes.c_void_p +SDL_CreateTexture = _libSDL2.SDL_CreateTexture + +_libSDL2.SDL_DestroyTexture.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_DestroyTexture.restype = None +SDL_DestroyTexture = _libSDL2.SDL_DestroyTexture + +_libSDL2.SDL_SetTextureBlendMode.argtypes = [ctypes.c_void_p, ctypes.c_int] +_libSDL2.SDL_SetTextureBlendMode.restype = ctypes.c_int +SDL_SetTextureBlendMode = _libSDL2.SDL_SetTextureBlendMode + +_libSDL2.SDL_UpdateTexture.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(SDL_Rect), + ctypes.c_void_p, + ctypes.c_int, +] +_libSDL2.SDL_UpdateTexture.restype = ctypes.c_int +SDL_UpdateTexture = _libSDL2.SDL_UpdateTexture + +# SDL timer functions +_libSDL2.SDL_AddTimer.argtypes = [ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p] +_libSDL2.SDL_AddTimer.restype = ctypes.c_void_p +SDL_AddTimer = _libSDL2.SDL_AddTimer + +_libSDL2.SDL_RemoveTimer.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_RemoveTimer.restype = ctypes.c_int +SDL_RemoveTimer = _libSDL2.SDL_RemoveTimer + +SDL_TimerCallback = ctypes.CFUNCTYPE(ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p) + + +############################################################################### +# SDL event union # +############################################################################### + +_event_struct_map = { + # Constants from _constants.py + SDL_KEYDOWN: SDL_KeyboardEvent, # noqa: F405 + SDL_KEYUP: SDL_KeyboardEvent, # noqa: F405 + SDL_MOUSEMOTION: SDL_MouseMotionEvent, # noqa: F405 + SDL_MOUSEBUTTONDOWN: SDL_MouseButtonEvent, # noqa: F405 + SDL_MOUSEBUTTONUP: SDL_MouseButtonEvent, # noqa: F405 + SDL_MOUSEWHEEL: SDL_MouseWheelEvent, # noqa: F405 + SDL_POLLSENTINEL: SDL_CommonEvent, # noqa: F405 +} + + +def SDL_Event(event=None): + """ + Convert event to an SDL_Event object using ctypes. + The size of the largest SDL_Event struct is 56 bytes. + """ + if event is None: + return SDL_CommonEvent.from_buffer(ctypes.create_string_buffer(56)) + + event_type = event.type + + if event_type in _event_struct_map: + return _event_struct_map[event_type].from_buffer(event) + return SDL_CommonEvent.from_buffer(event) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py new file mode 100644 index 000000000..4f8668b08 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py @@ -0,0 +1,188 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +An implementation of SDL2 written in MicroPython. +""" + +import uctypes +import ffi +import struct +from ._constants import * # noqa: F403 + + +# Load the SDL2 shared library using ffi +_libSDL2 = ffi.open("libSDL2-2.0.so.0") + + +############################################################################### +# SDL2 structs # +############################################################################### + + +def SDL_Rect(x=0, y=0, w=0, h=0): + return struct.pack("iiii", x, y, w, h) + + +def SDL_Point(x=0, y=0): + return struct.pack("ii", x, y) + + +SDL_CommonEvent = { + "type": uctypes.UINT32 | 0, + "timestamp": uctypes.UINT32 | 4, +} + +SDL_KeyboardEvent = { + "type": uctypes.UINT32 | 0, + "timestamp": uctypes.UINT32 | 4, + "key": ( + 8, + { + "windowID": uctypes.UINT32 | 0, + "state": uctypes.UINT8 | 4, + "repeat": uctypes.UINT8 | 5, + "padding2": uctypes.UINT8 | 6, + "padding3": uctypes.UINT8 | 7, + "keysym": ( + 8, + { + "scancode": 0 | uctypes.UINT32, + "sym": 4 | uctypes.UINT32, + "mod": 8 | uctypes.UINT16, + "unused": 10 | uctypes.UINT32, + }, + ), + }, + ), +} + +SDL_MouseMotionEvent = { + "type": uctypes.UINT32 | 0, + "timestamp": uctypes.UINT32 | 4, + "motion": ( + 8, + { + "windowID": uctypes.UINT32 | 0, + "which": uctypes.UINT32 | 4, + "state": uctypes.UINT32 | 8, + "x": uctypes.INT32 | 12, + "y": uctypes.INT32 | 16, + "xrel": uctypes.INT32 | 20, + "yrel": uctypes.INT32 | 8, + }, + ), +} + +SDL_MouseButtonEvent = { + "type": uctypes.UINT32 | 0, + "timestamp": uctypes.UINT32 | 4, + "button": ( + 8, + { + "windowID": uctypes.UINT32 | 0, + "which": uctypes.UINT32 | 4, + "button": uctypes.UINT8 | 8, + "state": uctypes.UINT8 | 9, + "clicks": uctypes.UINT8 | 10, + "padding1": uctypes.UINT8 | 11, + "x": uctypes.INT32 | 12, + "y": uctypes.INT32 | 16, + }, + ), +} + +SDL_MouseWheelEvent = { + "type": uctypes.UINT32 | 0, + "timestamp": uctypes.UINT32 | 4, + "wheel": ( + 8, + { + "windowID": uctypes.UINT32 | 0, + "which": uctypes.UINT32 | 4, + "x": uctypes.INT32 | 8, + "y": uctypes.INT32 | 12, + "direction": uctypes.UINT32 | 16, + "preciseX": uctypes.FLOAT32 | 20, + "preciseY": uctypes.FLOAT32 | 24, + }, + ), +} + + +############################################################################### +# SDL2 functions # +############################################################################### + +# SDL misc functions +SDL_Init = _libSDL2.func("i", "SDL_Init", "I") +SDL_Quit = _libSDL2.func("v", "SDL_Quit", "") +SDL_GetError = _libSDL2.func("s", "SDL_GetError", "") +SDL_PollEvent = _libSDL2.func("i", "SDL_PollEvent", "P") + +# SDL key functions +SDL_GetKeyName = _libSDL2.func("s", "SDL_GetKeyName", "i") +SDL_GetKeyFromName = _libSDL2.func("i", "SDL_GetKeyFromName", "s") + +# SDL window functions +SDL_CreateWindow = _libSDL2.func("P", "SDL_CreateWindow", "siiiii") +SDL_DestroyWindow = _libSDL2.func("v", "SDL_DestroyWindow", "P") +SDL_SetWindowSize = _libSDL2.func("v", "SDL_SetWindowSize", "Pii") + +# SDL renderer functions +SDL_CreateRenderer = _libSDL2.func("P", "SDL_CreateRenderer", "PiI") +SDL_DestroyRenderer = _libSDL2.func("v", "SDL_DestroyRenderer", "P") +SDL_SetRenderDrawColor = _libSDL2.func("i", "SDL_SetRenderDrawColor", "PPPP") +SDL_SetRenderTarget = _libSDL2.func("i", "SDL_SetRenderTarget", "pP") +SDL_RenderClear = _libSDL2.func("v", "SDL_RenderClear", "P") +SDL_RenderCopy = _libSDL2.func("v", "SDL_RenderCopy", "PPPP") +SDL_RenderCopyEx = _libSDL2.func("v", "SDL_RenderCopyEx", "PPPPdPPi") +SDL_RenderPresent = _libSDL2.func("v", "SDL_RenderPresent", "P") +SDL_RenderFillRect = _libSDL2.func("i", "SDL_RenderFillRect", "PP") +SDL_RenderSetLogicalSize = _libSDL2.func("i", "SDL_RenderSetLogicalSize", "Pii") + +# SDL texture functions +SDL_CreateTexture = _libSDL2.func("P", "SDL_CreateTexture", "PIiiii") +SDL_DestroyTexture = _libSDL2.func("v", "SDL_DestroyTexture", "P") +SDL_SetTextureBlendMode = _libSDL2.func("i", "SDL_SetTextureBlendMode", "PI") +SDL_UpdateTexture = _libSDL2.func("i", "SDL_UpdateTexture", "PPPi") + +# SDL timer functions NOT WORKING +SDL_AddTimer = _libSDL2.func("P", "SDL_AddTimer", "IPP") +SDL_RemoveTimer = _libSDL2.func("i", "SDL_RemoveTimer", "P") + + +def SDL_TimerCallback(tcb): + return ffi.callback("I", tcb, "IP") + + +############################################################################### +# SDL event union # +############################################################################### + +_event_struct_map = { + # Constants from _constants.py + SDL_KEYDOWN: SDL_KeyboardEvent, # noqa: F405 + SDL_KEYUP: SDL_KeyboardEvent, # noqa: F405 + SDL_MOUSEMOTION: SDL_MouseMotionEvent, # noqa: F405 + SDL_MOUSEBUTTONDOWN: SDL_MouseButtonEvent, # noqa: F405 + SDL_MOUSEBUTTONUP: SDL_MouseButtonEvent, # noqa: F405 + SDL_MOUSEWHEEL: SDL_MouseWheelEvent, # noqa: F405 + SDL_POLLSENTINEL: SDL_CommonEvent, # noqa: F405 +} + + +def SDL_Event(event=None): + """ + Convert event to an SDL_Event object using ctypes. + The size of the largest SDL_Event struct is 56 bytes. + """ + if event is None: + return bytearray(56) # Size of the largest SDL_Event struct + + event_type = int.from_bytes(event[:4], "little") + + if event_type in _event_struct_map: + return uctypes.struct(uctypes.addressof(event), _event_struct_map[event_type]) + return uctypes.struct(uctypes.addressof(event), SDL_CommonEvent) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py new file mode 100644 index 000000000..db8fac60d --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -0,0 +1,544 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`displaycore` +==================================================== + +A collection of classes and functions for working with displays and input devices +in *Python. The goal is to provide a common API for working with displays and +input devices across different platforms including MicroPython, CircuitPython and +CPython. It works on microcontrollers, desktops, web browsers and Jupyter notebooks. +""" + +import gc +from ._byteswap import byteswap + + +gc.collect() + + +def new_buffer(size): + """ + Create a new buffer of the specified size. In the future, this function may be + modified to use port-specific memory allocation such as ESP32's heap_caps_malloc. + + Args: + size (int): The size of the buffer to create. + + Returns: + (memoryview): The new buffer. + """ + return memoryview(bytearray(size)) + +def color888(r, g, b): + """ + Convert RGB values to a 24-bit color value. + + Args: + r (int): The red value. + g (int): The green value. + b (int): The blue value. + + Returns: + (int): The 24-bit color value. + """ + return (r << 16) | (g << 8) | b + + +def color565(r, g=None, b=None): + """ + Convert RGB values to a 16-bit color value. + + Args: + r (int, tuple or list): The red value or a tuple or list of RGB values. + g (int): The green value. + b (int): The blue value. + + Returns: + (int): The 16-bit color value + """ + if isinstance(r, (tuple, list)): + r, g, b = r[:3] + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) + + +def color565_swapped(r, g=0, b=0): + # Convert r, g, b in range 0-255 to a 16 bit color value RGB565 + # ggbbbbbb rrrrrggg + if isinstance(r, (tuple, list)): + r, g, b = r[:3] + color = color565(r, g, b) + return (color & 0xFF) << 8 | (color & 0xFF00) >> 8 + + +def color332(r, g, b): + # Convert r, g, b in range 0-255 to an 8 bit color value RGB332 + # rrrgggbb + return (r & 0xE0) | ((g >> 3) & 0x1C) | (b >> 6) + + +def color_rgb(color): + """ + color can be an 16-bit integer or a tuple, list or bytearray of length 2 or 3. + """ + if isinstance(color, int): + # convert 16-bit int color to 2 bytes + color = (color & 0xFF, color >> 8) + if len(color) == 2: + r = color[1] & 0xF8 | (color[1] >> 5) & 0x7 # 5 bit to 8 bit red + g = color[1] << 5 & 0xE0 | (color[0] >> 3) & 0x1F # 6 bit to 8 bit green + b = color[0] << 3 & 0xF8 | (color[0] >> 2) & 0x7 # 5 bit to 8 bit blue + else: + r, g, b = color + return (r, g, b) + + +class DisplayDriver: + def __init__(self, auto_refresh=False): + print(f"Initializing {self.__class__.__name__}...") + gc.collect() + + self.byteswap = byteswap + self._vssa = False # False means no vertical scroll + self._auto_byteswap = self.requires_byteswap + self._touch_device = None + if auto_refresh: + try: + from timer import get_timer + + self._timer = get_timer(self.show) + except ImportError: + raise ImportError("timer is required for auto_refresh") + else: + self._timer = None + self.init() + gc.collect() + print(f"{self.__class__.__name__} initialized.") + print(f"{self.__class__.__name__} requires_byteswap = {self.requires_byteswap}") + + def __del__(self): + self.deinit() + + ############### Universal API Methods, not usually overridden ################ + + @property + def width(self) -> int: + """The width of the display in pixels.""" + if ((self._rotation // 90) & 0x1) == 0x1: # if rotation index is odd + return self._height + return self._width + + @property + def height(self) -> int: + """The height of the display in pixels.""" + if ((self._rotation // 90) & 0x1) == 0x1: # if rotation index is odd + return self._width + return self._height + + @property + def rotation(self) -> int: + """ + The rotation of the display. + """ + return self._rotation + + @rotation.setter + def rotation(self, value) -> None: + """ + Sets the rotation of the display. + + Args: + value (int): The rotation of the display in degrees. + """ + + if value % 90 != 0: + value = value * 90 + + if value == self._rotation: + return + + print(f"{self.__class__.__name__}.rotation(): Setting rotation to {value}") + self._rotation_helper(value) + + self._rotation = value + + if self._touch_device is not None: + self._touch_device.rotation = value + + self.init() + + @property + def touch_device(self) -> object: + """ + The touch device. + """ + return self._touch_device + + @touch_device.setter + def touch_device(self, value) -> None: + """ + Sets the touch device. + + Args: + value (object): The touch device. + """ + if hasattr(value, "rotation") or value is None: + self._touch_device = value + else: + raise ValueError("touch_device must have a rotation attribute") + self._touch_device.rotation = self.rotation + + def fill(self, color): + """ + Fill the display with a color. + + Args: + color (int): The color to fill the display with. + """ + return self.fill_rect(0, 0, self.width, self.height, color) + + def scroll(self, dx, dy) -> None: + """ + Scroll the display. + + Args: + dx (int): The number of pixels to scroll horizontally. + dy (int): The number of pixels to scroll vertically. + """ + if dy != 0: + if self._vssa is not None: + self.vscsad(self._vssa + dy) + else: + self.vscsad(dy) + if dx != 0: + raise NotImplementedError("Horizontal scrolling not supported") + + def disable_auto_byteswap(self, value: bool) -> bool: + """ + Disable byte swapping in the display driver. + + If self.requires_byteswap and the guest application is capable of byte swapping color data + check to see if byte swapping can be disabled. If so, disable it. + + Usage: + ``` + # If byte swapping is required and the display driver is capable of having byte swapping disabled, + # disable it and set a flag so we can swap the color bytes as they are created. + if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) + else: + needs_swap = False + ``` + + Args: + value (bool): Whether to disable byte swapping. + + Returns: + (bool): Whether byte swapping was disabled successfully. + + """ + if self._requires_byteswap: + self._auto_byteswap = not value + else: + self._auto_byteswap = False + print(f"{self.__class__.__name__}: auto byte swapping = {self._auto_byteswap}") + return not self._auto_byteswap + + @property + def requires_byteswap(self) -> bool: + """ + Whether the display requires byte swapping. + """ + return self._requires_byteswap + + def blit_transparent(self, buf: memoryview, x: int, y: int, w: int, h: int, key: int): + """ + Blit a buffer with transparency. + + Args: + buf (memoryview): The buffer to blit. + x (int): The x coordinate to blit to. + y (int): The y coordinate to blit to. + w (int): The width to blit. + h (int): The height to blit. + key (int): The color key to use for transparency. + + Returns: + (tuple): The x, y, w, h coordinates of the blitted area. + """ + BPP = self.color_depth // 8 + key_bytes = key.to_bytes(BPP, "little") + stride = w * BPP + for j in range(h): + rowstart = j * stride + colstart = 0 + # iterate over each pixel looking for the first non-key pixel + while colstart < stride: + startoffset = rowstart + colstart + if buf[startoffset : startoffset + BPP] != key_bytes: + # found a non-key pixel + # then iterate over each pixel looking for the next key pixel + colend = colstart + while colend < stride: + endoffset = rowstart + colend + if buf[endoffset : endoffset + BPP] == key_bytes: + break + colend += BPP + # blit the non-key pixels + self.blit_rect( + buf[rowstart + colstart : rowstart + colend], + x + colstart // BPP, + y + j, + (colend - colstart) // BPP, + 1, + ) + colstart = colend + else: + colstart += BPP + return (x, y, w, h) + + @property + def vscroll(self) -> int: + """ + The vertical scroll position relative to the top fixed area. + + Returns: + (int): The vertical scroll position. + """ + return self.vscsad() - self._tfa + + @vscroll.setter + def vscroll(self, y) -> None: + """ + Set the vertical scroll position relative to the top fixed area. + + Args: + y (int): The vertical scroll position. + """ + self.vscsad((y % self._vsa) + self._tfa) + + def set_vscroll(self, tfa=0, bfa=0) -> None: + """ + Set the vertical scroll definition and move the vertical scroll to the top. + + Args: + tfa (int): The top fixed area. + bfa (int): The bottom fixed area. + """ + self.vscrdef(tfa, self.height - tfa - bfa, bfa) + self.vscroll = 0 + + @property + def tfa(self) -> int: + """ + The top fixed area set by set_vscroll or vscrdef. + + Returns: + (int): The top fixed area. + """ + return self._tfa + + @property + def vsa(self) -> int: + """ + The vertical scroll area set by set_vscroll or vscrdef. + + Returns: + (int): The vertical scroll area. + """ + return self._vsa + + @property + def bfa(self) -> int: + """ + The bottom fixed area set by set_vscroll or vscrdef. + + Returns: + (int): The bottom fixed area. + """ + return self._bfa + + def translate_point(self, point) -> tuple: + """ + Translate a point from real coordinates to scrolled coordinates. + + Useful for touch events. + + Args: + point (tuple): The x and y coordinates to translate. + + Returns: + (tuple): The translated x and y coordinates. + """ + x, y = point + if self.vscsad() and self.tfa < y < self.height - self.bfa: + y = y + self.vscsad() - self.tfa + if y >= (self.vsa + self.tfa): + y %= self.vsa + return x, y + + def scroll_by(self, value): + self.vscroll += value + + def scroll_to(self, value): + self.vscroll = value + + @property + def tfa_area(self): + """ + The top fixed area as an Area object. + + Returns: + (tuple): The top fixed area. + """ + return (0, 0, self.width, self.tfa) + + @property + def vsa_area(self): + """ + The vertical scroll area as an Area object. + + Returns: + (tuple): The vertical scroll area. + """ + return (0, self.tfa, self.width, self.vsa) + + @property + def bfa_area(self): + """ + The bottom fixed area as an Area object. + + Returns: + (tuple): The bottom fixed area. + """ + return (0, self.tfa + self.vsa, self.width, self.bfa) + + ############### Common API Methods, sometimes overridden ################ + + def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: + """ + Set the vertical scroll definition. Should be overridden by the + subclass and called as super().vscrdef(tfa, vsa, bfa). + + Args: + tfa (int): The top fixed area. + vsa (int): The vertical scroll area. + bfa (int): The bottom fixed area. + """ + if tfa + vsa + bfa != self.height: + raise ValueError("Sum of top, scroll and bottom areas must equal screen height") + self._tfa = tfa + self._vsa = vsa + self._bfa = bfa + + def vscsad(self, vssa: int | None = None) -> int: + """ + Set or get the vertical scroll start address. Should be overridden by the + subclass and called as super().vscsad(y). + + Args: + vssa (int): The vertical scroll start address. + + Returns: + (int): The vertical scroll start address. + """ + if vssa is not None: + while vssa < 0: + vssa += self._height + if vssa >= self._height: + vssa %= self._height + self._vssa = vssa + return vssa + + def _rotation_helper(self, value): + """ + Helper function to set the rotation of the display. + + Args: + value (int): The rotation of the display in degrees. + """ + # override this method in subclasses to handle rotation + pass + + ############### Empty API Methods, must be overridden if applicable ################ + + @property + def power(self) -> bool: + """The power state of the display.""" + return -1 + + @power.setter + def power(self, value: bool) -> None: + """ + Set the power state of the display. Should be overridden by the subclass. + + Args: + value (bool): True to power on, False to power off. + """ + return + + @property + def brightness(self) -> float: + """The brightness of the display.""" + return -1 + + @brightness.setter + def brightness(self, value: float) -> None: + """ + Set the brightness of the display. Should be overridden by the subclass. + + Args: + value (int, float): The brightness value from 0 to 1. + """ + return + + def invert_colors(self, value: bool) -> None: + """ + Invert the colors of the display. Should be overridden by the subclass. + + Args: + value (bool): True to invert the colors, False to restore the colors. + """ + return + + def reset(self) -> None: + """ + Perform a reset of the display. Should be overridden by the subclass. + """ + return + + def hard_reset(self) -> None: + """ + Perform a hardware reset of the display. Should be overridden by the subclass. + """ + return + + def soft_reset(self) -> None: + """ + Perform a software reset of the display. Should be overridden by the subclass. + """ + return + + def sleep_mode(self, value: bool) -> None: + """ + Set the sleep mode of the display. Should be overridden by the subclass. + + Args: + value (bool): True to enter sleep mode, False to exit sleep mode. + """ + return + + def deinit(self) -> None: + """ + Deinitialize the display. + """ + self.__del__() + return + + def show(self, *args, **kwargs) -> None: + """ + Show the display. Base class method does nothing. May be overridden by subclasses. + """ + return diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py new file mode 100644 index 000000000..4498ac2bd --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`displaycore._byteswap` +==================================================== + +A function to swap the bytes of a buffer in place. 3 implementations are provided: +- numpy: The preferred implementation using numpy, which is usually available in Python + and CircuitPython, and may be available in MicroPython with the numpy module. +- viper: A viper implementation for MicroPython only +- default: A default implementation with no dependencies +""" + +try: + # import byteswap from MicroPython if available + from byteswap import byteswap # type: ignore +except ImportError: + try: + # import numpy if available + try: + # import numpy for CPython + import numpy as np + except ImportError: + # import numpy for CircuitPython or MicroPython with numpy module + from ulab import numpy as np # type: ignore + def byteswap(buf): + """ + Swap the bytes of a 16-bit buffer in place using numpy. + """ + npbuf = np.frombuffer(buf, dtype=np.uint16) + npbuf.byteswap(inplace=True) + except Exception: + try: + # import byteswap_viper if available + from ._byteswap_viper import byteswap_viper + def byteswap(buf): + """ + Swap the bytes of a 16-bit buffer in place using viper. + """ + byteswap_viper(buf, len(buf)) + except Exception: + def byteswap(buf): + """ + Swap the bytes of a 16-bit buffer in place with no dependencies. + """ + buf[::2], buf[1::2] = buf[1::2], buf[::2] diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py new file mode 100644 index 000000000..fbd2777b5 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py @@ -0,0 +1,19 @@ +import micropython + +if 0: + class ptr8: + pass + +@micropython.viper +def byteswap_viper(buf: ptr8, buf_size: int): # noqa: F821 + """ + Swap the bytes in a buffer of 16-bit values in place. + + Args: + buf: The buffer to swap the bytes in. + buf_size: The size of the buffer in bytes + """ + for i in range(0, buf_size, 2): + tmp = buf[i] + buf[i] = buf[i + 1] + buf[i + 1] = tmp diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py new file mode 100644 index 000000000..0dba196ae --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("displaycore") \ No newline at end of file diff --git a/micropython/pydisplay/displaysys/examples/displaysys_block_test.py b/micropython/pydisplay/displaysys/examples/displaysys_block_test.py new file mode 100644 index 000000000..241a1b935 --- /dev/null +++ b/micropython/pydisplay/displaysys/examples/displaysys_block_test.py @@ -0,0 +1,46 @@ +""" pydisplay_simpletest.py """ +from board_config import display_drv +import random +import time +import gc + + +gc.collect() +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + +def test(): + raise Exception("Test exception") + +def main(): + # display_bus.register_callback(test) + block_size = 32 + blocks = [] + + max_x = display_drv.width - block_size - 1 + max_y = display_drv.height - block_size - 1 + + for pixel_color in [0x0000, 0xFFFF, 0xF800, 0x07E0, 0x001F, 0xFFE0, 0x07FF, 0xF81F]: + pixel_bytes = pixel_color.to_bytes(2, "big") if needs_swap else pixel_color.to_bytes(2, "little") + blocks.append(memoryview(bytearray(pixel_bytes * (block_size * block_size)))) + + print("Drawing blocks on display") + count = 0 + start_time = time.time() + while True: + display_drv.blit_rect( + random.choice(blocks), + random.randint(0, max_x), + random.randint(0, max_y), + block_size, + block_size, + ) + count += 1 + if count % 2000 == 0: + print("blocks/sec:", count / (time.time() - start_time)) + +main() diff --git a/micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py b/micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py new file mode 100644 index 000000000..05bddb4a4 --- /dev/null +++ b/micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py @@ -0,0 +1,38 @@ +""" pydisplay_simpletest.py """ +from board_config import display_drv +from random import randint, getrandbits +import time +import gc + + +gc.collect() +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + + +def main(): + block_size = 32 + + max_x = display_drv.width - block_size - 1 + max_y = display_drv.height - block_size - 1 + + print("Drawing blocks on display") + count = 0 + start_time = time.time() + while True: + display_drv.fill_rect( + randint(0, max_x), + randint(0, max_y), + block_size, + block_size, + getrandbits(16), + ) + count += 1 + if count % 1000 == 0: + print("blocks/sec:", count / (time.time() - start_time)) + +main() diff --git a/micropython/pydisplay/displaysys/examples/displaysys_simpletest.py b/micropython/pydisplay/displaysys/examples/displaysys_simpletest.py new file mode 100644 index 000000000..880e5cc84 --- /dev/null +++ b/micropython/pydisplay/displaysys/examples/displaysys_simpletest.py @@ -0,0 +1,11 @@ +from board_config import display_drv, broker +from random import getrandbits +from graphics import Area + +button_area = Area(display_drv.fill_rect(10, 10, 100, 100, 0xF800)) +while True: + if evt := broker.poll(): + if evt.type == broker.Events.MOUSEBUTTONDOWN: + if button_area.contains(evt.pos): + display_drv.fill_rect(*button_area, getrandbits(16)) + print(f"Button pressed at {evt.pos}") diff --git a/micropython/pydisplay/eventsys/eventsys/__init__.py b/micropython/pydisplay/eventsys/eventsys/__init__.py new file mode 100644 index 000000000..fea8077bb --- /dev/null +++ b/micropython/pydisplay/eventsys/eventsys/__init__.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`eventsys` +==================================================== +An Event System including event types and device types for *Python. +""" + +from micropython import const +from collections import namedtuple + + +class Events: + """ + A container for event types and classes. Similar to a C enum and struct. + """ + + # Event types (from SDL2 / PyGame, not complete) + QUIT = const(0x100) # User clicked the window close button + KEYDOWN = const(0x300) # Key pressed + KEYUP = const(0x301) # Key released + MOUSEMOTION = const(0x400) # Mouse moved + MOUSEBUTTONDOWN = const(0x401) # Mouse button pressed + MOUSEBUTTONUP = const(0x402) # Mouse button released + MOUSEWHEEL = const(0x403) # Mouse wheel motion + JOYAXISMOTION = const(0x600) # Joystick axis motion + JOYBALLMOTION = const(0x601) # Joystick trackball motion + JOYHATMOTION = const(0x602) # Joystick hat position change + JOYBUTTONDOWN = const(0x603) # Joystick button pressed + JOYBUTTONUP = const(0x604) # Joystick button released + _USER_TYPE_BASE = 0x8000 + + filter = [ + QUIT, + KEYDOWN, + KEYUP, + MOUSEMOTION, + MOUSEBUTTONDOWN, + MOUSEBUTTONUP, + MOUSEWHEEL, + ] + + # Event classes from PyGame + Unknown = namedtuple("Common", "type") + Motion = namedtuple("Motion", "type pos rel buttons touch window") + Button = namedtuple("Button", "type pos button touch window") + Wheel = namedtuple("Wheel", "type flipped x y precise_x precise_y touch window") + Key = namedtuple("Key", "type name key mod scancode window") + Quit = namedtuple("Quit", "type") + Any = namedtuple("Any", "type") + + @staticmethod + def new(types: list[str | tuple[str, int]] = [], classes: dict[str, str] = {}): + """ + Create new event types and classes for the Events class. + + For example, to create the events for the keypad device: + ``` + from eventsys import Events + + types = [("KEYDOWN", 0x300), ("KEYUP", 0x301)] + classes = { + "Key": "type name key mod scancode window", + } + Events.new_types(types, classes) + + # Optionally update the filter + Events.filter += [Events.KEYDOWN, Events.KEYUP] + ``` + + Args: + types (list[str | tuple[str, int]]): List of event types or tuples of event type and value. + If a value is not provided, the next available value will be used. + classes (dict[str, str]): Dictionary of event classes and fields. + """ + for type_name in types: + if isinstance(type_name, tuple): + type_name, value = type_name + else: + value = None + type_name = type_name.upper() + if hasattr(Events, type_name): + raise ValueError(f"Event type {type_name} already exists in Events class.") + else: + setattr(Events, type_name, value if value else Events._USER_TYPE_BASE) + if not value: + Events._USER_TYPE_BASE += 1 + + for event_class_name, event_class_fields in classes.items(): + event_class_name = event_class_name[0].upper() + event_class_name[1:].lower() + if hasattr(Events, event_class_name): + raise ValueError(f"Event class {event_class_name} already exists in Events class.") + else: + event_class_fields = event_class_fields.lower() + setattr( + Events, + event_class_name, + namedtuple(event_class_name, event_class_fields), + ) diff --git a/micropython/pydisplay/eventsys/eventsys/device.py b/micropython/pydisplay/eventsys/eventsys/device.py new file mode 100644 index 000000000..08769a17d --- /dev/null +++ b/micropython/pydisplay/eventsys/eventsys/device.py @@ -0,0 +1,692 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`eventsys.device` +==================================================== + +Device classes for eventsys's Event System. May also be used +with other applications. Devices are objects that poll for events +and return them. They can be subscribed to and unsubscribed from +to receive events. + +Devices can be created with Broker.create_device() or by calling the +constructor of the device class directly. Devices can be +subscribed to with .subscribe() and unsubscribed from with +.unsubscribe(). Devices can be polled for events with .poll(). +Devices can be registered with a broker device with .register_device() +and unregistered with .unregister_device(). Devices can be chained +together by setting the .broker property of a device to another device. + +Devices can be created with the following types: +- Types.BROKER: A device that polls multiple devices. +- Types.QUEUE: A device that returns multiple types of events. +- Types.TOUCH: A device that returns MOUSEBUTTONDOWN when touched, + MOUSEMOTION when moved and MOUSEBUTTONUP when released. +- Types.ENCODER: A device that returns MOUSEWHEEL events when turned, + MOUSEBUTTONDOWN when pressed. +- Types.KEYPAD: A device that returns KEYDOWN and KEYUP events when + keys are pressed or released. +- Types.JOYSTICK: A device that returns joystick events (not implemented). +""" + +from micropython import const +from . import Events +from sys import exit + + +_DEFAULT_TOUCH_ROTATION_TABLE = (0b000, 0b101, 0b110, 0b011) + +SWAP_XY = const(0b001) +REVERSE_X = const(0b010) +REVERSE_Y = const(0b100) + + +class Types: + """ + Device types for the Event System. + """ + + UNDEFINED = const(-1) + BROKER = const(0x00) + QUEUE = const(0x01) + TOUCH = const(0x02) + ENCODER = const(0x03) + KEYPAD = const(0x04) + JOYSTICK = const(0x05) + + @staticmethod + def new_type(type_name, responses): + """ + Create a new device type with a list of responses. + + Args: + type_name (str): The name of the device type. + responses (list[int]): A list of event types that the device can return. + + Returns: + (Device): The newly created device type. + + Raises: + ValueError: If `type_name` is not a string, `responses` is not a list, or any response is not an integer. + ValueError: If a device type with the same name already exists in the `Devices` class. + ValueError: If a device class with the same name already exists. + + Example: + To create the KEYPAD device type and `KeypadDevice` class: + + ```python + from eventsys.device import Types + from eventsys import Events + + KeypadDevice = Types.new_type("KEYPAD", [Events.KEYDOWN, Events.KEYUP]) + ``` + """ + if not isinstance(type_name, str): + raise ValueError("type_name must be a string") + type_name = type_name.strip().upper() + if not isinstance(responses, list): + raise ValueError("responses must be a list") + if not all(isinstance(event, int) for event in responses): + raise ValueError("all responses must be integers") + + if hasattr(Types, type_name): + raise ValueError(f"Device type {type_name} already exists in Devices class.") + class_name = type_name[0].upper() + type_name[1:].lower() + "Device" + if class_name in [cls.__name__ for cls in _mapping.values()]: + raise ValueError(f"Device class {class_name} already exists.") + + value = len(_mapping) + setattr(Types, type_name, value) + NewClass = type(class_name, (Device,), {"type": value, "responses": responses}) + _mapping[value] = NewClass + return NewClass + + +class Device: + """ + Base class for devices. Must be subclassed. Should not be instantiated directly. + + Attributes: + type (Devices): The type of the device. + responses (list): The list of event types that the device can respond to. + """ + + type = Types.UNDEFINED + responses = Events.filter + + def __init__(self, read=None, data=None, read2=None, data2=None): + """ + Create a new device object. + + Args: + read (callable, optional): A function that returns an event or None. Defaults to None. + data (Any, optional): Data to pass to the read function. Defaults to None. + read2 (callable, optional): A function that returns a value or None. Defaults to None. + data2 (Any, optional): Data to pass to the read2 function. Defaults to None. + """ + self._event_callbacks = dict() + + self._read = read if read else lambda: None + self._data = data + self._read2 = read2 if read2 else lambda: None + self._data2 = data2 + + self._broker = None + self._state = None + self._user_data = None # Can be set and retrieved by apps such as lv_config + + def poll(self, *args) -> Events: + """ + Poll the device for events. + + Args: + *args: Additional arguments that can be passed to the read callback functions. + + Returns: + Event: The event that was polled or None if no event was polled. + """ + if (event := self._poll()) is not None: + if event.type in Events.filter: + if event.type == Events.QUIT: + if self._broker: + self._broker.quit() + if callback_list := self._event_callbacks.get(event.type): + for callback in callback_list: + callback(event, *args) + return event + return None + + def subscribe(self, callback, event_types=None): + """ + Subscribe to events from the device. + + Args: + callback (function): The function to call when an event is received. + event_types (list[int] | None): A list of event types to subscribe to. + + Raises: + ValueError: If `callback` is not callable. + ValueError: If any event type in `event_types` is not a response from this device. + + Example: + ```python + def callback(event): + print(event) + + device.subscribe(callback, [Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP]) + ``` + + This will call `callback` when the device receives a MOUSEBUTTONDOWN or MOUSEBUTTONUP event. + """ + event_types = event_types or self.responses + if not callable(callback): + raise ValueError("callback is not callable.") + for event_type in event_types: + if event_type not in self.responses: + raise ValueError("the specified event_type is not a response from this device") + callback_set = self._event_callbacks.get(event_type, set()) + callback_set.add(callback) + self._event_callbacks[event_type] = callback_set + + def unsubscribe(self, callback, event_types=None): + """ + Unsubscribes a callback function from one or more event types. + + Args: + callback (function): The callback function to unsubscribe. + event_types (list): A list of event types to unsubscribe from. + """ + event_types = event_types or self.responses + for event_type in event_types: + if callback_set := self._event_callbacks.get(event_type): + callback_set.remove(callback) + + @property + def broker(self): + """ + The broker that manages this device. + """ + return self._broker + + @broker.setter + def broker(self, broker): + self._broker = broker + + @property + def user_data(self): + """ + User data that can be set and retrieved by applications. + """ + return self._user_data + + @user_data.setter + def user_data(self, value): + self._user_data = value + + +class Broker(Device): + """ + The Broker class is a device that polls multiple devices for events and forwards them to + subscribers. + + Attributes: + type (Devices): The type of the device (set to `Types.BROKER`). + responses (list): The list of event types that the device can respond to. + Events (Events): The Events class for convenience. + Applications can use Broker.Events.KEYDOWN, etc. + """ + + type = Types.BROKER + responses = Events.filter + Events = Events # Create a reference to the Events class for convenience. + + def __init__(self): + super().__init__() + self.devices = [] # List of devices to poll + self._device_callbacks = dict() + # Function to call when the window close button is clicked. + # Set it like `display_drv.quit_func = cleanup_func` where `cleanup_func` is a + # function that cleans up resources and calls `sys.exit()`. + # .poll() must be called periodically to check for the quit event. + self._quit_func = exit + + def subscribe(self, callback, event_types=None, device_types=None): + """ + Subscribes a callback function to receive events. + + Args: + callback (function): The callback function to subscribe. + event_types (list, optional): The list of event types to subscribe to. Defaults to None. + device_types (list, optional): The list of device types to subscribe to. Defaults to None. + + Raises: + ValueError: If the callback is not callable. + ValueError: If both device_types and event_types are provided. + ValueError: If neither device_types nor event_types are provided. + """ + if not callable(callback): + raise ValueError("callback is not callable.") + if device_types is not None and event_types is not None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is None and event_types is None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is not None: + for device_type in device_types: + callback_set = self._device_callbacks.get(device_type, set()) + callback_set.add(callback) + self._device_callbacks[device_type] = callback_set + else: + super().subscribe(callback, event_types) + + def unsubscribe(self, callback, event_types=None, device_types=None): + """ + Unsubscribes a callback function from receiving events. + + Args: + callback (function): The callback function to unsubscribe. + event_types (list, optional): The list of event types to unsubscribe from. Defaults to None. + device_types (list, optional): The list of device types to unsubscribe from. Defaults to None. + + Raises: + ValueError: If both device_types and event_types are provided. + ValueError: If neither device_types nor event_types are provided. + """ + if device_types is not None and event_types is not None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is None and event_types is None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is not None: + for device_type in device_types: + if callback_set := self._device_callbacks.get(device_type): + callback_set.remove(callback) + else: + super().unsubscribe(callback, event_types) + + def create_device(self, type=Types.QUEUE, **kwargs) -> Device: + """ + Create a device object. + + Args: + type (int, optional): The type of device to create. Defaults to Types.QUEUE. + **kwargs (Any): Arbitrary keyword arguments for the class constructor. + + Returns: + Device: The created device object. + + Raises: + ValueError: If the device type is invalid. + """ + if cls := _mapping.get(type): + dev = cls(**kwargs) + self.register_device(dev) + return dev + raise ValueError("Invalid device type") + + def register_device(self, dev): + """ + Register a device to be polled. + + Args: + dev (Device): The device object to register. + """ + dev.broker = self + self.devices.append(dev) + + def unregister_device(self, dev): + """ + Unregister a device. + + Args: + dev (Device): The device object to unregister. + """ + if dev in self.devices: + self.devices.remove(dev) + dev.broker = None + + @property + def quit_func(self): + """ + The function to call when the window close button is clicked. + """ + return self._quit_func + + @quit_func.setter + def quit_func(self, value): + """ + Sets the function to call when the window close button is clicked. + + Args: + value (function): The function to call when the window close button is clicked. + """ + if not callable(value): + raise ValueError("quit_func must be callable") + self._quit_func = value + + def quit(self): + """ + Call the quit function. + """ + self._quit_func() + + def _poll(self): + """ + Polls the registered devices for events. + + Returns: + object: The event object if an event is received, otherwise None. + """ + for device in self.devices: + if (event := device.poll()) is not None: + if callback_list := self._device_callbacks.get(device.type): + for func in callback_list(): + func(event) + return event + return None + + +class QueueDevice(Device): + """ + Represents a queue device. + + Attributes: + type (str): The type of the device. + responses (list): The list of events that the device can respond to. + """ + + type = Types.QUEUE + responses = Events.filter + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self._data2 is None: + self._data2 = Events.filter + if hasattr(self._data, "touch_scale"): + self.scale = self._data.touch_scale + else: + self.scale = 1 + + def _poll(self): + """ + Polls the device for events. + + Returns: + Event or None: The next event from the device, or None if no event is available. + """ + if (event := self._read()) is not None: + if event.type in self._data2: + if event.type in ( + Events.MOUSEMOTION, + Events.MOUSEBUTTONDOWN, + Events.MOUSEBUTTONUP, + ): + if (scale := self.scale) != 1: + event.pos = ( + int(event.pos[0] // scale), + int(event.pos[1] // scale), + ) + if event.type == Events.MOUSEMOTION: + event.rel = (event.rel[0] // scale, event.rel[1] // scale) + return event + return None + + +class TouchDevice(Device): + """ + Represents a touch input device. + + This class handles touch input events and provides methods to read touch data + from the underlying touch driver. It supports reporting mouse button 1 events + such as mouse motion, mouse button down, and mouse button up. + + Attributes: + type (str): The type of the device (set to Types.TOUCH). + responses (tuple): The supported event types for the device. + + Args: + *args (Any): Variable length argument list. + **kwargs (Any): Arbitrary keyword arguments. + """ + + type = Types.TOUCH + responses = (Events.MOUSEMOTION, Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self._data is None: + raise ValueError("TouchDevice requires a display device as 'data'") + if self._data2 is None: # self._data is a rotation table + self._data2 = _DEFAULT_TOUCH_ROTATION_TABLE + self._data.touch_device = self + self.rotation = self._data.rotation + + @property + def rotation(self): + """ + Get the rotation value of the touch device. + + Returns: + rotation (int): The rotation value in degrees. + """ + return self._rotation + + @rotation.setter + def rotation(self, value): + """ + Set the rotation value of the touch device. + + Args: + value (int): The rotation value in degrees. + """ + self._rotation = value % 360 + + # _mask is an integer from 0 to 7 (or 0b001 to 0b111, 3 bits) + # Currently, bit 2 = invert_y, bit 1 is invert_x and bit 0 is swap_xy, but that may change. + self._mask = self._data2[self._rotation // 90] + + @property + def rotation_table(self): + """ + Get the rotation table of the touch device. + + Returns: + list: The rotation table. + """ + return self._data2 + + @rotation_table.setter + def rotation_table(self, value): + """ + Set the rotation table of the touch device. + + Args: + value (list): The rotation table. + """ + self._data2 = value + + def _poll(self): + """ + Poll the touch device for touch events. + + Returns: + Event: The touch event generated by the touch device. + """ + try: # If called too quickly, the touch driver may raise OSError: [Errno 116] ETIMEDOUT + touched = self._read() + except OSError: + return None + if touched: + last_pos = self._state + # If it looks like a point, use it, otherwise get the first point out of the list / tuple + (x, y, *_) = touched if isinstance(touched[0], int) else touched[0] + + if self._mask & SWAP_XY: + x, y = y, x + if self._mask & REVERSE_X: + x = self._data.width - x - 1 + if self._mask & REVERSE_Y: + y = self._data.height - y - 1 + self._state = (x, y) + if last_pos is not None: + last_x, last_y = last_pos + return Events.Motion( + Events.MOUSEMOTION, + self._state, + (x - last_x, y - last_y), + (1, 0, 0), + False, + None, + ) + else: + return Events.Button(Events.MOUSEBUTTONDOWN, self._state, 1, False, None) + elif self._state is not None: + last_pos = self._state + self._state = None + return Events.Button(Events.MOUSEBUTTONUP, last_pos, 1, False, None) + return None + + +class EncoderDevice(Device): + """ + A class representing an encoder device. + + Attributes: + type (str): The type of the device (ENCODER). + responses (tuple): The events that the device can respond to (MOUSEWHEEL, MOUSEBUTTONDOWN, MOUSEBUTTONUP). + """ + + type = Types.ENCODER + responses = (Events.MOUSEWHEEL, Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP) + + def __init__(self, *args, **kwargs): + """ + Initializes a new instance of the EncoderDevice class. + + Args: + *args (Any): Variable length argument list. + **kwargs (Any): Arbitrary keyword arguments. + + Notes: + - self._data is the mouse button number to report for the switch. + Default is 2 (middle mouse button). If the mouse button number is even, + the wheel will report vertical (y) movement. If the mouse button number is odd, + the wheel will report horizontal (x) movement. This corresponds to a typical mouse + wheel being button 2 and the wheel moving vertically. It also corresponds to + scrolling horizontally on a touchpad with two-finger scrolling and using the right button. + """ + super().__init__(*args, **kwargs) + self._state = (0, False) # (position, pressed) + self._data = self._data if self._data else 2 # Default to middle mouse button + + def _poll(self): + """ + Polls the encoder device for changes and returns the corresponding event. + + Returns: + Event: The event generated by the encoder device, or None if no event occurred. + """ + last_pos, last_pressed = self._state + pressed = self._read2() + if pressed != last_pressed: + self._state = (last_pos, pressed) + return Events.Button( + Events.MOUSEBUTTONDOWN if pressed else Events.MOUSEBUTTONUP, + (0, 0), + self._data, + False, + None, + ) + + pos = self._read() + if pos != last_pos: + steps = pos - last_pos + self._state = (pos, last_pressed) + if self._data % 2 == 0: + return Events.Wheel(Events.MOUSEWHEEL, False, 0, steps, 0, steps, False, None) + return Events.Wheel(Events.MOUSEWHEEL, False, steps, 0, steps, 0, False, None) + return None + + +class KeypadDevice(Device): + """ + Represents a keypad device. + + Attributes: + type (Devices): The type of the device (set to `Types.KEYPAD`). + responses (tuple): The types of events that the device can respond to (set to `(Events.KEYDOWN, Events.KEYUP)`). + + Methods: + __init__: Initializes the KeypadDevice object. + _poll: Polls the keypad for key events. + """ + + type = Types.KEYPAD + responses = (Events.KEYDOWN, Events.KEYUP) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._state = set() + + def _poll(self): + """ + Polls the keypad for key events. + + Returns: + Events.Key or None: An instance of the `Events.Key` class representing the key event, or `None` if no key event occurred. + """ + keys = set(self._read()) + released = self._state - keys + if released: + key = released.pop() + self._state.remove(key) + return Events.Key(Events.KEYUP, chr(key), key, 0, 0) + pressed = keys - self._state + if pressed: + key = pressed.pop() + self._state.add(key) + return Events.Key(Events.KEYDOWN, chr(key), key, 0, 0) + return None + + +class JoystickDevice(Device): + """ + Represents a joystick device. + + Attributes: + type (Devices): The type of the device, set to `Types.JOYSTICK`. + responses (tuple): A tuple of event types that this device can respond to. + + Methods: + __init__(*args, **kwargs): Initializes the JoystickDevice instance. + _poll(): Polls the device for events. + + Raises: + NotImplementedError: If the `_poll` method is not implemented. + """ + + type = Types.JOYSTICK + responses = ( + Events.JOYAXISMOTION, + Events.JOYBALLMOTION, + Events.JOYHATMOTION, + Events.JOYBUTTONDOWN, + Events.JOYBUTTONUP, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _poll(self): + raise NotImplementedError("JoystickDevice.read() not implemented") + + +_mapping = { + # Mapping of device types to device classes + Types.BROKER: Broker, + Types.QUEUE: QueueDevice, + Types.TOUCH: TouchDevice, + Types.ENCODER: EncoderDevice, + Types.KEYPAD: KeypadDevice, + Types.JOYSTICK: JoystickDevice, +} diff --git a/micropython/pydisplay/eventsys/eventsys/keys.py b/micropython/pydisplay/eventsys/eventsys/keys.py new file mode 100755 index 000000000..9531c0dc6 --- /dev/null +++ b/micropython/pydisplay/eventsys/eventsys/keys.py @@ -0,0 +1,571 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`eventsys.keys` +==================================================== +""" + +from micropython import const as _const + + +class Keys: + """ + A container for key codes and names. Similar to a C enum and struct. + """ + + def keyname(x): + return Keys._keytable.get(x, "Unknown") + + def key(x): + return list(Keys._keytable.keys())[list(Keys._keytable.values()).index(x)] + + def modname(x): + return Keys._modtable.get(x, "Unknown") + + def mod(x): + return list(Keys._modtable.keys())[list(Keys._modtable.values()).index(x)] + + K_UNKNOWN = _const(0) + K_BACKSPACE = _const(8) + K_TAB = _const(9) + K_RETURN = _const(13) + K_ESCAPE = _const(27) + K_SPACE = _const(32) + K_EXCLAIM = _const(33) + K_QUOTEDBL = _const(34) + K_HASH = _const(35) + K_DOLLAR = _const(36) + K_PERCENT = _const(37) + K_AMPERSAND = _const(38) + K_QUOTE = _const(39) + K_LEFTPAREN = _const(40) + K_RIGHTPAREN = _const(41) + K_ASTERISK = _const(42) + K_PLUS = _const(43) + K_COMMA = _const(44) + K_MINUS = _const(45) + K_PERIOD = _const(46) + K_SLASH = _const(47) + K_0 = _const(48) + K_1 = _const(49) + K_2 = _const(50) + K_3 = _const(51) + K_4 = _const(52) + K_5 = _const(53) + K_6 = _const(54) + K_7 = _const(55) + K_8 = _const(56) + K_9 = _const(57) + K_COLON = _const(58) + K_SEMICOLON = _const(59) + K_LESS = _const(60) + K_EQUALS = _const(61) + K_GREATER = _const(62) + K_QUESTION = _const(63) + K_AT = _const(64) + K_LEFTBRACKET = _const(91) + K_BACKSLASH = _const(92) + K_RIGHTBRACKET = _const(93) + K_CARET = _const(94) + K_UNDERSCORE = _const(95) + K_BACKQUOTE = _const(96) + K_a = _const(97) + K_b = _const(98) + K_c = _const(99) + K_d = _const(100) + K_e = _const(101) + K_f = _const(102) + K_g = _const(103) + K_h = _const(104) + K_i = _const(105) + K_j = _const(106) + K_k = _const(107) + K_l = _const(108) + K_m = _const(109) + K_n = _const(110) + K_o = _const(111) + K_p = _const(112) + K_q = _const(113) + K_r = _const(114) + K_s = _const(115) + K_t = _const(116) + K_u = _const(117) + K_v = _const(118) + K_w = _const(119) + K_x = _const(120) + K_y = _const(121) + K_z = _const(122) + K_DELETE = _const(127) + K_CAPSLOCK = _const(1073741881) + K_F1 = _const(1073741882) + K_F2 = _const(1073741883) + K_F3 = _const(1073741884) + K_F4 = _const(1073741885) + K_F5 = _const(1073741886) + K_F6 = _const(1073741887) + K_F7 = _const(1073741888) + K_F8 = _const(1073741889) + K_F9 = _const(1073741890) + K_F10 = _const(1073741891) + K_F11 = _const(1073741892) + K_F12 = _const(1073741893) + K_PRINTSCREEN = _const(1073741894) + K_SCROLLLOCK = _const(1073741895) + K_PAUSE = _const(1073741896) + K_INSERT = _const(1073741897) + K_HOME = _const(1073741898) + K_PAGEUP = _const(1073741899) + K_END = _const(1073741901) + K_PAGEDOWN = _const(1073741902) + K_RIGHT = _const(1073741903) + K_LEFT = _const(1073741904) + K_DOWN = _const(1073741905) + K_UP = _const(1073741906) + K_NUMLOCKCLEAR = _const(1073741907) + K_KP_DIVIDE = _const(1073741908) + K_KP_MULTIPLY = _const(1073741909) + K_KP_MINUS = _const(1073741910) + K_KP_PLUS = _const(1073741911) + K_KP_ENTER = _const(1073741912) + K_KP_1 = _const(1073741913) + K_KP_2 = _const(1073741914) + K_KP_3 = _const(1073741915) + K_KP_4 = _const(1073741916) + K_KP_5 = _const(1073741917) + K_KP_6 = _const(1073741918) + K_KP_7 = _const(1073741919) + K_KP_8 = _const(1073741920) + K_KP_9 = _const(1073741921) + K_KP_0 = _const(1073741922) + K_KP_PERIOD = _const(1073741923) + K_APPLICATION = _const(1073741925) + K_POWER = _const(1073741926) + K_KP_EQUALS = _const(1073741927) + K_F13 = _const(1073741928) + K_F14 = _const(1073741929) + K_F15 = _const(1073741930) + K_F16 = _const(1073741931) + K_F17 = _const(1073741932) + K_F18 = _const(1073741933) + K_F19 = _const(1073741934) + K_F20 = _const(1073741935) + K_F21 = _const(1073741936) + K_F22 = _const(1073741937) + K_F23 = _const(1073741938) + K_F24 = _const(1073741939) + K_EXECUTE = _const(1073741940) + K_HELP = _const(1073741941) + K_MENU = _const(1073741942) + K_SELECT = _const(1073741943) + K_STOP = _const(1073741944) + K_AGAIN = _const(1073741945) + K_UNDO = _const(1073741946) + K_CUT = _const(1073741947) + K_COPY = _const(1073741948) + K_PASTE = _const(1073741949) + K_FIND = _const(1073741950) + K_MUTE = _const(1073741951) + K_VOLUMEUP = _const(1073741952) + K_VOLUMEDOWN = _const(1073741953) + K_KP_COMMA = _const(1073741957) + K_KP_EQUALSAS400 = _const(1073741958) + K_ALTERASE = _const(1073741977) + K_SYSREQ = _const(1073741978) + K_CANCEL = _const(1073741979) + K_CLEAR = _const(1073741980) + K_PRIOR = _const(1073741981) + K_RETURN2 = _const(1073741982) + K_SEPARATOR = _const(1073741983) + K_OUT = _const(1073741984) + K_OPER = _const(1073741985) + K_CLEARAGAIN = _const(1073741986) + K_CRSEL = _const(1073741987) + K_EXSEL = _const(1073741988) + K_KP_00 = _const(1073742000) + K_KP_000 = _const(1073742001) + K_THOUSANDSSEPARATOR = _const(1073742002) + K_DECIMALSEPARATOR = _const(1073742003) + K_CURRENCYUNIT = _const(1073742004) + K_CURRENCYSUBUNIT = _const(1073742005) + K_KP_LEFTPAREN = _const(1073742006) + K_KP_RIGHTPAREN = _const(1073742007) + K_KP_LEFTBRACE = _const(1073742008) + K_KP_RIGHTBRACE = _const(1073742009) + K_KP_TAB = _const(1073742010) + K_KP_BACKSPACE = _const(1073742011) + K_KP_A = _const(1073742012) + K_KP_B = _const(1073742013) + K_KP_C = _const(1073742014) + K_KP_D = _const(1073742015) + K_KP_E = _const(1073742016) + K_KP_F = _const(1073742017) + K_KP_XOR = _const(1073742018) + K_KP_POWER = _const(1073742019) + K_KP_PERCENT = _const(1073742020) + K_KP_LESS = _const(1073742021) + K_KP_GREATER = _const(1073742022) + K_KP_AMPERSAND = _const(1073742023) + K_KP_DBLAMPERSAND = _const(1073742024) + K_KP_VERTICALBAR = _const(1073742025) + K_KP_DBLVERTICALBAR = _const(1073742026) + K_KP_COLON = _const(1073742027) + K_KP_HASH = _const(1073742028) + K_KP_SPACE = _const(1073742029) + K_KP_AT = _const(1073742030) + K_KP_EXCLAM = _const(1073742031) + K_KP_MEMSTORE = _const(1073742032) + K_KP_MEMRECALL = _const(1073742033) + K_KP_MEMCLEAR = _const(1073742034) + K_KP_MEMADD = _const(1073742035) + K_KP_MEMSUBTRACT = _const(1073742036) + K_KP_MEMMULTIPLY = _const(1073742037) + K_KP_MEMDIVIDE = _const(1073742038) + K_KP_PLUSMINUS = _const(1073742039) + K_KP_CLEAR = _const(1073742040) + K_KP_CLEARENTRY = _const(1073742041) + K_KP_BINARY = _const(1073742042) + K_KP_OCTAL = _const(1073742043) + K_KP_DECIMAL = _const(1073742044) + K_KP_HEXADECIMAL = _const(1073742045) + K_LCTRL = _const(1073742048) + K_LSHIFT = _const(1073742049) + K_LALT = _const(1073742050) + K_LGUI = _const(1073742051) + K_RCTRL = _const(1073742052) + K_RSHIFT = _const(1073742053) + K_RALT = _const(1073742054) + K_RGUI = _const(1073742055) + K_MODE = _const(1073742081) + K_AUDIONEXT = _const(1073742082) + K_AUDIOPREV = _const(1073742083) + K_AUDIOSTOP = _const(1073742084) + K_AUDIOPLAY = _const(1073742085) + K_AUDIOMUTE = _const(1073742086) + K_MEDIASELECT = _const(1073742087) + K_WWW = _const(1073742088) + K_MAIL = _const(1073742089) + K_CALCULATOR = _const(1073742090) + K_COMPUTER = _const(1073742091) + K_AC_SEARCH = _const(1073742092) + K_AC_HOME = _const(1073742093) + K_AC_BACK = _const(1073742094) + K_AC_FORWARD = _const(1073742095) + K_AC_STOP = _const(1073742096) + K_AC_REFRESH = _const(1073742097) + K_AC_BOOKMARKS = _const(1073742098) + K_BRIGHTNESSDOWN = _const(1073742099) + K_BRIGHTNESSUP = _const(1073742100) + K_DISPLAYSWITCH = _const(1073742101) + K_KBDILLUMTOGGLE = _const(1073742102) + K_KBDILLUMDOWN = _const(1073742103) + K_KBDILLUMUP = _const(1073742104) + K_EJECT = _const(1073742105) + K_SLEEP = _const(1073742106) + + _keytable = { + K_UNKNOWN: "Unknown", + K_BACKSPACE: "Backspace", + K_TAB: "Tab", + K_RETURN: "Return", + K_ESCAPE: "Escape", + K_SPACE: "Space", + K_EXCLAIM: "!", + K_QUOTEDBL: '"', + K_HASH: "#", + K_DOLLAR: "$", + K_PERCENT: "%", + K_AMPERSAND: "&", + K_QUOTE: "'", + K_LEFTPAREN: "(", + K_RIGHTPAREN: ")", + K_ASTERISK: "*", + K_PLUS: "+", + K_COMMA: ",", + K_MINUS: "-", + K_PERIOD: ".", + K_SLASH: "/", + K_0: "0", + K_1: "1", + K_2: "2", + K_3: "3", + K_4: "4", + K_5: "5", + K_6: "6", + K_7: "7", + K_8: "8", + K_9: "9", + K_COLON: ":", + K_SEMICOLON: ";", + K_LESS: "<", + K_EQUALS: "=", + K_GREATER: ">", + K_QUESTION: "?", + K_AT: "@", + # 65: " A", + # 66: " B", + # 67: " C", + # 68: " D", + # 69: " E", + # 70: " F", + # 71: " G", + # 72: " H", + # 73: " I", + # 74: " J", + # 75: " K", + # 76: " L", + # 77: " M", + # 78: " N", + # 79: " O", + # 80: " P", + # 81: " Q", + # 82: " R", + # 83: " S", + # 84: " T", + # 85: " U", + # 86: " V", + # 87: " W", + # 88: " X", + # 89: " Y", + # 90: " Z", + K_LEFTBRACKET: "[", + K_BACKSLASH: "\\", + K_RIGHTBRACKET: "]", + K_CARET: "^", + K_UNDERSCORE: "_", + K_BACKQUOTE: "`", + K_a: "A", + K_b: "B", + K_c: "C", + K_d: "D", + K_e: "E", + K_f: "F", + K_g: "G", + K_h: "H", + K_i: "I", + K_j: "J", + K_k: "K", + K_l: "L", + K_m: "M", + K_n: "N", + K_o: "O", + K_p: "P", + K_q: "Q", + K_r: "R", + K_s: "S", + K_t: "T", + K_u: "U", + K_v: "V", + K_w: "W", + K_x: "X", + K_y: "Y", + K_z: "Z", + # 123: "{", + # 124: "|", + # 125: "}", + # 126: "~", + K_DELETE: "Delete", + K_CAPSLOCK: "CapsLock", + K_F1: "F1", + K_F2: "F2", + K_F3: "F3", + K_F4: "F4", + K_F5: "F5", + K_F6: "F6", + K_F7: "F7", + K_F8: "F8", + K_F9: "F9", + K_F10: "F10", + K_F11: "F11", + K_F12: "F12", + K_PRINTSCREEN: "PrintScreen", + K_SCROLLLOCK: "ScrollLock", + K_PAUSE: "Pause", + K_INSERT: "Insert", + K_HOME: "Home", + K_PAGEUP: "PageUp", + K_END: "End", + K_PAGEDOWN: "PageDown", + K_RIGHT: "Right", + K_LEFT: "Left", + K_DOWN: "Down", + K_UP: "Up", + K_NUMLOCKCLEAR: "Numlock", + K_KP_DIVIDE: "Keypad /", + K_KP_MULTIPLY: "Keypad *", + K_KP_MINUS: "Keypad -", + K_KP_PLUS: "Keypad +", + K_KP_ENTER: "Keypad Enter", + K_KP_1: "Keypad 1", + K_KP_2: "Keypad 2", + K_KP_3: "Keypad 3", + K_KP_4: "Keypad 4", + K_KP_5: "Keypad 5", + K_KP_6: "Keypad 6", + K_KP_7: "Keypad 7", + K_KP_8: "Keypad 8", + K_KP_9: "Keypad 9", + K_KP_0: "Keypad 0", + K_KP_PERIOD: "Keypad .", + K_APPLICATION: "Application", + K_POWER: "Power", + K_KP_EQUALS: "Keypad =", + K_F13: "F13", + K_F14: "F14", + K_F15: "F15", + K_F16: "F16", + K_F17: "F17", + K_F18: "F18", + K_F19: "F19", + K_F20: "F20", + K_F21: "F21", + K_F22: "F22", + K_F23: "F23", + K_F24: "F24", + K_EXECUTE: "Execute", + K_HELP: "Help", + K_MENU: "Menu", + K_SELECT: "Select", + K_STOP: "Stop", + K_AGAIN: "Again", + K_UNDO: "Undo", + K_CUT: "Cut", + K_COPY: "Copy", + K_PASTE: "Paste", + K_FIND: "Find", + K_MUTE: "Mute", + K_VOLUMEUP: "VolumeUp", + K_VOLUMEDOWN: "VolumeDown", + K_KP_COMMA: "Keypad ", + K_KP_EQUALSAS400: "Keypad = (AS400)", + K_ALTERASE: "AltErase", + K_SYSREQ: "SysReq", + K_CANCEL: "Cancel", + K_CLEAR: "Clear", + K_PRIOR: "Prior", + K_RETURN2: "Return", + K_SEPARATOR: "Separator", + K_OUT: "Out", + K_OPER: "Oper", + K_CLEARAGAIN: "Clear / Again", + K_CRSEL: "CrSel", + K_EXSEL: "ExSel", + K_KP_00: "Keypad 00", + K_KP_000: "Keypad 000", + K_THOUSANDSSEPARATOR: "ThousandsSeparator", + K_DECIMALSEPARATOR: "DecimalSeparator", + K_CURRENCYUNIT: "CurrencyUnit", + K_CURRENCYSUBUNIT: "CurrencySubUnit", + K_KP_LEFTPAREN: "Keypad (", + K_KP_RIGHTPAREN: "Keypad )", + K_KP_LEFTBRACE: "Keypad {", + K_KP_RIGHTBRACE: "Keypad }", + K_KP_TAB: "Keypad Tab", + K_KP_BACKSPACE: "Keypad Backspace", + K_KP_A: "Keypad A", + K_KP_B: "Keypad B", + K_KP_C: "Keypad C", + K_KP_D: "Keypad D", + K_KP_E: "Keypad E", + K_KP_F: "Keypad F", + K_KP_XOR: "Keypad XOR", + K_KP_POWER: "Keypad ^", + K_KP_PERCENT: "Keypad %", + K_KP_LESS: "Keypad <", + K_KP_GREATER: "Keypad >", + K_KP_AMPERSAND: "Keypad &", + K_KP_DBLAMPERSAND: "Keypad &&", + K_KP_VERTICALBAR: "Keypad |", + K_KP_DBLVERTICALBAR: "Keypad ||", + K_KP_COLON: "Keypad :", + K_KP_HASH: "Keypad #", + K_KP_SPACE: "Keypad Space", + K_KP_AT: "Keypad @", + K_KP_EXCLAM: "Keypad !", + K_KP_MEMSTORE: "Keypad MemStore", + K_KP_MEMRECALL: "Keypad MemRecall", + K_KP_MEMCLEAR: "Keypad MemClear", + K_KP_MEMADD: "Keypad MemAdd", + K_KP_MEMSUBTRACT: "Keypad MemSubtract", + K_KP_MEMMULTIPLY: "Keypad MemMultiply", + K_KP_MEMDIVIDE: "Keypad MemDivide", + K_KP_PLUSMINUS: "Keypad +/-", + K_KP_CLEAR: "Keypad Clear", + K_KP_CLEARENTRY: "Keypad ClearEntry", + K_KP_BINARY: "Keypad Binary", + K_KP_OCTAL: "Keypad Octal", + K_KP_DECIMAL: "Keypad Decimal", + K_KP_HEXADECIMAL: "Keypad Hexadecimal", + K_LCTRL: "Left Ctrl", + K_LSHIFT: "Left Shift", + K_LALT: "Left Alt", + K_LGUI: "Left GUI", + K_RCTRL: "Right Ctrl", + K_RSHIFT: "Right Shift", + K_RALT: "Right Alt", + K_RGUI: "Right GUI", + K_MODE: "ModeSwitch", + K_AUDIONEXT: "AudioNext", + K_AUDIOPREV: "AudioPrev", + K_AUDIOSTOP: "AudioStop", + K_AUDIOPLAY: "AudioPlay", + K_AUDIOMUTE: "AudioMute", + K_MEDIASELECT: "MediaSelect", + K_WWW: "WWW", + K_MAIL: "Mail", + K_CALCULATOR: "Calculator", + K_COMPUTER: "Computer", + K_AC_SEARCH: "AC Search", + K_AC_HOME: "AC Home", + K_AC_BACK: "AC Back", + K_AC_FORWARD: "AC Forward", + K_AC_STOP: "AC Stop", + K_AC_REFRESH: "AC Refresh", + K_AC_BOOKMARKS: "AC Bookmarks", + K_BRIGHTNESSDOWN: "BrightnessDown", + K_BRIGHTNESSUP: "BrightnessUp", + K_DISPLAYSWITCH: "DisplaySwitch", + K_KBDILLUMTOGGLE: "KBDIllumToggle", + K_KBDILLUMDOWN: "KBDIllumDown", + K_KBDILLUMUP: "KBDIllumUp", + K_EJECT: "Eject", + K_SLEEP: "Sleep", + } + + # SDL_Keycode mod values (not complete) + KMOD_NONE = _const(0x0000) + KMOD_LSHIFT = _const(0x0001) + KMOD_RSHIFT = _const(0x0002) + KMOD_LCTRL = _const(0x0040) + KMOD_RCTRL = _const(0x0080) + KMOD_LALT = _const(0x0100) + KMOD_RALT = _const(0x0200) + KMOD_LGUI = _const(0x0400) + KMOD_RGUI = _const(0x0800) + KMOD_NUM = _const(0x1000) + KMOD_CAPS = _const(0x2000) + KMOD_MODE = _const(0x4000) + KMOD_CTRL = KMOD_LCTRL | KMOD_RCTRL + KMOD_SHIFT = KMOD_LSHIFT | KMOD_RSHIFT + KMOD_ALT = KMOD_LALT | KMOD_RALT + KMOD_GUI = KMOD_LGUI | KMOD_RGUI + + _modtable = { + KMOD_NONE: "None", + KMOD_LSHIFT: "Left Shift", + KMOD_RSHIFT: "Right Shift", + KMOD_LCTRL: "Left Ctrl", + KMOD_RCTRL: "Right Ctrl", + KMOD_LALT: "Left Alt", + KMOD_RALT: "Right Alt", + KMOD_LGUI: "Left GUI", + KMOD_RGUI: "Right GUI", + KMOD_NUM: "Num Lock", + KMOD_CAPS: "Caps Lock", + KMOD_MODE: "Mode", + KMOD_CTRL: "Ctrl", + KMOD_SHIFT: "Shift", + KMOD_ALT: "Alt", + KMOD_GUI: "GUI", + } diff --git a/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py new file mode 100644 index 000000000..76453443d --- /dev/null +++ b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py @@ -0,0 +1,48 @@ +""" +A simple test of an encoder in pydisplay. +""" + +from board_config import display_drv, broker + +color_byte = 1 +bg_color = 0xFF00 +w = display_drv.width +h = display_drv.height +thickness = 10 +y_pos = h // 2 +x_pos = w // 2 +factor = -1 # change the sign to invert the direction + + +def draw_line(): + color = color_byte << 8 | color_byte + display_drv.fill_rect(0, 0, x_pos, thickness, color) + display_drv.fill_rect(x_pos, 0, w - x_pos, thickness, bg_color) + + +display_drv.vscsad(y_pos) +draw_line() + +while True: + if not (e := broker.poll()): + continue + if e.type == broker.Events.MOUSEWHEEL: + if e.y != 0: + direction = factor if e.y > 0 else -factor + delta = e.y * e.y * direction # Quadratic acceleration + y_pos = (y_pos + delta) % h + display_drv.vscsad(y_pos) + if e.x != 0: + direction = factor if e.x > 0 else -factor + delta = e.x * e.x * direction + x_pos = (x_pos + delta) % w + draw_line() + elif e.type == broker.Events.MOUSEBUTTONDOWN: + if e.button == 2: + color_byte = color_byte << 1 & 0xFF + if color_byte == 0: + color_byte = 1 + draw_line() + elif e.button == 3: + bg_color = ~bg_color + draw_line() diff --git a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py new file mode 100644 index 000000000..8c7a862e5 --- /dev/null +++ b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py @@ -0,0 +1,17 @@ +from board_config import broker +import asyncio + + +async def main(): + while True: + e = broker.poll() + if e: + print(e) + if e == broker.Events.QUIT: + break + await asyncio.sleep(0.001) + +loop = asyncio.get_event_loop() +loop.create_task(main()) +if hasattr(loop, "run_forever"): + loop.run_forever() diff --git a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py new file mode 100644 index 000000000..4a639e71d --- /dev/null +++ b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py @@ -0,0 +1,134 @@ +""" +pydisplay_touch_test.py - Touch rotation test. +Tests the touch driver and finds the correct rotation masks for the touch screen. +Sets the rotation to each of 4 possible values and asks the user to touch the rectangle in each of the 4 corners. +Then it prints the touch_rotation_table that should be set in board_config.py. +""" + +from board_config import display_drv, broker +from eventsys.device import Types +from graphics import round_rect, text16 + + +demo = False + +FG_COLOR = -1 # white +BG_COLOR = 0 # black + +text = "Touch here" +text_width = len(text) * 8 + +SWAP_XY = 0b001 +REVERSE_X = 0b010 +REVERSE_Y = 0b100 + + +def set_rotation_table(table): + if display_drv.touch_device is not None: + if display_drv.touch_device.type == Types.TOUCH: + display_drv.touch_device.rotation_table = table + + +def loop(): + display_drv.fill_rect(0, 0, display_drv.width - 1, display_drv.height - 1, BG_COLOR) + + print("Touch the rectangle in each corner for 4 rotations.\n") + + touch_rotation_table = [] + + for rotation in range(0, 360, 90): + touched_zones = [] + display_drv.rotation = rotation + + width = display_drv.width + height = display_drv.height + half_width = width // 2 + half_height = height // 2 + + for y in range(2): + for x in range(2): + round_rect( + display_drv, + x * half_width + 10, + y * half_height + 10, + half_width - 20, + half_height - 20, + 10, + FG_COLOR, + True, + ) + text16( + display_drv, + text, + x * half_width + ((half_width - text_width) // 2), + y * half_height + ((half_height - 8) // 2), + BG_COLOR, + ) + touched_point = None + while not touched_point: + event = broker.poll() + if ( + event + and event.type == broker.Events.MOUSEBUTTONDOWN + and event.button == 1 + ): + touched_point = event.pos + zone = (touched_point[1] // half_height) * 2 + ( + touched_point[0] // half_width + ) + touched_zones.append(zone) + print(f"{touched_point=} in {zone=}") + display_drv.fill_rect( + x * half_width, + y * half_height, + half_width - 1, + half_height - 1, + BG_COLOR, + ) + + if touched_zones == [0, 1, 2, 3]: + mask = 0b0 + elif touched_zones == [1, 0, 3, 2]: + mask = REVERSE_X + elif touched_zones == [2, 3, 0, 1]: + mask = REVERSE_Y + elif touched_zones == [3, 2, 1, 0]: + mask = REVERSE_X | REVERSE_Y + elif touched_zones == [0, 2, 1, 3]: + mask = SWAP_XY + elif touched_zones == [2, 0, 3, 1]: + mask = SWAP_XY | REVERSE_X + elif touched_zones == [1, 3, 0, 2]: + mask = SWAP_XY | REVERSE_Y + elif touched_zones == [3, 1, 2, 0]: + mask = SWAP_XY | REVERSE_X | REVERSE_Y + else: + print("Invalid touch sequence. Starting over...\n") + return False + + touch_rotation_table.append(mask) + print(f"{rotation=} {mask=} ({mask:#05b})\n") + + if not demo: + set_rotation_table(touch_rotation_table) + print("Set the `touch_rotation_table` in board_config.py to the following:") + else: + print("Demo complete.") + out_text = f"touch_rotation_table = {tuple(touch_rotation_table)}" + print(" ", out_text, "\n") + text16( + display_drv, + out_text, + (display_drv.width - len(out_text) * 8) // 2, + (display_drv.height - 8) // 2, + FG_COLOR, + ) + return True + + +if not demo: + set_rotation_table((0, 0, 0, 0)) + +completed = False +while not completed: + completed = loop() diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/gpio_pin/gpio_pin.py b/micropython/pydisplay/gpio_pin/gpio_pin.py new file mode 100644 index 000000000..a14ca95da --- /dev/null +++ b/micropython/pydisplay/gpio_pin/gpio_pin.py @@ -0,0 +1,219 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +from machine import Pin as _Pin +from sys import platform, implementation + + +if platform == "pyboard": + import stm + + gpio_data = { + "pyboard": { + "BSRR": stm.GPIO_BSRR, + } + } +else: + gpio_data = { + "esp32": { + "SET": 0x8, + "CLR": 0xC, + "gpios": { + "esp32s3": (0x60004000, 0x6000400C), + "esp32c3": (0x60004000, 0x6000400C), + "esp32c2": (0x60004000, 0x6000400C), + "esp32p4": (0x60091000, 0x6009100C), + "esp32c6": (0x60091000, 0x6009100C), + "esp32c5": (0x60091000, 0x6009100C), + "esp32h2": (0x60091000, 0x6009100C), + "esp32s2": (0x3F404000, 0x3F40400C), + "*": (0x3FF44000, 0x3FF4400C), + }, + }, + "samd": { + "SET": 0x18, + "CLR": 0x14, + "gpios": { + "samd21": (0x41004400, 0x41004480), + "samd51": (0x41008000, 0x41008080, 0x41008100, 0x41008180), + }, + }, + "rp2": { + # See 3.1.11 at https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf + "SET": 0x14, + "CLR": 0x18, + "gpios": { + "rp2040": ( + # The SIO registers start at a base address of 0xd0000000 (defined as SIO_BASE in SDK). + 0xD0000000, + ), + }, + }, + "nrf": { + "SET": 0x8, + "CLR": 0xC, + "gpios": { + "nrf5": (0x50000000, 0x50000300), + "nrf9": (0x40842000,), + }, + }, + "mimxrt": { + "SET": 0x84, + "CLR": 0x88, + "gpios": { + "mimxrt10": ( + 0x401B8000, + 0x401BC000, + 0x401C0000, + 0x401C4000, + 0x400C0000, + 0x42000000, + 0x42004000, + 0x42008000, + 0x4200C000, + ), + "mimxrt11": ( + 0x4012C000, + 0x40130000, + 0x40134000, + 0x40138000, + 0x4013C000, + 0x40140000, + 0x40C5C000, + 0x40C60000, + 0x40C64000, + 0x40C68000, + 0x40C6C000, + 0x40C70000, + 0x40CA0000, + ), + }, + }, + "renasas-ra": { + "BSRR": 0x8, + "gpios": { + "ra6m5": ( + 0x40080000, + 0x40080020, + 0x40080040, + 0x40080060, + 0x40080080, + 0x400800A0, + 0x400800C0, + 0x400800E0, + 0x40080100, + 0x40080120, + 0x40080140, + 0x40080160, + ), + "*": ( + 0x40040000, + 0x40040020, + 0x40040040, + 0x40040060, + 0x40040080, + 0x400400A0, + 0x400400C0, + 0x400400E0, + 0x40040100, + 0x40040120, + ), + }, + }, + } + + +def _init_module(): + data = gpio_data[platform.lower()] + if "BSRR" in data: + GPIO_Pin.BSRR = data["BSRR"] + else: + GPIO_Pin.SET = data["SET"] + GPIO_Pin.CLR = data["CLR"] + + if hasattr(_Pin, "gpio"): # If the gpio method is already implemented + return + + # Convert to lowercase and remove - and _ from sys.implementation._machine + # which is in the format "Generic ESP32S3 module with ESP32S3" + machine = implementation._machine.lower().replace("-", "").replace("_", "") + for mcu, gpios in data["gpios"].items(): # Find the GPIOs for the current machine + if mcu in machine: # If the mcu is in the machine name + GPIO_Pin._gpios = gpios # Set the GPIOs and break + break + if GPIO_Pin._gpios is None: # If the GPIOs are not set + if "*" in data["gpios"].keys(): # If there is a wildcard + GPIO_Pin._gpios = data["gpios"]["*"] # Set the GPIOs to the wildcard + else: + raise NotImplementedError( + f"class GPIO_Pin not implemented for {platform} on {machine}" + ) + + +class GPIO_Pin(_Pin): + """ + GPIO_Pin is a subclass of machine.Pin that provides additional methods for + accessing the GPIO registers on a microcontroller. It is used by the + I80Bus class to control the GPIO pins that are used to communicate with + the display. + """ + + PPP = None + SET = None + CLR = None + BSRR = None + _gpios = None + + def __init__(self, id, *args, **kwargs): + if hasattr(id, "names"): + id = id.names()[1] + super().__init__(id, *args, **kwargs) + self._id = id if isinstance(id, int) else None + if self.BSRR is None: + self.PPP = 32 + else: + self.PPP = 16 + + def pin(self): + """ + Returns the pin number in the port of the GPIO pin. + + Returns: + int: The pin number in the port of the GPIO pin + """ + if hasattr(super(), "pin"): + return super().pin() + if isinstance(self._id, int): + return self._id & (self.PPP - 1) + raise NotImplementedError("GPIO_Pin.pin not implemented for this platform") + + def port(self) -> int: + """ + Returns the port number of the GPIO pin. + + Returns: + int: The port number of the GPIO pin. + """ + if hasattr(super(), "port"): + return super().port() + if isinstance(self._id, int): + return self._id // self.PPP + raise NotImplementedError("GPIO_Pin.port not implemented for this platform") + + def gpio(self) -> int: + """ + Returns the address of the GPIO pin. + + Returns: + int: The address of the GPIO pin. + """ + if hasattr(super(), "gpio"): + x = super().gpio() # returns as signed int + if x < 0: # if it is negative + x = x + (1 << 31) # convert it to unsigned int + return x + return self._gpios[self.port()] + + +_init_module() diff --git a/micropython/pydisplay/gpio_pin/manifest.py b/micropython/pydisplay/gpio_pin/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/graphics/examples/graphics_area_test.py b/micropython/pydisplay/graphics/examples/graphics_area_test.py new file mode 100644 index 000000000..b4f1ffa28 --- /dev/null +++ b/micropython/pydisplay/graphics/examples/graphics_area_test.py @@ -0,0 +1,23 @@ +""" +Test the Area return type of the shapes functions. + +Shape functions return an Area object that represents the bounding box of the shape drawn. +This object can be used to optimize the display by redrawing only the area that has changed. + +Area objects have the attributes x, y, w and h. They may be added together, such as: + area3 = area1 + area2 + +and may be unpacked, such as: + x, y, w, h = area3 +or as a function argument: + rect(display_drv, *area3, 0x00FF) + +""" + +from board_config import display_drv +from graphics import rect, circle, ellipse + + +dirty = circle(display_drv, 120, 120, 50, 0XFF00, True) +dirty += ellipse(display_drv, 100, 85, 50, 30, 0X0FF0, True, 0b1111) +rect(display_drv, *dirty, 0x00FF) diff --git a/micropython/pydisplay/graphics/examples/graphics_simpletest.py b/micropython/pydisplay/graphics/examples/graphics_simpletest.py new file mode 100644 index 000000000..73612eae8 --- /dev/null +++ b/micropython/pydisplay/graphics/examples/graphics_simpletest.py @@ -0,0 +1,72 @@ +""" +Simple test example to demonstrate the use of graphics. +""" + +from board_config import display_drv +from array import array # for defining a polygon +from palettes import get_palette +import graphics + + +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + +WIDTH = display_drv.width +HEIGHT = display_drv.height +FONT_WIDTH = 8 + +# Define color palette +pal = get_palette(swapped=needs_swap) + +# Define objects +triangle = array("h", [0, 0, WIDTH // 2, -HEIGHT // 4, WIDTH - 1, 0]) + + +# Main loop +def main(animate=False, text1="Shapes", text2="simpletest", poly=triangle): + y_range = range(HEIGHT - 1, -1, -1) if animate else [HEIGHT - 1] + for y in y_range: + graphics.fill(display_drv, pal.BLACK) + graphics.poly(display_drv, 0, y, poly, pal.YELLOW, True) + graphics.fill_rect( + display_drv, + WIDTH // 6, + HEIGHT // 3, + WIDTH * 2 // 3, + HEIGHT // 3, + pal.GREY, + ) + graphics.line(display_drv, 0, 0, WIDTH - 1, HEIGHT - 1, pal.GREEN) + graphics.rect(display_drv, 0, 0, 15, 15, pal.RED, True) + graphics.rect(display_drv, WIDTH - 15, HEIGHT - 15, 15, 15, pal.BLUE, True) + graphics.hline(display_drv, WIDTH // 8, HEIGHT // 2, WIDTH * 3 // 4, pal.MAGENTA) + graphics.vline(display_drv, WIDTH // 2, HEIGHT // 4, HEIGHT // 2, pal.CYAN) + graphics.pixel(display_drv, WIDTH // 2, HEIGHT * 1 // 8, pal.WHITE) + graphics.ellipse( + display_drv, + WIDTH // 2, + HEIGHT // 2, + WIDTH // 4, + HEIGHT // 8, + pal.BLACK, + True, + 0b1111, + ) + graphics.text( + display_drv, text1, (WIDTH - FONT_WIDTH * len(text1)) // 2, HEIGHT // 2 - 8, pal.WHITE + ) + graphics.text( + display_drv, text2, (WIDTH - FONT_WIDTH * len(text2)) // 2, HEIGHT // 2, pal.WHITE + ) + + graphics.hline(display_drv, 0, 0, WIDTH, pal.BLACK) + graphics.vline(display_drv, 0, 0, HEIGHT, pal.BLACK) + + +launch = lambda: main(animate=True) # noqa: E731 + +main() diff --git a/micropython/pydisplay/graphics/graphics/__init__.py b/micropython/pydisplay/graphics/graphics/__init__.py new file mode 100644 index 000000000..7e0baf972 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/__init__.py @@ -0,0 +1,79 @@ +""" +`graphics` +==================================================== +Graphics library extending MicroPython's framebuf module. +""" + +from ._area import Area +from ._draw import Draw +from ._font import Font, text, text8, text14, text16 +from ._files import pbm_to_framebuffer, pgm_to_framebuffer, bmp_to_framebuffer +from ._framebuf_plus import ( + FrameBuffer, + MONO_VLSB, + MONO_HLSB, + MONO_HMSB, + GS2_HMSB, + GS4_HMSB, + GS8, + RGB565, +) +from ._shapes import ( + arc, + blit, + blit_rect, + blit_transparent, + circle, + ellipse, + fill, + fill_rect, + gradient_rect, + hline, + line, + pixel, + poly, + polygon, + rect, + round_rect, + triangle, + vline, +) + +__all__ = [ + Area, + Draw, + Font, + text, + text8, + text14, + text16, + pbm_to_framebuffer, + pgm_to_framebuffer, + bmp_to_framebuffer, + FrameBuffer, + MONO_VLSB, + MONO_HLSB, + MONO_HMSB, + GS2_HMSB, + GS4_HMSB, + GS8, + RGB565, + arc, + blit, + blit_rect, + blit_transparent, + circle, + ellipse, + fill, + fill_rect, + gradient_rect, + hline, + line, + pixel, + poly, + polygon, + rect, + round_rect, + triangle, + vline, +] diff --git a/micropython/pydisplay/graphics/graphics/_area.py b/micropython/pydisplay/graphics/graphics/_area.py new file mode 100644 index 000000000..285ed33d2 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_area.py @@ -0,0 +1,272 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`graphics._area` +==================================================== +Area class for defining rectangular areas. +""" + + +class Area: + """ + Represents a rectangular area defined by its position and dimensions. + + Attributes: + x (int | float): The x-coordinate of the top-left corner of the area. + y (int | float): The y-coordinate of the top-left corner of the area. + w (int | float): The width of the area. + h (int | float): The height of the area. + + Methods: + contains(x, y): Checks if the specified point is contained within the area. + contains_area(other): Checks if the specified area is contained within the area. + intersects(other): Checks if the current Area object intersects with another Area object. + touches_or_intersects(other): Checks if the current Area object touches or intersects with another Area object. + shift(dx=0, dy=0): Returns a new Area shifted by the specified amount in the x and y directions. + clip(other): Clips the current Area object to the specified Area object. + + Special Methods: + __eq__(other): Checks if the current Area object is equal to another Area object. + __ne__(other): Checks if the current Area object is not equal to another Area object. + __add__(other): Computes the union of the current Area object and another Area object. + __iter__(): Returns an iterator over the elements of the Area object. + __repr__(): Returns a string representation of the Area object. + __str__(): Returns a string representation of the Area object. + """ + + def __init__(self, x, y=None, w=None, h=None): + """ + Initializes a new instance of the Area class. + + Args: + x (int | float | tuple): The x-coordinate of the top-left corner of the area or + a tuple containing the x, y, w, and h coordinates of the area. + y (int | float): The y-coordinate of the top-left corner of the area. + w (int | float): The width of the area. + h (int | float): The height of the area. + """ + if isinstance(x, tuple): + x, y, w, h = x + if y is None or w is None or h is None: + raise ValueError("Invalid arguments") + self.x = x + self.y = y + self.w = w + self.h = h + + def contains(self, x, y=None): + """ + Checks if the specified point is contained within the area. + + Args: + x (int | tuple): The x-coordinate of the point to check + or a tuple containing the x and y coordinates of the point. + y (int): The y-coordinate of the point to check. + + Returns: + (bool): True if the point is contained within the area, False otherwise. + """ + if isinstance(x, tuple): + x, y = x + return self.x <= x < self.x + self.w and self.y <= y < self.y + self.h + + def contains_area(self, other): + """ + Checks if the specified area is contained within the area. + + Args: + other (Area): The other area to check. + + Returns: + (bool): True if the other area is contained within the area, False otherwise. + """ + return ( + self.x <= other.x + and self.y <= other.y + and self.x + self.w >= other.x + other.w + and self.y + self.h >= other.y + other.h + ) + + def intersects(self, other): + """ + Checks if the current Area object intersects with another Area object. + + Args: + other (Area): The other Area object to check for overlap. + + Returns: + (bool): True if the two Area objects intersect, False otherwise. + """ + if self.x + self.w <= other.x or other.x + other.w <= self.x: + return False + if self.y + self.h <= other.y or other.y + other.h <= self.y: + return False + return True + + def touches_or_intersects(self, other): + """ + Checks if the current Area object touches or intersects with another Area object. + + Args: + other (Area): The other Area object to check for overlap or touch. + + Returns: + (bool): True if the two Area objects touch or intersect, False otherwise. + """ + if self.x + self.w < other.x or other.x + other.w < self.x: + return False + if self.y + self.h < other.y or other.y + other.h < self.y: + return False + return True + + def shift(self, dx=0, dy=0): + """ + Returns a new Area shifted by the specified amount in the x and y directions. + + Args: + dx (int | float): The amount to shift the area in the x direction. + dy (int | float): The amount to shift the area in the y direction. + + Returns: + (Area): A new Area object shift by the specified amount in the x and y directions. + """ + return Area(self.x + dx, self.y + dy, self.w, self.h) + + def clip(self, other): + """ + Clips the current Area object to the specified Area object. + + Args: + other (Area): The other Area object to clip to. + + Returns: + (Area): A new Area object representing the clipped area. + """ + x = max(self.x, other.x) + y = max(self.y, other.y) + w = min(self.x + self.w, other.x + other.w) - x + h = min(self.y + self.h, other.y + other.h) - y + return Area(x, y, w, h) + + def offset(self, d1, d2=None, d3=None, d4=None): + """ + Returns a new Area offset by the specified amount(s). + + If only one argument is provided, it is used as the offset in all 4 directions. + If two arguments are provided, the first is used as the offset in the x direction and the second as the offset in the y direction. + If three arguments are provided, they are used as the offsets in the left, top/bottom, and right directions, respectively. + If four arguments are provided, they are used as the offsets in the left, top, right, and bottom directions, respectively. + + Args: + d1 (int | float): The offset in the x direction or the offset in all 4 directions. + d2 (int | float): The offset in the y direction or the offset in the top/bottom direction. + d3 (int | float): The offset in the right direction. + d4 (int | float): The offset in the bottom direction. + + Returns: + (Area): A new Area object offset by the specified amount(s). + """ + if d2 is None: + d2 = d3 = d4 = d1 + elif d3 is None: + d3 = d1 + d4 = d2 + elif d4 is None: + d4 = d2 + return Area(self.x - d1, self.y - d2, self.w + d1 + d3, self.h + d2 + d4) + + def inset(self, d1, d2=None, d3=None, d4=None): + """ + Returns a new Area inset by the specified amount(s). + + If only one argument is provided, it is used as the inset in all 4 directions. + If two arguments are provided, the first is used as the inset in the x direction and the second as the inset in the y direction. + If three arguments are provided, they are used as the insets in the left, top/bottom, and right directions, respectively. + If four arguments are provided, they are used as the insets in the left, top, right, and bottom directions, respectively. + + Args: + d1 (int | float): The inset in the x direction or the inset in all 4 directions. + d2 (int | float): The inset in the y direction or the inset in the top/bottom direction. + d3 (int | float): The inset in the right direction. + d4 (int | float): The inset in the bottom direction. + + Returns: + (Area): A new Area object inset by the specified amount(s). + """ + if d2 is None: + d2 = d3 = d4 = d1 + elif d3 is None: + d3 = d1 + d4 = d2 + elif d4 is None: + d4 = d2 + return Area(self.x + d1, self.y + d2, self.w - d1 - d3, self.h - d2 - d4) + + def __eq__(self, other): + """ + Checks if the current Area object is equal to another Area object. + + Args: + other (Area): The other Area object to compare with. + + Returns: + (bool): True if the two Area objects are equal, False otherwise. + """ + return self.x == other.x and self.y == other.y and self.w == other.w and self.h == other.h + + def __ne__(self, other): + """ + Checks if the current Area object is not equal to another Area object. + + Args: + other (Area): The other Area object to compare with. + + Returns: + (bool): True if the two Area objects are not equal, False otherwise. + """ + return not self.__eq__(other) + + def __add__(self, other): + """ + Computes the union of the current Area object and another Area object. + + Args: + other (Area): The other Area object to compute the union with. + + Returns: + (Area): A new Area object representing the union of the two areas. + """ + return Area( + min(self.x, other.x), + min(self.y, other.y), + max(self.x + self.w, other.x + other.w) - min(self.x, other.x), + max(self.y + self.h, other.y + other.h) - min(self.y, other.y), + ) + + def __iter__(self): + """ + Returns an iterator over the elements of the Area object. + + Returns: + (iterator): An iterator over the elements of the Area object. + """ + return iter((self.x, self.y, self.w, self.h)) + + def __repr__(self): + """ + Returns a string representation of the Area object. + + Returns: + (str): A string representation of the Area object. + """ + return f"Area({self.x}, {self.y}, {self.w}, {self.h})" + + def __str__(self): + """ + Returns a string representation of the Area object. + + Returns: + (str): A string representation of the Area object. + """ + return f"Area({self.x}, {self.y}, {self.w}, {self.h})" diff --git a/micropython/pydisplay/graphics/graphics/_draw.py b/micropython/pydisplay/graphics/graphics/_draw.py new file mode 100644 index 000000000..b676643d3 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_draw.py @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`graphics._draw` +==================================================== +Graphics Draw class +""" + +from . import _shapes +from . import _font + + +class Draw: + """ + A Draw class to draw shapes onto a specified canvas. + + Args: + canvas (Canvas): The canvas to draw on. + + Usage: + ``` + # canvas is an instance of DisplayDriver, FrameBuffer, or other canvas-like object + draw = Draw(canvas) + draw.fill(0x0000) + draw.rect(10, 10, 100, 100, 0xFFFF) + ``` + """ + + def __init__(self, canvas): + self.canvas = canvas + + def arc(self, x, y, r, a0, a1, c): + return _shapes.arc(self.canvas, x, y, r, a0, a1, c) + + def blit(self, source, x, y, key=-1, palette=None): + return _shapes.blit(self.canvas, source, x, y, key, palette) + + def blit_rect(self, buf, x, y, w, h): + return _shapes.blit_rect(self.canvas, buf, x, y, w, h) + + def blit_tranparent(self, buf, x, y, w, h, key=None): + return _shapes.blit_transparent(self.canvas, buf, x, y, w, h, key) + + def circle(self, x, y, r, c, f=False): + return _shapes.circle(self.canvas, x, y, r, c, f) + + def ellipse(self, x, y, r1, r2, c, f=False, m=0b1111, w=None, h=None): + return _shapes.ellipse(self.canvas, x, y, r1, r2, c, f, m, w, h) + + def fill(self, c): + return _shapes.fill(self.canvas, c) + + def fill_rect(self, x, y, w, h, c): + return _shapes.fill_rect(self.canvas, x, y, w, h, c) + + def gradient_rect(self, x, y, w, h, c1, c2=None, vertical=True): + return _shapes.gradient_rect(self.canvas, x, y, w, h, c1, c2, vertical) + + def hline(self, x, y, w, c): + return _shapes.hline(self.canvas, x, y, w, c) + + def line(self, x1, y1, x2, y2, c): + return _shapes.line(self.canvas, x1, y1, x2, y2, c) + + def pixel(self, x, y, c): + return _shapes.pixel(self.canvas, x, y, c) + + def poly(self, x, y, coords, c, f=False): + return _shapes.poly(self.canvas, x, y, coords, c, f) + + def polygon(self, points, x, y, c, angle=0, center_x=0, center_y=0): + return _shapes.polygon(self.canvas, points, x, y, c, angle, center_x, center_y) + + def rect(self, x, y, w, h, c, f=False): + return _shapes.rect(self.canvas, x, y, w, h, c, f) + + def round_rect(self, x, y, w, h, r, c, f=False): + return _shapes.round_rect(self.canvas, x, y, w, h, r, c, f) + + def triangle(self, x1, y1, x2, y2, x3, y3, c, f=False): + return _shapes.triangle(self.canvas, x1, y1, x2, y2, x3, y3, c, f) + + def vline(self, x, y, h, c): + return _shapes.vline(self.canvas, x, y, h, c) + + def text(self, *args, **kwargs): + return _font.text(self.canvas, *args, **kwargs) + + def text8(self, *args, **kwargs): + return _font.text8(self.canvas, *args, **kwargs) + + def text14(self, *args, **kwargs): + return _font.text14(self.canvas, *args, **kwargs) + + def text16(self, *args, **kwargs): + return _font.text16(self.canvas, *args, **kwargs) diff --git a/micropython/pydisplay/graphics/graphics/_files.py b/micropython/pydisplay/graphics/graphics/_files.py new file mode 100644 index 000000000..3362a4379 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_files.py @@ -0,0 +1,86 @@ +from ._framebuf_plus import FrameBuffer, MONO_HLSB, GS2_HMSB, GS4_HMSB, GS8, RGB565 +import struct + + +def pbm_to_framebuffer(filename): + """ + Convert a PBM file to a MONO_HLSB FrameBuffer + + Args: + filename (str): Filename of the PBM file + """ + with open(filename, "rb") as f: + if f.read(3) != b"P4\n": + raise ValueError(f"Invalid PBM file {filename}") + data = f.read() # Read the rest as binary, since MicroPython can't do readline here + while data[0] == 35: # Ignore comment lines starting with b'#' + data = data.split(b"\n", 1)[1] + dims, data = data.split(b"\n", 1) # Assumes no comments after dimensions + width, height = map(int, dims.split()) + buffer = memoryview(bytearray((width + 7) // 8 * height)) + buffer[:] = data + return FrameBuffer(buffer, width, height, MONO_HLSB) + + +def pgm_to_framebuffer(filename): + """ + Convert a PGM file to a GS2_HMSB, GS4_HMSB or GS8 FrameBuffer + + Args: + filename (str): Filename of the PGM file + """ + with open(filename, "rb") as f: + if f.read(3) != b"P5\n": + raise ValueError(f"Invalid PGM file {filename}") + data = f.read() # Read the rest as binary, since MicroPython can't do readline here + while data[0] == 35: # Ignore comment lines starting with b'#' + data = data.split(b"\n", 1)[1] + dims, data = data.split(b"\n", 1) + width, height = map(int, dims.split()) + while data[0] == 35: # Ignore comment lines starting with b'#' + data = data.split(b"\n", 1)[1] + max_val_b, data = data.split(b"\n", 1) # Assumes no comments after max val + max_value = int(max_val_b) + if max_value == 3: + format = GS2_HMSB + array_size = (width + 3) // 4 * height + elif max_value == 15: + format = GS4_HMSB + array_size = (width + 1) // 2 * height + elif max_value == 255: + format = GS8 + array_size = width * height + else: + raise ValueError(f"Unsupported max value {max_value}") + buffer = memoryview(bytearray(array_size)) + buffer[:] = data + return FrameBuffer(buffer, width, height, format) + + +def bmp_to_framebuffer(filename): + """ + Convert a BMP file to a RGB565 FrameBuffer. + First ensures planes is 1, bits per pixel is 16, and compression is 0. + + Args: + filename (str): Filename of the + """ + with open(filename, "rb") as f: + if f.read(2) != b"BM": + raise ValueError("Not a BMP file") + f.seek(10) + data_offset = struct.unpack("= canvas.width or \ + # y < -self.height or y >= canvas.height: + # return + # Go through each row of the character. + for char_y in range(self._font_height): + # Grab the byte for the current row of font data. + if not (line := self._read_line(char, char_y)): + continue # maybe character isnt there? go to next + # Go through each column in the row byte. + for char_x in range(self.width): + # Draw a pixel for each bit that's flipped on. + if (line >> (self.width - char_x - 1)) & 0x1: + canvas.fill_rect( + ( + x + char_x * scale + if not inverted + else x + (self._font_width - char_x - 1) * scale + ), + ( + y + char_y * scale + if not inverted + else y + (self._font_height - char_y - 1) * scale + ), + scale, + scale, + color, + ) + return Area(x, y, self._font_width * scale, self._font_height * scale) + + def text(self, canvas, string, x, y, color, scale=1, inverted=False): + """ + Draw text to the canvas. + + Args: + canvas (Canvas): The DisplayDriver, FrameBuffer, or other canvas-like object to draw on. + string (str): The text to draw. + x (int): The x position to start drawing the text. + y (int): The y position to start drawing the text. + color (int): The color to draw the text in. + scale (int): The scale factor to draw the text at. Default is 1. + inverted (bool): If True, draw the text inverted. Default is False. + + Returns: + (Area): The area that was drawn to. + """ + if inverted: + string = "".join(reversed(string)) + + char_y = y + largest_x = 0 # the last x position reached on the longest line + for chunk in string.split("\n"): + last_x = x # the last x position reached on the current line + for i, char in enumerate(chunk): + char_x = x + (i * self.width * scale) + if char_x < canvas.width if hasattr(canvas, "width") else True: + if char_y < canvas.height if hasattr(canvas, "height") else True: + if char_x + (self.width * scale) > 0: + if char_y + (self.height * scale) > 0: + self.draw_char( + char, + char_x, + char_y, + canvas, + color, + scale=scale, + inverted=inverted, + ) + last_x = char_x + (self.width * scale) + largest_x = max([largest_x, last_x]) # update the largest x position + char_y += self.height * scale + return Area(x, y, largest_x - x, char_y - y) + + def text_width(self, text, scale=1): + """ + Return the pixel width of the specified text message. + Takes into account the scale factor, but not any newlines. + + Args: + text (str): The text to measure. + scale (int): The scale factor to measure the text at. Default is 1. + """ + return len(text) * self._font_width * scale + + def _read_line(self, char, line): + """Read a line of font data for a character.""" + if self._cache: + return self._cache[(ord(char) * self.height) + line] + + self._font.seek((ord(char) * self.height) + line) + try: + return struct.unpack("B", self._font.read(1))[0] + except RuntimeError: # maybe character isnt there? go to next + return None + + def export(self, filename): + """ + Export the font data in self._cache to a .py file that can be imported. + The format is a single bytes object named _FONT. There are 256 lines, one for each character. + The last line is `FONT = memoryview(_FONT)`. + + Args: + filename (str): The path to save the file to. + """ + if not self._cache: + raise RuntimeError("Font data not cached, cannot export") + mv = memoryview(self._cache) + with open(filename, "w") as f: + f.write("_FONT =\\\n") + for i in range(256): + f.write("b'") + for j in range(self.height): + f.write(f"\\x{mv[(i * self.height) + j]:02x}") + f.write("'\\\n") + f.write("\nFONT = memoryview(_FONT)\n") + print(f"Font data saved to {filename}") diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py new file mode 100644 index 000000000..4366efbc8 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -0,0 +1,602 @@ +# SPDX-FileCopyrightText: 2018 Kattni Rembor, Melissa LeBlanc-Williams +# and Tony DiCola, for Adafruit Industries. +# Copyright 2024 Brad Barnett +# Original file created by Damien P. George +# SPDX-License-Identifier: MIT + +""" +`graphics._framebuf` +==================================================== + +*Python framebuf module, based on the micropython framebuf module. +Will not be imported on MicroPython boards since framebuf is included +in the compiled firmware. + +framebuf on MicroPython does not return an Area object for methods that +write to the buffer, but BasicShapes does, so the native shape methods +here return an Area object in order to keep it consistent within this +library. It is recommended to use framebuf_plus.py instead of this if +you need to use the returned Areas so your code will be transferable. + +""" + +from . import _shapes +from . import _font + +try: + from ulab import numpy as np # type: ignore +except ImportError: + try: + import numpy as np + except ImportError: + np = None + + +# Framebuf format constants: +MONO_VLSB = 0 +MONO_HLSB = 3 +MONO_HMSB = 4 +RGB565 = 1 +GS2_HMSB = 5 +GS4_HMSB = 2 +GS8 = 6 + + +class MVLSBFormat: + depth = 1 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = (y >> 3) * framebuf._stride + x + offset = y & 0x07 + framebuf._buffer[index] = (framebuf._buffer[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + @staticmethod + def get_pixel(framebuf, x, y): + index = (y >> 3) * framebuf._stride + x + offset = y & 0x07 + return (framebuf._buffer[index] >> offset) & 0x01 + + @staticmethod + def fill(framebuf, color): + if color: + fill = 0xFF + else: + fill = 0x00 + for i in range(len(framebuf._buffer)): # pylint: disable=consider-using-enumerate + framebuf._buffer[i] = fill + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + while height > 0: + index = (y >> 3) * framebuf._stride + x + offset = y & 0x07 + for w_w in range(width): + framebuf._buffer[index + w_w] = ( + framebuf._buffer[index + w_w] & ~(0x01 << offset) + ) | ((color != 0) << offset) + y += 1 + height -= 1 + + +class MHLSBFormat: + depth = 1 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = (y * framebuf._stride + x) >> 3 + offset = 7 - (x & 0x07) + framebuf._buffer[index] = (framebuf._buffer[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + @staticmethod + def get_pixel(framebuf, x, y): + index = (x + y * framebuf._stride) >> 3 + offset = 7 - (x & 0x07) + return (framebuf._buffer[index] >> offset) & 0x01 + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + for _x in range(x, x + width): + offset = 7 - _x & 0x07 + for _y in range(y, y + height): + index = (_y * framebuf._stride + _x) >> 3 + framebuf._buffer[index] = (framebuf._buffer[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + @staticmethod + def fill(framebuf, color): + if color: + fill = 0xFF + else: + fill = 0x00 + for i in range(len(framebuf._buffer)): + framebuf._buffer[i] = fill + + +class MHMSBFormat: + depth = 1 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = (y * framebuf._stride + x) >> 3 + offset = x & 0x07 + framebuf._buffer[index] = (framebuf._buffer[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + @staticmethod + def get_pixel(framebuf, x, y): + index = (y * framebuf._stride + x) // 8 + offset = 7 - x & 0x07 + return (framebuf._buffer[index] >> offset) & 0x01 + + @staticmethod + def fill(framebuf, color): + if color: + fill = 0xFF + else: + fill = 0x00 + for i in range(len(framebuf._buffer)): # pylint: disable=consider-using-enumerate + framebuf._buffer[i] = fill + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + for _x in range(x, x + width): + offset = 7 - _x & 0x07 + for _y in range(y, y + height): + index = (_y * framebuf._stride + _x) // 8 + framebuf._buffer[index] = (framebuf._buffer[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + +class GS2HMSBFormat: + depth = 2 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = (y * framebuf._stride + x) >> 2 + pixel = framebuf._buffer[index] + + shift = (x & 0b11) << 1 + mask = 0b11 << shift + color = (color & 0b11) << shift + + framebuf._buffer[index] = color | (pixel & (~mask)) + + @staticmethod + def get_pixel(framebuf, x, y): + index = (y * framebuf._stride + x) >> 2 + pixel = framebuf._buffer[index] + + shift = (x & 0b11) << 1 + return (pixel >> shift) & 0b11 + + @staticmethod + def fill(framebuf, color): + if color: + bits = color & 0b11 + fill = (bits << 6) | (bits << 4) | (bits << 2) | (bits << 0) + else: + fill = 0x00 + + framebuf._buffer = [fill for i in range(len(framebuf._buffer))] + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + """Draw the outline and interior of a rectangle at the given location, size and color.""" + # pylint: disable=too-many-arguments + for _x in range(x, x + width): + for _y in range(y, y + height): + GS2HMSBFormat.set_pixel(framebuf, _x, _y, color) + + +class GS4HMSBFormat: + depth = 4 + + @staticmethod + def set_pixel(framebuf, x, y, color): + raise NotImplementedError + + @staticmethod + def get_pixel(framebuf, x, y): + raise NotImplementedError + + @staticmethod + def fill(framebuf, color): + raise NotImplementedError + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + raise NotImplementedError + + +class GS8Format: + depth = 8 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = y * framebuf._stride + x + framebuf._buffer[index] = color.to_bytes(1, "little") + + @staticmethod + def get_pixel(framebuf, x, y): + index = y * framebuf._stride + x + return int.from_bytes(framebuf._buffer[index : index + 1], "little") + + @staticmethod + def fill(framebuf, color): + framebuf._buffer = color.to_bytes(1, "little") * len(framebuf._buffer) + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + color = color.to_bytes(1, "little") + for _y in range(y, y + height): + offset = _y * framebuf._stride + for _x in range(x, x + width): + index = offset + _x + framebuf._buffer[index] = color + + +class RGB565Format: + depth = 16 + + @staticmethod + def set_pixel(framebuf, x, y, color): + index = (y * framebuf._stride + x) * 2 + framebuf._buffer[index : index + 2] = (color & 0xFFFF).to_bytes(2, "little") + + @staticmethod + def get_pixel(framebuf, x, y): + index = (y * framebuf._stride + x) * 2 + color = framebuf._buffer[index : index + 2] + color = int.from_bytes(color, "little") + return color + + @staticmethod + def fill(framebuf, color): + rgb565_color = (color & 0xFFFF).to_bytes(2, "little") + if False: + rgb565_color_int = int.from_bytes(rgb565_color, "little") + arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) + arr[:] = rgb565_color_int + else: + for i in range(0, len(framebuf._buffer), 2): + framebuf._buffer[i : i + 2] = rgb565_color + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + # make sure x, y, width, height are within the bounds of the framebuf + if x < 0: + width += x + x = 0 + if y < 0: + height += y + y = 0 + if x + width > framebuf.width: + width = framebuf.width - x + if y + height > framebuf.height: + height = framebuf.height - y + rgb565_color = (color & 0xFFFF).to_bytes(2, "little") + if np: + rgb565_color_int = int.from_bytes(rgb565_color, "little") + arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) + for _y in range(y, y + height): + arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = rgb565_color_int + else: + for _y in range(y, y + height): + offset = _y * framebuf._stride + for _x in range(x, x + width): + index = (offset + _x) * 2 + framebuf.buffer[index : index + 2] = rgb565_color + + +class FrameBuffer: + """ + FrameBuffer object. + + Args: + buffer (bytearray): The buffer to use for the frame buffer. + width (int): The width of the frame buffer in pixels. + height (int): The height of the frame buffer in pixels. + format (int): The format of the frame buffer. One of: + - ``MONO_VLSB``: Single bit displays (like SSD1306 OLED) + - ``MONO_HLSB``: Single bit files like PBM (Portable BitMap) + - ``MONO_HMSB``: Single bit displays where the bits in a byte are horizontally mapped + Each byte occupies 8 horizontal pixels with bit 0 being the leftmost. + - ``RGB565``: 16-bit color displays + - ``GS2_HMSB``: 2-bit color displays like the HT16K33 8x8 Matrix + - ``GS4_HMSB``: Unimplemented! + - ``GS8``: Unimplemented! + stride (int): The number of bytes between each horizontal line of the frame buffer + If not given, it is assumed to be equal to the width. + """ + + def __init__(self, buffer, width, height, format, stride=None): + self._buffer = buffer + self._width = width + self._height = height + self._stride = stride if stride is not None else width + self._font = None + if format == MONO_VLSB: + self._stride = (self._stride + 7) & ~7 + self._format = MVLSBFormat() + elif format == MONO_HLSB: + self._stride = (self._stride + 7) & ~7 + self._format = MHLSBFormat() + elif format == MONO_HMSB: + self._format = MHMSBFormat() + elif format == RGB565: + self._format = RGB565Format() + elif format == GS2_HMSB: + self._stride = (self._stride + 3) & ~3 + self._format = GS2HMSBFormat() + elif format == GS4_HMSB: + self._stride = (self._stride + 1) & ~1 + self._format = GS4HMSBFormat() + elif format == GS8: + self._format = GS8Format() + else: + raise ValueError("invalid format") + + @property + def width(self): + """ + The width of the FrameBuffer in pixels. + """ + return self._width + + @property + def height(self): + """ + The height of the FrameBuffer in pixels. + """ + return self._height + + def fill_rect(self, x, y, w, h, c): + """ + Draw a rectangle at the given location, size and color. + + Args: + x (int): The x-coordinate of the top left corner of the rectangle. + y (int): The y-coordinate of the top left corner of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color of the rectangle. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the rectangle + """ + self._format.fill_rect(self, x, y, w, h, c) + return (x, y, w, h) + + def pixel(self, x, y, c=None): + """ + Set or get the color of a given pixel. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. If not given, the color of the pixel is returned. + + Returns: + (int, tuple or None): If c is not given, the color of the pixel is returned. + If c is given and x and y are within the bounds of the FrameBuffer, + the x, y, width, and height of the pixel are returned. + If x and y are not within the bounds of the FrameBuffer, None is returned. + """ + if x < 0 or x >= self._width or y < 0 or y >= self._height: + return None + if c is None: + return self._format.get_pixel(self, x, y) + self._format.set_pixel(self, x, y, c) + return (x, y, 1, 1) + + def fill(self, c): + """ + Fill the entire FrameBuffer with the specified color. + + Args: + c (int): The color to fill the FrameBuffer with. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the FrameBuffer. + """ + self._format.fill(self, c) + return (0, 0, self._width, self._height) + + def scroll(self, xstep, ystep): + """ + Shift the contents of the FrameBuffer by the given vector (xstep, ystep). + This may leave a footprint of the previous colors in the FrameBuffer. + + Args: + xstep (int): The number of pixels to shift the FrameBuffer in the x direction. + ystep (int): The number of pixels to shift the FrameBuffer in the y direction. + + Raises: + ValueError: If the FrameBuffer format depth is not a multiple of 8 + """ + # Check to make sure self._format.depth is a multiple of 8 + if self._format.depth % 8 != 0: + raise ValueError("Scrolling is only implemented for depths that are multiples of 8") + + BPP = self._format.depth // 8 # Bytes per pixel + + # Determine the width and height of the FrameBuffer + width = self._width + height = self._height + + # Calculate the number of bytes per row + bytes_per_row = width * BPP + + # Iterate over each row in the appropriate order (top to bottom for ystep > 0, bottom to top for ystep < 0) + if ystep > 0: + y_range = range(height - 1, -1, -1) + else: + y_range = range(height) + + # Iterate over each row in the appropriate order + for y in y_range: + # Calculate the new row index + new_y = y + ystep + + # Skip rows that go out of bounds + if new_y < 0 or new_y >= height: + continue + + # Calculate the byte offset for the current and new rows + offset = y * bytes_per_row + new_offset = new_y * bytes_per_row + + # Iterate over each column in the appropriate order (right to left for xstep > 0, left to right for xstep < 0) + if xstep > 0: + for i in range(bytes_per_row - 1, (xstep * BPP) - 1, -1): + self._buffer[new_offset + i] = self._buffer[offset + i - (xstep * BPP)] + elif xstep < 0: + for i in range(-xstep * BPP, bytes_per_row): + self._buffer[new_offset + i] = self._buffer[offset + i + (xstep * BPP)] + else: + # If there is no x shift, copy the row as is + self._buffer[new_offset : new_offset + bytes_per_row] = self._buffer[ + offset : offset + bytes_per_row + ] + + def blit(self, *args, **kwargs): + """ + Blit a source to the canvas at the specified x, y location. + + Args: + source (FrameBuffer): Source FrameBuffer object. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + key (int): Key value for transparency (default: -1). + palette (Palette): Palette object for color translation (default: None). + """ + _shapes.blit(self, *args, **kwargs) + + def ellipse(self, *args, **kwargs): + """ + Midpoint ellipse algorithm + Draw an ellipse at the given location. Radii r1 and r2 define the geometry; equal values cause a + circle to be drawn. The c parameter defines the color. + + The optional f parameter can be set to True to fill the ellipse. Otherwise just a one pixel outline + is drawn. + + The optional m parameter enables drawing to be restricted to certain quadrants of the ellipse. + The LS four bits determine which quadrants are to be drawn, with bit 0 specifying Q1, b1 Q2, + b2 Q3 and b3 Q4. Quadrants are numbered counterclockwise with Q1 being top right. + + Args: + x0 (int): Center x coordinate + y0 (int): Center y coordinate + r1 (int): x radius + r2 (int): y radius + c (int): color + f (bool): Fill the ellipse (default: False) + m (int): Bitmask to determine which quadrants to draw (default: 0b1111) + w (int): Width of the ellipse (default: None) + h (int): Height of the ellipse (default: None) + """ + _shapes.ellipse(self, *args, **kwargs) + + def hline(self, *args, **kwargs): + """ + Horizontal line drawing function. Will draw a single pixel wide line. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + w (int): Width of the line. + c (int): color. + """ + _shapes.hline(self, *args, **kwargs) + + def line(self, *args, **kwargs): + """ + Line drawing function. Will draw a single pixel wide line starting at + x0, y0 and ending at x1, y1. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + x1 (int): X-coordinate of the end of the line. + y1 (int): Y-coordinate of the end of the line. + c (int): color. + """ + _shapes.line(self, *args, **kwargs) + + def poly(self, *args, **kwargs): + """ + Given a list of coordinates, draw an arbitrary (convex or concave) closed polygon at the given x, y location + using the given color. + + The coords must be specified as an array of integers, e.g. array('h', [x0, y0, x1, y1, ... xn, yn]) or a + list or tuple of points, e.g. [(x0, y0), (x1, y1), ... (xn, yn)]. + + The optional f parameter can be set to True to fill the polygon. Otherwise, just a one-pixel outline is drawn. + + Args: + x (int): X-coordinate of the polygon's position. + y (int): Y-coordinate of the polygon's position. + coords (list): List of coordinates. + c (int): color. + f (bool): Fill the polygon (default: False). + """ + _shapes.poly(self, *args, **kwargs) + + def rect(self, *args, **kwargs): + """ + Rectangle drawing function. Will draw a single pixel wide rectangle starting at + x0, y0 and extending w, h pixels. + + Args: + x0 (int): X-coordinate of the top-left corner of the rectangle. + y0 (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + c (int): color. + f (bool): Fill the rectangle (default: False). + """ + _shapes.rect(self, *args, **kwargs) + + def vline(self, *args, **kwargs): + """ + Horizontal line drawing function. Will draw a single pixel wide line. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + h (int): Height of the line. + c (int): color. + """ + _shapes.vline(self, *args, **kwargs) + + def text(self, *args, **kwargs): + """ + Place text on the canvas with an 8 pixel high font. + Breaks on \n to next line. Does not break on line going off canvas. + + Args: + s (str): The text to draw. + x (int): The x position to start drawing the text. + y (int): The y position to start drawing the text. + c (int): The color to draw the text in. Default is 1. + scale (int): The scale factor to draw the text at. Default is 1. + inverted (bool): If True, draw the text inverted. Default is False. + font_data (str): The path to the font file to use. Default is None. + """ + _font.text(self, *args, **kwargs) + + +def FrameBuffer1(buffer, width, height, format, stride=None): + """ + Create a new FrameBuffer object. Here only for historical reasons. + """ + return FrameBuffer(buffer, width, height, format, stride) diff --git a/micropython/pydisplay/graphics/graphics/_framebuf_plus.py b/micropython/pydisplay/graphics/graphics/_framebuf_plus.py new file mode 100644 index 000000000..73fb6b22c --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_framebuf_plus.py @@ -0,0 +1,627 @@ +from ._area import Area +from . import _shapes +from . import _files +from . import _font + +try: # Try to import framebuf from MicroPython + from framebuf import ( # type: ignore + MONO_VLSB, + MONO_HLSB, + MONO_HMSB, + GS2_HMSB, + GS4_HMSB, + GS8, + RGB565, + FrameBuffer as _FrameBuffer, + ) +except ImportError: # If framebuf is not available, import from _framebuf.py + from ._framebuf import ( + MONO_VLSB, + MONO_HLSB, + MONO_HMSB, + GS2_HMSB, + GS4_HMSB, + GS8, + RGB565, + FrameBuffer as _FrameBuffer, + ) + + +class FrameBuffer(_FrameBuffer): + """ + An extension of MicroPython's framebuf.FrameBuffer that adds some useful methods for drawing shapes and text. + Each method returns a bounding box (x, y, w, h) of the drawn shape to indicate + the area of the display that was modified. This can be used to update only the + modified area of the display. Exposes attributes not exposed in the base class, such + as color_depth, width, height, buffer, and format. Also adds a save method to save + the framebuffer to a file, and a from_file method to load a framebuffer from a file. + + Inherits from frambuf.Framebuffer, which may be compiled into MicroPython + or may be from _framebuf.py. Methods should return an Area object, but + the MicroPython framebuf module returns None, so the methods inherited from + framebuf.FrameBuffer are overridden to return an Area object. + + Args: + buffer (bytearray): Framebuffer buffer + width (int): Width in pixels + height (int): Height in pixels + format (int): Framebuffer format + + Attributes: + buffer (bytearray): Framebuffer buffer + width (int): Width in pixels + height (int): Height in pixels + format (int): Framebuffer format + color_depth (int): Color depth + """ + + def __init__(self, buffer, width, height, format, *args, **kwargs): + super().__init__(buffer, width, height, format, *args, **kwargs) + self._width = width + self._height = height + self._fb_format = format + self._buffer = buffer + if format == MONO_VLSB: + self._color_depth = 1 + elif format == MONO_HLSB: + self._color_depth = 1 + elif format == MONO_HMSB: + self._color_depth = 1 + elif format == RGB565: + self._color_depth = 16 + elif format == GS2_HMSB: + self._color_depth = 2 + elif format == GS4_HMSB: + self._color_depth = 4 + elif format == GS8: + self._color_depth = 8 + else: + raise ValueError("invalid format") + + @property + def color_depth(self): + return self._color_depth + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def buffer(self): + return self._buffer + + @property + def format(self): + return self._fb_format + + def fill_rect(self, x, y, w, h, c): + """ + Fill the given rectangle with the given color. + + Args: + x (int): x coordinate + y (int): y coordinate + w (int): Width in pixels + h (int): Height in pixels + c (int): color + + Returns: + (Area): Bounding box of the filled rectangle + """ + super().fill_rect(x, y, w, h, c) + return Area(x, y, w, h) + + def pixel(self, x, y, c=None): + """ + Draw a single pixel at the given location and color. + + Args: + x (int): x coordinate + y (int): y coordinate + c (int): color (default: None) + + Returns: + (Area): Bounding box of the pixel + """ + if c is None: + return super().pixel(x, y) + super().pixel(x, y, c) + return Area(x, y, 1, 1) + + def fill(self, c): + """ + Fill the buffer with the given color. + + Args: + c (int): color + + Returns: + (Area): Bounding box of the filled buffer + """ + super().fill(c) + return Area(0, 0, self.width, self.height) + + def ellipse(self, x, y, rx, ry, c, f=False, m=0b1111): + """ + Draw an ellipse at the given location, radii and color. + + Args: + x (int): Center x coordinate + y (int): Center y coordinate + rx (int): X radius + ry (int): Y radius + c (int): color + f (bool): Fill the ellipse (default: False) + m (int): Bitmask to determine which quadrants to draw (default: 0b1111) + + Returns: + (Area): Bounding box of the ellipse + """ + super().ellipse(x, y, rx, ry, c, f, m) + return Area(x - rx, y - ry, 2 * rx, 2 * ry) + + def hline(self, x, y, w, c): + """ + Draw a horizontal line at the given location, width and color. + + Args: + x (int): x coordinate + y (int): y coordinate + w (int): Width in pixels + c (int): color + + Returns: + (Area): Bounding box of the horizontal line + """ + super().hline(x, y, w, c) + return Area(x, y, w, 1) + + def line(self, x1, y1, x2, y2, c): + """ + Draw a line between the given start and end points and color. + + Args: + x1 (int): Start x coordinate + y1 (int): Start y coordinate + x2 (int): End x coordinate + y2 (int): End y coordinate + c (int): color + + Returns: + (Area): Bounding box of the line + """ + super().line(x1, y1, x2, y2, c) + return Area(min(x1, x2), min(y1, y2), abs(x2 - x1) + 1, abs(y2 - y1) + 1) + + def poly(self, x, y, coords, c, f=False): + """ + Draw a polygon at the given location, coordinates and color. + + Args: + x (int): x coordinate + y (int): y coordinate + coords (array): Array of x, y coordinate tuples + c (int): color + f (bool): Fill the polygon (default: False) + + Returns: + (Area): Bounding box of the polygon + """ + super().poly(x, y, coords, c, f) + # Calculate the bounding box of the polygon + # Convert the coords to a list of x, y tuples if it is not already + if isinstance(coords, list): + vertices = coords + elif isinstance(coords, tuple): + vertices = list(coords) + else: + # Check that the coords array has an even number of elements + if len(coords) % 2 != 0: + raise ValueError("coords must have an even number of elements") + vertices = [(coords[i], coords[i + 1]) for i in range(0, len(coords), 2)] + # Find the min and max x and y values + min_x = min([v[0] for v in vertices]) + min_y = min([v[1] for v in vertices]) + max_x = max([v[0] for v in vertices]) + max_y = max([v[1] for v in vertices]) + return Area(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1) + + def rect(self, x, y, w, h, c, f=False): + """ + Draw a rectangle at the given location, size and color. + + Args: + x (int): Top left corner x coordinate + y (int): Top left corner y coordinate + w (int): Width in pixels + h (int): Height in pixels + c (int): color + f (bool): Fill the rectangle (default: False) + + Returns: + (Area): Bounding box of the rectangle + """ + super().rect(x, y, w, h, c, f) + return Area(x, y, w, h) + + def vline(self, x, y, h, c): + """ + Draw a vertical line at the given location, height and color. + + Args: + x (int): x coordinate + y (int): y coordinate + h (int): Height in pixels + c (int): color + + Returns: + (Area): Bounding box of the vertical line + """ + super().vline(x, y, h, c) + return Area(x, y, 1, h) + + def text(self, s, x, y, c=1, scale=1, inverted=False, font_data=None, height=8): + """ + Draw text at the given location, using the given font and color. + + Args: + s (str): Text to draw + x (int): x coordinate + y (int): y coordinate + c (int): color + scale (int): Scale factor (default: 1) + inverted (bool): Invert the text (default: False) + font_data (str): Path to the font file (default: None) + height (int): Height of the font (default: 8) + + Returns: + (Area): Bounding box of the text + """ + _font.text( + self, s, x, y, c, scale=scale, inverted=inverted, font_data=font_data, height=height + ) + + def blit(self, buf, x, y, key=-1, palette=None): + """ + Blit the given buffer at the given location. + + Args: + buf (FrameBuffer): FrameBuffer to blit + x (int): x coordinate + y (int): y coordinate + key (int): Color key (default: -1) + palette (list): Palette (default: None) + + Returns: + (Area): Bounding box of the blitted buffer + """ + super().blit(buf, x, y, key, palette) + return + + ########### Additional methods + + def arc(self, *args, **kwargs): + """ + Arc drawing function. Will draw a single pixel wide arc with a radius r + centered at x, y from a0 to a1. + + Args: + x (int): X-coordinate of the arc's center. + y (int): Y-coordinate of the arc's center. + r (int): Radius of the arc. + a0 (float): Starting angle in degrees. + a1 (float): Ending angle in degrees. + c (int): color. + + Returns: + (Area): The bounding box of the arc. + """ + return _shapes.arc(self, *args, **kwargs) + + def blit_rect(self, buf, x, y, w, h): + """ + Blit a rectangular area from a buffer to the canvas. Uses the canvas's blit_rect method if available, + otherwise writes directly to the buffer. + + Args: + buf (memoryview): Buffer to blit. Must already be byte-swapped if necessary. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + w (int): Width of the area to blit. + h (int): Height of the area to blit. + + Returns: + (Area): The bounding box of the blitted area. + """ + BPP = 2 + + if x < 0 or y < 0 or x + w > self.width or y + h > self.height: + raise ValueError("The provided x, y, w, h values are out of range") + + if len(buf) != w * h * BPP: + print(f"len(buf)={len(buf)} w={w} h={h} self.color_depth={self.color_depth}") + raise ValueError("The source buffer is not the correct size") + + for row in range(h): + source_begin = row * w * BPP + source_end = source_begin + w * BPP + dest_begin = ((y + row) * self.width + x) * BPP + dest_end = dest_begin + w * BPP + self.buffer[dest_begin:dest_end] = buf[source_begin:source_end] + return Area(x, y, w, h) + + def blit_transparent(self, *args, **kwargs): + """ + Blit a buffer with transparency. + + Args: + buf (memoryview): Buffer to blit. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + w (int): Width of the area to blit. + h (int): Height of the area to blit. + key (int): Key value for transparency. + + Returns: + (Area): The bounding box of the blitted area. + """ + return _shapes.blit_transparent(self, *args, **kwargs) + + def circle(self, *args, **kwargs): + """ + Circle drawing function. Will draw a single pixel wide circle + centered at x0, y0 and the specified r. + + Args: + x0 (int): Center x coordinate + y0 (int): Center y coordinate + r (int): Radius + c (int): Color + f (bool): Fill the circle (default: False) + + Returns: + (Area): The bounding box of the circle. + """ + return _shapes.circle(self, *args, **kwargs) + + def gradient_rect(self, *args, **kwargs): + """ + Fill a rectangle with a gradient. + + Args: + x (int): X-coordinate of the top-left corner of the rectangle. + y (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + c1 (int): 565 encoded color for the top or left edge. + c2 (int): 565 encoded color for the bottom or right edge. If None or the same as c1, + fill_rect will be called instead. + vertical (bool): If True, the gradient will be vertical. If False, the gradient will be horizontal. + + Returns: + (Area): The bounding box of the filled area. + """ + return _shapes.gradient_rect(self, *args, **kwargs) + + def polygon(self, *args, **kwargs): + """ + Draw a polygon on the canvas. + + Args: + points (list): List of points to draw. + x (int): X-coordinate of the polygon's position. + y (int): Y-coordinate of the polygon's position. + color (int): color. + angle (float): Rotation angle in radians (default: 0). + center_x (int): X-coordinate of the rotation center (default: 0). + center_y (int): Y-coordinate of the rotation center (default: 0). + + Raises: + ValueError: If the polygon has less than 3 points. + + Returns: + (Area): The bounding box of the polygon. + """ + return _shapes.polygon(self, *args, **kwargs) + + def round_rect(self, *args, **kwargs): + """ + Rounded rectangle drawing function. Will draw a single pixel wide rounded rectangle starting at + x0, y0 and extending w, h pixels with the specified radius. + + Args: + x0 (int): X-coordinate of the top-left corner of the rectangle. + y0 (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + r (int): Radius of the corners. + c (int): color. + f (bool): Fill the rectangle (default: False). + + Returns: + (Area): The bounding box of the rectangle. + """ + return _shapes.round_rect(self, *args, **kwargs) + + def triangle(self, *args, **kwargs): + """ + Triangle drawing function. Draws a single pixel wide triangle with vertices at + (x0, y0), (x1, y1), and (x2, y2). + + Args: + x0 (int): X-coordinate of the first vertex. + y0 (int): Y-coordinate of the first vertex. + x1 (int): X-coordinate of the second vertex. + y1 (int): Y-coordinate of the second vertex. + x2 (int): X-coordinate of the third vertex. + y2 (int): Y-coordinate of the third vertex. + c (int): color. + f (bool): Fill the triangle (default: False). + + Returns: + (Area): The bounding box of the triangle. + """ + return _shapes.triangle(self, *args, **kwargs) + + def text8(self, *args, **kwargs): + """ + Place text on the canvas with an 8 pixel high font. + Breaks on \n to next line. Does not break on line going off canvas. + + Args: + canvas (Canvas): The DisplayDriver, FrameBuffer, or other canvas-like object to draw on. + s (str): The text to draw. + x (int): The x position to start drawing the text. + y (int): The y position to start drawing the text. + c (int): The color to draw the text in. Default is 1. + scale (int): The scale factor to draw the text at. Default is 1. + inverted (bool): If True, draw the text inverted. Default is False. + font_data (str): The path to the font file to use. Default is None. + + Returns: + Area: The area that was drawn to. + """ + return _font.text8(self, *args, **kwargs) + + def text14(self, *args, **kwargs): + """ + Place text on the canvas with a 14 pixel high font. + Breaks on \n to next line. Does not break on line going off canvas. + + Args: + canvas (Canvas): The DisplayDriver, FrameBuffer, or other canvas-like object to draw on. + s (str): The text to draw. + x (int): The x position to start drawing the text. + y (int): The y position to start drawing the text. + c (int): The color to draw the text in. Default is 1. + scale (int): The scale factor to draw the text at. Default is 1. + inverted (bool): If True, draw the text inverted. Default is False. + font_data (str): The path to the font file to use. Default is None. + + Returns: + Area: The area that was drawn to. + """ + return _font.text14(self, *args, **kwargs) + + def text16(self, *args, **kwargs): + """ + Place text on the canvas with a 16 pixel high font. + Breaks on \n to next line. Does not break on line going off canvas. + + Args: + canvas (Canvas): The DisplayDriver, FrameBuffer, or other canvas-like object to draw on. + s (str): The text to draw. + x (int): The x position to start drawing the text. + y (int): The y position to start drawing the text. + c (int): The color to draw the text in. Default is 1. + scale (int): The scale factor to draw the text at. Default is 1. + inverted (bool): If True, draw the text inverted. Default is False. + font_data (str): The path to the font file to use. Default is None. + + Returns: + Area: The area that was drawn to. + """ + return _font.text16(self, *args, **kwargs) + + def save(self, filename=None): + """ + Save the framebuffer to a file. The file extension must match the format, otherwise + the extension will be appended to the filename. + + Saves 1-bit formats as PBM, 2-bit formats as PGM with max value 3, 4-bit formats as PGM with max value 15, + 8-bit formats as PGM with max value 255, and 16-bit formats as BMP. + + Args: + filename (str): Filename to save to + """ + if filename is None: + filename = "screenshot" + file_ext = filename.split(".")[-1] + if self.format == MONO_HLSB: + if file_ext != "pbm": + filename += ".pbm" + with open(filename, "wb") as f: + f.write(b"P4\n") + f.write(f"{self.width} {self.height}\n".encode()) + f.write(self.buffer) + elif self.format == GS2_HMSB: + if file_ext != "pgm": + filename += ".pgm" + with open(filename, "wb") as f: + f.write(b"P5\n") + f.write(f"{self.width} {self.height}\n".encode()) + f.write(b"3\n") + f.write(self.buffer) + elif self.format == GS4_HMSB: + if file_ext != "pgm": + filename += ".pgm" + with open(filename, "wb") as f: + f.write(b"P5\n") + f.write(f"{self.width} {self.height}\n".encode()) + f.write(b"15\n") + f.write(self.buffer) + elif self.format == GS8: + if file_ext != "pgm": + filename += ".pgm" + with open(filename, "wb") as f: + f.write(b"P5\n") + f.write(f"{self.width} {self.height}\n".encode()) + f.write(b"255\n") + f.write(self.buffer) + elif self.format == RGB565: + if file_ext != "bmp": + filename += ".bmp" + with open(filename, "wb") as f: + f.write(b"BM") # Offset 0: Signature + f.write((54 + len(self.buffer)).to_bytes(4, "little")) # Offset 2: File size + f.write(b"\x00\x00\x00\x00") # Offset 6: Unused + f.write(b"\x36\x00\x00\x00") # Offset 10: Offset to image data + f.write(b"\x28\x00\x00\x00") # Offset 14: DIB header size + f.write(self.width.to_bytes(4, "little")) # Offset 18: Width + f.write(self.height.to_bytes(4, "little")) # Offset 22: Height + f.write(b"\x01\x00") # Offset 26: Planes + f.write(b"\x10\x00") # Offset 28: Bits per pixel + f.write(b"\x00\x00\x00\x00") # Offset 30: Compression + f.write(len(self.buffer).to_bytes(4, "little")) # Offset 34: Image size + f.write( + b"\x00\x00\x00\x00\x00\x00\x00\x00" + ) # Offset 38: Horizontal and vertical resolution + f.write(b"\x00\x00\x00\x00") # Offset 46: Colors in palette + f.write(b"\x00\x00\x00\x00") # Offset 50: Important colors + # The order of the lines is reversed. We need to reverse them back. + for i in range(self.height): + f.write( + self.buffer[ + (self.height - i - 1) * self.width * 2 : (self.height - i) + * self.width + * 2 + ] + ) + else: + raise ValueError(f"Save method not implemented for format {self.format}") + + @staticmethod + def from_file(filename): + """ + Load a framebuffer from a file. + + Args: + filename (str): Filename to load from + """ + # Read the first two bytes to determine the file type + f = open(filename, "rb") + header = f.read(2) + f.close() + + if header == b"P4": + return _files.pbm_to_framebuffer(filename) + elif header == b"P5": + return _files.pgm_to_framebuffer(filename) + elif header == b"BM": + return _files.bmp_to_framebuffer(filename) + else: + raise ValueError(f"Unsupported file type {header}") diff --git a/micropython/pydisplay/graphics/graphics/_shapes.py b/micropython/pydisplay/graphics/graphics/_shapes.py new file mode 100644 index 000000000..99cd19b0b --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/_shapes.py @@ -0,0 +1,905 @@ +# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries, 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`graphics._shapes` +==================================================== +Graphics primitives for drawing on a canvas. + +Heavily modified from gfx.py at: +https://github.com/adafruit/Adafruit_CircuitPython_GFX +* Author(s): Kattni Rembor, Tony DiCola, Jonah Yolles-Murphy, based on code by Phil Burgess + +Implementation Notes +-------------------- +.pixel(), .fill_rect(), .fill() and .blit_rect() will be called from the canvas object if the canvas +object has these methods. + +.pixel() and .blit_rect() assume 16-bit color depth. + +""" + +import math +from ._area import Area + + +def arc(canvas, x, y, r, a0, a1, c): + """ + Arc drawing function. Will draw a single pixel wide arc with a radius r + centered at x, y from a0 to a1. + + Args: + x (int): X-coordinate of the arc's center. + y (int): Y-coordinate of the arc's center. + r (int): Radius of the arc. + a0 (float): Starting angle in degrees. + a1 (float): Ending angle in degrees. + c (int): color. + + Returns: + (Area): The bounding box of the arc. + """ + resolution = 60 + a0 = math.radians(a0) + a1 = math.radians(a1) + x0 = x + int(r * math.cos(a0)) + y0 = y + int(r * math.sin(a0)) + if a1 > a0: + arc_range = range(int(a0 * resolution), int(a1 * resolution)) + else: + arc_range = range(int(a0 * resolution), int(a1 * resolution), -1) + + x_min = x_max = x0 + y_min = y_max = y0 + for a in arc_range: + ar = a / resolution + x1 = x + int(r * math.cos(ar)) + y1 = y + int(r * math.sin(ar)) + line(canvas, x0, y0, x1, y1, c) + x_min = min(x0, x1, x_min) + x_max = max(x0, x1, x_max) + y_min = min(y0, y1, y_min) + y_max = max(y0, y1, y_max) + x0 = x1 + y0 = y1 + return Area(x_min, y_min, x_max - x_min, y_max - y_min) + + +def blit(canvas, source, x, y, key=-1, palette=None): + """ + Blit a source to the canvas at the specified x, y location. + + Args: + source (FrameBuffer): Source FrameBuffer object. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + key (int): Key value for transparency (default: -1). + palette (Palette): Palette object for color translation (default: None). + + Returns: + (Area): The bounding box of the blitted area. + """ + if ( + (-x >= source.width) + or (-y >= source.height) + or (x >= canvas.width) + or (y >= canvas.height) + ): + # Out of bounds, no-op. + return + + # Clip. + x0 = max(0, x) + y0 = max(0, y) + x1 = max(0, -x) + y1 = max(0, -y) + x0end = min(canvas.width, x + source.width) + y0end = min(canvas.height, y + source.height) + + for cy0 in range(y0, y0end): + cx1 = x1 + for cx0 in range(x0, x0end): + col = source.pixel(cx1, y1) + if palette: + col = palette.pixel(col, 0) + if col != key: + pixel(canvas, cx0, cy0, col) + cx1 += 1 + y1 += 1 + return Area(x0, y0, x0end - x0, y0end - y0) + + +def blit_rect(canvas, buf, x, y, w, h): + """ + Blit a rectangular area from a buffer to the canvas. Uses the canvas's blit_rect method if available, + otherwise writes directly to the buffer. + + Args: + buf (memoryview): Buffer to blit. Must already be byte-swapped if necessary. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + w (int): Width of the area to blit. + h (int): Height of the area to blit. + + Returns: + (Area): The bounding box of the blitted area. + """ + if hasattr(canvas, "blit_rect"): + canvas.blit_rect(buf, x, y, w, h) + else: + BPP = 2 + + if x < 0 or y < 0 or x + w > canvas.width or y + h > canvas.height: + raise ValueError("The provided x, y, w, h values are out of range") + + if len(buf) != w * h * BPP: + print(f"len(buf)={len(buf)} w={w} h={h} self.color_depth={canvas.color_depth}") + raise ValueError("The source buffer is not the correct size") + + for row in range(h): + source_begin = row * w * BPP + source_end = source_begin + w * BPP + dest_begin = ((y + row) * canvas.width + x) * BPP + dest_end = dest_begin + w * BPP + canvas.buffer[dest_begin:dest_end] = buf[source_begin:source_end] + return Area(x, y, w, h) + + +def blit_transparent(canvas, buf, x, y, w, h, key): + """ + Blit a buffer with transparency. + + Args: + buf (memoryview): Buffer to blit. + x (int): X-coordinate to blit to. + y (int): Y-coordinate to blit to. + w (int): Width of the area to blit. + h (int): Height of the area to blit. + key (int): Key value for transparency. + + Returns: + (Area): The bounding box of the blitted area. + """ + BPP = canvas.color_depth // 8 + key_bytes = key.to_bytes(BPP, "little") + stride = w * BPP + for j in range(h): + rowstart = j * stride + colstart = 0 + # iterate over each pixel looking for the first non-key pixel + while colstart < stride: + startoffset = rowstart + colstart + if buf[startoffset : startoffset + BPP] != key_bytes: + # found a non-key pixel + # then iterate over each pixel looking for the next key pixel + colend = colstart + while colend < stride: + endoffset = rowstart + colend + if buf[endoffset : endoffset + BPP] == key_bytes: + break + colend += BPP + # blit the non-key pixels + blit_rect( + canvas, + buf[rowstart + colstart : rowstart + colend], + x + colstart // BPP, + y + j, + (colend - colstart) // BPP, + 1, + ) + colstart = colend + else: + colstart += BPP + return Area(x, y, w, h) + + +def circle(canvas, x0, y0, r, c, f=False): + """ + Circle drawing function. Will draw a single pixel wide circle + centered at x0, y0 and the specified r. + + Args: + x0 (int): Center x coordinate + y0 (int): Center y coordinate + r (int): Radius + c (int): Color + f (bool): Fill the circle (default: False) + + Returns: + (Area): The bounding box of the circle. + """ + if f: + return _fill_circle_helper(canvas, x0, y0, r, c, 0, 0) + + _circle_helper(canvas, x0, y0, r, c, 0, 0) + return Area(x0 - r, y0 - r, 2 * r, 2 * r) + + +def _circle_helper(canvas, x0, y0, r, c, x_offset, y_offset): + """ + Circle helper function. Draws the 4 quadrants of a circle with center at x0, y0 and the specified r + separated by the specified x_offset and y_offset. Draws a circle if offsets are 0. Draws the 4 corners of + a round_rect if an offset is greater than 0. + """ + f = 1 - r + ddF_x = 1 + ddF_y = -2 * r + x = 0 + y = r + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + offset_x = x + x_offset + offset_y = y + y_offset + pixel(canvas, x0 + offset_x - 1, y0 - offset_y, c) # 90 to 45 + pixel(canvas, x0 - offset_x, y0 - offset_y, c) # 90 to 135 + pixel(canvas, x0 + offset_x - 1, y0 + offset_y - 1, c) # 270 to 315 + pixel(canvas, x0 - offset_x, y0 + offset_y - 1, c) # 270 to 225 + offset_x = y + x_offset + offset_y = x + y_offset + pixel(canvas, x0 + offset_x - 1, y0 + offset_y - 1, c) # 0 to 315 + pixel(canvas, x0 - offset_x, y0 + offset_y - 1, c) # 180 to 225 + pixel(canvas, x0 + offset_x - 1, y0 - offset_y, c) # 0 to 45 + pixel(canvas, x0 - offset_x, y0 - offset_y, c) # 180 to 135 + + +def _fill_circle_helper(canvas, x0, y0, r, c, x_offset, y_offset): + """ + Fill circle helper function. Draws the 4 quadrants of a filled circle with center at x0, y0 and the + specified r separated by the specified x_offset and y_offset. Fills a circle if offsets are 0. Fills the + 4 corners of a filled round_rect if an offset is greater than 0. + """ + # vline(canvas, x0, y0 - r, 2 * r + 1, c) + f = 1 - r + ddF_x = 1 + ddF_y = -2 * r + x = 0 + y = r + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + offset_x = x + x_offset + offset_y = y + y_offset + vline(canvas, x0 - offset_x, y0 - offset_y, 2 * offset_y, c) + vline(canvas, x0 + offset_x - 1, y0 - offset_y, 2 * offset_y, c) + offset_x = y + x_offset + offset_y = x + y_offset + vline(canvas, x0 - offset_x, y0 - offset_y, 2 * offset_y, c) + vline(canvas, x0 + offset_x - 1, y0 - offset_y, 2 * offset_y, c) + + return Area(x0 - r, y0 - r, 2 * r, 2 * r) + + +def ellipse(canvas, x0, y0, r1, r2, c, f=False, m=0b1111, w=None, h=None): + """ + Midpoint ellipse algorithm + Draw an ellipse at the given location. Radii r1 and r2 define the geometry; equal values cause a + circle to be drawn. The c parameter defines the color. + + The optional f parameter can be set to True to fill the ellipse. Otherwise just a one pixel outline + is drawn. + + The optional m parameter enables drawing to be restricted to certain quadrants of the ellipse. + The LS four bits determine which quadrants are to be drawn, with bit 0 specifying Q1, b1 Q2, + b2 Q3 and b3 Q4. Quadrants are numbered counterclockwise with Q1 being top right. + + Args: + x0 (int): Center x coordinate + y0 (int): Center y coordinate + r1 (int): x radius + r2 (int): y radius + c (int): color + f (bool): Fill the ellipse (default: False) + m (int): Bitmask to determine which quadrants to draw (default: 0b1111) + w (int): Width of the ellipse (default: None) + h (int): Height of the ellipse (default: None) + + Returns: + (Area): The bounding box of the ellipse. + """ + if r1 < 1 or r2 < 1: + return + + x_side = w - 2 * r1 if w else 0 + y_side = h - 2 * r2 if h else 0 + x_offset = x_side // 2 if w else 0 + y_offset = y_side // 2 if h else 0 + + if f: + if y_offset > 0: + fill_rect(canvas, x0 - w // 2, y0 - y_offset, w, y_side, c) + if x_offset > 0: + fill_rect(canvas, x0 - x_offset, y0 - h // 2, x_side, r1, c) + fill_rect(canvas, x0 - x_offset, y0 + h // 2 - r1, x_side, r1, c) + + if x_offset > 0: + hline(canvas, x0 - x_offset, y0 - h // 2, x_side, c) + hline(canvas, x0 - x_offset, y0 + h // 2, x_side, c) + if y_offset > 0: + vline(canvas, x0 - w // 2, y0 - y_offset, y_side, c) + vline(canvas, x0 + w // 2, y0 - y_offset, y_side, c) + + a2 = r1 * r1 + b2 = r2 * r2 + fa2 = 4 * a2 + fb2 = 4 * b2 + + x1 = r1 + y1 = 0 + sigma = 2 * a2 + b2 * (1 - 2 * r1) + while a2 * y1 <= b2 * x1: + if f: + if m & 0x1: + hline(canvas, x0 + x_offset, y0 - y1 - y_offset, x1, c) + if m & 0x2: + hline(canvas, x0 - x1 - x_offset, y0 - y1 - y_offset, x1, c) + if m & 0x4: + hline(canvas, x0 - x1 - x_offset, y0 + y1 + y_offset, x1, c) + if m & 0x8: + hline(canvas, x0 + x_offset, y0 + y1 + y_offset, x1, c) + else: + if m & 0x1: + pixel(canvas, x0 + x1 + x_offset, y0 - y1 - y_offset, c) + if m & 0x2: + pixel(canvas, x0 - x1 - x_offset, y0 - y1 - y_offset, c) + if m & 0x4: + pixel(canvas, x0 - x1 - x_offset, y0 + y1 + y_offset, c) + if m & 0x8: + pixel(canvas, x0 + x1 + x_offset, y0 + y1 + y_offset, c) + if sigma >= 0: + sigma += fb2 * (1 - x1) + x1 -= 1 + sigma += a2 * ((4 * y1) + 6) + y1 += 1 + + x1 = 0 + y1 = r2 + sigma = 2 * b2 + a2 * (1 - 2 * r2) + while b2 * x1 <= a2 * y1: + if f: + if m & 0x1: + hline(canvas, x0 + x_offset, y0 - y1 - y_offset, x1, c) + if m & 0x2: + hline(canvas, x0 - x1 - x_offset, y0 - y1 - y_offset, x1, c) + if m & 0x4: + hline(canvas, x0 - x1 - x_offset, y0 + y1 + y_offset, x1, c) + if m & 0x8: + hline(canvas, x0 + x_offset, y0 + y1 + y_offset, x1, c) + else: + if m & 0x1: + pixel(canvas, x0 + x1 + x_offset, y0 - y1 - y_offset, c) + if m & 0x2: + pixel(canvas, x0 - x1 - x_offset, y0 - y1 - y_offset, c) + if m & 0x4: + pixel(canvas, x0 - x1 - x_offset, y0 + y1 + y_offset, c) + if m & 0x8: + pixel(canvas, x0 + x1 + x_offset, y0 + y1 + y_offset, c) + if sigma >= 0: + sigma += fa2 * (1 - y1) + y1 -= 1 + sigma += b2 * ((4 * x1) + 6) + x1 += 1 + return Area(x0 - r1 - x_offset, y0 - r2 - y_offset, 2 * (r1 + x_offset), 2 * (r2 + y_offset)) + + +def fill(canvas, c): + """ + Fill the entire canvas with a color. Uses the canvas's fill method if available, + otherwise calls the fill_rect function. + + Args: + c (int): color. + + Returns: + (Area): The bounding box of the filled area. + """ + if hasattr(canvas, "fill"): + canvas.fill(c) + return Area(0, 0, canvas.width, canvas.height) + else: + return fill_rect(canvas, 0, 0, canvas.width, canvas.height, c) + + +def fill_rect(canvas, x, y, w, h, c): + """ + Filled rectangle drawing function. Draws a filled rectangle starting at + x, y and extending w, h pixels. Uses the canvas's fill_rect method if available, + otherwise calls the pixel function for each pixel. + + Args: + x (int): X-coordinate of the top-left corner of the rectangle. + y (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + c (int): color + + Returns: + (Area): The bounding box of the filled area. + """ + if y < -h or y > canvas.height or x < -w or x > canvas.width: + return + if hasattr(canvas, "fill_rect"): + canvas.fill_rect(x, y, w, h, c) + else: + for j in range(y, y + h): + for i in range(x, x + w): + pixel(canvas, i, j, c) + return Area(x, y, w, h) + + +def gradient_rect(canvas, x, y, w, h, c1, c2=None, vertical=True): + """ + Fill a rectangle with a gradient. + + Args: + x (int): X-coordinate of the top-left corner of the rectangle. + y (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + c1 (int): 565 encoded color for the top or left edge. + c2 (int): 565 encoded color for the bottom or right edge. If None or the same as c1, + fill_rect will be called instead. + vertical (bool): If True, the gradient will be vertical. If False, the gradient will be horizontal. + + Returns: + (Area): The bounding box of the filled area. + """ + if c2 is None or c1 == c2: + return fill_rect(canvas, x, y, w, h, c1) + r1, g1, b1 = (c1 >> 8) & 0xF8, (c1 >> 3) & 0xFC, (c1 << 3) & 0xF8 + r2, g2, b2 = (c2 >> 8) & 0xF8, (c2 >> 3) & 0xFC, (c2 << 3) & 0xF8 + if vertical: + for j in range(h): + r = r1 + (r2 - r1) * j // h + g = g1 + (g2 - g1) * j // h + b = b1 + (b2 - b1) * j // h + c = (r & 0xF8) << 8 | (g & 0xFC) << 3 | (b & 0xF8) >> 3 + fill_rect(canvas, x, y + j, w, 1, c) + else: + for i in range(w): + r = r1 + (r2 - r1) * i // w + g = g1 + (g2 - g1) * i // w + b = b1 + (b2 - b1) * i // w + c = (r & 0xF8) << 8 | (g & 0xFC) << 3 | (b & 0xF8) >> 3 + fill_rect(canvas, x + i, y, 1, h, c) + return Area(x, y, w, h) + + +def hline(canvas, x0, y0, w, c): + """ + Horizontal line drawing function. Will draw a single pixel wide line. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + w (int): Width of the line. + c (int): color. + + Returns: + (Area): The bounding box of the line. + """ + if y0 < 0 or y0 > canvas.height or x0 < -w or x0 > canvas.width: + return + fill_rect(canvas, x0, y0, w, 1, c) + return Area(x0, y0, w, 1) + + +def line(canvas, x0, y0, x1, y1, c): + """ + Line drawing function. Will draw a single pixel wide line starting at + x0, y0 and ending at x1, y1. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + x1 (int): X-coordinate of the end of the line. + y1 (int): Y-coordinate of the end of the line. + c (int): color. + + Returns: + (Area): The bounding box of the line. + """ + if x0 == x1: + return vline(canvas, x0, y0, abs(y1 - y0) + 1, c) + if y0 == y1: + return hline(canvas, x0, y0, abs(x1 - x0) + 1, c) + + steep = abs(y1 - y0) > abs(x1 - x0) + if steep: + x0, y0 = y0, x0 + x1, y1 = y1, x1 + if x0 > x1: + x0, x1 = x1, x0 + y0, y1 = y1, y0 + dx = x1 - x0 + dy = abs(y1 - y0) + err = dx // 2 + ystep = 0 + if y0 < y1: + ystep = 1 + else: + ystep = -1 + while x0 <= x1: + if steep: + pixel(canvas, y0, x0, c) + else: + pixel(canvas, x0, y0, c) + err -= dy + if err < 0: + y0 += ystep + err += dx + x0 += 1 + return Area(min(x0, x1), min(y0, y1), abs(x1 - x0), abs(y1 - y0)) + + +def pixel(canvas, x, y, c): + """ + Draw a single pixel at the specified x, y location. Uses the canvas's pixel method if available, + otherwise writes directly to the buffer. + + Args: + x (int): X-coordinate of the pixel. + y (int): Y-coordinate of the pixel. + c (int): color. + + Returns: + (Area): The bounding box of the pixel. + """ + if hasattr(canvas, "pixel"): + canvas.pixel(x, y, c) + else: + rgb565_color = (c & 0xFFFF).to_bytes(2, "little") + canvas.buffer[(y * canvas.width + x) * 2 : (y * canvas.width + x) * 2 + 2] = rgb565_color + return Area(x, y, 1, 1) + + +def poly(canvas, x, y, coords, c, f=False): + """ + Given a list of coordinates, draw an arbitrary (convex or concave) closed polygon at the given x, y location + using the given color. + + The coords must be specified as an array of integers, e.g. array('h', [x0, y0, x1, y1, ... xn, yn]) or a + list or tuple of points, e.g. [(x0, y0), (x1, y1), ... (xn, yn)]. + + The optional f parameter can be set to True to fill the polygon. Otherwise, just a one-pixel outline is drawn. + + Args: + x (int): X-coordinate of the polygon's position. + y (int): Y-coordinate of the polygon's position. + coords (list): List of coordinates. + c (int): color. + f (bool): Fill the polygon (default: False). + + Returns: + (Area): The bounding box of the polygon. + """ + + # Convert the coords to a list of x, y tuples if it is not already + if isinstance(coords, list): + vertices = coords + elif isinstance(coords, tuple): + vertices = list(coords) + else: + # Check that the coords array has an even number of elements + if len(coords) % 2 != 0: + raise ValueError("coords must have an even number of elements") + vertices = [(coords[i], coords[i + 1]) for i in range(0, len(coords), 2)] + + # Check that the polygon has at least 3 vertices + if len(vertices) < 3: + raise ValueError("polygon must have at least 3 vertices") + + # Close the polygon if it is not already closed + if vertices[0] != vertices[-1]: + vertices.append(vertices[0]) + + # Offset vertices by (x, y) + vertices = [(x + vertex[0], y + vertex[1]) for vertex in vertices] + + # Find the rectangle bounding box of the polygon + left = min(vertex[0] for vertex in vertices) + right = max(vertex[0] for vertex in vertices) + top = min(vertex[1] for vertex in vertices) + bottom = max(vertex[1] for vertex in vertices) + + if f: + # Fill the polygon using scanline algorithm + # Calculate the minimum and maximum y-coordinates in the polygon + y_min = min(vertex[1] for vertex in vertices) + y_max = max(vertex[1] for vertex in vertices) + + # Iterate through each y-coordinate within the bounding box + for y_scan in range(y_min, y_max + 1): + # Determine intersections with the polygon edges + intersections = [] + for i in range(len(vertices) - 1): + x1, y1 = vertices[i] + x2, y2 = vertices[i + 1] + # Check if the scanline intersects the edge + if y1 <= y_scan < y2 or y2 <= y_scan < y1: + # Calculate the intersection point using linear interpolation + x_intersection = x1 + ((y_scan - y1) / (y2 - y1)) * (x2 - x1) + intersections.append(x_intersection) + + # Sort intersections in increasing order + intersections.sort() + + # Draw horizontal lines between pairs of intersection points + for i in range(0, len(intersections), 2): + x_start = int(intersections[i]) + x_end = int(intersections[i + 1]) + hline(canvas, x_start, y_scan, x_end - x_start, c) + else: + for i in range(len(vertices) - 1): + line( + canvas, + vertices[i][0], + vertices[i][1], + vertices[i + 1][0], + vertices[i + 1][1], + c, + ) + return Area(left, top, right - left, bottom - top) + + +def polygon(canvas, points, x, y, color, angle=0, center_x=0, center_y=0): + """ + Draw a polygon on the canvas. + + Args: + points (list): List of points to draw. + x (int): X-coordinate of the polygon's position. + y (int): Y-coordinate of the polygon's position. + color (int): color. + angle (float): Rotation angle in radians (default: 0). + center_x (int): X-coordinate of the rotation center (default: 0). + center_y (int): Y-coordinate of the rotation center (default: 0). + + Raises: + ValueError: If the polygon has less than 3 points. + + Returns: + (Area): The bounding box of the polygon. + """ + # MIT License + # Copyright (c) 2024 Brad Barnett + # Copyright (c) 2020-2023 Russ Hughes + # Copyright (c) 2019 Ivan Belokobylskiy + if len(points) < 3: + raise ValueError("Polygon must have at least 3 points.") + + # fmt: off + if angle: + cos_a = math.cos(angle) + sin_a = math.sin(angle) + rotated = [ + (x + center_x + int((point[0] - center_x) * cos_a - (point[1] - center_y) * sin_a), + y + center_y + int((point[0] - center_x) * sin_a + (point[1] - center_y) * cos_a)) + for point in points + ] + else: + rotated = [(x + int((point[0])), y + int((point[1]))) for point in points] + + # Find the rectangle bounding box of the polygon + left = min(vertex[0] for vertex in rotated) + right = max(vertex[0] for vertex in rotated) + top = min(vertex[1] for vertex in rotated) + bottom = max(vertex[1] for vertex in rotated) + + for i in range(1, len(rotated)): + line(canvas, rotated[i - 1][0], rotated[i - 1][1], rotated[i][0], rotated[i][1], color) + # fmt: on + return Area(left, top, right - left, bottom - top) + + +def rect(canvas, x0, y0, w, h, c, f=False): + """ + Rectangle drawing function. Will draw a single pixel wide rectangle starting at + x0, y0 and extending w, h pixels. + + Args: + x0 (int): X-coordinate of the top-left corner of the rectangle. + y0 (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + c (int): color. + f (bool): Fill the rectangle (default: False). + + Returns: + (Area): The bounding box of the rectangle. + """ + if f: + return fill_rect(canvas, x0, y0, w, h, c) + if y0 < -h or y0 > canvas.height or x0 < -w or x0 > canvas.width: + return + hline(canvas, x0, y0, w, c) + hline(canvas, x0, y0 + h - 1, w, c) + vline(canvas, x0, y0, h, c) + vline(canvas, x0 + w - 1, y0, h, c) + return Area(x0, y0, w, h) + + +def round_rect(canvas, x0, y0, w, h, r, c, f=False): + """ + Rounded rectangle drawing function. Will draw a single pixel wide rounded rectangle starting at + x0, y0 and extending w, h pixels with the specified radius. + + Args: + x0 (int): X-coordinate of the top-left corner of the rectangle. + y0 (int): Y-coordinate of the top-left corner of the rectangle. + w (int): Width of the rectangle. + h (int): Height of the rectangle. + r (int): Radius of the corners. + c (int): color. + f (bool): Fill the rectangle (default: False). + + Returns: + (Area): The bounding box of the rectangle. + """ + # If the radius is 0, just draw a rectangle + if r == 0: + return rect(canvas, x0, y0, w, h, c, f) + + # If filled, draw the rounded rectangle using the _fill_round_rect function + if f: + return _fill_round_rect(canvas, x0, y0, w, h, r, c) + + # ensure that the r will only ever half of the shortest side or less + r = int(min(r, w / 2, h / 2)) + + hline(canvas, x0 + r, y0, w - 2 * r, c) # top + hline(canvas, x0 + r, y0 + h - 1, w - 2 * r, c) # bottom + vline(canvas, x0, y0 + r, h - 2 * r, c) # left + vline(canvas, x0 + w - 1, y0 + r, h - 2 * r, c) # right + _circle_helper(canvas, x0 + w // 2, y0 + h // 2, r, c, w // 2 - r, h // 2 - r) + return Area(x0, y0, w, h) + + +def _fill_round_rect(canvas, x0, y0, w, h, r, c): + """ + Filled rounded rectangle drawing function. Will draw a filled rounded rectangle starting at + x0, y0 and extending w, h pixels with the specified radius. + """ + + # ensure that the r will only ever be half of the shortest side or less + r = int(min(r, w / 2, h / 2)) + fill_rect(canvas, x0 + r, y0, w - 2 * r, h, c) # center + _fill_circle_helper(canvas, x0 + w // 2, y0 + h // 2, r, c, w // 2 - r, h // 2 - r) + return Area(x0, y0, w, h) + + +def triangle(canvas, x0, y0, x1, y1, x2, y2, c, f=False): + # pylint: disable=too-many-arguments + """ + Triangle drawing function. Draws a single pixel wide triangle with vertices at + (x0, y0), (x1, y1), and (x2, y2). + + Args: + x0 (int): X-coordinate of the first vertex. + y0 (int): Y-coordinate of the first vertex. + x1 (int): X-coordinate of the second vertex. + y1 (int): Y-coordinate of the second vertex. + x2 (int): X-coordinate of the third vertex. + y2 (int): Y-coordinate of the third vertex. + c (int): color. + f (bool): Fill the triangle (default: False). + + Returns: + (Area): The bounding box of the triangle. + """ + if f: + return _fill_triangle(canvas, x0, y0, x1, y1, x2, y2, c) + line(canvas, x0, y0, x1, y1, c) + line(canvas, x1, y1, x2, y2, c) + line(canvas, x2, y2, x0, y0, c) + left = min(x0, x1, x2) + top = min(y0, y1, y2) + right = max(x0, x1, x2) + bottom = max(y0, y1, y2) + return Area(left, top, right - left, bottom - top) + + +def _fill_triangle(canvas, x0, y0, x1, y1, x2, y2, c): + # pylint: disable=too-many-arguments + """ + Filled triangle drawing function. Will draw a filled triangle with vertices at + (x0, y0), (x1, y1), and (x2, y2). + """ + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + if y1 > y2: + y2, y1 = y1, y2 + x2, x1 = x1, x2 + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + a = 0 + b = 0 + last = 0 + if y0 == y2: + a = x0 + b = x0 + if x1 < a: + a = x1 + elif x1 > b: + b = x1 + if x2 < a: + a = x2 + elif x2 > b: + b = x2 + hline(canvas, a, y0, b - a + 1, c) + return + dx01 = x1 - x0 + dy01 = y1 - y0 + dx02 = x2 - x0 + dy02 = y2 - y0 + dx12 = x2 - x1 + dy12 = y2 - y1 + if dy01 == 0: + dy01 = 1 + if dy02 == 0: + dy02 = 1 + if dy12 == 0: + dy12 = 1 + sa = 0 + sb = 0 + y = y0 + if y0 == y1: + last = y1 - 1 + else: + last = y1 + while y <= last: + a = x0 + sa // dy01 + b = x0 + sb // dy02 + sa += dx01 + sb += dx02 + if a > b: + a, b = b, a + hline(canvas, a, y, b - a + 1, c) + y += 1 + sa = dx12 * (y - y1) + sb = dx02 * (y - y0) + while y <= y2: + a = x1 + sa // dy12 + b = x0 + sb // dy02 + sa += dx12 + sb += dx02 + if a > b: + a, b = b, a + hline(canvas, a, y, b - a + 1, c) + y += 1 + left = min(x0, x1, x2) + top = min(y0, y1, y2) + right = max(x0, x1, x2) + bottom = max(y0, y1, y2) + return Area(left, top, right - left, bottom - top) + + +def vline(canvas, x0, y0, h, c): + """ + Horizontal line drawing function. Will draw a single pixel wide line. + + Args: + x0 (int): X-coordinate of the start of the line. + y0 (int): Y-coordinate of the start of the line. + h (int): Height of the line. + c (int): color. + + Returns: + (Area): The bounding box of the line. + """ + if y0 < -h or y0 > canvas.height or x0 < 0 or x0 > canvas.width: + return + fill_rect(canvas, x0, y0, 1, h, c) + return Area(x0, y0, 1, h) diff --git a/micropython/pydisplay/graphics/graphics/font_8x14.py b/micropython/pydisplay/graphics/graphics/font_8x14.py new file mode 100644 index 000000000..a4810bc68 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/font_8x14.py @@ -0,0 +1,259 @@ +_FONT = ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x7e\x00\x00\x00" + b"\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\x7e\x00\x00\x00" + b"\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00" + b"\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00" + b"\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00" + b"\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff" + b"\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\x78\x00\x00\x00" + b"\x00\x00\x3c\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00" + b"\x00\x00\x3f\x33\x3f\x30\x30\x30\x70\xf0\xe0\x00\x00\x00" + b"\x00\x00\x7f\x63\x7f\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00" + b"\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00" + b"\x00\x00\x80\xc0\xe0\xf8\xfe\xf8\xe0\xc0\x80\x00\x00\x00" + b"\x00\x00\x02\x06\x0e\x3e\xfe\x3e\x0e\x06\x02\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00" + b"\x00\x00\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00" + b"\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x00\x00\x00" + b"\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x00\x00\x00" + b"\x00\x00\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00" + b"\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00" + b"\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x3c\x3c\x18\x18\x00\x18\x18\x00\x00\x00" + b"\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00" + b"\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x86\xc6\x7c\x18\x18\x00" + b"\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x66\xc6\x00\x00\x00" + b"\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00" + b"\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00" + b"\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00" + b"\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xce\xde\xf6\xe6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x7e\x00\x00\x00" + b"\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc6\xfe\x00\x00\x00" + b"\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\xc6\x7c\x00\x00\x00" + b"\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x1e\x00\x00\x00" + b"\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\xc6\x7c\x00\x00\x00" + b"\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\xfe\xc6\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x0c\x78\x00\x00\x00" + b"\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00" + b"\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00" + b"\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x00\x18\x18\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00" + b"\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\xfc\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00" + b"\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00" + b"\x00\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00" + b"\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\xf0\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\x66\x3a\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00" + b"\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\x78\x00\x00\x00" + b"\x00\x00\xe6\x66\x6c\x6c\x78\x6c\x6c\x66\xe6\x00\x00\x00" + b"\x00\x00\xf0\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00" + b"\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\x00\x00\x00" + b"\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\xf0\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\xe6\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\x60\x38\x0c\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xfe\x7c\x6c\x00\x00\x00" + b"\x00\x00\xc6\xc6\x6c\x38\x38\x38\x6c\xc6\xc6\x00\x00\x00" + b"\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\xfe\xc6\x8c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00" + b"\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00" + b"\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00" + b"\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00" + b"\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00" + b"\x30\x30\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x7c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xf0\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00" + b"\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\xe6\x00\x00\x00" + b"\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x66\x66\x3c\x00" + b"\x00\x00\xe0\x60\x60\x66\x6c\x78\x6c\x66\xe6\x00\x00\x00" + b"\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xc6\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x7c\x60\x60\xf0\x00" + b"\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00" + b"\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\xf0\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\x70\x1c\xc6\x7c\x00\x00\x00" + b"\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x36\x1c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x66\x66\x66\x66\x3c\x18\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xfe\x6c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x6c\xc6\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00" + b"\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x66\xfe\x00\x00\x00" + b"\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x0e\x00\x00\x00" + b"\x00\x00\x18\x18\x18\x18\x00\x18\x18\x18\x18\x00\x00\x00" + b"\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x70\x00\x00\x00" + b"\x00\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xfe\x00\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xc2\x66\x3c\x0c\x06\x7c\x00" + b"\x00\x00\xcc\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\xcc\xcc\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\x00\x00\x3c\x66\x60\x66\x3c\x0c\x06\x3c\x00\x00" + b"\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x00\xcc\xcc\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc6\x7c\x00\x00\x00" + b"\x00\x00\x66\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\xc6\xc6\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00" + b"\x38\x6c\x38\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x00\x00\x00" + b"\x18\x30\x60\x00\xfe\x66\x60\x7c\x60\x66\xfe\x00\x00\x00" + b"\x00\x00\x00\x00\xcc\x76\x36\x7e\xd8\xd8\x6e\x00\x00\x00" + b"\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xce\x00\x00\x00" + b"\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\xc6\xc6\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00" + b"\x00\xc6\xc6\x38\x6c\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00" + b"\x00\xc6\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x18\x18\x3c\x66\x60\x60\x66\x3c\x18\x18\x00\x00\x00" + b"\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\xe6\xfc\x00\x00\x00" + b"\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x00\x00\x00" + b"\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xc6\x00\x00\x00" + b"\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\x18\xd8\x70\x00" + b"\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\x76\x00\x00\x00" + b"\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x3c\x00\x00\x00" + b"\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00" + b"\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x00\x00\x00" + b"\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\x00\x00\x00" + b"\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x30\x30\x00\x30\x30\x60\xc6\xc6\x7c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x00\x00\x00\x00" + b"\x00\xc0\xc0\xc6\xcc\xd8\x30\x60\xdc\x86\x0c\x18\x3e\x00" + b"\x00\xc0\xc0\xc6\xcc\xd8\x30\x66\xce\x9e\x3e\x06\x06\x00" + b"\x00\x00\x18\x18\x00\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00" + b"\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00" + b"\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44" + b"\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa" + b"\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77" + b"\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36" + b"\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36" + b"\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18" + b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff" + b"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0" + b"\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f" + b"\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xdc\x76\x00\x00\x00" + b"\x00\x00\x00\x00\x7c\xc6\xfc\xc6\xc6\xfc\xc0\xc0\x40\x00" + b"\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00" + b"\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00" + b"\x00\x00\xfe\xc6\x60\x30\x18\x30\x60\xc6\xfe\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\x70\x00\x00\x00" + b"\x00\x00\x00\x00\x66\x66\x66\x66\x7c\x60\x60\xc0\x00\x00" + b"\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x00\x00\x00" + b"\x00\x00\x7e\x18\x3c\x66\x66\x66\x3c\x18\x7e\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\x6c\x38\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\xee\x00\x00\x00" + b"\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x3c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\xdb\xdb\x7e\x00\x00\x00\x00\x00" + b"\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00" + b"\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x30\x1c\x00\x00\x00" + b"\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00" + b"\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00" + b"\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\xff\x00\x00\x00" + b"\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00" + b"\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00" + b"\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\x70\x00\x00\x00" + b"\x00\x00\x00\x18\x18\x00\x7e\x00\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00" + b"\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00" + b"\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x3c\x1c\x00\x00\x00" + b"\x00\xd8\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x70\xd8\x30\x60\xc8\xf8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) +FONT = memoryview(_FONT) diff --git a/micropython/pydisplay/graphics/graphics/font_8x16.py b/micropython/pydisplay/graphics/graphics/font_8x16.py new file mode 100644 index 000000000..6ad3c7522 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/font_8x16.py @@ -0,0 +1,259 @@ +_FONT = ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x7e\x81\xa5\x81\x81\xbd\x99\x81\x81\x7e\x00\x00\x00\x00" + b"\x00\x00\x7e\xff\xdb\xff\xff\xc3\xe7\xff\xff\x7e\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x6c\xfe\xfe\xfe\xfe\x7c\x38\x10\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x38\x7c\xfe\x7c\x38\x10\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x18\x3c\x3c\xe7\xe7\xe7\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\x00\x18\x3c\x7e\xff\xff\x7e\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x18\x3c\x3c\x18\x00\x00\x00\x00\x00\x00" + b"\xff\xff\xff\xff\xff\xff\xe7\xc3\xc3\xe7\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x00\x3c\x66\x42\x42\x66\x3c\x00\x00\x00\x00\x00" + b"\xff\xff\xff\xff\xff\xc3\x99\xbd\xbd\x99\xc3\xff\xff\xff\xff\xff" + b"\x00\x00\x1e\x0e\x1a\x32\x78\xcc\xcc\xcc\xcc\x78\x00\x00\x00\x00" + b"\x00\x00\x3c\x66\x66\x66\x66\x3c\x18\x7e\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x3f\x33\x3f\x30\x30\x30\x30\x70\xf0\xe0\x00\x00\x00\x00" + b"\x00\x00\x7f\x63\x7f\x63\x63\x63\x63\x67\xe7\xe6\xc0\x00\x00\x00" + b"\x00\x00\x00\x18\x18\xdb\x3c\xe7\x3c\xdb\x18\x18\x00\x00\x00\x00" + b"\x00\x80\xc0\xe0\xf0\xf8\xfe\xf8\xf0\xe0\xc0\x80\x00\x00\x00\x00" + b"\x00\x02\x06\x0e\x1e\x3e\xfe\x3e\x1e\x0e\x06\x02\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00\x00" + b"\x00\x00\x66\x66\x66\x66\x66\x66\x66\x00\x66\x66\x00\x00\x00\x00" + b"\x00\x00\x7f\xdb\xdb\xdb\x7b\x1b\x1b\x1b\x1b\x1b\x00\x00\x00\x00" + b"\x00\x7c\xc6\x60\x38\x6c\xc6\xc6\x6c\x38\x0c\xc6\x7c\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\xfe\xfe\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x7e\x3c\x18\x7e\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x7e\x18\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x18\x18\x18\x18\x18\x18\x18\x7e\x3c\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x0c\xfe\x0c\x18\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x30\x60\xfe\x60\x30\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xc0\xc0\xc0\xfe\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x28\x6c\xfe\x6c\x28\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x38\x38\x7c\x7c\xfe\xfe\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xfe\xfe\x7c\x7c\x38\x38\x10\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x18\x3c\x3c\x3c\x18\x18\x18\x00\x18\x18\x00\x00\x00\x00" + b"\x00\x66\x66\x66\x24\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x6c\x6c\xfe\x6c\x6c\x6c\xfe\x6c\x6c\x00\x00\x00\x00" + b"\x18\x18\x7c\xc6\xc2\xc0\x7c\x06\x06\x86\xc6\x7c\x18\x18\x00\x00" + b"\x00\x00\x00\x00\xc2\xc6\x0c\x18\x30\x60\xc6\x86\x00\x00\x00\x00" + b"\x00\x00\x38\x6c\x6c\x38\x76\xdc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x30\x30\x30\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x0c\x18\x30\x30\x30\x30\x30\x30\x18\x0c\x00\x00\x00\x00" + b"\x00\x00\x30\x18\x0c\x0c\x0c\x0c\x0c\x0c\x18\x30\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x66\x3c\xff\x3c\x66\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x30\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x02\x06\x0c\x18\x30\x60\xc0\x80\x00\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xd6\xd6\xc6\xc6\x6c\x38\x00\x00\x00\x00" + b"\x00\x00\x18\x38\x78\x18\x18\x18\x18\x18\x18\x7e\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\x06\x0c\x18\x30\x60\xc0\xc6\xfe\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\x06\x06\x3c\x06\x06\x06\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x0c\x1c\x3c\x6c\xcc\xfe\x0c\x0c\x0c\x1e\x00\x00\x00\x00" + b"\x00\x00\xfe\xc0\xc0\xc0\xfc\x06\x06\x06\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x38\x60\xc0\xc0\xfc\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\xfe\xc6\x06\x06\x0c\x18\x30\x30\x30\x30\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\x7c\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\x7e\x06\x06\x06\x0c\x78\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x18\x18\x00\x00\x00\x18\x18\x30\x00\x00\x00\x00" + b"\x00\x00\x00\x06\x0c\x18\x30\x60\x30\x18\x0c\x06\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\x00\x00\x7e\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x60\x30\x18\x0c\x06\x0c\x18\x30\x60\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\x0c\x18\x18\x18\x00\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x7c\xc6\xc6\xde\xde\xde\xdc\xc0\x7c\x00\x00\x00\x00" + b"\x00\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x66\x66\x66\x66\xfc\x00\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc0\xc2\x66\x3c\x00\x00\x00\x00" + b"\x00\x00\xf8\x6c\x66\x66\x66\x66\x66\x66\x6c\xf8\x00\x00\x00\x00" + b"\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x62\x66\xfe\x00\x00\x00\x00" + b"\x00\x00\xfe\x66\x62\x68\x78\x68\x60\x60\x60\xf0\x00\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xde\xc6\xc6\x66\x3a\x00\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xfe\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\x3c\x18\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\x1e\x0c\x0c\x0c\x0c\x0c\xcc\xcc\xcc\x78\x00\x00\x00\x00" + b"\x00\x00\xe6\x66\x66\x6c\x78\x78\x6c\x66\x66\xe6\x00\x00\x00\x00" + b"\x00\x00\xf0\x60\x60\x60\x60\x60\x60\x62\x66\xfe\x00\x00\x00\x00" + b"\x00\x00\xc6\xee\xfe\xfe\xd6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x60\x60\x60\x60\xf0\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xd6\xde\x7c\x0c\x0e\x00\x00" + b"\x00\x00\xfc\x66\x66\x66\x7c\x6c\x66\x66\x66\xe6\x00\x00\x00\x00" + b"\x00\x00\x7c\xc6\xc6\x60\x38\x0c\x06\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x7e\x7e\x5a\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x6c\x38\x10\x00\x00\x00\x00" + b"\x00\x00\xc6\xc6\xc6\xc6\xd6\xd6\xd6\xfe\xee\x6c\x00\x00\x00\x00" + b"\x00\x00\xc6\xc6\x6c\x7c\x38\x38\x7c\x6c\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\x66\x66\x66\x66\x3c\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\xfe\xc6\x86\x0c\x18\x30\x60\xc2\xc6\xfe\x00\x00\x00\x00" + b"\x00\x00\x3c\x30\x30\x30\x30\x30\x30\x30\x30\x3c\x00\x00\x00\x00" + b"\x00\x00\x00\x80\xc0\xe0\x70\x38\x1c\x0e\x06\x02\x00\x00\x00\x00" + b"\x00\x00\x3c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x3c\x00\x00\x00\x00" + b"\x10\x38\x6c\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00" + b"\x00\x30\x18\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\xe0\x60\x60\x78\x6c\x66\x66\x66\x66\x7c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x1c\x0c\x0c\x3c\x6c\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xfe\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x1c\x36\x32\x30\x78\x30\x30\x30\x30\x78\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\xcc\xcc\x7c\x0c\xcc\x78\x00" + b"\x00\x00\xe0\x60\x60\x6c\x76\x66\x66\x66\x66\xe6\x00\x00\x00\x00" + b"\x00\x00\x18\x18\x00\x38\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\x06\x06\x00\x0e\x06\x06\x06\x06\x06\x06\x66\x66\x3c\x00" + b"\x00\x00\xe0\x60\x60\x66\x6c\x78\x78\x6c\x66\xe6\x00\x00\x00\x00" + b"\x00\x00\x38\x18\x18\x18\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xec\xfe\xd6\xd6\xd6\xd6\xc6\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x66\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xdc\x66\x66\x66\x66\x66\x7c\x60\x60\xf0\x00" + b"\x00\x00\x00\x00\x00\x76\xcc\xcc\xcc\xcc\xcc\x7c\x0c\x0c\x1e\x00" + b"\x00\x00\x00\x00\x00\xdc\x76\x66\x60\x60\x60\xf0\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\x60\x38\x0c\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x10\x30\x30\xfc\x30\x30\x30\x30\x36\x1c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xd6\xfe\x6c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\x6c\x38\x38\x38\x6c\xc6\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7e\x06\x0c\xf8\x00" + b"\x00\x00\x00\x00\x00\xfe\xcc\x18\x30\x60\xc6\xfe\x00\x00\x00\x00" + b"\x00\x00\x0e\x18\x18\x18\x70\x18\x18\x18\x18\x0e\x00\x00\x00\x00" + b"\x00\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x70\x18\x18\x18\x0e\x18\x18\x18\x18\x70\x00\x00\x00\x00" + b"\x00\x76\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x10\x38\x6c\xc6\xc6\xc6\xfe\x00\x00\x00\x00\x00" + b"\x00\x00\x3c\x66\xc2\xc0\xc0\xc0\xc0\xc2\x66\x3c\x18\x70\x00\x00" + b"\x00\x00\xcc\x00\x00\xcc\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x0c\x18\x30\x00\x7c\xc6\xfe\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x10\x38\x6c\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\xcc\x00\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x38\x6c\x38\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\xc0\xc6\x7c\x18\x70\x00\x00" + b"\x00\x10\x38\x6c\x00\x7c\xc6\xfe\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\xc6\x00\x00\x7c\xc6\xfe\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x7c\xc6\xfe\xc0\xc0\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x66\x00\x00\x38\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x18\x3c\x66\x00\x38\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x38\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\xc6\x00\x10\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x38\x6c\x38\x10\x38\x6c\xc6\xfe\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x0c\x18\x00\xfe\x66\x62\x68\x78\x68\x62\x66\xfe\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xec\x36\x36\x7e\xd8\xd8\x6e\x00\x00\x00\x00" + b"\x00\x00\x3e\x6c\xcc\xcc\xfe\xcc\xcc\xcc\xcc\xce\x00\x00\x00\x00" + b"\x00\x10\x38\x6c\x00\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\xc6\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x60\x30\x18\x00\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x30\x78\xcc\x00\xcc\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x60\x30\x18\x00\xcc\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\xc6\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6\x7e\x06\x0c\x78\x00" + b"\x00\xc6\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\xc6\x00\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x18\x18\x7c\xc6\xc0\xc0\xc0\xc6\x7c\x18\x18\x00\x00\x00\x00" + b"\x00\x38\x6c\x64\x60\xf0\x60\x60\x60\x60\xe6\xfc\x00\x00\x00\x00" + b"\x00\x00\x66\x66\x3c\x18\x7e\x18\x7e\x18\x18\x18\x00\x00\x00\x00" + b"\x00\xf8\xcc\xcc\xf8\xc4\xcc\xde\xcc\xcc\xcc\xc6\x00\x00\x00\x00" + b"\x00\x0e\x1b\x18\x18\x18\x7e\x18\x18\x18\xd8\x70\x00\x00\x00\x00" + b"\x00\x18\x30\x60\x00\x78\x0c\x7c\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x0c\x18\x30\x00\x38\x18\x18\x18\x18\x18\x3c\x00\x00\x00\x00" + b"\x00\x18\x30\x60\x00\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x18\x30\x60\x00\xcc\xcc\xcc\xcc\xcc\xcc\x76\x00\x00\x00\x00" + b"\x00\x00\x76\xdc\x00\xdc\x66\x66\x66\x66\x66\x66\x00\x00\x00\x00" + b"\x76\xdc\x00\xc6\xe6\xf6\xfe\xde\xce\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\x3c\x6c\x6c\x3e\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x38\x6c\x6c\x38\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x30\x30\x00\x30\x30\x60\xc0\xc6\xc6\x7c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xfe\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\xfe\x06\x06\x06\x06\x00\x00\x00\x00\x00" + b"\x00\x60\xe0\x62\x66\x6c\x18\x30\x60\xdc\x86\x0c\x18\x3e\x00\x00" + b"\x00\x60\xe0\x62\x66\x6c\x18\x30\x66\xce\x9a\x3f\x06\x06\x00\x00" + b"\x00\x00\x18\x18\x00\x18\x18\x18\x3c\x3c\x3c\x18\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x36\x6c\xd8\x6c\x36\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xd8\x6c\x36\x6c\xd8\x00\x00\x00\x00\x00\x00" + b"\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11\x44" + b"\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa" + b"\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77\xdd\x77" + b"\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\xf8\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\x36\x36\xf6\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\x00\x00\xfe\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xf8\x18\xf8\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\xf6\x06\xf6\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xfe\x06\xf6\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\xf6\x06\xfe\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\x36\x36\xfe\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\xf8\x18\xf8\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xf8\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x1f\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x18\x18\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x1f\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x18\x18\xff\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x36\x36\x36\x36\x36\x36\x36\x37\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x37\x30\x3f\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x3f\x30\x37\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\xf7\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xff\x00\xf7\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x37\x30\x37\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\x00\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\xf7\x00\xf7\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x18\x18\x18\x18\x18\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x36\x36\x36\x36\x36\x36\x36\xff\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xff\x00\xff\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\xff\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\x3f\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x18\x18\x18\x18\x18\x1f\x18\x1f\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x1f\x18\x1f\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x00\x00\x00\x00\x00\x00\x00\x3f\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\xff\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x18\x18\x18\x18\x18\xff\x18\xff\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\xf8\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x1f\x18\x18\x18\x18\x18\x18\x18\x18" + b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff" + b"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0" + b"\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f" + b"\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x76\xdc\xd8\xd8\xd8\xdc\x76\x00\x00\x00\x00" + b"\x00\x00\x78\xcc\xcc\xcc\xd8\xcc\xc6\xc6\xc6\xcc\x00\x00\x00\x00" + b"\x00\x00\xfe\xc6\xc6\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xfe\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x00\x00" + b"\x00\x00\xfe\xc6\x60\x30\x18\x18\x30\x60\xc6\xfe\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\xd8\xd8\xd8\xd8\xd8\x70\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x66\x66\x66\x66\x66\x66\x7c\x60\x60\xc0\x00" + b"\x00\x00\x00\x00\x76\xdc\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00" + b"\x00\x00\x7e\x18\x3c\x66\x66\x66\x66\x3c\x18\x7e\x00\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xfe\xc6\xc6\xc6\x6c\x38\x00\x00\x00\x00" + b"\x00\x00\x38\x6c\xc6\xc6\xc6\x6c\x6c\x6c\x6c\xee\x00\x00\x00\x00" + b"\x00\x00\x1e\x30\x18\x0c\x3e\x66\x66\x66\x66\x3c\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x7e\xdb\xdb\xdb\x7e\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x03\x06\x7e\xdb\xdb\xf3\x7e\x60\xc0\x00\x00\x00\x00" + b"\x00\x00\x1c\x30\x60\x60\x7c\x60\x60\x60\x30\x1c\x00\x00\x00\x00" + b"\x00\x00\x00\x7c\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\xfe\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x18\x18\x7e\x18\x18\x00\x00\x7e\x00\x00\x00\x00" + b"\x00\x00\x00\x30\x18\x0c\x06\x0c\x18\x30\x00\x7e\x00\x00\x00\x00" + b"\x00\x00\x00\x0c\x18\x30\x60\x30\x18\x0c\x00\x7e\x00\x00\x00\x00" + b"\x00\x00\x0e\x1b\x1b\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\x18\x18\x18\x18\xd8\xd8\xd8\x70\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x00\x7e\x00\x18\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x76\xdc\x00\x76\xdc\x00\x00\x00\x00\x00\x00" + b"\x00\x38\x6c\x6c\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x18\x18\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x0f\x0c\x0c\x0c\x0c\x0c\xec\x6c\x6c\x3c\x1c\x00\x00\x00\x00" + b"\x00\x6c\x36\x36\x36\x36\x36\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x3c\x66\x0c\x18\x32\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) +FONT = memoryview(_FONT) diff --git a/micropython/pydisplay/graphics/graphics/font_8x8.py b/micropython/pydisplay/graphics/graphics/font_8x8.py new file mode 100644 index 000000000..8b3bef231 --- /dev/null +++ b/micropython/pydisplay/graphics/graphics/font_8x8.py @@ -0,0 +1,259 @@ +_FONT = ( + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x7e\x81\xa5\x81\xbd\x99\x81\x7e" + b"\x7e\xff\xdb\xff\xc3\xe7\xff\x7e" + b"\x6c\xfe\xfe\xfe\x7c\x38\x10\x00" + b"\x10\x38\x7c\xfe\x7c\x38\x10\x00" + b"\x38\x7c\x38\xfe\xfe\xd6\x10\x38" + b"\x10\x38\x7c\xfe\xfe\x7c\x10\x38" + b"\x00\x00\x18\x3c\x3c\x18\x00\x00" + b"\xff\xff\xe7\xc3\xc3\xe7\xff\xff" + b"\x00\x3c\x66\x42\x42\x66\x3c\x00" + b"\xff\xc3\x99\xbd\xbd\x99\xc3\xff" + b"\x0f\x07\x0f\x7d\xcc\xcc\xcc\x78" + b"\x3c\x66\x66\x66\x3c\x18\x7e\x18" + b"\x3f\x33\x3f\x30\x30\x70\xf0\xe0" + b"\x7f\x63\x7f\x63\x63\x67\xe6\xc0" + b"\x18\xdb\x3c\xe7\xe7\x3c\xdb\x18" + b"\x80\xe0\xf8\xfe\xf8\xe0\x80\x00" + b"\x02\x0e\x3e\xfe\x3e\x0e\x02\x00" + b"\x18\x3c\x7e\x18\x18\x7e\x3c\x18" + b"\x66\x66\x66\x66\x66\x00\x66\x00" + b"\x7f\xdb\xdb\x7b\x1b\x1b\x1b\x00" + b"\x3e\x61\x3c\x66\x66\x3c\x86\x7c" + b"\x00\x00\x00\x00\x7e\x7e\x7e\x00" + b"\x18\x3c\x7e\x18\x7e\x3c\x18\xff" + b"\x18\x3c\x7e\x18\x18\x18\x18\x00" + b"\x18\x18\x18\x18\x7e\x3c\x18\x00" + b"\x00\x18\x0c\xfe\x0c\x18\x00\x00" + b"\x00\x30\x60\xfe\x60\x30\x00\x00" + b"\x00\x00\xc0\xc0\xc0\xfe\x00\x00" + b"\x00\x24\x66\xff\x66\x24\x00\x00" + b"\x00\x18\x3c\x7e\xff\xff\x00\x00" + b"\x00\xff\xff\x7e\x3c\x18\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x18\x3c\x3c\x18\x18\x00\x18\x00" + b"\x66\x66\x24\x00\x00\x00\x00\x00" + b"\x6c\x6c\xfe\x6c\xfe\x6c\x6c\x00" + b"\x18\x3e\x60\x3c\x06\x7c\x18\x00" + b"\x00\xc6\xcc\x18\x30\x66\xc6\x00" + b"\x38\x6c\x38\x76\xdc\xcc\x76\x00" + b"\x18\x18\x30\x00\x00\x00\x00\x00" + b"\x0c\x18\x30\x30\x30\x18\x0c\x00" + b"\x30\x18\x0c\x0c\x0c\x18\x30\x00" + b"\x00\x66\x3c\xff\x3c\x66\x00\x00" + b"\x00\x18\x18\x7e\x18\x18\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x18\x30" + b"\x00\x00\x00\x7e\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x18\x18\x00" + b"\x06\x0c\x18\x30\x60\xc0\x80\x00" + b"\x38\x6c\xc6\xd6\xc6\x6c\x38\x00" + b"\x18\x38\x18\x18\x18\x18\x7e\x00" + b"\x7c\xc6\x06\x1c\x30\x66\xfe\x00" + b"\x7c\xc6\x06\x3c\x06\xc6\x7c\x00" + b"\x1c\x3c\x6c\xcc\xfe\x0c\x1e\x00" + b"\xfe\xc0\xc0\xfc\x06\xc6\x7c\x00" + b"\x38\x60\xc0\xfc\xc6\xc6\x7c\x00" + b"\xfe\xc6\x0c\x18\x30\x30\x30\x00" + b"\x7c\xc6\xc6\x7c\xc6\xc6\x7c\x00" + b"\x7c\xc6\xc6\x7e\x06\x0c\x78\x00" + b"\x00\x18\x18\x00\x00\x18\x18\x00" + b"\x00\x18\x18\x00\x00\x18\x18\x30" + b"\x06\x0c\x18\x30\x18\x0c\x06\x00" + b"\x00\x00\x7e\x00\x00\x7e\x00\x00" + b"\x60\x30\x18\x0c\x18\x30\x60\x00" + b"\x7c\xc6\x0c\x18\x18\x00\x18\x00" + b"\x7c\xc6\xde\xde\xde\xc0\x78\x00" + b"\x38\x6c\xc6\xfe\xc6\xc6\xc6\x00" + b"\xfc\x66\x66\x7c\x66\x66\xfc\x00" + b"\x3c\x66\xc0\xc0\xc0\x66\x3c\x00" + b"\xf8\x6c\x66\x66\x66\x6c\xf8\x00" + b"\xfe\x62\x68\x78\x68\x62\xfe\x00" + b"\xfe\x62\x68\x78\x68\x60\xf0\x00" + b"\x3c\x66\xc0\xc0\xce\x66\x3a\x00" + b"\xc6\xc6\xc6\xfe\xc6\xc6\xc6\x00" + b"\x3c\x18\x18\x18\x18\x18\x3c\x00" + b"\x1e\x0c\x0c\x0c\xcc\xcc\x78\x00" + b"\xe6\x66\x6c\x78\x6c\x66\xe6\x00" + b"\xf0\x60\x60\x60\x62\x66\xfe\x00" + b"\xc6\xee\xfe\xfe\xd6\xc6\xc6\x00" + b"\xc6\xe6\xf6\xde\xce\xc6\xc6\x00" + b"\x7c\xc6\xc6\xc6\xc6\xc6\x7c\x00" + b"\xfc\x66\x66\x7c\x60\x60\xf0\x00" + b"\x7c\xc6\xc6\xc6\xc6\xce\x7c\x0e" + b"\xfc\x66\x66\x7c\x6c\x66\xe6\x00" + b"\x3c\x66\x30\x18\x0c\x66\x3c\x00" + b"\x7e\x7e\x5a\x18\x18\x18\x3c\x00" + b"\xc6\xc6\xc6\xc6\xc6\xc6\x7c\x00" + b"\xc6\xc6\xc6\xc6\xc6\x6c\x38\x00" + b"\xc6\xc6\xc6\xd6\xd6\xfe\x6c\x00" + b"\xc6\xc6\x6c\x38\x6c\xc6\xc6\x00" + b"\x66\x66\x66\x3c\x18\x18\x3c\x00" + b"\xfe\xc6\x8c\x18\x32\x66\xfe\x00" + b"\x3c\x30\x30\x30\x30\x30\x3c\x00" + b"\xc0\x60\x30\x18\x0c\x06\x02\x00" + b"\x3c\x0c\x0c\x0c\x0c\x0c\x3c\x00" + b"\x10\x38\x6c\xc6\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\xff" + b"\x30\x18\x0c\x00\x00\x00\x00\x00" + b"\x00\x00\x78\x0c\x7c\xcc\x76\x00" + b"\xe0\x60\x7c\x66\x66\x66\xdc\x00" + b"\x00\x00\x7c\xc6\xc0\xc6\x7c\x00" + b"\x1c\x0c\x7c\xcc\xcc\xcc\x76\x00" + b"\x00\x00\x7c\xc6\xfe\xc0\x7c\x00" + b"\x3c\x66\x60\xf8\x60\x60\xf0\x00" + b"\x00\x00\x76\xcc\xcc\x7c\x0c\xf8" + b"\xe0\x60\x6c\x76\x66\x66\xe6\x00" + b"\x18\x00\x38\x18\x18\x18\x3c\x00" + b"\x06\x00\x06\x06\x06\x66\x66\x3c" + b"\xe0\x60\x66\x6c\x78\x6c\xe6\x00" + b"\x38\x18\x18\x18\x18\x18\x3c\x00" + b"\x00\x00\xec\xfe\xd6\xd6\xd6\x00" + b"\x00\x00\xdc\x66\x66\x66\x66\x00" + b"\x00\x00\x7c\xc6\xc6\xc6\x7c\x00" + b"\x00\x00\xdc\x66\x66\x7c\x60\xf0" + b"\x00\x00\x76\xcc\xcc\x7c\x0c\x1e" + b"\x00\x00\xdc\x76\x60\x60\xf0\x00" + b"\x00\x00\x7e\xc0\x7c\x06\xfc\x00" + b"\x30\x30\xfc\x30\x30\x36\x1c\x00" + b"\x00\x00\xcc\xcc\xcc\xcc\x76\x00" + b"\x00\x00\xc6\xc6\xc6\x6c\x38\x00" + b"\x00\x00\xc6\xd6\xd6\xfe\x6c\x00" + b"\x00\x00\xc6\x6c\x38\x6c\xc6\x00" + b"\x00\x00\xc6\xc6\xc6\x7e\x06\xfc" + b"\x00\x00\x7e\x4c\x18\x32\x7e\x00" + b"\x0e\x18\x18\x70\x18\x18\x0e\x00" + b"\x18\x18\x18\x18\x18\x18\x18\x00" + b"\x70\x18\x18\x0e\x18\x18\x70\x00" + b"\x76\xdc\x00\x00\x00\x00\x00\x00" + b"\x00\x10\x38\x6c\xc6\xc6\xfe\x00" + b"\x7c\xc6\xc0\xc0\xc6\x7c\x0c\x78" + b"\xcc\x00\xcc\xcc\xcc\xcc\x76\x00" + b"\x0c\x18\x7c\xc6\xfe\xc0\x7c\x00" + b"\x7c\x82\x78\x0c\x7c\xcc\x76\x00" + b"\xc6\x00\x78\x0c\x7c\xcc\x76\x00" + b"\x30\x18\x78\x0c\x7c\xcc\x76\x00" + b"\x30\x30\x78\x0c\x7c\xcc\x76\x00" + b"\x00\x00\x7e\xc0\xc0\x7e\x0c\x38" + b"\x7c\x82\x7c\xc6\xfe\xc0\x7c\x00" + b"\xc6\x00\x7c\xc6\xfe\xc0\x7c\x00" + b"\x30\x18\x7c\xc6\xfe\xc0\x7c\x00" + b"\x66\x00\x38\x18\x18\x18\x3c\x00" + b"\x7c\x82\x38\x18\x18\x18\x3c\x00" + b"\x30\x18\x00\x38\x18\x18\x3c\x00" + b"\xc6\x38\x6c\xc6\xfe\xc6\xc6\x00" + b"\x38\x6c\x7c\xc6\xfe\xc6\xc6\x00" + b"\x18\x30\xfe\xc0\xf8\xc0\xfe\x00" + b"\x00\x00\x7e\x18\x7e\xd8\x7e\x00" + b"\x3e\x6c\xcc\xfe\xcc\xcc\xce\x00" + b"\x7c\x82\x7c\xc6\xc6\xc6\x7c\x00" + b"\xc6\x00\x7c\xc6\xc6\xc6\x7c\x00" + b"\x30\x18\x7c\xc6\xc6\xc6\x7c\x00" + b"\x78\x84\x00\xcc\xcc\xcc\x76\x00" + b"\x60\x30\xcc\xcc\xcc\xcc\x76\x00" + b"\xc6\x00\xc6\xc6\xc6\x7e\x06\xfc" + b"\xc6\x38\x6c\xc6\xc6\x6c\x38\x00" + b"\xc6\x00\xc6\xc6\xc6\xc6\x7c\x00" + b"\x18\x18\x7e\xc0\xc0\x7e\x18\x18" + b"\x38\x6c\x64\xf0\x60\x66\xfc\x00" + b"\x66\x66\x3c\x7e\x18\x7e\x18\x18" + b"\xf8\xcc\xcc\xfa\xc6\xcf\xc6\xc7" + b"\x0e\x1b\x18\x3c\x18\xd8\x70\x00" + b"\x18\x30\x78\x0c\x7c\xcc\x76\x00" + b"\x0c\x18\x00\x38\x18\x18\x3c\x00" + b"\x0c\x18\x7c\xc6\xc6\xc6\x7c\x00" + b"\x18\x30\xcc\xcc\xcc\xcc\x76\x00" + b"\x76\xdc\x00\xdc\x66\x66\x66\x00" + b"\x76\xdc\x00\xe6\xf6\xde\xce\x00" + b"\x3c\x6c\x6c\x3e\x00\x7e\x00\x00" + b"\x38\x6c\x6c\x38\x00\x7c\x00\x00" + b"\x18\x00\x18\x18\x30\x63\x3e\x00" + b"\x00\x00\x00\xfe\xc0\xc0\x00\x00" + b"\x00\x00\x00\xfe\x06\x06\x00\x00" + b"\x63\xe6\x6c\x7e\x33\x66\xcc\x0f" + b"\x63\xe6\x6c\x7a\x36\x6a\xdf\x06" + b"\x18\x00\x18\x18\x3c\x3c\x18\x00" + b"\x00\x33\x66\xcc\x66\x33\x00\x00" + b"\x00\xcc\x66\x33\x66\xcc\x00\x00" + b"\x22\x88\x22\x88\x22\x88\x22\x88" + b"\x55\xaa\x55\xaa\x55\xaa\x55\xaa" + b"\x77\xdd\x77\xdd\x77\xdd\x77\xdd" + b"\x18\x18\x18\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\xf8\x18\x18\x18" + b"\x18\x18\xf8\x18\xf8\x18\x18\x18" + b"\x36\x36\x36\x36\xf6\x36\x36\x36" + b"\x00\x00\x00\x00\xfe\x36\x36\x36" + b"\x00\x00\xf8\x18\xf8\x18\x18\x18" + b"\x36\x36\xf6\x06\xf6\x36\x36\x36" + b"\x36\x36\x36\x36\x36\x36\x36\x36" + b"\x00\x00\xfe\x06\xf6\x36\x36\x36" + b"\x36\x36\xf6\x06\xfe\x00\x00\x00" + b"\x36\x36\x36\x36\xfe\x00\x00\x00" + b"\x18\x18\xf8\x18\xf8\x00\x00\x00" + b"\x00\x00\x00\x00\xf8\x18\x18\x18" + b"\x18\x18\x18\x18\x1f\x00\x00\x00" + b"\x18\x18\x18\x18\xff\x00\x00\x00" + b"\x00\x00\x00\x00\xff\x18\x18\x18" + b"\x18\x18\x18\x18\x1f\x18\x18\x18" + b"\x00\x00\x00\x00\xff\x00\x00\x00" + b"\x18\x18\x18\x18\xff\x18\x18\x18" + b"\x18\x18\x1f\x18\x1f\x18\x18\x18" + b"\x36\x36\x36\x36\x37\x36\x36\x36" + b"\x36\x36\x37\x30\x3f\x00\x00\x00" + b"\x00\x00\x3f\x30\x37\x36\x36\x36" + b"\x36\x36\xf7\x00\xff\x00\x00\x00" + b"\x00\x00\xff\x00\xf7\x36\x36\x36" + b"\x36\x36\x37\x30\x37\x36\x36\x36" + b"\x00\x00\xff\x00\xff\x00\x00\x00" + b"\x36\x36\xf7\x00\xf7\x36\x36\x36" + b"\x18\x18\xff\x00\xff\x00\x00\x00" + b"\x36\x36\x36\x36\xff\x00\x00\x00" + b"\x00\x00\xff\x00\xff\x18\x18\x18" + b"\x00\x00\x00\x00\xff\x36\x36\x36" + b"\x36\x36\x36\x36\x3f\x00\x00\x00" + b"\x18\x18\x1f\x18\x1f\x00\x00\x00" + b"\x00\x00\x1f\x18\x1f\x18\x18\x18" + b"\x00\x00\x00\x00\x3f\x36\x36\x36" + b"\x36\x36\x36\x36\xff\x36\x36\x36" + b"\x18\x18\xff\x18\xff\x18\x18\x18" + b"\x18\x18\x18\x18\xf8\x00\x00\x00" + b"\x00\x00\x00\x00\x1f\x18\x18\x18" + b"\xff\xff\xff\xff\xff\xff\xff\xff" + b"\x00\x00\x00\x00\xff\xff\xff\xff" + b"\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0" + b"\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f" + b"\xff\xff\xff\xff\x00\x00\x00\x00" + b"\x00\x00\x76\xdc\xc8\xdc\x76\x00" + b"\x78\xcc\xcc\xd8\xcc\xc6\xcc\x00" + b"\xfe\xc6\xc0\xc0\xc0\xc0\xc0\x00" + b"\x00\x00\xfe\x6c\x6c\x6c\x6c\x00" + b"\xfe\xc6\x60\x30\x60\xc6\xfe\x00" + b"\x00\x00\x7e\xd8\xd8\xd8\x70\x00" + b"\x00\x00\x66\x66\x66\x66\x7c\xc0" + b"\x00\x76\xdc\x18\x18\x18\x18\x00" + b"\x7e\x18\x3c\x66\x66\x3c\x18\x7e" + b"\x38\x6c\xc6\xfe\xc6\x6c\x38\x00" + b"\x38\x6c\xc6\xc6\x6c\x6c\xee\x00" + b"\x0e\x18\x0c\x3e\x66\x66\x3c\x00" + b"\x00\x00\x7e\xdb\xdb\x7e\x00\x00" + b"\x06\x0c\x7e\xdb\xdb\x7e\x60\xc0" + b"\x1e\x30\x60\x7e\x60\x30\x1e\x00" + b"\x00\x7c\xc6\xc6\xc6\xc6\xc6\x00" + b"\x00\xfe\x00\xfe\x00\xfe\x00\x00" + b"\x18\x18\x7e\x18\x18\x00\x7e\x00" + b"\x30\x18\x0c\x18\x30\x00\x7e\x00" + b"\x0c\x18\x30\x18\x0c\x00\x7e\x00" + b"\x0e\x1b\x1b\x18\x18\x18\x18\x18" + b"\x18\x18\x18\x18\x18\xd8\xd8\x70" + b"\x00\x18\x00\x7e\x00\x18\x00\x00" + b"\x00\x76\xdc\x00\x76\xdc\x00\x00" + b"\x38\x6c\x6c\x38\x00\x00\x00\x00" + b"\x00\x00\x00\x18\x18\x00\x00\x00" + b"\x00\x00\x00\x18\x00\x00\x00\x00" + b"\x0f\x0c\x0c\x0c\xec\x6c\x3c\x1c" + b"\x6c\x36\x36\x36\x36\x00\x00\x00" + b"\x78\x0c\x18\x30\x7c\x00\x00\x00" + b"\x00\x00\x3c\x3c\x3c\x3c\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" +) +FONT = memoryview(_FONT) diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/i80bus/manifest.py b/micropython/pydisplay/i80bus/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/palettes/examples/palette_cube.py b/micropython/pydisplay/palettes/examples/palette_cube.py new file mode 100644 index 000000000..541c835c2 --- /dev/null +++ b/micropython/pydisplay/palettes/examples/palette_cube.py @@ -0,0 +1,47 @@ +from board_config import display_drv +from palettes import get_palette +from graphics import FrameBuffer, RGB565 +from time import sleep + +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + +display_drv.rotation = 0 + +palette = get_palette(name="cube", size=5, color_depth=16, swapped=needs_swap) +# palette = get_palette(name="wheel", length=522, color_depth=16, swapped=needs_swap) + +line_height = 20 +last_line = display_drv.height - line_height +scroll = 0 +y = 0 + +BPP = display_drv.color_depth // 8 # Bytes per pixel +ba = bytearray(display_drv.width * line_height * BPP) +fb = FrameBuffer(ba, display_drv.width, line_height, RGB565) + +def main(): + global y, scroll + for index, color in enumerate(palette): + if y - scroll - last_line > 0: + scroll = (y - last_line) % display_drv.height + display_drv.vscsad(scroll) + name = f"{index} - {palette.color_name(index)}" + text_color = palette.WHITE if palette.brightness(index) < 0.4 else palette.BLACK + fb.fill(color) + fb.text16(name, 2, 2, text_color) + display_drv.blit_rect(ba, 0, y % display_drv.height, display_drv.width, line_height) + y += line_height + sleep(.1) + + +def loop(): + while True: + main() + + +main() diff --git a/micropython/pydisplay/palettes/examples/palette_material.py b/micropython/pydisplay/palettes/examples/palette_material.py new file mode 100644 index 000000000..26451d655 --- /dev/null +++ b/micropython/pydisplay/palettes/examples/palette_material.py @@ -0,0 +1,22 @@ +from board_config import display_drv +from palettes import get_palette +from graphics import text16 + +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + +display_drv.rotation = 0 + +palette = get_palette(name="material_design", color_depth=16, swapped=needs_swap) + +def main(): + line_height=1 + for i, color in enumerate(palette): + display_drv.fill_rect(0, i*line_height, display_drv.width, line_height, color) + +while True: + main() \ No newline at end of file diff --git a/micropython/pydisplay/palettes/examples/palette_wheel.py b/micropython/pydisplay/palettes/examples/palette_wheel.py new file mode 100644 index 000000000..39725715b --- /dev/null +++ b/micropython/pydisplay/palettes/examples/palette_wheel.py @@ -0,0 +1,31 @@ +from board_config import display_drv +from palettes import get_palette + +# If byte swapping is required and the display bus is capable of having byte swapping disabled, +# disable it and set a flag so we can swap the color bytes as they are created. +if display_drv.requires_byteswap: + needs_swap = display_drv.disable_auto_byteswap(True) +else: + needs_swap = False + +display_drv.rotation = 0 + +palette = get_palette(name="wheel", swapped=needs_swap, length=256, saturation=1.0) +# palette = get_palette(name="wheel", color_depth=16, length=256) + +line_height = 2 + +i = 0 +def main(): + global i + for color in palette: + if i >= display_drv.height: + display_drv.vscsad((line_height + i) % display_drv.height) + display_drv.fill_rect(0, i % display_drv.height, display_drv.width, line_height, color) + i += line_height + +def loop(): + while True: + main() + +loop() \ No newline at end of file diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/palettes/palettes/__init__.py b/micropython/pydisplay/palettes/palettes/__init__.py new file mode 100644 index 000000000..eae4cc8b9 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/__init__.py @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`palettes` +==================================================== +""" + +# The 16 colors of the standard Windows 16-color palette +WIN16 = { + 0x000000: "Black", + 0x000080: "Navy", + 0x0000FF: "Blue", + 0x008000: "Green", + 0x008080: "Teal", + 0x00FF00: "Lime", + 0x00FFFF: "Cyan", + 0x800000: "Maroon", + 0x800080: "Purple", + 0x808000: "Olive", + 0x808080: "Grey", + 0xC0C0C0: "Silver", + 0xFF0000: "Red", + 0xFF00FF: "Magenta", + 0xFFFF00: "Yellow", + 0xFFFFFF: "White", +} + + +def get_palette(name="default", **kwargs): + if name == "wheel": + from .wheel import WheelPalette as MyPalette + elif name == "material_design": + from .material_design import MDPalette as MyPalette + elif name == "cube": + from .cube import CubePalette as MyPalette + else: + MyPalette = Palette + return MyPalette(name, **kwargs) + + +class Palette: + """ + A class to represent a color palette. + """ + + def __init__(self, name="", color_depth=16, swapped=False, cached=False): + self._name = name + self._color_depth = color_depth + self._swapped = swapped + self._cache = dict() if cached else None + + if not hasattr(self, "_names"): + self._names = WIN16 + if not hasattr(self, "_length"): + self._length = len(self._names) + + self._define_named_colors() + + def _define_named_colors(self): + for color, name in self._names.items(): + if self._color_depth == 16: + color = self.color565(color) + elif self._color_depth == 8: + color = self.color332(color) + elif self._color_depth == 4: + color = list(self._names.keys()).index(color) + setattr(self, name.replace(" ", "_").upper(), color) + + @property + def name(self): + return self._name + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + def __len__(self): + return self._length + + def __getitem__(self, index): + index = self._normalize(index) + + if self._cache is not None: + if index in self._cache: + return self._cache[index] + + r, g, b = self._get_rgb(index) + if self._color_depth == 24 or self._color_depth == 4: + return r << 16 | g << 8 | b + elif self._color_depth == 16: + return self.color565(r, g, b) + elif self._color_depth == 8: + return self.color332(r, g, b) + raise ValueError("Invalid color depth") + + def _normalize(self, index): + while index < 0: + index += len(self) + if index >= len(self): + index %= len(self) + return index + + def color565(self, r, g=None, b=None): + if isinstance(r, (tuple, list)): + # r is a tuple or list + r, g, b = r + elif g is None: + # r is a 24-bit color + r, g, b = r >> 16 & 0xFF, r >> 8 & 0xFF, r & 0xFF + + color = (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3 + if self._swapped: + return (color & 0xFF) << 8 | (color & 0xFF00) >> 8 + else: + return color + + def color332(self, r, g=None, b=None): + # Convert r, g, b to 8-bit + if isinstance(r, (tuple, list)): + # r is a tuple or list + r, g, b = r + elif g is None: + # r is a 24-bit color + r, g, b = r >> 16 & 0xFF, r >> 8 & 0xFF, r & 0xFF + + color = (r & 0xE0) | (g & 0xE0) >> 3 | (b & 0xC0) >> 6 + return color + + def color_rgb(self, color): + """ + color can be an 16-bit integer or a tuple, list or bytearray of length 2 or 3. + """ + if isinstance(color, int): + # convert 16-bit int color to 2 bytes + color = (color & 0xFF, color >> 8) + if len(color) == 2: + r = color[1] & 0xF8 | (color[1] >> 5) & 0x7 # 5 bit to 8 bit red + g = color[1] << 5 & 0xE0 | (color[0] >> 3) & 0x1F # 6 bit to 8 bit green + b = color[0] << 3 & 0xF8 | (color[0] >> 2) & 0x7 # 5 bit to 8 bit blue + else: + r, g, b = color + return (r, g, b) + + def color_name(self, index): + return self.rgb_name(self._get_rgb(self._normalize(index))) + + def rgb_name(self, r, g=None, b=None): + if isinstance(r, (tuple, list)): + r, g, b = r + return self._names.get(r << 16 | g << 8 | b, f"#{r:02X}{g:02X}{b:02X}") + + def luminance(self, index): + r, g, b = self._get_rgb(index) + return 0.299 * r + 0.587 * g + 0.114 * b + + def brightness(self, index): + r, g, b = self._get_rgb(index) + return (r + g + b) / 3 / 255 + + def _get_rgb(self, index): + color = list(self._names.keys())[index] + return color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF + + +class MappedPalette(Palette): + """ + A class to represent a color palette with a color map. + """ + + def __init__(self, name, color_depth, swapped, color_map): + self._color_map = color_map + self._length = len(color_map) // 3 + super().__init__(name, color_depth, swapped) + + def _get_rgb(self, index): + r, g, b = self._color_map[index * 3 : index * 3 + 3] + return r, g, b diff --git a/micropython/pydisplay/palettes/palettes/_cube125.py b/micropython/pydisplay/palettes/palettes/_cube125.py new file mode 100644 index 000000000..02b595e62 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/_cube125.py @@ -0,0 +1,131 @@ +# Color names based on https://www.chilliant.com/colournames.html + +# A color cube with 5 levels of red, green, and blue (125 colors in total) +# (values 0x00, 0x40, 0x80, 0xC0, and 0xFF for each of the red, green, and blue components) +CUBE125 = { + 0x000000: "Black", + 0x000040: "Stratos", + 0x000080: "Navy", + 0x0000C0: "Zaffre", + 0x0000FF: "Blue", + 0x004000: "Vine", + 0x004040: "Cyprus", + 0x004080: "Congress", + 0x0040C0: "Cobalt", + 0x0040FF: "Majorelle", + 0x008000: "Green", + 0x008040: "Salem", + 0x008080: "Teal", + 0x0080C0: "Lochmara", + 0x0080FF: "Azure", + 0x00C000: "Leaf", + 0x00C040: "Apple", + 0x00C080: "Shamrock", + 0x00C0C0: "Java", + 0x00C0FF: "Picton", + 0x00FF00: "Lime", + 0x00FF40: "Erin", + 0x00FF80: "Spring", + 0x00FFC0: "Lagoon", + 0x00FFFF: "Cyan", + 0x400000: "Aubergine", + 0x400040: "Loulou", + 0x400080: "Deep Purple", + 0x4000C0: "Felicia", + 0x4000FF: "Indigo", + 0x404000: "Waiouru", + 0x404040: "Masala", + 0x404080: "Astronaut", + 0x4040C0: "Ocean", + 0x4040FF: "Iris", + 0x408000: "Bilbao", + 0x408040: "Goblin", + 0x408080: "Blue Grey", + 0x4080C0: "Glaucous", + 0x4080FF: "Blueberry", + 0x40C000: "Kelly", + 0x40C040: "Mantis", + 0x40C080: "Emerald", + 0x40C0C0: "Tradewind", + 0x40C0FF: "Aero", + 0x40FF00: "Harlequin", + 0x40FF40: "Frog", + 0x40FF80: "Lettuce", + 0x40FFC0: "Padua", + 0x40FFFF: "Riptide", + 0x800000: "Maroon", + 0x800040: "Siren", + 0x800080: "Purple", + 0x8000C0: "Rebecca", + 0x8000FF: "Violet", + 0x804000: "Cinnamon", + 0x804040: "Lotus", + 0x804080: "Affair", + 0x8040C0: "Studio", + 0x8040FF: "Veronica", + 0x808000: "Olive", + 0x808040: "Pesto", + 0x808080: "Grey", + 0x8080C0: "Ube", + 0x8080FF: "Light Blue", + 0x80C000: "Lima", + 0x80C040: "Atlantis", + 0x80C080: "Fern", + 0x80C0C0: "Neptune", + 0x80C0FF: "Jordy", + 0x80FF00: "Chartreuse", + 0x80FF40: "Lawn", + 0x80FF80: "Light Green", + 0x80FFC0: "Aquamarine", + 0x80FFFF: "Light Cyan", + 0xC00000: "Guardsman", + 0xC00040: "Cardinal", + 0xC00080: "Hibiscus", + 0xC000C0: "Roxo", + 0xC000FF: "Sororia", + 0xC04000: "Rust", + 0xC04040: "Brown", + 0xC04080: "Mulberry", + 0xC040C0: "Byzantine", + 0xC040FF: "Phlox", + 0xC08000: "Meteor", + 0xC08040: "Peru", + 0xC08080: "Contessa", + 0xC080C0: "Bouquet", + 0xC080FF: "Heliotrope", + 0xC0C000: "Celery", + 0xC0C040: "Turmeric", + 0xC0C080: "Gimblet", + 0xC0C0C0: "Silver", + 0xC0C0FF: "Melrose", + 0xC0FF00: "Limon", + 0xC0FF40: "Mindaro", + 0xC0FF80: "Sulu", + 0xC0FFC0: "Madang", + 0xC0FFFF: "Scandal", + 0xFF0000: "Red", + 0xFF0040: "Alizarin", + 0xFF0080: "Rose", + 0xFF00C0: "Pizzazz", + 0xFF00FF: "Magenta", + 0xFF4000: "Deep Orange", + 0xFF4040: "Cinnabar", + 0xFF4080: "Watermelon", + 0xFF40C0: "Dazzle", + 0xFF40FF: "Kovidar", + 0xFF8000: "Orange", + 0xFF8040: "Coral", + 0xFF8080: "Salmon", + 0xFF80C0: "Shocking", + 0xFF80FF: "Orchid", + 0xFFC000: "Amber", + 0xFFC040: "Mango", + 0xFFC080: "Rajah", + 0xFFC0C0: "Pink", + 0xFFC0FF: "Chantilly", + 0xFFFF00: "Yellow", + 0xFFFF40: "Fizz", + 0xFFFF80: "Dolly", + 0xFFFFC0: "Shalimar", + 0xFFFFFF: "White", +} diff --git a/micropython/pydisplay/palettes/palettes/_cube27.py b/micropython/pydisplay/palettes/palettes/_cube27.py new file mode 100644 index 000000000..7dbd44eef --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/_cube27.py @@ -0,0 +1,35 @@ +# Color names based on https://www.chilliant.com/colournames.html + +# A color cube with 3 levels of red, green, and blue (27 colors in total) +# (values 0x00, 0x80, and 0xFF for each of the red, green, and blue components) +# A 28th color, "Silver", is added to the end of the list for compatibility with other palettes +CUBE27 = { + 0x000000: "Black", + 0x000080: "Navy", + 0x0000FF: "Blue", + 0x008000: "Green", + 0x008080: "Teal", + 0x0080FF: "Azure", + 0x00FF00: "Lime", + 0x00FF80: "Spring", + 0x00FFFF: "Cyan", + 0x800000: "Maroon", + 0x800080: "Purple", + 0x8000FF: "Violet", + 0x808000: "Olive", + 0x808080: "Grey", + 0x8080FF: "Light Blue", + 0x80FF00: "Chartreuse", + 0x80FF80: "Light Green", + 0x80FFFF: "Light Cyan", + 0xFF0000: "Red", + 0xFF0080: "Rose", + 0xFF00FF: "Magenta", + 0xFF8000: "Orange", + 0xFF8080: "Salmon", + 0xFF80FF: "Orchid", + 0xFFFF00: "Yellow", + 0xFFFF80: "Dolly", + 0xFFFFFF: "White", + 0xC0C0C0: "Silver", +} diff --git a/micropython/pydisplay/palettes/palettes/_cube64.py b/micropython/pydisplay/palettes/palettes/_cube64.py new file mode 100644 index 000000000..786ef8e51 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/_cube64.py @@ -0,0 +1,70 @@ +# Color names based on https://www.chilliant.com/colournames.html + +# A color cube with 4 levels of red, green, and blue (64 colors in total) +# (values 0x00, 0x55, 0xAA, and 0xFF for each of the red, green, and blue components) +CUBE64 = { + 0x000000: "Black", + 0x000055: "Navy", + 0x0000AA: "Blue", + 0x0000FF: "Vivid Blue", + 0x005500: "Dark Green", + 0x005555: "Teal", + 0x0055AA: "Dark Azure", + 0x0055FF: "Denim", + 0x00AA00: "Green", + 0x00AA55: "Jade", + 0x00AAAA: "Cyan", + 0x00AAFF: "Cerulean", + 0x00FF00: "Lime", + 0x00FF55: "Erin", + 0x00FFAA: "Spring", + 0x00FFFF: "Vivid Cyan", + 0x550000: "Maroon", + 0x550055: "Dark Magenta", + 0x5500AA: "Deep Purple", + 0x5500FF: "Indigo", + 0x555500: "Olive", + 0x555555: "Grey", + 0x5555AA: "Liberty", + 0x5555FF: "Light Blue", + 0x55AA00: "Dark Chartreuse", + 0x55AA55: "Apple", + 0x55AAAA: "Blue Grey", + 0x55AAFF: "Azure", + 0x55FF00: "Harlequin", + 0x55FF55: "Light Green", + 0x55FFAA: "Aquamarine", + 0x55FFFF: "Light Cyan", + 0xAA0000: "Red", + 0xAA0055: "Plum", + 0xAA00AA: "Magenta", + 0xAA00FF: "Violet", + 0xAA5500: "Brown", + 0xAA5555: "Deep Orange", + 0xAA55AA: "Orchid", + 0xAA55FF: "Purple", + 0xAAAA00: "Dark Yellow", + 0xAAAA55: "Light Olive", + 0xAAAAAA: "Silver", + 0xAAAAFF: "Bluebell", + 0xAAFF00: "Inchworm", + 0xAAFF55: "Chartreuse", + 0xAAFFAA: "Mint", + 0xAAFFFF: "Celeste", + 0xFF0000: "Vivid Red", + 0xFF0055: "Rose", + 0xFF00AA: "Cerise", + 0xFF00FF: "Vivid Magenta", + 0xFF5500: "Orange", + 0xFF5555: "Salmon", + 0xFF55AA: "Light Plum", + 0xFF55FF: "Light Magenta", + 0xFFAA00: "Amber", + 0xFFAA55: "Light Brown", + 0xFFAAAA: "Pink", + 0xFFAAFF: "Mauve", + 0xFFFF00: "Vivid Yellow", + 0xFFFF55: "Yellow", + 0xFFFFAA: "Dolly", + 0xFFFFFF: "White", +} diff --git a/micropython/pydisplay/palettes/palettes/_cube8.py b/micropython/pydisplay/palettes/palettes/_cube8.py new file mode 100644 index 000000000..c38c2ef5c --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/_cube8.py @@ -0,0 +1,12 @@ +# A color cube with 2 levels of red, green, and blue (8 colors in total) +# (values 0x00 and 0xFF for each of the red, green, and blue components) +CUBE8 = { + 0x000000: "Black", + 0x0000FF: "Blue", + 0x00FF00: "Green", + 0x00FFFF: "Cyan", + 0xFF0000: "Red", + 0xFF00FF: "Magenta", + 0xFFFF00: "Yellow", + 0xFFFFFF: "White", +} diff --git a/micropython/pydisplay/palettes/palettes/_material_design.py b/micropython/pydisplay/palettes/palettes/_material_design.py new file mode 100644 index 000000000..ccc2e91e1 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/_material_design.py @@ -0,0 +1,329 @@ +_colors = ( + # black + b"\x00\x00\x00" + # white + b"\xff\xff\xff" + # red + b"\xff\xeb\xee" # 50 + b"\xff\xcd\xd2" # 100 + b"\xef\x9a\x9a" # 200 + b"\xe5\x73\x73" # 300 + b"\xef\x53\x50" # 400 + b"\xf4\x43\x36" # 500 + b"\xe5\x39\x35" # 600 + b"\xd3\x2f\x2f" # 700 + b"\xc6\x28\x28" # 800 + b"\xb7\x1c\x1c" # 900 + b"\xff\x8a\x80" # A100 + b"\xff\x52\x52" # A200 + b"\xff\x17\x44" # A400 + b"\xd5\x00\x00" # A700 + # pink + b"\xfc\xeb\xee" # 50 + b"\xf8\xbb\xd0" # 100 + b"\xf4\x8f\xb1" # 200 + b"\xf0\x62\x92" # 300 + b"\xec\x40\x7a" # 400 + b"\xe9\x1e\x63" # 500 + b"\xd8\x1b\x60" # 600 + b"\xc2\x18\x5b" # 700 + b"\xad\x14\x57" # 800 + b"\x88\x0e\x4f" # 900 + b"\xff\x80\xab" # A100 + b"\xff\x40\x81" # A200 + b"\xf5\x00\x57" # A400 + b"\xc5\x11\x62" # A700 + # purple + b"\xf3\xe5\xf5" # 50 + b"\xe1\xbe\xe7" # 100 + b"\xce\x93\xd8" # 200 + b"\xba\x68\xc8" # 300 + b"\xab\x47\xbc" # 400 + b"\x9c\x27\xb0" # 500 + b"\x8e\x24\xaa" # 600 + b"\x7b\x1f\xa2" # 700 + b"\x6a\x1b\x9a" # 800 + b"\x4a\x14\x8c" # 900 + b"\xea\x80\xfc" # A100 + b"\xe0\x40\xfb" # A200 + b"\xd5\x00\xf9" # A400 + b"\xaa\x00\xff" # A700 + # deep_purple + b"\xed\xe7\xf6" # 50 + b"\xd1\xc4\xe9" # 100 + b"\xb3\x9d\xdb" # 200 + b"\x95\x75\xcd" # 300 + b"\x7e\x57\xc2" # 400 + b"\x67\x3a\xb7" # 500 + b"\x5e\x35\xb1" # 600 + b"\x51\x2d\xa8" # 700 + b"\x45\x27\xa0" # 800 + b"\x31\x1b\x92" # 900 + b"\xb3\x88\xff" # A100 + b"\x7c\x4d\xff" # A200 + b"\x65\x1f\xff" # A400 + b"\x62\x00\xea" # A700 + # indigo + b"\xe8\xea\xf6" # 50 + b"\xc5\xca\xe9" # 100 + b"\x9f\xa8\xda" # 200 + b"\x79\x86\xcb" # 300 + b"\x5c\x6b\xc0" # 400 + b"\x3f\x51\xb5" # 500 + b"\x39\x49\xab" # 600 + b"\x30\x3f\x9f" # 700 + b"\x28\x35\x93" # 800 + b"\x1a\x23\x7e" # 900 + b"\x8c\x9e\xff" # A100 + b"\x53\x6d\xfe" # A200 + b"\x3d\x5a\xfe" # A400 + b"\x30\x4f\xfe" # A700 + # blue + b"\xe3\xf2\xfd" # 50 + b"\xbb\xde\xfb" # 100 + b"\x90\xca\xf9" # 200 + b"\x64\xb5\xf6" # 300 + b"\x42\xa5\xf5" # 400 + b"\x21\x96\xf3" # 500 + b"\x1e\x88\xe5" # 600 + b"\x19\x76\xd2" # 700 + b"\x15\x65\xc0" # 800 + b"\x0d\x47\xa1" # 900 + b"\x82\xb1\xff" # A100 + b"\x44\x8a\xff" # A200 + b"\x29\x79\xff" # A400 + b"\x29\x62\xff" # A700 + # light_blue + b"\xe1\xf5\xfe" # 50 + b"\xb3\xe5\xfc" # 100 + b"\x81\xd4\xfa" # 200 + b"\x4f\xc3\xf7" # 300 + b"\x29\xb6\xf6" # 400 + b"\x03\xa9\xf4" # 500 + b"\x03\x9b\xe5" # 600 + b"\x02\x88\xd1" # 700 + b"\x02\x77\xbd" # 800 + b"\x01\x57\x9b" # 900 + b"\x80\xd8\xff" # A100 + b"\x40\xc4\xff" # A200 + b"\x00\xb0\xff" # A400 + b"\x00\x91\xea" # A700 + # cyan + b"\xe0\xf7\xfa" # 50 + b"\xb2\xeb\xf2" # 100 + b"\x80\xde\xea" # 200 + b"\x4d\xd0\xe1" # 300 + b"\x26\xc6\xda" # 400 + b"\x00\xbc\xd4" # 500 + b"\x00\xac\xc1" # 600 + b"\x00\x97\xa7" # 700 + b"\x00\x83\x8f" # 800 + b"\x00\x60\x64" # 900 + b"\x84\xff\xff" # A100 + b"\x18\xff\xff" # A200 + b"\x00\xe5\xff" # A400 + b"\x00\xb8\xd4" # A700 + # teal + b"\xe0\xf2\xf1" # 50 + b"\xb2\xdf\xdb" # 100 + b"\x80\xcb\xc4" # 200 + b"\x4d\xb6\xac" # 300 + b"\x26\xa6\x9a" # 400 + b"\x00\x96\x88" # 500 + b"\x00\x89\x7b" # 600 + b"\x00\x79\x6b" # 700 + b"\x00\x69\x5c" # 800 + b"\x00\x4d\x40" # 900 + b"\xa7\xff\xeb" # A100 + b"\x64\xff\xda" # A200 + b"\x1d\xe9\xb6" # A400 + b"\x00\xbf\xa5" # A700 + # green + b"\xe8\xf5\xe9" # 50 + b"\xc8\xe6\xc9" # 100 + b"\xa5\xd6\xa7" # 200 + b"\x81\xc7\x84" # 300 + b"\x66\xbb\x6a" # 400 + b"\x4c\xaf\x50" # 500 + b"\x43\xa0\x47" # 600 + b"\x38\x8e\x3c" # 700 + b"\x2e\x7d\x32" # 800 + b"\x1b\x5e\x20" # 900 + b"\xb9\xf6\xca" # A100 + b"\x69\xf0\xae" # A200 + b"\x00\xe6\x76" # A400 + b"\x00\xc8\x53" # A700 + # light_green + b"\xf1\xf8\xe9" # 50 + b"\xdc\xed\xc8" # 100 + b"\xc5\xe1\xa5" # 200 + b"\xae\xd5\x81" # 300 + b"\x9c\xcc\x65" # 400 + b"\x8b\xc3\x4a" # 500 + b"\x7c\xb3\x42" # 600 + b"\x68\x9f\x38" # 700 + b"\x55\x8b\x2f" # 800 + b"\x33\x69\x1e" # 900 + b"\xcc\xff\x90" # A100 + b"\xb2\xff\x59" # A200 + b"\x76\xff\x03" # A400 + b"\x64\xdd\x17" # A700 + # lime + b"\xf9\xfb\xe7" # 50 + b"\xf0\xf4\xc3" # 100 + b"\xe6\xee\x9c" # 200 + b"\xdc\xe7\x75" # 300 + b"\xd4\xe1\x57" # 400 + b"\xcd\xdc\x39" # 500 + b"\xc0\xca\x33" # 600 + b"\xaf\xb4\x2b" # 700 + b"\x9e\x9d\x24" # 800 + b"\x82\x77\x17" # 900 + b"\xf4\xff\x81" # A100 + b"\xee\xff\x41" # A200 + b"\xc6\xff\x00" # A400 + b"\xae\xea\x00" # A700 + # yellow + b"\xff\xfd\xe7" # 50 + b"\xff\xf9\xc4" # 100 + b"\xff\xf5\x9d" # 200 + b"\xff\xf1\x76" # 300 + b"\xff\xee\x58" # 400 + b"\xff\xeb\x3b" # 500 + b"\xfd\xd8\x35" # 600 + b"\xfb\xc0\x2d" # 700 + b"\xf9\xa8\x25" # 800 + b"\xf5\x7f\x17" # 900 + b"\xff\xff\x8d" # A100 + b"\xff\xff\x00" # A200 + b"\xff\xea\x00" # A400 + b"\xff\xd6\x00" # A700 + # amber + b"\xff\xf8\xe1" # 50 + b"\xff\xec\xb3" # 100 + b"\xff\xe0\x82" # 200 + b"\xff\xd5\x4f" # 300 + b"\xff\xca\x28" # 400 + b"\xff\xc1\x07" # 500 + b"\xff\xb3\x00" # 600 + b"\xff\xa0\x00" # 700 + b"\xff\x8f\x00" # 800 + b"\xff\x6f\x00" # 900 + b"\xff\xe5\x7f" # A100 + b"\xff\xd7\x40" # A200 + b"\xff\xc4\x00" # A400 + b"\xff\xab\x00" # A700 + # orange + b"\xff\xf3\xe0" # 50 + b"\xff\xe0\xb2" # 100 + b"\xff\xcc\x80" # 200 + b"\xff\xb7\x4d" # 300 + b"\xff\xa7\x26" # 400 + b"\xff\x98\x00" # 500 + b"\xfb\x8c\x00" # 600 + b"\xf5\x7c\x00" # 700 + b"\xef\x6c\x00" # 800 + b"\xe6\x51\x00" # 900 + b"\xff\xd1\x80" # A100 + b"\xff\xab\x40" # A200 + b"\xff\x91\x00" # A400 + b"\xff\x6d\x00" # A700 + # deep_orange + b"\xfb\xe9\xe7" # 50 + b"\xff\xcc\xbc" # 100 + b"\xff\xab\x91" # 200 + b"\xff\x8a\x65" # 300 + b"\xff\x70\x43" # 400 + b"\xff\x57\x22" # 500 + b"\xf4\x51\x1e" # 600 + b"\xe6\x4a\x19" # 700 + b"\xd8\x43\x15" # 800 + b"\xbf\x36\x0c" # 900 + b"\xff\x9e\x80" # A100 + b"\xff\x6e\x40" # A200 + b"\xff\x3d\x00" # A400 + b"\xdd\x2c\x00" # A700 + # brown + b"\xef\xeb\xe9" # 50 + b"\xd7\xcc\xc8" # 100 + b"\xbc\xaa\xa4" # 200 + b"\xa1\x88\x7f" # 300 + b"\x8d\x6e\x63" # 400 + b"\x79\x55\x48" # 500 + b"\x6d\x4c\x41" # 600 + b"\x5d\x40\x37" # 700 + b"\x4e\x34\x2e" # 800 + b"\x3e\x27\x23" # 900 + # grey + b"\xfa\xfa\xfa" # 50 + b"\xf5\xf5\xf5" # 100 + b"\xee\xee\xee" # 200 + b"\xe0\xe0\xe0" # 300 + b"\xbd\xbd\xbd" # 400 + b"\x9e\x9e\x9e" # 500 + b"\x75\x75\x75" # 600 + b"\x61\x61\x61" # 700 + b"\x42\x42\x42" # 800 + b"\x21\x21\x21" # 900 + # blue_grey + b"\xec\xef\xf1" # 50 + b"\xcf\xd8\xdc" # 100 + b"\xb0\xbe\xc5" # 200 + b"\x90\xa4\xae" # 300 + b"\x78\x90\x9c" # 400 + b"\x60\x7d\x8b" # 500 + b"\x54\x6e\x7a" # 600 + b"\x45\x5a\x64" # 700 + b"\x37\x47\x4f" # 800 + b"\x26\x32\x38" # 900 +) + +FAMILIES = [ + "black", + "white", + "red", + "pink", + "purple", + "deep_purple", + "indigo", + "blue", + "light_blue", + "cyan", + "teal", + "green", + "light_green", + "lime", + "yellow", + "amber", + "orange", + "deep_orange", + "brown", + "grey", + "blue_grey", +] + +LENGTHS = [ + 1, + 1, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 14, + 10, + 10, + 10, +] + +COLORS = memoryview(_colors) diff --git a/micropython/pydisplay/palettes/palettes/cube.py b/micropython/pydisplay/palettes/palettes/cube.py new file mode 100644 index 000000000..c463b6351 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/cube.py @@ -0,0 +1,54 @@ +# SPDIX:# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`palettes.cube` +==================================================== +Makes a color cube palette. + +Usage: + from palettes import get_palette + palette = get_palette(name="cube", size=5, color_depth=16, swapped=False) + # OR + palette = get_palette(name="cube") + + # OR + from palettes.cube import CubePalette + palette = CubePalette(size=5, color_depth=24) + + print(f"Palette: {palette.name}, Length: {len(palette)}") + for i, color in enumerate(palette): + for i, color in enumerate(palette): print(f"{i}. {color:#06X} {palette.color_name(i)}") +""" + +from . import Palette as _Palette + + +class CubePalette(_Palette): + """ + A color cube palette. The size of the cube is determined by the size parameter. + """ + + def __init__(self, name="", color_depth=16, swapped=False, cached=True, size=5): + self._size = size + self._length = size**3 + self._values = [round(i * (255 / (size - 1)) + 0.25) for i in range(size)] + + if self._size == 2: + from ._cube8 import CUBE8 as NAMES + elif self._size == 3: + from ._cube27 import CUBE27 as NAMES + elif self._size == 4: + from ._cube64 import CUBE64 as NAMES + else: + from ._cube125 import CUBE125 as NAMES + self._names = NAMES + super().__init__(name + str(self._length), color_depth, swapped, cached) + + def _get_rgb(self, index): + z = index % self._size + index //= self._size + y = index % self._size + index //= self._size + x = index % self._size + return self._values[x], self._values[y], self._values[z] diff --git a/micropython/pydisplay/palettes/palettes/material_design.py b/micropython/pydisplay/palettes/palettes/material_design.py new file mode 100644 index 000000000..78d3d6f9e --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/material_design.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`palettes.material_design` +==================================================== +This module contains the Material Design color palette as a class object. + + +Usage: + from palettes import get_palette + palette = get_palette(name="material_design", color_depth=16, swapped=False) + # OR + palette = get_palette("material_design") + + # OR + from palettes.material_design import MDPalette + palette = MDPalette(size=5, color_depth=24) + + # to access the primary variant of a color family by name: + x = palette.RED + x = palette.BLACK + + # to access all 256 colors directly: + x = palette[127] # color at index 127 + + # to access a shade by name: + x = palette.RED_S500 # shade 500 + x = palette.RED_S900 # shade 900 + x = palette.RED_S50 # shade 50 + + # to access an accent of a color family by name: + x = palette.RED_A100 + x = palette.RED_A700 + + # to iterate over all 256 colors: + for x in palette: + pass +""" + +from . import MappedPalette +from ._material_design import COLORS, FAMILIES, LENGTHS + + +class MDPalette(MappedPalette): + """ + A class to represent the Material Design color palette. + """ + + _shades = [ + "S50", + "S100", + "S200", + "S300", + "S400", + "S500", + "S600", + "S700", + "S800", + "S900", + ] + + _accents = ["A100", "A200", "A400", "A700"] + + def __init__(self, name="", color_depth=16, swapped=False, color_map=COLORS): + super().__init__(name, color_depth, swapped, color_map) + self._name = name if name else "MaterialDesign" + + def _define_named_colors(self): + # The colors are already available as pal[0], pal[1], etc. + # Now we want to add pal.BLACK = pal[0], pal.WHITE = pal[1], etc. + color_index = 0 + for name, length in zip(FAMILIES, LENGTHS): + if length == 1: # black or white + setattr(self, name.upper(), self[color_index]) + color_index += 1 + else: + for shade in self._shades: + setattr(self, f"{name}_{shade}".upper(), self[color_index]) + # S500 is the default shade for each family, so add it to the palette + # without the _S500 suffix + if shade == "S500": + setattr(self, name.upper(), self[color_index]) + color_index += 1 + if length == 14: + for accent in self._accents: + setattr(self, f"{name}_{accent}".upper(), self[color_index]) + color_index += 1 diff --git a/micropython/pydisplay/palettes/palettes/wheel.py b/micropython/pydisplay/palettes/palettes/wheel.py new file mode 100644 index 000000000..8dad3a4f4 --- /dev/null +++ b/micropython/pydisplay/palettes/palettes/wheel.py @@ -0,0 +1,107 @@ +""" +`pypalette.wheel` +==================================================== +This module contains the cool wheel color palette as a class object. + + +Usage: + from palettes import get_palette + palette = get_palette(name="wheel", color_depth=16, swapped=False, length=256) + # OR + palette = get_palette(name="wheel") + + # OR + from palettes.wheel import WheelPalette + palette = WheelPalette(color_depth=16, swapped=False, length=256) + + print(f"Palette: {palette.name}, Length: {len(palette)}") + for i, color in enumerate(palette): + for i, color in enumerate(palette): print(f"{i}. {color:#06X} {palette.color_name(i)}") + + # to access the named colors directly: + x = palette.RED + x = palette.BLACK + +""" + +from . import Palette as _Palette + + +class WheelPalette(_Palette): + """ + A class to represent a color wheel as a palette. + """ + + def __init__( + self, + name="", + color_depth=16, + swapped=False, + cached=True, + length=256, + saturation=1.0, + value=None, + ): + self._length = length + + if saturation is None and value is None: + self._mode = "wheel" + self._one_third = self._length // 3 + self._two_thirds = 2 * self._length // 3 + self._spacing = 256 * 3 / self._length + else: + self._mode = "hsv" + self._saturation = saturation if saturation is not None else 1.0 + self._value = value if value is not None else 1.0 + if not 0 <= self._saturation <= 1 or not 0 <= self._value <= 1: + raise ValueError("Saturation and value must be in the range of 0-1") + from . import WIN16 as NAMES + + self._names = NAMES + + super().__init__(name + str(self._length), color_depth, swapped, cached) + + def _get_rgb(self, index): + if self._mode == "wheel": + return self._wheel_to_rgb(index) + else: + return self._hsv_to_rgb(index / self._length, self._saturation, self._value) + + def _wheel_to_rgb(self, index): + # incoming index is in the range of 0-self._length + index = self._length - 1 - index # reverse the order + + if index < self._one_third: + return (255 - int(index * self._spacing), 0, int(index * self._spacing)) + elif index < self._two_thirds: + index -= self._one_third + return (0, int(index * self._spacing), 255 - int(index * self._spacing)) + else: + index -= self._two_thirds + return (int(index * self._spacing), 255 - int(index * self._spacing), 0) + + def _hsv_to_rgb(self, h, s, v): + # incoming values are in the range of 0-1 + # returns r, g, b values in the range of 0-255 + if s == 0.0: # when s=0, all values are shades of gray + return int(v * 255), int(v * 255), int(v * 255) + i = int(h * 6.0) + f = (h * 6.0) - i + p = int(255 * v * (1.0 - s)) + q = int(255 * v * (1.0 - s * f)) + t = int(255 * v * (1.0 - s * (1.0 - f))) + v = int(255 * v) + i = i % 6 + if i == 0: # red + return v, t, p + if i == 1: # yellow + return q, v, p + if i == 2: # green + return p, v, t + if i == 3: # cyan + return p, q, v + if i == 4: # blue + return t, p, v + if i == 5: # magenta + return v, p, q + return 0, 0, 0 diff --git a/micropython/pydisplay/spibus/manifest.py b/micropython/pydisplay/spibus/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/spibus/spibus.py b/micropython/pydisplay/spibus/spibus.py new file mode 100644 index 000000000..6115d2136 --- /dev/null +++ b/micropython/pydisplay/spibus/spibus.py @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: MIT +""" +pydisplay spibus +""" + +from machine import Pin, SPI +import struct +import micropython +from micropython import const + +try: + from typing import Optional, Union +except ImportError: + pass + + +DC_CMD = const(0) +DC_DATA = const(1) +CS_ACTIVE = const(0) +CS_INACTIVE = const(1) + + +class SPIBus: + """ + Represents an SPI bus. + + Args: + id (int): The ID of the SPI bus. + baudrate (int): The baudrate of the SPI bus. + polarity (int): The polarity of the SPI bus. + phase (int): The phase of the SPI bus. + bits (int): The number of bits per transfer. + lsb_first (bool): Whether to send the least significant bit first. + sck (int): The pin number of the SCK pin. + mosi (int): The pin number of the MOSI pin. + miso (int): The pin number of the MISO pin. + dc (int): The pin number of the DC pin. + cs (int): The pin number of the CS pin. + + Raises: + ValueError: If the DC pin is not specified. + """ + + def __init__( + self, + *, + id: int = 2, + baudrate: int = 24_000_000, + polarity: int = 0, + phase: int = 0, + bits: int = 8, + lsb_first: bool = False, + sck: int = -1, + mosi: int = -1, + miso: int = -1, + dc: int = -1, + cs: int = -1, + ) -> None: + print("SPIBus loading...") + if dc == -1: + raise ValueError("DC pin must be specified") + + self._baudrate: int = baudrate + self._polarity: int = polarity + self._phase: int = phase + self._bits: int = bits + self._firstbit: int = SPI.LSB if lsb_first else SPI.MSB + + if mosi == -1 and miso == -1 and sck == -1: + self._spi: SPI = SPI( + id, + baudrate=self._baudrate, + polarity=self._polarity, + phase=self._phase, + bits=self._bits, + firstbit=self._firstbit, + ) + else: + self._spi: SPI = SPI( + id, + baudrate=self._baudrate, + polarity=self._polarity, + phase=self._phase, + bits=self._bits, + firstbit=self._firstbit, + sck=Pin(sck, Pin.OUT), + mosi=Pin(mosi, Pin.OUT), + miso=Pin(miso, Pin.IN) if miso > -1 else None, + ) + + # DC and CS pins must be set AFTER the SPI bus is initialized on some boards + self._dc: Pin = Pin(dc, Pin.OUT, value=DC_DATA) + self._cs: Union[Pin, callable] = ( + Pin(cs, Pin.OUT, value=CS_INACTIVE) if cs != -1 else lambda val: None + ) + + self._buf1: bytearray = bytearray(1) + print("SPIBus loaded") + + @micropython.native + def send( + self, + command: Optional[int] = None, + data: Optional[memoryview] = None, + ) -> None: + """ + Sends a command and/or data over the SPI bus. + + Args: + command (int): The command to send. + data (memoryview): The data to send. + + Returns: + None + """ + + self._spi.init( + baudrate=self._baudrate, + polarity=self._polarity, + phase=self._phase, + bits=self._bits, + firstbit=self._firstbit, + ) + + self._cs(CS_ACTIVE) + + if command is not None: + struct.pack_into("B", self._buf1, 0, command) + self._dc(DC_CMD) + self._spi.write(self._buf1) + + if data and len(data): + self._dc(DC_DATA) + self._spi.write(data) + + self._cs(CS_INACTIVE) + + def deinit(self) -> None: + """ + Deinitializes the SPI bus. + + Returns: + None + """ + + self._spi.deinit() + + def __del__(self) -> None: + self.deinit() diff --git a/micropython/pydisplay/timer/examples/timer_simpletest.py b/micropython/pydisplay/timer/examples/timer_simpletest.py new file mode 100644 index 000000000..0be7b0cec --- /dev/null +++ b/micropython/pydisplay/timer/examples/timer_simpletest.py @@ -0,0 +1,32 @@ +""" +This is a simple test script that tests the basic functionality of the timer class. + +It creates a periodic timer in a class instance and a one-shot timer that stops the periodic timer. +""" + +from timer import Timer +from sys import platform + +class TimerTest: + def __init__(self): + self._tim = Timer(-1 if platform == "rp2" else 1) + + def start(self, period): + self._counter = 0 + self._tim.init(mode=Timer.PERIODIC, period=period, callback=self.do_something) + print("TimerTest: timer started...") + + def do_something(self, t): + self._counter += 1 + + def stop(self, t=None): + self._tim.deinit() + print(f"TimerTest: timer stopped after {self._counter:,} calls.") + +# Create a timer that calls tt.do_something every 1ms +tt = TimerTest() +tt.start(1) + +# Create a timer that stops the first timer after 5 seconds +tim2 = Timer(-1 if platform == "rp2" else 2) +tim2.init(mode=Timer.ONE_SHOT, period=5000, callback=tt.stop) diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py new file mode 100644 index 000000000..e69de29bb diff --git a/micropython/pydisplay/timer/timer/__init__.py b/micropython/pydisplay/timer/timer/__init__.py new file mode 100644 index 000000000..a6780f9c0 --- /dev/null +++ b/micropython/pydisplay/timer/timer/__init__.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`timer` +==================================================== + +Cross-platform Timer class for *Python. + +Enables using 'from timer import Timer' on MicroPython on microcontrollers, +on MicroPython on Unix (which doesn't have a machine.Timer) and CPython (ditto). + +_librt.py uses uses MicroPython ffi to connect to libc and librt, while _sdl2.py uses +SDL2 on CPython to connect to libSDL2. No compatibility for CircuitPython yet. + +Returns None if the platform is not supported rather than raising an ImportError so that +the client can handle the error more gracefully (e.g. by using `if Timer is not None:`). + +Usage: + from timer import Timer + tim = Timer() + tim.init(mode=Timer.PERIODIC, period=500, callback=lambda t: print(".")) + .... + tim.deinit() +""" + +import sys + +try: + from machine import Timer # MicroPython on microcontrollers +except ImportError: + if sys.implementation.name == "micropython": # MicroPython on Unix + from ._librt import Timer + elif sys.implementation.name == "cpython": # Big Python + from ._sdl2 import Timer + else: + Timer = None + +_next_timer_id = 1 + + +def get_timer(callback, period=33): + """ + Creates and returns a timer to periodically call the callback function + + Args: + callback (function): The function to call periodically + period (int): The period in milliseconds, default is 33ms (30fps) + """ + global _next_timer_id + if sys.platform == "rp2": + id = -1 + else: + id = _next_timer_id + _next_timer_id += 1 + t = Timer(id) + t.init(mode=Timer.PERIODIC, period=period, callback=lambda t: callback()) + print(f"Timer: timer started ({id=}, {period=})") + return t diff --git a/micropython/pydisplay/timer/timer/_librt.py b/micropython/pydisplay/timer/timer/_librt.py new file mode 100644 index 000000000..4e7a0273c --- /dev/null +++ b/micropython/pydisplay/timer/timer/_librt.py @@ -0,0 +1,155 @@ +# Timer that matches machine.Timer (https://docs.micropython.org/en/latest/library/machine.Timer.html) +# for the unix port. +# +# MIT license; Copyright (c) 2021 Amir Gonnen, 2024 Brad Barnett +# +# Based on timer.py from micropython-lib (https://github.com/micropython/micropython-lib/blob/master/unix-ffi/machine/machine/timer.py) + +from ._timerbase import _TimerBase +import ffi +import uctypes +import array +import os + +# FFI libraries + +libc = ffi.open("libc.so.6") +try: + librt = ffi.open("librt.so") +except OSError: + librt = libc + + +# C constants + +CLOCK_REALTIME = 0 +CLOCK_MONOTONIC = 1 +SIGEV_SIGNAL = 0 + +# C structs + +sigaction_t = { + "sa_handler": (0 | uctypes.UINT64), + "sa_mask": (8 | uctypes.ARRAY, 16 | uctypes.UINT64), + "sa_flags": (136 | uctypes.INT32), + "sa_restorer": (144 | uctypes.PTR, uctypes.UINT8), +} + +sigval_t = { + "sival_int": 0 | uctypes.INT32, + "sival_ptr": (0 | uctypes.PTR, uctypes.UINT8), +} + +sigevent_t = { + "sigev_value": (0, sigval_t), + "sigev_signo": uctypes.sizeof(sigval_t) | uctypes.INT32, + "sigev_notify": (uctypes.sizeof(sigval_t) + 4) | uctypes.INT32, +} + +timespec_t = { + "tv_sec": 0 | uctypes.INT32, + "tv_nsec": 8 | uctypes.INT64, +} + +itimerspec_t = { + "it_interval": (0, timespec_t), + "it_value": (16, timespec_t), +} + +# C functions + +__libc_current_sigrtmin = libc.func("i", "__libc_current_sigrtmin", "") +SIGRTMIN = __libc_current_sigrtmin() + +timer_create_ = librt.func("i", "timer_create", "ipp") +timer_delete_ = librt.func("i", "timer_delete", "i") +timer_settime_ = librt.func("i", "timer_settime", "PiPp") + +sigaction_ = libc.func("i", "sigaction", "iPp") + +# Create a new C struct + + +def new(sdesc): + buf = bytearray(uctypes.sizeof(sdesc)) + s = uctypes.struct(uctypes.addressof(buf), sdesc, uctypes.NATIVE) + return s + + +# Posix Signal handling + + +def sigaction(signum, handler, flags=0): + sa = new(sigaction_t) + sa_old = new(sigaction_t) + cb = ffi.callback("v", handler, "i", lock=True) + sa.sa_handler = cb.cfun() + sa.sa_flags = flags + r = sigaction_(signum, sa, sa_old) + if r != 0: + raise RuntimeError("sigaction_ error: %d (errno = %d)" % (r, os.errno())) + return cb # sa_old.sa_handler + + +# Posix Timer handling + + +def timer_create(sig_id): + sev = new(sigevent_t) + # print(sev) + sev.sigev_notify = SIGEV_SIGNAL + sev.sigev_signo = SIGRTMIN + sig_id + timerid = array.array("P", [0]) + r = timer_create_(CLOCK_MONOTONIC, sev, timerid) + if r != 0: + raise RuntimeError("timer_create_ error: %d (errno = %d)" % (r, os.errno())) + # print("timerid", hex(timerid[0])) + return timerid[0] + + +def timer_delete(tid): + r = timer_delete_(tid) + if r != 0: + raise RuntimeError("timer_delete_ error: %d (errno = %d)" % (r, os.errno())) + + +def timer_settime(tid, period_ms, periodic): + period_ns = (period_ms * 1000000) % 1000000000 + period_sec = (period_ms * 1000000) // 1000000000 + + new_val = new(itimerspec_t) + new_val.it_value.tv_sec = period_sec + new_val.it_value.tv_nsec = period_ns + if periodic: + new_val.it_interval.tv_sec = period_sec + new_val.it_interval.tv_nsec = period_ns + # print("new_val:", bytes(new_val)) + old_val = new(itimerspec_t) + # print(new_val, old_val) + r = timer_settime_(tid, 0, new_val, old_val) + if r != 0: + raise RuntimeError("timer_settime_ error: %d (errno = %d)" % (r, os.errno())) + # print("old_val:", bytes(old_val)) + + +# Timer class + + +class Timer(_TimerBase): + """librt Timer class""" + + def _start(self): + self.id = ( + self.id if self.id != -1 else 0xF + ) # id must be non-negative, so we use 0xF as a default + self._timer = timer_create(self.id) + timer_settime(self._timer, self._interval, self._mode == Timer.PERIODIC) + self._handler_ref = self._handler + self._action = sigaction(SIGRTMIN + self.id, self._handler_ref) + + def _stop(self): + # timer_settime(self._timer, 0, False) + timer_delete(self._timer) + self._timer = None + self._action = None + self._handler_ref = None diff --git a/micropython/pydisplay/timer/timer/_sdl2.py b/micropython/pydisplay/timer/timer/_sdl2.py new file mode 100755 index 000000000..e011662ca --- /dev/null +++ b/micropython/pydisplay/timer/timer/_sdl2.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +Timer using SDL2 for CPython with the same API as machine.Timer in MicroPython. +""" + +from ._timerbase import _TimerBase +import ctypes +from sys import platform + + +if platform == "win32": + _libSDL2 = ctypes.CDLL("SDL2.dll") +else: + _libSDL2 = ctypes.CDLL("libSDL2-2.0.so.0") + +SDL_INIT_TIMER = 0x00000001 + +_libSDL2.SDL_Init.argtypes = [ctypes.c_uint] +_libSDL2.SDL_Init.restype = ctypes.c_int +SDL_Init = _libSDL2.SDL_Init + +_libSDL2.SDL_AddTimer.argtypes = [ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p] +_libSDL2.SDL_AddTimer.restype = ctypes.c_void_p +SDL_AddTimer = _libSDL2.SDL_AddTimer + +_libSDL2.SDL_RemoveTimer.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_RemoveTimer.restype = ctypes.c_int +SDL_RemoveTimer = _libSDL2.SDL_RemoveTimer + +SDL_TimerCallback = ctypes.CFUNCTYPE(ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p) + + +class Timer(_TimerBase): + """SDL2 Timer class""" + + def _start(self): + SDL_Init(SDL_INIT_TIMER) + self._handler_ref = self._handler + self._tcb = SDL_TimerCallback(self._handler_ref) + self._timer = SDL_AddTimer(self._interval, self._tcb, None) + + def _stop(self): + if self._timer: + SDL_RemoveTimer(self._timer) + self._timer = None + self._tcb = None + self._handler_ref = None diff --git a/micropython/pydisplay/timer/timer/_timerbase.py b/micropython/pydisplay/timer/timer/_timerbase.py new file mode 100644 index 000000000..5562abf9b --- /dev/null +++ b/micropython/pydisplay/timer/timer/_timerbase.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +try: + from micropython import const, schedule +except ImportError: + + def const(x): + return x + + def schedule(cb, interval): + cb(interval) + + +class _TimerBase: + """ + A class to create a timer with the same API and similar functionality to + MicroPython's machine.Timer class. + """ + + PERIODIC = const(0) + ONE_SHOT = const(1) + + def __init__(self, id=-1, **kwargs): + """ + Initializes the timer with the given parameters. + + Args: + id (int): The timer ID (default is -1). + **kwargs: Additional keyword arguments. + """ + self.id = id + self._busy = False + self._timer = None + if kwargs: + self.init(**kwargs) + + def init(self, *, mode, freq=-1, period=-1, callback=None): + """ + Initialize the timer. + + Args: + mode (int): Timer mode (Timer.ONE_SHOT or Timer.PERIODIC). + freq (int, optional): Timer frequency in Hz. Defaults to -1. + period (int, optional): Timer period in milliseconds. Ignored if freq is specified. Defaults to -1. + callback (callable, optional): Callable to execute upon timer expiration. Defaults to None. + + Raises: + ValueError: If an invalid timer mode or interval is provided. + """ + if mode in (self.ONE_SHOT, self.PERIODIC): + self._mode = mode + else: + raise ValueError("Invalid timer mode") + + self._interval = int(1000 / freq) if freq > 0 else period + if self._interval < 1: + raise ValueError("Invalid freq or period") + + self._callback = callback + self._start() # _start() is implemented in subclasses + + def deinit(self): + """ + Deinitializes the timer. + """ + while self._busy: + pass + + self._stop() # _stop() is implemented in subclasses + self._mode = None + self._interval = 0 + self._callback = None + self._timer = None + + def _handler(self, interval, param=None): + """ + Internal callback function called when the timer expires. + SDL2 timers call the handler with the interval and a user-defined parameter, + while librt timers call the handler with the interval only. + They are ignored here. + + Args: + interval (int): The interval at which the timer expires. + param: User-defined parameter (ignored). + + Returns: + int: The next interval for SDL2 timers, 0 for one-shot timers. + """ + if self._busy: + return + + self._busy = True + try: + schedule(self._callback, 0) + except RuntimeError: # MicroPython raises RuntimeError if the schedule queue is full + pass + self._busy = False + + if self._mode == self.ONE_SHOT: + self.deinit() + return 0 # SDL2 expects the callback to return the next interval, 0 for one-shot + return self._interval + + def _start(self): + """ + Starts the timer. Must be implemented by subclasses. + + Raises: + NotImplementedError: If not implemented by subclass. + """ + raise NotImplementedError("Subclasses must implement this method") + + def _stop(self): + """ + Stops the timer. Must be implemented by subclasses. + + Raises: + NotImplementedError: If not implemented by subclass. + """ + raise NotImplementedError("Subclasses must implement this method") From 82246c137961f545fbed3756e1d01208c20dc8c1 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Fri, 22 Nov 2024 16:45:52 -0600 Subject: [PATCH 02/35] add hardware_setup.py and color_setup.py to displaybuf --- .../displaybuf/examples/color_setup.py | 22 +++++++ .../displaybuf/examples/hardware_setup.py | 59 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 micropython/pydisplay/displaybuf/examples/color_setup.py create mode 100644 micropython/pydisplay/displaybuf/examples/hardware_setup.py diff --git a/micropython/pydisplay/displaybuf/examples/color_setup.py b/micropython/pydisplay/displaybuf/examples/color_setup.py new file mode 100644 index 000000000..a83e9444f --- /dev/null +++ b/micropython/pydisplay/displaybuf/examples/color_setup.py @@ -0,0 +1,22 @@ +""" +color_setup.py - color setup for DisplayBuffer with pydisplay +Usage: + from color_setup import ssd + +""" + +from displaybuf import DisplayBuffer as SSD +from board_config import display_drv +import sys + +# SSD.RGB565 is supported by all implementations, so set it as the default format +# Micropython also supports SSD.GS4_HMSB and SSD.GS8 + +if sys.implementation.name == "micropython": + # format = SSD.GS4_HMSB # 4-bit (16 item) lookup table of 16-bit RGB565 colors; w*h/2 buffer + # format = SSD.GS8 # 256 8-bit RGB332 colors; w*h buffer + format = SSD.RGB565 # all 65,536 16-bit RGB565 colors; w*h*2 buffer +else: + format = SSD.RGB565 + +ssd = SSD(display_drv, format) diff --git a/micropython/pydisplay/displaybuf/examples/hardware_setup.py b/micropython/pydisplay/displaybuf/examples/hardware_setup.py new file mode 100644 index 000000000..c9604b28e --- /dev/null +++ b/micropython/pydisplay/displaybuf/examples/hardware_setup.py @@ -0,0 +1,59 @@ +""" +hardware_setup.py - hardware setup for MicroPython-Touch using DisplayBuffer on pydisplay +See: https://github.com/peterhinch/micropython-touch + +Usage: + from hardware_setup import display + +""" + +from displaybuf import DisplayBuffer as SSD +from board_config import display_drv, broker + +# format = SSD.GS4_HMSB # 4-bit (16 item) lookup table of 16-bit RGB565 colors; w*h/2 buffer +# format = SSD.GS8 # 256 8-bit RGB332 colors; w*h buffer +format = SSD.RGB565 # all 65,536 16-bit RGB565 colors; w*h*2 buffer + +ssd = SSD(display_drv, format) + + +# enable screenshot functionality +def screenshot(event): + if event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 3: + ssd.screenshot() + + +broker.subscribe(screenshot, event_types=[broker.Events.MOUSEBUTTONDOWN]) +# End screenshot functionality + + +class Poller: + def __init__(self, poll_func): + self._poll_func = poll_func + self._touched = False + self.col = None + self.row = None + + def poll(self): + self._poll_func() + return True if self._touched else False + + def callback(self, event): + if event.type == broker.Events.MOUSEMOTION and event.buttons[0] == 1: + self.col, self.row = event.pos + self._touched = True + elif event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 1: + self.col, self.row = event.pos + self._touched = True + elif event.type == broker.Events.MOUSEBUTTONUP and event.button == 1: + self._touched = False + + +tpad = Poller(broker.poll) +broker.subscribe( + tpad.callback, event_types=[broker.Events.MOUSEMOTION, broker.Events.MOUSEBUTTONDOWN, broker.Events.MOUSEBUTTONUP] +) + +from gui.core.tgui import Display # noqa: E402 + +display = Display(ssd, tpad) From cc96b5c0603c507fffc015bf838c29266f5bcdcc Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Fri, 22 Nov 2024 21:51:28 -0600 Subject: [PATCH 03/35] update manifest.py files --- micropython/pydisplay/displaybuf/manifest.py | 2 + micropython/pydisplay/displaysys/README.md | 2 + .../displaysys-busdisplay/manifest.py | 3 + .../displaysys-fbdisplay/manifest.py | 3 + .../displaysys-pgdisplay/manifest.py | 3 + .../displaysys-psdisplay/manifest.py | 3 + .../displaysys-sdldisplay/manifest.py | 3 + .../displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/manifest.py | 2 + micropython/pydisplay/gpio_pin/gpio_pin.py | 4 +- micropython/pydisplay/gpio_pin/manifest.py | 3 + micropython/pydisplay/graphics/manifest.py | 2 + micropython/pydisplay/i80bus/i80bus.py | 366 ++++++++++++++++++ micropython/pydisplay/i80bus/manifest.py | 3 + micropython/pydisplay/palettes/manifest.py | 2 + micropython/pydisplay/spibus/manifest.py | 3 + micropython/pydisplay/timer/manifest.py | 2 + 17 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 micropython/pydisplay/i80bus/i80bus.py diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index e69de29bb..978e34af7 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("displaybuf") diff --git a/micropython/pydisplay/displaysys/README.md b/micropython/pydisplay/displaysys/README.md index e69de29bb..ea5d10ddb 100644 --- a/micropython/pydisplay/displaysys/README.md +++ b/micropython/pydisplay/displaysys/README.md @@ -0,0 +1,2 @@ +# displaysys +================= \ No newline at end of file diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index e69de29bb..9466dd44b 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("lordisplaysysa") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index e69de29bb..9466dd44b 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("lordisplaysysa") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index e69de29bb..9466dd44b 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("lordisplaysysa") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index e69de29bb..9466dd44b 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("lordisplaysysa") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index e69de29bb..9466dd44b 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("lordisplaysysa") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 0dba196ae..54caaed46 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,2 +1,2 @@ metadata(version="0.1.0") -package("displaycore") \ No newline at end of file +package("displaysys") \ No newline at end of file diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index e69de29bb..f502e73d7 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("eventsys") diff --git a/micropython/pydisplay/gpio_pin/gpio_pin.py b/micropython/pydisplay/gpio_pin/gpio_pin.py index a14ca95da..c9268a99f 100644 --- a/micropython/pydisplay/gpio_pin/gpio_pin.py +++ b/micropython/pydisplay/gpio_pin/gpio_pin.py @@ -2,12 +2,12 @@ # # SPDX-License-Identifier: MIT -from machine import Pin as _Pin +from machine import Pin as _Pin from sys import platform, implementation if platform == "pyboard": - import stm + import stm gpio_data = { "pyboard": { diff --git a/micropython/pydisplay/gpio_pin/manifest.py b/micropython/pydisplay/gpio_pin/manifest.py index e69de29bb..02330a21a 100644 --- a/micropython/pydisplay/gpio_pin/manifest.py +++ b/micropython/pydisplay/gpio_pin/manifest.py @@ -0,0 +1,3 @@ +metadata(description="GPIO Wrapper for machine.Pin", version="0.1.0") + +module("gpio_pin.py", opt=3) diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index e69de29bb..8917f3898 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("graphics") diff --git a/micropython/pydisplay/i80bus/i80bus.py b/micropython/pydisplay/i80bus/i80bus.py new file mode 100644 index 000000000..66c2ff24d --- /dev/null +++ b/micropython/pydisplay/i80bus/i80bus.py @@ -0,0 +1,366 @@ +# SPDX-FileCopyrightText: 2023 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +i80bus +""" + +from array import array +from uctypes import addressof +import struct +import micropython +from micropython import const + +try: + from typing import Optional +except ImportError: + pass + +# _I80BaseBus will work with either Pin class, but I80Bus will only work with GPIO_Pin +try: + from gpio_pin import GPIO_Pin as Pin +except ImportError: + from machine import Pin + +if 0: + ptr8 = ptr16 = ptr32 = None # For type hints + + +DC_CMD = const(0) +DC_DATA = const(1) +CS_ACTIVE = const(0) +CS_INACTIVE = const(1) +WR_ACTIVE = const(1) +WR_INACTIVE = const(0) + + +class _I80BaseBus: + """ + Base class for I80 bus communication. + + Args: + dc (int): The pin number for the data/command control. + cs (int): The pin number for the chip select. + wr (int): The pin number for the write control. + data (list[int]): A list of pin numbers for the data pins. + freq (int): The frequency for the bus. Defaults to 20,000,000. + """ + + def __init__( + self, + dc: int, + cs: int, + wr: int, + data: list[int], + freq: int = 20_000_000, + ) -> None: + print("I80Bus loading...") + # Not used in this class; may be used in subclasses like _i80bus_rp2.py + self._freq = freq + + # Create a list of Pin objects for the data pins + # NOTE: data8-15 are optional and not implemented in most subclasses + data_pins = [Pin(pin, Pin.OUT) for pin in data] + + # Setup the control pins + # _wr_active, _wr_inactive are boolean values + # indicating the level of the pin in that state. True is high, False is low. + self._dc: Pin = Pin(dc, Pin.OUT) + self._dc(DC_CMD) # Set the DC pin to the command level + + # If cs was not specified, set it to a lambda that does nothing + # so lines like the next won't fail. + self._cs: Pin = Pin(cs, Pin.OUT) if cs != -1 else lambda val: None + self._cs(CS_INACTIVE) # Set the CS pin to the inactive level + + self._wr: Pin = Pin(wr, Pin.OUT) + self._wr(WR_INACTIVE) # Set the WR pin to the inactive level + + self._buf1: bytearray = bytearray(1) + + self._setup(data_pins) + print("I80Bus loaded") + + @micropython.native + def send( + self, command: Optional[int] = None, data: Optional[memoryview] = None + ) -> None: + """ + Sends a command and/or data to the device. + Args: + command (Optional[int]): The command to send. Defaults to None. + data (Optional[memoryview]): The data to send. Defaults to None. + Returns: + None + """ + + self._cs(CS_ACTIVE) + + if command is not None: + struct.pack_into("B", self._buf1, 0, command) + self._dc(DC_CMD) + self._write(self._buf1, 1) + + if data and len(data): + self._dc(DC_DATA) + self._write(data, len(data)) + + self._cs(CS_INACTIVE) + + def deinit(self): + pass + + def __del__(self): + self.deinit() + + +class I80Bus(_I80BaseBus): + """ + Class for I80 bus communication. + + Args: + dc (int): The pin number for the data/command control. + cs (int): The pin number for the chip select. + wr (int): The pin number for the write control. + data (list[int]): A list of pin numbers for the data pins. + freq (int): The frequency for the bus. Defaults to 20,000,000. + """ + + def _setup(self, data_pins: list[Pin]) -> None: + # Make sure GPIO_Pin was imported + if not hasattr(Pin, "BSRR"): + raise ValueError("GPIO_Pin not imported") + + # If self._is_32bit is True the _write method will use a 32-bit set and a 32-bit + # clear register. Otherwise, the _write method will use set_reset registers + # which use the lower 16 bits for set and the upper 16 bits for clear. + self._is_32bit = True if self._wr.BSRR is None else False + + # Both lut mode and sequential mode need the write pin registers and masks saved to use in viper. + # Subclasses may not need the write pin registers and masks, so they are defined here instead of + # in __init__. Subclasses should override _setup. + if self._is_32bit: + self._wr_mask = self._wr_not_mask = 1 << self._wr.pin() + self._wr_reg = self._wr.gpio() + ( + self._wr.SET if WR_ACTIVE else self._wr.CLR + ) + self._wr_not_reg = self._wr.gpio() + ( + self._wr.CLR if WR_ACTIVE else self._wr.SET + ) + else: + self._wr_reg = self._wr_not_reg = self._wr.gpio() + self._wr.BSRR + self._wr_mask = 1 << (self._wr.pin() + (0 if WR_ACTIVE else 16)) + self._wr_not_mask = 1 << (self._wr.pin() + (16 if WR_ACTIVE else 0)) + + if False: # Set to True to print the write pin registers and masks + print( + f"\n{self._wr=}\n {self._wr_reg=:#010x}, {self._wr_mask=:#034b}\n {self._wr_not_reg=:#010x}, {self._wr_not_mask=:#034b}\n" + ) + + # Determine which mode, lut or sequential, to use + # If all pins are on the same port and sequential: + if all(p.port() == data_pins[0].port() for p in data_pins) and all( + data_pins[i].pin() + 1 == data_pins[i + 1].pin() + for i in range(len(data_pins) - 1) + ): + # Use sequential mode + self._setup_seq(data_pins) + else: + # Use LUT mode + self._setup_lut(data_pins) + + def _setup_lut(self, pins: list[Pin]) -> None: + """ + Setup lookup tables, pin data and the _write method for LUT mode. + """ + print("Using LUT mode") + if len(pins) != 8: + raise ValueError("LUT mode only supports 8 data pins") + self._write = self._write_lut + + # Setup the data for pin_data and the lookup tables + lut_len = 2 ** len(pins) # Number of entries per lut -- 256 for 8-bit bus width + port_list = [] # list of port numbers in use + for item in [p.port() for p in pins]: # Create a list of unique port numbers + if item not in port_list: + port_list.append(item) + # Map port numbers to lookup table index + lut_map = {port: i for i, port in enumerate(port_list)} + self._num_luts = len(lut_map) # Number of lookup tables + self._lookup_tables = [None] * self._num_luts # list of bytearray lookup tables + pin_masks = [None] * self._num_luts # list of 32-bit pin masks + regsA = [ + None + ] * self._num_luts # list of SET registers if _is_32bit else BSRR registers + regsB = [ + None + ] * self._num_luts # list of CLR registers if _is_32bit else unused + + # Create the pin_masks, populate the 2 reg lists and initialize the lookup_tables + # for each port of 16 or 32 pins. Will be saved in array pin_data later. + for i in range(self._num_luts): + port = port_list[i] + port_pins = [p for p in pins if p.port() == port] + first_pin = port_pins[0] + pin_mask = sum([1 << p.pin() for p in port_pins]) + if self._is_32bit: + self._lookup_tables[i] = array("I", [0] * lut_len) # 32-bit array + regsA[i] = first_pin.gpio() + first_pin.SET + regsB[i] = first_pin.gpio() + first_pin.CLR + else: + self._lookup_tables[i] = array("H", [0] * lut_len) # 16-bit array + regsA[i] = first_pin.gpio() + first_pin.BSRR + regsB[i] = 0x0 + pin_masks[i] = pin_mask + if False: # Set to True to print the pin data + print( + f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="" + ) + print(f"mask={pin_masks[i]:#034b}, pins={[p.pin() for p in port_pins]}") + + # Populate the lookup tables + for index in range( + lut_len + ): # Iterate through all possible 8-bit values (0 to 255) + for bit_number, pin in enumerate(pins): # Iterate through each pin + if index & (1 << bit_number): # If the bit is set in the index + # Get the current value for index from the appropriate lookup table + value = self._lookup_tables[lut_map[pin.port()]][index] + value |= 1 << pin.pin() # Update the value for the pin + self._lookup_tables[lut_map[pin.port()]][index] = ( + value # Save the value + ) + + # Save all settings in a struct-like array pin_data for use in viper. + # Could be merged with the first loop above, but left here for clarity. + pin_data = array("I", [0] * 4 * self._num_luts) + for i in range(self._num_luts): + pin_data[i * 4 + 0] = pin_masks[i] + pin_data[i * 4 + 1] = regsA[i] + pin_data[i * 4 + 2] = regsB[i] + pin_data[i * 4 + 3] = addressof(self._lookup_tables[i]) + + if False: # Set to True to print the lookup tables + print( + f"\nlut={i}: mask={pin_masks[i]:#034b}, {regsA[i]=:#010x}, {regsB[i]=:#010x}" + ) + for j in range(0, lut_len): + print(f" {j:3d}: {self._lookup_tables[i][j]:#034b}") + + self._pin_data = memoryview( + pin_data + ) # Save a memoryview into pin_data for use in viper + + @micropython.viper + def _write_lut(self, data: ptr8, length: int): + # Cache these values to avoid accessing the self namespace every iteration + wr_not_reg = ptr32(self._wr_not_reg) + wr_not_mask = int(self._wr_not_mask) + wr_reg = ptr32(self._wr_reg) + wr_mask = int(self._wr_mask) + is_32bit = bool(self._is_32bit) # noqa: F841 + pin_data = ptr32(self._pin_data) + num_luts = int(self._num_luts) + + last: int = -1 + for i in range(length): # Iterate through the data + wr_not_reg[0] = wr_not_mask # WR Inactive + val = data[i] # Get the value from the data + # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) + if val != last: + if False: + print(f"{val=:#010b} ({val=:#04x})") # noqa: E701 + for n in range(num_luts): # Iterate through the lookup tables + if False: + print(f"{ n=}") # noqa: E701 + pin_mask = pin_data[n * 4 + 0] # Get the pin mask + regA = ptr32(pin_data[n * 4 + 1]) # Get the SET or BSRR register + if True: # Should be `if is_32bit:` but not supported in viper + regB = ptr32( + pin_data[n * 4 + 2] + ) # Only need regB (CLEAR) for 32-bit + lut = ptr32(pin_data[n * 4 + 3]) # 32-bit lookup table + tx_value: int = lut[ + val + ] # Get the 32-bit value from the lookup table + regA[0] = tx_value # Set the bits that are on + regB[0] = tx_value ^ pin_mask # Clear the bits that are off + else: + lut = ptr16(pin_data[n * 4 + 3]) # 16-bit lookup table + tx_value: int = lut[ + val + ] # Get the 16-bit value from the lookup table + # Set the bits that are on and clear the bits that are off + regA[0] = (tx_value << 0) | ((tx_value ^ pin_mask) << 16) + if False: # Print debug info + print(f" {tx_value=:#034b}") + print(f" {pin_mask=:#034b}") + print( + f" wrote: {(tx_value | ((tx_value ^ pin_mask) << 16)):#034b}" + ) + # raise ValueError("Debugging") + last = val # Save the value for the next iteration + wr_reg[0] = wr_mask # WR Active + + def _setup_seq(self, pins: list[Pin]) -> None: + print("Using sequential mode") + if len(pins) == 8: + self._write = self._write_seq8 + elif len(pins) == 16: + self._write = self._write_seq16 + else: + raise ValueError("Sequential mode only supports 8 or 16 data pins") + + # Setup the data for pin_data + pin_mask = sum([1 << p.pin() for p in pins]) + first_pin = pins[0] + if self._is_32bit: + regA = first_pin.gpio() + first_pin.SET + regB = first_pin.gpio() + first_pin.CLR + else: + regA = first_pin.gpio() + first_pin.BSRR + regB = 0x0 + shift = first_pin.pin() + + # save all settings in an array pin_data for use in viper + pin_data = array("I", [0] * 4) + pin_data[0] = pin_mask + pin_data[1] = regA + pin_data[2] = regB + pin_data[3] = shift + self._pin_data = memoryview(pin_data) + + @micropython.viper + def _write_seq8(self, data: ptr8, length: int): + # Cache these values to avoid accessing the self namespace every iteration + wr_not_reg = ptr32(self._wr_not_reg) + wr_not_mask = int(self._wr_not_mask) + wr_reg = ptr32(self._wr_reg) + wr_mask = int(self._wr_mask) + is_32bit = bool(self._is_32bit) + pin_data = ptr32(self._pin_data) + + pin_mask = pin_data[0] + regA = ptr32(pin_data[1]) + regB = ptr32(pin_data[2]) + shift = pin_data[3] + + last: int = -1 + for i in range(length): # Iterate through the data + wr_not_reg[0] = wr_not_mask # WR Inactive + val = data[i] # Get the value from the data + # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) + if val != last: + tx_value: int = val << shift # Shift the value to the correct position + if is_32bit: + regA[0] = tx_value # Set the bits that are on + regB[0] = tx_value ^ pin_mask # Clear the bits that are off + else: + # Set the bits that are on and clear the bits that are off + regA[0] = tx_value | ((tx_value ^ pin_mask) << 16) + last = val # Save the value for the next iteration + wr_reg[0] = wr_mask # WR Active + + @micropython.viper + def _write_seq16(self, data: ptr16, length: int): + raise NotImplementedError("16 pin sequential mode not implemented") diff --git a/micropython/pydisplay/i80bus/manifest.py b/micropython/pydisplay/i80bus/manifest.py index e69de29bb..d31d5f2f9 100644 --- a/micropython/pydisplay/i80bus/manifest.py +++ b/micropython/pydisplay/i80bus/manifest.py @@ -0,0 +1,3 @@ +metadata(description="I80Bus Driver", version="0.1.0") + +module("i80bus.py", opt=3) diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index e69de29bb..d624644d5 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("palettes") diff --git a/micropython/pydisplay/spibus/manifest.py b/micropython/pydisplay/spibus/manifest.py index e69de29bb..564517f38 100644 --- a/micropython/pydisplay/spibus/manifest.py +++ b/micropython/pydisplay/spibus/manifest.py @@ -0,0 +1,3 @@ +metadata(description="SPIBus driver", version="0.1.0") + +module("spibus.py", opt=3) diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index e69de29bb..cd4f4f9a6 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -0,0 +1,2 @@ +metadata(version="0.1.0") +package("timer") From 9f826dfa9f8c15d07075f2508d6b832e6c0f1ffa Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Fri, 22 Nov 2024 22:02:17 -0600 Subject: [PATCH 04/35] . --- micropython/pydisplay/displaysys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/pydisplay/displaysys/README.md b/micropython/pydisplay/displaysys/README.md index ea5d10ddb..90bff7163 100644 --- a/micropython/pydisplay/displaysys/README.md +++ b/micropython/pydisplay/displaysys/README.md @@ -1,2 +1,2 @@ # displaysys -================= \ No newline at end of file +================= From 29106e0524d770ba2827b4120f0892ca6679cddb Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Fri, 22 Nov 2024 23:52:24 -0600 Subject: [PATCH 05/35] pydevices packages and modules --- .../displaybuf/displaybuf/__init__.py | 4 +- .../displaysys/busdisplay.py | 10 +- .../displaysys-busdisplay/manifest.py | 2 +- .../displaysys/fbdisplay.py | 4 +- .../displaysys-fbdisplay/manifest.py | 2 +- .../displaysys/jndisplay.py | 123 ++++++++++++++++++ .../displaysys-jndisplay/manifest.py | 3 + .../displaysys/pgdisplay.py | 4 +- .../displaysys-pgdisplay/manifest.py | 2 +- .../displaysys/psdisplay.py | 4 +- .../displaysys-psdisplay/manifest.py | 2 +- .../displaysys/sdldisplay/__init__.py | 4 +- .../sdldisplay/_sdl2_lib/_cpython.py | 2 +- .../sdldisplay/_sdl2_lib/_micropython.py | 2 +- .../displaysys-sdldisplay/manifest.py | 2 +- .../displaysys/displaysys/__init__.py | 3 +- .../displaysys/displaysys/_byteswap.py | 9 +- .../displaysys/displaysys/_byteswap_viper.py | 2 + .../pydisplay/eventsys/eventsys/device.py | 2 +- .../examples/eventsys_encoder_test.py | 2 +- .../eventsys/examples/eventsys_touch_test.py | 2 +- .../pydisplay/graphics/graphics/_framebuf.py | 4 +- micropython/pydisplay/i80bus/i80bus.py | 61 +++------ .../{palette_cube.py => palettes_cube.py} | 0 ...lette_material.py => palettes_material.py} | 0 .../{palette_wheel.py => palettes_wheel.py} | 0 micropython/pydisplay/spibus/spibus.py | 4 +- 27 files changed, 184 insertions(+), 75 deletions(-) create mode 100644 micropython/pydisplay/displaysys/displaysys-jndisplay/displaysys/jndisplay.py create mode 100644 micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py rename micropython/pydisplay/palettes/examples/{palette_cube.py => palettes_cube.py} (100%) rename micropython/pydisplay/palettes/examples/{palette_material.py => palettes_material.py} (100%) rename micropython/pydisplay/palettes/examples/{palette_wheel.py => palettes_wheel.py} (100%) diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py index d7550f1b7..9ad59f4a4 100644 --- a/micropython/pydisplay/displaybuf/displaybuf/__init__.py +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -6,7 +6,7 @@ `displaybuf` ==================================================== -FrameBuffer wrapper for using framebuf based GUIs with pydisplay. +FrameBuffer wrapper for using framebuf based GUIs with displaysys. Works with MicroPython Nano-GUI, Micro-GUI and MicroPython-Touch from Peter Hinch, but may also be used without them. @@ -58,7 +58,7 @@ def alloc_buffer(size): class DisplayBuffer(framebuf.FrameBuffer): """ - DisplayBuffer: A class to wrap an pydisplay driver and provide a framebuf + DisplayBuffer: A class to wrap an displaysys driver and provide a framebuf compatible interface to it. It provides a show() method to copy the framebuf to the display. The show() method is optimized for the format. The format must be one of the following: diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py index 908514dbe..8f7d0c2cd 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py @@ -3,10 +3,10 @@ # SPDX-License-Identifier: MIT """ -pydisplay busdisplay +displaysys.busdisplay """ -from displaycore import DisplayDriver +from displaysys import DisplayDriver from micropython import const import struct import sys @@ -147,7 +147,7 @@ def __init__( data_as_commands=False, # For color OLEDs single_byte_bounds=False, # For color OLEDs ): - print(f"Started BusDisplay") + print("Started BusDisplay") gc.collect() self.display_bus = display_bus self._width = width @@ -217,7 +217,7 @@ def __init__( self.brightness = brightness gc.collect() - print(f"Finished BusDisplay") + print("Finished BusDisplay") ############### Required API Methods ################ @@ -518,7 +518,7 @@ def sleep_mode(self, value: bool) -> None: ############### Class Specific Methods ############## def _set_window(self, x1, y1, x2, y2): - # See https://github.com/adafruit/Adafruit_Blinka_Displayio/blob/main/displayio/_displaycore.py#L271-L363 + # See https://github.com/adafruit/Adafruit_Blinka_Displayio/blob/main/displayio/_displaysys.py#L271-L363 # TODO: Add `if self._single_byte_bounds is True:` for Column and Row _param_buf packing # Column addresses diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 9466dd44b..4ed1d5887 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,3 +1,3 @@ metadata(version="0.1.0") require("displaysys") -package("lordisplaysysa") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py index 1a784d27a..e4fcdaffb 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py @@ -3,10 +3,10 @@ # SPDX-License-Identifier: MIT """ -pydisplay fbdisplay +displaysys.fbdisplay """ -from displaycore import DisplayDriver +from displaysys import DisplayDriver class FBDisplay(DisplayDriver): diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 9466dd44b..4ed1d5887 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,3 +1,3 @@ metadata(version="0.1.0") require("displaysys") -package("lordisplaysysa") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/displaysys/jndisplay.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/displaysys/jndisplay.py new file mode 100644 index 000000000..44c25ec85 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/displaysys/jndisplay.py @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +displaysys.jndisplay +""" + +from displaysys import DisplayDriver, color_rgb +from IPython.display import display, update_display +from PIL import Image, ImageDraw + + +class JNDisplay(DisplayDriver): + """ + A class to emulate a display on Jupyter Notebook. + + Args: + width (int): The width of the display. + height (int): The height of the display. + + Attributes: + color_depth (int): The color depth of the display + """ + + _next_display_id = 0 + + def __init__(self, width, height): + self._display_id = f"JNDisplay_{JNDisplay._next_display_id}" + JNDisplay._next_display_id += 1 + self._width = width + self._height = height + self._requires_byteswap = False + self._rotation = 0 + self.color_depth = 16 + self._buffer = Image.new("RGB", (self.width, self.height)) + self._draw = ImageDraw.Draw(self._buffer) + + super().__init__(auto_refresh=True) + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Initializes the display instance. Called by __init__ and rotation setter. + """ + display(self._buffer, display_id=self._display_id) + + def fill_rect(self, x, y, w, h, c): + """ + Fills a rectangle with the given color. + + Args: + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle. + h (int): The height of the rectangle. + c (int): The color to fill the rectangle with. + + Returns: + (tuple): A tuple containing the x, y, w, h values + """ + color = c & 0xFFFF + r, g, b = color_rgb(color) + x2 = x + w + y2 = y + h + top = min(y, y2) + left = min(x, x2) + bottom = max(y, y2) + right = max(x, x2) + self._draw.rectangle([(left, top), (right, bottom)], fill=(r, g, b)) + return (x, y, w, h) + + def blit_rect(self, buf, x, y, w, h): + """ + Blits a buffer to the display at the given coordinates. + + Args: + buf (bytearray): The buffer to blit to the display. + x (int): The x-coordinate of the top-left corner of the buffer. + y (int): The y-coordinate of the top-left corner of the buffer. + w (int): The width of the buffer. + h (int): The height of the buffer. + + Returns: + (tuple): A tuple containing the x, y, w, h values. + """ + + BPP = self.color_depth // 8 + if x < 0 or y < 0 or x + w > self.width or y + h > self.height: + raise ValueError("The provided x, y, w, h values are out of range") + if len(buf) != w * h * BPP: + raise ValueError("The source buffer is not the correct size") + + for j in range(h): + for i in range(w): + color = buf[(j * w + i) * BPP : (j * w + i) * BPP + BPP] + self.pixel(x + i, y + j, color) + return (x, y, w, h) + + def pixel(self, x, y, c): + """ + Sets a pixel to the given color. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color to set the pixel to. + + Returns: + (tuple): A tuple containing the x, y, w and h values. + """ + r, g, b = color_rgb(c) + self._draw.point((x, y), fill=(r, g, b)) + return (x, y, 1, 1) + + ############### Optional API Methods ################ + + def show(self) -> None: + """ + Updates the display with the current buffer. + """ + update_display(self._buffer, display_id=self._display_id) diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py new file mode 100644 index 000000000..4ed1d5887 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -0,0 +1,3 @@ +metadata(version="0.1.0") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py index 1c3a09d58..50a67891b 100755 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py @@ -36,7 +36,7 @@ class PGDisplay(DisplayDriver): height (int, optional): The height of the display. Defaults to 240. rotation (int, optional): The rotation of the display. Defaults to 0. color_depth (int, optional): The color depth of the display. Defaults to 16. - title (str, optional): The title of the display window. Defaults to "pydisplay". + title (str, optional): The title of the display window. Defaults to "displaysys". scale (float, optional): The scale of the display. Defaults to 1.0. window_flags (int, optional): The flags for creating the display window. Defaults to pg.SHOWN @@ -51,7 +51,7 @@ def __init__( height=240, rotation=0, color_depth=16, - title="pydisplay", + title="displaysys", scale=1.0, window_flags=pg.SHOWN, ): diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 9466dd44b..4ed1d5887 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,3 +1,3 @@ metadata(version="0.1.0") require("displaysys") -package("lordisplaysysa") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py index fd6fb1ad0..f820969d9 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py @@ -3,10 +3,10 @@ # SPDX-License-Identifier: MIT """ -pydisplay psdisplay +displaysys.psdisplay """ -from displaycore import DisplayDriver, color_rgb +from displaysys import DisplayDriver, color_rgb from pyscript.ffi import create_proxy # type: ignore from js import document, console # type: ignore diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 9466dd44b..4ed1d5887 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,3 +1,3 @@ metadata(version="0.1.0") require("displaysys") -package("lordisplaysysa") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py index 3b2ddcc8e..1bf189230 100755 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py @@ -3,10 +3,10 @@ # SPDX-License-Identifier: MIT """ -pydisplay sdldisplay +displaysys.sdldisplay """ -from displaycore import DisplayDriver, color_rgb +from displaysys import DisplayDriver, color_rgb from eventsys import Events from sys import implementation from ._sdl2_lib import ( diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py index f6187c26f..0b7d39506 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT """ -An implementation of SDL2 written in CPython using ctypes. +A bare implementation of SDL2 written in CPython using ctypes. """ import ctypes diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py index 4f8668b08..fda726fa3 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT """ -An implementation of SDL2 written in MicroPython. +A bare implementation of SDL2 written in MicroPython using ffi """ import uctypes diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 9466dd44b..4ed1d5887 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,3 +1,3 @@ metadata(version="0.1.0") require("displaysys") -package("lordisplaysysa") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index db8fac60d..fcb6ca18b 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT """ -`displaycore` +`displaysys` ==================================================== A collection of classes and functions for working with displays and input devices @@ -32,6 +32,7 @@ def new_buffer(size): """ return memoryview(bytearray(size)) + def color888(r, g, b): """ Convert RGB values to a 24-bit color value. diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py index 4498ac2bd..74db96e98 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT """ -`displaycore._byteswap` +`displaysys._byteswap` ==================================================== A function to swap the bytes of a buffer in place. 3 implementations are provided: @@ -15,7 +15,7 @@ try: # import byteswap from MicroPython if available - from byteswap import byteswap # type: ignore + from byteswap import byteswap # type: ignore except ImportError: try: # import numpy if available @@ -24,7 +24,8 @@ import numpy as np except ImportError: # import numpy for CircuitPython or MicroPython with numpy module - from ulab import numpy as np # type: ignore + from ulab import numpy as np # type: ignore + def byteswap(buf): """ Swap the bytes of a 16-bit buffer in place using numpy. @@ -35,12 +36,14 @@ def byteswap(buf): try: # import byteswap_viper if available from ._byteswap_viper import byteswap_viper + def byteswap(buf): """ Swap the bytes of a 16-bit buffer in place using viper. """ byteswap_viper(buf, len(buf)) except Exception: + def byteswap(buf): """ Swap the bytes of a 16-bit buffer in place with no dependencies. diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py index fbd2777b5..3c0df05fc 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py @@ -1,9 +1,11 @@ import micropython if 0: + class ptr8: pass + @micropython.viper def byteswap_viper(buf: ptr8, buf_size: int): # noqa: F821 """ diff --git a/micropython/pydisplay/eventsys/eventsys/device.py b/micropython/pydisplay/eventsys/eventsys/device.py index 08769a17d..33f0131bc 100644 --- a/micropython/pydisplay/eventsys/eventsys/device.py +++ b/micropython/pydisplay/eventsys/eventsys/device.py @@ -494,7 +494,7 @@ def rotation_table(self): list: The rotation table. """ return self._data2 - + @rotation_table.setter def rotation_table(self, value): """ diff --git a/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py index 76453443d..da1fd4422 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py @@ -1,5 +1,5 @@ """ -A simple test of an encoder in pydisplay. +A simple test of an encoder in eventsys. """ from board_config import display_drv, broker diff --git a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py index 4a639e71d..17e2b9ea3 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py @@ -1,5 +1,5 @@ """ -pydisplay_touch_test.py - Touch rotation test. +eventsys_touch_test.py - Touch rotation test. Tests the touch driver and finds the correct rotation masks for the touch screen. Sets the rotation to each of 4 possible values and asks the user to touch the rectangle in each of the 4 corners. Then it prints the touch_rotation_table that should be set in board_config.py. diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py index 4366efbc8..074cb336b 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -287,7 +287,9 @@ def fill_rect(framebuf, x, y, width, height, color): rgb565_color_int = int.from_bytes(rgb565_color, "little") arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) for _y in range(y, y + height): - arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = rgb565_color_int + arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = ( + rgb565_color_int + ) else: for _y in range(y, y + height): offset = _y * framebuf._stride diff --git a/micropython/pydisplay/i80bus/i80bus.py b/micropython/pydisplay/i80bus/i80bus.py index 66c2ff24d..7a2ff154b 100644 --- a/micropython/pydisplay/i80bus/i80bus.py +++ b/micropython/pydisplay/i80bus/i80bus.py @@ -6,7 +6,7 @@ """ from array import array -from uctypes import addressof +from uctypes import addressof import struct import micropython from micropython import const @@ -20,7 +20,7 @@ try: from gpio_pin import GPIO_Pin as Pin except ImportError: - from machine import Pin + from machine import Pin if 0: ptr8 = ptr16 = ptr32 = None # For type hints @@ -82,9 +82,7 @@ def __init__( print("I80Bus loaded") @micropython.native - def send( - self, command: Optional[int] = None, data: Optional[memoryview] = None - ) -> None: + def send(self, command: Optional[int] = None, data: Optional[memoryview] = None) -> None: """ Sends a command and/or data to the device. Args: @@ -141,12 +139,8 @@ def _setup(self, data_pins: list[Pin]) -> None: # in __init__. Subclasses should override _setup. if self._is_32bit: self._wr_mask = self._wr_not_mask = 1 << self._wr.pin() - self._wr_reg = self._wr.gpio() + ( - self._wr.SET if WR_ACTIVE else self._wr.CLR - ) - self._wr_not_reg = self._wr.gpio() + ( - self._wr.CLR if WR_ACTIVE else self._wr.SET - ) + self._wr_reg = self._wr.gpio() + (self._wr.SET if WR_ACTIVE else self._wr.CLR) + self._wr_not_reg = self._wr.gpio() + (self._wr.CLR if WR_ACTIVE else self._wr.SET) else: self._wr_reg = self._wr_not_reg = self._wr.gpio() + self._wr.BSRR self._wr_mask = 1 << (self._wr.pin() + (0 if WR_ACTIVE else 16)) @@ -160,8 +154,7 @@ def _setup(self, data_pins: list[Pin]) -> None: # Determine which mode, lut or sequential, to use # If all pins are on the same port and sequential: if all(p.port() == data_pins[0].port() for p in data_pins) and all( - data_pins[i].pin() + 1 == data_pins[i + 1].pin() - for i in range(len(data_pins) - 1) + data_pins[i].pin() + 1 == data_pins[i + 1].pin() for i in range(len(data_pins) - 1) ): # Use sequential mode self._setup_seq(data_pins) @@ -189,12 +182,8 @@ def _setup_lut(self, pins: list[Pin]) -> None: self._num_luts = len(lut_map) # Number of lookup tables self._lookup_tables = [None] * self._num_luts # list of bytearray lookup tables pin_masks = [None] * self._num_luts # list of 32-bit pin masks - regsA = [ - None - ] * self._num_luts # list of SET registers if _is_32bit else BSRR registers - regsB = [ - None - ] * self._num_luts # list of CLR registers if _is_32bit else unused + regsA = [None] * self._num_luts # list of SET registers if _is_32bit else BSRR registers + regsB = [None] * self._num_luts # list of CLR registers if _is_32bit else unused # Create the pin_masks, populate the 2 reg lists and initialize the lookup_tables # for each port of 16 or 32 pins. Will be saved in array pin_data later. @@ -213,23 +202,17 @@ def _setup_lut(self, pins: list[Pin]) -> None: regsB[i] = 0x0 pin_masks[i] = pin_mask if False: # Set to True to print the pin data - print( - f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="" - ) + print(f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="") print(f"mask={pin_masks[i]:#034b}, pins={[p.pin() for p in port_pins]}") # Populate the lookup tables - for index in range( - lut_len - ): # Iterate through all possible 8-bit values (0 to 255) + for index in range(lut_len): # Iterate through all possible 8-bit values (0 to 255) for bit_number, pin in enumerate(pins): # Iterate through each pin if index & (1 << bit_number): # If the bit is set in the index # Get the current value for index from the appropriate lookup table value = self._lookup_tables[lut_map[pin.port()]][index] value |= 1 << pin.pin() # Update the value for the pin - self._lookup_tables[lut_map[pin.port()]][index] = ( - value # Save the value - ) + self._lookup_tables[lut_map[pin.port()]][index] = value # Save the value # Save all settings in a struct-like array pin_data for use in viper. # Could be merged with the first loop above, but left here for clarity. @@ -247,12 +230,10 @@ def _setup_lut(self, pins: list[Pin]) -> None: for j in range(0, lut_len): print(f" {j:3d}: {self._lookup_tables[i][j]:#034b}") - self._pin_data = memoryview( - pin_data - ) # Save a memoryview into pin_data for use in viper + self._pin_data = memoryview(pin_data) # Save a memoryview into pin_data for use in viper @micropython.viper - def _write_lut(self, data: ptr8, length: int): + def _write_lut(self, data: ptr8, length: int): # Cache these values to avoid accessing the self namespace every iteration wr_not_reg = ptr32(self._wr_not_reg) wr_not_mask = int(self._wr_not_mask) @@ -276,20 +257,14 @@ def _write_lut(self, data: ptr8, length: int): pin_mask = pin_data[n * 4 + 0] # Get the pin mask regA = ptr32(pin_data[n * 4 + 1]) # Get the SET or BSRR register if True: # Should be `if is_32bit:` but not supported in viper - regB = ptr32( - pin_data[n * 4 + 2] - ) # Only need regB (CLEAR) for 32-bit + regB = ptr32(pin_data[n * 4 + 2]) # Only need regB (CLEAR) for 32-bit lut = ptr32(pin_data[n * 4 + 3]) # 32-bit lookup table - tx_value: int = lut[ - val - ] # Get the 32-bit value from the lookup table + tx_value: int = lut[val] # Get the 32-bit value from the lookup table regA[0] = tx_value # Set the bits that are on regB[0] = tx_value ^ pin_mask # Clear the bits that are off else: lut = ptr16(pin_data[n * 4 + 3]) # 16-bit lookup table - tx_value: int = lut[ - val - ] # Get the 16-bit value from the lookup table + tx_value: int = lut[val] # Get the 16-bit value from the lookup table # Set the bits that are on and clear the bits that are off regA[0] = (tx_value << 0) | ((tx_value ^ pin_mask) << 16) if False: # Print debug info @@ -331,7 +306,7 @@ def _setup_seq(self, pins: list[Pin]) -> None: self._pin_data = memoryview(pin_data) @micropython.viper - def _write_seq8(self, data: ptr8, length: int): + def _write_seq8(self, data: ptr8, length: int): # Cache these values to avoid accessing the self namespace every iteration wr_not_reg = ptr32(self._wr_not_reg) wr_not_mask = int(self._wr_not_mask) @@ -362,5 +337,5 @@ def _write_seq8(self, data: ptr8, length: int): wr_reg[0] = wr_mask # WR Active @micropython.viper - def _write_seq16(self, data: ptr16, length: int): + def _write_seq16(self, data: ptr16, length: int): raise NotImplementedError("16 pin sequential mode not implemented") diff --git a/micropython/pydisplay/palettes/examples/palette_cube.py b/micropython/pydisplay/palettes/examples/palettes_cube.py similarity index 100% rename from micropython/pydisplay/palettes/examples/palette_cube.py rename to micropython/pydisplay/palettes/examples/palettes_cube.py diff --git a/micropython/pydisplay/palettes/examples/palette_material.py b/micropython/pydisplay/palettes/examples/palettes_material.py similarity index 100% rename from micropython/pydisplay/palettes/examples/palette_material.py rename to micropython/pydisplay/palettes/examples/palettes_material.py diff --git a/micropython/pydisplay/palettes/examples/palette_wheel.py b/micropython/pydisplay/palettes/examples/palettes_wheel.py similarity index 100% rename from micropython/pydisplay/palettes/examples/palette_wheel.py rename to micropython/pydisplay/palettes/examples/palettes_wheel.py diff --git a/micropython/pydisplay/spibus/spibus.py b/micropython/pydisplay/spibus/spibus.py index 6115d2136..61e4eb24b 100644 --- a/micropython/pydisplay/spibus/spibus.py +++ b/micropython/pydisplay/spibus/spibus.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: MIT """ -pydisplay spibus +spibus """ -from machine import Pin, SPI +from machine import Pin, SPI import struct import micropython from micropython import const From 3052848159a1f36d2a84a751882a730cfc63c06b Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 02:33:53 -0600 Subject: [PATCH 06/35] pydisplay packages --- .../displaybuf/examples/color_setup.py | 22 - .../displaybuf/examples/hardware_setup.py | 59 -- .../examples/board_config.py | 104 +-- .../examples/board_config.py | 71 ++ .../examples/board_config.py | 93 ++- .../examples/board_config.py | 76 ++- .../examples/board_config.py | 71 ++ .../displaysys/displaysys/busdisplay.py | 607 ++++++++++++++++++ .../examples/displaysys_block_test.py | 0 .../examples/displaysys_fill_rect_test.py | 0 .../examples/displaysys_simpletest.py | 0 11 files changed, 931 insertions(+), 172 deletions(-) delete mode 100644 micropython/pydisplay/displaybuf/examples/color_setup.py delete mode 100644 micropython/pydisplay/displaybuf/examples/hardware_setup.py create mode 100755 micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py mode change 100644 => 100755 micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py create mode 100755 micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py rename micropython/pydisplay/displaysys/{ => displaysys}/examples/displaysys_block_test.py (100%) rename micropython/pydisplay/displaysys/{ => displaysys}/examples/displaysys_fill_rect_test.py (100%) rename micropython/pydisplay/displaysys/{ => displaysys}/examples/displaysys_simpletest.py (100%) diff --git a/micropython/pydisplay/displaybuf/examples/color_setup.py b/micropython/pydisplay/displaybuf/examples/color_setup.py deleted file mode 100644 index a83e9444f..000000000 --- a/micropython/pydisplay/displaybuf/examples/color_setup.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -color_setup.py - color setup for DisplayBuffer with pydisplay -Usage: - from color_setup import ssd - -""" - -from displaybuf import DisplayBuffer as SSD -from board_config import display_drv -import sys - -# SSD.RGB565 is supported by all implementations, so set it as the default format -# Micropython also supports SSD.GS4_HMSB and SSD.GS8 - -if sys.implementation.name == "micropython": - # format = SSD.GS4_HMSB # 4-bit (16 item) lookup table of 16-bit RGB565 colors; w*h/2 buffer - # format = SSD.GS8 # 256 8-bit RGB332 colors; w*h buffer - format = SSD.RGB565 # all 65,536 16-bit RGB565 colors; w*h*2 buffer -else: - format = SSD.RGB565 - -ssd = SSD(display_drv, format) diff --git a/micropython/pydisplay/displaybuf/examples/hardware_setup.py b/micropython/pydisplay/displaybuf/examples/hardware_setup.py deleted file mode 100644 index c9604b28e..000000000 --- a/micropython/pydisplay/displaybuf/examples/hardware_setup.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -hardware_setup.py - hardware setup for MicroPython-Touch using DisplayBuffer on pydisplay -See: https://github.com/peterhinch/micropython-touch - -Usage: - from hardware_setup import display - -""" - -from displaybuf import DisplayBuffer as SSD -from board_config import display_drv, broker - -# format = SSD.GS4_HMSB # 4-bit (16 item) lookup table of 16-bit RGB565 colors; w*h/2 buffer -# format = SSD.GS8 # 256 8-bit RGB332 colors; w*h buffer -format = SSD.RGB565 # all 65,536 16-bit RGB565 colors; w*h*2 buffer - -ssd = SSD(display_drv, format) - - -# enable screenshot functionality -def screenshot(event): - if event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 3: - ssd.screenshot() - - -broker.subscribe(screenshot, event_types=[broker.Events.MOUSEBUTTONDOWN]) -# End screenshot functionality - - -class Poller: - def __init__(self, poll_func): - self._poll_func = poll_func - self._touched = False - self.col = None - self.row = None - - def poll(self): - self._poll_func() - return True if self._touched else False - - def callback(self, event): - if event.type == broker.Events.MOUSEMOTION and event.buttons[0] == 1: - self.col, self.row = event.pos - self._touched = True - elif event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 1: - self.col, self.row = event.pos - self._touched = True - elif event.type == broker.Events.MOUSEBUTTONUP and event.button == 1: - self._touched = False - - -tpad = Poller(broker.poll) -broker.subscribe( - tpad.callback, event_types=[broker.Events.MOUSEMOTION, broker.Events.MOUSEBUTTONDOWN, broker.Events.MOUSEBUTTONUP] -) - -from gui.core.tgui import Display # noqa: E402 - -display = Display(ssd, tpad) diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py index 085ec53e9..f79f55bd8 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -2,22 +2,22 @@ # Similar configs may be available for RGBMatrix, is31fl3741 and picodvi from rgbframebuffer import RGBFrameBuffer # type: ignore -# from machine import I2C, Pin # type: ignore -# from pca9554 import PCA9554 # type: ignore -# from ft6x36 import FT6x36 # type: ignore -# from fbdisplay import FBDisplay -# import eventsys.device as device +from machine import I2C, Pin # type: ignore +from pca9554 import PCA9554 # type: ignore +from ft6x36 import FT6x36 # type: ignore +from displaysys.fbdisplay import FBDisplay +import eventsys.device as device -# def send_init_sequence(init_sequence, mosi, sck, cs): -# cs(0) -# for byte in init_sequence: -# for _ in range(8): -# mosi(byte & 0x80) -# sck(1) -# byte <<= 1 -# sck(0) -# cs(1) +def send_init_sequence(init_sequence, mosi, sck, cs): + cs(0) + for byte in init_sequence: + for _ in range(8): + mosi(byte & 0x80) + sck(1) + byte <<= 1 + sck(0) + cs(1) tft_pins = { @@ -47,44 +47,44 @@ "pclk_idle_high": False, } -# init_sequence = bytes() -# -# i2c = I2C(0, sda=Pin(8), scl=Pin(18), freq=100000) -# iox = PCA9554(i2c, address=0x38) -# btn_down = iox.Pin(6, Pin.IN) -# btn_up = iox.Pin(5, Pin.IN) -# reset = iox.Pin(2, Pin.OUT, value=1) -# backlight = iox.Pin(4, Pin.OUT, value=1) -# -# send_init_sequence(init_sequence, mosi=iox.Pin(7, Pin.OUT), -# sck=iox.Pin(0, Pin.OUT, value=0), cs=iox.Pin(1, Pin.OUT, value=1)) +init_sequence = bytes() + +i2c = I2C(0, sda=Pin(8), scl=Pin(18), freq=100000) +iox = PCA9554(i2c, address=0x38) +btn_down = iox.Pin(6, Pin.IN) +btn_up = iox.Pin(5, Pin.IN) +reset = iox.Pin(2, Pin.OUT, value=1) +backlight = iox.Pin(4, Pin.OUT, value=1) + +send_init_sequence(init_sequence, mosi=iox.Pin(7, Pin.OUT), + sck=iox.Pin(0, Pin.OUT, value=0), cs=iox.Pin(1, Pin.OUT, value=1)) fb = RGBFrameBuffer(**tft_pins, **tft_timings) -# mv = memoryview(fb) -# mv[:] = b'\xFF' * len(mv) -# fb.refresh() -# -# touch_drv = FT6x36(i2c, address=0x48) #, irq = iox.Pin(3, Pin.OUT)) -# -# def touch_read_func(): -# touches = touch_drv.touches -# if len(touches): -# return touches[0]['x'], touches[0]['y'] -# return None -# -# -# # Typical board_config.py setup from here on out -# -# display_drv = FBDisplay(fb) -# -# touch_rotation_table=(0, 0, 0, 0) -# -# broker = device.Broker() -# -# touch_dev = broker.create_device( -# type=device.Types.TOUCH, -# read=touch_read_func, -# data=display_drv, -# data2=touch_rotation_table, -# ) +mv = memoryview(fb) +mv[:] = b'\xFF' * len(mv) +fb.refresh() + +touch_drv = FT6x36(i2c, address=0x48) #, irq = iox.Pin(3, Pin.OUT)) + +def touch_read_func(): + touches = touch_drv.touches + if len(touches): + return touches[0]['x'], touches[0]['y'] + return None + + +# Typical board_config.py setup from here on out + +display_drv = FBDisplay(fb) + +touch_rotation_table=(0, 0, 0, 0) + +broker = device.Broker() + +touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_read_func, + data=display_drv, + data2=touch_rotation_table, +) diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py new file mode 100755 index 000000000..3909e255c --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py @@ -0,0 +1,71 @@ +""" +Combination board configuration for desktop, pyscript and jupyter notebook platforms. +""" + +width = 320 +height = 480 +rotation = 0 +scale = 2.0 + +_ps = _jn = False +try: + import pyscript # type: ignore # noqa: F401 + + _ps = True +except ImportError: + try: + get_ipython() # type: ignore # noqa: F821 + _jn = True + except NameError: + pass + +if _ps: + from displaysys.psdisplay import PSDisplay, PSDevices + import eventsys.device as device + + display_drv = PSDisplay("display_canvas", width, height) + + broker = device.Broker() + + touch_drv = PSDevices("display_canvas") + + touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, + ) +elif _jn: + from displaysys.jndisplay import JNDisplay + import eventsys.device as device + + broker = device.Broker() + + display_drv = JNDisplay(width, height) +else: + import eventsys.device as device + import sys + try: + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll + except ImportError: + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll + + + display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + color_depth=16, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, + ) + + broker = device.Broker() + + events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, + ) + +display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index d1ec2de58..3909e255c 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -1,34 +1,71 @@ """ -Combination board configuration for desktop platforms. - -Tested with CPython on Linux, Windows and ChromeOS. -Tested with MicroPython on Linux. -Should work on MacOS, but not tested. +Combination board configuration for desktop, pyscript and jupyter notebook platforms. """ -import eventsys.device as device -import sys +width = 320 +height = 480 +rotation = 0 +scale = 2.0 +_ps = _jn = False try: - from pgdisplay import PGDisplay as DTDisplay, poll + import pyscript # type: ignore # noqa: F401 + + _ps = True except ImportError: - from sdldisplay import SDLDisplay as DTDisplay, poll - - -display_drv = DTDisplay( - width=320, - height=480, - rotation=0, - color_depth=16, - title=f"{sys.implementation.name} on {sys.platform}", - scale=1.0, -) - -broker = device.Broker() - -events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, -) + try: + get_ipython() # type: ignore # noqa: F821 + _jn = True + except NameError: + pass + +if _ps: + from displaysys.psdisplay import PSDisplay, PSDevices + import eventsys.device as device + + display_drv = PSDisplay("display_canvas", width, height) + + broker = device.Broker() + + touch_drv = PSDevices("display_canvas") + + touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, + ) +elif _jn: + from displaysys.jndisplay import JNDisplay + import eventsys.device as device + + broker = device.Broker() + + display_drv = JNDisplay(width, height) +else: + import eventsys.device as device + import sys + try: + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll + except ImportError: + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll + + + display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + color_depth=16, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, + ) + + broker = device.Broker() + + events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, + ) + +display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py old mode 100644 new mode 100755 index 1cfb995bb..3909e255c --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -1,17 +1,71 @@ -"""board_config.py - board configuration for PyScript""" +""" +Combination board configuration for desktop, pyscript and jupyter notebook platforms. +""" -from psdisplay import PSDisplay, PSDevices -import eventsys.device as device +width = 320 +height = 480 +rotation = 0 +scale = 2.0 +_ps = _jn = False +try: + import pyscript # type: ignore # noqa: F401 -display_drv = PSDisplay("display_canvas", 480, 320) + _ps = True +except ImportError: + try: + get_ipython() # type: ignore # noqa: F821 + _jn = True + except NameError: + pass -broker = device.Broker() +if _ps: + from displaysys.psdisplay import PSDisplay, PSDevices + import eventsys.device as device -touch_drv = PSDevices("display_canvas") + display_drv = PSDisplay("display_canvas", width, height) -touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, -) + broker = device.Broker() + + touch_drv = PSDevices("display_canvas") + + touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, + ) +elif _jn: + from displaysys.jndisplay import JNDisplay + import eventsys.device as device + + broker = device.Broker() + + display_drv = JNDisplay(width, height) +else: + import eventsys.device as device + import sys + try: + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll + except ImportError: + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll + + + display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + color_depth=16, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, + ) + + broker = device.Broker() + + events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, + ) + +display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py new file mode 100755 index 000000000..3909e255c --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -0,0 +1,71 @@ +""" +Combination board configuration for desktop, pyscript and jupyter notebook platforms. +""" + +width = 320 +height = 480 +rotation = 0 +scale = 2.0 + +_ps = _jn = False +try: + import pyscript # type: ignore # noqa: F401 + + _ps = True +except ImportError: + try: + get_ipython() # type: ignore # noqa: F821 + _jn = True + except NameError: + pass + +if _ps: + from displaysys.psdisplay import PSDisplay, PSDevices + import eventsys.device as device + + display_drv = PSDisplay("display_canvas", width, height) + + broker = device.Broker() + + touch_drv = PSDevices("display_canvas") + + touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, + ) +elif _jn: + from displaysys.jndisplay import JNDisplay + import eventsys.device as device + + broker = device.Broker() + + display_drv = JNDisplay(width, height) +else: + import eventsys.device as device + import sys + try: + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll + except ImportError: + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll + + + display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + color_depth=16, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, + ) + + broker = device.Broker() + + events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, + ) + +display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py new file mode 100644 index 000000000..8f7d0c2cd --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py @@ -0,0 +1,607 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett and Kevin Schlosser +# +# SPDX-License-Identifier: MIT + +""" +displaysys.busdisplay +""" + +from displaysys import DisplayDriver +from micropython import const +import struct +import sys +import gc + +try: + from typing import Optional +except ImportError: + pass + +if sys.implementation.name == "micropython": + from machine import Pin + from time import sleep_ms + from micropython import alloc_emergency_exception_buf + + alloc_emergency_exception_buf(256) +elif sys.implementation.name == "circuitpython": + import digitalio + from time import sleep + + def sleep_ms(ms): + return sleep(ms / 1000) +else: + raise ImportError("BusDisplay is not supported on this platform.") + + +gc.collect() + +# MIPI DCS (Display Command Set) Command Constants +_INVOFF = const(0x20) +_INVON = const(0x21) +_CASET = const(0x2A) +_RASET = const(0x2B) +_RAMWR = const(0x2C) +_COLMOD = const(0x3A) +_MADCTL = const(0x36) +_RAMCONT = const(0x3C) +_SWRESET = const(0x01) +_SLPIN = const(0x10) +_SLPOUT = const(0x11) +_VSCRDEF = const(0x33) +_VSCSAD = const(0x37) + +# fmt: off + +# MIPI DCS MADCTL bits +# Bits 0 (Flip Vertical) and 1 (Flip Horizontal) affect how the display is refreshed, not how frame memory is written. +# Instead of using them, we only change Bits 6 (column/horizontal) and 7 (page/vertical). +_RGB = const(0x00) # (Bit 3: 0=RGB order, 1=BGR order) +_BGR = const(0x08) # (Bit 3: 0=RGB order, 1=BGR order) +_MADCTL_MH = const(0x04) # Refresh 0=Left to Right, 1=Right to Left (Bit 2: Display Data Latch Order) +_MADCTL_ML = const(0x10) # Refresh 0=Top to Bottom, 1=Bottom to Top (Bit 4: Line Refresh Order) +_MADCTL_MV = const(0x20) # 0=Normal, 1=Row/column exchange (Bit 5: Page/Column Addressing Order) +_MADCTL_MX = const(0x40) # 0=Left to Right, 1=Right to Left (Bit 6: Column Address Order) +_MADCTL_MY = const(0x80) # 0=Top to Bottom, 1=Bottom to Top (Bit 7: Page Address Order) + +# MADCTL values for each of the rotation constants. +_DEFAULT_ROTATION_TABLE = ( + _MADCTL_MX, # mirrored = False, rotation = 0 + _MADCTL_MV, # mirrored = False, rotation = 90 + _MADCTL_MY, # mirrored = False, rotation = 180 + _MADCTL_MY | _MADCTL_MX | _MADCTL_MV, # mirrored = False, rotation = 270 +) + +_MIRRORED_ROTATION_TABLE = ( + 0, # mirrored = True, rotation = 0 + _MADCTL_MV | _MADCTL_MX, # mirrored = True, rotation = 90 + _MADCTL_MX | _MADCTL_MY, # mirrored = True, rotation = 180 + _MADCTL_MV | _MADCTL_MY, # mirrored = True, rotation = 270 +) +# fmt: on + + +class BusDisplay(DisplayDriver): + """ + Base class for displays connected via a bus. + + Args: + display_bus (SPIBus, I80Bus): The bus the display is connected to. + init_sequence (bytes, list): The initialization sequence for the display. + width (int): The width of the display in pixels. + height (int): The height of the display in pixels. + colstart (int): The column start address for the display. + rowstart (int): The row start address for the display. + rotation (int): The rotation of the display in degrees. + mirrored (bool): If True, the display is mirrored. + color_depth (int): The color depth of the display in bits. + bgr (bool): If True, the display uses BGR color order. + invert (bool): If True, the display colors are inverted. + reverse_bytes_in_word (bool): If True, the bytes in 16-bit colors are reversed. + brightness (float): The brightness of the display as a float between 0.0 and 1.0. + backlight_pin (int, Pin): The pin the display backlight is connected to. + backlight_on_high (bool): If True, the backlight is on when the pin is high. + reset_pin (int, Pin): The pin the display reset is connected to. + reset_high (bool): If True, the reset pin is high. + power_pin (int, Pin): The pin the display power is connected to. + power_on_high (bool): If True, the power pin is high. + set_column_command (int): The command to set the column address. + set_row_command (int): The command to set the row address. + write_ram_command (int): The command to write to the display RAM. + brightness_command (int): The command to set the display brightness. + data_as_commands (bool): If True, data is sent as commands. + single_byte_bounds (bool): If True, single byte bounds are used. + + Attributes: + display_bus (SPIBus, I80Bus): The bus the display is connected to. + color_depth (int): The color depth of the display in bits. + bgr (bool): If True, the display uses BGR color order. + rotation_table (tuple): The rotation table for the display. + """ + + def __init__( + self, + display_bus, + init_sequence=None, + *, + width=0, + height=0, + colstart=0, + rowstart=0, + rotation=0, + mirrored=False, + color_depth=16, + bgr=False, + invert=False, + reverse_bytes_in_word=False, + brightness=1.0, + backlight_pin=None, + backlight_on_high=True, + reset_pin=None, + reset_high=True, + power_pin=None, + power_on_high=True, + set_column_command=_CASET, + set_row_command=_RASET, + write_ram_command=_RAMWR, + brightness_command=None, # For color OLEDs + data_as_commands=False, # For color OLEDs + single_byte_bounds=False, # For color OLEDs + ): + print("Started BusDisplay") + gc.collect() + self.display_bus = display_bus + self._width = width + self._height = height + self._colstart = colstart + self._rowstart = rowstart + self._rotation = rotation + self.color_depth = color_depth + self.bgr = bgr + self._invert = invert + self._requires_byteswap = reverse_bytes_in_word + 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 # not implemented + self._single_byte_bounds = single_byte_bounds # not implemented + + self.send = display_bus.send + self.send_color = ( + display_bus.send if not hasattr(display_bus, "send_color") else display_bus.send_color + ) + + self.rotation_table = _DEFAULT_ROTATION_TABLE if not mirrored else _MIRRORED_ROTATION_TABLE + + self._param_buf = bytearray(4) + self._param_mv = memoryview(self._param_buf) + + self._reset_pin = self._config_output_pin(reset_pin, value=not reset_high) + self._reset_high = reset_high + + self._power_pin = self._config_output_pin(power_pin, value=power_on_high) + self._power_on_high = power_on_high + + self._backlight_pin = self._config_output_pin(backlight_pin, value=backlight_on_high) + self._backlight_on_high = backlight_on_high + + if self._backlight_pin is not None: + try: + from machine import PWM + + self._backlight_pin = PWM(self._backlight_pin, freq=1000, duty_u16=0) + self._backlight_is_pwm = True + except ImportError: + # PWM not implemented on this platform or Pin + self._backlight_is_pwm = False + + # Run the display driver init_sequence. + if type(init_sequence) is bytes: + self._init_bytes(init_sequence) + elif type(init_sequence) is list or type(init_sequence) is tuple: + self._init_list(init_sequence) + + # Run the display driver init() method, which also gets called by rotation.setter + # This should run immediately after _init_bytes() or _init_list() but before + # sending other commands such as _INVON, _INVOFF, _COLMOD, brightness, etc. + self._initialized = False + super().__init__() + if not self._initialized: + raise RuntimeError("Display driver init() must call super().init()") + + # Set COLMOD (color mode) based on color_depth + pixel_formats = {3: 0x11, 8: 0x22, 12: 0x33, 16: 0x55, 18: 0x66, 24: 0x77} + self._param_buf[0] = pixel_formats[self.color_depth] + self.send(_COLMOD, self._param_mv[:1]) + + self.brightness = brightness + + gc.collect() + print("Finished BusDisplay") + + ############### Required API Methods ################ + + def init(self) -> None: + """ + Post initialization tasks. + + This method may be overridden by subclasses to perform any post initialization. + If it is overridden, it must call super().init() or set self._initialized = True. + """ + self._initialized = True + + # Convert from degrees to one quarter rotations. Wrap at the number of entries in the rotations table. + # For example, rotation = 90 -> index = 1. With 4 entries in the rotation table, rotation = 540 -> index = 2 + index = (self._rotation // 90) % len(self.rotation_table) + + # Set the display MADCTL bits for the given rotation. + self._param_buf[0] = self.rotation_table[index] | _BGR if self.bgr else _RGB + self.send(_MADCTL, self._param_mv[:1]) + + # Set the display inversion mode + self.invert_colors(self._invert) + + def blit_rect(self, buf: memoryview, x: int, y: int, w: int, h: int): + """ + Blit a buffer to the display. + + This method takes a buffer of pixel data and writes it to a specified + rectangular area of the display. The top-left corner of the rectangle is + specified by the x and y parameters, and the size of the rectangle is + specified by the width and height parameters. + + Args: + buf (memoryview): The buffer containing the pixel data. + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle in pixels. + h (int): The height of the rectangle in pixels. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the rectangle. + """ + if self._auto_byteswap: + self.byteswap(buf) + + x1 = x + self.colstart + x2 = x1 + w - 1 + y1 = y + self.rowstart + y2 = y1 + h - 1 + + self._set_window(x1, y1, x2, y2) + self.send_color(self._write_ram_command, buf) + return (x, y, w, h) + + def fill_rect(self, x: int, y: int, w: int, h: int, c: int): + """ + Draw a rectangle at the given location, size and filled with color. + + This method draws a filled rectangle on the display. The top-left corner of + the rectangle is specified by the x and y parameters, and the size of the + rectangle is specified by the width and height parameters. The rectangle is + filled with the specified color. + + Args: + x (int): The x-coordinate of the top-left corner of the rectangle. + y (int): The y-coordinate of the top-left corner of the rectangle. + w (int): The width of the rectangle in pixels. + h (int): The height of the rectangle in pixels. + c (int): The color of the rectangle. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the rectangle. + """ + color_bytes = ( + (c & 0xFFFF).to_bytes(2, "big") + if self._auto_byteswap + else (c & 0xFFFF).to_bytes(2, "little") + ) + x1 = x + self.colstart + x2 = x1 + w - 1 + y1 = y + self.rowstart + y2 = y1 + h - 1 + + if h > w: + buf = memoryview(bytearray(color_bytes * h)) + passes = w + else: + buf = memoryview(bytearray(color_bytes * w)) + passes = h + + self._set_window(x1, y1, x2, y2) + self.send(_RAMWR) + for _ in range(passes): + self.send_color(_RAMCONT, buf) + return (x, y, w, h) + + def pixel(self, x: int, y: int, c: int): + """ + Set a pixel on the display. + + Args: + x (int): The x-coordinate of the pixel. + y (int): The y-coordinate of the pixel. + c (int): The color of the pixel. + + Returns: + (tuple): A tuple containing the x, y, width, and height of the pixel. + """ + color_bytes = ( + (c & 0xFFFF).to_bytes(2, "big") + if self._auto_byteswap + else (c & 0xFFFF).to_bytes(2, "little") + ) + if self._auto_byteswap: + c = c >> 8 | c << 8 + xpos = x + self.colstart + ypos = y + self.rowstart + self._set_window(xpos, ypos, xpos, ypos) + self.send(_RAMWR, color_bytes) + return (x, y, 1, 1) + + ############### API Method Overrides ################ + + def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: + """ + Set Vertical Scrolling Definition. + + To scroll a 135x240 display these values should be 40, 240, 40. + There are 40 lines above the display that are not shown followed by + 240 lines that are shown followed by 40 more lines that are not shown. + You could write to these areas off display and scroll them into view by + changing the TFA, VSA and BFA values. + + Args: + tfa (int): Top Fixed Area. + vsa (int): Vertical Scrolling Area. + bfa (int): Bottom Fixed Area. + """ + super().vscrdef(tfa, vsa, bfa) + self.send(_VSCRDEF, struct.pack(">HHH", tfa, vsa, bfa)) + + def vscsad(self, vssa: Optional[int] = None) -> int: + """ + Set the vertical scroll start address. + + Args: + vssa (int, None): The vertical scroll start address. + + Returns: + int: The vertical scroll start address. + """ + if vssa is not None: + super().vscsad(vssa) + self.send(_VSCSAD, struct.pack(">H", self._vssa)) + return self._vssa + + ############### Optional API Methods ################ + + @property + def colstart(self): + """ + The offset in pixels to the first column of the visible display. + """ + rot = self.rotation % 360 + if rot == 0 or rot == 180: + return self._colstart + return self._rowstart + + @property + def rowstart(self): + """ + The offset in pixels to the first row of the visible display. + """ + rot = self.rotation % 360 + if rot == 0 or rot == 180: + return self._rowstart + return self._colstart + + @property + def power(self) -> bool: + """ + The power state of the display. + + Returns: + bool: The power state of the display. + """ + if self._power_pin is None: + return -1 + + state = self._power_pin.value() + if self._power_on_high: + return state + + return not state + + @power.setter + def power(self, value: bool) -> None: + """ + Set the power state of the display. + + Args: + value (bool): The power state to set, True for on, False for off. + """ + if self._power_pin is None: + return + + if self._power_on_high: + self._power_pin.value(value) + else: + self._power_pin.value(not value) + + @property + def brightness(self) -> float: + """ + The brightness of the display. + """ + if self._backlight_pin is None and self._brightness_command is None: + return -1 + + return self._brightness + + @brightness.setter + def brightness(self, value: float) -> None: + """ + Set the brightness of the display. + + Args: + value (float): The brightness of the display as a float between 0.0 and 1.0. + """ + if 0 <= float(value) <= 1.0: + self._brightness = value + if self._backlight_pin: + if not self._backlight_on_high: + value = 1.0 - value + if self._backlight_is_pwm: + if sys.implementation.name == "micropython": + self._backlight_pin.duty_u16(int(value * 0xFFFF)) + elif sys.implementation.name == "circuitpython": + self._backlight_pin.duty_cycle = int(value * 0xFFFF) + else: + if sys.implementation.name == "micropython": + self._backlight_pin.value(value > 0.5) + elif sys.implementation.name == "circuitpython": + self._backlight_pin.value = value > 0.5 + elif self._brightness_command is not None: + self._param_buf[0] = int(value * 255) + self.send(self._brightness_command, self._param_mv[:1]) + + def invert_colors(self, value: bool) -> None: + """ + Invert the colors of the display. + + Args: + value (bool): If True, invert the colors of the display. + """ + if value: + self.send(_INVON) + else: + self.send(_INVOFF) + + def reset(self) -> None: + """ + Reset display. + + This method resets the display. If the display has a reset pin, it is + reset using the reset pin. Otherwise, the display is reset using the + software reset command. + """ + if self._reset_pin is not None: + self.hard_reset() + else: + self.soft_reset() + + def hard_reset(self) -> None: + """ + Hard reset display. + """ + self._reset_pin.value(self._reset_high) + sleep_ms(120) + self._reset_pin.value(not self._reset_high) + + def soft_reset(self) -> None: + """ + Soft reset display. + """ + self.send(_SWRESET) + sleep_ms(150) + + def sleep_mode(self, value: bool) -> None: + """ + Enable or disable display sleep mode. + + Args: + value (bool): If True, enable sleep mode. If False, disable sleep mode. + """ + self.send(_SLPIN if value else _SLPOUT) + + ############### Class Specific Methods ############## + + def _set_window(self, x1, y1, x2, y2): + # See https://github.com/adafruit/Adafruit_Blinka_Displayio/blob/main/displayio/_displaysys.py#L271-L363 + # TODO: Add `if self._single_byte_bounds is True:` for Column and Row _param_buf packing + + # Column addresses + self._param_buf[0] = (x1 >> 8) & 0xFF + self._param_buf[1] = x1 & 0xFF + self._param_buf[2] = (x2 >> 8) & 0xFF + self._param_buf[3] = x2 & 0xFF + self.send(self._set_column_command, self._param_mv[:4]) + + # Row addresses + self._param_buf[0] = (y1 >> 8) & 0xFF + self._param_buf[1] = y1 & 0xFF + self._param_buf[2] = (y2 >> 8) & 0xFF + self._param_buf[3] = y2 & 0xFF + self.send(self._set_row_command, self._param_mv[:4]) + + def _init_bytes(self, init_sequence): + """ + Send an initialization sequence to the display. + + Used by display driver subclass if init_sequence is a CircuitPython displayIO compatible bytes object. + The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins + with a command byte followed by a byte to determine the parameter count and if a + delay is need after. When the top bit of the second byte is 1, the next byte will be + the delay time in milliseconds. The remaining 7 bits are the parameter count + excluding any delay byte. The third through final bytes are the remaining command + parameters. The next byte will begin a new command definition. + + Args: + init_sequence (bytes): The initialization sequence to send to the display. + """ + DELAY = 0x80 + + i = 0 + while i < len(init_sequence): + command = init_sequence[i] + data_size = init_sequence[i + 1] + delay = (data_size & DELAY) != 0 + data_size &= ~DELAY + + self.send(command, init_sequence[i + 2 : i + 2 + data_size]) + + delay_time_ms = 10 + if delay: + data_size += 1 + delay_time_ms = init_sequence[i + 1 + data_size] + if delay_time_ms == 255: + delay_time_ms = 500 + + sleep_ms(delay_time_ms) + i += 2 + data_size + + def _init_list(self, init_sequence): + """ + Send an initialization sequence to the display. + + Used by display driver subclass if init_sequence is a list of tuples. + As a list, it can be modified in .init(), for example: + self._INIT_SEQUENCE[-1] = (0x29, b"\x00", 100) + Each tuple contains the following: + - The first element is the register address (command) + - The second element is the register value (data) + - The third element is the delay in milliseconds after the register is set + + Args: + init_sequence (list): The initialization sequence to send to the display + """ + for line in init_sequence: + self.send(line[0], line[1]) + if line[2] != 0: + sleep_ms(line[2]) + + def _config_output_pin(self, pin, value=None): + if pin is None: + return None + + if sys.implementation.name == "micropython": + p = Pin(pin, Pin.OUT) + if value is not None: + p.value(value) + elif sys.implementation.name == "circuitpython": + p = digitalio.DigitalInOut(pin) + p.direction = digitalio.Direction.OUTPUT + if value is not None: + p.value = value + return p diff --git a/micropython/pydisplay/displaysys/examples/displaysys_block_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py similarity index 100% rename from micropython/pydisplay/displaysys/examples/displaysys_block_test.py rename to micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py diff --git a/micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py similarity index 100% rename from micropython/pydisplay/displaysys/examples/displaysys_fill_rect_test.py rename to micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py diff --git a/micropython/pydisplay/displaysys/examples/displaysys_simpletest.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py similarity index 100% rename from micropython/pydisplay/displaysys/examples/displaysys_simpletest.py rename to micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py From 735a1b34454e36b95c8ca3657edce8337e8fc25d Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 02:39:24 -0600 Subject: [PATCH 07/35] pydisplay: testing. --- .../displaybuf/displaybuf/__init__.py | 4 +- .../pydisplay/displaybuf/displaybuf/_viper.py | 39 ------------------- 2 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 micropython/pydisplay/displaybuf/displaybuf/_viper.py diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py index 9ad59f4a4..ae8d0b8f2 100644 --- a/micropython/pydisplay/displaybuf/displaybuf/__init__.py +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -33,9 +33,9 @@ except ImportError: import framebuf # type: ignore -if sys.implementation.name == "micropython": +try: from ._viper import _bounce8, _bounce4 -else: +except Exception: def _bounce8(*args, **kwargs): raise NotImplementedError( diff --git a/micropython/pydisplay/displaybuf/displaybuf/_viper.py b/micropython/pydisplay/displaybuf/displaybuf/_viper.py deleted file mode 100644 index 5da3c6bcb..000000000 --- a/micropython/pydisplay/displaybuf/displaybuf/_viper.py +++ /dev/null @@ -1,39 +0,0 @@ -import micropython - - -@micropython.viper -def _bounce8(dest: ptr8, source: ptr8, length: int, swap: bool): # noqa: F821 - # Convert a line in 8 bit RGB332 format to 16 bit RGB565 format. - # Each byte becomes 2 in destination. Source format: - # - # dest: - # swap==False: <00 00 00 B1 B0 00 00 00> - # swap==True: <00 00 00 B1 B0 00 00 00> - - if swap: - lsb = 0 - msb = 1 - else: - lsb = 1 - msb = 0 - n = 0 - for x in range(length): - c = source[x] - dest[n + lsb] = (c & 0xE0) | ((c & 0x1C) >> 2) # Red Green - dest[n + msb] = (c & 0x03) << 3 # Blue - n += 2 - - -@micropython.viper -def _bounce4(dest: ptr16, source: ptr8, length: int, lut: ptr16): # noqa: F821 - # Convert a line in 4+4 bit index format to two * 16 bit RGB565 format - # using a color lookup table. Each byte becomes 4 in destination. - # Source format: - # Dest format: the same as self.rgb * 2 - n = 0 - for x in range(length): - c = source[x] # Get the indices of the 2 pixels - dest[n] = lut[c >> 4] # lookup top 4 bits for even pixels - n += 1 - dest[n] = lut[c & 0x0F] # lookup bottom 4 bits for odd pixels - n += 1 From 302ab6d0c650af3517d6da8ba355ff43a466894f Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 02:43:27 -0600 Subject: [PATCH 08/35] pydisplay: Testing packages. --- micropython/pydisplay/spibus/spibus.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/micropython/pydisplay/spibus/spibus.py b/micropython/pydisplay/spibus/spibus.py index 61e4eb24b..8b5e5b6b6 100644 --- a/micropython/pydisplay/spibus/spibus.py +++ b/micropython/pydisplay/spibus/spibus.py @@ -5,7 +5,6 @@ from machine import Pin, SPI import struct -import micropython from micropython import const try: @@ -97,7 +96,6 @@ def __init__( self._buf1: bytearray = bytearray(1) print("SPIBus loaded") - @micropython.native def send( self, command: Optional[int] = None, From 22f5ba6c3767d0ffed6f49c3ff8b54a3e877e62c Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 02:52:40 -0600 Subject: [PATCH 09/35] pydisplay: Testing packages. --- .../displaysys/displaysys/_byteswap_viper.py | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py deleted file mode 100644 index 3c0df05fc..000000000 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py +++ /dev/null @@ -1,21 +0,0 @@ -import micropython - -if 0: - - class ptr8: - pass - - -@micropython.viper -def byteswap_viper(buf: ptr8, buf_size: int): # noqa: F821 - """ - Swap the bytes in a buffer of 16-bit values in place. - - Args: - buf: The buffer to swap the bytes in. - buf_size: The size of the buffer in bytes - """ - for i in range(0, buf_size, 2): - tmp = buf[i] - buf[i] = buf[i + 1] - buf[i + 1] = tmp From 9fefd06d27d83bb7a00c8cc9b69194b95d646a49 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 02:59:24 -0600 Subject: [PATCH 10/35] pydisplay: Troubleshooting the process. Signed-off-by: Brad Barnett --- micropython/pydisplay/i80bus/i80bus.py | 341 ----------------------- micropython/pydisplay/i80bus/manifest.py | 3 - 2 files changed, 344 deletions(-) delete mode 100644 micropython/pydisplay/i80bus/i80bus.py delete mode 100644 micropython/pydisplay/i80bus/manifest.py diff --git a/micropython/pydisplay/i80bus/i80bus.py b/micropython/pydisplay/i80bus/i80bus.py deleted file mode 100644 index 7a2ff154b..000000000 --- a/micropython/pydisplay/i80bus/i80bus.py +++ /dev/null @@ -1,341 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Brad Barnett -# -# SPDX-License-Identifier: MIT -""" -i80bus -""" - -from array import array -from uctypes import addressof -import struct -import micropython -from micropython import const - -try: - from typing import Optional -except ImportError: - pass - -# _I80BaseBus will work with either Pin class, but I80Bus will only work with GPIO_Pin -try: - from gpio_pin import GPIO_Pin as Pin -except ImportError: - from machine import Pin - -if 0: - ptr8 = ptr16 = ptr32 = None # For type hints - - -DC_CMD = const(0) -DC_DATA = const(1) -CS_ACTIVE = const(0) -CS_INACTIVE = const(1) -WR_ACTIVE = const(1) -WR_INACTIVE = const(0) - - -class _I80BaseBus: - """ - Base class for I80 bus communication. - - Args: - dc (int): The pin number for the data/command control. - cs (int): The pin number for the chip select. - wr (int): The pin number for the write control. - data (list[int]): A list of pin numbers for the data pins. - freq (int): The frequency for the bus. Defaults to 20,000,000. - """ - - def __init__( - self, - dc: int, - cs: int, - wr: int, - data: list[int], - freq: int = 20_000_000, - ) -> None: - print("I80Bus loading...") - # Not used in this class; may be used in subclasses like _i80bus_rp2.py - self._freq = freq - - # Create a list of Pin objects for the data pins - # NOTE: data8-15 are optional and not implemented in most subclasses - data_pins = [Pin(pin, Pin.OUT) for pin in data] - - # Setup the control pins - # _wr_active, _wr_inactive are boolean values - # indicating the level of the pin in that state. True is high, False is low. - self._dc: Pin = Pin(dc, Pin.OUT) - self._dc(DC_CMD) # Set the DC pin to the command level - - # If cs was not specified, set it to a lambda that does nothing - # so lines like the next won't fail. - self._cs: Pin = Pin(cs, Pin.OUT) if cs != -1 else lambda val: None - self._cs(CS_INACTIVE) # Set the CS pin to the inactive level - - self._wr: Pin = Pin(wr, Pin.OUT) - self._wr(WR_INACTIVE) # Set the WR pin to the inactive level - - self._buf1: bytearray = bytearray(1) - - self._setup(data_pins) - print("I80Bus loaded") - - @micropython.native - def send(self, command: Optional[int] = None, data: Optional[memoryview] = None) -> None: - """ - Sends a command and/or data to the device. - Args: - command (Optional[int]): The command to send. Defaults to None. - data (Optional[memoryview]): The data to send. Defaults to None. - Returns: - None - """ - - self._cs(CS_ACTIVE) - - if command is not None: - struct.pack_into("B", self._buf1, 0, command) - self._dc(DC_CMD) - self._write(self._buf1, 1) - - if data and len(data): - self._dc(DC_DATA) - self._write(data, len(data)) - - self._cs(CS_INACTIVE) - - def deinit(self): - pass - - def __del__(self): - self.deinit() - - -class I80Bus(_I80BaseBus): - """ - Class for I80 bus communication. - - Args: - dc (int): The pin number for the data/command control. - cs (int): The pin number for the chip select. - wr (int): The pin number for the write control. - data (list[int]): A list of pin numbers for the data pins. - freq (int): The frequency for the bus. Defaults to 20,000,000. - """ - - def _setup(self, data_pins: list[Pin]) -> None: - # Make sure GPIO_Pin was imported - if not hasattr(Pin, "BSRR"): - raise ValueError("GPIO_Pin not imported") - - # If self._is_32bit is True the _write method will use a 32-bit set and a 32-bit - # clear register. Otherwise, the _write method will use set_reset registers - # which use the lower 16 bits for set and the upper 16 bits for clear. - self._is_32bit = True if self._wr.BSRR is None else False - - # Both lut mode and sequential mode need the write pin registers and masks saved to use in viper. - # Subclasses may not need the write pin registers and masks, so they are defined here instead of - # in __init__. Subclasses should override _setup. - if self._is_32bit: - self._wr_mask = self._wr_not_mask = 1 << self._wr.pin() - self._wr_reg = self._wr.gpio() + (self._wr.SET if WR_ACTIVE else self._wr.CLR) - self._wr_not_reg = self._wr.gpio() + (self._wr.CLR if WR_ACTIVE else self._wr.SET) - else: - self._wr_reg = self._wr_not_reg = self._wr.gpio() + self._wr.BSRR - self._wr_mask = 1 << (self._wr.pin() + (0 if WR_ACTIVE else 16)) - self._wr_not_mask = 1 << (self._wr.pin() + (16 if WR_ACTIVE else 0)) - - if False: # Set to True to print the write pin registers and masks - print( - f"\n{self._wr=}\n {self._wr_reg=:#010x}, {self._wr_mask=:#034b}\n {self._wr_not_reg=:#010x}, {self._wr_not_mask=:#034b}\n" - ) - - # Determine which mode, lut or sequential, to use - # If all pins are on the same port and sequential: - if all(p.port() == data_pins[0].port() for p in data_pins) and all( - data_pins[i].pin() + 1 == data_pins[i + 1].pin() for i in range(len(data_pins) - 1) - ): - # Use sequential mode - self._setup_seq(data_pins) - else: - # Use LUT mode - self._setup_lut(data_pins) - - def _setup_lut(self, pins: list[Pin]) -> None: - """ - Setup lookup tables, pin data and the _write method for LUT mode. - """ - print("Using LUT mode") - if len(pins) != 8: - raise ValueError("LUT mode only supports 8 data pins") - self._write = self._write_lut - - # Setup the data for pin_data and the lookup tables - lut_len = 2 ** len(pins) # Number of entries per lut -- 256 for 8-bit bus width - port_list = [] # list of port numbers in use - for item in [p.port() for p in pins]: # Create a list of unique port numbers - if item not in port_list: - port_list.append(item) - # Map port numbers to lookup table index - lut_map = {port: i for i, port in enumerate(port_list)} - self._num_luts = len(lut_map) # Number of lookup tables - self._lookup_tables = [None] * self._num_luts # list of bytearray lookup tables - pin_masks = [None] * self._num_luts # list of 32-bit pin masks - regsA = [None] * self._num_luts # list of SET registers if _is_32bit else BSRR registers - regsB = [None] * self._num_luts # list of CLR registers if _is_32bit else unused - - # Create the pin_masks, populate the 2 reg lists and initialize the lookup_tables - # for each port of 16 or 32 pins. Will be saved in array pin_data later. - for i in range(self._num_luts): - port = port_list[i] - port_pins = [p for p in pins if p.port() == port] - first_pin = port_pins[0] - pin_mask = sum([1 << p.pin() for p in port_pins]) - if self._is_32bit: - self._lookup_tables[i] = array("I", [0] * lut_len) # 32-bit array - regsA[i] = first_pin.gpio() + first_pin.SET - regsB[i] = first_pin.gpio() + first_pin.CLR - else: - self._lookup_tables[i] = array("H", [0] * lut_len) # 16-bit array - regsA[i] = first_pin.gpio() + first_pin.BSRR - regsB[i] = 0x0 - pin_masks[i] = pin_mask - if False: # Set to True to print the pin data - print(f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="") - print(f"mask={pin_masks[i]:#034b}, pins={[p.pin() for p in port_pins]}") - - # Populate the lookup tables - for index in range(lut_len): # Iterate through all possible 8-bit values (0 to 255) - for bit_number, pin in enumerate(pins): # Iterate through each pin - if index & (1 << bit_number): # If the bit is set in the index - # Get the current value for index from the appropriate lookup table - value = self._lookup_tables[lut_map[pin.port()]][index] - value |= 1 << pin.pin() # Update the value for the pin - self._lookup_tables[lut_map[pin.port()]][index] = value # Save the value - - # Save all settings in a struct-like array pin_data for use in viper. - # Could be merged with the first loop above, but left here for clarity. - pin_data = array("I", [0] * 4 * self._num_luts) - for i in range(self._num_luts): - pin_data[i * 4 + 0] = pin_masks[i] - pin_data[i * 4 + 1] = regsA[i] - pin_data[i * 4 + 2] = regsB[i] - pin_data[i * 4 + 3] = addressof(self._lookup_tables[i]) - - if False: # Set to True to print the lookup tables - print( - f"\nlut={i}: mask={pin_masks[i]:#034b}, {regsA[i]=:#010x}, {regsB[i]=:#010x}" - ) - for j in range(0, lut_len): - print(f" {j:3d}: {self._lookup_tables[i][j]:#034b}") - - self._pin_data = memoryview(pin_data) # Save a memoryview into pin_data for use in viper - - @micropython.viper - def _write_lut(self, data: ptr8, length: int): - # Cache these values to avoid accessing the self namespace every iteration - wr_not_reg = ptr32(self._wr_not_reg) - wr_not_mask = int(self._wr_not_mask) - wr_reg = ptr32(self._wr_reg) - wr_mask = int(self._wr_mask) - is_32bit = bool(self._is_32bit) # noqa: F841 - pin_data = ptr32(self._pin_data) - num_luts = int(self._num_luts) - - last: int = -1 - for i in range(length): # Iterate through the data - wr_not_reg[0] = wr_not_mask # WR Inactive - val = data[i] # Get the value from the data - # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) - if val != last: - if False: - print(f"{val=:#010b} ({val=:#04x})") # noqa: E701 - for n in range(num_luts): # Iterate through the lookup tables - if False: - print(f"{ n=}") # noqa: E701 - pin_mask = pin_data[n * 4 + 0] # Get the pin mask - regA = ptr32(pin_data[n * 4 + 1]) # Get the SET or BSRR register - if True: # Should be `if is_32bit:` but not supported in viper - regB = ptr32(pin_data[n * 4 + 2]) # Only need regB (CLEAR) for 32-bit - lut = ptr32(pin_data[n * 4 + 3]) # 32-bit lookup table - tx_value: int = lut[val] # Get the 32-bit value from the lookup table - regA[0] = tx_value # Set the bits that are on - regB[0] = tx_value ^ pin_mask # Clear the bits that are off - else: - lut = ptr16(pin_data[n * 4 + 3]) # 16-bit lookup table - tx_value: int = lut[val] # Get the 16-bit value from the lookup table - # Set the bits that are on and clear the bits that are off - regA[0] = (tx_value << 0) | ((tx_value ^ pin_mask) << 16) - if False: # Print debug info - print(f" {tx_value=:#034b}") - print(f" {pin_mask=:#034b}") - print( - f" wrote: {(tx_value | ((tx_value ^ pin_mask) << 16)):#034b}" - ) - # raise ValueError("Debugging") - last = val # Save the value for the next iteration - wr_reg[0] = wr_mask # WR Active - - def _setup_seq(self, pins: list[Pin]) -> None: - print("Using sequential mode") - if len(pins) == 8: - self._write = self._write_seq8 - elif len(pins) == 16: - self._write = self._write_seq16 - else: - raise ValueError("Sequential mode only supports 8 or 16 data pins") - - # Setup the data for pin_data - pin_mask = sum([1 << p.pin() for p in pins]) - first_pin = pins[0] - if self._is_32bit: - regA = first_pin.gpio() + first_pin.SET - regB = first_pin.gpio() + first_pin.CLR - else: - regA = first_pin.gpio() + first_pin.BSRR - regB = 0x0 - shift = first_pin.pin() - - # save all settings in an array pin_data for use in viper - pin_data = array("I", [0] * 4) - pin_data[0] = pin_mask - pin_data[1] = regA - pin_data[2] = regB - pin_data[3] = shift - self._pin_data = memoryview(pin_data) - - @micropython.viper - def _write_seq8(self, data: ptr8, length: int): - # Cache these values to avoid accessing the self namespace every iteration - wr_not_reg = ptr32(self._wr_not_reg) - wr_not_mask = int(self._wr_not_mask) - wr_reg = ptr32(self._wr_reg) - wr_mask = int(self._wr_mask) - is_32bit = bool(self._is_32bit) - pin_data = ptr32(self._pin_data) - - pin_mask = pin_data[0] - regA = ptr32(pin_data[1]) - regB = ptr32(pin_data[2]) - shift = pin_data[3] - - last: int = -1 - for i in range(length): # Iterate through the data - wr_not_reg[0] = wr_not_mask # WR Inactive - val = data[i] # Get the value from the data - # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) - if val != last: - tx_value: int = val << shift # Shift the value to the correct position - if is_32bit: - regA[0] = tx_value # Set the bits that are on - regB[0] = tx_value ^ pin_mask # Clear the bits that are off - else: - # Set the bits that are on and clear the bits that are off - regA[0] = tx_value | ((tx_value ^ pin_mask) << 16) - last = val # Save the value for the next iteration - wr_reg[0] = wr_mask # WR Active - - @micropython.viper - def _write_seq16(self, data: ptr16, length: int): - raise NotImplementedError("16 pin sequential mode not implemented") diff --git a/micropython/pydisplay/i80bus/manifest.py b/micropython/pydisplay/i80bus/manifest.py deleted file mode 100644 index d31d5f2f9..000000000 --- a/micropython/pydisplay/i80bus/manifest.py +++ /dev/null @@ -1,3 +0,0 @@ -metadata(description="I80Bus Driver", version="0.1.0") - -module("i80bus.py", opt=3) From 875447e708b60635488100606a4d48b6485e0f8c Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 03:14:00 -0600 Subject: [PATCH 11/35] pydisplay: Testing again. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 978e34af7..581b0b882 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,2 +1,3 @@ metadata(version="0.1.0") package("displaybuf") + From 8cc43296cfb2cdc12d98690751c68048394d3fc3 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 03:29:36 -0600 Subject: [PATCH 12/35] pydisplay: Testing once more. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 581b0b882..978e34af7 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,3 +1,2 @@ metadata(version="0.1.0") package("displaybuf") - From 3ed9451b5e49e008c436c069181fc86145ba305e Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 04:46:07 -0600 Subject: [PATCH 13/35] pydisplay: Linting with ruff. Signed-off-by: Brad Barnett --- .../displaybuf/displaybuf/__init__.py | 6 +- .../pydisplay/displaybuf/displaybuf/_viper.py | 39 ++ .../displaybuf/examples/displaybuf_blit.py | 2 +- .../displaysys/busdisplay.py | 6 +- .../displaysys/fbdisplay.py | 1 - .../examples/board_config.py | 10 +- .../examples/board_config.py | 10 +- .../displaysys/psdisplay.py | 4 +- .../examples/board_config.py | 10 +- .../examples/board_config.py | 10 +- .../displaysys/displaysys/__init__.py | 2 - .../displaysys/displaysys/_byteswap.py | 4 +- .../displaysys/displaysys/_byteswap_viper.py | 21 ++ .../pydisplay/eventsys/eventsys/device.py | 4 +- .../eventsys/examples/eventsys_simpletest.py | 2 +- .../pydisplay/graphics/graphics/__init__.py | 72 ++-- .../pydisplay/graphics/graphics/_framebuf.py | 2 +- .../graphics/graphics/_framebuf_plus.py | 3 +- micropython/pydisplay/i80bus/i80bus.py | 341 ++++++++++++++++++ .../palettes/examples/palettes_cube.py | 2 +- .../palettes/examples/palettes_material.py | 2 +- .../palettes/examples/palettes_wheel.py | 2 +- .../pydisplay/palettes/palettes/__init__.py | 2 +- micropython/pydisplay/spibus/spibus.py | 2 + .../timer/examples/timer_simpletest.py | 2 +- 25 files changed, 480 insertions(+), 81 deletions(-) create mode 100644 micropython/pydisplay/displaybuf/displaybuf/_viper.py create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py create mode 100644 micropython/pydisplay/i80bus/i80bus.py diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py index ae8d0b8f2..8691f6bb4 100644 --- a/micropython/pydisplay/displaybuf/displaybuf/__init__.py +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -31,11 +31,11 @@ try: import graphics as framebuf except ImportError: - import framebuf # type: ignore + import framebuf -try: +if sys.implementation.name == "micropython": from ._viper import _bounce8, _bounce4 -except Exception: +else: def _bounce8(*args, **kwargs): raise NotImplementedError( diff --git a/micropython/pydisplay/displaybuf/displaybuf/_viper.py b/micropython/pydisplay/displaybuf/displaybuf/_viper.py new file mode 100644 index 000000000..5da3c6bcb --- /dev/null +++ b/micropython/pydisplay/displaybuf/displaybuf/_viper.py @@ -0,0 +1,39 @@ +import micropython + + +@micropython.viper +def _bounce8(dest: ptr8, source: ptr8, length: int, swap: bool): # noqa: F821 + # Convert a line in 8 bit RGB332 format to 16 bit RGB565 format. + # Each byte becomes 2 in destination. Source format: + # + # dest: + # swap==False: <00 00 00 B1 B0 00 00 00> + # swap==True: <00 00 00 B1 B0 00 00 00> + + if swap: + lsb = 0 + msb = 1 + else: + lsb = 1 + msb = 0 + n = 0 + for x in range(length): + c = source[x] + dest[n + lsb] = (c & 0xE0) | ((c & 0x1C) >> 2) # Red Green + dest[n + msb] = (c & 0x03) << 3 # Blue + n += 2 + + +@micropython.viper +def _bounce4(dest: ptr16, source: ptr8, length: int, lut: ptr16): # noqa: F821 + # Convert a line in 4+4 bit index format to two * 16 bit RGB565 format + # using a color lookup table. Each byte becomes 4 in destination. + # Source format: + # Dest format: the same as self.rgb * 2 + n = 0 + for x in range(length): + c = source[x] # Get the indices of the 2 pixels + dest[n] = lut[c >> 4] # lookup top 4 bits for even pixels + n += 1 + dest[n] = lut[c & 0x0F] # lookup bottom 4 bits for odd pixels + n += 1 diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py index 27c1a0194..18a9bb415 100644 --- a/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py @@ -11,4 +11,4 @@ fb.fill(0x000F) ssd.blit(fb, 100, 100) -ssd.show() \ No newline at end of file +ssd.show() diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py index 8f7d0c2cd..893c739f2 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py @@ -118,7 +118,7 @@ class BusDisplay(DisplayDriver): rotation_table (tuple): The rotation table for the display. """ - def __init__( + def __init__( # noqa: PLR0913 self, display_bus, init_sequence=None, @@ -459,9 +459,9 @@ def brightness(self, value: float) -> None: self._backlight_pin.duty_cycle = int(value * 0xFFFF) else: if sys.implementation.name == "micropython": - self._backlight_pin.value(value > 0.5) + self._backlight_pin.value(value > 0.5) # noqa: PLR2004 elif sys.implementation.name == "circuitpython": - self._backlight_pin.value = value > 0.5 + self._backlight_pin.value = value > 0.5 # noqa: PLR2004 elif self._brightness_command is not None: self._param_buf[0] = int(value * 255) self.send(self._brightness_command, self._param_mv[:1]) diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py index e4fcdaffb..056d2db0b 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/displaysys/fbdisplay.py @@ -40,7 +40,6 @@ def init(self) -> None: """ Initializes the display instance. Called by __init__ and rotation setter. """ - pass def fill_rect(self, x, y, w, h, c): """ diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py index 3909e255c..704986f19 100755 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py @@ -9,19 +9,19 @@ _ps = _jn = False try: - import pyscript # type: ignore # noqa: F401 + import pyscript _ps = True except ImportError: try: - get_ipython() # type: ignore # noqa: F821 + get_ipython() _jn = True except NameError: pass if _ps: from displaysys.psdisplay import PSDisplay, PSDevices - import eventsys.device as device + from eventsys import device display_drv = PSDisplay("display_canvas", width, height) @@ -36,13 +36,13 @@ ) elif _jn: from displaysys.jndisplay import JNDisplay - import eventsys.device as device + from eventsys import device broker = device.Broker() display_drv = JNDisplay(width, height) else: - import eventsys.device as device + from eventsys import device import sys try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index 3909e255c..704986f19 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -9,19 +9,19 @@ _ps = _jn = False try: - import pyscript # type: ignore # noqa: F401 + import pyscript _ps = True except ImportError: try: - get_ipython() # type: ignore # noqa: F821 + get_ipython() _jn = True except NameError: pass if _ps: from displaysys.psdisplay import PSDisplay, PSDevices - import eventsys.device as device + from eventsys import device display_drv = PSDisplay("display_canvas", width, height) @@ -36,13 +36,13 @@ ) elif _jn: from displaysys.jndisplay import JNDisplay - import eventsys.device as device + from eventsys import device broker = device.Broker() display_drv = JNDisplay(width, height) else: - import eventsys.device as device + from eventsys import device import sys try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py index f820969d9..038cebe3c 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/displaysys/psdisplay.py @@ -7,8 +7,8 @@ """ from displaysys import DisplayDriver, color_rgb -from pyscript.ffi import create_proxy # type: ignore -from js import document, console # type: ignore +from pyscript.ffi import create_proxy +from js import document, console def log(*args): diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py index 3909e255c..704986f19 100755 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -9,19 +9,19 @@ _ps = _jn = False try: - import pyscript # type: ignore # noqa: F401 + import pyscript _ps = True except ImportError: try: - get_ipython() # type: ignore # noqa: F821 + get_ipython() _jn = True except NameError: pass if _ps: from displaysys.psdisplay import PSDisplay, PSDevices - import eventsys.device as device + from eventsys import device display_drv = PSDisplay("display_canvas", width, height) @@ -36,13 +36,13 @@ ) elif _jn: from displaysys.jndisplay import JNDisplay - import eventsys.device as device + from eventsys import device broker = device.Broker() display_drv = JNDisplay(width, height) else: - import eventsys.device as device + from eventsys import device import sys try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index 3909e255c..704986f19 100755 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -9,19 +9,19 @@ _ps = _jn = False try: - import pyscript # type: ignore # noqa: F401 + import pyscript _ps = True except ImportError: try: - get_ipython() # type: ignore # noqa: F821 + get_ipython() _jn = True except NameError: pass if _ps: from displaysys.psdisplay import PSDisplay, PSDevices - import eventsys.device as device + from eventsys import device display_drv = PSDisplay("display_canvas", width, height) @@ -36,13 +36,13 @@ ) elif _jn: from displaysys.jndisplay import JNDisplay - import eventsys.device as device + from eventsys import device broker = device.Broker() display_drv = JNDisplay(width, height) else: - import eventsys.device as device + from eventsys import device import sys try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index fcb6ca18b..ec0309b30 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -461,7 +461,6 @@ def _rotation_helper(self, value): value (int): The rotation of the display in degrees. """ # override this method in subclasses to handle rotation - pass ############### Empty API Methods, must be overridden if applicable ################ @@ -536,7 +535,6 @@ def deinit(self) -> None: Deinitialize the display. """ self.__del__() - return def show(self, *args, **kwargs) -> None: """ diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py index 74db96e98..e1027336e 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py @@ -15,7 +15,7 @@ try: # import byteswap from MicroPython if available - from byteswap import byteswap # type: ignore + from byteswap import byteswap except ImportError: try: # import numpy if available @@ -24,7 +24,7 @@ import numpy as np except ImportError: # import numpy for CircuitPython or MicroPython with numpy module - from ulab import numpy as np # type: ignore + from ulab import numpy as np def byteswap(buf): """ diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py new file mode 100644 index 000000000..3c0df05fc --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py @@ -0,0 +1,21 @@ +import micropython + +if 0: + + class ptr8: + pass + + +@micropython.viper +def byteswap_viper(buf: ptr8, buf_size: int): # noqa: F821 + """ + Swap the bytes in a buffer of 16-bit values in place. + + Args: + buf: The buffer to swap the bytes in. + buf_size: The size of the buffer in bytes + """ + for i in range(0, buf_size, 2): + tmp = buf[i] + buf[i] = buf[i + 1] + buf[i + 1] = tmp diff --git a/micropython/pydisplay/eventsys/eventsys/device.py b/micropython/pydisplay/eventsys/eventsys/device.py index 33f0131bc..a8d863917 100644 --- a/micropython/pydisplay/eventsys/eventsys/device.py +++ b/micropython/pydisplay/eventsys/eventsys/device.py @@ -126,7 +126,7 @@ def __init__(self, read=None, data=None, read2=None, data2=None): read2 (callable, optional): A function that returns a value or None. Defaults to None. data2 (Any, optional): Data to pass to the read2 function. Defaults to None. """ - self._event_callbacks = dict() + self._event_callbacks = {} self._read = read if read else lambda: None self._data = data @@ -245,7 +245,7 @@ class Broker(Device): def __init__(self): super().__init__() self.devices = [] # List of devices to poll - self._device_callbacks = dict() + self._device_callbacks = {} # Function to call when the window close button is clicked. # Set it like `display_drv.quit_func = cleanup_func` where `cleanup_func` is a # function that cleans up resources and calls `sys.exit()`. diff --git a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py index 8c7a862e5..47f74dce5 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py @@ -12,6 +12,6 @@ async def main(): await asyncio.sleep(0.001) loop = asyncio.get_event_loop() -loop.create_task(main()) +main_task = loop.create_task(main()) # noqa: RUF006 if hasattr(loop, "run_forever"): loop.run_forever() diff --git a/micropython/pydisplay/graphics/graphics/__init__.py b/micropython/pydisplay/graphics/graphics/__init__.py index 7e0baf972..ecc4b0935 100644 --- a/micropython/pydisplay/graphics/graphics/__init__.py +++ b/micropython/pydisplay/graphics/graphics/__init__.py @@ -40,40 +40,40 @@ ) __all__ = [ - Area, - Draw, - Font, - text, - text8, - text14, - text16, - pbm_to_framebuffer, - pgm_to_framebuffer, - bmp_to_framebuffer, - FrameBuffer, - MONO_VLSB, - MONO_HLSB, - MONO_HMSB, - GS2_HMSB, - GS4_HMSB, - GS8, - RGB565, - arc, - blit, - blit_rect, - blit_transparent, - circle, - ellipse, - fill, - fill_rect, - gradient_rect, - hline, - line, - pixel, - poly, - polygon, - rect, - round_rect, - triangle, - vline, + "Area", + "Draw", + "Font", + "text", + "text8", + "text14", + "text16", + "pbm_to_framebuffer", + "pgm_to_framebuffer", + "bmp_to_framebuffer", + "FrameBuffer", + "MONO_VLSB", + "MONO_HLSB", + "MONO_HMSB", + "GS2_HMSB", + "GS4_HMSB", + "GS8", + "RGB565", + "arc", + "blit", + "blit_rect", + "blit_transparent", + "circle", + "ellipse", + "fill", + "fill_rect", + "gradient_rect", + "hline", + "line", + "pixel", + "poly", + "polygon", + "rect", + "round_rect", + "triangle", + "vline", ] diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py index 074cb336b..4d8e36011 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -24,7 +24,7 @@ from . import _font try: - from ulab import numpy as np # type: ignore + from ulab import numpy as np except ImportError: try: import numpy as np diff --git a/micropython/pydisplay/graphics/graphics/_framebuf_plus.py b/micropython/pydisplay/graphics/graphics/_framebuf_plus.py index 73fb6b22c..a4097df3e 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf_plus.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf_plus.py @@ -4,7 +4,7 @@ from . import _font try: # Try to import framebuf from MicroPython - from framebuf import ( # type: ignore + from framebuf import ( MONO_VLSB, MONO_HLSB, MONO_HMSB, @@ -300,7 +300,6 @@ def blit(self, buf, x, y, key=-1, palette=None): (Area): Bounding box of the blitted buffer """ super().blit(buf, x, y, key, palette) - return ########### Additional methods diff --git a/micropython/pydisplay/i80bus/i80bus.py b/micropython/pydisplay/i80bus/i80bus.py new file mode 100644 index 000000000..262dfcc37 --- /dev/null +++ b/micropython/pydisplay/i80bus/i80bus.py @@ -0,0 +1,341 @@ +# SPDX-FileCopyrightText: 2023 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +i80bus +""" + +from array import array +from uctypes import addressof +import struct +import micropython +from micropython import const + +try: + from typing import Optional +except ImportError: + pass + +# _I80BaseBus will work with either Pin class, but I80Bus will only work with GPIO_Pin +try: + from gpio_pin import GPIO_Pin as Pin +except ImportError: + from machine import Pin + +if 0: + ptr8 = ptr16 = ptr32 = None # For type hints + + +DC_CMD = const(0) +DC_DATA = const(1) +CS_ACTIVE = const(0) +CS_INACTIVE = const(1) +WR_ACTIVE = const(1) +WR_INACTIVE = const(0) + + +class _I80BaseBus: + """ + Base class for I80 bus communication. + + Args: + dc (int): The pin number for the data/command control. + cs (int): The pin number for the chip select. + wr (int): The pin number for the write control. + data (list[int]): A list of pin numbers for the data pins. + freq (int): The frequency for the bus. Defaults to 20,000,000. + """ + + def __init__( + self, + dc: int, + cs: int, + wr: int, + data: list[int], + freq: int = 20_000_000, + ) -> None: + print("I80Bus loading...") + # Not used in this class; may be used in subclasses like _i80bus_rp2.py + self._freq = freq + + # Create a list of Pin objects for the data pins + # NOTE: data8-15 are optional and not implemented in most subclasses + data_pins = [Pin(pin, Pin.OUT) for pin in data] + + # Setup the control pins + # _wr_active, _wr_inactive are boolean values + # indicating the level of the pin in that state. True is high, False is low. + self._dc: Pin = Pin(dc, Pin.OUT) + self._dc(DC_CMD) # Set the DC pin to the command level + + # If cs was not specified, set it to a lambda that does nothing + # so lines like the next won't fail. + self._cs: Pin = Pin(cs, Pin.OUT) if cs != -1 else lambda val: None + self._cs(CS_INACTIVE) # Set the CS pin to the inactive level + + self._wr: Pin = Pin(wr, Pin.OUT) + self._wr(WR_INACTIVE) # Set the WR pin to the inactive level + + self._buf1: bytearray = bytearray(1) + + self._setup(data_pins) + print("I80Bus loaded") + + @micropython.native + def send(self, command: Optional[int] = None, data: Optional[memoryview] = None) -> None: + """ + Sends a command and/or data to the device. + Args: + command (Optional[int]): The command to send. Defaults to None. + data (Optional[memoryview]): The data to send. Defaults to None. + Returns: + None + """ + + self._cs(CS_ACTIVE) + + if command is not None: + struct.pack_into("B", self._buf1, 0, command) + self._dc(DC_CMD) + self._write(self._buf1, 1) + + if data and len(data): + self._dc(DC_DATA) + self._write(data, len(data)) + + self._cs(CS_INACTIVE) + + def deinit(self): + pass + + def __del__(self): + self.deinit() + + +class I80Bus(_I80BaseBus): + """ + Class for I80 bus communication. + + Args: + dc (int): The pin number for the data/command control. + cs (int): The pin number for the chip select. + wr (int): The pin number for the write control. + data (list[int]): A list of pin numbers for the data pins. + freq (int): The frequency for the bus. Defaults to 20,000,000. + """ + + def _setup(self, data_pins: list[Pin]) -> None: + # Make sure GPIO_Pin was imported + if not hasattr(Pin, "BSRR"): + raise ValueError("GPIO_Pin not imported") + + # If self._is_32bit is True the _write method will use a 32-bit set and a 32-bit + # clear register. Otherwise, the _write method will use set_reset registers + # which use the lower 16 bits for set and the upper 16 bits for clear. + self._is_32bit = True if self._wr.BSRR is None else False + + # Both lut mode and sequential mode need the write pin registers and masks saved to use in viper. + # Subclasses may not need the write pin registers and masks, so they are defined here instead of + # in __init__. Subclasses should override _setup. + if self._is_32bit: + self._wr_mask = self._wr_not_mask = 1 << self._wr.pin() + self._wr_reg = self._wr.gpio() + (self._wr.SET if WR_ACTIVE else self._wr.CLR) + self._wr_not_reg = self._wr.gpio() + (self._wr.CLR if WR_ACTIVE else self._wr.SET) + else: + self._wr_reg = self._wr_not_reg = self._wr.gpio() + self._wr.BSRR + self._wr_mask = 1 << (self._wr.pin() + (0 if WR_ACTIVE else 16)) + self._wr_not_mask = 1 << (self._wr.pin() + (16 if WR_ACTIVE else 0)) + + if False: # Set to True to print the write pin registers and masks + print( + f"\n{self._wr=}\n {self._wr_reg=:#010x}, {self._wr_mask=:#034b}\n {self._wr_not_reg=:#010x}, {self._wr_not_mask=:#034b}\n" + ) + + # Determine which mode, lut or sequential, to use + # If all pins are on the same port and sequential: + if all(p.port() == data_pins[0].port() for p in data_pins) and all( + data_pins[i].pin() + 1 == data_pins[i + 1].pin() for i in range(len(data_pins) - 1) + ): + # Use sequential mode + self._setup_seq(data_pins) + else: + # Use LUT mode + self._setup_lut(data_pins) + + def _setup_lut(self, pins: list[Pin]) -> None: + """ + Setup lookup tables, pin data and the _write method for LUT mode. + """ + print("Using LUT mode") + if len(pins) != 8: + raise ValueError("LUT mode only supports 8 data pins") + self._write = self._write_lut + + # Setup the data for pin_data and the lookup tables + lut_len = 2 ** len(pins) # Number of entries per lut -- 256 for 8-bit bus width + port_list = [] # list of port numbers in use + for item in [p.port() for p in pins]: # Create a list of unique port numbers + if item not in port_list: + port_list.append(item) + # Map port numbers to lookup table index + lut_map = {port: i for i, port in enumerate(port_list)} + self._num_luts = len(lut_map) # Number of lookup tables + self._lookup_tables = [None] * self._num_luts # list of bytearray lookup tables + pin_masks = [None] * self._num_luts # list of 32-bit pin masks + regsA = [None] * self._num_luts # list of SET registers if _is_32bit else BSRR registers + regsB = [None] * self._num_luts # list of CLR registers if _is_32bit else unused + + # Create the pin_masks, populate the 2 reg lists and initialize the lookup_tables + # for each port of 16 or 32 pins. Will be saved in array pin_data later. + for i in range(self._num_luts): + port = port_list[i] + port_pins = [p for p in pins if p.port() == port] + first_pin = port_pins[0] + pin_mask = sum([1 << p.pin() for p in port_pins]) + if self._is_32bit: + self._lookup_tables[i] = array("I", [0] * lut_len) # 32-bit array + regsA[i] = first_pin.gpio() + first_pin.SET + regsB[i] = first_pin.gpio() + first_pin.CLR + else: + self._lookup_tables[i] = array("H", [0] * lut_len) # 16-bit array + regsA[i] = first_pin.gpio() + first_pin.BSRR + regsB[i] = 0x0 + pin_masks[i] = pin_mask + if False: # Set to True to print the pin data + print(f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="") + print(f"mask={pin_masks[i]:#034b}, pins={[p.pin() for p in port_pins]}") + + # Populate the lookup tables + for index in range(lut_len): # Iterate through all possible 8-bit values (0 to 255) + for bit_number, pin in enumerate(pins): # Iterate through each pin + if index & (1 << bit_number): # If the bit is set in the index + # Get the current value for index from the appropriate lookup table + value = self._lookup_tables[lut_map[pin.port()]][index] + value |= 1 << pin.pin() # Update the value for the pin + self._lookup_tables[lut_map[pin.port()]][index] = value # Save the value + + # Save all settings in a struct-like array pin_data for use in viper. + # Could be merged with the first loop above, but left here for clarity. + pin_data = array("I", [0] * 4 * self._num_luts) + for i in range(self._num_luts): + pin_data[i * 4 + 0] = pin_masks[i] + pin_data[i * 4 + 1] = regsA[i] + pin_data[i * 4 + 2] = regsB[i] + pin_data[i * 4 + 3] = addressof(self._lookup_tables[i]) + + if False: # Set to True to print the lookup tables + print( + f"\nlut={i}: mask={pin_masks[i]:#034b}, {regsA[i]=:#010x}, {regsB[i]=:#010x}" + ) + for j in range(lut_len): + print(f" {j:3d}: {self._lookup_tables[i][j]:#034b}") + + self._pin_data = memoryview(pin_data) # Save a memoryview into pin_data for use in viper + + @micropython.viper + def _write_lut(self, data: ptr8, length: int): + # Cache these values to avoid accessing the self namespace every iteration + wr_not_reg = ptr32(self._wr_not_reg) + wr_not_mask = int(self._wr_not_mask) + wr_reg = ptr32(self._wr_reg) + wr_mask = int(self._wr_mask) + is_32bit = bool(self._is_32bit) # noqa: F841 + pin_data = ptr32(self._pin_data) + num_luts = int(self._num_luts) + + last: int = -1 + for i in range(length): # Iterate through the data + wr_not_reg[0] = wr_not_mask # WR Inactive + val = data[i] # Get the value from the data + # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) + if val != last: + if False: + print(f"{val=:#010b} ({val=:#04x})") # noqa: E701 + for n in range(num_luts): # Iterate through the lookup tables + if False: + print(f"{ n=}") # noqa: E701 + pin_mask = pin_data[n * 4 + 0] # Get the pin mask + regA = ptr32(pin_data[n * 4 + 1]) # Get the SET or BSRR register + if True: # Should be `if is_32bit:` but not supported in viper + regB = ptr32(pin_data[n * 4 + 2]) # Only need regB (CLEAR) for 32-bit + lut = ptr32(pin_data[n * 4 + 3]) # 32-bit lookup table + tx_value: int = lut[val] # Get the 32-bit value from the lookup table + regA[0] = tx_value # Set the bits that are on + regB[0] = tx_value ^ pin_mask # Clear the bits that are off + else: + lut = ptr16(pin_data[n * 4 + 3]) # 16-bit lookup table + tx_value: int = lut[val] # Get the 16-bit value from the lookup table + # Set the bits that are on and clear the bits that are off + regA[0] = (tx_value << 0) | ((tx_value ^ pin_mask) << 16) + if False: # Print debug info + print(f" {tx_value=:#034b}") + print(f" {pin_mask=:#034b}") + print( + f" wrote: {(tx_value | ((tx_value ^ pin_mask) << 16)):#034b}" + ) + # raise ValueError("Debugging") + last = val # Save the value for the next iteration + wr_reg[0] = wr_mask # WR Active + + def _setup_seq(self, pins: list[Pin]) -> None: + print("Using sequential mode") + if len(pins) == 8: + self._write = self._write_seq8 + elif len(pins) == 16: + self._write = self._write_seq16 + else: + raise ValueError("Sequential mode only supports 8 or 16 data pins") + + # Setup the data for pin_data + pin_mask = sum([1 << p.pin() for p in pins]) + first_pin = pins[0] + if self._is_32bit: + regA = first_pin.gpio() + first_pin.SET + regB = first_pin.gpio() + first_pin.CLR + else: + regA = first_pin.gpio() + first_pin.BSRR + regB = 0x0 + shift = first_pin.pin() + + # save all settings in an array pin_data for use in viper + pin_data = array("I", [0] * 4) + pin_data[0] = pin_mask + pin_data[1] = regA + pin_data[2] = regB + pin_data[3] = shift + self._pin_data = memoryview(pin_data) + + @micropython.viper + def _write_seq8(self, data: ptr8, length: int): + # Cache these values to avoid accessing the self namespace every iteration + wr_not_reg = ptr32(self._wr_not_reg) + wr_not_mask = int(self._wr_not_mask) + wr_reg = ptr32(self._wr_reg) + wr_mask = int(self._wr_mask) + is_32bit = bool(self._is_32bit) + pin_data = ptr32(self._pin_data) + + pin_mask = pin_data[0] + regA = ptr32(pin_data[1]) + regB = ptr32(pin_data[2]) + shift = pin_data[3] + + last: int = -1 + for i in range(length): # Iterate through the data + wr_not_reg[0] = wr_not_mask # WR Inactive + val = data[i] # Get the value from the data + # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) + if val != last: + tx_value: int = val << shift # Shift the value to the correct position + if is_32bit: + regA[0] = tx_value # Set the bits that are on + regB[0] = tx_value ^ pin_mask # Clear the bits that are off + else: + # Set the bits that are on and clear the bits that are off + regA[0] = tx_value | ((tx_value ^ pin_mask) << 16) + last = val # Save the value for the next iteration + wr_reg[0] = wr_mask # WR Active + + @micropython.viper + def _write_seq16(self, data: ptr16, length: int): + raise NotImplementedError("16 pin sequential mode not implemented") diff --git a/micropython/pydisplay/palettes/examples/palettes_cube.py b/micropython/pydisplay/palettes/examples/palettes_cube.py index 541c835c2..440f1a48b 100644 --- a/micropython/pydisplay/palettes/examples/palettes_cube.py +++ b/micropython/pydisplay/palettes/examples/palettes_cube.py @@ -31,7 +31,7 @@ def main(): scroll = (y - last_line) % display_drv.height display_drv.vscsad(scroll) name = f"{index} - {palette.color_name(index)}" - text_color = palette.WHITE if palette.brightness(index) < 0.4 else palette.BLACK + text_color = palette.WHITE if palette.brightness(index) < 0.4 else palette.BLACK # noqa: PLR2004 fb.fill(color) fb.text16(name, 2, 2, text_color) display_drv.blit_rect(ba, 0, y % display_drv.height, display_drv.width, line_height) diff --git a/micropython/pydisplay/palettes/examples/palettes_material.py b/micropython/pydisplay/palettes/examples/palettes_material.py index 26451d655..0ceac2e13 100644 --- a/micropython/pydisplay/palettes/examples/palettes_material.py +++ b/micropython/pydisplay/palettes/examples/palettes_material.py @@ -19,4 +19,4 @@ def main(): display_drv.fill_rect(0, i*line_height, display_drv.width, line_height, color) while True: - main() \ No newline at end of file + main() diff --git a/micropython/pydisplay/palettes/examples/palettes_wheel.py b/micropython/pydisplay/palettes/examples/palettes_wheel.py index 39725715b..7d65b4c01 100644 --- a/micropython/pydisplay/palettes/examples/palettes_wheel.py +++ b/micropython/pydisplay/palettes/examples/palettes_wheel.py @@ -28,4 +28,4 @@ def loop(): while True: main() -loop() \ No newline at end of file +loop() diff --git a/micropython/pydisplay/palettes/palettes/__init__.py b/micropython/pydisplay/palettes/palettes/__init__.py index eae4cc8b9..58064ff57 100644 --- a/micropython/pydisplay/palettes/palettes/__init__.py +++ b/micropython/pydisplay/palettes/palettes/__init__.py @@ -48,7 +48,7 @@ def __init__(self, name="", color_depth=16, swapped=False, cached=False): self._name = name self._color_depth = color_depth self._swapped = swapped - self._cache = dict() if cached else None + self._cache = {} if cached else None if not hasattr(self, "_names"): self._names = WIN16 diff --git a/micropython/pydisplay/spibus/spibus.py b/micropython/pydisplay/spibus/spibus.py index 8b5e5b6b6..61e4eb24b 100644 --- a/micropython/pydisplay/spibus/spibus.py +++ b/micropython/pydisplay/spibus/spibus.py @@ -5,6 +5,7 @@ from machine import Pin, SPI import struct +import micropython from micropython import const try: @@ -96,6 +97,7 @@ def __init__( self._buf1: bytearray = bytearray(1) print("SPIBus loaded") + @micropython.native def send( self, command: Optional[int] = None, diff --git a/micropython/pydisplay/timer/examples/timer_simpletest.py b/micropython/pydisplay/timer/examples/timer_simpletest.py index 0be7b0cec..9df918505 100644 --- a/micropython/pydisplay/timer/examples/timer_simpletest.py +++ b/micropython/pydisplay/timer/examples/timer_simpletest.py @@ -25,7 +25,7 @@ def stop(self, t=None): # Create a timer that calls tt.do_something every 1ms tt = TimerTest() -tt.start(1) +tt.start(1) # Create a timer that stops the first timer after 5 seconds tim2 = Timer(-1 if platform == "rp2" else 2) From 3ef804ba3ad9309f1714853084fd102cf449b9f2 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 16:17:23 -0600 Subject: [PATCH 14/35] pydisplay: Packaging and manifests. Signed-off-by: Brad Barnett --- .../displaybuf/displaybuf/__init__.py | 18 +- .../pydisplay/displaybuf/displaybuf/_viper.py | 39 -- micropython/pydisplay/displaybuf/manifest.py | 6 +- micropython/pydisplay/displaysys/README.md | 2 - .../displaysys/busdisplay.py | 4 +- .../examples/board_config.py | 2 +- .../displaysys-busdisplay/manifest.py | 6 +- .../examples/board_config.py | 10 +- .../displaysys-fbdisplay/manifest.py | 6 +- .../displaysys-jndisplay/manifest.py | 6 +- .../examples/board_config.py | 0 .../displaysys-pgdisplay/manifest.py | 6 +- .../displaysys-psdisplay/manifest.py | 6 +- .../displaysys-sdldisplay/manifest.py | 6 +- .../displaysys/displaysys/__init__.py | 13 +- .../displaysys/displaysys/_byteswap.py | 51 -- .../displaysys/displaysys/_byteswap_viper.py | 21 - .../displaysys/displaysys/busdisplay.py | 607 ------------------ .../examples/displaysys_block_test.py | 2 +- .../examples/displaysys_fill_rect_test.py | 2 +- .../displaysys/displaysys/manifest.py | 8 +- micropython/pydisplay/eventsys/manifest.py | 6 +- micropython/pydisplay/gpio_pin/gpio_pin.py | 219 ------- micropython/pydisplay/gpio_pin/manifest.py | 3 - micropython/pydisplay/graphics/manifest.py | 6 +- micropython/pydisplay/i80bus/i80bus.py | 341 ---------- micropython/pydisplay/palettes/manifest.py | 6 +- micropython/pydisplay/spibus/manifest.py | 3 - micropython/pydisplay/spibus/spibus.py | 149 ----- micropython/pydisplay/timer/manifest.py | 6 +- 30 files changed, 91 insertions(+), 1469 deletions(-) delete mode 100644 micropython/pydisplay/displaybuf/displaybuf/_viper.py delete mode 100644 micropython/pydisplay/displaysys/README.md mode change 100644 => 100755 micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py delete mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py delete mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py delete mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py delete mode 100644 micropython/pydisplay/gpio_pin/gpio_pin.py delete mode 100644 micropython/pydisplay/gpio_pin/manifest.py delete mode 100644 micropython/pydisplay/i80bus/i80bus.py delete mode 100644 micropython/pydisplay/spibus/manifest.py delete mode 100644 micropython/pydisplay/spibus/spibus.py diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py index 8691f6bb4..f3c1d4da7 100644 --- a/micropython/pydisplay/displaybuf/displaybuf/__init__.py +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -26,32 +26,32 @@ import gc import sys -from displaysys import color565, color565_swapped, color332 +from displaysys import alloc_buffer, color565, color565_swapped, color332 try: import graphics as framebuf except ImportError: import framebuf +_has_viper_tools = False if sys.implementation.name == "micropython": - from ._viper import _bounce8, _bounce4 -else: + try: + from viper_tools import _bounce8, _bounce4 + _has_viper_tools = True + except Exception: + pass +if not _has_viper_tools: def _bounce8(*args, **kwargs): raise NotImplementedError( - ".GS8 and .GS4_HMSB DisplayBuffer formats are only implemented for MicroPython." + ".GS8 and .GS4_HMSB DisplayBuffer formats are only implemented in viper_tools.py for MicroPython." ) - _bounce4 = _bounce8 gc.collect() -def alloc_buffer(size): - return memoryview(bytearray(size)) - - _display_drv_get_attrs = {"set_vscroll", "tfa", "bfa", "vsa", "vscroll", "translate_point"} _display_drv_set_attrs = {"vscroll"} diff --git a/micropython/pydisplay/displaybuf/displaybuf/_viper.py b/micropython/pydisplay/displaybuf/displaybuf/_viper.py deleted file mode 100644 index 5da3c6bcb..000000000 --- a/micropython/pydisplay/displaybuf/displaybuf/_viper.py +++ /dev/null @@ -1,39 +0,0 @@ -import micropython - - -@micropython.viper -def _bounce8(dest: ptr8, source: ptr8, length: int, swap: bool): # noqa: F821 - # Convert a line in 8 bit RGB332 format to 16 bit RGB565 format. - # Each byte becomes 2 in destination. Source format: - # - # dest: - # swap==False: <00 00 00 B1 B0 00 00 00> - # swap==True: <00 00 00 B1 B0 00 00 00> - - if swap: - lsb = 0 - msb = 1 - else: - lsb = 1 - msb = 0 - n = 0 - for x in range(length): - c = source[x] - dest[n + lsb] = (c & 0xE0) | ((c & 0x1C) >> 2) # Red Green - dest[n + msb] = (c & 0x03) << 3 # Blue - n += 2 - - -@micropython.viper -def _bounce4(dest: ptr16, source: ptr8, length: int, lut: ptr16): # noqa: F821 - # Convert a line in 4+4 bit index format to two * 16 bit RGB565 format - # using a color lookup table. Each byte becomes 4 in destination. - # Source format: - # Dest format: the same as self.rgb * 2 - n = 0 - for x in range(length): - c = source[x] # Get the indices of the 2 pixels - dest[n] = lut[c >> 4] # lookup top 4 bits for even pixels - n += 1 - dest[n] = lut[c & 0x0F] # lookup bottom 4 bits for odd pixels - n += 1 diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 978e34af7..a16d5f52f 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") +metadata( + description="displaybuf", + version="0.1.0", + pypi_publish="pydisplay-displaybuf", +) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/README.md b/micropython/pydisplay/displaysys/README.md deleted file mode 100644 index 90bff7163..000000000 --- a/micropython/pydisplay/displaysys/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# displaysys -================= diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py index 893c739f2..84647c926 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py @@ -196,9 +196,9 @@ def __init__( # noqa: PLR0913 self._backlight_is_pwm = False # Run the display driver init_sequence. - if type(init_sequence) is bytes: + if isinstance(init_sequence, bytes): self._init_bytes(init_sequence) - elif type(init_sequence) is list or type(init_sequence) is tuple: + elif isinstance(init_sequence, list) or isinstance(init_sequence, tuple): self._init_list(init_sequence) # Run the display driver init() method, which also gets called by rotation.setter diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py index 63530ced5..ffbf320ab 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py @@ -5,7 +5,7 @@ from machine import I2C, Pin # See the note about reset below from ft6x36 import FT6x36 from machine import freq -import eventsys.device as device +from eventsys import device freq(240_000_000) diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 4ed1d5887..0bfed6d0f 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-busdisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-busdisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py index f79f55bd8..e88ac8591 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -1,12 +1,12 @@ """Qualia S3 RGB-666 with TL040HDS20 4.0" 720x720 Square Display""" # Similar configs may be available for RGBMatrix, is31fl3741 and picodvi -from rgbframebuffer import RGBFrameBuffer # type: ignore -from machine import I2C, Pin # type: ignore -from pca9554 import PCA9554 # type: ignore -from ft6x36 import FT6x36 # type: ignore +from rgbframebuffer import RGBFrameBuffer +from machine import I2C, Pin +from pca9554 import PCA9554 +from ft6x36 import FT6x36 from displaysys.fbdisplay import FBDisplay -import eventsys.device as device +from eventsys import device def send_init_sequence(init_sequence, mosi, sck, cs): diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 4ed1d5887..a069e9d45 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-fbdisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-fbdisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 4ed1d5887..a6f0c2690 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-jndisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-jndisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py old mode 100644 new mode 100755 diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 4ed1d5887..397ae332b 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-pgdisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-pgdisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 4ed1d5887..41cc113ef 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-psdisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-psdisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 4ed1d5887..eaa14b064 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,3 +1,7 @@ -metadata(version="0.1.0") +metadata( + description="displaysys-sdldisplay", + version="0.1.0", + pypi_publish="pydisplay-displaysys-sdldisplay", +) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index ec0309b30..08729f140 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -13,13 +13,22 @@ """ import gc -from ._byteswap import byteswap + +try: + from byteswap import byteswap +except ImportError: + def byteswap(buf): + """ + Swap the bytes of a 16-bit buffer in place with no dependencies. + """ + buf[::2], buf[1::2] = buf[1::2], buf[::2] + gc.collect() -def new_buffer(size): +def alloc_buffer(size): """ Create a new buffer of the specified size. In the future, this function may be modified to use port-specific memory allocation such as ESP32's heap_caps_malloc. diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py deleted file mode 100644 index e1027336e..000000000 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap.py +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT - -""" -`displaysys._byteswap` -==================================================== - -A function to swap the bytes of a buffer in place. 3 implementations are provided: -- numpy: The preferred implementation using numpy, which is usually available in Python - and CircuitPython, and may be available in MicroPython with the numpy module. -- viper: A viper implementation for MicroPython only -- default: A default implementation with no dependencies -""" - -try: - # import byteswap from MicroPython if available - from byteswap import byteswap -except ImportError: - try: - # import numpy if available - try: - # import numpy for CPython - import numpy as np - except ImportError: - # import numpy for CircuitPython or MicroPython with numpy module - from ulab import numpy as np - - def byteswap(buf): - """ - Swap the bytes of a 16-bit buffer in place using numpy. - """ - npbuf = np.frombuffer(buf, dtype=np.uint16) - npbuf.byteswap(inplace=True) - except Exception: - try: - # import byteswap_viper if available - from ._byteswap_viper import byteswap_viper - - def byteswap(buf): - """ - Swap the bytes of a 16-bit buffer in place using viper. - """ - byteswap_viper(buf, len(buf)) - except Exception: - - def byteswap(buf): - """ - Swap the bytes of a 16-bit buffer in place with no dependencies. - """ - buf[::2], buf[1::2] = buf[1::2], buf[::2] diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py b/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py deleted file mode 100644 index 3c0df05fc..000000000 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/_byteswap_viper.py +++ /dev/null @@ -1,21 +0,0 @@ -import micropython - -if 0: - - class ptr8: - pass - - -@micropython.viper -def byteswap_viper(buf: ptr8, buf_size: int): # noqa: F821 - """ - Swap the bytes in a buffer of 16-bit values in place. - - Args: - buf: The buffer to swap the bytes in. - buf_size: The size of the buffer in bytes - """ - for i in range(0, buf_size, 2): - tmp = buf[i] - buf[i] = buf[i + 1] - buf[i + 1] = tmp diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py deleted file mode 100644 index 8f7d0c2cd..000000000 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/busdisplay.py +++ /dev/null @@ -1,607 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett and Kevin Schlosser -# -# SPDX-License-Identifier: MIT - -""" -displaysys.busdisplay -""" - -from displaysys import DisplayDriver -from micropython import const -import struct -import sys -import gc - -try: - from typing import Optional -except ImportError: - pass - -if sys.implementation.name == "micropython": - from machine import Pin - from time import sleep_ms - from micropython import alloc_emergency_exception_buf - - alloc_emergency_exception_buf(256) -elif sys.implementation.name == "circuitpython": - import digitalio - from time import sleep - - def sleep_ms(ms): - return sleep(ms / 1000) -else: - raise ImportError("BusDisplay is not supported on this platform.") - - -gc.collect() - -# MIPI DCS (Display Command Set) Command Constants -_INVOFF = const(0x20) -_INVON = const(0x21) -_CASET = const(0x2A) -_RASET = const(0x2B) -_RAMWR = const(0x2C) -_COLMOD = const(0x3A) -_MADCTL = const(0x36) -_RAMCONT = const(0x3C) -_SWRESET = const(0x01) -_SLPIN = const(0x10) -_SLPOUT = const(0x11) -_VSCRDEF = const(0x33) -_VSCSAD = const(0x37) - -# fmt: off - -# MIPI DCS MADCTL bits -# Bits 0 (Flip Vertical) and 1 (Flip Horizontal) affect how the display is refreshed, not how frame memory is written. -# Instead of using them, we only change Bits 6 (column/horizontal) and 7 (page/vertical). -_RGB = const(0x00) # (Bit 3: 0=RGB order, 1=BGR order) -_BGR = const(0x08) # (Bit 3: 0=RGB order, 1=BGR order) -_MADCTL_MH = const(0x04) # Refresh 0=Left to Right, 1=Right to Left (Bit 2: Display Data Latch Order) -_MADCTL_ML = const(0x10) # Refresh 0=Top to Bottom, 1=Bottom to Top (Bit 4: Line Refresh Order) -_MADCTL_MV = const(0x20) # 0=Normal, 1=Row/column exchange (Bit 5: Page/Column Addressing Order) -_MADCTL_MX = const(0x40) # 0=Left to Right, 1=Right to Left (Bit 6: Column Address Order) -_MADCTL_MY = const(0x80) # 0=Top to Bottom, 1=Bottom to Top (Bit 7: Page Address Order) - -# MADCTL values for each of the rotation constants. -_DEFAULT_ROTATION_TABLE = ( - _MADCTL_MX, # mirrored = False, rotation = 0 - _MADCTL_MV, # mirrored = False, rotation = 90 - _MADCTL_MY, # mirrored = False, rotation = 180 - _MADCTL_MY | _MADCTL_MX | _MADCTL_MV, # mirrored = False, rotation = 270 -) - -_MIRRORED_ROTATION_TABLE = ( - 0, # mirrored = True, rotation = 0 - _MADCTL_MV | _MADCTL_MX, # mirrored = True, rotation = 90 - _MADCTL_MX | _MADCTL_MY, # mirrored = True, rotation = 180 - _MADCTL_MV | _MADCTL_MY, # mirrored = True, rotation = 270 -) -# fmt: on - - -class BusDisplay(DisplayDriver): - """ - Base class for displays connected via a bus. - - Args: - display_bus (SPIBus, I80Bus): The bus the display is connected to. - init_sequence (bytes, list): The initialization sequence for the display. - width (int): The width of the display in pixels. - height (int): The height of the display in pixels. - colstart (int): The column start address for the display. - rowstart (int): The row start address for the display. - rotation (int): The rotation of the display in degrees. - mirrored (bool): If True, the display is mirrored. - color_depth (int): The color depth of the display in bits. - bgr (bool): If True, the display uses BGR color order. - invert (bool): If True, the display colors are inverted. - reverse_bytes_in_word (bool): If True, the bytes in 16-bit colors are reversed. - brightness (float): The brightness of the display as a float between 0.0 and 1.0. - backlight_pin (int, Pin): The pin the display backlight is connected to. - backlight_on_high (bool): If True, the backlight is on when the pin is high. - reset_pin (int, Pin): The pin the display reset is connected to. - reset_high (bool): If True, the reset pin is high. - power_pin (int, Pin): The pin the display power is connected to. - power_on_high (bool): If True, the power pin is high. - set_column_command (int): The command to set the column address. - set_row_command (int): The command to set the row address. - write_ram_command (int): The command to write to the display RAM. - brightness_command (int): The command to set the display brightness. - data_as_commands (bool): If True, data is sent as commands. - single_byte_bounds (bool): If True, single byte bounds are used. - - Attributes: - display_bus (SPIBus, I80Bus): The bus the display is connected to. - color_depth (int): The color depth of the display in bits. - bgr (bool): If True, the display uses BGR color order. - rotation_table (tuple): The rotation table for the display. - """ - - def __init__( - self, - display_bus, - init_sequence=None, - *, - width=0, - height=0, - colstart=0, - rowstart=0, - rotation=0, - mirrored=False, - color_depth=16, - bgr=False, - invert=False, - reverse_bytes_in_word=False, - brightness=1.0, - backlight_pin=None, - backlight_on_high=True, - reset_pin=None, - reset_high=True, - power_pin=None, - power_on_high=True, - set_column_command=_CASET, - set_row_command=_RASET, - write_ram_command=_RAMWR, - brightness_command=None, # For color OLEDs - data_as_commands=False, # For color OLEDs - single_byte_bounds=False, # For color OLEDs - ): - print("Started BusDisplay") - gc.collect() - self.display_bus = display_bus - self._width = width - self._height = height - self._colstart = colstart - self._rowstart = rowstart - self._rotation = rotation - self.color_depth = color_depth - self.bgr = bgr - self._invert = invert - self._requires_byteswap = reverse_bytes_in_word - 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 # not implemented - self._single_byte_bounds = single_byte_bounds # not implemented - - self.send = display_bus.send - self.send_color = ( - display_bus.send if not hasattr(display_bus, "send_color") else display_bus.send_color - ) - - self.rotation_table = _DEFAULT_ROTATION_TABLE if not mirrored else _MIRRORED_ROTATION_TABLE - - self._param_buf = bytearray(4) - self._param_mv = memoryview(self._param_buf) - - self._reset_pin = self._config_output_pin(reset_pin, value=not reset_high) - self._reset_high = reset_high - - self._power_pin = self._config_output_pin(power_pin, value=power_on_high) - self._power_on_high = power_on_high - - self._backlight_pin = self._config_output_pin(backlight_pin, value=backlight_on_high) - self._backlight_on_high = backlight_on_high - - if self._backlight_pin is not None: - try: - from machine import PWM - - self._backlight_pin = PWM(self._backlight_pin, freq=1000, duty_u16=0) - self._backlight_is_pwm = True - except ImportError: - # PWM not implemented on this platform or Pin - self._backlight_is_pwm = False - - # Run the display driver init_sequence. - if type(init_sequence) is bytes: - self._init_bytes(init_sequence) - elif type(init_sequence) is list or type(init_sequence) is tuple: - self._init_list(init_sequence) - - # Run the display driver init() method, which also gets called by rotation.setter - # This should run immediately after _init_bytes() or _init_list() but before - # sending other commands such as _INVON, _INVOFF, _COLMOD, brightness, etc. - self._initialized = False - super().__init__() - if not self._initialized: - raise RuntimeError("Display driver init() must call super().init()") - - # Set COLMOD (color mode) based on color_depth - pixel_formats = {3: 0x11, 8: 0x22, 12: 0x33, 16: 0x55, 18: 0x66, 24: 0x77} - self._param_buf[0] = pixel_formats[self.color_depth] - self.send(_COLMOD, self._param_mv[:1]) - - self.brightness = brightness - - gc.collect() - print("Finished BusDisplay") - - ############### Required API Methods ################ - - def init(self) -> None: - """ - Post initialization tasks. - - This method may be overridden by subclasses to perform any post initialization. - If it is overridden, it must call super().init() or set self._initialized = True. - """ - self._initialized = True - - # Convert from degrees to one quarter rotations. Wrap at the number of entries in the rotations table. - # For example, rotation = 90 -> index = 1. With 4 entries in the rotation table, rotation = 540 -> index = 2 - index = (self._rotation // 90) % len(self.rotation_table) - - # Set the display MADCTL bits for the given rotation. - self._param_buf[0] = self.rotation_table[index] | _BGR if self.bgr else _RGB - self.send(_MADCTL, self._param_mv[:1]) - - # Set the display inversion mode - self.invert_colors(self._invert) - - def blit_rect(self, buf: memoryview, x: int, y: int, w: int, h: int): - """ - Blit a buffer to the display. - - This method takes a buffer of pixel data and writes it to a specified - rectangular area of the display. The top-left corner of the rectangle is - specified by the x and y parameters, and the size of the rectangle is - specified by the width and height parameters. - - Args: - buf (memoryview): The buffer containing the pixel data. - x (int): The x-coordinate of the top-left corner of the rectangle. - y (int): The y-coordinate of the top-left corner of the rectangle. - w (int): The width of the rectangle in pixels. - h (int): The height of the rectangle in pixels. - - Returns: - (tuple): A tuple containing the x, y, width, and height of the rectangle. - """ - if self._auto_byteswap: - self.byteswap(buf) - - x1 = x + self.colstart - x2 = x1 + w - 1 - y1 = y + self.rowstart - y2 = y1 + h - 1 - - self._set_window(x1, y1, x2, y2) - self.send_color(self._write_ram_command, buf) - return (x, y, w, h) - - def fill_rect(self, x: int, y: int, w: int, h: int, c: int): - """ - Draw a rectangle at the given location, size and filled with color. - - This method draws a filled rectangle on the display. The top-left corner of - the rectangle is specified by the x and y parameters, and the size of the - rectangle is specified by the width and height parameters. The rectangle is - filled with the specified color. - - Args: - x (int): The x-coordinate of the top-left corner of the rectangle. - y (int): The y-coordinate of the top-left corner of the rectangle. - w (int): The width of the rectangle in pixels. - h (int): The height of the rectangle in pixels. - c (int): The color of the rectangle. - - Returns: - (tuple): A tuple containing the x, y, width, and height of the rectangle. - """ - color_bytes = ( - (c & 0xFFFF).to_bytes(2, "big") - if self._auto_byteswap - else (c & 0xFFFF).to_bytes(2, "little") - ) - x1 = x + self.colstart - x2 = x1 + w - 1 - y1 = y + self.rowstart - y2 = y1 + h - 1 - - if h > w: - buf = memoryview(bytearray(color_bytes * h)) - passes = w - else: - buf = memoryview(bytearray(color_bytes * w)) - passes = h - - self._set_window(x1, y1, x2, y2) - self.send(_RAMWR) - for _ in range(passes): - self.send_color(_RAMCONT, buf) - return (x, y, w, h) - - def pixel(self, x: int, y: int, c: int): - """ - Set a pixel on the display. - - Args: - x (int): The x-coordinate of the pixel. - y (int): The y-coordinate of the pixel. - c (int): The color of the pixel. - - Returns: - (tuple): A tuple containing the x, y, width, and height of the pixel. - """ - color_bytes = ( - (c & 0xFFFF).to_bytes(2, "big") - if self._auto_byteswap - else (c & 0xFFFF).to_bytes(2, "little") - ) - if self._auto_byteswap: - c = c >> 8 | c << 8 - xpos = x + self.colstart - ypos = y + self.rowstart - self._set_window(xpos, ypos, xpos, ypos) - self.send(_RAMWR, color_bytes) - return (x, y, 1, 1) - - ############### API Method Overrides ################ - - def vscrdef(self, tfa: int, vsa: int, bfa: int) -> None: - """ - Set Vertical Scrolling Definition. - - To scroll a 135x240 display these values should be 40, 240, 40. - There are 40 lines above the display that are not shown followed by - 240 lines that are shown followed by 40 more lines that are not shown. - You could write to these areas off display and scroll them into view by - changing the TFA, VSA and BFA values. - - Args: - tfa (int): Top Fixed Area. - vsa (int): Vertical Scrolling Area. - bfa (int): Bottom Fixed Area. - """ - super().vscrdef(tfa, vsa, bfa) - self.send(_VSCRDEF, struct.pack(">HHH", tfa, vsa, bfa)) - - def vscsad(self, vssa: Optional[int] = None) -> int: - """ - Set the vertical scroll start address. - - Args: - vssa (int, None): The vertical scroll start address. - - Returns: - int: The vertical scroll start address. - """ - if vssa is not None: - super().vscsad(vssa) - self.send(_VSCSAD, struct.pack(">H", self._vssa)) - return self._vssa - - ############### Optional API Methods ################ - - @property - def colstart(self): - """ - The offset in pixels to the first column of the visible display. - """ - rot = self.rotation % 360 - if rot == 0 or rot == 180: - return self._colstart - return self._rowstart - - @property - def rowstart(self): - """ - The offset in pixels to the first row of the visible display. - """ - rot = self.rotation % 360 - if rot == 0 or rot == 180: - return self._rowstart - return self._colstart - - @property - def power(self) -> bool: - """ - The power state of the display. - - Returns: - bool: The power state of the display. - """ - if self._power_pin is None: - return -1 - - state = self._power_pin.value() - if self._power_on_high: - return state - - return not state - - @power.setter - def power(self, value: bool) -> None: - """ - Set the power state of the display. - - Args: - value (bool): The power state to set, True for on, False for off. - """ - if self._power_pin is None: - return - - if self._power_on_high: - self._power_pin.value(value) - else: - self._power_pin.value(not value) - - @property - def brightness(self) -> float: - """ - The brightness of the display. - """ - if self._backlight_pin is None and self._brightness_command is None: - return -1 - - return self._brightness - - @brightness.setter - def brightness(self, value: float) -> None: - """ - Set the brightness of the display. - - Args: - value (float): The brightness of the display as a float between 0.0 and 1.0. - """ - if 0 <= float(value) <= 1.0: - self._brightness = value - if self._backlight_pin: - if not self._backlight_on_high: - value = 1.0 - value - if self._backlight_is_pwm: - if sys.implementation.name == "micropython": - self._backlight_pin.duty_u16(int(value * 0xFFFF)) - elif sys.implementation.name == "circuitpython": - self._backlight_pin.duty_cycle = int(value * 0xFFFF) - else: - if sys.implementation.name == "micropython": - self._backlight_pin.value(value > 0.5) - elif sys.implementation.name == "circuitpython": - self._backlight_pin.value = value > 0.5 - elif self._brightness_command is not None: - self._param_buf[0] = int(value * 255) - self.send(self._brightness_command, self._param_mv[:1]) - - def invert_colors(self, value: bool) -> None: - """ - Invert the colors of the display. - - Args: - value (bool): If True, invert the colors of the display. - """ - if value: - self.send(_INVON) - else: - self.send(_INVOFF) - - def reset(self) -> None: - """ - Reset display. - - This method resets the display. If the display has a reset pin, it is - reset using the reset pin. Otherwise, the display is reset using the - software reset command. - """ - if self._reset_pin is not None: - self.hard_reset() - else: - self.soft_reset() - - def hard_reset(self) -> None: - """ - Hard reset display. - """ - self._reset_pin.value(self._reset_high) - sleep_ms(120) - self._reset_pin.value(not self._reset_high) - - def soft_reset(self) -> None: - """ - Soft reset display. - """ - self.send(_SWRESET) - sleep_ms(150) - - def sleep_mode(self, value: bool) -> None: - """ - Enable or disable display sleep mode. - - Args: - value (bool): If True, enable sleep mode. If False, disable sleep mode. - """ - self.send(_SLPIN if value else _SLPOUT) - - ############### Class Specific Methods ############## - - def _set_window(self, x1, y1, x2, y2): - # See https://github.com/adafruit/Adafruit_Blinka_Displayio/blob/main/displayio/_displaysys.py#L271-L363 - # TODO: Add `if self._single_byte_bounds is True:` for Column and Row _param_buf packing - - # Column addresses - self._param_buf[0] = (x1 >> 8) & 0xFF - self._param_buf[1] = x1 & 0xFF - self._param_buf[2] = (x2 >> 8) & 0xFF - self._param_buf[3] = x2 & 0xFF - self.send(self._set_column_command, self._param_mv[:4]) - - # Row addresses - self._param_buf[0] = (y1 >> 8) & 0xFF - self._param_buf[1] = y1 & 0xFF - self._param_buf[2] = (y2 >> 8) & 0xFF - self._param_buf[3] = y2 & 0xFF - self.send(self._set_row_command, self._param_mv[:4]) - - def _init_bytes(self, init_sequence): - """ - Send an initialization sequence to the display. - - Used by display driver subclass if init_sequence is a CircuitPython displayIO compatible bytes object. - The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins - with a command byte followed by a byte to determine the parameter count and if a - delay is need after. When the top bit of the second byte is 1, the next byte will be - the delay time in milliseconds. The remaining 7 bits are the parameter count - excluding any delay byte. The third through final bytes are the remaining command - parameters. The next byte will begin a new command definition. - - Args: - init_sequence (bytes): The initialization sequence to send to the display. - """ - DELAY = 0x80 - - i = 0 - while i < len(init_sequence): - command = init_sequence[i] - data_size = init_sequence[i + 1] - delay = (data_size & DELAY) != 0 - data_size &= ~DELAY - - self.send(command, init_sequence[i + 2 : i + 2 + data_size]) - - delay_time_ms = 10 - if delay: - data_size += 1 - delay_time_ms = init_sequence[i + 1 + data_size] - if delay_time_ms == 255: - delay_time_ms = 500 - - sleep_ms(delay_time_ms) - i += 2 + data_size - - def _init_list(self, init_sequence): - """ - Send an initialization sequence to the display. - - Used by display driver subclass if init_sequence is a list of tuples. - As a list, it can be modified in .init(), for example: - self._INIT_SEQUENCE[-1] = (0x29, b"\x00", 100) - Each tuple contains the following: - - The first element is the register address (command) - - The second element is the register value (data) - - The third element is the delay in milliseconds after the register is set - - Args: - init_sequence (list): The initialization sequence to send to the display - """ - for line in init_sequence: - self.send(line[0], line[1]) - if line[2] != 0: - sleep_ms(line[2]) - - def _config_output_pin(self, pin, value=None): - if pin is None: - return None - - if sys.implementation.name == "micropython": - p = Pin(pin, Pin.OUT) - if value is not None: - p.value(value) - elif sys.implementation.name == "circuitpython": - p = digitalio.DigitalInOut(pin) - p.direction = digitalio.Direction.OUTPUT - if value is not None: - p.value = value - return p diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py index 241a1b935..928c521f3 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py @@ -1,4 +1,4 @@ -""" pydisplay_simpletest.py """ +""" displaysys_block_test.py """ from board_config import display_drv import random import time diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py index 05bddb4a4..6faff91b9 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py @@ -1,4 +1,4 @@ -""" pydisplay_simpletest.py """ +""" displaysys_fill_rect_test.py """ from board_config import display_drv from random import randint, getrandbits import time diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 54caaed46..6d494ea65 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") -package("displaysys") \ No newline at end of file +metadata( + description="displaysys", + version="0.1.0", + pypi_publish="pydisplay-displaysys", +) +package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index f502e73d7..9dc64547d 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") +metadata( + description="eventsys", + version="0.1.0", + pypi_publish="pydisplay-eventsys", +) package("eventsys") diff --git a/micropython/pydisplay/gpio_pin/gpio_pin.py b/micropython/pydisplay/gpio_pin/gpio_pin.py deleted file mode 100644 index c9268a99f..000000000 --- a/micropython/pydisplay/gpio_pin/gpio_pin.py +++ /dev/null @@ -1,219 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT - -from machine import Pin as _Pin -from sys import platform, implementation - - -if platform == "pyboard": - import stm - - gpio_data = { - "pyboard": { - "BSRR": stm.GPIO_BSRR, - } - } -else: - gpio_data = { - "esp32": { - "SET": 0x8, - "CLR": 0xC, - "gpios": { - "esp32s3": (0x60004000, 0x6000400C), - "esp32c3": (0x60004000, 0x6000400C), - "esp32c2": (0x60004000, 0x6000400C), - "esp32p4": (0x60091000, 0x6009100C), - "esp32c6": (0x60091000, 0x6009100C), - "esp32c5": (0x60091000, 0x6009100C), - "esp32h2": (0x60091000, 0x6009100C), - "esp32s2": (0x3F404000, 0x3F40400C), - "*": (0x3FF44000, 0x3FF4400C), - }, - }, - "samd": { - "SET": 0x18, - "CLR": 0x14, - "gpios": { - "samd21": (0x41004400, 0x41004480), - "samd51": (0x41008000, 0x41008080, 0x41008100, 0x41008180), - }, - }, - "rp2": { - # See 3.1.11 at https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf - "SET": 0x14, - "CLR": 0x18, - "gpios": { - "rp2040": ( - # The SIO registers start at a base address of 0xd0000000 (defined as SIO_BASE in SDK). - 0xD0000000, - ), - }, - }, - "nrf": { - "SET": 0x8, - "CLR": 0xC, - "gpios": { - "nrf5": (0x50000000, 0x50000300), - "nrf9": (0x40842000,), - }, - }, - "mimxrt": { - "SET": 0x84, - "CLR": 0x88, - "gpios": { - "mimxrt10": ( - 0x401B8000, - 0x401BC000, - 0x401C0000, - 0x401C4000, - 0x400C0000, - 0x42000000, - 0x42004000, - 0x42008000, - 0x4200C000, - ), - "mimxrt11": ( - 0x4012C000, - 0x40130000, - 0x40134000, - 0x40138000, - 0x4013C000, - 0x40140000, - 0x40C5C000, - 0x40C60000, - 0x40C64000, - 0x40C68000, - 0x40C6C000, - 0x40C70000, - 0x40CA0000, - ), - }, - }, - "renasas-ra": { - "BSRR": 0x8, - "gpios": { - "ra6m5": ( - 0x40080000, - 0x40080020, - 0x40080040, - 0x40080060, - 0x40080080, - 0x400800A0, - 0x400800C0, - 0x400800E0, - 0x40080100, - 0x40080120, - 0x40080140, - 0x40080160, - ), - "*": ( - 0x40040000, - 0x40040020, - 0x40040040, - 0x40040060, - 0x40040080, - 0x400400A0, - 0x400400C0, - 0x400400E0, - 0x40040100, - 0x40040120, - ), - }, - }, - } - - -def _init_module(): - data = gpio_data[platform.lower()] - if "BSRR" in data: - GPIO_Pin.BSRR = data["BSRR"] - else: - GPIO_Pin.SET = data["SET"] - GPIO_Pin.CLR = data["CLR"] - - if hasattr(_Pin, "gpio"): # If the gpio method is already implemented - return - - # Convert to lowercase and remove - and _ from sys.implementation._machine - # which is in the format "Generic ESP32S3 module with ESP32S3" - machine = implementation._machine.lower().replace("-", "").replace("_", "") - for mcu, gpios in data["gpios"].items(): # Find the GPIOs for the current machine - if mcu in machine: # If the mcu is in the machine name - GPIO_Pin._gpios = gpios # Set the GPIOs and break - break - if GPIO_Pin._gpios is None: # If the GPIOs are not set - if "*" in data["gpios"].keys(): # If there is a wildcard - GPIO_Pin._gpios = data["gpios"]["*"] # Set the GPIOs to the wildcard - else: - raise NotImplementedError( - f"class GPIO_Pin not implemented for {platform} on {machine}" - ) - - -class GPIO_Pin(_Pin): - """ - GPIO_Pin is a subclass of machine.Pin that provides additional methods for - accessing the GPIO registers on a microcontroller. It is used by the - I80Bus class to control the GPIO pins that are used to communicate with - the display. - """ - - PPP = None - SET = None - CLR = None - BSRR = None - _gpios = None - - def __init__(self, id, *args, **kwargs): - if hasattr(id, "names"): - id = id.names()[1] - super().__init__(id, *args, **kwargs) - self._id = id if isinstance(id, int) else None - if self.BSRR is None: - self.PPP = 32 - else: - self.PPP = 16 - - def pin(self): - """ - Returns the pin number in the port of the GPIO pin. - - Returns: - int: The pin number in the port of the GPIO pin - """ - if hasattr(super(), "pin"): - return super().pin() - if isinstance(self._id, int): - return self._id & (self.PPP - 1) - raise NotImplementedError("GPIO_Pin.pin not implemented for this platform") - - def port(self) -> int: - """ - Returns the port number of the GPIO pin. - - Returns: - int: The port number of the GPIO pin. - """ - if hasattr(super(), "port"): - return super().port() - if isinstance(self._id, int): - return self._id // self.PPP - raise NotImplementedError("GPIO_Pin.port not implemented for this platform") - - def gpio(self) -> int: - """ - Returns the address of the GPIO pin. - - Returns: - int: The address of the GPIO pin. - """ - if hasattr(super(), "gpio"): - x = super().gpio() # returns as signed int - if x < 0: # if it is negative - x = x + (1 << 31) # convert it to unsigned int - return x - return self._gpios[self.port()] - - -_init_module() diff --git a/micropython/pydisplay/gpio_pin/manifest.py b/micropython/pydisplay/gpio_pin/manifest.py deleted file mode 100644 index 02330a21a..000000000 --- a/micropython/pydisplay/gpio_pin/manifest.py +++ /dev/null @@ -1,3 +0,0 @@ -metadata(description="GPIO Wrapper for machine.Pin", version="0.1.0") - -module("gpio_pin.py", opt=3) diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 8917f3898..b25d754a4 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") +metadata( + description="graphics", + version="0.1.0", + pypi_publish="pydisplay-graphics", +) package("graphics") diff --git a/micropython/pydisplay/i80bus/i80bus.py b/micropython/pydisplay/i80bus/i80bus.py deleted file mode 100644 index 262dfcc37..000000000 --- a/micropython/pydisplay/i80bus/i80bus.py +++ /dev/null @@ -1,341 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Brad Barnett -# -# SPDX-License-Identifier: MIT -""" -i80bus -""" - -from array import array -from uctypes import addressof -import struct -import micropython -from micropython import const - -try: - from typing import Optional -except ImportError: - pass - -# _I80BaseBus will work with either Pin class, but I80Bus will only work with GPIO_Pin -try: - from gpio_pin import GPIO_Pin as Pin -except ImportError: - from machine import Pin - -if 0: - ptr8 = ptr16 = ptr32 = None # For type hints - - -DC_CMD = const(0) -DC_DATA = const(1) -CS_ACTIVE = const(0) -CS_INACTIVE = const(1) -WR_ACTIVE = const(1) -WR_INACTIVE = const(0) - - -class _I80BaseBus: - """ - Base class for I80 bus communication. - - Args: - dc (int): The pin number for the data/command control. - cs (int): The pin number for the chip select. - wr (int): The pin number for the write control. - data (list[int]): A list of pin numbers for the data pins. - freq (int): The frequency for the bus. Defaults to 20,000,000. - """ - - def __init__( - self, - dc: int, - cs: int, - wr: int, - data: list[int], - freq: int = 20_000_000, - ) -> None: - print("I80Bus loading...") - # Not used in this class; may be used in subclasses like _i80bus_rp2.py - self._freq = freq - - # Create a list of Pin objects for the data pins - # NOTE: data8-15 are optional and not implemented in most subclasses - data_pins = [Pin(pin, Pin.OUT) for pin in data] - - # Setup the control pins - # _wr_active, _wr_inactive are boolean values - # indicating the level of the pin in that state. True is high, False is low. - self._dc: Pin = Pin(dc, Pin.OUT) - self._dc(DC_CMD) # Set the DC pin to the command level - - # If cs was not specified, set it to a lambda that does nothing - # so lines like the next won't fail. - self._cs: Pin = Pin(cs, Pin.OUT) if cs != -1 else lambda val: None - self._cs(CS_INACTIVE) # Set the CS pin to the inactive level - - self._wr: Pin = Pin(wr, Pin.OUT) - self._wr(WR_INACTIVE) # Set the WR pin to the inactive level - - self._buf1: bytearray = bytearray(1) - - self._setup(data_pins) - print("I80Bus loaded") - - @micropython.native - def send(self, command: Optional[int] = None, data: Optional[memoryview] = None) -> None: - """ - Sends a command and/or data to the device. - Args: - command (Optional[int]): The command to send. Defaults to None. - data (Optional[memoryview]): The data to send. Defaults to None. - Returns: - None - """ - - self._cs(CS_ACTIVE) - - if command is not None: - struct.pack_into("B", self._buf1, 0, command) - self._dc(DC_CMD) - self._write(self._buf1, 1) - - if data and len(data): - self._dc(DC_DATA) - self._write(data, len(data)) - - self._cs(CS_INACTIVE) - - def deinit(self): - pass - - def __del__(self): - self.deinit() - - -class I80Bus(_I80BaseBus): - """ - Class for I80 bus communication. - - Args: - dc (int): The pin number for the data/command control. - cs (int): The pin number for the chip select. - wr (int): The pin number for the write control. - data (list[int]): A list of pin numbers for the data pins. - freq (int): The frequency for the bus. Defaults to 20,000,000. - """ - - def _setup(self, data_pins: list[Pin]) -> None: - # Make sure GPIO_Pin was imported - if not hasattr(Pin, "BSRR"): - raise ValueError("GPIO_Pin not imported") - - # If self._is_32bit is True the _write method will use a 32-bit set and a 32-bit - # clear register. Otherwise, the _write method will use set_reset registers - # which use the lower 16 bits for set and the upper 16 bits for clear. - self._is_32bit = True if self._wr.BSRR is None else False - - # Both lut mode and sequential mode need the write pin registers and masks saved to use in viper. - # Subclasses may not need the write pin registers and masks, so they are defined here instead of - # in __init__. Subclasses should override _setup. - if self._is_32bit: - self._wr_mask = self._wr_not_mask = 1 << self._wr.pin() - self._wr_reg = self._wr.gpio() + (self._wr.SET if WR_ACTIVE else self._wr.CLR) - self._wr_not_reg = self._wr.gpio() + (self._wr.CLR if WR_ACTIVE else self._wr.SET) - else: - self._wr_reg = self._wr_not_reg = self._wr.gpio() + self._wr.BSRR - self._wr_mask = 1 << (self._wr.pin() + (0 if WR_ACTIVE else 16)) - self._wr_not_mask = 1 << (self._wr.pin() + (16 if WR_ACTIVE else 0)) - - if False: # Set to True to print the write pin registers and masks - print( - f"\n{self._wr=}\n {self._wr_reg=:#010x}, {self._wr_mask=:#034b}\n {self._wr_not_reg=:#010x}, {self._wr_not_mask=:#034b}\n" - ) - - # Determine which mode, lut or sequential, to use - # If all pins are on the same port and sequential: - if all(p.port() == data_pins[0].port() for p in data_pins) and all( - data_pins[i].pin() + 1 == data_pins[i + 1].pin() for i in range(len(data_pins) - 1) - ): - # Use sequential mode - self._setup_seq(data_pins) - else: - # Use LUT mode - self._setup_lut(data_pins) - - def _setup_lut(self, pins: list[Pin]) -> None: - """ - Setup lookup tables, pin data and the _write method for LUT mode. - """ - print("Using LUT mode") - if len(pins) != 8: - raise ValueError("LUT mode only supports 8 data pins") - self._write = self._write_lut - - # Setup the data for pin_data and the lookup tables - lut_len = 2 ** len(pins) # Number of entries per lut -- 256 for 8-bit bus width - port_list = [] # list of port numbers in use - for item in [p.port() for p in pins]: # Create a list of unique port numbers - if item not in port_list: - port_list.append(item) - # Map port numbers to lookup table index - lut_map = {port: i for i, port in enumerate(port_list)} - self._num_luts = len(lut_map) # Number of lookup tables - self._lookup_tables = [None] * self._num_luts # list of bytearray lookup tables - pin_masks = [None] * self._num_luts # list of 32-bit pin masks - regsA = [None] * self._num_luts # list of SET registers if _is_32bit else BSRR registers - regsB = [None] * self._num_luts # list of CLR registers if _is_32bit else unused - - # Create the pin_masks, populate the 2 reg lists and initialize the lookup_tables - # for each port of 16 or 32 pins. Will be saved in array pin_data later. - for i in range(self._num_luts): - port = port_list[i] - port_pins = [p for p in pins if p.port() == port] - first_pin = port_pins[0] - pin_mask = sum([1 << p.pin() for p in port_pins]) - if self._is_32bit: - self._lookup_tables[i] = array("I", [0] * lut_len) # 32-bit array - regsA[i] = first_pin.gpio() + first_pin.SET - regsB[i] = first_pin.gpio() + first_pin.CLR - else: - self._lookup_tables[i] = array("H", [0] * lut_len) # 16-bit array - regsA[i] = first_pin.gpio() + first_pin.BSRR - regsB[i] = 0x0 - pin_masks[i] = pin_mask - if False: # Set to True to print the pin data - print(f" {i=}: {port=}, A={regsA[i]:#0x}, B={regsB[i]:#0x}, ", end="") - print(f"mask={pin_masks[i]:#034b}, pins={[p.pin() for p in port_pins]}") - - # Populate the lookup tables - for index in range(lut_len): # Iterate through all possible 8-bit values (0 to 255) - for bit_number, pin in enumerate(pins): # Iterate through each pin - if index & (1 << bit_number): # If the bit is set in the index - # Get the current value for index from the appropriate lookup table - value = self._lookup_tables[lut_map[pin.port()]][index] - value |= 1 << pin.pin() # Update the value for the pin - self._lookup_tables[lut_map[pin.port()]][index] = value # Save the value - - # Save all settings in a struct-like array pin_data for use in viper. - # Could be merged with the first loop above, but left here for clarity. - pin_data = array("I", [0] * 4 * self._num_luts) - for i in range(self._num_luts): - pin_data[i * 4 + 0] = pin_masks[i] - pin_data[i * 4 + 1] = regsA[i] - pin_data[i * 4 + 2] = regsB[i] - pin_data[i * 4 + 3] = addressof(self._lookup_tables[i]) - - if False: # Set to True to print the lookup tables - print( - f"\nlut={i}: mask={pin_masks[i]:#034b}, {regsA[i]=:#010x}, {regsB[i]=:#010x}" - ) - for j in range(lut_len): - print(f" {j:3d}: {self._lookup_tables[i][j]:#034b}") - - self._pin_data = memoryview(pin_data) # Save a memoryview into pin_data for use in viper - - @micropython.viper - def _write_lut(self, data: ptr8, length: int): - # Cache these values to avoid accessing the self namespace every iteration - wr_not_reg = ptr32(self._wr_not_reg) - wr_not_mask = int(self._wr_not_mask) - wr_reg = ptr32(self._wr_reg) - wr_mask = int(self._wr_mask) - is_32bit = bool(self._is_32bit) # noqa: F841 - pin_data = ptr32(self._pin_data) - num_luts = int(self._num_luts) - - last: int = -1 - for i in range(length): # Iterate through the data - wr_not_reg[0] = wr_not_mask # WR Inactive - val = data[i] # Get the value from the data - # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) - if val != last: - if False: - print(f"{val=:#010b} ({val=:#04x})") # noqa: E701 - for n in range(num_luts): # Iterate through the lookup tables - if False: - print(f"{ n=}") # noqa: E701 - pin_mask = pin_data[n * 4 + 0] # Get the pin mask - regA = ptr32(pin_data[n * 4 + 1]) # Get the SET or BSRR register - if True: # Should be `if is_32bit:` but not supported in viper - regB = ptr32(pin_data[n * 4 + 2]) # Only need regB (CLEAR) for 32-bit - lut = ptr32(pin_data[n * 4 + 3]) # 32-bit lookup table - tx_value: int = lut[val] # Get the 32-bit value from the lookup table - regA[0] = tx_value # Set the bits that are on - regB[0] = tx_value ^ pin_mask # Clear the bits that are off - else: - lut = ptr16(pin_data[n * 4 + 3]) # 16-bit lookup table - tx_value: int = lut[val] # Get the 16-bit value from the lookup table - # Set the bits that are on and clear the bits that are off - regA[0] = (tx_value << 0) | ((tx_value ^ pin_mask) << 16) - if False: # Print debug info - print(f" {tx_value=:#034b}") - print(f" {pin_mask=:#034b}") - print( - f" wrote: {(tx_value | ((tx_value ^ pin_mask) << 16)):#034b}" - ) - # raise ValueError("Debugging") - last = val # Save the value for the next iteration - wr_reg[0] = wr_mask # WR Active - - def _setup_seq(self, pins: list[Pin]) -> None: - print("Using sequential mode") - if len(pins) == 8: - self._write = self._write_seq8 - elif len(pins) == 16: - self._write = self._write_seq16 - else: - raise ValueError("Sequential mode only supports 8 or 16 data pins") - - # Setup the data for pin_data - pin_mask = sum([1 << p.pin() for p in pins]) - first_pin = pins[0] - if self._is_32bit: - regA = first_pin.gpio() + first_pin.SET - regB = first_pin.gpio() + first_pin.CLR - else: - regA = first_pin.gpio() + first_pin.BSRR - regB = 0x0 - shift = first_pin.pin() - - # save all settings in an array pin_data for use in viper - pin_data = array("I", [0] * 4) - pin_data[0] = pin_mask - pin_data[1] = regA - pin_data[2] = regB - pin_data[3] = shift - self._pin_data = memoryview(pin_data) - - @micropython.viper - def _write_seq8(self, data: ptr8, length: int): - # Cache these values to avoid accessing the self namespace every iteration - wr_not_reg = ptr32(self._wr_not_reg) - wr_not_mask = int(self._wr_not_mask) - wr_reg = ptr32(self._wr_reg) - wr_mask = int(self._wr_mask) - is_32bit = bool(self._is_32bit) - pin_data = ptr32(self._pin_data) - - pin_mask = pin_data[0] - regA = ptr32(pin_data[1]) - regB = ptr32(pin_data[2]) - shift = pin_data[3] - - last: int = -1 - for i in range(length): # Iterate through the data - wr_not_reg[0] = wr_not_mask # WR Inactive - val = data[i] # Get the value from the data - # If the pin states need to be changed (optimization for colors where LSB == MSB, like white and black) - if val != last: - tx_value: int = val << shift # Shift the value to the correct position - if is_32bit: - regA[0] = tx_value # Set the bits that are on - regB[0] = tx_value ^ pin_mask # Clear the bits that are off - else: - # Set the bits that are on and clear the bits that are off - regA[0] = tx_value | ((tx_value ^ pin_mask) << 16) - last = val # Save the value for the next iteration - wr_reg[0] = wr_mask # WR Active - - @micropython.viper - def _write_seq16(self, data: ptr16, length: int): - raise NotImplementedError("16 pin sequential mode not implemented") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index d624644d5..a0c8d77a2 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") +metadata( + description="palettes", + version="0.1.0", + pypi_publish="pydisplay-palettes", +) package("palettes") diff --git a/micropython/pydisplay/spibus/manifest.py b/micropython/pydisplay/spibus/manifest.py deleted file mode 100644 index 564517f38..000000000 --- a/micropython/pydisplay/spibus/manifest.py +++ /dev/null @@ -1,3 +0,0 @@ -metadata(description="SPIBus driver", version="0.1.0") - -module("spibus.py", opt=3) diff --git a/micropython/pydisplay/spibus/spibus.py b/micropython/pydisplay/spibus/spibus.py deleted file mode 100644 index 61e4eb24b..000000000 --- a/micropython/pydisplay/spibus/spibus.py +++ /dev/null @@ -1,149 +0,0 @@ -# SPDX-License-Identifier: MIT -""" -spibus -""" - -from machine import Pin, SPI -import struct -import micropython -from micropython import const - -try: - from typing import Optional, Union -except ImportError: - pass - - -DC_CMD = const(0) -DC_DATA = const(1) -CS_ACTIVE = const(0) -CS_INACTIVE = const(1) - - -class SPIBus: - """ - Represents an SPI bus. - - Args: - id (int): The ID of the SPI bus. - baudrate (int): The baudrate of the SPI bus. - polarity (int): The polarity of the SPI bus. - phase (int): The phase of the SPI bus. - bits (int): The number of bits per transfer. - lsb_first (bool): Whether to send the least significant bit first. - sck (int): The pin number of the SCK pin. - mosi (int): The pin number of the MOSI pin. - miso (int): The pin number of the MISO pin. - dc (int): The pin number of the DC pin. - cs (int): The pin number of the CS pin. - - Raises: - ValueError: If the DC pin is not specified. - """ - - def __init__( - self, - *, - id: int = 2, - baudrate: int = 24_000_000, - polarity: int = 0, - phase: int = 0, - bits: int = 8, - lsb_first: bool = False, - sck: int = -1, - mosi: int = -1, - miso: int = -1, - dc: int = -1, - cs: int = -1, - ) -> None: - print("SPIBus loading...") - if dc == -1: - raise ValueError("DC pin must be specified") - - self._baudrate: int = baudrate - self._polarity: int = polarity - self._phase: int = phase - self._bits: int = bits - self._firstbit: int = SPI.LSB if lsb_first else SPI.MSB - - if mosi == -1 and miso == -1 and sck == -1: - self._spi: SPI = SPI( - id, - baudrate=self._baudrate, - polarity=self._polarity, - phase=self._phase, - bits=self._bits, - firstbit=self._firstbit, - ) - else: - self._spi: SPI = SPI( - id, - baudrate=self._baudrate, - polarity=self._polarity, - phase=self._phase, - bits=self._bits, - firstbit=self._firstbit, - sck=Pin(sck, Pin.OUT), - mosi=Pin(mosi, Pin.OUT), - miso=Pin(miso, Pin.IN) if miso > -1 else None, - ) - - # DC and CS pins must be set AFTER the SPI bus is initialized on some boards - self._dc: Pin = Pin(dc, Pin.OUT, value=DC_DATA) - self._cs: Union[Pin, callable] = ( - Pin(cs, Pin.OUT, value=CS_INACTIVE) if cs != -1 else lambda val: None - ) - - self._buf1: bytearray = bytearray(1) - print("SPIBus loaded") - - @micropython.native - def send( - self, - command: Optional[int] = None, - data: Optional[memoryview] = None, - ) -> None: - """ - Sends a command and/or data over the SPI bus. - - Args: - command (int): The command to send. - data (memoryview): The data to send. - - Returns: - None - """ - - self._spi.init( - baudrate=self._baudrate, - polarity=self._polarity, - phase=self._phase, - bits=self._bits, - firstbit=self._firstbit, - ) - - self._cs(CS_ACTIVE) - - if command is not None: - struct.pack_into("B", self._buf1, 0, command) - self._dc(DC_CMD) - self._spi.write(self._buf1) - - if data and len(data): - self._dc(DC_DATA) - self._spi.write(data) - - self._cs(CS_INACTIVE) - - def deinit(self) -> None: - """ - Deinitializes the SPI bus. - - Returns: - None - """ - - self._spi.deinit() - - def __del__(self) -> None: - self.deinit() diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index cd4f4f9a6..30d178f87 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,2 +1,6 @@ -metadata(version="0.1.0") +metadata( + description="timer", + version="0.1.0", + pypi_publish="pydisplay-timer", +) package("timer") From 76d4b6c565db0ac209389102477f7582c8fa5481 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:01:28 -0600 Subject: [PATCH 15/35] pydisplay: Code linting. Signed-off-by: Brad Barnett --- .../pydisplay/displaybuf/examples/displaybuf_simpletest.py | 3 --- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-busdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-fbdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-jndisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-pgdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-psdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-sdldisplay/manifest.py | 2 +- micropython/pydisplay/displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/manifest.py | 2 +- micropython/pydisplay/timer/manifest.py | 2 +- 13 files changed, 12 insertions(+), 15 deletions(-) diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py index 29b858875..220775bac 100644 --- a/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py @@ -61,7 +61,4 @@ def main(scroll=False, animate=False, text1="displaybuf", text2="simpletest"): ssd.show() -launch = lambda: main(animate=True) -wipe = lambda: main(scroll=True) - main() diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index a16d5f52f..afcc65c79 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaybuf", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 0bfed6d0f..e17edead2 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-busdisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index a069e9d45..efff6ff7a 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-fbdisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index a6f0c2690..e5e6a5843 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-jndisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 397ae332b..a8cf88ec7 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-pgdisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 41cc113ef..44b9643bc 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-psdisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index eaa14b064..a8b0948b1 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-sdldisplay", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 6d494ea65..e6fd64631 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 9dc64547d..27009a024 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( description="eventsys", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index b25d754a4..a6c213be6 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( description="graphics", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index a0c8d77a2..73461af69 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( description="palettes", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 30d178f87..ffc9cb2be 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( description="timer", - version="0.1.0", + version="0.1.1", pypi_publish="pydisplay-timer", ) package("timer") From 3edae08edccf266b6914293731c9c2d00fcd6237 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:17:25 -0600 Subject: [PATCH 16/35] pydisplay: Keep going. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../displaysys/displaysys-busdisplay/displaysys/busdisplay.py | 4 ++-- .../pydisplay/displaysys/displaysys-busdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-fbdisplay/manifest.py | 2 +- .../displaysys/displaysys-jndisplay/examples/board_config.py | 0 .../pydisplay/displaysys/displaysys-jndisplay/manifest.py | 2 +- .../displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py | 0 .../displaysys/displaysys-pgdisplay/examples/board_config.py | 0 .../pydisplay/displaysys/displaysys-pgdisplay/manifest.py | 2 +- .../displaysys/displaysys-psdisplay/examples/board_config.py | 0 .../pydisplay/displaysys/displaysys-psdisplay/manifest.py | 2 +- .../displaysys-sdldisplay/displaysys/sdldisplay/__init__.py | 0 .../displaysys/displaysys-sdldisplay/examples/board_config.py | 0 .../pydisplay/displaysys/displaysys-sdldisplay/manifest.py | 2 +- micropython/pydisplay/displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/eventsys/keys.py | 0 micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/manifest.py | 2 +- micropython/pydisplay/timer/manifest.py | 2 +- micropython/pydisplay/timer/timer/_sdl2.py | 0 21 files changed, 14 insertions(+), 14 deletions(-) mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py mode change 100755 => 100644 micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py mode change 100755 => 100644 micropython/pydisplay/eventsys/eventsys/keys.py mode change 100755 => 100644 micropython/pydisplay/timer/timer/_sdl2.py diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index afcc65c79..410bb415b 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaybuf", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py index 84647c926..1b7998e15 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/displaysys/busdisplay.py @@ -447,11 +447,11 @@ def brightness(self, value: float) -> None: Args: value (float): The brightness of the display as a float between 0.0 and 1.0. """ - if 0 <= float(value) <= 1.0: + if 0 <= float(value) <= 1: self._brightness = value if self._backlight_pin: if not self._backlight_on_high: - value = 1.0 - value + value = 1 - value if self._backlight_is_pwm: if sys.implementation.name == "micropython": self._backlight_pin.duty_u16(int(value * 0xFFFF)) diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index e17edead2..e02a315dd 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-busdisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index efff6ff7a..a1d9044a1 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-fbdisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index e5e6a5843..215bf7ecc 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-jndisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index a8cf88ec7..44362f435 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-pgdisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 44b9643bc..2f01aa3bc 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-psdisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index a8b0948b1..204559c85 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-sdldisplay", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index e6fd64631..3597f20a9 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/eventsys/keys.py b/micropython/pydisplay/eventsys/eventsys/keys.py old mode 100755 new mode 100644 diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 27009a024..8c492e6cc 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( description="eventsys", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index a6c213be6..424b86aa1 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( description="graphics", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 73461af69..d71ff6de0 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( description="palettes", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index ffc9cb2be..e1b60ea13 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( description="timer", - version="0.1.1", + version="0.1.2", pypi_publish="pydisplay-timer", ) package("timer") diff --git a/micropython/pydisplay/timer/timer/_sdl2.py b/micropython/pydisplay/timer/timer/_sdl2.py old mode 100755 new mode 100644 From 2b5f61a0b8eb3fe217113f56a06b166debdbbd3b Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:24:40 -0600 Subject: [PATCH 17/35] pydisplay: Still going. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../displaysys/displaysys-busdisplay/manifest.py | 2 +- .../displaysys/displaysys-fbdisplay/manifest.py | 2 +- .../displaysys/displaysys-jndisplay/manifest.py | 2 +- .../displaysys/displaysys-pgdisplay/manifest.py | 2 +- .../displaysys/displaysys-psdisplay/manifest.py | 2 +- .../displaysys/displaysys-sdldisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys/manifest.py | 2 +- .../pydisplay/eventsys/eventsys/__init__.py | 16 ++++++++-------- micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/manifest.py | 2 +- micropython/pydisplay/palettes/palettes/wheel.py | 2 +- micropython/pydisplay/timer/manifest.py | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 410bb415b..a499b68ec 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaybuf", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index e02a315dd..825471a84 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-busdisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index a1d9044a1..bb819122f 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-fbdisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 215bf7ecc..fd0190c60 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-jndisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 44362f435..0c8259d05 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-pgdisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 2f01aa3bc..bfae054ca 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-psdisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 204559c85..1c7b67d10 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-sdldisplay", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 3597f20a9..f8536d8c3 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/eventsys/__init__.py b/micropython/pydisplay/eventsys/eventsys/__init__.py index fea8077bb..e76baedeb 100644 --- a/micropython/pydisplay/eventsys/eventsys/__init__.py +++ b/micropython/pydisplay/eventsys/eventsys/__init__.py @@ -42,13 +42,13 @@ class Events: ] # Event classes from PyGame - Unknown = namedtuple("Common", "type") - Motion = namedtuple("Motion", "type pos rel buttons touch window") - Button = namedtuple("Button", "type pos button touch window") - Wheel = namedtuple("Wheel", "type flipped x y precise_x precise_y touch window") - Key = namedtuple("Key", "type name key mod scancode window") - Quit = namedtuple("Quit", "type") - Any = namedtuple("Any", "type") + Unknown = namedtuple("Common", "type") # noqa: PYI024 + Motion = namedtuple("Motion", "type pos rel buttons touch window") # noqa: PYI024 + Button = namedtuple("Button", "type pos button touch window") # noqa: PYI024 + Wheel = namedtuple("Wheel", "type flipped x y precise_x precise_y touch window") # noqa: PYI024 + Key = namedtuple("Key", "type name key mod scancode window") # noqa: PYI024 + Quit = namedtuple("Quit", "type") # noqa: PYI024 + Any = namedtuple("Any", "type") # noqa: PYI024 @staticmethod def new(types: list[str | tuple[str, int]] = [], classes: dict[str, str] = {}): @@ -96,5 +96,5 @@ def new(types: list[str | tuple[str, int]] = [], classes: dict[str, str] = {}): setattr( Events, event_class_name, - namedtuple(event_class_name, event_class_fields), + namedtuple(event_class_name, event_class_fields), # noqa: PYI024 ) diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 8c492e6cc..5359c76af 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( description="eventsys", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 424b86aa1..72c162aad 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( description="graphics", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index d71ff6de0..5ad129959 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( description="palettes", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/palettes/palettes/wheel.py b/micropython/pydisplay/palettes/palettes/wheel.py index 8dad3a4f4..b32fe8123 100644 --- a/micropython/pydisplay/palettes/palettes/wheel.py +++ b/micropython/pydisplay/palettes/palettes/wheel.py @@ -83,7 +83,7 @@ def _wheel_to_rgb(self, index): def _hsv_to_rgb(self, h, s, v): # incoming values are in the range of 0-1 # returns r, g, b values in the range of 0-255 - if s == 0.0: # when s=0, all values are shades of gray + if s == 0: # when s=0, all values are shades of gray return int(v * 255), int(v * 255), int(v * 255) i = int(h * 6.0) f = (h * 6.0) - i diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index e1b60ea13..0e1774118 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( description="timer", - version="0.1.2", + version="0.1.3", pypi_publish="pydisplay-timer", ) package("timer") From 3ab6ae964c9be038e3b37d505b492e01ba76c38c Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:43:28 -0600 Subject: [PATCH 18/35] pydisplay: Still linting. Signed-off-by: Brad Barnett --- .../pydisplay/displaybuf/displaybuf/__init__.py | 3 +++ .../examples/board_config.py | 17 +++++++++++------ .../examples/board_config.py | 2 +- .../examples/board_config.py | 2 +- .../examples/board_config.py | 2 +- .../examples/board_config.py | 2 +- .../displaysys/displaysys/__init__.py | 2 +- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/micropython/pydisplay/displaybuf/displaybuf/__init__.py b/micropython/pydisplay/displaybuf/displaybuf/__init__.py index f3c1d4da7..901a33543 100644 --- a/micropython/pydisplay/displaybuf/displaybuf/__init__.py +++ b/micropython/pydisplay/displaybuf/displaybuf/__init__.py @@ -37,15 +37,18 @@ if sys.implementation.name == "micropython": try: from viper_tools import _bounce8, _bounce4 + _has_viper_tools = True except Exception: pass if not _has_viper_tools: + def _bounce8(*args, **kwargs): raise NotImplementedError( ".GS8 and .GS4_HMSB DisplayBuffer formats are only implemented in viper_tools.py for MicroPython." ) + _bounce4 = _bounce8 diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py index e88ac8591..c6a2c02e7 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -56,21 +56,26 @@ def send_init_sequence(init_sequence, mosi, sck, cs): reset = iox.Pin(2, Pin.OUT, value=1) backlight = iox.Pin(4, Pin.OUT, value=1) -send_init_sequence(init_sequence, mosi=iox.Pin(7, Pin.OUT), - sck=iox.Pin(0, Pin.OUT, value=0), cs=iox.Pin(1, Pin.OUT, value=1)) +send_init_sequence( + init_sequence, + mosi=iox.Pin(7, Pin.OUT), + sck=iox.Pin(0, Pin.OUT, value=0), + cs=iox.Pin(1, Pin.OUT, value=1), +) fb = RGBFrameBuffer(**tft_pins, **tft_timings) mv = memoryview(fb) -mv[:] = b'\xFF' * len(mv) +mv[:] = b"\xff" * len(mv) fb.refresh() -touch_drv = FT6x36(i2c, address=0x48) #, irq = iox.Pin(3, Pin.OUT)) +touch_drv = FT6x36(i2c, address=0x48) # , irq = iox.Pin(3, Pin.OUT)) + def touch_read_func(): touches = touch_drv.touches if len(touches): - return touches[0]['x'], touches[0]['y'] + return touches[0]["x"], touches[0]["y"] return None @@ -78,7 +83,7 @@ def touch_read_func(): display_drv = FBDisplay(fb) -touch_rotation_table=(0, 0, 0, 0) +touch_rotation_table = (0, 0, 0, 0) broker = device.Broker() diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py index 704986f19..bc6f00f1e 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py @@ -44,12 +44,12 @@ else: from eventsys import device import sys + try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll except ImportError: from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - display_drv = DTDisplay( width=width, height=height, diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index 704986f19..bc6f00f1e 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -44,12 +44,12 @@ else: from eventsys import device import sys + try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll except ImportError: from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - display_drv = DTDisplay( width=width, height=height, diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py index 704986f19..bc6f00f1e 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -44,12 +44,12 @@ else: from eventsys import device import sys + try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll except ImportError: from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - display_drv = DTDisplay( width=width, height=height, diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index 704986f19..bc6f00f1e 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -44,12 +44,12 @@ else: from eventsys import device import sys + try: from displaysys.pgdisplay import PGDisplay as DTDisplay, poll except ImportError: from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - display_drv = DTDisplay( width=width, height=height, diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index 08729f140..6c046c2fd 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -17,6 +17,7 @@ try: from byteswap import byteswap except ImportError: + def byteswap(buf): """ Swap the bytes of a 16-bit buffer in place with no dependencies. @@ -24,7 +25,6 @@ def byteswap(buf): buf[::2], buf[1::2] = buf[1::2], buf[::2] - gc.collect() From 8f12210fb0efbcd16ad5191dc755cc6da067b456 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:48:24 -0600 Subject: [PATCH 19/35] pydisplay: Getting closer. Signed-off-by: Brad Barnett --- .../displaybuf/examples/displaybuf_blit.py | 2 +- .../examples/displaybuf_simpletest.py | 8 ++----- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../displaysys-busdisplay/manifest.py | 2 +- .../displaysys-fbdisplay/manifest.py | 2 +- .../displaysys-jndisplay/manifest.py | 2 +- .../displaysys-pgdisplay/manifest.py | 2 +- .../displaysys-psdisplay/manifest.py | 2 +- .../displaysys-sdldisplay/manifest.py | 2 +- .../examples/displaysys_block_test.py | 10 +++++++-- .../examples/displaysys_fill_rect_test.py | 4 +++- .../displaysys/displaysys/manifest.py | 2 +- .../eventsys/examples/eventsys_simpletest.py | 1 + .../eventsys/examples/eventsys_touch_test.py | 22 +++++++------------ micropython/pydisplay/eventsys/manifest.py | 2 +- .../graphics/examples/graphics_area_test.py | 4 ++-- micropython/pydisplay/graphics/manifest.py | 2 +- .../palettes/examples/palettes_cube.py | 3 ++- .../palettes/examples/palettes_material.py | 6 +++-- .../palettes/examples/palettes_wheel.py | 4 ++++ micropython/pydisplay/palettes/manifest.py | 2 +- .../timer/examples/timer_simpletest.py | 2 ++ micropython/pydisplay/timer/manifest.py | 2 +- 23 files changed, 49 insertions(+), 41 deletions(-) diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py index 18a9bb415..0aebcd282 100644 --- a/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_blit.py @@ -5,7 +5,7 @@ ssd.fill(0xF800) ssd.show() -ba = bytearray(100*100*2) +ba = bytearray(100 * 100 * 2) mv = memoryview(ba) fb = FrameBuffer(mv, 100, 100, RGB565) fb.fill(0x000F) diff --git a/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py index 220775bac..16b712214 100644 --- a/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py +++ b/micropython/pydisplay/displaybuf/examples/displaybuf_simpletest.py @@ -22,9 +22,7 @@ LIGHT_GREY = ssd.color(192, 192, 192) GREY = ssd.color(96, 96, 96) DARK_GREY = ssd.color(64, 64, 64) -GREY = ssd.color( - 128, 128, 128, GREY -) # Example of how to redefine a color in the lookup table +GREY = ssd.color(128, 128, 128, GREY) # Example of how to redefine a color in the lookup table if ssd.colors_registered: # Will be 0 if not using lookup tables / GS4_HMSB mode. print(f"{ssd.colors_registered} colors registered.") @@ -45,9 +43,7 @@ def main(scroll=False, animate=False, text1="displaybuf", text2="simpletest"): ssd.hline(WIDTH // 8, HEIGHT // 2, WIDTH * 3 // 4, MAGENTA) ssd.vline(WIDTH // 2, HEIGHT // 4, HEIGHT // 2, CYAN) ssd.pixel(WIDTH // 2, HEIGHT * 1 // 8, WHITE) - ssd.ellipse( - WIDTH // 2, HEIGHT // 2, WIDTH // 4, HEIGHT // 8, BLACK, True, 0b1111 - ) + ssd.ellipse(WIDTH // 2, HEIGHT // 2, WIDTH // 4, HEIGHT // 8, BLACK, True, 0b1111) ssd.text(text1, (WIDTH - FONT_WIDTH * len(text1)) // 2, HEIGHT // 2 - 8, WHITE) ssd.text(text2, (WIDTH - FONT_WIDTH * len(text2)) // 2, HEIGHT // 2, WHITE) ssd.show() diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index a499b68ec..7dc6aa644 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaybuf", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 825471a84..4a839069a 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-busdisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index bb819122f..70b52d362 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-fbdisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index fd0190c60..e7198baeb 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-jndisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 0c8259d05..ec9caf361 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-pgdisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index bfae054ca..a5bc1fe1a 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-psdisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 1c7b67d10..ce5ce69aa 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-sdldisplay", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py index 928c521f3..467efef62 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py @@ -1,4 +1,5 @@ -""" displaysys_block_test.py """ +"""displaysys_block_test.py""" + from board_config import display_drv import random import time @@ -13,9 +14,11 @@ else: needs_swap = False + def test(): raise Exception("Test exception") + def main(): # display_bus.register_callback(test) block_size = 32 @@ -25,7 +28,9 @@ def main(): max_y = display_drv.height - block_size - 1 for pixel_color in [0x0000, 0xFFFF, 0xF800, 0x07E0, 0x001F, 0xFFE0, 0x07FF, 0xF81F]: - pixel_bytes = pixel_color.to_bytes(2, "big") if needs_swap else pixel_color.to_bytes(2, "little") + pixel_bytes = ( + pixel_color.to_bytes(2, "big") if needs_swap else pixel_color.to_bytes(2, "little") + ) blocks.append(memoryview(bytearray(pixel_bytes * (block_size * block_size)))) print("Drawing blocks on display") @@ -43,4 +48,5 @@ def main(): if count % 2000 == 0: print("blocks/sec:", count / (time.time() - start_time)) + main() diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py index 6faff91b9..5f5f1554e 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py @@ -1,4 +1,5 @@ -""" displaysys_fill_rect_test.py """ +"""displaysys_fill_rect_test.py""" + from board_config import display_drv from random import randint, getrandbits import time @@ -35,4 +36,5 @@ def main(): if count % 1000 == 0: print("blocks/sec:", count / (time.time() - start_time)) + main() diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index f8536d8c3..3b14cc43e 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py index 47f74dce5..60e7b97e7 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py @@ -11,6 +11,7 @@ async def main(): break await asyncio.sleep(0.001) + loop = asyncio.get_event_loop() main_task = loop.create_task(main()) # noqa: RUF006 if hasattr(loop, "run_forever"): diff --git a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py index 17e2b9ea3..7ec3fa530 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py @@ -67,15 +67,9 @@ def loop(): touched_point = None while not touched_point: event = broker.poll() - if ( - event - and event.type == broker.Events.MOUSEBUTTONDOWN - and event.button == 1 - ): + if event and event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 1: touched_point = event.pos - zone = (touched_point[1] // half_height) * 2 + ( - touched_point[0] // half_width - ) + zone = (touched_point[1] // half_height) * 2 + (touched_point[0] // half_width) touched_zones.append(zone) print(f"{touched_point=} in {zone=}") display_drv.fill_rect( @@ -117,12 +111,12 @@ def loop(): out_text = f"touch_rotation_table = {tuple(touch_rotation_table)}" print(" ", out_text, "\n") text16( - display_drv, - out_text, - (display_drv.width - len(out_text) * 8) // 2, - (display_drv.height - 8) // 2, - FG_COLOR, - ) + display_drv, + out_text, + (display_drv.width - len(out_text) * 8) // 2, + (display_drv.height - 8) // 2, + FG_COLOR, + ) return True diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 5359c76af..ae5abd7f4 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( description="eventsys", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/examples/graphics_area_test.py b/micropython/pydisplay/graphics/examples/graphics_area_test.py index b4f1ffa28..39e836c77 100644 --- a/micropython/pydisplay/graphics/examples/graphics_area_test.py +++ b/micropython/pydisplay/graphics/examples/graphics_area_test.py @@ -18,6 +18,6 @@ from graphics import rect, circle, ellipse -dirty = circle(display_drv, 120, 120, 50, 0XFF00, True) -dirty += ellipse(display_drv, 100, 85, 50, 30, 0X0FF0, True, 0b1111) +dirty = circle(display_drv, 120, 120, 50, 0xFF00, True) +dirty += ellipse(display_drv, 100, 85, 50, 30, 0x0FF0, True, 0b1111) rect(display_drv, *dirty, 0x00FF) diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 72c162aad..5d1c11af6 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( description="graphics", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/examples/palettes_cube.py b/micropython/pydisplay/palettes/examples/palettes_cube.py index 440f1a48b..71c983993 100644 --- a/micropython/pydisplay/palettes/examples/palettes_cube.py +++ b/micropython/pydisplay/palettes/examples/palettes_cube.py @@ -24,6 +24,7 @@ ba = bytearray(display_drv.width * line_height * BPP) fb = FrameBuffer(ba, display_drv.width, line_height, RGB565) + def main(): global y, scroll for index, color in enumerate(palette): @@ -36,7 +37,7 @@ def main(): fb.text16(name, 2, 2, text_color) display_drv.blit_rect(ba, 0, y % display_drv.height, display_drv.width, line_height) y += line_height - sleep(.1) + sleep(0.1) def loop(): diff --git a/micropython/pydisplay/palettes/examples/palettes_material.py b/micropython/pydisplay/palettes/examples/palettes_material.py index 0ceac2e13..e717fe261 100644 --- a/micropython/pydisplay/palettes/examples/palettes_material.py +++ b/micropython/pydisplay/palettes/examples/palettes_material.py @@ -13,10 +13,12 @@ palette = get_palette(name="material_design", color_depth=16, swapped=needs_swap) + def main(): - line_height=1 + line_height = 1 for i, color in enumerate(palette): - display_drv.fill_rect(0, i*line_height, display_drv.width, line_height, color) + display_drv.fill_rect(0, i * line_height, display_drv.width, line_height, color) + while True: main() diff --git a/micropython/pydisplay/palettes/examples/palettes_wheel.py b/micropython/pydisplay/palettes/examples/palettes_wheel.py index 7d65b4c01..c8c73b16a 100644 --- a/micropython/pydisplay/palettes/examples/palettes_wheel.py +++ b/micropython/pydisplay/palettes/examples/palettes_wheel.py @@ -16,6 +16,8 @@ line_height = 2 i = 0 + + def main(): global i for color in palette: @@ -24,8 +26,10 @@ def main(): display_drv.fill_rect(0, i % display_drv.height, display_drv.width, line_height, color) i += line_height + def loop(): while True: main() + loop() diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 5ad129959..a0cf5c1dd 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( description="palettes", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/timer/examples/timer_simpletest.py b/micropython/pydisplay/timer/examples/timer_simpletest.py index 9df918505..c62e42d97 100644 --- a/micropython/pydisplay/timer/examples/timer_simpletest.py +++ b/micropython/pydisplay/timer/examples/timer_simpletest.py @@ -7,6 +7,7 @@ from timer import Timer from sys import platform + class TimerTest: def __init__(self): self._tim = Timer(-1 if platform == "rp2" else 1) @@ -23,6 +24,7 @@ def stop(self, t=None): self._tim.deinit() print(f"TimerTest: timer stopped after {self._counter:,} calls.") + # Create a timer that calls tt.do_something every 1ms tt = TimerTest() tt.start(1) diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 0e1774118..8dedc596d 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( description="timer", - version="0.1.3", + version="0.1.4", pypi_publish="pydisplay-timer", ) package("timer") From 1cd4aa53483811a612fdb32ae03ad9dee7c89dcc Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 19:54:34 -0600 Subject: [PATCH 20/35] pydisplay: Still closer. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-busdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-fbdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-jndisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-pgdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-psdisplay/manifest.py | 2 +- .../pydisplay/displaysys/displaysys-sdldisplay/manifest.py | 2 +- micropython/pydisplay/displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/graphics/_framebuf.py | 6 +++--- micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/manifest.py | 2 +- micropython/pydisplay/timer/manifest.py | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 7dc6aa644..586ba89dc 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaybuf", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 4a839069a..2554e340b 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-busdisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 70b52d362..ecac193d0 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-fbdisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index e7198baeb..943511b71 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-jndisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index ec9caf361..9bfa4be2d 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-pgdisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index a5bc1fe1a..0fa8ef02a 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-psdisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index ce5ce69aa..7c40535a3 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys-sdldisplay", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 3b14cc43e..a1a6f5c7f 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( description="displaysys", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index ae5abd7f4..c109c59f2 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( description="eventsys", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py index 4d8e36011..91abb9857 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -287,9 +287,9 @@ def fill_rect(framebuf, x, y, width, height, color): rgb565_color_int = int.from_bytes(rgb565_color, "little") arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) for _y in range(y, y + height): - arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = ( - rgb565_color_int - ) + arr[ + _y * framebuf._stride + x : _y * framebuf._stride + x + width + ] = rgb565_color_int else: for _y in range(y, y + height): offset = _y * framebuf._stride diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 5d1c11af6..12127894d 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( description="graphics", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index a0cf5c1dd..52b883df9 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( description="palettes", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 8dedc596d..5bfabf91e 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( description="timer", - version="0.1.4", + version="0.1.5", pypi_publish="pydisplay-timer", ) package("timer") From 51c043ae890f5abda0c63e470de2beb887b0345e Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 21:59:15 -0600 Subject: [PATCH 21/35] pydisplay: Add bundle. Signed-off-by: Brad Barnett --- micropython/pydisplay/bundle/manifest.py | 17 +++++++++++++++++ micropython/pydisplay/displaybuf/manifest.py | 6 +++--- .../displaysys-busdisplay/manifest.py | 8 ++++---- .../displaysys/displaysys-fbdisplay/manifest.py | 8 ++++---- .../displaysys/displaysys-jndisplay/manifest.py | 8 ++++---- .../displaysys/displaysys-pgdisplay/manifest.py | 8 ++++---- .../displaysys/displaysys-psdisplay/manifest.py | 8 ++++---- .../displaysys-sdldisplay/manifest.py | 8 ++++---- .../pydisplay/displaysys/displaysys/manifest.py | 6 +++--- micropython/pydisplay/eventsys/manifest.py | 6 +++--- micropython/pydisplay/graphics/manifest.py | 6 +++--- micropython/pydisplay/palettes/manifest.py | 6 +++--- micropython/pydisplay/timer/manifest.py | 6 +++--- 13 files changed, 59 insertions(+), 42 deletions(-) create mode 100644 micropython/pydisplay/bundle/manifest.py diff --git a/micropython/pydisplay/bundle/manifest.py b/micropython/pydisplay/bundle/manifest.py new file mode 100644 index 000000000..a53e0ec29 --- /dev/null +++ b/micropython/pydisplay/bundle/manifest.py @@ -0,0 +1,17 @@ +metadata( + description="PyDisplay bundle", + version="0.1.5", + author="Brad Barnett", +) +require("pydisplay-displaybuf") +require("pydisplay-eventsys") +require("pydisplay-graphics") +require("pydisplay-palettes") +require("pydisplay-timer") +require("pydisplay-displaysys") +require("pydisplay-displaysys-busdisplay") +require("pydisplay-displaysys-fbdisplay") +require("pydisplay-displaysys-jndisplay") +require("pydisplay-displaysys-pgdisplay") +require("pydisplay-displaysys-psdisplay") +require("pydisplay-displaysys-sdldisplay") diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 586ba89dc..8e15c69d7 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,6 @@ metadata( - description="displaybuf", + description="PyDisplay displaybuf", version="0.1.5", - pypi_publish="pydisplay-displaybuf", + author="Brad Barnett", ) -package("displaybuf") +package("pydisplay-displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 2554e340b..dd6182f67 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-busdisplay", + description="PyDisplay displaysys-busdisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-busdisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index ecac193d0..3b695a6be 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-fbdisplay", + description="PyDisplay displaysys-fbdisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-fbdisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 943511b71..91d1f6eac 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-jndisplay", + description="PyDisplay displaysys-jndisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-jndisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 9bfa4be2d..cb5fb79eb 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-pgdisplay", + description="PyDisplay displaysys-pgdisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-pgdisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 0fa8ef02a..d928f4b0c 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-psdisplay", + description="PyDisplay displaysys-psdisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-psdisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 7c40535a3..dc208d329 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,7 +1,7 @@ metadata( - description="displaysys-sdldisplay", + description="PyDisplay displaysys-sdldisplay", version="0.1.5", - pypi_publish="pydisplay-displaysys-sdldisplay", + author="Brad Barnett", ) -require("displaysys") -package("displaysys") +require("pydisplay-displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index a1a6f5c7f..a7febf81d 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,6 @@ metadata( - description="displaysys", + description="PyDisplay displaysys", version="0.1.5", - pypi_publish="pydisplay-displaysys", + author="Brad Barnett", ) -package("displaysys") +package("pydisplay-displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index c109c59f2..373f98e45 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,6 @@ metadata( - description="eventsys", + description="PyDisplay eventsys", version="0.1.5", - pypi_publish="pydisplay-eventsys", + author="Brad Barnett", ) -package("eventsys") +package("pydisplay-eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 12127894d..da54fc2ec 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,6 @@ metadata( - description="graphics", + description="PyDisplay graphics", version="0.1.5", - pypi_publish="pydisplay-graphics", + author="Brad Barnett", ) -package("graphics") +package("pydisplay-graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 52b883df9..831a51d71 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,6 @@ metadata( - description="palettes", + description="PyDisplay palettes", version="0.1.5", - pypi_publish="pydisplay-palettes", + author="Brad Barnett", ) -package("palettes") +package("pydisplay-palettes") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 5bfabf91e..26e3557fd 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,6 @@ metadata( - description="timer", + description="PyDisplay timer", version="0.1.5", - pypi_publish="pydisplay-timer", + author="Brad Barnett", ) -package("timer") +package("pydisplay-timer") From e3c2e399f77b5eabb4bcd7c35cc21857ebe2fd70 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 22:43:42 -0600 Subject: [PATCH 22/35] pydisplay: Add bundle. Signed-off-by: Brad Barnett --- micropython/pydisplay/bundle/manifest.py | 17 ----------------- micropython/pydisplay/displaybuf/manifest.py | 2 +- .../displaysys-busdisplay/manifest.py | 4 ++-- .../displaysys/displaysys-fbdisplay/manifest.py | 4 ++-- .../displaysys/displaysys-jndisplay/manifest.py | 4 ++-- .../displaysys/displaysys-pgdisplay/manifest.py | 4 ++-- .../displaysys/displaysys-psdisplay/manifest.py | 4 ++-- .../displaysys-sdldisplay/manifest.py | 4 ++-- .../pydisplay/displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/manifest.py | 2 +- micropython/pydisplay/pydisplay/manifest.py | 17 +++++++++++++++++ micropython/pydisplay/timer/manifest.py | 2 +- 14 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 micropython/pydisplay/bundle/manifest.py create mode 100644 micropython/pydisplay/pydisplay/manifest.py diff --git a/micropython/pydisplay/bundle/manifest.py b/micropython/pydisplay/bundle/manifest.py deleted file mode 100644 index a53e0ec29..000000000 --- a/micropython/pydisplay/bundle/manifest.py +++ /dev/null @@ -1,17 +0,0 @@ -metadata( - description="PyDisplay bundle", - version="0.1.5", - author="Brad Barnett", -) -require("pydisplay-displaybuf") -require("pydisplay-eventsys") -require("pydisplay-graphics") -require("pydisplay-palettes") -require("pydisplay-timer") -require("pydisplay-displaysys") -require("pydisplay-displaysys-busdisplay") -require("pydisplay-displaysys-fbdisplay") -require("pydisplay-displaysys-jndisplay") -require("pydisplay-displaysys-pgdisplay") -require("pydisplay-displaysys-psdisplay") -require("pydisplay-displaysys-sdldisplay") diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 8e15c69d7..f398ba870 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-displaybuf") +package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index dd6182f67..61328e6f8 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 3b695a6be..439bd1ecd 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 91d1f6eac..bccad39a4 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index cb5fb79eb..70b02aca3 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index d928f4b0c..aa3256713 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index dc208d329..5f03d7a44 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -3,5 +3,5 @@ version="0.1.5", author="Brad Barnett", ) -require("pydisplay-displaysys") -package("pydisplay-displaysys") +require("displaysys") +package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index a7febf81d..eaa3ad388 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-displaysys") +package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 373f98e45..64771dfc2 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-eventsys") +package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index da54fc2ec..51de8ee86 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-graphics") +package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 831a51d71..a1632ee8e 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-palettes") +package("palettes") diff --git a/micropython/pydisplay/pydisplay/manifest.py b/micropython/pydisplay/pydisplay/manifest.py new file mode 100644 index 000000000..3ba5765a7 --- /dev/null +++ b/micropython/pydisplay/pydisplay/manifest.py @@ -0,0 +1,17 @@ +metadata( + description="PyDisplay bundle", + version="0.1.5", + author="Brad Barnett", +) +require("displaybuf") +require("eventsys") +require("graphics") +require("palettes") +require("timer") +require("displaysys") +require("displaysys-busdisplay") +require("displaysys-fbdisplay") +require("displaysys-jndisplay") +require("displaysys-pgdisplay") +require("displaysys-psdisplay") +require("displaysys-sdldisplay") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 26e3557fd..da6181983 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -3,4 +3,4 @@ version="0.1.5", author="Brad Barnett", ) -package("pydisplay-timer") +package("timer") From ea884fd8a101c961556b3425eeb7281f85a17163 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sat, 23 Nov 2024 23:53:45 -0600 Subject: [PATCH 23/35] pydisplay: Add example board_config.py. Signed-off-by: Brad Barnett --- .../examples/board_config.py | 69 ++-------------- .../examples/board_config.py | 82 +++++-------------- .../examples/board_config.py | 71 +++------------- .../examples/board_config.py | 79 +++++------------- .../pydisplay-board_config/board_config.py | 70 ++++++++++++++++ .../pydisplay-board_config/manifest.py | 6 ++ 6 files changed, 134 insertions(+), 243 deletions(-) create mode 100644 micropython/pydisplay/pydisplay-board_config/board_config.py create mode 100644 micropython/pydisplay/pydisplay-board_config/manifest.py diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py index bc6f00f1e..9ea7aae87 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py @@ -1,71 +1,16 @@ """ -Combination board configuration for desktop, pyscript and jupyter notebook platforms. +Board configuration for Jupyter Notebook. """ -width = 320 -height = 480 -rotation = 0 -scale = 2.0 - -_ps = _jn = False -try: - import pyscript - - _ps = True -except ImportError: - try: - get_ipython() - _jn = True - except NameError: - pass - -if _ps: - from displaysys.psdisplay import PSDisplay, PSDevices - from eventsys import device - - display_drv = PSDisplay("display_canvas", width, height) - - broker = device.Broker() +from displaysys.jndisplay import JNDisplay +from eventsys import device - touch_drv = PSDevices("display_canvas") - touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, - ) -elif _jn: - from displaysys.jndisplay import JNDisplay - from eventsys import device - - broker = device.Broker() - - display_drv = JNDisplay(width, height) -else: - from eventsys import device - import sys - - try: - from displaysys.pgdisplay import PGDisplay as DTDisplay, poll - except ImportError: - from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - - display_drv = DTDisplay( - width=width, - height=height, - rotation=rotation, - color_depth=16, - title=f"{sys.implementation.name} on {sys.platform}", - scale=scale, - ) +width = 320 +height = 480 - broker = device.Broker() +broker = device.Broker() - events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, - ) +display_drv = JNDisplay(width, height) display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index bc6f00f1e..c0395b2f9 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -1,71 +1,31 @@ """ -Combination board configuration for desktop, pyscript and jupyter notebook platforms. +Board configuration for PyGame. """ +from displaysys.pgdisplay import PGDisplay as DTDisplay, poll +from eventsys import device +import sys + width = 320 height = 480 rotation = 0 scale = 2.0 -_ps = _jn = False -try: - import pyscript - - _ps = True -except ImportError: - try: - get_ipython() - _jn = True - except NameError: - pass - -if _ps: - from displaysys.psdisplay import PSDisplay, PSDevices - from eventsys import device - - display_drv = PSDisplay("display_canvas", width, height) - - broker = device.Broker() - - touch_drv = PSDevices("display_canvas") - - touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, - ) -elif _jn: - from displaysys.jndisplay import JNDisplay - from eventsys import device - - broker = device.Broker() - - display_drv = JNDisplay(width, height) -else: - from eventsys import device - import sys - - try: - from displaysys.pgdisplay import PGDisplay as DTDisplay, poll - except ImportError: - from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - - display_drv = DTDisplay( - width=width, - height=height, - rotation=rotation, - color_depth=16, - title=f"{sys.implementation.name} on {sys.platform}", - scale=scale, - ) - - broker = device.Broker() - - events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, - ) +display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, +) + +broker = device.Broker() + +events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, +) display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py index bc6f00f1e..509265bda 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -1,71 +1,22 @@ """ -Combination board configuration for desktop, pyscript and jupyter notebook platforms. +Board configuration for PyScript. """ +from displaysys.psdisplay import PSDisplay, PSDevices +from eventsys import device width = 320 height = 480 -rotation = 0 -scale = 2.0 -_ps = _jn = False -try: - import pyscript +display_drv = PSDisplay("display_canvas", width, height) - _ps = True -except ImportError: - try: - get_ipython() - _jn = True - except NameError: - pass +broker = device.Broker() -if _ps: - from displaysys.psdisplay import PSDisplay, PSDevices - from eventsys import device +touch_drv = PSDevices("display_canvas") - display_drv = PSDisplay("display_canvas", width, height) - - broker = device.Broker() - - touch_drv = PSDevices("display_canvas") - - touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, - ) -elif _jn: - from displaysys.jndisplay import JNDisplay - from eventsys import device - - broker = device.Broker() - - display_drv = JNDisplay(width, height) -else: - from eventsys import device - import sys - - try: - from displaysys.pgdisplay import PGDisplay as DTDisplay, poll - except ImportError: - from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - - display_drv = DTDisplay( - width=width, - height=height, - rotation=rotation, - color_depth=16, - title=f"{sys.implementation.name} on {sys.platform}", - scale=scale, - ) - - broker = device.Broker() - - events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, - ) +touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, +) display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index bc6f00f1e..de8ab137a 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -1,71 +1,30 @@ """ Combination board configuration for desktop, pyscript and jupyter notebook platforms. """ +from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll +from eventsys import device +import sys width = 320 height = 480 rotation = 0 scale = 2.0 -_ps = _jn = False -try: - import pyscript - - _ps = True -except ImportError: - try: - get_ipython() - _jn = True - except NameError: - pass - -if _ps: - from displaysys.psdisplay import PSDisplay, PSDevices - from eventsys import device - - display_drv = PSDisplay("display_canvas", width, height) - - broker = device.Broker() - - touch_drv = PSDevices("display_canvas") - - touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, - ) -elif _jn: - from displaysys.jndisplay import JNDisplay - from eventsys import device - - broker = device.Broker() - - display_drv = JNDisplay(width, height) -else: - from eventsys import device - import sys - - try: - from displaysys.pgdisplay import PGDisplay as DTDisplay, poll - except ImportError: - from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - - display_drv = DTDisplay( - width=width, - height=height, - rotation=rotation, - color_depth=16, - title=f"{sys.implementation.name} on {sys.platform}", - scale=scale, - ) - - broker = device.Broker() - - events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, - ) +display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, +) + +broker = device.Broker() + +events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, +) display_drv.fill(0) diff --git a/micropython/pydisplay/pydisplay-board_config/board_config.py b/micropython/pydisplay/pydisplay-board_config/board_config.py new file mode 100644 index 000000000..1faf00647 --- /dev/null +++ b/micropython/pydisplay/pydisplay-board_config/board_config.py @@ -0,0 +1,70 @@ +""" +Combination board configuration for desktop, pyscript and jupyter notebook platforms. +""" + +width = 320 +height = 480 +rotation = 0 +scale = 2.0 + +_ps = _jn = False +try: + import pyscript + + _ps = True +except ImportError: + try: + get_ipython() + _jn = True + except NameError: + pass + +if _ps: + from displaysys.psdisplay import PSDisplay, PSDevices + from eventsys import device + + display_drv = PSDisplay("display_canvas", width, height) + + broker = device.Broker() + + touch_drv = PSDevices("display_canvas") + + touch_dev = broker.create_device( + type=device.Types.TOUCH, + read=touch_drv.get_mouse_pos, + data=display_drv, + ) +elif _jn: + from displaysys.jndisplay import JNDisplay + from eventsys import device + + broker = device.Broker() + + display_drv = JNDisplay(width, height) +else: + from eventsys import device + import sys + + try: + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll + except ImportError: + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll + + display_drv = DTDisplay( + width=width, + height=height, + rotation=rotation, + title=f"{sys.implementation.name} on {sys.platform}", + scale=scale, + ) + + broker = device.Broker() + + events_dev = broker.create_device( + type=device.Types.QUEUE, + read=poll, + data=display_drv, + # data2=Events.filter, + ) + +display_drv.fill(0) diff --git a/micropython/pydisplay/pydisplay-board_config/manifest.py b/micropython/pydisplay/pydisplay-board_config/manifest.py new file mode 100644 index 000000000..3c595d608 --- /dev/null +++ b/micropython/pydisplay/pydisplay-board_config/manifest.py @@ -0,0 +1,6 @@ +metadata( + description="PyDisplay example board_config", + version="0.1.5", + author="Brad Barnett", +) +module("board_config.py", opt=3) From 69b13a7d1599c4080ddfa62bdc035e5a8d4b787d Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 01:06:01 -0600 Subject: [PATCH 24/35] pydevices: Add display and touch drivers. Signed-off-by: Brad Barnett --- micropython/drivers/display/gc9a01/gc9a01.py | 62 +++++ .../drivers/display/gc9a01/manifest.py | 5 + micropython/drivers/display/gc9d01/gc9d01.py | 106 ++++++++ .../drivers/display/gc9d01/manifest.py | 5 + micropython/drivers/display/hx8357/hx8357.py | 66 +++++ .../drivers/display/hx8357/manifest.py | 5 + .../drivers/display/ili9163/ili9163.py | 82 ++++++ .../drivers/display/ili9163/manifest.py | 5 + .../drivers/display/ili9341/ili9341.py | 80 ++++++ .../drivers/display/ili9341/manifest.py | 5 + .../drivers/display/ili9488/ili9488.py | 61 +++++ .../drivers/display/ili9488/manifest.py | 5 + .../drivers/display/st7701/manifest.py | 5 + micropython/drivers/display/st7701/st7701.py | 127 ++++++++++ .../drivers/display/st7735/manifest.py | 5 + micropython/drivers/display/st7735/st7735.py | 60 +++++ .../drivers/display/st7735r/manifest.py | 5 + .../drivers/display/st7735r/st7735r.py | 75 ++++++ .../drivers/display/st7735r_1/manifest.py | 5 + .../drivers/display/st7735r_1/st7735r_1.py | 32 +++ .../drivers/display/st7789/manifest.py | 5 + micropython/drivers/display/st7789/st7789.py | 40 +++ .../drivers/display/st7789vw/manifest.py | 5 + .../drivers/display/st7789vw/st7789vw.py | 40 +++ .../drivers/display/st7796/manifest.py | 5 + micropython/drivers/display/st7796/st7796.py | 142 +++++++++++ .../drivers/display/st7796_test/manifest.py | 5 + .../display/st7796_test/st7796_test.py | 50 ++++ micropython/drivers/touch/chsc6x/chsc6x.py | 63 +++++ micropython/drivers/touch/chsc6x/manifest.py | 5 + micropython/drivers/touch/cst226/cst226.py | 149 +++++++++++ micropython/drivers/touch/cst226/manifest.py | 5 + micropython/drivers/touch/cst8xx/cst8xx.py | 115 +++++++++ micropython/drivers/touch/cst8xx/manifest.py | 5 + micropython/drivers/touch/ft6x36/ft6x36.py | 235 ++++++++++++++++++ micropython/drivers/touch/ft6x36/manifest.py | 5 + micropython/drivers/touch/gt911/gt911.py | 137 ++++++++++ micropython/drivers/touch/gt911/manifest.py | 5 + micropython/drivers/touch/xpt2046/manifest.py | 5 + micropython/drivers/touch/xpt2046/xpt2046.py | 99 ++++++++ .../examples/board_config.py | 1 + .../examples/board_config.py | 1 + .../examples/board_config.py | 1 + .../pydisplay/graphics/graphics/_framebuf.py | 6 +- 44 files changed, 1927 insertions(+), 3 deletions(-) create mode 100644 micropython/drivers/display/gc9a01/gc9a01.py create mode 100644 micropython/drivers/display/gc9a01/manifest.py create mode 100644 micropython/drivers/display/gc9d01/gc9d01.py create mode 100644 micropython/drivers/display/gc9d01/manifest.py create mode 100644 micropython/drivers/display/hx8357/hx8357.py create mode 100644 micropython/drivers/display/hx8357/manifest.py create mode 100644 micropython/drivers/display/ili9163/ili9163.py create mode 100644 micropython/drivers/display/ili9163/manifest.py create mode 100644 micropython/drivers/display/ili9341/ili9341.py create mode 100644 micropython/drivers/display/ili9341/manifest.py create mode 100644 micropython/drivers/display/ili9488/ili9488.py create mode 100644 micropython/drivers/display/ili9488/manifest.py create mode 100644 micropython/drivers/display/st7701/manifest.py create mode 100644 micropython/drivers/display/st7701/st7701.py create mode 100644 micropython/drivers/display/st7735/manifest.py create mode 100644 micropython/drivers/display/st7735/st7735.py create mode 100644 micropython/drivers/display/st7735r/manifest.py create mode 100644 micropython/drivers/display/st7735r/st7735r.py create mode 100644 micropython/drivers/display/st7735r_1/manifest.py create mode 100644 micropython/drivers/display/st7735r_1/st7735r_1.py create mode 100644 micropython/drivers/display/st7789/manifest.py create mode 100644 micropython/drivers/display/st7789/st7789.py create mode 100644 micropython/drivers/display/st7789vw/manifest.py create mode 100644 micropython/drivers/display/st7789vw/st7789vw.py create mode 100644 micropython/drivers/display/st7796/manifest.py create mode 100644 micropython/drivers/display/st7796/st7796.py create mode 100644 micropython/drivers/display/st7796_test/manifest.py create mode 100644 micropython/drivers/display/st7796_test/st7796_test.py create mode 100644 micropython/drivers/touch/chsc6x/chsc6x.py create mode 100644 micropython/drivers/touch/chsc6x/manifest.py create mode 100644 micropython/drivers/touch/cst226/cst226.py create mode 100644 micropython/drivers/touch/cst226/manifest.py create mode 100644 micropython/drivers/touch/cst8xx/cst8xx.py create mode 100644 micropython/drivers/touch/cst8xx/manifest.py create mode 100644 micropython/drivers/touch/ft6x36/ft6x36.py create mode 100644 micropython/drivers/touch/ft6x36/manifest.py create mode 100644 micropython/drivers/touch/gt911/gt911.py create mode 100644 micropython/drivers/touch/gt911/manifest.py create mode 100644 micropython/drivers/touch/xpt2046/manifest.py create mode 100644 micropython/drivers/touch/xpt2046/xpt2046.py diff --git a/micropython/drivers/display/gc9a01/gc9a01.py b/micropython/drivers/display/gc9a01/gc9a01.py new file mode 100644 index 000000000..6c7cadf94 --- /dev/null +++ b/micropython/drivers/display/gc9a01/gc9a01.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# SPDX-FileCopyrightText: Copyright (c) 2021 Tyler Crumpton +# +# SPDX-License-Identifier: MIT +""" +`gc9a01` +================================================================================ + +displayio driver for GC9A01 TFT LCD displays + + +* Author(s): Tyler Crumpton + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +""" + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/tylercrumpton/CircuitPython_GC9A01.git" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +_INIT_SEQUENCE = bytearray( + b"\xfe\x00" # Inter Register Enable1 (FEh) + b"\xef\x00" # Inter Register Enable2 (EFh) + b"\xb6\x02\x00\x00" # Display Function Control (B6h) [S1→S360 source, G1→G32 gate] + b"\x36\x01\x48" # Memory Access Control(36h) [Invert Row order, invert vertical scan order] + b"\x3a\x01\x05" # COLMOD: Pixel Format Set (3Ah) [16 bits / pixel] + b"\xc3\x01\x13" # Power Control 2 (C3h) [VREG1A = 5.06, VREG1B = 0.68] + b"\xc4\x01\x13" # Power Control 3 (C4h) [VREG2A = -3.7, VREG2B = 0.68] + b"\xc9\x01\x22" # Power Control 4 (C9h) + b"\xf0\x06\x45\x09\x08\x08\x26\x2a" # SET_GAMMA1 (F0h) + b"\xf1\x06\x43\x70\x72\x36\x37\x6f" # SET_GAMMA2 (F1h) + b"\xf2\x06\x45\x09\x08\x08\x26\x2a" # SET_GAMMA3 (F2h) + b"\xf3\x06\x43\x70\x72\x36\x37\x6f" # SET_GAMMA4 (F3h) + b"\x66\x0a\x3c\x00\xcd\x67\x45\x45\x10\x00\x00\x00" + b"\x67\x0a\x00\x3c\x00\x00\x00\x01\x54\x10\x32\x98" + b"\x74\x07\x10\x85\x80\x00\x00\x4e\x00" + b"\x98\x02\x3e\x07" + b"\x35\x00" # Tearing Effect Line ON (35h) [both V-blanking and H-blanking] + b"\x21\x00" # Display Inversion ON (21h) + b"\x11\x80\x78" # Sleep Out Mode (11h) and delay(120) + b"\x29\x80\x14" # Display ON (29h) and delay(20) + b"\x2a\x04\x00\x00\x00\xef" # Column Address Set (2Ah) [Start col = 0, end col = 239] + b"\x2b\x04\x00\x00\x00\xef" # Row Address Set (2Bh) [Start row = 0, end row = 239] +) + + +# pylint: disable=too-few-public-methods +class GC9A01(BusDisplay): + """GC9A01 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/gc9a01/manifest.py b/micropython/drivers/display/gc9a01/manifest.py new file mode 100644 index 000000000..a6cf92171 --- /dev/null +++ b/micropython/drivers/display/gc9a01/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay gc9a01 display driver", + version="0.1.5", +) +module("gc9a01.py", opt=3) diff --git a/micropython/drivers/display/gc9d01/gc9d01.py b/micropython/drivers/display/gc9d01/gc9d01.py new file mode 100644 index 000000000..dfeeb7dc2 --- /dev/null +++ b/micropython/drivers/display/gc9d01/gc9d01.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# SPDX-FileCopyrightText: Copyright (c) 2023 Tyler Crumpton +# +# SPDX-License-Identifier: MIT +""" +`gc9d01` +================================================================================ + +displayio driver for GC9D01 TFT LCD displays + + +* Author(s): Tyler Crumpton + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads +""" + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/tylercrumpton/CircuitPython_GC9D01.git" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +_INIT_SEQUENCE = bytearray( + b"\xfe\x00" # Inter Register Enable1 (FEh) + b"\xef\x00" # Inter Register Enable2 (EFh) + b"\x80\x01\xff" + b"\x81\x01\xff" + b"\x82\x01\xff" + b"\x83\x01\xff" + b"\x84\x01\xff" + b"\x85\x01\xff" + b"\x86\x01\xff" + b"\x87\x01\xff" + b"\x88\x01\xff" + b"\x89\x01\xff" + b"\x8a\x01\xff" + b"\x8b\x01\xff" + b"\x8c\x01\xff" + b"\x8d\x01\xff" + b"\x8e\x01\xff" + b"\x8f\x01\xff" + b"\x3a\x01\x05" # COLMOD: Pixel Format Set (3Ah) MCU interface, 16 bits / pixel + b"\xec\x01\x10" # Inversion (ECh) DINV=1+2 column for Single Gate (BFh=0) + b"\x7e\x01\x7a" + b"\x74\x07\x02\x0e\x00\x00\x28\x00\x00" + b"\x98\x01\x3e" + b"\x99\x01\x3e" + b"\xb5\x03\x0e\x0e\x00" # Blanking Porch Control (B5h) VFP=14 VBP=14 HBP=Off + b"\x60\x04\x38\x09\x6d\x67" + b"\x63\x05\x38\xad\x6d\x67\x05" + b"\x64\x06\x38\x0b\x70\xab\x6d\x67" + b"\x66\x06\x38\x0f\x70\xaf\x6d\x67" + b"\x6a\x02\x00\x00" + b"\x68\x07\x3b\x08\x04\x00\x04\x64\x67" + b"\x6c\x07\x22\x02\x22\x02\x22\x22\x50" + b"\x6e\x1e\x00\x00\x00\x00\x07\x01\x13\x11\x0b\x09\x16\x15\x1d\x1e\x00\x00\x00\x00\x1e\x1d\x15\x16\x0a\x0c\x12\x14\x02\x08\x00\x00\x00\x00" # pylint: disable=line-too-long + b"\xa9\x01\x1b" + b"\xa8\x01\x6b" + b"\xa8\x01\x6d" + b"\xa7\x01\x40" + b"\xad\x01\x47" + b"\xaf\x01\x73" + b"\xaf\x01\x73" + b"\xac\x01\x44" + b"\xa3\x01\x6c" + b"\xcb\x01\x00" + b"\xcd\x01\x22" + b"\xc2\x01\x10" + b"\xc5\x01\x00" + b"\xc6\x01\x0e" + b"\xc7\x01\x1f" + b"\xc8\x01\x0e" + b"\xbf\x01\x00" # Dual-Single Gate Select (BFh) 0=>Single gate + b"\xf9\x01\x20" + b"\x9b\x01\x3b" + b"\x93\x03\x33\x7f\x00" + b"\x70\x06\x0e\x0f\x03\x0e\x0f\x03" + b"\x71\x03\x0e\x16\x03" + b"\x91\x02\x0e\x09" + b"\xc3\x01\x2c" # Vreg1a Voltage Control 2 (C3h) vreg1_vbp_d=0x2C + b"\xc4\x01\x1a" # Vreg1b Voltage Control 2 (C4h) vreg1_vbn_d=0x1A + b"\xf0\x06\x51\x13\x0c\x06\x00\x2f" # SET_GAMMA1 (F0h) + b"\xf2\x06\x51\x13\x0c\x06\x00\x33" # SET_GAMMA3 (F2h) + b"\xf1\x06\x3c\x94\x4f\x33\x34\xcf" # SET_GAMMA2 (F1h) + b"\xf3\x06\x4d\x94\x4f\x33\x34\xcf" # SET_GAMMA4 (F3h) + b"\x36\x01\x00" # Memory Access Control (36h) MY=0, MX=0, MV=0, ML=0, BGR=0, MH=0 + b"\x11\x80\xc8" # Sleep Out Mode (11h) and delay(200) + b"\x29\x80\x14" # Display ON (29h) and delay(20) + b"\x2c\x00" # Memory Write (2Ch) D=0 +) + + +# pylint: disable=too-few-public-methods +class GC9D01(BusDisplay): + """GC9D01 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/gc9d01/manifest.py b/micropython/drivers/display/gc9d01/manifest.py new file mode 100644 index 000000000..ee25420d2 --- /dev/null +++ b/micropython/drivers/display/gc9d01/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay gc9d01 display driver", + version="0.1.5", +) +module("gc9d01.py", opt=3) diff --git a/micropython/drivers/display/hx8357/hx8357.py b/micropython/drivers/display/hx8357/hx8357.py new file mode 100644 index 000000000..7489b2376 --- /dev/null +++ b/micropython/drivers/display/hx8357/hx8357.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_hx8357` +================================================================================ + +displayio driver for HX8357 Displays such as the 3.5-inch TFT FeatherWing and Breakout + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Hardware:** + +* 3.5" PiTFT Plus 480x320 3.5" TFT+Touchscreen for Raspberry Pi: + +* 3.5" TFT 320x480 + Touchscreen Breakout Board w/MicroSD Socket: + +* Adafruit TFT FeatherWing - 3.5" 480x320 Touchscreen for Feathers: + + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HX8357.git" + +_INIT_SEQUENCE = ( + b"\x01\x80\x64" # _SWRESET and Delay 100ms + b"\xb9\x83\xff\x83\x57\xff" # _SETC and delay 500ms + b"\xb3\x04\x80\x00\x06\x06" # _SETRGB 0x80 enables SDO pin (0x00 disables) + b"\xb6\x01\x25" # _SETCOM -1.52V + b"\xb0\x01\x68" # _SETOSC Normal mode 70Hz, Idle mode 55 Hz + b"\xcc\x01\x05" # _SETPANEL BGR, Gate direction swapped + b"\xb1\x06\x00\x15\x1c\x1c\x83\xaa" # _SETPWR1 Not deep standby BT VSPR VSNR AP + b"\xc0\x06\x50\x50\x01\x3c\x1e\x08" # _SETSTBA OPON normal OPON idle STBA GEN + b"\xb4\x07\x02\x40\x00\x2a\x2a\x0d\x78" # _SETCYC NW 0x02 RTN DIV DUM DUM GDON GDOFF + b"\xe0\x22\x02\x0a\x11\x1d\x23\x35\x41\x4b\x4b\x42\x3a\x27\x1b\x08\x09\x03\x02\x0a" + b"\x11\x1d\x23\x35\x41\x4b\x4b\x42\x3a\x27\x1b\x08\x09\x03\x00\x01" # _SETGAMMA + b"\x3a\x01\x55" # _COLMOD 16 bit + b"\x36\x01\xc0" # _MADCTL + b"\x35\x01\x00" # _TEON TW off + b"\x44\x02\x00\x02" # _TEARLINE + b"\x11\x80\x96" # _SLPOUT and delay 150 ms + b"\x36\x01\xa0" + b"\x29\x80\x32" # _DISPON and delay 50 ms +) + + +# pylint: disable=too-few-public-methods +class HX8357(BusDisplay): + """HX8357D driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/hx8357/manifest.py b/micropython/drivers/display/hx8357/manifest.py new file mode 100644 index 000000000..a4c069229 --- /dev/null +++ b/micropython/drivers/display/hx8357/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay hx8357 display driver", + version="0.1.5", +) +module("hx8357.py", opt=3) diff --git a/micropython/drivers/display/ili9163/ili9163.py b/micropython/drivers/display/ili9163/ili9163.py new file mode 100644 index 000000000..5952805c3 --- /dev/null +++ b/micropython/drivers/display/ili9163/ili9163.py @@ -0,0 +1,82 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Tavish Naruka for Electronut Labs (electronut.in) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`electronutlabs_ili9163` +================================================================================ + +displayio driver for ILI9163 TFT-LCD displays. + + +* Author(s): Tavish Naruka + +Implementation Notes +-------------------- + +**Hardware:** + + * `Electronut Labs Blip `_ + * `TFTM018 `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/electronut/Electronutlabs_CircuitPython_ILI9163.git" + +_INIT_SEQUENCE = ( + b"\x01\x80\x80" + b"\x11\x80\x05" + b"\x3a\x01\x05" + b"\x26\x01\x04" + b"\xf2\x01\x01" + b"\xe0\x0f\x3f\x25\x1c\x1e\x20\x12\x2a\x90\x24\x11\x00\x00\x00\x00\x00" + b"\xe1\x0f\x20\x20\x20\x20\x05\x00\x15\xa7\x3d\x18\x25\x2a\x2b\x2b\x3a" + b"\xb1\x02\x08\x08" + b"\xb4\x01\x07" + b"\xc0\x02\x0a\x02" + b"\xc1\x01\x02" + b"\xc5\x02\x50\x5b" + b"\xc7\x01\x40" + b"\x2a\x04\x00\x00\x00\x7f" + b"\x2b\x04\x00\x00\x00\x7f" + b"\x36\x01\x68" # rotation + b"\x29\x80\x78" + b"\x2c\x80\x78" +) + +# pylint: disable=too-few-public-methods + + +class ILI9163(BusDisplay): + """ILI9163 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/ili9163/manifest.py b/micropython/drivers/display/ili9163/manifest.py new file mode 100644 index 000000000..2c7d18648 --- /dev/null +++ b/micropython/drivers/display/ili9163/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay ili9163 display driver", + version="0.1.5", +) +module("ili9163.py", opt=3) diff --git a/micropython/drivers/display/ili9341/ili9341.py b/micropython/drivers/display/ili9341/ili9341.py new file mode 100644 index 000000000..ca9d9563b --- /dev/null +++ b/micropython/drivers/display/ili9341/ili9341.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`ili9341` +==================================================== + +Display driver for ILI9341 + +* Author(s): Scott Shawcroft + +Implementation Notes +-------------------- + +**Hardware:** + +* Adafruit PiTFT 2.2" HAT Mini Kit - 320x240 2.2" TFT - No Touch + +* Adafruit PiTFT 2.4" HAT Mini Kit - 320x240 TFT Touchscreen + +* Adafruit PiTFT - 320x240 2.8" TFT+Touchscreen for Raspberry Pi + +* PiTFT 2.8" TFT 320x240 + Capacitive Touchscreen for Raspberry Pi + +* Adafruit PiTFT Plus 320x240 2.8" TFT + Capacitive Touchscreen + +* PiTFT Plus Assembled 320x240 2.8" TFT + Resistive Touchscreen + +* PiTFT Plus 320x240 3.2" TFT + Resistive Touchscreen + +* 2.2" 18-bit color TFT LCD display with microSD card breakout + +* 2.4" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket + +* 2.8" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket + +* 3.2" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket + +* TFT FeatherWing - 2.4" 320x240 Touchscreen For All Feathers + +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +_INIT_SEQUENCE = ( + b"\x01\x80\x80" # Software reset then delay 0x80 (128ms) + b"\xef\x03\x03\x80\x02" + b"\xcf\x03\x00\xc1\x30" + b"\xed\x04\x64\x03\x12\x81" + b"\xe8\x03\x85\x00\x78" + b"\xcb\x05\x39\x2c\x00\x34\x02" + b"\xf7\x01\x20" + b"\xea\x02\x00\x00" + b"\xc0\x01\x23" # Power control VRH[5:0] + b"\xc1\x01\x10" # Power control SAP[2:0];BT[3:0] + b"\xc5\x02\x3e\x28" # VCM control + b"\xc7\x01\x86" # VCM control2 + b"\x36\x01\x38" # Memory Access Control + b"\x37\x01\x00" # Vertical scroll zero + b"\x3a\x01\x55" # COLMOD: Pixel Format Set + b"\xb1\x02\x00\x18" # Frame Rate Control (In Normal Mode/Full Colors) + b"\xb6\x03\x08\x82\x27" # Display Function Control + b"\xf2\x01\x00" # 3Gamma Function Disable + b"\x26\x01\x01" # Gamma curve selected + b"\xe0\x0f\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00" # Set Gamma + b"\xe1\x0f\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f" # Set Gamma + b"\x11\x80\x78" # Exit Sleep then delay 0x78 (120ms) + b"\x29\x80\x78" # Display on then delay 0x78 (120ms) +) + + +class ILI9341(BusDisplay): + """ILI9341 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/ili9341/manifest.py b/micropython/drivers/display/ili9341/manifest.py new file mode 100644 index 000000000..82a8828ed --- /dev/null +++ b/micropython/drivers/display/ili9341/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay ili9341 display driver", + version="0.1.5", +) +module("ili9341.py", opt=3) diff --git a/micropython/drivers/display/ili9488/ili9488.py b/micropython/drivers/display/ili9488/ili9488.py new file mode 100644 index 000000000..ae2259231 --- /dev/null +++ b/micropython/drivers/display/ili9488/ili9488.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`bagaloozy_ili9488` +==================================================== + +Display driver for ILI9488 + +* Author(s): Mark Winney + +Implementation Notes +-------------------- + +**Hardware:** + +* Buy Display LCD 3.5" 320x480 TFT Display Module,OPTL Touch Screen w/Breakout Board + + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ILI9488.git" + +_INIT_SEQUENCE = ( + b"\xe0\x0f\x00\x03\x09\x08\x16\x0a\x3f\x78\x4c\x09\x0a\x08\x16\x1a\x0f" + b"\xe1\x0f\x00\x16\x19\x03\x0f\x05\x32\x45\x46\x04\x0e\x0d\x35\x37\x0f" + b"\xc0\x02\x17\x15" # Power Control 1 Vreg1out Verg2out + b"\xc1\x01\x41" # Power Control 2 VGH,VGL + b"\xc5\x03\x00\x12\x80" # Power Control 3 Vcom + b"\x36\x01\x48" # Memory Access + b"\x3a\x01\x55" # Interface Pixel Format 16 bit + b"\xb0\x01\x00" # Interface Mode Control + b"\xb1\x01\xa0" # Frame rate 60Hz + b"\xb4\x01\x02" # Display Inversion Control 2-dot + b"\xb6\x00" # Display Function Control RGB/MCU Interface Control + b"\x02\x01\x02" # MCU Source,Gate scan direction + b"\xe9\x01\x00" # Set Image Function Disable 24 bit data + b"\xf7\x04\xa9\x51\x2c\x82" # Adjust Control D7 stream, loose + b"\x11\x80\x78" # Sleep out delay 120ms + b"\x29\x00" +) + + +# pylint: disable=too-few-public-methods +class ILI9488(BusDisplay): + """ILI9488 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/ili9488/manifest.py b/micropython/drivers/display/ili9488/manifest.py new file mode 100644 index 000000000..488c9b19b --- /dev/null +++ b/micropython/drivers/display/ili9488/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay ili9488 display driver", + version="0.1.5", +) +module("ili9488.py", opt=3) diff --git a/micropython/drivers/display/st7701/manifest.py b/micropython/drivers/display/st7701/manifest.py new file mode 100644 index 000000000..fc3fbf8dc --- /dev/null +++ b/micropython/drivers/display/st7701/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7701 display driver", + version="0.1.5", +) +module("st7701.py", opt=3) diff --git a/micropython/drivers/display/st7701/st7701.py b/micropython/drivers/display/st7701/st7701.py new file mode 100644 index 000000000..8a1d69e22 --- /dev/null +++ b/micropython/drivers/display/st7701/st7701.py @@ -0,0 +1,127 @@ +""" +GPL-3.0 License +see https://github.com/Xinyuan-LilyGO/lilygo-micropython/tree/master/target/esp32s3/boards/LILYGO_T-RGB/modules +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay +from time import sleep_ms + + +_INIT_SEQUENCE = [ + (0xFF, b"\x77\x01\x00\x00\x10", 0), + (0xC0, b"\x3b\x00", 0), + (0xC1, b"\x0b\x02", 0), + (0xC2, b"\x07\x02", 0), + (0xCC, b"\x10", 0), + (0xCD, b"\x08", 0), # 用565时屏蔽 666打开 + (0xB0, b"\x00\x11\x16\x0e\x11\x06\x05\x09\x08\x21\x06\x13\x10\x29\x31\x18", 0), + (0xB1, b"\x00\x11\x16\x0e\x11\x07\x05\x09\x09\x21\x05\x13\x11\x2a\x31\x18", 0), + (0xFF, b"\x77\x01\x00\x00\x11", 0), + (0xB0, b"\x6d", 0), + (0xB1, b"\x37", 0), + (0xB2, b"\x81", 0), + (0xB3, b"\x80", 0), + (0xB5, b"\x43", 0), + (0xB7, b"\x85", 0), + (0xB8, b"\x20", 0), + (0xC1, b"\x78", 0), + (0xC2, b"\x78", 0), + (0xC3, b"\x8c", 0), + (0xD0, b"\x88", 0), + (0xE0, b"\x00\x00\x02", 0), + (0xE1, b"\x03\xa0\x00\x00\x04\xa0\x00\x00\x00\x20\x20", 0), + (0xE2, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0), + (0xE3, b"\x00\x00\x11\x00", 0), + (0xE4, b"\x22\x00", 0), + (0xE5, b"\x05\xec\xa0\xa0\x07\xee\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00", 0), + (0xE6, b"\x00\x00\x11\x00", 0), + (0xE7, b"\x22\x00", 0), + (0xE8, b"\x06\xed\xa0\xa0\x08\xef\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00", 0), + (0xEB, b"\x00\x00\x40\x40\x00\x00\x00", 0), + (0xED, b"\xff\xff\xff\xba\x0a\xbf\x45\xff\xff\x54\xfb\xa0\xab\xff\xff\xff", 0), + (0xEF, b"\x10\x0d\x04\x08\x3f\x1f", 0), + (0xFF, b"\x77\x01\x00\x00\x13", 0), + (0xEF, b"\x08", 0), + (0xFF, b"\x77\x01\x00\x00\x00", 0), + (0x36, b"\x08", 0), + (0x3A, b"\x66", 0), + (0x11, b"\x00", 100), + # (0xFF, b'\x77\x01\x00\x00\x12', 0), + # (0xd1, b'\x81', 0), + # (0xd2, b'\x06', 0), + (0x29, b"\x00", 120), +] + + +class LCDPins: + def __init__(self, *, pwr_en, cs, sda, clk, rst): + self.pwr_en = pwr_en + self.cs = cs + self.sda = sda + self.clk = clk + self.rst = rst + + +class ST7701(BusDisplay): + """ + ST7701 display driver + + :param lcd_pins: the io pins to configure the display + """ + + def __init__(self, lcd_pins, bus, **kwargs): + self.lcd_pins = lcd_pins + + self.lcd_pins.pwr_en(1) + self.lcd_pins.cs(1) + self.lcd_pins.sda(1) + self.lcd_pins.clk(1) + + # Reset the display + self.lcd_pins.rst(1) + sleep_ms(200) + self.lcd_pins.rst(0) + sleep_ms(200) + self.lcd_pins.rst(1) + sleep_ms(200) + + super()._init_(bus, _INIT_SEQUENCE, **kwargs) + + def init(self): + # self.rotation_table = _ROTATION_TABLE + super().init(render_mode_full=True) + + def send(self, cmd, params=None): + self._tx_cmd(cmd) + if params: + self._tx_data(params) + + def _tx_cmd(self, cmd): + self.lcd_pins.cs(0) + self.lcd_pins.sda(0) + self.lcd_pins.clk(0) + self.lcd_pins.clk(1) + self._tx_byte(cmd) + self.lcd_pins.cs(1) + + def _tx_data(self, data): + for i in range(len(data)): + self.lcd_pins.cs(0) + self.lcd_pins.sda(1) + self.lcd_pins.clk(0) + self.lcd_pins.clk(1) + self._tx_byte(data[i]) + self.lcd_pins.cs(1) + + def _tx_byte(self, bits): + for _ in range(8): + if bits & 0x80: + self.lcd_pins.sda(1) + else: + self.lcd_pins.sda(0) + bits <<= 1 + self.lcd_pins.clk(0) + self.lcd_pins.clk(1) diff --git a/micropython/drivers/display/st7735/manifest.py b/micropython/drivers/display/st7735/manifest.py new file mode 100644 index 000000000..7776b952a --- /dev/null +++ b/micropython/drivers/display/st7735/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7735 display driver", + version="0.1.5", +) +module("st7735.py", opt=3) diff --git a/micropython/drivers/display/st7735/st7735.py b/micropython/drivers/display/st7735/st7735.py new file mode 100644 index 000000000..ce6f603f7 --- /dev/null +++ b/micropython/drivers/display/st7735/st7735.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_st7735` +==================================================== + +Displayio driver for ST7735 based displays. + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ST7735.git" + +_INIT_SEQUENCE = ( + b"\x01\x80\x32" # _SWRESET and Delay 50ms + b"\x11\x80\xff" # _SLPOUT + b"\x3a\x81\x05\x0a" # _COLMOD + b"\xb1\x83\x00\x06\x03\x0a" # _FRMCTR1 + b"\x36\x01\x08" # _MADCTL + b"\xb6\x02\x15\x02" # _DISSET5 + # 1 clk cycle nonoverlap, 2 cycle gate, rise, 3 cycle osc equalize, Fix on VTL + b"\xb4\x01\x00" # _INVCTR line inversion + b"\xc0\x82\x02\x70\x0a" # _PWCTR1 GVDD = 4.7V, 1.0uA, 10 ms delay + b"\xc1\x01\x05" # _PWCTR2 VGH = 14.7V, VGL = -7.35V + b"\xc2\x02\x01\x02" # _PWCTR3 Opamp current small, Boost frequency + b"\xc5\x82\x3c\x38\x0a" # _VMCTR1 + b"\xfc\x02\x11\x15" # _PWCTR6 + b"\xe0\x10\x09\x16\x09\x20\x21\x1b\x13\x19\x17\x15\x1e\x2b\x04\x05\x02\x0e" # _GMCTRP1 Gamma + b"\xe1\x90\x0b\x14\x08\x1e\x22\x1d\x18\x1e\x1b\x1a\x24\x2b\x06\x06\x02\x0f\x0a" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\xff" # _DISPON +) + + +# pylint: disable=too-few-public-methods +class ST7735(BusDisplay): + """ST7735 driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/st7735r/manifest.py b/micropython/drivers/display/st7735r/manifest.py new file mode 100644 index 000000000..7a0ee55f2 --- /dev/null +++ b/micropython/drivers/display/st7735r/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7735r display driver", + version="0.1.5", +) +module("st7735r.py", opt=3) diff --git a/micropython/drivers/display/st7735r/st7735r.py b/micropython/drivers/display/st7735r/st7735r.py new file mode 100644 index 000000000..0702cd2cd --- /dev/null +++ b/micropython/drivers/display/st7735r/st7735r.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_st7735r` +==================================================== + +Displayio driver for ST7735R based displays. + +* Author(s): Scott Shawcroft and Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Hardware:** + +* `1.8" SPI TFT display, 160x128 18-bit color + `_ (Product ID: 618) +* `Adafruit 0.96" 160x80 Color TFT Display w/ MicroSD Card Breakout + `_ (Product ID: 3533) +* `1.8" Color TFT LCD display with MicroSD Card Breakout: + `_ (Product ID: 358) +* `Adafruit 1.44" Color TFT LCD Display with MicroSD Card breakout: + `_ (Product ID: 2088) +* `Adafruit Mini Color TFT with Joystick FeatherWing: + `_ (Product ID: 3321) + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ST7735R.git" + +_INIT_SEQUENCE = bytearray( + b"\x01\x80\x96" # SWRESET and Delay 150ms + b"\x11\x80\xff" # SLPOUT and Delay + b"\xb1\x03\x01\x2c\x2d" # _FRMCTR1 + b"\xb2\x03\x01\x2c\x2d" # _FRMCTR2 + b"\xb3\x06\x01\x2c\x2d\x01\x2c\x2d" # _FRMCTR3 + b"\xb4\x01\x07" # _INVCTR line inversion + b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA + b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V + b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency + b"\xc3\x02\x8a\x2a" + b"\xc4\x02\x8a\xee" + b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V + b"\x20\x00" # _INVOFF + b"\x36\x01\x18" # _MADCTL bottom to top refresh + # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, + # fix on VTL + b"\x3a\x01\x05" # COLMOD - 16bit color + b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2b\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma + b"\xe1\x10\x03\x1d\x07\x06\x2e\x2c\x29\x2d\x2e\x2e\x37\x3f\x00\x00\x02\x10" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\x64" # _DISPON +) + + +# pylint: disable=too-few-public-methods +class ST7735R(BusDisplay): + """ST7735R display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/st7735r_1/manifest.py b/micropython/drivers/display/st7735r_1/manifest.py new file mode 100644 index 000000000..a80c1e837 --- /dev/null +++ b/micropython/drivers/display/st7735r_1/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7735r_1 display driver", + version="0.1.5", +) +module("st7735r_1.py", opt=3) diff --git a/micropython/drivers/display/st7735r_1/st7735r_1.py b/micropython/drivers/display/st7735r_1/st7735r_1.py new file mode 100644 index 000000000..8e119fe3a --- /dev/null +++ b/micropython/drivers/display/st7735r_1/st7735r_1.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +from busdisplay import BusDisplay + + +_INIT_SEQUENCE = [ + (0x36, b"\x70", 0), + (0x3A, b"\x05", 0), + (0xB1, b"\x01\x2c\x2d", 0), + (0xB2, b"\x01\x2c\x2d", 0), + (0xB3, b"\x01\x2c\x2d\x01\x2c\x2d", 0), + (0xB4, b"\x07", 0), + (0xC0, b"\xa2\x02\x84", 0), + (0xC1, b"\xc5", 0), + (0xC2, b"\x0a\x00", 0), + (0xC3, b"\x8a\x2a", 0), + (0xC4, b"\x8a\xee", 0), + (0xC5, b"\x0e", 0), + (0xE0, b"\x0f\x1a\x0f\x18\x2f\x28\x20\x22\x1f\x1b\x23\x37\x00\x07\x02\x10", 0), + (0xE1, b"\x0f\x1b\x0f\x17\x33\x2c\x29\x2e\x30\x30\x39\x3f\x00\x07\x03\x10", 0), + (0xF0, b"\x01", 0), + (0xF6, b"\x00", 0), + (0x11, None, 255), + (0x29, None, 255), +] + + +class ST7735R(BusDisplay): + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/st7789/manifest.py b/micropython/drivers/display/st7789/manifest.py new file mode 100644 index 000000000..18477d310 --- /dev/null +++ b/micropython/drivers/display/st7789/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7789 display driver", + version="0.1.5", +) +module("st7789.py", opt=3) diff --git a/micropython/drivers/display/st7789/st7789.py b/micropython/drivers/display/st7789/st7789.py new file mode 100644 index 000000000..a8a56c647 --- /dev/null +++ b/micropython/drivers/display/st7789/st7789.py @@ -0,0 +1,40 @@ +""" +see https://github.com/Xinyuan-LilyGO/lilygo-micropython/tree/master/target/esp32s3/boards/LILYGO_T-RGB/modules +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + + +_INIT_SEQUENCE = [ + (0x11, b"\x00", 120), # Exit sleep mode + (0x13, b"\x00", 0), # Turn on the display + (0xB6, b"\x0a\x82", 0), # Set display function control + (0x31, b"\x55", 10), # Set pixel format to 16 bits per pixel (RGB565) + (0xB2, b"\x0c\x0c\x00\x33\x33", 0), # Set porch control + (0xB7, b"\x35", 0), # Set gate control + (0xBB, b"\x28", 0), # Set VCOMS setting + (0xC0, b"\x0c", 0), # Set power control 1 + (0xC2, b"\x01\xff", 0), # Set power control 2 + (0xC3, b"\x10", 0), # Set power control 3 + (0xC4, b"\x20", 0), # Set power control 4 + (0xC6, b"\x0f", 0), # Set VCOM control 1 + (0xD0, b"\xa4\xa1", 0), # Set power control A + # Set gamma curve positive polarity + (0xE0, b"\xd0\x00\x02\x07\x0a\x28\x32\x44\x42\x06\x0e\x12\x14\x17", 0), + # Set gamma curve negative polarity + (0xE1, b"\xd0\x00\x02\x07\x0a\x28\x31\x54\x47\x0e\x1c\x17\x1b\x1e", 0), + (0x21, b"\x00", 0), # Enable display inversion + (0x29, b"\x00", 120), # Turn on the display +] + + +class ST7789(BusDisplay): + """ + ST7789 display driver + """ + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/st7789vw/manifest.py b/micropython/drivers/display/st7789vw/manifest.py new file mode 100644 index 000000000..255f67da9 --- /dev/null +++ b/micropython/drivers/display/st7789vw/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7789vw display driver", + version="0.1.5", +) +module("st7789vw.py", opt=3) diff --git a/micropython/drivers/display/st7789vw/st7789vw.py b/micropython/drivers/display/st7789vw/st7789vw.py new file mode 100644 index 000000000..836d50c86 --- /dev/null +++ b/micropython/drivers/display/st7789vw/st7789vw.py @@ -0,0 +1,40 @@ +""" +ST7789VW Driver +Adapted from LCD_Module_RPI_code.zip/LCD_Module_RPI_code/RaspberryPi/python/lib +at https://files.waveshare.com/upload/8/8d/LCD_Module_RPI_code.zip +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay + + +_INIT_SEQUENCE = [ + (0x36, b"\x00", 0), + (0x3A, b"\x05", 0), + (0x21, b"\x00", 0), + (0x2A, b"\x00\x00\x01\x3f", 0), + (0x2B, b"\x00\x00\x00\xef", 0), + (0xB2, b"\x0c\x0c\x00\x33\x33", 0), + (0xB7, b"\x35", 0), + (0xBB, b"\x1f", 0), + (0xC0, b"\x2c", 0), + (0xC2, b"\x01", 0), + (0xC3, b"\x12", 0), + (0xC4, b"\x20", 0), + (0xC6, b"\x0f", 0), + (0xD0, b"\xa4\xa1", 0), + (0xE0, b"\xd0\x08\x11\x08\x0c\x15\x39\x33\x50\x36\x13\x14\x29\x2d", 0), + (0xE1, b"\xd0\x08\x10\x08\x06\x06\x39\x44\x51\x0b\x16\x14\x2f\x31", 0), + (0x21, b"\x00", 0), + (0x11, b"\x00", 0), + (0x29, b"\x00", 120), +] + + +class ST7789VW(BusDisplay): + """ST789VW display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/display/st7796/manifest.py b/micropython/drivers/display/st7796/manifest.py new file mode 100644 index 000000000..38346a387 --- /dev/null +++ b/micropython/drivers/display/st7796/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7796 display driver", + version="0.1.5", +) +module("st7796.py", opt=3) diff --git a/micropython/drivers/display/st7796/st7796.py b/micropython/drivers/display/st7796/st7796.py new file mode 100644 index 000000000..fbb8c3888 --- /dev/null +++ b/micropython/drivers/display/st7796/st7796.py @@ -0,0 +1,142 @@ +""" +The init sequence is written out line by line in .init() +""" + +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay +from time import sleep_ms +from micropython import const + + +_SWRESET = const(0x01) +_SLPOUT = const(0x11) +_CSCON = const(0xF0) +_MADCTL = const(0x36) +_COLMOD = const(0x3A) +_DIC = const(0xB4) +_DFC = const(0xB6) +_DOCA = const(0xE8) +_PWR2 = const(0xC1) +_PWR3 = const(0xC2) +_VCMPCTL = const(0xC5) +_PGC = const(0xE0) +_NGC = const(0xE1) +_DISPON = const(0x29) + + +class ST7796(BusDisplay): + """ST7796 display driver""" + + def __init__(self, bus, **kwargs): + super().__init__(bus, **kwargs) + + def init(self): + # self.rotation_table = _ROTATION_TABLE + param_buf = bytearray(14) + param_mv = memoryview(param_buf) + + self.send(_SWRESET) + + sleep_ms(120) + + self.send(_SLPOUT) + + sleep_ms(120) + + param_buf[0] = 0xC3 + self.send(_CSCON, param_mv[:1]) + + param_buf[0] = 0x96 + self.send(_CSCON, param_mv[:1]) + + if self.color_depth // 8 == 2: + pixel_format = 0x55 + elif self.color_depth // 8 == 3: + pixel_format = 0x77 + else: + raise RuntimeError( + "ST7796 IC only supports " "lv.COLOR_FORMAT.RGB565 or lv.COLOR_FORMAT.RGB888" + ) + + param_buf[0] = pixel_format + self.send(_COLMOD, param_mv[:1]) + + param_buf[0] = 0x01 + self.send(_DIC, param_mv[:1]) + + param_buf[0] = 0x80 + param_buf[1] = 0x02 + param_buf[2] = 0x3B + self.send(_DFC, param_mv[:3]) + + param_buf[:8] = bytearray([0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33]) + self.send(_DOCA, param_mv[:8]) + + param_buf[0] = 0x06 + self.send(_PWR2, param_mv[:1]) + + param_buf[0] = 0xA7 + self.send(_PWR3, param_mv[:1]) + + param_buf[0] = 0x18 + self.send(_VCMPCTL, param_mv[:1]) + + sleep_ms(120) + + param_buf[:14] = bytearray( + [ + 0xF0, + 0x09, + 0x0B, + 0x06, + 0x04, + 0x15, + 0x2F, + 0x54, + 0x42, + 0x3C, + 0x17, + 0x14, + 0x18, + 0x1B, + ] + ) + self.send(_PGC, param_mv[:14]) + + param_buf[:14] = bytearray( + [ + 0xE0, + 0x09, + 0x0B, + 0x06, + 0x04, + 0x03, + 0x2B, + 0x43, + 0x42, + 0x3B, + 0x16, + 0x14, + 0x17, + 0x1B, + ] + ) + self.send(_NGC, param_mv[:14]) + + sleep_ms(120) + + param_buf[0] = 0x3C + self.send(_CSCON, param_mv[:1]) + + param_buf[0] = 0x69 + self.send(_CSCON, param_mv[:1]) + + sleep_ms(120) + + self.send(_DISPON) + + sleep_ms(120) + + super().init() diff --git a/micropython/drivers/display/st7796_test/manifest.py b/micropython/drivers/display/st7796_test/manifest.py new file mode 100644 index 000000000..d3923a70e --- /dev/null +++ b/micropython/drivers/display/st7796_test/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay st7796_test display driver", + version="0.1.5", +) +module("st7796_test.py", opt=3) diff --git a/micropython/drivers/display/st7796_test/st7796_test.py b/micropython/drivers/display/st7796_test/st7796_test.py new file mode 100644 index 000000000..5a52903c6 --- /dev/null +++ b/micropython/drivers/display/st7796_test/st7796_test.py @@ -0,0 +1,50 @@ +try: + from displaysys.busdisplay import BusDisplay +except ImportError: + from busdisplay import BusDisplay +from micropython import const + +_SWRESET = const(0x01) +_SLPOUT = const(0x11) +_CSCON = const(0xF0) +_MADCTL = const(0x36) +_COLMOD = const(0x3A) +_DIC = const(0xB4) +_DFC = const(0xB6) +_DOCA = const(0xE8) +_PWR2 = const(0xC1) +_PWR3 = const(0xC2) +_VCMPCTL = const(0xC5) +_PGC = const(0xE0) +_NGC = const(0xE1) +_DISPON = const(0x29) + +_INIT_SEQUENCE = [ + (_SWRESET, None, 120), # Software reset + (_SLPOUT, None, 120), # Sleep out + (_CSCON, b"\xc3", 0), # Enable extension command 2 partI + (_CSCON, b"\x96", 0), # Enable extension command 2 partII + (_MADCTL, b"\x48", 0), # Memory data access control + (_COLMOD, b"\x55", 0), # Interface pixel format set to 16 + (_DIC, b"\x01", 0), # Column inversion + (_DFC, b"\x80\x02\x3b", 0), # Display function control + (_DOCA, b"\x40\x8a\x00\x00\x29\x19\xa5\x33", 0), # Display output control adjust + (_PWR2, b"\x06", 0), # Power control2 + (_PWR3, b"\xa7", 0), # Power control3 + (_VCMPCTL, b"\x18", 120), # VCOM control + (_PGC, b"\xf0\x09\x0b\x06\x04\x15\x2f\x54\x42\x3c\x17\x14\x18\x1b", 0), # Gamma positive + # Should first byte be 0xF0 or 0xE0? + (_NGC, b"\xe0\x09\x0b\x06\x04\x03\x2b\x43\x42\x3b\x16\x14\x17\x1b", 120), # Gamma negative + (_CSCON, b"\x3c", 0), # Command Set control + (_CSCON, b"\x69", 120), # Command Set control + (_DISPON, None, 120), # Display on +] + + +class ST7796(BusDisplay): + """ + ST7796 display driver + """ + + def __init__(self, bus, **kwargs): + super().__init__(bus, _INIT_SEQUENCE, **kwargs) diff --git a/micropython/drivers/touch/chsc6x/chsc6x.py b/micropython/drivers/touch/chsc6x/chsc6x.py new file mode 100644 index 000000000..7c1c58806 --- /dev/null +++ b/micropython/drivers/touch/chsc6x/chsc6x.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2023 Brad Barnett +# +# SPDX-License-Identifier: MIT +# +# Suggest setting I2C freq = 400000 and using IRQ pin for best performance +# Set IRQ speed to 100000 if not using an IRQ pin + + +from machine import Pin, I2C +from time import sleep_ms +from micropython import const + + +CHSC6X_I2C_ID = const(0x2E) +CHSC6X_READ_POINT_LEN = const(5) + + +class CHSC6X: + def __init__(self, i2c, addr=CHSC6X_I2C_ID, irq_pin=None): + self._i2c = i2c + self._addr = addr + self._irq = Pin(irq_pin, Pin.IN, Pin.PULL_UP) if irq_pin else None + self._buffer = bytearray(CHSC6X_READ_POINT_LEN) + sleep_ms(100) + + def is_touched(self): + if self._irq is not None: + if self._irq.value() is False: + return True + return False + return self.touch_read() is not None + + def touch_read(self): + if self._irq is not None: + if self.is_touched() is True: + self._i2c.readfrom_into(self._addr, self._buffer) + else: + return None + else: + try: + self._i2c.readfrom_into(self._addr, self._buffer) + except OSError: # Thrown when reading too fast + return None + + results = list(self._buffer) + # first byte is non-zero when touched, 3rd byte is x, 5th byte is y + if results[0]: + return results[2], results[4] + return None + + +def main(): + print("Started...") + i2c = I2C(0, sda=Pin(7), scl=Pin(6), freq=400000) + touch = CHSC6X(i2c, irq_pin=16) + + while True: + if touch.is_touched(): + print("Touched: ", touch.touch_read()) + + +if __name__ == "__main__": + main() diff --git a/micropython/drivers/touch/chsc6x/manifest.py b/micropython/drivers/touch/chsc6x/manifest.py new file mode 100644 index 000000000..2e19b2682 --- /dev/null +++ b/micropython/drivers/touch/chsc6x/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay chsc6x touch driver", + version="0.1.5", +) +module("chsc6x.py", opt=3) diff --git a/micropython/drivers/touch/cst226/cst226.py b/micropython/drivers/touch/cst226/cst226.py new file mode 100644 index 000000000..d79851bf5 --- /dev/null +++ b/micropython/drivers/touch/cst226/cst226.py @@ -0,0 +1,149 @@ +""" +Adapted from: + https://github.com/lewisxhe/SensorLib/blob/master/src/touch/TouchClassCST226.cpp +""" + +from machine import Pin +from time import sleep_ms +from micropython import const + +# CST226-specific constants +_CST226_ID = const(0xA8) # CST226 specific chip ID +_CST226_REG_STATUS = const(0x00) +_CST226_BUFFER_NUM = const(28) + +# Register Definitions +_REG_SLEEP_MODE = const(0xE5) +_REG_LONG_PRESS_TICK = const(0xEB) +_REG_MOTION_MASK = const(0xEC) +_REG_IRQ_CTL = const(0xFA) +_REG_DIS_AUTOSLEEP = const(0xFE) + +# Motion and IRQ masks +MOTION_MASK_CONTINUOUS_LEFT_RIGHT = const(0b100) +MOTION_MASK_CONTINUOUS_UP_DOWN = const(0b010) +MOTION_MASK_DOUBLE_CLICK = const(0b001) + +IRQ_EN_TOUCH = const(0x40) +IRQ_EN_CHANGE = const(0x20) +IRQ_EN_MOTION = const(0x10) +IRQ_EN_LONGPRESS = const(0x01) + + +class CST226: + def __init__( + self, + bus, + address=0x5A, + rst_pin=None, + irq_pin=None, + irq_handler=lambda pin: None, + irq_en=0x00, + motion_mask=0b000, + ): + self._bus = bus + self._address = address + + # Detect if the chip is CST226 + buffer = bytearray(8) + write_buffer = bytearray(2) + write_buffer[0] = 0xD2 + write_buffer[1] = 0x04 + self._write_then_read(write_buffer, buffer, 4) + chipType = (buffer[3] << 8) | buffer[2] + if chipType != _CST226_ID: + raise ValueError("Error: CST226 not detected.") + + # Setup pins and reset the device + self.rst = Pin(rst_pin, Pin.OUT) if isinstance(rst_pin, int) else rst_pin + self.reset() + self.disable_autosleep() + + # Setup interrupt pin if available + self.irq = Pin(irq_pin, Pin.IN, Pin.PULL_UP) if isinstance(irq_pin, int) else irq_pin + if self.irq: + self.irq.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler) + self.set_irq_ctl(irq_en, motion_mask) + + def touched(self): + # Read finger count for the CST226 + return self._read(0x02)[0] + + def get_point(self): + # CST226-specific point handling + buffer = self._read(_CST226_REG_STATUS, _CST226_BUFFER_NUM) + if buffer[0] == 0xAB or buffer[5] == 0x80: + return 0 # No touch detected or button press + + point_count = buffer[5] & 0x7F + if point_count > 5 or point_count == 0: + self._write(0x00, 0xAB) + return 0 + + points = [] + index = 0 + for i in range(point_count): + x = (buffer[index + 1] << 4) | ((buffer[index + 3] >> 4) & 0x0F) + y = (buffer[index + 2] << 4) | (buffer[index + 3] & 0x0F) + points.append((x, y)) + index += 7 if i == 0 else 5 + + return points + + def get_gestures(self): + # CST226-specific gesture handling (not available in this example) + return None + + def get_points(self): + raise NotImplementedError("get_points() not implemented (yet)") + + def reset(self): + if self.rst: + self.rst(0) + sleep_ms(1) + self.rst(1) + sleep_ms(50) + else: + # For CST226 specific reset + self._write(0xD1, 0x0E) + sleep_ms(20) + + def disable_autosleep(self, val=0x01): + self._write(_REG_DIS_AUTOSLEEP, val) + + def set_irq_ctl(self, irq_en, motion_mask=0b000): + self._write(_REG_IRQ_CTL, irq_en) + self._write(_REG_MOTION_MASK, motion_mask) + + def set_long_press_tick(self, val): + self._write(_REG_LONG_PRESS_TICK, val) + + def set_sleep_mode(self, val): + self._write(_REG_SLEEP_MODE, val) + + def sleep(self): + # CST226-specific sleep command + self._write(0xD1, 0x05) + + def wakeup(self): + # CST226-specific wakeup using reset + self.reset() + + def get_resolution(self): + # CST226-specific resolution handling + buffer = self._read(0xD1, 8) + x_res = (buffer[1] << 8) | buffer[0] + y_res = (buffer[3] << 8) | buffer[2] + return x_res, y_res + + def _read(self, reg, length=1): + return self._bus.readfrom_mem(self._address, int(reg), length) + + def _write(self, reg, val): + self._bus.writeto_mem(self._address, int(reg), bytes([int(val)])) + + def _write_then_read(self, write_buffer, read_buffer, read_length): + self._bus.writeto(self._address, write_buffer) + read_data = self._bus.readfrom(self._address, read_length) + for i in range(read_length): + read_buffer[i] = read_data[i] diff --git a/micropython/drivers/touch/cst226/manifest.py b/micropython/drivers/touch/cst226/manifest.py new file mode 100644 index 000000000..ae410fd5d --- /dev/null +++ b/micropython/drivers/touch/cst226/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay cst226 touch driver", + version="0.1.5", +) +module("cst226.py", opt=3) diff --git a/micropython/drivers/touch/cst8xx/cst8xx.py b/micropython/drivers/touch/cst8xx/cst8xx.py new file mode 100644 index 000000000..8aba5e715 --- /dev/null +++ b/micropython/drivers/touch/cst8xx/cst8xx.py @@ -0,0 +1,115 @@ +""" +Adapted from https://github.com/waveshareteam/RP2040-Touch-LCD-1.28/blob/main/python/RP2040-LCD-1.28.py +and https://github.com/koendv/cst816t/blob/master/src/cst816t.cpp +by Brad Barnett, 2024 +Reference: https://files.waveshare.com/upload/c/c2/CST816S_register_declaration.pdf +""" + +from machine import Pin +from time import sleep_ms +from micropython import const + + +_CST816S_ID = const(0xB4) +_CST816T_ID = const(0xB5) +_CST816D_ID = const(0xB6) +_CST820_ID = const(0xB7) +_CST826_ID = const(0x11) + +_REG_GESTURE_ID = const(0x01) +_REG_FINGER_NUM = const(0x02) # Number of fingers currently touching the screen +_REG_TOUCHDATA = const(0x03) # 4 bytes: X[11:8], X[7:0], Y[11:8], Y[7:0] +_REG_CHIP_ID = const(0xA7) +# _REG_PROJ_ID = const(0xA8) +# _REG_FW_VERSION = const(0xA9) +# _REG_FACTORY_ID = const(0xAA) +_REG_SLEEP_MODE = const(0xE5) +_REG_LONG_PRESS_TICK = const(0xEB) +_REG_MOTION_MASK = const(0xEC) +_REG_IRQ_CTL = const(0xFA) +_REG_DIS_AUTOSLEEP = const(0xFE) + +MOTION_MASK_CONTINUOUS_LEFT_RIGHT = const(0b100) +MOTION_MASK_CONTINUOUS_UP_DOWN = const(0b010) +MOTION_MASK_DOUBLE_CLICK = const(0b001) + +IRQ_EN_TOUCH = const(0x40) +IRQ_EN_CHANGE = const(0x20) +IRQ_EN_MOTION = const(0x10) +IRQ_EN_LONGPRESS = const(0x01) + + +class CST8XX: + def __init__( + self, + bus, + address=0x15, + rst_pin=None, + irq_pin=None, + irq_handler=lambda pin: None, + irq_en=0x00, + motion_mask=0b000, + ): + self._bus = bus + self._address = address + self.rst = Pin(rst_pin, Pin.OUT) if isinstance(rst_pin, int) else rst_pin + self.reset() + if self._read(_REG_CHIP_ID)[0] not in ( + _CST816S_ID, + _CST816T_ID, + _CST816D_ID, + _CST820_ID, + _CST826_ID, + ): + raise ValueError("Error: CST8xx not detected.") + self.disable_autosleep() + + self.irq = Pin(irq_pin, Pin.IN, Pin.PULL_UP) if isinstance(irq_pin, int) else irq_pin + if self.irq: + self.irq.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler) + self.set_irq_ctl(irq_en, motion_mask) + + def touched(self): + return self._read(_REG_FINGER_NUM)[0] + + def get_point(self): + if self.touched() != 1: + return None + xy_data = self._read(_REG_TOUCHDATA, 4) + x = ((xy_data[0] & 0x0F) << 8) + xy_data[1] + y = ((xy_data[2] & 0x0F) << 8) + xy_data[3] + return (x, y) + + def get_gestures(self): + if not self.touched(): + return None + return self._read(_REG_GESTURE_ID)[0] + + def get_points(self): + raise NotImplementedError("get_points() not implemented (yet)") + + def reset(self): + if self.rst: + self.rst(0) + sleep_ms(1) + self.rst(1) + sleep_ms(50) + + def disable_autosleep(self, val=0x01): + self._write(_REG_DIS_AUTOSLEEP, val) + + def set_irq_ctl(self, irq_en, motion_mask=0b000): + self._write(_REG_IRQ_CTL, irq_en) + self._write(_REG_MOTION_MASK, motion_mask) + + def set_long_press_tick(self, val): + self._write(_REG_LONG_PRESS_TICK, val) + + def set_sleep_mode(self, val): + self._write(_REG_SLEEP_MODE, val) + + def _read(self, reg, length=1): + return self._bus.readfrom_mem(self._address, int(reg), length) + + def _write(self, reg, val): + self._bus.writeto_mem(self._address, int(reg), bytes([int(val)])) diff --git a/micropython/drivers/touch/cst8xx/manifest.py b/micropython/drivers/touch/cst8xx/manifest.py new file mode 100644 index 000000000..a801a1ba3 --- /dev/null +++ b/micropython/drivers/touch/cst8xx/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay cst8xx touch driver", + version="0.1.5", +) +module("cst8xx.py", opt=3) diff --git a/micropython/drivers/touch/ft6x36/ft6x36.py b/micropython/drivers/touch/ft6x36/ft6x36.py new file mode 100644 index 000000000..277e01135 --- /dev/null +++ b/micropython/drivers/touch/ft6x36/ft6x36.py @@ -0,0 +1,235 @@ +import time as _time +from micropython import const + + +_FT6x36_ADDR = const(0x38) + +_DEV_MODE_REG = const(0x00) +_GEST_ID_REG = const(0x01) +_TD_STATUS_REG = const(0x02) +_P1_XH_REG = const(0x03) +_P1_XL_REG = const(0x04) +_P1_YH_REG = const(0x05) +_P1_YL_REG = const(0x06) +_P1_WEIGHT_REG = const(0x07) +_P1_MISC_REG = const(0x08) +_P2_XH_REG = const(0x09) +_P2_XL_REG = const(0x0A) +_P2_YH_REG = const(0x0B) +_P2_YL_REG = const(0x0C) +_P2_WEIGHT_REG = const(0x0D) +_P2_MISC_REG = const(0x0E) +_TH_GROUP_REG = const(0x80) +_TH_DIFF_REG = const(0x85) +_CTRL_REG = const(0x86) +_TIMEENTERMONITOR_REG = const(0x87) +_PERIODACTIVE_REG = const(0x88) +_PERIODMONITOR_REG = const(0x89) +_RADIAN_VALUE_REG = const(0x91) +_OFFSET_LEFT_RIGHT_REG = const(0x92) +_OFFSET_UP_DOWN_REG = const(0x93) +_DISTANCE_LEFT_RIGHT_REG = const(0x94) +_DISTANCE_UP_DOWN_REG = const(0x95) +_DISTANCE_ZOOM_REG = const(0x96) +_LIB_VER_H_REG = const(0xA1) +_LIB_VER_L_REG = const(0xA2) +_CIPHER_REG = const(0xA3) +_G_MODE_REG = const(0xA4) +_PWR_MODE_REG = const(0xA5) +_FIRMID_REG = const(0xA6) +_FOCALTECH_ID_REG = const(0xA8) +_RELEASE_CODE_ID_REG = const(0xAF) +_STATE_REG = const(0xBC) + +GESTURE_NO_GESTRUE = const(0) +GESTURE_MOVE_UP = const(1) +GESTURE_MOVE_LEFT = const(2) +GESTURE_MOVE_DOWN = const(3) +GESTURE_MOVE_RIGHT = const(4) +GESTURE_ZOOM_IN = const(5) +GESTURE_ZOOM_OUT = const(6) + +POLLING_MODE = const(0x00) +TRIGGER_MODE = const(0x01) + + +class FT6x36: + """ + FocalTech Self-Capacitive Touch Panel Controller module + + :param I2C i2c: The board I2C object + :param int address: The I2C address + :param Pin rst: The reset Pin object + """ + + def __init__(self, i2c, address: int = _FT6x36_ADDR, rst=None) -> None: + self._i2c = i2c + self._address = address + self._rst = rst + self._read_buffer = bytearray(4) + self._write_buffer = bytearray(1) + + def get_gesture(self) -> int: + """ + Get Gesture events. Should be a value of: + + * ``GESTURE_NO_GESTRUE``: No Gesture + * ``GESTURE_MOVE_UP``: Move Up + * ``GESTURE_MOVE_RIGHT``: Move Right + * ``GESTURE_MOVE_DOWN``: Move Down + * ``GESTURE_MOVE_LEFT``: Move Left + * ``GESTURE_ZOOM_IN``: Zoom In + * ``GESTURE_ZOOM_OUT``: Zoom Out + """ + gesture = self._i2c.readfrom_mem(self._address, _GEST_ID_REG, 1)[0] + if 0x10 == gesture: + return GESTURE_MOVE_UP + elif 0x14 == gesture: + return GESTURE_MOVE_RIGHT + elif 0x18 == gesture: + return GESTURE_MOVE_DOWN + elif 0x1C == gesture: + return GESTURE_MOVE_LEFT + elif 0x48 == gesture: + return GESTURE_ZOOM_IN + elif 0x49 == gesture: + return GESTURE_ZOOM_OUT + else: + return GESTURE_NO_GESTRUE + + def get_positions(self) -> list: + positions = [] + num_points = self._i2c.readfrom_mem(self._address, _TD_STATUS_REG, 1)[0] & 0x0F + if num_points > 0: + positions.append(self._get_p1()) + if num_points > 1: + positions.append(self._get_p2()) + return positions + + @property + def theshold(self) -> int: + """ + Threshold for touch detection. + """ + return self._i2c.readfrom_mem(self._address, _TH_GROUP_REG, 1)[0] + + @theshold.setter + def theshold(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _TH_GROUP_REG, self._write_buffer) + + @property + def monitor_time(self) -> int: + """ + The time period of switching from Active mode to Monitor mode when there is no touching. + """ + return self._i2c.readfrom_mem(self._address, _TIMEENTERMONITOR_REG, 1)[0] + + @monitor_time.setter + def monitor_time(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _TIMEENTERMONITOR_REG, self._write_buffer) + + @property + def active_period(self) -> int: + """ + Report rate in Active mode. + """ + return self._i2c.readfrom_mem(self._address, _PERIODACTIVE_REG, 1)[0] + + @active_period.setter + def active_period(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _PERIODACTIVE_REG, self._write_buffer) + + @property + def monitor_period(self) -> int: + """ + Report rate in Monitor mode. + """ + return self._i2c.readfrom_mem(self._address, _PERIODMONITOR_REG, 1)[0] + + @monitor_period.setter + def monitor_period(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _PERIODMONITOR_REG, self._write_buffer) + + @property + def library_version(self): + """ + Library Version info. + """ + buffer = self._i2c.readfrom_mem(self._address, _LIB_VER_H_REG, 2) + return buffer[0] << 8 | buffer[1] + + @property + def firmware_version(self) -> int: + """ + Firmware Version. + """ + return self._i2c.readfrom_mem(self._address, _FIRMID_REG, 1) + + @property + def interrupt_mode(self) -> int: + """ + Interrupt mode for valid data. Should be a value of: + + * ``POLLING_MODE``: Interrupt Polling mode + * ``TRIGGER_MODE``: Interrupt Trigger mode + """ + return self._i2c.readfrom_mem(self._address, _G_MODE_REG, 1)[0] + + @interrupt_mode.setter + def interrupt_mode(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _G_MODE_REG, self._write_buffer) + + @property + def power_mode(self) -> int: + """ + Current power mode which system is in. + """ + return self._i2c.readfrom_mem(self._address, _PWR_MODE_REG, 1)[0] + + @power_mode.setter + def power_mode(self, val: int) -> None: + self._write_buffer[0] = val + self._i2c.writeto_mem(self._address, _PWR_MODE_REG, self._write_buffer) + + @property + def vendor_id(self) -> None: + """ + Chip Selecting. + """ + return self._i2c.readfrom_mem(self._address, _CIPHER_REG, 1)[0] + + @property + def panel_id(self) -> None: + """ + FocalTech's Panel ID. + """ + return self._i2c.readfrom_mem(self._address, _FOCALTECH_ID_REG, 1)[0] + + def reset(self) -> None: + """ + Hardware reset touch screen. + """ + if self._rst is None: + return + self._rst.off() + _time.sleep_ms(1) + self._rst.on() + + def _get_p1(self) -> tuple: + self._i2c.readfrom_mem_into(self._address, _P1_XH_REG, self._read_buffer) + return ( + (self._read_buffer[0] << 8 | self._read_buffer[1]) & 0x0FFF, + (self._read_buffer[2] << 8 | self._read_buffer[3]) & 0x0FFF, + ) + + def _get_p2(self) -> tuple: + self._i2c.readfrom_mem_into(self._address, _P2_XH_REG, self._read_buffer) + return ( + (self._read_buffer[0] << 8 | self._read_buffer[1]) & 0x0FFF, + (self._read_buffer[2] << 8 | self._read_buffer[3]) & 0x0FFF, + ) diff --git a/micropython/drivers/touch/ft6x36/manifest.py b/micropython/drivers/touch/ft6x36/manifest.py new file mode 100644 index 000000000..445b7811f --- /dev/null +++ b/micropython/drivers/touch/ft6x36/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay ft6x36 touch driver", + version="0.1.5", +) +module("ft6x36.py", opt=3) diff --git a/micropython/drivers/touch/gt911/gt911.py b/micropython/drivers/touch/gt911/gt911.py new file mode 100644 index 000000000..664ee8161 --- /dev/null +++ b/micropython/drivers/touch/gt911/gt911.py @@ -0,0 +1,137 @@ +""" +This file is part of the OpenMV project. + +Copyright (c) 2023 Ibrahim Abdelkader +Copyright (c) 2023 Kwabena W. Agyeman + +This work is licensed under the MIT license, see the file LICENSE for details. + +GT911 5-Point Capacitive Touch Controller driver for MicroPython. + +Basic polling mode example usage: + +import time +from gt911 import GT911 +from machine import I2C + +# Note use pin numbers or names not Pin objects because the +# driver needs to change pin directions to reset the controller. +touch = GT911(I2C(1, freq=400_000), reset_pin="P1", irq_pin="P2", touch_points=5) + +while True: + n, points = touch.read_points() + for i in range(0, n): + print(f"id {points[i][3]} x {points[i][0]} y {points[i][1]} size {points[i][2]}") + time.sleep_ms(100) +""" + +from time import sleep_ms +from array import array +from machine import Pin +from micropython import const + +_DEFAULT_ADDR = const(0x5D) + +_COMMAND = const(0x8040) +_REFRESH_RATE = const(0x8056) +_RESOLUTION_X = const(0x8048) +_RESOLUTION_Y = const(0x804A) +_TOUCH_POINTS = const(0x804C) +_MODULE_SWITCH1 = const(0x804D) +_CONFIG_CHKSUM = const(0x80FF) +_CONFIG_FRESH = const(0x8100) +_POINT_DATA_START = const(0x8150) +_DATA_BUFFER = const(0x814E) + + +class GT911: + def __init__( + self, + bus, + reset_pin, + irq_pin, + address=_DEFAULT_ADDR, + width=800, + height=480, + touch_points=1, + reverse_x=False, + reverse_y=False, + reverse_axis=True, + sito=True, + refresh_rate=240, + touch_callback=None, + ): + self.bus = bus + self.address = address + self.touch_callback = touch_callback + self.rst_pin = Pin(reset_pin, Pin.OUT_PP, value=0) + self.irq_pin = None + self.irq_pin_label = irq_pin + + # Reset the touch panel controller. + self.reset() + + # Write and update the config. + self._write_reg(_RESOLUTION_X, width, 2) + self._write_reg(_RESOLUTION_Y, height, 2) + self._write_reg(_TOUCH_POINTS, touch_points) + self._write_reg( + _MODULE_SWITCH1, + (int(reverse_y) << 7) + | (int(reverse_x) << 6) + | (int(reverse_axis) << 3) + | (int(sito) << 2) + | 0x01, + ) + self._write_reg(_REFRESH_RATE, (1000 * 1000) // (refresh_rate * 250)) + self._write_reg(_COMMAND, 0x00) + self._update_config() + + # Allocate scratch buffer. + self.points_data = [array("H", [0, 0, 0, 0]) for x in range(5)] + + def _read_reg(self, reg, size=1, buf=None): + if buf is not None: + self.bus.readfrom_mem_into(self.address, reg, buf, addrsize=16) + else: + return self.bus.readfrom_mem(self.address, reg, size, addrsize=16) + + def _write_reg(self, reg, val, size=1): + buf = bytes([val & 0xFF]) if size == 1 else bytes([val & 0xFF, val >> 8]) + self.bus.writeto_mem(self.address, reg, buf, addrsize=16) + + def _update_config(self): + # Read current config + chksum = ~sum(self._read_reg(0x8047, 184)) + 1 + # Calculate checksum + self._write_reg(_CONFIG_CHKSUM, chksum) + # Update the config + self._write_reg(_CONFIG_FRESH, 0x01) + + def read_id(self): + return self._read_reg(0x8140, 4) + + def read_points(self): + status = self._read_reg(_DATA_BUFFER)[0] + n_points = status & 0x0F + if status & 0x80: + for i in range(n_points): + self._read_reg(_POINT_DATA_START + i * 8, buf=self.points_data[i]) + # We read an extra reserved byte, shift track ID to fix it. + self.points_data[i][-1] = self.points_data[i][-1] >> 8 + self._write_reg(_DATA_BUFFER, 0) + return n_points, self.points_data + + def reset(self): + if self.irq_pin is not None: + self.irq_pin.irq(handler=None) + self.rst_pin(0) + sleep_ms(10) + self.irq_pin = Pin(self.irq_pin_label, Pin.OUT_PP, value=0) + sleep_ms(50) + self.rst_pin(1) + # Note must wait for at least 50ms before switching the IRQ pin to input. + sleep_ms(100) + self.irq_pin = Pin(self.irq_pin_label, Pin.IN, Pin.PULL_UP) + if self.touch_callback is not None: + self.irq_pin.irq(handler=self.touch_callback, trigger=Pin.IRQ_FALLING, hard=False) diff --git a/micropython/drivers/touch/gt911/manifest.py b/micropython/drivers/touch/gt911/manifest.py new file mode 100644 index 000000000..0c3b7e0cc --- /dev/null +++ b/micropython/drivers/touch/gt911/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay gt911 touch driver", + version="0.1.5", +) +module("gt911.py", opt=3) diff --git a/micropython/drivers/touch/xpt2046/manifest.py b/micropython/drivers/touch/xpt2046/manifest.py new file mode 100644 index 000000000..52d453c18 --- /dev/null +++ b/micropython/drivers/touch/xpt2046/manifest.py @@ -0,0 +1,5 @@ +metadata( + description="PyDisplay xpt2046 touch driver", + version="0.1.5", +) +module("xpt2046.py", opt=3) diff --git a/micropython/drivers/touch/xpt2046/xpt2046.py b/micropython/drivers/touch/xpt2046/xpt2046.py new file mode 100644 index 000000000..046c3a15a --- /dev/null +++ b/micropython/drivers/touch/xpt2046/xpt2046.py @@ -0,0 +1,99 @@ +"""XPT2046 Touch module Micropython driver. + +Made by Lesept (May 2023) +Inspired by https://github.com/rdagger/micropython-ili9341 + +""" + +from time import sleep +from micropython import const + + +class Touch(object): + """Serial interface for XPT2046 Touch Screen Controller.""" + + GET_X = const(0b11010000) # X position + GET_Y = const(0b10010000) # Y position + + def __init__(self, spi, cs, int_pin=None, int_handler=None): + self.spi = spi + self.cs = cs + self.cs.init(self.cs.OUT, value=1) + self.cal = False + self.rx_buf = bytearray(3) # Receive buffer + self.tx_buf = bytearray(3) # Transmit buffer + if int_pin is not None: + self.int_pin = int_pin + self.int_pin.init(int_pin.IN) + if int_handler is not None: + self.int_handler = int_handler + self.int_locked = False + int_pin.irq(trigger=int_pin.IRQ_FALLING | int_pin.IRQ_RISING, handler=self.int_press) + + def set_orientation(self, orientation): + self.orientation = orientation + + def int_press(self, pin): + """Send X,Y values to passed interrupt handler.""" + if not pin.value() and not self.int_locked: + self.int_locked = True # Lock Interrupt + x, y = self.get_touch() + self.int_handler(x, y) + sleep(0.1) # Debounce falling edge + elif pin.value() and self.int_locked: + sleep(0.1) # Debounce rising edge + self.int_locked = False # Unlock interrupt + + def calibrate(self, xmin, xmax, ymin, ymax, width, height, orientation): + self.xmin = xmin + self.xmax = xmax + self.ymin = ymin + self.ymax = ymax + self.orientation = orientation + if self.orientation % 2 == 0: + self.width = height + self.height = width + else: + self.width = width + self.height = height + self.cal = True + + def raw_touch(self): + x = self.send_command(self.GET_X) + y = self.send_command(self.GET_Y) + return x, y + + def map_value(self, v, vmin, vmax, maxv): + # Map x or y value to display + return int((v - vmin) / (vmax - vmin) * maxv) + + def get_touch(self, clip=False): + if not self.cal: + print("Touch is not calibrated: use raw_touch or calibrate") + return 0, 0 + xraw, yraw = self.raw_touch() + x = self.map_value(xraw, self.xmin, self.xmax, self.width) + y = self.map_value(yraw, self.ymin, self.ymax, self.height) + + # Clip values + if clip: + x = max(x, 0) + y = max(y, 0) + x = min(x, self.width) + y = min(y, self.height) + + if self.orientation % 2 == 1: + return y, self.width - x + else: + return x, y + + def is_touched(self): + return self.int_pin.value() == 0 + + def send_command(self, command): + # Write command to XPT2046 + self.tx_buf[0] = command + self.cs(0) + self.spi.write_readinto(self.tx_buf, self.rx_buf) + self.cs(1) + return (self.rx_buf[1] << 4) | (self.rx_buf[2] >> 4) diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index c0395b2f9..e65abc96b 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -1,6 +1,7 @@ """ Board configuration for PyGame. """ + from displaysys.pgdisplay import PGDisplay as DTDisplay, poll from eventsys import device import sys diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py index 509265bda..ba2fc6646 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -1,6 +1,7 @@ """ Board configuration for PyScript. """ + from displaysys.psdisplay import PSDisplay, PSDevices from eventsys import device diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index de8ab137a..0518d4a56 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -1,6 +1,7 @@ """ Combination board configuration for desktop, pyscript and jupyter notebook platforms. """ + from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll from eventsys import device import sys diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py index 91abb9857..4d8e36011 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -287,9 +287,9 @@ def fill_rect(framebuf, x, y, width, height, color): rgb565_color_int = int.from_bytes(rgb565_color, "little") arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) for _y in range(y, y + height): - arr[ - _y * framebuf._stride + x : _y * framebuf._stride + x + width - ] = rgb565_color_int + arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = ( + rgb565_color_int + ) else: for _y in range(y, y + height): offset = _y * framebuf._stride From 0353cc12248090832970eca13ebe6b09beb2c9f9 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 01:17:10 -0600 Subject: [PATCH 25/35] pydisplay: Fix format in _framebuf.py. Signed-off-by: Brad Barnett --- micropython/pydisplay/graphics/graphics/_framebuf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/micropython/pydisplay/graphics/graphics/_framebuf.py b/micropython/pydisplay/graphics/graphics/_framebuf.py index 4d8e36011..5e333cb2f 100644 --- a/micropython/pydisplay/graphics/graphics/_framebuf.py +++ b/micropython/pydisplay/graphics/graphics/_framebuf.py @@ -282,17 +282,16 @@ def fill_rect(framebuf, x, y, width, height, color): width = framebuf.width - x if y + height > framebuf.height: height = framebuf.height - y + stride = framebuf._stride rgb565_color = (color & 0xFFFF).to_bytes(2, "little") if np: rgb565_color_int = int.from_bytes(rgb565_color, "little") arr = np.frombuffer(framebuf._buffer, dtype=np.uint16) for _y in range(y, y + height): - arr[_y * framebuf._stride + x : _y * framebuf._stride + x + width] = ( - rgb565_color_int - ) + arr[_y * stride + x : _y * stride + x + width] = rgb565_color_int else: for _y in range(y, y + height): - offset = _y * framebuf._stride + offset = _y * stride for _x in range(x, x + width): index = (offset + _x) * 2 framebuf.buffer[index : index + 2] = rgb565_color From 8be9070af41e44ee1b28d403808d778d03d005b9 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 15:42:40 -0600 Subject: [PATCH 26/35] pydisplay: Testing scripts. Signed-off-by: Brad Barnett --- micropython/drivers/display/gc9a01/manifest.py | 2 +- micropython/drivers/display/gc9d01/manifest.py | 2 +- micropython/drivers/display/hx8357/manifest.py | 2 +- micropython/drivers/display/ili9163/manifest.py | 2 +- micropython/drivers/display/ili9341/manifest.py | 2 +- micropython/drivers/display/ili9488/manifest.py | 2 +- micropython/drivers/display/st7701/manifest.py | 2 +- micropython/drivers/display/st7735/manifest.py | 2 +- micropython/drivers/display/st7735r/manifest.py | 2 +- micropython/drivers/display/st7735r_1/manifest.py | 2 +- micropython/drivers/display/st7789/manifest.py | 2 +- micropython/drivers/display/st7789vw/manifest.py | 2 +- micropython/drivers/display/st7796/manifest.py | 2 +- micropython/drivers/display/st7796_test/manifest.py | 2 +- micropython/drivers/touch/chsc6x/manifest.py | 2 +- micropython/drivers/touch/cst226/manifest.py | 2 +- micropython/drivers/touch/cst8xx/manifest.py | 2 +- micropython/drivers/touch/ft6x36/manifest.py | 2 +- micropython/drivers/touch/gt911/manifest.py | 2 +- micropython/drivers/touch/xpt2046/manifest.py | 2 +- micropython/pydisplay/displaybuf/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-busdisplay/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-fbdisplay/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-jndisplay/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-pgdisplay/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-psdisplay/manifest.py | 3 +-- .../pydisplay/displaysys/displaysys-sdldisplay/manifest.py | 3 +-- micropython/pydisplay/displaysys/displaysys/manifest.py | 3 +-- micropython/pydisplay/eventsys/manifest.py | 3 +-- micropython/pydisplay/graphics/manifest.py | 3 +-- micropython/pydisplay/palettes/manifest.py | 3 +-- micropython/pydisplay/pydisplay-board_config/manifest.py | 3 +-- micropython/pydisplay/pydisplay/manifest.py | 3 +-- micropython/pydisplay/timer/manifest.py | 3 +-- 34 files changed, 34 insertions(+), 48 deletions(-) diff --git a/micropython/drivers/display/gc9a01/manifest.py b/micropython/drivers/display/gc9a01/manifest.py index a6cf92171..e4006ce6d 100644 --- a/micropython/drivers/display/gc9a01/manifest.py +++ b/micropython/drivers/display/gc9a01/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gc9a01 display driver", - version="0.1.5", + version="0.1.6", ) module("gc9a01.py", opt=3) diff --git a/micropython/drivers/display/gc9d01/manifest.py b/micropython/drivers/display/gc9d01/manifest.py index ee25420d2..6b617bf3b 100644 --- a/micropython/drivers/display/gc9d01/manifest.py +++ b/micropython/drivers/display/gc9d01/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gc9d01 display driver", - version="0.1.5", + version="0.1.6", ) module("gc9d01.py", opt=3) diff --git a/micropython/drivers/display/hx8357/manifest.py b/micropython/drivers/display/hx8357/manifest.py index a4c069229..44375e1e0 100644 --- a/micropython/drivers/display/hx8357/manifest.py +++ b/micropython/drivers/display/hx8357/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay hx8357 display driver", - version="0.1.5", + version="0.1.6", ) module("hx8357.py", opt=3) diff --git a/micropython/drivers/display/ili9163/manifest.py b/micropython/drivers/display/ili9163/manifest.py index 2c7d18648..18409d499 100644 --- a/micropython/drivers/display/ili9163/manifest.py +++ b/micropython/drivers/display/ili9163/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9163 display driver", - version="0.1.5", + version="0.1.6", ) module("ili9163.py", opt=3) diff --git a/micropython/drivers/display/ili9341/manifest.py b/micropython/drivers/display/ili9341/manifest.py index 82a8828ed..9281a8802 100644 --- a/micropython/drivers/display/ili9341/manifest.py +++ b/micropython/drivers/display/ili9341/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9341 display driver", - version="0.1.5", + version="0.1.6", ) module("ili9341.py", opt=3) diff --git a/micropython/drivers/display/ili9488/manifest.py b/micropython/drivers/display/ili9488/manifest.py index 488c9b19b..e4314631a 100644 --- a/micropython/drivers/display/ili9488/manifest.py +++ b/micropython/drivers/display/ili9488/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9488 display driver", - version="0.1.5", + version="0.1.6", ) module("ili9488.py", opt=3) diff --git a/micropython/drivers/display/st7701/manifest.py b/micropython/drivers/display/st7701/manifest.py index fc3fbf8dc..8dbc596f3 100644 --- a/micropython/drivers/display/st7701/manifest.py +++ b/micropython/drivers/display/st7701/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7701 display driver", - version="0.1.5", + version="0.1.6", ) module("st7701.py", opt=3) diff --git a/micropython/drivers/display/st7735/manifest.py b/micropython/drivers/display/st7735/manifest.py index 7776b952a..5291dd56d 100644 --- a/micropython/drivers/display/st7735/manifest.py +++ b/micropython/drivers/display/st7735/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735 display driver", - version="0.1.5", + version="0.1.6", ) module("st7735.py", opt=3) diff --git a/micropython/drivers/display/st7735r/manifest.py b/micropython/drivers/display/st7735r/manifest.py index 7a0ee55f2..5fcb97d1e 100644 --- a/micropython/drivers/display/st7735r/manifest.py +++ b/micropython/drivers/display/st7735r/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735r display driver", - version="0.1.5", + version="0.1.6", ) module("st7735r.py", opt=3) diff --git a/micropython/drivers/display/st7735r_1/manifest.py b/micropython/drivers/display/st7735r_1/manifest.py index a80c1e837..21ca8ad7f 100644 --- a/micropython/drivers/display/st7735r_1/manifest.py +++ b/micropython/drivers/display/st7735r_1/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735r_1 display driver", - version="0.1.5", + version="0.1.6", ) module("st7735r_1.py", opt=3) diff --git a/micropython/drivers/display/st7789/manifest.py b/micropython/drivers/display/st7789/manifest.py index 18477d310..47bafc2ae 100644 --- a/micropython/drivers/display/st7789/manifest.py +++ b/micropython/drivers/display/st7789/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7789 display driver", - version="0.1.5", + version="0.1.6", ) module("st7789.py", opt=3) diff --git a/micropython/drivers/display/st7789vw/manifest.py b/micropython/drivers/display/st7789vw/manifest.py index 255f67da9..36157aad5 100644 --- a/micropython/drivers/display/st7789vw/manifest.py +++ b/micropython/drivers/display/st7789vw/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7789vw display driver", - version="0.1.5", + version="0.1.6", ) module("st7789vw.py", opt=3) diff --git a/micropython/drivers/display/st7796/manifest.py b/micropython/drivers/display/st7796/manifest.py index 38346a387..e4240db8e 100644 --- a/micropython/drivers/display/st7796/manifest.py +++ b/micropython/drivers/display/st7796/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7796 display driver", - version="0.1.5", + version="0.1.6", ) module("st7796.py", opt=3) diff --git a/micropython/drivers/display/st7796_test/manifest.py b/micropython/drivers/display/st7796_test/manifest.py index d3923a70e..8c8b44d01 100644 --- a/micropython/drivers/display/st7796_test/manifest.py +++ b/micropython/drivers/display/st7796_test/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7796_test display driver", - version="0.1.5", + version="0.1.6", ) module("st7796_test.py", opt=3) diff --git a/micropython/drivers/touch/chsc6x/manifest.py b/micropython/drivers/touch/chsc6x/manifest.py index 2e19b2682..a1e221cf9 100644 --- a/micropython/drivers/touch/chsc6x/manifest.py +++ b/micropython/drivers/touch/chsc6x/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay chsc6x touch driver", - version="0.1.5", + version="0.1.6", ) module("chsc6x.py", opt=3) diff --git a/micropython/drivers/touch/cst226/manifest.py b/micropython/drivers/touch/cst226/manifest.py index ae410fd5d..e398cc534 100644 --- a/micropython/drivers/touch/cst226/manifest.py +++ b/micropython/drivers/touch/cst226/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay cst226 touch driver", - version="0.1.5", + version="0.1.6", ) module("cst226.py", opt=3) diff --git a/micropython/drivers/touch/cst8xx/manifest.py b/micropython/drivers/touch/cst8xx/manifest.py index a801a1ba3..52331956d 100644 --- a/micropython/drivers/touch/cst8xx/manifest.py +++ b/micropython/drivers/touch/cst8xx/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay cst8xx touch driver", - version="0.1.5", + version="0.1.6", ) module("cst8xx.py", opt=3) diff --git a/micropython/drivers/touch/ft6x36/manifest.py b/micropython/drivers/touch/ft6x36/manifest.py index 445b7811f..3bfb5175a 100644 --- a/micropython/drivers/touch/ft6x36/manifest.py +++ b/micropython/drivers/touch/ft6x36/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ft6x36 touch driver", - version="0.1.5", + version="0.1.6", ) module("ft6x36.py", opt=3) diff --git a/micropython/drivers/touch/gt911/manifest.py b/micropython/drivers/touch/gt911/manifest.py index 0c3b7e0cc..55c637a1c 100644 --- a/micropython/drivers/touch/gt911/manifest.py +++ b/micropython/drivers/touch/gt911/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gt911 touch driver", - version="0.1.5", + version="0.1.6", ) module("gt911.py", opt=3) diff --git a/micropython/drivers/touch/xpt2046/manifest.py b/micropython/drivers/touch/xpt2046/manifest.py index 52d453c18..842d0a9d8 100644 --- a/micropython/drivers/touch/xpt2046/manifest.py +++ b/micropython/drivers/touch/xpt2046/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay xpt2046 touch driver", - version="0.1.5", + version="0.1.6", ) module("xpt2046.py", opt=3) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index f398ba870..66176820d 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay displaybuf", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 61328e6f8..1bbe03a50 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-busdisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 439bd1ecd..a70e43125 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-fbdisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index bccad39a4..d88cda136 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-jndisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 70b02aca3..3d46ba2a4 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-pgdisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index aa3256713..920c5173d 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-psdisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 5f03d7a44..0d761d8cf 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay displaysys-sdldisplay", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index eaa3ad388..c0bc32887 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay displaysys", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 64771dfc2..af53843ee 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay eventsys", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 51de8ee86..fdd60b061 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay graphics", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index a1632ee8e..5eabfdbf2 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay palettes", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("palettes") diff --git a/micropython/pydisplay/pydisplay-board_config/manifest.py b/micropython/pydisplay/pydisplay-board_config/manifest.py index 3c595d608..bdcf91b92 100644 --- a/micropython/pydisplay/pydisplay-board_config/manifest.py +++ b/micropython/pydisplay/pydisplay-board_config/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay example board_config", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) module("board_config.py", opt=3) diff --git a/micropython/pydisplay/pydisplay/manifest.py b/micropython/pydisplay/pydisplay/manifest.py index 3ba5765a7..fe9a8493e 100644 --- a/micropython/pydisplay/pydisplay/manifest.py +++ b/micropython/pydisplay/pydisplay/manifest.py @@ -1,7 +1,6 @@ metadata( description="PyDisplay bundle", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) require("displaybuf") require("eventsys") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index da6181983..d6441f266 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,6 +1,5 @@ metadata( description="PyDisplay timer", - version="0.1.5", - author="Brad Barnett", + version="0.1.6", ) package("timer") From ad91d426810f356ed4b1510eda2223e09e864cd5 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 21:40:06 -0600 Subject: [PATCH 27/35] pydisplay: Testing packages. Signed-off-by: Brad Barnett --- .../pydisplay/pydisplay-bundle/manifest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 micropython/pydisplay/pydisplay-bundle/manifest.py diff --git a/micropython/pydisplay/pydisplay-bundle/manifest.py b/micropython/pydisplay/pydisplay-bundle/manifest.py new file mode 100644 index 000000000..fe9a8493e --- /dev/null +++ b/micropython/pydisplay/pydisplay-bundle/manifest.py @@ -0,0 +1,16 @@ +metadata( + description="PyDisplay bundle", + version="0.1.6", +) +require("displaybuf") +require("eventsys") +require("graphics") +require("palettes") +require("timer") +require("displaysys") +require("displaysys-busdisplay") +require("displaysys-fbdisplay") +require("displaysys-jndisplay") +require("displaysys-pgdisplay") +require("displaysys-psdisplay") +require("displaysys-sdldisplay") From cf7a6fc700c24c409cad4dd8f7db4d5592395a64 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 21:42:06 -0600 Subject: [PATCH 28/35] pydisplay: Remove old bundle. Signed-off-by: Brad Barnett --- micropython/pydisplay/pydisplay/manifest.py | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 micropython/pydisplay/pydisplay/manifest.py diff --git a/micropython/pydisplay/pydisplay/manifest.py b/micropython/pydisplay/pydisplay/manifest.py deleted file mode 100644 index fe9a8493e..000000000 --- a/micropython/pydisplay/pydisplay/manifest.py +++ /dev/null @@ -1,16 +0,0 @@ -metadata( - description="PyDisplay bundle", - version="0.1.6", -) -require("displaybuf") -require("eventsys") -require("graphics") -require("palettes") -require("timer") -require("displaysys") -require("displaysys-busdisplay") -require("displaysys-fbdisplay") -require("displaysys-jndisplay") -require("displaysys-pgdisplay") -require("displaysys-psdisplay") -require("displaysys-sdldisplay") From 66f596f8b91ba6242a7789f83b051abcc1c6cea1 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Sun, 24 Nov 2024 23:13:29 -0600 Subject: [PATCH 29/35] pydisplay: Remove pydisplay-board_config. Signed-off-by: Brad Barnett --- .../pydisplay-board_config/board_config.py | 70 ------------------- .../pydisplay-board_config/manifest.py | 5 -- 2 files changed, 75 deletions(-) delete mode 100644 micropython/pydisplay/pydisplay-board_config/board_config.py delete mode 100644 micropython/pydisplay/pydisplay-board_config/manifest.py diff --git a/micropython/pydisplay/pydisplay-board_config/board_config.py b/micropython/pydisplay/pydisplay-board_config/board_config.py deleted file mode 100644 index 1faf00647..000000000 --- a/micropython/pydisplay/pydisplay-board_config/board_config.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -Combination board configuration for desktop, pyscript and jupyter notebook platforms. -""" - -width = 320 -height = 480 -rotation = 0 -scale = 2.0 - -_ps = _jn = False -try: - import pyscript - - _ps = True -except ImportError: - try: - get_ipython() - _jn = True - except NameError: - pass - -if _ps: - from displaysys.psdisplay import PSDisplay, PSDevices - from eventsys import device - - display_drv = PSDisplay("display_canvas", width, height) - - broker = device.Broker() - - touch_drv = PSDevices("display_canvas") - - touch_dev = broker.create_device( - type=device.Types.TOUCH, - read=touch_drv.get_mouse_pos, - data=display_drv, - ) -elif _jn: - from displaysys.jndisplay import JNDisplay - from eventsys import device - - broker = device.Broker() - - display_drv = JNDisplay(width, height) -else: - from eventsys import device - import sys - - try: - from displaysys.pgdisplay import PGDisplay as DTDisplay, poll - except ImportError: - from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll - - display_drv = DTDisplay( - width=width, - height=height, - rotation=rotation, - title=f"{sys.implementation.name} on {sys.platform}", - scale=scale, - ) - - broker = device.Broker() - - events_dev = broker.create_device( - type=device.Types.QUEUE, - read=poll, - data=display_drv, - # data2=Events.filter, - ) - -display_drv.fill(0) diff --git a/micropython/pydisplay/pydisplay-board_config/manifest.py b/micropython/pydisplay/pydisplay-board_config/manifest.py deleted file mode 100644 index bdcf91b92..000000000 --- a/micropython/pydisplay/pydisplay-board_config/manifest.py +++ /dev/null @@ -1,5 +0,0 @@ -metadata( - description="PyDisplay example board_config", - version="0.1.6", -) -module("board_config.py", opt=3) From 5f9bddd568f5b5645a3567f152f52ec576fa9b05 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Mon, 25 Nov 2024 01:47:57 -0600 Subject: [PATCH 30/35] cd ~/tmp Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-busdisplay/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-fbdisplay/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-jndisplay/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-pgdisplay/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-psdisplay/manifest.py | 3 +++ .../pydisplay/displaysys/displaysys-sdldisplay/manifest.py | 3 +++ micropython/pydisplay/displaysys/displaysys/manifest.py | 3 +++ micropython/pydisplay/eventsys/manifest.py | 3 +++ micropython/pydisplay/graphics/manifest.py | 3 +++ micropython/pydisplay/palettes/manifest.py | 3 +++ micropython/pydisplay/pydisplay-bundle/manifest.py | 3 +++ micropython/pydisplay/timer/manifest.py | 3 +++ 13 files changed, 39 insertions(+) diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index 66176820d..b55a42e3d 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay displaybuf", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 1bbe03a50..e3f5142ff 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-busdisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-busdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index a70e43125..45c72e20d 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-fbdisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-fbdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index d88cda136..28cba7fac 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-jndisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-jndisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 3d46ba2a4..52235530b 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-pgdisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-pgdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 920c5173d..97e1bf290 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-psdisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-psdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index 0d761d8cf..a66706625 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay displaysys-sdldisplay", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys-sdldisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index c0bc32887..9a0bb2196 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay displaysys", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index af53843ee..a23471136 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay eventsys", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index fdd60b061..d88f3578a 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay graphics", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 5eabfdbf2..557b2800c 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay palettes", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="palettes", ) package("palettes") diff --git a/micropython/pydisplay/pydisplay-bundle/manifest.py b/micropython/pydisplay/pydisplay-bundle/manifest.py index fe9a8493e..5f9387bec 100644 --- a/micropython/pydisplay/pydisplay-bundle/manifest.py +++ b/micropython/pydisplay/pydisplay-bundle/manifest.py @@ -1,6 +1,9 @@ metadata( description="PyDisplay bundle", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="", ) require("displaybuf") require("eventsys") diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index d6441f266..3e329bed2 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -1,5 +1,8 @@ metadata( description="PyDisplay timer", version="0.1.6", + author="Brad Barnett ", + license="MIT", + pypi_publish="timer", ) package("timer") From 726ad2c9ac175234c0577fff5355f9bc24f903df Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Mon, 25 Nov 2024 03:12:48 -0600 Subject: [PATCH 31/35] pydisplay: Package testing. --- micropython/pydisplay/displaybuf/README.md | 151 ++++++++++++++++++ micropython/pydisplay/displaybuf/manifest.py | 2 +- .../displaysys-busdisplay/manifest.py | 2 +- .../displaysys-fbdisplay/manifest.py | 2 +- .../displaysys-jndisplay/manifest.py | 2 +- .../displaysys-pgdisplay/manifest.py | 2 +- .../displaysys-psdisplay/manifest.py | 2 +- .../displaysys-sdldisplay/manifest.py | 2 +- .../displaysys/displaysys/README.md | 151 ++++++++++++++++++ .../displaysys/displaysys/manifest.py | 2 +- micropython/pydisplay/eventsys/README.md | 151 ++++++++++++++++++ micropython/pydisplay/eventsys/manifest.py | 2 +- micropython/pydisplay/graphics/README.md | 151 ++++++++++++++++++ micropython/pydisplay/graphics/manifest.py | 2 +- micropython/pydisplay/palettes/README.md | 151 ++++++++++++++++++ micropython/pydisplay/palettes/manifest.py | 2 +- .../pydisplay/pydisplay-bundle/manifest.py | 2 +- micropython/pydisplay/timer/README.md | 151 ++++++++++++++++++ micropython/pydisplay/timer/manifest.py | 2 +- 19 files changed, 919 insertions(+), 13 deletions(-) create mode 100644 micropython/pydisplay/displaybuf/README.md create mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/README.md create mode 100644 micropython/pydisplay/eventsys/README.md create mode 100644 micropython/pydisplay/graphics/README.md create mode 100644 micropython/pydisplay/palettes/README.md create mode 100644 micropython/pydisplay/timer/README.md diff --git a/micropython/pydisplay/displaybuf/README.md b/micropython/pydisplay/displaybuf/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/displaybuf/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index b55a42e3d..b97ac8973 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaybuf", + pypi_publish="pydisplay-displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index e3f5142ff..56c4aba31 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-busdisplay", + pypi_publish="pydisplay-displaysys-busdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index 45c72e20d..e84612008 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-fbdisplay", + pypi_publish="pydisplay-displaysys-fbdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 28cba7fac..509265987 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-jndisplay", + pypi_publish="pydisplay-displaysys-jndisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 52235530b..26e651560 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-pgdisplay", + pypi_publish="pydisplay-displaysys-pgdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 97e1bf290..6aa7db725 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-psdisplay", + pypi_publish="pydisplay-displaysys-psdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index a66706625..d91968579 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys-sdldisplay", + pypi_publish="pydisplay-displaysys-sdldisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/README.md b/micropython/pydisplay/displaysys/displaysys/displaysys/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index 9a0bb2196..b03c5f650 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="displaysys", + pypi_publish="pydisplay-displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/README.md b/micropython/pydisplay/eventsys/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/eventsys/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index a23471136..9bdb0ab9f 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="eventsys", + pypi_publish="pydisplay-eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/README.md b/micropython/pydisplay/graphics/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/graphics/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index d88f3578a..4b4b10ffd 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="graphics", + pypi_publish="pydisplay-graphics", ) package("graphics") diff --git a/micropython/pydisplay/palettes/README.md b/micropython/pydisplay/palettes/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/palettes/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 557b2800c..4170bcddf 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="palettes", + pypi_publish="pydisplay-palettes", ) package("palettes") diff --git a/micropython/pydisplay/pydisplay-bundle/manifest.py b/micropython/pydisplay/pydisplay-bundle/manifest.py index 5f9387bec..c983c998f 100644 --- a/micropython/pydisplay/pydisplay-bundle/manifest.py +++ b/micropython/pydisplay/pydisplay-bundle/manifest.py @@ -3,7 +3,7 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="", + pypi_publish="pydisplay-bundle", ) require("displaybuf") require("eventsys") diff --git a/micropython/pydisplay/timer/README.md b/micropython/pydisplay/timer/README.md new file mode 100644 index 000000000..18d23042b --- /dev/null +++ b/micropython/pydisplay/timer/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py index 3e329bed2..5a0c85d3d 100644 --- a/micropython/pydisplay/timer/manifest.py +++ b/micropython/pydisplay/timer/manifest.py @@ -3,6 +3,6 @@ version="0.1.6", author="Brad Barnett ", license="MIT", - pypi_publish="timer", + pypi_publish="pydisplay-timer", ) package("timer") From d36eaf4bf52b95d9ea1776fb1242604bad4ea29d Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Tue, 26 Nov 2024 22:31:03 -0600 Subject: [PATCH 32/35] pydisplay: Testing build system. Signed-off-by: Brad Barnett --- .../drivers/display/gc9a01/manifest.py | 2 +- .../drivers/display/gc9d01/manifest.py | 2 +- .../drivers/display/hx8357/manifest.py | 2 +- .../drivers/display/ili9163/manifest.py | 2 +- .../drivers/display/ili9341/manifest.py | 2 +- .../drivers/display/ili9488/manifest.py | 2 +- .../drivers/display/st7701/manifest.py | 2 +- .../drivers/display/st7735/manifest.py | 2 +- .../drivers/display/st7735r/manifest.py | 2 +- .../drivers/display/st7735r_1/manifest.py | 2 +- .../drivers/display/st7789/manifest.py | 2 +- .../drivers/display/st7789vw/manifest.py | 2 +- .../drivers/display/st7796/manifest.py | 2 +- .../drivers/display/st7796_test/manifest.py | 2 +- micropython/drivers/touch/chsc6x/manifest.py | 2 +- micropython/drivers/touch/cst226/manifest.py | 2 +- micropython/drivers/touch/cst8xx/manifest.py | 2 +- micropython/drivers/touch/ft6x36/manifest.py | 2 +- micropython/drivers/touch/gt911/manifest.py | 2 +- micropython/drivers/touch/xpt2046/manifest.py | 2 +- micropython/pydisplay/displaybuf/README.md | 4 +- micropython/pydisplay/displaybuf/manifest.py | 4 +- .../examples/board_config.py | 6 +- .../displaysys-busdisplay/manifest.py | 4 +- .../examples/board_config.py | 6 +- .../displaysys-fbdisplay/manifest.py | 4 +- .../examples/board_config.py | 4 +- .../displaysys-jndisplay/manifest.py | 4 +- .../displaysys/pgdisplay.py | 22 +- .../examples/board_config.py | 11 +- .../displaysys-pgdisplay/manifest.py | 4 +- .../examples/board_config.py | 6 +- .../displaysys-psdisplay/manifest.py | 4 +- .../displaysys/sdldisplay/__init__.py | 55 +- .../sdldisplay/_sdl2_lib/_constants.py | 4 + .../sdldisplay/_sdl2_lib/_cpython.py | 8 + .../sdldisplay/_sdl2_lib/_micropython.py | 2 + .../examples/board_config.py | 11 +- .../displaysys-sdldisplay/manifest.py | 4 +- .../pydisplay/displaysys/displaysys/README.md | 151 ++++ .../displaysys/displaysys/__init__.py | 2 +- .../examples/displaysys_simpletest.py | 2 +- .../displaysys/displaysys/manifest.py | 4 +- micropython/pydisplay/eventsys/README.md | 4 +- .../pydisplay/eventsys/eventsys/__init__.py | 92 +-- .../pydisplay/eventsys/eventsys/devices.py | 738 ++++++++++++++++++ .../examples/eventsys_encoder_test.py | 4 +- .../eventsys/examples/eventsys_simpletest.py | 2 +- .../eventsys/examples/eventsys_touch_test.py | 6 +- micropython/pydisplay/eventsys/manifest.py | 4 +- micropython/pydisplay/graphics/README.md | 4 +- micropython/pydisplay/graphics/manifest.py | 4 +- micropython/pydisplay/multimer/README.md | 151 ++++ micropython/pydisplay/multimer/manifest.py | 8 + .../pydisplay/multimer/multimer/__init__.py | 59 ++ .../pydisplay/multimer/multimer/_librt.py | 155 ++++ .../pydisplay/multimer/multimer/_sdl2.py | 49 ++ .../pydisplay/multimer/multimer/_timerbase.py | 121 +++ micropython/pydisplay/palettes/README.md | 4 +- micropython/pydisplay/palettes/manifest.py | 4 +- .../pydisplay/pydisplay-bundle/manifest.py | 10 +- 61 files changed, 1633 insertions(+), 152 deletions(-) create mode 100644 micropython/pydisplay/displaysys/displaysys/README.md create mode 100644 micropython/pydisplay/eventsys/eventsys/devices.py create mode 100644 micropython/pydisplay/multimer/README.md create mode 100644 micropython/pydisplay/multimer/manifest.py create mode 100644 micropython/pydisplay/multimer/multimer/__init__.py create mode 100644 micropython/pydisplay/multimer/multimer/_librt.py create mode 100644 micropython/pydisplay/multimer/multimer/_sdl2.py create mode 100644 micropython/pydisplay/multimer/multimer/_timerbase.py diff --git a/micropython/drivers/display/gc9a01/manifest.py b/micropython/drivers/display/gc9a01/manifest.py index e4006ce6d..b8ca01c94 100644 --- a/micropython/drivers/display/gc9a01/manifest.py +++ b/micropython/drivers/display/gc9a01/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gc9a01 display driver", - version="0.1.6", + version="0.0.1", ) module("gc9a01.py", opt=3) diff --git a/micropython/drivers/display/gc9d01/manifest.py b/micropython/drivers/display/gc9d01/manifest.py index 6b617bf3b..817612645 100644 --- a/micropython/drivers/display/gc9d01/manifest.py +++ b/micropython/drivers/display/gc9d01/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gc9d01 display driver", - version="0.1.6", + version="0.0.1", ) module("gc9d01.py", opt=3) diff --git a/micropython/drivers/display/hx8357/manifest.py b/micropython/drivers/display/hx8357/manifest.py index 44375e1e0..25693c67e 100644 --- a/micropython/drivers/display/hx8357/manifest.py +++ b/micropython/drivers/display/hx8357/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay hx8357 display driver", - version="0.1.6", + version="0.0.1", ) module("hx8357.py", opt=3) diff --git a/micropython/drivers/display/ili9163/manifest.py b/micropython/drivers/display/ili9163/manifest.py index 18409d499..951c75a48 100644 --- a/micropython/drivers/display/ili9163/manifest.py +++ b/micropython/drivers/display/ili9163/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9163 display driver", - version="0.1.6", + version="0.0.1", ) module("ili9163.py", opt=3) diff --git a/micropython/drivers/display/ili9341/manifest.py b/micropython/drivers/display/ili9341/manifest.py index 9281a8802..ad9ab1d57 100644 --- a/micropython/drivers/display/ili9341/manifest.py +++ b/micropython/drivers/display/ili9341/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9341 display driver", - version="0.1.6", + version="0.0.1", ) module("ili9341.py", opt=3) diff --git a/micropython/drivers/display/ili9488/manifest.py b/micropython/drivers/display/ili9488/manifest.py index e4314631a..5cc0e9063 100644 --- a/micropython/drivers/display/ili9488/manifest.py +++ b/micropython/drivers/display/ili9488/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ili9488 display driver", - version="0.1.6", + version="0.0.1", ) module("ili9488.py", opt=3) diff --git a/micropython/drivers/display/st7701/manifest.py b/micropython/drivers/display/st7701/manifest.py index 8dbc596f3..badd9c589 100644 --- a/micropython/drivers/display/st7701/manifest.py +++ b/micropython/drivers/display/st7701/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7701 display driver", - version="0.1.6", + version="0.0.1", ) module("st7701.py", opt=3) diff --git a/micropython/drivers/display/st7735/manifest.py b/micropython/drivers/display/st7735/manifest.py index 5291dd56d..2515b52cb 100644 --- a/micropython/drivers/display/st7735/manifest.py +++ b/micropython/drivers/display/st7735/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735 display driver", - version="0.1.6", + version="0.0.1", ) module("st7735.py", opt=3) diff --git a/micropython/drivers/display/st7735r/manifest.py b/micropython/drivers/display/st7735r/manifest.py index 5fcb97d1e..46e0bbc5e 100644 --- a/micropython/drivers/display/st7735r/manifest.py +++ b/micropython/drivers/display/st7735r/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735r display driver", - version="0.1.6", + version="0.0.1", ) module("st7735r.py", opt=3) diff --git a/micropython/drivers/display/st7735r_1/manifest.py b/micropython/drivers/display/st7735r_1/manifest.py index 21ca8ad7f..f03144a0e 100644 --- a/micropython/drivers/display/st7735r_1/manifest.py +++ b/micropython/drivers/display/st7735r_1/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7735r_1 display driver", - version="0.1.6", + version="0.0.1", ) module("st7735r_1.py", opt=3) diff --git a/micropython/drivers/display/st7789/manifest.py b/micropython/drivers/display/st7789/manifest.py index 47bafc2ae..bd0649c0f 100644 --- a/micropython/drivers/display/st7789/manifest.py +++ b/micropython/drivers/display/st7789/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7789 display driver", - version="0.1.6", + version="0.0.1", ) module("st7789.py", opt=3) diff --git a/micropython/drivers/display/st7789vw/manifest.py b/micropython/drivers/display/st7789vw/manifest.py index 36157aad5..e7a30b68d 100644 --- a/micropython/drivers/display/st7789vw/manifest.py +++ b/micropython/drivers/display/st7789vw/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7789vw display driver", - version="0.1.6", + version="0.0.1", ) module("st7789vw.py", opt=3) diff --git a/micropython/drivers/display/st7796/manifest.py b/micropython/drivers/display/st7796/manifest.py index e4240db8e..ee1ada811 100644 --- a/micropython/drivers/display/st7796/manifest.py +++ b/micropython/drivers/display/st7796/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7796 display driver", - version="0.1.6", + version="0.0.1", ) module("st7796.py", opt=3) diff --git a/micropython/drivers/display/st7796_test/manifest.py b/micropython/drivers/display/st7796_test/manifest.py index 8c8b44d01..aad5d8b7e 100644 --- a/micropython/drivers/display/st7796_test/manifest.py +++ b/micropython/drivers/display/st7796_test/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay st7796_test display driver", - version="0.1.6", + version="0.0.1", ) module("st7796_test.py", opt=3) diff --git a/micropython/drivers/touch/chsc6x/manifest.py b/micropython/drivers/touch/chsc6x/manifest.py index a1e221cf9..f5f84355a 100644 --- a/micropython/drivers/touch/chsc6x/manifest.py +++ b/micropython/drivers/touch/chsc6x/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay chsc6x touch driver", - version="0.1.6", + version="0.0.1", ) module("chsc6x.py", opt=3) diff --git a/micropython/drivers/touch/cst226/manifest.py b/micropython/drivers/touch/cst226/manifest.py index e398cc534..b6d9b86fb 100644 --- a/micropython/drivers/touch/cst226/manifest.py +++ b/micropython/drivers/touch/cst226/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay cst226 touch driver", - version="0.1.6", + version="0.0.1", ) module("cst226.py", opt=3) diff --git a/micropython/drivers/touch/cst8xx/manifest.py b/micropython/drivers/touch/cst8xx/manifest.py index 52331956d..24d81ccdb 100644 --- a/micropython/drivers/touch/cst8xx/manifest.py +++ b/micropython/drivers/touch/cst8xx/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay cst8xx touch driver", - version="0.1.6", + version="0.0.1", ) module("cst8xx.py", opt=3) diff --git a/micropython/drivers/touch/ft6x36/manifest.py b/micropython/drivers/touch/ft6x36/manifest.py index 3bfb5175a..5e1ade085 100644 --- a/micropython/drivers/touch/ft6x36/manifest.py +++ b/micropython/drivers/touch/ft6x36/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay ft6x36 touch driver", - version="0.1.6", + version="0.0.1", ) module("ft6x36.py", opt=3) diff --git a/micropython/drivers/touch/gt911/manifest.py b/micropython/drivers/touch/gt911/manifest.py index 55c637a1c..3e26e3d41 100644 --- a/micropython/drivers/touch/gt911/manifest.py +++ b/micropython/drivers/touch/gt911/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay gt911 touch driver", - version="0.1.6", + version="0.0.1", ) module("gt911.py", opt=3) diff --git a/micropython/drivers/touch/xpt2046/manifest.py b/micropython/drivers/touch/xpt2046/manifest.py index 842d0a9d8..a5c419665 100644 --- a/micropython/drivers/touch/xpt2046/manifest.py +++ b/micropython/drivers/touch/xpt2046/manifest.py @@ -1,5 +1,5 @@ metadata( description="PyDisplay xpt2046 touch driver", - version="0.1.6", + version="0.0.1", ) module("xpt2046.py", opt=3) diff --git a/micropython/pydisplay/displaybuf/README.md b/micropython/pydisplay/displaybuf/README.md index 18d23042b..5841c6112 100644 --- a/micropython/pydisplay/displaybuf/README.md +++ b/micropython/pydisplay/displaybuf/README.md @@ -26,7 +26,7 @@ WARNINGS: pydisplay is currently alpha quality. Every effort has been made to pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) ## Key Features @@ -86,7 +86,7 @@ Where possible, existing, proven APIs were used. - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. - All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. diff --git a/micropython/pydisplay/displaybuf/manifest.py b/micropython/pydisplay/displaybuf/manifest.py index b97ac8973..5a51c3275 100644 --- a/micropython/pydisplay/displaybuf/manifest.py +++ b/micropython/pydisplay/displaybuf/manifest.py @@ -1,8 +1,8 @@ metadata( description="PyDisplay displaybuf", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaybuf", + pypi_publish="displaybuf", ) package("displaybuf") diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py index ffbf320ab..fa43bd07f 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/examples/board_config.py @@ -5,7 +5,7 @@ from machine import I2C, Pin # See the note about reset below from ft6x36 import FT6x36 from machine import freq -from eventsys import device +from eventsys import devices freq(240_000_000) @@ -49,10 +49,10 @@ touch_read_func = touch_drv.get_positions touch_rotation_table = None -broker = device.Broker() +broker = devices.Broker() touch_dev = broker.create_device( - type=device.Types.TOUCH, + type=devices.types.TOUCH, read=touch_read_func, data=display_drv, data2=touch_rotation_table, diff --git a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py index 56c4aba31..d09f92cae 100644 --- a/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-busdisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-busdisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-busdisplay", + pypi_publish="displaysys-busdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py index c6a2c02e7..fc4b7e0d4 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -6,7 +6,7 @@ from pca9554 import PCA9554 from ft6x36 import FT6x36 from displaysys.fbdisplay import FBDisplay -from eventsys import device +from eventsys import devices def send_init_sequence(init_sequence, mosi, sck, cs): @@ -85,10 +85,10 @@ def touch_read_func(): touch_rotation_table = (0, 0, 0, 0) -broker = device.Broker() +broker = devices.Broker() touch_dev = broker.create_device( - type=device.Types.TOUCH, + type=devices.types.TOUCH, read=touch_read_func, data=display_drv, data2=touch_rotation_table, diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py index e84612008..a7505bcca 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-fbdisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-fbdisplay", + pypi_publish="displaysys-fbdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py index 9ea7aae87..e91b2adef 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/examples/board_config.py @@ -3,13 +3,13 @@ """ from displaysys.jndisplay import JNDisplay -from eventsys import device +from eventsys import devices width = 320 height = 480 -broker = device.Broker() +broker = devices.Broker() display_drv = JNDisplay(width, height) diff --git a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py index 509265987..c9970edb4 100644 --- a/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-jndisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-jndisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-jndisplay", + pypi_publish="displaysys-jndisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py index 50a67891b..a7be44ad1 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py @@ -10,20 +10,36 @@ import pygame as pg try: - from typing import Optional + from typing import Optional, Union, Sequence except ImportError: pass -def poll() -> Optional[pg.event.Event]: +def poll() -> Optional[pg.event.Event | False]: """ Polls for an event and returns the event type and data. Returns: - Optional[pg.event.Event]: The event type and data. + Optional[pg.event.Event | False]: The event type and data. """ return pg.event.poll() +def peek(eventtype: Optional[Union[int, Sequence[int]]] = None, pump: bool = True) -> bool: + """ + Check the event queue for messages. Returns True if there are any events of the given type waiting on the queue. + If a sequence of event types is passed, this will return True if any of those events are on the queue. + + If pump is True (the default), then pygame.event.pump() will be called. + + Args: + eventtype (Optional[Union[int, Sequence[int]]]): The event type(s) to check. Defaults to None. + pump (bool): Whether to call pygame.event.pump(). Defaults to True. + + Returns: + bool: True if there are any events of the given type waiting on the queue. + """ + return pg.event.peek(eventtype, pump) + class PGDisplay(DisplayDriver): """ diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index e65abc96b..1b50b8638 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -2,8 +2,8 @@ Board configuration for PyGame. """ -from displaysys.pgdisplay import PGDisplay as DTDisplay, poll -from eventsys import device +from displaysys.pgdisplay import PGDisplay as DTDisplay, poll, peek +from eventsys import devices import sys @@ -20,13 +20,14 @@ scale=scale, ) -broker = device.Broker() +broker = devices.Broker() events_dev = broker.create_device( - type=device.Types.QUEUE, + type=devices.types.QUEUE, read=poll, data=display_drv, - # data2=Events.filter, + read2=peek, + # data2=events.filter, ) display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py index 26e651560..f171682c5 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-pgdisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-pgdisplay", + pypi_publish="displaysys-pgdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py index ba2fc6646..04fc367b9 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/examples/board_config.py @@ -3,19 +3,19 @@ """ from displaysys.psdisplay import PSDisplay, PSDevices -from eventsys import device +from eventsys import devices width = 320 height = 480 display_drv = PSDisplay("display_canvas", width, height) -broker = device.Broker() +broker = devices.Broker() touch_drv = PSDevices("display_canvas") touch_dev = broker.create_device( - type=device.Types.TOUCH, + type=devices.types.TOUCH, read=touch_drv.get_mouse_pos, data=display_drv, ) diff --git a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py index 6aa7db725..03bd8e795 100644 --- a/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-psdisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-psdisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-psdisplay", + pypi_publish="displaysys-psdisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py index 1bf189230..584973962 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py @@ -7,7 +7,7 @@ """ from displaysys import DisplayDriver, color_rgb -from eventsys import Events +from eventsys import events from sys import implementation from ._sdl2_lib import ( SDL_Init, @@ -41,6 +41,8 @@ SDL_INIT_EVERYTHING, SDL_Rect, SDL_PollEvent, + SDL_PeepEvents, + SDL_PumpEvents, SDL_GetKeyName, SDL_Event, SDL_QUIT, @@ -53,10 +55,13 @@ SDL_MOUSEWHEEL, SDL_KEYDOWN, SDL_KEYUP, + SDL_PEEKEVENT, + SDL_FIRSTEVENT, + SDL_LASTEVENT, ) try: - from typing import Optional + from typing import Optional, Union, Sequence except ImportError: pass @@ -71,23 +76,49 @@ _event = SDL_Event() -def poll() -> Optional[Events]: +def poll() -> Optional[events]: """ Polls for an event and returns the event type and data. Returns: - Optional[Events]: The event type and data. + Optional[events]: The event type and data. """ global _event if SDL_PollEvent(_event): if is_cpython: - if _event.type in Events.filter: + if _event.type in events.filter: return _convert(SDL_Event(_event)) else: - if int.from_bytes(_event[:4], "little") in Events.filter: + if int.from_bytes(_event[:4], "little") in events.filter: return _convert(SDL_Event(_event)) return None +def peek(eventtype: Optional[Union[int, Sequence[int]]] = None, pump: bool = True) -> bool: + """ + Check the event queue for messages. Returns True if there are any events of the given type waiting on the queue. + If a sequence of event types is passed, this will return True if any of those events are on the queue. + + If pump is True (the default), then pygame.event.pump() will be called. + + Args: + eventtype (Optional[Union[int, Sequence[int]]]): The event type(s) to check. Defaults to None. + pump (bool): Whether to call pygame.event.pump(). Defaults to True. + + Returns: + bool: True if there are any events of the given type waiting on the queue. + """ + global _event + _event = SDL_Event() + if pump: + SDL_PumpEvents() + num_matching = SDL_PeepEvents(_event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) + if num_matching == 0: + return False + if eventtype is None: + return True + if isinstance(eventtype, int): + return _event.type == eventtype + return _event.type in eventtype def _convert(e): # Convert an SDL event to a Pygame event @@ -95,7 +126,7 @@ def _convert(e): l = 1 if e.motion.state & SDL_BUTTON_LMASK else 0 # noqa: E741 m = 1 if e.motion.state & SDL_BUTTON_MMASK else 0 r = 1 if e.motion.state & SDL_BUTTON_RMASK else 0 - evt = Events.Motion( + evt = events.Motion( e.type, (e.motion.x, e.motion.y), (e.motion.xrel, e.motion.yrel), @@ -104,7 +135,7 @@ def _convert(e): e.motion.windowID, ) elif e.type in (SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP): - evt = Events.Button( + evt = events.Button( e.type, (e.button.x, e.button.y), e.button.button, @@ -112,7 +143,7 @@ def _convert(e): e.button.windowID, ) elif e.type == SDL_MOUSEWHEEL: - evt = Events.Wheel( + evt = events.Wheel( e.type, e.wheel.direction != 0, e.wheel.x, @@ -124,7 +155,7 @@ def _convert(e): ) elif e.type in (SDL_KEYDOWN, SDL_KEYUP): name = SDL_GetKeyName(e.key.keysym.sym) - evt = Events.Key( + evt = events.Key( e.type, name, e.key.keysym.sym, @@ -133,9 +164,9 @@ def _convert(e): e.key.windowID, ) elif e.type == SDL_QUIT: - evt = Events.Quit(e.type) + evt = events.Quit(e.type) else: - evt = Events.Unknown(e.type) + evt = events.Unknown(e.type) return evt diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py index a7200073d..07184158f 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py @@ -79,6 +79,10 @@ SDL_BUTTON_MMASK = const(1 << 1) # Middle mouse button SDL_BUTTON_RMASK = const(1 << 2) # Right mouse button +# Event constants +SDL_PEEKEVENT = const(0x1) +SDL_FIRSTEVENT = const(0x2) +SDL_LASTEVENT = const(0x3) ############################################################################### # SDL2 Pixel Formats # diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py index 0b7d39506..88f4d7bf9 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py @@ -140,6 +140,14 @@ class Wheel(ctypes.Structure): _libSDL2.SDL_PollEvent.restype = ctypes.c_int SDL_PollEvent = _libSDL2.SDL_PollEvent +_libSDL2.SDL_PeepEvents.argtypes = [ctypes.POINTER(SDL_CommonEvent), ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint] +_libSDL2.SDL_PeepEvents.restype = ctypes.c_int +SDL_PeepEvents = _libSDL2.SDL_PeepEvents + +_libSDL2.SDL_PumpEvents.argtypes = [] +_libSDL2.SDL_PumpEvents.restype = None +SDL_PumpEvents = _libSDL2.SDL_PumpEvents + # SDL key functions _libSDL2.SDL_GetKeyName.argtypes = [ctypes.c_int] _libSDL2.SDL_GetKeyName.restype = ctypes.c_char_p diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py index fda726fa3..f2df6b568 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py @@ -120,6 +120,8 @@ def SDL_Point(x=0, y=0): SDL_Quit = _libSDL2.func("v", "SDL_Quit", "") SDL_GetError = _libSDL2.func("s", "SDL_GetError", "") SDL_PollEvent = _libSDL2.func("i", "SDL_PollEvent", "P") +SDL_PeepEvents = _libSDL2.func("i", "SDL_PeepEvents", "Piiii") +SDL_PumpEvents = _libSDL2.func("v", "SDL_PumpEvents", "") # SDL key functions SDL_GetKeyName = _libSDL2.func("s", "SDL_GetKeyName", "i") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index 0518d4a56..a8261dfd1 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -2,8 +2,8 @@ Combination board configuration for desktop, pyscript and jupyter notebook platforms. """ -from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll -from eventsys import device +from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll, peek +from eventsys import devices import sys width = 320 @@ -19,13 +19,14 @@ scale=scale, ) -broker = device.Broker() +broker = devices.Broker() events_dev = broker.create_device( - type=device.Types.QUEUE, + type=devices.types.QUEUE, read=poll, data=display_drv, - # data2=Events.filter, + read2=peek, + # data2=events.filter, ) display_drv.fill(0) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py index d91968579..2bfafbe70 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/manifest.py @@ -1,9 +1,9 @@ metadata( description="PyDisplay displaysys-sdldisplay", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys-sdldisplay", + pypi_publish="displaysys-sdldisplay", ) require("displaysys") package("displaysys") diff --git a/micropython/pydisplay/displaysys/displaysys/README.md b/micropython/pydisplay/displaysys/displaysys/README.md new file mode 100644 index 000000000..5841c6112 --- /dev/null +++ b/micropython/pydisplay/displaysys/displaysys/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index 6c046c2fd..3656b1191 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -116,7 +116,7 @@ def __init__(self, auto_refresh=False): self._touch_device = None if auto_refresh: try: - from timer import get_timer + from multimer import get_timer self._timer = get_timer(self.show) except ImportError: diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py index 880e5cc84..388bc4701 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_simpletest.py @@ -5,7 +5,7 @@ button_area = Area(display_drv.fill_rect(10, 10, 100, 100, 0xF800)) while True: if evt := broker.poll(): - if evt.type == broker.Events.MOUSEBUTTONDOWN: + if evt.type == broker.events.MOUSEBUTTONDOWN: if button_area.contains(evt.pos): display_drv.fill_rect(*button_area, getrandbits(16)) print(f"Button pressed at {evt.pos}") diff --git a/micropython/pydisplay/displaysys/displaysys/manifest.py b/micropython/pydisplay/displaysys/displaysys/manifest.py index b03c5f650..fc8f4cf9f 100644 --- a/micropython/pydisplay/displaysys/displaysys/manifest.py +++ b/micropython/pydisplay/displaysys/displaysys/manifest.py @@ -1,8 +1,8 @@ metadata( description="PyDisplay displaysys", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-displaysys", + pypi_publish="displaysys", ) package("displaysys") diff --git a/micropython/pydisplay/eventsys/README.md b/micropython/pydisplay/eventsys/README.md index 18d23042b..5841c6112 100644 --- a/micropython/pydisplay/eventsys/README.md +++ b/micropython/pydisplay/eventsys/README.md @@ -26,7 +26,7 @@ WARNINGS: pydisplay is currently alpha quality. Every effort has been made to pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) ## Key Features @@ -86,7 +86,7 @@ Where possible, existing, proven APIs were used. - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. - All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. diff --git a/micropython/pydisplay/eventsys/eventsys/__init__.py b/micropython/pydisplay/eventsys/eventsys/__init__.py index e76baedeb..ec5eff21c 100644 --- a/micropython/pydisplay/eventsys/eventsys/__init__.py +++ b/micropython/pydisplay/eventsys/eventsys/__init__.py @@ -11,7 +11,48 @@ from collections import namedtuple -class Events: +def custom_type(types: dict[str, int]={}, classes: dict[str, str]={}): + """ + Create new event types and classes for the events class. + + For example, to recreate the events for the keypad device: + ``` + import eventsys + + types = [("KEYDOWN", 0x300), ("KEYUP", 0x301)] + classes = {"Key": "type name key mod scancode window"} + eventsys.custom_type(types, classes) + + # Optionally update the filter + events.filter += [events.KEYDOWN, events.KEYUP] + ``` + + Args: + types (dict[str, int]): Dictionary of event types and values. + classes (dict[str, str]): Dictionary of event classes and fields. + """ + for type_name, value in types.items(): + type_name = type_name.upper() + if hasattr(events, type_name): + raise ValueError(f"Event type {type_name} already exists in events class.") + else: + setattr(events, type_name, value or events._USER_TYPE_BASE) + if not value: + events._USER_TYPE_BASE += 1 + + for event_class_name, event_class_fields in classes.items(): + event_class_name = event_class_name[0].upper() + event_class_name[1:].lower() + if hasattr(events, event_class_name): + raise ValueError(f"Event class {event_class_name} already exists in events class.") + else: + event_class_fields = event_class_fields.lower() + setattr( + events, + event_class_name, + namedtuple(event_class_name, event_class_fields), # noqa: PYI024 + ) + +class events: """ A container for event types and classes. Similar to a C enum and struct. """ @@ -49,52 +90,3 @@ class Events: Key = namedtuple("Key", "type name key mod scancode window") # noqa: PYI024 Quit = namedtuple("Quit", "type") # noqa: PYI024 Any = namedtuple("Any", "type") # noqa: PYI024 - - @staticmethod - def new(types: list[str | tuple[str, int]] = [], classes: dict[str, str] = {}): - """ - Create new event types and classes for the Events class. - - For example, to create the events for the keypad device: - ``` - from eventsys import Events - - types = [("KEYDOWN", 0x300), ("KEYUP", 0x301)] - classes = { - "Key": "type name key mod scancode window", - } - Events.new_types(types, classes) - - # Optionally update the filter - Events.filter += [Events.KEYDOWN, Events.KEYUP] - ``` - - Args: - types (list[str | tuple[str, int]]): List of event types or tuples of event type and value. - If a value is not provided, the next available value will be used. - classes (dict[str, str]): Dictionary of event classes and fields. - """ - for type_name in types: - if isinstance(type_name, tuple): - type_name, value = type_name - else: - value = None - type_name = type_name.upper() - if hasattr(Events, type_name): - raise ValueError(f"Event type {type_name} already exists in Events class.") - else: - setattr(Events, type_name, value if value else Events._USER_TYPE_BASE) - if not value: - Events._USER_TYPE_BASE += 1 - - for event_class_name, event_class_fields in classes.items(): - event_class_name = event_class_name[0].upper() + event_class_name[1:].lower() - if hasattr(Events, event_class_name): - raise ValueError(f"Event class {event_class_name} already exists in Events class.") - else: - event_class_fields = event_class_fields.lower() - setattr( - Events, - event_class_name, - namedtuple(event_class_name, event_class_fields), # noqa: PYI024 - ) diff --git a/micropython/pydisplay/eventsys/eventsys/devices.py b/micropython/pydisplay/eventsys/eventsys/devices.py new file mode 100644 index 000000000..057f67371 --- /dev/null +++ b/micropython/pydisplay/eventsys/eventsys/devices.py @@ -0,0 +1,738 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT + +""" +`eventsys.devices` +==================================================== + +Device classes for eventsys's Event System. May also be used +with other applications. Devices are objects that poll for events +and return them. They can be subscribed to and unsubscribed from +to receive events. + +Devices can be created with Broker.create_device() or by calling the +constructor of the device class directly. Devices can be +subscribed to with .subscribe() and unsubscribed from with +.unsubscribe(). Devices can be polled for events with .poll(). +Devices can be registered with a broker device with .register_device() +and unregistered with .unregister_device(). Devices can be chained +together by setting the .broker property of a device to another device. + +Devices can be created with the following types: +- types.BROKER: A device that polls multiple devices. +- types.QUEUE: A device that returns multiple types of events. +- types.TOUCH: A device that returns MOUSEBUTTONDOWN when touched, + MOUSEMOTION when moved and MOUSEBUTTONUP when released. +- types.ENCODER: A device that returns MOUSEWHEEL events when turned, + MOUSEBUTTONDOWN when pressed. +- types.KEYPAD: A device that returns KEYDOWN and KEYUP events when + keys are pressed or released. +- types.JOYSTICK: A device that returns joystick events (not implemented). +""" + +from micropython import const +from . import events +from sys import exit + + +_DEFAULT_TOUCH_ROTATION_TABLE = (0b000, 0b101, 0b110, 0b011) + +SWAP_XY = const(0b001) +REVERSE_X = const(0b010) +REVERSE_Y = const(0b100) + + +def custom_type(type_name, responses): + """ + Create a new device type with a list of responses. + + Args: + type_name (str): The name of the device type. + responses (list[int]): A list of event types that the device can return. + + Returns: + Device: The newly created device type. + + Raises: + ValueError: If `type_name` is not a string, `responses` is not a list, or any response is not an integer. + ValueError: If a device type with the same name already exists in the `types` class. + ValueError: If a device class with the same name already exists. + + Example: + To create the KEYPAD device type and `KeypadDevice` class: + + ```python + import eventsys.device as device + from eventsys import events + + KeypadDevice = device.new_type("KEYPAD", [events.KEYDOWN, events.KEYUP]) + ``` + """ + if not isinstance(type_name, str): + raise ValueError("type_name must be a string") + type_name = type_name.strip().upper() + if not isinstance(responses, list): + raise ValueError("responses must be a list") + if not all(isinstance(event, int) for event in responses): + raise ValueError("all responses must be integers") + + if hasattr(types, type_name): + raise ValueError(f"Device type {type_name} already exists in types class.") + class_name = type_name[0].upper() + type_name[1:].lower() + "Device" + if class_name in [cls.__name__ for cls in _mapping.values()]: + raise ValueError(f"Device class {class_name} already exists.") + + value = len(_mapping) + setattr(types, type_name, value) + NewClass = type(class_name, (Device,), {"type": value, "responses": responses}) + _mapping[value] = NewClass + return NewClass + + + +class types: + """ + Device types for the Event System. + """ + UNDEFINED = const(-1) + BROKER = const(0x00) + QUEUE = const(0x01) + TOUCH = const(0x02) + ENCODER = const(0x03) + KEYPAD = const(0x04) + JOYSTICK = const(0x05) + + +class Device: + """ + Base class for devices. Must be subclassed. Should not be instantiated directly. + + Attributes: + type (Devices): The type of the device. + responses (list): The list of event types that the device can respond to. + """ + + type = types.UNDEFINED + responses = events.filter + + def __init__(self, read=None, data=None, read2=None, data2=None): + """ + Create a new device object. + + Args: + read (callable, optional): A function that returns an event or None. Defaults to None. + data (Any, optional): Data to pass to the read function. Defaults to None. + read2 (callable, optional): A function that returns a value or None. Defaults to None. + data2 (Any, optional): Data to pass to the read2 function. Defaults to None. + """ + self._event_callbacks = {} + + self._read = read if read else lambda: None + self._data = data + self._read2 = read2 if read2 else lambda: None + self._data2 = data2 + + self._broker = None + self._state = None + self._user_data = None # Can be set and retrieved by apps such as lv_config + + def poll(self, *args) -> events: + """ + Poll the device for events. + + Args: + *args: Additional arguments that can be passed to the read callback functions. + + Returns: + Event: The event that was polled or None if no event was polled. + """ + if (event := self._poll()) is not None: + if event.type in events.filter: + if event.type == events.QUIT: + if self._broker: + self._broker.quit() + if callback_list := self._event_callbacks.get(event.type): + for callback in callback_list: + callback(event, *args) + return event + return None + + def subscribe(self, callback, event_types=None): + """ + Subscribe to events from the device. + + Args: + callback (function): The function to call when an event is received. + event_types (list[int] | None): A list of event types to subscribe to. + + Raises: + ValueError: If `callback` is not callable. + ValueError: If any event type in `event_types` is not a response from this device. + + Example: + ```python + def callback(event): + print(event) + + device.subscribe(callback, [events.MOUSEBUTTONDOWN, events.MOUSEBUTTONUP]) + ``` + + This will call `callback` when the receives a MOUSEBUTTONDOWN or MOUSEBUTTONUP event. + """ + event_types = event_types or self.responses + if not callable(callback): + raise ValueError("callback is not callable.") + for event_type in event_types: + if event_type not in self.responses: + raise ValueError("the specified event_type is not a response from this device") + callback_set = self._event_callbacks.get(event_type, set()) + callback_set.add(callback) + self._event_callbacks[event_type] = callback_set + + def unsubscribe(self, callback, event_types=None): + """ + Unsubscribes a callback function from one or more event types. + + Args: + callback (function): The callback function to unsubscribe. + event_types (list): A list of event types to unsubscribe from. + """ + event_types = event_types or self.responses + for event_type in event_types: + if callback_set := self._event_callbacks.get(event_type): + callback_set.remove(callback) + + @property + def broker(self): + """ + The broker that manages this device. + """ + return self._broker + + @broker.setter + def broker(self, broker): + self._broker = broker + + @property + def user_data(self): + """ + User data that can be set and retrieved by applications. + """ + return self._user_data + + @user_data.setter + def user_data(self, value): + self._user_data = value + + +class Broker(Device): + """ + The Broker class is a device that polls multiple devices for events and forwards them to + subscribers. + + Attributes: + type (Devices): The type of the device (set to `types.BROKER`). + responses (list): The list of event types that the device can respond to. + events (events): The events class for convenience. + Applications can use Broker.events.KEYDOWN, etc. + """ + + type = types.BROKER + responses = events.filter + events = events # Create a reference to the events class for convenience. + + def __init__(self): + super().__init__() + self.devices = [] # List of devices to poll + self._device_callbacks = {} + # Function to call when the window close button is clicked. + # Set it like `display_drv.quit_func = cleanup_func` where `cleanup_func` is a + # function that cleans up resources and calls `sys.exit()`. + # .poll() must be called periodically to check for the quit event. + self._quit_func = exit + + def subscribe(self, callback, event_types=None, device_types=None): + """ + Subscribes a callback function to receive events. + + Args: + callback (function): The callback function to subscribe. + event_types (list, optional): The list of event types to subscribe to. Defaults to None. + device_types (list, optional): The list of device types to subscribe to. Defaults to None. + + Raises: + ValueError: If the callback is not callable. + ValueError: If both device_types and event_types are provided. + ValueError: If neither device_types nor event_types are provided. + """ + if not callable(callback): + raise ValueError("callback is not callable.") + if device_types is not None and event_types is not None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is None and event_types is None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is not None: + for device_type in device_types: + callback_set = self._device_callbacks.get(device_type, set()) + callback_set.add(callback) + self._device_callbacks[device_type] = callback_set + else: + super().subscribe(callback, event_types) + + def unsubscribe(self, callback, event_types=None, device_types=None): + """ + Unsubscribes a callback function from receiving events. + + Args: + callback (function): The callback function to unsubscribe. + event_types (list, optional): The list of event types to unsubscribe from. Defaults to None. + device_types (list, optional): The list of device types to unsubscribe from. Defaults to None. + + Raises: + ValueError: If both device_types and event_types are provided. + ValueError: If neither device_types nor event_types are provided. + """ + if device_types is not None and event_types is not None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is None and event_types is None: + raise ValueError("set one of device_type or event_type but not both.") + if device_types is not None: + for device_type in device_types: + if callback_set := self._device_callbacks.get(device_type): + callback_set.remove(callback) + else: + super().unsubscribe(callback, event_types) + + def create_device(self, type=types.QUEUE, **kwargs) -> Device: + """ + Create a device object. + + Args: + type (int, optional): The type of device to create. Defaults to types.QUEUE. + **kwargs (Any): Arbitrary keyword arguments for the class constructor. + + Returns: + Device: The created device object. + + Raises: + ValueError: If the device type is invalid. + """ + if cls := _mapping.get(type): + dev = cls(**kwargs) + self.register_device(dev) + return dev + raise ValueError("Invalid device type") + + def register_device(self, dev): + """ + Register a device to be polled. + + Args: + dev (Device): The device object to register. + """ + dev.broker = self + self.devices.append(dev) + + def unregister_device(self, dev): + """ + Unregister a device. + + Args: + dev (Device): The device object to unregister. + """ + if dev in self.devices: + self.devices.remove(dev) + dev.broker = None + + @property + def quit_func(self): + """ + The function to call when the window close button is clicked. + """ + return self._quit_func + + @quit_func.setter + def quit_func(self, value): + """ + Sets the function to call when the window close button is clicked. + + Args: + value (function): The function to call when the window close button is clicked. + """ + if not callable(value): + raise ValueError("quit_func must be callable") + self._quit_func = value + + def quit(self): + """ + Call the quit function. + """ + self._quit_func() + + def _poll(self): + """ + Polls the registered devices for events. + + Returns: + object: The event object if an event is received, otherwise None. + """ + for device in self.devices: + if (event := device.poll()) is not None: + if callback_list := self._device_callbacks.get(device.type): + for func in callback_list(): + func(event) + return event + return None + + +class QueueDevice(Device): + """ + Represents a queue device. + + Attributes: + type (str): The type of the device. + responses (list): The list of events that the device can respond to. + """ + + type = types.QUEUE + responses = events.filter + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self._data2 is None: + self._data2 = events.filter + if hasattr(self._data, "touch_scale"): + self.scale = self._data.touch_scale + else: + self.scale = 1 + + def _poll(self): + """ + Polls the device for events. + + Returns: + Event or None: The next event from the device, or None if no event is available. + """ + if (event := self._read()) is not None: + if event.type in self._data2: + if event.type in ( + events.MOUSEMOTION, + events.MOUSEBUTTONDOWN, + events.MOUSEBUTTONUP, + ): + if (scale := self.scale) != 1: + event.pos = ( + int(event.pos[0] // scale), + int(event.pos[1] // scale), + ) + if event.type == events.MOUSEMOTION: + event.rel = (event.rel[0] // scale, event.rel[1] // scale) + return event + return None + + def peek(self) -> bool: + """ + Peek at the next event in the queue without removing it. + + Returns: + bool: True if there is an event in the queue that matches the filter in self._data, otherwise False. + Note: self._data defaults to events.filter but may be set to a different list. + """ + return self._read2(self._data2) + + +class TouchDevice(Device): + """ + Represents a touch input device. + + This class handles touch input events and provides methods to read touch data + from the underlying touch driver. It supports reporting mouse button 1 events + such as mouse motion, mouse button down, and mouse button up. + + Attributes: + type (str): The type of the device (set to types.TOUCH). + responses (tuple): The supported event types for the device. + + Args: + *args (Any): Variable length argument list. + **kwargs (Any): Arbitrary keyword arguments. + """ + + type = types.TOUCH + responses = (events.MOUSEMOTION, events.MOUSEBUTTONDOWN, events.MOUSEBUTTONUP) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self._data is None: + raise ValueError("TouchDevice requires a display device as 'data'") + if self._data2 is None: # self._data is a rotation table + self._data2 = _DEFAULT_TOUCH_ROTATION_TABLE + self._data.touch_device = self + self.rotation = self._data.rotation + + @property + def rotation(self): + """ + Get the rotation value of the touch device. + + Returns: + rotation (int): The rotation value in degrees. + """ + return self._rotation + + @rotation.setter + def rotation(self, value): + """ + Set the rotation value of the touch device. + + Args: + value (int): The rotation value in degrees. + """ + self._rotation = value % 360 + + # _mask is an integer from 0 to 7 (or 0b001 to 0b111, 3 bits) + # Currently, bit 2 = invert_y, bit 1 is invert_x and bit 0 is swap_xy, but that may change. + self._mask = self._data2[self._rotation // 90] + + @property + def rotation_table(self): + """ + Get the rotation table of the touch device. + + Returns: + list: The rotation table. + """ + return self._data2 + + @rotation_table.setter + def rotation_table(self, value): + """ + Set the rotation table of the touch device. + + Args: + value (list): The rotation table. + """ + self._data2 = value + + def _poll(self): + """ + Poll the touch device for touch events. + + Returns: + Event: The touch event generated by the touch device. + """ + try: # If called too quickly, the touch driver may raise OSError: [Errno 116] ETIMEDOUT + touched = self._read() + except OSError: + return None + if touched: + last_pos = self._state + # If it looks like a point, use it, otherwise get the first point out of the list / tuple + (x, y, *_) = touched if isinstance(touched[0], int) else touched[0] + + if self._mask & SWAP_XY: + x, y = y, x + if self._mask & REVERSE_X: + x = self._data.width - x - 1 + if self._mask & REVERSE_Y: + y = self._data.height - y - 1 + self._state = (x, y) + if last_pos is not None: + last_x, last_y = last_pos + return events.Motion( + events.MOUSEMOTION, + self._state, + (x - last_x, y - last_y), + (1, 0, 0), + False, + None, + ) + else: + return events.Button(events.MOUSEBUTTONDOWN, self._state, 1, False, None) + elif self._state is not None: + last_pos = self._state + self._state = None + return events.Button(events.MOUSEBUTTONUP, last_pos, 1, False, None) + return None + + +class EncoderDevice(Device): + """ + A class representing an encoder device. + + Attributes: + type (str): The type of the device (ENCODER). + responses (tuple): The events that the device can respond to (MOUSEWHEEL, MOUSEBUTTONDOWN, MOUSEBUTTONUP). + """ + + type = types.ENCODER + responses = (events.MOUSEWHEEL, events.MOUSEBUTTONDOWN, events.MOUSEBUTTONUP) + + def __init__(self, *args, **kwargs): + """ + Initializes a new instance of the EncoderDevice class. + + Args: + *args (Any): Variable length argument list. + **kwargs (Any): Arbitrary keyword arguments. + + Notes: + - self._data is the mouse button number to report for the switch. + Default is 2 (middle mouse button). If the mouse button number is even, + the wheel will report vertical (y) movement. If the mouse button number is odd, + the wheel will report horizontal (x) movement. This corresponds to a typical mouse + wheel being button 2 and the wheel moving vertically. It also corresponds to + scrolling horizontally on a touchpad with two-finger scrolling and using the right button. + """ + super().__init__(*args, **kwargs) + self._state = (0, False) # (position, pressed) + self._data = self._data if self._data else 2 # Default to middle mouse button + + def _poll(self): + """ + Polls the encoder device for changes and returns the corresponding event. + + Returns: + Event: The event generated by the encoder device, or None if no event occurred. + """ + last_pos, last_pressed = self._state + pressed = self._read2() + if pressed != last_pressed: + self._state = (last_pos, pressed) + return events.Button( + events.MOUSEBUTTONDOWN if pressed else events.MOUSEBUTTONUP, + (0, 0), + self._data, + False, + None, + ) + + pos = self._read() + if pos != last_pos: + steps = pos - last_pos + self._state = (pos, last_pressed) + if self._data % 2 == 0: + return events.Wheel(events.MOUSEWHEEL, False, 0, steps, 0, steps, False, None) + return events.Wheel(events.MOUSEWHEEL, False, steps, 0, steps, 0, False, None) + return None + + +class KeypadDevice(Device): + """ + Represents a keypad device. + + Attributes: + type (Devices): The type of the device (set to `types.KEYPAD`). + responses (tuple): The types of events that the device can respond to (set to `(events.KEYDOWN, events.KEYUP)`). + + Methods: + __init__: Initializes the KeypadDevice object. + _poll: Polls the keypad for key events. + """ + + type = types.KEYPAD + responses = (events.KEYDOWN, events.KEYUP) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._state = set() + + def _poll(self): + """ + Polls the keypad for key events. + + Returns: + events.Key or None: An instance of the `events.Key` class representing the key event, or `None` if no key event occurred. + """ + keys = set(self._read()) + released = self._state - keys + if released: + key = released.pop() + self._state.remove(key) + return events.Key(events.KEYUP, chr(key), key, 0, 0) + pressed = keys - self._state + if pressed: + key = pressed.pop() + self._state.add(key) + return events.Key(events.KEYDOWN, chr(key), key, 0, 0) + return None + + +class JoystickDevice(Device): + """ + Represents a joystick device. + + Attributes: + type (Devices): The type of the device, set to `types.JOYSTICK`. + responses (tuple): A tuple of event types that this device can respond to. + + Methods: + __init__(*args, **kwargs): Initializes the JoystickDevice instance. + _poll(): Polls the device for events. + + Raises: + NotImplementedError: If the `_poll` method is not implemented. + """ + + type = types.JOYSTICK + responses = ( + events.JOYAXISMOTION, + events.JOYBALLMOTION, + events.JOYHATMOTION, + events.JOYBUTTONDOWN, + events.JOYBUTTONUP, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _poll(self): + raise NotImplementedError("JoystickDevice.read() not implemented") + + + +class VirtualDevices: + class VirtualDevice: + def __init__(self, virtual_devices, device_type): + self._virtual_devices = virtual_devices + self.type = device_type + self.user_data = None + self._fifo = [] + + def subscribe(self, callback): + self._callback = callback + + def poll(self, *args): + self._virtual_devices.poll_queue_device() + event = self._fifo.pop(0) if self._fifo else None + self._callback(event, *args) + + def add_event(self, event): + self._fifo.append(event) + + def __init__(self, queue_device): + self._queue_device = queue_device + self._vd_touch = self.VirtualDevice(self, types.TOUCH) + self._vd_encoder = self.VirtualDevice(self, types.ENCODER) + self._vd_keypad = self.VirtualDevice(self, types.KEYPAD) + self.devices = [self._vd_touch, self._vd_encoder, self._vd_keypad] + + def poll_queue_device(self): + if e:= self._queue_device.poll(): + if e.type == events.MOUSEBUTTONDOWN or e.type == events.MOUSEBUTTONUP: + self._vd_touch.add_event(e) + elif e.type == events.MOUSEWHEEL: + self._vd_encoder.add_event(e) + elif e.type == events.KEYDOWN or e.type == events.KEYUP: + self._vd_keypad.add_event(e) + +_mapping = { + # Mapping of device types to device classes + types.BROKER: Broker, + types.QUEUE: QueueDevice, + types.TOUCH: TouchDevice, + types.ENCODER: EncoderDevice, + types.KEYPAD: KeypadDevice, + types.JOYSTICK: JoystickDevice, +} diff --git a/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py index da1fd4422..5e4820224 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_encoder_test.py @@ -26,7 +26,7 @@ def draw_line(): while True: if not (e := broker.poll()): continue - if e.type == broker.Events.MOUSEWHEEL: + if e.type == broker.events.MOUSEWHEEL: if e.y != 0: direction = factor if e.y > 0 else -factor delta = e.y * e.y * direction # Quadratic acceleration @@ -37,7 +37,7 @@ def draw_line(): delta = e.x * e.x * direction x_pos = (x_pos + delta) % w draw_line() - elif e.type == broker.Events.MOUSEBUTTONDOWN: + elif e.type == broker.events.MOUSEBUTTONDOWN: if e.button == 2: color_byte = color_byte << 1 & 0xFF if color_byte == 0: diff --git a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py index 60e7b97e7..f523d9aeb 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_simpletest.py @@ -7,7 +7,7 @@ async def main(): e = broker.poll() if e: print(e) - if e == broker.Events.QUIT: + if e == broker.events.QUIT: break await asyncio.sleep(0.001) diff --git a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py index 7ec3fa530..d51d7cac6 100644 --- a/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py +++ b/micropython/pydisplay/eventsys/examples/eventsys_touch_test.py @@ -6,7 +6,7 @@ """ from board_config import display_drv, broker -from eventsys.device import Types +from eventsys.devices import types from graphics import round_rect, text16 @@ -25,7 +25,7 @@ def set_rotation_table(table): if display_drv.touch_device is not None: - if display_drv.touch_device.type == Types.TOUCH: + if display_drv.touch_device.type == types.TOUCH: display_drv.touch_device.rotation_table = table @@ -67,7 +67,7 @@ def loop(): touched_point = None while not touched_point: event = broker.poll() - if event and event.type == broker.Events.MOUSEBUTTONDOWN and event.button == 1: + if event and event.type == broker.events.MOUSEBUTTONDOWN and event.button == 1: touched_point = event.pos zone = (touched_point[1] // half_height) * 2 + (touched_point[0] // half_width) touched_zones.append(zone) diff --git a/micropython/pydisplay/eventsys/manifest.py b/micropython/pydisplay/eventsys/manifest.py index 9bdb0ab9f..0b16ea038 100644 --- a/micropython/pydisplay/eventsys/manifest.py +++ b/micropython/pydisplay/eventsys/manifest.py @@ -1,8 +1,8 @@ metadata( description="PyDisplay eventsys", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-eventsys", + pypi_publish="eventsys", ) package("eventsys") diff --git a/micropython/pydisplay/graphics/README.md b/micropython/pydisplay/graphics/README.md index 18d23042b..5841c6112 100644 --- a/micropython/pydisplay/graphics/README.md +++ b/micropython/pydisplay/graphics/README.md @@ -26,7 +26,7 @@ WARNINGS: pydisplay is currently alpha quality. Every effort has been made to pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) ## Key Features @@ -86,7 +86,7 @@ Where possible, existing, proven APIs were used. - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. - All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. diff --git a/micropython/pydisplay/graphics/manifest.py b/micropython/pydisplay/graphics/manifest.py index 4b4b10ffd..24e620bcf 100644 --- a/micropython/pydisplay/graphics/manifest.py +++ b/micropython/pydisplay/graphics/manifest.py @@ -1,8 +1,8 @@ metadata( description="PyDisplay graphics", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-graphics", + pypi_publish="graphics", ) package("graphics") diff --git a/micropython/pydisplay/multimer/README.md b/micropython/pydisplay/multimer/README.md new file mode 100644 index 000000000..5841c6112 --- /dev/null +++ b/micropython/pydisplay/multimer/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/multimer/manifest.py b/micropython/pydisplay/multimer/manifest.py new file mode 100644 index 000000000..d471312af --- /dev/null +++ b/micropython/pydisplay/multimer/manifest.py @@ -0,0 +1,8 @@ +metadata( + description="PyDisplay multimer", + version="0.0.1", + author="Brad Barnett ", + license="MIT", + pypi_publish="multimer", +) +package("multimer") diff --git a/micropython/pydisplay/multimer/multimer/__init__.py b/micropython/pydisplay/multimer/multimer/__init__.py new file mode 100644 index 000000000..5fde589c5 --- /dev/null +++ b/micropython/pydisplay/multimer/multimer/__init__.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +`multimer` +==================================================== + +Cross-platform Timer class for *Python. + +Enables using 'from multimer import Timer' on MicroPython on microcontrollers, +on MicroPython on Unix (which doesn't have a machine.Timer) and CPython (ditto). + +_librt.py uses uses MicroPython ffi to connect to libc and librt, while _sdl2.py uses +SDL2 on CPython to connect to libSDL2. No compatibility for CircuitPython yet. + +Returns None if the platform is not supported rather than raising an ImportError so that +the client can handle the error more gracefully (e.g. by using `if Timer is not None:`). + +Usage: + from multimer import Timer + tim = Timer() + tim.init(mode=Timer.PERIODIC, period=500, callback=lambda t: print(".")) + .... + tim.deinit() +""" + +import sys + +try: + from machine import Timer # MicroPython on microcontrollers +except ImportError: + if sys.implementation.name == "micropython": # MicroPython on Unix + from ._librt import Timer + elif sys.implementation.name == "cpython": # Big Python + from ._sdl2 import Timer + else: + Timer = None + +_next_timer_id = 1 + + +def get_timer(callback, period=33): + """ + Creates and returns a timer to periodically call the callback function + + Args: + callback (function): The function to call periodically + period (int): The period in milliseconds, default is 33ms (30fps) + """ + global _next_timer_id + if sys.platform == "rp2": + id = -1 + else: + id = _next_timer_id + _next_timer_id += 1 + t = Timer(id) + t.init(mode=Timer.PERIODIC, period=period, callback=lambda t: callback()) + print(f"Timer: timer started ({id=}, {period=})") + return t diff --git a/micropython/pydisplay/multimer/multimer/_librt.py b/micropython/pydisplay/multimer/multimer/_librt.py new file mode 100644 index 000000000..4e7a0273c --- /dev/null +++ b/micropython/pydisplay/multimer/multimer/_librt.py @@ -0,0 +1,155 @@ +# Timer that matches machine.Timer (https://docs.micropython.org/en/latest/library/machine.Timer.html) +# for the unix port. +# +# MIT license; Copyright (c) 2021 Amir Gonnen, 2024 Brad Barnett +# +# Based on timer.py from micropython-lib (https://github.com/micropython/micropython-lib/blob/master/unix-ffi/machine/machine/timer.py) + +from ._timerbase import _TimerBase +import ffi +import uctypes +import array +import os + +# FFI libraries + +libc = ffi.open("libc.so.6") +try: + librt = ffi.open("librt.so") +except OSError: + librt = libc + + +# C constants + +CLOCK_REALTIME = 0 +CLOCK_MONOTONIC = 1 +SIGEV_SIGNAL = 0 + +# C structs + +sigaction_t = { + "sa_handler": (0 | uctypes.UINT64), + "sa_mask": (8 | uctypes.ARRAY, 16 | uctypes.UINT64), + "sa_flags": (136 | uctypes.INT32), + "sa_restorer": (144 | uctypes.PTR, uctypes.UINT8), +} + +sigval_t = { + "sival_int": 0 | uctypes.INT32, + "sival_ptr": (0 | uctypes.PTR, uctypes.UINT8), +} + +sigevent_t = { + "sigev_value": (0, sigval_t), + "sigev_signo": uctypes.sizeof(sigval_t) | uctypes.INT32, + "sigev_notify": (uctypes.sizeof(sigval_t) + 4) | uctypes.INT32, +} + +timespec_t = { + "tv_sec": 0 | uctypes.INT32, + "tv_nsec": 8 | uctypes.INT64, +} + +itimerspec_t = { + "it_interval": (0, timespec_t), + "it_value": (16, timespec_t), +} + +# C functions + +__libc_current_sigrtmin = libc.func("i", "__libc_current_sigrtmin", "") +SIGRTMIN = __libc_current_sigrtmin() + +timer_create_ = librt.func("i", "timer_create", "ipp") +timer_delete_ = librt.func("i", "timer_delete", "i") +timer_settime_ = librt.func("i", "timer_settime", "PiPp") + +sigaction_ = libc.func("i", "sigaction", "iPp") + +# Create a new C struct + + +def new(sdesc): + buf = bytearray(uctypes.sizeof(sdesc)) + s = uctypes.struct(uctypes.addressof(buf), sdesc, uctypes.NATIVE) + return s + + +# Posix Signal handling + + +def sigaction(signum, handler, flags=0): + sa = new(sigaction_t) + sa_old = new(sigaction_t) + cb = ffi.callback("v", handler, "i", lock=True) + sa.sa_handler = cb.cfun() + sa.sa_flags = flags + r = sigaction_(signum, sa, sa_old) + if r != 0: + raise RuntimeError("sigaction_ error: %d (errno = %d)" % (r, os.errno())) + return cb # sa_old.sa_handler + + +# Posix Timer handling + + +def timer_create(sig_id): + sev = new(sigevent_t) + # print(sev) + sev.sigev_notify = SIGEV_SIGNAL + sev.sigev_signo = SIGRTMIN + sig_id + timerid = array.array("P", [0]) + r = timer_create_(CLOCK_MONOTONIC, sev, timerid) + if r != 0: + raise RuntimeError("timer_create_ error: %d (errno = %d)" % (r, os.errno())) + # print("timerid", hex(timerid[0])) + return timerid[0] + + +def timer_delete(tid): + r = timer_delete_(tid) + if r != 0: + raise RuntimeError("timer_delete_ error: %d (errno = %d)" % (r, os.errno())) + + +def timer_settime(tid, period_ms, periodic): + period_ns = (period_ms * 1000000) % 1000000000 + period_sec = (period_ms * 1000000) // 1000000000 + + new_val = new(itimerspec_t) + new_val.it_value.tv_sec = period_sec + new_val.it_value.tv_nsec = period_ns + if periodic: + new_val.it_interval.tv_sec = period_sec + new_val.it_interval.tv_nsec = period_ns + # print("new_val:", bytes(new_val)) + old_val = new(itimerspec_t) + # print(new_val, old_val) + r = timer_settime_(tid, 0, new_val, old_val) + if r != 0: + raise RuntimeError("timer_settime_ error: %d (errno = %d)" % (r, os.errno())) + # print("old_val:", bytes(old_val)) + + +# Timer class + + +class Timer(_TimerBase): + """librt Timer class""" + + def _start(self): + self.id = ( + self.id if self.id != -1 else 0xF + ) # id must be non-negative, so we use 0xF as a default + self._timer = timer_create(self.id) + timer_settime(self._timer, self._interval, self._mode == Timer.PERIODIC) + self._handler_ref = self._handler + self._action = sigaction(SIGRTMIN + self.id, self._handler_ref) + + def _stop(self): + # timer_settime(self._timer, 0, False) + timer_delete(self._timer) + self._timer = None + self._action = None + self._handler_ref = None diff --git a/micropython/pydisplay/multimer/multimer/_sdl2.py b/micropython/pydisplay/multimer/multimer/_sdl2.py new file mode 100644 index 000000000..e011662ca --- /dev/null +++ b/micropython/pydisplay/multimer/multimer/_sdl2.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +""" +Timer using SDL2 for CPython with the same API as machine.Timer in MicroPython. +""" + +from ._timerbase import _TimerBase +import ctypes +from sys import platform + + +if platform == "win32": + _libSDL2 = ctypes.CDLL("SDL2.dll") +else: + _libSDL2 = ctypes.CDLL("libSDL2-2.0.so.0") + +SDL_INIT_TIMER = 0x00000001 + +_libSDL2.SDL_Init.argtypes = [ctypes.c_uint] +_libSDL2.SDL_Init.restype = ctypes.c_int +SDL_Init = _libSDL2.SDL_Init + +_libSDL2.SDL_AddTimer.argtypes = [ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p] +_libSDL2.SDL_AddTimer.restype = ctypes.c_void_p +SDL_AddTimer = _libSDL2.SDL_AddTimer + +_libSDL2.SDL_RemoveTimer.argtypes = [ctypes.c_void_p] +_libSDL2.SDL_RemoveTimer.restype = ctypes.c_int +SDL_RemoveTimer = _libSDL2.SDL_RemoveTimer + +SDL_TimerCallback = ctypes.CFUNCTYPE(ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p) + + +class Timer(_TimerBase): + """SDL2 Timer class""" + + def _start(self): + SDL_Init(SDL_INIT_TIMER) + self._handler_ref = self._handler + self._tcb = SDL_TimerCallback(self._handler_ref) + self._timer = SDL_AddTimer(self._interval, self._tcb, None) + + def _stop(self): + if self._timer: + SDL_RemoveTimer(self._timer) + self._timer = None + self._tcb = None + self._handler_ref = None diff --git a/micropython/pydisplay/multimer/multimer/_timerbase.py b/micropython/pydisplay/multimer/multimer/_timerbase.py new file mode 100644 index 000000000..5562abf9b --- /dev/null +++ b/micropython/pydisplay/multimer/multimer/_timerbase.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2024 Brad Barnett +# +# SPDX-License-Identifier: MIT +try: + from micropython import const, schedule +except ImportError: + + def const(x): + return x + + def schedule(cb, interval): + cb(interval) + + +class _TimerBase: + """ + A class to create a timer with the same API and similar functionality to + MicroPython's machine.Timer class. + """ + + PERIODIC = const(0) + ONE_SHOT = const(1) + + def __init__(self, id=-1, **kwargs): + """ + Initializes the timer with the given parameters. + + Args: + id (int): The timer ID (default is -1). + **kwargs: Additional keyword arguments. + """ + self.id = id + self._busy = False + self._timer = None + if kwargs: + self.init(**kwargs) + + def init(self, *, mode, freq=-1, period=-1, callback=None): + """ + Initialize the timer. + + Args: + mode (int): Timer mode (Timer.ONE_SHOT or Timer.PERIODIC). + freq (int, optional): Timer frequency in Hz. Defaults to -1. + period (int, optional): Timer period in milliseconds. Ignored if freq is specified. Defaults to -1. + callback (callable, optional): Callable to execute upon timer expiration. Defaults to None. + + Raises: + ValueError: If an invalid timer mode or interval is provided. + """ + if mode in (self.ONE_SHOT, self.PERIODIC): + self._mode = mode + else: + raise ValueError("Invalid timer mode") + + self._interval = int(1000 / freq) if freq > 0 else period + if self._interval < 1: + raise ValueError("Invalid freq or period") + + self._callback = callback + self._start() # _start() is implemented in subclasses + + def deinit(self): + """ + Deinitializes the timer. + """ + while self._busy: + pass + + self._stop() # _stop() is implemented in subclasses + self._mode = None + self._interval = 0 + self._callback = None + self._timer = None + + def _handler(self, interval, param=None): + """ + Internal callback function called when the timer expires. + SDL2 timers call the handler with the interval and a user-defined parameter, + while librt timers call the handler with the interval only. + They are ignored here. + + Args: + interval (int): The interval at which the timer expires. + param: User-defined parameter (ignored). + + Returns: + int: The next interval for SDL2 timers, 0 for one-shot timers. + """ + if self._busy: + return + + self._busy = True + try: + schedule(self._callback, 0) + except RuntimeError: # MicroPython raises RuntimeError if the schedule queue is full + pass + self._busy = False + + if self._mode == self.ONE_SHOT: + self.deinit() + return 0 # SDL2 expects the callback to return the next interval, 0 for one-shot + return self._interval + + def _start(self): + """ + Starts the timer. Must be implemented by subclasses. + + Raises: + NotImplementedError: If not implemented by subclass. + """ + raise NotImplementedError("Subclasses must implement this method") + + def _stop(self): + """ + Stops the timer. Must be implemented by subclasses. + + Raises: + NotImplementedError: If not implemented by subclass. + """ + raise NotImplementedError("Subclasses must implement this method") diff --git a/micropython/pydisplay/palettes/README.md b/micropython/pydisplay/palettes/README.md index 18d23042b..5841c6112 100644 --- a/micropython/pydisplay/palettes/README.md +++ b/micropython/pydisplay/palettes/README.md @@ -26,7 +26,7 @@ WARNINGS: pydisplay is currently alpha quality. Every effort has been made to pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) ## Key Features @@ -86,7 +86,7 @@ Where possible, existing, proven APIs were used. - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. - All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. diff --git a/micropython/pydisplay/palettes/manifest.py b/micropython/pydisplay/palettes/manifest.py index 4170bcddf..8fb3416c3 100644 --- a/micropython/pydisplay/palettes/manifest.py +++ b/micropython/pydisplay/palettes/manifest.py @@ -1,8 +1,8 @@ metadata( description="PyDisplay palettes", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", - pypi_publish="pydisplay-palettes", + pypi_publish="palettes", ) package("palettes") diff --git a/micropython/pydisplay/pydisplay-bundle/manifest.py b/micropython/pydisplay/pydisplay-bundle/manifest.py index c983c998f..ea028d4e5 100644 --- a/micropython/pydisplay/pydisplay-bundle/manifest.py +++ b/micropython/pydisplay/pydisplay-bundle/manifest.py @@ -1,6 +1,6 @@ metadata( description="PyDisplay bundle", - version="0.1.6", + version="0.0.1", author="Brad Barnett ", license="MIT", pypi_publish="pydisplay-bundle", @@ -8,12 +8,6 @@ require("displaybuf") require("eventsys") require("graphics") +require("multimer") require("palettes") -require("timer") require("displaysys") -require("displaysys-busdisplay") -require("displaysys-fbdisplay") -require("displaysys-jndisplay") -require("displaysys-pgdisplay") -require("displaysys-psdisplay") -require("displaysys-sdldisplay") From 9f3e44374ddcc5849a464ab130879164a2da08ba Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Wed, 27 Nov 2024 19:57:58 -0600 Subject: [PATCH 33/35] pydisplay: Package troubleshooting. Signed-off-by: Brad Barnett --- .../displaysys/pgdisplay.py | 20 +-- .../examples/board_config.py | 3 +- .../displaysys/sdldisplay/__init__.py | 49 ++---- .../sdldisplay/_sdl2_lib/_constants.py | 4 - .../sdldisplay/_sdl2_lib/_cpython.py | 8 - .../sdldisplay/_sdl2_lib/_micropython.py | 2 - .../examples/board_config.py | 3 +- .../displaysys/displaysys/__init__.py | 7 +- .../pydisplay/eventsys/eventsys/devices.py | 2 +- micropython/pydisplay/timer/README.md | 151 ----------------- .../timer/examples/timer_simpletest.py | 34 ---- micropython/pydisplay/timer/manifest.py | 8 - micropython/pydisplay/timer/timer/__init__.py | 59 ------- micropython/pydisplay/timer/timer/_librt.py | 155 ------------------ micropython/pydisplay/timer/timer/_sdl2.py | 49 ------ .../pydisplay/timer/timer/_timerbase.py | 121 -------------- 16 files changed, 25 insertions(+), 650 deletions(-) delete mode 100644 micropython/pydisplay/timer/README.md delete mode 100644 micropython/pydisplay/timer/examples/timer_simpletest.py delete mode 100644 micropython/pydisplay/timer/manifest.py delete mode 100644 micropython/pydisplay/timer/timer/__init__.py delete mode 100644 micropython/pydisplay/timer/timer/_librt.py delete mode 100644 micropython/pydisplay/timer/timer/_sdl2.py delete mode 100644 micropython/pydisplay/timer/timer/_timerbase.py diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py index a7be44ad1..eb936be09 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py @@ -15,7 +15,7 @@ pass -def poll() -> Optional[pg.event.Event | False]: +def poll(): """ Polls for an event and returns the event type and data. @@ -24,22 +24,6 @@ def poll() -> Optional[pg.event.Event | False]: """ return pg.event.poll() -def peek(eventtype: Optional[Union[int, Sequence[int]]] = None, pump: bool = True) -> bool: - """ - Check the event queue for messages. Returns True if there are any events of the given type waiting on the queue. - If a sequence of event types is passed, this will return True if any of those events are on the queue. - - If pump is True (the default), then pygame.event.pump() will be called. - - Args: - eventtype (Optional[Union[int, Sequence[int]]]): The event type(s) to check. Defaults to None. - pump (bool): Whether to call pygame.event.pump(). Defaults to True. - - Returns: - bool: True if there are any events of the given type waiting on the queue. - """ - return pg.event.peek(eventtype, pump) - class PGDisplay(DisplayDriver): """ @@ -262,7 +246,7 @@ def render(self, renderRect: Optional[pg.Rect] = None) -> None: bfaRect = pg.Rect(0, tfa + vsa, width, bfa) self._window.blit(buffer, bfaRect, bfaRect) - def show(self) -> None: + def show(self, param=None) -> None: """ Show the display. """ diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py index 1b50b8638..c913c137f 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/examples/board_config.py @@ -2,7 +2,7 @@ Board configuration for PyGame. """ -from displaysys.pgdisplay import PGDisplay as DTDisplay, poll, peek +from displaysys.pgdisplay import PGDisplay as DTDisplay, poll from eventsys import devices import sys @@ -26,7 +26,6 @@ type=devices.types.QUEUE, read=poll, data=display_drv, - read2=peek, # data2=events.filter, ) diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py index 584973962..1cec1d50a 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/__init__.py @@ -9,6 +9,7 @@ from displaysys import DisplayDriver, color_rgb from eventsys import events from sys import implementation +from micropython import schedule from ._sdl2_lib import ( SDL_Init, SDL_Quit, @@ -41,8 +42,6 @@ SDL_INIT_EVERYTHING, SDL_Rect, SDL_PollEvent, - SDL_PeepEvents, - SDL_PumpEvents, SDL_GetKeyName, SDL_Event, SDL_QUIT, @@ -55,9 +54,6 @@ SDL_MOUSEWHEEL, SDL_KEYDOWN, SDL_KEYUP, - SDL_PEEKEVENT, - SDL_FIRSTEVENT, - SDL_LASTEVENT, ) try: @@ -72,6 +68,20 @@ else: is_cpython = False +try: + from time import ticks_ms, ticks_add +except ImportError: + from adafruit_ticks import ticks_ms, ticks_add + + +def scheduler(param): + func, next_run, interval = param + if (current_time := ticks_ms()) >= next_run: + interval = func(interval) + next_run = ticks_add(current_time, interval) + if interval > 0: + schedule(scheduler, (func, next_run, interval)) + _event = SDL_Event() @@ -93,33 +103,6 @@ def poll() -> Optional[events]: return _convert(SDL_Event(_event)) return None -def peek(eventtype: Optional[Union[int, Sequence[int]]] = None, pump: bool = True) -> bool: - """ - Check the event queue for messages. Returns True if there are any events of the given type waiting on the queue. - If a sequence of event types is passed, this will return True if any of those events are on the queue. - - If pump is True (the default), then pygame.event.pump() will be called. - - Args: - eventtype (Optional[Union[int, Sequence[int]]]): The event type(s) to check. Defaults to None. - pump (bool): Whether to call pygame.event.pump(). Defaults to True. - - Returns: - bool: True if there are any events of the given type waiting on the queue. - """ - global _event - _event = SDL_Event() - if pump: - SDL_PumpEvents() - num_matching = SDL_PeepEvents(_event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - if num_matching == 0: - return False - if eventtype is None: - return True - if isinstance(eventtype, int): - return _event.type == eventtype - return _event.type in eventtype - def _convert(e): # Convert an SDL event to a Pygame event if e.type == SDL_MOUSEMOTION: @@ -169,7 +152,6 @@ def _convert(e): evt = events.Unknown(e.type) return evt - def retcheck(retvalue): # Check the return value of an SDL function and raise an exception if it's not 0 if retvalue: @@ -395,6 +377,7 @@ def _rotation_helper(self, value): value (int): The new rotation value. """ + print("here") if (angle := (value % 360) - (self._rotation % 360)) != 0: if implementation.name == "cpython": tempBuffer = SDL_CreateTexture( diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py index 07184158f..a7200073d 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_constants.py @@ -79,10 +79,6 @@ SDL_BUTTON_MMASK = const(1 << 1) # Middle mouse button SDL_BUTTON_RMASK = const(1 << 2) # Right mouse button -# Event constants -SDL_PEEKEVENT = const(0x1) -SDL_FIRSTEVENT = const(0x2) -SDL_LASTEVENT = const(0x3) ############################################################################### # SDL2 Pixel Formats # diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py index 88f4d7bf9..0b7d39506 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_cpython.py @@ -140,14 +140,6 @@ class Wheel(ctypes.Structure): _libSDL2.SDL_PollEvent.restype = ctypes.c_int SDL_PollEvent = _libSDL2.SDL_PollEvent -_libSDL2.SDL_PeepEvents.argtypes = [ctypes.POINTER(SDL_CommonEvent), ctypes.c_int, ctypes.c_int, ctypes.c_uint, ctypes.c_uint] -_libSDL2.SDL_PeepEvents.restype = ctypes.c_int -SDL_PeepEvents = _libSDL2.SDL_PeepEvents - -_libSDL2.SDL_PumpEvents.argtypes = [] -_libSDL2.SDL_PumpEvents.restype = None -SDL_PumpEvents = _libSDL2.SDL_PumpEvents - # SDL key functions _libSDL2.SDL_GetKeyName.argtypes = [ctypes.c_int] _libSDL2.SDL_GetKeyName.restype = ctypes.c_char_p diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py index f2df6b568..fda726fa3 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/displaysys/sdldisplay/_sdl2_lib/_micropython.py @@ -120,8 +120,6 @@ def SDL_Point(x=0, y=0): SDL_Quit = _libSDL2.func("v", "SDL_Quit", "") SDL_GetError = _libSDL2.func("s", "SDL_GetError", "") SDL_PollEvent = _libSDL2.func("i", "SDL_PollEvent", "P") -SDL_PeepEvents = _libSDL2.func("i", "SDL_PeepEvents", "Piiii") -SDL_PumpEvents = _libSDL2.func("v", "SDL_PumpEvents", "") # SDL key functions SDL_GetKeyName = _libSDL2.func("s", "SDL_GetKeyName", "i") diff --git a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py index a8261dfd1..49b3a2191 100644 --- a/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-sdldisplay/examples/board_config.py @@ -2,7 +2,7 @@ Combination board configuration for desktop, pyscript and jupyter notebook platforms. """ -from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll, peek +from displaysys.sdldisplay import SDLDisplay as DTDisplay, poll from eventsys import devices import sys @@ -25,7 +25,6 @@ type=devices.types.QUEUE, read=poll, data=display_drv, - read2=peek, # data2=events.filter, ) diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index 3656b1191..4712cc787 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -118,15 +118,15 @@ def __init__(self, auto_refresh=False): try: from multimer import get_timer - self._timer = get_timer(self.show) + self._timer = get_timer(self.show, period=33) except ImportError: raise ImportError("timer is required for auto_refresh") else: self._timer = None self.init() gc.collect() - print(f"{self.__class__.__name__} initialized.") - print(f"{self.__class__.__name__} requires_byteswap = {self.requires_byteswap}") + print(f"{self.__class__.__name__}: initialized.") + print(f"{self.__class__.__name__}: requires_byteswap = {self.requires_byteswap}") def __del__(self): self.deinit() @@ -171,6 +171,7 @@ def rotation(self, value) -> None: print(f"{self.__class__.__name__}.rotation(): Setting rotation to {value}") self._rotation_helper(value) + print("done setting rotation") self._rotation = value diff --git a/micropython/pydisplay/eventsys/eventsys/devices.py b/micropython/pydisplay/eventsys/eventsys/devices.py index 057f67371..7c439f3b5 100644 --- a/micropython/pydisplay/eventsys/eventsys/devices.py +++ b/micropython/pydisplay/eventsys/eventsys/devices.py @@ -439,7 +439,7 @@ def peek(self) -> bool: bool: True if there is an event in the queue that matches the filter in self._data, otherwise False. Note: self._data defaults to events.filter but may be set to a different list. """ - return self._read2(self._data2) + raise NotImplementedError("QueueDevice.peek() not implemented") class TouchDevice(Device): diff --git a/micropython/pydisplay/timer/README.md b/micropython/pydisplay/timer/README.md deleted file mode 100644 index 18d23042b..000000000 --- a/micropython/pydisplay/timer/README.md +++ /dev/null @@ -1,151 +0,0 @@ -logo - -

pydisplay

- -

Cross-platform User Interface and Event Drivers for *Python

- -

- About • - Key Features • - Getting Started • - Running Your First App • - API • - Roadmap • - Contributing • - Thanks • - Screenshots -

- -| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | -|-------------------------|--------------------------------| -| @peterhinch's active.py | @russhughes's tiny_toasters.py | - -## About - -WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. - -pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. - -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) - -## Key Features - -- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. -- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! -- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. -- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. -- Provides several built-in color palettes and a mechanism to generate your own palettes. -- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. -- Support MicroPython on microcontrollers and on Unix(-like) operating systems. -- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. -- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix - -## Getting Started - -This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. - - -## Running your first app - -You will need to import the `path.py` file before running any of the examples. - -On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: -``` -python3 -i path.py -``` -or -``` -micropython -i path.py -``` - -On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: -``` -import path -``` - -The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: -``` -import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension -``` - -To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: -``` -import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension -``` - -## API - -Where possible, existing, proven APIs were used. - -- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. - - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). - - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). - - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: - - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't - - On Windows, it is easier to install PyGame than SDL2 - - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. -- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. - - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. - - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. -- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. -- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. -- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. - - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) - - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. - - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) - - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). - - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. -- Graphics files may be used by 3 mechanisms: - - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. - - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. - - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. -- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: - - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. - - `path.py` - required in all circumstances - - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) - - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) - - `lv_config.py` - required for LVGL - - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. - - -## Roadmap - -- [ ] Much more documentation on Github -- [ ] Document the files to produce output for ReadTheDocs -- [ ] Implement EPaperDisplay -- [ ] Optimize with more Numpy and Viper code -- [ ] Decrease the memory footprint where possible -- [ ] Test with frozen modules -- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. -- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. -- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. -- [ ] Ensure multiple displays work at the same time -- [ ] Implement color depths other than 16 bit -- [ ] Add a Joystick class to eventsys -- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 -- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT - -## Contributing - -This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. -Don't forget to give the project a star! Thanks again! - -1. Fork the project -2. Clone it open the repository in command line -3. Create your feature branch (`git checkout -b feature/amazing-feature`) -4. Commit your changes (`git commit -m 'Add some amazing feature'`) -5. Push to the branch (`git push origin feature/amazing-feature`) -6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes - -## Thanks - -I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. - -## Why - -I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/timer/examples/timer_simpletest.py b/micropython/pydisplay/timer/examples/timer_simpletest.py deleted file mode 100644 index c62e42d97..000000000 --- a/micropython/pydisplay/timer/examples/timer_simpletest.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -This is a simple test script that tests the basic functionality of the timer class. - -It creates a periodic timer in a class instance and a one-shot timer that stops the periodic timer. -""" - -from timer import Timer -from sys import platform - - -class TimerTest: - def __init__(self): - self._tim = Timer(-1 if platform == "rp2" else 1) - - def start(self, period): - self._counter = 0 - self._tim.init(mode=Timer.PERIODIC, period=period, callback=self.do_something) - print("TimerTest: timer started...") - - def do_something(self, t): - self._counter += 1 - - def stop(self, t=None): - self._tim.deinit() - print(f"TimerTest: timer stopped after {self._counter:,} calls.") - - -# Create a timer that calls tt.do_something every 1ms -tt = TimerTest() -tt.start(1) - -# Create a timer that stops the first timer after 5 seconds -tim2 = Timer(-1 if platform == "rp2" else 2) -tim2.init(mode=Timer.ONE_SHOT, period=5000, callback=tt.stop) diff --git a/micropython/pydisplay/timer/manifest.py b/micropython/pydisplay/timer/manifest.py deleted file mode 100644 index 5a0c85d3d..000000000 --- a/micropython/pydisplay/timer/manifest.py +++ /dev/null @@ -1,8 +0,0 @@ -metadata( - description="PyDisplay timer", - version="0.1.6", - author="Brad Barnett ", - license="MIT", - pypi_publish="pydisplay-timer", -) -package("timer") diff --git a/micropython/pydisplay/timer/timer/__init__.py b/micropython/pydisplay/timer/timer/__init__.py deleted file mode 100644 index a6780f9c0..000000000 --- a/micropython/pydisplay/timer/timer/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT -""" -`timer` -==================================================== - -Cross-platform Timer class for *Python. - -Enables using 'from timer import Timer' on MicroPython on microcontrollers, -on MicroPython on Unix (which doesn't have a machine.Timer) and CPython (ditto). - -_librt.py uses uses MicroPython ffi to connect to libc and librt, while _sdl2.py uses -SDL2 on CPython to connect to libSDL2. No compatibility for CircuitPython yet. - -Returns None if the platform is not supported rather than raising an ImportError so that -the client can handle the error more gracefully (e.g. by using `if Timer is not None:`). - -Usage: - from timer import Timer - tim = Timer() - tim.init(mode=Timer.PERIODIC, period=500, callback=lambda t: print(".")) - .... - tim.deinit() -""" - -import sys - -try: - from machine import Timer # MicroPython on microcontrollers -except ImportError: - if sys.implementation.name == "micropython": # MicroPython on Unix - from ._librt import Timer - elif sys.implementation.name == "cpython": # Big Python - from ._sdl2 import Timer - else: - Timer = None - -_next_timer_id = 1 - - -def get_timer(callback, period=33): - """ - Creates and returns a timer to periodically call the callback function - - Args: - callback (function): The function to call periodically - period (int): The period in milliseconds, default is 33ms (30fps) - """ - global _next_timer_id - if sys.platform == "rp2": - id = -1 - else: - id = _next_timer_id - _next_timer_id += 1 - t = Timer(id) - t.init(mode=Timer.PERIODIC, period=period, callback=lambda t: callback()) - print(f"Timer: timer started ({id=}, {period=})") - return t diff --git a/micropython/pydisplay/timer/timer/_librt.py b/micropython/pydisplay/timer/timer/_librt.py deleted file mode 100644 index 4e7a0273c..000000000 --- a/micropython/pydisplay/timer/timer/_librt.py +++ /dev/null @@ -1,155 +0,0 @@ -# Timer that matches machine.Timer (https://docs.micropython.org/en/latest/library/machine.Timer.html) -# for the unix port. -# -# MIT license; Copyright (c) 2021 Amir Gonnen, 2024 Brad Barnett -# -# Based on timer.py from micropython-lib (https://github.com/micropython/micropython-lib/blob/master/unix-ffi/machine/machine/timer.py) - -from ._timerbase import _TimerBase -import ffi -import uctypes -import array -import os - -# FFI libraries - -libc = ffi.open("libc.so.6") -try: - librt = ffi.open("librt.so") -except OSError: - librt = libc - - -# C constants - -CLOCK_REALTIME = 0 -CLOCK_MONOTONIC = 1 -SIGEV_SIGNAL = 0 - -# C structs - -sigaction_t = { - "sa_handler": (0 | uctypes.UINT64), - "sa_mask": (8 | uctypes.ARRAY, 16 | uctypes.UINT64), - "sa_flags": (136 | uctypes.INT32), - "sa_restorer": (144 | uctypes.PTR, uctypes.UINT8), -} - -sigval_t = { - "sival_int": 0 | uctypes.INT32, - "sival_ptr": (0 | uctypes.PTR, uctypes.UINT8), -} - -sigevent_t = { - "sigev_value": (0, sigval_t), - "sigev_signo": uctypes.sizeof(sigval_t) | uctypes.INT32, - "sigev_notify": (uctypes.sizeof(sigval_t) + 4) | uctypes.INT32, -} - -timespec_t = { - "tv_sec": 0 | uctypes.INT32, - "tv_nsec": 8 | uctypes.INT64, -} - -itimerspec_t = { - "it_interval": (0, timespec_t), - "it_value": (16, timespec_t), -} - -# C functions - -__libc_current_sigrtmin = libc.func("i", "__libc_current_sigrtmin", "") -SIGRTMIN = __libc_current_sigrtmin() - -timer_create_ = librt.func("i", "timer_create", "ipp") -timer_delete_ = librt.func("i", "timer_delete", "i") -timer_settime_ = librt.func("i", "timer_settime", "PiPp") - -sigaction_ = libc.func("i", "sigaction", "iPp") - -# Create a new C struct - - -def new(sdesc): - buf = bytearray(uctypes.sizeof(sdesc)) - s = uctypes.struct(uctypes.addressof(buf), sdesc, uctypes.NATIVE) - return s - - -# Posix Signal handling - - -def sigaction(signum, handler, flags=0): - sa = new(sigaction_t) - sa_old = new(sigaction_t) - cb = ffi.callback("v", handler, "i", lock=True) - sa.sa_handler = cb.cfun() - sa.sa_flags = flags - r = sigaction_(signum, sa, sa_old) - if r != 0: - raise RuntimeError("sigaction_ error: %d (errno = %d)" % (r, os.errno())) - return cb # sa_old.sa_handler - - -# Posix Timer handling - - -def timer_create(sig_id): - sev = new(sigevent_t) - # print(sev) - sev.sigev_notify = SIGEV_SIGNAL - sev.sigev_signo = SIGRTMIN + sig_id - timerid = array.array("P", [0]) - r = timer_create_(CLOCK_MONOTONIC, sev, timerid) - if r != 0: - raise RuntimeError("timer_create_ error: %d (errno = %d)" % (r, os.errno())) - # print("timerid", hex(timerid[0])) - return timerid[0] - - -def timer_delete(tid): - r = timer_delete_(tid) - if r != 0: - raise RuntimeError("timer_delete_ error: %d (errno = %d)" % (r, os.errno())) - - -def timer_settime(tid, period_ms, periodic): - period_ns = (period_ms * 1000000) % 1000000000 - period_sec = (period_ms * 1000000) // 1000000000 - - new_val = new(itimerspec_t) - new_val.it_value.tv_sec = period_sec - new_val.it_value.tv_nsec = period_ns - if periodic: - new_val.it_interval.tv_sec = period_sec - new_val.it_interval.tv_nsec = period_ns - # print("new_val:", bytes(new_val)) - old_val = new(itimerspec_t) - # print(new_val, old_val) - r = timer_settime_(tid, 0, new_val, old_val) - if r != 0: - raise RuntimeError("timer_settime_ error: %d (errno = %d)" % (r, os.errno())) - # print("old_val:", bytes(old_val)) - - -# Timer class - - -class Timer(_TimerBase): - """librt Timer class""" - - def _start(self): - self.id = ( - self.id if self.id != -1 else 0xF - ) # id must be non-negative, so we use 0xF as a default - self._timer = timer_create(self.id) - timer_settime(self._timer, self._interval, self._mode == Timer.PERIODIC) - self._handler_ref = self._handler - self._action = sigaction(SIGRTMIN + self.id, self._handler_ref) - - def _stop(self): - # timer_settime(self._timer, 0, False) - timer_delete(self._timer) - self._timer = None - self._action = None - self._handler_ref = None diff --git a/micropython/pydisplay/timer/timer/_sdl2.py b/micropython/pydisplay/timer/timer/_sdl2.py deleted file mode 100644 index e011662ca..000000000 --- a/micropython/pydisplay/timer/timer/_sdl2.py +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT -""" -Timer using SDL2 for CPython with the same API as machine.Timer in MicroPython. -""" - -from ._timerbase import _TimerBase -import ctypes -from sys import platform - - -if platform == "win32": - _libSDL2 = ctypes.CDLL("SDL2.dll") -else: - _libSDL2 = ctypes.CDLL("libSDL2-2.0.so.0") - -SDL_INIT_TIMER = 0x00000001 - -_libSDL2.SDL_Init.argtypes = [ctypes.c_uint] -_libSDL2.SDL_Init.restype = ctypes.c_int -SDL_Init = _libSDL2.SDL_Init - -_libSDL2.SDL_AddTimer.argtypes = [ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p] -_libSDL2.SDL_AddTimer.restype = ctypes.c_void_p -SDL_AddTimer = _libSDL2.SDL_AddTimer - -_libSDL2.SDL_RemoveTimer.argtypes = [ctypes.c_void_p] -_libSDL2.SDL_RemoveTimer.restype = ctypes.c_int -SDL_RemoveTimer = _libSDL2.SDL_RemoveTimer - -SDL_TimerCallback = ctypes.CFUNCTYPE(ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p) - - -class Timer(_TimerBase): - """SDL2 Timer class""" - - def _start(self): - SDL_Init(SDL_INIT_TIMER) - self._handler_ref = self._handler - self._tcb = SDL_TimerCallback(self._handler_ref) - self._timer = SDL_AddTimer(self._interval, self._tcb, None) - - def _stop(self): - if self._timer: - SDL_RemoveTimer(self._timer) - self._timer = None - self._tcb = None - self._handler_ref = None diff --git a/micropython/pydisplay/timer/timer/_timerbase.py b/micropython/pydisplay/timer/timer/_timerbase.py deleted file mode 100644 index 5562abf9b..000000000 --- a/micropython/pydisplay/timer/timer/_timerbase.py +++ /dev/null @@ -1,121 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT -try: - from micropython import const, schedule -except ImportError: - - def const(x): - return x - - def schedule(cb, interval): - cb(interval) - - -class _TimerBase: - """ - A class to create a timer with the same API and similar functionality to - MicroPython's machine.Timer class. - """ - - PERIODIC = const(0) - ONE_SHOT = const(1) - - def __init__(self, id=-1, **kwargs): - """ - Initializes the timer with the given parameters. - - Args: - id (int): The timer ID (default is -1). - **kwargs: Additional keyword arguments. - """ - self.id = id - self._busy = False - self._timer = None - if kwargs: - self.init(**kwargs) - - def init(self, *, mode, freq=-1, period=-1, callback=None): - """ - Initialize the timer. - - Args: - mode (int): Timer mode (Timer.ONE_SHOT or Timer.PERIODIC). - freq (int, optional): Timer frequency in Hz. Defaults to -1. - period (int, optional): Timer period in milliseconds. Ignored if freq is specified. Defaults to -1. - callback (callable, optional): Callable to execute upon timer expiration. Defaults to None. - - Raises: - ValueError: If an invalid timer mode or interval is provided. - """ - if mode in (self.ONE_SHOT, self.PERIODIC): - self._mode = mode - else: - raise ValueError("Invalid timer mode") - - self._interval = int(1000 / freq) if freq > 0 else period - if self._interval < 1: - raise ValueError("Invalid freq or period") - - self._callback = callback - self._start() # _start() is implemented in subclasses - - def deinit(self): - """ - Deinitializes the timer. - """ - while self._busy: - pass - - self._stop() # _stop() is implemented in subclasses - self._mode = None - self._interval = 0 - self._callback = None - self._timer = None - - def _handler(self, interval, param=None): - """ - Internal callback function called when the timer expires. - SDL2 timers call the handler with the interval and a user-defined parameter, - while librt timers call the handler with the interval only. - They are ignored here. - - Args: - interval (int): The interval at which the timer expires. - param: User-defined parameter (ignored). - - Returns: - int: The next interval for SDL2 timers, 0 for one-shot timers. - """ - if self._busy: - return - - self._busy = True - try: - schedule(self._callback, 0) - except RuntimeError: # MicroPython raises RuntimeError if the schedule queue is full - pass - self._busy = False - - if self._mode == self.ONE_SHOT: - self.deinit() - return 0 # SDL2 expects the callback to return the next interval, 0 for one-shot - return self._interval - - def _start(self): - """ - Starts the timer. Must be implemented by subclasses. - - Raises: - NotImplementedError: If not implemented by subclass. - """ - raise NotImplementedError("Subclasses must implement this method") - - def _stop(self): - """ - Stops the timer. Must be implemented by subclasses. - - Raises: - NotImplementedError: If not implemented by subclass. - """ - raise NotImplementedError("Subclasses must implement this method") From d150f8db5b342a15f49e5ff200dcb49dc1bb0ef4 Mon Sep 17 00:00:00 2001 From: Brad Barnett Date: Thu, 28 Nov 2024 00:30:39 -0600 Subject: [PATCH 34/35] pydisplay: Packaging changes. Signed-off-by: Brad Barnett --- micropython/pydisplay/displaybuf/README.md | 2 +- .../displaysys/pgdisplay.py | 2 +- .../pydisplay/displaysys/displaysys/README.md | 2 +- .../displaysys/displaysys/README.md | 151 ---- .../examples/displaysys_block_test.py | 2 +- .../examples/displaysys_fill_rect_test.py | 2 +- micropython/pydisplay/eventsys/README.md | 2 +- .../pydisplay/eventsys/eventsys/device.py | 692 ------------------ .../pydisplay/eventsys/eventsys/devices.py | 6 +- micropython/pydisplay/graphics/README.md | 2 +- .../pydisplay/graphics/graphics/_font.py | 14 +- .../graphics/{font_8x14.py => _font_8x14.py} | 0 .../graphics/{font_8x16.py => _font_8x16.py} | 0 .../graphics/{font_8x8.py => _font_8x8.py} | 0 micropython/pydisplay/multimer/README.md | 2 +- micropython/pydisplay/palettes/README.md | 2 +- 16 files changed, 19 insertions(+), 862 deletions(-) delete mode 100644 micropython/pydisplay/displaysys/displaysys/displaysys/README.md delete mode 100644 micropython/pydisplay/eventsys/eventsys/device.py rename micropython/pydisplay/graphics/graphics/{font_8x14.py => _font_8x14.py} (100%) rename micropython/pydisplay/graphics/graphics/{font_8x16.py => _font_8x16.py} (100%) rename micropython/pydisplay/graphics/graphics/{font_8x8.py => _font_8x8.py} (100%) diff --git a/micropython/pydisplay/displaybuf/README.md b/micropython/pydisplay/displaybuf/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/displaybuf/README.md +++ b/micropython/pydisplay/displaybuf/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: diff --git a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py index eb936be09..2a4fb524c 100644 --- a/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py +++ b/micropython/pydisplay/displaysys/displaysys-pgdisplay/displaysys/pgdisplay.py @@ -15,7 +15,7 @@ pass -def poll(): +def poll() -> Optional[pg.event.Event]: """ Polls for an event and returns the event type and data. diff --git a/micropython/pydisplay/displaysys/displaysys/README.md b/micropython/pydisplay/displaysys/displaysys/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/displaysys/displaysys/README.md +++ b/micropython/pydisplay/displaysys/displaysys/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/README.md b/micropython/pydisplay/displaysys/displaysys/displaysys/README.md deleted file mode 100644 index 18d23042b..000000000 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/README.md +++ /dev/null @@ -1,151 +0,0 @@ -logo - -

pydisplay

- -

Cross-platform User Interface and Event Drivers for *Python

- -

- About • - Key Features • - Getting Started • - Running Your First App • - API • - Roadmap • - Contributing • - Thanks • - Screenshots -

- -| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | -|-------------------------|--------------------------------| -| @peterhinch's active.py | @russhughes's tiny_toasters.py | - -## About - -WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. - -pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. - -It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [timer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/timer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) - -## Key Features - -- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. -- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! -- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. -- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. -- Provides several built-in color palettes and a mechanism to generate your own palettes. -- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. -- Support MicroPython on microcontrollers and on Unix(-like) operating systems. -- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. -- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix - -## Getting Started - -This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. - - -## Running your first app - -You will need to import the `path.py` file before running any of the examples. - -On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: -``` -python3 -i path.py -``` -or -``` -micropython -i path.py -``` - -On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: -``` -import path -``` - -The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: -``` -import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension -``` - -To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: -``` -import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension -``` - -## API - -Where possible, existing, proven APIs were used. - -- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. - - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). - - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). - - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: - - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't - - On Windows, it is easier to install PyGame than SDL2 - - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) - - JNDisplay for Jupyter Notebooks. No input devices are currently supported. - - PSDisplay for PyScript. Only touchscreens are currently supported. -- Names of Events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. -- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API - - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. - - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. - - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. - - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. -- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. -- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. -- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. - - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) - - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. - - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) - - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). - - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. -- Graphics files may be used by 3 mechanisms: - - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. - - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. - - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. -- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: - - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. - - `path.py` - required in all circumstances - - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) - - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) - - `lv_config.py` - required for LVGL - - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. - - -## Roadmap - -- [ ] Much more documentation on Github -- [ ] Document the files to produce output for ReadTheDocs -- [ ] Implement EPaperDisplay -- [ ] Optimize with more Numpy and Viper code -- [ ] Decrease the memory footprint where possible -- [ ] Test with frozen modules -- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. -- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. -- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. -- [ ] Ensure multiple displays work at the same time -- [ ] Implement color depths other than 16 bit -- [ ] Add a Joystick class to eventsys -- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 -- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT - -## Contributing - -This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. -Don't forget to give the project a star! Thanks again! - -1. Fork the project -2. Clone it open the repository in command line -3. Create your feature branch (`git checkout -b feature/amazing-feature`) -4. Commit your changes (`git commit -m 'Add some amazing feature'`) -5. Push to the branch (`git push origin feature/amazing-feature`) -6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes - -## Thanks - -I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. - -## Why - -I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py index 467efef62..b6afb8f49 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_block_test.py @@ -46,7 +46,7 @@ def main(): ) count += 1 if count % 2000 == 0: - print("blocks/sec:", count / (time.time() - start_time)) + print(f"\rblocks/sec: {(count / (time.time() - start_time)):5.2f}", end="") main() diff --git a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py index 5f5f1554e..dc16a2a09 100644 --- a/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py +++ b/micropython/pydisplay/displaysys/displaysys/examples/displaysys_fill_rect_test.py @@ -34,7 +34,7 @@ def main(): ) count += 1 if count % 1000 == 0: - print("blocks/sec:", count / (time.time() - start_time)) + print(f"\rblocks/sec: {(count / (time.time() - start_time)):5.2f}", end="") main() diff --git a/micropython/pydisplay/eventsys/README.md b/micropython/pydisplay/eventsys/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/eventsys/README.md +++ b/micropython/pydisplay/eventsys/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: diff --git a/micropython/pydisplay/eventsys/eventsys/device.py b/micropython/pydisplay/eventsys/eventsys/device.py deleted file mode 100644 index a8d863917..000000000 --- a/micropython/pydisplay/eventsys/eventsys/device.py +++ /dev/null @@ -1,692 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Brad Barnett -# -# SPDX-License-Identifier: MIT - -""" -`eventsys.device` -==================================================== - -Device classes for eventsys's Event System. May also be used -with other applications. Devices are objects that poll for events -and return them. They can be subscribed to and unsubscribed from -to receive events. - -Devices can be created with Broker.create_device() or by calling the -constructor of the device class directly. Devices can be -subscribed to with .subscribe() and unsubscribed from with -.unsubscribe(). Devices can be polled for events with .poll(). -Devices can be registered with a broker device with .register_device() -and unregistered with .unregister_device(). Devices can be chained -together by setting the .broker property of a device to another device. - -Devices can be created with the following types: -- Types.BROKER: A device that polls multiple devices. -- Types.QUEUE: A device that returns multiple types of events. -- Types.TOUCH: A device that returns MOUSEBUTTONDOWN when touched, - MOUSEMOTION when moved and MOUSEBUTTONUP when released. -- Types.ENCODER: A device that returns MOUSEWHEEL events when turned, - MOUSEBUTTONDOWN when pressed. -- Types.KEYPAD: A device that returns KEYDOWN and KEYUP events when - keys are pressed or released. -- Types.JOYSTICK: A device that returns joystick events (not implemented). -""" - -from micropython import const -from . import Events -from sys import exit - - -_DEFAULT_TOUCH_ROTATION_TABLE = (0b000, 0b101, 0b110, 0b011) - -SWAP_XY = const(0b001) -REVERSE_X = const(0b010) -REVERSE_Y = const(0b100) - - -class Types: - """ - Device types for the Event System. - """ - - UNDEFINED = const(-1) - BROKER = const(0x00) - QUEUE = const(0x01) - TOUCH = const(0x02) - ENCODER = const(0x03) - KEYPAD = const(0x04) - JOYSTICK = const(0x05) - - @staticmethod - def new_type(type_name, responses): - """ - Create a new device type with a list of responses. - - Args: - type_name (str): The name of the device type. - responses (list[int]): A list of event types that the device can return. - - Returns: - (Device): The newly created device type. - - Raises: - ValueError: If `type_name` is not a string, `responses` is not a list, or any response is not an integer. - ValueError: If a device type with the same name already exists in the `Devices` class. - ValueError: If a device class with the same name already exists. - - Example: - To create the KEYPAD device type and `KeypadDevice` class: - - ```python - from eventsys.device import Types - from eventsys import Events - - KeypadDevice = Types.new_type("KEYPAD", [Events.KEYDOWN, Events.KEYUP]) - ``` - """ - if not isinstance(type_name, str): - raise ValueError("type_name must be a string") - type_name = type_name.strip().upper() - if not isinstance(responses, list): - raise ValueError("responses must be a list") - if not all(isinstance(event, int) for event in responses): - raise ValueError("all responses must be integers") - - if hasattr(Types, type_name): - raise ValueError(f"Device type {type_name} already exists in Devices class.") - class_name = type_name[0].upper() + type_name[1:].lower() + "Device" - if class_name in [cls.__name__ for cls in _mapping.values()]: - raise ValueError(f"Device class {class_name} already exists.") - - value = len(_mapping) - setattr(Types, type_name, value) - NewClass = type(class_name, (Device,), {"type": value, "responses": responses}) - _mapping[value] = NewClass - return NewClass - - -class Device: - """ - Base class for devices. Must be subclassed. Should not be instantiated directly. - - Attributes: - type (Devices): The type of the device. - responses (list): The list of event types that the device can respond to. - """ - - type = Types.UNDEFINED - responses = Events.filter - - def __init__(self, read=None, data=None, read2=None, data2=None): - """ - Create a new device object. - - Args: - read (callable, optional): A function that returns an event or None. Defaults to None. - data (Any, optional): Data to pass to the read function. Defaults to None. - read2 (callable, optional): A function that returns a value or None. Defaults to None. - data2 (Any, optional): Data to pass to the read2 function. Defaults to None. - """ - self._event_callbacks = {} - - self._read = read if read else lambda: None - self._data = data - self._read2 = read2 if read2 else lambda: None - self._data2 = data2 - - self._broker = None - self._state = None - self._user_data = None # Can be set and retrieved by apps such as lv_config - - def poll(self, *args) -> Events: - """ - Poll the device for events. - - Args: - *args: Additional arguments that can be passed to the read callback functions. - - Returns: - Event: The event that was polled or None if no event was polled. - """ - if (event := self._poll()) is not None: - if event.type in Events.filter: - if event.type == Events.QUIT: - if self._broker: - self._broker.quit() - if callback_list := self._event_callbacks.get(event.type): - for callback in callback_list: - callback(event, *args) - return event - return None - - def subscribe(self, callback, event_types=None): - """ - Subscribe to events from the device. - - Args: - callback (function): The function to call when an event is received. - event_types (list[int] | None): A list of event types to subscribe to. - - Raises: - ValueError: If `callback` is not callable. - ValueError: If any event type in `event_types` is not a response from this device. - - Example: - ```python - def callback(event): - print(event) - - device.subscribe(callback, [Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP]) - ``` - - This will call `callback` when the device receives a MOUSEBUTTONDOWN or MOUSEBUTTONUP event. - """ - event_types = event_types or self.responses - if not callable(callback): - raise ValueError("callback is not callable.") - for event_type in event_types: - if event_type not in self.responses: - raise ValueError("the specified event_type is not a response from this device") - callback_set = self._event_callbacks.get(event_type, set()) - callback_set.add(callback) - self._event_callbacks[event_type] = callback_set - - def unsubscribe(self, callback, event_types=None): - """ - Unsubscribes a callback function from one or more event types. - - Args: - callback (function): The callback function to unsubscribe. - event_types (list): A list of event types to unsubscribe from. - """ - event_types = event_types or self.responses - for event_type in event_types: - if callback_set := self._event_callbacks.get(event_type): - callback_set.remove(callback) - - @property - def broker(self): - """ - The broker that manages this device. - """ - return self._broker - - @broker.setter - def broker(self, broker): - self._broker = broker - - @property - def user_data(self): - """ - User data that can be set and retrieved by applications. - """ - return self._user_data - - @user_data.setter - def user_data(self, value): - self._user_data = value - - -class Broker(Device): - """ - The Broker class is a device that polls multiple devices for events and forwards them to - subscribers. - - Attributes: - type (Devices): The type of the device (set to `Types.BROKER`). - responses (list): The list of event types that the device can respond to. - Events (Events): The Events class for convenience. - Applications can use Broker.Events.KEYDOWN, etc. - """ - - type = Types.BROKER - responses = Events.filter - Events = Events # Create a reference to the Events class for convenience. - - def __init__(self): - super().__init__() - self.devices = [] # List of devices to poll - self._device_callbacks = {} - # Function to call when the window close button is clicked. - # Set it like `display_drv.quit_func = cleanup_func` where `cleanup_func` is a - # function that cleans up resources and calls `sys.exit()`. - # .poll() must be called periodically to check for the quit event. - self._quit_func = exit - - def subscribe(self, callback, event_types=None, device_types=None): - """ - Subscribes a callback function to receive events. - - Args: - callback (function): The callback function to subscribe. - event_types (list, optional): The list of event types to subscribe to. Defaults to None. - device_types (list, optional): The list of device types to subscribe to. Defaults to None. - - Raises: - ValueError: If the callback is not callable. - ValueError: If both device_types and event_types are provided. - ValueError: If neither device_types nor event_types are provided. - """ - if not callable(callback): - raise ValueError("callback is not callable.") - if device_types is not None and event_types is not None: - raise ValueError("set one of device_type or event_type but not both.") - if device_types is None and event_types is None: - raise ValueError("set one of device_type or event_type but not both.") - if device_types is not None: - for device_type in device_types: - callback_set = self._device_callbacks.get(device_type, set()) - callback_set.add(callback) - self._device_callbacks[device_type] = callback_set - else: - super().subscribe(callback, event_types) - - def unsubscribe(self, callback, event_types=None, device_types=None): - """ - Unsubscribes a callback function from receiving events. - - Args: - callback (function): The callback function to unsubscribe. - event_types (list, optional): The list of event types to unsubscribe from. Defaults to None. - device_types (list, optional): The list of device types to unsubscribe from. Defaults to None. - - Raises: - ValueError: If both device_types and event_types are provided. - ValueError: If neither device_types nor event_types are provided. - """ - if device_types is not None and event_types is not None: - raise ValueError("set one of device_type or event_type but not both.") - if device_types is None and event_types is None: - raise ValueError("set one of device_type or event_type but not both.") - if device_types is not None: - for device_type in device_types: - if callback_set := self._device_callbacks.get(device_type): - callback_set.remove(callback) - else: - super().unsubscribe(callback, event_types) - - def create_device(self, type=Types.QUEUE, **kwargs) -> Device: - """ - Create a device object. - - Args: - type (int, optional): The type of device to create. Defaults to Types.QUEUE. - **kwargs (Any): Arbitrary keyword arguments for the class constructor. - - Returns: - Device: The created device object. - - Raises: - ValueError: If the device type is invalid. - """ - if cls := _mapping.get(type): - dev = cls(**kwargs) - self.register_device(dev) - return dev - raise ValueError("Invalid device type") - - def register_device(self, dev): - """ - Register a device to be polled. - - Args: - dev (Device): The device object to register. - """ - dev.broker = self - self.devices.append(dev) - - def unregister_device(self, dev): - """ - Unregister a device. - - Args: - dev (Device): The device object to unregister. - """ - if dev in self.devices: - self.devices.remove(dev) - dev.broker = None - - @property - def quit_func(self): - """ - The function to call when the window close button is clicked. - """ - return self._quit_func - - @quit_func.setter - def quit_func(self, value): - """ - Sets the function to call when the window close button is clicked. - - Args: - value (function): The function to call when the window close button is clicked. - """ - if not callable(value): - raise ValueError("quit_func must be callable") - self._quit_func = value - - def quit(self): - """ - Call the quit function. - """ - self._quit_func() - - def _poll(self): - """ - Polls the registered devices for events. - - Returns: - object: The event object if an event is received, otherwise None. - """ - for device in self.devices: - if (event := device.poll()) is not None: - if callback_list := self._device_callbacks.get(device.type): - for func in callback_list(): - func(event) - return event - return None - - -class QueueDevice(Device): - """ - Represents a queue device. - - Attributes: - type (str): The type of the device. - responses (list): The list of events that the device can respond to. - """ - - type = Types.QUEUE - responses = Events.filter - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self._data2 is None: - self._data2 = Events.filter - if hasattr(self._data, "touch_scale"): - self.scale = self._data.touch_scale - else: - self.scale = 1 - - def _poll(self): - """ - Polls the device for events. - - Returns: - Event or None: The next event from the device, or None if no event is available. - """ - if (event := self._read()) is not None: - if event.type in self._data2: - if event.type in ( - Events.MOUSEMOTION, - Events.MOUSEBUTTONDOWN, - Events.MOUSEBUTTONUP, - ): - if (scale := self.scale) != 1: - event.pos = ( - int(event.pos[0] // scale), - int(event.pos[1] // scale), - ) - if event.type == Events.MOUSEMOTION: - event.rel = (event.rel[0] // scale, event.rel[1] // scale) - return event - return None - - -class TouchDevice(Device): - """ - Represents a touch input device. - - This class handles touch input events and provides methods to read touch data - from the underlying touch driver. It supports reporting mouse button 1 events - such as mouse motion, mouse button down, and mouse button up. - - Attributes: - type (str): The type of the device (set to Types.TOUCH). - responses (tuple): The supported event types for the device. - - Args: - *args (Any): Variable length argument list. - **kwargs (Any): Arbitrary keyword arguments. - """ - - type = Types.TOUCH - responses = (Events.MOUSEMOTION, Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self._data is None: - raise ValueError("TouchDevice requires a display device as 'data'") - if self._data2 is None: # self._data is a rotation table - self._data2 = _DEFAULT_TOUCH_ROTATION_TABLE - self._data.touch_device = self - self.rotation = self._data.rotation - - @property - def rotation(self): - """ - Get the rotation value of the touch device. - - Returns: - rotation (int): The rotation value in degrees. - """ - return self._rotation - - @rotation.setter - def rotation(self, value): - """ - Set the rotation value of the touch device. - - Args: - value (int): The rotation value in degrees. - """ - self._rotation = value % 360 - - # _mask is an integer from 0 to 7 (or 0b001 to 0b111, 3 bits) - # Currently, bit 2 = invert_y, bit 1 is invert_x and bit 0 is swap_xy, but that may change. - self._mask = self._data2[self._rotation // 90] - - @property - def rotation_table(self): - """ - Get the rotation table of the touch device. - - Returns: - list: The rotation table. - """ - return self._data2 - - @rotation_table.setter - def rotation_table(self, value): - """ - Set the rotation table of the touch device. - - Args: - value (list): The rotation table. - """ - self._data2 = value - - def _poll(self): - """ - Poll the touch device for touch events. - - Returns: - Event: The touch event generated by the touch device. - """ - try: # If called too quickly, the touch driver may raise OSError: [Errno 116] ETIMEDOUT - touched = self._read() - except OSError: - return None - if touched: - last_pos = self._state - # If it looks like a point, use it, otherwise get the first point out of the list / tuple - (x, y, *_) = touched if isinstance(touched[0], int) else touched[0] - - if self._mask & SWAP_XY: - x, y = y, x - if self._mask & REVERSE_X: - x = self._data.width - x - 1 - if self._mask & REVERSE_Y: - y = self._data.height - y - 1 - self._state = (x, y) - if last_pos is not None: - last_x, last_y = last_pos - return Events.Motion( - Events.MOUSEMOTION, - self._state, - (x - last_x, y - last_y), - (1, 0, 0), - False, - None, - ) - else: - return Events.Button(Events.MOUSEBUTTONDOWN, self._state, 1, False, None) - elif self._state is not None: - last_pos = self._state - self._state = None - return Events.Button(Events.MOUSEBUTTONUP, last_pos, 1, False, None) - return None - - -class EncoderDevice(Device): - """ - A class representing an encoder device. - - Attributes: - type (str): The type of the device (ENCODER). - responses (tuple): The events that the device can respond to (MOUSEWHEEL, MOUSEBUTTONDOWN, MOUSEBUTTONUP). - """ - - type = Types.ENCODER - responses = (Events.MOUSEWHEEL, Events.MOUSEBUTTONDOWN, Events.MOUSEBUTTONUP) - - def __init__(self, *args, **kwargs): - """ - Initializes a new instance of the EncoderDevice class. - - Args: - *args (Any): Variable length argument list. - **kwargs (Any): Arbitrary keyword arguments. - - Notes: - - self._data is the mouse button number to report for the switch. - Default is 2 (middle mouse button). If the mouse button number is even, - the wheel will report vertical (y) movement. If the mouse button number is odd, - the wheel will report horizontal (x) movement. This corresponds to a typical mouse - wheel being button 2 and the wheel moving vertically. It also corresponds to - scrolling horizontally on a touchpad with two-finger scrolling and using the right button. - """ - super().__init__(*args, **kwargs) - self._state = (0, False) # (position, pressed) - self._data = self._data if self._data else 2 # Default to middle mouse button - - def _poll(self): - """ - Polls the encoder device for changes and returns the corresponding event. - - Returns: - Event: The event generated by the encoder device, or None if no event occurred. - """ - last_pos, last_pressed = self._state - pressed = self._read2() - if pressed != last_pressed: - self._state = (last_pos, pressed) - return Events.Button( - Events.MOUSEBUTTONDOWN if pressed else Events.MOUSEBUTTONUP, - (0, 0), - self._data, - False, - None, - ) - - pos = self._read() - if pos != last_pos: - steps = pos - last_pos - self._state = (pos, last_pressed) - if self._data % 2 == 0: - return Events.Wheel(Events.MOUSEWHEEL, False, 0, steps, 0, steps, False, None) - return Events.Wheel(Events.MOUSEWHEEL, False, steps, 0, steps, 0, False, None) - return None - - -class KeypadDevice(Device): - """ - Represents a keypad device. - - Attributes: - type (Devices): The type of the device (set to `Types.KEYPAD`). - responses (tuple): The types of events that the device can respond to (set to `(Events.KEYDOWN, Events.KEYUP)`). - - Methods: - __init__: Initializes the KeypadDevice object. - _poll: Polls the keypad for key events. - """ - - type = Types.KEYPAD - responses = (Events.KEYDOWN, Events.KEYUP) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._state = set() - - def _poll(self): - """ - Polls the keypad for key events. - - Returns: - Events.Key or None: An instance of the `Events.Key` class representing the key event, or `None` if no key event occurred. - """ - keys = set(self._read()) - released = self._state - keys - if released: - key = released.pop() - self._state.remove(key) - return Events.Key(Events.KEYUP, chr(key), key, 0, 0) - pressed = keys - self._state - if pressed: - key = pressed.pop() - self._state.add(key) - return Events.Key(Events.KEYDOWN, chr(key), key, 0, 0) - return None - - -class JoystickDevice(Device): - """ - Represents a joystick device. - - Attributes: - type (Devices): The type of the device, set to `Types.JOYSTICK`. - responses (tuple): A tuple of event types that this device can respond to. - - Methods: - __init__(*args, **kwargs): Initializes the JoystickDevice instance. - _poll(): Polls the device for events. - - Raises: - NotImplementedError: If the `_poll` method is not implemented. - """ - - type = Types.JOYSTICK - responses = ( - Events.JOYAXISMOTION, - Events.JOYBALLMOTION, - Events.JOYHATMOTION, - Events.JOYBUTTONDOWN, - Events.JOYBUTTONUP, - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def _poll(self): - raise NotImplementedError("JoystickDevice.read() not implemented") - - -_mapping = { - # Mapping of device types to device classes - Types.BROKER: Broker, - Types.QUEUE: QueueDevice, - Types.TOUCH: TouchDevice, - Types.ENCODER: EncoderDevice, - Types.KEYPAD: KeypadDevice, - Types.JOYSTICK: JoystickDevice, -} diff --git a/micropython/pydisplay/eventsys/eventsys/devices.py b/micropython/pydisplay/eventsys/eventsys/devices.py index 7c439f3b5..d1e8954c4 100644 --- a/micropython/pydisplay/eventsys/eventsys/devices.py +++ b/micropython/pydisplay/eventsys/eventsys/devices.py @@ -52,7 +52,7 @@ def custom_type(type_name, responses): responses (list[int]): A list of event types that the device can return. Returns: - Device: The newly created device type. + (Device): The newly created device type. Raises: ValueError: If `type_name` is not a string, `responses` is not a list, or any response is not an integer. @@ -142,7 +142,7 @@ def poll(self, *args) -> events: Poll the device for events. Args: - *args: Additional arguments that can be passed to the read callback functions. + *args (Any): Additional arguments that can be passed to the read callback functions. Returns: Event: The event that was polled or None if no event was polled. @@ -501,7 +501,7 @@ def rotation_table(self): Get the rotation table of the touch device. Returns: - list: The rotation table. + (list): The rotation table. """ return self._data2 diff --git a/micropython/pydisplay/graphics/README.md b/micropython/pydisplay/graphics/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/graphics/README.md +++ b/micropython/pydisplay/graphics/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: diff --git a/micropython/pydisplay/graphics/graphics/_font.py b/micropython/pydisplay/graphics/graphics/_font.py index d62c33078..336468a71 100644 --- a/micropython/pydisplay/graphics/graphics/_font.py +++ b/micropython/pydisplay/graphics/graphics/_font.py @@ -25,14 +25,14 @@ # Try to import the font data from .py files in the same directory as this module. # If that fails, use the .bin files in the same directory. try: - from . import font_8x8 - from . import font_8x14 - from . import font_8x16 + from . import _font_8x8 + from . import _font_8x14 + from . import _font_8x16 _FONTS = { - 8: font_8x8.FONT, - 14: font_8x14.FONT, - 16: font_8x16.FONT, + 8: _font_8x8.FONT, + 14: _font_8x14.FONT, + 16: _font_8x16.FONT, } except ImportError: font_dir = __file__.split(sep)[0:-1] # get the path this module is in @@ -166,7 +166,7 @@ class Font: def __init__(self, font_data=None, height=None, cached=True): # Optionally specify font_data to override the font data to use (default - # is font_8x8.FONT). font_data may be a memoryview or a string path to a + # is _font_8x8.FONT). font_data may be a memoryview or a string path to a # font file. The font format is a binary file with the following # format: # - bytes: font data, in ASCII order covering all 256 characters. diff --git a/micropython/pydisplay/graphics/graphics/font_8x14.py b/micropython/pydisplay/graphics/graphics/_font_8x14.py similarity index 100% rename from micropython/pydisplay/graphics/graphics/font_8x14.py rename to micropython/pydisplay/graphics/graphics/_font_8x14.py diff --git a/micropython/pydisplay/graphics/graphics/font_8x16.py b/micropython/pydisplay/graphics/graphics/_font_8x16.py similarity index 100% rename from micropython/pydisplay/graphics/graphics/font_8x16.py rename to micropython/pydisplay/graphics/graphics/_font_8x16.py diff --git a/micropython/pydisplay/graphics/graphics/font_8x8.py b/micropython/pydisplay/graphics/graphics/_font_8x8.py similarity index 100% rename from micropython/pydisplay/graphics/graphics/font_8x8.py rename to micropython/pydisplay/graphics/graphics/_font_8x8.py diff --git a/micropython/pydisplay/multimer/README.md b/micropython/pydisplay/multimer/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/multimer/README.md +++ b/micropython/pydisplay/multimer/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: diff --git a/micropython/pydisplay/palettes/README.md b/micropython/pydisplay/palettes/README.md index 5841c6112..1a01afa0c 100644 --- a/micropython/pydisplay/palettes/README.md +++ b/micropython/pydisplay/palettes/README.md @@ -60,7 +60,7 @@ micropython -i path.py On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: ``` -import path +import lib.path ``` The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: From 58a6594a87568342bbcd614e834313543a089f9f Mon Sep 17 00:00:00 2001 From: bdbarnett Date: Wed, 11 Dec 2024 13:03:55 -0600 Subject: [PATCH 35/35] clear Signed-off-by: bdbarnett --- .../examples/board_config.py | 4 +- .../displaysys/displaysys/__init__.py | 5 +- .../pydisplay/pydisplay-bundle/README.md | 151 ++++++++++++++++++ .../pydisplay/pydisplay-bundle/manifest.py | 1 - 4 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 micropython/pydisplay/pydisplay-bundle/README.md diff --git a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py index fc4b7e0d4..fa3e915f6 100644 --- a/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py +++ b/micropython/pydisplay/displaysys/displaysys-fbdisplay/examples/board_config.py @@ -50,6 +50,7 @@ def send_init_sequence(init_sequence, mosi, sck, cs): init_sequence = bytes() i2c = I2C(0, sda=Pin(8), scl=Pin(18), freq=100000) +print(f"i2c.scan() = {i2c.scan()}") iox = PCA9554(i2c, address=0x38) btn_down = iox.Pin(6, Pin.IN) btn_up = iox.Pin(5, Pin.IN) @@ -66,7 +67,8 @@ def send_init_sequence(init_sequence, mosi, sck, cs): fb = RGBFrameBuffer(**tft_pins, **tft_timings) mv = memoryview(fb) -mv[:] = b"\xff" * len(mv) +# mv is typecode "H" (unsigned short) and we need to fill it with 0x1234 +mv[::] = b"\x34\x12" * (fb.width * fb.height) fb.refresh() touch_drv = FT6x36(i2c, address=0x48) # , irq = iox.Pin(3, Pin.OUT)) diff --git a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py index 4712cc787..4382cd0d4 100644 --- a/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py +++ b/micropython/pydisplay/displaysys/displaysys/displaysys/__init__.py @@ -115,12 +115,13 @@ def __init__(self, auto_refresh=False): self._auto_byteswap = self.requires_byteswap self._touch_device = None if auto_refresh: + period = 33 if isinstance(auto_refresh, bool) else auto_refresh try: from multimer import get_timer - self._timer = get_timer(self.show, period=33) + self._timer = get_timer(self.show, period=period) except ImportError: - raise ImportError("timer is required for auto_refresh") + raise ImportError("multimer is required for auto_refresh") else: self._timer = None self.init() diff --git a/micropython/pydisplay/pydisplay-bundle/README.md b/micropython/pydisplay/pydisplay-bundle/README.md new file mode 100644 index 000000000..1a01afa0c --- /dev/null +++ b/micropython/pydisplay/pydisplay-bundle/README.md @@ -0,0 +1,151 @@ +logo + +

pydisplay

+ +

Cross-platform User Interface and Event Drivers for *Python

+ +

+ About • + Key Features • + Getting Started • + Running Your First App • + API • + Roadmap • + Contributing • + Thanks • + Screenshots +

+ +| ![peterhinch's active.py](screenshots/active.gif) | ![russhughes's tiny_toasters.py](screenshots/tiny_toasters.gif) | +|-------------------------|--------------------------------| +| @peterhinch's active.py | @russhughes's tiny_toasters.py | + +## About + +WARNINGS: pydisplay is currently alpha quality. Every effort has been made to test on as many platforms as possible, but I need your help and feedback to get it to its inital release. A lot has changed and I am working on catching up the documentation. + +pydisplay is a universal display, event and device driver framework for multiple flavors of Python, including MicroPython, CircuitPython and CPython (big Python). It may be used as-is to create graphic frontends to your apps, or may be used as a foundation with GUI libraries such as [LVGL](https://github.com/lvgl/lv_micropython), [MicroPython-touch](https://github.com/peterhinch/micropython-touch) or maybe even a GUI framework you've been thinking of developing. Its primary purpose is to provide display and touch drivers for MicroPython, but it is equally useful for developers who may never touch MicroPython. + +It is important to note that pydisplay is meant to be a foundation for GUI libraries and is not itself a GUI library. It doesn't provide widgets, such as buttons, checkboxes or sliders, and it doesn't provide a timing mechanism. You will need a GUI library to provide those if necessary, although many apps won't need them. (There is a cross-platform repository [multimer](https://github.com/PyDevices/pydisplay/tree/main/src/lib/multimer) you can use if you want to used scheduled interrupts. It works with CPython and MicroPython, but doesn't work with CircuitPython. You can also use asyncio for timing.) + +## Key Features + +- May be used without additional libraries to add graphics capabilities to MicroPython, CircuitPython and CPython, with a consistent API across them all. +- Enables moving from one platform to another, for example MicroPython on ESP32-S3 to CPython on Windows without changing your code. Do your graphics development on your desktop, laptop or ChromeBook and then move to a microcontroller when you are ready to interface with your sensors and devices. CPython has much better error messages than MicroPython making it easier to troubleshoot when things go wrong! +- Built around devices available on microcontrollers but not necessarily available on desktop operating systems. For instance, rotary encoders and mouse scroll wheels show up as the same device type and yield the same events. Touchscreens on microcontrollers yield the same events as mice on desktops. Likewise with keypads / keyboards. +- Easily extensible. Use the primitives provided by pydisplay and add your own libraries, classes and functions to have even greater functionality. +- Provides several built-in color palettes and a mechanism to generate your own palettes. +- Lots of examples included, whether developed specifically for pydisplay or ported from [Russ Hughes's st7789py_mpy](https://github.com/russhughes/st7789py_mpy). Also works with all of the examples from Peter Hinch's MicroPython GUI libraries [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) and [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) on MicroPython. +- Support MicroPython on microcontrollers and on Unix(-like) operating systems. +- On MicroPython, can be configured to work with [kdschlosser's lvgl_micropython bus drivers](https://github.com/kdschlosser/lvgl_micropython), which are very fast bus drivers written in C. +- Works with CircuitPython's FourWire and ParallelBus bus drivers, as well as FrameBufferDisplay based interfaces such as dotclockframebuffer, usb_video and rgbmatrix + +## Getting Started + +This section is under construction. For now, see [Getting Started](GETTING-STARTED.md) for more information. + + +## Running your first app + +You will need to import the `path.py` file before running any of the examples. + +On desktop operating systems, `cd` into the `mp` directory (or wherever you have the files staged) and type: +``` +python3 -i path.py +``` +or +``` +micropython -i path.py +``` + +On microcontrollers, either add the following to your `boot.py` (MicroPython) or `code.py` (CircuitPython), or simply import it at the REPL before importing your desired app: +``` +import lib.path +``` + +The [examples](examples) directory will be on the system path, so to run an app from it, you just need to type: +``` +import calculator # substitute `calculator` with the file OR directory you want to run, omitting the .py extension +``` + +To run any of the examples from MicroPython-Touch (remember, its for MicroPython only) type: +``` +import gui.demos.various # substitute `various` with the file you want to run, omitting the .py extension +``` + +## API + +Where possible, existing, proven APIs were used. + +- There are currently 5 display classes, and hopefully another `EPaperDisplay` display class will be added soon, although I will need help from the community for this. + - BusDisplay is for microcontrollers, both on MicroPython and CircuitPython. CircuitPython provides the required bus drivers, as mentioned elsewhere in this README, but MicroPython doesn't have display bus drivers. The [buses](src/lib/buses) packages are included with the installer. It is my hope that community members will create other C bus drivers similar to @kdschlosser's bus drivers in [lvgl_micropython](https://github.com/kdschlosser/lvgl_micropython). + - SDL2Display - the preferred class for desktop operating systems as it is faster than PGDisplay. It uses an SDL `texture` in place of an LCD's Graphics RAM (GRAM). + - PGDisplay - an optional class for desktop operating systems. It uses a pygame `surface` in place of an LCD's GRAM. It can be benificial in a couple of instances: + - SDL2Display "glitches" on my ChromeBook, but PGDisplay doesn't + - On Windows, it is easier to install PyGame than SDL2 + - FBDisplay works with CircuitPython framebufferio.FramebufferDisplay objects, such as dotclockframebuffer (RGB displays), usb_video and rgbmatrix. (usb_video may be the coolest thing you can do with displaysys, although I'm not sure how practical or useful it is. It allows your board to function as a webcam, even without a camera, and to render the display through USB to any application on your host PC that can open a webcam! My Windows machine sees it as an unsupported device, so it will not work, but it does work on my ChromeBook. Currently it is limited to RP2040 only and is hardcoded to a 128 x 96 resolution, but that likely will change. See the [screen capture](examples/circuitpython_usb_video_chromebook.gif) and the [board_config.py](board_configs/circuitpython/usb_video/board_config.py) for more details.) + - JNDisplay for Jupyter Notebooks. No input devices are currently supported. + - PSDisplay for PyScript. Only touchscreens are currently supported. +- Names of events and Devices in [eventsys](src/lib/eventsys/) are taken from PyGame and/or SDL2 to keep the API consistent. +- All drawing targets, sometimes referred to as `canvas` in the code, may be written to using the API from MicroPython's framebuf.FrameBuffer API + - CPython and CircuitPython don't have a `framebuf` module that is API compliant with MicroPython's `framebuf`, so [framebuf.py](add_ons/framebuf.py) is provided for those platforms. It is not used in MicroPython unless framebuf wasn't compiled in. + - A `graphics` module is provided that subclasses `FrameBuffer` (either built-in or from framebuf.py) and provides additional drawing tools, such as `round_rect`. All methods in graphics return an Area object with x, y, w and h attributes describing a bounding box of what was changed. This can be used by applications to only update the part of the display that needs it. That functionality is implemented in DisplayBuffer and will likely be required by EPaperDisplay when it is implemented. + - Canvases include, but are not limited to, the display itself, framebuf bytearrays, bmp565 (16-bit Windows Bitmap files) and displaybuf.DisplayBuffer objects. + - displaybuf.DisplayBuffer implements @peterhinch's API that represents the full display as a framebuffer and allows for 4-, 8- and 16-bit bytearrays while still drawing to the screen as 16-bit. It is required for `MicroPython-Touch` and is very useful outside of that library as well, especially when memory is constrained. +- Display drivers for MicroPython BusDisplay use the constructor API of CircuitPython's DisplayIO drivers. This includes rotation = 0, 90, 180, 270 instead of 0, 1, 2, 3. +- BusDisplay can communicate with the underlying bus driver using either CircuitPython's DisplayIO method calls or @kdschlosser's [lvgl_micropython] method calls. +- There are 3 primary mechanism's for fonts: the graphics.Font class, tft_text.text() and tft_write.write() methods. All 3 of these return an Area object as mentioned earlier. A fourth font mechanism called EZFont is included in the utils folder, but it doesn't return an Area object, which is why it isn't in the lib folder. + - Font is derived from Tony DiCola's 5x7 font class and reads 8x8, 8x14 and 8x16 .bin files from [@spaceraces romfont repo](https://github.com/spacerace/romfont) + - .text() is written by @russhughes and uses fonts generated by his [text_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) It reads 8 and 16bit wide fonts in heights that are multiples of 8. + - .write() is written by @russhughes and uses fonts generated by his [write_font_converter](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) + - EZFont is a subclass of [@easytarget's microPyEZfonts](https://github.com/easytarget/microPyEZfonts) which uses fonts generated from [@peterhinch's font-to-py](https://github.com/peterhinch/micropython-font-to-py). + - NOTE: @peterhinch's Writer class is inlcuded in MicroPyton-Touch and may be used on MicroPython platforms, but, like EZFont, it doesn't return an Area object. +- Graphics files may be used by 3 mechanisms: + - bmp565.BM565 is a class that can read and write Windows Bitmap files saved with RGB565 color encoding. GIMP supports exporting RGB565 .BMPs. The BMP565 class can open a file and read it's entire contents into memory, or with the `streamed = True` flag, it will only read the slice requested, allowing progressive rendering of files much too large to fit into memory. The slice can be 2 dimensional (BMP565[1:5, 6:10] gets pixels 1 through 5 on rows 6 through 10) or 1 dimensional (BMP565[6:10] gets all pixels in rows 6 through 10). This slicing mechanism is very useful when rendering sprites. It can reverse the order of pixels in a row with `mirrored = False`, which is needed when rendering a background image when rotation is 90 or 270 and (horizontal) scrolling is desired. Finally, it can use an existing bytearray as its buffer instead of reading from a file, which allows saving screenshots from existing canvases such as a FrameBuffer or DisplayBuffer. + - .bitmap() is written by @russhughes and reads .py graphics files encoded with his [image_converter.py utiliity](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) or his [sprite_converter.py utility](https://github.com/russhughes/st7789py_mpy/blob/master/utils/sprites_converter.py). It renders the entire image to a buffer, and then copies that buffer to the display. + - .pbitmap() is also written by @russhughes and renders the same fonts as .bitmap(), but it does it progressively, one line at a time using a one line buffer. +- Config files - All files that are intended for you to edit to customize your configuration are in the [configs](src/configs/) directory. They are: + - `board_config.py` - required in all circumstances. Feel free to add your own setup code here, such as for real-time clocks, wifi, sensors, etc. + - `path.py` - required in all circumstances + - `color_setup.py` - required for [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) + - `hardware_setup.py` - required for [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) + - `lv_config.py` - required for LVGL + - `tft_config.py` - required for @russhughes's examples. I had to do some search and replace to get those examples to work. + + +## Roadmap + +- [ ] Much more documentation on Github +- [ ] Document the files to produce output for ReadTheDocs +- [ ] Implement EPaperDisplay +- [ ] Optimize with more Numpy and Viper code +- [ ] Decrease the memory footprint where possible +- [ ] Test with frozen modules +- [ ] On MicroPython on Unix, the screen gets cleared when the display is rotated. Microcontroller displays don't do this. It's not an issue unless you want to draw to the display, rotate it, then draw more on top. This functionality allow drawing text in all four 90 degree orientations. +- [ ] Scrolling vertically on desktop operating sytems works correctly, but not when rotated. When rotated, it show scroll horizontally, but continues to scroll vertically. +- [ ] Scrolling on microcontrollers has issues when trying to write spanning the cutoff line. For instance, if drawing a 16 pixel high image at the 8th line from the cutoff line, the bottom 8 lines don't end up where you expect. See the [bmp565_sprite](examples/bmp565_sprite.py) example. +- [ ] Ensure multiple displays work at the same time +- [ ] Implement color depths other than 16 bit +- [ ] Add a Joystick class to eventsys +- [ ] Test with CircuitPython Blinka on SBC's such as Raspberry Pi 4 +- [ ] Need C bus drivers from the community, especially for STM32H7 and MIMXRT + +## Contributing + +This is a community project and I need your help! If you have a suggestion that would make this better, please fork the repo and create a pull request. +Don't forget to give the project a star! Thanks again! + +1. Fork the project +2. Clone it open the repository in command line +3. Create your feature branch (`git checkout -b feature/amazing-feature`) +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a pull request from your feature branch from your repository into this repository main branch, and provide a description of your changes + +## Thanks + +I very much appreciate @peterhinch, @russhughes and the team at Adafruit for their contributions to the Python on microcontrollers community. + +## Why + +I started out just wanting to create drivers that worked with MicroPython the way DisplayIO drivers work for CircuitPython, except without DisplayIO and instead usable by any GUI framework like, but not limited to, LVGL. That snowballed into adding more platforms and then adding drawing primitives, font classes, palettes, an event system, a barebones SDL2 library, a Bitmap 565 reader/writer and supporting as many platforms as possible. I stopped short of creating a full fledged GUI and plan to leave it as a very capable graphics library. I think this is a great foundation for building a GUI framework with widgets and a task scheduler, although it is very usable and useful without one. @peterhinch has a great GUI for MicroPython that works on top of pydisplay, and I'm hoping someone will make a GUI that works across platforms. diff --git a/micropython/pydisplay/pydisplay-bundle/manifest.py b/micropython/pydisplay/pydisplay-bundle/manifest.py index ea028d4e5..cc346af3a 100644 --- a/micropython/pydisplay/pydisplay-bundle/manifest.py +++ b/micropython/pydisplay/pydisplay-bundle/manifest.py @@ -5,7 +5,6 @@ license="MIT", pypi_publish="pydisplay-bundle", ) -require("displaybuf") require("eventsys") require("graphics") require("multimer")