[copybara] Sync with robotpy (#8964)

GitOrigin-RevId: 9dff8f977401e78be0bb6f39cea2328320ab2d95
This commit is contained in:
PJ Reiniger
2026-06-08 22:22:48 -04:00
committed by GitHub
parent 0213ecf382
commit 111130d8bb
20 changed files with 1026 additions and 265 deletions

View File

@@ -234,6 +234,7 @@ PKG_CONFIG_DEPS = [
generate_robotpy_pybind_build_info(
name = "robotpy-wpilib-generator",
additional_srcs = [
"src/main/python/wpilib/src/rpy/AddressableLEDBuffer.h",
"src/main/python/wpilib/src/rpy/Filesystem.h",
"src/main/python/wpilib/src/rpy/Notifier.h",
"src/main/python/wpilib/src/rpy/MotorControllerGroup.h",

View File

@@ -36,6 +36,17 @@ def wpilib_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includ
("wpi::PyNotifier", "wpi__PyNotifier.hpp"),
],
),
struct(
class_name = "AddressableLEDBuffer",
yml_file = "semiwrap/AddressableLEDBuffer.yml",
header_root = "wpilibc/src/main/python/wpilib/src",
header_file = "wpilibc/src/main/python/wpilib/src/rpy/AddressableLEDBuffer.h",
tmpl_class_names = [],
trampolines = [
("wpi::AddressableLEDBuffer", "wpi__AddressableLEDBuffer.hpp"),
("wpi::AddressableLEDBuffer::View", "wpi__AddressableLEDBuffer__View.hpp"),
],
),
struct(
class_name = "EdgeConfiguration",
yml_file = "semiwrap/EdgeConfiguration.yml",

View File

@@ -94,6 +94,8 @@ DYNAMIC_CAMERA_SERVER = 1
Filesystem = "rpy/Filesystem.h"
MotorControllerGroup = "rpy/MotorControllerGroup.h"
Notifier = "rpy/Notifier.h"
# rpy only
AddressableLEDBuffer = "rpy/AddressableLEDBuffer.h"
# wpi/counter
EdgeConfiguration = "wpi/counter/EdgeConfiguration.hpp"

View File

@@ -1,3 +1,6 @@
extra_includes:
- rpy/AddressableLEDBuffer.h
functions:
format_as:
ignore: true
@@ -19,6 +22,10 @@ classes:
SetGlobalData:
enums:
ColorOrder:
inline_code: |
.def("setData", [](wpi::AddressableLED& self, const wpi::AddressableLEDBuffer& data) {
return self.SetData(data);
}, release_gil(), py::prepend());
wpi::AddressableLED::LEDData:
force_no_trampoline: true
ignored_bases:

View File

@@ -0,0 +1,58 @@
classes:
wpi::AddressableLEDBuffer:
methods:
AddressableLEDBuffer:
SetRGB:
SetHSV:
SetLED:
overloads:
size_t, const wpi::util::Color&:
size_t, const wpi::util::Color8Bit&:
size:
rename: __len__
GetRed:
GetGreen:
GetBlue:
GetLED:
GetLED8Bit:
at:
rename: __getitem__
begin:
ignore: true
end:
ignore: true
CreateView:
rename: __getitem__
no_release_gil: true
keepalive:
- [0, 1]
inline_code: |
.def("__iter__", [](wpi::AddressableLEDBuffer& self) {
return py::make_iterator(self.begin(), self.end());
}, py::keep_alive<0, 1>())
wpi::AddressableLEDBuffer::View:
methods:
size:
rename: __len__
SetRGB:
SetHSV:
SetLED:
overloads:
size_t, const wpi::util::Color&:
size_t, const wpi::util::Color8Bit&:
at:
rename: __getitem__
overloads:
size_t:
size_t [const]:
ignore: true
begin:
ignore: true
end:
ignore: true
GetLED:
GetLED8Bit:
inline_code: |
.def("__iter__", [](wpi::AddressableLEDBuffer::View& self) {
return py::make_iterator(self.begin(), self.end());
}, py::keep_alive<0, 1>())

View File

@@ -1,3 +1,6 @@
extra_includes:
- rpy/AddressableLEDBuffer.h
classes:
wpi::LEDPattern:
enums:
@@ -8,8 +11,16 @@ classes:
ApplyTo:
overloads:
std::span<wpi::AddressableLED::LEDData> [const]:
cpp_code: |
[](const wpi::LEDPattern& self, wpi::AddressableLEDBuffer::View data) {
return self.ApplyTo(data);
}
LEDReader, std::function<void (int, wpi::util::Color)> [const]:
std::span<wpi::AddressableLED::LEDData>, std::function<void (int, wpi::util::Color)> [const]:
cpp_code: |
[](const wpi::LEDPattern& self, wpi::AddressableLEDBuffer::View data, std::function<void(int, wpi::util::Color)> writer) {
return self.ApplyTo(data, writer);
}
Reversed:
OffsetBy:
ScrollAtRelativeVelocity:
@@ -38,6 +49,14 @@ classes:
ignore: true
Rainbow:
MapIndex:
inline_code: |
.def("applyTo", [](const wpi::LEDPattern& self, wpi::AddressableLEDBuffer& data) {
self.ApplyTo(static_cast<std::span<wpi::AddressableLED::LEDData>>(data));
}, py::arg("data"), release_gil())
.def("applyTo", [](const wpi::LEDPattern& self, wpi::AddressableLEDBuffer& data,
std::function<void (int, wpi::util::Color)> writer) {
self.ApplyTo(static_cast<std::span<wpi::AddressableLED::LEDData>>(data), std::move(writer));
}, py::arg("data"), py::arg("writer").none(false), release_gil())
wpi::LEDPattern::LEDReader:
methods:
LEDReader:

View File

@@ -4,6 +4,7 @@ from . import _init__wpilib
from ._wpilib import (
ADXL345_I2C,
AddressableLED,
AddressableLEDBuffer,
Alert,
Alliance,
AnalogAccelerometer,
@@ -117,6 +118,7 @@ from ._wpilib import (
__all__ = [
"ADXL345_I2C",
"AddressableLED",
"AddressableLEDBuffer",
"Alert",
"Alliance",
"AnalogAccelerometer",

View File

@@ -177,7 +177,7 @@ class RobotStarter:
for i in range(100):
if (
inst.getNetworkMode()
& ntcore.NetworkTableInstance.NetworkMode.kNetModeStarting
& ntcore.NetworkTableInstance.NetworkMode.STARTING.value
) == 0:
break
# real sleep since we're waiting for the server, not simulated sleep

View File

@@ -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 <stdexcept>
#include <span>
namespace wpi {
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 wpi::util::Color& color) {
m_buffer.at(index).SetLED(color);
}
void AddressableLEDBuffer::SetLED(size_t index, const wpi::util::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; }
wpi::util::Color AddressableLEDBuffer::GetLED(size_t index) const {
const auto& led = m_buffer.at(index);
return wpi::util::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0};
}
wpi::util::Color8Bit AddressableLEDBuffer::GetLED8Bit(size_t index) const {
const auto& led = m_buffer.at(index);
return wpi::util::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 wpi::util::Color& color) {
at(index).SetLED(color);
}
void AddressableLEDBuffer::View::SetLED(size_t index,
const wpi::util::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);
}
wpi::util::Color AddressableLEDBuffer::View::GetLED(size_t index) const {
const auto& led = at(index);
return wpi::util::Color{led.r / 255.0, led.g / 255.0, led.b / 255.0};
}
wpi::util::Color8Bit AddressableLEDBuffer::View::GetLED8Bit(size_t index) const {
const auto& led = at(index);
return wpi::util::Color8Bit{led.r, led.g, led.b};
}
AddressableLEDBuffer::View::View(std::span<AddressableLED::LEDData> data)
: m_data(data) {}
AddressableLEDBuffer::View AddressableLEDBuffer::CreateView(
pybind11::slice slice) {
size_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 wpi

View File

@@ -0,0 +1,285 @@
// 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 <span>
#include <stdexcept>
#include <vector>
#include "pybind11/pytypes.h"
#include "wpi/hardware/led/AddressableLED.hpp"
#include "wpi/util/Color.hpp"
#include "wpi/util/Color8Bit.hpp"
namespace wpi {
/**
* 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);
/**
* 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);
/**
* 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 wpi::util::Color& 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 wpi::util::Color8Bit& 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;
/**
* Gets the green value at the specified index.
*
* @param index the index
* @return the green value
*/
int GetGreen(size_t index) const;
/**
* Gets the blue value at the specified index.
*
* @param index the index
* @return the blue value
*/
int GetBlue(size_t index) const;
/**
* Gets the color at the specified index.
*
* @param index the index
* @return the LED color
*/
wpi::util::Color GetLED(size_t index) const;
/**
* Gets the color at the specified index.
*
* @param index the index
* @return the LED color
*/
wpi::util::Color8Bit GetLED8Bit(size_t index) const;
/**
* Implicit conversion to span of LED data
*/
operator std::span<wpi::AddressableLED::LEDData>() {
return std::span{m_buffer};
}
/**
* Implicit conversion to span of const LED data
*/
operator std::span<const wpi::AddressableLED::LEDData>() const {
return std::span{m_buffer};
}
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return reference to the LED data
*/
wpi::AddressableLED::LEDData& at(size_t index);
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return reference to the LED data
*/
wpi::AddressableLED::LEDData& operator[](size_t index);
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return const reference to the LED data
*/
const wpi::AddressableLED::LEDData& operator[](size_t index) const;
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);
/**
* 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);
/**
* 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 wpi::util::Color& 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 wpi::util::Color8Bit& color);
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return reference to the LED data
*/
wpi::AddressableLED::LEDData& at(size_t index);
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return reference to the LED data
*/
wpi::AddressableLED::LEDData& operator[](size_t index);
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return const reference to the LED data
*/
const wpi::AddressableLED::LEDData& at(size_t index) const;
/**
* Gets the LED data at the specified index.
*
* @param index the index
* @return const reference to the LED data
*/
const wpi::AddressableLED::LEDData& operator[](size_t index) const;
auto begin() { return m_data.begin(); }
auto end() { return m_data.end(); }
/**
* Gets the color at the specified index.
*
* @param index the index
* @return the LED color
*/
wpi::util::Color GetLED(size_t index) const;
/**
* Gets the color at the specified index.
*
* @param index the index
* @return the LED color
*/
wpi::util::Color8Bit GetLED8Bit(size_t index) const;
/**
* Implicit conversion to span of LED data
*/
operator std::span<wpi::AddressableLED::LEDData>() {
return m_data;
}
/**
* Implicit conversion to span of const LED data
*/
operator std::span<const wpi::AddressableLED::LEDData>() const {
return m_data;
}
private:
friend class AddressableLEDBuffer;
explicit View(std::span<wpi::AddressableLED::LEDData> data);
std::span<wpi::AddressableLED::LEDData> m_data;
};
/**
* Creates a read/write view of this buffer.
*
* @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(pybind11::slice slice);
private:
std::vector<wpi::AddressableLED::LEDData> m_buffer;
};
} // namespace frc

View File

@@ -0,0 +1,164 @@
# 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
from wpiutil import 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.DENIM)
first_blue_color_8bit = Color8Bit(Color.FIRST_BLUE)
first_red_color_8bit = Color8Bit(Color.FIRST_RED)
buffer.setLED(0, Color.FIRST_BLUE)
buffer.setLED(1, denim_color_8bit)
buffer.setLED(2, Color.FIRST_RED)
buffer.setLED(3, Color.FIRST_BLUE)
assert buffer.getLED(0) == Color.FIRST_BLUE
assert buffer.getLED(1) == Color.DENIM
assert buffer.getLED(2) == Color.FIRST_RED
assert buffer.getLED(3) == Color.FIRST_BLUE
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[5:6]
color = Color.AQUA
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[2:9]
view.setLED(0, Color.AQUA)
assert buffer.getLED(2) == Color.AQUA
view.setLED(6, Color.AZURE)
assert buffer.getLED(8) == Color.AZURE
@pytest.mark.skip("reversed views are not implemented")
def test_manual_reversed(self):
"""Test manually reversed view"""
buffer = AddressableLEDBuffer(10)
view = buffer[8:1:-1]
# LED 0 in the view should write to LED 8 on the real buffer
view.setLED(0, Color.AQUA)
assert buffer.getLED(8) == Color.AQUA
# LED 6 in the view should write to LED 2 on the real buffer
view.setLED(6, Color.AZURE)
assert buffer.getLED(2) == Color.AZURE
@pytest.mark.skip("reversed views are not implemented")
def test_full_manual_reversed(self):
"""Test full manual reversed view"""
buffer = AddressableLEDBuffer(10)
view = buffer[9::-1]
view.setLED(0, Color.WHITE)
assert buffer.getLED(9) == Color.WHITE
buffer.setLED(8, Color.RED)
assert view.getLED(1) == Color.RED
@pytest.mark.skip("reversed views are not implemented")
def test_reversed(self):
"""Test reversed view"""
buffer = AddressableLEDBuffer(10)
view = buffer[:].reversed()
view.setLED(0, Color.WHITE)
assert buffer.getLED(9) == Color.WHITE
view.setLED(9, Color.RED)
assert buffer.getLED(0) == Color.RED

View File

@@ -0,0 +1,329 @@
# 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, LEDPattern, RobotController
from wpiutil import Color, Color8Bit
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.YELLOW).applyTo(buffer)
for i in range(len(buffer)):
assert buffer.getLED8Bit(i) == Color8Bit(Color.YELLOW)
def test_apply_to_view_direct():
buffer = AddressableLEDBuffer(6)
view = buffer[2:5]
LEDPattern.solid(Color.AQUA).applyTo(view)
assert buffer.getLED8Bit(1) == Color8Bit(Color.BLACK)
assert buffer.getLED8Bit(2) == Color8Bit(Color.AQUA)
assert buffer.getLED8Bit(3) == Color8Bit(Color.AQUA)
assert buffer.getLED8Bit(4) == Color8Bit(Color.AQUA)
assert buffer.getLED8Bit(5) == Color8Bit(Color.BLACK)
def test_solid_color():
buffer = AddressableLEDBuffer(99)
LEDPattern.solid(Color.YELLOW).applyTo(buffer)
for i in range(len(buffer)):
assert buffer.getLED8Bit(i) == Color8Bit(Color.YELLOW)
def test_gradient_0_sets_to_black():
pattern = LEDPattern.gradient(LEDPattern.GradientType.CONTINUOUS, [])
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.BLACK)
def test_gradient_1_sets_to_solid():
pattern = LEDPattern.gradient(LEDPattern.GradientType.CONTINUOUS, [Color.YELLOW])
buffer = AddressableLEDBuffer(99)
pattern.applyTo(buffer)
for i in range(len(buffer)):
assert buffer.getLED8Bit(i) == Color8Bit(Color.YELLOW)
def test_continuous_gradient_2_colors():
pattern = LEDPattern.gradient(
LEDPattern.GradientType.CONTINUOUS, [Color.YELLOW, Color.PURPLE]
)
buffer = AddressableLEDBuffer(99)
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.YELLOW)
assert buffer.getLED8Bit(25) == lerp_rgb(Color.YELLOW, Color.PURPLE, 25 / 49.0)
assert buffer.getLED8Bit(49) == Color8Bit(Color.PURPLE)
assert buffer.getLED8Bit(73) == lerp_rgb(Color.YELLOW, Color.PURPLE, 25 / 49.0)
assert buffer.getLED8Bit(98) == Color8Bit(Color.YELLOW)
def test_discontinuous_gradient_2_colors():
pattern = LEDPattern.gradient(
LEDPattern.GradientType.DISCONTINUOUS, [Color.YELLOW, Color.PURPLE]
)
buffer = AddressableLEDBuffer(99)
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.YELLOW)
assert buffer.getLED8Bit(49) == lerp_rgb(Color.YELLOW, Color.PURPLE, 0.5)
assert buffer.getLED8Bit(98) == Color8Bit(Color.PURPLE)
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.BLACK)
def test_step_1_sets_to_solid():
pattern = LEDPattern.steps([(0.0, Color.YELLOW)])
buffer = AddressableLEDBuffer(99)
pattern.applyTo(buffer)
for i in range(len(buffer)):
assert buffer.getLED8Bit(i) == Color8Bit(Color.YELLOW)
def test_step_half_sets_to_half_off_half_color():
pattern = LEDPattern.steps([(0.5, Color.YELLOW)])
buffer = AddressableLEDBuffer(99)
pattern.applyTo(buffer)
for i in range(49):
assert buffer.getLED8Bit(i) == Color8Bit(Color.BLACK)
for i in range(49, len(buffer)):
assert buffer.getLED8Bit(i) == Color8Bit(Color.YELLOW)
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.ROSY_BROWN).reversed()
pattern.applyTo(buffer)
for led in range(len(buffer)):
assert buffer.getLED8Bit(led) == Color8Bit(Color.ROSY_BROWN)
def test_reverse_steps():
buffer = AddressableLEDBuffer(100)
pattern = LEDPattern.steps([(0.0, Color.WHITE), (0.5, Color.YELLOW)]).reversed()
pattern.applyTo(buffer)
for led in range(len(buffer)):
expected = Color8Bit(Color.YELLOW if led < 50 else Color.WHITE)
assert buffer.getLED8Bit(led) == expected
def white_yellow_purple(reader, writer):
colors = [Color.WHITE, Color.YELLOW, Color.PURPLE]
for led in range(reader.size()):
writer(led, colors[led % 3])
@pytest.mark.parametrize(
"offset,expected",
[
(1, [Color.PURPLE, Color.WHITE, Color.YELLOW]),
(-1, [Color.YELLOW, Color.PURPLE, Color.WHITE]),
(0, [Color.WHITE, Color.YELLOW, Color.PURPLE]),
],
)
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.WHITE).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.WHITE if t % 4 < 2 else Color.BLACK)
assert buffer.getLED8Bit(0) == expected
def test_blink_in_sync():
state = {"on": False}
pattern = LEDPattern.solid(Color.WHITE).synchronizedBlink(lambda: state["on"])
buffer = AddressableLEDBuffer(1)
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.BLACK)
state["on"] = True
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.WHITE)
state["on"] = False
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.BLACK)
@pytest.mark.skip(reason="Python bindings do not expose a way to mock wpi::util::Now()")
def test_breathe():
pattern = LEDPattern.solid(Color.WHITE).breathe(units.microseconds(4))
buffer = AddressableLEDBuffer(1)
RobotController.setTimeSource(lambda: 0)
pattern.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.WHITE)
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.BLACK)
def test_overlay_solid_on_solid():
overlay = LEDPattern.solid(Color.YELLOW).overlayOn(LEDPattern.solid(Color.WHITE))
buffer = AddressableLEDBuffer(1)
overlay.applyTo(buffer)
assert buffer.getLED8Bit(0) == Color8Bit(Color.YELLOW)
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.WHITE)
for i in range(3, 10):
assert buffer.getLED8Bit(i) == Color8Bit(Color.BLACK)
def test_blend():
pattern = LEDPattern.solid(Color.BLUE).blend(LEDPattern.solid(Color.RED))
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.WHITE), (0.5, Color.BLACK)])
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.BLACK)

View File

@@ -1,6 +1,7 @@
from wpilib import OnboardIMU
from wpilib.simulation import OnboardIMUSim
def test_sim_device() -> None:
imu = OnboardIMU(OnboardIMU.MountOrientation.FLAT)
@@ -15,15 +16,15 @@ def test_sim_device() -> None:
assert 0.0 == imu.getAccelX()
assert 0.0 == imu.getAccelY()
assert 0.0 == imu.getAccelZ()
sim.setAngleX(1)
sim.setAngleY(2)
sim.setAngleZ(3)
sim.setGyroRateX(3.504)
sim.setGyroRateY(1.91)
sim.setGyroRateZ(22.9)
sim.setAccelX(-1)
sim.setAccelY(-2)
sim.setAccelZ(-3)
@@ -38,4 +39,4 @@ def test_sim_device() -> None:
assert -1.0 == imu.getAccelX()
assert -2.0 == imu.getAccelY()
assert -3.0 == imu.getAccelZ()
assert -3.0 == imu.getAccelZ()