/ src / pymain.cpp
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  }