Skip to content

Commit

Permalink
AP-STA mode (#299)
Browse files Browse the repository at this point in the history
* Configure AP-STA mode

* Split up link state waking into two branches

* Add WifiMode::ApSta

* Simplify mode setup

* Deduce wifi mode from config if available

* Rework internals to support AP-STA mode

* Add `new_ap_sta` constructor

* Demote Rx token warning

* Allow using is_sta_enabled and is_ap_enabled

* Disallow certain config changes

* Update readme

* Return capability based on configuration

* Add a default-config constructor for AP-STA

* Shorten unsafe block

* Typestate devices

* Add examples
  • Loading branch information
bugadani authored Nov 6, 2023
1 parent 9e44fe3 commit 5166089
Show file tree
Hide file tree
Showing 17 changed files with 1,200 additions and 354 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ jobs:
run: cd esp-wifi && cargo b${{ matrix.chip }} --features=async,wifi,esp-now,embassy-net,log,${{ matrix.chip }}-hal/embassy-time-timg0
- name: build (common features + defmt)
run: cd esp-wifi && cargo b${{ matrix.chip }} --no-default-features --features=async,wifi,esp-now,embassy-net,defmt,${{ matrix.chip }}-hal/embassy-time-timg0
- name: build (access_point)
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=access_point --features=wifi
- name: build (access_point_with_sta)
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=access_point_with_sta --features=wifi
- name: build (dhcp)
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=dhcp --features=wifi
- name: build (bench)
Expand All @@ -81,6 +85,8 @@ jobs:
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_bench --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0
- name: build (embassy_access_point)
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_access_point --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0
- name: build (embassy_access_point_with_sta)
run: cd esp-wifi && cargo b${{ matrix.chip }} --release --example=embassy_access_point_with_sta --features=async,wifi,embassy-net,${{ matrix.chip }}-hal/embassy-time-timg0

- name: build (common features + ble)
if: ${{ matrix.chip != 'esp32s2' }}
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ See [Examples] for details.
## Missing / To be done

- Make CoEx work on ESP32 (it kind of works when commenting out setting the country in wifi_start, probably some mis-compilation since it then crashes in a totally different code path)
- Combined SoftAP/STA mode
- Support for non-open SoftAP
- Direct-boot mode isn't supported

Expand Down
22 changes: 22 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ cargo esp32c3 --release ...

`cargo $CHIP --example access_point --release --features "embedded-svc,wifi"`

### access_point_with_sta

- set SSID and PASSWORD env variable
- gets an ip address via DHCP
- creates an open access-point with SSID `esp-wifi`
- you can connect to it using a static IP in range 192.168.2.2 .. 192.168.2.255, gateway 192.168.2.1
- open http://192.168.2.1:8080/ in your browser - the example will perform an HTTP get request to some "random" server
- on Android you might need to choose _Keep Accesspoint_ when it tells you the WiFi has no internet connection, Chrome might not want to load the URL - you can use a shell and try `curl` and `ping`

`cargo $CHIP --example access_point_with_sta --release --features "embedded-svc,wifi"`

### embassy_access_point

- creates an open access-point with SSID `esp-wifi`
Expand All @@ -100,6 +111,17 @@ cargo esp32c3 --release ...

`cargo $CHIP --example embassy_access_point --release --features "async,embedded-svc,wifi,embassy-net"`

### embassy_access_point_with_sta

- set SSID and PASSWORD env variable
- gets an ip address via DHCP
- creates an open access-point with SSID `esp-wifi`
- you can connect to it using a static IP in range 192.168.2.2 .. 192.168.2.255, gateway 192.168.2.1
- open http://192.168.2.1:8080/ in your browser - the example will perform an HTTP get request to some "random" server
- on Android you might need to choose _Keep Accesspoint_ when it tells you the WiFi has no internet connection, Chrome might not want to load the URL - you can use a shell and try `curl` and `ping`

`cargo $CHIP --example embassy_access_point_with_sta --release --features "async,embedded-svc,wifi,embassy-net"`

## Benchmarking

A prerequisite to running the benchmark examples is to run the benchmark server on your local machine. Simply run the following commands to do so.
Expand Down
4 changes: 2 additions & 2 deletions esp-wifi/examples/access_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use esp_backtrace as _;
use esp_println::{print, println};
use esp_wifi::initialize;
use esp_wifi::wifi::utils::create_network_interface;
use esp_wifi::wifi::WifiMode;
use esp_wifi::wifi::WifiApDevice;
use esp_wifi::wifi_interface::WifiStack;
use esp_wifi::{current_millis, EspWifiInitFor};
use hal::clock::ClockControl;
Expand Down Expand Up @@ -48,7 +48,7 @@ fn main() -> ! {
let wifi = peripherals.WIFI;
let mut socket_set_entries: [SocketStorage; 3] = Default::default();
let (iface, device, mut controller, sockets) =
create_network_interface(&init, wifi, WifiMode::Ap, &mut socket_set_entries).unwrap();
create_network_interface(&init, wifi, WifiApDevice, &mut socket_set_entries).unwrap();
let mut wifi_stack = WifiStack::new(iface, device, sockets, current_millis);

let client_config = Configuration::AccessPoint(AccessPointConfiguration {
Expand Down
224 changes: 224 additions & 0 deletions esp-wifi/examples/access_point_with_sta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#![no_std]
#![no_main]

#[path = "../../examples-util/util.rs"]
mod examples_util;
use examples_util::hal;

use embedded_io::*;
use embedded_svc::ipv4::Interface;
use embedded_svc::wifi::{AccessPointConfiguration, ClientConfiguration, Configuration, Wifi};

use esp_backtrace as _;
use esp_println::{print, println};
use esp_wifi::initialize;
use esp_wifi::wifi::utils::{create_ap_sta_network_interface, ApStaInterface};
use esp_wifi::wifi_interface::WifiStack;
use esp_wifi::{current_millis, EspWifiInitFor};
use hal::clock::ClockControl;
use hal::Rng;
use hal::{peripherals::Peripherals, prelude::*};

use smoltcp::iface::SocketStorage;
use smoltcp::wire::IpAddress;
use smoltcp::wire::Ipv4Address;

const SSID: &str = env!("SSID");
const PASSWORD: &str = env!("PASSWORD");

#[entry]
fn main() -> ! {
#[cfg(feature = "log")]
esp_println::logger::init_logger(log::LevelFilter::Info);

let peripherals = Peripherals::take();

let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();

#[cfg(target_arch = "xtensa")]
let timer = hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks).timer0;
#[cfg(target_arch = "riscv32")]
let timer = hal::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0;
let init = initialize(
EspWifiInitFor::Wifi,
timer,
Rng::new(peripherals.RNG),
system.radio_clock_control,
&clocks,
)
.unwrap();

let wifi = peripherals.WIFI;

let mut ap_socket_set_entries: [SocketStorage; 3] = Default::default();
let mut sta_socket_set_entries: [SocketStorage; 3] = Default::default();

let ApStaInterface {
ap_interface,
sta_interface,
ap_device,
sta_device,
mut controller,
ap_socket_set,
sta_socket_set,
} = create_ap_sta_network_interface(
&init,
wifi,
&mut ap_socket_set_entries,
&mut sta_socket_set_entries,
)
.unwrap();

let mut wifi_ap_stack = WifiStack::new(ap_interface, ap_device, ap_socket_set, current_millis);
let wifi_sta_stack = WifiStack::new(sta_interface, sta_device, sta_socket_set, current_millis);

let client_config = Configuration::Mixed(
ClientConfiguration {
ssid: SSID.into(),
password: PASSWORD.into(),
..Default::default()
},
AccessPointConfiguration {
ssid: "esp-wifi".into(),
..Default::default()
},
);
let res = controller.set_configuration(&client_config);
println!("wifi_set_configuration returned {:?}", res);

controller.start().unwrap();
println!("is wifi started: {:?}", controller.is_started());

println!("{:?}", controller.get_capabilities());

wifi_ap_stack
.set_iface_configuration(&embedded_svc::ipv4::Configuration::Client(
embedded_svc::ipv4::ClientConfiguration::Fixed(embedded_svc::ipv4::ClientSettings {
ip: embedded_svc::ipv4::Ipv4Addr::from(parse_ip("192.168.2.1")),
subnet: embedded_svc::ipv4::Subnet {
gateway: embedded_svc::ipv4::Ipv4Addr::from(parse_ip("192.168.2.1")),
mask: embedded_svc::ipv4::Mask(24),
},
dns: None,
secondary_dns: None,
}),
))
.unwrap();

println!("wifi_connect {:?}", controller.connect());

// wait for STA getting an ip address
println!("Wait to get an ip address");
loop {
wifi_sta_stack.work();

if wifi_sta_stack.is_iface_up() {
println!("got ip {:?}", wifi_sta_stack.get_ip_info());
break;
}
}

println!("Start busy loop on main. Connect to the AP `esp-wifi` and point your browser to http://192.168.2.1:8080/");
println!("Use a static IP in the range 192.168.2.2 .. 192.168.2.255, use gateway 192.168.2.1");

let mut rx_buffer = [0u8; 1536];
let mut tx_buffer = [0u8; 1536];
let mut ap_socket = wifi_ap_stack.get_socket(&mut rx_buffer, &mut tx_buffer);

let mut sta_rx_buffer = [0u8; 1536];
let mut sta_tx_buffer = [0u8; 1536];
let mut sta_socket = wifi_sta_stack.get_socket(&mut sta_rx_buffer, &mut sta_tx_buffer);

ap_socket.listen(8080).unwrap();

loop {
ap_socket.work();

if !ap_socket.is_open() {
ap_socket.listen(8080).unwrap();
}

if ap_socket.is_connected() {
println!("Connected");

let mut time_out = false;
let wait_end = current_millis() + 20 * 1000;
let mut buffer = [0u8; 1024];
let mut pos = 0;
loop {
if let Ok(len) = ap_socket.read(&mut buffer[pos..]) {
let to_print =
unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) };

if to_print.contains("\r\n\r\n") {
print!("{}", to_print);
println!();
break;
}

pos += len;
} else {
break;
}

if current_millis() > wait_end {
println!("Timeout");
time_out = true;
break;
}
}

if !time_out {
println!("Making HTTP request");
sta_socket.work();

sta_socket
.open(IpAddress::Ipv4(Ipv4Address::new(142, 250, 185, 115)), 80)
.unwrap();

sta_socket
.write(b"GET / HTTP/1.0\r\nHost: www.mobile-j.de\r\n\r\n")
.unwrap();
sta_socket.flush().unwrap();

let wait_end = current_millis() + 20 * 1000;
loop {
let mut buffer = [0u8; 512];
if let Ok(len) = sta_socket.read(&mut buffer) {
ap_socket.write_all(&buffer[..len]).unwrap();
ap_socket.flush().unwrap();
} else {
break;
}

if current_millis() > wait_end {
println!("Timeout");
break;
}
}
println!();

sta_socket.disconnect();
}

ap_socket.close();

println!("Done\n");
println!();
}

let wait_end = current_millis() + 5 * 1000;
while current_millis() < wait_end {
ap_socket.work();
}
}
}

fn parse_ip(ip: &str) -> [u8; 4] {
let mut result = [0u8; 4];
for (idx, octet) in ip.split(".").into_iter().enumerate() {
result[idx] = u8::from_str_radix(octet, 10).unwrap();
}
result
}
10 changes: 5 additions & 5 deletions esp-wifi/examples/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wi
use esp_backtrace as _;
use esp_println::println;
use esp_wifi::wifi::utils::create_network_interface;
use esp_wifi::wifi::{WifiError, WifiMode};
use esp_wifi::wifi::{WifiError, WifiStaDevice};
use esp_wifi::wifi_interface::WifiStack;
use esp_wifi::{current_millis, initialize, EspWifiInitFor};
use hal::clock::ClockControl;
Expand Down Expand Up @@ -62,7 +62,7 @@ fn main() -> ! {
let wifi = peripherals.WIFI;
let mut socket_set_entries: [SocketStorage; 3] = Default::default();
let (iface, device, mut controller, sockets) =
create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap();
create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();
let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);

let client_config = Configuration::Client(ClientConfiguration {
Expand Down Expand Up @@ -137,7 +137,7 @@ fn main() -> ! {

fn test_download<'a>(
server_address: Ipv4Address,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>,
) {
println!("Testing download...");
socket.work();
Expand Down Expand Up @@ -171,7 +171,7 @@ fn test_download<'a>(

fn test_upload<'a>(
server_address: Ipv4Address,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>,
) {
println!("Testing upload...");
socket.work();
Expand Down Expand Up @@ -205,7 +205,7 @@ fn test_upload<'a>(

fn test_upload_download<'a>(
server_address: Ipv4Address,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a>,
socket: &mut esp_wifi::wifi_interface::Socket<'a, 'a, WifiStaDevice>,
) {
println!("Testing upload+download...");
socket.work();
Expand Down
4 changes: 2 additions & 2 deletions esp-wifi/examples/coex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use bleps::{
};

use esp_wifi::{
ble::controller::BleConnector, current_millis, wifi::WifiMode, wifi_interface::WifiStack,
ble::controller::BleConnector, current_millis, wifi::WifiStaDevice, wifi_interface::WifiStack,
EspWifiInitFor,
};

Expand Down Expand Up @@ -61,7 +61,7 @@ fn main() -> ! {

let mut socket_set_entries: [SocketStorage; 3] = Default::default();
let (iface, device, mut controller, sockets) =
create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap();
create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();
let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);

let client_config = Configuration::Client(ClientConfiguration {
Expand Down
4 changes: 2 additions & 2 deletions esp-wifi/examples/dhcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use embedded_svc::wifi::{AccessPointInfo, ClientConfiguration, Configuration, Wi
use esp_backtrace as _;
use esp_println::{print, println};
use esp_wifi::wifi::utils::create_network_interface;
use esp_wifi::wifi::{WifiError, WifiMode};
use esp_wifi::wifi::{WifiError, WifiStaDevice};
use esp_wifi::wifi_interface::WifiStack;
use esp_wifi::{current_millis, initialize, EspWifiInitFor};
use hal::clock::ClockControl;
Expand Down Expand Up @@ -51,7 +51,7 @@ fn main() -> ! {
let wifi = peripherals.WIFI;
let mut socket_set_entries: [SocketStorage; 3] = Default::default();
let (iface, device, mut controller, sockets) =
create_network_interface(&init, wifi, WifiMode::Sta, &mut socket_set_entries).unwrap();
create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();
let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);

let client_config = Configuration::Client(ClientConfiguration {
Expand Down
Loading

0 comments on commit 5166089

Please sign in to comment.