Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/led_strip/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES "base_component" "color"
REQUIRES "heap" "base_component" "color"
)
10 changes: 8 additions & 2 deletions components/led_strip/example/main/led_strip_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,14 @@ extern "C" void app_main(void) {
});

//! [led strip ex1]
// create the rmt object
// create the rmt object with DMA enabled for better performance
espp::Rmt rmt(espp::Rmt::Config{
.gpio_num = NEO_BFF_IO,
.clock_src = RMT_CLK_SRC_DEFAULT,
.dma_enabled = true,
.block_size = 1024,
.resolution_hz = SK6805_FREQ_HZ,
.transaction_queue_depth = 2,
.log_level = espp::Logger::Verbosity::INFO,
});

Expand All @@ -138,14 +142,16 @@ extern "C" void app_main(void) {
rmt.transmit(data, len);
};

// now create the LedStrip object
// now create the LedStrip object with DMA support
espp::LedStrip led_strip(espp::LedStrip::Config{
.num_leds = NEO_BFF_NUM_LEDS,
.write = neopixel_write,
.send_brightness = false,
.byte_order = espp::LedStrip::ByteOrder::GRB,
.start_frame = {},
.end_frame = {},
.use_dma = true,
.dma_allocation_flags = MALLOC_CAP_DMA,
.log_level = espp::Logger::Verbosity::INFO,
});

Expand Down
208 changes: 174 additions & 34 deletions components/led_strip/include/led_strip.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "base_component.hpp"
#include "color.hpp"
#include <esp_heap_caps.h>

