You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
WLED 0.16.0 alpha on ESP32 can only connect to WiFi access points on channel 1 #5345
I have four APs in my home, two are on channel 1, one on channel 6, and one on channel 11. They all share the same ESSID and WPA passphrase. My WLED device is closest to, and gets the strongest signal from, the channel 11 access point. That's the one I would like it to use.
I upgraded my ESP32 (a Gledopto GL-C-017WL-D) from 0.15.3 to 0.16.0 and noticed that WLED now will only connect to the two access points that are on channel 1, resulting in a weaker connection. Interestingly, when I do a network scan, the signal strength of the network that it finds in the scan results is much stronger and what I would expect for the closer AP on channel 11, so I think this just happens when connecting to the network.
Some things I've tried:
Restarting WLED and doing a full power cycle
Specifying the BSSID of the close access point in the WLED configuration <- seems to be ignored, does not connect to the specified AP
Doing a factory reset and setting up WiFi again <- still connects to the weaker channel 1 APs
Locking the WLED device to the desired access point in the Unifi network controller <- WLED can no longer connect to the network at all until I remove the lock
Changing WLED's access point channel setting to 11 <- no noticeable effect
I reverted back to 0.15.3 and it immediately connected to the correct access point as it did before.
To Reproduce Bug
Install the latest 0.16.0 nightly build of WLED
Attempt to connect to a wireless network on channel 6 or 11
Observe that it does not successfully connect
Expected Behavior
WLED successfully connects to an access point on any channel.
Install Method
Binary from WLED.me
What version of WLED?
WLED 0.16.0-alpha (2602030)
Which microcontroller/board are you seeing the problem on?
@coderabbitai please check if there is any reason in the code for such a behaviour, i.e. does WLED handle signal strength correctly or is this all handled by the arduino library?
#!/bin/bash
# Fetch the issue details to understand the specific behavior being discussed
gh issue view 5345 --repo wled/WLED --json title,body,comments --jq '{title: .title, body: .body, comments: .comments[].body}'
Length of output: 2335
🏁 Script executed:
#!/bin/bash
# Search for signal strength related code in the codebase
# Look for RSSI (Received Signal Strength Indicator), WiFi signal, signal strength
rg -n -C3 -i "rssi|signal.*strength|wifi.*signal" --type cpp --type c --type h
# Also search for WiFi status and connection handling
rg -n -C3 "WiFi\.(status|RSSI|signalStrength)" --type cpp --type c --type h
Length of output: 17701
🏁 Script executed:
#!/bin/bash
# Look for any network or WiFi related handling files
fd -e cpp -e h -e c "wifi|network" -i
Length of output: 104
🏁 Script executed:
#!/bin/bash
# Look at the full WiFi connection logic in network.cpp to understand how connection is established
cat wled00/network.cpp | head -400
Length of output: 14696
🏁 Script executed:
#!/bin/bash
# Search for WiFi.begin calls to see how the connection is initiated
rg -n -C5 "WiFi\.begin" --type cpp
Length of output: 2134
🏁 Script executed:
#!/bin/bash
# Look for any channel-related code in WiFi connection
rg -n -C3 "channel|Channel" wled00/network.cpp
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there are any recent commits that changed network.cpp between 0.15.3 and 0.16.0
git log --oneline --since="2024-01-01" --until="2025-02-01" -- wled00/network.cpp | head -20
Length of output: 366
🏁 Script executed:
#!/bin/bash
# Look at the WiFi connection code around WiFi.begin to see if BSSID should be used
cat wled00/wled.cpp | sed -n '700,760p'
Length of output: 2803
🏁 Script executed:
#!/bin/bash
# Check if there's an overload of WiFi.begin that takes BSSID as parameter
rg -n "WiFi.begin.*BSSID|WiFi.begin.*bssid" --type cpp
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the full git commit that added BSSID support
git show 272129f6 --stat
Length of output: 658
🏁 Script executed:
#!/bin/bash
# See the actual changes in that commit for network.cpp
git show 272129f6 -- wled00/wled.cpp | head -100
Length of output: 3759
🏁 Script executed:
#!/bin/bash
# See the network.cpp changes in the BSSID commit
git show 272129f6 -- wled00/network.cpp | head -250
Length of output: 10590
🏁 Script executed:
#!/bin/bash
# Look for where WiFi connection actually happens in current network.cpp
rg -n "WiFi\.begin|wifi_station_connect" wled00/network.cpp -A3 -B3
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check what the multiWiFi structure contains, especially BSSID field
rg -n "struct.*multiWiFi|bssid" wled00/wled.h -A10 -B2
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for any channel-related filtering or restrictions in wled.cpp
rg -n -i "channel" wled00/wled.cpp -C3
Length of output: 927
🏁 Script executed:
#!/bin/bash
# Search for the multiWiFi structure/class definition
rg -n "class.*WiFi|struct.*WiFi" wled00/wled.h -A15
Length of output: 514
🏁 Script executed:
#!/bin/bash
# Look for wifiSettings or WiFiSettings structure
rg -n "wifiSettings|WiFiSettings" wled00/wled.h -A10
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check ESP32 WiFi.begin signature documentation or usage with BSSID
rg -n "uint8_t.*bssid|bssid\[6\]" --type h --type cpp
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Look at the rest of the commit to see WiFi connection code changes
git show 272129f6 -- wled00/network.cpp | grep -A50 "WiFi.begin"
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the structure containing the bssid field
cat wled00/fcn_declare.h | sed -n '50,80p'
Length of output: 1034
🏁 Script executed:
#!/bin/bash
# Search for all occurrences of WiFi.begin in the entire commit diff
git show 272129f6 | grep -C10 "WiFi.begin"
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the full network.cpp after the commit to see if there's WiFi connection code
git show 272129f6:wled00/network.cpp | tail -200
Length of output: 7990
🏁 Script executed:
#!/bin/bash
# Check if there's a function that handles WiFi connection with BSSID
rg -n "initWiFi|connectWiFi|startWiFi" wled00/network.cpp -A20
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check the exact WiFi.begin calls in current wled.cpp with more context
cat wled00/wled.cpp | sed -n '720,760p'
Length of output: 2223
🏁 Script executed:
#!/bin/bash
# Search for any channel-based filtering in findWiFi or scan logic
rg -n "channel\(" wled00/network.cpp -B2 -A2
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for WiFi.scanNetworks parameters that might filter channels
rg -n "scanNetworks" wled00/network.cpp -B3 -A3
Length of output: 467
🏁 Script executed:
#!/bin/bash
# Check if there's a specific channel being set for WiFi STA mode
rg -n "setChannel|channel.*=" wled00/wled.cpp wled00/network.cpp -C3
Length of output: 0
@DedeHai I've thoroughly analyzed the code and found the root cause of this behavior. WLED does handle signal strength selection, but there's a bug in how it connects to the selected AP.
Key Findings
WLED has its own signal strength handling logic:
getSignalQuality() converts RSSI to percentage quality (network.cpp:265-282)
findWiFi() scans networks and selects the AP with strongest signal (network.cpp:302-332), with logic to prefer BSSID matches and handle priority with 10dB hysteresis
The Critical Bug:
The WiFi.begin() calls in wled.cpp (lines 730, 745, 749) only pass SSID and password:
Even though findWiFi() correctly selects the best AP and stores its BSSID in the multiWiFi[selectedWiFi].bssid field, this information is never used when connecting. The ESP32's WiFi stack is left to make its own decision about which AP to connect to when multiple APs share the same SSID, and it appears to prefer channel 1.
The ESP32 Arduino WiFi library supports:
WiFi.begin(ssid, password, channel, bssid) // Connect to specific AP
But WLED only uses:
WiFi.begin(ssid, password) // Connect to any AP with matching SSID
Why This Explains the Reported Behavior
✅ BSSID configuration is ignored - because it's never passed to WiFi.begin()
✅ Network scan shows strong signal - scanning works correctly
✅ Connection goes to wrong AP - ESP32 makes its own choice without BSSID constraint
✅ Worked in 0.15.3 - the multi-WiFi/BSSID feature was added in commit 272129f (Dec 2024) but incompletely implemented
This is a WLED code bug, not an Arduino library issue. The fix would be to pass the selected AP's BSSID and channel to WiFi.begin().
🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.
Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.
Learnt from: willmmiles
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1183-1194
Timestamp: 2025-09-18T03:17:30.107Z
Learning: In WLED bus management code, dead code branches in error handling (like unreachable else breaks) are intentionally preserved to accommodate potential future API changes, even when currently unreachable.
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: In WLED, code paths that appear unreachable may actually be safety fallbacks for low-heap conditions. These fallbacks are intentional defensive programming measures to ensure UI functionality is preserved when system resources are constrained.
Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.
@coderabbitai there was no fundamental change in code between 0_15_x branch and main branch with regards to WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass);
What did change is the arduino and IDF version.
#!/bin/bash
# Check git tags and platform changes around release times
echo "=== Available release tags ==="
git tag | grep -E "v0\.(15\.3|16\.0)" | sort -V
echo -e "\n=== Checking platformio.ini at v0.15.3 tag ==="
git show v0.15.3:platformio.ini 2>/dev/null | grep -A5 "^\[esp32_idf_V4\]" || echo "Tag v0.15.3 not found, trying v0_15_3"
git show v0_15_3:platformio.ini 2>/dev/null | grep -A5 "^\[esp32_idf_V4\]"
echo -e "\n=== Checking platformio.ini at v0.16.0 tag ==="
git show v0.16.0:platformio.ini 2>/dev/null | grep -A5 "^\[esp32_idf_V4\]" || echo "Tag v0.16.0 not found, trying v0_16_0"
git show v0_16_0:platformio.ini 2>/dev/null | grep -A5 "^\[esp32_idf_V4\]"
echo -e "\n=== List all tags ==="
git tag | tail -20
echo -e "\n=== Check network.cpp changes in commit history ==="
git log --oneline --all -- wled00/network.cpp | head -20
Length of output: 1402
🌐 Web query:
ESP32 Arduino WiFi.begin BSSID parameter multiple access points same SSID
💡 Result:
When you have multiple APs broadcasting the same SSID, ESP32 Arduino lets you force a specific AP by passing its BSSID (AP MAC address) to:
WiFi.begin(ssid, pass, channel, bssid, connect)—the bssid argument is explicitly “the AP BSSID”. [1]
If you don’t pass a BSSID, recent ESP32 Arduino cores will typically scan all channels and pick the strongest AP (highest RSSI) when you call WiFi.begin(ssid, pass) with explicit credentials. [2]
Practical pattern: scan → pick AP → connect by BSSID
The Arduino-ESP32 WiFiScan API can return RSSI, BSSID, and channel for each scan result via getNetworkInfo(...). [1]
`#include` <WiFi.h>
constchar* SSID = "MySSID";
constchar* PASS = "MyPass";
uint8_t targetBssid[6];
int32_t targetChannel = 0;
voidsetup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect(true);
int n = WiFi.scanNetworks();
int best = -1;
int bestRssi = -9999;
for (int i = 0; i < n; i++) {
String ssid;
uint8_t enc;
int32_t rssi;
uint8_t* bssidPtr;
int32_t ch;
WiFi.getNetworkInfo(i, ssid, enc, rssi, bssidPtr, ch); // [1]if (ssid == SSID && rssi > bestRssi) {
bestRssi = rssi;
best = i;
targetChannel = ch;
memcpy(targetBssid, bssidPtr, 6);
}
}
if (best < 0) {
Serial.println("SSID not found");
return;
}
WiFi.begin(SSID, PASS, targetChannel, targetBssid, true); // force that AP [1]
}
voidloop() {}
Notes
Using bothchannel and bssid can make association faster/more deterministic (you already know which AP you want). The parameters are part of WiFi.begin(...). [1]
After connecting, you can verify which AP you ended up on by printing the connected BSSID (e.g., WiFi.BSSIDstr()), but the key selection mechanism is the bssid parameter to WiFi.begin. [1]
Sources: Arduino-ESP32 Wi-Fi API docs (WiFi.begin signature + WiFiScan/getNetworkInfo) [1] and behavior note about strongest-AP selection when multiple APs share SSID [2].
#!/bin/bash
# Look at the commit that added BSSID support to understand intent
echo "=== Full commit message for BSSID support ==="
git log --format=fuller 272129f6 -1
echo -e "\n=== Full diff of the BSSID commit ==="
git show 272129f6 --stat
echo -e "\n=== Look at how BSSID is stored and used ==="
git show 272129f6 -- wled00/wled.h | grep -A10 -B5 "bssid"
echo -e "\n=== Check if there are any TODOs or comments about BSSID in the code ==="
git checkout main 2>/dev/null
rg -n "TODO.*bssid|bssid.*TODO|FIXME.*bssid" --type cpp -i
echo -e "\n=== Check findWiFi function to see how BSSID is selected ==="
cat wled00/network.cpp | sed -n '302,350p'
Length of output: 3845
🏁 Script executed:
#!/bin/bash
# Check how BSSID is populated in findWiFi and stored
echo "=== Check how BSSID is stored in multiWiFi structure ==="
git checkout main 2>/dev/null
rg -n "multiWiFi.*bssid|bssid.*=" wled00/network.cpp -C3
echo -e "\n=== Check multiWiFi structure definition ==="
rg -n "struct.*multiWiFi|class.*multiWiFi" wled00/ -A20 | head -50
echo -e "\n=== Check where the scanned BSSID is copied to multiWiFi ==="
rg -n "memcpy.*bssid|bssid.*memcpy" wled00/ -C3
echo -e "\n=== Look at WiFi connection code in wled.cpp to see if channel/BSSID could be passed ==="
cat wled00/wled.cpp | sed -n '720,760p'
Length of output: 3172
🏁 Script executed:
#!/bin/bash
# Check if BSSID feature exists in 0_15_x branch
echo "=== Check if findWiFi uses BSSID in 0_15_x branch ==="
git checkout 0_15_x 2>/dev/null
cat wled00/network.cpp | sed -n '302,350p'
echo -e "\n=== Check multiWiFi structure in 0_15_x ==="
rg -n "struct.*WiFiConfig|bssid" wled00/fcn_declare.h -A20
echo -e "\n=== Check multiWiFi structure in main branch ==="
git checkout main 2>/dev/null
rg -n "struct.*WiFiConfig|bssid" wled00/fcn_declare.h -A20
echo -e "\n=== Look at issue `#2151` that was supposedly fixed ==="
gh issue view 2151 --repo wled/WLED --json title,body,comments --jq '{title: .title, body: .body}' 2>/dev/null || echo "Could not fetch issue `#2151`"
Length of output: 6774
🏁 Script executed:
#!/bin/bash
# Check when each version was released and if they contain the BSSID commit
echo "=== Check git log to find release dates ==="
git log --all --oneline --grep="0.15.3\|0.16.0\|v0.15.3\|v0.16.0" | head -10
echo -e "\n=== Check when BSSID commit (272129f6) was made ==="
git log --format="%H %ai %s" 272129f6 -1
echo -e "\n=== Check if BSSID commit is in 0_15_x branch ==="
git branch --contains 272129f6 | grep "0_15_x"
echo -e "\n=== Find most recent commits on 0_15_x ==="
git checkout 0_15_x 2>/dev/null
git log --oneline -20
echo -e "\n=== Check release notes or changelog ==="
cat CHANGELOG.md 2>/dev/null | head -100 || echo "No CHANGELOG.md found"
Length of output: 6777
@DedeHai You're absolutely right, and I apologize for the confusion. Let me clarify my findings:
What I Found
Both branches have identical code:
✅ WiFi.begin() calls are the same (no BSSID/channel passed)
✅ findWiFi() function includes BSSID preference logic
✅ WiFiConfig struct has bssid[6] field
The incomplete BSSID implementation:
Even though commit 272129f (Dec 21, 2024) added BSSID support to fix issue #2151, the implementation appears incomplete in both branches:
findWiFi() correctly checks if a scanned AP matches the user-configured BSSID:
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass);
// Should be: WiFi.begin(ssid, pass, channel, bssid, true)
The Real Issue
According to ESP32 Arduino WiFi documentation, when you call WiFi.begin(ssid, password)without BSSID/channel parameters, the ESP32 WiFi stack makes its own AP selection decision when multiple APs share the same SSID.
The behavior difference you're seeing is likely due to:
Changes in the underlying ESP32 WiFi library's AP selection algorithm between Arduino/IDF versions
The older version may have had better RSSI-based selection
The newer version may prefer lower channels or have different criteria
The Solution
To properly implement BSSID-based AP selection (as issue #2151 requested), the connection code needs to be updated to pass the BSSID and channel from the selected AP to WiFi.begin(). This would force connection to the specific AP that findWiFi() selected based on signal strength and BSSID preference.
Would you like me to investigate the specific Arduino/IDF version changes that might have affected AP selection behavior?
🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.
Learnt from: willmmiles
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1183-1194
Timestamp: 2025-09-18T03:17:30.107Z
Learning: In WLED bus management code, dead code branches in error handling (like unreachable else breaks) are intentionally preserved to accommodate potential future API changes, even when currently unreachable.
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.
Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.
The problem description is similar - instead of using the strongest station, WLED connects to another station with weaker signal. And the reporter confirms that the behaviour already exists in versions 15.1 and 15.2. 15.0 was release before the BSSID commit, which explains that the initial 15.0 release behaved differently.
Edit:
I reverted back to 0.15.3
This can only be explained by the new default framework version (tasmota/arduino-esp32 v2.0.9) behaving differently; 0.15.3 already had the BSSID commit.
@DedeHai in case we change the call to Wifi.begin() to the 4 parameter (with BSSID and channel) version, do you think it would still be compatible with PR #5337 ?
@softhack007 I have no way really of testing multi BSSID. And yes, I think #5337 is independent of function call used, I can check underlaying code to see if it does anything different but I doubt it.
I would also not update the way #5337 works: i.e. not update NVM based on BSSID, so should be smooth sailing (fingers crossed).
edit:
if NVM stored credentials are used, it will just connect so much faster, if WLED later in the code decides a differen AP is better suited, it will just switch (causing a glitch, I am not claiming #5337 is fixing all glitches related to wifi, just the one at boot)
About true "wifi roaming" - i.e. WLED automatically moving to the strongest station at any time - it looks like we cannot really have roaming and mesh support at the moment.
Our expectation "Connect to the strongest access point on startup or when signal lost" might be possible; but real roaming = "Always select the strongest access point dynamicially" will not arrive in WLED for longer time.
Both tickets together tell me: Roaming is not possible in arduino-esp32 version 1.0.x or 2.0.x; It was planned for 3.0.x, but then disabled again due to stability problems. A future v3.x.y arduino-esp32 might support it, or not.
there is always the way to bypass arduino and use a different library if any exist. The very first test would need to be: does switching AP cause glitches in LED outputs like it currently is i.e. when switching from STA to AP mode or vice versa. If that is the case and a controller is between two good wifi AP's it will constantly glitch - a not uncommon configuration in a multi AP home environment.
About true "wifi roaming" - i.e. WLED automatically moving to the strongest station at any time - it looks like we cannot really have roaming and mesh support at the moment.
IMO that's fine. For my use case, and I suspect for the majority of use cases, the WLED controller never moves around. I'd be fine if it connected to the strongest AP on startup and then just stayed there. I have several WLED devices (mostly ESP32 and one ESP32-S3) on 0.15.3 and they all seem to behave this way right now.
Also, something I didn't make clear in my initial report, I have seen the 0.16 build connect to different access points on my mesh network, however just ones that are on channel 1. I have two APs on channel 1, both have a weak signal from where the WLED controller is, and I've seen it connect to both of them. What I've never seen it do is connect to either of my other two APs on 6 or 11. This would suggest to me that it's not latching onto a specific access point, and is why I thought it might have to do with the WiFi channel.
If it would help, I could try creating a new network that only has a single AP on channel 11 and see what happens when I connect there.
I did an experiment today and uncovered some bizarre behavior with the access point/BSSID selection logic. On a mesh network, it seems to pick the AP(s) with the lowest channel number rather than the strongest signal strength. For example, if a mesh network has two access points, one on channel 6 and one on channel 11, it consistently connects to the one on channel 6, even if channel 11 is significantly stronger.
Here's the experiment I did (all against the same 0.16.0 build):
I created a new WiFi network with a single access point, on channel 11, and configured WLED to connect to it
WLED connected to it just fine, with no issues (good!)
I added a more distant access point to the network on channel 6
WLED remained connected to the same AP on channel 11 (fine, expected)
I rebooted WLED
WLED connected to the weaker channel 6 access point (unexpected)
This behavior is consistent - rebooting several times always resulted in it connecting to channel 6
I added a third, even more distant channel 1 access point
WLED remained connected to the same AP on channel 6 (fine, expected)
I rebooted WLED again
WLED connected to the weakest channel 1 access point
Again, this behavior is consistent across subsequent reboots
I turned off the channel 1 access point while WLED was connected to it
WLED reconnected to the weak channel 6 access point (the reconnection part is expected, but it ideally would have picked channel 11)
I turned off the channel 6 access point while WLED was connected to it
WLED reconnected to the only remaining access point on channel 11
This behavior all seems new compared to 0.15.3, and I suspect it has to do with the framework version change that @softhack007 mentioned rather than anything WLED is doing.
It seems that the default scanning behavior is WIFI_FAST_SCAN, which scans channels sequentially from 1 to 13 and connects to the first AP with a matching SSID. That would exactly explain the behavior I'm seeing.
This could be fixed by changing the scan behavior to WIFI_ALL_CHANNEL_SCAN, which would scan all channels and pick the strongest AP.
Reacted by Frank Möhle, Will Tatam and George Karavaev
Swapping the scan mode sounds like a good idea and we should also fix the fact that the BSSID implementation is incomplete. There is no point having quite an advanced technical field in the settings menu if we are then failing to actually pass to the WiFi stack
I do. I just tried that and it partially works - adding the BSSID and saving the settings doesn't immediately do anything, but after rebooting WLED it connects to the specified BSSID during startup.
Activity
DedeHai commentedon Feb 4, 2026
@coderabbitai please check if there is any reason in the code for such a behaviour, i.e. does WLED handle signal strength correctly or is this all handled by the arduino library?
coderabbitai commentedon Feb 4, 2026
🧩 Analysis chain
🏁 Script executed:
Length of output: 2335
🏁 Script executed:
Length of output: 17701
🏁 Script executed:
Length of output: 104
🏁 Script executed:
Length of output: 14696
🏁 Script executed:
Length of output: 2134
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 366
🏁 Script executed:
Length of output: 2803
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 658
🏁 Script executed:
Length of output: 3759
🏁 Script executed:
Length of output: 10590
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 927
🏁 Script executed:
Length of output: 514
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 43
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 1034
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 7990
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 2223
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 467
🏁 Script executed:
Length of output: 0
@DedeHaiI've thoroughly analyzed the code and found the root cause of this behavior. WLED does handle signal strength selection, but there's a bug in how it connects to the selected AP.Key Findings
WLED has its own signal strength handling logic:
getSignalQuality()converts RSSI to percentage quality (network.cpp:265-282)findWiFi()scans networks and selects the AP with strongest signal (network.cpp:302-332), with logic to prefer BSSID matches and handle priority with 10dB hysteresisThe Critical Bug:
The
WiFi.begin()calls in wled.cpp (lines 730, 745, 749) only pass SSID and password:They do NOT pass the BSSID or channel parameters.
Root Cause
Even though
findWiFi()correctly selects the best AP and stores its BSSID in themultiWiFi[selectedWiFi].bssidfield, this information is never used when connecting. The ESP32's WiFi stack is left to make its own decision about which AP to connect to when multiple APs share the same SSID, and it appears to prefer channel 1.The ESP32 Arduino WiFi library supports:
WiFi.begin(ssid, password, channel, bssid) // Connect to specific APBut WLED only uses:
WiFi.begin(ssid, password) // Connect to any AP with matching SSIDWhy This Explains the Reported Behavior
WiFi.begin()This is a WLED code bug, not an Arduino library issue. The fix would be to pass the selected AP's BSSID and channel to
WiFi.begin().🧠 Learnings used
DedeHai commentedon Feb 4, 2026
@coderabbitai there was no fundamental change in code between 0_15_x branch and main branch with regards to
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass);What did change is the arduino and IDF version.
coderabbitai commentedon Feb 4, 2026
🧩 Analysis chain
🏁 Script executed:
Length of output: 7624
🏁 Script executed:
Length of output: 6521
🏁 Script executed:
Length of output: 9624
🏁 Script executed:
Length of output: 1402
🌐 Web query:
💡 Result:
When you have multiple APs broadcasting the same SSID, ESP32 Arduino lets you force a specific AP by passing its BSSID (AP MAC address) to:
WiFi.begin(ssid, pass, channel, bssid, connect)—thebssidargument is explicitly “the AP BSSID”. [1]If you don’t pass a BSSID, recent ESP32 Arduino cores will typically scan all channels and pick the strongest AP (highest RSSI) when you call
WiFi.begin(ssid, pass)with explicit credentials. [2]Practical pattern: scan → pick AP → connect by BSSID
The Arduino-ESP32 WiFiScan API can return RSSI, BSSID, and channel for each scan result via
getNetworkInfo(...). [1]Notes
channelandbssidcan make association faster/more deterministic (you already know which AP you want). The parameters are part ofWiFi.begin(...). [1]WiFi.BSSIDstr()), but the key selection mechanism is thebssidparameter toWiFi.begin. [1]Sources: Arduino-ESP32 Wi-Fi API docs (WiFi.begin signature + WiFiScan/getNetworkInfo) [1] and behavior note about strongest-AP selection when multiple APs share SSID [2].
[1] https://docs.espressif.com/projects/arduino-esp32/en/latest/api/wifi.html
[2] https://hieromon.github.io/AutoConnect/adconnection.html
🏁 Script executed:
Length of output: 3845
🏁 Script executed:
Length of output: 3172
🏁 Script executed:
Length of output: 6774
🏁 Script executed:
Length of output: 6777
@DedeHaiYou're absolutely right, and I apologize for the confusion. Let me clarify my findings:What I Found
Both branches have identical code:
WiFi.begin()calls are the same (no BSSID/channel passed)findWiFi()function includes BSSID preference logicWiFiConfigstruct hasbssid[6]fieldThe incomplete BSSID implementation:
Even though commit 272129f (Dec 21, 2024) added BSSID support to fix issue
#2151, the implementation appears incomplete in both branches:findWiFi()correctly checks if a scanned AP matches the user-configured BSSID:But
WiFi.begin()never uses this information:WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // Should be: WiFi.begin(ssid, pass, channel, bssid, true)The Real Issue
According to ESP32 Arduino WiFi documentation, when you call
WiFi.begin(ssid, password)without BSSID/channel parameters, the ESP32 WiFi stack makes its own AP selection decision when multiple APs share the same SSID.The behavior difference you're seeing is likely due to:
The Solution
To properly implement BSSID-based AP selection (as issue
#2151requested), the connection code needs to be updated to pass the BSSID and channel from the selected AP toWiFi.begin(). This would force connection to the specific AP thatfindWiFi()selected based on signal strength and BSSID preference.Would you like me to investigate the specific Arduino/IDF version changes that might have affected AP selection behavior?
🧠 Learnings used
softhack007 commentedon Feb 4, 2026
Possibly related:
The problem description is similar - instead of using the strongest station, WLED connects to another station with weaker signal. And the reporter confirms that the behaviour already exists in versions 15.1 and 15.2. 15.0 was release before the BSSID commit, which explains that the initial 15.0 release behaved differently.
Edit:
This can only be explained by the new default framework version (tasmota/arduino-esp32 v2.0.9) behaving differently; 0.15.3 already had the BSSID commit.
softhack007 commentedon Feb 4, 2026
@DedeHai in case we change the call to Wifi.begin() to the 4 parameter (with BSSID and channel) version, do you think it would still be compatible with PR #5337 ?
DedeHai commentedon Feb 4, 2026
@softhack007 I have no way really of testing multi BSSID. And yes, I think #5337 is independent of function call used, I can check underlaying code to see if it does anything different but I doubt it.
I would also not update the way #5337 works: i.e. not update NVM based on BSSID, so should be smooth sailing (fingers crossed).
edit:
if NVM stored credentials are used, it will just connect so much faster, if WLED later in the code decides a differen AP is better suited, it will just switch (causing a glitch, I am not claiming #5337 is fixing all glitches related to wifi, just the one at boot)
softhack007 commentedon Feb 4, 2026
About true "wifi roaming" - i.e. WLED automatically moving to the strongest station at any time - it looks like we cannot really have roaming and mesh support at the moment.
Our expectation "Connect to the strongest access point on startup or when signal lost" might be possible; but real roaming = "Always select the strongest access point dynamicially" will not arrive in WLED for longer time.
Both tickets together tell me: Roaming is not possible in arduino-esp32 version 1.0.x or 2.0.x; It was planned for 3.0.x, but then disabled again due to stability problems. A future v3.x.y arduino-esp32 might support it, or not.
DedeHai commentedon Feb 4, 2026
there is always the way to bypass arduino and use a different library if any exist. The very first test would need to be: does switching AP cause glitches in LED outputs like it currently is i.e. when switching from STA to AP mode or vice versa. If that is the case and a controller is between two good wifi AP's it will constantly glitch - a not uncommon configuration in a multi AP home environment.
spectrumjade commentedon Feb 4, 2026
IMO that's fine. For my use case, and I suspect for the majority of use cases, the WLED controller never moves around. I'd be fine if it connected to the strongest AP on startup and then just stayed there. I have several WLED devices (mostly ESP32 and one ESP32-S3) on 0.15.3 and they all seem to behave this way right now.
Also, something I didn't make clear in my initial report, I have seen the 0.16 build connect to different access points on my mesh network, however just ones that are on channel 1. I have two APs on channel 1, both have a weak signal from where the WLED controller is, and I've seen it connect to both of them. What I've never seen it do is connect to either of my other two APs on 6 or 11. This would suggest to me that it's not latching onto a specific access point, and is why I thought it might have to do with the WiFi channel.
If it would help, I could try creating a new network that only has a single AP on channel 11 and see what happens when I connect there.
spectrumjade commentedon Feb 4, 2026
I did an experiment today and uncovered some bizarre behavior with the access point/BSSID selection logic. On a mesh network, it seems to pick the AP(s) with the lowest channel number rather than the strongest signal strength. For example, if a mesh network has two access points, one on channel 6 and one on channel 11, it consistently connects to the one on channel 6, even if channel 11 is significantly stronger.
Here's the experiment I did (all against the same 0.16.0 build):
This behavior all seems new compared to 0.15.3, and I suspect it has to do with the framework version change that @softhack007 mentioned rather than anything WLED is doing.
spectrumjade commentedon Feb 4, 2026
I found this issue that alludes to what I believe is the root cause: espressif/arduino-esp32#4533
It seems that the default scanning behavior is
WIFI_FAST_SCAN, which scans channels sequentially from 1 to 13 and connects to the first AP with a matching SSID. That would exactly explain the behavior I'm seeing.This could be fixed by changing the scan behavior to
WIFI_ALL_CHANNEL_SCAN, which would scan all channels and pick the strongest AP.softhack007 commentedon Feb 4, 2026
https://github.com/espressif/arduino-esp32/blob/3670e2bf2aca822f2e1225fdb0e0796e490005a8/libraries/WiFi/src/WiFiSTA.cpp#L427-L436
https://github.com/espressif/arduino-esp32/blob/3670e2bf2aca822f2e1225fdb0e0796e490005a8/libraries/WiFi/src/WiFiSTA.cpp#L119
This would explain the behaviour we see, because the default seems to be WIFI_FAST_SCAN.
netmindz commentedon Feb 4, 2026
Swapping the scan mode sounds like a good idea and we should also fix the fact that the BSSID implementation is incomplete. There is no point having quite an advanced technical field in the settings menu if we are then failing to actually pass to the WiFi stack
DedeHai commentedon Feb 6, 2026
@spectrumjade can you please test if #5351 works?
spectrumjade commentedon Feb 6, 2026
@DedeHai Just built and tested, and it works great! It's now connecting to the strongest AP automatically.
DedeHai commentedon Feb 6, 2026
do you by any chance also have a setup to test the fix for BSSID support?
spectrumjade commentedon Feb 6, 2026
I do. I just tried that and it partially works - adding the BSSID and saving the settings doesn't immediately do anything, but after rebooting WLED it connects to the specified BSSID during startup.
DedeHai commentedon Feb 6, 2026
ok, seems like there is a "reconnect if BSSID changed" missing. Will try to fix that.
DedeHai commentedon Feb 6, 2026
@spectrumjade fixed the reboot problem.
spectrumjade commentedon Feb 8, 2026
@DedeHai the updated PR now correctly connects to the specified BSSID without a reboot, and it is doing the full scan properly as well.
DedeHai commentedon Feb 8, 2026
perfect, thanks for reporting back. I will merge it then.