wibble.rs (9282B)
1 use embassy_time::{Duration,Timer}; 2 use trouble_host::prelude::*; 3 use esp_radio::ble::controller::BleConnector; 4 use esp_radio::Controller as RadioController; 5 use log::{warn,info}; 6 use embassy_futures::join::join; 7 use embassy_futures::select::select; 8 9 use crypto_box::{ 10 aead::Aead, 11 SalsaBox, PublicKey, SecretKey, Nonce 12 }; 13 14 use alloc::vec::Vec; 15 use alloc::string::String; 16 17 use crate::ota::Flasher; 18 19 /// Max number of connections 20 const CONNECTIONS_MAX: usize = 1; 21 22 /// Max number of L2CAP channels. 23 // XXX why not 0? 24 const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att 25 26 // GATT Server definition 27 #[gatt_server] 28 struct Server { 29 wifi_provisioning_service: WifiProvisioningService, 30 } 31 32 33 #[gatt_service(uuid = "76be25c2-0000-44fc-a97c-bcb56c69c926")] 34 struct WifiProvisioningService { 35 #[characteristic(uuid = "76be25c2-0801-44fc-a97c-bcb56c69c926", read, notify, value = false)] 36 connecting: bool, 37 38 #[characteristic(uuid = "76be25c2-0802-44fc-a97c-bcb56c69c926", read, notify, value = 0)] 39 max_network_index: u8, 40 41 #[characteristic(uuid = "76be25c2-0803-44fc-a97c-bcb56c69c926", read, write, value = 0)] 42 current_network_index: u8, 43 44 // for arrays > 32 we must supply a default value : see 45 // https://github.com/embassy-rs/trouble/issues/320 46 #[characteristic(uuid = "76be25c2-0804-44fc-a97c-bcb56c69c926", 47 value = [0; 40], 48 read, notify)] 49 current_network: [u8; 40], 50 51 #[characteristic(uuid = "76be25c2-0805-44fc-a97c-bcb56c69c926", 52 value = [0; (1 + 32 + 1 + 24 + 16 + 64)], 53 write)] 54 secret: [u8; 1+ 32 + 1 + 24 + 16 + 64] 55 } 56 57 58 pub async fn run_ble(radio_init: &RadioController<'static>, 59 peripheral: esp_hal::peripherals::BT<'_>, 60 networks: Vec<[u8; 40]>, 61 mut flasher: Flasher<'_>) { 62 let connector = BleConnector::new(&radio_init, peripheral, Default::default()).expect("connector"); 63 let controller: ExternalController<_, 20> = ExternalController::new(connector); 64 65 // Using a fixed "random" address can be useful for testing. In real scenarios, one would 66 // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). 67 let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); 68 info!("Our address = {:?}", address); 69 70 let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new(); 71 let stack = trouble_host::new(controller, &mut resources).set_random_address(address); 72 let Host { 73 mut peripheral, runner, .. 74 } = stack.build(); 75 76 info!("Starting advertising and GATT service"); 77 let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { 78 name: "eculocate", 79 appearance: &appearance::power_device::GENERIC_POWER_DEVICE, 80 })) 81 .unwrap(); 82 83 let _ = join( 84 ble_task(runner), 85 async { 86 loop { 87 match advertise("Eculocate", &mut peripheral, &server).await { 88 Ok(conn) => { 89 // set up tasks when the connection is established to a central, so they don't run when no one is connected. 90 let a = gatt_events_task(&server, &conn, &networks, &mut flasher); 91 let b = custom_task(&server, &conn, &stack); 92 // run until any task ends (usually because the connection has been closed), 93 // then return to advertising state. 94 select(a, b).await; 95 } 96 Err(e) => { 97 panic!("[adv] error: {:?}", e); 98 } 99 } 100 } 101 }) 102 .await; 103 } 104 105 /// This is a background task that is required to run forever alongside any other BLE tasks. 106 /// 107 async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) { 108 loop { 109 if let Err(e) = runner.run().await { 110 panic!("[ble_task] error: {:?}", e); 111 } 112 } 113 } 114 115 const CLIENT_PUBLIC_KEY_BYTES: &[u8; 32] = include_bytes!("../../keys/client.pub"); 116 const SECRET_KEY_BYTES : &[u8; 32] = include_bytes!("../../keys/device.key"); 117 118 #[derive(Debug)] 119 enum DecryptError { 120 Crypto(crypto_box::aead::Error), 121 Encoding(alloc::str::Utf8Error) 122 } 123 impl From<crypto_box::aead::Error> for DecryptError { 124 fn from(e: crypto_box::aead::Error) -> Self { 125 Self::Crypto(e) 126 } 127 } 128 impl From<alloc::str::Utf8Error> for DecryptError { 129 fn from(e: alloc::str::Utf8Error) -> Self { Self::Encoding(e) } 130 } 131 132 fn decrypt_secret(ciphertext : &[u8]) -> Result<Vec<u8>, DecryptError> { 133 let client_public_key = PublicKey::from(*CLIENT_PUBLIC_KEY_BYTES); 134 let secret_key= SecretKey::from(*SECRET_KEY_BYTES); 135 136 let len : usize = ciphertext[0].into(); 137 let ciphertext = &ciphertext[1..len+1]; 138 139 let (nonce, encrypted) = ciphertext.split_at(24); 140 let nonce = Nonce::from_slice(nonce); 141 142 let sbox = SalsaBox::new(&client_public_key, &secret_key); 143 let plain_bytes = sbox.decrypt(nonce, encrypted)?; 144 145 Ok(Vec::from(plain_bytes)) 146 } 147 148 149 /// Stream Events until the connection closes. 150 /// 151 /// This function will handle the GATT events and process them. 152 /// This is how we interact with read and write requests. 153 async fn gatt_events_task<P: PacketPool>(server: &Server<'_>, 154 conn: &GattConnection<'_, '_, P>, 155 networks: &Vec<[u8; 40]>, 156 flasher: &mut Flasher<'_>) -> Result<(), Error> { 157 let wps = &server.wifi_provisioning_service; 158 let max_index : u8 = (networks.len() & 0xff).try_into().unwrap(); 159 160 server.set(&wps.max_network_index, &max_index); 161 let current_index = wps.current_network_index; 162 let reason = loop { 163 match conn.next().await { 164 GattConnectionEvent::Disconnected { reason } => break reason, 165 GattConnectionEvent::Gatt { event } => { 166 match &event { 167 GattEvent::Write(event) => { 168 info!("[gatt] write handle {:?} {:?}", event.handle(), 169 event.data()); 170 if event.handle() == current_index.handle { 171 let i = (event.data()[0]) as usize; 172 info!("[gatt] switch current index: {:?}", i); 173 if i == 0 { 174 server.set(&wps.current_network, &[0u8;40]); 175 } else if i <= networks.len() { 176 server.set(&wps.current_network, 177 &(networks[i - 1])); 178 } else { 179 warn!("index {:?} out of range", i); 180 } 181 } else if event.handle() == wps.connecting.handle { 182 let val = server.get(&wps.secret).unwrap(); 183 let name_len : usize = val[0].into(); 184 let name = &val[1..name_len+1]; 185 186 match decrypt_secret(&val[name_len+1..]) { 187 Ok(pw) => { 188 write_wifi_config(flasher, name, &pw[..]).await; 189 }, 190 Err(e) => { 191 warn!("decrypt error: {:?}", e); 192 } 193 } 194 } 195 }, 196 _ => {} 197 }; 198 // This step is also performed at drop(), but writing 199 // it explicitly is necessary in order to ensure reply 200 // is sent. 201 match event.accept() { 202 Ok(reply) => reply.send().await, 203 Err(e) => warn!("[gatt] error sending response: {:?}", e), 204 }; 205 } 206 _ => {} // ignore other Gatt Connection Events 207 } 208 }; 209 info!("[gatt] disconnected: {:?}", reason); 210 Ok(()) 211 } 212 213 async fn write_wifi_config(flasher: &mut Flasher<'_>, ssid: &[u8], password: &[u8]) -> Result<(),esp_nvs::error::Error> { 214 let ssid = core::str::from_utf8(ssid).unwrap(); 215 let password = core::str::from_utf8(password).unwrap(); 216 flasher.set_var("ssid", Some(ssid))?; 217 flasher.set_var("secret", Some(password))?; 218 info!("[gatt] deep breath then reboot"); 219 Timer::after(Duration::from_millis(2000)).await; 220 esp_hal::system::software_reset(); 221 } 222 223 /// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. 224 async fn advertise<'values, 'server, C: Controller>( 225 name: &'values str, 226 peripheral: &mut Peripheral<'values, C, DefaultPacketPool>, 227 server: &'server Server<'values>, 228 ) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> { 229 let mut advertiser_data = [0; 31]; 230 let len = AdStructure::encode_slice( 231 &[ 232 AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), 233 AdStructure::ServiceUuids16(&[[0x0f, 0x18]]), 234 AdStructure::CompleteLocalName(name.as_bytes()), 235 ], 236 &mut advertiser_data[..], 237 )?; 238 let advertiser = peripheral 239 .advertise( 240 &Default::default(), 241 Advertisement::ConnectableScannableUndirected { 242 adv_data: &advertiser_data[..len], 243 scan_data: &[], 244 }, 245 ) 246 .await?; 247 info!("[adv] advertising"); 248 let conn = advertiser.accept().await?.with_attribute_server(server)?; 249 info!("[adv] connection established"); 250 Ok(conn) 251 } 252 253 async fn custom_task<C: Controller, P: PacketPool>( 254 _server: &Server<'_>, 255 conn: &GattConnection<'_, '_, P>, 256 stack: &Stack<'_, C, P>, 257 ) { 258 loop { 259 // read RSSI (Received Signal Strength Indicator) of the connection. 260 if let Ok(rssi) = conn.raw().rssi(stack).await { 261 info!("[custom_task] RSSI: {:?}", rssi); 262 } else { 263 info!("[custom_task] error getting RSSI"); 264 break; 265 }; 266 267 Timer::after_secs(2).await; 268 } 269 }