From 9eafd20e9d60ee36e0317130f2fce0610f1d8a1d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 8 Feb 2025 10:18:02 -0600 Subject: [PATCH 1/5] Support swapped (bgr) matrices & add enumerated types so there's not an explosion of constructors in the Python code. --- src/include/piomatter/pins.h | 19 +++++++ src/pymain.cpp | 99 ++++++++++++++++++++++++++++++------ 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/include/piomatter/pins.h b/src/include/piomatter/pins.h index b1f42a9..840477d 100644 --- a/src/include/piomatter/pins.h +++ b/src/include/piomatter/pins.h @@ -22,4 +22,23 @@ struct adafruit_matrix_bonnet_pinout { static constexpr uint32_t post_addr_delay = 500; }; +struct adafruit_matrix_bonnet_pinout_bgr { + static constexpr pin_t PIN_RGB[] = {6, 13, 5, 23, 16, 12}; + static constexpr pin_t PIN_ADDR[] = {22, 26, 27, 20, 24}; + static constexpr pin_t PIN_OE = 4; // /OE: output enable when LOW + static constexpr pin_t PIN_CLK = 17; // SRCLK: clocks on RISING edge + static constexpr pin_t PIN_LAT = 21; // RCLK: latches on RISING edge + + static constexpr uint32_t clk_bit = 1u << PIN_CLK; + static constexpr uint32_t lat_bit = 1u << PIN_LAT; + static constexpr uint32_t oe_bit = 1u << PIN_OE; + static constexpr uint32_t oe_active = 0; + static constexpr uint32_t oe_inactive = oe_bit; + + static constexpr uint32_t post_oe_delay = 0; + static constexpr uint32_t post_latch_delay = 0; + static constexpr uint32_t post_addr_delay = 500; +}; + + } // namespace piomatter diff --git a/src/pymain.cpp b/src/pymain.cpp index 4d79d42..f89129d 100644 --- a/src/pymain.cpp +++ b/src/pymain.cpp @@ -23,7 +23,7 @@ struct PyPiomatter { template std::unique_ptr -make_piomatter(py::buffer buffer, const piomatter::matrix_geometry &geometry) { +make_piomatter_pc(py::buffer buffer, const piomatter::matrix_geometry &geometry) { using cls = piomatter::piomatter; using data_type = colorspace::data_type; @@ -35,8 +35,8 @@ make_piomatter(py::buffer buffer, const piomatter::matrix_geometry &geometry) { if (buffer_size_in_bytes != data_size_in_bytes) { throw std::runtime_error( py::str( - "Framebuffer size must be {} bytes, got a buffer of {} bytes") - .attr("format")(data_size_in_bytes, buffer_size_in_bytes) + "Framebuffer size must be {} bytes ({} elements of {} bytes each), got a buffer of {} bytes") + .attr("format")(data_size_in_bytes, n_pixels, colorspace::data_size_in_bytes(1), buffer_size_in_bytes) .template cast()); } @@ -45,6 +45,47 @@ make_piomatter(py::buffer buffer, const piomatter::matrix_geometry &geometry) { return std::make_unique( buffer, std::move(std::make_unique(framebuffer, geometry))); } + +enum Colorspace { + RGB565, RGB888, RGB888Packed +}; + +enum Pinout { + AdafruitMatrixBonnet, + AdafruitMatrixBonnetBGR, +}; + +template +std::unique_ptr +make_piomatter_p(Colorspace c, py::buffer buffer, const piomatter::matrix_geometry &geometry) { + switch(c) { + case RGB565: + return make_piomatter_pc(buffer, geometry); + case RGB888: + return make_piomatter_pc(buffer, geometry); + case RGB888Packed: + return make_piomatter_pc(buffer, geometry); + + default: + throw std::runtime_error( + py::str("Invalid colorspace {!r}").attr("format")(c) + .template cast()); + } +} + +std::unique_ptr +make_piomatter(Colorspace c, Pinout p, py::buffer buffer, const piomatter::matrix_geometry &geometry) { + switch(p) { + case AdafruitMatrixBonnet: + return make_piomatter_p(c, buffer, geometry); + case AdafruitMatrixBonnetBGR: + return make_piomatter_p(c, buffer, geometry); + default: + throw std::runtime_error( + py::str("Invalid pinout {!r}").attr("format")(p) + .template cast()); + } +} } // namespace PYBIND11_MODULE(adafruit_raspberry_pi5_piomatter, m) { @@ -78,6 +119,21 @@ PYBIND11_MODULE(adafruit_raspberry_pi5_piomatter, m) { .value("CW", piomatter::orientation::cw, "Rotated 90 degress clockwise"); + py::enum_( + m, "Pinout", "Describes the pins used for the connection to the matrix") + .value("AdafruitMatrixBonnet", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") + .value("AdafruitMatrixBonnetBGR", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") + .value("AdafruitMatrixHat", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") + .value("AdafruitMatrixHatBGR", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") + ; + + py::enum_( + m, "Colorspace", "Describes the organization of the graphics data in memory") + .value("RGB888Packed", Colorspace::RGB888Packed, "3 bytes per pixel in RGB order") + .value("RGB888", Colorspace::RGB888, "4 bytes per pixel in RGB order") + .value("RGB565", Colorspace::RGB565, "2 bytes per pixel in RGB order") + ; + py::class_(m, "Geometry", R"pbdoc( Describe the geometry of a set of panels @@ -141,11 +197,10 @@ The default, 10, is the maximum value. py::class_(m, "PioMatter", R"pbdoc( HUB75 matrix driver for Raspberry Pi 5 using PIO - -Do not create instances of this class directly. Instead, use one of -the constructors such as `AdafruitMatrixBonnetRGB888Packed` to -select a specific pinout & in-memory framebuffer layout. )pbdoc") + .def(py::init(&make_piomatter), + py::arg("colorspace"), py::arg("pinout"), + py::arg("framebuffer"), py::arg("geometry")) .def("show", &PyPiomatter::show, R"pbdoc( Update the displayed image @@ -158,32 +213,44 @@ The approximate number of matrix refreshes per second. )pbdoc"); m.def("AdafruitMatrixBonnetRGB565", - make_piomatter, - py::arg("buffer"), py::arg("geometry")) - //.doc() = "Drive panels connected to an Adafruit Matrix Bonnet using - // the RGB565 memory layout (4 bytes per pixel)" + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB565, Pinout::AdafruitMatrixBonnet, buffer, geometry); + }, + py::arg("buffer"), py::arg("geometry"), + R"pbdoc( +Construct a PioMatter object to drive panels connected to an +Adafruit Matrix Bonnet using the RGB565 memory layout (2 bytes per +pixel) + +This is deprecated shorthand for `PioMatter(Colorspace.RGB565, Pinout.AdafruitMatrixBonnet, ...)`. +)pbdoc") ; m.def("AdafruitMatrixBonnetRGB888", - make_piomatter, + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB888, Pinout::AdafruitMatrixBonnet, buffer, geometry); + }, py::arg("framebuffer"), py::arg("geometry"), R"pbdoc( Construct a PioMatter object to drive panels connected to an Adafruit Matrix Bonnet using the RGB888 memory layout (4 bytes per pixel) + +This is deprecated shorthand for `PioMatter(Colorspace.RGB888, Pinout.AdafruitMatrixBonnet, ...)`. )pbdoc") //.doc() = ; m.def("AdafruitMatrixBonnetRGB888Packed", - make_piomatter, + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB888Packed, Pinout::AdafruitMatrixBonnet, buffer, geometry); + }, py::arg("framebuffer"), py::arg("geometry"), R"pbdoc( Construct a PioMatter object to drive panels connected to an Adafruit Matrix Bonnet using the RGB888 packed memory layout (3 bytes per pixel) + +This is deprecated shorthand for `PioMatter(Colorspace.RGB888Packed, Pinout.AdafruitMatrixBonnet, ...)`. )pbdoc"); } From a2f147054888d33c9bad7870fbcf8c355f654a89 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 8 Feb 2025 10:28:38 -0600 Subject: [PATCH 2/5] fix ignore of files in top level --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 83eb938..ba69057 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -./*.pio.h +/*.pio.h protodemo -./build +/build *.egg-info From f33e9ca82e07b6b7449dfdb8b35c80a15d1f3697 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 8 Feb 2025 10:29:34 -0600 Subject: [PATCH 3/5] Format code with pre-commit --- src/include/piomatter/pins.h | 1 - src/pymain.cpp | 143 ++++++++++++++++++++--------------- 2 files changed, 81 insertions(+), 63 deletions(-) diff --git a/src/include/piomatter/pins.h b/src/include/piomatter/pins.h index 840477d..ba61903 100644 --- a/src/include/piomatter/pins.h +++ b/src/include/piomatter/pins.h @@ -40,5 +40,4 @@ struct adafruit_matrix_bonnet_pinout_bgr { static constexpr uint32_t post_addr_delay = 500; }; - } // namespace piomatter diff --git a/src/pymain.cpp b/src/pymain.cpp index f89129d..d7be92b 100644 --- a/src/pymain.cpp +++ b/src/pymain.cpp @@ -23,7 +23,8 @@ struct PyPiomatter { template std::unique_ptr -make_piomatter_pc(py::buffer buffer, const piomatter::matrix_geometry &geometry) { +make_piomatter_pc(py::buffer buffer, + const piomatter::matrix_geometry &geometry) { using cls = piomatter::piomatter; using data_type = colorspace::data_type; @@ -34,9 +35,11 @@ make_piomatter_pc(py::buffer buffer, const piomatter::matrix_geometry &geometry) if (buffer_size_in_bytes != data_size_in_bytes) { throw std::runtime_error( - py::str( - "Framebuffer size must be {} bytes ({} elements of {} bytes each), got a buffer of {} bytes") - .attr("format")(data_size_in_bytes, n_pixels, colorspace::data_size_in_bytes(1), buffer_size_in_bytes) + py::str("Framebuffer size must be {} bytes ({} elements of {} " + "bytes each), got a buffer of {} bytes") + .attr("format")(data_size_in_bytes, n_pixels, + colorspace::data_size_in_bytes(1), + buffer_size_in_bytes) .template cast()); } @@ -46,44 +49,49 @@ make_piomatter_pc(py::buffer buffer, const piomatter::matrix_geometry &geometry) buffer, std::move(std::make_unique(framebuffer, geometry))); } -enum Colorspace { - RGB565, RGB888, RGB888Packed -}; +enum Colorspace { RGB565, RGB888, RGB888Packed }; enum Pinout { AdafruitMatrixBonnet, AdafruitMatrixBonnetBGR, }; -template +template std::unique_ptr -make_piomatter_p(Colorspace c, py::buffer buffer, const piomatter::matrix_geometry &geometry) { - switch(c) { - case RGB565: - return make_piomatter_pc(buffer, geometry); - case RGB888: - return make_piomatter_pc(buffer, geometry); - case RGB888Packed: - return make_piomatter_pc(buffer, geometry); +make_piomatter_p(Colorspace c, py::buffer buffer, + const piomatter::matrix_geometry &geometry) { + switch (c) { + case RGB565: + return make_piomatter_pc( + buffer, geometry); + case RGB888: + return make_piomatter_pc( + buffer, geometry); + case RGB888Packed: + return make_piomatter_pc( + buffer, geometry); - default: - throw std::runtime_error( - py::str("Invalid colorspace {!r}").attr("format")(c) - .template cast()); + default: + throw std::runtime_error(py::str("Invalid colorspace {!r}") + .attr("format")(c) + .template cast()); } } std::unique_ptr -make_piomatter(Colorspace c, Pinout p, py::buffer buffer, const piomatter::matrix_geometry &geometry) { - switch(p) { - case AdafruitMatrixBonnet: - return make_piomatter_p(c, buffer, geometry); - case AdafruitMatrixBonnetBGR: - return make_piomatter_p(c, buffer, geometry); - default: - throw std::runtime_error( - py::str("Invalid pinout {!r}").attr("format")(p) - .template cast()); +make_piomatter(Colorspace c, Pinout p, py::buffer buffer, + const piomatter::matrix_geometry &geometry) { + switch (p) { + case AdafruitMatrixBonnet: + return make_piomatter_p( + c, buffer, geometry); + case AdafruitMatrixBonnetBGR: + return make_piomatter_p( + c, buffer, geometry); + default: + throw std::runtime_error(py::str("Invalid pinout {!r}") + .attr("format")(p) + .template cast()); } } } // namespace @@ -121,18 +129,22 @@ PYBIND11_MODULE(adafruit_raspberry_pi5_piomatter, m) { py::enum_( m, "Pinout", "Describes the pins used for the connection to the matrix") - .value("AdafruitMatrixBonnet", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") - .value("AdafruitMatrixBonnetBGR", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") - .value("AdafruitMatrixHat", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") - .value("AdafruitMatrixHatBGR", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") - ; + .value("AdafruitMatrixBonnet", Pinout::AdafruitMatrixBonnet, + "Adafruit Matrix Bonnet or Matrix Hat") + .value("AdafruitMatrixBonnetBGR", Pinout::AdafruitMatrixBonnet, + "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") + .value("AdafruitMatrixHat", Pinout::AdafruitMatrixBonnet, + "Adafruit Matrix Bonnet or Matrix Hat") + .value("AdafruitMatrixHatBGR", Pinout::AdafruitMatrixBonnet, + "Adafruit Matrix Bonnet or Matrix Hat with BGR color order"); py::enum_( - m, "Colorspace", "Describes the organization of the graphics data in memory") - .value("RGB888Packed", Colorspace::RGB888Packed, "3 bytes per pixel in RGB order") + m, "Colorspace", + "Describes the organization of the graphics data in memory") + .value("RGB888Packed", Colorspace::RGB888Packed, + "3 bytes per pixel in RGB order") .value("RGB888", Colorspace::RGB888, "4 bytes per pixel in RGB order") - .value("RGB565", Colorspace::RGB565, "2 bytes per pixel in RGB order") - ; + .value("RGB565", Colorspace::RGB565, "2 bytes per pixel in RGB order"); py::class_(m, "Geometry", R"pbdoc( Describe the geometry of a set of panels @@ -198,9 +210,8 @@ The default, 10, is the maximum value. py::class_(m, "PioMatter", R"pbdoc( HUB75 matrix driver for Raspberry Pi 5 using PIO )pbdoc") - .def(py::init(&make_piomatter), - py::arg("colorspace"), py::arg("pinout"), - py::arg("framebuffer"), py::arg("geometry")) + .def(py::init(&make_piomatter), py::arg("colorspace"), + py::arg("pinout"), py::arg("framebuffer"), py::arg("geometry")) .def("show", &PyPiomatter::show, R"pbdoc( Update the displayed image @@ -212,26 +223,31 @@ data is triple-buffered to prevent tearing. The approximate number of matrix refreshes per second. )pbdoc"); - m.def("AdafruitMatrixBonnetRGB565", - [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { - return make_piomatter(Colorspace::RGB565, Pinout::AdafruitMatrixBonnet, buffer, geometry); - }, - py::arg("buffer"), py::arg("geometry"), - R"pbdoc( + m.def( + "AdafruitMatrixBonnetRGB565", + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB565, + Pinout::AdafruitMatrixBonnet, buffer, + geometry); + }, + py::arg("buffer"), py::arg("geometry"), + R"pbdoc( Construct a PioMatter object to drive panels connected to an Adafruit Matrix Bonnet using the RGB565 memory layout (2 bytes per pixel) This is deprecated shorthand for `PioMatter(Colorspace.RGB565, Pinout.AdafruitMatrixBonnet, ...)`. -)pbdoc") - ; +)pbdoc"); - m.def("AdafruitMatrixBonnetRGB888", - [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { - return make_piomatter(Colorspace::RGB888, Pinout::AdafruitMatrixBonnet, buffer, geometry); - }, - py::arg("framebuffer"), py::arg("geometry"), - R"pbdoc( + m.def( + "AdafruitMatrixBonnetRGB888", + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB888, + Pinout::AdafruitMatrixBonnet, buffer, + geometry); + }, + py::arg("framebuffer"), py::arg("geometry"), + R"pbdoc( Construct a PioMatter object to drive panels connected to an Adafruit Matrix Bonnet using the RGB888 memory layout (4 bytes per pixel) @@ -241,12 +257,15 @@ This is deprecated shorthand for `PioMatter(Colorspace.RGB888, Pinout.AdafruitMa //.doc() = ; - m.def("AdafruitMatrixBonnetRGB888Packed", - [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { - return make_piomatter(Colorspace::RGB888Packed, Pinout::AdafruitMatrixBonnet, buffer, geometry); - }, - py::arg("framebuffer"), py::arg("geometry"), - R"pbdoc( + m.def( + "AdafruitMatrixBonnetRGB888Packed", + [](py::buffer buffer, const piomatter::matrix_geometry &geometry) { + return make_piomatter(Colorspace::RGB888Packed, + Pinout::AdafruitMatrixBonnet, buffer, + geometry); + }, + py::arg("framebuffer"), py::arg("geometry"), + R"pbdoc( Construct a PioMatter object to drive panels connected to an Adafruit Matrix Bonnet using the RGB888 packed memory layout (3 bytes per pixel) From d27897fd18ed9a03d93faac54dc1e4e532852c63 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 8 Feb 2025 18:34:50 -0600 Subject: [PATCH 4/5] add an example for the 64x64 matrix --- examples/simpletest_addre_bgr.py | 25 +++++++++++++++++++++++++ src/pymain.cpp | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 examples/simpletest_addre_bgr.py diff --git a/examples/simpletest_addre_bgr.py b/examples/simpletest_addre_bgr.py new file mode 100644 index 0000000..52d33c1 --- /dev/null +++ b/examples/simpletest_addre_bgr.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +""" +Display a static 64x64 image + +This assumes a 64x64 matrix with "BGR" pixel order, such as https://www.adafruit.com/product/5362 + +Run like this: + +$ python simpletest.py + +The image is displayed until the user hits enter to exit. +""" + +import pathlib + +import adafruit_raspberry_pi5_piomatter as piomatter +import numpy as np +import PIL.Image as Image + +geometry = piomatter.Geometry(width=64, height=64, n_addr_lines=5, rotation=piomatter.Orientation.Normal, n_planes=8) +framebuffer = np.asarray(Image.open(pathlib.Path(__file__).parent / "blinka64x64.png")) +matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed, pinout=piomatter.Pinout.AdafruitMatrixBonnetBGR, framebuffer=framebuffer, geometry=geometry) +matrix.show() + +input("Hit enter to exit") diff --git a/src/pymain.cpp b/src/pymain.cpp index d7be92b..9d5a7f9 100644 --- a/src/pymain.cpp +++ b/src/pymain.cpp @@ -131,7 +131,7 @@ PYBIND11_MODULE(adafruit_raspberry_pi5_piomatter, m) { m, "Pinout", "Describes the pins used for the connection to the matrix") .value("AdafruitMatrixBonnet", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") - .value("AdafruitMatrixBonnetBGR", Pinout::AdafruitMatrixBonnet, + .value("AdafruitMatrixBonnetBGR", Pinout::AdafruitMatrixBonnetBGR, "Adafruit Matrix Bonnet or Matrix Hat with BGR color order") .value("AdafruitMatrixHat", Pinout::AdafruitMatrixBonnet, "Adafruit Matrix Bonnet or Matrix Hat") From 1a3f5d4ebef77ca3d938fcfa1edca33cdfeb4e37 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 9 Feb 2025 10:25:33 -0600 Subject: [PATCH 5/5] examples: Add common argument parsing code, use it in fbmirror Now you can set most anything you'd want from the commandline: ``` Usage: fbmirror.py [OPTIONS] Options: --x-offset INTEGER The x offset of top left corner of the region to mirror --y-offset INTEGER The y offset of top left corner of the region to mirror --num-address-lines INTEGER The number of address lines used by the panels --num-planes INTEGER The number of bit planes (color depth. Lower values can improve refresh rate in frames per second --orientation [Normal|R180|CCW|CW] The overall orientation (rotation) of the panels --pinout [AdafruitMatrixBonnet|AdafruitMatrixBonnetBGR|AdafruitMatrixHat|AdafruitMatrixHatBGR] The details of the electrical connection to the panels --colorspace [RGB888Packed|RGB888|RGB565] The memory organization of the framebuffer --serpentine / --no-serpentine The organization of multiple panels --height INTEGER The panel height in pixels --width INTEGER The panel width in pixels --help Show this message and exit. ``` It might would be good to apply this generally across the examples. --- examples/fbmirror.py | 71 +++++++++-------------------- examples/piomatter_click.py | 90 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 examples/piomatter_click.py 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)