Skip to content

Usermod for ESP32-C3 Super Mini OLED 72x40, rebased to v0.16.x#5475

Open
doccaz wants to merge 7 commits intowled:mainfrom
doccaz:oled-72x40-v016
Open

Usermod for ESP32-C3 Super Mini OLED 72x40, rebased to v0.16.x#5475
doccaz wants to merge 7 commits intowled:mainfrom
doccaz:oled-72x40-v016

Conversation

@doccaz
Copy link
Copy Markdown

@doccaz doccaz commented Apr 4, 2026

Port of the OLED 72x40 usermod from PR #5335 (originally targeting 0_15_x) to the v0.16.x main branch. Addresses all review feedback from softhack007:

  • Use WLED global I2C pins instead of hardcoded SDA/SCL
  • Remove WiFi.setTxPower() workaround (use -DLOLIN_WIFI_FIX instead)
  • Replace blocking delay(5000) splash with non-blocking state machine
  • Fix strip.isUpdating() guard to prevent display stalling
  • Remove direct btnPin[]/buttonType[] modification; use configurable buttonIndex with handleButton() instead
  • Fix bootButtonPin config persistence
  • Fix brightness graph off-by-one rendering bug
  • Use getJsonValue() 3-arg form to prevent config defaults drift
  • Migrate to v0.16.x usermod format (REGISTER_USERMOD, custom_usermods, appendConfigData with oappend)
  • Assign USERMOD_ID 59 (next available on main)

Summary by CodeRabbit

  • New Features

    • New WLED usermod for 72x40 SSD1306 OLED: boot splash (logo + name/IP) then non-blocking dashboard
    • Dashboard shows effect name, speed/intensity/brightness stats and a scrolling brightness history; rate-limited redraws and inactivity blanking
    • Optional heartbeat LED: blink when offline, smooth pulse when online; wake on button or state change
    • Display flip/offset remapping to keep UI centered when rotated
  • Documentation

    • Added README with usage and build instructions
  • Chores

    • Added packaging/manifest for dependencies

Port of the OLED 72x40 usermod from PR wled#5335 (originally targeting
0_15_x) to the v0.16.x main branch. Addresses all review feedback
from softhack007:

- Use WLED global I2C pins instead of hardcoded SDA/SCL
- Remove WiFi.setTxPower() workaround (use -DLOLIN_WIFI_FIX instead)
- Replace blocking delay(5000) splash with non-blocking state machine
- Fix strip.isUpdating() guard to prevent display stalling
- Remove direct btnPin[]/buttonType[] modification; use configurable
  buttonIndex with handleButton() instead
- Fix bootButtonPin config persistence
- Fix brightness graph off-by-one rendering bug
- Use getJsonValue() 3-arg form to prevent config defaults drift
- Migrate to v0.16.x usermod format (REGISTER_USERMOD, custom_usermods,
  appendConfigData with oappend)
- Assign USERMOD_ID 59 (next available on main)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Adds a new WLED usermod "OLED_72x40" that drives a U8g2 SSD1306 I2C display (72x40 UI in 128x64 buffer), implements a non-blocking splash→dashboard state machine, inactivity power-save, optional heartbeat GPIO, persistent config, registration, and a library manifest plus README.

Changes

