Skip to main content

Module sdo_client

Module sdo_client 

Source
Expand description

Non-blocking SDO read/write client for EtherCAT devices. Non-blocking SDO read/write client for EtherCAT devices.

SdoClient wraps CommandClient to provide an ergonomic, handle-based interface for runtime SDO (Service Data Object) operations over CoE (CANopen over EtherCAT). Create one per device, issue reads/writes from your control loop, and check results by handle on subsequent ticks.

§When to use

Use SdoClient for runtime SDO access — reading diagnostic registers, changing operating parameters on the fly, or any CoE transfer that happens after the cyclic loop is running. For SDOs that must be applied before the cyclic loop starts (e.g. setting modes_of_operation), use the startup_sdo array in project.json instead.

§Topic format

Requests are sent as IPC commands through the existing WebSocket channel. Topics are scoped to the device name configured in project.json:

OperationTopicPayload
Writeethercat.{device}.sdo_write{"index": "0x6060", "sub": 0, "value": "0x01"}
Readethercat.{device}.sdo_read{"index": "0x6060", "sub": 0}

§Usage with a state machine

A typical pattern pairs SdoClient with StateMachine to fire an SDO write in one state, then advance on success:

use autocore_std::{ControlProgram, TickContext};
use autocore_std::ethercat::{SdoClient, SdoResult};
use autocore_std::fb::StateMachine;
use serde_json::json;
use std::time::Duration;

pub struct MyProgram {
    sm: StateMachine,
    sdo: SdoClient,
    write_tid: Option<u32>,
}

impl MyProgram {
    pub fn new() -> Self {
        Self {
            sm: StateMachine::new(),
            sdo: SdoClient::new("ClearPath_0"),
            write_tid: None,
        }
    }
}

impl ControlProgram for MyProgram {
    type Memory = GlobalMemory;

    fn process_tick(&mut self, ctx: &mut TickContext<Self::Memory>) {
        self.sm.call();
        match self.sm.index {
            // State 10: Send SDO write for modes_of_operation = PP
            10 => {
                self.write_tid = Some(
                    self.sdo.write(ctx.client, 0x6060, 0, json!(1))
                );
                self.sm.timeout_preset = Duration::from_secs(3);
                self.sm.index = 20;
            }
            // State 20: Wait for response
            20 => {
                let tid = self.write_tid.unwrap();
                match self.sdo.result(ctx.client, tid, Duration::from_secs(3)) {
                    SdoResult::Pending => { /* keep waiting */ }
                    SdoResult::Ok(_) => {
                        log::info!("modes_of_operation set to PP");
                        self.sm.index = 30;
                    }
                    SdoResult::Err(e) => {
                        log::error!("SDO write failed: {}", e);
                        self.sm.set_error(1);
                    }
                    SdoResult::Timeout => {
                        log::error!("SDO write timed out");
                        self.sm.set_error(2);
                    }
                }
            }
            // State 30: Done — continue with normal operation
            30 => { /* ... */ }
            _ => {}
        }
    }
}

§Reading an SDO

// Fire a read request
let tid = sdo.read(ctx.client, 0x6064, 0); // Position Actual Value

// On a later tick, check the result
match sdo.result(ctx.client, tid, Duration::from_secs(3)) {
    SdoResult::Ok(data) => {
        let position: i32 = serde_json::from_value(data).unwrap();
        log::info!("Current position: {}", position);
    }
    SdoResult::Pending => { /* still waiting */ }
    SdoResult::Err(e) => { log::error!("SDO read failed: {}", e); }
    SdoResult::Timeout => { log::error!("SDO read timed out"); }
}

Structs§

SdoClient
Non-blocking SDO client scoped to a single EtherCAT device.
SdoRequest
Metadata for an in-flight SDO request.

Enums§

SdoRequestKind
Discriminates SDO reads from writes.
SdoResult
Result of checking an in-flight SDO request.