Feat: Integrate WledClient and update HyperionGrabber for direct WLED communication

This commit is contained in:
Tobias J. Endres 2025-08-15 21:45:28 +02:00
parent edf041a09a
commit c45cba611d
4 changed files with 130 additions and 72 deletions

View File

@ -9,14 +9,15 @@
HyperionGrabber::HyperionGrabber(QHash<QString, QString> 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<QString, QString> i(opts);
while (i.hasNext()) {
@ -25,44 +26,26 @@ HyperionGrabber::HyperionGrabber(QHash<QString, QString> 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);
}
// 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.";
}

View File

@ -1,9 +1,12 @@
#ifndef HYPERIONGRABBER_H
#define HYPERIONGRABBER_H
#include <QObject>
#include <QTimer>
#include <QImage>
#include <QVideoFrame>
#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);
};
};
#endif // HYPERIONGRABBER_H

76
wledclient.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "wledclient.h"
#include <QDebug>
#include <QColor>
// 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;
}
}
}

28
wledclient.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef WLEDCLIENT_H
#define WLEDCLIENT_H
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QImage>
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