-
-
Notifications
You must be signed in to change notification settings - Fork 37
feat:add example for BLE send message #81
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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(); | ||
| }}; | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you have seen problems due to io not appearing immediately?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's just that if you print! output is not flushed, and you need it to report progress by printing dots. |
||
| struct Router { | ||
| sent: VecDeque<MeshPacket>, | ||
adria0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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<FromRadio> 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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for showing discovery also, when device is not specified in the command line options.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Luka wants to have a separate discovery example, that's fine for me. Just having some discovery example would be good.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a discovery example on a local branch, but I didn't like it. I'll try to polish it. |
||
| 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,47 +114,61 @@ 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; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
adria0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's very recent. Do you need to force people onto such a recent version?
Does something in the code need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't change rust-version in a non-related PR. You can file an issue for that if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really only 1.88 is needed to support if let chains. It really makes the code more ergonomic to handle this protobuf de-encapsulations.
https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/
But,why not to bump till last stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, you're forcing anyone on 1.88 etc to upgrade before they can run the example, for no real reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a draft @andrewdavidmackenzie, I'm just experimenting different options here.
Not forcing anyone to anything.