Skip to content

Conversation

@coderabbitai
Copy link
Contributor

@coderabbitai coderabbitai bot commented Jan 17, 2026

Docstrings generation was requested by @srg74.

The following files were modified:

  • usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.cpp
  • usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.h
  • usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.cpp
These file types are not supported
  • .gitignore
  • usermods/usermod_v2_gc9a01_display/library.json
  • usermods/usermod_v2_gc9a01_display/readme.md
ℹ️ Note

CodeRabbit cannot perform edits on its own pull requests yet.

Summary by CodeRabbit

  • New Features
    • Added GC9A01 240x240 TFT display support featuring status indicators (WiFi connectivity, time, power state, current effect name), multiple interactive overlays for brightness, effect, speed, intensity, palette, and network information.
    • Includes 12/24-hour clock display, sleep/wake functionality with backlight control, and seamless integration with rotary encoder controls for intuitive navigation and adjustments.

✏️ Tip: You can customize this high-level summary in your review settings.

Docstrings generation was requested by @srg74.

* #4989 (comment)

The following files were modified:

* `usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.cpp`
* `usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.h`
* `usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.cpp`
@coderabbitai
Copy link
Contributor Author

coderabbitai bot commented Jan 17, 2026

Walkthrough

This PR introduces a new GC9A01 240x240 TFT display usermod with hardware initialization, dynamic UI rendering, overlay management, sleep/wake logic, and clock support. It also integrates GC9A01 support into the rotary encoder UI usermod for coordinated display updates.

Changes

Cohort / File(s) Summary
GC9A01 Display Usermod (New)
usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.h, usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.cpp
Introduces complete GC9A01 display usermod with singleton pattern, TFT hardware integration, display initialization, and comprehensive state management (brightness, backlight, sleep/clock modes). Implements dynamic redraw logic with overlay prioritization, welcome screen timeout handling, and UI rendering for main interface, WiFi icon, clock screen, and multiple contextual overlays. Includes brightness/backlight control, overlay management (overlay/updateRedrawTime/isOverlayActive/getActiveOverlayMode), JSON/config serialization, and standard Usermod lifecycle hooks.
Rotary Encoder UI Integration
usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.cpp
Adds GC9A01 display integration via conditional compilation; introduces gc9a01Display member to reference GC9A01 usermod instance. Extends setup/loop logic and UI action methods (brightness, effect, speed, intensity, palette, hue, saturation, presets, CCT) to coordinate with GC9A01 via wake/overlay/updateRedrawTime calls. Adds public methods: displayNetworkInfo, findCurrentEffectAndPalette, changeState, lampUpdated, and brightness/effect/parameter change handlers. Implements logic to handle overlay expiration and enforce state consistency across both FourLineDisplay and GC9A01 displays.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • PR #4623 — Modifies rotary encoder UI integration for GC9A01 display support with overlay and wake semantics.
  • PR #4658 — Also modifies rotary encoder UI for GC9A01 display with overlay, wake, and redraw interaction adjustments.

Suggested reviewers

  • DedeHai
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title references adding docstrings to the GC9A01 display usermod feature, but the raw summary shows the PR introduces the entire GC9A01 display usermod implementation (1223+ lines of new code), not just docstrings. Revise the title to accurately reflect that the PR introduces a new GC9A01 display usermod with full implementation, e.g., 'Add GC9A01 240x240 TFT display usermod with rotary encoder integration'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 94.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@srg74
Copy link
Contributor

srg74 commented Jan 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor Author

coderabbitai bot commented Jan 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor Author

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.cpp`:
- Around line 663-691: The code calls gc9a01Display methods (wakeDisplay,
redraw) when USERMOD_GC9A01_DISPLAY may be undefined causing a compile error
because gc9a01Display is a void*; wrap the GC9A01-specific branch and any direct
calls to gc9a01Display (the special-case "newState == 0" block and its
wakeDisplay()/redraw() calls) in an `#ifdef` USERMOD_GC9A01_DISPLAY ... `#endif`
guard so those methods are only compiled when the GC9A01 display type is
enabled; keep the surrounding logic for display and the switch/newState handling
unchanged and ensure no other unguarded calls to gc9a01Display remain (search
for gc9a01Display, wakeDisplay, redraw).
🧹 Nitpick comments (4)
usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.h (1)

34-183: Floating docstrings are disconnected from their declarations.

