- 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.
26 KiB
Image Transformation Algorithm (HyperionGrabber)
The HyperionGrabber is responsible for acquiring video frames, transforming them, and sending them to the Hyperion server (via HyperionClient). The core image transformation logic is found in the _processFrame method of hyperiongrabber.cpp.
Algorithm:
Function _processFrame(input_frame: QVideoFrame):
// 1. Validate the input frame
If NOT input_frame.isValid():
Return
// 2. Increment frame counter and apply frame skipping if configured
_frameCounter_m = _frameCounter_m + 1
If _frameskip_m > 0 AND (_frameCounter_m MOD (_frameskip_m + 1) != 0):
Return
// 3. Convert QVideoFrame to QImage
image = input_frame.toImage()
If image.isNull():
Log "Failed to convert QVideoFrame to QImage."
Return
// 4. Calculate target size for scaling based on _scale_m (default 8)
target_width = image.width() / _scale_m
target_height = image.height() / _scale_m
target_size = QSize(target_width, target_height)
If NOT target_size.isValid():
Log "Invalid target size for scaling."
Return
// 5. Scale the image using Qt's optimized scaling with smooth transformation
scaled_image = image.scaled(target_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)
// 6. Convert image format to RGB888 if necessary (24-bit RGB)
If scaled_image.format() IS NOT QImage::Format_RGB888:
scaled_image = scaled_image.convertToFormat(QImage::Format_RGB888)
// 7. Send the processed image dimensions and data to the Hyperion client
_hclient_p.setImgSize(scaled_image.width(), scaled_image.height())
_hclient_p.sendImage(scaled_image.constBits(), scaled_image.sizeInBytes())
End Function
New Strategy: Direct WLED Communication
Goal: Modify the Hyperion_Grabber_X11_QT application to send image data directly to a WLED device, bypassing the Hyperion.ng server.
Current Understanding:
- The
HyperionGrabbersuccessfully captures and processes screen images (scaling, format conversion). - The
HyperionClientcurrently sends these processed images to a Hyperion.ng server using JSON-RPC over TCP.
Proposed Plan:
-
Research WLED API for Image Data:
- WLED supports several UDP protocols for live image data, including DDP (Distributed Display Protocol) on UDP port 4048 and WLED UDP Realtime (WARLS) on UDP port 21324.
- DDP appears to be a good fit for sending raw RGB pixel data efficiently.
-
Develop a WLED Client (
WledClient):- Created a new C++ class (
WledClient) that usesQUdpSocketto send data. - Implemented the DNRGB (WLED UDP Realtime) protocol for sending raw RGB pixel data.
- The client is configured with the WLED device's IP address and DDP port (4048).
- Created a new C++ class (
-
Integrate WLED Client into HyperionGrabber:
- Modified
HyperionGrabberto useWledClientinstead ofHyperionClient. - Updated the
_processFramemethod to callWledClient'ssendImagemethod.
- Modified
-
Remove HyperionClient Dependency:
- Removed the
HyperionClientclass and its related code.
- Removed the
Step-by-Step: Screen to LEDs (Direct WLED Communication)
-
Screen Capture (HyperionGrabber):
- The
WaylandGrabber(or an X11 grabber in the X11 version) continuously captures frames from your screen. - Each captured frame is received by the
HyperionGrabber's_processFramemethod as aQVideoFrame.
- The
-
Image Processing (HyperionGrabber):
- The
QVideoFrameis converted into aQImage. - Scaling: The
QImageis scaled down. The_scale_mparameter (default 8) determines the scaling factor. For example, if your screen is 1920x1080 and_scale_mis 8, the image will be scaled to 240x135. This is done usingQt::SmoothTransformationfor quality. - Format Conversion: The scaled image is converted to
QImage::Format_RGB888(24-bit RGB), which is a standard format for LED data. - Frame Skipping (Optional): If configured, some frames might be skipped to reduce the processing load.
- The
-
Direct WLED Data Transmission (WledClient):
- The
HyperionGrabberpasses the processed (scaled and formatted)QImagedirectly to theWledClient'ssendImagemethod. - The
WledClientthen:- Iterates through the pixels of the
QImage. - Constructs DNRGB packets (WLED UDP Realtime protocol):
- Each packet starts with a 4-byte header:
- Byte 0: Protocol type (
4for DNRGB). - Byte 1: Timeout value (e.g., 1 second, after which WLED returns to its normal mode if no more packets are received).
- Byte 2 & 3: 16-bit starting LED index (low byte, then high byte). This allows sending parts of the image in multiple packets if the image is too large for a single UDP datagram.
- Byte 0: Protocol type (
- The RGB data for each pixel (3 bytes per pixel: Red, Green, Blue) is appended to the header.
- Each packet starts with a 4-byte header:
- Sends these UDP datagrams to the configured WLED device's IP address and port (default 4048).
- Iterates through the pixels of the
- The
-
WLED Device Processing:
- The WLED device receives the DNRGB UDP packets.
- It interprets the pixel data and updates the connected LEDs accordingly.
- Any internal WLED effects or configurations (like color correction, gamma, or specific LED mapping) will be applied by the WLED firmware itself.
Parameters for Testing with Your LED Setup:
The Hyperion_Grabber_X11_QT application now directly communicates with WLED.
Parameters you need to consider for the Hyperion_Grabber_X11_QT application:
--addressor-a: The IP address or hostname of your WLED device. (e.g.,192.168.1.177as set in the code, but you should use your WLED's actual IP).--portor-p: The UDP port of your WLED device for the UDP Realtime protocol. (Default:4048for DDP, or21324for WARLS, but we are using DDP/DNRGB which is typically 4048).--scaleor-s: The scaling factor for the captured image. This is crucial for performance vs. accuracy.- Recommendation: Start with the default
8. If the performance is good and you want more detail, you can try4or2. If performance is an issue, you might even go higher (e.g.,16).
- Recommendation: Start with the default
--frameskipor-f: Number of frames to skip between processing. (Default:0- no skipping). You can increase this if you experience performance issues.--inactiveor-i: Time in seconds after which the grabber logs an inactivity message. (Default:0- no inactivity timer). Note: This currently only logs a message; it does not send a black frame or clear LEDs to WLED. This functionality would need to be added toWledClientif desired.
Example Command to run the grabber:
./Hyperion_Grabber_X11_QT -a <YOUR_WLED_IP_ADDRESS> -p 4048 -s 8 -f 0
Replace <YOUR_WLED_IP_ADDRESS> with the actual IP address of your WLED device.
For your LED configuration (30 at the bottom, 20 at the right, 70 at the top, 20 at the left, and another 41 at the bottom):
This configuration is still primarily handled within the WLED device itself. You will need to configure the LED segments and their order within the WLED web interface to match your physical LED layout.
- Total LEDs: 30 + 20 + 70 + 20 + 41 = 181 LEDs.
- Ensure your WLED device is configured for this total number of LEDs and that the segments are correctly defined to match your physical setup.
- Enable "UDP Realtime" in WLED settings and confirm it's listening on port 4048 (or the port you specify with
-p).
Summary of what you need to do for testing:
- Configure WLED Device:
- Ensure your WLED device has the correct total number of LEDs configured.
- Set up LED segments in WLED to match your physical layout (bottom, right, top, left).
- Enable "UDP Realtime" in WLED settings and confirm it's listening on the correct port (default 4048).
- Build the Grabber: You will need to compile the modified
Hyperion_Grabber_X11_QTproject. - Run the Grabber: Execute the compiled application with the appropriate WLED IP address and port.
Color Order Troubleshooting and Resolution
Problem: Initial tests showed only red LEDs, despite sending a sequence of red, green, blue, white, and black.
Diagnosis:
- WLED's
/json/cfgendpoint showedhw.led.ins[0].order: 0, which typically means RGB color order. - Our
WledClientwas initially sending RGB, but then a test was performed with a GRB swap based on user input. This caused a mismatch.
Resolution:
- Reverted the R and G channel swap in
wledclient.cppto ensure data is sent in RGB order. - Confirmed that WLED is listening on UDP port
21324for Realtime data (from/json/cfgif.sync.port0). - After rebuilding and re-running
wled_test, the LEDs successfully displayed the full color sequence (Red, Green, Blue, White, Black) as expected.
Conclusion: The issue was a mismatch between the sent color order (RGB) and the WLED's expected color order (RGB, confirmed by configuration). The wled_test program now correctly demonstrates full color control.
Current Problem: Flickering with Static Screen
Observation: When running Hyperion_Grabber_Wayland_QT, some LEDs change color even when the screen is visually static, leading to flickering.
Diagnosis:
- Analysis of
output.logrevealed that thescaledImage(the input toWledClient) is not perfectly static, even when the screen appears still. TheWaylandGrabberor underlying system introduces subtle pixel variations, causing the image checksum to constantly change. - Our initial image difference threshold (
_changeThreshold_m = 100) was too low, allowing these minor, visually imperceptible changes to trigger new frames to be sent to WLED.
Proposed Solution:
- Increase the
_changeThreshold_msignificantly to filter out the noise from the screen capture. This will prevent sending new frames to WLED unless there's a visually significant change on the screen.
Ambilight TV Concept
Ambilight TV, primarily a Philips technology, enhances the viewing experience by projecting dynamic, colored light onto the wall behind the television. This is achieved using intelligent LED lights positioned around the edges of the screen.
Key Principles:
- Real-time Color Matching: The core idea is to analyze the colors displayed on the TV screen in real-time and project corresponding hues onto the surrounding wall. For example, if the left side of the screen shows a bright blue sky, the LEDs on that side will emit a matching blue light.
- Extended Immersion: This creates the illusion that the picture extends beyond the physical boundaries of the TV, making the screen appear larger and drawing the viewer deeper into the content.
- Reduced Eye Strain: By softening the contrast between the bright TV screen and a dark room, Ambilight can help reduce eye strain and fatigue.
- Dynamic and Adaptive: The lights are not static; they change dynamically with the on-screen content, reacting to color shifts, movement, and even audio (in some modes).
How it Works (Generalizing for our project):
- Screen Analysis: The system continuously captures and analyzes the video frame displayed on the screen.
- Edge/Region Sampling: Instead of analyzing every single pixel, the system typically samples colors from specific regions or "zones" along the edges of the screen. These zones correspond to the physical placement of the LEDs.
- Color Averaging/Dominant Color Extraction: For each sampled zone, a dominant color or an average color is calculated.
- LED Control: This calculated color is then sent to the corresponding LED(s) or LED segment(s) on the back of the TV, which illuminate the wall with that color.
Relevance to Hyperion_Grabber_X11_QT
Our Hyperion_Grabber_X11_QT project aims to replicate this Ambilight functionality by:
- Screen Capture: The
WaylandGrabber(or X11 grabber) continuously captures frames from your screen. - Image Processing (
HyperionGrabber): The capturedQVideoFrameis converted to aQImageand then scaled down (scaledImage). ThisscaledImagerepresents a lower-resolution version of the entire screen. - Color Extraction/Mapping (Implicit in
WledClient): This is where the crucial step of mapping thescaledImageto the individual LEDs happens. TheWledClientiterates through the pixels of thescaledImageand sends them as RGB data to the WLED device.
The Problem with Left-Side LEDs:
Given the Ambilight concept, if the LEDs on the left side are not showing colors that match the left side of the screen, it strongly suggests an issue in one of these areas:
- Incorrect Sampling/Mapping in
WledClient: TheWledClientmight be iterating through thescaledImagepixels in an order that doesn't correspond to the physical layout of the LEDs. For example, it might be reading pixels from the top-left to bottom-right of thescaledImageand sending them sequentially, but the physical LEDs might be arranged starting from the bottom-left, then up the left side, across the top, and down the right. - WLED Configuration Mismatch: Even if the
WledClientsends the data correctly, the WLED device itself needs to be configured to interpret that incoming stream of RGB data correctly for its physical LED layout. If WLED expects a different order (e.g., starting from the top-left LED and going clockwise), but our client sends data starting from the bottom-left, there will be a mismatch. - Scaling/Aspect Ratio Issues: While less likely to cause a complete side mismatch, incorrect scaling or aspect ratio handling could subtly distort the sampled colors.
WLED API Information
Based on research of WLED API documentation (JSON and UDP Realtime):
-
WLED JSON API (HTTP -
/json/info):- Provides detailed, read-only information about the WLED device via a GET request to
http://<WLED_IP_ADDRESS>/json/info. - Key information available:
leds.count: Total number of configured LEDs.info.udpport: The UDP port WLED is listening on for Realtime data (default 21324, but can be changed).
- Provides detailed, read-only information about the WLED device via a GET request to
-
WLED UDP Realtime API (WARLS/DNRGB):
- Confirms that DNRGB (protocol type 4) is the correct protocol for sending LED data.
- The default UDP port for Realtime data is 21324, not 4048 (which is DDP). This is a critical finding and a potential mismatch with our current code, which uses 4048 by default.
Implications for Diagnostic Script:
To improve the diagnostic script, we can:
- Fetch
json/info: Useweb_fetchto get thejson/infofrom the WLED device. - Extract
leds.countandinfo.udpport: Parse the JSON response to get the total LED count and the configured UDP port. - Use extracted values: Use these values in our diagnostic flashing commands, potentially overriding user-provided
-por providing a defaultcountfor--flash.
Confirmed WLED Configuration
- IP Address:
192.168.178.69 - Total LEDs:
181 - UDP Port for Realtime Data:
21324
New Strategy: Dedicated WLED Configuration & LED Testing Tool
Goal: Create a standalone tool that can reliably connect to a WLED device, read its configuration, save it, and provide interactive LED testing (flashing in steps/batches).
Phase 1: WLED Configuration Retrieval
- Step 1: Implement WLED Configuration Client (C++):
- Create a new C++ class (e.g.,
WledConfigClient) that focuses solely on interacting with the WLED JSON API over HTTP. - This client will be responsible for making GET requests to endpoints like
/json/infoand parsing the JSON response. - It will handle potential network errors and provide clear feedback.
- Create a new C++ class (e.g.,
- Step 2: Create a Command-Line Tool for Configuration Retrieval:
- Develop a new executable (e.g.,
wled_config_tool) that usesWledConfigClient. - This tool will take the WLED IP address as a command-line argument.
- It will attempt to retrieve the WLED configuration (total LEDs, UDP port, segment info if available) and print it to
stdout. - It will also have an option to save this configuration to a file (e.g.,
wled_config.json).
- Develop a new executable (e.g.,
Phase 2: Interactive LED Testing
- Step 3: Integrate LED Testing into
wled_config_tool:- Extend
wled_config_toolto include functionality for flashing LEDs. - It will reuse the
WledClient(or a modified version of it) to send DNRGB packets. - The tool will take arguments for
startIndex,count, andcolor, similar to our previous--flashattempt. - It will use the retrieved WLED configuration (total LEDs, UDP port) as defaults or for validation.
- Extend
- Step 4: Interactive Flashing and Mapping:
- You will run
wled_config_toolwith flashing commands. - You will report which physical LEDs illuminate, and we will use this to build a precise mapping of LED indices to physical locations.
- This mapping will be documented in
lessons_learned.md.
- You will run
LED Demo Mode Implementation
Goal: Implement a demo mode in wled_config_tool to sequentially light up LEDs with random colors, turn them off, and repeat.
Challenges & Solutions:
-
Blocking Delays (
QThread::msleep()):- Problem: Initial attempts used
QThread::msleep()for delays, which blocked the Qt event loop, preventing UDP packets from being sent and processed correctly by the WLED device. This resulted in only a brief, single flash of all LEDs. - Solution: Implemented a non-blocking
delay()helper function usingQEventLoopandQTimer. This allows the Qt event loop to continue processing events (like network communication) during delays.
- Problem: Initial attempts used
-
Sequential Lighting (Single LED Update):
- Problem: Initially, the demo mode only lit up one LED at a time because
WledClient::setLedsColorwas called for individual LEDs, and the WLED device would time out and turn off previous LEDs before the next one was lit. - Solution: Modified
WledClient::setLedsColorto accept aQVector<QColor>representing the entire LED strip's state. InrunDemoMode, aQVector<QColor>(currentColors) is maintained, and in each iteration, the color of the current LED is added to this vector. Then,client.setLedsColor(currentColors)is called, sending a single DDP packet with the state of all currently lit LEDs. This ensures that previously lit LEDs remain on.
- Problem: Initially, the demo mode only lit up one LED at a time because
Implementation Details:
- Added
--demo(-d) command-line option towled_config_tool. - Implemented
runDemoModehelper function to encapsulate the demo logic. - Used
std::srandandstd::randfor random color generation. - The demo mode lights up LEDs sequentially with random colors, then turns them all off, and repeats indefinitely until
Ctrl+Cis pressed.
Phase 3: Integrate into HyperionGrabber
- Step 5: Refine
HyperionGrabber's LED Mapping: Once the LED mapping is definitively established and documented, we will integrate this knowledge intoHyperionGrabberto correctly sample screen regions and send data to WLED.
The Case of the Vanishing Code: A Cautionary Tale
Problem: During development, the implementation of the --demo and interactive flashing functionality in wled_config_tool was lost. The features were well-documented in this file, but the corresponding code disappeared from all existing branches.
Diagnosis: Extensive searching of the git history using various commands (git log -S, git log -p, etc.) failed to locate the commit that removed the code. The code was not present on the master branch or the wled-color-fix feature branch, where it was expected to be. The most likely cause is that the branch containing the feature was either deleted or reset, or the commit was reverted in a non-obvious way.
Resolution: The functionality had to be re-implemented from scratch, using this lessons_learned.md file as the specification.
Lessons Re-Learned:
- Documentation is not a substitute for code: While detailed documentation is invaluable, it's useless if the code it describes doesn't exist.
- Git hygiene is critical: Ensure that feature branches are not deleted prematurely and that commits are not lost. Use
git pushto back up branches to a remote repository. - When searching for lost code:
git log -Sis a powerful tool, but it can be misled by documentation files. Use pathspec exclusions (:(exclude)filename) to narrow the search.- If searching fails, a direct inspection of the files on relevant branches (
git checkout <branch> && cat <file>) is the most reliable way to determine what code is actually present.
Re-implementing the WLED Config Tool Features
Goal: Re-implement the --demo and --flash functionality in wled_config_tool.
Process:
-
Extended
WledClient: Added a new methodsetLedsColor(const QVector<QColor> &colors)toWledClient. This method sends the state of the entire LED strip in a single UDP packet, which is crucial for avoiding flickering and state loss issues, as learned from the previous implementation. -
Re-implemented
wled_config_toolfeatures:- Added the
--demoand--flashcommand-line options. - Implemented the
runDemoModefunction to sequentially light up LEDs with random colors, using a non-blocking delay. - Implemented the
runFlashModefunction to light up a user-defined range of LEDs with a specific color.
- Added the
-
Build System Fixes: The build process revealed several issues that needed to be addressed:
- Missing Qt Module: The
wled_config_tooltarget was missing a dependency on theQt6::Guimodule, which was required forQImageandQColor. This was fixed by addingQt6::Guito thetarget_link_librariesinCMakeLists.txt. - Deprecated Functions: The code was using the deprecated Qt functions
qrand()andqsrand(). These were replaced with the standard C++ functionsrand()andsrand()from<cstdlib>. - Linker Errors: The
wled_config_toolwas not being linked withwledclient.cpp, resulting in "undefined reference" errors. This was resolved by addingwledclient.cppto theadd_executablesection forwled_config_toolinCMakeLists.txt.
Outcome: The
wled_config_toolis now fully functional with the re-implemented demo and flash modes, and the build system is more robust. - Missing Qt Module: The
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:
-
Persistent Segmentation Faults:
- Cause: The interactive layout configuration, initially implemented with a state machine and asynchronous input, suffered from critical memory management issues. Specifically,
QObjectinstances (likeWledConfigClient,QSocketNotifier,WledClient,QVector,QTimer) were being created on the heap but were not properly parented to aQObject(likeQCoreApplication app), leading to premature destruction or dangling pointers when accessed from asynchronous callbacks (lambdas). Additionally,QTextStreamobjects were being captured by value in lambdas, making themconstand preventing non-constoperations. - Resolution:
- All dynamically allocated
QObjectinstances within the interactive configuration were explicitly parented to theQCoreApplication appobject (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. QTextStreamobjects (cinandcout) are now created locally within theQSocketNotifier::activatedlambda, ensuring they are always valid when used.
- All dynamically allocated
- Cause: The interactive layout configuration, initially implemented with a state machine and asynchronous input, suffered from critical memory management issues. Specifically,
-
LED Flickering in Interactive Mode:
- Cause: The previous implementation used a
QTimerto 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::setLedsColormethod was modified to accept atimeoutparameter. - 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
QTimerpreviously used for refreshing was removed.
- Cause: The previous implementation used a
-
ASCII Layout Visualization:
- Goal: Provide a clear, terminal-based visual representation of the configured LED layout.
- Implementation: A new function
printAsciiLayoutwas 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.