128 lines
4.3 KiB
C++
128 lines
4.3 KiB
C++
#include "PixelStreamController.h"
|
|
|
|
namespace {
|
|
constexpr int COMPONENTS_PER_PIXEL = 3;
|
|
}
|
|
|
|
PixelStreamController::PixelStreamController(NodeContext& ctxRef, const PixelStreamConfig& cfg)
|
|
: ctx(ctxRef), config(cfg), pixels(cfg.pixelCount, cfg.pin, cfg.pixelType) {
|
|
}
|
|
|
|
void PixelStreamController::begin() {
|
|
pixels.begin();
|
|
pixels.setBrightness(config.brightness);
|
|
// Default all pixels to green so we can verify hardware before streaming frames
|
|
for (uint16_t i = 0; i < config.pixelCount; ++i) {
|
|
pixels.setPixelColor(i, pixels.Color(0, 255, 0));
|
|
}
|
|
pixels.show();
|
|
|
|
ctx.on("udp/raw", [this](void* data) {
|
|
this->handleEvent(data);
|
|
});
|
|
|
|
LOG_INFO("PixelStream", String("PixelStreamController ready on pin ") + String(config.pin) + " with " + String(config.pixelCount) + " pixels");
|
|
}
|
|
|
|
void PixelStreamController::handleEvent(void* data) {
|
|
if (data == nullptr) {
|
|
return;
|
|
}
|
|
|
|
String* payload = static_cast<String*>(data);
|
|
if (!payload) {
|
|
return;
|
|
}
|
|
|
|
if (!applyFrame(*payload)) {
|
|
LOG_WARN("PixelStream", String("Ignoring RAW payload with invalid length (") + String(payload->length()) + ")");
|
|
}
|
|
}
|
|
|
|
bool PixelStreamController::applyFrame(const String& payload) {
|
|
static constexpr std::size_t frameWidth = COMPONENTS_PER_PIXEL * 2;
|
|
const std::size_t payloadLength = static_cast<std::size_t>(payload.length());
|
|
|
|
if (payloadLength == 0 || (payloadLength % frameWidth) != 0) {
|
|
LOG_WARN("PixelStream", String("Payload size ") + String(payloadLength) + " is not a multiple of " + String(frameWidth));
|
|
return false;
|
|
}
|
|
|
|
const uint16_t framesProvided = static_cast<uint16_t>(payloadLength / frameWidth);
|
|
const uint16_t pixelsToUpdate = std::min(config.pixelCount, framesProvided);
|
|
|
|
for (uint16_t index = 0; index < pixelsToUpdate; ++index) {
|
|
const std::size_t base = static_cast<std::size_t>(index) * frameWidth;
|
|
FrameComponents components{};
|
|
if (!tryParsePixel(payload, base, components)) {
|
|
LOG_WARN("PixelStream", String("Invalid hex data at pixel index ") + String(index));
|
|
return false;
|
|
}
|
|
const uint16_t hardwareIndex = mapPixelIndex(index);
|
|
pixels.setPixelColor(hardwareIndex, pixels.Color(components.red, components.green, components.blue));
|
|
}
|
|
|
|
// Clear any remaining pixels so stale data is removed when fewer frames are provided
|
|
for (uint16_t index = pixelsToUpdate; index < config.pixelCount; ++index) {
|
|
const uint16_t hardwareIndex = mapPixelIndex(index);
|
|
pixels.setPixelColor(hardwareIndex, 0);
|
|
}
|
|
|
|
pixels.show();
|
|
return true;
|
|
}
|
|
|
|
uint16_t PixelStreamController::mapPixelIndex(uint16_t logicalIndex) const {
|
|
if (config.matrixWidth == 0) {
|
|
return logicalIndex;
|
|
}
|
|
|
|
const uint16_t row = logicalIndex / config.matrixWidth;
|
|
const uint16_t col = logicalIndex % config.matrixWidth;
|
|
|
|
if (!config.matrixSerpentine || (row % 2 == 0)) {
|
|
return row * config.matrixWidth + col;
|
|
}
|
|
|
|
const uint16_t reversedCol = (config.matrixWidth - 1) - col;
|
|
return row * config.matrixWidth + reversedCol;
|
|
}
|
|
|
|
int PixelStreamController::hexToNibble(char c) {
|
|
if (c >= '0' && c <= '9') {
|
|
return c - '0';
|
|
}
|
|
if (c >= 'a' && c <= 'f') {
|
|
return 10 + c - 'a';
|
|
}
|
|
if (c >= 'A' && c <= 'F') {
|
|
return 10 + c - 'A';
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool PixelStreamController::tryParsePixel(const String& payload, std::size_t startIndex, FrameComponents& components) const {
|
|
static constexpr std::size_t frameWidth = COMPONENTS_PER_PIXEL * 2;
|
|
if (startIndex + frameWidth > static_cast<std::size_t>(payload.length())) {
|
|
return false;
|
|
}
|
|
|
|
const int rHi = hexToNibble(payload[startIndex]);
|
|
const int rLo = hexToNibble(payload[startIndex + 1]);
|
|
const int gHi = hexToNibble(payload[startIndex + 2]);
|
|
const int gLo = hexToNibble(payload[startIndex + 3]);
|
|
const int bHi = hexToNibble(payload[startIndex + 4]);
|
|
const int bLo = hexToNibble(payload[startIndex + 5]);
|
|
|
|
if (rHi < 0 || rLo < 0 || gHi < 0 || gLo < 0 || bHi < 0 || bLo < 0) {
|
|
return false;
|
|
}
|
|
|
|
components.red = static_cast<uint8_t>((rHi << 4) | rLo);
|
|
components.green = static_cast<uint8_t>((gHi << 4) | gLo);
|
|
components.blue = static_cast<uint8_t>((bHi << 4) | bLo);
|
|
return true;
|
|
}
|
|
|
|
|