Cohort / File(s) Summary
OLED_72x40 Usermod
usermods/OLED_72x40/OLED_72x40.cpp
New UsermodOLED72x40 class: allocates U8g2 SSD1306 (uses global I2C pins), splash (Akemi bitmap + IP/mDNS) → dashboard state machine, ~100ms redraw rate, effect name extraction, scrolling brightness history, stats line from speed/intensity/brightness, inactivity power-save with wake on button/state, optional heartbeat ledPin (digital blink when disconnected, analog sinusoid when connected), dynamic GPIO allocation/release, persistent config, REGISTER_USERMOD and global instance.
Documentation & Build
usermods/OLED_72x40/README.md, usermods/OLED_72x40/library.json
Added README describing hardware assumptions, usage, build flags and wiring; added library.json declaring U8g2 and Wire dependencies and build hints.
Usermod ID
wled00/const.h
Added macro USERMOD_ID_OLED_72x40 59 to assign an ID for the new usermod.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • softhack007
  • DedeHai
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: introduction of a new OLED 72x40 usermod for ESP32-C3 Super Mini, rebased to v0.16.x. It is concise, specific, and directly reflects the primary purpose of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 135-150: The LED heartbeat in loop() currently manipulates ledPin
before validating the usermod state; update loop() so it first returns if
!enabled || !initDone || !u8g2, or alternatively include those same guards in
the LED heartbeat condition, so digitalWrite()/analogWrite() are only called
when the usermod is enabled and initialized; locate the heartbeat code
referencing ledPin and ledTimer inside loop() and add the enabled/initDone/u8g2
checks (or move the heartbeat block below the existing if (!enabled || !initDone
|| !u8g2) return;) to prevent driving a GPIO when initialization failed.
- Around line 90-112: The code is incorrectly using the persisted usermod
setting enabled as a runtime error flag and leaking allocated resources on init
failure; change the init paths in the OLED_72x40 usermod so you do NOT modify
the persisted enabled flag on I2C or display begin() failures (leave enabled
untouched), introduce or use a separate runtime flag such as initialized or
available to reflect whether the display is usable, and on any early failure
after allocating ledPin call PinManager::releasePin(ledPin) (and reset ledPin =
-1) so the heartbeat pin is not permanently reserved; also ensure u8g2 is
deleted and nulled on failure while keeping enabled for config persistence and
use addToConfig() to serialize only the persisted enabled flag.

In `@usermods/OLED_72x40/README.md`:
- Around line 82-91: The example custom_usermods list is not minimal and
incorrectly enables the unrelated audioreactive usermod; update the example so
custom_usermods only includes OLED_72x40 (remove audioreactive) so readers won't
pull in unintended dependencies — edit the README example line containing
custom_usermods and ensure it reads only "custom_usermods = OLED_72x40"
(referencing the custom_usermods, audioreactive, and OLED_72x40 symbols).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1538640b-a040-4371-8f2e-036dd7c41d53

📥 Commits

Reviewing files that changed from the base of the PR and between aaf5192 and c6ff365.

⛔ Files ignored due to path filters (1)
  • usermods/OLED_72x40/esp32-c3-super-mini.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • usermods/OLED_72x40/OLED_72x40.cpp
  • usermods/OLED_72x40/README.md
  • usermods/OLED_72x40/library.json
  • wled00/const.h

@netmindz netmindz requested a review from softhack007 April 4, 2026 11:07
@netmindz netmindz added the usermod usermod related label Apr 4, 2026
- Add setupFailed flag to avoid persisting init failures in the
  enabled config setting (which would silently disable the usermod
  on next config save)
- Deallocate heartbeat LED pin on u8g2->begin() failure to prevent
  pin leak
- Move loop() early-return guard above heartbeat code so GPIO is
  not driven when usermod failed to initialize
- Remove unrelated audioreactive from README custom_usermods example
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 97-103: The code currently mutates the persisted configuration
variable ledPin when allocation/init fails; instead introduce a separate runtime
variable (e.g., allocatedLedPin or runtimeLedPin) and use that for calls to
PinManager::allocatePin(ledPin,...), pinMode(allocatedLedPin,...), and any pin
release logic; do not change the persisted ledPin value so addToConfig()
continues to save the original user setting. Update all places that currently
set ledPin = -1 on allocation/failure or set pinMode/release to operate on the
new runtime variable (keep ledPin read-only for config), and ensure
addToConfig() and any config serialization reference the original ledPin, not
the runtime variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 61e8c3cf-c87a-4590-b3b9-ee783dbdfbb7

📥 Commits

Reviewing files that changed from the base of the PR and between c6ff365 and 0a54ce5.

📒 Files selected for processing (2)
  • usermods/OLED_72x40/OLED_72x40.cpp
  • usermods/OLED_72x40/README.md

