Skip to main content

discover/
discover.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! Discovers MX Remote devices and prints what they report.
5//!
6//! The shape here is the one most programs want: two handler methods that say
7//! only which device or bay moved, and a snapshot read back for the detail.
8//! Both run on the library's receive thread, so what they do is kept short.
9//!
10//! Usage: `cargo run --example discover [interface-address]`
11
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::{Arc, OnceLock};
14use std::time::Duration;
15
16use mx_remote::{BayUid, Config, DeviceUid, EventHandler, Remote};
17
18/// The client the snapshots are read from.
19///
20/// A handler is handed to the client that will call it, so it cannot hold one
21/// at the time it is built. It is filled in before the client is started,
22/// which is before anything can call back.
23static CLIENT: OnceLock<Arc<Remote>> = OnceLock::new();
24
25struct Printer;
26
27impl EventHandler for Printer {
28    fn on_device_update(&self, device: DeviceUid) {
29        let Some(remote) = CLIENT.get() else { return };
30        let Some(info) = remote.device(device) else {
31            return;
32        };
33        println!(
34            "  {device}  {:<16} {:<12} {:<16} protocol {:#04x}, {} bays{}",
35            info.model,
36            info.serial,
37            info.name,
38            info.supported_protocol,
39            info.bays.len(),
40            if info.online { "" } else { " (offline)" },
41        );
42    }
43
44    fn on_bay_update(&self, bay: BayUid) {
45        let Some(remote) = CLIENT.get() else { return };
46        let Some(info) = remote.bay(bay) else { return };
47        println!(
48            "    bay {} {:<16} {}",
49            bay.port,
50            info.user_name,
51            match info.signal_detected {
52                Some(true) => "signal",
53                _ => "no signal",
54            },
55        );
56    }
57}
58
59fn main() -> std::io::Result<()> {
60    let mut config = Config::default();
61    config.name = Some("discover".to_owned());
62    if let Some(address) = std::env::args().nth(1) {
63        config.local_ip = Some(address.parse().map_err(|_| {
64            std::io::Error::new(std::io::ErrorKind::InvalidInput, "not an IPv4 address")
65        })?);
66    }
67
68    let remote = Arc::new(Remote::new(config, Arc::new(Printer))?);
69    let _ = CLIENT.set(Arc::clone(&remote));
70    remote.start()?;
71
72    if let Some(target) = remote.target() {
73        println!("listening, sending to {target}. Ctrl-C to stop.");
74    }
75
76    let running = Arc::new(AtomicBool::new(true));
77    let stop = Arc::clone(&running);
78    ctrl_c(move || stop.store(false, Ordering::Relaxed));
79    while running.load(Ordering::Relaxed) {
80        std::thread::sleep(Duration::from_millis(200));
81    }
82
83    let devices = remote.devices();
84    println!("\n{} device(s):", devices.len());
85    for device in devices {
86        Printer.on_device_update(device);
87    }
88
89    remote.close();
90    Ok(())
91}
92
93/// Runs `f` on the first Ctrl-C.
94///
95/// The library takes no signal handler of its own - it is meant to link into a
96/// host program that has its own - so an example that wants one installs it.
97fn ctrl_c(f: impl FnOnce() + Send + 'static) {
98    static HANDLER: OnceLock<()> = OnceLock::new();
99    let _ = HANDLER.set(());
100    std::thread::spawn(move || {
101        let mut line = String::new();
102        // Reading to end of input is the portable stand-in for a signal
103        // handler: Ctrl-C closes the terminal's read on most shells, and
104        // Ctrl-D ends it everywhere.
105        let _ = std::io::stdin().read_line(&mut line);
106        f();
107    });
108}