202 lines
8.3 KiB
C++
202 lines
8.3 KiB
C++
#include <QCommandLineParser>
|
|
#include <signal.h>
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QDateTime>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QCoreApplication>
|
|
#include <QTimer>
|
|
#include <QApplication> // Added for QApplication
|
|
|
|
#include "hyperiongrabber.h"
|
|
#include "HyperionProcessor.h"
|
|
#include "wledclient.h"
|
|
#include "LedColorMapping.h"
|
|
#include "LinearColorSmoothing.h"
|
|
|
|
// Global pointers for cleanup
|
|
static HyperionGrabber *grabber = nullptr;
|
|
static QApplication *qapp = nullptr;
|
|
static HyperionProcessor *processor = nullptr;
|
|
static WledClient *wledClient = nullptr;
|
|
static QTimer *processTimer = nullptr;
|
|
static QImage currentImage; // Store the latest captured image
|
|
|
|
// Custom message handler for logging to file
|
|
static QFile *logFile = nullptr;
|
|
void customMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
|
{
|
|
QByteArray localMsg = msg.toLocal8Bit();
|
|
QString logMessage = QString("%1 %2 %3 %4 %5")
|
|
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
|
|
.arg(context.category)
|
|
.arg(context.function)
|
|
.arg(context.line)
|
|
.arg(localMsg.constData());
|
|
|
|
switch (type) {
|
|
case QtDebugMsg:
|
|
logMessage = "Debug: " + logMessage;
|
|
break;
|
|
case QtInfoMsg:
|
|
logMessage = "Info: " + logMessage;
|
|
break;
|
|
case QtWarningMsg:
|
|
logMessage = "Warning: " + logMessage;
|
|
break;
|
|
case QtCriticalMsg:
|
|
logMessage = "Critical: " + logMessage;
|
|
break;
|
|
case QtFatalMsg:
|
|
logMessage = "Fatal: " + logMessage;
|
|
break;
|
|
}
|
|
|
|
if (logFile && logFile->isOpen()) {
|
|
QTextStream stream(logFile);
|
|
stream << logMessage << Qt::endl;
|
|
stream.flush();
|
|
}
|
|
|
|
if (type == QtCriticalMsg || type == QtFatalMsg || !logFile || !logFile->isOpen()) {
|
|
fprintf(stderr, "%s\n", logMessage.toLocal8Bit().constData());
|
|
}
|
|
}
|
|
|
|
// Signal handler for graceful shutdown
|
|
static void quit(int)
|
|
{
|
|
if (processTimer) {
|
|
processTimer->stop();
|
|
delete processTimer;
|
|
processTimer = nullptr;
|
|
}
|
|
if (grabber != nullptr) {
|
|
delete grabber;
|
|
grabber = nullptr;
|
|
}
|
|
if (processor != nullptr) {
|
|
delete processor;
|
|
processor = nullptr;
|
|
}
|
|
if (wledClient != nullptr) {
|
|
delete wledClient;
|
|
wledClient = nullptr;
|
|
}
|
|
if (logFile) {
|
|
logFile->close();
|
|
delete logFile;
|
|
logFile = nullptr;
|
|
}
|
|
if (qapp != nullptr) {
|
|
qapp->exit();
|
|
}
|
|
}
|
|
|
|
// Slot to receive and store the latest image from HyperionGrabber
|
|
void onImageReady(const QImage &image)
|
|
{
|
|
currentImage = image;
|
|
}
|
|
|
|
// Slot to process the current image and send to WLED (triggered by timer)
|
|
void processCurrentFrame()
|
|
{
|
|
if (currentImage.isNull()) {
|
|
qDebug() << "No image available yet for processing.";
|
|
return;
|
|
}
|
|
|
|
// Process the image with HyperionProcessor
|
|
QVector<QColor> ledColors = processor->process(currentImage);
|
|
|
|
// Send colors to WLED
|
|
wledClient->setLedsColor(ledColors);
|
|
|
|
qDebug() << "Processed frame and sent to WLED.";
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// Initialize logging to file
|
|
logFile = new QFile("output.log");
|
|
if (!logFile->open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {
|
|
fprintf(stderr, "Warning: Could not open log file output.log for writing.\n");
|
|
delete logFile;
|
|
logFile = nullptr;
|
|
}
|
|
qInstallMessageHandler(customMessageOutput);
|
|
|
|
qapp = new QApplication(argc, argv);
|
|
signal(SIGINT, quit);
|
|
signal(SIGTERM, quit);
|
|
|
|
QApplication::setApplicationName("HyperionGrabber");
|
|
QApplication::setApplicationVersion("0.2");
|
|
|
|
// Read configuration from environment variables
|
|
QString wledAddress = qgetenv("HYPERION_GRABBER_WLED_ADDRESS");
|
|
if (wledAddress.isEmpty()) {
|
|
qCritical() << "Error: HYPERION_GRABBER_WLED_ADDRESS environment variable not set.";
|
|
return 1;
|
|
}
|
|
|
|
quint16 wledPort = qgetenv("HYPERION_GRABBER_WLED_PORT").toUShort();
|
|
if (wledPort == 0) wledPort = 21324; // Default WLED UDP Realtime port
|
|
|
|
QHash<QString, QString> grabberOpts;
|
|
grabberOpts.insert("scale", qgetenv("HYPERION_GRABBER_SCALE").isEmpty() ? "8" : qgetenv("HYPERION_GRABBER_SCALE"));
|
|
grabberOpts.insert("frameskip", qgetenv("HYPERION_GRABBER_FRAMESKIP").isEmpty() ? "0" : qgetenv("HYPERION_GRABBER_FRAMESKIP"));
|
|
grabberOpts.insert("changeThreshold", qgetenv("HYPERION_GRABBER_CHANGE_THRESHOLD").isEmpty() ? "100" : qgetenv("HYPERION_GRABBER_CHANGE_THRESHOLD"));
|
|
|
|
LedLayout layout;
|
|
layout.bottom = qgetenv("HYPERION_GRABBER_LEDS_BOTTOM").isEmpty() ? 70 : qgetenv("HYPERION_GRABBER_LEDS_BOTTOM").toInt();
|
|
layout.right = qgetenv("HYPERION_GRABBER_LEDS_RIGHT").isEmpty() ? 20 : qgetenv("HYPERION_GRABBER_LEDS_RIGHT").toInt();
|
|
layout.top = qgetenv("HYPERION_GRABBER_LEDS_TOP").isEmpty() ? 70 : qgetenv("HYPERION_GRABBER_LEDS_TOP").toInt();
|
|
layout.left = qgetenv("HYPERION_GRABBER_LEDS_LEFT").isEmpty() ? 20 : qgetenv("HYPERION_GRABBER_LEDS_LEFT").toInt();
|
|
|
|
// Black Border Detector Configuration
|
|
QJsonObject blackBorderDetectorConfig;
|
|
blackBorderDetectorConfig["enable"] = qgetenv("HYPERION_GRABBER_BB_ENABLE").isEmpty() ? true : (qgetenv("HYPERION_GRABBER_BB_ENABLE").toLower() == "true");
|
|
blackBorderDetectorConfig["threshold"] = qgetenv("HYPERION_GRABBER_BB_THRESHOLD").isEmpty() ? 5 : qgetenv("HYPERION_GRABBER_BB_THRESHOLD").toInt();
|
|
blackBorderDetectorConfig["unknownFrameCnt"] = qgetenv("HYPERION_GRABBER_BB_UNKNOWN_FRAME_CNT").isEmpty() ? 600 : qgetenv("HYPERION_GRABBER_BB_UNKNOWN_FRAME_CNT").toInt();
|
|
blackBorderDetectorConfig["borderFrameCnt"] = qgetenv("HYPERION_GRABBER_BB_BORDER_FRAME_CNT").isEmpty() ? 50 : qgetenv("HYPERION_GRABBER_BB_BORDER_FRAME_CNT").toInt();
|
|
blackBorderDetectorConfig["maxInconsistentCnt"] = qgetenv("HYPERION_GRABBER_BB_MAX_INCONSISTENT_CNT").isEmpty() ? 10 : qgetenv("HYPERION_GRABBER_BB_MAX_INCONSISTENT_CNT").toInt();
|
|
blackBorderDetectorConfig["blurRemoveCnt"] = qgetenv("HYPERION_GRABBER_BB_BLUR_REMOVE_CNT").isEmpty() ? 1 : qgetenv("HYPERION_GRABBER_BB_BLUR_REMOVE_CNT").toInt();
|
|
blackBorderDetectorConfig["mode"] = qgetenv("HYPERION_GRABBER_BB_MODE").isEmpty() ? "default" : qgetenv("HYPERION_GRABBER_BB_MODE");
|
|
|
|
// Smoothing Configuration
|
|
QJsonObject smoothingConfig;
|
|
smoothingConfig["enable"] = qgetenv("HYPERION_GRABBER_SMOOTH_ENABLE").isEmpty() ? true : (qgetenv("HYPERION_GRABBER_SMOOTH_ENABLE").toLower() == "true");
|
|
smoothingConfig["type"] = qgetenv("HYPERION_GRABBER_SMOOTH_TYPE").isEmpty() ? "linear" : qgetenv("HYPERION_GRABBER_SMOOTH_TYPE");
|
|
smoothingConfig["time_ms"] = qgetenv("HYPERION_GRABBER_SMOOTH_TIME_MS").isEmpty() ? 150 : qgetenv("HYPERION_GRABBER_SMOOTH_TIME_MS").toInt();
|
|
smoothingConfig["updateFrequency"] = qgetenv("HYPERION_GRABBER_SMOOTH_UPDATE_FREQUENCY").isEmpty() ? 25.0 : qgetenv("HYPERION_GRABBER_SMOOTH_UPDATE_FREQUENCY").toDouble();
|
|
smoothingConfig["interpolationRate"] = qgetenv("HYPERION_GRABBER_SMOOTH_INTERPOLATION_RATE").isEmpty() ? 1.0 : qgetenv("HYPERION_GRABBER_SMOOTH_INTERPOLATION_RATE").toDouble();
|
|
smoothingConfig["decay"] = qgetenv("HYPERION_GRABBER_SMOOTH_DECAY").isEmpty() ? 1.0 : qgetenv("HYPERION_GRABBER_SMOOTH_DECAY").toDouble();
|
|
smoothingConfig["dithering"] = qgetenv("HYPERION_GRABBER_SMOOTH_DITHERING").isEmpty() ? true : (qgetenv("HYPERION_GRABBER_SMOOTH_DITHERING").toLower() == "true");
|
|
smoothingConfig["updateDelay"] = qgetenv("HYPERION_GRABBER_SMOOTH_UPDATE_DELAY").isEmpty() ? 0 : qgetenv("HYPERION_GRABBER_SMOOTH_UPDATE_DELAY").toInt();
|
|
|
|
QJsonObject processorConfig;
|
|
processorConfig["blackborderdetector"] = blackBorderDetectorConfig;
|
|
processorConfig["smoothing"] = smoothingConfig;
|
|
|
|
// Instantiate components
|
|
grabber = new HyperionGrabber(grabberOpts);
|
|
processor = new HyperionProcessor(layout, processorConfig);
|
|
wledClient = new WledClient(wledAddress, wledPort);
|
|
|
|
// Connect grabber to image receiver slot
|
|
QObject::connect(grabber, &HyperionGrabber::imageReady, &onImageReady);
|
|
|
|
// Setup timer for processing frames
|
|
processTimer = new QTimer();
|
|
processTimer->setInterval(1000); // Process every 1 second
|
|
QObject::connect(processTimer, &QTimer::timeout, &processCurrentFrame);
|
|
processTimer->start();
|
|
|
|
qInfo() << "HyperionGrabber started. Sending LED colors to WLED device:" << wledAddress << ":" << wledPort;
|
|
|
|
return qapp->exec();
|
|
}
|