Separate ledPin (persisted config value) from activeLedPin (runtime
allocation state). Previously, ledPin was set to -1 on allocation
failure, which caused addToConfig() to persist -1 and erase the
user's configured heartbeat pin.
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
usermods/OLED_72x40/OLED_72x40.cpp (1)

71-78: Hoist the repeated defaults into named constants.

60000/60, 28, 24, 0, and 8 are duplicated between the member initializers and the getJsonValue() fallbacks. A future tweak to only one side will reintroduce config drift.

Based on learnings: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR.

Also applies to: 247-257

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/OLED_72x40/OLED_72x40.cpp` around lines 71 - 78, Several numeric
literals (60000, 28, 24, 0, 8 and the 72 array size) are duplicated between the
member initializers (screenTimeoutMS, xOff, yOff, buttonIndex, ledPin, vValues)
and the getJsonValue() fallback values; hoist these into named constants (e.g.,
DEFAULT_SCREEN_TIMEOUT_MS, DEFAULT_SCREEN_TIMEOUT_S if needed, DEFAULT_X_OFFSET,
DEFAULT_Y_OFFSET, DEFAULT_BUTTON_INDEX, DEFAULT_LED_PIN, DEFAULT_VVALUES_SIZE)
and use those constants both when initializing the members and as the fallback
arguments inside getJsonValue(); update any other repeated literals in the same
file (including the section referenced around getJsonValue usages) to use the
new constants so defaults remain single-source-of-truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 106-114: When releasing the heartbeat LED GPIO (activeLedPin)
ensure the pin is no longer electrically driven before calling
PinManager::deallocatePin; specifically, before the deallocation path in the
u8g2->begin() failure block and the other release path around lines ~258-264,
first stop driving the pin (e.g., drive it to a safe state and set it to input)
and then call PinManager::deallocatePin(activeLedPin, PinOwner::UM_Unspecified),
set activeLedPin = -1, delete u8g2 and set setupFailed = true as currently done;
update both places that currently only call PinManager::deallocatePin to perform
the “stop driving” step first to avoid leaving the GPIO actively driven.

---

Nitpick comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 71-78: Several numeric literals (60000, 28, 24, 0, 8 and the 72
array size) are duplicated between the member initializers (screenTimeoutMS,
xOff, yOff, buttonIndex, ledPin, vValues) and the getJsonValue() fallback
values; hoist these into named constants (e.g., DEFAULT_SCREEN_TIMEOUT_MS,
DEFAULT_SCREEN_TIMEOUT_S if needed, DEFAULT_X_OFFSET, DEFAULT_Y_OFFSET,
DEFAULT_BUTTON_INDEX, DEFAULT_LED_PIN, DEFAULT_VVALUES_SIZE) and use those
constants both when initializing the members and as the fallback arguments
inside getJsonValue(); update any other repeated literals in the same file
(including the section referenced around getJsonValue usages) to use the new
constants so defaults remain single-source-of-truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 65f2ecfd-544e-419f-a828-2266044ca3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 0a54ce5 and d206276.

📒 Files selected for processing (1)
  • usermods/OLED_72x40/OLED_72x40.cpp

Add pinMode(activeLedPin, INPUT) before PinManager::deallocatePin()
in both failure/reconfig paths so the pin is not left electrically
driven after release.
Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 133-160: When transitioning from the splash to the dashboard in
loop(), reset the inactivity timer so the dashboard isn't immediately turned
off: when you set displayState = DASHBOARD after checking millis() -
splashStartTime >= SPLASH_DURATION, also assign lastInteraction = millis() (or
equivalent) to update the inactivity timestamp; touch the block that uses
splashStartTime, displayState, SPLASH, SPLASH_DURATION, DASHBOARD and
lastInteraction to implement this change.
- Around line 244-269: readFromConfig updates the enabled flag but doesn’t bring
the live hardware into sync; update readFromConfig to handle enabled
transitions: detect oldEnabled vs enabled, and if enabled changed from
false->true call the same initialization path used by setup() (or extract the
setup body into a reusable method e.g., initHardware/initDisplay that sets
initDone, allocates activeLedPin via PinManager and calls pinMode, and
initializes the OLED), and if true->false call a teardown method (e.g.,
deinitHardware/deinitDisplay) that clears initDone, resets/inputs activeLedPin
and deallocates it via PinManager, and ensures display is powered down; reuse
symbols enabled, initDone, setup(), activeLedPin, ledPin, and PinManager to
implement this so enabling after boot or setupFailed initializes correctly and
disabling immediately cleans up hardware before any further reconfiguration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 40c4230f-702a-4ecb-8d8b-7e8957bc727c

📥 Commits

Reviewing files that changed from the base of the PR and between d206276 and 28b1e3e.

📒 Files selected for processing (1)
  • usermods/OLED_72x40/OLED_72x40.cpp

doccaz and others added 2 commits April 4, 2026 13:05
- Reset lastInteraction when splash ends so the dashboard isn't
  immediately blanked if sleepTimeout < 5 seconds
- Handle enabled->disabled transition in readFromConfig: power-save
  the display, release and tristate the heartbeat LED pin
- Guard LED pin reallocation with enabled check so pins aren't
  allocated when usermod is disabled
Saving config triggers a state change event which was waking the
display immediately after the teardown disabled it. Add the same
enabled/setupFailed/initDone guards to onStateChange() and
handleButton() so they are no-ops when the usermod is disabled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 192-194: The buffer lineBuffer used with
extractModeName(effectCurrent, JSON_mode_names, lineBuffer, 16) must be
initialized before the call to avoid drawStr reading uninitialized data when
extractModeName returns 0; initialize lineBuffer (e.g., set lineBuffer[0] = '\0'
or zero-fill the 17-byte array) immediately after its declaration (char
lineBuffer[17];) and before calling extractModeName, then call
u8g2->drawStr(currentX, currentY + 7, lineBuffer) as before.
- Around line 215-221: The onStateChange(uint8_t mode) callback (and the other
wake callbacks around lines 224-230) can re-power the display even after
readFromConfig() disabled the usermod; add an early guard that returns
immediately when the usermod is disabled (e.g., if (!enabled) return; or if
(!isEnabled()) return;) before touching u8g2, displayState, lastInteraction, or
lastUpdate so callbacks remain inert while the usermod is disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: efb3be77-4499-48c7-88a7-f877f6eafbbb

📥 Commits

Reviewing files that changed from the base of the PR and between 28b1e3e and 0ed2598.

📒 Files selected for processing (1)
  • usermods/OLED_72x40/OLED_72x40.cpp

Zero-initialize lineBuffer so drawStr() doesn't read garbage if
extractModeName() returns 0 for an out-of-range mode index.
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
usermods/OLED_72x40/OLED_72x40.cpp (1)

123-127: Consider extracting the panel/layout constants.

128, 64, 72, 40, and the fixed y-baselines are repeated in several coordinate calculations. Giving them static constexpr names would make the flip/layout math easier to audit.

Based on learnings, replacing meaningful magic numbers with named constants improves maintainability and helps avoid layout regressions.

Also applies to: 187-209

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/OLED_72x40/OLED_72x40.cpp` around lines 123 - 127, Extract the
repeated layout numbers into named constants and use them in the flip math:
define static constexpr ints like SCREEN_WIDTH (128), SCREEN_HEIGHT (64),
PANEL_WIDTH (72), PANEL_HEIGHT (40), LOGO_WIDTH (16) and optional
X_BASELINE/Y_BASELINE to replace the raw literals used when computing splashY
and textX; update calculations that reference flipDisplay, xOff, yOff, splashY
and textX (and the bitmap draw call u8g2->drawBitmap and subsequent layout code
around lines 187-209) to use those constants so the flip/layout math is clearer
and consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 264-286: The code only attempts to allocate activeLedPin when
oldLedPin != ledPin, so if the usermod was disabled (which clears activeLedPin)
but the configured ledPin didn't change you never retry allocation; change the
logic to also attempt allocation whenever enabled && initDone && activeLedPin <
0. Concretely, keep the existing branch that handles pin changes (if oldLedPin
!= ledPin) but add (or replace with) a separate branch that runs when enabled &&
initDone && activeLedPin < 0 && ledPin >= 0 and calls
PinManager::allocatePin(ledPin, true, PinOwner::UM_Unspecified), sets
activeLedPin and pinMode(activeLedPin, OUTPUT) on success; ensure you still
deallocate any previous activeLedPin before allocating (using pinMode(...,
INPUT) and PinManager::deallocatePin) as in the current code paths.

