fn main() {
#[cfg(target_os = "linux")]
{
if let Err(e) = linux::run() {
eprintln!("vcan_loopback FAILED: {e}");
std::process::exit(1);
}
}
#[cfg(not(target_os = "linux"))]
{
eprintln!("This example requires Linux SocketCAN (vcan0); see tools/vcan_setup.sh.");
}
}
#[cfg(target_os = "linux")]
mod linux {
use std::error::Error;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use canopen_host::transport::{Received, SocketCan};
use canopen_rs::node::{Node, MAX_PDO_MAPPING};
use canopen_rs::sync::SYNC_COB_ID;
use canopen_rs::{
Address, DataType, Entry, MappingEntry, NmtCommand, NodeId, ObjectDictionary, PdoMapping,
TransmissionType, Value,
};
const IFACE: &str = "vcan0";
pub fn run() -> Result<(), Box<dyn Error>> {
let node = NodeId::new(0x10)?;
let (ready_tx, ready_rx) = mpsc::channel::<()>();
thread::spawn(move || {
if let Err(e) = serve(node, ready_tx) {
eprintln!("server thread error: {e}");
}
});
ready_rx.recv().map_err(|_| "server failed to start")?;
let bus = SocketCan::open(IFACE)?;
bus.set_read_timeout(Duration::from_secs(2))?;
let device_type = bus.sdo_read(node, Address::new(0x1000, 0), DataType::Unsigned32)?;
println!("read 0x1000 device type = {device_type:?}");
assert_eq!(device_type, Value::Unsigned32(0x0004_0192));
bus.sdo_write(node, Address::new(0x1017, 0), Value::Unsigned16(2500))?;
let heartbeat = bus.sdo_read(node, Address::new(0x1017, 0), DataType::Unsigned16)?;
println!("write 0x1017 heartbeat -> read = {heartbeat:?}");
assert_eq!(heartbeat, Value::Unsigned16(2500));
let big = Value::Unsigned64(0x0102_0304_0506_0708);
bus.sdo_write(node, Address::new(0x2000, 0), big)?;
let back = bus.sdo_read(node, Address::new(0x2000, 0), DataType::Unsigned64)?;
println!("write 0x2000 (segmented) -> read = {back:?}");
assert_eq!(back, big);
let rpdo1 = 0x200 + node.raw() as u16;
let tpdo1 = 0x180 + node.raw() as u16;
bus.send_nmt(NmtCommand::StartRemoteNode, node)?; bus.send(rpdo1, &[0xCD, 0xAB])?; bus.send(SYNC_COB_ID, &[])?;
let tpdo = recv_cob(&bus, tpdo1)?;
println!("RPDO in 0xABCD -> SYNC -> TPDO out = {:02X?}", tpdo.data());
assert_eq!(tpdo.data(), &[0xCD, 0xAB]);
println!(
"\nvcan0 loopback OK — SDO (expedited + segmented), NMT, and PDO all round-tripped."
);
Ok(())
}
fn recv_cob(bus: &SocketCan, cob_id: u16) -> Result<Received, Box<dyn Error>> {
loop {
let frame = bus.recv()?;
if frame.cob_id == cob_id {
return Ok(frame);
}
}
}
fn serve(node_id: NodeId, ready: mpsc::Sender<()>) -> Result<(), Box<dyn Error>> {
let bus = SocketCan::open(IFACE)?;
bus.set_read_timeout(Duration::from_secs(3))?;
let mut od = ObjectDictionary::<8>::new();
od.insert(
Address::new(0x1000, 0),
Entry::constant(Value::Unsigned32(0x0004_0192)),
)?;
od.insert(Address::new(0x1017, 0), Entry::rw(Value::Unsigned16(1000)))?;
od.insert(Address::new(0x2000, 0), Entry::rw(Value::Unsigned64(0)))?;
od.insert(Address::new(0x6000, 1), Entry::rw(Value::Unsigned16(0)))?;
let mut node = Node::new(node_id, od);
node.add_rpdo(0x200 + node_id.raw() as u16, mapping(0x6000, 1, 16))?;
node.add_tpdo(
0x180 + node_id.raw() as u16,
mapping(0x6000, 1, 16),
TransmissionType::SynchronousAcyclic,
)?;
node.boot(); ready.send(()).map_err(|_| "client went away")?;
while let Ok(frame) = bus.recv() {
if let Some(tx) = node.on_frame(frame.cob_id, frame.data()) {
bus.send(tx.cob_id, tx.data())?;
}
if frame.cob_id == SYNC_COB_ID {
for tx in node.sync_tpdos() {
bus.send(tx.cob_id, tx.data())?;
}
}
}
Ok(())
}
fn mapping(index: u16, subindex: u8, bits: u8) -> PdoMapping<MAX_PDO_MAPPING> {
let mut m = PdoMapping::new();
m.push(MappingEntry::new(index, subindex, bits))
.expect("one entry fits");
m
}
}