ESP8266 Wi-Fi Scans Need a Result-Ownership State Machine

A provisioning screen that refreshes nearby networks can consume memory even when it displays only a few rows. ESP8266 RTOS SDK separates the driver's discovered-AP storage from the application's result array. Treating scan completion as merely a notification to redraw the screen misses the operation that releases the driver's results.

This article examines ESP8266 RTOS SDK v3.4 at commit 89a3f254b63819035f65d9c5dcdae8864f1a6a8a. It is a legacy-maintenance review, not an assertion of current vendor support; consult Espressif's maintenance advisory when assessing deployment. The proposed tests have not been executed on hardware.

There are two separate allocations to explain

The pinned Wi-Fi API header says scan results occupy dynamically allocated driver memory and identifies esp_wifi_scan_get_ap_records as the operation that frees that storage after completion. Calling esp_wifi_scan_get_ap_num obtains a count; it is not documented as the cleanup operation.

Keep that distinction in the application's ownership model. The driver owns discovery results. The application owns the array into which selected records are copied. Releasing the application's array alone does not fulfill the driver's result-consumption contract. Conversely, a displayed list must not depend on temporary application storage that has already been reused.

The scan controller moves from idle through accepted scan and completion to result retrieval, then publishes an application-owned snapshot; unresolved retrieval errors enter recovery rather than another scan.

Original scan-lifecycle diagram. It shows proposed application states, not undocumented Wi-Fi driver internals.

A small display buffer does not cap scan memory

The record-retrieval API uses its number argument in both directions: initially it gives the array's capacity, then it reports the number actually returned. A fixed array can therefore bound the application's copy. For an illustrative twelve-record array, initialize the count to twelve before every retrieval and consume only the returned range after a successful call.

That twelve-record limit is not documented as a limit on how many APs the driver discovers or stores during scanning. Do not subtract twelve records from a RAM budget and declare the whole operation bounded. Dense environments can affect the driver's transient requirements before retrieval happens. The ESP8266 configuration separately defines SCAN_AP_MAX, with a default of 99 and a configurable range of 1–100. Inspect that setting and any project override when defining the supported scan population. It is still not a complete byte-level allocation budget.

Use an application-owned snapshot with an explicit capacity and generation. If a web response or another task reads the current snapshot, publish a completed replacement under a suitable ownership rule. Do not overwrite the same array while a slow response is still serializing it. A double buffer is one possible design, but its additional RAM belongs in the budget too.

One controller should own the entire scan cycle

A useful application sequence is IDLE, SCANNING, RESULTS_PENDING, RETRIEVING, then IDLE. These are proposed states, not SDK enum values. Only the controller starts scans and retrieves their results. A user refresh while busy can set a single pending-refresh flag rather than creating another operation.

Choose one completion model. With a blocking request, the caller waits for completion and must handle timeout or failure. With a nonblocking request, normal progress follows the selected event interface. The legacy event definitions expose SYSTEM_EVENT_SCAN_DONE and its scan_done data. Do not paste ESP32 event-registration code without checking which ESP8266 event interface the project actually initializes.

The scan-event structure includes status, number and scan_id, with scan_id described for blocking scans. That description does not justify assuming an arbitrary application request token will be returned. Serialization and teardown handling remain necessary even if a convenient field appears to resemble a generation counter.

Cancellation needs a terminal state, not an early return

Closing the setup page does not cancel the memory obligation. The device may finish scanning after the browser disappears. Keep the scan owner alive until it has reconciled completion and the documented result-retrieval path, even if nobody still wants to display the list.

Similarly, esp_wifi_scan_stop is a scan-control API; the public contract does not say that a successful stop proves every completed-result allocation was released. Record the stop outcome and completion state. Do not use a presumed cleanup side effect as the only memory argument.

If retrieval fails, retain a recovery state and stop admitting repeated refreshes while resolving it. An unconditional transition to IDLE would hide the unresolved result lifecycle. Qualification should determine supported behavior for cancellation, Wi-Fi shutdown and failed retrieval in the selected binary. Avoid inventing a null-buffer free operation or borrowing esp_wifi_clear_ap_list from another SDK without checking availability.

Measure at equivalent lifecycle boundaries

Take memory observations before the scan, after completion and after successful retrieval. Repeated comparisons should use equivalent idle conditions, with the same application snapshots retained. A low point during discovery and a decreasing post-cleanup baseline are different observations.

The SDK's system API warns that total free heap can exceed the largest allocatable contiguous block. A single free-byte number therefore cannot prove every future request will fit. Keep allocation failures, scan outcomes and retained application objects alongside the memory trace.

Proposed cases include no discovered APs, fewer records than the array capacity, many more records, repeated refresh requests, a disconnected browser and shutdown around completion. Verify that the published list uses only valid returned records, a failed operation does not masquerade as an empty successful list, and the controller has a documented terminal path.

The useful outcome is one accountable scan owner and two explicit memory budgets. Refreshing the screen is the visible feature; releasing driver results and preserving application snapshots are the contracts that keep it repeatable.

Similar Posts