diff --git a/examples/fbmirror.py b/examples/fbmirror.py index 8f3678f..5b097d1 100644 --- a/examples/fbmirror.py +++ b/examples/fbmirror.py @@ -1,59 +1,21 @@ #!/usr/bin/python3 """ -Mirror a scaled copy of the framebuffer to a 64x32 matrix +Mirror a scaled copy of the framebuffer to a matrix -The upper left corner of the framebuffer is displayed until the user hits ctrl-c. - -Control matrix size, and orientation with command line arguments. - -python fbmirror_scaled.py [width] [height] [orientation] - - width int: Total width of matrices in pixels. Default is 64. - height int: Total height of matrices in pixels. Default is 32. - orientation int: Orientation in degrees, must be 0, 90, 180, or 270. - Default is 0 or Normal orientation. +A portion of the framebuffer is displayed until the user hits ctrl-c. The `/dev/fb0` special file will exist if a monitor is plugged in at boot time, or if `/boot/firmware/cmdline.txt` specifies a resolution such as `... video=HDMI-A-1:640x480M@60D`. + +For help with commandline arguments, run `python fbmirror.py --help` """ -import sys -import adafruit_raspberry_pi5_piomatter +import adafruit_raspberry_pi5_piomatter as piomatter +import click import numpy as np - -width = 64 -height = 32 - -yoffset = 0 -xoffset = 0 - - -if len(sys.argv) >= 2: - width = int(sys.argv[1]) -else: - width = 64 - -if len(sys.argv) >= 3: - height = int(sys.argv[2]) -else: - height = 32 - -if len(sys.argv) >= 4: - rotation = int(sys.argv[3]) - if rotation == 90: - rotation = adafruit_raspberry_pi5_piomatter.Orientation.CW - elif rotation == 180: - rotation = adafruit_raspberry_pi5_piomatter.Orientation.R180 - elif rotation == 270: - rotation = adafruit_raspberry_pi5_piomatter.Orientation.CCW - elif rotation == 0: - rotation = adafruit_raspberry_pi5_piomatter.Orientation.Normal - else: - raise ValueError("Invalid rotation. Must be 0, 90, 180, or 270.") -else: - rotation = adafruit_raspberry_pi5_piomatter.Orientation.Normal +import piomatter_click with open("/sys/class/graphics/fb0/virtual_size") as f: screenx, screeny = [int(word) for word in f.read().split(",")] @@ -71,11 +33,18 @@ with open("/sys/class/graphics/fb0/stride") as f: linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype) +@click.command +@click.option("--x-offset", "xoffset", type=int, help="The x offset of top left corner of the region to mirror") +@click.option("--y-offset", "yoffset", type=int, help="The y offset of top left corner of the region to mirror") +@piomatter_click.standard_options +def main(xoffset, yoffset, width, height, serpentine, rotation, colorspace, pinout, n_planes, n_addr_lines): + geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, rotation=rotation) + framebuffer = np.zeros(shape=(geometry.height, geometry.width), dtype=dtype) + matrix = piomatter.PioMatter(colorspace=colorspace, pinout=pinout, framebuffer=framebuffer, geometry=geometry) -geometry = adafruit_raspberry_pi5_piomatter.Geometry(width=width, height=height, n_addr_lines=4, rotation=rotation) -matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width), dtype=dtype) -matrix = adafruit_raspberry_pi5_piomatter.AdafruitMatrixBonnetRGB565(matrix_framebuffer, geometry) + while True: + framebuffer[:,:] = linux_framebuffer[yoffset:yoffset+height, xoffset:xoffset+width] + matrix.show() -while True: - matrix_framebuffer[:,:] = linux_framebuffer[yoffset:yoffset+height, xoffset:xoffset+width] - matrix.show() +if __name__ == '__main__': + main() diff --git a/examples/piomatter_click.py b/examples/piomatter_click.py new file mode 100644 index 0000000..ec9577e --- /dev/null +++ b/examples/piomatter_click.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2025 Jeff Epler for Adafruit Industries +# SPDX-License-Identifier: Unlicense + +"""A helper for parsing piomatter settings on the commandline""" +from collections.abc import Callable +from typing import Any + +import adafruit_raspberry_pi5_piomatter as piomatter +import click + + +class PybindEnumChoice(click.Choice): + def __init__(self, enum, case_sensitive=False): + self.enum = enum + choices = [k for k, v in enum.__dict__.items() if isinstance(v, enum)] + super().__init__(choices, case_sensitive) + + def convert( + self, value: Any, param: click.Parameter | None, ctx: click.Context | None + ) -> Any: + if isinstance(value, self.enum): + return value + + value = super().convert(value, param, ctx) + r = getattr(self.enum, value) + return r + +def standard_options( + f: click.decorators.FC | None = None, + *, + width=64, + height=32, + serpentine=True, + rotation=piomatter.Orientation.Normal, + colorspace=piomatter.Colorspace.RGB888, + pinout=piomatter.Pinout.AdafruitMatrixBonnet, + n_planes=10, + n_addr_lines=4, +) -> Callable[[], None]: + """Add standard commandline flags, with the defaults given + + Use like a click decorator: + + .. code-block:: python + + @click.command + @piomatter_click.standard_options() + def my_awesome_code(width, height, ...): + ... + + If a kwarg to this function is None, then the corresponding commandline + option is not added at all. For example, if you don't want to offer the + ``--colorspace`` argument, write ``piomatter_click(..., colorspace=None)``.""" + def wrapper(f: click.decorators.FC): + if width is not None: + f = click.option("--width", default=width, help="The panel width in pixels")(f) + if height is not None: + f = click.option("--height", default=height, help="The panel height in pixels")(f) + if serpentine is not None: + f = click.option("--serpentine/--no-serpentine", default=serpentine, help="The organization of multiple panels")(f) + if colorspace is not None: + f = click.option( + "--colorspace", + default=colorspace, + type=PybindEnumChoice(piomatter.Colorspace), + help="The memory organization of the framebuffer" + )(f) + if pinout is not None: + f = click.option( + "--pinout", + default=pinout, + type=PybindEnumChoice(piomatter.Pinout), + help="The details of the electrical connection to the panels" + )(f) + if rotation is not None: + f = click.option( + "--orientation", + "rotation", + default=rotation, + type=PybindEnumChoice(piomatter.Orientation), + help="The overall orientation (rotation) of the panels" + )(f) + if n_planes is not None: + f = click.option("--num-planes", "n_planes", default=n_planes, help="The number of bit planes (color depth. Lower values can improve refresh rate in frames per second")(f) + if n_addr_lines is not None: + f = click.option("--num-address-lines", "n_addr_lines", default=n_addr_lines, help="The number of address lines used by the panels")(f) + return f + if f is None: + return wrapper + return wrapper(f)