These docstrings (lines 34–183) are placed before the class definition and are not associated with any actual declarations. They appear to document methods that are declared later inside the class (lines 184–274), but there's no syntactic connection between them.

Consider one of the following approaches:

  1. Move these docstrings directly above their corresponding method declarations inside the class
  2. Remove them entirely since the .cpp file already contains comprehensive docstrings for each implementation
  3. Use Doxygen-style grouping if you want to keep them as a reference block

The current placement may confuse documentation generators and doesn't follow C++ conventions.

usermods/usermod_v2_gc9a01_display/usermod_v2_gc9a01_display.cpp (3)

325-357: Consider extracting duplicated arc drawing code.

The arc drawing logic appears twice: once for the brightness ring (lines 325–357) and again for value arcs in overlay modes (lines 435–466). This could be refactored into a helper function to reduce code duplication.

♻️ Proposed helper function
// Add as private method in header:
void drawArc(float startAngle, float arcLength, float progressAngle, uint16_t bgColor, uint16_t fgColor);

// Implementation:
void UsermodGC9A01Display::drawArc(float startAngle, float arcLength, float progressAngle, uint16_t bgColor, uint16_t fgColor) {
  // Draw background arc
  for (float angle = 0; angle < arcLength; angle += 6) {
    float currentAngle = startAngle + angle;
    float rad = radians(currentAngle - 90);
    for (int ringWidth = 0; ringWidth < 3; ringWidth++) {
      int radius = 108 - ringWidth;
      int x = 120 + radius * cos(rad);
      int y = 120 + radius * sin(rad);
      if (x >= 0 && x < 240 && y >= 0 && y < 240) {
        tft.drawPixel(x, y, bgColor);
      }
    }
  }
  // Draw progress arc
  if (progressAngle > 0) {
    for (float angle = 0; angle < progressAngle; angle += 3) {
      float currentAngle = startAngle + angle;
      float rad = radians(currentAngle - 90);
      for (int ringWidth = 0; ringWidth < 3; ringWidth++) {
        int radius = 108 - ringWidth;
        int x = 120 + radius * cos(rad);
        int y = 120 + radius * sin(rad);
        if (x >= 0 && x < 240 && y >= 0 && y < 240) {
          tft.drawPixel(x, y, fgColor);
        }
      }
    }
  }
}

Also applies to: 435-466


684-697: Consider using named constants for logo dimensions.

The magic number 14400 (120 × 120 pixels) is used directly in the loop. While LOGO_SIZE is defined, it could be used more consistently.

♻️ Suggested improvement
   const int LOGO_SIZE = 120;
+  const int LOGO_PIXELS = LOGO_SIZE * LOGO_SIZE; // 14400
   const int OFFSET_X = (240 - LOGO_SIZE) / 2;
   const int OFFSET_Y = (240 - LOGO_SIZE) / 2;
   
   // ...
   
