mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpilibj, wpilibc] Fix LED patterns not offsetting reads (#6948)
Was causing bugs when combined with patterns that need to read back from the buffer (eg masks and overlays) Co-authored-by: Joseph Eng <s-engjo@bsd405.org>
This commit is contained in:
@@ -18,33 +18,46 @@
|
||||
|
||||
using namespace frc;
|
||||
|
||||
LEDPattern::LEDPattern(LEDPatternFn impl) : m_impl(std::move(impl)) {}
|
||||
LEDPattern::LEDPattern(std::function<void(frc::LEDPattern::LEDReader,
|
||||
std::function<void(int, frc::Color)>)>
|
||||
impl)
|
||||
: m_impl(std::move(impl)) {}
|
||||
|
||||
void LEDPattern::ApplyTo(LEDPattern::LEDReader reader,
|
||||
std::function<void(int, frc::Color)> writer) const {
|
||||
m_impl(reader, writer);
|
||||
}
|
||||
|
||||
void LEDPattern::ApplyTo(std::span<AddressableLED::LEDData> data,
|
||||
LEDWriterFn writer) const {
|
||||
m_impl(data, writer);
|
||||
std::function<void(int, frc::Color)> writer) const {
|
||||
ApplyTo(LEDPattern::LEDReader{[=](size_t i) { return data[i]; }, data.size()},
|
||||
writer);
|
||||
}
|
||||
|
||||
void LEDPattern::ApplyTo(std::span<AddressableLED::LEDData> data) const {
|
||||
ApplyTo(data, [&](int index, Color color) { data[index].SetLED(color); });
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::Reversed() {
|
||||
return LEDPattern{[self = *this](auto data, auto writer) {
|
||||
self.ApplyTo(data, [&](int i, Color color) {
|
||||
writer((data.size() - 1) - i, color);
|
||||
});
|
||||
LEDPattern LEDPattern::MapIndex(
|
||||
std::function<size_t(size_t, size_t)> indexMapper) {
|
||||
return LEDPattern{[self = *this, indexMapper](auto data, auto writer) {
|
||||
size_t bufLen = data.size();
|
||||
self.ApplyTo(
|
||||
LEDPattern::LEDReader{
|
||||
[=](auto i) { return data[indexMapper(bufLen, i)]; }, bufLen},
|
||||
[&](int i, Color color) { writer(indexMapper(bufLen, i), color); });
|
||||
}};
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::Reversed() {
|
||||
return MapIndex([](size_t bufLen, size_t i) { return bufLen - 1 - i; });
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::OffsetBy(int offset) {
|
||||
return LEDPattern{[=, self = *this](auto data, auto writer) {
|
||||
self.ApplyTo(data, [&data, &writer, offset](int i, Color color) {
|
||||
int shiftedIndex =
|
||||
frc::FloorMod(i + offset, static_cast<int>(data.size()));
|
||||
writer(shiftedIndex, color);
|
||||
});
|
||||
}};
|
||||
return MapIndex([offset](size_t bufLen, size_t i) {
|
||||
return frc::FloorMod(static_cast<int>(i) + offset,
|
||||
static_cast<int>(bufLen));
|
||||
});
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::ScrollAtRelativeSpeed(units::hertz_t velocity) {
|
||||
@@ -53,8 +66,7 @@ LEDPattern LEDPattern::ScrollAtRelativeSpeed(units::hertz_t velocity) {
|
||||
// Invert and multiply by 1,000,000 to get microseconds
|
||||
double periodMicros = 1e6 / velocity.value();
|
||||
|
||||
return LEDPattern{[=, self = *this](auto data, auto writer) {
|
||||
auto bufLen = data.size();
|
||||
return MapIndex([=](size_t bufLen, size_t i) {
|
||||
auto now = wpi::Now();
|
||||
|
||||
// index should move by (bufLen) / (period)
|
||||
@@ -62,12 +74,9 @@ LEDPattern LEDPattern::ScrollAtRelativeSpeed(units::hertz_t velocity) {
|
||||
(now % static_cast<int64_t>(std::floor(periodMicros))) / periodMicros;
|
||||
int offset = static_cast<int>(std::floor(t * bufLen));
|
||||
|
||||
self.ApplyTo(data, [=](int i, Color color) {
|
||||
// floorMod so if the offset is negative, we still get positive outputs
|
||||
int shiftedIndex = frc::FloorMod(i + offset, static_cast<int>(bufLen));
|
||||
writer(shiftedIndex, color);
|
||||
});
|
||||
}};
|
||||
return frc::FloorMod(static_cast<int>(i) + offset,
|
||||
static_cast<int>(bufLen));
|
||||
});
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
|
||||
@@ -77,8 +86,7 @@ LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
|
||||
auto microsPerLed =
|
||||
static_cast<int64_t>(std::floor((ledSpacing / velocity).value() * 1e6));
|
||||
|
||||
return LEDPattern{[=, self = *this](auto data, auto writer) {
|
||||
auto bufLen = data.size();
|
||||
return MapIndex([=](size_t bufLen, size_t i) {
|
||||
auto now = wpi::Now();
|
||||
|
||||
// every step in time that's a multiple of microsPerLED will increment
|
||||
@@ -87,13 +95,9 @@ LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
|
||||
// offset values for negative velocities
|
||||
auto offset = static_cast<int64_t>(now) / microsPerLed;
|
||||
|
||||
self.ApplyTo(data, [=, &writer](int i, Color color) {
|
||||
// FloorMod so if the offset is negative, we still get positive outputs
|
||||
int shiftedIndex = frc::FloorMod(i + offset, static_cast<int>(bufLen));
|
||||
|
||||
writer(shiftedIndex, color);
|
||||
});
|
||||
}};
|
||||
return frc::FloorMod(static_cast<int>(i) + offset,
|
||||
static_cast<int>(bufLen));
|
||||
});
|
||||
}
|
||||
|
||||
LEDPattern LEDPattern::Blink(units::second_t onTime, units::second_t offTime) {
|
||||
|
||||
@@ -18,21 +18,36 @@
|
||||
|
||||
namespace frc {
|
||||
|
||||
/**
|
||||
* Sets the LED at the given index to the given color.
|
||||
*/
|
||||
using LEDWriterFn = std::function<void(int, frc::Color)>;
|
||||
|
||||
/**
|
||||
* Accepts a data buffer (1st argument) and a callback (2nd argument) for
|
||||
* writing data.
|
||||
*/
|
||||
using LEDPatternFn =
|
||||
std::function<void(std::span<frc::AddressableLED::LEDData>, LEDWriterFn)>;
|
||||
|
||||
class LEDPattern {
|
||||
public:
|
||||
explicit LEDPattern(LEDPatternFn impl);
|
||||
/**
|
||||
* A wrapper around a length and an arbitrary reader function that accepts an
|
||||
* LED index and returns data for the LED at that index. This configuration
|
||||
* allows us to abstract over different container types without templating.
|
||||
*/
|
||||
class LEDReader {
|
||||
public:
|
||||
LEDReader(std::function<frc::AddressableLED::LEDData(int)> impl,
|
||||
size_t size)
|
||||
: m_impl{std::move(impl)}, m_size{size} {}
|
||||
|
||||
frc::AddressableLED::LEDData operator[](size_t index) const {
|
||||
return m_impl(index);
|
||||
}
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
private:
|
||||
std::function<frc::AddressableLED::LEDData(int)> m_impl;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
explicit LEDPattern(std::function<void(frc::LEDPattern::LEDReader,
|
||||
std::function<void(int, frc::Color)>)>
|
||||
impl);
|
||||
|
||||
void ApplyTo(LEDReader reader,
|
||||
std::function<void(int, frc::Color)> writer) const;
|
||||
|
||||
/**
|
||||
* Writes the pattern to an LED buffer. Dynamic animations should be called
|
||||
@@ -48,7 +63,7 @@ class LEDPattern {
|
||||
* @param writer data writer for setting new LED colors on the LED strip
|
||||
*/
|
||||
void ApplyTo(std::span<frc::AddressableLED::LEDData> data,
|
||||
LEDWriterFn writer) const;
|
||||
std::function<void(int, frc::Color)> writer) const;
|
||||
|
||||
/**
|
||||
* Writes the pattern to an LED buffer. Dynamic animations should be called
|
||||
@@ -64,6 +79,15 @@ class LEDPattern {
|
||||
*/
|
||||
void ApplyTo(std::span<frc::AddressableLED::LEDData> data) const;
|
||||
|
||||
/**
|
||||
* Creates a pattern with remapped indices.
|
||||
*
|
||||
* @param indexMapper the index mapper
|
||||
* @return the mapped pattern
|
||||
*/
|
||||
[[nodiscard]]
|
||||
LEDPattern MapIndex(std::function<size_t(size_t, size_t)> indexMapper);
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays this one in reverse. Scrolling patterns
|
||||
* will scroll in the opposite direction (but at the same speed). It will
|
||||
@@ -373,6 +397,8 @@ class LEDPattern {
|
||||
static LEDPattern Rainbow(int saturation, int value);
|
||||
|
||||
private:
|
||||
LEDPatternFn m_impl;
|
||||
std::function<void(frc::LEDPattern::LEDReader,
|
||||
std::function<void(int, frc::Color)>)>
|
||||
m_impl;
|
||||
};
|
||||
} // namespace frc
|
||||
|
||||
@@ -813,6 +813,220 @@ TEST(LEDPatternTest, ClippingBrightness) {
|
||||
AssertIndexColor(buffer, 0, Color::kWhite);
|
||||
}
|
||||
|
||||
TEST(LEDPatternTest, ReverseMask) {
|
||||
std::array<AddressableLED::LEDData, 8> buffer;
|
||||
|
||||
std::array<std::pair<double, Color>, 4> colorSteps{
|
||||
std::pair{0.0, Color::kRed}, std::pair{0.25, Color::kBlue},
|
||||
std::pair{0.5, Color::kYellow}, std::pair{0.75, Color::kGreen}};
|
||||
std::array<std::pair<double, Color>, 2> maskSteps{
|
||||
std::pair{0, Color::kWhite}, std::pair{0.5, Color::kBlack}};
|
||||
|
||||
auto pattern = LEDPattern::Steps(colorSteps)
|
||||
.Mask(LEDPattern::Steps(maskSteps))
|
||||
.Reversed();
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 7, Color::kRed);
|
||||
AssertIndexColor(buffer, 6, Color::kRed);
|
||||
AssertIndexColor(buffer, 5, Color::kBlue);
|
||||
AssertIndexColor(buffer, 4, Color::kBlue);
|
||||
AssertIndexColor(buffer, 3, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
}
|
||||
|
||||
TEST(LEDPatternTest, OffsetMask) {
|
||||
std::array<AddressableLED::LEDData, 8> buffer;
|
||||
|
||||
std::array<std::pair<double, Color>, 4> colorSteps{
|
||||
std::pair{0.0, Color::kRed}, std::pair{0.25, Color::kBlue},
|
||||
std::pair{0.5, Color::kYellow}, std::pair{0.75, Color::kGreen}};
|
||||
std::array<std::pair<double, Color>, 2> maskSteps{
|
||||
std::pair{0, Color::kWhite}, std::pair{0.5, Color::kBlack}};
|
||||
|
||||
auto pattern = LEDPattern::Steps(colorSteps)
|
||||
.Mask(LEDPattern::Steps(maskSteps))
|
||||
.OffsetBy(4);
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kBlack);
|
||||
AssertIndexColor(buffer, 3, Color::kBlack);
|
||||
AssertIndexColor(buffer, 4, Color::kRed);
|
||||
AssertIndexColor(buffer, 5, Color::kRed);
|
||||
AssertIndexColor(buffer, 6, Color::kBlue);
|
||||
AssertIndexColor(buffer, 7, Color::kBlue);
|
||||
}
|
||||
|
||||
TEST(LEDPatternTest, RelativeScrollingMask) {
|
||||
std::array<AddressableLED::LEDData, 8> buffer;
|
||||
|
||||
std::array<std::pair<double, Color>, 4> colorSteps{
|
||||
std::pair{0.0, Color::kRed}, std::pair{0.25, Color::kBlue},
|
||||
std::pair{0.5, Color::kYellow}, std::pair{0.75, Color::kGreen}};
|
||||
std::array<std::pair<double, Color>, 2> maskSteps{
|
||||
std::pair{0, Color::kWhite}, std::pair{0.5, Color::kBlack}};
|
||||
|
||||
auto pattern = LEDPattern::Steps(colorSteps)
|
||||
.Mask(LEDPattern::Steps(maskSteps))
|
||||
.ScrollAtRelativeSpeed(units::hertz_t{1e6 / 8.0});
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
static uint64_t now = 0ull;
|
||||
WPI_SetNowImpl([] { return now; });
|
||||
|
||||
{
|
||||
now = 0ull; // start
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kRed);
|
||||
AssertIndexColor(buffer, 1, Color::kRed);
|
||||
AssertIndexColor(buffer, 2, Color::kBlue);
|
||||
AssertIndexColor(buffer, 3, Color::kBlue);
|
||||
AssertIndexColor(buffer, 4, Color::kBlack);
|
||||
AssertIndexColor(buffer, 5, Color::kBlack);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 1ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kRed);
|
||||
AssertIndexColor(buffer, 2, Color::kRed);
|
||||
AssertIndexColor(buffer, 3, Color::kBlue);
|
||||
AssertIndexColor(buffer, 4, Color::kBlue);
|
||||
AssertIndexColor(buffer, 5, Color::kBlack);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 2ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kRed);
|
||||
AssertIndexColor(buffer, 3, Color::kRed);
|
||||
AssertIndexColor(buffer, 4, Color::kBlue);
|
||||
AssertIndexColor(buffer, 5, Color::kBlue);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 3ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kBlack);
|
||||
AssertIndexColor(buffer, 3, Color::kRed);
|
||||
AssertIndexColor(buffer, 4, Color::kRed);
|
||||
AssertIndexColor(buffer, 5, Color::kBlue);
|
||||
AssertIndexColor(buffer, 6, Color::kBlue);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
|
||||
WPI_SetNowImpl(nullptr); // cleanup
|
||||
}
|
||||
|
||||
TEST(LEDPatternTest, AbsoluteScrollingMask) {
|
||||
std::array<AddressableLED::LEDData, 8> buffer;
|
||||
|
||||
std::array<std::pair<double, Color>, 4> colorSteps{
|
||||
std::pair{0.0, Color::kRed}, std::pair{0.25, Color::kBlue},
|
||||
std::pair{0.5, Color::kYellow}, std::pair{0.75, Color::kGreen}};
|
||||
std::array<std::pair<double, Color>, 2> maskSteps{
|
||||
std::pair{0, Color::kWhite}, std::pair{0.5, Color::kBlack}};
|
||||
|
||||
auto pattern = LEDPattern::Steps(colorSteps)
|
||||
.Mask(LEDPattern::Steps(maskSteps))
|
||||
.ScrollAtAbsoluteSpeed(1_mps, 1_m);
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
static uint64_t now = 0ull;
|
||||
WPI_SetNowImpl([] { return now; });
|
||||
|
||||
{
|
||||
now = 0ull; // start
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kRed);
|
||||
AssertIndexColor(buffer, 1, Color::kRed);
|
||||
AssertIndexColor(buffer, 2, Color::kBlue);
|
||||
AssertIndexColor(buffer, 3, Color::kBlue);
|
||||
AssertIndexColor(buffer, 4, Color::kBlack);
|
||||
AssertIndexColor(buffer, 5, Color::kBlack);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 1000000ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kRed);
|
||||
AssertIndexColor(buffer, 2, Color::kRed);
|
||||
AssertIndexColor(buffer, 3, Color::kBlue);
|
||||
AssertIndexColor(buffer, 4, Color::kBlue);
|
||||
AssertIndexColor(buffer, 5, Color::kBlack);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 2000000ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kRed);
|
||||
AssertIndexColor(buffer, 3, Color::kRed);
|
||||
AssertIndexColor(buffer, 4, Color::kBlue);
|
||||
AssertIndexColor(buffer, 5, Color::kBlue);
|
||||
AssertIndexColor(buffer, 6, Color::kBlack);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
{
|
||||
now = 3000000ull;
|
||||
SCOPED_TRACE(fmt::format("Time {}", now));
|
||||
|
||||
pattern.ApplyTo(buffer);
|
||||
|
||||
AssertIndexColor(buffer, 0, Color::kBlack);
|
||||
AssertIndexColor(buffer, 1, Color::kBlack);
|
||||
AssertIndexColor(buffer, 2, Color::kBlack);
|
||||
AssertIndexColor(buffer, 3, Color::kRed);
|
||||
AssertIndexColor(buffer, 4, Color::kRed);
|
||||
AssertIndexColor(buffer, 5, Color::kBlue);
|
||||
AssertIndexColor(buffer, 6, Color::kBlue);
|
||||
AssertIndexColor(buffer, 7, Color::kBlack);
|
||||
}
|
||||
|
||||
WPI_SetNowImpl(nullptr); // cleanup
|
||||
}
|
||||
|
||||
void AssertIndexColor(std::span<AddressableLED::LEDData> data, int index,
|
||||
Color color) {
|
||||
frc::Color8Bit color8bit{color};
|
||||
|
||||
@@ -93,6 +93,19 @@ import java.util.function.DoubleSupplier;
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LEDPattern {
|
||||
/** A functional interface for index mapping functions. */
|
||||
@FunctionalInterface
|
||||
interface IndexMapper {
|
||||
/**
|
||||
* Maps the index.
|
||||
*
|
||||
* @param bufLen Length of the buffer
|
||||
* @param index The index to map
|
||||
* @return The mapped index
|
||||
*/
|
||||
int apply(int bufLen, int index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the pattern to an LED buffer. Dynamic animations should be called periodically (such as
|
||||
* with a command or with a periodic method) to refresh the buffer over time.
|
||||
@@ -129,6 +142,41 @@ public interface LEDPattern {
|
||||
applyTo(readWriter, readWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern with remapped indices.
|
||||
*
|
||||
* @param indexMapper the index mapper
|
||||
* @return the mapped pattern
|
||||
*/
|
||||
default LEDPattern mapIndex(IndexMapper indexMapper) {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
applyTo(
|
||||
new LEDReader() {
|
||||
@Override
|
||||
public int getLength() {
|
||||
return reader.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(int index) {
|
||||
return reader.getRed(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(int index) {
|
||||
return reader.getGreen(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(int index) {
|
||||
return reader.getBlue(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
},
|
||||
(i, r, g, b) -> writer.setRGB(indexMapper.apply(bufLen, i), r, g, b));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays this one in reverse. Scrolling patterns will scroll in the
|
||||
* opposite direction (but at the same speed). It will treat the end of an LED strip as the start,
|
||||
@@ -143,10 +191,7 @@ public interface LEDPattern {
|
||||
* @see AddressableLEDBufferView#reversed()
|
||||
*/
|
||||
default LEDPattern reversed() {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
applyTo(reader, (i, r, g, b) -> writer.setRGB((bufLen - 1) - i, r, g, b));
|
||||
};
|
||||
return mapIndex((length, index) -> length - 1 - index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,15 +202,7 @@ public interface LEDPattern {
|
||||
* @return the offset pattern
|
||||
*/
|
||||
default LEDPattern offsetBy(int offset) {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
int shiftedIndex = Math.floorMod(i + offset, bufLen);
|
||||
writer.setRGB(shiftedIndex, r, g, b);
|
||||
});
|
||||
};
|
||||
return mapIndex((length, index) -> Math.floorMod(index + offset, length));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,23 +226,16 @@ public interface LEDPattern {
|
||||
default LEDPattern scrollAtRelativeSpeed(Frequency velocity) {
|
||||
final double periodMicros = velocity.asPeriod().in(Microseconds);
|
||||
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
long now = RobotController.getTime();
|
||||
return mapIndex(
|
||||
(bufLen, index) -> {
|
||||
long now = RobotController.getTime();
|
||||
|
||||
// index should move by (buf.length) / (period)
|
||||
double t = (now % (long) periodMicros) / periodMicros;
|
||||
int offset = (int) (t * bufLen);
|
||||
// index should move by (buf.length) / (period)
|
||||
double t = (now % (long) periodMicros) / periodMicros;
|
||||
int offset = (int) (t * bufLen);
|
||||
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
// floorMod so if the offset is negative, we still get positive outputs
|
||||
int shiftedIndex = Math.floorMod(i + offset, bufLen);
|
||||
|
||||
writer.setRGB(shiftedIndex, r, g, b);
|
||||
});
|
||||
};
|
||||
return Math.floorMod(index + offset, bufLen);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,22 +270,16 @@ public interface LEDPattern {
|
||||
var metersPerMicro = velocity.in(Meters.per(Microsecond));
|
||||
var microsPerLED = (int) (ledSpacing.in(Meters) / metersPerMicro);
|
||||
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
long now = RobotController.getTime();
|
||||
return mapIndex(
|
||||
(bufLen, index) -> {
|
||||
long now = RobotController.getTime();
|
||||
|
||||
// every step in time that's a multiple of microsPerLED will increment the offset by 1
|
||||
var offset = now / microsPerLED;
|
||||
// every step in time that's a multiple of microsPerLED will increment the offset by 1
|
||||
var offset = (int) (now / microsPerLED);
|
||||
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
// floorMod so if the offset is negative, we still get positive outputs
|
||||
int shiftedIndex = Math.floorMod(i + offset, bufLen);
|
||||
|
||||
writer.setRGB(shiftedIndex, r, g, b);
|
||||
});
|
||||
};
|
||||
// floorMod so if the offset is negative, we still get positive outputs
|
||||
return Math.floorMod(index + offset, bufLen);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import static edu.wpi.first.units.Units.Centimeters;
|
||||
import static edu.wpi.first.units.Units.Meters;
|
||||
import static edu.wpi.first.units.Units.MetersPerSecond;
|
||||
import static edu.wpi.first.units.Units.Microsecond;
|
||||
import static edu.wpi.first.units.Units.Microseconds;
|
||||
@@ -15,6 +16,7 @@ import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kContinuous;
|
||||
import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kDiscontinuous;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kBlack;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kBlue;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kGreen;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kLime;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kMagenta;
|
||||
import static edu.wpi.first.wpilibj.util.Color.kMidnightBlue;
|
||||
@@ -815,6 +817,176 @@ class LEDPatternTest {
|
||||
assertColorEquals(kWhite, buffer.getLED(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reverseMask() {
|
||||
var pattern =
|
||||
LEDPattern.steps(Map.of(0, kRed, 0.25, kBlue, 0.5, kYellow, 0.75, kGreen))
|
||||
.mask(LEDPattern.steps(Map.of(0, kWhite, 0.5, kBlack)))
|
||||
.reversed();
|
||||
var buffer = new AddressableLEDBuffer(8);
|
||||
|
||||
pattern.applyTo(buffer);
|
||||
|
||||
assertColorEquals(kRed, buffer.getLED(7));
|
||||
assertColorEquals(kRed, buffer.getLED(6));
|
||||
assertColorEquals(kBlue, buffer.getLED(5));
|
||||
assertColorEquals(kBlue, buffer.getLED(4));
|
||||
assertColorEquals(kBlack, buffer.getLED(3));
|
||||
assertColorEquals(kBlack, buffer.getLED(2));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetMask() {
|
||||
var pattern =
|
||||
LEDPattern.steps(Map.of(0, kRed, 0.25, kBlue, 0.5, kYellow, 0.75, kGreen))
|
||||
.mask(LEDPattern.steps(Map.of(0, kWhite, 0.5, kBlack)))
|
||||
.offsetBy(4);
|
||||
var buffer = new AddressableLEDBuffer(8);
|
||||
|
||||
pattern.applyTo(buffer);
|
||||
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kBlack, buffer.getLED(2));
|
||||
assertColorEquals(kBlack, buffer.getLED(3));
|
||||
assertColorEquals(kRed, buffer.getLED(4));
|
||||
assertColorEquals(kRed, buffer.getLED(5));
|
||||
assertColorEquals(kBlue, buffer.getLED(6));
|
||||
assertColorEquals(kBlue, buffer.getLED(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
void relativeScrollingMask() {
|
||||
// [red, red, blue, blue, yellow, yellow, green, green]
|
||||
// under a mask of first 50% on, last 50% off
|
||||
// [red, red, blue, blue, black, black, black, black]
|
||||
// all scrolling at 1 LED per microsecond
|
||||
var pattern =
|
||||
LEDPattern.steps(Map.of(0, kRed, 0.25, kBlue, 0.5, kYellow, 0.75, kGreen))
|
||||
.mask(LEDPattern.steps(Map.of(0, kWhite, 0.5, kBlack)))
|
||||
.scrollAtRelativeSpeed(Percent.per(Microsecond).of(12.5));
|
||||
var buffer = new AddressableLEDBuffer(8);
|
||||
|
||||
{
|
||||
m_mockTime = 0; // start
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kRed, buffer.getLED(0));
|
||||
assertColorEquals(kRed, buffer.getLED(1));
|
||||
assertColorEquals(kBlue, buffer.getLED(2));
|
||||
assertColorEquals(kBlue, buffer.getLED(3));
|
||||
assertColorEquals(kBlack, buffer.getLED(4));
|
||||
assertColorEquals(kBlack, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 1;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kRed, buffer.getLED(1));
|
||||
assertColorEquals(kRed, buffer.getLED(2));
|
||||
assertColorEquals(kBlue, buffer.getLED(3));
|
||||
assertColorEquals(kBlue, buffer.getLED(4));
|
||||
assertColorEquals(kBlack, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 2;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kRed, buffer.getLED(2));
|
||||
assertColorEquals(kRed, buffer.getLED(3));
|
||||
assertColorEquals(kBlue, buffer.getLED(4));
|
||||
assertColorEquals(kBlue, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 3;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kBlack, buffer.getLED(2));
|
||||
assertColorEquals(kRed, buffer.getLED(3));
|
||||
assertColorEquals(kRed, buffer.getLED(4));
|
||||
assertColorEquals(kBlue, buffer.getLED(5));
|
||||
assertColorEquals(kBlue, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void absoluteScrollingMask() {
|
||||
// [red, red, blue, blue, yellow, yellow, green, green]
|
||||
// under a mask of first 50% on, last 50% off
|
||||
// [red, red, blue, blue, black, black, black, black]
|
||||
// all scrolling at 1 LED per microsecond
|
||||
var pattern =
|
||||
LEDPattern.steps(Map.of(0, kRed, 0.25, kBlue, 0.5, kYellow, 0.75, kGreen))
|
||||
.mask(LEDPattern.steps(Map.of(0, kWhite, 0.5, kBlack)))
|
||||
.scrollAtAbsoluteSpeed(Meters.per(Microsecond).of(1), Meters.one());
|
||||
var buffer = new AddressableLEDBuffer(8);
|
||||
|
||||
{
|
||||
m_mockTime = 0; // start
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kRed, buffer.getLED(0));
|
||||
assertColorEquals(kRed, buffer.getLED(1));
|
||||
assertColorEquals(kBlue, buffer.getLED(2));
|
||||
assertColorEquals(kBlue, buffer.getLED(3));
|
||||
assertColorEquals(kBlack, buffer.getLED(4));
|
||||
assertColorEquals(kBlack, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 1;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kRed, buffer.getLED(1));
|
||||
assertColorEquals(kRed, buffer.getLED(2));
|
||||
assertColorEquals(kBlue, buffer.getLED(3));
|
||||
assertColorEquals(kBlue, buffer.getLED(4));
|
||||
assertColorEquals(kBlack, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 2;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kRed, buffer.getLED(2));
|
||||
assertColorEquals(kRed, buffer.getLED(3));
|
||||
assertColorEquals(kBlue, buffer.getLED(4));
|
||||
assertColorEquals(kBlue, buffer.getLED(5));
|
||||
assertColorEquals(kBlack, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
|
||||
{
|
||||
m_mockTime = 3;
|
||||
pattern.applyTo(buffer);
|
||||
assertColorEquals(kBlack, buffer.getLED(0));
|
||||
assertColorEquals(kBlack, buffer.getLED(1));
|
||||
assertColorEquals(kBlack, buffer.getLED(2));
|
||||
assertColorEquals(kRed, buffer.getLED(3));
|
||||
assertColorEquals(kRed, buffer.getLED(4));
|
||||
assertColorEquals(kBlue, buffer.getLED(5));
|
||||
assertColorEquals(kBlue, buffer.getLED(6));
|
||||
assertColorEquals(kBlack, buffer.getLED(7));
|
||||
}
|
||||
}
|
||||
|
||||
void assertColorEquals(Color expected, Color actual) {
|
||||
assertEquals(new Color8Bit(expected), new Color8Bit(actual));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user