---

Nitpick comments:
In `@usermods/OLED_72x40/OLED_72x40.cpp`:
- Around line 123-127: Extract the repeated layout numbers into named constants
and use them in the flip math: define static constexpr ints like SCREEN_WIDTH
(128), SCREEN_HEIGHT (64), PANEL_WIDTH (72), PANEL_HEIGHT (40), LOGO_WIDTH (16)
and optional X_BASELINE/Y_BASELINE to replace the raw literals used when
computing splashY and textX; update calculations that reference flipDisplay,
xOff, yOff, splashY and textX (and the bitmap draw call u8g2->drawBitmap and
subsequent layout code around lines 187-209) to use those constants so the
flip/layout math is clearer and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 63276b47-3a0d-4afb-9a23-3726d8b22d4d

📥 Commits

Reviewing files that changed from the base of the PR and between 28b1e3e and 2c867a0.

📒 Files selected for processing (1)
  • usermods/OLED_72x40/OLED_72x40.cpp

Comment on lines +264 to +286
// Handle enabled -> disabled: tear down hardware
if (initDone && oldEnabled && !enabled) {
if (activeLedPin >= 0) {
pinMode(activeLedPin, INPUT);
PinManager::deallocatePin(activeLedPin, PinOwner::UM_Unspecified);
activeLedPin = -1;
}
if (u8g2) u8g2->setPowerSave(1);
displayState = OFF;
}

