├── .cargo └── config.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── Cross.toml ├── ReadMe.md ├── Xargo.toml ├── app ├── Cargo.toml ├── Makefile ├── build.rs ├── examples │ ├── memory_leak.rs │ ├── mutex_rwlock.rs │ ├── nvs.rs │ ├── thread_local.rs │ └── wifi_scan.rs ├── partitions.csv ├── sdkconfig.defaults └── src │ ├── dns.rs │ ├── index.html │ ├── main.rs │ └── wifi_manager.rs ├── build.sh ├── esp-idf-hal ├── Cargo.toml ├── build.rs └── src │ ├── esp_error.rs │ ├── heap.rs │ ├── interface.rs │ ├── lib.rs │ ├── nvs │ ├── get_set.rs │ └── mod.rs │ └── wifi │ ├── ap_config.rs │ ├── auth_mode.rs │ ├── cipher.rs │ ├── event_handler.rs │ ├── mod.rs │ ├── password.rs │ ├── scan.rs │ ├── ssid.rs │ └── sta_config.rs └── rust-toolchain /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "xtensa-esp32-espidf" 3 | 4 | [target.xtensa-esp32-espidf] 5 | linker = "ldproxy" 6 | 7 | [target.'cfg(target_arch = "xtensa")'] 8 | runner = "espflash --monitor /dev/tty.usbserial-140" 9 | 10 | [env] 11 | ESP_IDF_VERSION = { value = "release/v4.4" } 12 | ESP_IDF_SDKCONFIG_DEFAULTS = { value = "app/sdkconfig.defaults" } 13 | ESP_IDF_TOOLS_INSTALL_DIR = { value = "out" } 14 | 15 | [unstable] 16 | build-std = ["std", "panic_abort"] 17 | # build-std-features = ["panic_immediate_abort"] 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request, push] 2 | 3 | name: CI 4 | 5 | jobs: 6 | ci: 7 | uses: reitermarkus/.github/.github/workflows/cargo-build-publish.yml@main 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /app/sdkconfig 4 | /esp-idf-tools 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "esp-idf-hal", 4 | "app", 5 | ] 6 | 7 | [profile.dev] 8 | panic = "unwind" 9 | 10 | [profile.release] 11 | lto = true 12 | panic = "unwind" 13 | opt-level = "s" 14 | 15 | [patch.crates-io] 16 | esp-idf-sys = { git = "https://github.com/reitermarkus/esp-idf-sys", branch = "improve-codegen" } 17 | # esp-idf-sys = { path = "esp-idf-sys" } 18 | esp32-hal = { git = "https://github.com/esp-rs/esp-hal" } 19 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.xtensa-esp32-espidf] 2 | image = "espressif/idf-rust" 3 | xargo = false 4 | 5 | [target.xtensa-esp8266-espidf] 6 | xargo = false 7 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | - [Docker](https://docs.docker.com/get-docker/) 4 | - `jq` 5 | - [`cross`](https://github.com/rust-embedded/cross) with images 6 | from https://github.com/reitermarkus/cross/tree/xtensa: 7 | ``` 8 | git clone -b xtensa https://github.com/reitermarkus/cross 9 | cd cross 10 | cargo install --path . --force 11 | ./build-docker-image.sh xtensa-esp32-none-elf 12 | ``` 13 | - [`esptool.py`](https://github.com/espressif/esptool) 14 | 15 | # Building 16 | 17 | When building the first time, fetch the submodules using 18 | 19 | ``` 20 | git submodule update --init --recursive 21 | ``` 22 | 23 | Afterwards, you can build the project using 24 | 25 | ``` 26 | ./build.sh [--chip (default: esp32)] [--release] [--package (default: app)] [--example ] [--flash-baudrate (default: 460800)] [--erase-flash] 27 | ``` 28 | 29 | For example, you can build the main example application in debug mode using 30 | 31 | ``` 32 | ./build 33 | ``` 34 | 35 | or the `thread_local` example in release mode using 36 | 37 | 38 | ``` 39 | ./build --release --example thread_local 40 | ``` 41 | -------------------------------------------------------------------------------- /Xargo.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | std = {} 3 | 4 | [patch.crates-io] 5 | compiler_builtins = { git = "https://github.com/reitermarkus/compiler-builtins", branch = "xtensa-support" } 6 | -------------------------------------------------------------------------------- /app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | dnsparse = "0.2" 8 | esp-idf-hal = { path = "../esp-idf-hal" } 9 | esp32-hal = { version = "0.1", default-features = false } 10 | embedded-hal = "0.2" 11 | bitflags = "1" 12 | libc = "0.2" 13 | httparse = "1" 14 | url = "2" 15 | futures = "0.3" 16 | macaddr = "1" 17 | esp-idf-sys = { version = "0.30", default-features = false, features = ["binstart", "native"] } 18 | 19 | [build-dependencies] 20 | jobserver = "0.1" 21 | embuild = "0.28.5" 22 | anyhow = "1.0.56" 23 | -------------------------------------------------------------------------------- /app/Makefile: -------------------------------------------------------------------------------- 1 | CARGO_TARGET_DIR ?= $(realpath ../target) 2 | 3 | PROJECT_NAME := app 4 | BUILD_DIR_BASE := $(CARGO_TARGET_DIR)/$(TARGET)/esp-build 5 | 6 | include $(IDF_PATH)/make/project.mk 7 | -------------------------------------------------------------------------------- /app/build.rs: -------------------------------------------------------------------------------- 1 | // Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641 2 | fn main() -> anyhow::Result<()> { 3 | embuild::build::CfgArgs::output_propagated("ESP_IDF")?; 4 | embuild::build::LinkArgs::output_propagated("ESP_IDF") 5 | } 6 | -------------------------------------------------------------------------------- /app/examples/memory_leak.rs: -------------------------------------------------------------------------------- 1 | use std::{time::Duration, thread}; 2 | 3 | use esp_idf_sys as _; 4 | use esp_idf_hal::Heap; 5 | 6 | fn main() { 7 | eprintln!("Total Memory: {}", Heap::total_size()); 8 | 9 | let size_before = Heap::free_size(); 10 | eprintln!("Free Memory (Main Thread): {}", size_before); 11 | 12 | thread::Builder::new().stack_size(8192).spawn(|| { 13 | eprintln!("Free Memory (Thread 1): {}", Heap::free_size()); 14 | 15 | thread::Builder::new().stack_size(8192).spawn(|| { 16 | eprintln!("Free Memory (Thread 2): {}", Heap::free_size()); 17 | }).unwrap(); 18 | }).unwrap(); 19 | 20 | thread::sleep(Duration::from_secs(1)); 21 | 22 | let size_after = Heap::free_size(); 23 | eprintln!("Free Memory (Main Thread): {}", size_after); 24 | 25 | let leaked_memory = if size_after < size_before { size_before - size_after } else { 0 }; 26 | eprintln!("Leaked Memory: {}", leaked_memory); 27 | } 28 | -------------------------------------------------------------------------------- /app/examples/mutex_rwlock.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, RwLock}; 2 | use std::time::Duration; 3 | use std::thread::sleep; 4 | 5 | use esp_idf_sys as _; 6 | 7 | fn main() { 8 | let mutex = Mutex::new(0usize); 9 | assert_eq!(*mutex.lock().unwrap(), 0); 10 | *mutex.lock().unwrap() = 1; 11 | assert_eq!(*mutex.lock().unwrap(), 1); 12 | println!("Mutex: Success!"); 13 | 14 | let rwlock = RwLock::new(0usize); 15 | assert_eq!(*rwlock.read().unwrap(), 0); 16 | *rwlock.write().unwrap() = 1; 17 | assert_eq!(*rwlock.read().unwrap(), 1); 18 | println!("RwLock: Success!"); 19 | 20 | loop { 21 | sleep(Duration::from_secs(1)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/examples/nvs.rs: -------------------------------------------------------------------------------- 1 | use esp_idf_sys as _; 2 | use esp_idf_hal::nvs::*; 3 | 4 | fn main() { 5 | let mut nvs = NonVolatileStorage::default(); 6 | 7 | let mut nvs = nvs.namespace("test").unwrap(); 8 | 9 | macro_rules! test_set_get { 10 | ($nvs:expr, $ty:ty, $value:expr) => { 11 | let original_value = <$ty>::from($value); 12 | let original_value_clone = original_value.clone(); 13 | 14 | let owned_type = stringify!($ty); 15 | let ref_type = concat!("&", stringify!($ty)); 16 | 17 | nvs.set(ref_type, &original_value).expect(&format!("failed setting {}", ref_type)); 18 | nvs.set(owned_type, original_value).expect(&format!("failed setting {}", owned_type)); 19 | 20 | let ref_value = nvs.get::<$ty>(ref_type).expect(&format!("failed getting {}", ref_type)); 21 | let owned_value = nvs.get::<$ty>(owned_type).expect(&format!("failed getting {}", owned_type)); 22 | 23 | assert_eq!(owned_value, ref_value, "did not get the same value for {} and {}", owned_type, ref_type); 24 | assert_eq!(owned_value, original_value_clone, "did not get the original value for {}", owned_type); 25 | assert_eq!(ref_value, original_value_clone, "did not get the original value for {}", ref_type); 26 | 27 | println!("Success: {} == {}", owned_type, ref_type); 28 | } 29 | } 30 | 31 | test_set_get!(nvs, bool, true); 32 | test_set_get!(nvs, i8, -8i8); 33 | test_set_get!(nvs, i16, -16i16); 34 | test_set_get!(nvs, i32, -32i32); 35 | test_set_get!(nvs, i64, -64i64); 36 | test_set_get!(nvs, u8, 8u8); 37 | test_set_get!(nvs, u16, 16u16); 38 | test_set_get!(nvs, u32, 32u32); 39 | test_set_get!(nvs, u64, 64u64); 40 | test_set_get!(nvs, String, "String"); 41 | test_set_get!(nvs, Vec, vec![1, 2, 3, 4]); 42 | } 43 | -------------------------------------------------------------------------------- /app/examples/thread_local.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::thread::{self, sleep}; 3 | use std::time::Duration; 4 | 5 | use esp_idf_sys as _; 6 | 7 | thread_local! { 8 | pub static FOO: RefCell = RefCell::new(0); 9 | } 10 | 11 | fn main() { 12 | FOO.with(|f| { 13 | *f.borrow_mut() += 1; 14 | }); 15 | 16 | thread::spawn(|| { 17 | FOO.with(|f| { 18 | *f.borrow_mut() += 1; 19 | }); 20 | 21 | FOO.with(|f| { 22 | assert_eq!(1, *f.borrow()); 23 | println!("Thread 1: Success!"); 24 | }); 25 | }); 26 | 27 | thread::spawn(|| { 28 | FOO.with(|f| { 29 | *f.borrow_mut() += 1; 30 | }); 31 | 32 | FOO.with(|f| { 33 | assert_eq!(1, *f.borrow()); 34 | println!("Thread 2: Success!"); 35 | }); 36 | }); 37 | 38 | FOO.with(|f| { 39 | assert_eq!(1, *f.borrow()); 40 | println!("Main Thread: Success!"); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /app/examples/wifi_scan.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use futures::executor::block_on; 4 | 5 | use esp_idf_sys as _; 6 | use esp_idf_hal::wifi::*; 7 | 8 | fn main() { 9 | block_on(async { 10 | let mut wifi = Wifi::take().unwrap(); 11 | 12 | let scan_config = ScanConfig::builder() 13 | .show_hidden(true) 14 | .scan_type(ScanType::Passive { max: Duration::from_secs(1) }) 15 | .build(); 16 | 17 | loop { 18 | println!("Scanning..."); 19 | 20 | match wifi.scan(&scan_config).await { 21 | Ok(aps) => { 22 | if aps.is_empty() { 23 | println!("No access points found."); 24 | } else { 25 | println!("Found {} access points:", aps.len()); 26 | 27 | for ap in aps { 28 | println!(" - {} '{}'", ap.bssid(), ap.ssid()) 29 | } 30 | } 31 | } 32 | Err(err) => { 33 | eprintln!("WiFi Scan failed: {}", err); 34 | } 35 | } 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /app/partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, , 0x6000, 4 | phy_init, data, phy, , 0x1000, 5 | factory, app, factory, , 3M, 6 | -------------------------------------------------------------------------------- /app/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 3 | -------------------------------------------------------------------------------- /app/src/dns.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::{Ipv4Addr, UdpSocket, SocketAddr}; 3 | use std::time::Duration; 4 | use std::thread; 5 | 6 | use esp_idf_hal::interface::Interface; 7 | 8 | use dnsparse::{Header, HeaderKind, Answer, QueryKind, QueryClass, Message, OpCode, ResponseCode}; 9 | 10 | pub fn handle_request(socket: &UdpSocket, src: SocketAddr, request: Message, ip: &Ipv4Addr) -> io::Result { 11 | let response_header = Header::builder() 12 | .id(request.header().id()) 13 | .kind(HeaderKind::Response) 14 | .recursion_desired(request.header().recursion_desired()) 15 | .response_code(ResponseCode::NotImplemented); 16 | 17 | let mut buf = Message::BUFFER; 18 | 19 | let mut response = Message::builder(&mut buf) 20 | .header(response_header.build()) 21 | .build(); 22 | 23 | let question_count = request.header().question_count(); 24 | let kind = request.header().kind(); 25 | let opcode = request.header().opcode(); 26 | 27 | if question_count == 1 && kind == HeaderKind::Query && opcode == OpCode::Query { 28 | for question in request.questions() { 29 | if *question.kind() == QueryKind::A && *question.class() == QueryClass::IN { 30 | if question.name() == "captive.apple.com" { 31 | response.header_mut().set_response_code(ResponseCode::NoError); 32 | 33 | let answer = Answer { 34 | name: question.name().clone(), 35 | kind: *question.kind(), 36 | class: *question.class(), 37 | ttl: 60, 38 | rdata: &ip.octets(), 39 | }; 40 | 41 | response.add_question(&question); 42 | response.add_answer(&answer); 43 | } else { 44 | response.header_mut().set_response_code(ResponseCode::NonExistentDomain); 45 | break; 46 | } 47 | } 48 | } 49 | } 50 | 51 | socket.send_to(&response, src) 52 | } 53 | 54 | pub fn server() { 55 | println!("Starting DNS server …"); 56 | 57 | let socket = UdpSocket::bind("0.0.0.0:53").unwrap(); 58 | socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); 59 | socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); 60 | 61 | let ip = *Interface::Ap.ip_info().ip(); 62 | println!("IP: {:?}", ip); 63 | 64 | loop { 65 | thread::yield_now(); 66 | 67 | let mut buf = Message::BUFFER; 68 | 69 | let (len, src) = match socket.recv_from(&mut buf) { 70 | Ok(ok) => ok, 71 | Err(err) => { 72 | if err.kind() != std::io::ErrorKind::WouldBlock { 73 | eprintln!("Receiving DNS request failed: {}", err); 74 | } 75 | continue 76 | } 77 | }; 78 | 79 | let request = if let Ok(frame) = Message::parse(&mut buf[..len]) { 80 | frame 81 | } else { 82 | eprintln!("Failed to parse DNS request."); 83 | continue 84 | }; 85 | 86 | if let Err(err) = handle_request(&socket, src, request, &ip) { 87 | eprintln!("Error sending response to '{:?}': {}", src, err); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::sync::{Arc, Mutex}; 4 | use std::thread::{self, sleep}; 5 | use std::time::Duration; 6 | use std::net::{Ipv4Addr, SocketAddrV4}; 7 | use std::net::TcpListener; 8 | 9 | use embedded_hal::digital::v2::OutputPin; 10 | use macaddr::MacAddr; 11 | 12 | use esp_idf_sys as _; 13 | use esp_idf_hal::{*, interface::*, nvs::*, wifi::*}; 14 | 15 | use futures::executor::block_on; 16 | 17 | mod wifi_manager; 18 | use wifi_manager::*; 19 | 20 | mod dns; 21 | 22 | pub fn main() { 23 | block_on(async { 24 | if let Err(err) = rust_blink_and_write().await { 25 | println!("{}", err); 26 | } 27 | }) 28 | } 29 | 30 | async fn rust_blink_and_write() -> Result { 31 | use esp32_hal::{pac, gpio::GpioExt}; 32 | 33 | let dp = unsafe { pac::Peripherals::steal() }; 34 | let pins = dp.GPIO.split(); 35 | 36 | let mut gpio = pins.gpio22.into_open_drain_output(); 37 | 38 | let mut nvs = NonVolatileStorage::default(); 39 | 40 | let mut wifi = Wifi::take().unwrap(); 41 | 42 | println!("AP started."); 43 | 44 | let namespace = nvs.namespace("wifi")?; 45 | println!("namespace: {:?}", namespace); 46 | 47 | let t = thread::Builder::new() 48 | .name("hello_thread".into()) 49 | .stack_size(2048) 50 | .spawn(|| { 51 | println!("HELLO, WORLD!"); 52 | 42 53 | }); 54 | 55 | println!("Thread spawn result: {:?}", t); 56 | println!("Thread join result: {:?}", t.map(|t| t.join().unwrap())); 57 | 58 | thread::Builder::new() 59 | .name("dns_thread".into()) 60 | .stack_size(8192) 61 | .spawn(dns::server) 62 | .unwrap(); 63 | 64 | thread::Builder::new() 65 | .name("blink_thread".into()) 66 | .stack_size(1024) 67 | .spawn(move || { 68 | loop { 69 | gpio.set_low().unwrap(); 70 | sleep(Duration::from_millis(100)); 71 | gpio.set_high().unwrap(); 72 | sleep(Duration::from_secs(3)); 73 | } 74 | }) 75 | .unwrap(); 76 | 77 | thread::Builder::new() 78 | .name("server_thread".into()) 79 | .stack_size(8192) 80 | .spawn(move || block_on(async { 81 | let mac = MacAddr::from(Interface::Ap); 82 | 83 | let ap_ssid = Ssid::from_bytes(format!("ESP {}", mac).as_bytes()).unwrap(); 84 | 85 | let ap_config = ApConfig::builder() 86 | .ssid(ap_ssid) 87 | .build(); 88 | 89 | let wifi_storage = namespace; 90 | 91 | let ssid = wifi_storage.get::("ssid").ok().and_then(|s| Ssid::from_bytes(s.as_bytes()).ok()); 92 | let password = wifi_storage.get::("password").ok().and_then(|s| Password::from_bytes(s.as_bytes()).ok()); 93 | 94 | if let (Some(ssid), Some(password)) = (ssid, password) { 95 | match wifi_manager::connect_ssid_password(&mut wifi, ssid, password).await { 96 | Ok(_) => (), 97 | Err(_) => { 98 | println!("Starting Access Point '{}' …", ap_config.ssid()); 99 | wifi.start_ap(ap_config).expect("Failed to start access point"); 100 | }, 101 | }; 102 | } else { 103 | println!("Starting Access Point '{}' …", ap_config.ssid()); 104 | wifi.start_ap(ap_config).expect("Failed to start access point"); 105 | } 106 | 107 | let stream = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 80)).expect("failed starting TCP listener"); 108 | 109 | let wifi = Arc::new(Mutex::new(wifi)); 110 | let wifi_storage = Arc::new(Mutex::new(wifi_storage)); 111 | 112 | loop { 113 | thread::yield_now(); 114 | 115 | let client = stream.accept().and_then(|(client, addr)| { 116 | client.set_read_timeout(Some(Duration::from_secs(30)))?; 117 | client.set_write_timeout(Some(Duration::from_secs(30)))?; 118 | Ok((client, addr)) 119 | }); 120 | 121 | match client { 122 | Ok((client, addr)) => { 123 | let wifi_storage = Arc::clone(&wifi_storage); 124 | let wifi = Arc::clone(&wifi); 125 | 126 | thread::Builder::new() 127 | .stack_size(8192) 128 | .spawn(move || block_on(async { 129 | handle_request(client, addr, wifi_storage, wifi).await 130 | })) 131 | .unwrap(); 132 | }, 133 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => (), 134 | Err(e) => eprintln!("Client error: {}", e), 135 | } 136 | } 137 | })) 138 | .unwrap(); 139 | 140 | loop { 141 | sleep(Duration::from_secs(5)) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/wifi_manager.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Write}; 2 | use std::net::{SocketAddr, TcpStream}; 3 | use std::sync::{Arc, Mutex}; 4 | use std::str; 5 | use std::time::Duration; 6 | 7 | use esp_idf_hal::{nvs::NameSpace, wifi::*}; 8 | 9 | /// Try parsing `Ssid` and `Password` from URL parameters. 10 | fn ssid_and_password(params: &[u8]) -> (Option, Option) { 11 | let mut ssid = None; 12 | let mut password = None; 13 | 14 | for (name, value) in url::form_urlencoded::parse(¶ms) { 15 | match name.as_ref() { 16 | "ssid" => ssid = Ssid::from_bytes(value.as_bytes()).ok(), 17 | "password" => password = Password::from_bytes(value.as_bytes()).ok(), 18 | _ => if ssid.is_some() && password.is_some() { break }, 19 | } 20 | } 21 | 22 | (ssid, password) 23 | } 24 | 25 | fn write_ok(client: &mut TcpStream) -> io::Result<()> { 26 | writeln!(client, "HTTP/1.1 200 OK")?; 27 | writeln!(client, "Content-Type: text/html")?; 28 | writeln!(client) 29 | } 30 | 31 | fn write_template(client: &mut TcpStream) -> io::Result<()> { 32 | write_ok(client)?; 33 | writeln!(client, "{}", include_str!("index.html")) 34 | } 35 | 36 | async fn handle_index(wifi: Arc>, mut client: TcpStream) -> io::Result<()> { 37 | write_template(&mut client)?; 38 | 39 | writeln!(client, r##" 40 | 46 |
47 | 48 | 49 | 50 | 51 |
52 | "##)?; 53 | 54 | let scan_config = ScanConfig::builder() 55 | .show_hidden(true) 56 | .scan_type(ScanType::Passive { max: Duration::from_millis(100) }) 57 | .build(); 58 | 59 | writeln!(client, "")?; 60 | 61 | let wifi = &mut *wifi.lock().unwrap(); 62 | match wifi.scan(&scan_config).await { 63 | Ok(mut aps) => { 64 | aps.sort_by(|a, b| a.ssid().cmp(b.ssid())); 65 | aps.dedup_by(|a, b| a.ssid() == b.ssid()); 66 | 67 | for ssid in aps.iter().map(|ap| ap.ssid()).filter(|ssid| !ssid.is_empty()) { 68 | writeln!(client, "", ssid)?; 69 | } 70 | 71 | }, 72 | Err(err) => { 73 | eprintln!("WiFi scan failed: {}", err); 74 | } 75 | } 76 | 77 | writeln!(client, "")?; 78 | 79 | Ok(()) 80 | } 81 | 82 | fn handle_hotspot_detect(mut client: TcpStream) -> io::Result<()> { 83 | writeln!(client, "HTTP/1.1 303 See Other")?; 84 | writeln!(client, "Location: /")?; 85 | writeln!(client, "Content-Type: text/plain")?; 86 | writeln!(client)?; 87 | writeln!(client, "Redirecting …") 88 | } 89 | 90 | fn handle_connection_error(mut client: TcpStream, message: &str) -> io::Result<()> { 91 | write_template(&mut client)?; 92 | writeln!(client, "

Failed to connect.{} Retry?

", message) 93 | } 94 | 95 | fn handle_connection_success(mut client: TcpStream, message: &str) -> io::Result<()> { 96 | write_template(&mut client)?; 97 | writeln!(client, "

Success.{}

", message) 98 | } 99 | 100 | fn handle_not_found(mut client: TcpStream) -> io::Result<()> { 101 | writeln!(client, "HTTP/1.1 404 Not Found")?; 102 | writeln!(client) 103 | } 104 | 105 | fn handle_internal_error(mut client: TcpStream) -> io::Result<()> { 106 | writeln!(client, "HTTP/1.1 500 INTERNAL SERVER ERROR")?; 107 | writeln!(client) 108 | } 109 | 110 | pub async fn handle_request( 111 | mut client: TcpStream, addr: SocketAddr, 112 | wifi_storage: Arc>, 113 | wifi: Arc>, 114 | ) { 115 | println!("Handling request from {} …", addr); 116 | 117 | let mut buf: [u8; 1024] = [0; 1024]; 118 | let len = match client.read(&mut buf) { 119 | Ok(len) => len, 120 | Err(err) => { 121 | eprintln!("Error reading from client: {:?}", err); 122 | let _ = handle_internal_error(client); 123 | return; 124 | }, 125 | }; 126 | 127 | let mut headers = [httparse::EMPTY_HEADER; 16]; 128 | let mut req = httparse::Request::new(&mut headers); 129 | 130 | let status = req.parse(&buf); 131 | 132 | let res = match (status, req.method, req.path) { 133 | (Ok(httparse::Status::Complete(header_len)), Some(method), Some(path)) => { 134 | println!("{} {} - {} bytes", method, path, len); 135 | 136 | match (method, path) { 137 | ("GET", "/") => handle_index(Arc::clone(&wifi), client).await, 138 | ("GET", "/hotspot-detect.html") => handle_hotspot_detect(client), 139 | ("POST", "/connect") => { 140 | let body = &buf[header_len..len]; 141 | 142 | if let (Some(ssid), Some(password)) = ssid_and_password(body) { 143 | let mut wifi_storage = wifi_storage.lock().unwrap(); 144 | 145 | wifi_storage.set::<&str>("ssid", &ssid.as_str()).expect("Failed saving SSID"); 146 | wifi_storage.set::<&str>("password", &password.as_str()).expect("Failed saving password"); 147 | 148 | let wifi = &mut *wifi.lock().unwrap(); 149 | let ap_config = wifi.as_ap().unwrap().config(); 150 | 151 | let message = format!(" Connecting to “{}” …", ssid.as_str()); 152 | let res = handle_connection_success(client, &message); 153 | 154 | wifi.stop_ap(); 155 | match connect_ssid_password(wifi, ssid.clone(), password).await { 156 | Ok(_) => (), 157 | Err(err) => { 158 | eprintln!("Failed to connect to {}: {}", ssid.as_str(), err); 159 | wifi.start_ap(ap_config).expect("Failed to start access point"); 160 | } 161 | } 162 | 163 | res 164 | } else { 165 | handle_connection_error(client, " SSID is empty.") 166 | } 167 | }, 168 | _ => handle_not_found(client), 169 | } 170 | } 171 | _ => handle_internal_error(client), 172 | }; 173 | 174 | if let Err(err) = res { 175 | eprintln!("Error handling request: {}", err); 176 | } 177 | } 178 | 179 | /// Try to connect to an access point with the given `ssid` and `password` in station mode, otherwise revert to access point mode. 180 | pub async fn connect_ssid_password(wifi: &mut Wifi, ssid: Ssid, password: Password) -> Result { 181 | let sta_config = StaConfig::builder() 182 | .ssid(ssid) 183 | .password(password) 184 | .build(); 185 | 186 | eprintln!("Connecting to '{}' with password '{}' …", sta_config.ssid(), sta_config.password()); 187 | 188 | match wifi.connect_sta(sta_config).await { 189 | Ok(connection_info) => { 190 | eprintln!("Connected to '{}' ({}) on channel {} with IP '{}'.", 191 | connection_info.ssid(), connection_info.bssid(), 192 | connection_info.channel(), connection_info.ip_info().ip()); 193 | Ok(connection_info) 194 | }, 195 | Err(err) => Err(err), 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Workaround until https://github.com/espressif/esp-idf/pull/6587 is merged. 4 | export ZSH_VERSION='' 5 | 6 | set -euo pipefail 7 | 8 | cd "$(dirname "${0}")" 9 | 10 | CHIP=esp32 11 | PACKAGE=app 12 | EXAMPLE= 13 | FLASH_BAUDRATE=460800 14 | MONITOR_BAUDRATE=115200 15 | PROFILE= 16 | ERASE_FLASH=false 17 | 18 | while (( ${#@} )); do 19 | case "${1}" in 20 | --chip) 21 | shift 22 | CHIP="${1}" 23 | ;; 24 | -p|--package) 25 | shift 26 | PACKAGE="${1}" 27 | ;; 28 | --example) 29 | shift 30 | EXAMPLE="${1}" 31 | ;; 32 | --release) 33 | PROFILE=release 34 | ;; 35 | --flash-baudrate) 36 | shift 37 | FLASH_BAUDRATE="${1}" 38 | ;; 39 | --erase-flash) 40 | ERASE_FLASH=true 41 | ;; 42 | *) 43 | echo "Invalid argument: '${1}'" >&2 44 | exit 1 45 | ;; 46 | esac 47 | shift 48 | done 49 | 50 | SERIAL_PORT="$(find /dev -name 'tty.usbserial-*' 2>/dev/null | head -n 1 || true)" 51 | 52 | TARGET="xtensa-${CHIP}-none-elf" 53 | 54 | if [[ "${CHIP}" == esp32 ]]; then 55 | IDF_PATH="$(pwd)/esp-idf" 56 | else 57 | IDF_PATH="$(pwd)/ESP8266_RTOS_SDK" 58 | fi 59 | 60 | export IDF_PATH 61 | 62 | IDF_TOOLS_PATH="$(pwd)/esp-idf-tools" 63 | export IDF_TOOLS_PATH 64 | 65 | mkdir -p "${IDF_TOOLS_PATH}" 66 | 67 | idf_version="$(git -C "${IDF_PATH}" rev-parse HEAD 2>/dev/null || true)" 68 | version_file="${IDF_TOOLS_PATH}/installed_version" 69 | 70 | if ! [[ -f "${version_file}" ]] || [[ "${idf_version}" != "$(cat "${version_file}")" ]]; then 71 | case "${CHIP}" in 72 | esp32) 73 | "${IDF_PATH}/install.sh" 74 | ;; 75 | esp8266) 76 | python -m pip install --user -r "${IDF_PATH}/requirements.txt" 77 | ;; 78 | esac 79 | 80 | if [[ -n "${idf_version:-}" ]]; then 81 | echo "${idf_version}" > "${version_file}" 82 | fi 83 | fi 84 | 85 | export PATH="$(brew --prefix rust-xtensa)/bin:$(brew --prefix llvm-xtensa)/bin:${PATH}" 86 | 87 | source "${IDF_PATH}/export.sh" >/dev/null 88 | 89 | 90 | if [[ "${CHIP}" = 'esp32' ]]; then 91 | ln -sfn sdkconfig.esp32 app/sdkconfig 92 | else 93 | ln -sfn sdkconfig.esp8266 app/sdkconfig 94 | fi 95 | 96 | export CARGO_TARGET_DIR="$(pwd)/target" 97 | 98 | cargo build ${PROFILE:+"--${PROFILE}"} --target "${TARGET}" ${PACKAGE:+--package "${PACKAGE}"} ${EXAMPLE:+--example "${EXAMPLE}"} 99 | 100 | cargo doc ${PROFILE:+"--${PROFILE}"} --target "${TARGET}" --no-deps 101 | 102 | if [[ -z "${SERIAL_PORT}" ]]; then 103 | exit 104 | fi 105 | 106 | esptool() { 107 | "${IDF_PATH}/components/esptool_py/esptool/esptool.py" --chip "${CHIP}" --port "${SERIAL_PORT}" ${FLASH_BAUDRATE:+--baud "${FLASH_BAUDRATE}"} "${@}" | \ 108 | grep -E -v 'esptool.py|Serial port|Changing baud rate|Changed.|Uploading stub|Running stub|Stub running|Configuring flash size|Leaving' 109 | } 110 | 111 | mapfile -t binary_targets < <( 112 | cargo metadata --format-version 1 \ 113 | | jq -c ' 114 | .workspace_members as $members | .packages 115 | | map(select(.id as $id | $members[] | contains($id) )) 116 | | map(.targets)[] | map(select(.kind[] | contains("bin") or contains("example")))[] 117 | | .name 118 | ' -r 119 | ) 120 | 121 | for binary_target in "${binary_targets[@]}"; do 122 | for t in "${CARGO_TARGET_DIR}"/${TARGET}/{release,debug}/{,examples/}"${binary_target}"; do 123 | if [[ -f "${t}" ]]; then 124 | "${IDF_PATH}/components/esptool_py/esptool/esptool.py" \ 125 | --chip "${CHIP}" \ 126 | elf2image \ 127 | --version "${ELF2IMAGE_VERSION}" \ 128 | -o "${t}.bin" \ 129 | "${t}" | tail -n +2 130 | fi 131 | done 132 | done 133 | 134 | FLASH_ARGS=( -z --flash_mode dio --flash_freq 80m --flash_size detect ) 135 | 136 | if [[ "${CHIP}" = 'esp32' ]]; then 137 | BOOTLOADER_OFFSET=0x1000 138 | else 139 | BOOTLOADER_OFFSET=0x0000 140 | fi 141 | BOOTLOADER_BINARY="target/${TARGET}/esp-build/bootloader/bootloader.bin" 142 | PARTITION_TABLE_OFFSET=0x8000 143 | PARTITION_TABLE_BINARY="target/${TARGET}/esp-build/partitions.bin" 144 | APPLICATION_OFFSET=0x10000 145 | if [[ -n "${EXAMPLE-}" ]]; then 146 | BINARY_PATH="examples/${EXAMPLE}" 147 | else 148 | BINARY_PATH="${PACKAGE}" 149 | fi 150 | APPLICATION_BINARY="target/${TARGET}/${PROFILE:-debug}/${BINARY_PATH}.bin" 151 | 152 | if "${ERASE_FLASH}"; then 153 | echo 'Erasing flash …' 154 | esptool --after no_reset erase_flash 155 | fi 156 | 157 | echo "Verifying bootloader …" 158 | if esptool --no-stub --after no_reset verify_flash "${BOOTLOADER_OFFSET}" "${BOOTLOADER_BINARY}" &>/dev/null; then 159 | echo 'Bootloader is up to date.' 160 | else 161 | echo 'Flashing new bootloader …' 162 | esptool --after no_reset write_flash "${FLASH_ARGS[@]}" \ 163 | "${BOOTLOADER_OFFSET}" "${BOOTLOADER_BINARY}" 164 | fi 165 | 166 | echo "Verifying partition table …" 167 | if esptool --no-stub --after no_reset verify_flash "${PARTITION_TABLE_OFFSET}" "${PARTITION_TABLE_BINARY}" &>/dev/null; then 168 | echo 'Partition table is up to date.' 169 | else 170 | echo 'Flashing new partition table …' 171 | esptool --after no_reset write_flash "${FLASH_ARGS[@]}" \ 172 | "${PARTITION_TABLE_OFFSET}" "${PARTITION_TABLE_BINARY}" 173 | fi 174 | 175 | echo "Verifying application …" 176 | if esptool --no-stub --after no_reset verify_flash "${APPLICATION_OFFSET}" "${APPLICATION_BINARY}" &>/dev/null; then 177 | echo 'Application table is up to date.' 178 | else 179 | echo 'Flashing new application …' 180 | esptool --after no_reset write_flash "${FLASH_ARGS[@]}" \ 181 | "${APPLICATION_OFFSET}" "${APPLICATION_BINARY}" 182 | fi 183 | 184 | python -m serial.tools.miniterm --raw --exit-char=3 --rts=0 --dtr=0 "${SERIAL_PORT}" "${MONITOR_BAUDRATE}" 185 | -------------------------------------------------------------------------------- /esp-idf-hal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp-idf-hal" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bitflags = "1" 8 | esp-idf-bindgen = { package = "esp-idf-sys", version = "0.30", default-features = false, features = ["native"] } 9 | embedded-hal = { version = "0.2", features = ["unproven"] } 10 | static_assertions = "1" 11 | macaddr = "1" 12 | memchr = "2" 13 | libc = { version = "0.2", default-features = false } 14 | pin-project = "1.0" 15 | -------------------------------------------------------------------------------- /esp-idf-hal/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let target = env::var("TARGET").expect("TARGET not set"); 5 | let target_device = if target.contains("esp32") { 6 | "esp32" 7 | } else if target.contains("esp8266") { 8 | "esp8266" 9 | } else { 10 | return 11 | }; 12 | 13 | println!(r#"cargo:rustc-cfg=target_device="{}""#, target_device); 14 | } 15 | -------------------------------------------------------------------------------- /esp-idf-hal/src/esp_error.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::str; 3 | 4 | use esp_idf_bindgen::{esp_err_t, esp_err_to_name}; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct EspError { pub(crate) code: esp_err_t } 8 | 9 | 10 | impl From for EspError { 11 | fn from(_: !) -> Self { 12 | loop {} 13 | } 14 | } 15 | 16 | impl core::fmt::Display for EspError { 17 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 18 | unsafe { 19 | let s = CStr::from_ptr(esp_err_to_name(self.code)); 20 | str::from_utf8_unchecked(s.to_bytes()).fmt(f) 21 | } 22 | } 23 | } 24 | 25 | macro_rules! esp_ok { 26 | ($err:expr) => {{ 27 | let code = unsafe { $err }; 28 | if code == ::esp_idf_bindgen::ESP_OK as ::esp_idf_bindgen::esp_err_t { 29 | Ok(()) 30 | } else { 31 | Err($crate::esp_error::EspError { code }) 32 | } 33 | }} 34 | } 35 | -------------------------------------------------------------------------------- /esp-idf-hal/src/heap.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use esp_idf_bindgen::*; 4 | 5 | #[derive(Debug)] 6 | pub struct Heap { 7 | _marker: PhantomData<()>, 8 | } 9 | 10 | impl Heap { 11 | #[cfg(target_device = "esp32")] 12 | pub fn total_size() -> usize { 13 | unsafe { heap_caps_get_total_size(MALLOC_CAP_32BIT) as usize } 14 | } 15 | 16 | pub fn free_size() -> usize { 17 | unsafe { heap_caps_get_free_size(MALLOC_CAP_32BIT) as usize } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /esp-idf-hal/src/interface.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | use std::net::Ipv4Addr; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | use std::ptr; 5 | 6 | use esp_idf_bindgen::{esp_mac_type_t, esp_read_mac}; 7 | #[cfg(target_device = "esp8266")] 8 | use esp_idf_bindgen::{tcpip_adapter_get_ip_info, tcpip_adapter_if_t, tcpip_adapter_ip_info_t as ip_info_t}; 9 | #[cfg(target_device = "esp32")] 10 | use esp_idf_bindgen::{esp_netif_get_ip_info, esp_netif_ip_info_t as ip_info_t, esp_netif_t, esp_netif_create_default_wifi_ap, esp_netif_create_default_wifi_sta}; 11 | use macaddr::{MacAddr, MacAddr6}; 12 | 13 | static AP_PTR: AtomicUsize = AtomicUsize::new(0); 14 | static STA_PTR: AtomicUsize = AtomicUsize::new(0); 15 | const INIT_SENTINEL: usize = usize::max_value(); 16 | 17 | /// Enumeration of all available interfaces. 18 | #[derive(Debug, Clone, Copy)] 19 | pub enum Interface { 20 | /// WiFi interface in station mode. 21 | Sta, 22 | /// WiFi interface in access point mode. 23 | Ap, 24 | /// Bluetooth interface. 25 | #[cfg(target_device = "esp32")] 26 | Bt, 27 | /// Ethernet interface. 28 | #[cfg(target_device = "esp32")] 29 | Eth, 30 | } 31 | 32 | impl Interface { 33 | #[cfg(target_device = "esp8266")] 34 | pub fn ip_info(&self) -> IpInfo { 35 | let interface = match self { 36 | Self::Ap => tcpip_adapter_if_t::TCPIP_ADAPTER_IF_AP, 37 | Self::Sta => tcpip_adapter_if_t::TCPIP_ADAPTER_IF_STA, 38 | }; 39 | 40 | let mut ip_info = MaybeUninit::::uninit(); 41 | esp_ok!(tcpip_adapter_get_ip_info(interface, ip_info.as_mut_ptr())).unwrap(); // Can only fail with invalid arguments. 42 | unsafe { IpInfo::from_native_unchecked(ip_info.assume_init()) } 43 | } 44 | 45 | 46 | #[cfg(target_device = "esp8266")] 47 | pub(crate) fn init(&self) { 48 | } 49 | 50 | #[cfg(target_device = "esp32")] 51 | pub fn ip_info(&self) -> IpInfo { 52 | let mut ip_info = MaybeUninit::::uninit(); 53 | esp_ok!(esp_netif_get_ip_info(self.ptr(), ip_info.as_mut_ptr())).unwrap(); // Can only fail if `self.ptr()` returns `NUL`, in which case the interface does not support IPs. 54 | unsafe { IpInfo::from_native_unchecked(ip_info.assume_init()) } 55 | } 56 | 57 | #[cfg(target_device = "esp32")] 58 | fn ptr(&self) -> *mut esp_netif_t { 59 | match self { 60 | Self::Ap => { 61 | loop { 62 | match AP_PTR.compare_exchange(0, INIT_SENTINEL, Ordering::SeqCst, Ordering::SeqCst) { 63 | Ok(_) => { 64 | let ptr = unsafe { esp_netif_create_default_wifi_ap() }; 65 | AP_PTR.store(ptr as _, Ordering::SeqCst); 66 | return ptr; 67 | }, 68 | Err(INIT_SENTINEL) => continue, 69 | Err(ptr) => return ptr as _, 70 | } 71 | } 72 | }, 73 | Self::Sta => { 74 | loop { 75 | match STA_PTR.compare_exchange(0, INIT_SENTINEL, Ordering::SeqCst, Ordering::SeqCst) { 76 | Ok(_) => { 77 | let ptr = unsafe { esp_netif_create_default_wifi_sta() }; 78 | STA_PTR.store(ptr as _, Ordering::SeqCst); 79 | return ptr; 80 | }, 81 | Err(INIT_SENTINEL) => continue, 82 | Err(ptr) => return ptr as _, 83 | } 84 | } 85 | }, 86 | _ => ptr::null_mut() 87 | } 88 | } 89 | 90 | #[cfg(target_device = "esp32")] 91 | pub(crate) fn init(&self) { 92 | self.ptr(); 93 | } 94 | } 95 | 96 | /// ```no_run 97 | /// use macaddr::MacAddr6; 98 | /// use esp32_hal::Interface; 99 | /// 100 | /// MacAddr6::from(Interface::Ap) 101 | /// ``` 102 | impl From for MacAddr6 { 103 | fn from(interface: Interface) -> Self { 104 | let mac_address_type = match interface { 105 | Interface::Sta => esp_mac_type_t::ESP_MAC_WIFI_STA, 106 | Interface::Ap => esp_mac_type_t::ESP_MAC_WIFI_SOFTAP, 107 | #[cfg(target_device = "esp32")] 108 | Interface::Bt => esp_mac_type_t::ESP_MAC_BT, 109 | #[cfg(target_device = "esp32")] 110 | Interface::Eth => esp_mac_type_t::ESP_MAC_ETH, 111 | }; 112 | 113 | let mut mac_address = MaybeUninit::::uninit(); 114 | esp_ok!(esp_read_mac(mac_address.as_mut_ptr() as *mut _, mac_address_type)).unwrap(); // Can only fail with invalid arguments. 115 | unsafe { mac_address.assume_init() } 116 | } 117 | } 118 | 119 | /// ```no_run 120 | /// use macaddr::MacAddr; 121 | /// use esp32_hal::Interface; 122 | /// 123 | /// MacAddr::from(Interface::Ap) 124 | /// ``` 125 | impl From for MacAddr { 126 | fn from(interface: Interface) -> Self { 127 | Self::V6(interface.into()) 128 | } 129 | } 130 | 131 | /// IP information for an [`Interface`](enum.Interface.html). 132 | #[derive(Debug, Copy, Clone)] 133 | pub struct IpInfo { 134 | pub(crate) ip: Ipv4Addr, 135 | pub(crate) netmask: Ipv4Addr, 136 | pub(crate) gateway: Ipv4Addr, 137 | } 138 | 139 | impl IpInfo { 140 | pub fn ip(&self) -> &Ipv4Addr { 141 | &self.ip 142 | } 143 | 144 | pub fn netmask(&self) -> &Ipv4Addr { 145 | &self.netmask 146 | } 147 | 148 | pub fn gateway(&self) -> &Ipv4Addr { 149 | &self.gateway 150 | } 151 | } 152 | 153 | impl IpInfo { 154 | pub(crate) unsafe fn from_native_unchecked(ip_info: ip_info_t) -> Self { 155 | IpInfo { 156 | ip: u32::from_be(ip_info.ip.addr).into(), 157 | netmask: u32::from_be(ip_info.netmask.addr).into(), 158 | gateway: u32::from_be(ip_info.gw.addr).into(), 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /esp-idf-hal/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | #![warn(missing_debug_implementations)] 3 | 4 | use std::ffi::CStr; 5 | use std::str; 6 | 7 | #[macro_use] 8 | extern crate alloc; 9 | 10 | #[macro_use] 11 | mod esp_error; 12 | pub use esp_error::EspError; 13 | 14 | pub mod interface; 15 | mod heap; 16 | pub use heap::Heap; 17 | pub mod wifi; 18 | pub mod nvs; 19 | -------------------------------------------------------------------------------- /esp-idf-hal/src/nvs/get_set.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | 3 | use esp_idf_bindgen::{ 4 | esp_err_t, 5 | nvs_get_i8, 6 | nvs_set_i8, 7 | nvs_get_u8, 8 | nvs_set_u8, 9 | nvs_get_i16, 10 | nvs_set_i16, 11 | nvs_get_u16, 12 | nvs_set_u16, 13 | nvs_get_i32, 14 | nvs_set_i32, 15 | nvs_get_u32, 16 | nvs_set_u32, 17 | nvs_get_i64, 18 | nvs_set_i64, 19 | nvs_get_u64, 20 | nvs_set_u64, 21 | nvs_get_blob, 22 | nvs_set_blob, 23 | nvs_get_str, 24 | nvs_set_str, 25 | ESP_ERR_NVS_NOT_FOUND, 26 | }; 27 | 28 | use super::*; 29 | 30 | /// Trait for retrieving data from non-volatile storage. 31 | pub trait NvsGet: Sized { 32 | fn nvs_get(namespace: &NameSpace, key: &CStr) -> Result; 33 | } 34 | 35 | /// Trait for saving data in non-volatile storage. 36 | pub trait NvsSet { 37 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError>; 38 | } 39 | 40 | impl NvsSet for &T where T: NvsSet { 41 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 42 | (*self).nvs_set(namespace, key) 43 | } 44 | } 45 | 46 | macro_rules! nvs_int { 47 | ($ty:ty as $as_ty:ty, $set_function:ident, $get_function:ident) => { 48 | impl NvsSet for $ty { 49 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 50 | esp_ok!($set_function(namespace.handle, key.as_ptr(), *self as $as_ty)) 51 | } 52 | } 53 | 54 | impl NvsGet for $ty { 55 | fn nvs_get(namespace: &NameSpace, key: &CStr) -> Result { 56 | let mut out_value = <$ty>::default(); 57 | esp_ok!($get_function(namespace.handle, key.as_ptr(), &mut out_value as *mut $ty as *mut $as_ty))?; 58 | Ok(out_value) 59 | } 60 | } 61 | }; 62 | ($ty:ty, $set_function:ident, $get_function:ident) => { 63 | nvs_int!($ty as $ty, $set_function, $get_function); 64 | }; 65 | } 66 | 67 | nvs_int!(bool as u8, nvs_set_u8, nvs_get_u8); 68 | 69 | nvs_int!( i8, nvs_set_i8, nvs_get_i8); 70 | nvs_int!(i16, nvs_set_i16, nvs_get_i16); 71 | nvs_int!(i32, nvs_set_i32, nvs_get_i32); 72 | nvs_int!(i64, nvs_set_i64, nvs_get_i64); 73 | nvs_int!( u8, nvs_set_u8, nvs_get_u8); 74 | nvs_int!(u16, nvs_set_u16, nvs_get_u16); 75 | nvs_int!(u32, nvs_set_u32, nvs_get_u32); 76 | nvs_int!(u64, nvs_set_u64, nvs_get_u64); 77 | 78 | impl NvsSet for &CStr { 79 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 80 | esp_ok!(nvs_set_str(namespace.handle, key.as_ptr(), self.as_ptr())) 81 | } 82 | } 83 | 84 | impl NvsSet for CString { 85 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 86 | self.as_c_str().nvs_set(namespace, key) 87 | } 88 | } 89 | 90 | impl NvsGet for CString { 91 | fn nvs_get(namespace: &NameSpace, key: &CStr) -> Result { 92 | let mut len = 0; 93 | esp_ok!(nvs_get_str(namespace.handle, key.as_ptr(), ptr::null_mut(), &mut len))?; 94 | 95 | let mut buffer = vec![0u8; len as usize]; 96 | esp_ok!(nvs_get_str(namespace.handle, key.as_ptr(), buffer.as_mut_ptr() as *mut _, &mut len))?; 97 | 98 | Ok(unsafe { CString::from_vec_unchecked(buffer) }) 99 | } 100 | } 101 | 102 | impl NvsSet for &[u8] { 103 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 104 | esp_ok!(nvs_set_blob(namespace.handle, key.as_ptr(), self.as_ptr() as *const _, self.len() as u32)) 105 | } 106 | } 107 | 108 | impl NvsSet for Vec { 109 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 110 | self.as_slice().nvs_set(namespace, key) 111 | } 112 | } 113 | 114 | impl NvsGet for Vec { 115 | fn nvs_get(namespace: &NameSpace, key: &CStr) -> Result { 116 | let mut len = 0; 117 | esp_ok!(nvs_get_blob(namespace.handle, key.as_ptr(), ptr::null_mut(), &mut len))?; 118 | 119 | let mut buffer = vec![0u8; len as usize]; 120 | esp_ok!(nvs_get_blob(namespace.handle, key.as_ptr(), buffer.as_mut_ptr() as *mut _, &mut len))?; 121 | Ok(buffer) 122 | } 123 | } 124 | 125 | impl NvsSet for &str { 126 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 127 | self.as_bytes().nvs_set(namespace, key) 128 | } 129 | } 130 | 131 | impl NvsSet for String { 132 | fn nvs_set(&self, namespace: &mut NameSpace, key: &CStr) -> Result<(), EspError> { 133 | self.as_str().nvs_set(namespace, key) 134 | } 135 | } 136 | 137 | 138 | impl NvsGet for String { 139 | fn nvs_get(namespace: &NameSpace, key: &CStr) -> Result { 140 | let buffer = Vec::::nvs_get(namespace, key)?; 141 | String::from_utf8(buffer).map_err(|_| EspError { code: ESP_ERR_NVS_NOT_FOUND as esp_err_t }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /esp-idf-hal/src/nvs/mod.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | use core::mem::MaybeUninit; 3 | 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | use std::ffi::CString; 6 | 7 | use esp_idf_bindgen::{ 8 | esp_err_t, 9 | ESP_ERR_NVS_NO_FREE_PAGES, 10 | ESP_ERR_NVS_NEW_VERSION_FOUND, 11 | nvs_open_mode_t, 12 | nvs_handle_t, 13 | nvs_flash_init_partition, 14 | nvs_flash_erase_partition, 15 | nvs_flash_deinit_partition, 16 | nvs_open_from_partition, 17 | nvs_close, 18 | NVS_DEFAULT_PART_NAME, 19 | ESP_ERR_NVS_INVALID_NAME, 20 | }; 21 | 22 | use super::*; 23 | 24 | mod get_set; 25 | pub use get_set::*; 26 | 27 | /// A non-volatile storage partition. 28 | #[derive(Debug)] 29 | pub struct NonVolatileStorage { 30 | partition_name: CString, 31 | } 32 | 33 | /// A namespace on a non-volatile storage partition. 34 | #[derive(Debug)] 35 | pub struct NameSpace { 36 | handle: nvs_handle_t, 37 | } 38 | 39 | impl NameSpace { 40 | pub fn get(&self, key: &str) -> Result { 41 | let key = CString::new(key).map_err(|_| EspError { code: ESP_ERR_NVS_INVALID_NAME as esp_err_t })?; 42 | T::nvs_get(self, key.as_ref()) 43 | } 44 | 45 | pub fn set(&mut self, key: &str, value: T) -> Result<(), EspError> { 46 | let key = CString::new(key).map_err(|_| EspError { code: ESP_ERR_NVS_INVALID_NAME as esp_err_t })?; 47 | value.nvs_set(self, key.as_ref()) 48 | } 49 | } 50 | 51 | impl Drop for NameSpace { 52 | fn drop(&mut self) { 53 | unsafe { nvs_close(self.handle) }; 54 | } 55 | } 56 | 57 | const DEFAULT_PART_NAME: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(NVS_DEFAULT_PART_NAME) }; 58 | static DEFAULT_INSTANCES: AtomicUsize = AtomicUsize::new(0); 59 | 60 | impl NonVolatileStorage { 61 | /// Open a non-volatile storage partition. 62 | pub fn open(name: &str) -> Result { 63 | let partition_name = CString::new(name).map_err(|_| EspError { code: ESP_ERR_NVS_INVALID_NAME as esp_err_t })?; 64 | Self::open_cstring(partition_name) 65 | } 66 | 67 | fn open_cstring(partition_name: CString) -> Result { 68 | if partition_name.as_c_str() == DEFAULT_PART_NAME { 69 | Self::init_default()?; 70 | } else { 71 | Self::init(&partition_name)?; 72 | } 73 | 74 | Ok(Self { partition_name }) 75 | } 76 | 77 | /// Open a namespace on a non-volatile storage partition. 78 | pub fn namespace(&mut self, name: &str) -> Result { 79 | let name = CString::new(name).map_err(|_| EspError { code: ESP_ERR_NVS_INVALID_NAME as esp_err_t })?; 80 | 81 | let mut handle = MaybeUninit::::uninit(); 82 | 83 | esp_ok!(nvs_open_from_partition( 84 | self.partition_name.as_ptr(), 85 | name.as_ptr(), 86 | nvs_open_mode_t::NVS_READWRITE, 87 | handle.as_mut_ptr(), 88 | ))?; 89 | 90 | Ok(NameSpace { handle: unsafe { handle.assume_init() } }) 91 | } 92 | 93 | fn init(partition_name: &CStr) -> Result<(), EspError> { 94 | esp_ok!(nvs_flash_init_partition(partition_name.as_ptr())) 95 | } 96 | 97 | fn erase(partition_name: &CStr) -> Result<(), EspError> { 98 | esp_ok!(nvs_flash_erase_partition(partition_name.as_ptr())) 99 | } 100 | 101 | pub(crate) fn init_default() -> Result<(), EspError> { 102 | loop { 103 | match DEFAULT_INSTANCES.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst) { 104 | Ok(_) => { 105 | let res = match Self::init(DEFAULT_PART_NAME) { 106 | Err(err) if err.code == ESP_ERR_NVS_NO_FREE_PAGES as esp_err_t || err.code == ESP_ERR_NVS_NEW_VERSION_FOUND as esp_err_t => { 107 | let _ = Self::erase(DEFAULT_PART_NAME); 108 | Self::init(DEFAULT_PART_NAME) 109 | }, 110 | res => res, 111 | }; 112 | 113 | return match res { 114 | Ok(()) => { 115 | DEFAULT_INSTANCES.fetch_add(1, Ordering::SeqCst); 116 | Ok(()) 117 | }, 118 | Err(err) => { 119 | DEFAULT_INSTANCES.store(0, Ordering::SeqCst); 120 | Err(err) 121 | } 122 | } 123 | }, 124 | Err(1) => continue, 125 | _ => { 126 | DEFAULT_INSTANCES.fetch_add(1, Ordering::SeqCst); 127 | return Ok(()) 128 | }, 129 | } 130 | } 131 | } 132 | 133 | pub(crate) fn deinit_default() { 134 | loop { 135 | match DEFAULT_INSTANCES.compare_exchange(2, 1, Ordering::SeqCst, Ordering::SeqCst) { 136 | Ok(_) => { 137 | unsafe { nvs_flash_deinit_partition(DEFAULT_PART_NAME.as_ptr()) }; 138 | DEFAULT_INSTANCES.fetch_sub(1, Ordering::SeqCst); 139 | return; 140 | }, 141 | Err(1) => continue, 142 | _ => { 143 | DEFAULT_INSTANCES.fetch_sub(1, Ordering::SeqCst); 144 | return 145 | }, 146 | } 147 | } 148 | } 149 | } 150 | 151 | impl Default for NonVolatileStorage { 152 | fn default() -> Self { 153 | Self::open_cstring(DEFAULT_PART_NAME.to_owned()).expect("failed to initialize default NVS partition") 154 | } 155 | } 156 | 157 | impl Drop for NonVolatileStorage { 158 | fn drop(&mut self) { 159 | if self.partition_name.as_c_str() == DEFAULT_PART_NAME { 160 | Self::deinit_default(); 161 | } else { 162 | unsafe { nvs_flash_deinit_partition(self.partition_name.as_ptr()) }; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/ap_config.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::mem; 3 | use core::num::{NonZeroU8, NonZeroU16}; 4 | 5 | use esp_idf_bindgen::{wifi_config_t, wifi_ap_config_t}; 6 | 7 | use super::{AuthMode, Cipher, Ssid, Password}; 8 | 9 | /// Configuration for an access point. 10 | #[derive(Clone)] 11 | #[repr(transparent)] 12 | pub struct ApConfig(pub(crate) wifi_config_t); 13 | 14 | impl fmt::Debug for ApConfig { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | f.debug_struct("ApConfig") 17 | .field("ssid", &self.ssid()) 18 | .field("password", &self.password()) 19 | .field("channel", &self.channel()) 20 | .field("auth_mode", &self.auth_mode()) 21 | .field("max_connection", &self.max_connection()) 22 | .field("ssid_hidden", &self.ssid_hidden()) 23 | .field("beacon_interval", &self.beacon_interval()) 24 | .field("pairwise_cipher", &self.pairwise_cipher()) 25 | .field("ftm_responder", &self.ftm_responder()) 26 | .finish() 27 | } 28 | } 29 | 30 | impl ApConfig { 31 | pub fn ssid(&self) -> &Ssid { 32 | unsafe { mem::transmute(&self.0.ap.ssid) } 33 | } 34 | 35 | pub fn password(&self) -> &Password { 36 | unsafe { mem::transmute(&self.0.ap.password) } 37 | } 38 | 39 | pub fn channel(&self) -> Option { 40 | unsafe { mem::transmute(self.0.ap.channel) } 41 | } 42 | 43 | pub fn auth_mode(&self) -> AuthMode { 44 | AuthMode::from(unsafe { self.0.ap.authmode }) 45 | } 46 | 47 | pub fn max_connection(&self) -> Option { 48 | NonZeroU8::new(unsafe { self.0.ap.max_connection }) 49 | } 50 | 51 | pub fn ssid_hidden(&self) -> bool { 52 | (unsafe { self.0.ap.ssid_hidden }) != 0 53 | } 54 | 55 | pub fn beacon_interval(&self) -> Option { 56 | NonZeroU16::new(unsafe { self.0.ap.beacon_interval }) 57 | } 58 | 59 | pub fn pairwise_cipher(&self) -> Cipher { 60 | Cipher::from(unsafe { self.0.ap.pairwise_cipher }) 61 | } 62 | 63 | pub fn ftm_responder(&self) -> bool { 64 | unsafe { self.0.ap.ftm_responder } 65 | } 66 | 67 | pub fn builder() -> ApConfigBuilder { 68 | ApConfigBuilder::default() 69 | } 70 | } 71 | 72 | /// Builder for [`ApConfig`](struct.ApConfig.html). 73 | pub struct ApConfigBuilder { 74 | ssid: Option, 75 | password: Password, 76 | channel: Option, 77 | auth_mode: AuthMode, 78 | max_connection: Option, 79 | ssid_hidden: bool, 80 | beacon_interval: Option, 81 | pairwise_cipher: Cipher, 82 | ftm_responder: bool, 83 | } 84 | 85 | impl fmt::Debug for ApConfigBuilder { 86 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 87 | f.debug_struct("ApConfigBuilder") 88 | .field("ssid", &self.ssid) 89 | .field("password", &"********") 90 | .field("channel", &self.channel) 91 | .field("auth_mode", &self.auth_mode) 92 | .field("max_connection", &self.max_connection) 93 | .field("ssid_hidden", &self.ssid_hidden) 94 | .field("beacon_interval", &self.beacon_interval) 95 | .field("pairwise_cipher", &self.pairwise_cipher) 96 | .field("ftm_responder", &self.ftm_responder) 97 | .finish() 98 | } 99 | } 100 | 101 | impl Default for ApConfigBuilder { 102 | fn default() -> Self { 103 | Self { 104 | ssid: None, 105 | password: Default::default(), 106 | channel: None, 107 | auth_mode: AuthMode::Open, 108 | max_connection: NonZeroU8::new(4), 109 | ssid_hidden: false, 110 | beacon_interval: NonZeroU16::new(100), 111 | pairwise_cipher: Cipher::None, 112 | ftm_responder: false, 113 | } 114 | } 115 | } 116 | 117 | impl ApConfigBuilder { 118 | pub fn ssid(&mut self, ssid: Ssid) -> &mut Self { 119 | self.ssid = Some(ssid); 120 | self 121 | } 122 | 123 | pub fn password(&mut self, password: Password) -> &mut Self { 124 | self.password = password; 125 | self 126 | } 127 | 128 | pub fn build(&self) -> ApConfig { 129 | let ssid = self.ssid.clone().expect("missing SSID"); 130 | let ssid_len = ssid.len() as u8; 131 | 132 | ApConfig(wifi_config_t { 133 | ap: wifi_ap_config_t { 134 | ssid: ssid.0, 135 | ssid_len, 136 | password: self.password.clone().0, 137 | channel: unsafe { mem::transmute(self.channel) }, 138 | authmode: self.auth_mode.into(), 139 | ssid_hidden: self.ssid_hidden as u8, 140 | max_connection: unsafe { mem::transmute(self.max_connection) }, 141 | beacon_interval: unsafe { mem::transmute(self.beacon_interval) }, 142 | pairwise_cipher: self.pairwise_cipher.into(), 143 | ftm_responder: self.ftm_responder, 144 | }, 145 | }) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/auth_mode.rs: -------------------------------------------------------------------------------- 1 | use esp_idf_bindgen::wifi_auth_mode_t; 2 | 3 | /// A WiFi authentication mode. 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum AuthMode { 6 | Open, 7 | Wep, 8 | WpaPsk, 9 | WpaWpa2Psk, 10 | Wpa2Psk, 11 | #[cfg(target_device = "esp32")] 12 | Wpa2Wpa3Psk, 13 | #[cfg(target_device = "esp32")] 14 | Wpa3Psk, 15 | Wpa2Enterprise, 16 | WapiPsk, 17 | } 18 | 19 | impl From for AuthMode { 20 | fn from(auth_mode: wifi_auth_mode_t) -> Self { 21 | match auth_mode { 22 | wifi_auth_mode_t::WIFI_AUTH_OPEN => AuthMode::Open, 23 | wifi_auth_mode_t::WIFI_AUTH_WEP => AuthMode::Wep, 24 | wifi_auth_mode_t::WIFI_AUTH_WPA_PSK => AuthMode::WpaPsk, 25 | wifi_auth_mode_t::WIFI_AUTH_WPA_WPA2_PSK => AuthMode::WpaWpa2Psk, 26 | wifi_auth_mode_t::WIFI_AUTH_WPA2_PSK => AuthMode::Wpa2Psk, 27 | #[cfg(target_device = "esp32")] 28 | wifi_auth_mode_t::WIFI_AUTH_WPA2_WPA3_PSK => AuthMode::Wpa2Wpa3Psk, 29 | #[cfg(target_device = "esp32")] 30 | wifi_auth_mode_t::WIFI_AUTH_WPA3_PSK => AuthMode::Wpa3Psk, 31 | wifi_auth_mode_t::WIFI_AUTH_WPA2_ENTERPRISE => AuthMode::Wpa2Enterprise, 32 | wifi_auth_mode_t::WIFI_AUTH_WAPI_PSK => AuthMode::WapiPsk, 33 | wifi_auth_mode_t::WIFI_AUTH_MAX => unreachable!("WIFI_AUTH_MAX"), 34 | } 35 | } 36 | } 37 | 38 | impl From for wifi_auth_mode_t { 39 | fn from(auth_mode: AuthMode) -> Self { 40 | match auth_mode { 41 | AuthMode::Open => wifi_auth_mode_t::WIFI_AUTH_OPEN, 42 | AuthMode::Wep => wifi_auth_mode_t::WIFI_AUTH_WEP, 43 | AuthMode::WpaPsk => wifi_auth_mode_t::WIFI_AUTH_WPA_PSK, 44 | AuthMode::WpaWpa2Psk => wifi_auth_mode_t::WIFI_AUTH_WPA_WPA2_PSK, 45 | AuthMode::Wpa2Psk => wifi_auth_mode_t::WIFI_AUTH_WPA2_PSK, 46 | #[cfg(target_device = "esp32")] 47 | AuthMode::Wpa2Wpa3Psk => wifi_auth_mode_t::WIFI_AUTH_WPA2_WPA3_PSK, 48 | #[cfg(target_device = "esp32")] 49 | AuthMode::Wpa3Psk => wifi_auth_mode_t::WIFI_AUTH_WPA3_PSK, 50 | AuthMode::Wpa2Enterprise => wifi_auth_mode_t::WIFI_AUTH_WPA2_ENTERPRISE, 51 | AuthMode::WapiPsk => wifi_auth_mode_t::WIFI_AUTH_WAPI_PSK, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/cipher.rs: -------------------------------------------------------------------------------- 1 | use esp_idf_bindgen::wifi_cipher_type_t; 2 | 3 | /// A WiFi cipher type. 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum Cipher { 6 | None, 7 | Wep40, /// WEP40 8 | Wep104, /// WEP104 9 | Tkip, /// TKIP 10 | Ccmp, /// CCMP 11 | TkipCcmp, /// TKIP and CCMP 12 | AesCmac128, /// AES-CMAC-128 13 | AesGmac128, /// AES-GMAC-128 14 | AesGmac256, /// AES-GMAC-256 15 | Sms4, /// SMS4 16 | Gcmp, /// GCMP 17 | Gcmp256, /// GCMP-256 18 | Unknown, 19 | } 20 | 21 | impl From for wifi_cipher_type_t { 22 | fn from(cipher: Cipher) -> Self { 23 | match cipher { 24 | Cipher::None => wifi_cipher_type_t::WIFI_CIPHER_TYPE_NONE, 25 | Cipher::Wep40 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_WEP40, 26 | Cipher::Wep104 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_WEP104, 27 | Cipher::Tkip => wifi_cipher_type_t::WIFI_CIPHER_TYPE_TKIP, 28 | Cipher::Ccmp => wifi_cipher_type_t::WIFI_CIPHER_TYPE_CCMP, 29 | Cipher::TkipCcmp => wifi_cipher_type_t::WIFI_CIPHER_TYPE_TKIP_CCMP, 30 | Cipher::AesCmac128 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_CMAC128, 31 | Cipher::AesGmac128 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_GMAC128, 32 | Cipher::AesGmac256 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_GMAC256, 33 | Cipher::Sms4 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_SMS4, 34 | Cipher::Gcmp => wifi_cipher_type_t::WIFI_CIPHER_TYPE_GCMP, 35 | Cipher::Gcmp256 => wifi_cipher_type_t::WIFI_CIPHER_TYPE_GCMP256, 36 | Cipher::Unknown => wifi_cipher_type_t::WIFI_CIPHER_TYPE_UNKNOWN, 37 | } 38 | } 39 | } 40 | 41 | impl From for Cipher { 42 | fn from(cipher: wifi_cipher_type_t) -> Self { 43 | match cipher { 44 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_NONE => Cipher::None, 45 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_WEP40 => Cipher::Wep40, 46 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_WEP104 => Cipher::Wep104, 47 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_TKIP => Cipher::Tkip, 48 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_CCMP => Cipher::Ccmp, 49 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_TKIP_CCMP => Cipher::TkipCcmp, 50 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_CMAC128 => Cipher::AesCmac128, 51 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_GMAC128 => Cipher::AesGmac128, 52 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_AES_GMAC256 => Cipher::AesGmac256, 53 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_SMS4 => Cipher::Sms4, 54 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_GCMP => Cipher::Gcmp, 55 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_GCMP256 => Cipher::Gcmp256, 56 | wifi_cipher_type_t::WIFI_CIPHER_TYPE_UNKNOWN => Cipher::Unknown, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/event_handler.rs: -------------------------------------------------------------------------------- 1 | use esp_idf_bindgen::{esp_event_base_t, esp_event_handler_register, esp_event_handler_unregister}; 2 | 3 | use crate::EspError; 4 | 5 | #[derive(Debug)] 6 | pub struct EventHandler { 7 | base: esp_event_base_t, 8 | id: i32, 9 | handler: extern "C" fn(*mut libc::c_void, *const i8, i32, *mut libc::c_void), 10 | } 11 | 12 | impl EventHandler { 13 | pub fn register( 14 | base: esp_event_base_t, 15 | id: i32, 16 | handler: extern "C" fn(*mut libc::c_void, *const i8, i32, *mut libc::c_void), 17 | arg: *mut libc::c_void 18 | ) -> Result { 19 | esp_ok!(esp_event_handler_register(base, id, Some(handler), arg))?; 20 | Ok(Self { base, id, handler }) 21 | } 22 | } 23 | 24 | impl Drop for EventHandler { 25 | fn drop(&mut self) { 26 | let _ = esp_ok!(esp_event_handler_unregister(self.base, self.id, Some(self.handler))); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/mod.rs: -------------------------------------------------------------------------------- 1 | use core::mem::{self, transmute, MaybeUninit}; 2 | use core::num::NonZeroU8; 3 | use std::str::Utf8Error; 4 | use std::sync::atomic::{AtomicBool, AtomicU8, Ordering::SeqCst}; 5 | use core::task::{Poll, Context, Waker}; 6 | use core::pin::Pin; 7 | 8 | use core::fmt; 9 | use macaddr::MacAddr6; 10 | use pin_project::pin_project; 11 | 12 | use crate::{EspError, nvs::NonVolatileStorage, interface::{Interface, IpInfo}}; 13 | 14 | use esp_idf_bindgen::*; 15 | 16 | mod sta_config; 17 | pub use sta_config::*; 18 | 19 | mod ap_config; 20 | pub use ap_config::*; 21 | 22 | mod scan; 23 | pub use scan::*; 24 | 25 | mod ssid; 26 | pub use ssid::Ssid; 27 | 28 | mod password; 29 | pub use password::Password; 30 | 31 | mod event_handler; 32 | use event_handler::EventHandler; 33 | 34 | mod auth_mode; 35 | pub use auth_mode::AuthMode; 36 | 37 | mod cipher; 38 | pub use cipher::Cipher; 39 | 40 | /// Error returned by [`Ssid::from_bytes`](struct.Ssid.html#method.from_bytes) 41 | /// and [`Password::from_bytes`](struct.Password.html#method.from_bytes). 42 | #[derive(Debug)] 43 | pub enum WifiConfigError { 44 | /// SSID or password contains interior `NUL`-bytes. 45 | InteriorNul(usize), 46 | /// SSID or password is too long. 47 | TooLong(usize, usize), 48 | /// SSID or password is not valid UTF-8. 49 | Utf8Error(Utf8Error), 50 | } 51 | 52 | impl fmt::Display for WifiConfigError { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | match self { 55 | Self::InteriorNul(pos) => write!(f, "data provided contains an interior nul byte at pos {}", pos), 56 | Self::TooLong(max, actual) => write!(f, "data provided is {} bytes long, but maximum is {} bytes", max, actual), 57 | Self::Utf8Error(utf8_error) => utf8_error.fmt(f), 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct Ap { 64 | mode: ApMode, 65 | } 66 | 67 | impl Ap { 68 | pub fn config(&self) -> ApConfig { 69 | let config = MaybeUninit::::uninit(); 70 | esp_ok!(esp_wifi_get_config(wifi_interface_t::WIFI_IF_AP, config.as_ptr() as *mut _)).unwrap(); 71 | unsafe { config.assume_init() } 72 | } 73 | 74 | pub fn ip_info(&self) -> IpInfo { 75 | Interface::Ap.ip_info() 76 | } 77 | } 78 | 79 | #[derive(Debug)] 80 | pub struct Sta { 81 | mode: StaMode, 82 | } 83 | 84 | impl Sta { 85 | pub fn config(&self) -> StaConfig { 86 | let config = MaybeUninit::::uninit(); 87 | esp_ok!(esp_wifi_get_config(wifi_interface_t::WIFI_IF_STA, config.as_ptr() as *mut _)).unwrap(); 88 | unsafe { config.assume_init() } 89 | } 90 | 91 | pub fn ip_info(&self) -> IpInfo { 92 | Interface::Sta.ip_info() 93 | } 94 | } 95 | 96 | #[derive(Debug)] 97 | enum WifiInner { 98 | None, 99 | Ap(Ap), 100 | Sta(Sta), 101 | ApSta(Ap, Sta), 102 | } 103 | 104 | impl Default for WifiInner { 105 | fn default() -> Self { 106 | Self::None 107 | } 108 | } 109 | 110 | /// An instance of the WiFi peripheral. 111 | #[must_use = "WiFi will be stopped and deinitialized immediately. Drop it explicitly after you are done using it or create a named binding."] 112 | #[derive(Debug)] 113 | pub struct Wifi { 114 | inner: WifiInner, 115 | } 116 | 117 | #[cfg(target_device = "esp8266")] 118 | fn initialize_network_interface() { 119 | unsafe { tcpip_adapter_init() }; 120 | } 121 | 122 | #[cfg(target_device = "esp32")] 123 | fn initialize_network_interface() { 124 | static NETIF_STATE: AtomicU8 = AtomicU8::new(0); 125 | 126 | loop { 127 | match NETIF_STATE.compare_exchange(0, 1, SeqCst, SeqCst) { 128 | Ok(0) => { 129 | esp_ok!(esp_netif_init()).expect("failed to initialize network interface"); 130 | NETIF_STATE.store(2, SeqCst); 131 | return; 132 | }, 133 | Err(1) => continue, 134 | _ => return, 135 | } 136 | } 137 | } 138 | 139 | fn event_loop_create_default() { 140 | static EVENT_LOOP_STATE: AtomicU8 = AtomicU8::new(0); 141 | 142 | loop { 143 | match EVENT_LOOP_STATE.compare_exchange(0, 1, SeqCst, SeqCst) { 144 | Ok(0) => { 145 | esp_ok!(esp_event_loop_create_default()).expect("failed to initialize default event loop"); 146 | EVENT_LOOP_STATE.store(2, SeqCst); 147 | return; 148 | }, 149 | Err(1) => continue, 150 | _ => return, 151 | } 152 | } 153 | } 154 | 155 | static AP_COUNT: AtomicU8 = AtomicU8::new(0); 156 | static STA_COUNT: AtomicU8 = AtomicU8::new(0); 157 | 158 | fn get_mode() -> Result { 159 | let mut mode = wifi_mode_t::WIFI_MODE_NULL; 160 | esp_ok!(esp_wifi_get_mode(&mut mode))?; 161 | eprintln!("esp_wifi_get_mode: {:?} ({} APs, {} STAs)", mode, AP_COUNT.load(SeqCst), STA_COUNT.load(SeqCst)); 162 | Ok(mode) 163 | } 164 | 165 | #[derive(Debug)] 166 | struct ApMode; 167 | 168 | impl ApMode { 169 | pub fn enter() -> Self { 170 | enter_ap_mode().unwrap(); 171 | Self 172 | } 173 | } 174 | 175 | impl Drop for ApMode { 176 | fn drop(&mut self) { 177 | let _ = leave_ap_mode(); 178 | } 179 | } 180 | 181 | fn enter_ap_mode() -> Result<(), EspError> { 182 | let count = AP_COUNT.fetch_add(1, SeqCst); 183 | eprintln!("AP ENTER {}", count); 184 | if count > 0 { 185 | return Ok(()) 186 | } 187 | 188 | let current_mode = get_mode()?; 189 | 190 | let new_mode = match current_mode { 191 | wifi_mode_t::WIFI_MODE_AP | wifi_mode_t::WIFI_MODE_APSTA => return Ok(()), 192 | wifi_mode_t::WIFI_MODE_NULL => wifi_mode_t::WIFI_MODE_AP, 193 | wifi_mode_t::WIFI_MODE_STA => wifi_mode_t::WIFI_MODE_APSTA, 194 | _ => unreachable!(), 195 | }; 196 | 197 | eprintln!("{:?} -> {:?}", current_mode, new_mode); 198 | esp_ok!(esp_wifi_set_mode(new_mode)) 199 | } 200 | 201 | fn leave_ap_mode() -> Result<(), EspError> { 202 | let count = AP_COUNT.fetch_sub(1, SeqCst); 203 | eprintln!("AP LEAVE {}", count); 204 | if count != 1 { 205 | return Ok(()) 206 | } 207 | 208 | let current_mode = get_mode()?; 209 | 210 | let new_mode = match current_mode { 211 | wifi_mode_t::WIFI_MODE_AP => wifi_mode_t::WIFI_MODE_NULL, 212 | wifi_mode_t::WIFI_MODE_APSTA => wifi_mode_t::WIFI_MODE_STA, 213 | _ => unreachable!(), 214 | }; 215 | 216 | eprintln!("{:?} -> {:?}", current_mode, new_mode); 217 | esp_ok!(esp_wifi_set_mode(new_mode))?; 218 | get_mode()?; 219 | 220 | if new_mode == wifi_mode_t::WIFI_MODE_NULL { 221 | // esp_ok!(esp_wifi_restore())?; 222 | esp_ok!(esp_wifi_stop())?; 223 | } 224 | 225 | Ok(()) 226 | } 227 | 228 | #[derive(Debug)] 229 | struct StaMode; 230 | 231 | impl StaMode { 232 | pub fn enter() -> Self { 233 | enter_sta_mode().unwrap(); 234 | Self 235 | } 236 | } 237 | 238 | impl Drop for StaMode { 239 | fn drop(&mut self) { 240 | let _ = leave_sta_mode(); 241 | } 242 | } 243 | 244 | fn enter_sta_mode() -> Result<(), EspError> { 245 | let count = STA_COUNT.fetch_add(1, SeqCst); 246 | eprintln!("STA ENTER {}", count); 247 | if count > 0 { 248 | return Ok(()) 249 | } 250 | 251 | let current_mode = get_mode()?; 252 | 253 | let new_mode = match current_mode { 254 | wifi_mode_t::WIFI_MODE_STA | wifi_mode_t::WIFI_MODE_APSTA => return Ok(()), 255 | wifi_mode_t::WIFI_MODE_NULL => wifi_mode_t::WIFI_MODE_STA, 256 | wifi_mode_t::WIFI_MODE_AP => wifi_mode_t::WIFI_MODE_APSTA, 257 | _ => unreachable!(), 258 | }; 259 | 260 | eprintln!("{:?} -> {:?}", current_mode, new_mode); 261 | esp_ok!(esp_wifi_set_mode(new_mode)) 262 | } 263 | 264 | fn leave_sta_mode() -> Result<(), EspError> { 265 | let count = STA_COUNT.fetch_sub(1, SeqCst); 266 | eprintln!("STA LEAVE {}", count); 267 | if count != 1 { 268 | return Ok(()) 269 | } 270 | 271 | let current_mode = get_mode()?; 272 | 273 | let new_mode = match current_mode { 274 | wifi_mode_t::WIFI_MODE_STA => wifi_mode_t::WIFI_MODE_NULL, 275 | wifi_mode_t::WIFI_MODE_APSTA => wifi_mode_t::WIFI_MODE_AP, 276 | _ => unreachable!(), 277 | }; 278 | 279 | eprintln!("{:?} -> {:?}", current_mode, new_mode); 280 | esp_ok!(esp_wifi_set_mode(new_mode))?; 281 | get_mode()?; 282 | 283 | if new_mode == wifi_mode_t::WIFI_MODE_NULL { 284 | esp_ok!(esp_wifi_stop())?; 285 | } 286 | 287 | Ok(()) 288 | } 289 | 290 | static WIFI_ACTIVE: AtomicBool = AtomicBool::new(false); 291 | 292 | impl Wifi { 293 | /// Take the WiFi peripheral if it is not already in use. 294 | pub fn take() -> Option { 295 | if WIFI_ACTIVE.compare_exchange(false, true, SeqCst, SeqCst) == Err(true) { 296 | None 297 | } else { 298 | initialize_network_interface(); 299 | 300 | event_loop_create_default(); 301 | 302 | NonVolatileStorage::init_default().expect("failed to initialize default NVS partition"); 303 | let config = wifi_init_config_t::default(); 304 | esp_ok!(esp_wifi_init(&config)).expect("failed to initialize WiFi with default configuration"); 305 | esp_ok!(esp_wifi_set_mode(wifi_mode_t::WIFI_MODE_NULL)).unwrap(); 306 | 307 | Some(Wifi { inner: WifiInner::None }) 308 | } 309 | } 310 | 311 | /// Start an access point using the specified [`ApConfig`](struct.ApConfig.html). 312 | pub fn start_ap(&mut self, mut config: ApConfig) -> Result<(), WifiError> { 313 | eprintln!("Starting AP"); 314 | 315 | let interface = Interface::Ap; 316 | interface.init(); 317 | 318 | let ap_mode = ApMode::enter(); 319 | 320 | if let Err(err) = esp_ok!(esp_wifi_set_config(wifi_interface_t::WIFI_IF_AP, &mut config.0)).and_then(|_| { 321 | esp_ok!(esp_wifi_start()) 322 | }) { 323 | return Err(err.into()); 324 | } 325 | 326 | let inner = match mem::take(&mut self.inner) { 327 | WifiInner::Sta(sta) => WifiInner::ApSta(Ap { mode: ap_mode }, sta), 328 | _ => WifiInner::Ap(Ap { mode: ap_mode }), 329 | }; 330 | self.inner = inner; 331 | 332 | Ok(()) 333 | } 334 | 335 | /// Connect to a WiFi network using the specified [`StaConfig`](struct.StaConfig.html). 336 | pub fn connect_sta<'w>(&'w mut self, mut config: StaConfig) -> ConnectFuture<'w> { 337 | eprintln!("Starting STA connection"); 338 | 339 | Interface::Sta.init(); 340 | 341 | let sta_mode = Some(StaMode::enter()); 342 | 343 | let state = if let Err(err) = esp_ok!(esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &mut config.0)) { 344 | ConnectFutureState::Failed(err.into()) 345 | } else { 346 | ConnectFutureState::Starting 347 | }; 348 | 349 | ConnectFuture { waker: None, mode: sta_mode, state, handlers: None, wifi: self } 350 | } 351 | } 352 | 353 | impl Wifi { 354 | /// Scan nearby WiFi networks using the specified [`ScanConfig`](struct.ScanConfig.html). 355 | pub fn scan(&mut self, scan_config: &ScanConfig) -> ScanFuture<'_> { 356 | ScanFuture::new(self, scan_config) 357 | } 358 | 359 | pub fn as_sta(&self) -> Option<&Sta> { 360 | match &self.inner { 361 | WifiInner::Sta(sta) => Some(sta), 362 | WifiInner::ApSta(_, sta) => Some(sta), 363 | _ => None, 364 | } 365 | } 366 | 367 | pub fn as_ap(&self) -> Option<&Ap> { 368 | match &self.inner { 369 | WifiInner::Ap(ap) => Some(ap), 370 | WifiInner::ApSta(ap, _) => Some(ap), 371 | _ => None, 372 | } 373 | } 374 | } 375 | 376 | impl Drop for Wifi { 377 | /// Stops a running WiFi instance and deinitializes it, making it available again 378 | /// by calling [`Wifi::take()`](struct.Wifi.html#method.take). 379 | fn drop(&mut self) { 380 | if !matches!(self.inner, WifiInner::None) { 381 | unsafe { esp_wifi_stop() }; 382 | } 383 | 384 | let _ = esp_ok!(esp_wifi_deinit()); 385 | NonVolatileStorage::deinit_default(); 386 | 387 | WIFI_ACTIVE.store(false, SeqCst); 388 | } 389 | } 390 | 391 | impl Wifi { 392 | /// Stop a running WiFi in station mode. 393 | pub fn stop_sta(&mut self) -> &mut Wifi { 394 | eprintln!("Stopping STA"); 395 | 396 | let inner = match mem::take(&mut self.inner) { 397 | WifiInner::ApSta(ap, _) => WifiInner::Ap(ap), 398 | _ => WifiInner::None, 399 | }; 400 | self.inner = inner; 401 | 402 | self 403 | } 404 | 405 | /// Stop a running WiFi access point. 406 | pub fn stop_ap(&mut self) -> &mut Wifi { 407 | eprintln!("Stopping AP"); 408 | 409 | let inner = match mem::take(&mut self.inner) { 410 | WifiInner::ApSta(_, sta) => WifiInner::Sta(sta), 411 | _ => WifiInner::None, 412 | }; 413 | self.inner = inner; 414 | 415 | self 416 | } 417 | } 418 | 419 | #[derive(Debug)] 420 | enum ConnectFutureState { 421 | Failed(WifiError), 422 | Starting, 423 | ConnectedWithoutIp { ssid: Ssid, bssid: MacAddr6, channel: Option, auth_mode: AuthMode }, 424 | Connected { ip_info: IpInfo, ssid: Ssid, bssid: MacAddr6, channel: Option, auth_mode: AuthMode }, 425 | } 426 | 427 | /// A future representing an ongoing connection to an access point. 428 | #[must_use = "futures do nothing unless polled"] 429 | #[pin_project] 430 | #[derive(Debug)] 431 | pub struct ConnectFuture<'w> { 432 | waker: Option, 433 | mode: Option, 434 | state: ConnectFutureState, 435 | handlers: Option<[EventHandler; 4]>, 436 | wifi: &'w mut Wifi, 437 | } 438 | 439 | /// The type returned when a [`ConnectFuture`](struct.ConnectFuture.html) succeeds. 440 | #[derive(Debug, Clone)] 441 | pub struct ConnectionInfo { 442 | ip_info: IpInfo, 443 | ssid: Ssid, 444 | bssid: MacAddr6, 445 | channel: NonZeroU8, 446 | auth_mode: AuthMode, 447 | } 448 | 449 | impl ConnectionInfo { 450 | #[inline] 451 | pub fn ip_info(&self) -> &IpInfo { 452 | &self.ip_info 453 | } 454 | 455 | #[inline] 456 | pub fn ssid(&self) -> &Ssid { 457 | &self.ssid 458 | } 459 | 460 | #[inline] 461 | pub fn bssid(&self) -> &MacAddr6 { 462 | &self.bssid 463 | } 464 | 465 | #[inline] 466 | pub fn channel(&self) -> NonZeroU8 { 467 | self.channel 468 | } 469 | 470 | #[inline] 471 | pub fn auth_mode(&self) -> AuthMode { 472 | self.auth_mode 473 | } 474 | } 475 | 476 | /// The error type returned when a [`ConnectFuture`](struct.ConnectFuture.html) fails. 477 | #[derive(Debug, Clone)] 478 | pub struct ConnectionError { 479 | ssid: Ssid, 480 | bssid: MacAddr6, 481 | reason: wifi_err_reason_t, 482 | } 483 | 484 | impl fmt::Display for ConnectionError { 485 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 486 | write!(f, "Error connecting to {} ({}): {:?}", self.ssid, self.bssid, self.reason) 487 | } 488 | } 489 | 490 | /// The error type for operations on a [`Wifi`](struct.Wifi.html) instance. 491 | #[derive(Debug, Clone)] 492 | pub enum WifiError { 493 | /// An internal error not directly related to WiFi. 494 | Internal(EspError), 495 | /// A connection error returned when a [`ConnectFuture`](struct.ConnectFuture.html) fails. 496 | ConnectionError(ConnectionError), 497 | } 498 | 499 | impl From for WifiError { 500 | fn from(esp_error: EspError) -> Self { 501 | Self::Internal(esp_error) 502 | } 503 | } 504 | 505 | impl fmt::Display for WifiError { 506 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 507 | match self { 508 | Self::Internal(esp_error) => esp_error.fmt(f), 509 | Self::ConnectionError(error) => error.fmt(f), 510 | } 511 | } 512 | } 513 | 514 | impl core::future::Future for ConnectFuture<'_> { 515 | type Output = Result; 516 | 517 | #[cfg(target_device = "esp8266")] 518 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { 519 | Poll::Pending 520 | } 521 | 522 | #[cfg(target_device = "esp32")] 523 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 524 | match self.state { 525 | ConnectFutureState::Starting => { 526 | self.waker.replace(cx.waker().clone()); 527 | 528 | let register_handlers = |arg: *mut ConnectFuture| -> Result<[EventHandler; 4], EspError> { 529 | Ok([ 530 | EventHandler::register(unsafe { WIFI_EVENT }, wifi_event_t::WIFI_EVENT_STA_START as _, wifi_sta_handler, arg as _)?, 531 | EventHandler::register(unsafe { WIFI_EVENT }, wifi_event_t::WIFI_EVENT_STA_CONNECTED as _, wifi_sta_handler, arg as _)?, 532 | EventHandler::register(unsafe { WIFI_EVENT }, wifi_event_t::WIFI_EVENT_STA_DISCONNECTED as _, wifi_sta_handler, arg as _)?, 533 | EventHandler::register(unsafe { IP_EVENT }, ip_event_t::IP_EVENT_STA_GOT_IP as _, wifi_sta_handler, arg as _)?, 534 | ]) 535 | }; 536 | 537 | match register_handlers(&mut *self as *mut _) { 538 | Ok(handlers) => { self.handlers.replace(handlers); }, 539 | Err(err) => { 540 | return Poll::Ready(Err(WifiError::from(err))); 541 | }, 542 | } 543 | 544 | if let Err(err) = esp_ok!(esp_wifi_start()) { 545 | return Poll::Ready(Err(WifiError::from(err))); 546 | } 547 | 548 | Poll::Pending 549 | }, 550 | ConnectFutureState::Failed(ref err) => { 551 | return Poll::Ready(Err(WifiError::from(err.clone()))); 552 | }, 553 | ConnectFutureState::ConnectedWithoutIp { .. } => { 554 | Poll::Pending 555 | }, 556 | ConnectFutureState::Connected { 557 | ip_info, 558 | ssid, 559 | bssid, 560 | channel, 561 | auth_mode, 562 | } => { 563 | eprintln!("Ended STA connection"); 564 | 565 | let connection_info = ConnectionInfo { 566 | ip_info, 567 | ssid, 568 | bssid, 569 | channel: channel.unwrap(), 570 | auth_mode, 571 | }; 572 | 573 | let mode = self.mode.take().unwrap(); 574 | let inner = match mem::take(&mut self.wifi.inner) { 575 | WifiInner::Ap(ap) => WifiInner::ApSta(ap, Sta { mode }), 576 | _ => WifiInner::Sta(Sta { mode }), 577 | }; 578 | self.wifi.inner = inner; 579 | 580 | Poll::Ready(Ok(connection_info)) 581 | }, 582 | } 583 | } 584 | } 585 | 586 | #[cfg(target_device = "esp32")] 587 | extern "C" fn wifi_sta_handler( 588 | event_handler_arg: *mut libc::c_void, 589 | event_base: esp_event_base_t, 590 | event_id: i32, 591 | event_data: *mut libc::c_void, 592 | ) { 593 | // SAFETY: `wifi_sta_handler` is only registered while the `event_handler_arg` is 594 | // pointing to a `ConnectFuture` contained in a `Pin`. 595 | let mut f = unsafe { Pin::new_unchecked(&mut *(event_handler_arg as *mut ConnectFuture)) }; 596 | 597 | if event_base == unsafe { WIFI_EVENT } { 598 | let event_id: wifi_event_t = unsafe { transmute(event_id) }; 599 | 600 | eprintln!("WIFI_EVENT: {:?}", event_id); 601 | 602 | match event_id { 603 | wifi_event_t::WIFI_EVENT_STA_START => { 604 | if let Err(err) = esp_ok!(esp_wifi_connect()) { 605 | f.state = ConnectFutureState::Failed(err.into()); 606 | f.waker.as_ref().map(|w| w.wake_by_ref()); 607 | } 608 | }, 609 | wifi_event_t::WIFI_EVENT_STA_CONNECTED => { 610 | let event = unsafe { &*(event_data as *const wifi_event_sta_connected_t) }; 611 | 612 | eprintln!("EVENT_DATA: {:?}", event); 613 | 614 | let ssid = Ssid(event.ssid); 615 | let bssid = MacAddr6::from(event.bssid); 616 | let channel = NonZeroU8::new(event.channel); 617 | let auth_mode = AuthMode::from(event.authmode); 618 | 619 | f.state = ConnectFutureState::ConnectedWithoutIp { ssid, bssid, channel, auth_mode }; 620 | 621 | eprintln!("EVENT_STATE: {:?}", f.state); 622 | }, 623 | wifi_event_t::WIFI_EVENT_STA_DISCONNECTED => { 624 | let event = unsafe { &*(event_data as *const wifi_event_sta_disconnected_t) }; 625 | 626 | eprintln!("EVENT_DATA: {:?}", event); 627 | 628 | let ssid = Ssid(event.ssid); 629 | let bssid = MacAddr6::from(event.bssid); 630 | let reason: wifi_err_reason_t = unsafe { transmute(event.reason as u32) }; 631 | 632 | let error = ConnectionError { 633 | ssid, bssid, reason 634 | }; 635 | 636 | f.state = ConnectFutureState::Failed(WifiError::ConnectionError(error)); 637 | 638 | eprintln!("EVENT_STATE: {:?}", f.state); 639 | 640 | f.waker.as_ref().map(|w| w.wake_by_ref()); 641 | }, 642 | _ => (), 643 | } 644 | } else if event_base == unsafe { IP_EVENT } { 645 | let event_id: ip_event_t = unsafe { transmute(event_id) }; 646 | 647 | eprintln!("IP_EVENT: {:?}", event_id); 648 | 649 | match event_id { 650 | ip_event_t::IP_EVENT_STA_GOT_IP => { 651 | let event = unsafe { &*(event_data as *const ip_event_got_ip_t) }; 652 | 653 | let ip_info = unsafe { IpInfo::from_native_unchecked(event.ip_info) }; 654 | 655 | eprintln!("EVENT_DATA: {:?}", event); 656 | 657 | if let ConnectFutureState::ConnectedWithoutIp { ssid, bssid, channel, auth_mode } = mem::replace(&mut f.state, ConnectFutureState::Starting) { 658 | f.state = ConnectFutureState::Connected { ip_info, ssid, bssid, channel, auth_mode }; 659 | } else { 660 | unreachable!(); 661 | } 662 | 663 | eprintln!("EVENT_STATE: {:?}", f.state); 664 | 665 | f.waker.as_ref().map(|w| w.wake_by_ref()); 666 | }, 667 | _ => (), 668 | } 669 | } 670 | } 671 | 672 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/password.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::ptr; 3 | use std::str::{self, FromStr}; 4 | 5 | use super::WifiConfigError; 6 | 7 | const PASSWORD_MAX_LEN: usize = 64; 8 | 9 | /// A WiFi password. 10 | #[derive(Copy, Clone)] 11 | #[repr(transparent)] 12 | pub struct Password(pub(crate) [u8; PASSWORD_MAX_LEN]); 13 | 14 | impl Password { 15 | fn len(&self) -> usize { 16 | memchr::memchr(0, &self.0).unwrap_or(PASSWORD_MAX_LEN) 17 | } 18 | 19 | pub fn as_str(&self) -> &str { 20 | &unsafe { str::from_utf8_unchecked(&self.0[..self.len()]) } 21 | } 22 | 23 | pub fn from_bytes(bytes: &[u8]) -> Result { 24 | let ssid_len = bytes.len(); 25 | 26 | if ssid_len > PASSWORD_MAX_LEN { 27 | return Err(WifiConfigError::TooLong(PASSWORD_MAX_LEN, ssid_len)) 28 | } 29 | 30 | if let Err(utf8_error) = str::from_utf8(bytes) { 31 | return Err(WifiConfigError::Utf8Error(utf8_error)) 32 | } 33 | 34 | if let Some(pos) = memchr::memchr(0, bytes) { 35 | return Err(WifiConfigError::InteriorNul(pos)) 36 | } 37 | 38 | Ok(unsafe { Self::from_bytes_unchecked(bytes) }) 39 | } 40 | 41 | /// SAFTEY: The caller has to ensure that `bytes` does not contain a `NUL` byte and 42 | /// does not exceed 64 bytes. 43 | pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Password { 44 | let password_len = bytes.len(); 45 | let mut password = Self([0; PASSWORD_MAX_LEN]); 46 | ptr::copy_nonoverlapping(bytes.as_ptr(), password.0.as_mut_ptr(), password_len); 47 | password 48 | } 49 | } 50 | 51 | impl Default for Password { 52 | #[inline(always)] 53 | fn default() -> Self { 54 | Self([0; PASSWORD_MAX_LEN]) 55 | } 56 | } 57 | 58 | impl FromStr for Password { 59 | type Err = WifiConfigError; 60 | 61 | fn from_str(s: &str) -> Result { 62 | Self::from_bytes(s.as_bytes()) 63 | } 64 | } 65 | 66 | impl fmt::Debug for Password { 67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 68 | f.debug_struct("Password") 69 | .field("password", &self.as_str()) 70 | .finish() 71 | } 72 | } 73 | 74 | impl fmt::Display for Password { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | #[cfg(debug)] 77 | return self.as_str().fmt(f); 78 | 79 | #[cfg(not(debug))] 80 | return "********".fmt(f); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/scan.rs: -------------------------------------------------------------------------------- 1 | use core::cmp; 2 | use core::future::Future; 3 | use core::mem::{self, MaybeUninit}; 4 | use core::pin::Pin; 5 | use core::ptr; 6 | use core::task::{Poll, Context, Waker}; 7 | use std::time::Duration; 8 | 9 | use esp_idf_bindgen::{ 10 | esp_wifi_scan_start, 11 | esp_wifi_scan_get_ap_num, 12 | esp_wifi_scan_get_ap_records, 13 | wifi_ap_record_t, 14 | wifi_scan_config_t, 15 | wifi_scan_time_t, 16 | wifi_active_scan_time_t, 17 | wifi_scan_type_t, 18 | }; 19 | use macaddr::MacAddr6; 20 | use pin_project::pin_project; 21 | 22 | use super::*; 23 | 24 | /// Scan type used for scanning nearby WiFi networks. 25 | /// 26 | /// For an explanation of the two types, refer to https://www.wi-fi.org/knowledge-center/faq/what-are-passive-and-active-scanning. 27 | /// 28 | /// All durations must be between `1` and `u32::max_value()` milliseconds. A duration of `0` means that the default duration will be used. 29 | #[derive(Debug, Clone)] 30 | pub enum ScanType { 31 | /// Active scanning with a minimum duration of `min` and a maximum duration of `max` per channel. 32 | Active { min: Duration, max: Duration }, 33 | /// Passive scanning with a maximum duration of `max` per channel. 34 | Passive { max: Duration }, 35 | } 36 | 37 | impl Default for ScanType { 38 | fn default() -> Self { 39 | Self::Active { min: Duration::from_millis(0), max: Duration::from_millis(0) } 40 | } 41 | } 42 | 43 | /// Configuration used for scanning nearby WiFi networks. 44 | #[derive(Default, Debug, Clone)] 45 | pub struct ScanConfig { 46 | ssid: Option, 47 | bssid: Option, 48 | channel: u8, 49 | show_hidden: bool, 50 | scan_type: ScanType, 51 | } 52 | 53 | impl ScanConfig { 54 | pub fn builder() -> ScanConfigBuilder { 55 | ScanConfigBuilder { 56 | ssid: None, 57 | bssid: None, 58 | channel: 0, 59 | show_hidden: false, 60 | scan_type: Default::default(), 61 | } 62 | } 63 | } 64 | 65 | /// Builder for [`ScanConfig`](struct.ScanConfig.html). 66 | #[derive(Debug, Clone)] 67 | pub struct ScanConfigBuilder { 68 | ssid: Option, 69 | bssid: Option, 70 | channel: u8, 71 | show_hidden: bool, 72 | scan_type: ScanType, 73 | } 74 | 75 | impl ScanConfigBuilder { 76 | pub fn ssid(mut self, ssid: impl Into>) -> ScanConfigBuilder { 77 | self.ssid = ssid.into(); 78 | self 79 | } 80 | 81 | pub fn bssid(mut self, bssid: impl Into>) -> ScanConfigBuilder { 82 | self.bssid = bssid.into(); 83 | self 84 | } 85 | 86 | 87 | pub fn channel(mut self, channel: u8) -> ScanConfigBuilder { 88 | self.channel = channel; 89 | self 90 | } 91 | 92 | pub fn show_hidden(mut self, show_hidden: bool) -> ScanConfigBuilder { 93 | self.show_hidden = show_hidden; 94 | self 95 | } 96 | 97 | pub fn scan_type(mut self, scan_type: ScanType) -> ScanConfigBuilder { 98 | #[cfg(debug)] 99 | if let ScanType::Active { min, max } = scan_type { 100 | if max != Duration::default() { 101 | assert!(min <= max); 102 | } 103 | } 104 | self.scan_type = scan_type; 105 | self 106 | } 107 | 108 | pub fn build(self) -> ScanConfig { 109 | let Self { ssid, bssid, channel, show_hidden, scan_type } = self; 110 | ScanConfig { ssid, bssid, channel, show_hidden, scan_type } 111 | } 112 | } 113 | 114 | /// An access point record returned by a [`ScanFuture`](struct.ScanFuture.html). 115 | #[derive(Debug, Clone)] 116 | pub struct ApRecord { 117 | ssid: Ssid, 118 | bssid: MacAddr6, 119 | auth_mode: AuthMode, 120 | } 121 | 122 | impl ApRecord { 123 | pub fn ssid(&self) -> &Ssid { 124 | &self.ssid 125 | } 126 | 127 | pub fn bssid(&self) -> &MacAddr6 { 128 | &self.bssid 129 | } 130 | 131 | pub fn auth_mode(&self) -> &AuthMode { 132 | &self.auth_mode 133 | } 134 | } 135 | 136 | #[derive(Debug)] 137 | enum ScanFutureState { 138 | Starting(wifi_scan_config_t, StaMode, Option), 139 | Done, 140 | } 141 | 142 | /// A future representing a scan of nearby WiFi networks. 143 | #[must_use = "futures do nothing unless polled"] 144 | #[pin_project] 145 | #[derive(Debug)] 146 | pub struct ScanFuture<'w> { 147 | handler: Option, 148 | state: ScanFutureState, 149 | wifi: &'w mut Wifi, 150 | } 151 | 152 | impl<'w> ScanFuture<'w> { 153 | #[inline] 154 | pub(crate) fn new(wifi: &'w mut Wifi, config: &ScanConfig) -> Self { 155 | let duration_as_millis_rounded = |dur: Duration| { 156 | let nanos = dur.as_nanos(); 157 | 158 | if nanos == 0 { 159 | 0 160 | } else { 161 | cmp::min(u32::max_value() as u128, cmp::max(1_000_000, nanos) / 1_000_000) as u32 162 | } 163 | }; 164 | 165 | let (scan_type, scan_time) = match config.scan_type { 166 | ScanType::Active { min, max } => ( 167 | wifi_scan_type_t::WIFI_SCAN_TYPE_ACTIVE, 168 | wifi_scan_time_t { 169 | active: wifi_active_scan_time_t { 170 | min: duration_as_millis_rounded(min), 171 | max: duration_as_millis_rounded(max), 172 | }, 173 | #[cfg(target_device = "esp32")] 174 | passive: 0, 175 | }, 176 | ), 177 | ScanType::Passive { max } => ( 178 | wifi_scan_type_t::WIFI_SCAN_TYPE_PASSIVE, 179 | wifi_scan_time_t { 180 | #[cfg(target_device = "esp32")] 181 | active: wifi_active_scan_time_t { min: 0, max: 0 }, 182 | passive: duration_as_millis_rounded(max), 183 | }, 184 | ) 185 | }; 186 | 187 | let config = wifi_scan_config_t { 188 | ssid: config.ssid.as_ref().map_or_else(ptr::null_mut, |ssid| ssid.0.as_ptr() as *mut _), 189 | bssid: config.bssid.as_ref().map_or_else(ptr::null_mut, |bssid| bssid as *const _ as *mut _), 190 | channel: config.channel, 191 | show_hidden: config.show_hidden, 192 | scan_type, 193 | scan_time, 194 | }; 195 | 196 | Self { 197 | handler: None, 198 | state: ScanFutureState::Starting(config, StaMode::enter(), None), 199 | wifi, 200 | } 201 | } 202 | } 203 | 204 | impl Future for ScanFuture<'_> { 205 | type Output = Result, WifiError>; 206 | 207 | #[cfg(target_device = "esp8266")] 208 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { 209 | Poll::Pending 210 | } 211 | 212 | #[cfg(target_device = "esp32")] 213 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 214 | match self.state { 215 | ScanFutureState::Starting(config, _, ref mut waker) => { 216 | waker.replace(cx.waker().clone()); 217 | 218 | esp_ok!(esp_wifi_start())?; 219 | 220 | let arg = &mut *self as *mut _; 221 | self.handler.replace(EventHandler::register( 222 | unsafe { WIFI_EVENT }, wifi_event_t::WIFI_EVENT_SCAN_DONE as _, wifi_scan_done_handler, arg as _ 223 | )?); 224 | 225 | if let Err(err) = esp_ok!(esp_wifi_scan_start(&config, false)) { 226 | return Poll::Ready(Err(err.into())) 227 | }; 228 | 229 | Poll::Pending 230 | }, 231 | ScanFutureState::Done => { 232 | Poll::Ready(Ok(get_ap_records()?)) 233 | } 234 | } 235 | } 236 | } 237 | 238 | #[inline] 239 | fn get_ap_records() -> Result, EspError> { 240 | let mut ap_num = 0; 241 | esp_ok!(esp_wifi_scan_get_ap_num(&mut ap_num))?; 242 | 243 | let mut aps: Vec> = vec![MaybeUninit::uninit(); ap_num as usize]; 244 | esp_ok!(esp_wifi_scan_get_ap_records(&mut ap_num as _, aps.as_mut_ptr() as *mut wifi_ap_record_t))?; 245 | 246 | Ok(aps.into_iter().map(|ap| { 247 | // SAFETY: At this point we have asserted that `esp_wifi_scan_get_ap_records` returned `ESP_OK`. 248 | let ap = unsafe { ap.assume_init() }; 249 | 250 | // SAFETY: We made sure that the SSID does not contain a `NUL` byte and 251 | // `ap.ssid` is at most 32 bytes long. 252 | let ssid = unsafe { 253 | let ssid_len = memchr::memchr(0, &ap.ssid).unwrap_or(ap.ssid.len()); 254 | Ssid::from_bytes_unchecked(&ap.ssid[..ssid_len]) 255 | }; 256 | 257 | let bssid = MacAddr6::from(ap.bssid); 258 | let auth_mode = ap.authmode.into(); 259 | 260 | ApRecord { ssid, bssid, auth_mode } 261 | }).collect()) 262 | } 263 | 264 | #[cfg(target_device = "esp32")] 265 | extern "C" fn wifi_scan_done_handler( 266 | event_handler_arg: *mut libc::c_void, 267 | _event_base: esp_idf_bindgen::esp_event_base_t, 268 | _event_id: i32, 269 | _event_data: *mut libc::c_void, 270 | ) { 271 | // SAFETY: `wifi_scan_done_handler` is only registered while the `event_handler_arg` is 272 | // pointing to a `ScanFuture` contained in a `Pin`. 273 | let mut f = unsafe { Pin::new_unchecked(&mut *(event_handler_arg as *mut ScanFuture)) }; 274 | if let ScanFutureState::Starting(_, _, Some(waker)) = mem::replace(&mut f.state, ScanFutureState::Done) { 275 | waker.wake(); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/ssid.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use core::fmt; 3 | use core::ops::Deref; 4 | use core::ptr; 5 | use std::str::{self, FromStr}; 6 | 7 | use super::WifiConfigError; 8 | 9 | const SSID_MAX_LEN: usize = 32; 10 | 11 | /// A WiFi SSID. 12 | #[derive(Copy, Clone)] 13 | #[repr(transparent)] 14 | pub struct Ssid(pub(crate) [u8; SSID_MAX_LEN]); 15 | 16 | impl Deref for Ssid { 17 | type Target = str; 18 | 19 | fn deref(&self) -> &Self::Target { 20 | self.as_str() 21 | } 22 | } 23 | 24 | impl PartialEq for Ssid { 25 | fn eq(&self, other: &Self) -> bool { 26 | self.as_str() == other.as_str() 27 | } 28 | } 29 | 30 | impl Eq for Ssid {} 31 | 32 | impl Ord for Ssid { 33 | fn cmp(&self, other: &Self) -> Ordering { 34 | self.as_str().cmp(&other.as_str()) 35 | } 36 | } 37 | 38 | impl PartialOrd for Ssid { 39 | fn partial_cmp(&self, other: &Self) -> Option { 40 | Some(self.cmp(other)) 41 | } 42 | } 43 | 44 | impl Ssid { 45 | pub(crate) fn len(&self) -> usize { 46 | memchr::memchr(0, &self.0).unwrap_or(SSID_MAX_LEN) 47 | } 48 | 49 | pub fn as_str(&self) -> &str { 50 | unsafe { &str::from_utf8_unchecked(&self.0[..self.len()]) } 51 | } 52 | 53 | pub fn from_bytes(bytes: &[u8]) -> Result { 54 | let ssid_len = bytes.len(); 55 | 56 | if ssid_len > SSID_MAX_LEN { 57 | return Err(WifiConfigError::TooLong(SSID_MAX_LEN, ssid_len)) 58 | } 59 | 60 | if let Err(utf8_error) = str::from_utf8(bytes) { 61 | return Err(WifiConfigError::Utf8Error(utf8_error)) 62 | } 63 | 64 | if let Some(pos) = memchr::memchr(0, bytes) { 65 | return Err(WifiConfigError::InteriorNul(pos)) 66 | } 67 | 68 | Ok(unsafe { Self::from_bytes_unchecked(bytes) }) 69 | } 70 | 71 | /// SAFTEY: The caller has to ensure that `bytes` does not contain a `NUL` byte and 72 | /// does not exceed 32 bytes. 73 | pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Ssid { 74 | let ssid_len = bytes.len(); 75 | let mut ssid = Self([0; SSID_MAX_LEN]); 76 | ptr::copy_nonoverlapping(bytes.as_ptr(), ssid.0.as_mut_ptr(), ssid_len); 77 | ssid 78 | } 79 | } 80 | 81 | impl FromStr for Ssid { 82 | type Err = WifiConfigError; 83 | 84 | fn from_str(s: &str) -> Result { 85 | Self::from_bytes(s.as_bytes()) 86 | } 87 | } 88 | 89 | impl fmt::Debug for Ssid { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | write!(f, "Ssid({:?})", self.as_str()) 92 | } 93 | } 94 | 95 | impl fmt::Display for Ssid { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | self.as_str().fmt(f) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /esp-idf-hal/src/wifi/sta_config.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::mem; 3 | use core::num::{NonZeroU8, NonZeroU16}; 4 | 5 | use esp_idf_bindgen::{ 6 | wifi_config_t, 7 | wifi_sta_config_t, 8 | wifi_scan_method_t, 9 | wifi_sort_method_t, 10 | wifi_scan_threshold_t, 11 | }; 12 | 13 | use super::{AuthMode, Ssid, Password}; 14 | 15 | /// Scan method used when connecting to an access point. 16 | #[derive(Debug, Clone, Copy)] 17 | pub enum ScanMethod { 18 | Fast, 19 | Full, 20 | } 21 | 22 | impl Default for ScanMethod { 23 | fn default() -> Self { 24 | Self::Fast 25 | } 26 | } 27 | 28 | impl From for wifi_scan_method_t { 29 | fn from(scan_method: ScanMethod) -> Self { 30 | match scan_method { 31 | ScanMethod::Fast => wifi_scan_method_t::WIFI_FAST_SCAN, 32 | ScanMethod::Full => wifi_scan_method_t::WIFI_ALL_CHANNEL_SCAN, 33 | } 34 | } 35 | } 36 | 37 | /// Sort method for prioritization of access points to connect to. 38 | #[derive(Debug, Clone, Copy)] 39 | pub enum SortMethod { 40 | BySignal, 41 | BySecurity, 42 | } 43 | 44 | impl Default for SortMethod { 45 | fn default() -> Self { 46 | Self::BySignal 47 | } 48 | } 49 | 50 | impl From for wifi_sort_method_t { 51 | fn from(sort_method: SortMethod) -> Self { 52 | match sort_method { 53 | SortMethod::BySignal => wifi_sort_method_t::WIFI_CONNECT_AP_BY_SIGNAL, 54 | SortMethod::BySecurity => wifi_sort_method_t::WIFI_CONNECT_AP_BY_SECURITY, 55 | } 56 | } 57 | } 58 | 59 | /// Scan threshold used when connecting to an access point. 60 | #[derive(Debug, Clone, Copy)] 61 | pub struct ScanThreshold { 62 | rssi: i8, 63 | auth_mode: AuthMode, 64 | } 65 | 66 | impl Default for ScanThreshold { 67 | fn default() -> Self { 68 | Self { 69 | rssi: -127, 70 | auth_mode: AuthMode::Open, 71 | } 72 | } 73 | } 74 | 75 | impl From for wifi_scan_threshold_t { 76 | fn from(scan_threshold: ScanThreshold) -> Self { 77 | Self { 78 | rssi: scan_threshold.rssi, 79 | authmode: scan_threshold.auth_mode.into(), 80 | } 81 | } 82 | } 83 | 84 | /// Configuration for a station. 85 | #[derive(Clone)] 86 | pub struct StaConfig(pub(crate) wifi_config_t); 87 | 88 | impl fmt::Debug for StaConfig { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | f.debug_struct("StaConfig") 91 | .field("ssid", &self.ssid()) 92 | .field("password", &self.password()) 93 | .finish() 94 | } 95 | } 96 | 97 | impl StaConfig { 98 | #[inline] 99 | pub fn ssid(&self) -> &Ssid { 100 | unsafe { mem::transmute(&self.0.sta.ssid) } 101 | } 102 | 103 | #[inline] 104 | pub fn password(&self) -> &Password { 105 | unsafe { mem::transmute(&self.0.sta.password) } 106 | } 107 | 108 | #[inline] 109 | pub fn scan_method(&self) -> &ScanMethod { 110 | unsafe { mem::transmute(&self.0.sta.scan_method) } 111 | } 112 | 113 | #[inline] 114 | pub fn channel(&self) -> Option<&NonZeroU8> { 115 | unsafe { mem::transmute(&self.0.sta.channel) } 116 | } 117 | 118 | pub fn builder() -> StaConfigBuilder { 119 | StaConfigBuilder::default() 120 | } 121 | } 122 | 123 | /// Builder for [`StaConfig`](struct.StaConfig.html). 124 | pub struct StaConfigBuilder { 125 | ssid: Option, 126 | password: Password, 127 | scan_method: ScanMethod, 128 | bssid: Option<[u8; 6]>, 129 | channel: Option, 130 | listen_interval: Option, 131 | sort_method: SortMethod, 132 | threshold: Option, 133 | } 134 | 135 | impl fmt::Debug for StaConfigBuilder { 136 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 137 | f.debug_struct("StaConfigBuilder") 138 | .field("ssid", &self.ssid) 139 | .field("password", &"********") 140 | .field("scan_method", &self.scan_method) 141 | .field("bssid", &self.bssid) 142 | .field("channel", &self.channel) 143 | .field("listen_interval", &self.listen_interval) 144 | .field("sort_method", &self.sort_method) 145 | .field("threshold", &self.threshold) 146 | .finish() 147 | } 148 | } 149 | 150 | impl Default for StaConfigBuilder { 151 | fn default() -> Self { 152 | Self { 153 | ssid: None, 154 | password: Default::default(), 155 | scan_method: Default::default(), 156 | bssid: Default::default(), 157 | channel: Default::default(), 158 | listen_interval: Default::default(), 159 | sort_method: Default::default(), 160 | threshold: Default::default(), 161 | } 162 | } 163 | } 164 | 165 | impl StaConfigBuilder { 166 | pub fn ssid(&mut self, ssid: Ssid) -> &mut Self { 167 | self.ssid = Some(ssid); 168 | self 169 | } 170 | 171 | pub fn password(&mut self, password: Password) -> &mut Self { 172 | self.password = password; 173 | self 174 | } 175 | 176 | pub fn build(&self) -> StaConfig { 177 | StaConfig(wifi_config_t { 178 | sta: wifi_sta_config_t { 179 | ssid: self.ssid.clone().expect("missing SSID").0, 180 | password: self.password.clone().0, 181 | scan_method: self.scan_method.into(), 182 | bssid_set: self.bssid.is_some(), 183 | bssid: self.bssid.unwrap_or([0, 0, 0, 0, 0, 0]), 184 | channel: unsafe { mem::transmute(self.channel) }, 185 | listen_interval: unsafe { mem::transmute(self.listen_interval) }, 186 | sort_method: self.sort_method.into(), 187 | threshold: self.threshold.unwrap_or_default().into(), 188 | #[cfg(target_device = "esp32")] 189 | pmf_cfg: esp_idf_bindgen::wifi_pmf_config_t { 190 | capable: false, 191 | required: false, 192 | }, 193 | _bitfield_align_1: Default::default(), 194 | _bitfield_1: wifi_sta_config_t::new_bitfield_1(0, 0, 0, 0), 195 | } 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | esp 2 | --------------------------------------------------------------------------------