KDEAmbi/main.cpp

206 lines
8.8 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() ? 71 : 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() ? 72 : 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() ? QString("default") : QString(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() ? QString("linear") : QString(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;
processorConfig["colorAlgorithm"] = qgetenv("HYPERION_GRABBER_COLOR_ALGORITHM").isEmpty() ? QString("mean_sqrt") : QString(qgetenv("HYPERION_GRABBER_COLOR_ALGORITHM"));
processorConfig["offset"] = qgetenv("HYPERION_GRABBER_OFFSET").isEmpty() ? 0 : qgetenv("HYPERION_GRABBER_OFFSET").toInt();
QString wledColorOrder = qgetenv("HYPERION_GRABBER_WLED_COLOR_ORDER").isEmpty() ? QString("GRB") : QString(qgetenv("HYPERION_GRABBER_WLED_COLOR_ORDER"));
// Instantiate components
grabber = new HyperionGrabber(grabberOpts);
processor = new HyperionProcessor(layout, processorConfig);
wledClient = new WledClient(wledAddress, wledPort, wledColorOrder);
// 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();
}