// Reallocate LED pin on config change (only when enabled)
if (enabled && initDone && oldLedPin != ledPin) {
if (activeLedPin >= 0) {
pinMode(activeLedPin, INPUT);
PinManager::deallocatePin(activeLedPin, PinOwner::UM_Unspecified);
}
activeLedPin = -1;
if (ledPin >= 0 && PinManager::allocatePin(ledPin, true, PinOwner::UM_Unspecified)) {
activeLedPin = ledPin;
pinMode(activeLedPin, OUTPUT);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Retry activeLedPin allocation when the configured GPIO didn't change.

The disable path clears activeLedPin, but the reallocation path only runs when oldLedPin != ledPin. If the original allocation failed, or the user toggles the usermod off and back on with the same configured pin, the heartbeat stays off until reboot or another pin edit.

🔧 Minimal fix
-      if (enabled && initDone && oldLedPin != ledPin) {
+      if (enabled && initDone && (oldLedPin != ledPin || (ledPin >= 0 && activeLedPin < 0))) {
         if (activeLedPin >= 0) {
           pinMode(activeLedPin, INPUT);
           PinManager::deallocatePin(activeLedPin, PinOwner::UM_Unspecified);
         }
         activeLedPin = -1;
         if (ledPin >= 0 && PinManager::allocatePin(ledPin, true, PinOwner::UM_Unspecified)) {
           activeLedPin = ledPin;
           pinMode(activeLedPin, OUTPUT);
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/OLED_72x40/OLED_72x40.cpp` around lines 264 - 286, The code only
attempts to allocate activeLedPin when oldLedPin != ledPin, so if the usermod
was disabled (which clears activeLedPin) but the configured ledPin didn't change
you never retry allocation; change the logic to also attempt allocation whenever
enabled && initDone && activeLedPin < 0. Concretely, keep the existing branch
that handles pin changes (if oldLedPin != ledPin) but add (or replace with) a
separate branch that runs when enabled && initDone && activeLedPin < 0 && ledPin
>= 0 and calls PinManager::allocatePin(ledPin, true, PinOwner::UM_Unspecified),
sets activeLedPin and pinMode(activeLedPin, OUTPUT) on success; ensure you still
deallocate any previous activeLedPin before allocating (using pinMode(...,
INPUT) and PinManager::deallocatePin) as in the current code paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants