From 1b9d133564df008d61912a1f74300e9fe6b90711 Mon Sep 17 00:00:00 2001 From: adria0 Date: Thu, 13 Nov 2025 21:35:41 +0100 Subject: [PATCH 1/5] feat:add example for BLE send message --- Cargo.toml | 4 + examples/send_message_ble.rs | 235 +++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 examples/send_message_ble.rs diff --git a/Cargo.toml b/Cargo.toml index 30c1b08..df36568 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,10 @@ required-features = ["ts-gen", "tokio"] name = "basic_ble" required-features = ["bluetooth-le", "tokio"] +[[example]] +name = "send_message_ble" +required-features = ["bluetooth-le", "tokio"] + [build-dependencies] prost-build = { version = "0.14", optional = true } protoc-bin-vendored = { version = "3.1.0", optional = true } diff --git a/examples/send_message_ble.rs b/examples/send_message_ble.rs new file mode 100644 index 0000000..10d1510 --- /dev/null +++ b/examples/send_message_ble.rs @@ -0,0 +1,235 @@ +//! Example: send_message_ble.rs +//! +//! This example demonstrates how to use the Meshtastic Rust API to send a message over +//! a Bluetooth Low Energy (BLE) connection and checking for its ACK. +//! +//! Usage: +//! cargo run --example send_message_ble --features="bluetooth-le tokio" -- [BLE_DEVICE_NAME] +//! +//! If the BLE_DEVICE_NAME is not provided, the example will scan and prompt to specify a device if multiple +//! are found. +//! +//! if the DESTINATION_NODE is not found the example will dump all destination nodes found. + +use std::collections::{BTreeMap, VecDeque}; +use std::convert::Infallible; +use std::io::Write; +use std::time::Duration; + +use meshtastic::api::StreamApi; +use meshtastic::packet::{PacketDestination, PacketRouter}; +use meshtastic::protobufs::from_radio::PayloadVariant; +use meshtastic::protobufs::{mesh_packet, Data, MyNodeInfo, User}; +use meshtastic::protobufs::{FromRadio, MeshPacket, PortNum}; +use meshtastic::types::{MeshChannel, NodeId}; +use meshtastic::utils::generate_rand_id; +use meshtastic::utils::stream::{build_ble_stream, BleId}; + +use meshtastic::Message; + +struct Router { + sent: VecDeque, + node_id: NodeId, +} +impl Router { + fn new(node_id: NodeId) -> Self { + Self { + sent: VecDeque::new(), + node_id, + } + } +} +impl PacketRouter<(), Infallible> for Router { + fn handle_packet_from_radio(&mut self, _packet: FromRadio) -> Result<(), Infallible> { + Ok(()) + } + fn handle_mesh_packet(&mut self, packet: MeshPacket) -> Result<(), Infallible> { + self.sent.push_back(packet); + Ok(()) + } + fn source_node_id(&self) -> NodeId { + self.node_id + } +} + +enum RecievedPacket { + ConnectionClosed, + RoutingApp(Data), + MyInfo(MyNodeInfo), + NodeInfo(NodeId, User), + Other, +} +impl From> for RecievedPacket { + fn from(from_radio: Option) -> Self { + use RecievedPacket::*; + let Some(from_radio) = from_radio else { + return ConnectionClosed; + }; + let Some(payload) = from_radio.payload_variant else { + return Other; + }; + match payload { + PayloadVariant::MyInfo(my_node_info) => MyInfo(my_node_info), + PayloadVariant::NodeInfo(node_info) => { + if let Some(user) = node_info.user { + NodeInfo(NodeId::new(node_info.num), user) + } else { + Other + } + } + PayloadVariant::Packet(recv_packet) => { + let Some(pv) = recv_packet.payload_variant else { + return Other; + }; + let mesh_packet::PayloadVariant::Decoded(data) = pv else { + return Other; + }; + match PortNum::try_from(data.portnum) { + Ok(PortNum::RoutingApp) => RoutingApp(data), + Ok(PortNum::NodeinfoApp) => { + if let Ok(user) = User::decode(data.payload.as_slice()) { + NodeInfo(NodeId::new(recv_packet.from), user) + } else { + Other + } + } + _ => Other, + } + } + _ => Other, + } + } +} + +async fn get_ble_device() -> String { + println!("Scanning devices 5s..."); + let devices = meshtastic::utils::stream::available_ble_devices(Duration::from_secs(5)) + .await + .expect("available_ble_devices failed"); + + match devices.len() { + 0 => { + panic!("No BLE devices found"); + } + 1 => devices[0] + .name + .clone() + .expect("Device name should be present"), + _ => { + println!("Multiple BLE devices found: {:?}", devices); + panic!("Please specify a device as last argument"); + } + } +} + +#[tokio::main] +async fn main() { + let Some(to) = std::env::args().nth(1) else { + panic!("First argument should be the destination node or BROADCAST"); + }; + let Some(msg) = std::env::args().nth(2) else { + panic!("Second argument should be the message"); + }; + let ble_device = if let Some(ble_device) = std::env::args().nth(3) { + ble_device + } else { + get_ble_device().await + }; + + // Initialize BLE stream + // ----------------------------------------------------------------------- + println!("Connecting to {}", ble_device); + let ble_stream = build_ble_stream(&BleId::from_name(&ble_device), Duration::from_secs(5)) + .await + .expect("Unable to build BLE stream"); + + let stream_api = StreamApi::new(); + let (mut packet_rx, stream_api) = stream_api.connect(ble_stream).await; + let config_id = generate_rand_id(); + let mut stream_api = stream_api + .configure(config_id) + .await + .expect("Unable to open stream api"); + + // Get MyInfo from the first message of stream + // ----------------------------------------------------------------------- + let RecievedPacket::MyInfo(my_node_info) = RecievedPacket::from(packet_rx.recv().await) else { + panic!("Failed to receive MyInfo"); + }; + + println!("Got my node id {}", my_node_info.my_node_num); + + // Retrieve all node names by processing incoming packets. + // This also clears the BLE connection buffer to free up space, + // ensuring there is room to send outgoing messages without issues. + // ----------------------------------------------------------------------- + + let mut nodes: BTreeMap<_, _> = [(String::from("BROADCAST"), NodeId::new(u32::MAX))].into(); + + print!("Emptying I/O buffer & getting other nodes info..."); + loop { + tokio::select! { + packet = packet_rx.recv() => { + match RecievedPacket::from(packet).into() { + RecievedPacket::NodeInfo(node_id, node_info) => { + nodes.insert(node_info.short_name, node_id); + } + RecievedPacket::ConnectionClosed => { + panic!("Connection closed"); + } + _ => {} + } + print!("."); + std::io::stdout().flush().unwrap(); + } + _ = tokio::time::sleep(Duration::from_millis(200)) => { + break; + } + } + } + + let Some(to) = nodes.get(&to) else { + println!("\nAvailable nodes: {:?}", nodes.keys()); + panic!("Specified node not found"); + }; + + // Send a message + // ----------------------------------------------------------------------- + print!("\nSending message..."); + + let mut packet_router = Router::new(NodeId::new(my_node_info.my_node_num)); + stream_api + .send_text( + &mut packet_router, + msg, + PacketDestination::Node(*to), + true, + MeshChannel::default(), + ) + .await + .expect("Unable to send message"); + + let sent_packet = packet_router.sent.pop_front().unwrap(); + + println!(" sent."); + + // Wait for ACK + // ----------------------------------------------------------------------- + print!("Waiting for ACK (packet_id={})...", sent_packet.id); + std::io::stdout().flush().unwrap(); + + loop { + match packet_rx.recv().await.into() { + RecievedPacket::RoutingApp(data) => { + if data.portnum == PortNum::RoutingApp as i32 && data.request_id == sent_packet.id { + println!("got ACK"); + break; + } + } + RecievedPacket::ConnectionClosed => { + panic!("Connection closed"); + } + _ => {} + } + } +} From eada493d3091dc0bf8f7598b112fd9e75c00b07e Mon Sep 17 00:00:00 2001 From: adria0 Date: Fri, 14 Nov 2025 16:09:13 +0100 Subject: [PATCH 2/5] Address @andrewdavidmackenzie review comments. --- examples/send_message_ble.rs | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/examples/send_message_ble.rs b/examples/send_message_ble.rs index 10d1510..0501a96 100644 --- a/examples/send_message_ble.rs +++ b/examples/send_message_ble.rs @@ -24,7 +24,6 @@ use meshtastic::protobufs::{FromRadio, MeshPacket, PortNum}; use meshtastic::types::{MeshChannel, NodeId}; use meshtastic::utils::generate_rand_id; use meshtastic::utils::stream::{build_ble_stream, BleId}; - use meshtastic::Message; struct Router { @@ -44,6 +43,7 @@ impl PacketRouter<(), Infallible> for Router { Ok(()) } fn handle_mesh_packet(&mut self, packet: MeshPacket) -> Result<(), Infallible> { + println!("Mesh packet sent.."); self.sent.push_back(packet); Ok(()) } @@ -53,18 +53,14 @@ impl PacketRouter<(), Infallible> for Router { } enum RecievedPacket { - ConnectionClosed, RoutingApp(Data), MyInfo(MyNodeInfo), NodeInfo(NodeId, User), Other, } -impl From> for RecievedPacket { - fn from(from_radio: Option) -> Self { +impl From for RecievedPacket { + fn from(from_radio: FromRadio) -> Self { use RecievedPacket::*; - let Some(from_radio) = from_radio else { - return ConnectionClosed; - }; let Some(payload) = from_radio.payload_variant else { return Other; }; @@ -102,7 +98,7 @@ impl From> for RecievedPacket { } async fn get_ble_device() -> String { - println!("Scanning devices 5s..."); + println!("Scanning devices 5s, will connect if only one device is found,..."); let devices = meshtastic::utils::stream::available_ble_devices(Duration::from_secs(5)) .await .expect("available_ble_devices failed"); @@ -153,7 +149,8 @@ async fn main() { // Get MyInfo from the first message of stream // ----------------------------------------------------------------------- - let RecievedPacket::MyInfo(my_node_info) = RecievedPacket::from(packet_rx.recv().await) else { + let from_radio = packet_rx.recv().await.expect("BLE stream closed"); + let RecievedPacket::MyInfo(my_node_info) = from_radio.into() else { panic!("Failed to receive MyInfo"); }; @@ -164,19 +161,18 @@ async fn main() { // ensuring there is room to send outgoing messages without issues. // ----------------------------------------------------------------------- + // Map of node names to NodeId let mut nodes: BTreeMap<_, _> = [(String::from("BROADCAST"), NodeId::new(u32::MAX))].into(); print!("Emptying I/O buffer & getting other nodes info..."); loop { tokio::select! { packet = packet_rx.recv() => { + let packet = packet.expect("BLE stream closed"); match RecievedPacket::from(packet).into() { RecievedPacket::NodeInfo(node_id, node_info) => { nodes.insert(node_info.short_name, node_id); } - RecievedPacket::ConnectionClosed => { - panic!("Connection closed"); - } _ => {} } print!("."); @@ -190,7 +186,7 @@ async fn main() { let Some(to) = nodes.get(&to) else { println!("\nAvailable nodes: {:?}", nodes.keys()); - panic!("Specified node not found"); + panic!("Specified node '{to}' not found"); }; // Send a message @@ -211,25 +207,23 @@ async fn main() { let sent_packet = packet_router.sent.pop_front().unwrap(); - println!(" sent."); - // Wait for ACK // ----------------------------------------------------------------------- print!("Waiting for ACK (packet_id={})...", sent_packet.id); std::io::stdout().flush().unwrap(); loop { - match packet_rx.recv().await.into() { + let from_radio = packet_rx.recv().await.expect("BLE stream closed"); + match from_radio.into() { RecievedPacket::RoutingApp(data) => { if data.portnum == PortNum::RoutingApp as i32 && data.request_id == sent_packet.id { println!("got ACK"); break; } } - RecievedPacket::ConnectionClosed => { - panic!("Connection closed"); - } _ => {} } } + + let _ = stream_api.disconnect().await.expect("Unable to disconnect"); } From ec6b20e84b2ff8de9dcdbeb70e6ea7a2594cf869 Mon Sep 17 00:00:00 2001 From: adria0 Date: Thu, 20 Nov 2025 20:18:37 +0100 Subject: [PATCH 3/5] Fix example. --- examples/send_message_ble.rs | 65 +++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/examples/send_message_ble.rs b/examples/send_message_ble.rs index 0501a96..ed92f44 100644 --- a/examples/send_message_ble.rs +++ b/examples/send_message_ble.rs @@ -14,7 +14,7 @@ use std::collections::{BTreeMap, VecDeque}; use std::convert::Infallible; use std::io::Write; -use std::time::Duration; +use std::time::{Duration, Instant}; use meshtastic::api::StreamApi; use meshtastic::packet::{PacketDestination, PacketRouter}; @@ -25,11 +25,11 @@ use meshtastic::types::{MeshChannel, NodeId}; use meshtastic::utils::generate_rand_id; use meshtastic::utils::stream::{build_ble_stream, BleId}; use meshtastic::Message; - struct Router { sent: VecDeque, node_id: NodeId, } + impl Router { fn new(node_id: NodeId) -> Self { Self { @@ -38,6 +38,7 @@ impl Router { } } } + impl PacketRouter<(), Infallible> for Router { fn handle_packet_from_radio(&mut self, _packet: FromRadio) -> Result<(), Infallible> { Ok(()) @@ -53,11 +54,12 @@ impl PacketRouter<(), Infallible> for Router { } enum RecievedPacket { - RoutingApp(Data), + RoutingApp(MeshPacket, Data), MyInfo(MyNodeInfo), NodeInfo(NodeId, User), Other, } + impl From for RecievedPacket { fn from(from_radio: FromRadio) -> Self { use RecievedPacket::*; @@ -74,14 +76,14 @@ impl From for RecievedPacket { } } PayloadVariant::Packet(recv_packet) => { - let Some(pv) = recv_packet.payload_variant else { + let Some(pv) = recv_packet.payload_variant.clone() else { return Other; }; let mesh_packet::PayloadVariant::Decoded(data) = pv else { return Other; }; match PortNum::try_from(data.portnum) { - Ok(PortNum::RoutingApp) => RoutingApp(data), + Ok(PortNum::RoutingApp) => RoutingApp(recv_packet, data), Ok(PortNum::NodeinfoApp) => { if let Ok(user) = User::decode(data.payload.as_slice()) { NodeInfo(NodeId::new(recv_packet.from), user) @@ -167,18 +169,16 @@ async fn main() { print!("Emptying I/O buffer & getting other nodes info..."); loop { tokio::select! { - packet = packet_rx.recv() => { - let packet = packet.expect("BLE stream closed"); - match RecievedPacket::from(packet).into() { - RecievedPacket::NodeInfo(node_id, node_info) => { - nodes.insert(node_info.short_name, node_id); - } - _ => {} - } + from_radio = packet_rx.recv() => { print!("."); + let from_radio = from_radio.expect("BLE stream closed"); + let RecievedPacket::NodeInfo(node_id, node_info) = RecievedPacket::from(from_radio).into() else { + continue; + }; + nodes.insert(node_info.short_name, node_id); std::io::stdout().flush().unwrap(); } - _ = tokio::time::sleep(Duration::from_millis(200)) => { + _ = tokio::time::sleep(Duration::from_millis(1000)) => { break; } } @@ -189,6 +189,8 @@ async fn main() { panic!("Specified node '{to}' not found"); }; + println!("Destination node {}", to.id()); + // Send a message // ----------------------------------------------------------------------- print!("\nSending message..."); @@ -212,18 +214,35 @@ async fn main() { print!("Waiting for ACK (packet_id={})...", sent_packet.id); std::io::stdout().flush().unwrap(); - loop { - let from_radio = packet_rx.recv().await.expect("BLE stream closed"); - match from_radio.into() { - RecievedPacket::RoutingApp(data) => { - if data.portnum == PortNum::RoutingApp as i32 && data.request_id == sent_packet.id { - println!("got ACK"); - break; + let start = Instant::now(); + let mut found = false; + while start.elapsed().as_secs() < 60 && !found { + print!("."); + std::io::stdout().flush().unwrap(); + tokio::select! { + from_radio = packet_rx.recv() => { + let from_radio = from_radio.expect("BLE stream closed"); + let RecievedPacket::RoutingApp(mesh_packet,data) = RecievedPacket::from(from_radio).into() else { + continue; + }; + if data.portnum == PortNum::RoutingApp as i32 + && data.request_id == sent_packet.id + && mesh_packet.from == to.id() + && mesh_packet.to == my_node_info.my_node_num + { + found = true; } } - _ => {} + _ = tokio::time::sleep(Duration::from_millis(1000)) => { + } } } - let _ = stream_api.disconnect().await.expect("Unable to disconnect"); + if found { + println!("got ACK in {}s", start.elapsed().as_secs()); + } else { + println!("ACK timeout"); + } + + let _ = stream_api.disconnect().await; } From ca29df738581a181824b5d241692668c1f07ed8d Mon Sep 17 00:00:00 2001 From: adria0 Date: Sun, 23 Nov 2025 21:28:44 +0100 Subject: [PATCH 4/5] Better example --- Cargo.toml | 4 +- examples/send_message_ble.rs | 181 +++++++++++++++++------------------ 2 files changed, 91 insertions(+), 94 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df36568..e22639c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ authors = ["Adam McQuilkin"] readme = "README.md" license = "GPL-3.0" version = "0.1.7" -rust-version = "1.84" -edition = "2021" +rust-version = "1.91" +edition = "2024" [lib] doctest = false diff --git a/examples/send_message_ble.rs b/examples/send_message_ble.rs index ed92f44..d241417 100644 --- a/examples/send_message_ble.rs +++ b/examples/send_message_ble.rs @@ -13,18 +13,29 @@ use std::collections::{BTreeMap, VecDeque}; use std::convert::Infallible; -use std::io::Write; use std::time::{Duration, Instant}; +use meshtastic::Message; use meshtastic::api::StreamApi; use meshtastic::packet::{PacketDestination, PacketRouter}; -use meshtastic::protobufs::from_radio::PayloadVariant; -use meshtastic::protobufs::{mesh_packet, Data, MyNodeInfo, User}; +use meshtastic::protobufs::mesh_packet::Priority; use meshtastic::protobufs::{FromRadio, MeshPacket, PortNum}; +use meshtastic::protobufs::{Routing, from_radio}; +use meshtastic::protobufs::{mesh_packet, routing}; use meshtastic::types::{MeshChannel, NodeId}; use meshtastic::utils::generate_rand_id; -use meshtastic::utils::stream::{build_ble_stream, BleId}; -use meshtastic::Message; +use meshtastic::utils::stream::{BleId, build_ble_stream}; + +const BROADCAST: u32 = 0xffffffff; + +macro_rules! print_flush { + ($($arg:tt)*) => {{ + use std::io::{Write, stdout}; + print!($($arg)*); + stdout().flush().unwrap(); + }}; +} + struct Router { sent: VecDeque, node_id: NodeId, @@ -53,52 +64,6 @@ impl PacketRouter<(), Infallible> for Router { } } -enum RecievedPacket { - RoutingApp(MeshPacket, Data), - MyInfo(MyNodeInfo), - NodeInfo(NodeId, User), - Other, -} - -impl From for RecievedPacket { - fn from(from_radio: FromRadio) -> Self { - use RecievedPacket::*; - let Some(payload) = from_radio.payload_variant else { - return Other; - }; - match payload { - PayloadVariant::MyInfo(my_node_info) => MyInfo(my_node_info), - PayloadVariant::NodeInfo(node_info) => { - if let Some(user) = node_info.user { - NodeInfo(NodeId::new(node_info.num), user) - } else { - Other - } - } - PayloadVariant::Packet(recv_packet) => { - let Some(pv) = recv_packet.payload_variant.clone() else { - return Other; - }; - let mesh_packet::PayloadVariant::Decoded(data) = pv else { - return Other; - }; - match PortNum::try_from(data.portnum) { - Ok(PortNum::RoutingApp) => RoutingApp(recv_packet, data), - Ok(PortNum::NodeinfoApp) => { - if let Ok(user) = User::decode(data.payload.as_slice()) { - NodeInfo(NodeId::new(recv_packet.from), user) - } else { - Other - } - } - _ => Other, - } - } - _ => Other, - } - } -} - async fn get_ble_device() -> String { println!("Scanning devices 5s, will connect if only one device is found,..."); let devices = meshtastic::utils::stream::available_ble_devices(Duration::from_secs(5)) @@ -149,34 +114,39 @@ async fn main() { .await .expect("Unable to open stream api"); - // Get MyInfo from the first message of stream - // ----------------------------------------------------------------------- - let from_radio = packet_rx.recv().await.expect("BLE stream closed"); - let RecievedPacket::MyInfo(my_node_info) = from_radio.into() else { - panic!("Failed to receive MyInfo"); - }; - - println!("Got my node id {}", my_node_info.my_node_num); - // Retrieve all node names by processing incoming packets. // This also clears the BLE connection buffer to free up space, // ensuring there is room to send outgoing messages without issues. // ----------------------------------------------------------------------- + // My node + let mut my_node_info = None; // Map of node names to NodeId - let mut nodes: BTreeMap<_, _> = [(String::from("BROADCAST"), NodeId::new(u32::MAX))].into(); - + let mut nodes: BTreeMap<_, _> = BTreeMap::new(); print!("Emptying I/O buffer & getting other nodes info..."); + loop { tokio::select! { from_radio = packet_rx.recv() => { - print!("."); + print_flush!("."); + let from_radio = from_radio.expect("BLE stream closed"); - let RecievedPacket::NodeInfo(node_id, node_info) = RecievedPacket::from(from_radio).into() else { - continue; + let Some(payload) = from_radio.payload_variant else { + continue }; - nodes.insert(node_info.short_name, node_id); - std::io::stdout().flush().unwrap(); + match payload { + // Check for information about my node + from_radio::PayloadVariant::MyInfo(node_info) => { + my_node_info = Some(node_info) + }, + // Check for the data in NodeDB + from_radio::PayloadVariant::NodeInfo(node_info) => { + if let Some(user) = node_info.user { + nodes.insert(user.short_name, node_info.num); + } + }, + _=> {} + } } _ = tokio::time::sleep(Duration::from_millis(1000)) => { break; @@ -184,12 +154,21 @@ async fn main() { } } - let Some(to) = nodes.get(&to) else { + let my_node_info = my_node_info.expect("Failed to receive MyInfo"); + + let to = if to == "BROADCAST" { + BROADCAST + } else if let Some(to) = nodes.get(&to) { + *to + } else { println!("\nAvailable nodes: {:?}", nodes.keys()); panic!("Specified node '{to}' not found"); }; - println!("Destination node {}", to.id()); + println!( + "\nMy node id {}, destination node id {}", + my_node_info.my_node_num, to + ); // Send a message // ----------------------------------------------------------------------- @@ -200,49 +179,67 @@ async fn main() { .send_text( &mut packet_router, msg, - PacketDestination::Node(*to), + PacketDestination::Node(NodeId::new(to)), true, - MeshChannel::default(), + MeshChannel::new(0).unwrap(), ) .await .expect("Unable to send message"); let sent_packet = packet_router.sent.pop_front().unwrap(); - // Wait for ACK + // Wait for ACK (not available in Broadcast) // ----------------------------------------------------------------------- - print!("Waiting for ACK (packet_id={})...", sent_packet.id); - std::io::stdout().flush().unwrap(); + print_flush!("Waiting for ACK (packet_id={})...", sent_packet.id); let start = Instant::now(); - let mut found = false; - while start.elapsed().as_secs() < 60 && !found { - print!("."); - std::io::stdout().flush().unwrap(); + loop { + print_flush!("."); tokio::select! { from_radio = packet_rx.recv() => { let from_radio = from_radio.expect("BLE stream closed"); - let RecievedPacket::RoutingApp(mesh_packet,data) = RecievedPacket::from(from_radio).into() else { - continue; - }; - if data.portnum == PortNum::RoutingApp as i32 - && data.request_id == sent_packet.id - && mesh_packet.from == to.id() + if let Some(from_radio::PayloadVariant::Packet(mesh_packet)) = from_radio.payload_variant + // Check mesh packet destination && mesh_packet.to == my_node_info.my_node_num + // Check request id + && let Some(mesh_packet::PayloadVariant::Decoded(data)) = mesh_packet.payload_variant + && data.request_id == sent_packet.id + // Check that is a Routing app without error + && PortNum::try_from(data.portnum) == Ok(PortNum::RoutingApp) + && let Ok(Routing{ variant }) = Routing::decode(data.payload.as_slice()) + && let Some(routing::Variant::ErrorReason(routing_error)) = variant { - found = true; + if routing_error != routing::Error::None as i32 { + println!("Error in routing: {:?}", routing_error); + break; + } + if mesh_packet.from == to { + // Normal ACK: if comes from the destination node + println!("got ACK from destination in {}s", start.elapsed().as_secs()); + break; + } else if mesh_packet.from == my_node_info.my_node_num && mesh_packet.priority == Priority::Ack.into() { + // Implicit ACK: my node heard another node rebroadcast my message + println!("got Implicit ACK in {}s", start.elapsed().as_secs()); + if to == BROADCAST { + // In the case of BROADCAST, this is the only packet that we will recieve. + // (from documentation) + // The original sender listens to see if at least one node is rebroadcasting + // this packet (because naive flooding algorithm). + // If it hears that the odds (given typical LoRa topologies) the odds are very + // high that every node should eventually receive the message. + break; + } + } } } _ = tokio::time::sleep(Duration::from_millis(1000)) => { - } + if start.elapsed().as_secs() > 60 { + println!("ACK timeout"); + break; + } + } } } - if found { - println!("got ACK in {}s", start.elapsed().as_secs()); - } else { - println!("ACK timeout"); - } - let _ = stream_api.disconnect().await; } From 17a43dac34cce7824e64af3fef8c77fb003b4393 Mon Sep 17 00:00:00 2001 From: adria0 Date: Mon, 24 Nov 2025 15:19:51 +0100 Subject: [PATCH 5/5] Removed BLE discovery --- Cargo.toml | 4 +- examples/send_message_ble.rs | 85 ++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e22639c..df36568 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ authors = ["Adam McQuilkin"] readme = "README.md" license = "GPL-3.0" version = "0.1.7" -rust-version = "1.91" -edition = "2024" +rust-version = "1.84" +edition = "2021" [lib] doctest = false diff --git a/examples/send_message_ble.rs b/examples/send_message_ble.rs index d241417..f7646cf 100644 --- a/examples/send_message_ble.rs +++ b/examples/send_message_ble.rs @@ -4,10 +4,7 @@ //! a Bluetooth Low Energy (BLE) connection and checking for its ACK. //! //! Usage: -//! cargo run --example send_message_ble --features="bluetooth-le tokio" -- [BLE_DEVICE_NAME] -//! -//! If the BLE_DEVICE_NAME is not provided, the example will scan and prompt to specify a device if multiple -//! are found. +//! cargo run --example send_message_ble --features="bluetooth-le tokio" -- //! //! if the DESTINATION_NODE is not found the example will dump all destination nodes found. @@ -15,16 +12,16 @@ use std::collections::{BTreeMap, VecDeque}; use std::convert::Infallible; use std::time::{Duration, Instant}; -use meshtastic::Message; use meshtastic::api::StreamApi; use meshtastic::packet::{PacketDestination, PacketRouter}; use meshtastic::protobufs::mesh_packet::Priority; -use meshtastic::protobufs::{FromRadio, MeshPacket, PortNum}; -use meshtastic::protobufs::{Routing, from_radio}; +use meshtastic::protobufs::{from_radio, Routing}; use meshtastic::protobufs::{mesh_packet, routing}; +use meshtastic::protobufs::{FromRadio, MeshPacket, PortNum}; use meshtastic::types::{MeshChannel, NodeId}; use meshtastic::utils::generate_rand_id; -use meshtastic::utils::stream::{BleId, build_ble_stream}; +use meshtastic::utils::stream::{build_ble_stream, BleId}; +use meshtastic::Message; const BROADCAST: u32 = 0xffffffff; @@ -93,10 +90,9 @@ async fn main() { let Some(msg) = std::env::args().nth(2) else { panic!("Second argument should be the message"); }; - let ble_device = if let Some(ble_device) = std::env::args().nth(3) { - ble_device - } else { - get_ble_device().await + let Some(ble_device) = std::env::args().nth(3) else { + // You can use available_ble_devices(Duration::from_secs(5)) to discover BLE devices + panic!("Third argument should be the BLE device name"); }; // Initialize BLE stream @@ -198,37 +194,42 @@ async fn main() { tokio::select! { from_radio = packet_rx.recv() => { let from_radio = from_radio.expect("BLE stream closed"); - if let Some(from_radio::PayloadVariant::Packet(mesh_packet)) = from_radio.payload_variant - // Check mesh packet destination - && mesh_packet.to == my_node_info.my_node_num - // Check request id - && let Some(mesh_packet::PayloadVariant::Decoded(data)) = mesh_packet.payload_variant - && data.request_id == sent_packet.id - // Check that is a Routing app without error - && PortNum::try_from(data.portnum) == Ok(PortNum::RoutingApp) - && let Ok(Routing{ variant }) = Routing::decode(data.payload.as_slice()) - && let Some(routing::Variant::ErrorReason(routing_error)) = variant - { - if routing_error != routing::Error::None as i32 { - println!("Error in routing: {:?}", routing_error); - break; - } - if mesh_packet.from == to { - // Normal ACK: if comes from the destination node - println!("got ACK from destination in {}s", start.elapsed().as_secs()); + let Some(from_radio::PayloadVariant::Packet(mesh_packet)) = from_radio.payload_variant else { + continue; + }; + let Some(mesh_packet::PayloadVariant::Decoded(data)) = mesh_packet.payload_variant else { + continue; + }; + if mesh_packet.to != my_node_info.my_node_num + || data.request_id != sent_packet.id + || PortNum::try_from(data.portnum) != Ok(PortNum::RoutingApp) { + continue; + } + let Ok(Routing{ variant }) = Routing::decode(data.payload.as_slice()) else { + continue; + }; + let Some(routing::Variant::ErrorReason(routing_error)) = variant else { + continue; + }; + if routing_error != routing::Error::None as i32 { + println!("Error in routing: {:?}", routing_error); + break; + } + if mesh_packet.from == to { + // Normal ACK: if comes from the destination node + println!("got ACK from destination in {}s", start.elapsed().as_secs()); + break; + } else if mesh_packet.from == my_node_info.my_node_num && mesh_packet.priority == Priority::Ack.into() { + // Implicit ACK: my node heard another node rebroadcast my message + println!("got Implicit ACK in {}s", start.elapsed().as_secs()); + if to == BROADCAST { + // In the case of BROADCAST, this is the only packet that we will recieve. + // (from documentation) + // The original sender listens to see if at least one node is rebroadcasting + // this packet (because naive flooding algorithm). + // If it hears that the odds (given typical LoRa topologies) the odds are very + // high that every node should eventually receive the message. break; - } else if mesh_packet.from == my_node_info.my_node_num && mesh_packet.priority == Priority::Ack.into() { - // Implicit ACK: my node heard another node rebroadcast my message - println!("got Implicit ACK in {}s", start.elapsed().as_secs()); - if to == BROADCAST { - // In the case of BROADCAST, this is the only packet that we will recieve. - // (from documentation) - // The original sender listens to see if at least one node is rebroadcasting - // this packet (because naive flooding algorithm). - // If it hears that the odds (given typical LoRa topologies) the odds are very - // high that every node should eventually receive the message. - break; - } } } }