namespace espp {
/// \brief Class to control LED strips
Expand Down Expand Up @@ -65,11 +66,14 @@ class LedStrip : public BaseComponent {
write_fn write; ///< Function to write data to the strip
bool send_brightness{true}; ///< Whether to use the brightness value for the LEDs
ByteOrder byte_order{ByteOrder::RGB}; ///< Byte order for the LEDs
std::vector<uint8_t> start_frame{}; ///< Start frame for the strip. Optional - will be sent
std::span<uint8_t> start_frame{}; ///< Start frame for the strip. Optional - will be sent
///< before the first LED if not empty.
std::vector<uint8_t> end_frame{}; ///< End frame for the strip. Optional - will be sent after
///< the last LED if not empty.
Logger::Verbosity log_level; ///< Log level for this class
std::span<uint8_t> end_frame{}; ///< End frame for the strip. Optional - will be sent after
///< the last LED if not empty.
bool use_dma{false}; ///< Whether to use DMA-capable memory allocation
uint32_t dma_allocation_flags{
MALLOC_CAP_DMA}; ///< DMA allocation flags (if use_dma is true). Defaults to MALLOC_CAP_DMA.
Logger::Verbosity log_level; ///< Log level for this class
};

/// \brief Constructor
Expand All @@ -79,19 +83,115 @@ class LedStrip : public BaseComponent {
, num_leds_(config.num_leds)
, send_brightness_(config.send_brightness)
, byte_order_(config.byte_order)
, write_(config.write) {
, write_(config.write)
, use_dma_(config.use_dma) {
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no validation that num_leds is greater than 0. If num_leds is 0, data_size_ could be very small (just the size of start_frame and end_frame), and various operations like shift_left, shift_right, and set_pixel would behave unexpectedly. Consider adding validation in the constructor to ensure num_leds is at least 1.

Suggested change
, use_dma_(config.use_dma) {
, use_dma_(config.use_dma) {
// validate the number of LEDs to ensure internal invariants
if (num_leds_ == 0) {
logger_.error("Config.num_leds must be at least 1; defaulting to 1");
num_leds_ = 1;
}

Copilot uses AI. Check for mistakes.
if (num_leds_ == 0) {
logger_.warn("Initialized LedStrip with 0 LEDs");
return;
}

// set the color data size
pixel_size_ = send_brightness_ ? 4 : 3;
data_.resize(num_leds_ * pixel_size_ + config.start_frame.size() + config.end_frame.size());
data_size_ = num_leds_ * pixel_size_ + config.start_frame.size() + config.end_frame.size();

// Allocate memory based on DMA preference
if (use_dma_) {
uint32_t dma_flags = config.dma_allocation_flags;
if (dma_flags == 0) {
dma_flags = MALLOC_CAP_DMA;
}
data_ = static_cast<uint8_t *>(heap_caps_malloc(data_size_, dma_flags));
if (!data_) {
logger_.warn("Failed to allocate DMA memory, falling back to regular malloc");
data_ = static_cast<uint8_t *>(malloc(data_size_));
}
} else {
data_ = static_cast<uint8_t *>(malloc(data_size_));
}

if (!data_) {
logger_.error("Failed to allocate memory for LED strip data");
return;
}
Comment on lines +112 to +115
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor logs an error when memory allocation fails but continues to leave the object in an invalid state. Methods like set_pixel, shift_left, and shift_right will attempt to dereference the nullptr data_ without checking, leading to undefined behavior. Consider throwing an exception or marking the object as invalid in a way that all methods can check.

Copilot uses AI. Check for mistakes.

// copy the start frame
std::copy(config.start_frame.begin(), config.start_frame.end(), data_.begin());
if (!config.start_frame.empty()) {
memcpy(data_, config.start_frame.data(), config.start_frame.size());
}

// copy the end frame
std::copy(config.end_frame.begin(), config.end_frame.end(),
data_.end() - config.end_frame.size());
if (!config.end_frame.empty()) {
memcpy(data_ + data_size_ - config.end_frame.size(), config.end_frame.data(),
config.end_frame.size());
}

start_offset_ = config.start_frame.size();
end_offset_ = config.end_frame.size();
}

/// \brief Destructor
/// \details This function frees the memory allocated for the LED strip data.
~LedStrip() {
if (data_) {
free(data_);
data_ = nullptr;
}
}

/// \brief Copy constructor (deleted to prevent double-free)
LedStrip(const LedStrip &) = delete;

/// \brief Assignment operator (deleted to prevent double-free)
LedStrip &operator=(const LedStrip &) = delete;

/// \brief Move constructor
/// \param other The other LedStrip to move from
LedStrip(LedStrip &&other) noexcept
: BaseComponent(std::move(other))
, num_leds_(other.num_leds_)
, send_brightness_(other.send_brightness_)
, byte_order_(other.byte_order_)
, pixel_size_(other.pixel_size_)
, start_offset_(other.start_offset_)
, end_offset_(other.end_offset_)
, data_size_(other.data_size_)
, data_(other.data_)
, write_(std::move(other.write_))
, use_dma_(other.use_dma_) {
other.data_ = nullptr;
other.data_size_ = 0;
}

/// \brief Move assignment operator
/// \param other The other LedStrip to move from
/// \return Reference to this LedStrip
LedStrip &operator=(LedStrip &&other) noexcept {
if (this != &other) {
// Free existing data
if (data_) {
free(data_);
}

// Move members from other
logger_ = std::move(other.logger_);
num_leds_ = other.num_leds_;
send_brightness_ = other.send_brightness_;
byte_order_ = other.byte_order_;
pixel_size_ = other.pixel_size_;
start_offset_ = other.start_offset_;
end_offset_ = other.end_offset_;
data_size_ = other.data_size_;
data_ = other.data_;
write_ = std::move(other.write_);
use_dma_ = other.use_dma_;

// Nullify other's pointers
other.data_ = nullptr;
other.data_size_ = 0;
}
return *this;
}

/// \brief Get the number of LEDs in the strip
/// \return Number of LEDs in the strip
size_t num_leds() const { return num_leds_; }
Expand All @@ -102,61 +202,77 @@ class LedStrip : public BaseComponent {

/// \brief Shift the LEDs to the left
/// \param shift_by Number of LEDs to shift by
/// \return true if successful, false if data is invalid or shift_by is out of range
/// \note A negative value for shift_by will shift the LEDs to the right
void shift_left(int shift_by = 1) {
bool shift_left(int shift_by = 1) {
if (!data_) {
logger_.error("No data allocated for LED strip");
return false;
}
if (shift_by == 0)
return;
return true;
if (shift_by >= num_leds_) {
logger_.error("Shift by {} is greater than the number of LEDs ({})", shift_by, num_leds_);
return;
return false;
}
if (shift_by < 0)
shift_by += num_leds_;
std::rotate(data_.begin() + start_offset_,
data_.begin() + start_offset_ + pixel_size_ * shift_by, data_.end() + end_offset_);
std::rotate(data_ + start_offset_, data_ + start_offset_ + pixel_size_ * shift_by,
data_ + data_size_ - end_offset_);
return true;
}

/// \brief Shift the LEDs to the right
/// \param shift_by Number of LEDs to shift by
/// \return true if successful, false if data is invalid or shift_by is out of range
/// \note A negative value for shift_by will shift the LEDs to the left
void shift_right(int shift_by = 1) {
bool shift_right(int shift_by = 1) {
if (!data_) {
logger_.error("No data allocated for LED strip");
return false;
}
if (shift_by == 0)
return;
return true;
if (shift_by >= num_leds_) {
logger_.error("Shift by {} is greater than the number of LEDs ({})", shift_by, num_leds_);
return;
return false;
}
if (shift_by < 0)
shift_by += num_leds_;
std::rotate(data_.rbegin() + end_offset_, data_.rbegin() + end_offset_ + pixel_size_ * shift_by,
data_.rend() + start_offset_);
std::rotate(std::reverse_iterator<uint8_t *>(data_ + data_size_ - end_offset_),
std::reverse_iterator<uint8_t *>(data_ + data_size_ - end_offset_) +
pixel_size_ * shift_by,
std::reverse_iterator<uint8_t *>(data_ + start_offset_));
Comment on lines +242 to +245
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shift_right method does not check if data_ is nullptr before dereferencing it. If the constructor fails to allocate memory, calling this method will result in undefined behavior.

Copilot uses AI. Check for mistakes.
return true;
}

/// \brief Set the color of a single LED
/// \param index Index of the LED to set
/// \param hsv Color to set the LED to
/// \param brightness Brightness of the LED
/// \return true if successful, false if data is invalid or index is out of range
/// \note The index is zero-based.
/// \sa Hsv for more information on the HSV color space
/// \sa show
void set_pixel(int index, const Hsv &hsv, float brightness = 1.0f) {
set_pixel(index, hsv.rgb(), brightness);
bool set_pixel(int index, const Hsv &hsv, float brightness = 1.0f) {
return set_pixel(index, hsv.rgb(), brightness);
}

/// \brief Set the color of a single LED
/// \param index Index of the LED to set
/// \param rgb Color to set the LED to
/// \param brightness Brightness of the LED
/// \return true if successful, false if data is invalid or index is out of range
/// \note The index is zero-based.
/// \sa Rgb for more information on the RGB color space
/// \sa show
void set_pixel(int index, const Rgb &rgb, float brightness = 1.0f) {
bool set_pixel(int index, const Rgb &rgb, float brightness = 1.0f) {
uint8_t brightness_byte = std::clamp<uint8_t>(brightness * 31.0f, 0, 31);
uint8_t r, g, b;
r = std::clamp<uint8_t>(rgb.r * 255.0f, 0, 255);
g = std::clamp<uint8_t>(rgb.g * 255.0f, 0, 255);
b = std::clamp<uint8_t>(rgb.b * 255.0f, 0, 255);
set_pixel(index, r, g, b, brightness_byte);
return set_pixel(index, r, g, b, brightness_byte);
}

/// \brief Set the color of a single LED
Expand All @@ -165,12 +281,17 @@ class LedStrip : public BaseComponent {
/// \param g Green component of the color to set the LED to [0-255]
/// \param b Blue component of the color to set the LED to [0-255]
/// \param brightness Brightness of the LED [0-31]
/// \return true if successful, false if data is invalid or index is out of range
/// \note The index is zero-based.
/// \sa show
void set_pixel(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness = 0b11111) {
bool set_pixel(int index, uint8_t r, uint8_t g, uint8_t b, uint8_t brightness = 0b11111) {
if (!data_) {
logger_.error("No data allocated for LED strip");
return false;
}
if (index < 0 || index >= num_leds_) {
logger_.error("set_pixel: index out of range: %d", index);
return;
return false;
}

int offset = start_offset_ + index * pixel_size_;
Expand Down Expand Up @@ -201,50 +322,67 @@ class LedStrip : public BaseComponent {
data_[offset++] = r;
data_[offset++] = g;
data_[offset++] = b;
return true;
}

/// \brief Set the color of all the LEDs
/// \param hsv Color to set the LEDs to
/// \param brightness Brightness of the LEDs
/// \return true if successful, false if data is invalid
/// \note The index is zero-based.
/// \sa set_pixel
/// \sa show
void set_all(const Hsv &hsv, float brightness = 1.0f) { set_all(hsv.rgb(), brightness); }
bool set_all(const Hsv &hsv, float brightness = 1.0f) { return set_all(hsv.rgb(), brightness); }

/// \brief Set the color of all the LEDs
/// \param rgb Color to set the LEDs to
/// \param brightness Brightness of the LEDs
/// \return true if successful, false if data is invalid
/// \sa set_pixel
/// \sa show
void set_all(const Rgb &rgb, float brightness = 1.0f) {
bool set_all(const Rgb &rgb, float brightness = 1.0f) {
uint8_t brightness_byte = std::clamp<uint8_t>(brightness * 255.0f, 0, 255);
set_all(rgb.r, rgb.g, rgb.b, brightness_byte);
return set_all(rgb.r, rgb.g, rgb.b, brightness_byte);
}

/// \brief Set the color of all the LEDs
/// \param r Red component of the color to set the LEDs to
/// \param g Green component of the color to set the LEDs to
/// \param b Blue component of the color to set the LEDs to
/// \param brightness Brightness of the LEDs
/// \return true if successful, false if data is invalid
/// \sa set_pixel
/// \sa show
void set_all(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness = 0xff) {
bool set_all(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness = 0xff) {
if (!data_) {
logger_.error("No data allocated for LED strip");
return false;
}
// fill out all the color data
for (int i = 0; i < num_leds_; i++) {
set_pixel(i, r, g, b, brightness);
if (!set_pixel(i, r, g, b, brightness)) {
return false;
}
}
return true;
}

/// \brief Show the colors on the strip
/// \details This function writes the colors to the strip. It
/// should be called after setting the colors of the LEDs.
/// \return true if successful, false if data is invalid
/// \note This function blocks until the colors have been written
/// to the strip.
/// \sa set_pixel
/// \sa set_all
void show() {
logger_.debug("writing data {::02x}", data_);
write_(&data_[0], data_.size());
bool show() {
if (!data_) {
logger_.error("No data allocated for LED strip");
return false;
}
logger_.debug("writing data {::02x}", std::span<uint8_t>(data_, data_ + data_size_));
write_(data_, data_size_);
return true;
}

protected:
Expand All @@ -254,7 +392,9 @@ class LedStrip : public BaseComponent {
size_t pixel_size_{3};
size_t start_offset_{0};
size_t end_offset_{0};
std::vector<uint8_t> data_;
size_t data_size_{0};
uint8_t *data_{nullptr};
write_fn write_;
bool use_dma_{false};
};
} // namespace espp
Loading