1mod transport;
10
11use std::io::{self, Read, Write};
12use std::sync::{Arc, Mutex};
13use std::thread;
14use std::time::Duration;
15use transport::Transport;
16
17use rns_core::transport::types::{AirtimeProfile, InterfaceId};
18
19use crate::event::{Event, EventSender};
20use crate::interface::{lock_or_recover, Writer};
21use crate::rnode_kiss;
22use crate::serial::{Parity, SerialConfig};
23
24pub const FREQ_MIN: u32 = 137_000_000;
26pub const FREQ_MAX: u32 = 3_000_000_000;
27pub const BW_MIN: u32 = 7_800;
28pub const BW_MAX: u32 = 1_625_000;
29pub const SF_MIN: u8 = 5;
30pub const SF_MAX: u8 = 12;
31pub const CR_MIN: u8 = 5;
32pub const CR_MAX: u8 = 8;
33pub const TXPOWER_MIN: i8 = 0;
34pub const TXPOWER_MAX: i8 = 37;
35pub const HW_MTU: u32 = 508;
36pub const LORA_PREAMBLE_SYMBOLS: u16 = 8;
37pub const LORA_EXPLICIT_HEADER: bool = true;
38pub const LORA_CRC: bool = true;
39
40#[derive(Debug, Clone)]
42pub struct RNodeSubConfig {
43 pub name: String,
44 pub vport: u8,
45 pub outgoing: bool,
46 pub frequency: u32,
47 pub bandwidth: u32,
48 pub txpower: i8,
49 pub spreading_factor: u8,
50 pub coding_rate: u8,
51 pub flow_control: bool,
52 pub st_alock: Option<f32>,
53 pub lt_alock: Option<f32>,
54}
55
56#[derive(Debug, Clone)]
58pub struct RNodeConfig {
59 pub name: String,
60 pub port: String,
61 pub speed: u32,
62 pub subinterfaces: Vec<RNodeSubConfig>,
63 pub multi: bool,
64 pub id_interval: Option<u32>,
65 pub id_callsign: Option<Vec<u8>>,
66 pub base_interface_id: InterfaceId,
67 pub pre_opened_fd: Option<i32>,
70 pub runtime: Arc<Mutex<RNodeRuntime>>,
71}
72
73#[derive(Debug, Clone)]
74pub struct RNodeRuntime {
75 pub sub: RNodeSubConfig,
76 pub writer: Option<Arc<Mutex<Transport>>>,
77}
78
79impl RNodeRuntime {
80 pub fn from_config(config: &RNodeConfig) -> Self {
81 Self {
82 sub: config
83 .subinterfaces
84 .first()
85 .cloned()
86 .unwrap_or_else(|| RNodeSubConfig {
87 name: config.name.clone(),
88 vport: 0,
89 outgoing: true,
90 frequency: 868_000_000,
91 bandwidth: 125_000,
92 txpower: 7,
93 spreading_factor: 8,
94 coding_rate: 5,
95 flow_control: false,
96 st_alock: None,
97 lt_alock: None,
98 }),
99 writer: None,
100 }
101 }
102}
103
104#[derive(Debug, Clone)]
105pub struct RNodeRuntimeConfigHandle {
106 pub interface_name: String,
107 pub runtime: Arc<Mutex<RNodeRuntime>>,
108 pub startup: RNodeRuntime,
109}
110
111impl Default for RNodeConfig {
112 fn default() -> Self {
113 let mut config = RNodeConfig {
114 name: String::new(),
115 port: String::new(),
116 speed: 115200,
117 subinterfaces: Vec::new(),
118 multi: false,
119 id_interval: None,
120 id_callsign: None,
121 base_interface_id: InterfaceId(0),
122 pre_opened_fd: None,
123 runtime: Arc::new(Mutex::new(RNodeRuntime {
124 sub: RNodeSubConfig {
125 name: String::new(),
126 vport: 0,
127 outgoing: true,
128 frequency: 868_000_000,
129 bandwidth: 125_000,
130 txpower: 7,
131 spreading_factor: 8,
132 coding_rate: 5,
133 flow_control: false,
134 st_alock: None,
135 lt_alock: None,
136 },
137 writer: None,
138 })),
139 };
140 let startup = RNodeRuntime::from_config(&config);
141 config.runtime = Arc::new(Mutex::new(startup));
142 config
143 }
144}
145
146pub fn validate_sub_config(sub: &RNodeSubConfig) -> Option<String> {
148 if sub.frequency < FREQ_MIN || sub.frequency > FREQ_MAX {
149 return Some(format!(
150 "Invalid frequency {} for {}",
151 sub.frequency, sub.name
152 ));
153 }
154 if sub.bandwidth < BW_MIN || sub.bandwidth > BW_MAX {
155 return Some(format!(
156 "Invalid bandwidth {} for {}",
157 sub.bandwidth, sub.name
158 ));
159 }
160 if sub.spreading_factor < SF_MIN || sub.spreading_factor > SF_MAX {
161 return Some(format!(
162 "Invalid SF {} for {}",
163 sub.spreading_factor, sub.name
164 ));
165 }
166 if sub.coding_rate < CR_MIN || sub.coding_rate > CR_MAX {
167 return Some(format!("Invalid CR {} for {}", sub.coding_rate, sub.name));
168 }
169 if sub.txpower < TXPOWER_MIN || sub.txpower > TXPOWER_MAX {
170 return Some(format!("Invalid TX power {} for {}", sub.txpower, sub.name));
171 }
172 if let Some(st) = sub.st_alock {
173 if st < 0.0 || st > 100.0 {
174 return Some(format!("Invalid ST airtime limit {} for {}", st, sub.name));
175 }
176 }
177 if let Some(lt) = sub.lt_alock {
178 if lt < 0.0 || lt > 100.0 {
179 return Some(format!("Invalid LT airtime limit {} for {}", lt, sub.name));
180 }
181 }
182 None
183}
184
185pub fn estimate_lora_bitrate_bps(sub: &RNodeSubConfig) -> u64 {
189 let symbols_per_second = sub.bandwidth as f64 / (1u64 << sub.spreading_factor) as f64;
190 let bits_per_symbol = sub.spreading_factor as f64 * (4.0 / sub.coding_rate as f64);
191 (symbols_per_second * bits_per_symbol).round().max(1.0) as u64
192}
193
194pub fn lora_airtime_profile(sub: &RNodeSubConfig) -> AirtimeProfile {
195 AirtimeProfile::Lora {
196 bandwidth: sub.bandwidth,
197 spreading_factor: sub.spreading_factor,
198 coding_rate: sub.coding_rate,
199 preamble_symbols: LORA_PREAMBLE_SYMBOLS,
200 explicit_header: LORA_EXPLICIT_HEADER,
201 crc: LORA_CRC,
202 }
203}
204
205struct RNodeSubWriter {
208 writer: Arc<Mutex<Transport>>,
209 index: u8,
210 flow_control: bool,
211 flow_state: Arc<Mutex<SubFlowState>>,
212 multi: bool,
213}
214
215struct SubFlowState {
216 ready: bool,
217 queue: std::collections::VecDeque<Vec<u8>>,
218}
219
220fn make_sub_writer(
221 writer: Arc<Mutex<Transport>>,
222 index: u8,
223 flow_control: bool,
224 flow_state: Arc<Mutex<SubFlowState>>,
225 multi: bool,
226) -> Box<dyn Writer> {
227 Box::new(RNodeSubWriter {
228 writer,
229 index,
230 flow_control,
231 flow_state,
232 multi,
233 })
234}
235
236impl Writer for RNodeSubWriter {
237 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
238 let frame = if self.multi {
239 rnode_kiss::rnode_multi_data_frame(self.index, data)
240 } else {
241 rnode_kiss::rnode_data_frame(self.index, data)
242 };
243 if self.flow_control {
244 let mut state = lock_or_recover(&self.flow_state, "rnode flow state");
245 if state.ready {
246 state.ready = false;
247 drop(state);
248 lock_or_recover(&self.writer, "rnode shared writer").write_all(&frame)
249 } else {
250 state.queue.push_back(data.to_vec());
251 Ok(())
252 }
253 } else {
254 lock_or_recover(&self.writer, "rnode shared writer").write_all(&frame)
255 }
256 }
257}
258
259pub fn start(
266 config: RNodeConfig,
267 tx: EventSender,
268) -> io::Result<Vec<(InterfaceId, Box<dyn Writer>)>> {
269 for sub in &config.subinterfaces {
271 if let Some(err) = validate_sub_config(sub) {
272 return Err(io::Error::new(io::ErrorKind::InvalidInput, err));
273 }
274 }
275
276 if config.subinterfaces.is_empty() {
277 return Err(io::Error::new(
278 io::ErrorKind::InvalidInput,
279 "No subinterfaces configured",
280 ));
281 }
282
283 let (reader_file, shared_writer) = if let Some(fd) = config.pre_opened_fd {
284 let (r, w) = Transport::open_from_fd(fd)?;
286 (r, Arc::new(Mutex::new(w)))
287 } else {
288 let serial_config = SerialConfig {
289 path: config.port.clone(),
290 baud: config.speed,
291 data_bits: 8,
292 parity: Parity::None,
293 stop_bits: 1,
294 };
295 let (r, w) = Transport::open(&serial_config)?;
296 (r, Arc::new(Mutex::new(w)))
297 };
298
299 let num_subs = config.subinterfaces.len();
301 let mut writers: Vec<(InterfaceId, Box<dyn Writer>)> = Vec::with_capacity(num_subs);
302 let mut flow_states: Vec<Arc<Mutex<SubFlowState>>> = Vec::with_capacity(num_subs);
303
304 for (i, sub) in config.subinterfaces.iter().enumerate() {
305 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
306 let flow_state = Arc::new(Mutex::new(SubFlowState {
307 ready: true,
308 queue: std::collections::VecDeque::new(),
309 }));
310 flow_states.push(flow_state.clone());
311 let sub_writer = make_sub_writer(
312 shared_writer.clone(),
313 sub.vport,
314 sub.flow_control,
315 flow_state,
316 config.multi,
317 );
318 writers.push((sub_id, sub_writer));
319 }
320
321 let reader_shared_writer = shared_writer.clone();
323 {
324 let mut runtime = lock_or_recover(&config.runtime, "rnode runtime");
325 runtime.writer = Some(shared_writer.clone());
326 runtime.sub = config
327 .subinterfaces
328 .first()
329 .cloned()
330 .unwrap_or(runtime.sub.clone());
331 }
332 let reader_config = config.clone();
333 let reader_flow_states = flow_states;
334 thread::Builder::new()
335 .name(format!("rnode-reader-{}", config.base_interface_id.0))
336 .spawn(move || {
337 reader_loop(
338 reader_file,
339 reader_shared_writer,
340 reader_config,
341 tx,
342 reader_flow_states,
343 );
344 })?;
345
346 let keepalive_writer = shared_writer.clone();
349 let keepalive_name = config.name.clone();
350 thread::Builder::new()
351 .name(format!("rnode-keepalive-{}", config.base_interface_id.0))
352 .spawn(move || {
353 let detect = rnode_kiss::detect_request();
354 loop {
355 thread::sleep(Duration::from_secs(15));
356 if let Err(e) =
357 lock_or_recover(&keepalive_writer, "rnode shared writer").write_all(&detect)
358 {
359 log::debug!("[{}] keepalive write failed: {}", keepalive_name, e);
360 }
361 }
362 })?;
363
364 Ok(writers)
365}
366
367fn reader_loop(
369 mut reader: Transport,
370 writer: Arc<Mutex<Transport>>,
371 config: RNodeConfig,
372 tx: EventSender,
373 flow_states: Vec<Arc<Mutex<SubFlowState>>>,
374) {
375 const RECONNECT_INITIAL_DELAY: Duration = Duration::from_millis(200);
376 const RECONNECT_MAX_DELAY: Duration = Duration::from_secs(2);
377 thread::sleep(Duration::from_secs(2));
379 let mut connected_once = false;
380 let mut last_rssi: Option<i16> = None;
381 let mut last_snr: Option<f32> = None;
382 if let Err(e) = detect_and_configure(&mut reader, &writer, &config) {
383 log::error!("[{}] initial RNode setup failed: {}", config.name, e);
384 return;
385 }
386 signal_interface_up(&tx, &config, &writer, &flow_states, connected_once);
387 connected_once = true;
388 loop {
389 let mut decoder = rnode_kiss::RNodeDecoder::new();
390 let mut buf = [0u8; 4096];
391 let disconnected = loop {
392 match reader.read(&mut buf) {
393 Ok(0) => {
394 log::warn!("[{}] serial port closed", config.name);
395 signal_interface_down(&tx, &config);
396 break true;
397 }
398 Ok(n) => {
399 for event in decoder.feed(&buf[..n]) {
400 match event {
401 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
402 let Some(position) = config
403 .subinterfaces
404 .iter()
405 .position(|sub| usize::from(sub.vport) == index)
406 else {
407 continue;
408 };
409 let sub_id =
410 InterfaceId(config.base_interface_id.0 + position as u64);
411 if tx
412 .send(Event::Frame {
413 interface_id: sub_id,
414 data,
415 rssi: last_rssi,
416 snr: last_snr,
417 })
418 .is_err()
419 {
420 return;
421 }
422 last_rssi = None;
423 last_snr = None;
424 }
425 rnode_kiss::RNodeEvent::Ready => {
426 for (i, fs) in flow_states.iter().enumerate() {
428 if config.subinterfaces[i].flow_control {
429 process_flow_queue(
430 fs,
431 &writer,
432 config.subinterfaces[i].vport,
433 config.multi,
434 );
435 }
436 }
437 }
438 rnode_kiss::RNodeEvent::StatRssi(rssi) => {
439 last_rssi = Some(rssi as i16 - 157);
440 }
441 rnode_kiss::RNodeEvent::StatSnr(snr) => {
442 last_snr = Some(snr as f32 * 0.25);
443 }
444 rnode_kiss::RNodeEvent::Error(code) => {
445 log::error!("[{}] RNode error: 0x{:02X}", config.name, code);
446 }
447 _ => {
448 }
450 }
451 }
452 }
453 Err(e) => {
454 log::error!("[{}] serial read error: {}", config.name, e);
455 signal_interface_down(&tx, &config);
456 break true;
457 }
458 }
459 };
460
461 clear_pending_rx_metadata(&mut last_rssi, &mut last_snr);
462
463 if !disconnected || config.pre_opened_fd.is_some() {
464 return;
465 }
466
467 let mut backoff = RECONNECT_INITIAL_DELAY;
468 loop {
469 match reopen_connection(&config, &writer) {
470 Ok(new_reader) => {
471 reset_flow_states(&flow_states);
472 reader = new_reader;
473 if let Err(e) = detect_and_configure(&mut reader, &writer, &config) {
474 log::warn!("[{}] reconnect configure failed: {}", config.name, e);
475 thread::sleep(backoff);
476 backoff = std::cmp::min(backoff.saturating_mul(2), RECONNECT_MAX_DELAY);
477 continue;
478 }
479 signal_interface_up(&tx, &config, &writer, &flow_states, connected_once);
480 break;
481 }
482 Err(e) => {
483 log::warn!("[{}] reconnect open failed: {}", config.name, e);
484 thread::sleep(backoff);
485 backoff = std::cmp::min(backoff.saturating_mul(2), RECONNECT_MAX_DELAY);
486 }
487 }
488 }
489 }
490}
491
492fn detect_and_configure(
493 reader: &mut Transport,
494 writer: &Arc<Mutex<Transport>>,
495 config: &RNodeConfig,
496) -> io::Result<()> {
497 let detect_cmd = rnode_kiss::detect_request();
498 let mut cmd = detect_cmd;
499 cmd.extend_from_slice(&rnode_kiss::rnode_command(
500 rnode_kiss::CMD_FW_VERSION,
501 &[0x00],
502 ));
503 cmd.extend_from_slice(&rnode_kiss::rnode_command(
504 rnode_kiss::CMD_FW_DETAIL,
505 &[0x00],
506 ));
507 cmd.extend_from_slice(&rnode_kiss::rnode_command(
508 rnode_kiss::CMD_PLATFORM,
509 &[0x00],
510 ));
511 cmd.extend_from_slice(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x00]));
512
513 lock_or_recover(writer, "rnode shared writer").write_all(&cmd)?;
514
515 let mut decoder = rnode_kiss::RNodeDecoder::new();
516 let mut buf = [0u8; 4096];
517 let mut detected = false;
518 let detect_start = std::time::Instant::now();
519 let detect_timeout = Duration::from_secs(5);
520
521 while !detected && detect_start.elapsed() < detect_timeout {
522 match reader.read(&mut buf) {
523 Ok(0) => {
524 return Err(io::Error::new(
525 io::ErrorKind::UnexpectedEof,
526 "serial port closed during detect",
527 ));
528 }
529 Ok(n) => {
530 for event in decoder.feed(&buf[..n]) {
531 match event {
532 rnode_kiss::RNodeEvent::Detected(true) => {
533 detected = true;
534 log::info!("[{}] RNode device detected", config.name);
535 }
536 rnode_kiss::RNodeEvent::FirmwareVersion { major, minor } => {
537 log::info!("[{}] firmware version {}.{}", config.name, major, minor);
538 }
539 rnode_kiss::RNodeEvent::FirmwareDetail(ref detail) => {
540 log::info!("[{}] firmware detail: {}", config.name, detail);
541 }
542 rnode_kiss::RNodeEvent::Platform(p) => {
543 log::info!("[{}] platform: 0x{:02X}", config.name, p);
544 }
545 rnode_kiss::RNodeEvent::Mcu(m) => {
546 log::info!("[{}] MCU: 0x{:02X}", config.name, m);
547 }
548 _ => {}
549 }
550 }
551 }
552 Err(e) => {
553 return Err(io::Error::new(
554 e.kind(),
555 format!("serial read error during detect: {}", e),
556 ));
557 }
558 }
559 }
560
561 if !detected {
562 return Err(io::Error::new(
563 io::ErrorKind::TimedOut,
564 "RNode detection timed out",
565 ));
566 }
567
568 for sub in &config.subinterfaces {
569 configure_subinterface(writer, sub.vport, sub, config.multi)?;
570 }
571
572 thread::sleep(Duration::from_millis(300));
573 log::info!(
574 "[{}] RNode configured with {} subinterface(s)",
575 config.name,
576 config.subinterfaces.len()
577 );
578 Ok(())
579}
580
581fn signal_interface_down(tx: &EventSender, config: &RNodeConfig) {
582 for i in 0..config.subinterfaces.len() {
583 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
584 let _ = tx.send(Event::InterfaceDown(sub_id));
585 }
586}
587
588fn signal_interface_up(
589 tx: &EventSender,
590 config: &RNodeConfig,
591 writer: &Arc<Mutex<Transport>>,
592 flow_states: &[Arc<Mutex<SubFlowState>>],
593 reconnected: bool,
594) {
595 for (i, flow_state) in flow_states.iter().enumerate() {
596 let sub_id = InterfaceId(config.base_interface_id.0 + i as u64);
597 let new_writer = reconnected.then(|| {
598 make_sub_writer(
599 writer.clone(),
600 config.subinterfaces[i].vport,
601 config.subinterfaces[i].flow_control,
602 flow_state.clone(),
603 config.multi,
604 )
605 });
606 let _ = tx.send(Event::InterfaceUp(sub_id, new_writer, None));
607 }
608}
609
610fn reset_flow_states(flow_states: &[Arc<Mutex<SubFlowState>>]) {
611 for flow_state in flow_states {
612 let mut state = lock_or_recover(flow_state, "rnode flow state");
613 state.ready = true;
614 state.queue.clear();
615 }
616}
617
618fn clear_pending_rx_metadata(last_rssi: &mut Option<i16>, last_snr: &mut Option<f32>) {
619 *last_rssi = None;
620 *last_snr = None;
621}
622
623fn reopen_connection(
624 config: &RNodeConfig,
625 writer: &Arc<Mutex<Transport>>,
626) -> io::Result<Transport> {
627 let serial_config = SerialConfig {
628 path: config.port.clone(),
629 baud: config.speed,
630 data_bits: 8,
631 parity: Parity::None,
632 stop_bits: 1,
633 };
634
635 let (reader, new_writer) = Transport::open(&serial_config)?;
636 *lock_or_recover(writer, "rnode shared writer") = new_writer;
637 Ok(reader)
638}
639
640pub(crate) fn configure_subinterface(
642 writer: &Arc<Mutex<Transport>>,
643 index: u8,
644 sub: &RNodeSubConfig,
645 multi: bool,
646) -> io::Result<()> {
647 let mut w = lock_or_recover(writer, "rnode shared writer");
648
649 let freq_bytes = [
651 (sub.frequency >> 24) as u8,
652 (sub.frequency >> 16) as u8,
653 (sub.frequency >> 8) as u8,
654 (sub.frequency & 0xFF) as u8,
655 ];
656 let bw_bytes = [
657 (sub.bandwidth >> 24) as u8,
658 (sub.bandwidth >> 16) as u8,
659 (sub.bandwidth >> 8) as u8,
660 (sub.bandwidth & 0xFF) as u8,
661 ];
662 let txp = if sub.txpower < 0 {
663 (sub.txpower as i16 + 256) as u8
664 } else {
665 sub.txpower as u8
666 };
667
668 if multi {
669 w.write_all(&rnode_kiss::rnode_select_command(
670 index,
671 rnode_kiss::CMD_FREQUENCY,
672 &freq_bytes,
673 ))?;
674 w.write_all(&rnode_kiss::rnode_select_command(
675 index,
676 rnode_kiss::CMD_BANDWIDTH,
677 &bw_bytes,
678 ))?;
679 w.write_all(&rnode_kiss::rnode_select_command(
680 index,
681 rnode_kiss::CMD_TXPOWER,
682 &[txp],
683 ))?;
684 w.write_all(&rnode_kiss::rnode_select_command(
685 index,
686 rnode_kiss::CMD_SF,
687 &[sub.spreading_factor],
688 ))?;
689 w.write_all(&rnode_kiss::rnode_select_command(
690 index,
691 rnode_kiss::CMD_CR,
692 &[sub.coding_rate],
693 ))?;
694 } else {
695 w.write_all(&rnode_kiss::rnode_command(
696 rnode_kiss::CMD_FREQUENCY,
697 &freq_bytes,
698 ))?;
699 w.write_all(&rnode_kiss::rnode_command(
700 rnode_kiss::CMD_BANDWIDTH,
701 &bw_bytes,
702 ))?;
703 w.write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_TXPOWER, &[txp]))?;
704 w.write_all(&rnode_kiss::rnode_command(
705 rnode_kiss::CMD_SF,
706 &[sub.spreading_factor],
707 ))?;
708 w.write_all(&rnode_kiss::rnode_command(
709 rnode_kiss::CMD_CR,
710 &[sub.coding_rate],
711 ))?;
712 }
713
714 if let Some(st) = sub.st_alock {
716 let st_val = (st * 100.0) as u16;
717 let st_bytes = [(st_val >> 8) as u8, (st_val & 0xFF) as u8];
718 if multi {
719 w.write_all(&rnode_kiss::rnode_select_command(
720 index,
721 rnode_kiss::CMD_ST_ALOCK,
722 &st_bytes,
723 ))?;
724 } else {
725 w.write_all(&rnode_kiss::rnode_command(
726 rnode_kiss::CMD_ST_ALOCK,
727 &st_bytes,
728 ))?;
729 }
730 }
731 if let Some(lt) = sub.lt_alock {
732 let lt_val = (lt * 100.0) as u16;
733 let lt_bytes = [(lt_val >> 8) as u8, (lt_val & 0xFF) as u8];
734 if multi {
735 w.write_all(&rnode_kiss::rnode_select_command(
736 index,
737 rnode_kiss::CMD_LT_ALOCK,
738 <_bytes,
739 ))?;
740 } else {
741 w.write_all(&rnode_kiss::rnode_command(
742 rnode_kiss::CMD_LT_ALOCK,
743 <_bytes,
744 ))?;
745 }
746 }
747
748 if multi {
750 w.write_all(&rnode_kiss::rnode_select_command(
751 index,
752 rnode_kiss::CMD_RADIO_STATE,
753 &[rnode_kiss::RADIO_STATE_ON],
754 ))?;
755 } else {
756 w.write_all(&rnode_kiss::rnode_command(
757 rnode_kiss::CMD_RADIO_STATE,
758 &[rnode_kiss::RADIO_STATE_ON],
759 ))?;
760 }
761
762 Ok(())
763}
764
765fn process_flow_queue(
767 flow_state: &Arc<Mutex<SubFlowState>>,
768 writer: &Arc<Mutex<Transport>>,
769 index: u8,
770 multi: bool,
771) {
772 let mut state = lock_or_recover(flow_state, "rnode flow state");
773 if let Some(data) = state.queue.pop_front() {
774 state.ready = false;
775 drop(state);
776 let frame = if multi {
777 rnode_kiss::rnode_multi_data_frame(index, &data)
778 } else {
779 rnode_kiss::rnode_data_frame(index, &data)
780 };
781 let _ = lock_or_recover(writer, "rnode shared writer").write_all(&frame);
782 } else {
783 state.ready = true;
784 }
785}
786
787use super::{
790 ConfigSection, InterfaceConfigData, InterfaceFactory, StartContext, StartResult, SubInterface,
791};
792use rns_core::transport::types::InterfaceInfo;
793use std::collections::HashMap;
794
795pub struct RNodeFactory;
797
798impl InterfaceFactory for RNodeFactory {
799 fn type_name(&self) -> &str {
800 "RNodeInterface"
801 }
802
803 fn default_ifac_size(&self) -> usize {
804 8
805 }
806
807 fn parse_config(
808 &self,
809 name: &str,
810 id: InterfaceId,
811 params: &HashMap<String, String>,
812 ) -> Result<Box<dyn InterfaceConfigData>, String> {
813 let pre_opened_fd = params.get("fd").and_then(|v| v.parse::<i32>().ok());
814
815 let port = params
816 .get("port")
817 .cloned()
818 .or_else(|| pre_opened_fd.map(|_| "usb-bridge".to_string()))
819 .ok_or_else(|| "RNodeInterface requires 'port' or 'fd'".to_string())?;
820
821 let speed = params
822 .get("speed")
823 .and_then(|v| v.parse().ok())
824 .unwrap_or(115200u32);
825
826 let frequency = params
827 .get("frequency")
828 .and_then(|v| v.parse().ok())
829 .unwrap_or(868_000_000u32);
830
831 let bandwidth = params
832 .get("bandwidth")
833 .and_then(|v| v.parse().ok())
834 .unwrap_or(125_000u32);
835
836 let txpower = params
837 .get("txpower")
838 .and_then(|v| v.parse().ok())
839 .unwrap_or(7i8);
840
841 let spreading_factor = params
842 .get("spreadingfactor")
843 .or_else(|| params.get("spreading_factor"))
844 .and_then(|v| v.parse().ok())
845 .unwrap_or(8u8);
846
847 let coding_rate = params
848 .get("codingrate")
849 .or_else(|| params.get("coding_rate"))
850 .and_then(|v| v.parse().ok())
851 .unwrap_or(5u8);
852
853 let flow_control = params
854 .get("flow_control")
855 .and_then(|v| crate::config::parse_bool_pub(v))
856 .unwrap_or(false);
857
858 let st_alock = params.get("st_alock").and_then(|v| v.parse().ok());
859
860 let lt_alock = params.get("lt_alock").and_then(|v| v.parse().ok());
861
862 let id_interval = params.get("id_interval").and_then(|v| v.parse().ok());
863
864 let id_callsign = params.get("id_callsign").map(|v| v.as_bytes().to_vec());
865
866 let sub = RNodeSubConfig {
867 name: name.to_string(),
868 vport: 0,
869 outgoing: true,
870 frequency,
871 bandwidth,
872 txpower,
873 spreading_factor,
874 coding_rate,
875 flow_control,
876 st_alock,
877 lt_alock,
878 };
879
880 Ok(Box::new(RNodeConfig {
881 name: name.to_string(),
882 port,
883 speed,
884 subinterfaces: vec![sub],
885 multi: false,
886 id_interval,
887 id_callsign,
888 base_interface_id: id,
889 pre_opened_fd,
890 runtime: Arc::new(Mutex::new(RNodeRuntime {
891 sub: RNodeSubConfig {
892 name: name.to_string(),
893 vport: 0,
894 outgoing: true,
895 frequency,
896 bandwidth,
897 txpower,
898 spreading_factor,
899 coding_rate,
900 flow_control,
901 st_alock,
902 lt_alock,
903 },
904 writer: None,
905 })),
906 }))
907 }
908
909 fn start(
910 &self,
911 config: Box<dyn InterfaceConfigData>,
912 ctx: StartContext,
913 ) -> std::io::Result<StartResult> {
914 let rnode_config = *config.into_any().downcast::<RNodeConfig>().map_err(|_| {
915 std::io::Error::new(std::io::ErrorKind::InvalidData, "wrong config type")
916 })?;
917
918 let sub_bitrates: Vec<u64> = rnode_config
919 .subinterfaces
920 .iter()
921 .map(estimate_lora_bitrate_bps)
922 .collect();
923 let airtime_profiles: Vec<AirtimeProfile> = rnode_config
924 .subinterfaces
925 .iter()
926 .map(lora_airtime_profile)
927 .collect();
928
929 let pairs = start(rnode_config.clone(), ctx.tx)?;
930
931 let mut subs = Vec::with_capacity(pairs.len());
932 for (index, (sub_id, writer)) in pairs.into_iter().enumerate() {
933 let sub_name = rnode_config.subinterfaces[index].name.clone();
934
935 let info = InterfaceInfo {
936 id: sub_id,
937 name: sub_name,
938 mode: ctx.mode,
939 gravity: ctx.gravity,
940 recursive_prs: ctx.recursive_prs,
941 announces_from_internal: ctx.announces_from_internal,
942 announces_to_internal: ctx.announces_to_internal,
943 out_capable: rnode_config.subinterfaces[index].outgoing,
944 in_capable: true,
945 bitrate: sub_bitrates.get(index).copied(),
946 airtime_profile: airtime_profiles.get(index).copied(),
947 announce_rate_target: None,
948 announce_rate_grace: 0,
949 announce_rate_penalty: 0.0,
950 announce_cap: rns_core::constants::ANNOUNCE_CAP,
951 is_local_client: false,
952 wants_tunnel: false,
953 tunnel_id: None,
954 mtu: rns_core::constants::MTU as u32,
955 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
956 ia_freq: 0.0,
957 ip_freq: 0.0,
958 op_freq: 0.0,
959 op_samples: 0,
960 started: crate::time::now(),
961 };
962
963 subs.push(SubInterface {
964 id: sub_id,
965 info,
966 writer,
967 interface_type_name: if rnode_config.multi {
968 "RNodeMultiInterface"
969 } else {
970 "RNodeInterface"
971 }
972 .to_string(),
973 });
974 }
975
976 Ok(StartResult::Multi(subs))
977 }
978}
979
980pub struct RNodeMultiFactory;
982
983impl InterfaceFactory for RNodeMultiFactory {
984 fn type_name(&self) -> &str {
985 "RNodeMultiInterface"
986 }
987
988 fn default_ifac_size(&self) -> usize {
989 8
990 }
991
992 fn parse_config(
993 &self,
994 name: &str,
995 id: InterfaceId,
996 params: &HashMap<String, String>,
997 ) -> Result<Box<dyn InterfaceConfigData>, String> {
998 self.parse_config_section(
999 name,
1000 id,
1001 ConfigSection {
1002 params,
1003 children: &[],
1004 },
1005 )
1006 }
1007
1008 fn parse_config_section(
1009 &self,
1010 name: &str,
1011 id: InterfaceId,
1012 section: ConfigSection<'_>,
1013 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1014 let base = RNodeFactory.parse_config(name, id, section.params)?;
1015 let mut config = *base
1016 .into_any()
1017 .downcast::<RNodeConfig>()
1018 .map_err(|_| "wrong RNode config type".to_string())?;
1019
1020 let mut subinterfaces = Vec::new();
1021 for child in section.children {
1022 let enabled = child
1023 .params
1024 .get("interface_enabled")
1025 .or_else(|| child.params.get("enabled"))
1026 .and_then(|value| crate::config::parse_bool_pub(value))
1027 .unwrap_or(true);
1028 if !enabled {
1029 continue;
1030 }
1031 if subinterfaces.len() >= 11 {
1032 return Err("RNodeMultiInterface supports at most 11 enabled subinterfaces".into());
1033 }
1034 let vport = child
1035 .params
1036 .get("vport")
1037 .ok_or_else(|| format!("subinterface '{}' requires 'vport'", child.name))?
1038 .parse::<u8>()
1039 .map_err(|_| format!("invalid RNode virtual port for '{}'", child.name))?;
1040 if vport > 10
1041 || subinterfaces
1042 .iter()
1043 .any(|sub: &RNodeSubConfig| sub.vport == vport)
1044 {
1045 return Err(format!("invalid or duplicate RNode virtual port {}", vport));
1046 }
1047 let parse_u32 = |key: &str, default| {
1048 child
1049 .params
1050 .get(key)
1051 .and_then(|v| v.parse().ok())
1052 .unwrap_or(default)
1053 };
1054 let parse_u8_alias = |first: &str, second: &str, default| {
1055 child
1056 .params
1057 .get(first)
1058 .or_else(|| child.params.get(second))
1059 .and_then(|v| v.parse().ok())
1060 .unwrap_or(default)
1061 };
1062 let sub = RNodeSubConfig {
1063 name: child.name.clone(),
1064 vport,
1065 outgoing: child
1066 .params
1067 .get("outgoing")
1068 .and_then(|v| crate::config::parse_bool_pub(v))
1069 .unwrap_or(true),
1070 frequency: parse_u32("frequency", 868_000_000),
1071 bandwidth: parse_u32("bandwidth", 125_000),
1072 txpower: child
1073 .params
1074 .get("txpower")
1075 .and_then(|v| v.parse().ok())
1076 .unwrap_or(7),
1077 spreading_factor: parse_u8_alias("spreadingfactor", "spreading_factor", 8),
1078 coding_rate: parse_u8_alias("codingrate", "coding_rate", 5),
1079 flow_control: child
1080 .params
1081 .get("flow_control")
1082 .and_then(|v| crate::config::parse_bool_pub(v))
1083 .unwrap_or(false),
1084 st_alock: child
1085 .params
1086 .get("airtime_limit_short")
1087 .or_else(|| child.params.get("st_alock"))
1088 .and_then(|v| v.parse().ok()),
1089 lt_alock: child
1090 .params
1091 .get("airtime_limit_long")
1092 .or_else(|| child.params.get("lt_alock"))
1093 .and_then(|v| v.parse().ok()),
1094 };
1095 if let Some(error) = validate_sub_config(&sub) {
1096 return Err(error);
1097 }
1098 subinterfaces.push(sub);
1099 }
1100 if subinterfaces.is_empty() {
1101 return Err("RNodeMultiInterface requires at least one enabled subinterface".into());
1102 }
1103 config.multi = true;
1104 config.subinterfaces = subinterfaces;
1105 *lock_or_recover(&config.runtime, "rnode runtime") = RNodeRuntime::from_config(&config);
1106 Ok(Box::new(config))
1107 }
1108
1109 fn start(
1110 &self,
1111 config: Box<dyn InterfaceConfigData>,
1112 ctx: StartContext,
1113 ) -> io::Result<StartResult> {
1114 RNodeFactory.start(config, ctx)
1115 }
1116}
1117
1118pub(crate) fn rnode_runtime_handle_from_config(config: &RNodeConfig) -> RNodeRuntimeConfigHandle {
1119 RNodeRuntimeConfigHandle {
1120 interface_name: config.name.clone(),
1121 runtime: Arc::clone(&config.runtime),
1122 startup: RNodeRuntime::from_config(config),
1123 }
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129 use crate::event;
1130 use crate::kiss;
1131 use crate::serial::open_pty_pair;
1132 use std::os::unix::io::{AsRawFd, FromRawFd};
1133 use std::path::PathBuf;
1134 use std::sync::mpsc::RecvTimeoutError;
1135 use tempfile::tempdir;
1136 fn poll_read(fd: i32, timeout_ms: i32) -> bool {
1138 let mut pfd = libc::pollfd {
1139 fd,
1140 events: libc::POLLIN,
1141 revents: 0,
1142 };
1143 let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
1144 ret > 0
1145 }
1146
1147 fn read_available(file: &mut Transport) -> Vec<u8> {
1149 let mut all = Vec::new();
1150 let mut buf = [0u8; 4096];
1151 while poll_read(file.as_raw_fd(), 100) {
1152 match file.read(&mut buf) {
1153 Ok(n) if n > 0 => all.extend_from_slice(&buf[..n]),
1154 _ => break,
1155 }
1156 }
1157 all
1158 }
1159
1160 fn slave_tty_path(fd: i32) -> PathBuf {
1161 let mut buf = [0u8; 256];
1162 let rc = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr().cast(), buf.len()) };
1163 assert_eq!(rc, 0, "ttyname_r failed for fd {}", fd);
1164 let nul = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
1165 PathBuf::from(std::str::from_utf8(&buf[..nul]).unwrap())
1166 }
1167
1168 unsafe fn transport_from_raw_fd(fd: i32) -> Transport {
1169 Transport::Serial(std::fs::File::from_raw_fd(fd))
1170 }
1171
1172 fn wait_for_interface_event<F>(
1173 rx: &std::sync::mpsc::Receiver<Event>,
1174 timeout: Duration,
1175 predicate: F,
1176 ) -> Event
1177 where
1178 F: Fn(&Event) -> bool,
1179 {
1180 let deadline = std::time::Instant::now() + timeout;
1181 loop {
1182 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
1183 match rx.recv_timeout(remaining) {
1184 Ok(event) if predicate(&event) => return event,
1185 Ok(_) => continue,
1186 Err(RecvTimeoutError::Timeout) => panic!("timed out waiting for interface event"),
1187 Err(RecvTimeoutError::Disconnected) => panic!("event channel disconnected"),
1188 }
1189 }
1190 }
1191
1192 fn mock_respond_detect(master: &mut Transport) {
1194 master
1196 .write_all(&rnode_kiss::rnode_command(
1197 rnode_kiss::CMD_DETECT,
1198 &[rnode_kiss::DETECT_RESP],
1199 ))
1200 .unwrap();
1201 master
1203 .write_all(&rnode_kiss::rnode_command(
1204 rnode_kiss::CMD_FW_VERSION,
1205 &[0x01, 0x4A],
1206 ))
1207 .unwrap();
1208 master
1210 .write_all(&rnode_kiss::rnode_command(
1211 rnode_kiss::CMD_PLATFORM,
1212 &[0x80],
1213 ))
1214 .unwrap();
1215 master
1217 .write_all(&rnode_kiss::rnode_command(rnode_kiss::CMD_MCU, &[0x01]))
1218 .unwrap();
1219 master.flush().unwrap();
1220 }
1221
1222 #[test]
1223 fn rnode_detect_over_pty() {
1224 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1226 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1227 let mut slave = unsafe { transport_from_raw_fd(slave_fd) };
1228
1229 mock_respond_detect(&mut master);
1231
1232 let mut decoder = rnode_kiss::RNodeDecoder::new();
1233 let mut events = Vec::new();
1234 let deadline = std::time::Instant::now() + Duration::from_secs(2);
1235
1236 while std::time::Instant::now() < deadline {
1237 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
1238 let timeout_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
1239 if !poll_read(slave.as_raw_fd(), timeout_ms) {
1240 break;
1241 }
1242
1243 let chunk = read_available(&mut slave);
1244 if chunk.is_empty() {
1245 continue;
1246 }
1247
1248 events.extend(decoder.feed(&chunk));
1249
1250 let saw_detect = events
1251 .iter()
1252 .any(|e| matches!(e, rnode_kiss::RNodeEvent::Detected(true)));
1253 let saw_firmware = events
1254 .iter()
1255 .any(|e| matches!(e, rnode_kiss::RNodeEvent::FirmwareVersion { .. }));
1256 if saw_detect && saw_firmware {
1257 break;
1258 }
1259 }
1260
1261 assert!(
1262 events
1263 .iter()
1264 .any(|e| matches!(e, rnode_kiss::RNodeEvent::Detected(true))),
1265 "should detect device"
1266 );
1267 assert!(
1268 events
1269 .iter()
1270 .any(|e| matches!(e, rnode_kiss::RNodeEvent::FirmwareVersion { .. })),
1271 "should get firmware version"
1272 );
1273 }
1274
1275 #[test]
1276 fn rnode_configure_commands() {
1277 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1278 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1279 let writer_file = unsafe { transport_from_raw_fd(libc::dup(slave_fd)) };
1280 let writer = Arc::new(Mutex::new(writer_file));
1281
1282 let sub = RNodeSubConfig {
1283 name: "test".into(),
1284 vport: 0,
1285 outgoing: true,
1286 frequency: 868_000_000,
1287 bandwidth: 125_000,
1288 txpower: 7,
1289 spreading_factor: 8,
1290 coding_rate: 5,
1291 flow_control: false,
1292 st_alock: None,
1293 lt_alock: None,
1294 };
1295
1296 configure_subinterface(&writer, 0, &sub, false).unwrap();
1297
1298 let data = read_available(&mut master);
1300
1301 assert!(
1303 data.windows(2)
1304 .any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_FREQUENCY),
1305 "should contain FREQUENCY command"
1306 );
1307 assert!(
1309 data.windows(2)
1310 .any(|w| w[0] == kiss::FEND && w[1] == rnode_kiss::CMD_BANDWIDTH),
1311 "should contain BANDWIDTH command"
1312 );
1313 assert!(
1315 data.windows(3).any(|w| w[0] == kiss::FEND
1316 && w[1] == rnode_kiss::CMD_RADIO_STATE
1317 && w[2] == rnode_kiss::RADIO_STATE_ON),
1318 "should contain RADIO_STATE ON command"
1319 );
1320
1321 unsafe { libc::close(slave_fd) };
1322 }
1323
1324 #[test]
1325 fn rnode_data_roundtrip() {
1326 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1327 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1328 let slave = unsafe { transport_from_raw_fd(slave_fd) };
1329
1330 let payload = vec![0x01, 0x02, 0x03, 0x04, 0x05];
1332 let frame = rnode_kiss::rnode_data_frame(0, &payload);
1333 master.write_all(&frame).unwrap();
1334 master.flush().unwrap();
1335
1336 assert!(poll_read(slave.as_raw_fd(), 2000));
1338 let mut decoder = rnode_kiss::RNodeDecoder::new();
1339 let mut buf = [0u8; 4096];
1340 let mut slave_file = slave;
1341 let n = slave_file.read(&mut buf).unwrap();
1342 let events = decoder.feed(&buf[..n]);
1343
1344 assert_eq!(events.len(), 1);
1345 match &events[0] {
1346 rnode_kiss::RNodeEvent::DataFrame { index, data } => {
1347 assert_eq!(*index, 0);
1348 assert_eq!(data, &payload);
1349 }
1350 other => panic!("expected DataFrame, got {:?}", other),
1351 }
1352 }
1353
1354 #[test]
1355 fn rnode_flow_control() {
1356 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1357 let writer_file = unsafe { transport_from_raw_fd(slave_fd) };
1358 let shared_writer = Arc::new(Mutex::new(writer_file));
1359
1360 let flow_state = Arc::new(Mutex::new(SubFlowState {
1361 ready: true,
1362 queue: std::collections::VecDeque::new(),
1363 }));
1364
1365 let mut sub_writer = RNodeSubWriter {
1366 writer: shared_writer.clone(),
1367 index: 0,
1368 flow_control: true,
1369 flow_state: flow_state.clone(),
1370 multi: false,
1371 };
1372
1373 sub_writer.send_frame(b"hello").unwrap();
1375 assert!(!flow_state.lock().unwrap().ready);
1376
1377 sub_writer.send_frame(b"world").unwrap();
1379 assert_eq!(flow_state.lock().unwrap().queue.len(), 1);
1380
1381 process_flow_queue(&flow_state, &shared_writer, 0, false);
1383 assert_eq!(flow_state.lock().unwrap().queue.len(), 0);
1384 assert!(!flow_state.lock().unwrap().ready); process_flow_queue(&flow_state, &shared_writer, 0, false);
1388 assert!(flow_state.lock().unwrap().ready);
1389
1390 unsafe { libc::close(master_fd) };
1391 }
1392
1393 #[test]
1394 fn rnode_reset_clears_pending_rx_metadata() {
1395 let mut last_rssi = Some(-101);
1396 let mut last_snr = Some(7.25);
1397
1398 clear_pending_rx_metadata(&mut last_rssi, &mut last_snr);
1399
1400 assert_eq!(last_rssi, None);
1401 assert_eq!(last_snr, None);
1402 }
1403
1404 #[test]
1405 fn rnode_sub_writer_format() {
1406 let (master_fd, slave_fd) = open_pty_pair().unwrap();
1407 let mut master = unsafe { transport_from_raw_fd(master_fd) };
1408 let writer_file = unsafe { transport_from_raw_fd(slave_fd) };
1409 let shared_writer = Arc::new(Mutex::new(writer_file));
1410
1411 let flow_state = Arc::new(Mutex::new(SubFlowState {
1412 ready: true,
1413 queue: std::collections::VecDeque::new(),
1414 }));
1415
1416 let mut sub_writer = RNodeSubWriter {
1417 writer: shared_writer,
1418 index: 1, flow_control: false,
1420 flow_state,
1421 multi: false,
1422 };
1423
1424 let payload = vec![0xAA, 0xBB, 0xCC];
1425 sub_writer.send_frame(&payload).unwrap();
1426
1427 assert!(poll_read(master.as_raw_fd(), 2000));
1429 let mut buf = [0u8; 256];
1430 let n = master.read(&mut buf).unwrap();
1431
1432 assert_eq!(buf[0], kiss::FEND);
1434 assert_eq!(buf[1], 0x10); assert_eq!(buf[n - 1], kiss::FEND);
1436 }
1437
1438 #[test]
1439 fn rnode_multi_sub_routing() {
1440 let mut decoder = rnode_kiss::RNodeDecoder::new();
1442
1443 let payload0 = vec![0x01, 0x02];
1444 let frame0 = rnode_kiss::rnode_data_frame(0, &payload0);
1445 let events0 = decoder.feed(&frame0);
1446 assert_eq!(events0.len(), 1);
1447 assert_eq!(
1448 events0[0],
1449 rnode_kiss::RNodeEvent::DataFrame {
1450 index: 0,
1451 data: payload0
1452 }
1453 );
1454
1455 let payload1 = vec![0x03, 0x04];
1456 let frame1 = rnode_kiss::rnode_data_frame(1, &payload1);
1457 let events1 = decoder.feed(&frame1);
1458 assert_eq!(events1.len(), 1);
1459 assert_eq!(
1460 events1[0],
1461 rnode_kiss::RNodeEvent::DataFrame {
1462 index: 1,
1463 data: payload1
1464 }
1465 );
1466 }
1467
1468 #[test]
1469 fn rnode_error_handling() {
1470 let mut decoder = rnode_kiss::RNodeDecoder::new();
1478 let frame = rnode_kiss::rnode_command(rnode_kiss::CMD_ERROR, &[0x02]);
1479 let events = decoder.feed(&frame);
1480 assert_eq!(events.len(), 1);
1481 assert_eq!(
1483 events[0],
1484 rnode_kiss::RNodeEvent::DataFrame {
1485 index: 5,
1486 data: vec![0x02]
1487 }
1488 );
1489 }
1490
1491 #[test]
1492 fn rnode_config_validation() {
1493 let good = RNodeSubConfig {
1494 name: "test".into(),
1495 vport: 0,
1496 outgoing: true,
1497 frequency: 868_000_000,
1498 bandwidth: 125_000,
1499 txpower: 7,
1500 spreading_factor: 8,
1501 coding_rate: 5,
1502 flow_control: false,
1503 st_alock: None,
1504 lt_alock: None,
1505 };
1506 assert!(validate_sub_config(&good).is_none());
1507
1508 let mut bad = good.clone();
1510 bad.frequency = 100;
1511 assert!(validate_sub_config(&bad).is_some());
1512
1513 bad = good.clone();
1515 bad.spreading_factor = 13;
1516 assert!(validate_sub_config(&bad).is_some());
1517
1518 bad = good.clone();
1520 bad.coding_rate = 9;
1521 assert!(validate_sub_config(&bad).is_some());
1522
1523 bad = good.clone();
1525 bad.bandwidth = 5;
1526 assert!(validate_sub_config(&bad).is_some());
1527
1528 bad = good.clone();
1530 bad.txpower = 50;
1531 assert!(validate_sub_config(&bad).is_some());
1532 }
1533
1534 #[test]
1535 fn multi_factory_preserves_nonsequential_virtual_ports_and_disabled_children() {
1536 let parent = HashMap::from([
1537 ("port".to_string(), "/dev/ttyUSB0".to_string()),
1538 ("speed".to_string(), "115200".to_string()),
1539 ]);
1540 let children = vec![
1541 crate::config::ParsedSubinterface {
1542 name: "disabled".into(),
1543 params: HashMap::from([
1544 ("interface_enabled".into(), "no".into()),
1545 ("vport".into(), "2".into()),
1546 ]),
1547 },
1548 crate::config::ParsedSubinterface {
1549 name: "high".into(),
1550 params: HashMap::from([
1551 ("interface_enabled".into(), "yes".into()),
1552 ("vport".into(), "7".into()),
1553 ("frequency".into(), "915000000".into()),
1554 ("airtime_limit_short".into(), "12.5".into()),
1555 ("outgoing".into(), "no".into()),
1556 ]),
1557 },
1558 crate::config::ParsedSubinterface {
1559 name: "low".into(),
1560 params: HashMap::from([
1561 ("enabled".into(), "yes".into()),
1562 ("vport".into(), "1".into()),
1563 ]),
1564 },
1565 ];
1566 let parsed = RNodeMultiFactory
1567 .parse_config_section(
1568 "multi",
1569 InterfaceId(40),
1570 ConfigSection {
1571 params: &parent,
1572 children: &children,
1573 },
1574 )
1575 .unwrap();
1576 let config = parsed.into_any().downcast::<RNodeConfig>().unwrap();
1577 assert_eq!(config.subinterfaces.len(), 2);
1578 assert_eq!(config.subinterfaces[0].vport, 7);
1579 assert_eq!(config.subinterfaces[1].vport, 1);
1580 assert!(!config.subinterfaces[0].outgoing);
1581 assert_eq!(config.subinterfaces[0].st_alock, Some(12.5));
1582 }
1583
1584 #[test]
1585 fn rnode_lora_bitrate_estimate_uses_radio_params() {
1586 let mut sub = RNodeSubConfig {
1587 name: "test".into(),
1588 vport: 0,
1589 outgoing: true,
1590 frequency: 868_000_000,
1591 bandwidth: 125_000,
1592 txpower: 7,
1593 spreading_factor: 8,
1594 coding_rate: 5,
1595 flow_control: false,
1596 st_alock: None,
1597 lt_alock: None,
1598 };
1599
1600 assert_eq!(estimate_lora_bitrate_bps(&sub), 3125);
1601
1602 sub.spreading_factor = 12;
1603 assert_eq!(estimate_lora_bitrate_bps(&sub), 293);
1604
1605 sub.bandwidth = 250_000;
1606 assert_eq!(estimate_lora_bitrate_bps(&sub), 586);
1607 }
1608
1609 #[test]
1610 fn rnode_lora_airtime_profile_uses_radio_params() {
1611 let sub = RNodeSubConfig {
1612 name: "test".into(),
1613 vport: 0,
1614 outgoing: true,
1615 frequency: 868_000_000,
1616 bandwidth: 125_000,
1617 txpower: 7,
1618 spreading_factor: 8,
1619 coding_rate: 5,
1620 flow_control: false,
1621 st_alock: None,
1622 lt_alock: None,
1623 };
1624
1625 let profile = lora_airtime_profile(&sub);
1626 assert!((profile.transmit_time_secs(100) - 0.307712).abs() < 0.000001);
1627 assert_eq!(
1628 profile,
1629 AirtimeProfile::Lora {
1630 bandwidth: 125_000,
1631 spreading_factor: 8,
1632 coding_rate: 5,
1633 preamble_symbols: LORA_PREAMBLE_SYMBOLS,
1634 explicit_header: LORA_EXPLICIT_HEADER,
1635 crc: LORA_CRC,
1636 }
1637 );
1638 }
1639
1640 #[test]
1641 fn rnode_reconnects_after_serial_disconnect() {
1642 let tempdir = tempdir().unwrap();
1643 let port_path = tempdir.path().join("rnode-port");
1644
1645 let (master1_fd, slave1_fd) = open_pty_pair().unwrap();
1646 let slave1_path = slave_tty_path(slave1_fd);
1647 std::os::unix::fs::symlink(&slave1_path, &port_path).unwrap();
1648
1649 let mut master1 = unsafe { transport_from_raw_fd(master1_fd) };
1650 let slave1 = unsafe { transport_from_raw_fd(slave1_fd) };
1651
1652 let (tx, rx) = event::channel();
1653 let sub = RNodeSubConfig {
1654 name: "test-rnode".into(),
1655 vport: 0,
1656 outgoing: true,
1657 frequency: 868_000_000,
1658 bandwidth: 125_000,
1659 txpower: 7,
1660 spreading_factor: 8,
1661 coding_rate: 5,
1662 flow_control: false,
1663 st_alock: None,
1664 lt_alock: None,
1665 };
1666 let mut config = RNodeConfig {
1667 name: "test-rnode".into(),
1668 port: port_path.display().to_string(),
1669 speed: 115200,
1670 subinterfaces: vec![sub],
1671 multi: false,
1672 id_interval: None,
1673 id_callsign: None,
1674 base_interface_id: InterfaceId(41),
1675 pre_opened_fd: None,
1676 runtime: Arc::new(Mutex::new(RNodeRuntime {
1677 sub: RNodeSubConfig {
1678 name: String::new(),
1679 vport: 0,
1680 outgoing: true,
1681 frequency: 868_000_000,
1682 bandwidth: 125_000,
1683 txpower: 7,
1684 spreading_factor: 8,
1685 coding_rate: 5,
1686 flow_control: false,
1687 st_alock: None,
1688 lt_alock: None,
1689 },
1690 writer: None,
1691 })),
1692 };
1693 config.runtime = Arc::new(Mutex::new(RNodeRuntime::from_config(&config)));
1694
1695 let _writers = start(config, tx).unwrap();
1696
1697 thread::sleep(Duration::from_secs(3));
1698 mock_respond_detect(&mut master1);
1699 let up = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
1700 matches!(event, Event::InterfaceUp(InterfaceId(41), _, _))
1701 });
1702 assert!(matches!(
1703 up,
1704 Event::InterfaceUp(InterfaceId(41), None, None)
1705 ));
1706
1707 drop(master1);
1708 drop(slave1);
1709
1710 let down = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
1711 matches!(event, Event::InterfaceDown(InterfaceId(41)))
1712 });
1713 assert!(matches!(down, Event::InterfaceDown(InterfaceId(41))));
1714
1715 let (master2_fd, slave2_fd) = open_pty_pair().unwrap();
1716 let slave2_path = slave_tty_path(slave2_fd);
1717 std::fs::remove_file(&port_path).unwrap();
1718 std::os::unix::fs::symlink(&slave2_path, &port_path).unwrap();
1719
1720 let mut master2 = unsafe { transport_from_raw_fd(master2_fd) };
1721 let _slave2 = unsafe { transport_from_raw_fd(slave2_fd) };
1722
1723 thread::sleep(Duration::from_secs(3));
1724 mock_respond_detect(&mut master2);
1725 let up = wait_for_interface_event(&rx, Duration::from_secs(4), |event| {
1726 matches!(event, Event::InterfaceUp(InterfaceId(41), _, _))
1727 });
1728 assert!(matches!(
1729 up,
1730 Event::InterfaceUp(InterfaceId(41), Some(_), None)
1731 ));
1732 }
1733}