Skip to content

Commit a45b777

Browse files
authored
Merge pull request #11 from meshtastic/ajmcquilkin/connection-hang-debugging
Connection reliability fixes
2 parents aa95930 + 501ad98 commit a45b777

File tree

9 files changed

+676
-118
lines changed

9 files changed

+676
-118
lines changed

.github/workflows/testing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ jobs:
3737
3838
- name: Run test suite
3939
working-directory: ./
40-
run: cargo test
40+
run: cargo test --features ts-gen

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ categories = ["embedded", "config", "encoding"]
88
authors = ["Adam McQuilkin"]
99
readme = "README.md"
1010
license = "GPL-3.0"
11-
version = "0.1.5"
11+
version = "0.1.6"
1212
edition = "2021"
1313

1414
[lib]
@@ -54,3 +54,7 @@ serde_json = { version = "1.0", optional = true }
5454
thiserror = "1.0.48"
5555
uuid = "1.6.1"
5656
btleplug = "0.11.5"
57+
58+
[dev-dependencies]
59+
fern = { version = "0.6.2", features = ["colored"] }
60+
humantime = "2.1.0"

examples/basic_serial.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,36 @@
44
extern crate meshtastic;
55

66
use std::io::{self, BufRead};
7+
use std::time::SystemTime;
78

89
use meshtastic::api::StreamApi;
910
use meshtastic::utils;
1011

12+
/// Set up the logger to output to stdout
13+
/// **Note:** the invokation of this function is commented out in main by default.
14+
fn setup_logger() -> Result<(), fern::InitError> {
15+
fern::Dispatch::new()
16+
.format(|out, message, record| {
17+
out.finish(format_args!(
18+
"[{} {} {}] {}",
19+
humantime::format_rfc3339_seconds(SystemTime::now()),
20+
record.level(),
21+
record.target(),
22+
message
23+
))
24+
})
25+
.level(log::LevelFilter::Trace)
26+
.chain(std::io::stdout())
27+
.apply()?;
28+
29+
Ok(())
30+
}
31+
1132
#[tokio::main]
1233
async fn main() -> Result<(), Box<dyn std::error::Error>> {
34+
// Uncomment this to enable logging
35+
// setup_logger()?;
36+
1337
let stream_api = StreamApi::new();
1438

1539
let available_ports = utils::stream::available_serial_ports()?;

examples/basic_tcp.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,36 @@
44
extern crate meshtastic;
55

66
use std::io::{self, BufRead};
7+
use std::time::SystemTime;
78

89
use meshtastic::api::StreamApi;
910
use meshtastic::utils;
1011

12+
/// Set up the logger to output to stdout
13+
/// **Note:** the invokation of this function is commented out in main by default.
14+
fn setup_logger() -> Result<(), fern::InitError> {
15+
fern::Dispatch::new()
16+
.format(|out, message, record| {
17+
out.finish(format_args!(
18+
"[{} {} {}] {}",
19+
humantime::format_rfc3339_seconds(SystemTime::now()),
20+
record.level(),
21+
record.target(),
22+
message
23+
))
24+
})
25+
.level(log::LevelFilter::Trace)
26+
.chain(std::io::stdout())
27+
.apply()?;
28+
29+
Ok(())
30+
}
31+
1132
#[tokio::main]
1233
async fn main() -> Result<(), Box<dyn std::error::Error>> {
34+
// Uncomment this to enable logging
35+
// setup_logger()?;
36+
1337
let stream_api = StreamApi::new();
1438

1539
println!("Enter the address of a TCP port to connect to, in the form \"IP:PORT\":");

src/connections/handlers.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::errors_internal::{Error, InternalChannelError, InternalStreamError};
22
use crate::protobufs;
33
use crate::types::EncodedToRadioPacketWithHeader;
4+
use crate::utils::format_data_packet;
45
use log::{debug, error, trace};
6+
use prost::Message;
57
use tokio::io::{AsyncReadExt, AsyncWriteExt};
68
use tokio::spawn;
79
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
@@ -12,6 +14,10 @@ use crate::connections::stream_buffer::StreamBuffer;
1214

1315
use super::wrappers::encoded_data::IncomingStreamData;
1416

17+
/// Interval for sending heartbeat packets to the radio (in seconds).
18+
/// Needs to be less than this: https://github.com/meshtastic/firmware/blob/eb372c190ec82366998c867acc609a418130d842/src/SerialConsole.cpp#L8
19+
pub const CLIENT_HEARTBEAT_INTERVAL: u64 = 5 * 60; // 5 minutes
20+
1521
pub fn spawn_read_handler<R>(
1622
cancellation_token: CancellationToken,
1723
read_stream: R,
@@ -159,14 +165,86 @@ async fn start_processing_handler(
159165
mut read_output_rx: tokio::sync::mpsc::UnboundedReceiver<IncomingStreamData>,
160166
decoded_packet_tx: UnboundedSender<protobufs::FromRadio>,
161167
) {
162-
trace!("Started message processing handler");
168+
debug!("Started message processing handler");
163169

164170
let mut buffer = StreamBuffer::new(decoded_packet_tx);
165171

166172
while let Some(message) = read_output_rx.recv().await {
167-
trace!("Processing {} bytes from radio", message.data().len());
168173
buffer.process_incoming_bytes(message);
169174
}
170175

171-
trace!("Processing read_output_rx channel closed");
176+
debug!("Processing read_output_rx channel closed");
177+
}
178+
179+
pub fn spawn_heartbeat_handler(
180+
cancellation_token: CancellationToken,
181+
write_input_tx: UnboundedSender<EncodedToRadioPacketWithHeader>,
182+
) -> JoinHandle<Result<(), Error>> {
183+
let handle = start_heartbeat_handler(cancellation_token.clone(), write_input_tx);
184+
185+
spawn(async move {
186+
tokio::select! {
187+
_ = cancellation_token.cancelled() => {
188+
debug!("Heartbeat handler cancelled");
189+
Ok(())
190+
}
191+
write_result = handle => {
192+
if let Err(e) = &write_result {
193+
error!("Heartbeat handler unexpectedly terminated {e:?}");
194+
}
195+
write_result
196+
}
197+
}
198+
})
199+
}
200+
201+
async fn start_heartbeat_handler(
202+
_cancellation_token: CancellationToken,
203+
write_input_tx: UnboundedSender<EncodedToRadioPacketWithHeader>,
204+
) -> Result<(), Error> {
205+
debug!("Started heartbeat handler");
206+
207+
loop {
208+
tokio::time::sleep(std::time::Duration::from_secs(CLIENT_HEARTBEAT_INTERVAL)).await;
209+
210+
let heartbeat_packet = protobufs::ToRadio {
211+
payload_variant: Some(protobufs::to_radio::PayloadVariant::Heartbeat(
212+
protobufs::Heartbeat::default(),
213+
)),
214+
};
215+
216+
let mut buffer = Vec::new();
217+
match heartbeat_packet.encode(&mut buffer) {
218+
Ok(_) => (),
219+
Err(e) => {
220+
error!("Error encoding heartbeat packet: {:?}", e);
221+
continue;
222+
}
223+
};
224+
225+
let packet_with_header = match format_data_packet(buffer.into()) {
226+
Ok(p) => p,
227+
Err(e) => {
228+
error!("Error formatting heartbeat packet: {:?}", e);
229+
continue;
230+
}
231+
};
232+
233+
trace!("Sending heartbeat packet");
234+
235+
if let Err(e) = write_input_tx.send(packet_with_header) {
236+
error!("Error writing heartbeat packet to stream: {:?}", e);
237+
return Err(Error::InternalStreamError(
238+
InternalStreamError::StreamWriteError {
239+
source: Box::new(e),
240+
},
241+
));
242+
}
243+
244+
log::info!("Sent heartbeat packet");
245+
}
246+
247+
// debug!("Heartbeat handler finished");
248+
249+
// Return type should be never (!)
172250
}

src/connections/stream_api.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub struct ConnectedStreamApi<State = state::Configured> {
7474
read_handle: JoinHandle<Result<(), Error>>,
7575
write_handle: JoinHandle<Result<(), Error>>,
7676
processing_handle: JoinHandle<Result<(), Error>>,
77+
heartbeat_handle: JoinHandle<Result<(), Error>>,
7778

7879
cancellation_token: CancellationToken,
7980

@@ -197,6 +198,8 @@ impl<State> ConnectedStreamApi<State> {
197198
priority: 0, // * not transmitted
198199
rx_rssi: 0, // * not transmitted
199200
delayed: 0, // * not transmitted
201+
hop_start: 0, // * set on device
202+
via_mqtt: false,
200203
from: own_node_id.id(),
201204
to: packet_destination.id(),
202205
id: generate_rand_id(),
@@ -447,6 +450,9 @@ impl StreamApi {
447450
decoded_packet_tx,
448451
);
449452

453+
let heartbeat_handle =
454+
handlers::spawn_heartbeat_handler(cancellation_token.clone(), write_input_tx.clone());
455+
450456
// Persist channels and kill switch to struct
451457

452458
let write_input_tx = write_input_tx;
@@ -461,6 +467,7 @@ impl StreamApi {
461467
read_handle,
462468
write_handle,
463469
processing_handle,
470+
heartbeat_handle,
464471
cancellation_token,
465472
typestate: PhantomData,
466473
},
@@ -536,6 +543,7 @@ impl ConnectedStreamApi<state::Connected> {
536543
read_handle: self.read_handle,
537544
write_handle: self.write_handle,
538545
processing_handle: self.processing_handle,
546+
heartbeat_handle: self.heartbeat_handle,
539547
cancellation_token: self.cancellation_token,
540548
typestate: PhantomData,
541549
})

0 commit comments

Comments
 (0)