Files
pbpctrl/libgfps/examples/gfps_get_battery.rs
CGH0S7 6675986579
Some checks failed
Rust / Clippy (push) Has been cancelled
Rust / Test (nightly) (push) Has been cancelled
Rust / Test (stable) (push) Has been cancelled
first commit
2026-01-04 16:50:19 +08:00

156 lines
5.3 KiB
Rust

//! Simple example for receiving battery info via the GFPS RFCOMM channel.
//!
//! Usage:
//! cargo run --example gfps_get_battery -- <bluetooth-device-address>
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<bluer::rfcomm::Stream>
{
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);
}
},
}
}
}