From 9ad8db6ca08542e69ec8a16d4983113858fa8c72 Mon Sep 17 00:00:00 2001 From: David Vo Date: Sun, 9 Feb 2025 02:41:17 +1100 Subject: [PATCH 01/10] WIP: Add AddressableLEDBuffer --- .../wpilib/src/rpy/AddressableLEDBuffer.h | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h new file mode 100644 index 000000000..a33f3a1d3 --- /dev/null +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -0,0 +1,163 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include "frc/AddressableLED.h" +#include "frc/util/Color.h" +#include "frc/util/Color8Bit.h" + +namespace frc { + +/** + * Buffer storage for Addressable LEDs. + */ +class AddressableLEDBuffer { + public: + /** + * Constructs a new LED buffer with the specified length. + * + * @param length The length of the buffer in pixels + */ + explicit AddressableLEDBuffer(size_t length) : m_buffer(length) {} + + /** + * Sets a specific LED in the buffer. + * + * @param index the index to write + * @param r the r value [0-255] + * @param g the g value [0-255] + * @param b the b value [0-255] + */ + void SetRGB(size_t index, int r, int g, int b) { + m_buffer.at(index).SetRGB(r, g, b); + } + + /** + * Sets a specific LED in the buffer. + * + * @param index the index to write + * @param h the h value [0-180) + * @param s the s value [0-255] + * @param v the v value [0-255] + */ + void SetHSV(size_t index, int h, int s, int v) { + m_buffer.at(index).SetHSV(h, s, v); + } + + /** + * Sets a specific LED in the buffer. + * + * @param index the index to write + * @param color the color to write + */ + void SetLED(size_t index, const Color& color) { + m_buffer.at(index).SetLED(color); + } + + /** + * Sets a specific LED in the buffer. + * + * @param index the index to write + * @param color the color to write + */ + void SetLED(size_t index, const Color8Bit& color) { + m_buffer.at(index).SetLED(color); + } + + /** + * Gets the buffer length. + * + * @return the buffer length + */ + size_t size() const { return m_buffer.size(); } + + /** + * Gets the red value at the specified index. + * + * @param index the index + * @return the red value + */ + int GetRed(size_t index) const { + return m_buffer.at(index).r; + } + + /** + * Gets the green value at the specified index. + * + * @param index the index + * @return the green value + */ + int GetGreen(size_t index) const { + return m_buffer.at(index).g; + } + + /** + * Gets the blue value at the specified index. + * + * @param index the index + * @return the blue value + */ + int GetBlue(size_t index) const { + return m_buffer.at(index).b; + } + + /** + * Gets the color at the specified index. + * + * @param index the index + * @return the LED color + */ + Color GetLED(size_t index) const { + const auto& led = m_buffer.at(index); + return Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; + } + + /** + * Gets the color at the specified index. + * + * @param index the index + * @return the LED color + */ + Color8Bit GetLED8Bit(size_t index) const { + const auto& led = m_buffer.at(index); + return Color8Bit{led.r, led.g, led.b}; + } + + /** + * Implicit conversion to span of LED data + */ + operator std::span() { + return std::span{m_buffer}; + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return reference to the LED data + */ + AddressableLED::LEDData& operator[](size_t index) { + return m_buffer.at(index); + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return const reference to the LED data + */ + const AddressableLED::LEDData& operator[](size_t index) const { + return m_buffer.at(index); + } + + auto begin() { return m_buffer.begin(); } + auto end() { return m_buffer.end(); } + + private: + std::vector m_buffer; +}; + +} // namespace frc From 58df7cfaea09ae52cc9a55566260ed4a2f479083 Mon Sep 17 00:00:00 2001 From: David Vo Date: Mon, 10 Feb 2025 01:20:20 +1100 Subject: [PATCH 02/10] Fully qualify frc::AddressableLED --- .../wpilib/src/rpy/AddressableLEDBuffer.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index a33f3a1d3..e77b09553 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -129,8 +129,8 @@ class AddressableLEDBuffer { /** * Implicit conversion to span of LED data */ - operator std::span() { - return std::span{m_buffer}; + operator std::span() { + return std::span{m_buffer}; } /** @@ -139,7 +139,7 @@ class AddressableLEDBuffer { * @param index the index * @return reference to the LED data */ - AddressableLED::LEDData& operator[](size_t index) { + frc::AddressableLED::LEDData& operator[](size_t index) { return m_buffer.at(index); } @@ -149,7 +149,7 @@ class AddressableLEDBuffer { * @param index the index * @return const reference to the LED data */ - const AddressableLED::LEDData& operator[](size_t index) const { + const frc::AddressableLED::LEDData& operator[](size_t index) const { return m_buffer.at(index); } @@ -157,7 +157,7 @@ class AddressableLEDBuffer { auto end() { return m_buffer.end(); } private: - std::vector m_buffer; + std::vector m_buffer; }; } // namespace frc From 3983a09d1d37c9cd1b5977e96b5db49a7747c74f Mon Sep 17 00:00:00 2001 From: David Vo Date: Fri, 14 Feb 2025 02:24:43 +1100 Subject: [PATCH 03/10] Fully qualify Color --- .../wpilib/src/rpy/AddressableLEDBuffer.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index e77b09553..3f4e8f703 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -53,7 +53,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const Color& color) { + void SetLED(size_t index, const frc::Color& color) { m_buffer.at(index).SetLED(color); } @@ -63,7 +63,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const Color8Bit& color) { + void SetLED(size_t index, const frc::Color8Bit& color) { m_buffer.at(index).SetLED(color); } @@ -110,9 +110,9 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - Color GetLED(size_t index) const { + frc::Color GetLED(size_t index) const { const auto& led = m_buffer.at(index); - return Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; + return frc::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; } /** @@ -121,9 +121,9 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - Color8Bit GetLED8Bit(size_t index) const { + frc::Color8Bit GetLED8Bit(size_t index) const { const auto& led = m_buffer.at(index); - return Color8Bit{led.r, led.g, led.b}; + return frc::Color8Bit{led.r, led.g, led.b}; } /** From 102d43dc5426b804780508c9a32c5d0884cdcf48 Mon Sep 17 00:00:00 2001 From: David Vo Date: Fri, 14 Feb 2025 02:27:24 +1100 Subject: [PATCH 04/10] Add AddressableLEDBuffer::View Probably not a great idea, we could probably manually bind std::span instead... --- .../wpilib/src/rpy/AddressableLEDBuffer.h | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index 3f4e8f703..6ecc3dfcf 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "frc/AddressableLED.h" #include "frc/util/Color.h" @@ -156,6 +157,140 @@ class AddressableLEDBuffer { auto begin() { return m_buffer.begin(); } auto end() { return m_buffer.end(); } + /** + * A view of another addressable LED buffer. Views provide an easy way to split a large LED + * strip into smaller sections that can be animated individually. + */ + class View { + public: + /** + * Gets the length of the view. + */ + size_t size() const { return m_data.size(); } + + /** + * Sets a specific LED in the view. + * + * @param index the index to write + * @param r the r value [0-255] + * @param g the g value [0-255] + * @param b the b value [0-255] + */ + void SetRGB(size_t index, int r, int g, int b) { + m_data.at(index).SetRGB(r, g, b); + } + + /** + * Sets a specific LED in the view. + * + * @param index the index to write + * @param h the h value [0-180) + * @param s the s value [0-255] + * @param v the v value [0-255] + */ + void SetHSV(size_t index, int h, int s, int v) { + m_data.at(index).SetHSV(h, s, v); + } + + /** + * Sets a specific LED in the view. + * + * @param index the index to write + * @param color the color to write + */ + void SetLED(size_t index, const frc::Color& color) { + m_data.at(index).SetLED(color); + } + + /** + * Sets a specific LED in the view. + * + * @param index the index to write + * @param color the color to write + */ + void SetLED(size_t index, const frc::Color8Bit& color) { + m_data.at(index).SetLED(color); + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return reference to the LED data + */ + frc::AddressableLED::LEDData& operator[](size_t index) { + return m_data.at(index); + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return const reference to the LED data + */ + const frc::AddressableLED::LEDData& operator[](size_t index) const { + return m_data.at(index); + } + + /** + * Gets the color at the specified index. + * + * @param index the index + * @return the LED color + */ + frc::Color GetLED(size_t index) const { + const auto& led = m_data.at(index); + return frc::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; + } + + /** + * Gets the color at the specified index. + * + * @param index the index + * @return the LED color + */ + frc::Color8Bit GetLED8Bit(size_t index) const { + const auto& led = m_data.at(index); + return frc::Color8Bit{led.r, led.g, led.b}; + } + + /** + * Implicit conversion to span of LED data + */ + operator std::span() { + return m_data; + } + + /** + * Implicit conversion to span of const LED data + */ + operator std::span() const { + return m_data; + } + + private: + friend class AddressableLEDBuffer; + explicit View(std::span data) + : m_data(data) {} + + std::span m_data; + }; + + /** + * Creates a read/write view of this buffer. + * + * @param start the starting index (inclusive) + * @param end the ending index (exclusive) + * @return View object representing the view + * @throws std::out_of_range if the view would exceed buffer bounds + */ + View CreateView(size_t start, size_t end) { + if (start >= m_buffer.size() || end >= m_buffer.size()) { + throw std::out_of_range("View range out of bounds"); + } + return View(std::span(m_buffer).subspan(start, end - start)); + } + private: std::vector m_buffer; }; From 239e800636ed0d68a79fa0386919683752276393 Mon Sep 17 00:00:00 2001 From: David Vo Date: Mon, 12 Jan 2026 17:29:44 +1100 Subject: [PATCH 05/10] Get it building --- subprojects/robotpy-wpilib/pyproject.toml | 2 + .../semiwrap/AddressableLEDBuffer.yml | 56 ++++++++++++++++++ subprojects/robotpy-wpilib/wpilib/__init__.py | 2 + .../wpilib/src/rpy/AddressableLEDBuffer.h | 57 ++++++++++++++++--- 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml diff --git a/subprojects/robotpy-wpilib/pyproject.toml b/subprojects/robotpy-wpilib/pyproject.toml index 7db9006c6..3721f5bee 100644 --- a/subprojects/robotpy-wpilib/pyproject.toml +++ b/subprojects/robotpy-wpilib/pyproject.toml @@ -213,6 +213,8 @@ SysIdRoutineLog = "frc/sysid/SysIdRoutineLog.h" Color = "frc/util/Color.h" Color8Bit = "frc/util/Color8Bit.h" +# rpy only +AddressableLEDBuffer = "rpy/AddressableLEDBuffer.h" [tool.semiwrap.extension_modules."wpilib.counter._counter"] name = "wpilib_counter" diff --git a/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml b/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml new file mode 100644 index 000000000..8e345a3d7 --- /dev/null +++ b/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml @@ -0,0 +1,56 @@ +classes: + frc::AddressableLEDBuffer: + methods: + AddressableLEDBuffer: + SetRGB: + SetHSV: + SetLED: + overloads: + size_t, const frc::Color&: + size_t, const frc::Color8Bit&: + size: + rename: __len__ + GetRed: + GetGreen: + GetBlue: + GetLED: + GetLED8Bit: + at: + rename: __getitem__ + begin: + ignore: true + end: + ignore: true + CreateView: + keepalive: + - [0, 1] + inline_code: | + .def("__iter__", [](frc::AddressableLEDBuffer& self) { + return py::make_iterator(self.begin(), self.end()); + }, py::keep_alive<0, 1>()) + frc::AddressableLEDBuffer::View: + methods: + size: + rename: __len__ + SetRGB: + SetHSV: + SetLED: + overloads: + size_t, const frc::Color&: + size_t, const frc::Color8Bit&: + at: + rename: __getitem__ + overloads: + size_t: + size_t [const]: + ignore: true + begin: + ignore: true + end: + ignore: true + GetLED: + GetLED8Bit: + inline_code: | + .def("__iter__", [](frc::AddressableLEDBuffer::View& self) { + return py::make_iterator(self.begin(), self.end()); + }, py::keep_alive<0, 1>()) diff --git a/subprojects/robotpy-wpilib/wpilib/__init__.py b/subprojects/robotpy-wpilib/wpilib/__init__.py index d11a4e410..206fa0447 100644 --- a/subprojects/robotpy-wpilib/wpilib/__init__.py +++ b/subprojects/robotpy-wpilib/wpilib/__init__.py @@ -12,6 +12,7 @@ ADXL362, ADXRS450_Gyro, AddressableLED, + AddressableLEDBuffer, Alert, AnalogAccelerometer, AnalogEncoder, @@ -118,6 +119,7 @@ "ADXL362", "ADXRS450_Gyro", "AddressableLED", + "AddressableLEDBuffer", "Alert", "AnalogAccelerometer", "AnalogEncoder", diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index 6ecc3dfcf..8281c3e41 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -134,6 +134,16 @@ class AddressableLEDBuffer { return std::span{m_buffer}; } + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return reference to the LED data + */ + frc::AddressableLED::LEDData& at(size_t index) { + return m_buffer.at(index); + } + /** * Gets the LED data at the specified index. * @@ -177,7 +187,7 @@ class AddressableLEDBuffer { * @param b the b value [0-255] */ void SetRGB(size_t index, int r, int g, int b) { - m_data.at(index).SetRGB(r, g, b); + at(index).SetRGB(r, g, b); } /** @@ -189,7 +199,7 @@ class AddressableLEDBuffer { * @param v the v value [0-255] */ void SetHSV(size_t index, int h, int s, int v) { - m_data.at(index).SetHSV(h, s, v); + at(index).SetHSV(h, s, v); } /** @@ -199,7 +209,7 @@ class AddressableLEDBuffer { * @param color the color to write */ void SetLED(size_t index, const frc::Color& color) { - m_data.at(index).SetLED(color); + at(index).SetLED(color); } /** @@ -209,7 +219,21 @@ class AddressableLEDBuffer { * @param color the color to write */ void SetLED(size_t index, const frc::Color8Bit& color) { - m_data.at(index).SetLED(color); + at(index).SetLED(color); + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return reference to the LED data + */ + frc::AddressableLED::LEDData& at(size_t index) { + // std::span::at doesn't exist until C++26 + if (index >= m_data.size()) { + throw std::out_of_range("Index out of range"); + } + return m_data[index]; } /** @@ -219,7 +243,21 @@ class AddressableLEDBuffer { * @return reference to the LED data */ frc::AddressableLED::LEDData& operator[](size_t index) { - return m_data.at(index); + return at(index); + } + + /** + * Gets the LED data at the specified index. + * + * @param index the index + * @return const reference to the LED data + */ + const frc::AddressableLED::LEDData& at(size_t index) const { + // std::span::at doesn't exist until C++26 + if (index >= m_data.size()) { + throw std::out_of_range("Index out of range"); + } + return m_data[index]; } /** @@ -229,9 +267,12 @@ class AddressableLEDBuffer { * @return const reference to the LED data */ const frc::AddressableLED::LEDData& operator[](size_t index) const { - return m_data.at(index); + return at(index); } + auto begin() { return m_data.begin(); } + auto end() { return m_data.end(); } + /** * Gets the color at the specified index. * @@ -239,7 +280,7 @@ class AddressableLEDBuffer { * @return the LED color */ frc::Color GetLED(size_t index) const { - const auto& led = m_data.at(index); + const auto& led = at(index); return frc::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; } @@ -250,7 +291,7 @@ class AddressableLEDBuffer { * @return the LED color */ frc::Color8Bit GetLED8Bit(size_t index) const { - const auto& led = m_data.at(index); + const auto& led = at(index); return frc::Color8Bit{led.r, led.g, led.b}; } From d7a3f75cb60f886061f38227efbe58238e726364 Mon Sep 17 00:00:00 2001 From: David Vo Date: Sun, 15 Feb 2026 16:04:35 +1100 Subject: [PATCH 06/10] AddressableLEDBuffer[slice] -> View --- .../wpilib/src/rpy/AddressableLEDBuffer.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index 8281c3e41..d5fa679b5 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -5,10 +5,12 @@ #pragma once #include +#include #include #include "frc/AddressableLED.h" #include "frc/util/Color.h" #include "frc/util/Color8Bit.h" +#include "pybind11/pytypes.h" namespace frc { @@ -332,6 +334,18 @@ class AddressableLEDBuffer { return View(std::span(m_buffer).subspan(start, end - start)); } + View operator[](pybind11::slice slice) { + ssize_t start = 0, stop = 0, step = 0, slicelength = 0; + slice.compute(m_buffer.size(), &start, &stop, &step, &slicelength); + if (step != 1) { + throw std::out_of_range("step != 1"); + } + if (!slicelength) { + throw std::out_of_range("zero length view"); + } + return View(std::span(m_buffer).subspan(start, slicelength)); + } + private: std::vector m_buffer; }; From a600dc8e8c9239086d6ec265323e778a0a6f2e2c Mon Sep 17 00:00:00 2001 From: David Vo Date: Sun, 15 Feb 2026 16:05:23 +1100 Subject: [PATCH 07/10] tests --- .../tests/test_addressable_led_buffer.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py diff --git a/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py b/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py new file mode 100644 index 000000000..fc38086ca --- /dev/null +++ b/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py @@ -0,0 +1,163 @@ +# Copyright (c) FIRST and other WPILib contributors. +# Open Source Software; you can modify and/or share it under the terms of +# the WPILib BSD license file in the root directory of this project. + +import pytest + +from wpilib import AddressableLEDBuffer, Color, Color8Bit + +AddressableLEDBufferView = AddressableLEDBuffer.View + + +class TestAddressableLEDBuffer: + """Tests for AddressableLEDBuffer""" + + @pytest.mark.parametrize( + "h,s,v,r,g,b", + [ + (0, 0, 0, 0, 0, 0), # Black + (0, 0, 255, 255, 255, 255), # White + (0, 255, 255, 255, 0, 0), # Red + (60, 255, 255, 0, 255, 0), # Lime + (120, 255, 255, 0, 0, 255), # Blue + (30, 255, 255, 255, 255, 0), # Yellow + (90, 255, 255, 0, 255, 255), # Cyan + (150, 255, 255, 255, 0, 255), # Magenta + (0, 0, 191, 191, 191, 191), # Silver + (0, 0, 128, 128, 128, 128), # Gray + (0, 255, 128, 128, 0, 0), # Maroon + (30, 255, 128, 128, 128, 0), # Olive + (60, 255, 128, 0, 128, 0), # Green + (150, 255, 128, 128, 0, 128), # Purple + (90, 255, 128, 0, 128, 128), # Teal + (120, 255, 128, 0, 0, 128), # Navy + ], + ) + def test_hsv_convert(self, h, s, v, r, g, b): + """Test HSV to RGB conversion""" + buffer = AddressableLEDBuffer(length=1) + buffer.setHSV(0, h, s, v) + color = buffer.getLED8Bit(0) + assert color.red == r, "R value didn't match" + assert color.green == g, "G value didn't match" + assert color.blue == b, "B value didn't match" + + def test_get_color(self): + """Test getting colors from buffer""" + buffer = AddressableLEDBuffer(4) + denim_color_8bit = Color8Bit(Color.kDenim) + first_blue_color_8bit = Color8Bit(Color.kFirstBlue) + first_red_color_8bit = Color8Bit(Color.kFirstRed) + + buffer.setLED(0, Color.kFirstBlue) + buffer.setLED(1, denim_color_8bit) + buffer.setLED(2, Color.kFirstRed) + buffer.setLED(3, Color.kFirstBlue) + + assert buffer.getLED(0) == Color.kFirstBlue + assert buffer.getLED(1) == Color.kDenim + assert buffer.getLED(2) == Color.kFirstRed + assert buffer.getLED(3) == Color.kFirstBlue + assert buffer.getLED8Bit(0) == first_blue_color_8bit + assert buffer.getLED8Bit(1) == denim_color_8bit + assert buffer.getLED8Bit(2) == first_red_color_8bit + assert buffer.getLED8Bit(3) == first_blue_color_8bit + + def test_get_red(self): + """Test getting red component""" + buffer = AddressableLEDBuffer(1) + buffer.setRGB(0, 127, 128, 129) + assert buffer.getRed(0) == 127 + + def test_get_green(self): + """Test getting green component""" + buffer = AddressableLEDBuffer(1) + buffer.setRGB(0, 127, 128, 129) + assert buffer.getGreen(0) == 128 + + def test_get_blue(self): + """Test getting blue component""" + buffer = AddressableLEDBuffer(1) + buffer.setRGB(0, 127, 128, 129) + assert buffer.getBlue(0) == 129 + + def test_iteration(self): + buffer = AddressableLEDBuffer(3) + buffer.setRGB(0, 1, 2, 3) + buffer.setRGB(1, 4, 5, 6) + buffer.setRGB(2, 7, 8, 9) + + results = [] + + for led in buffer: + results.append((led.r, led.g, led.b)) + + assert len(results) == 3 + assert results[0] == (1, 2, 3) + assert results[1] == (4, 5, 6) + assert results[2] == (7, 8, 9) + + def test_iteration_on_empty_buffer(self): + buffer = AddressableLEDBuffer(0) + + for led in buffer: + assert False, "Iterator should not return items on an empty buffer" + + +class TestAddressableLEDBufferView: + """Tests for AddressableLEDBufferView""" + + def test_single_led(self): + """Test setting a single LED through a view""" + buffer = AddressableLEDBuffer(10) + view = buffer.createView(5, 5) + color = Color.kAqua + view.setLED(0, color) + assert buffer.getLED(5) == color + assert view.getLED(0) == color + + def test_segment(self): + """Test segment view""" + buffer = AddressableLEDBuffer(10) + view = buffer.createView(2, 8) + view.setLED(0, Color.kAqua) + assert buffer.getLED(2) == Color.kAqua + + view.setLED(6, Color.kAzure) + assert buffer.getLED(8) == Color.kAzure + + @pytest.mark.skip("reversed views are not implemented") + def test_manual_reversed(self): + """Test manually reversed view""" + buffer = AddressableLEDBuffer(10) + view = buffer.createView(8, 2) + + # LED 0 in the view should write to LED 8 on the real buffer + view.setLED(0, Color.kAqua) + assert buffer.getLED(8) == Color.kAqua + + # LED 6 in the view should write to LED 2 on the real buffer + view.setLED(6, Color.kAzure) + assert buffer.getLED(2) == Color.kAzure + + @pytest.mark.skip("reversed views are not implemented") + def test_full_manual_reversed(self): + """Test full manual reversed view""" + buffer = AddressableLEDBuffer(10) + view = buffer.createView(9, 0) + view.setLED(0, Color.kWhite) + assert buffer.getLED(9) == Color.kWhite + + buffer.setLED(8, Color.kRed) + assert view.getLED(1) == Color.kRed + + @pytest.mark.skip("reversed views are not implemented") + def test_reversed(self): + """Test reversed view""" + buffer = AddressableLEDBuffer(10) + view = buffer.createView(0, 9).reversed() + view.setLED(0, Color.kWhite) + assert buffer.getLED(9) == Color.kWhite + + view.setLED(9, Color.kRed) + assert buffer.getLED(0) == Color.kRed From 19610559f1509ce681305faba69f8ef5a7904fe2 Mon Sep 17 00:00:00 2001 From: David Vo Date: Sat, 4 Apr 2026 02:22:02 +1100 Subject: [PATCH 08/10] take slice to create view --- .../robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml | 1 + .../tests/test_addressable_led_buffer.py | 10 +++++----- .../wpilib/src/rpy/AddressableLEDBuffer.h | 12 ++---------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml b/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml index 8e345a3d7..9c9787dd5 100644 --- a/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml +++ b/subprojects/robotpy-wpilib/semiwrap/AddressableLEDBuffer.yml @@ -22,6 +22,7 @@ classes: end: ignore: true CreateView: + rename: __getitem__ keepalive: - [0, 1] inline_code: | diff --git a/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py b/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py index fc38086ca..da1303004 100644 --- a/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py +++ b/subprojects/robotpy-wpilib/tests/test_addressable_led_buffer.py @@ -110,7 +110,7 @@ class TestAddressableLEDBufferView: def test_single_led(self): """Test setting a single LED through a view""" buffer = AddressableLEDBuffer(10) - view = buffer.createView(5, 5) + view = buffer[5:6] color = Color.kAqua view.setLED(0, color) assert buffer.getLED(5) == color @@ -119,7 +119,7 @@ def test_single_led(self): def test_segment(self): """Test segment view""" buffer = AddressableLEDBuffer(10) - view = buffer.createView(2, 8) + view = buffer[2:9] view.setLED(0, Color.kAqua) assert buffer.getLED(2) == Color.kAqua @@ -130,7 +130,7 @@ def test_segment(self): def test_manual_reversed(self): """Test manually reversed view""" buffer = AddressableLEDBuffer(10) - view = buffer.createView(8, 2) + view = buffer[8:1:-1] # LED 0 in the view should write to LED 8 on the real buffer view.setLED(0, Color.kAqua) @@ -144,7 +144,7 @@ def test_manual_reversed(self): def test_full_manual_reversed(self): """Test full manual reversed view""" buffer = AddressableLEDBuffer(10) - view = buffer.createView(9, 0) + view = buffer[9::-1] view.setLED(0, Color.kWhite) assert buffer.getLED(9) == Color.kWhite @@ -155,7 +155,7 @@ def test_full_manual_reversed(self): def test_reversed(self): """Test reversed view""" buffer = AddressableLEDBuffer(10) - view = buffer.createView(0, 9).reversed() + view = buffer[:].reversed() view.setLED(0, Color.kWhite) assert buffer.getLED(9) == Color.kWhite diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index d5fa679b5..ae2174d4f 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -322,19 +322,11 @@ class AddressableLEDBuffer { /** * Creates a read/write view of this buffer. * - * @param start the starting index (inclusive) - * @param end the ending index (exclusive) + * @param slice the desired slice of the buffer (e.g. 2:4), step must be unspecified or 1 * @return View object representing the view * @throws std::out_of_range if the view would exceed buffer bounds */ - View CreateView(size_t start, size_t end) { - if (start >= m_buffer.size() || end >= m_buffer.size()) { - throw std::out_of_range("View range out of bounds"); - } - return View(std::span(m_buffer).subspan(start, end - start)); - } - - View operator[](pybind11::slice slice) { + View CreateView(pybind11::slice slice) { ssize_t start = 0, stop = 0, step = 0, slicelength = 0; slice.compute(m_buffer.size(), &start, &stop, &step, &slicelength); if (step != 1) { From 5712640dd741eacafe4f3ba63866d67f1215a753 Mon Sep 17 00:00:00 2001 From: David Vo Date: Sat, 4 Apr 2026 03:30:35 +1100 Subject: [PATCH 09/10] LEDPattern now writes to AddressableLEDBuffer --- .../robotpy-wpilib/semiwrap/LEDPattern.yml | 18 + .../robotpy-wpilib/tests/test_led_pattern.py | 326 ++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 subprojects/robotpy-wpilib/tests/test_led_pattern.py diff --git a/subprojects/robotpy-wpilib/semiwrap/LEDPattern.yml b/subprojects/robotpy-wpilib/semiwrap/LEDPattern.yml index 0cff3e7d6..a8368e6aa 100644 --- a/subprojects/robotpy-wpilib/semiwrap/LEDPattern.yml +++ b/subprojects/robotpy-wpilib/semiwrap/LEDPattern.yml @@ -1,3 +1,6 @@ +extra_includes: +- rpy/AddressableLEDBuffer.h + classes: frc::LEDPattern: enums: @@ -38,6 +41,21 @@ classes: ignore: true Rainbow: MapIndex: + inline_code: | + .def("applyTo", [](const frc::LEDPattern& self, frc::AddressableLEDBuffer& data) { + self.ApplyTo(static_cast>(data)); + }, py::arg("data"), release_gil(), py::prepend()) + .def("applyTo", [](const frc::LEDPattern& self, frc::AddressableLEDBuffer& data, + std::function writer) { + self.ApplyTo(static_cast>(data), std::move(writer)); + }, py::arg("data"), py::arg("writer").none(false), release_gil(), py::prepend()) + .def("applyTo", [](const frc::LEDPattern& self, frc::AddressableLEDBuffer::View& data) { + self.ApplyTo(static_cast>(data)); + }, py::arg("data"), release_gil(), py::prepend()) + .def("applyTo", [](const frc::LEDPattern& self, frc::AddressableLEDBuffer::View& data, + std::function writer) { + self.ApplyTo(static_cast>(data), std::move(writer)); + }, py::arg("data"), py::arg("writer").none(false), release_gil(), py::prepend()) frc::LEDPattern::LEDReader: methods: LEDReader: diff --git a/subprojects/robotpy-wpilib/tests/test_led_pattern.py b/subprojects/robotpy-wpilib/tests/test_led_pattern.py new file mode 100644 index 000000000..af4ce1249 --- /dev/null +++ b/subprojects/robotpy-wpilib/tests/test_led_pattern.py @@ -0,0 +1,326 @@ +# Copyright (c) FIRST and other WPILib contributors. +# Open Source Software; you can modify and/or share it under the terms of +# the WPILib BSD license file in the root directory of this project. + +import math + +import pytest +import wpimath.units as units + +from wpilib import AddressableLEDBuffer, Color, Color8Bit, LEDPattern, RobotController + + +def lerp_rgb(a: Color, b: Color, t: float) -> Color8Bit: + a8 = Color8Bit(a) + b8 = Color8Bit(b) + return Color8Bit( + int(a8.red + (b8.red - a8.red) * t), + int(a8.green + (b8.green - a8.green) * t), + int(a8.blue + (b8.blue - a8.blue) * t), + ) + + +@pytest.fixture(autouse=True) +def restore_time_source(): + RobotController.setTimeSource(lambda: 0) + yield + RobotController.setTimeSource(RobotController.getTime) + + +def test_apply_to_buffer_direct(): + buffer = AddressableLEDBuffer(4) + LEDPattern.solid(Color.kYellow).applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kYellow) + + +def test_apply_to_view_direct(): + buffer = AddressableLEDBuffer(6) + view = buffer[2:5] + LEDPattern.solid(Color.kAqua).applyTo(view) + + assert buffer.getLED8Bit(1) == Color8Bit(Color.kBlack) + assert buffer.getLED8Bit(2) == Color8Bit(Color.kAqua) + assert buffer.getLED8Bit(3) == Color8Bit(Color.kAqua) + assert buffer.getLED8Bit(4) == Color8Bit(Color.kAqua) + assert buffer.getLED8Bit(5) == Color8Bit(Color.kBlack) + + +def test_solid_color(): + buffer = AddressableLEDBuffer(99) + LEDPattern.solid(Color.kYellow).applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kYellow) + + +def test_gradient_0_sets_to_black(): + pattern = LEDPattern.gradient(LEDPattern.GradientType.kContinuous, []) + buffer = AddressableLEDBuffer(99) + + for i in range(len(buffer)): + buffer.setRGB(i, 127, 128, 129) + + pattern.applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kBlack) + + +def test_gradient_1_sets_to_solid(): + pattern = LEDPattern.gradient(LEDPattern.GradientType.kContinuous, [Color.kYellow]) + buffer = AddressableLEDBuffer(99) + pattern.applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kYellow) + + +def test_continuous_gradient_2_colors(): + pattern = LEDPattern.gradient( + LEDPattern.GradientType.kContinuous, [Color.kYellow, Color.kPurple] + ) + buffer = AddressableLEDBuffer(99) + pattern.applyTo(buffer) + + assert buffer.getLED8Bit(0) == Color8Bit(Color.kYellow) + assert buffer.getLED8Bit(25) == lerp_rgb(Color.kYellow, Color.kPurple, 25 / 49.0) + assert buffer.getLED8Bit(49) == Color8Bit(Color.kPurple) + assert buffer.getLED8Bit(73) == lerp_rgb(Color.kYellow, Color.kPurple, 25 / 49.0) + assert buffer.getLED8Bit(98) == Color8Bit(Color.kYellow) + + +def test_discontinuous_gradient_2_colors(): + pattern = LEDPattern.gradient( + LEDPattern.GradientType.kDiscontinuous, [Color.kYellow, Color.kPurple] + ) + buffer = AddressableLEDBuffer(99) + pattern.applyTo(buffer) + + assert buffer.getLED8Bit(0) == Color8Bit(Color.kYellow) + assert buffer.getLED8Bit(49) == lerp_rgb(Color.kYellow, Color.kPurple, 0.5) + assert buffer.getLED8Bit(98) == Color8Bit(Color.kPurple) + + +def test_step_0_sets_to_black(): + pattern = LEDPattern.steps([]) + buffer = AddressableLEDBuffer(99) + for i in range(len(buffer)): + buffer.setRGB(i, 127, 128, 129) + + pattern.applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kBlack) + + +def test_step_1_sets_to_solid(): + pattern = LEDPattern.steps([(0.0, Color.kYellow)]) + buffer = AddressableLEDBuffer(99) + pattern.applyTo(buffer) + + for i in range(len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kYellow) + + +def test_step_half_sets_to_half_off_half_color(): + pattern = LEDPattern.steps([(0.5, Color.kYellow)]) + buffer = AddressableLEDBuffer(99) + pattern.applyTo(buffer) + + for i in range(49): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kBlack) + for i in range(49, len(buffer)): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kYellow) + + +def make_grayscale_pattern(): + return LEDPattern( + lambda reader, writer: [ + writer(led, Color(led % 256, led % 256, led % 256)) + for led in range(reader.size()) + ] + ) + + +@pytest.mark.skip(reason="Python bindings do not expose a way to mock wpi::util::Now()") +def test_scroll_forward(): + buffer = AddressableLEDBuffer(256) + pattern = make_grayscale_pattern().scrollAtRelativeSpeed(units.hertz(1 / 256.0)) + + for time in range(10): + RobotController.setTimeSource(lambda t=time: t) + pattern.applyTo(buffer) + + for led in range(len(buffer)): + ch = (led - time) % 256 + assert buffer.getLED8Bit(led) == Color8Bit(ch, ch, ch) + + +@pytest.mark.skip(reason="Python bindings do not expose a way to mock wpi::util::Now()") +def test_scroll_backward(): + buffer = AddressableLEDBuffer(256) + pattern = make_grayscale_pattern().scrollAtRelativeSpeed(units.hertz(-1 / 256.0)) + + for time in range(10): + RobotController.setTimeSource(lambda t=time: t) + pattern.applyTo(buffer) + + for led in range(len(buffer)): + ch = (led + time) % 256 + assert buffer.getLED8Bit(led) == Color8Bit(ch, ch, ch) + + +def test_rainbow_at_full_size(): + buffer = AddressableLEDBuffer(180) + saturation = 255 + value = 255 + pattern = LEDPattern.rainbow(saturation, value) + pattern.applyTo(buffer) + + for led in range(len(buffer)): + assert buffer.getLED8Bit(led) == Color8Bit(Color.fromHSV(led, saturation, value)) + + +def test_rainbow_odd_size(): + buffer = AddressableLEDBuffer(127) + scale = 180.0 / len(buffer) + saturation = 73 + value = 128 + pattern = LEDPattern.rainbow(saturation, value) + pattern.applyTo(buffer) + + for led in range(len(buffer)): + expected = Color8Bit(Color.fromHSV(int(led * scale), saturation, value)) + assert buffer.getLED8Bit(led) == expected + + +def test_reverse_solid(): + buffer = AddressableLEDBuffer(90) + pattern = LEDPattern.solid(Color.kRosyBrown).reversed() + pattern.applyTo(buffer) + + for led in range(len(buffer)): + assert buffer.getLED8Bit(led) == Color8Bit(Color.kRosyBrown) + + +def test_reverse_steps(): + buffer = AddressableLEDBuffer(100) + pattern = LEDPattern.steps([(0.0, Color.kWhite), (0.5, Color.kYellow)]).reversed() + pattern.applyTo(buffer) + + for led in range(len(buffer)): + expected = Color8Bit(Color.kYellow if led < 50 else Color.kWhite) + assert buffer.getLED8Bit(led) == expected + + +def white_yellow_purple(reader, writer): + colors = [Color.kWhite, Color.kYellow, Color.kPurple] + for led in range(reader.size()): + writer(led, colors[led % 3]) + + +@pytest.mark.parametrize( + "offset,expected", + [ + (1, [Color.kPurple, Color.kWhite, Color.kYellow]), + (-1, [Color.kYellow, Color.kPurple, Color.kWhite]), + (0, [Color.kWhite, Color.kYellow, Color.kPurple]), + ], +) +def test_offset_pattern(offset, expected): + buffer = AddressableLEDBuffer(21) + pattern = LEDPattern(white_yellow_purple).offsetBy(offset) + pattern.applyTo(buffer) + + for led in range(len(buffer)): + assert buffer.getLED8Bit(led) == Color8Bit(expected[led % 3]) + + +@pytest.mark.skip(reason="Python bindings do not expose a way to mock wpi::util::Now()") +def test_blink_symmetric(): + pattern = LEDPattern.solid(Color.kWhite).blink(units.seconds(2)) + buffer = AddressableLEDBuffer(1) + + for t in range(8): + RobotController.setTimeSource(lambda tick=t: tick * 1_000_000) + pattern.applyTo(buffer) + expected = Color8Bit(Color.kWhite if t % 4 < 2 else Color.kBlack) + assert buffer.getLED8Bit(0) == expected + + +def test_blink_in_sync(): + state = {"on": False} + pattern = LEDPattern.solid(Color.kWhite).synchronizedBlink(lambda: state["on"]) + buffer = AddressableLEDBuffer(1) + + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kBlack) + + state["on"] = True + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kWhite) + + state["on"] = False + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kBlack) + + +@pytest.mark.skip(reason="Python bindings do not expose a way to mock wpi::util::Now()") +def test_breathe(): + pattern = LEDPattern.solid(Color.kWhite).breathe(units.microseconds(4)) + buffer = AddressableLEDBuffer(1) + + RobotController.setTimeSource(lambda: 0) + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kWhite) + + RobotController.setTimeSource(lambda: 1) + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color(0.5, 0.5, 0.5)) + + RobotController.setTimeSource(lambda: 2) + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kBlack) + + +def test_overlay_solid_on_solid(): + overlay = LEDPattern.solid(Color.kYellow).overlayOn(LEDPattern.solid(Color.kWhite)) + buffer = AddressableLEDBuffer(1) + overlay.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color.kYellow) + + +def test_progress_mask_layer(): + progress = {"value": 0.0} + pattern = LEDPattern.progressMaskLayer(lambda: progress["value"]) + buffer = AddressableLEDBuffer(10) + + progress["value"] = 0.3 + pattern.applyTo(buffer) + + for i in range(3): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kWhite) + for i in range(3, 10): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kBlack) + + +def test_blend(): + pattern = LEDPattern.solid(Color.kBlue).blend(LEDPattern.solid(Color.kRed)) + buffer = AddressableLEDBuffer(1) + pattern.applyTo(buffer) + assert buffer.getLED8Bit(0) == Color8Bit(Color(127, 0, 127)) + + +def test_binary_mask(): + base = LEDPattern.solid(Color(123, 123, 123)) + mask = LEDPattern.steps([(0.0, Color.kWhite), (0.5, Color.kBlack)]) + pattern = base.mask(mask) + buffer = AddressableLEDBuffer(4) + pattern.applyTo(buffer) + + for i in range(2): + assert buffer.getLED8Bit(i) == Color8Bit(Color(123, 123, 123)) + for i in range(2, 4): + assert buffer.getLED8Bit(i) == Color8Bit(Color.kBlack) From 22c498bd98328ce732ac7ae79a20a34a651c95bd Mon Sep 17 00:00:00 2001 From: David Vo Date: Sat, 4 Apr 2026 03:37:08 +1100 Subject: [PATCH 10/10] split AddressableLEDBuffer method bodies into TU --- subprojects/robotpy-wpilib/meson.build | 1 + .../wpilib/src/rpy/AddressableLEDBuffer.cpp | 129 ++++++++++++++++++ .../wpilib/src/rpy/AddressableLEDBuffer.h | 115 ++++------------ 3 files changed, 154 insertions(+), 91 deletions(-) create mode 100644 subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.cpp diff --git a/subprojects/robotpy-wpilib/meson.build b/subprojects/robotpy-wpilib/meson.build index 6343d6a5a..846a737ae 100644 --- a/subprojects/robotpy-wpilib/meson.build +++ b/subprojects/robotpy-wpilib/meson.build @@ -6,6 +6,7 @@ subdir('semiwrap') wpilib_sources += files( 'wpilib/src/main.cpp', + 'wpilib/src/rpy/AddressableLEDBuffer.cpp', 'wpilib/src/rpy/ControlWord.cpp', 'wpilib/src/rpy/Notifier.cpp', 'wpilib/src/rpy/SmartDashboardData.cpp', diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.cpp b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.cpp new file mode 100644 index 000000000..32c9bf938 --- /dev/null +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.cpp @@ -0,0 +1,129 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "rpy/AddressableLEDBuffer.h" + +#include +#include + +namespace frc { + +void AddressableLEDBuffer::SetRGB(size_t index, int r, int g, int b) { + m_buffer.at(index).SetRGB(r, g, b); +} + +void AddressableLEDBuffer::SetHSV(size_t index, int h, int s, int v) { + m_buffer.at(index).SetHSV(h, s, v); +} + +void AddressableLEDBuffer::SetLED(size_t index, const Color& color) { + m_buffer.at(index).SetLED(color); +} + +void AddressableLEDBuffer::SetLED(size_t index, const Color8Bit& color) { + m_buffer.at(index).SetLED(color); +} + +int AddressableLEDBuffer::GetRed(size_t index) const { return m_buffer.at(index).r; } + +int AddressableLEDBuffer::GetGreen(size_t index) const { + return m_buffer.at(index).g; +} + +int AddressableLEDBuffer::GetBlue(size_t index) const { return m_buffer.at(index).b; } + +Color AddressableLEDBuffer::GetLED(size_t index) const { + const auto& led = m_buffer.at(index); + return Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; +} + +Color8Bit AddressableLEDBuffer::GetLED8Bit(size_t index) const { + const auto& led = m_buffer.at(index); + return Color8Bit{led.r, led.g, led.b}; +} + +AddressableLED::LEDData& AddressableLEDBuffer::at(size_t index) { + return m_buffer.at(index); +} + +AddressableLED::LEDData& AddressableLEDBuffer::operator[](size_t index) { + return m_buffer.at(index); +} + +const AddressableLED::LEDData& AddressableLEDBuffer::operator[]( + size_t index) const { + return m_buffer.at(index); +} + +void AddressableLEDBuffer::View::SetRGB(size_t index, int r, int g, int b) { + at(index).SetRGB(r, g, b); +} + +void AddressableLEDBuffer::View::SetHSV(size_t index, int h, int s, int v) { + at(index).SetHSV(h, s, v); +} + +void AddressableLEDBuffer::View::SetLED(size_t index, const Color& color) { + at(index).SetLED(color); +} + +void AddressableLEDBuffer::View::SetLED(size_t index, + const Color8Bit& color) { + at(index).SetLED(color); +} + +AddressableLED::LEDData& AddressableLEDBuffer::View::at(size_t index) { + // std::span::at doesn't exist until C++26 + if (index >= m_data.size()) { + throw std::out_of_range("Index out of range"); + } + return m_data[index]; +} + +AddressableLED::LEDData& AddressableLEDBuffer::View::operator[]( + size_t index) { + return at(index); +} + +const AddressableLED::LEDData& AddressableLEDBuffer::View::at( + size_t index) const { + // std::span::at doesn't exist until C++26 + if (index >= m_data.size()) { + throw std::out_of_range("Index out of range"); + } + return m_data[index]; +} + +const AddressableLED::LEDData& AddressableLEDBuffer::View::operator[]( + size_t index) const { + return at(index); +} + +Color AddressableLEDBuffer::View::GetLED(size_t index) const { + const auto& led = at(index); + return Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; +} + +Color8Bit AddressableLEDBuffer::View::GetLED8Bit(size_t index) const { + const auto& led = at(index); + return Color8Bit{led.r, led.g, led.b}; +} + +AddressableLEDBuffer::View::View(std::span data) + : m_data(data) {} + +AddressableLEDBuffer::View AddressableLEDBuffer::CreateView( + pybind11::slice slice) { + ssize_t start = 0, stop = 0, step = 0, slicelength = 0; + slice.compute(m_buffer.size(), &start, &stop, &step, &slicelength); + if (step != 1) { + throw std::out_of_range("step != 1"); + } + if (!slicelength) { + throw std::out_of_range("zero length view"); + } + return View(std::span(m_buffer).subspan(start, slicelength)); +} + +} // namespace frc diff --git a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h index ae2174d4f..048ee9292 100644 --- a/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h +++ b/subprojects/robotpy-wpilib/wpilib/src/rpy/AddressableLEDBuffer.h @@ -34,9 +34,7 @@ class AddressableLEDBuffer { * @param g the g value [0-255] * @param b the b value [0-255] */ - void SetRGB(size_t index, int r, int g, int b) { - m_buffer.at(index).SetRGB(r, g, b); - } + void SetRGB(size_t index, int r, int g, int b); /** * Sets a specific LED in the buffer. @@ -46,9 +44,7 @@ class AddressableLEDBuffer { * @param s the s value [0-255] * @param v the v value [0-255] */ - void SetHSV(size_t index, int h, int s, int v) { - m_buffer.at(index).SetHSV(h, s, v); - } + void SetHSV(size_t index, int h, int s, int v); /** * Sets a specific LED in the buffer. @@ -56,9 +52,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const frc::Color& color) { - m_buffer.at(index).SetLED(color); - } + void SetLED(size_t index, const frc::Color& color); /** * Sets a specific LED in the buffer. @@ -66,9 +60,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const frc::Color8Bit& color) { - m_buffer.at(index).SetLED(color); - } + void SetLED(size_t index, const frc::Color8Bit& color); /** * Gets the buffer length. @@ -83,9 +75,7 @@ class AddressableLEDBuffer { * @param index the index * @return the red value */ - int GetRed(size_t index) const { - return m_buffer.at(index).r; - } + int GetRed(size_t index) const; /** * Gets the green value at the specified index. @@ -93,9 +83,7 @@ class AddressableLEDBuffer { * @param index the index * @return the green value */ - int GetGreen(size_t index) const { - return m_buffer.at(index).g; - } + int GetGreen(size_t index) const; /** * Gets the blue value at the specified index. @@ -103,9 +91,7 @@ class AddressableLEDBuffer { * @param index the index * @return the blue value */ - int GetBlue(size_t index) const { - return m_buffer.at(index).b; - } + int GetBlue(size_t index) const; /** * Gets the color at the specified index. @@ -113,10 +99,7 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - frc::Color GetLED(size_t index) const { - const auto& led = m_buffer.at(index); - return frc::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; - } + frc::Color GetLED(size_t index) const; /** * Gets the color at the specified index. @@ -124,10 +107,7 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - frc::Color8Bit GetLED8Bit(size_t index) const { - const auto& led = m_buffer.at(index); - return frc::Color8Bit{led.r, led.g, led.b}; - } + frc::Color8Bit GetLED8Bit(size_t index) const; /** * Implicit conversion to span of LED data @@ -142,9 +122,7 @@ class AddressableLEDBuffer { * @param index the index * @return reference to the LED data */ - frc::AddressableLED::LEDData& at(size_t index) { - return m_buffer.at(index); - } + frc::AddressableLED::LEDData& at(size_t index); /** * Gets the LED data at the specified index. @@ -152,9 +130,7 @@ class AddressableLEDBuffer { * @param index the index * @return reference to the LED data */ - frc::AddressableLED::LEDData& operator[](size_t index) { - return m_buffer.at(index); - } + frc::AddressableLED::LEDData& operator[](size_t index); /** * Gets the LED data at the specified index. @@ -162,9 +138,7 @@ class AddressableLEDBuffer { * @param index the index * @return const reference to the LED data */ - const frc::AddressableLED::LEDData& operator[](size_t index) const { - return m_buffer.at(index); - } + const frc::AddressableLED::LEDData& operator[](size_t index) const; auto begin() { return m_buffer.begin(); } auto end() { return m_buffer.end(); } @@ -188,9 +162,7 @@ class AddressableLEDBuffer { * @param g the g value [0-255] * @param b the b value [0-255] */ - void SetRGB(size_t index, int r, int g, int b) { - at(index).SetRGB(r, g, b); - } + void SetRGB(size_t index, int r, int g, int b); /** * Sets a specific LED in the view. @@ -200,9 +172,7 @@ class AddressableLEDBuffer { * @param s the s value [0-255] * @param v the v value [0-255] */ - void SetHSV(size_t index, int h, int s, int v) { - at(index).SetHSV(h, s, v); - } + void SetHSV(size_t index, int h, int s, int v); /** * Sets a specific LED in the view. @@ -210,9 +180,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const frc::Color& color) { - at(index).SetLED(color); - } + void SetLED(size_t index, const frc::Color& color); /** * Sets a specific LED in the view. @@ -220,9 +188,7 @@ class AddressableLEDBuffer { * @param index the index to write * @param color the color to write */ - void SetLED(size_t index, const frc::Color8Bit& color) { - at(index).SetLED(color); - } + void SetLED(size_t index, const frc::Color8Bit& color); /** * Gets the LED data at the specified index. @@ -230,13 +196,7 @@ class AddressableLEDBuffer { * @param index the index * @return reference to the LED data */ - frc::AddressableLED::LEDData& at(size_t index) { - // std::span::at doesn't exist until C++26 - if (index >= m_data.size()) { - throw std::out_of_range("Index out of range"); - } - return m_data[index]; - } + frc::AddressableLED::LEDData& at(size_t index); /** * Gets the LED data at the specified index. @@ -244,9 +204,7 @@ class AddressableLEDBuffer { * @param index the index * @return reference to the LED data */ - frc::AddressableLED::LEDData& operator[](size_t index) { - return at(index); - } + frc::AddressableLED::LEDData& operator[](size_t index); /** * Gets the LED data at the specified index. @@ -254,13 +212,7 @@ class AddressableLEDBuffer { * @param index the index * @return const reference to the LED data */ - const frc::AddressableLED::LEDData& at(size_t index) const { - // std::span::at doesn't exist until C++26 - if (index >= m_data.size()) { - throw std::out_of_range("Index out of range"); - } - return m_data[index]; - } + const frc::AddressableLED::LEDData& at(size_t index) const; /** * Gets the LED data at the specified index. @@ -268,9 +220,7 @@ class AddressableLEDBuffer { * @param index the index * @return const reference to the LED data */ - const frc::AddressableLED::LEDData& operator[](size_t index) const { - return at(index); - } + const frc::AddressableLED::LEDData& operator[](size_t index) const; auto begin() { return m_data.begin(); } auto end() { return m_data.end(); } @@ -281,10 +231,7 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - frc::Color GetLED(size_t index) const { - const auto& led = at(index); - return frc::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0}; - } + frc::Color GetLED(size_t index) const; /** * Gets the color at the specified index. @@ -292,10 +239,7 @@ class AddressableLEDBuffer { * @param index the index * @return the LED color */ - frc::Color8Bit GetLED8Bit(size_t index) const { - const auto& led = at(index); - return frc::Color8Bit{led.r, led.g, led.b}; - } + frc::Color8Bit GetLED8Bit(size_t index) const; /** * Implicit conversion to span of LED data @@ -313,8 +257,7 @@ class AddressableLEDBuffer { private: friend class AddressableLEDBuffer; - explicit View(std::span data) - : m_data(data) {} + explicit View(std::span data); std::span m_data; }; @@ -326,17 +269,7 @@ class AddressableLEDBuffer { * @return View object representing the view * @throws std::out_of_range if the view would exceed buffer bounds */ - View CreateView(pybind11::slice slice) { - ssize_t start = 0, stop = 0, step = 0, slicelength = 0; - slice.compute(m_buffer.size(), &start, &stop, &step, &slicelength); - if (step != 1) { - throw std::out_of_range("step != 1"); - } - if (!slicelength) { - throw std::out_of_range("zero length view"); - } - return View(std::span(m_buffer).subspan(start, slicelength)); - } + View CreateView(pybind11::slice slice); private: std::vector m_buffer;