//! Simple example for receiving battery info via the GFPS RFCOMM channel. //! //! Usage: //! cargo run --example gfps_get_battery -- use std::str::FromStr; use bluer::{Address, Session, Device}; use bluer::rfcomm::{Profile, ReqError, Role, ProfileHandle}; use futures::StreamExt; use gfps::msg::{Codec, DeviceEventCode, EventGroup, BatteryInfo}; use num_enum::FromPrimitive; #[tokio::main(flavor = "current_thread")] async fn main() -> bluer::Result<()> { // handle command line arguments let addr = std::env::args().nth(1).expect("need device address as argument"); let addr = Address::from_str(&addr)?; // set up session let session = Session::new().await?; let adapter = session.default_adapter().await?; // get device let dev = adapter.device(addr)?; // get RFCOMM stream let stream = { // register GFPS profile let profile = Profile { uuid: gfps::msg::UUID, role: Some(Role::Client), require_authentication: Some(false), require_authorization: Some(false), auto_connect: Some(false), ..Default::default() }; let mut profile_handle = session.register_profile(profile).await?; // connect profile connect_device_to_profile(&mut profile_handle, &dev).await? }; // listen to event messages let codec = Codec::new(); let mut stream = codec.wrap(stream); // The battery status cannot be queried via a normal command. However, it // is sent right after we connect to the GFPS stream. In addition, multiple // events are often sent in sequence. Therefore we do the following: // - Set a deadline for a general timeout. If this passes, we just return // the current state (and if necessary "unknown"): // - Use a timestamp for checking whether we have received any new updates // in a given interval. If we have not received any, we consider the // state to be "settled" and return the battery info. // - On battery events we simply store the sent information. We retreive // the stored information once either of the timeouts kicks in. let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); let mut timestamp = deadline; let mut bat_left = BatteryInfo::Unknown; let mut bat_right = BatteryInfo::Unknown; let mut bat_case = BatteryInfo::Unknown; let time_settle = std::time::Duration::from_millis(500); loop { tokio::select! { // receive and handle events msg = stream.next() => { match msg { Some(Ok(msg)) => { let group = EventGroup::from_primitive(msg.group); if group != EventGroup::Device { continue; } let code = DeviceEventCode::from_primitive(msg.code); if code == DeviceEventCode::BatteryInfo { timestamp = std::time::Instant::now(); bat_left = BatteryInfo::from_byte(msg.data[0]); bat_right = BatteryInfo::from_byte(msg.data[1]); bat_case = BatteryInfo::from_byte(msg.data[2]); } }, Some(Err(err)) => { Err(err)?; }, None => { let err = std::io::Error::new( std::io::ErrorKind::ConnectionAborted, "connection closed" ); Err(err)?; } } }, // timeout for determining when the state has "settled" _ = tokio::time::sleep(tokio::time::Duration::from_millis(time_settle.as_millis() as _)) => { let delta = std::time::Instant::now() - timestamp; if delta > time_settle { break } }, // general deadline _ = tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)) => { break }, } } println!("Battery status:"); println!(" left bud: {}", bat_left); println!(" right bud: {}", bat_right); println!(" case: {}", bat_case); Ok(()) } async fn connect_device_to_profile(profile: &mut ProfileHandle, dev: &Device) -> bluer::Result { loop { tokio::select! { res = async { let _ = dev.connect().await; dev.connect_profile(&gfps::msg::UUID).await } => { if let Err(err) = res { println!("Connecting GFPS profile failed: {:?}", err); } tokio::time::sleep(std::time::Duration::from_millis(3000)).await; }, req = profile.next() => { let req = req.expect("no connection request received"); if req.device() == dev.address() { // accept our device break req.accept(); } else { // reject unknown devices req.reject(ReqError::Rejected); } }, } } }