-  for (int i = 0; i < 14400; i++) { // 120 * 120 = 14,400 pixels
+  for (int i = 0; i < LOGO_PIXELS; i++) {

378-388: Remove or clarify the defensive UTF-8 note symbol stripping logic.

The code checks for and removes a 5-byte UTF-8 "note symbol" prefix from effect names, but this format does not exist in the current codebase. The extractModeName() function returns plain ASCII text without any leading space or special character prefix. The same defensive pattern exists in usermod_v2_four_line_display_ALT.cpp (lines 507-510) with identical logic, suggesting this may be legacy code or anticipatory for future formats that never materialized.

Consider either removing this check (since it's unreachable with current data) or documenting why this defensive handling is necessary if it's intended to support future extensibility.

Comment on lines +663 to 691
// Support both Four Line Display and GC9A01 display
if (display != nullptr || gc9a01Display != nullptr) {
// Special handling for state 0 (Brightness) with GC9A01 - no overlay needed
if (newState == 0 && gc9a01Display != nullptr && display == nullptr) {
// For GC9A01 only (no four line display), just update main screen for brightness
if (gc9a01Display->wakeDisplay()) {
gc9a01Display->redraw(true);
changedState = false; // Throw away wake up input
} else {
gc9a01Display->redraw(false); // Update main screen directly
changedState = true;
}
} else {
// Normal overlay handling for all other states and for Four Line Display
switch (newState) {
case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun
case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward
case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire
case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette
case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece
case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush
case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast
case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart
case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Compilation error due to missing preprocessor guard.

The pipeline failure indicates that when USERMOD_GC9A01_DISPLAY is not defined, gc9a01Display is declared as void* (line 188), but this code attempts to call methods on it without the proper #ifdef guard. This causes the compilation error: 'void*' is not a pointer-to-object type.

🐛 Proposed fix: wrap GC9A01-specific code in preprocessor guard
       // Support both Four Line Display and GC9A01 display
-      if (display != nullptr || gc9a01Display != nullptr) {
+      `#ifdef` USERMOD_GC9A01_DISPLAY
+      bool hasGc9a01 = (gc9a01Display != nullptr);
+      `#else`
+      bool hasGc9a01 = false;
+      `#endif`
+      if (display != nullptr || hasGc9a01) {
         // Special handling for state 0 (Brightness) with GC9A01 - no overlay needed
-        if (newState == 0 && gc9a01Display != nullptr && display == nullptr) {
+        `#ifdef` USERMOD_GC9A01_DISPLAY
+        if (newState == 0 && hasGc9a01 && display == nullptr) {
           // For GC9A01 only (no four line display), just update main screen for brightness
           if (gc9a01Display->wakeDisplay()) {
             gc9a01Display->redraw(true);
             changedState = false; // Throw away wake up input
           } else {
             gc9a01Display->redraw(false); // Update main screen directly
             changedState = true;
           }
-        } else {
+        } else
+        `#endif`
+        {
           // Normal overlay handling for all other states and for Four Line Display
           switch (newState) {
             // ... existing switch cases ...
           }
         }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Support both Four Line Display and GC9A01 display
if (display != nullptr || gc9a01Display != nullptr) {
// Special handling for state 0 (Brightness) with GC9A01 - no overlay needed
if (newState == 0 && gc9a01Display != nullptr && display == nullptr) {
// For GC9A01 only (no four line display), just update main screen for brightness
if (gc9a01Display->wakeDisplay()) {
gc9a01Display->redraw(true);
changedState = false; // Throw away wake up input
} else {
gc9a01Display->redraw(false); // Update main screen directly
changedState = true;
}
} else {
// Normal overlay handling for all other states and for Four Line Display
switch (newState) {
case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun
case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward
case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire
case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette
case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece
case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush
case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast
case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart
case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
}
}
// Support both Four Line Display and GC9A01 display
`#ifdef` USERMOD_GC9A01_DISPLAY
bool hasGc9a01 = (gc9a01Display != nullptr);
`#else`
bool hasGc9a01 = false;
`#endif`
if (display != nullptr || hasGc9a01) {
// Special handling for state 0 (Brightness) with GC9A01 - no overlay needed
`#ifdef` USERMOD_GC9A01_DISPLAY
if (newState == 0 && hasGc9a01 && display == nullptr) {
// For GC9A01 only (no four line display), just update main screen for brightness
if (gc9a01Display->wakeDisplay()) {
gc9a01Display->redraw(true);
changedState = false; // Throw away wake up input
} else {
gc9a01Display->redraw(false); // Update main screen directly
changedState = true;
}
} else
`#endif`
{
// Normal overlay handling for all other states and for Four Line Display
switch (newState) {
case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun
case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward
case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire
case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette
case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece
case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush
case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast
case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart
case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
}
}
}
🧰 Tools
🪛 GitHub Actions: WLED CI

[error] 668-672: Compilation error: 'void*' is not a pointer-to-object type. Accessing gc9a01Display->wakeDisplay()/redraw() with a void* type in RotaryEncoder UI ALT module. This suggests a type mismatch where gc9a01Display is not a class instance.

🤖 Prompt for AI Agents
In
`@usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.cpp`
around lines 663 - 691, The code calls gc9a01Display methods (wakeDisplay,
redraw) when USERMOD_GC9A01_DISPLAY may be undefined causing a compile error
because gc9a01Display is a void*; wrap the GC9A01-specific branch and any direct
calls to gc9a01Display (the special-case "newState == 0" block and its
wakeDisplay()/redraw() calls) in an `#ifdef` USERMOD_GC9A01_DISPLAY ... `#endif`
guard so those methods are only compiled when the GC9A01 display type is
enabled; keep the surrounding logic for display and the switch/newState handling
unchanged and ensure no other unguarded calls to gc9a01Display remain (search
for gc9a01Display, wakeDisplay, redraw).

@softhack007 softhack007 added the AI Partly generated by an AI. Make sure that the contributor fully understands the code! label Jan 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Partly generated by an AI. Make sure that the contributor fully understands the code!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants