use core::net::Ipv4Addr;
use core::net::SocketAddrV4;
use edge_dhcp::io::{self, DEFAULT_SERVER_PORT};
use edge_dhcp::server::{Server, ServerOptions};
use edge_nal::UdpBind;
use edge_nal_embassy::{Udp, UdpBuffers};
use embassy_executor::Spawner;
use embassy_net::DhcpConfig;
use embassy_net::tcp::TcpSocket;
use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_time::{Duration, Timer};
use esp_hal::peripherals::WIFI;
use esp_hal::rng::Rng;
use esp_radio::wifi::{
AuthenticationMethodConfig, BandMode as RadioBandMode, Config as RadioConfig, ControllerConfig,
Interface, Password, Ssid, WifiController, ap::AccessPointConfig, ap::EventInfo,
sta::StationConfig,
};
use log::info;
use log::{debug, error, warn};
use ssh_stamp::settings::STATION_MODE_MAX_RETRY_SECONDS;
use ssh_stamp_hal::{
BandMode, HalError, NetworkProviderHal, WifiApConfigStatic, WifiError, WifiHal,
};
use static_cell::StaticCell;
extern crate alloc;
pub struct EspWifi {
spawner: Spawner,
wifi_peri: Option<WIFI<'static>>,
rng: Rng,
ap_config: Option<WifiApConfigStatic>,
gateway: Ipv4Addr,
}
impl EspWifi {
#[must_use]
pub fn new(spawner: Spawner, wifi_peri: WIFI<'static>, rng: Rng, gateway: Ipv4Addr) -> Self {
Self {
spawner,
wifi_peri: Some(wifi_peri),
rng,
ap_config: None,
gateway,
}
}
}
impl WifiHal for EspWifi {
fn configure_ap(&mut self, config: WifiApConfigStatic) -> Result<(), HalError> {
self.ap_config = Some(config);
Ok(())
}
}
impl NetworkProviderHal for EspWifi {
async fn bring_up(&mut self) -> Result<Stack<'static>, HalError> {
static RESOURCES_CELL: StaticCell<StackResources<3>> = StaticCell::new();
static STA_SSID_CELL: StaticCell<heapless::String<32>> = StaticCell::new();
let ap_config = self
.ap_config
.clone()
.ok_or(HalError::Wifi(WifiError::Initialization))?;
let wifi_peri = self
.wifi_peri
.take()
.ok_or(HalError::Wifi(WifiError::Initialization))?;
esp_hal::efuse::override_mac_address(esp_hal::efuse::MacAddress::new_eui48(ap_config.mac))
.map_err(|_| HalError::Wifi(WifiError::Initialization))?;
let sta_ssid_static: &'static str = STA_SSID_CELL.init(ap_config.sta_ssid.clone()).as_str();
let (ap_radio_config, net_config, wifi_interface) =
build_radio_config(&ap_config, sta_ssid_static, self.gateway);
let controller_config = ControllerConfig::default()
.with_initial_config(ap_radio_config)
.with_static_rx_buf_num(4)
.with_dynamic_rx_buf_num(16)
.with_dynamic_tx_buf_num(16)
.with_ampdu_rx_enable(false)
.with_ampdu_tx_enable(false);
let mut wifi_controller = WifiController::new(wifi_peri, controller_config)
.map_err(|_| HalError::Wifi(WifiError::Initialization))?;
if sta_ssid_static.is_empty() {
set_band_mode(&mut wifi_controller, ap_config.band);
}
let seed = u64::from(self.rng.random()) << 32 | u64::from(self.rng.random());
let (ap_stack, runner) = embassy_net::new(
wifi_interface,
net_config,
RESOURCES_CELL.init(StackResources::<3>::new()),
seed,
);
self.spawner.spawn(
wifi_up(wifi_controller, sta_ssid_static)
.map_err(|_| HalError::Wifi(WifiError::Initialization))?,
);
self.spawner
.spawn(net_up(runner).map_err(|_| HalError::Wifi(WifiError::Initialization))?);
if sta_ssid_static.is_empty() {
self.spawner.spawn(
dhcp_server(ap_stack, self.gateway)
.map_err(|_| HalError::Wifi(WifiError::Initialization))?,
);
loop {
debug!("Checking if link is up");
if ap_stack.is_link_up() {
if let Some(config) = ap_stack.config_v4() {
info!(
"Connect to the AP `{}` with IP {}",
ap_config.ap_ssid.as_str(),
config.address,
);
}
break;
}
Timer::after(Duration::from_millis(500)).await;
}
} else {
let mut retry_count = 0;
loop {
debug!("Checking if station has received IP address");
if ap_stack.is_config_up() {
if let Some(config) = ap_stack.config_v4() {
info!(
"Connect to the AP `{}` with IP {}",
sta_ssid_static, config.address,
);
}
break;
}
retry_count += 1;
if retry_count > STATION_MODE_MAX_RETRY_SECONDS {
return Err(HalError::Wifi(WifiError::StationMode));
}
Timer::after(Duration::from_millis(1000)).await;
}
}
Ok(ap_stack)
}
}
fn ssid(stored: &heapless::String<32>) -> Ssid {
Ssid::try_from(stored.as_str()).expect("SSID capacity is 32 bytes, the Ssid limit")
}
fn password(stored: &heapless::String<63>) -> Password {
Password::try_from(stored.as_str()).expect("password capacity is 63 bytes, under the 64 limit")
}
fn build_radio_config(
ap_config: &WifiApConfigStatic,
sta_ssid: &str,
gateway: Ipv4Addr,
) -> (RadioConfig, embassy_net::Config, Interface) {
if sta_ssid.is_empty() {
info!("Wifi configuring Access Point Mode");
let radio = RadioConfig::AccessPoint(
AccessPointConfig::default()
.with_ssid(ssid(&ap_config.ap_ssid))
.with_authentication(AuthenticationMethodConfig::Wpa2Personal(password(
&ap_config.ap_password,
)))
.with_channel(ap_config.channel),
);
let net = embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(gateway, 24),
gateway: Some(gateway),
dns_servers: Default::default(),
});
(radio, net, Interface::access_point())
} else {
info!("Wifi configuring Station Mode");
let radio = RadioConfig::Station(
StationConfig::default()
.with_ssid(ssid(&ap_config.sta_ssid))
.with_authentication(AuthenticationMethodConfig::Wpa2Personal(password(
&ap_config.sta_password,
))),
);
let net = embassy_net::Config::dhcpv4(DhcpConfig::default());
(radio, net, Interface::station())
}
}
fn set_band_mode(wifi_controller: &mut WifiController<'static>, band: BandMode) {
let radio_band = match band {
BandMode::Band2_4G => RadioBandMode::_2_4G,
#[cfg(wifi_has_5g)]
BandMode::Band5G => RadioBandMode::_5G,
#[cfg(wifi_has_5g)]
BandMode::Auto => RadioBandMode::Auto,
#[cfg(not(wifi_has_5g))]
_ => RadioBandMode::_2_4G,
};
match wifi_controller.set_band_mode(radio_band.clone()) {
Ok(()) => debug!("Set WiFi band mode: {radio_band:?}"),
Err(e) => warn!("Failed to set band mode {radio_band:?}: {e:?} (non-5G chip?)"),
}
}
pub async fn accept_requests<'a>(
tcp_stack: Stack<'a>,
rx_buffer: &'a mut [u8],
tx_buffer: &'a mut [u8],
) -> Result<TcpSocket<'a>, HalError> {
let mut tcp_socket = TcpSocket::new(tcp_stack, rx_buffer, tx_buffer);
debug!("Waiting for SSH client...");
if let Err(_e) = tcp_socket
.accept(IpListenEndpoint {
addr: None,
port: 22,
})
.await
{
error!("Failed to accept incoming TCP connection");
return Err(HalError::Wifi(WifiError::SocketAccept));
}
debug!("Connected, port 22");
Ok(tcp_socket)
}
#[embassy_executor::task]
pub async fn wifi_up(mut wifi_controller: WifiController<'static>, sta_ssid: &'static str) {
if sta_ssid.is_empty() {
debug!("Wifi AP starting...");
loop {
let ev = wifi_controller
.wait_for_access_point_connected_event_async()
.await;
match ev {
Ok(EventInfo::Connected(info)) => {
info!("Station connected: {info:?}");
}
Ok(EventInfo::Disconnected(info)) => {
info!("Station disconnected: {info:?}");
}
_ => (),
}
Timer::after(Duration::from_millis(5000)).await;
}
} else {
loop {
debug!("Connecting to access point...");
match wifi_controller.connect_async().await {
Ok(info) => {
info!("Wifi connected to {info:?}");
let info = wifi_controller.wait_for_disconnect_async().await.ok();
info!("Disconnected: {info:?}");
}
Err(e) => {
info!("Failed to connect to wifi: {e:?}");
}
}
Timer::after(Duration::from_millis(1000)).await;
}
}
}
#[embassy_executor::task]
pub async fn net_up(mut runner: Runner<'static, Interface>) {
debug!("Bringing up network stack...");
runner.run().await;
}
#[embassy_executor::task]
pub async fn dhcp_server(stack: Stack<'static>, ip: Ipv4Addr) {
let mut buf = [0u8; 1500];
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
let unbound_socket = Udp::new(stack, &buffers);
let mut bound_socket = match unbound_socket
.bind(core::net::SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::UNSPECIFIED,
DEFAULT_SERVER_PORT,
)))
.await
{
Ok(socket) => socket,
Err(e) => {
warn!("Failed to bind DHCP server socket: {e:?}");
return;
}
};
loop {
if let Err(e) = io::server::run(
&mut Server::<_, 64>::new_with_et(ip),
&ServerOptions::new(ip, Some(&mut gw_buf)),
&mut bound_socket,
&mut buf,
)
.await
{
error!("DHCP server error: {e:?}");
}
Timer::after(Duration::from_millis(500)).await;
}
}