From c45cba611d0646de328aaacf99f23df6667841f5 Mon Sep 17 00:00:00 2001 From: "Tobias J. Endres" Date: Fri, 15 Aug 2025 21:45:28 +0200 Subject: [PATCH] Feat: Integrate WledClient and update HyperionGrabber for direct WLED communication --- hyperiongrabber.cpp | 82 ++++++++++----------------------------------- hyperiongrabber.h | 16 ++++----- wledclient.cpp | 76 +++++++++++++++++++++++++++++++++++++++++ wledclient.h | 28 ++++++++++++++++ 4 files changed, 130 insertions(+), 72 deletions(-) create mode 100644 wledclient.cpp create mode 100644 wledclient.h diff --git a/hyperiongrabber.cpp b/hyperiongrabber.cpp index e970de9..d1713b7 100644 --- a/hyperiongrabber.cpp +++ b/hyperiongrabber.cpp @@ -9,14 +9,15 @@ HyperionGrabber::HyperionGrabber(QHash opts) { - QString addr = "localhost"; - unsigned short port = 19444; + QString addr = "192.168.1.177"; // Default WLED IP + unsigned short port = 4048; // Default DDP port unsigned short scale = 8; - unsigned short priority = 100; - QString redAdjust = "", greenAdjust = "", blueAdjust = ""; - QString temperature = "", threshold = "", transform = ""; + unsigned short frameskip = 0; + int inactiveTime = 0; _scale_m = scale; + _frameskip_m = frameskip; + _inactiveTime_m = inactiveTime; QHashIterator i(opts); while (i.hasNext()) { @@ -25,44 +26,26 @@ HyperionGrabber::HyperionGrabber(QHash opts) addr = i.value(); } else if ((i.key() == "p" || i.key() == "port") && i.value().toUShort()) { port = i.value().toUShort(); - } else if ((i.key() == "c" || i.key() == "priority") && (i.value().toUShort() && i.value().toUShort() <= 255)) { - priority = i.value().toUShort(); } else if ((i.key() == "s" || i.key() == "scale") && i.value().toUShort()) { - scale = i.value().toUShort(); + _scale_m = i.value().toUShort(); } else if ((i.key() == "f" || i.key() == "frameskip") && i.value().toUShort()) { _frameskip_m = i.value().toUShort(); } else if ((i.key() == "i" || i.key() == "inactive") && i.value().toInt()) { _inactiveTime_m = (i.value().toInt() * 1000); - } else if (i.key() == "r" || i.key() == "redadjust") { - redAdjust = _parseColorArr(i.value(), 1); - } else if (i.key() == "g" || i.key() == "greenadjust") { - greenAdjust = _parseColorArr(i.value(), 1); - } else if (i.key() == "b" || i.key() == "blueadjust") { - blueAdjust = _parseColorArr(i.value(), 1); - } else if (i.key() == "t" || i.key() == "temperature") { - temperature = _parseColorArr(i.value(), 1); - } else if (i.key() == "d" || i.key() == "threshold") { - threshold = _parseColorArr(i.value(), 0); - } else if ((i.key() == "l" || i.key() == "transform") && _parseColorArr(i.value(), 0) != "") { - transform = i.value(); } } - _hyperionPriority_m = QString::number(priority); - _hclient_p = new HyperionClient(addr, port, _hyperionPriority_m); - _hclient_p->ledAdjustments(redAdjust, greenAdjust, blueAdjust, temperature, threshold, transform); + _wledClient_p = new WledClient(addr, port, this); _waylandGrabber_p = new WaylandGrabber(this); connect(_waylandGrabber_p, &WaylandGrabber::frameReady, this, &HyperionGrabber::_processFrame); _waylandGrabber_p->start(); - QScreen *screen = QGuiApplication::primaryScreen(); if (screen) { - _setImgSize(screen->size().width() / scale, screen->size().height() / scale); + qDebug() << "Screen size:" << screen->size().width() << "x" << screen->size().height(); } else { - qWarning() << "Could not get primary screen size for Wayland grabber."; - _setImgSize(1920 / scale, 1080 / scale); // Default to 1080p if screen not found + qWarning() << "Could not get primary screen size for Wayland grabber. Defaulting to 1920x1080."; } if (_inactiveTime_m) { @@ -78,40 +61,11 @@ HyperionGrabber::~HyperionGrabber() _timer_p->stop(); delete _timer_p; } - delete _hclient_p; -} - -// private - -QString HyperionGrabber::_parseColorArr(QString value, bool isInt) -{ - QStringList values = value.split(','); - if (values.size() != 3) { - return ""; - } - value = "["; - for (int i = 0; i < 3; i++) { - if (isInt && (values.at(i).toInt() < 0 || values.at(i).toInt() > 255)) { - return ""; - } - if (!isInt && (values.at(i).toDouble() < 0.0 || values.at(i).toDouble() > 1.0)) { - return ""; - } - value.append(values.at(i)); - value.append(","); - } - value.chop(1); - value.append("]"); - return value; + delete _wledClient_p; } // private slots -void HyperionGrabber::_inActivity() -{ - _hclient_p->clearLeds(); -} - void HyperionGrabber::_processFrame(const QVideoFrame &frame) { if (!frame.isValid()) { @@ -139,17 +93,17 @@ void HyperionGrabber::_processFrame(const QVideoFrame &frame) // Scale the image using Qt's optimized scaling QImage scaledImage = image.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - // Convert to a format suitable for Hyperion (RGB888) + // Convert to a format suitable for WLED (RGB888) if (scaledImage.format() != QImage::Format_RGB888) { scaledImage = scaledImage.convertToFormat(QImage::Format_RGB888); } - // Update the image size in the client and send - _hclient_p->setImgSize(scaledImage.width(), scaledImage.height()); - _hclient_p->sendImage(scaledImage.constBits(), scaledImage.sizeInBytes()); + _wledClient_p->sendImage(scaledImage); } -void HyperionGrabber::_setImgSize(int width, int height) +void HyperionGrabber::_inActivity() { - _hclient_p->setImgSize(width, height); -} \ No newline at end of file + // Inactivity handling for WLED might involve sending a black frame or turning off LEDs + // For now, we'll just log it. Further implementation needed if desired. + qDebug() << "Inactivity detected. Consider sending black frame to WLED."; +} diff --git a/hyperiongrabber.h b/hyperiongrabber.h index d21bef1..bd322c5 100644 --- a/hyperiongrabber.h +++ b/hyperiongrabber.h @@ -1,9 +1,12 @@ +#ifndef HYPERIONGRABBER_H +#define HYPERIONGRABBER_H + #include #include #include #include -#include "hyperionclient.h" +#include "wledclient.h" // Use WledClient instead of HyperionClient #include "WaylandGrabber.h" class HyperionGrabber : public QObject @@ -14,20 +17,17 @@ public: ~HyperionGrabber(); private: - HyperionClient *_hclient_p; + WledClient *_wledClient_p; // Changed from HyperionClient QTimer *_timer_p; WaylandGrabber *_waylandGrabber_p; int _inactiveTime_m = 0; - QString _hyperionPriority_m; unsigned short _scale_m = 8; unsigned short _frameskip_m = 0; long long _frameCounter_m = 0; - QString _parseColorArr(QString, bool); - private slots: - void _inActivity(); void _processFrame(const QVideoFrame &frame); - void _setImgSize(int width, int height); -}; \ No newline at end of file +}; + +#endif // HYPERIONGRABBER_H diff --git a/wledclient.cpp b/wledclient.cpp new file mode 100644 index 0000000..ce67376 --- /dev/null +++ b/wledclient.cpp @@ -0,0 +1,76 @@ +#include "wledclient.h" +#include +#include + +// DDP Port for WLED +const ushort WLED_DDP_PORT = 4048; +// DNRGB Protocol Type +const quint8 DDP_PROTOCOL_DNRGB = 4; +// Max UDP payload size (approx 508 bytes for safe transmission) +// 4 bytes for DDP header, so 504 bytes for LED data +const int MAX_LED_DATA_PER_PACKET = 504; // 504 bytes / 3 bytes per LED = 168 LEDs + +WledClient::WledClient(QString host, ushort port, QObject *parent) : + QObject(parent), + _wledHost(host), + _wledPort(port) +{ + _udpSocket = new QUdpSocket(this); + qDebug() << "WledClient initialized for host:" << _wledHost.toString() << "port:" << _wledPort; +} + +WledClient::~WledClient() +{ + _udpSocket->close(); + qDebug() << "WledClient destroyed."; +} + +void WledClient::sendImage(const QImage &image) +{ + if (image.isNull()) { + qWarning() << "WledClient: Cannot send null image."; + return; + } + + // Ensure image is in RGB888 format for direct byte access + QImage rgbImage = image.convertToFormat(QImage::Format_RGB888); + + int totalLeds = rgbImage.width() * rgbImage.height(); + int ledsPerPacket = MAX_LED_DATA_PER_PACKET / 3; // 3 bytes per LED (RGB) + + for (int i = 0; i < totalLeds; i += ledsPerPacket) { + QByteArray datagram; + datagram.reserve(4 + (ledsPerPacket * 3)); // Header + max LED data + + // Byte 0: Protocol type (DNRGB) + datagram.append(DDP_PROTOCOL_DNRGB); + // Byte 1: Timeout (1 second) + datagram.append(1); + + // Bytes 2 & 3: Starting LED index (low byte, then high byte) + quint16 startIndex = i; + datagram.append(startIndex & 0xFF); // Low byte + datagram.append((startIndex >> 8) & 0xFF); // High byte + + int currentLedsInPacket = qMin(ledsPerPacket, totalLeds - i); + + for (int j = 0; j < currentLedsInPacket; ++j) { + int pixelIndex = i + j; + int x = pixelIndex % rgbImage.width(); + int y = pixelIndex / rgbImage.width(); + + QRgb pixel = rgbImage.pixel(x, y); + datagram.append(qRed(pixel)); + datagram.append(qGreen(pixel)); + datagram.append(qBlue(pixel)); + } + + qint64 bytesSent = _udpSocket->writeDatagram(datagram, _wledHost, _wledPort); + if (bytesSent == -1) { + qWarning() << "WledClient: Failed to send datagram:" << _udpSocket->errorString(); + emit error(_udpSocket->errorString()); + } else if (bytesSent != datagram.size()) { + qWarning() << "WledClient: Sent fewer bytes than expected. Expected:" << datagram.size() << "Sent:" << bytesSent; + } + } +} \ No newline at end of file diff --git a/wledclient.h b/wledclient.h new file mode 100644 index 0000000..b86c64d --- /dev/null +++ b/wledclient.h @@ -0,0 +1,28 @@ +#ifndef WLEDCLIENT_H +#define WLEDCLIENT_H + +#include +#include +#include +#include + +class WledClient : public QObject +{ + Q_OBJECT + +public: + explicit WledClient(QString host, ushort port, QObject *parent = nullptr); + ~WledClient(); + + void sendImage(const QImage &image); + +private: + QUdpSocket *_udpSocket; + QHostAddress _wledHost; + ushort _wledPort; + +signals: + void error(QString message); +}; + +#endif // WLEDCLIENT_H