-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Introduce Non-blocking wifi scan for ESP32 #7526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
* Adds SYSTEM_EVENT_SCAN_DONE handling to event_handler * Extracts AP record retrieval to new function esp_get_scan_results * Adds boolean argument to esp_scan, defaulting to blocking scan * Non-blocking scan requires calling new function get_scan_results * Adds check for zero records retrieved Signed-off-by: Matt Arcidy <marcidy@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @marcidy !
I think this approach makes sense but I wonder if it would be better to try and do something callback-oriented instead:
def on_scan_complete(sta_if, results):
...
sta.scan() # blocking scan
sta.scan(on_scan_complete) # non-blocking
this would be easier to integrate with asyncio (i.e. just use a asyncio.Event to turn this into await async_sta.scan()
).
Roughly to implement this you'd need:
- Add a mp_obj_t pointing to the
callback
. This would need to be a root pointer. - Set the callback in
esp_scan()
- Use mp_sched_schedule in
event_handler
to schedule a method that would then callesp_wifi_scan_get_ap_num
etc and then call the registered callback. (Note the two-level thing here where you schedule an intermediate function rather than the user callback directly, you need to get into scheduler context before you can construct the results list).
Thanks for the review, @jimmo ! Makes sense, ill give it a shot in the next few days. I'm not familiar with the term 'root pointer', and google isn't helpful. Is that a pointer declared outside any other scope in the file (e.g. at the top)? Might need a bit of guidance later, but I think I'm clear on what's needed to implement this approach. |
Because MicroPython uses a mark & sweep garbage collector, it can only find things that are reachable from the "roots". When you assign the callback to a global C variable, the GC won't know about it, and therefore might collect the callback function. See mpstate.h (and gc_collect_start in gc.c). In particular you will need to add it to the port-specific root pointers (see MICROPY_PORT_ROOT_POINTERS in esp32/mpconfigport.h). |
got it, that's perfectly clear, thanks! |
Adds a new root pointer for a user-supplied callback function for a non-blocking AP scan. Signed-off-by: Matt Arcidy <marcidy@gmail.com>
1755596
to
8e0fd5f
Compare
@jimmo My latest commits have a callback working, but I have some questions before finishing:
def on_scan_complete(sta_if, results)
178 mp_sched_schedule(MP_OBJ_FROM_PTR(&esp_wifi_scan_cb_obj), mp_const_none); This was since mp_sched_schedule needs instances, and I decided to use a function rather than a class (e.g. like edit: I found instances similar to this in other ports, so it's not as strange as i first thought.
Thanks! example: >>> import network
>>> sta = network.WLAN(network.STA_IF)
>>> sta.active(1)
True
>>> sta.scan()
# scan results
>>>
>>> def cb(results):
... print("Callback!")
... for ap in results:
... print(ap)
...
...
...
>>> sta.scan(cb);print("not blocking!")
not blocking!
>>> Callback!
# scan results |
* Add optional callback to esp_scan to triggers a non-blocking scan * Schedule AP results retrieval when a SCAN_DONE event is emitted * Schedule user callback and pass it the results Signed-off-by: Matt Arcidy <marcidy@gmail.com>
8e0fd5f
to
89caebb
Compare
To use 2 args with a user-supplied callback, I made use of I'm unclear what to do with an async.Event to see if it will work with asyncio, i haven't used Events in a way that it's clear to me. With the syntax you showed, I would expect scan to return something awaitable. I'm also a bit unclear on initializing the callback in memset(&MP_STATE_PORT(esp_wifi_scan_cb_handler), 0, sizeof(MP_STATE_PORT(esp_wifi_scan_cb_handler))); example: >>> def cb(sta_if, results):
... ssid = `testssid`
... pword = 'testpword'
... for ap in results:
... if ap[0].decode('ascii') == ssid:
... sta_if.connect(ssid, pword)
...
...
...
>>>
>>> sta.isconnected()
False
>>> sta.scan(cb);print("non-blocking")
non-blocking
>>> sta.isconnected()
True |
Just checking in on the question, and wow awesome updates! Can't wait to give this a try, especially with asyncio compatbility. |
I realized that this PR is not merged, but it doesn't matter that much since we can still combine asyncio and call this wifi.scan() in _thread |
Add front buttons as D0/1/2, matching the silk
I think about this PR periodically. Would love to see callback based non-blocking wifi scan for esp32. Any chance of brining this one back into the fold? |
This is an automated heads-up that we've just merged a Pull Request See #13763 A search suggests this PR might apply the STATIC macro to some C code. If it Although this is an automated message, feel free to @-reply to me directly if |
Scan for APs in the background and retrieve ap records with a new function call.
scan
run without any arguments, or asscan(True)
is backwards compatible and returns a list of AP records.scan(False)
runs a non-blocking scan, taking advantage of theblocking
argument toesp_wifi_scan_start
.When the scan is complete, the records are available by calling
wlan.get_scan_results()
.When running a non-blocking scan, the results are stored in the wifi driver's dynamic memory. A call to
esp_wifi_scan_get_ap_records
is requires to free that memory.While testing, i noticed that other calls to the driver (e.g. connect, deactivate, etc). interrupt the scan and emit a SYSTEM_EVENT_SCAN_DONE event. These appear to clear out the memory also, meaning no records are retrieved. I didn't add any extra behavior to free the driver's memory as the IDF seems to handle it as long as the driver's state is changed. e.g., the records don't hang around in memory if a connect is run after a scan, but before a call to
esp_wifi_scan_get_ap_records
.wlan.get_scan_results
has 2 behaviors:I added a check against the record size as there are cases when a SCAN_DONE event is emitted but there are zero records returned, specifically when a scan was interrupted.
I didn't add anything to esp_state for scanning / scan_done since it wasn't strictly necessary, as the driver manages it's memory correctly imo. If the user want's to check if there are scan results, just call wlan.get_scan_results().
to test:
To trigger edge cases which clear records: