Skip to content
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

Add support for esp32 network driver scanning for access points #1165

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

UncleGrumpy
Copy link
Collaborator

@UncleGrumpy UncleGrumpy commented May 24, 2024

Add network:wifi_scan/0,1 to esp32 network driver, giving the ability for devices configured for "station mode" (or with "station + access point mode") to scan for available access points.

note: these changes depend on PR #1181

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

@UncleGrumpy UncleGrumpy changed the base branch from main to release-0.6 May 24, 2024 00:58
@UncleGrumpy UncleGrumpy force-pushed the esp32_wifi_scan branch 2 times, most recently from 58cf1f8 to cdadc33 Compare June 6, 2024 01:14
@petermm
Copy link
Contributor

petermm commented Jun 11, 2024

I struggled here:

I started out empty example and tried:

    scan = :network.wifi_scan()
    IO.inspect(scan)

This crashes with: {noproc,{gen_server,call,[network,get_config]}} - looks like there is a code path for handling the error but it's never reached

Then I read docs, about having to start the network, and tried:

 config = [
      {:sta,
       [
         {:ssid, "Wokwi-GUEST"},
         {:psk, ""},
         {:connected,
          fn ->
            IO.inspect("network CONNECTED")
            :ok
          end}
       ]}
    ]

    case :network.start(config) do
      {:ok, _pid} ->
        IO.inspect("network started")
      error ->
        :io.put_chars("\nAn error occurred starting network:")
        :erlang.display(error)
    end

    scan = :network.wifi_scan()
    IO.inspect(scan)

which crashed with: wifi:sta_scan: STA is connecting, scan are not allowed!

delaying things and scanning after connection is done made it further to a crash:

CRASH 
======
pid: <0.1.0>

Stacktrace:
undefined

cp: #CP<module: 11, label: 31, offset: 26>

x[0]: error
x[1]: badarg
x[2]: error

Stack 
------

#CP<module: 11, label: 32, offset: 26>
<<"0">>
#CP<module: 11, label: 24, offset: 20>
#CP<module: 10, label: 11, offset: 11>
{0,[{none,[{rssi,undefined},{authmode,undefined},{channel,undefined}]}]}

It also seems that connecting to a network not available, disallows scanning with: wifi:sta_scan: STA is connecting, scan are not allowed!

Would be great seeing an example that could be used in CI eg:

  1. scan with no network configured
  2. connect known good network - scan
  3. config known unavailable network - scan
  4. probably some ap stuff
    etc.

I'll work on getting wokwi CI going so this can be covered by CI.

@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Jun 11, 2024

I did not add an example yet, my plan was to write a demonstration of “roaming” for atomvm_examples. It is a little difficult to use until #1181 is merged, that will allow starting WiFi without immediately starting a connection. Scans will always fail when a connection is in progress, so your application will need to start a scan after obtaining an IP address, or before any connection is started (which is not possible until after #1181).

@UncleGrumpy
Copy link
Collaborator Author

I wish your second example had a stacktrace! You can see the scan result did come back with no networks found… this can happen with default scans (the dwell time is very short by default - and can easily miss networks, in my experience). I think it’s just a bad match in your test that is crashing there.

@petermm
Copy link
Contributor

petermm commented Jun 11, 2024

I wish your second example had a stacktrace! You can see the scan result did come back with no networks found… this can happen with default scans (the dwell time is very short by default - and can easily miss networks, in my experience). I think it’s just a bad match in your test that is crashing there.

ohh the embarrassment lol - it's the IO.inspect call that crashes, will look into it - used to throwing anything at IO.inspect..

Makes sense with the other stuff, suppose some kind of WifiManager GenServer will materialize, while this PR is on driver primitives level..

I'll test #1181 and help land that first..

@UncleGrumpy
Copy link
Collaborator Author

My plan precisely! The PRs I have already submitted will provide all the necessary low level functionality, but I would like to create a higher level network_manager module that can simplify configuration and orchestration.

@UncleGrumpy
Copy link
Collaborator Author

As far as “dwell” time for the scan, I have found between 300-500ms will find all of the available networks, with the default (120ms) I do notice networks being frequently missed.

@UncleGrumpy
Copy link
Collaborator Author

it's the IO.inspect call that crashes, will look into it - used to throwing anything at IO.inspect..

That does sound like a bug, from my limmited understanding of Elixir you should be able to give it just about anything, much like erlang:display/1.

@UncleGrumpy
Copy link
Collaborator Author

{0,[{none,[{rssi,undefined},{authmode,undefined},{channel,undefined}]}]}

You did point me to a problem here, I started testing more boards and realized that even with maximum dwell time no network are being found. I cherry-picked these commits from a different local branch and must have missed something.

@UncleGrumpy UncleGrumpy marked this pull request as draft June 13, 2024 15:10
@UncleGrumpy UncleGrumpy force-pushed the esp32_wifi_scan branch 2 times, most recently from 480b056 to 50a78c7 Compare August 19, 2024 19:18
@UncleGrumpy
Copy link
Collaborator Author

When I split up the working branch I was testing new network features on I mistakenly submitted this PR first, but in order to use the scan function PR #1181 needs to be merged first, and this will need to be rebased.

@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Aug 19, 2024

{0,[{none,[{rssi,undefined},{authmode,undefined},{channel,undefined}]}]}

I did find a bug that would cause the results to be empty if only a single network was found, during most of my testing I had multiple networks, so this didn't get caught. @petermm, thanks for testing and helping me correct this!

@UncleGrumpy UncleGrumpy marked this pull request as ready for review August 19, 2024 19:43
UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Nov 20, 2024
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
@UncleGrumpy UncleGrumpy changed the base branch from release-0.6 to main November 20, 2024 07:05
UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Nov 21, 2024
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
@UncleGrumpy UncleGrumpy marked this pull request as draft November 21, 2024 21:38
UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Dec 22, 2024
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
@UncleGrumpy UncleGrumpy marked this pull request as ready for review December 22, 2024 07:06
@petermm
Copy link
Contributor

petermm commented Dec 22, 2024

could we improve DX for a fast moving dev who calls
:network.wifi_scan([:passive, {:results, 20}, {:dwell, 1500}, :hidden])

with no network started and gets:

CRASH 
======
pid: <0.1.0>

Stacktrace:
undefined

cp: #CP<module: 4, label: 13, offset: 12>

x[0]: exit
x[1]: {noproc,{gen_server,call,[network,{scan,[passive,{results,20},{dwell,1500},hidden]}]}}
x[2]: exit

Stack 
------

26000
{scan,[passive,{results,20},{dwell,1500},hidden]}
network
#CP<module: 4, label: 13, offset: 12>
#CP<module: 0, label: 34, offset: 0>


Mailbox
--------


Monitors
--------


**End Of Crash Report**

Maybe catch that one and return error and log 'network must be started before calling wifi_scan' or similar..

otherwise great, once hidden network bug is solved!

@UncleGrumpy
Copy link
Collaborator Author

Great idea! I refrained from adding explanatory error logs when badarg is returned (i.e. warn that the pin number is out of range, etc...) it keep the driver from increasing in size too much, but this circumstance may be less obvious than a bad value for a pin or an invalid pull direction.

@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Dec 23, 2024

:network.wifi_scan([:passive, {:results, 20}, {:dwell, 1500}, :hidden])

I went one step further and just made that work. If the network has not been started it will be started in sta mode with the managed option, and all other options left to default, this has a downside which is explained in the module docs.

There was already a check for a valid mode once the port is open to the driver, so any other invalid mode will get an error log with an explanation.

UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Dec 23, 2024
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
For example to do a passive scan, including hidden networks, using the longest allowed scan time and showing the maximum number of networks available use the following:

```erlang
{ok, Results} = network:wifi_scan([passive, {results, 20}, {dwell, 1500}, hidden]),
Copy link
Contributor

@petermm petermm Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

believe its show_hidden option? (not hidden)

@petermm
Copy link
Contributor

petermm commented Dec 23, 2024

works great! tested esp32(sim/real), and esp32c3 + esp32s2

The bssid stuff is most excellent, I'll try to get some wifi-geolocation going..

BUT, esp32 real world will consistently stack overflow if results option is higher than 14.

:network.wifi_scan([:passive, {:results, 14}, {:dwell, 1500}, :show_hidden])
|> IO.inspect()

works 100% of time, changing to results,15 (and above) stackoverflows 100%
(esp32 - c3/s2 handles results 20 no sweat)

It seems to happen 100% even in a spot where it only finds around 7 networks..

@petermm
Copy link
Contributor

petermm commented Dec 23, 2024

I (1242) network_driver: WIFI_EVENT_STA_START received.
I (22352) network_driver: Adding BSSID: ac:15:removed
I (22352) network_driver: Adding BSSID: f4:6b:removed
I (22352) network_driver: Adding BSSID: 62:23:removed
I (22362) network_driver: Adding BSSID: f4:6b:removed
I (22362) network_driver: Adding BSSID: 28:9e:removed
I (22372) network_driver: Adding BSSID: ac:3b:removed
I (22372) network_driver: Adding BSSID: 80:ea:removed

***ERROR*** A stack overflow in task pthread has been detected.


Backtrace: 0x40081f36:0x3ffc9e80 0x4008d455:0x3ffc9ea0 0x4008e476:0x3ffc9ec0 0x4008f7af:0x3ffc9f40 0x4008e580:0x3ffc9f60 0x4008e532:0x00000000 |<-CORRUPTED
--- 0x40081f36: panic_abort at /Users/me/esp/esp-idf/components/esp_system/panic.c:463
0x4008d455: esp_system_abort at /Users/me/esp/esp-idf/components/esp_system/port/esp_system_chip.c:92
0x4008e476: vApplicationStackOverflowHook at /Users/me/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:563
0x4008f7af: vTaskSwitchContext at /Users/me/esp/esp-idf/components/freertos/FreeRTOS-Kernel/tasks.c:3701 (discriminator 7)
0x4008e580: _frxt_dispatch at /Users/me/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/portasm.S:451
0x4008e532: _frxt_int_exit at /Users/me/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/portasm.S:246


@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Dec 23, 2024

This is very interesting, and I really appreciate the help testing... I don't know if I have enough outlets and power supplies to create enough networks to see what actually happens when it can find 20. I only pick up two, plus any extra APs hosted on MCUs for testing.

We probably need to adjust the maximum number down for the esp32 classic. I do have esp32 with PSRAM to test with... it might work fine with the lower 4M added to the heap. At the very least the esp32 limit will need to be documented.

@petermm
Copy link
Contributor

petermm commented Dec 23, 2024

s2 works with {results,20} - tested.

Believe you just have to scan for 20 {results, 20}, you don't need to find 20 - if you get what I mean..

but seems there is some stack limit of 14 for esp32, we could also increase some stack most likely.


Example return results:
```erlang
{ok,{13,[{"atomvm_test_ap",[{rssi,-25},{authmode,wpa_wpa2_psk},{channel,6}]},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs updating to new return.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I updated the edocs, but forgot to expand on that here in the Network Programming Guide. Thanks for catching that.

This function is currently only supported on the ESP32 platform.
```

After the network has been configured for STA mode and started, as long as no connection has been initiated or associated, you may scan for available access points using [`network:wifi_scan/0`](./apidocs/erlang/eavmlib/network.md#wifi_scan1) or [`network:wifi_scan/1`](./apidocs/erlang/eavmlib/network.md#wifi_scan1). Scanning for access points will temporarily inhibit other traffic on the access point network if it is in use, but should not cause any active connections to be dropped. With no options, a default 'active' scan, with a per-channel dwell time of 120ms will be used and will return network details for up to 6 access points. The return value for the scan takes the form of a tuple consisting of `{ok, Results}`, where `Results = {FoundAPs [NetworkList]}`. `FoundAPs` may be a number larger than the length of the NetworkList if more access points were discoverd than the number of results requested. The entries in the `NetworkList` take the form of `{SSID, [AP_Properties]}`. `SSID` is the name of the network, and the `AP_Properties` is a proplist with the keys `rssi` for the dBm signal strength of the access point, `authmode` value is the authentication method used by the network, and the `channel` key for obtaining the primary channel for the network.
Copy link
Contributor

@petermm petermm Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reword for new functionality, where a network isn't required to have been started.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I felt like I was forgetting something.

@UncleGrumpy UncleGrumpy force-pushed the esp32_wifi_scan branch 3 times, most recently from 0ba04f5 to 7998285 Compare December 27, 2024 22:47
Corrects the type name db() to the more correct `dbm()`, and adds a brief
edoc explanation for the value.

Signed-off-by: Winford <[email protected]>
@UncleGrumpy UncleGrumpy force-pushed the esp32_wifi_scan branch 3 times, most recently from 5643274 to b071124 Compare December 28, 2024 07:11
@petermm
Copy link
Contributor

petermm commented Dec 28, 2024

Reading the docs, I'm backtracking a bit on using wifi_scan with no network configured.

It seems like it will implicitly bring you into an unwanted state of things, and it will work once, but not twice.

I suggest just returning an error, so nobody ventures down a codepath they can't really recover from.

I'll view this code as primitives and then somebody will make a atomvm_super_wifi lib, that can handle/abstract all of this.

but just my 2 cents, obviously no strong opinions and PR is awesome!

@UncleGrumpy
Copy link
Collaborator Author

UncleGrumpy commented Dec 28, 2024

There are still use cases where this behavior would still be fine... like a device that only needs to discover which network to connect to when it first turned on, and is not expected to change networks unless it is powers off and moved to a new location... or even battery powered devices that choose the correct network when they wake up, and return to a sleeping state between connections. But I did feel I needed to make that more clear in the documentation that developers who choose to use this convenience are making a trade off. Most applications will still want to start the network first with a configuration with callback functions.

@UncleGrumpy
Copy link
Collaborator Author

I'll view this code as primitives and then somebody will make a atomvm_super_wifi lib, that can handle/abstract all of this.

Indeed. Eventually a network_manager module could offer some higher level abstractions. Especially once ethernet support is added, and possibly support for esp-hosted to support esp32 device wifi slaves for the ESP32-P4.

UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Dec 28, 2024
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
UncleGrumpy added a commit to UncleGrumpy/AtomVM that referenced this pull request Jan 4, 2025
For more fine grained connection management in applications the driver can now
be started, without perfoming an inital connection, by the use of the key
`managed` in the STA configuration.

Adds network:sta_connect/0,1 to allow connecting to an access point after the
driver has been started in STA or STA+AP mode. If the function is used without
parameters a connection to the last configured access point will be started.

Adds network:sta_disconnect/0 to disconnect a station from an access point.

The station mode disconnected callback now maintains the default behavior of
reconnecting to the last access point if the connection is lost, but if the
user defines a custom callback the automatic re-connection will not happen,
allowing for users to take advantage of scan results or some other means to
determine when and which access point to associate with.

The combination of the use of a disconnected callback and `managed` mode allow
for the use of `network:wifi_scan/0,1` (PR atomvm#1165), since the wifi must not be
connected to a station when perfoming a scan and the current implementation
always starts a connection immediatly and always reconnects when disconnected.

Signed-off-by: Winford <[email protected]>
Adds the ability for devices configured for sta or ap+sta to scan for available
access points when not currently associated to an AP. With no arguments the
default maximum results returned is 6. All default options can be configured
in the `sta_config()` section of the configuration used to start the network
driver, or set in the configuration parameter of `network:wifi_scan/1`.

Signed-off-by: Winford <[email protected]>
Adds documentation for `network:wifi_scan/0,1` and updates details for
`network:sta_rssi/0`, as well as better organizing the sub sections that these
commands are documented in.

Signed-off-by: Winford <[email protected]>
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