Files
pbpctrl/libgfps/examples/ring.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

242 lines
8.1 KiB
Rust

//! Simple example for "ringing" the buds to locate them.
//!
//! WARNING: DO NOT RUN THIS EXAMPLE WITH THE BUDS IN YOUR EAR! YOU HAVE BEEN WARNED.
//!
//! Usage:
//! cargo run --example ring -- <bluetooth-device-address>
use std::str::FromStr;
use bluer::{Address, Session, Device};
use bluer::rfcomm::{Profile, Role, ProfileHandle, ReqError};
use futures::{StreamExt, SinkExt};
use gfps::msg::{Codec, Message, EventGroup, DeviceActionEventCode, AcknowledgementEventCode};
use num_enum::FromPrimitive;
use smallvec::smallvec;
#[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?
};
// set up message stream
let codec = Codec::new();
let mut stream = codec.wrap(stream);
// send "ring" message
//
// Note: Pixel Buds Pro ignore messages with a timeout. So don't specify
// one here.
let msg = Message {
group: EventGroup::DeviceAction.into(),
code: DeviceActionEventCode::Ring.into(),
data: smallvec![0x03], // 0b01: right, 0b10: left, 0b10|0b01 = 0b11: both
};
println!("Ringing buds...");
stream.send(&msg).await?;
// An ACK message should come in 1s. Wait for that.
let timeout = tokio::time::Instant::now() + tokio::time::Duration::from_secs(1);
loop {
tokio::select! {
msg = stream.next() => {
match msg {
Some(Ok(msg)) => {
println!("{:?}", msg);
let group = EventGroup::from_primitive(msg.group);
if group != EventGroup::Acknowledgement {
continue;
}
let ack_group = EventGroup::from_primitive(msg.data[0]);
if ack_group != EventGroup::DeviceAction {
continue;
}
let ack_code = DeviceActionEventCode::from_primitive(msg.data[1]);
if ack_code != DeviceActionEventCode::Ring {
continue;
}
let code = AcknowledgementEventCode::from_primitive(msg.code);
if code == AcknowledgementEventCode::Ack {
println!("Received ACK for ring command");
break;
} else if code == AcknowledgementEventCode::Nak {
println!("Received NAK for ring command");
let err = std::io::Error::new(
std::io::ErrorKind::Unsupported,
"ring has been NAK'ed by device"
);
Err(err)?;
}
},
Some(Err(e)) => {
Err(e)?;
},
None => {
let err = std::io::Error::new(
std::io::ErrorKind::ConnectionAborted,
"connection closed"
);
Err(err)?;
}
}
},
_ = tokio::time::sleep_until(timeout) => {
let err = std::io::Error::new(
std::io::ErrorKind::TimedOut,
"timed out, ring action might be unsupported"
);
Err(err)?;
},
}
}
// Next, the device will communicate back status updates. This may include
// an initial update to confirm ringing and follow-up updates once the user
// has touched the buds and ringing stops.
//
// Stop this program once we have no more rining or once we have reached a
// timeout of 30s.
let mut timeout = tokio::time::Instant::now() + tokio::time::Duration::from_secs(30);
loop {
tokio::select! {
msg = stream.next() => {
match msg {
Some(Ok(msg)) => {
println!("{:?}", msg);
let group = EventGroup::from_primitive(msg.group);
if group != EventGroup::DeviceAction {
continue;
}
// send ACK
let ack = Message {
group: EventGroup::Acknowledgement.into(),
code: AcknowledgementEventCode::Ack.into(),
data: smallvec![msg.group, msg.code],
};
stream.send(&ack).await?;
let status = msg.data[0];
println!("Received ring update:");
if status & 0b01 != 0 {
println!(" right: ringing");
} else {
println!(" right: not ringing");
}
if status & 0b10 != 0 {
println!(" left: ringing");
} else {
println!(" left: not ringing");
}
if status & 0b11 == 0 {
println!("Buds stopped ringing, exiting...");
return Ok(());
}
},
Some(Err(e)) => {
Err(e)?;
},
None => {
let err = std::io::Error::new(
std::io::ErrorKind::ConnectionAborted,
"connection closed"
);
Err(err)?;
}
}
},
_ = tokio::time::sleep_until(timeout) => {
println!("Sending command to stop ringing...");
// send message to stop ringing
let msg = Message {
group: EventGroup::DeviceAction.into(),
code: DeviceActionEventCode::Ring.into(),
data: smallvec![0x00],
};
stream.send(&msg).await?;
timeout = tokio::time::Instant::now() + tokio::time::Duration::from_secs(10);
},
}
}
}
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);
}
},
}
}
}