Skip to content

fix(wifi): use data-ssid for scan-list autofill so SSIDs with spaces connect - #45

Merged
SneezeGUI merged 1 commit into
SneezeGUI:mainfrom
terahertz5k:fix/ssid-space-nbsp-autofill
Sep 19, 2026
Merged

SneezeGUI merged 1 commit into
SneezeGUI:mainfrom
terahertz5k:fix/ssid-space-nbsp-autofill

Conversation

@terahertz5k

Copy link
Copy Markdown

Problem

Picking a network from the config portal's scan list makes the device fail to join any AP whose name contains a space. It fails with disconnect reason 201 (WIFI_REASON_NO_AP_FOUND) even though the AP is present and the password is correct. Typing the same SSID by hand works — which makes this look like a router, signal, or credential problem rather than a portal bug.

Root cause

wifi_manager_init() injects a custom c(l) handler through setCustomHeadElement() that fills the SSID field from innerText:

function c(l){...s.value=l.innerText||l.textContent;...}

Since _customHeadElement is appended after the bundled HTTP_SCRIPT, this overrides WiFiManager's own c().

WiFiManager renders each scan-list entry from HTTP_ITEM:

<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>...</div>

populating the two tokens differently:

item.replace(FPSTR(T_V), htmlEntities(WiFi.SSID(i)));        // {V} -> data-ssid
item.replace(FPSTR(T_v), htmlEntities(WiFi.SSID(i), true));  // {v} -> visible text

and htmlEntities(str, whitespace=true) does str.replace(" ", "&#160;").

So the visible link text carries U+00A0 (non-breaking space) while data-ssid keeps real 0x20 spaces. Reading innerText submits My<U+00A0>Network, which never matches the real SSID during the scan — hence NO_AP_FOUND rather than an auth error.

This is precisely why WiFiManager's own handler reads data-ssid first.

Fix

Read data-ssid first, falling back to innerText/textContent with U+00A0 normalized in case a future WiFiManager version drops the attribute.

Testing

Verified on an ESP32-2432S028 (CYD, 2-USB) against a WPA2 network whose SSID contains a space:

  • Before: clicking the network in the scan list produced repeated [WIFI] Disconnected, reason: 201 and never associated.
  • After: clicking the same network associates, gets a DHCP lease, and proceeds to stratum.
  • Typing the SSID manually worked both before and after, since that path never invokes c().

The portal code is shared, so this affects all boards.

Notes

  • Behaviour for manually-typed SSIDs is unchanged.
  • The pre-existing override already dropped WiFiManager's l.nextElementSibling password-field enable/disable logic; this change keeps that as-is rather than widening scope.

🤖 Generated with Claude Code

…connect

Clicking a network in the config portal's scan list filled the SSID field
from the link's innerText. WiFiManager renders that visible text with
htmlEntities(ssid, true), which replaces every space with &#160; (U+00A0
non-breaking space), so the submitted SSID never matched the real network.
Any AP whose name contains a space therefore failed to join with reason
201 (NO_AP_FOUND), while typing the same SSID by hand worked -- which made
the failure look like a credential, router or signal problem.

Read the data-ssid attribute first, the same source WiFiManager's own
bundled c() handler uses; it is populated with htmlEntities(ssid) and
keeps real spaces. Normalize U+00A0 on the innerText/textContent
fallbacks in case data-ssid is unavailable.
@SneezeGUI

Copy link
Copy Markdown
Owner

Thanks @terahertz5k — root cause confirmed against the pinned WiFiManager v2.0.17: HTTP_ITEM renders data-ssid='{V}' with real spaces while the visible text goes through htmlEntities(ssid, true) (spaces → &#160;), and our custom c() read innerText — so every scan-picked SSID containing a space went out as My<U+00A0>Network and died with reason 201. Your fix matches what the bundled c() does (data-ssid first) plus a normalized fallback, and since the custom head element is injected after HTTP_SCRIPT the override order works as described.

I extracted the embedded JS from the branch and ran it: the data-ssid path preserves real spaces, &amp;/&#39; decode correctly through getAttribute, and the innerText/textContent fallbacks normalize NBSP properly. Manually-typed SSIDs are untouched.

One tiny note, non-blocking: the NBSP .replace() also runs on the data-ssid value, so an SSID containing a literal U+00A0 (legal, but I've never seen one in the wild) would get flattened to a plain space. Applying the replace only on the fallback paths would be strictly exact — happy to take that as a follow-up.

CI never actually built this branch (the run sat queued until it timed out), but it's a single string-literal change and I've verified the JS independently. Landing this — thanks for the clean writeup.

@SneezeGUI
SneezeGUI merged commit 8a6b204 into SneezeGUI:main Sep 19, 2026
@SneezeGUI SneezeGUI mentioned this pull request Sep 19, 2026
trcjr pushed a commit to trcjr/SparkMiner that referenced this pull request Sep 22, 2026
…se automation

- Bump AUTO_VERSION to v2.9.6-pre across all envs
- README: honest S3 (~50-55 KH/s, software-verified) and C3 (pending
  validation) numbers; old 280-400 KH/s figures were invalid shares
- CHANGELOG entry for everything in this batch (SneezeGUI#36 SneezeGUI#37 SneezeGUI#38 SneezeGUI#39 SneezeGUI#44 SneezeGUI#45)
- build.yml: upload factory-bin test builds on every PR
- release.yml: tag-triggered build of all 17 targets + GitHub Release
  with factory/firmware bins (uses post_build_merge.py output)

Co-Authored-By: Claude <noreply@anthropic.com>
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