- Resolved persistent segmentation faults by correcting memory management and simplifying interactive input logic. - Eliminated LED flickering by implementing a longer timeout in DNRGB packets and removing unnecessary refresh timers. - Added an ASCII visualization of the LED layout to the terminal for better user feedback. - Updated lessons_learned.md with detailed explanations of the problems and their solutions.
324 lines
11 KiB
C++
324 lines
11 KiB
C++
#include <QCoreApplication>
|
|
#include <QCommandLineParser>
|
|
#include <QDebug>
|
|
#include <QJsonDocument>
|
|
#include <QFile>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <QTimer>
|
|
#include <QTime>
|
|
#include <QSocketNotifier>
|
|
#include "wledconfigclient.h"
|
|
#include "wledclient.h"
|
|
|
|
// Forward declarations
|
|
void runDemoMode(const QString &address, quint16 port, int totalLeds);
|
|
void runFlashMode(const QString &address, quint16 port, int totalLeds, const QString &flashParams);
|
|
void delay(int milliseconds);
|
|
void printAsciiLayout(int ledsBottom, int ledsRight, int ledsTop, int ledsLeft, int offset, int totalLeds);
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
QCoreApplication::setApplicationName("wled_config_tool");
|
|
QCoreApplication::setApplicationVersion("1.0");
|
|
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("WLED Configuration Tool");
|
|
parser.addHelpOption();
|
|
parser.addVersionOption();
|
|
|
|
QCommandLineOption addressOption(QStringList() << "a" << "address",
|
|
"WLED device IP address or hostname.",
|
|
"address");
|
|
parser.addOption(addressOption);
|
|
|
|
QCommandLineOption portOption(QStringList() << "p" << "port",
|
|
"WLED device UDP port.",
|
|
"port", "21324");
|
|
parser.addOption(portOption);
|
|
|
|
QCommandLineOption saveOption(QStringList() << "s" << "save",
|
|
"Save configuration to a file.",
|
|
"filepath");
|
|
parser.addOption(saveOption);
|
|
|
|
QCommandLineOption configureLayoutOption(QStringList() << "l" << "configure-layout",
|
|
"Start interactive LED layout configuration.");
|
|
parser.addOption(configureLayoutOption);
|
|
|
|
QCommandLineOption demoOption(QStringList() << "d" << "demo",
|
|
"Run a demo sequence on the LEDs.");
|
|
parser.addOption(demoOption);
|
|
|
|
QCommandLineOption flashOption(QStringList() << "f" << "flash",
|
|
"Flash a range of LEDs. Format: startIndex,count,color (e.g., 10,20,#FF0000).",
|
|
"params");
|
|
parser.addOption(flashOption);
|
|
|
|
|
|
parser.process(app);
|
|
|
|
if (!parser.isSet(addressOption)) {
|
|
qCritical() << "Error: WLED address not specified. Use -a or --address.";
|
|
parser.showHelp(1);
|
|
}
|
|
|
|
QString wledAddress = parser.value(addressOption);
|
|
quint16 wledPort = parser.value(portOption).toUShort();
|
|
|
|
if (parser.isSet(configureLayoutOption)) {
|
|
auto configClient = new WledConfigClient(wledAddress, &app);
|
|
|
|
QObject::connect(configClient, &WledConfigClient::infoReceived, [&](const QJsonObject &info) {
|
|
int totalLeds = info["leds"].toObject()["count"].toInt();
|
|
qDebug() << "Your WLED device reports" << totalLeds << "LEDs.";
|
|
|
|
QTextStream cout(stdout);
|
|
QTextStream cin(stdin);
|
|
|
|
cout << "Starting interactive LED layout configuration.\n";
|
|
cout << "Please enter the number of LEDs for each section.\n";
|
|
|
|
int ledsBottom, ledsRight, ledsTop, ledsLeft, offset;
|
|
|
|
cout << "LEDs on the bottom: ";
|
|
cout.flush();
|
|
cin >> ledsBottom;
|
|
|
|
cout << "LEDs on the right side: ";
|
|
cout.flush();
|
|
cin >> ledsRight;
|
|
|
|
cout << "LEDs on the top: ";
|
|
cout.flush();
|
|
cin >> ledsTop;
|
|
|
|
cout << "LEDs on the left side: ";
|
|
cout.flush();
|
|
cin >> ledsLeft;
|
|
|
|
cout << "Enter the starting offset (0 if the strip starts at the first LED): ";
|
|
cout.flush();
|
|
cin >> offset;
|
|
|
|
int configuredLeds = ledsBottom + ledsRight + ledsTop + ledsLeft;
|
|
cout << "Total LEDs configured: " << configuredLeds << "\n";
|
|
cout.flush();
|
|
|
|
if (configuredLeds > totalLeds) {
|
|
qWarning() << "Warning: The number of configured LEDs (" << configuredLeds
|
|
<< ") is greater than the number of LEDs reported by the device (" << totalLeds << ").";
|
|
}
|
|
|
|
auto wledClient = new WledClient(wledAddress, wledPort, &app);
|
|
auto colors = new QVector<QColor>(totalLeds, Qt::black);
|
|
|
|
int currentIndex = offset;
|
|
// Bottom
|
|
for (int i = 0; i < ledsBottom; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::red; }
|
|
currentIndex += ledsBottom;
|
|
// Right
|
|
for (int i = 0; i < ledsRight; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::green; }
|
|
currentIndex += ledsRight;
|
|
// Top
|
|
for (int i = 0; i < ledsTop; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::blue; }
|
|
currentIndex += ledsTop;
|
|
// Left
|
|
for (int i = 0; i < ledsLeft; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::yellow; }
|
|
|
|
wledClient->setLedsColor(*colors, 60); // Set timeout to 60 seconds
|
|
cout << "LEDs have been lit up according to your configuration.\n";
|
|
cout.flush();
|
|
|
|
printAsciiLayout(ledsBottom, ledsRight, ledsTop, ledsLeft, offset, totalLeds);
|
|
|
|
cout << "Press Enter to turn off LEDs and exit.\n";
|
|
cout.flush();
|
|
|
|
auto notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, &app);
|
|
QObject::connect(notifier, &QSocketNotifier::activated, [=, &app]() {
|
|
colors->fill(Qt::black);
|
|
wledClient->setLedsColor(*colors);
|
|
app.quit();
|
|
});
|
|
});
|
|
|
|
QObject::connect(configClient, &WledConfigClient::error, [&](const QString &message) {
|
|
qCritical() << "Error:" << message;
|
|
app.quit();
|
|
});
|
|
|
|
configClient->getInfo();
|
|
return app.exec();
|
|
}
|
|
|
|
|
|
WledConfigClient client(wledAddress);
|
|
|
|
QObject::connect(&client, &WledConfigClient::infoReceived, [&](const QJsonObject &info) {
|
|
if (parser.isSet(demoOption) || parser.isSet(flashOption)) {
|
|
int totalLeds = info["leds"].toObject()["count"].toInt();
|
|
if (parser.isSet(demoOption)) {
|
|
runDemoMode(wledAddress, wledPort, totalLeds);
|
|
app.quit();
|
|
} else if (parser.isSet(flashOption)) {
|
|
runFlashMode(wledAddress, wledPort, totalLeds, parser.value(flashOption));
|
|
app.quit();
|
|
}
|
|
} else {
|
|
QJsonDocument doc(info);
|
|
QFile stdoutFile;
|
|
stdoutFile.open(stdout, QFile::WriteOnly);
|
|
QTextStream ts(&stdoutFile);
|
|
ts << "WLED Configuration Info:\n" << doc.toJson(QJsonDocument::Indented) << Qt::endl;
|
|
stdoutFile.close();
|
|
|
|
QString saveFilePath = parser.value(saveOption);
|
|
if (!saveFilePath.isEmpty()) {
|
|
QFile file(saveFilePath);
|
|
if (file.open(QIODevice::WriteOnly)) {
|
|
file.write(doc.toJson(QJsonDocument::Indented));
|
|
file.close();
|
|
qDebug() << "Configuration saved to" << saveFilePath;
|
|
} else {
|
|
qWarning() << "Failed to save configuration to" << saveFilePath << ":" << file.errorString();
|
|
}
|
|
}
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
QObject::connect(&client, &WledConfigClient::error, [&](const QString &message) {
|
|
qCritical() << "Error:" << message;
|
|
app.quit();
|
|
});
|
|
|
|
client.getInfo();
|
|
|
|
return app.exec();
|
|
}
|
|
|
|
// ANSI Color Codes
|
|
const QString ANSI_RESET = "\033[0m";
|
|
const QString ANSI_RED_BG = "\033[41m";
|
|
const QString ANSI_GREEN_BG = "\033[42m";
|
|
const QString ANSI_BLUE_BG = "\033[44m";
|
|
const QString ANSI_YELLOW_BG = "\033[43m";
|
|
|
|
void printAsciiLayout(int ledsBottom, int ledsRight, int ledsTop, int ledsLeft, int offset, int totalLeds) {
|
|
QTextStream cout(stdout);
|
|
|
|
int maxHorizontal = qMax(ledsTop, ledsBottom);
|
|
int maxVertical = qMax(ledsLeft, ledsRight);
|
|
|
|
// Top Row
|
|
cout << " "; // Indent for left side
|
|
for (int i = 0; i < ledsTop; ++i) {
|
|
cout << ANSI_BLUE_BG << " " << ANSI_RESET; // Two spaces for a square
|
|
}
|
|
cout << "\n";
|
|
|
|
// Middle Rows (Left and Right)
|
|
for (int i = 0; i < maxVertical; ++i) {
|
|
if (i < ledsLeft) {
|
|
cout << ANSI_YELLOW_BG << " " << ANSI_RESET;
|
|
} else {
|
|
cout << " "; // Empty space if no left LED
|
|
}
|
|
// Calculate spaces between left and right
|
|
int horizontalSpace = maxHorizontal * 2; // Each LED is two chars
|
|
cout << QString(horizontalSpace, ' ');
|
|
if (i < ledsRight) {
|
|
cout << ANSI_GREEN_BG << " " << ANSI_RESET;
|
|
}
|
|
cout << "\n";
|
|
}
|
|
|
|
// Bottom Row
|
|
cout << " "; // Indent for left side
|
|
for (int i = 0; i < ledsBottom; ++i) {
|
|
cout << ANSI_RED_BG << " " << ANSI_RESET;
|
|
}
|
|
cout << "\n";
|
|
|
|
cout << "\nLayout details:\n";
|
|
cout << " Bottom LEDs (Red): " << ledsBottom << "\n";
|
|
cout << " Right LEDs (Green): " << ledsRight << "\n";
|
|
cout << " Top LEDs (Blue): " << ledsTop << "\n";
|
|
cout << " Left LEDs (Yellow): " << ledsLeft << "\n";
|
|
cout << " Offset: " << offset << "\n";
|
|
cout << " Total LEDs: " << totalLeds << "\n";
|
|
cout.flush();
|
|
}
|
|
|
|
void runDemoMode(const QString &address, quint16 port, int totalLeds)
|
|
{
|
|
qDebug() << "Running demo mode with" << totalLeds << "LEDs on" << address << ":" << port;
|
|
qDebug() << "Press Ctrl+C to exit.";
|
|
|
|
WledClient client(address, port);
|
|
QVector<QColor> colors(totalLeds, Qt::black);
|
|
|
|
// Seed the random number generator
|
|
QTime time = QTime::currentTime();
|
|
srand(time.msec());
|
|
|
|
while (true) {
|
|
// Turn on LEDs one by one with random colors
|
|
for (int i = 0; i < totalLeds; ++i) {
|
|
colors[i] = QColor::fromRgb(rand() % 256, rand() % 256, rand() % 256);
|
|
client.setLedsColor(colors);
|
|
delay(50);
|
|
}
|
|
|
|
// Turn off all LEDs
|
|
colors.fill(Qt::black);
|
|
client.setLedsColor(colors);
|
|
delay(1000);
|
|
}
|
|
}
|
|
|
|
void runFlashMode(const QString &address, quint16 port, int totalLeds, const QString &flashParams)
|
|
{
|
|
qDebug() << "Running flash mode with params:" << flashParams;
|
|
|
|
QStringList params = flashParams.split(',');
|
|
if (params.size() != 3) {
|
|
qCritical() << "Error: Invalid flash parameters. Expected format: startIndex,count,color";
|
|
return;
|
|
}
|
|
|
|
bool ok1, ok2;
|
|
int startIndex = params[0].toInt(&ok1);
|
|
int count = params[1].toInt(&ok2);
|
|
QColor color(params[2]);
|
|
|
|
if (!ok1 || !ok2 || !color.isValid()) {
|
|
qCritical() << "Error: Invalid flash parameters. Could not parse values.";
|
|
return;
|
|
}
|
|
|
|
if (startIndex < 0 || count <= 0 || startIndex + count > totalLeds) {
|
|
qCritical() << "Error: Invalid range. Start index must be non-negative, count must be positive, and the range must be within the total number of LEDs (" << totalLeds << ").";
|
|
return;
|
|
}
|
|
|
|
WledClient client(address, port);
|
|
QVector<QColor> colors(totalLeds, Qt::black);
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
colors[startIndex + i] = color;
|
|
}
|
|
|
|
client.setLedsColor(colors);
|
|
qDebug() << "Flashed" << count << "LEDs starting from index" << startIndex << "with color" << color.name();
|
|
}
|
|
|
|
void delay(int milliseconds)
|
|
{
|
|
QEventLoop loop;
|
|
QTimer::singleShot(milliseconds, &loop, &QEventLoop::quit);
|
|
loop.exec();
|
|
} |