Open alexandrefresnais opened 1 year ago
BlueZ does provide the device address of the device that started the notification session along with the file descriptor that is used for sending notifications. Thus if you have two CharacteristicWriter
s I would expect BlueZ to send the data to each corresponding client.
However, I haven't checked how this is implemented in BlueZ. So maybe we get two file descriptors, but both send notifications to all clients. You would have to read the BlueZ source to find out what is going on here.
Could you share your source so that we can have a look at it?
Thanks for your quick reply.
Sure I can give you the code. I have tried to do something very naive.
//! Serves a Bluetooth GATT echo server.
use bluer::{
adv::Advertisement,
gatt::{
local::{
characteristic_control, Application, Characteristic, CharacteristicControlEvent,
CharacteristicNotify, CharacteristicNotifyMethod, CharacteristicWrite,
CharacteristicWriteMethod, Service,
},
CharacteristicReader, CharacteristicWriter,
},
};
use futures::{future, pin_mut, StreamExt};
use std::time::Duration;
use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
time::sleep,
};
/// Nordic UART Service UUIDs for GATT server.
#[allow(dead_code)]
const NUS_UUID: uuid::Uuid = uuid::Uuid::from_u128(0x6e400001b5a3f393e0a9e50e24dcca9e);
#[allow(dead_code)]
const NUS_RX_UUID: uuid::Uuid = uuid::Uuid::from_u128(0x6e400002b5a3f393e0a9e50e24dcca9e);
#[allow(dead_code)]
const NUS_TX_UUID: uuid::Uuid = uuid::Uuid::from_u128(0x6e400003b5a3f393e0a9e50e24dcca9e);
#[tokio::main]
async fn main() -> bluer::Result<()> {
let session = bluer::Session::new().await?;
let adapter = session.default_adapter().await?;
adapter.set_powered(true).await?;
println!(
"Advertising on Bluetooth adapter {} with address {}",
adapter.name(),
adapter.address().await?
);
let le_advertisement = Advertisement {
service_uuids: vec![NUS_UUID].into_iter().collect(),
discoverable: Some(true),
local_name: Some("nus_multiperipheral".to_string()),
..Default::default()
};
let adv_handle = adapter.advertise(le_advertisement).await?;
println!(
"Serving GATT echo service on Bluetooth adapter {}",
adapter.name()
);
let (rx_char_control, rx_char_handle) = characteristic_control();
let (tx_char_control, tx_char_handle) = characteristic_control();
let app = Application {
services: vec![Service {
uuid: NUS_UUID,
primary: true,
characteristics: vec![
Characteristic {
uuid: NUS_RX_UUID,
write: Some(CharacteristicWrite {
write: true,
write_without_response: true,
method: CharacteristicWriteMethod::Io,
..Default::default()
}),
control_handle: rx_char_handle,
..Default::default()
},
Characteristic {
uuid: NUS_TX_UUID,
notify: Some(CharacteristicNotify {
notify: true,
method: CharacteristicNotifyMethod::Io,
..Default::default()
}),
control_handle: tx_char_handle,
..Default::default()
},
],
..Default::default()
}],
..Default::default()
};
let app_handle = adapter.serve_gatt_application(app).await?;
println!("Echo service ready. Press enter to quit.");
let stdin = BufReader::new(tokio::io::stdin());
let mut lines = stdin.lines();
pin_mut!(rx_char_control);
pin_mut!(tx_char_control);
let mut reader_opt1: Option<CharacteristicReader> = None;
let mut writer_opt1: Option<CharacteristicWriter> = None;
let mut reader_opt2: Option<CharacteristicReader> = None;
let mut writer_opt2: Option<CharacteristicWriter> = None;
let mut read_buf1 = Vec::new();
let mut read_buf2 = Vec::new();
loop {
tokio::select! {
_ = lines.next_line() => break,
evt = rx_char_control.next() => {
match evt {
Some(CharacteristicControlEvent::Write(req)) => {
if reader_opt1.is_none() {
read_buf1 = vec![0; req.mtu()];
reader_opt1 = Some(req.accept()?);
} else {
read_buf2 = vec![0; req.mtu()];
reader_opt2 = Some(req.accept()?);
}
},
Some(CharacteristicControlEvent::Notify(_)) => break,
None => break,
}
},
evt = tx_char_control.next() => {
match evt {
Some(CharacteristicControlEvent::Notify(notifier)) => {
if writer_opt1.is_none() {
writer_opt1 = Some(notifier);
} else {
writer_opt2 = Some(notifier);
}
},
_ => break,
}
},
read_res = async {
match &mut reader_opt1 {
Some(reader) if writer_opt1.is_some() => reader.read(&mut read_buf1).await,
_ => future::pending().await,
}
} => {
match read_res {
Ok(0) => {
println!("Read stream ended");
reader_opt1 = None;
}
Ok(n) => {
let value = read_buf1[..n].to_vec();
println!("(1) Echoing {} bytes: {:x?} ... {:x?}", value.len(), &value[0..4.min(value.len())], &value[value.len().saturating_sub(4) ..]);
if value.len() < 512 {
println!();
}
if let Err(err) = writer_opt1.as_mut().unwrap().write_all(&value).await {
println!("Write failed: {}", &err);
writer_opt1 = None;
}
}
Err(err) => {
println!("Read stream error: {}", &err);
reader_opt1 = None;
}
}
}
read_res = async {
match &mut reader_opt2 {
Some(reader) if writer_opt2.is_some() => reader.read(&mut read_buf2).await,
_ => future::pending().await,
}
} => {
match read_res {
Ok(0) => {
println!("Read stream ended");
reader_opt2 = None;
}
Ok(n) => {
let value = read_buf2[..n].to_vec();
println!("(2) Echoing {} bytes: {:x?} ... {:x?}", value.len(), &value[0..4.min(value.len())], &value[value.len().saturating_sub(4) ..]);
if value.len() < 512 {
println!();
}
if let Err(err) = writer_opt2.as_mut().unwrap().write_all(&value).await {
println!("Write failed: {}", &err);
writer_opt2 = None;
}
}
Err(err) => {
println!("Read stream error: {}", &err);
reader_opt2 = None;
}
}
}
}
}
println!("Removing service and advertisement");
drop(app_handle);
drop(adv_handle);
sleep(Duration::from_secs(1)).await;
Ok(())
}
This was tested with BlueZ 5.55, and BlueR 0.15.5, running on a Raspberry Pi CM4. Tested with two iPhones running NRF Connect.
I really do not think that the notifier is specific to a device. I have also tested the gatt_echo_server
example with my two phones. And I am receiving the echo on both phones, whereas the example stores only one notifier (It should be the last one to subscribe shouldn't it ?).
Your code looks fine to me.
Yes, I agree. It seems that BlueZ is using one notification session for all connected clients that have enabled notifications. However, the D-Bus API would indeed allow one notification session per client without changes to the API.
BlueR can do nothing about it. You will need to enhance bluetoothd
to support this behavior. Since you won't have to modify the API the required changes should be pretty straightforward.
Hello here!
Coming back on https://github.com/bluez/bluer/issues/62 I was preparing a Nordic UART Service example following your addition to the library but I cannot make it working. When writing to the notifier of a client, both my phones get notified.
A few months ago, I took a look at BlueZ DBuS API and saw nothing to notify a specific client. We asked if it was possible in an issue but no one answered (https://github.com/bluez/bluez/issues/409)
So, following the previous issue, do you think this service is really possible with BlueR ? I have serious belief that it is not currently possible in anyway with BlueZ.