pymain.cpp
1 #include <iostream> 2 #include <pybind11/pybind11.h> 3 #include <string> 4 5 #include "piomatter/piomatter.h" 6 7 #define STRINGIFY(x) #x 8 #define MACRO_STRINGIFY(x) STRINGIFY(x) 9 10 namespace py = pybind11; 11 12 namespace { 13 struct PyPiomatter { 14 PyPiomatter(py::buffer buffer, 15 std::unique_ptr<piomatter::piomatter_base> &&matter) 16 : buffer{buffer}, matter{std::move(matter)} {} 17 py::buffer buffer; 18 std::unique_ptr<piomatter::piomatter_base> matter; 19 20 void show() { matter->show(); } 21 }; 22 23 template <typename pinout, typename colorspace> 24 std::unique_ptr<PyPiomatter> 25 make_piomatter(py::buffer buffer, const piomatter::matrix_geometry &geometry) { 26 using cls = piomatter::piomatter<pinout, colorspace>; 27 using data_type = colorspace::data_type; 28 29 const auto n_pixels = geometry.width * geometry.height; 30 const auto data_size_in_bytes = colorspace::data_size_in_bytes(n_pixels); 31 const py::buffer_info info = buffer.request(); 32 const size_t buffer_size_in_bytes = info.size * info.itemsize; 33 34 if (buffer_size_in_bytes != data_size_in_bytes) { 35 throw std::runtime_error( 36 py::str( 37 "Framebuffer size must be {} bytes, got a buffer of {} bytes") 38 .attr("format")(data_size_in_bytes, buffer_size_in_bytes) 39 .template cast<std::string>()); 40 } 41 42 std::span<data_type> framebuffer(reinterpret_cast<data_type *>(info.ptr), 43 data_size_in_bytes / sizeof(data_type)); 44 return std::make_unique<PyPiomatter>( 45 buffer, std::move(std::make_unique<cls>(framebuffer, geometry))); 46 } 47 } // namespace 48 49 PYBIND11_MODULE(adafruit_raspberry_pi5_piomatter, m) { 50 py::options options; 51 options.enable_enum_members_docstring(); 52 options.enable_function_signatures(); 53 options.enable_user_defined_docstrings(); 54 55 m.doc() = R"pbdoc( 56 HUB75 matrix driver for Raspberry Pi 5 using PIO 57 ------------------------------------------------ 58 59 .. currentmodule:: adafruit_raspberry_pi5_piomatter 60 61 .. autosummary:: 62 :toctree: _generate 63 64 Orientation 65 Geometry 66 PioMatter 67 AdafruitMatrixBonnetRGB888 68 AdafruitMatrixBonnetRGB888Packed 69 )pbdoc"; 70 71 py::enum_<piomatter::orientation>( 72 m, "Orientation", "Describe the orientation of a set of panels") 73 .value("Normal", piomatter::orientation::normal, "Normal orientation") 74 .value("R180", piomatter::orientation::r180, "Rotated 180 degrees") 75 .value("CCW", piomatter::orientation::ccw, 76 "Rotated 90 degress counterclockwise") 77 .value("CW", piomatter::orientation::cw, 78 "Rotated 90 degress clockwise"); 79 80 py::class_<piomatter::matrix_geometry>(m, "Geometry", R"pbdoc( 81 Describe the geometry of a set of panels 82 83 ``width`` and ``height`` give the panel resolution in pixels. 84 85 ``n_addr_lines`` gives the number of connected address lines. 86 87 The number of pixels in the shift register is automatically computed from these values. 88 89 ``serpentine`` controls the arrangement of multiple panels when they are stacked in rows. 90 If it is `True`, then each row goes in the opposite direction of the previous row. 91 92 ``n_planes`` controls the color depth of the panel. This is separate from the framebuffer 93 layout. Decreasing ``n_planes`` can increase FPS at the cost of reduced color fidelity. 94 The default, 10, is the maximum value. 95 )pbdoc") 96 .def(py::init([](size_t width, size_t height, size_t n_addr_lines, 97 bool serpentine, piomatter::orientation rotation, 98 size_t n_planes) { 99 size_t n_lines = 2 << n_addr_lines; 100 size_t pixels_across = width * height / n_lines; 101 size_t odd = (width * height) % n_lines; 102 if (odd) { 103 throw std::runtime_error( 104 py::str( 105 "Total pixel count {} must be a multiple of {}, " 106 "the number of distinct row addresses for {}") 107 .attr("format")(width * height, n_lines, 108 n_addr_lines) 109 .cast<std::string>()); 110 } 111 switch (rotation) { 112 case piomatter::orientation::normal: 113 return piomatter::matrix_geometry( 114 pixels_across, n_addr_lines, n_planes, width, height, 115 serpentine, piomatter::orientation_normal); 116 117 case piomatter::orientation::r180: 118 return piomatter::matrix_geometry( 119 pixels_across, n_addr_lines, n_planes, width, height, 120 serpentine, piomatter::orientation_r180); 121 122 case piomatter::orientation::ccw: 123 return piomatter::matrix_geometry( 124 pixels_across, n_addr_lines, n_planes, width, height, 125 serpentine, piomatter::orientation_ccw); 126 127 case piomatter::orientation::cw: 128 return piomatter::matrix_geometry( 129 pixels_across, n_addr_lines, n_planes, width, height, 130 serpentine, piomatter::orientation_cw); 131 } 132 throw std::runtime_error("invalid rotation"); 133 }), 134 py::arg("width"), py::arg("height"), py::arg("n_addr_lines"), 135 py::arg("serpentine") = true, 136 py::arg("rotation") = piomatter::orientation::normal, 137 py::arg("n_planes") = 10u) 138 .def_readonly("width", &piomatter::matrix_geometry::width) 139 .def_readonly("height", &piomatter::matrix_geometry::height); 140 141 py::class_<PyPiomatter>(m, "PioMatter", R"pbdoc( 142 HUB75 matrix driver for Raspberry Pi 5 using PIO 143 144 Do not create instances of this class directly. Instead, use one of 145 the constructors such as `AdafruitMatrixBonnetRGB888Packed` to 146 select a specific pinout & in-memory framebuffer layout. 147 )pbdoc") 148 .def("show", &PyPiomatter::show, R"pbdoc( 149 Update the displayed image 150 151 After modifying the content of the framebuffer, call this method to 152 update the data actually displayed on the panel. Internally, the 153 data is triple-buffered to prevent tearing. 154 )pbdoc"); 155 156 m.def("AdafruitMatrixBonnetRGB888", 157 make_piomatter<piomatter::adafruit_matrix_bonnet_pinout, 158 piomatter::colorspace_rgb888>, 159 py::arg("framebuffer"), py::arg("geometry"), 160 R"pbdoc( 161 Construct a PioMatter object to drive panels connected to an 162 Adafruit Matrix Bonnet using the RGB888 memory layout (4 bytes per 163 pixel) 164 )pbdoc") 165 //.doc() = 166 ; 167 168 m.def("AdafruitMatrixBonnetRGB888Packed", 169 make_piomatter<piomatter::adafruit_matrix_bonnet_pinout, 170 piomatter::colorspace_rgb888_packed>, 171 py::arg("framebuffer"), py::arg("geometry"), 172 R"pbdoc( 173 Construct a PioMatter object to drive panels connected to an 174 Adafruit Matrix Bonnet using the RGB888 packed memory layout (3 175 bytes per pixel) 176 )pbdoc"); 177 }