Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bluetooth mesh support #60

Merged
merged 15 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Gavin Li <gavinli@thegavinli.com>
Daniel Thoma <dthoma@gmx.net>
Sebastian Urban <surban@surban.net>
André Zwing <andre.zwing@zf.com>
Dejan Bosanac <dbosanac@redhat.com>
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@
members = [
"bluer",
"bluer-tools",
]
]

[patch.crates-io]
# dbus = { path = "../dbus-rs/dbus/", features = ["futures"], optional = true }
# dbus-tokio = { path = "../dbus-rs/dbus-tokio/", optional = true }
# dbus-crossroads = { path = "../dbus-rs/dbus-crossroads/", optional = true }
1 change: 1 addition & 0 deletions bluer-tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ libc = "0.2"
log = "0.4"
hex = { version = "0.4" }
rand = "0.8"
uuid = { version = "1", features = ["v4"] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary

27 changes: 24 additions & 3 deletions bluer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ authors = [
"BlueR contributors",
"Attila Dusnoki <adusnoki@inf.u-szeged.hu>",
"Ben Stern <bstern@fortian.com>",
"Dejan Bosanac <dbosanac@redhat.com>",
]
repository = "https://github.com/bluez/bluer"
keywords = ["bluetooth", "bluez", "gatt", "l2cap", "rfcomm"]
keywords = ["bluetooth", "bluez", "gatt", "l2cap", "rfcomm", "mesh"]
categories = ["asynchronous", "hardware-support", "os::linux-apis"]
license = "BSD-2-Clause"
edition = "2021"
Expand All @@ -21,11 +22,12 @@ exclude = [
"bluetooth-numbers-database/*.js",
"bluetooth-numbers-database/*.json",
"bluetooth-numbers-database/node_modules/*",
"meshd/*",
]

[features]
default = []
full = ["bluetoothd", "id", "l2cap", "rfcomm", "serde"]
full = ["bluetoothd", "id", "l2cap", "rfcomm", "mesh", "serde"]
bluetoothd = [
"dbus",
"dbus-tokio",
Expand All @@ -42,6 +44,7 @@ bluetoothd = [
id = []
l2cap = []
rfcomm = []
mesh = ["bluetoothd"]
serde = ["uuid/serde", "dep:serde"]

[dependencies]
Expand Down Expand Up @@ -72,9 +75,15 @@ serde_json = "1"
uuid = "1"

[dev-dependencies]
tokio = { version = "1", features = ["io-std", "io-util", "rt-multi-thread"] }
tokio = { version = "1", features = [
"io-std",
"io-util",
"rt-multi-thread",
"signal",
] }
env_logger = "0.10"
rand = "0.8"
clap = { version = "3", features = ["derive"] }

[package.metadata.docs.rs]
all-features = true
Expand Down Expand Up @@ -121,3 +130,15 @@ required-features = ["bluetoothd"]
[[example]]
name = "list_adapters"
required-features = ["bluetoothd"]

[[example]]
name = "mesh_sensor_server"
required-features = ["mesh", "disabled"]

[[example]]
name = "mesh_sensor_client"
required-features = ["mesh", "disabled"]

[[example]]
name = "mesh_provisioner"
required-features = ["mesh", "disabled"]
13 changes: 13 additions & 0 deletions bluer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ The following functionality is provided:
* support for classic Bluetooth (BR/EDR)
* stream oriented
* async IO interface with `AsyncRead` and `AsyncWrite` support
* Bluetooth Mesh
* provision and join networks
* send and receive messages
* database of assigned numbers
* manufacturer ids
* service classes, GATT services, characteristics and descriptors
Expand Down Expand Up @@ -78,6 +81,7 @@ The following crate features are available.
* `id`: Enables database of assigned numbers.
* `l2cap`: Enables L2CAP sockets.
* `rfcomm`: Enables RFCOMM sockets.
* `mesh`: Enables Bluetooth mesh functionality.
* `serde`: Enables serialization and deserialization of some data types.

To enable all crate features specify the `full` crate feature.
Expand All @@ -94,6 +98,9 @@ Refer to the [official changelog] for details.
If any `bluetoothd` feature is used the Bluetooth daemon must be running and configured for access over D-Bus.
On most distributions this should work out of the box.

For `mesh` feature Bluetooth mesh daemon must be running and configured for access over D-Bus. Also, Rust `nightly`
toolchain must be installed for compiling and running examples.

[BlueZ 5.60]: http://www.bluez.org/release-of-bluez-5-60/
[official changelog]: https://github.com/bluez/bluez/blob/master/ChangeLog

Expand Down Expand Up @@ -162,6 +169,12 @@ The following example applications are provided.

- **list_adapters**: List installed Bluetooth adapters and their properties.

- **mesh_sensor_client**: Simple Bluetooth mesh client sending sensor model messages

- **mesh_sensor_server**: Simple Bluetooth mesh server receiving sensor model messages

- **mesh_provisioner**: Simple Bluetooth mesh provisioner

Use `cargo run --all-features --example <name>` to run a particular example application.

[API documentation]: https://docs.rs/bluer
Expand Down
164 changes: 164 additions & 0 deletions bluer/examples/mesh_provisioner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! Attach and send/receive BT Mesh messages
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be useful as a standalone tool?
Then consider moving it to bluer-tools and properly documenting the CLI arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is just an example for now (as it is tied to the predefined model) ... The proper tool needs more work

//!
//! Example meshd
//! [bluer/bluer]$ sudo /usr/libexec/bluetooth/bluetooth-meshd --config ${PWD}/examples/meshd/config --storage ${PWD}/meshd/lib --debug
//!
//! To demo device join, run client or server without a token
//! [bluer/bluer]$ RUST_LOG=TRACE cargo +nightly run --features=mesh --example mesh_sensor_client
//!
//! Example provisioner
//! [bluer/bluer]$ RUST_LOG=TRACE cargo +nightly run --features=mesh --example mesh_provisioner -- --token 84783e12f11c4dcd --uuid 4bd9876a3e4844bbb4339ef42f614f1f

use bluer::{
mesh::{
application::Application,
element::*,
provisioner::{Provisioner, ProvisionerControlHandle, ProvisionerEvent},
},
Uuid,
};
use btmesh_models::{
foundation::configuration::{
app_key::AppKeyMessage, ConfigurationMessage, ConfigurationServer, CONFIGURATION_CLIENT,
CONFIGURATION_SERVER,
},
Message, Model,
};
use clap::Parser;
use futures::{pin_mut, StreamExt};
use std::time::Duration;
use tokio::{signal, sync::mpsc, time::sleep};
use tokio_stream::wrappers::ReceiverStream;

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(short, long)]
token: String,
#[clap(short, long)]
uuid: String,
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let args = Args::parse();
let session = bluer::Session::new().await?;

let mesh = session.mesh().await?;

let (element_control, element_handle) = element_control(5);
let (app_tx, _app_rx) = mpsc::channel(1);

let (prov_tx, prov_rx) = mpsc::channel(1);

let sim = Application {
device_id: Uuid::new_v4(),
elements: vec![Element {
location: None,
models: vec![CONFIGURATION_SERVER, CONFIGURATION_CLIENT],
control_handle: Some(element_handle),
}],
provisioner: Some(Provisioner {
control_handle: ProvisionerControlHandle { messages_tx: prov_tx },
start_address: 0xbd,
}),
events_tx: app_tx,
agent: Default::default(),
properties: Default::default(),
};

let registered = mesh.application(sim.clone()).await?;
let node = mesh.attach(sim.clone(), &args.token).await?;

node.management.add_node(Uuid::parse_str(&args.uuid)?).await?;

let mut prov_stream = ReceiverStream::new(prov_rx);
pin_mut!(element_control);

loop {
tokio::select! {
_ = signal::ctrl_c() => break,
evt = prov_stream.next() => {
match evt {
Some(msg) => {
match msg {
ProvisionerEvent::AddNodeComplete(uuid, unicast, count) => {
println!("Successfully added node {:?} to the address {:#04x} with {:?} elements", uuid, unicast, count);

sleep(Duration::from_secs(1)).await;
node.add_app_key(0, unicast, 0, 0, false).await?;

// example composition get
// let message = ConfigurationMessage::CompositionData(CompositionDataMessage::Get(0));
// node.dev_key_send::<ConfigurationServer>(message, element_path.clone(), unicast, true, 0 as u16).await?;

// example bind
// let payload = ModelAppPayload {
// element_address: unicast.try_into().map_err(|_| ReqError::Failed)?,
// app_key_index: AppKeyIndex::new(0),
// model_identifier: SENSOR_SERVER,
// };

// let message = ConfigurationMessage::from(ModelAppMessage::Bind(payload));
// node.dev_key_send::<ConfigurationServer>(message, element_path.clone(), unicast, true, 0 as u16).await?;
},
ProvisionerEvent::AddNodeFailed(uuid, reason) => {
println!("Failed to add node {:?}: '{:?}'", uuid, reason);
break;
}
}
},
None => break,
}
},
evt = element_control.next() => {
match evt {
Some(msg) => {
match msg {
ElementEvent::MessageReceived(received) => {
println!("Received element message: {:?}", received);
},
ElementEvent::DevKeyMessageReceived(received) => {
println!("Received dev key message: {:?}", received);
match ConfigurationServer::parse(&received.opcode, &received.parameters).map_err(|_| std::fmt::Error)? {
Some(message) => {
match message {
ConfigurationMessage::AppKey(key) => {
match key {
AppKeyMessage::Status(status) => {
println!("Received keys {:?} {:?}", status.indexes.net_key(), status.indexes.app_key())
},
_ => println!("Received key message {:?}", key.opcode()),
}
break;
},
_ => {
println!("Received dev key message {:?}", message.opcode());
break;
}
}
},
None => break,
}
}
}
},
None => break,
}
},
}
}

// Example agent function
// pub fn display_numeric(req: DisplayNumeric) -> ReqResult<()> {
// println!("Enter '{:?}' on the remote device!", req.number);
// Ok(())
// }

println!("Shutting down");
drop(registered);
sleep(Duration::from_secs(1)).await;

Ok(())
}
Loading