ESP8266 wifi connection speed optimization

It’s been a while since I initially looked into what it takes to make an optimal wifi connection with an Espressif ESP8266 / ESP-01 (old post). Using the code from the old post didn’t work … anymore? What the heck. So much for rolling out another kind of battery-powered ESP8266 device. Time to dig into what goes on with the wifi connection timing.

The code is on Github. Most of this post is from the readme file there. Which will rank better in Google, a subdirectory of a well-known domain, or my blog post? Whatever, it’s all the same to me :-).

TLDR: Fastest ESP8266 wifi connection time can be less than 170ms

The fastest connection (that I found; including publishing to MQTT) is done with the following setup:

  1. Connect normally. Cache received BSSID & channel. O(4'000ms)
  2. Set WiFi.persistent(true). Connect with BSSID & channel. O(1'500ms)
  3. Set WiFi.persistent(true) to get persisted infos. Connect again with BSSID & channel. O(170ms)

You have to do all of these steps, then repeated connections will be around 170ms. In C++, steps 2 & 3 look like this:

    IPAddress(data->ip_gateway), IPAddress(data->ip_mask), 
    IPAddress(data->ip_dns1), IPAddress(data->ip_dns2));
WiFi.begin(data->wifi_ssid, data->wifi_auth, data->wifi_channel, data->wifi_bssid, true);
uint32_t timeout = millis() + FAST_TIMEOUT;
while ((WiFi.status() != WL_CONNECTED) && (millis()<timeout)) { delay(10); }
// connected or timed out

I suspect using this configuration persists not only BSSID and channel-number, but also information from the key-exchange for the WPA2 connection. Weirdly, without using persistent(true), the device still scans for the best channel, wasting time. You could recreate the same configuration by manually doing what the ESP8266 Wifi-code does; it’s probably a bit flakey since you can’t check what the firmware does (you just see the interfaces in user_interface.h).


Arduino sketches

This repo includes Arduino sketches if you prefer those. They’re at:

Variations tested

I included median and 90’th percentile (“p90”) duration timing. 90th percentile means that 90% of the runs were below this number. Timings were measured with the micros() function, and tracked over a number of iterations. The timing data was output as <key=value> to the serial port, aggregated with a script that monitors the serial port and tracks the entries into a CSV file).

The total time includes:

  • Restoring settings from flash
  • Connecting to wifi
  • Connecting to the MQTT server
  • Publishing 1 MQTT topic (in last variant, 5 topics)

Some of the variants tested (see variations.txt for more):

  • Normal connection (just SSID, auth): median 3898ms, p90 4267ms
  • Connect with BSSID, channel (Variant “O”): median 1086ms, p90 1146ms
  • With persistent(true), no BSSID, channel specified (Variant “K”): median 2911ms, p90 3915ms
  • With persistent(true), giving BSSID, channel, manually connecting using wifi_station_connect() (Variant “J”): median 940ms, p90 1552ms
  • With persistent(true), giving BSSID, channel, manually connecting using reconnect() (variant “I”): median 587ms, p90 1749ms
  • With persistent(true), giving BSSID, channel, requesting connection in begin() (Variant “G”): median 197ms, p90 1201ms
  • same, but doing 5x MQTT publishes instead of one (Variant “P”): median 188ms, p90 1227ms

The last variant (“P”; similar to the second-last one) is the one that’s overall the fastest, with a median time from start to publishing 5 MQTT topics of 188ms (stddev: 334ms).

The MQTT portion of variant “P” (connecting to server, publishing topics) has a median time of 19ms (p90: 25ms, stddev: 290ms!). The high standard deviation for MQTT publishing is probably what throws off the total time standard deviation. My MQTT server is on a local Raspberry Pi running Home Assistant.

Without doing an IP/port pre-connection (variant “Q”), the total median time goes to 190ms (p90: 278ms, stddev: 257ms), so the IP pre-connection does not save much. Using the hostname instead of IP address (requiring a DNS lookup; variant “R”), the total time goes to median 202ms (p90 840ms, stddev 410ms), so caching the IP address is a good idea.


The weird & wonderful:

  • persistent() is disabled by default in the ESP SDK for wifi. I suspect because of flash wear? YOLO.
  • Platformio uses ancient ESP8266 “Non-OS” (Arduino) SDKs – the ESP8266 SDKs are much newer. Issue filed in 2018. WTH Platformio?!
  • Connecting with BSSID and channel (without persistent(true)), will result in scanning for a channel, and may use a different channel. This adds ca 900ms to the connection time. This is probably a bug. This is the configuration that most external mentions for speed optimization suggest, which is better than nothing, but still slower than it needs to be.
  • The ESP SDK code in ESP8266WiFiSTA.cpp shows how the connection is built, but you can’t check the code for what actually happens. However, you can tell that using persistent(true) without specifying a BSSID will result in the persistent data not being used (despite having the BSSID too).
  • In the code, you also see that wifi_station_connect() is called before setting the channel number. Does this mean the persistent channel is not used? Weird.
  • Using persistent(true) without specifying BSSID / channel does not persist the received settings.
  • The debug output (see below) is the same for all connection types; it’s useless to look at for speed optimizations.
  • The SDK versions provided by Platformio (v2.2.1 - 2.2.x, pre-3.0; 2018 to 2019) all have similar timings.
  • The unclear difference between persistent(true) + connect with BSSID & channel imo suggest that future SDK versions may be different, and that ESP32 may handle this differently. It’s unclear what persistent(true) actually does. Magic.

ESP8266 Wifi debug output

Platformio has build flags to enable ESP wifi debug output. Unfortunately the main variations all show exactly the same debug output, so you can’t tell what’s happening.

In platformio.ini (docs):

build_flags = -DDEBUG_ESP_WIFI -DDEBUG_ESP_PORT=Serial 

Output with normal connection (scan for BSSID, channel, connect), but also without specifying a channel, and the persistent fast connection:

fpm close 3 
mode : sta(c4:5b:be:4a:69:34)
add if0
wifi evt: 8
wifi evt: 2
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 3

connected with XXXXXX, channel 11
dhcp client start...
wifi evt: 0
wifi evt: 3

Comments / questions

There's currently no commenting functionality here. If you'd like to comment, please use Mastodon and mention me ( ) there. Thanks!

Related pages