Skip to content

Commit

Permalink
Add network:wifi_scan/0,1 to esp32 network driver
Browse files Browse the repository at this point in the history
Adds the ability for devices configured for sta or ap+sta to scan for available access points. 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 <winford@object.stream>
  • Loading branch information
UncleGrumpy committed Aug 19, 2024
1 parent a85b928 commit 8bf20d5
Show file tree
Hide file tree
Showing 3 changed files with 401 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.6.5] - Unreleased

### Added

- Add `network:wifi_scan/0,1` to ESP32 network driver to scan available APs when in station (or sta+ap) mode.

## [0.6.4] - 2024-08-18

### Added
Expand Down
133 changes: 132 additions & 1 deletion libs/eavmlib/src/network.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
-export([
wait_for_sta/0, wait_for_sta/1, wait_for_sta/2,
wait_for_ap/0, wait_for_ap/1, wait_for_ap/2,
sta_rssi/0
sta_rssi/0,
wifi_scan/0, wifi_scan/1
]).
-export([start/1, start_link/1, stop/0]).
-export([
Expand All @@ -37,6 +38,10 @@
]).

-define(SERVER, ?MODULE).
% These values ate used to calculate the gen_server:call/3 timeout.
-define(DEVICE_TOTAL_CHANNELS, 14).
-define(GEN_RESPONSE_MS, 5000).
-define(MAX_SHORT_DWELL, 320).

-type octet() :: 0..255.
-type ipv4_address() :: {octet(), octet(), octet(), octet()}.
Expand All @@ -52,10 +57,16 @@
-type sta_connected_config() :: {connected, fun(() -> term())}.
-type sta_disconnected_config() :: {disconnected, fun(() -> term())}.
-type sta_got_ip_config() :: {got_ip, fun((ip_info()) -> term())}.
-type sta_scan_config() ::
{default_scan_results, 1..20}
| {scan_dwell_ms, 1..1500}
| scan_show_hidden
| scan_passive.
-type sta_config_property() ::
ssid_config()
| psk_config()
| dhcp_hostname_config()
| sta_scan_config()
| sta_connected_config()
| sta_disconnected_config()
| sta_got_ip_config().
Expand Down Expand Up @@ -148,6 +159,37 @@
%% a powerful signal this can be a positive number. A level of 0 dBm corresponds to the power of 1
%% milliwatt. A 10 dBm decrease in level is equivalent to a ten-fold decrease in signal power.

-type scan_options() ::
{results, 1..20}
| {dwell, 1..1500}
| show_hidden
| passive.
%% The `results' key is used to set the maximum number of networks returned in the
%% networks list, and the `dwell' is used to set the dwell time (in milliseconds)
%% spent on each channel. The option `show_hidden' will also include hidden networks
%% in the scan results. Default options are: `[{results, 6}, {dwell, 120}]', if
%% `passive' is used the default dwell time per channel is 360 ms.

-type auth_type() ::
open
| wep
| wpa_psk
| wpa2_psk
| wpa_wpa2_psk
| eap
| wpa3_psk
| wpa2_wpa3_psk
| wapi
| owe
| wpa3_enterprise_192
| wpa3_ext_psk
| wpa3_ext_psk_mixed.

-type network_properties() :: [
{rssi, dbm()} | [{authmode, auth_type()} | [{channel, wifi_channel()}]]
].
%% A proplist of network properties with the keys: `rssi', `authmode' and `channel'

-record(state, {
config :: network_config(),
port :: port(),
Expand Down Expand Up @@ -324,6 +366,75 @@ sta_rssi() ->
Other -> {error, Other}
end.

%% @param Options is a list of `scan_options()'
%% @returns Scan result tuple, or {error, Reason} if a failure occurred.
%%
%% @doc Scan for available WiFi networks.
%%
%% The network must first be started in sta or sta+ap mode before scanning for access points. While
%% a scan is in progress network traffic will be inhibited, but should not cause an active connection
%% to be lost. Espressif's documentation recommends not exceeding 1500 ms per-chanel scan times or
%% network connections may be lost, this is enforced as a hard limit. The return is a tuple
%% `{ok, Results}', where Results is a tuple with the number of discovered networks and a list of
%% networks, which may be shorter than the size of the discovered networks if a smaller `MaxAPs' was
%% used. The network tuples in the list consist of network name and a proplist of network information:
%%
%% `{ok, {NumberResults, [{SSID, [{rssi, DBm}, {authmode, Mode}, {channel, Number}]}, ...]}}'
%%
%% Example:
%% <pre>
%% ...
%% {ok, {Number, Results}} = wifi_scan(10),
%% io:format("Number of networks discovered: ~p~n", [Number])
%% lists:foreach(fun(Network = {SSID, [{rssi, DBm}, {authmode, Mode}, {channel, Number}]}) ->
%% io:format("Network: ~p, signal ~p dBm, Security: ~p, channel ~p~n",
%% [SSID, DBm, Mode, Number]) end,
%% Results).
%% </pre>
%%
%% For convenience `network_wifi_scan/0' may be used to scan with default options.
%%
%% Note: If a long dwell time is used, the return time for this function can be considerably longer
%% than the default gen_server timeout, especially when performing a passive scan. Passive scans
%% always use the full dwell time for each channel, active scans with a dwell time of more than 240
%% milliseconds will have a minimum dwell of 1/2 the maximum dwell time set by the `dwell' option.
%% The timeout for these longer scans is determined by the following:
%% Timeout = (dwell * 14) + 5000.
%% That is the global maximum wifi channels multiplied by the dwell time with an additional
%% gen_server default timeout period added. The actual number of channels scanned will be
%% determined by the country code set by the devices connection to an access point. This is 13
%% channels most of the world, 11 for North America, and 14 in some parts of Asia.
%%
%% The default options may be configured by adding `sta_scan_config()' options to the
%% `sta_config()'.
%%
%% Warning: This feature is not yet available on the rp2040 platform.
%%
%% @end
%%-----------------------------------------------------------------------------
-spec wifi_scan([Options :: scan_options(), ...]) ->
{ok, {NetworksDiscovered :: 0..20, [{SSID :: string(), [ApInfo :: network_properties()]}, ...]}}
| {error, Reason :: term()}.
wifi_scan(Options) ->
Dwell = proplists:get_value(dwell, Options),
case Dwell of
undefined ->
gen_server:call(?SERVER, {scan, Options});
Millis when Millis =< ?MAX_SHORT_DWELL ->
gen_server:call(?SERVER, {scan, Options});
ChanDwellMs ->
Timeout = (ChanDwellMs * ?DEVICE_TOTAL_CHANNELS) + ?GEN_RESPONSE_MS,
gen_server:call(?SERVER, {scan, Options}, Timeout)
end.

wifi_scan() ->
Config = gen_server:call(?SERVER, get_config),
Results = proplists:get_value(default_scan_results, proplists:get_value(sta, Config), 6),
Dwell = proplists:get_value(scan_dwell_ms, proplists:get_value(sta, Config), 120),
Hidden = proplists:get_value(scan_show_hidden, proplists:get_value(sta, Config), false),
Passive = proplists:get_value(scan_passive, proplists:get_value(sta, Config), false),
wifi_scan([{results, Results}, {dwell, Dwell}, {show_hidden, Hidden}, {passive, Passive}]).

%%
%% gen_server callbacks
%%
Expand All @@ -338,6 +449,12 @@ handle_call(start, From, #state{config = Config} = State) ->
Ref = make_ref(),
Port ! {self(), Ref, {start, Config}},
wait_start_reply(Ref, From, Port, State);
handle_call({scan, ScanOpts}, From, State) ->
Ref = make_ref(),
network_port ! {self(), Ref, {scan, ScanOpts}},
wait_scan_results(Ref, From, State#state{ref = Ref});
handle_call(get_config, _From, #state{config = Config} = State) ->
{reply, Config, State};
handle_call(_Msg, _From, State) ->
{reply, {error, unknown_message}, State}.

Expand All @@ -352,6 +469,20 @@ wait_start_reply(Ref, From, Port, State) ->
{stop, {start_failed, Reason}, ER, State}
end.

%% @private
wait_scan_results(Ref, From, State) ->
receive
{Ref, {error, _Reason} = ER} ->
gen_server:reply(From, ER),
{noreply, State#state{ref = Ref}};
{Ref, Results} ->
gen_server:reply(From, Results),
{noreply, State#state{ref = Ref}};
Any ->
gen_server:reply(From, Any),
{noreply, State#state{ref = Ref}}
end.

%% @hidden
handle_cast(_Msg, State) ->
{noreply, State}.
Expand Down
Loading

0 comments on commit 8bf20d5

Please sign in to comment.