-
Notifications
You must be signed in to change notification settings - Fork 197
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 WiFi Easy Connect (DPP) wrappers and associated example #228
Open
jasta
wants to merge
7
commits into
esp-rs:master
Choose a base branch
from
jasta:esp_dpp
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
89ad844
Add WiFi Easy Connect (DPP) wrappers and associated example
jasta 4514611
Removed local paths added by mistake, will use patch.crates-io from n…
jasta c4feaf3
Removed wifi_dpp_setup example, can't get it to compile
jasta c1a0f05
Run cargo fix / cargo fmt
jasta e647aab
Don't forward disconnect/stop errors during init
jasta 96f80ae
wifi_dpp: Reworked to not use std and generally simplified code
jasta a6132ea
cargo fmt
jasta File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//! Example using Wi-Fi Easy Connect (DPP) to get a device onto a Wi-Fi network | ||
//! without hardcoding credentials. | ||
|
||
extern crate core; | ||
|
||
use esp_idf_hal as _; | ||
|
||
use std::time::Duration; | ||
use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; | ||
use esp_idf_hal::peripherals::Peripherals; | ||
use esp_idf_svc::eventloop::EspSystemEventLoop; | ||
use esp_idf_svc::log::EspLogger; | ||
use esp_idf_svc::nvs::EspDefaultNvsPartition; | ||
use esp_idf_svc::wifi::{EspWifi, WifiWait}; | ||
use esp_idf_sys::EspError; | ||
use log::{error, info, LevelFilter, warn}; | ||
use esp_idf_svc::wifi_dpp::EspDppBootstrapper; | ||
|
||
fn main() { | ||
esp_idf_sys::link_patches(); | ||
|
||
EspLogger::initialize_default(); | ||
|
||
let peripherals = Peripherals::take().unwrap(); | ||
let sysloop = EspSystemEventLoop::take().unwrap(); | ||
let nvs = EspDefaultNvsPartition::take().unwrap(); | ||
|
||
let mut wifi = EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs)).unwrap(); | ||
|
||
if !wifi_has_sta_config(&wifi).unwrap() { | ||
info!("No existing STA config, let's try DPP..."); | ||
let config = dpp_listen_forever(&mut wifi).unwrap(); | ||
info!("Got config: {config:?}"); | ||
wifi.set_configuration(&Configuration::Client(config)).unwrap(); | ||
} | ||
|
||
wifi.start().unwrap(); | ||
|
||
let timeout = Duration::from_secs(60); | ||
loop { | ||
let ssid = match wifi.get_configuration().unwrap() { | ||
Configuration::None => None, | ||
Configuration::Client(ap) => Some(ap.ssid), | ||
Configuration::AccessPoint(_) => None, | ||
Configuration::Mixed(_, _) => None, | ||
}.unwrap(); | ||
info!("Connecting to {ssid}..."); | ||
wifi.connect().unwrap(); | ||
let waiter = WifiWait::new(&sysloop).unwrap(); | ||
let is_connected = waiter.wait_with_timeout(timeout, || wifi.is_connected().unwrap()); | ||
if is_connected { | ||
info!("Connected!"); | ||
waiter.wait(|| !wifi.is_connected().unwrap()); | ||
warn!("Got disconnected, connecting again..."); | ||
} else { | ||
error!("Failed to connect after {}s, trying again...", timeout.as_secs()); | ||
} | ||
} | ||
} | ||
|
||
fn wifi_has_sta_config(wifi: &EspWifi) -> Result<bool, EspError> { | ||
match wifi.get_configuration()? { | ||
Configuration::Client(c) => Ok(!c.ssid.is_empty()), | ||
_ => Ok(false), | ||
} | ||
} | ||
|
||
fn dpp_listen_forever(wifi: &mut EspWifi) -> Result<ClientConfiguration, EspError> { | ||
let mut dpp = EspDppBootstrapper::new(wifi)?; | ||
let channels: Vec<_> = (1..=11).collect(); | ||
let bootstrapped = dpp.gen_qrcode(&channels, None, None)?; | ||
println!("Got: {}", bootstrapped.data.0); | ||
println!("(use a QR code generator and scan the code in the Wi-Fi setup flow on your phone)"); | ||
|
||
bootstrapped.listen_forever() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y | ||
CONFIG_LOG_MAXIMUM_LEVEL_INFO=y | ||
|
||
CONFIG_WPA_DPP_SUPPORT=y | ||
|
||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=15000 | ||
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=12000 | ||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=8000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
//! Wi-Fi Easy Connect (DPP) support | ||
//! | ||
//! To use this feature, you must add CONFIG_WPA_DPP_SUPPORT=y to your sdkconfig. | ||
|
||
use ::log::*; | ||
|
||
use std::ffi::{c_char, CStr, CString}; | ||
use std::fmt::Write; | ||
use std::ops::Deref; | ||
use std::ptr; | ||
use std::sync::mpsc::{Receiver, sync_channel, SyncSender}; | ||
use embedded_svc::wifi::{ClientConfiguration, Configuration, Wifi}; | ||
use esp_idf_sys::*; | ||
use esp_idf_sys::EspError; | ||
use crate::private::common::Newtype; | ||
use crate::private::mutex; | ||
use crate::wifi::EspWifi; | ||
|
||
static EVENTS_TX: mutex::Mutex<Option<SyncSender<DppEvent>>> = | ||
mutex::Mutex::wrap(mutex::RawMutex::new(), None); | ||
|
||
pub struct EspDppBootstrapper<'d, 'w> { | ||
wifi: &'d mut EspWifi<'w>, | ||
events_rx: Receiver<DppEvent>, | ||
} | ||
|
||
impl<'d, 'w> EspDppBootstrapper<'d, 'w> { | ||
pub fn new(wifi: &'d mut EspWifi<'w>) -> Result<Self, EspError> { | ||
if wifi.is_started()? { | ||
wifi.disconnect()?; | ||
wifi.stop()?; | ||
} | ||
|
||
Self::init(wifi) | ||
} | ||
|
||
fn init(wifi: &'d mut EspWifi<'w>) -> Result<Self, EspError> { | ||
let (events_tx, events_rx) = sync_channel(1); | ||
let mut dpp_event_relay = EVENTS_TX.lock(); | ||
*dpp_event_relay = Some(events_tx); | ||
drop(dpp_event_relay); | ||
esp!(unsafe { esp_supp_dpp_init(Some(Self::dpp_event_cb_unsafe)) })?; | ||
Ok(Self { | ||
wifi, | ||
events_rx, | ||
}) | ||
} | ||
|
||
/// Generate a QR code that can be scanned by a mobile phone or other configurator | ||
/// to securely provide us with the Wi-Fi credentials. Must invoke a listen API on the returned | ||
/// bootstrapped instance (e.g. [EspDppBootstrapped::listen_once]) or scanning the | ||
/// QR code will not be able to deliver the credentials to us. | ||
/// | ||
/// Important implementation notes: | ||
/// | ||
/// 1. You must provide _all_ viable channels that the AP could be using | ||
/// in order to successfully acquire credentials! For example, in the US, you can use | ||
/// `(1..=11).collect()`. | ||
/// | ||
/// 2. The WiFi driver will be forced started and with a default STA config unless the | ||
/// state is set-up ahead of time. It's unclear if the AuthMethod that you select | ||
/// for this STA config affects the results. | ||
pub fn gen_qrcode<'b>( | ||
&'b mut self, | ||
channels: &[u8], | ||
key: Option<&[u8; 32]>, | ||
associated_data: Option<&[u8]> | ||
) -> Result<EspDppBootstrapped<'b, QrCode>, EspError> { | ||
let mut channels_str = channels.into_iter() | ||
.fold(String::new(), |mut a, c| { | ||
write!(a, "{c},").unwrap(); | ||
a | ||
}); | ||
channels_str.pop(); | ||
let channels_cstr = CString::new(channels_str).unwrap(); | ||
let key_ascii_cstr = key.map(|k| { | ||
let result = k.iter() | ||
.fold(String::new(), |mut a, b| { | ||
write!(a, "{b:02X}").unwrap(); | ||
a | ||
}); | ||
CString::new(result).unwrap() | ||
}); | ||
let associated_data_cstr = match associated_data { | ||
Some(associated_data) => { | ||
Some(CString::new(associated_data) | ||
.map_err(|_| { | ||
warn!("associated data contains an embedded NUL character!"); | ||
EspError::from_infallible::<ESP_ERR_INVALID_ARG>() | ||
})?) | ||
} | ||
None => None, | ||
}; | ||
debug!("dpp_bootstrap_gen..."); | ||
esp!(unsafe { | ||
esp_supp_dpp_bootstrap_gen( | ||
channels_cstr.as_ptr(), | ||
dpp_bootstrap_type_DPP_BOOTSTRAP_QR_CODE, | ||
key_ascii_cstr.map_or_else(ptr::null, |x| x.as_ptr()), | ||
associated_data_cstr.map_or_else(ptr::null, |x| x.as_ptr())) | ||
})?; | ||
let event = self.events_rx.recv() | ||
.map_err(|_| { | ||
warn!("Internal error receiving event!"); | ||
EspError::from_infallible::<ESP_ERR_INVALID_STATE>() | ||
})?; | ||
debug!("dpp_bootstrap_gen got: {event:?}"); | ||
match event { | ||
DppEvent::UriReady(qrcode) => { | ||
// Bit of a hack to put the wifi driver in the correct mode. | ||
self.ensure_config_and_start()?; | ||
Ok(EspDppBootstrapped::<QrCode> { | ||
events_rx: &self.events_rx, | ||
data: QrCode(qrcode), | ||
}) | ||
} | ||
_ => { | ||
warn!("Got unexpected event: {event:?}"); | ||
Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>()) | ||
}, | ||
} | ||
} | ||
|
||
fn ensure_config_and_start(&mut self) -> Result<ClientConfiguration, EspError> { | ||
let operating_config = match self.wifi.get_configuration()? { | ||
Configuration::Client(c) => c, | ||
_ => { | ||
let fallback_config = ClientConfiguration::default(); | ||
self.wifi.set_configuration(&Configuration::Client(fallback_config.clone()))?; | ||
fallback_config | ||
}, | ||
}; | ||
if !self.wifi.is_started()? { | ||
self.wifi.start()?; | ||
} | ||
Ok(operating_config) | ||
} | ||
|
||
unsafe extern "C" fn dpp_event_cb_unsafe( | ||
evt: esp_supp_dpp_event_t, | ||
data: *mut ::core::ffi::c_void | ||
) { | ||
debug!("dpp_event_cb_unsafe: evt={evt}"); | ||
let event = match evt { | ||
esp_supp_dpp_event_t_ESP_SUPP_DPP_URI_READY => { | ||
DppEvent::UriReady(CStr::from_ptr(data as *mut c_char).to_str().unwrap().into()) | ||
}, | ||
esp_supp_dpp_event_t_ESP_SUPP_DPP_CFG_RECVD => { | ||
let config = data as *mut wifi_config_t; | ||
// TODO: We're losing pmf_cfg.required=true setting due to missing | ||
// information in ClientConfiguration. | ||
DppEvent::ConfigurationReceived(Newtype((*config).sta).into()) | ||
}, | ||
esp_supp_dpp_event_t_ESP_SUPP_DPP_FAIL => { | ||
DppEvent::Fail(EspError::from(data as esp_err_t).unwrap()) | ||
} | ||
_ => panic!(), | ||
}; | ||
dpp_event_cb(event) | ||
} | ||
} | ||
|
||
fn dpp_event_cb(event: DppEvent) { | ||
match EVENTS_TX.lock().deref() { | ||
Some(tx) => { | ||
debug!("Sending: {event:?}"); | ||
if let Err(e) = tx.try_send(event) { | ||
error!("Cannot relay event: {e}"); | ||
} | ||
} | ||
None => warn!("Got spurious {event:?} ???"), | ||
} | ||
} | ||
|
||
|
||
#[derive(Debug)] | ||
enum DppEvent { | ||
UriReady(String), | ||
ConfigurationReceived(ClientConfiguration), | ||
Fail(EspError), | ||
} | ||
|
||
impl<'d, 'w> Drop for EspDppBootstrapper<'d, 'w> { | ||
fn drop(&mut self) { | ||
unsafe { esp_supp_dpp_deinit() }; | ||
} | ||
} | ||
|
||
pub struct EspDppBootstrapped<'d, T> { | ||
events_rx: &'d Receiver<DppEvent>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this should probably be &mut so that it guarantees the bootstrapper binding cannot be used while the bootstrapped binding is in scope. |
||
pub data: T, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct QrCode(pub String); | ||
|
||
impl<'d, T> EspDppBootstrapped<'d, T> { | ||
pub fn listen_once(&self) -> Result<ClientConfiguration, EspError> { | ||
esp!(unsafe { esp_supp_dpp_start_listen() })?; | ||
let event = self.events_rx.recv() | ||
.map_err(|e| { | ||
warn!("Internal receive error: {e}"); | ||
EspError::from_infallible::<ESP_ERR_INVALID_STATE>() | ||
})?; | ||
match event { | ||
DppEvent::ConfigurationReceived(config) => Ok(config), | ||
DppEvent::Fail(e) => Err(e), | ||
_ => { | ||
warn!("Ignoring unexpected event {event:?}"); | ||
Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>()) | ||
} | ||
} | ||
} | ||
|
||
pub fn listen_forever(&self) -> Result<ClientConfiguration, EspError> { | ||
loop { | ||
match self.listen_once() { | ||
Ok(config) => return Ok(config), | ||
Err(e) => warn!("DPP error: {e}, trying again..."), | ||
} | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, will remove :)