diff --git a/Hyperion_Grabber_Wayland_QT b/Hyperion_Grabber_Wayland_QT index 2ab80d5..6af3468 100755 Binary files a/Hyperion_Grabber_Wayland_QT and b/Hyperion_Grabber_Wayland_QT differ diff --git a/lessons_learned.md b/lessons_learned.md index 2cbab1d..e8ef28e 100644 --- a/lessons_learned.md +++ b/lessons_learned.md @@ -308,4 +308,30 @@ To improve the diagnostic script, we can: * **Deprecated Functions:** The code was using the deprecated Qt functions `qrand()` and `qsrand()`. These were replaced with the standard C++ functions `rand()` and `srand()` from ``. * **Linker Errors:** The `wled_config_tool` was not being linked with `wledclient.cpp`, resulting in "undefined reference" errors. This was resolved by adding `wledclient.cpp` to the `add_executable` section for `wled_config_tool` in `CMakeLists.txt`. -**Outcome:** The `wled_config_tool` is now fully functional with the re-implemented demo and flash modes, and the build system is more robust. \ No newline at end of file + **Outcome:** The `wled_config_tool` is now fully functional with the re-implemented demo and flash modes, and the build system is more robust. + +## Further Refinements to WLED Config Tool: Stability, Flickering, and Visualization + +**Goal:** Address recurring segmentation faults, eliminate LED flickering, and provide a visual ASCII representation of the configured LED layout. + +**Problems Encountered & Solutions:** + +1. **Persistent Segmentation Faults:** + * **Cause:** The interactive layout configuration, initially implemented with a state machine and asynchronous input, suffered from critical memory management issues. Specifically, `QObject` instances (like `WledConfigClient`, `QSocketNotifier`, `WledClient`, `QVector`, `QTimer`) were being created on the heap but were not properly parented to a `QObject` (like `QCoreApplication app`), leading to premature destruction or dangling pointers when accessed from asynchronous callbacks (lambdas). Additionally, `QTextStream` objects were being captured by value in lambdas, making them `const` and preventing non-`const` operations. + * **Resolution:** + * All dynamically allocated `QObject` instances within the interactive configuration were explicitly parented to the `QCoreApplication app` object (`new MyObject(&app)`). This ensures their lifetime is managed by the Qt event loop and they are automatically deleted when the application exits. + * The interactive input logic was simplified. Instead of a complex state machine with asynchronous input for each number, the tool now prompts for all LED counts and the offset upfront using blocking `cin >>` calls. This simplifies the control flow and eliminates the need for complex asynchronous state management during input, making the code more robust. + * `QTextStream` objects (`cin` and `cout`) are now created locally within the `QSocketNotifier::activated` lambda, ensuring they are always valid when used. + +2. **LED Flickering in Interactive Mode:** + * **Cause:** The previous implementation used a `QTimer` to send a full LED data packet every second to keep the WLED device in real-time mode. This constant refreshing, even with identical data, caused a noticeable flicker on the LEDs. + * **Resolution:** + * Research into the WLED UDP Realtime protocol confirmed that the second byte of the DNRGB packet specifies a timeout in seconds. + * The `WledClient::setLedsColor` method was modified to accept a `timeout` parameter. + * In the interactive layout configuration, the LED data is now sent **only once** with a significantly longer timeout (e.g., 60 seconds). This eliminates the need for periodic refresh packets and thus resolves the flickering. The `QTimer` previously used for refreshing was removed. + +3. **ASCII Layout Visualization:** + * **Goal:** Provide a clear, terminal-based visual representation of the configured LED layout. + * **Implementation:** A new function `printAsciiLayout` was added. This function takes the LED counts for each side, the offset, and the total number of LEDs. It generates a rectangular ASCII art representation of the layout using ANSI escape codes to display colored squares (Red for Bottom, Green for Right, Blue for Top, Yellow for Left). This visualization is printed to the console after the LEDs are lit up on the physical device. + +**Outcome:** The `wled_config_tool` is now significantly more stable, the LED flickering issue is resolved, and users can visually confirm their configured layout directly in the terminal. \ No newline at end of file diff --git a/wled_config_tool b/wled_config_tool index 8028b78..ea2886b 100755 Binary files a/wled_config_tool and b/wled_config_tool differ diff --git a/wled_config_tool.cpp b/wled_config_tool.cpp index 560edfe..75c2198 100644 --- a/wled_config_tool.cpp +++ b/wled_config_tool.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "wledconfigclient.h" #include "wledclient.h" @@ -14,6 +15,7 @@ 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[]) @@ -67,57 +69,89 @@ int main(int argc, char *argv[]) quint16 wledPort = parser.value(portOption).toUShort(); if (parser.isSet(configureLayoutOption)) { - // Interactive LED layout configuration - QTextStream cin(stdin); - QTextStream cout(stdout); + auto configClient = new WledConfigClient(wledAddress, &app); - cout << "Starting interactive LED layout configuration.\n"; - cout.flush(); - cout << "Please enter the number of LEDs for each section.\n"; - cout.flush(); + QObject::connect(configClient, &WledConfigClient::infoReceived, [&](const QJsonObject &info) { + int totalLeds = info["leds"].toObject()["count"].toInt(); + qDebug() << "Your WLED device reports" << totalLeds << "LEDs."; - int ledsToFirstCorner = 0; - cout << "LEDs until the first corner is reached: "; - cout.flush(); - cin >> ledsToFirstCorner; + QTextStream cout(stdout); + QTextStream cin(stdin); - int ledsRightSide = 0; - cout << "LEDs on the right side: "; - cout.flush(); - cin >> ledsRightSide; + cout << "Starting interactive LED layout configuration.\n"; + cout << "Please enter the number of LEDs for each section.\n"; + + int ledsBottom, ledsRight, ledsTop, ledsLeft, offset; - int ledsTop = 0; - cout << "LEDs on the top: "; - cout.flush(); - cin >> ledsTop; + cout << "LEDs on the bottom: "; + cout.flush(); + cin >> ledsBottom; - int ledsLeftSide = 0; - cout << "LEDs on the left side: "; - cout.flush(); - cin >> ledsLeftSide; + cout << "LEDs on the right side: "; + cout.flush(); + cin >> ledsRight; - int ledsBottomRemaining = 0; - cout << "LEDs at the bottom again until the last one is reached: "; - cout.flush(); - cin >> ledsBottomRemaining; + cout << "LEDs on the top: "; + cout.flush(); + cin >> ledsTop; - cout << "\n--- LED Layout Summary ---\n"; - cout.flush(); - cout << "LEDs to first corner: " << ledsToFirstCorner << "\n"; - cout.flush(); - cout << "LEDs on right side: " << ledsRightSide << "\n"; - cout.flush(); - cout << "LEDs on top: " << ledsTop << "\n"; - cout.flush(); - cout << "LEDs on left side: " << ledsLeftSide << "\n"; - cout.flush(); - cout << "LEDs bottom remaining: " << ledsBottomRemaining << "\n"; - cout.flush(); + 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 totalLeds = ledsToFirstCorner + ledsRightSide + ledsTop + ledsLeftSide + ledsBottomRemaining; - cout << "Total LEDs configured: " << totalLeds << "\n"; + int configuredLeds = ledsBottom + ledsRight + ledsTop + ledsLeft; + cout << "Total LEDs configured: " << configuredLeds << "\n"; + cout.flush(); - return 0; // Exit after configuration + 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(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(); } @@ -166,6 +200,59 @@ int main(int argc, char *argv[]) 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; diff --git a/wledclient.cpp b/wledclient.cpp index 807d496..65d1089 100644 --- a/wledclient.cpp +++ b/wledclient.cpp @@ -77,7 +77,7 @@ void WledClient::sendImage(const QImage &image) } } -void WledClient::setLedsColor(const QVector &colors) +void WledClient::setLedsColor(const QVector &colors, int timeout) { if (colors.isEmpty()) { return; @@ -92,8 +92,8 @@ void WledClient::setLedsColor(const QVector &colors) // Byte 0: Protocol type (DNRGB) datagram.append(DDP_PROTOCOL_DNRGB); - // Byte 1: Timeout (1 second) - datagram.append(1); + // Byte 1: Timeout (in seconds) + datagram.append(timeout); // Bytes 2 & 3: Starting LED index (low byte, then high byte) quint16 startIndex = i; diff --git a/wledclient.h b/wledclient.h index dd0404b..c29b2fa 100644 --- a/wledclient.h +++ b/wledclient.h @@ -15,7 +15,7 @@ public: ~WledClient(); void sendImage(const QImage &image); - void setLedsColor(const QVector &colors); + void setLedsColor(const QVector &colors, int timeout = 1); void flashLeds(int startIndex, int count, QColor color); private: