Skip to content

Use drop-downs for pins#5473

Open
DedeHai wants to merge 13 commits intowled:mainfrom
DedeHai:pin_dropdowns
Open

Use drop-downs for pins#5473
DedeHai wants to merge 13 commits intowled:mainfrom
DedeHai:pin_dropdowns

Conversation

@DedeHai
Copy link
Copy Markdown
Collaborator

@DedeHai DedeHai commented Apr 3, 2026

Adding functions to easily create (restricted) dropdowns for pins and use those in LED settings and UM settings.

In LED settings pins are restricted by pinmanager, info about pin owner is also pulled from firmware. When populating a dropdown, flags can be passed and the dropdown will omit unusable pins i.e. does not show input only pins in LED outputs or non-analog or touch capable pins for analog/touch buttons.

This restriction is not implemented for usermods (yet) - usermods still show pins without restrictions.

Summary by CodeRabbit

  • New Features

    • Rebuilt pin-selection UI with dynamic dropdowns for LEDs, buttons and peripherals; can switch between numeric inputs and selects.
    • Asynchronous pin-info retrieval (with retries) exposing ADC/touch capabilities and human-readable owner/type labels.
    • New DMX pin dropdowns with clearer labels and reboot warning placement.
  • Bug Fixes

    • Stronger validation for non‑virtual LED pins and type-aware conflict detection/clearing.
    • Pin option disabling now reflects actual occupancy.
  • Chores

    • DMX input pins now default to unassigned at startup; DMX input pin handling updated.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 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

Centralized GPIO/pin utilities were added to wled00/data/common.js. Settings pages defer to async pin-info, rebuild pin-select UIs, and use pin metadata. Backend emits ADC-capable GPIO info and DMX input pin defaults changed; a small debug log text was adjusted.

Changes

Cohort / File(s) Summary
Pin utilities
wled00/data/common.js
Add shared helpers: getOwnerName, getBtnTypeName, getCaps, fetchPinInfo (with retry + /json/pins), and UI helpers makePinSelect, unmakePinSelect, addOption. Caches d.pinsData and populates pin occupancy asynchronously.
LED settings UI
wled00/data/settings_leds.htm
Defer pin-init via fetchPinInfo(pinDropdowns); validate LED pins for both numeric inputs and selects; convert inputs↔selects per LED type; refactor pin conflict detection/update to use new utilities; remove old dropdown helpers; add deferred pin-update flush (d.pinUpdPending).
Pin info page
wled00/data/settings_pininfo.htm
Replace dynamic /settings/s.js?p=11 load with fetchPinInfo(...); start loadPins() polling after pin data availability; remove local copies of getOwnerName, getBtnTypeName, getCaps.
User module settings (UM)
wled00/data/settings_um.htm
Replace recursive getPins() logic with lookups into d.pinsData from fetchPinInfo(pinDD); disable used/read-only pins via pInfo; render owner text with getOwnerName(...); guard absent selects.
DMX/Sync settings UI
wled00/data/settings_sync.htm
Call fetchPinInfo(pinDropdowns) after loading settings; add DMX pin dropdown generation via makePinSelect, handlers and conflict-disabling logic for RX/TX/EN; minor warning/label markup adjustments.
Backend: pin metadata emission
wled00/xml.cpp
Emit new d.adc=[...] array listing analog-capable GPIO indices (via PinManager::isAnalogPin).
Backend: settings handler log
wled00/set.cpp
Small debug message change: missing LED bus pin log reports bus index as s instead of s+1.
DMX input defaults
wled00/wled.h
Change DMX input pin globals initialization from 0 to -1 for dmxInputTransmitPin, dmxInputReceivePin, and dmxInputEnablePin.
Pin owner enum comment
wled00/pin_manager.h
Clarify comment for PinOwner::DMX to denote it represents a DMX output (still hard-coded to IO2).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% 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 'Use drop-downs for pins' accurately captures the main change: converting pin selection controls from numeric inputs to dropdown selects across multiple settings pages.

✏️ 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: 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 `@wled00/data/settings_leds.htm`:
- Around line 844-859: pinDropdowns() currently only re-calls makePinSelect()
for IR, RL and button input fields, so LED selects and already-converted button
<select>s never get updated with pInfo.a/pInfo.n loaded from /json/pins; update
pinDropdowns() so after pins are fetched you rebuild or refresh all pin-related
controls by iterating over the LED fields plus all button controls (both inputs
and existing selects under gId("btns") and d.Sf), re-invoking makePinSelect()
for each relevant name (use the same btnPinFlags(t) logic for buttons), reattach
onchange handlers that call UI() and pinUpd(), and finally re-run
d.Sf.querySelectorAll("select.pin").forEach(pinUpd) so pinUpd() receives the new
pInfo for every select; reference functions/ids: pinDropdowns(),
makePinSelect(), pinUpd(), addBtn(), gId("btns"), d.Sf.
- Around line 872-875: The code collecting LED bus pin values is accidentally
including HUB75 dimension fields (L0..L4); update the iteration in
d.Sf.querySelectorAll(".iST input.s[name^=L]") to explicitly exclude HUB75
dimension inputs (e.g. skip when i.name matches /^L[0-4]$/) before pushing
values into pins so only actual GPIO pin entries are considered (retain the
existing checks for i.value and parseInt(i.value) >= 0 and i.max < 255).
🪄 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: 9bb318c0-37e4-4130-8b53-052de0b9326a

📥 Commits

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

📒 Files selected for processing (6)
  • wled00/data/common.js
  • wled00/data/settings_leds.htm
  • wled00/data/settings_pininfo.htm
  • wled00/data/settings_um.htm
  • wled00/set.cpp
  • wled00/xml.cpp

@netmindz
Copy link
Copy Markdown
Member

netmindz commented Apr 4, 2026

can we also include the settings_sync page in this PR?

@DedeHai
Copy link
Copy Markdown
Collaborator Author

DedeHai commented Apr 4, 2026

sure can.

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.

🧹 Nitpick comments (3)
wled00/data/settings_leds.htm (3)

866-870: Minor: Inconsistent type handling for numeric comparison.

Line 868 uses i.value >= 0 which relies on implicit string-to-number coercion, while line 884 explicitly uses parseInt(i.value, 10) >= 0. For consistency and clarity, prefer the explicit approach.

🔧 Suggested fix
 		d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i) => {
-			if (i.value !== "" && i.value >= 0)
+			if (i.value !== "" && parseInt(i.value, 10) >= 0)
 				i.dataset.val = i.value;
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_leds.htm` around lines 866 - 870, The comparison uses
implicit string-to-number coercion for inputs selected by
d.Sf.querySelectorAll(".iST input.s[name^=L]") when setting i.dataset.val;
change the check to parse the value explicitly (e.g., use parseInt(i.value, 10)
or Number(i.value) and ensure it is not NaN) before comparing to >= 0 so it
matches the explicit style used elsewhere and avoids type-coercion bugs when
assigning i.dataset.val.

315-328: Minor indentation inconsistency.

Lines 316-318 appear to have a leading space before tabs. As per coding guidelines, web files should use tabs for indentation.

🔧 Suggested fix
 				// convert pin fields so show/hide applies to the correct element type
-				for (let p=0; p<5; p++) {
-					let nm2 = "L"+p+n;
-					let el = d.Sf[nm2];
+				for (let p=0; p<5; p++) {
+					let nm2 = "L"+p+n;
+					let el = d.Sf[nm2];

(Ensure consistent tab indentation throughout the block)

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

In `@wled00/data/settings_leds.htm` around lines 315 - 328, The indentation in the
for-loop block that converts pin fields is inconsistent (leading spaces before
tabs on the lines handling isVir/isHub75 and the INPUT branch); normalize those
lines to use tabs only to match the surrounding code style—specifically adjust
the lines referencing isVir(t), isHub75(t), unmakePinSelect(nm2) and the INPUT
branch that calls makePinSelect(nm2, 1) and sets d.pinUpdPending so all lines
use tabs for indentation.

441-461: Consider removing dead code in a follow-up.

The commented-out pin conflict coloring block has been replaced by the deferred pinUpd() approach. While keeping it for reference during development is fine, consider removing it entirely once the new approach is validated to reduce code noise.

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

In `@wled00/data/settings_leds.htm` around lines 441 - 461, Remove the obsolete
commented-out pin conflict coloring block and any related unused helper
references left only for that logic; specifically delete the entire /* ... */
block that references LC, gN, nList, d.rsvd, d.um_p, d.Sf, d.ro_gpio and the old
conflict-coloring checks, since pin conflict handling is now implemented by
pinUpd(); ensure no remaining dead-symbol usages of that logic linger (e.g.,
unused local vars or commented helper calls) so the file only contains the
active pinUpd()-based implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@wled00/data/settings_leds.htm`:
- Around line 866-870: The comparison uses implicit string-to-number coercion
for inputs selected by d.Sf.querySelectorAll(".iST input.s[name^=L]") when
setting i.dataset.val; change the check to parse the value explicitly (e.g., use
parseInt(i.value, 10) or Number(i.value) and ensure it is not NaN) before
comparing to >= 0 so it matches the explicit style used elsewhere and avoids
type-coercion bugs when assigning i.dataset.val.
- Around line 315-328: The indentation in the for-loop block that converts pin
fields is inconsistent (leading spaces before tabs on the lines handling
isVir/isHub75 and the INPUT branch); normalize those lines to use tabs only to
match the surrounding code style—specifically adjust the lines referencing
isVir(t), isHub75(t), unmakePinSelect(nm2) and the INPUT branch that calls
makePinSelect(nm2, 1) and sets d.pinUpdPending so all lines use tabs for
indentation.
- Around line 441-461: Remove the obsolete commented-out pin conflict coloring
block and any related unused helper references left only for that logic;
specifically delete the entire /* ... */ block that references LC, gN, nList,
d.rsvd, d.um_p, d.Sf, d.ro_gpio and the old conflict-coloring checks, since pin
conflict handling is now implemented by pinUpd(); ensure no remaining
dead-symbol usages of that logic linger (e.g., unused local vars or commented
helper calls) so the file only contains the active pinUpd()-based
implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d649a646-35ac-4d6c-a9bd-c4ce5e909026

📥 Commits

Reviewing files that changed from the base of the PR and between 7153fd8 and f7d9461.

📒 Files selected for processing (1)
  • wled00/data/settings_leds.htm

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)
wled00/data/settings_sync.htm (1)

51-68: Consider restoring owner text when option becomes disabled again.

Line 65 clears the owner name when an option is enabled (o.text = String(p)), but when the option becomes disabled again (e.g., another DMX dropdown selects it), the owner name is not restored. This is a minor cosmetic issue since the dropdown still functions correctly.

Also, the flags logic is correct: RX pin (i=0) uses flags=0 to allow read-only pins, while TX and Enable pins (i=1,2) use flags=1 to exclude read-only pins.

💡 Optional fix to restore owner text
 function dmxPinUpd() {
 	const vs = dmxNs.map(n => parseInt(gN(n)?.value??-1));
 	dmxNs.forEach((n,i) => {
 		for (const o of gN(n)?.options??[]) {
 			const p = parseInt(o.value); if (p<0) continue;
+			const pInfo = d.pinsData?.find(pi=>pi.p===p);
 			// unlock old selected pin, lock newly selected one
-			o.disabled = (p!==vs[i] && !dmxOwnPins.includes(p) && !!d.pinsData?.find(pi=>pi.p===p)?.a) || vs.some((v,j)=>j!==i&&v===p);
-			if (!o.disabled) o.text = String(p); // clear owner name i.e. (DMX input)
+			const usedByOther = p!==vs[i] && !dmxOwnPins.includes(p) && pInfo?.a;
+			const usedByDmx = vs.some((v,j)=>j!==i&&v===p);
+			o.disabled = usedByOther || usedByDmx;
+			o.text = o.disabled && pInfo?.a ? `${p} (${getOwnerName(pInfo.o, pInfo.t, pInfo.n)})` : String(p);
 		}
 	});
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_sync.htm` around lines 51 - 68, The dropdown option
label isn't restored when an option becomes disabled; update dmxPinUpd() so
inside the loop where you compute o.disabled you also set o.text based on
whether the option is disabled: look up the owner name from d.pinsData (e.g.
const owner = d.pinsData?.find(pi => pi.p===p)?.n) and if o.disabled set o.text
= owner ?? String(p) (otherwise keep the existing enabled behavior o.text =
String(p)); this uses the existing symbols dmxPinUpd, dmxNs, dmxOwnPins and o to
restore the owner name when an option returns to disabled state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/wled.h`:
- Around line 472-474: The guard that decides whether to call DMXInput::init()
incorrectly treats the -1 sentinel (promoted to 255 as uint8_t) as a valid pin;
update the check around the DMX init call to explicitly reject the 255 sentinel
(e.g., require rxPin > 0 && rxPin < 255 && enPin > 0 && enPin < 255 && txPin > 0
&& txPin < 255) or change the DMXInput::init() parameter types to signed
(preserve -1) and adjust the guard to check for >= 0; modify the code paths that
call DMXInput::init() and the DMXInput::init() signature accordingly
(referencing dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin and
DMXInput::init).

---

Nitpick comments:
In `@wled00/data/settings_sync.htm`:
- Around line 51-68: The dropdown option label isn't restored when an option
becomes disabled; update dmxPinUpd() so inside the loop where you compute
o.disabled you also set o.text based on whether the option is disabled: look up
the owner name from d.pinsData (e.g. const owner = d.pinsData?.find(pi =>
pi.p===p)?.n) and if o.disabled set o.text = owner ?? String(p) (otherwise keep
the existing enabled behavior o.text = String(p)); this uses the existing
symbols dmxPinUpd, dmxNs, dmxOwnPins and o to restore the owner name when an
option returns to disabled state.
🪄 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: 8055b1f3-bd9b-4472-b8fd-d3444a934ee9

📥 Commits

Reviewing files that changed from the base of the PR and between fa6fc4f and 1284aba.

📒 Files selected for processing (3)
  • wled00/data/settings_sync.htm
  • wled00/pin_manager.h
  • wled00/wled.h
✅ Files skipped from review due to trivial changes (1)
  • wled00/pin_manager.h

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 `@wled00/data/settings_sync.htm`:
- Around line 61-65: The code that updates option labels in the loop (using
gN(n)?.options, vs and d.pinsData) overwrites any warning marker added by
makePinSelect(), so dmxPinUpd() removes the "⚠" for invalid pins; fix by
preserving any existing warning prefix when setting o.text (e.g. detect if
o.text currently contains the warning marker added by makePinSelect() and
prepend it to the new String(p) instead of blindly replacing the full label),
keep the same disabled logic and only change the label assignment so invalid-pin
labels retain their marker after update.
🪄 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: b3180985-ec3e-4914-b537-fc3b94b4e976

📥 Commits

Reviewing files that changed from the base of the PR and between 1284aba and 9b94742.

📒 Files selected for processing (1)
  • wled00/data/settings_sync.htm

@DedeHai DedeHai requested a review from netmindz April 4, 2026 16:19
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.

🧹 Nitpick comments (2)
wled00/data/settings_sync.htm (2)

58-68: Consider updating dmxOwnPins on change for stricter conflict tracking.

dmxOwnPins is captured once at initialization and never updated when the user changes selections. This means a pin that was originally assigned to DMX remains "unlocked" even after being deselected and potentially claimed by another component via a page reload.

The current behavior is acceptable for most workflows (user will save and reload to see updated allocations), but for stricter parity with settings_leds.htm's pinUpd() pattern, you could update dmxOwnPins at the end of dmxPinUpd():

 function dmxPinUpd() {
 	const vs = dmxNs.map(n => parseInt(gN(n)?.value??-1));
 	dmxNs.forEach((n,i) => {
 		for (const o of gN(n)?.options??[]) {
 			const p = parseInt(o.value); if (p<0) continue;
 			o.disabled = (p!==vs[i] && !dmxOwnPins.includes(p) && !!d.pinsData?.find(pi=>pi.p===p)?.a) || vs.some((v,j)=>j!==i&&v===p);
 			if (!o.disabled) o.text = String(p);
 		}
 	});
+	dmxOwnPins = vs.filter(v => v >= 0);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_sync.htm` around lines 58 - 68, dmxPinUpd currently
reads dmxOwnPins once at init and never refreshes it, so update dmxOwnPins at
the end of dmxPinUpd to reflect the current selections (like settings_leds.htm's
pinUpd pattern); compute the currently selected pin list from dmxNs (using
parseInt(gN(n)?.value) or similar) and assign it to dmxOwnPins so subsequent
calls use the up-to-date ownership when disabling/enabling options.

53-55: Clarify the flag comment.

The comment "RX pin is allowed input only" is ambiguous—it could be read as "RX may only be an input" (correct intent: RX accepts input-only GPIOs) or "RX is restricted to input". Consider rewording:

-		dmxNs.forEach((n,i) => { const s = makePinSelect(n, i?1:0); if (s) s.onchange = dmxPinUpd; }); // note on the i?1:0 -> RX pin is allowed input only but TX/EN pins must be GPIO
+		dmxNs.forEach((n,i) => { const s = makePinSelect(n, i?1:0); if (s) s.onchange = dmxPinUpd; }); // i?1:0: RX (i=0) allows input-only GPIOs; TX/EN require output-capable pins
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_sync.htm` around lines 53 - 55, The inline comment in
function pinDropdowns is ambiguous about RX pin behavior; update the comment
near the makePinSelect call (involving dmxNs, makePinSelect, and dmxPinUpd) to
clearly state that RX may accept GPIOs that are input-only (not that RX is
restricted to being an input), e.g. rephrase to “RX may use input-only GPIOs;
TX/EN must be full GPIOs” so the intent is unambiguous when reading the
dmxNs.map and dmxOwnPins logic that follows (which uses gN).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@wled00/data/settings_sync.htm`:
- Around line 58-68: dmxPinUpd currently reads dmxOwnPins once at init and never
refreshes it, so update dmxOwnPins at the end of dmxPinUpd to reflect the
current selections (like settings_leds.htm's pinUpd pattern); compute the
currently selected pin list from dmxNs (using parseInt(gN(n)?.value) or similar)
and assign it to dmxOwnPins so subsequent calls use the up-to-date ownership
when disabling/enabling options.
- Around line 53-55: The inline comment in function pinDropdowns is ambiguous
about RX pin behavior; update the comment near the makePinSelect call (involving
dmxNs, makePinSelect, and dmxPinUpd) to clearly state that RX may accept GPIOs
that are input-only (not that RX is restricted to being an input), e.g. rephrase
to “RX may use input-only GPIOs; TX/EN must be full GPIOs” so the intent is
unambiguous when reading the dmxNs.map and dmxOwnPins logic that follows (which
uses gN).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9d5a9f0f-5b36-499f-970f-a8e7ec5cc033

📥 Commits

Reviewing files that changed from the base of the PR and between 1284aba and 875711d.

📒 Files selected for processing (3)
  • wled00/data/settings_sync.htm
  • wled00/dmx_input.cpp
  • wled00/dmx_input.h

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants