Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/elixir/esp32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ include(BuildElixir)

pack_runnable(Blink Blink estdlib eavmlib exavmlib)
pack_runnable(Ledc_x4 Ledc_x4 estdlib eavmlib exavmlib)
pack_runnable(WifiScan WifiScan estdlib eavmlib exavmlib)
if(NOT (AVM_DISABLE_FP))
pack_runnable(SHT31 SHT31 estdlib eavmlib exavmlib)
endif()
147 changes: 147 additions & 0 deletions examples/elixir/esp32/WifiScan.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#
# This file is part of AtomVM.
#
# Copyright 2025 AtomVM Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
#

defmodule WifiScan do
@moduledoc """
Example demonstrating WiFi network scanning on ESP32.

This example shows how to:
- Initialize the network driver in STA mode
- Scan for available WiFi networks
- Display information about discovered access points
"""

def start() do
IO.puts("Starting WiFi Scan Example...")

case scan_wifi() do
{:ok, access_points} ->
aps =
sort_by_rssi(access_points)
|> filter_hidden()

IO.puts("\n=== Scan Results ===")
count = :erlang.length(aps)
IO.puts("Found #{count} visible network(s)\n")

for ap <- aps do
print_access_point(ap)
end

IO.puts("\nScan completed successfully!")
:ok

{:error, reason} ->
IO.puts("Scan failed: #{inspect(reason)}")
{:error, reason}
end
end

defp sort_by_rssi(access_points) do
# Sort by RSSI (signal strength) descending using lists:sort/2
:lists.sort(
fn {_ssid1, rssi1, _ch1, _auth1, _bssid1}, {_ssid2, rssi2, _ch2, _auth2, _bssid2} ->
rssi1 >= rssi2
end,
access_points
)
end

defp filter_hidden(sorted),
do: Enum.filter(sorted, &(elem(&1, 0) != ""))

defp scan_wifi() do
# Initialize network in STA mode (required for scanning)
# We don't need to connect to a network, just initialize WiFi
config = [sta: [ssid: "", psk: ""]]

IO.puts("Initializing WiFi driver...")

case :network.start(config) do

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-11, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-9, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-14, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-13, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-18, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (cc, 23, ubuntu:20.04, c++, 1.11, 3.20.0, g++)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-10, -O3, 25, 1.11.1)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (cc, 24, ubuntu:20.04, c++, 1.14, 3.23.0, g++)

:network.start/1 is undefined (module :network is not available or is yet to be defined)

Check warning on line 77 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-10, -m32 -O3, 23, ubuntu:20.04, g++-10, 1.11, 3.20.0, -DAVM_CREATE_STACKTRACE...

:network.start/1 is undefined (module :network is not available or is yet to be defined)
{:ok, _pid} ->
IO.puts("WiFi driver initialized. Starting scan...")
# Give the WiFi driver a moment to initialize
Process.sleep(1000)

perform_scan()

{:error, reason} ->
IO.puts("Failed to start network driver: #{inspect(reason)}")
{:error, reason}
end
end

defp perform_scan() do
try do
case :network.scan() do

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-11, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-9, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-14, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-13, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-18, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (cc, 23, ubuntu:20.04, c++, 1.11, 3.20.0, g++)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (clang-10, -O3, 25, 1.11.1)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (cc, 24, ubuntu:20.04, c++, 1.14, 3.23.0, g++)

:network.scan/0 is undefined (module :network is not available or is yet to be defined)

Check warning on line 93 in examples/elixir/esp32/WifiScan.ex

View workflow job for this annotation

