Skip to main content

vcan_loopback/
vcan_loopback.rs

1//! End-to-end SDO over a Linux `vcan0` virtual CAN bus — the first time the
2//! SocketCAN transport actually executes on a bus (no hardware needed).
3//!
4//! A server thread exposes an object dictionary on `vcan0`; the main thread is
5//! a client that reads and writes it, exercising both expedited and segmented
6//! transfers. Because SocketCAN delivers every frame to all sockets bound to
7//! the interface, the two ends simply share `vcan0`.
8//!
9//! Set up the interface first (see `tools/vcan_setup.sh`), then:
10//!
11//! ```text
12//! cargo run -p canopen-host --example vcan_loopback
13//! ```
14
15fn main() {
16    #[cfg(target_os = "linux")]
17    {
18        if let Err(e) = linux::run() {
19            eprintln!("vcan_loopback FAILED: {e}");
20            std::process::exit(1);
21        }
22    }
23    #[cfg(not(target_os = "linux"))]
24    {
25        eprintln!("This example requires Linux SocketCAN (vcan0); see tools/vcan_setup.sh.");
26    }
27}
28
29#[cfg(target_os = "linux")]
30mod linux {
31    use std::error::Error;
32    use std::sync::mpsc;
33    use std::thread;
34    use std::time::Duration;
35
36    use canopen_host::transport::SocketCan;
37    use canopen_rs::sdo::SdoServer;
38    use canopen_rs::{Address, DataType, Entry, NodeId, ObjectDictionary, Value};
39
40    const IFACE: &str = "vcan0";
41
42    pub fn run() -> Result<(), Box<dyn Error>> {
43        let node = NodeId::new(0x10)?;
44
45        // --- Server node: serve an object dictionary on vcan0 in a thread. ---
46        let (ready_tx, ready_rx) = mpsc::channel::<()>();
47        thread::spawn(move || {
48            if let Err(e) = serve(node, ready_tx) {
49                eprintln!("server thread error: {e}");
50            }
51        });
52        // Wait until the server socket is open and listening.
53        ready_rx.recv().map_err(|_| "server failed to start")?;
54
55        // --- Client: talk to the node over vcan0. ---
56        let bus = SocketCan::open(IFACE)?;
57        bus.set_read_timeout(Duration::from_secs(2))?;
58
59        // Expedited read (4-byte object).
60        let device_type = bus.sdo_read(node, Address::new(0x1000, 0), DataType::Unsigned32)?;
61        println!("read  0x1000 device type      = {device_type:?}");
62        assert_eq!(device_type, Value::Unsigned32(0x0004_0192));
63
64        // Expedited write then read-back (2-byte object).
65        bus.sdo_write(node, Address::new(0x1017, 0), Value::Unsigned16(2500))?;
66        let heartbeat = bus.sdo_read(node, Address::new(0x1017, 0), DataType::Unsigned16)?;
67        println!("write 0x1017 heartbeat -> read = {heartbeat:?}");
68        assert_eq!(heartbeat, Value::Unsigned16(2500));
69
70        // 8-byte object forces SEGMENTED transfer over the real bus.
71        let big = Value::Unsigned64(0x0102_0304_0506_0708);
72        bus.sdo_write(node, Address::new(0x2000, 0), big)?;
73        let back = bus.sdo_read(node, Address::new(0x2000, 0), DataType::Unsigned64)?;
74        println!("write 0x2000 (segmented) -> read = {back:?}");
75        assert_eq!(back, big);
76
77        println!("\nvcan0 loopback OK — expedited and segmented SDO round-trips succeeded.");
78        Ok(())
79    }
80
81    /// A minimal device node: build an OD and answer SDO requests addressed to
82    /// this node until the bus goes quiet.
83    fn serve(node: NodeId, ready: mpsc::Sender<()>) -> Result<(), Box<dyn Error>> {
84        let bus = SocketCan::open(IFACE)?;
85        bus.set_read_timeout(Duration::from_secs(3))?;
86
87        let mut od = ObjectDictionary::<8>::new();
88        od.insert(
89            Address::new(0x1000, 0),
90            Entry::constant(Value::Unsigned32(0x0004_0192)),
91        )?;
92        od.insert(Address::new(0x1017, 0), Entry::rw(Value::Unsigned16(1000)))?;
93        od.insert(Address::new(0x2000, 0), Entry::rw(Value::Unsigned64(0)))?;
94
95        let mut server = SdoServer::new(node);
96        ready.send(()).map_err(|_| "client went away")?;
97
98        // Serve until a read times out (the client is done and the bus is idle).
99        while let Ok(frame) = bus.recv() {
100            if frame.cob_id != server.request_cob_id() {
101                continue;
102            }
103            if let Some(response) = server.handle(&mut od, frame.payload()) {
104                bus.send(server.response_cob_id(), &response)?;
105            }
106        }
107        Ok(())
108    }
109}