GitHub Actions / build-and-test (gcc-10, -m32 -O3, 23, ubuntu:20.04, g++-10, 1.11, 3.20.0, -DAVM_CREATE_STACKTRACE...

:network.scan/0 is undefined (module :network is not available or is yet to be defined)
{:ok, access_points} ->
IO.puts("Scan completed. Processing results...")
{:ok, access_points}

{:error, reason} ->
IO.puts("Scan error: #{inspect(reason)}")
{:error, reason}

other ->
IO.puts("Unexpected scan result: #{inspect(other)}")
{:error, :unexpected_result}
end
catch
kind, error ->
IO.puts("Exception during scan: #{inspect(kind)} - #{inspect(error)}")
{:error, {:exception, kind, error}}
end
end

defp print_access_point(ap) do
case ap do
{ssid, rssi, _channel, _authmode, _bssid} ->
IO.puts(" ")
IO.puts(" SSID: " <> format_ssid(ssid))
IO.puts(" RSSI: " <> format_signal_strength(rssi))

_ ->
IO.puts("N/A")
end
end

defp format_ssid(ssid) when is_binary(ssid) do
if ssid == "" do
"<hidden>"
else
# Just use the binary directly - io:format will handle it
ssid
end
end

defp format_ssid(_), do: "<invalid>"

defp format_signal_strength(rssi) when is_integer(rssi) do
cond do
rssi >= -50 -> "[████] Excellent"
rssi >= -60 -> "[███ ] Good"
rssi >= -70 -> "[██ ] Fair"
rssi >= -80 -> "[█ ] Weak"
true -> "[ ] Very Weak"
end
end

defp format_signal_strength(_), do: "Unknown"
end
1 change: 1 addition & 0 deletions examples/erlang/esp32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ pack_runnable(reformat_nvs reformat_nvs eavmlib)
pack_runnable(uartecho uartecho eavmlib estdlib)
pack_runnable(ledc_example ledc_example eavmlib estdlib)
pack_runnable(epmd_disterl epmd_disterl eavmlib estdlib)
pack_runnable(wifi_scan wifi_scan estdlib eavmlib)
57 changes: 57 additions & 0 deletions examples/erlang/esp32/wifi_scan.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-module(wifi_scan).
-export([start/0]).

start() ->
io:format("Starting WiFi Scan Example...~n"),

%% Configure for STA mode.
%% We provide an empty SSID to initialize STA mode without connecting,
%% which allows us to perform a scan immediately.
Config = [
{sta, [
{ssid, ""},
{psk, ""}
]}
],

io:format("Starting network...~n"),
case network:start(Config) of
{ok, _Pid} ->
io:format("Network started. Waiting a bit...~n"),
timer:sleep(1000), %% Give it a moment to initialize

io:format("Scanning for networks...~n"),
try network:scan() of
{ok, Results} ->
io:format("DEBUG: Got ok response from scan~n"),
try
Len = length(Results),
io:format("Scan complete. Found ~p networks.~n", [Len]),
% Just print the first one to be safe
case Results of
[First | _] ->
io:format("First network: ~p~n", [First]);
[] ->
io:format("No networks found~n")
end
catch
EC:EE:ES ->
io:format("Error processing results: ~p:~p~nStack: ~p~n", [EC, EE, ES])
end;
{error, Reason} ->
io:format("Scan failed: ~p~n", [Reason]);
Other ->
io:format("Scan returned unexpected: ~p~n", [Other])
catch
Class:Error:Stack ->
io:format("Scan crashed: ~p:~p~nStack: ~p~n", [Class, Error, Stack])
end,

io:format("Stopping network...~n"),
% network:stop();
io:format("Done.~n"),
ok;
Error ->
io:format("Failed to start network: ~p~n", [Error]),
{error, Error}
end.
48 changes: 45 additions & 3 deletions 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,
scan/0
]).
-export([start/1, start_link/1, stop/0]).
-export([
Expand Down Expand Up @@ -158,7 +159,8 @@
port :: port(),
ref :: reference(),
sta_ip_info :: ip_info(),
mdns :: pid() | undefined
mdns :: pid() | undefined,
scan_from :: {pid(), term()} | undefined
}).

%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -322,7 +324,7 @@ sta_rssi() ->
init(Config) ->
Port = get_port(),
Ref = make_ref(),
{ok, #state{config = Config, port = Port, ref = Ref}, {continue, start_port}}.
{ok, #state{config = Config, port = Port, ref = Ref, scan_from = undefined}, {continue, start_port}}.

handle_continue(start_port, #state{config = Config, port = Port, ref = Ref} = State) ->
Port ! {self(), Ref, {start, Config}},
Expand All @@ -334,6 +336,19 @@ handle_continue(start_port, #state{config = Config, port = Port, ref = Ref} = St
end.

%% @hidden
handle_call(scan, From, #state{port = Port, ref = Ref} = State) ->
Port ! {self(), Ref, scan},
receive
{Ref, ok} ->
%% Scan started successfully, store the caller and wait for results in handle_info
{noreply, State#state{scan_from = From}};
{Ref, Error} ->
io:format("network:scan received initial error: ~p~n", [Error]),
{reply, Error, State}
after 5000 ->
io:format("ERROR: Timeout waiting for scan start~n"),
{reply, {error, timeout}, State}
end;
handle_call(_Msg, _From, State) ->
{reply, {error, unknown_message}, State}.

Expand Down Expand Up @@ -373,6 +388,29 @@ handle_info(
handle_info({Ref, {sntp_sync, TimeVal}} = _Msg, #state{ref = Ref, config = Config} = State) ->
maybe_sntp_sync_callback(Config, TimeVal),
{noreply, State};
handle_info({Ref, {scan_results, Results}} = _Msg, #state{ref = Ref, scan_from = From} = State) ->
try
case From of
undefined ->
io:format("Received scan_results but no caller waiting~n"),
{noreply, State};
_ ->
Reply = {ok, Results},
gen_server:reply(From, Reply),
{noreply, State#state{scan_from = undefined}}
end
catch
Class:Error:Stacktrace ->
io:format("ERROR in handle_info scan_results: ~p:~p~nStacktrace: ~p~n", [Class, Error, Stacktrace]),
case From of
undefined -> ok;
_ ->
try gen_server:reply(From, {error, internal_error})
catch _:_ -> io:format("Failed to send error reply~n")
end
end,
{noreply, State#state{scan_from = undefined}}
end;
handle_info(Msg, State) ->
io:format("Received spurious message ~p~n", [Msg]),
{noreply, State}.
Expand All @@ -398,6 +436,10 @@ wait_for_port_close(PortMonitor, Port) ->
{error, timeout}
end.

-spec scan() -> {ok, list()} | {error, Reason :: term()}.
scan() ->
gen_server:call(?SERVER, scan, infinity).

%%
%% Internal operations
%%
Expand Down
2 changes: 1 addition & 1 deletion libs/estdlib/src/code_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ load(Module) ->
%% Currently, the only live stream backend that needs estimate is mmap
%% and it should not be passed a value too large to not slow down valgrind too
%% much during tests. A factor of 32 is more than enough, the largest observed
%% ration is 21 for aarch64 and Elixir code. Also apply a minimum of 128 kb
%% ratio is 21 for aarch64 and Elixir code. Also apply a minimum of 128 kb
%% which shouldn't affect valgrind too much.
%% jit_stream_flash and jit_stream_binary ignore the size parameter.
%% @return size in bytes
Expand Down
Loading
Loading