Git Product home page Git Product logo

ethercrab's People

Contributors

dirreke avatar jamwaffles avatar lynaghk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ethercrab's Issues

Issue configuring EL2262

Using master commit 6ac2e08.

Running RUST_LOG=ethercrab=trace just linux-example-release ek1100 enp2s0 and seeing this output:

[2024-03-04T14:32:36Z TRACE ethercrab::slave::eeprom] Range 6656..=7167 value 5890
[2024-03-04T14:32:36Z ERROR ethercrab::slave::eeprom] Invalid PDO range
thread 'main' panicked at examples/ek1100.rs:83:50:
PRE-OP -> OP: Eeprom(Decode)

See if it's possible to report a better error for incorrect datatypes

For example,

// was doing this:  implicitely a usize I guess, implying u64
slave.write_sdo(0x1c12, SubIndex::Index(1), 0x1600).await?;
// instead of this: u16
slave.write_sdo(0x1c12, SubIndex::Index(1), 0x1600_u16).await?;

The error is Init: Mailbox(Aborted { code: General, address: 7186, sub_index: 1 }) which doesn't help the user much. If possible, EtherCrab should tell the user they've used the incorrect length value (or better, incorrect type) for that field. This information is read from the EEPROM during PDO discovery so maybe that could be used.

Split TX/RX out from `Client`

As a final part of the safety around #7, it must be ensured that only one thread can TX or RX packets. To this end, something like the following API should be created:

// Only has a `split` method
let client = ClientMaker::new(...);

// Only allows splitting once
let (tx, rx, client) = client.split();

// Or put them in the same thread, at your option
thread::spawn(move || { tx });
thread::spawn(move || { rx });

// App logic with `client` here

I'm not wedded to the idea of a builder but just something that gives a unique handle to TX, RX (which are both Send) and Client which does everything else.

Maybe just Client::new() -> (Tx, Rx, Client) so it's impossible to hold an invalid state?

Consistent naming

@willglynn brought up EtherCAT's prior use of "master" and "slave" terminology. I've been at odds with these names since starting this project, but wasn't aware the ETG changed the naming to "main device" and "subdevice" (or "subdevice controller" to maintain compatiblity with the ESC acronym). Example document here.

EtherCrab's naming is weird in other ways as well - for example why is the main controller struct called Client??

Anyway, some new name proposals:

  • Client -> Controller
  • Slave (and SlaveGroup, etc) -> Device, DeviceGroup, etc

This would be a breaking change.

error with more than 3 servodrives `Init: Timeout`

Hello hello, that's me again with new problems

I'm trying to run ec400 on a chain of 3 servodrives, but I'm facing a timeout error and I don't really know what is its cause since when connecting to 1 or 2 only of these slaves, the problem disappears.
The following backtrace is with the current master branch, but it was also happening with the previous commits.

dump.txt

Any idea ?

cannot run `ec400`: panicked at 'Init: WorkingCounter`

Hello, it's me again
I have an issue running the latest commit

ethercrab$ RUST_LOG=debug sudo -E ./target/release/examples/ec400 eno1
[2023-04-11T10:11:23Z INFO  ec400] Starting EC400 demo...
[2023-04-11T10:11:23Z INFO  ec400] Ensure an EC400 servo drive is the first and only slave
[2023-04-11T10:11:23Z INFO  ec400] Run with RUST_LOG=ethercrab=debug or =trace for debug information
[2023-04-11T10:11:23Z DEBUG ethercrab::slave] Slave 0x1000 name R88D-1SN02H-ECT
[2023-04-11T10:11:23Z DEBUG ethercrab::client] Configuring topology/distributed clocks
[2023-04-11T10:11:23Z DEBUG ethercrab::dc] Slave 0x1000 R88D-1SN02H-ECT has DC
[2023-04-11T10:11:23Z DEBUG ethercrab::dc] --> Times 1955227410 (0) 1717989224 (0) 0 (1819436374) 1819436374
[2023-04-11T10:11:23Z DEBUG ethercrab::dc] --> Propagation time None ns, child delay 0 ns
[2023-04-11T10:11:23Z DEBUG ethercrab::dc] Distributed clock config complete
[2023-04-11T10:11:23Z DEBUG ethercrab::dc] Performing static drift compensation using slave 0x1000 R88D-1SN02H-ECT as reference. This can take some time...
[2023-04-11T10:11:25Z DEBUG ethercrab::dc] Static drift compensation complete
[2023-04-11T10:11:25Z DEBUG ethercrab::slave_group::configurator] Going to configure group, starting PDI offset 0x000000
thread 'main' panicked at 'Init: WorkingCounter { expected: 1, received: 0, context: Some("debug write") }', examples/ec400.rs:118:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

It is fine with the previous commits, however I'm having troubles to start the cyclic communication (this is probably a different issue)

Remove `TIMEOUT` generic param

Use e.g. async-io for std targets and embassy-time for no_std targets.

The only downside of choosing a specific implementation would be multiple timer implementations in the final binary. This is fine for std, but may be a cause for concern in no_std applications. In that case, it shouldn't be too hard to add Cargo features to target a specific timer implementation, e.g. for RTIC when it supports async.

Add a derive for `SlaveGroupContainer`

Implementing SlaveGroupContainer for a groups struct can be quite error prone. This:

struct Groups {
    slow_outputs: SlaveGroup<2, 2>,
    fast_outputs: SlaveGroup<1, 1>,
}

impl SlaveGroupContainer for Groups {
    fn num_groups(&self) -> usize {
        2
    }

    fn group(&mut self, index: usize) -> Option<SlaveGroupRef> {
        match index {
            0 => Some(self.slow_outputs.as_mut_ref()),
            1 => Some(self.fast_outputs.as_mut_ref()),
            _ => None,
        }
    }
}

Should turn into this:

#[derive(ethercrab::SlaveGroupContainer)]
struct Groups {
    slow_outputs: SlaveGroup<2, 2>,
    fast_outputs: SlaveGroup<1, 1>,
}

Invalid SDO response error

Hi! Thanks for opening an issue. Please provide some specific details to make it easier to debug any
problems.

Environment

  • Version of ethercrab in use or git hash: 0.3.1
  • Operating system kind and version: [kind] [version]
  • EtherCAT devices in use: EK1100, EL6224, possibly others.

Uploads

captures.zip

Description of the problem/feature request/other

Issue encountered on Matrix, here.

Encountering this error when going into PRE-OP:

Invalid SDO response. Type: Coe (expected Coe), counter 1 (expected 2)

Also mentioned:

So it looks like it‘s always the first slave behind this card here that‘s affected of the „invalid SDO“ bug.

First, the slave behind ek1100 always fails :) you can clip different slaves in this ek1100 and no matter which I put in there, the first slave behind it that’s not within the ek 1100 causes the error

Investigate frame timeouts when using `smol::future::zip()`

When changing some sequential awaits to use zip(), some odd errors occurr. Example pseudocode:

let (r1, r2) = smol::future::zip(
    f1(&client),
    f2(&client),
)
.await;

Calling f1().await then f2().await works fine, but zipping them does not. After reaching OP, logs look something like this:

[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 20 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::std::linux] Failed to receive frame: pdu: invalid PDU index 20
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 22 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 23 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 24 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 25 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 26 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 27 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 28 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 29 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 30 timed out, releasing
[2023-10-02T18:53:00Z ERROR ethercrab::pdu_loop] Frame 31 timed out, releasing

Why `try_split` can only be called once?

I use ethercrab as a part of my program. And I have to reset the program in some reason.
However, for ethercrab, I get an error when call try_split. Then, I tried to make PduRx, PduTx.... be a static variable and I'm failed, either

I'm not familiar with the underlying logic. However, I want to know if we can use once to make sure we only call compare_exchange once and then every time I call try_split, it will return (rx, tx, pduloop).
Alternatively, I want to know if we can split the try_split into two parts, one for calling compare_exchange and the other for returning the (rx, tx, pdu_loop). In this way, I can use the former method to get the (rx, tx, pdu_loop) and run the task.

Thanks!

example `ec400` stalling with slave state not switching to OP

Hello
This may be the first of a serie of stupid interogations for setting up a master using CoE

I'm trying to start a cyclic communication with PDO read/write on an omron servodrive (R88D-1SN04H-ECT), based on example/ec400.rs. While starting, the program stalls in the loop waiting for servo.tick(). The slave never switches to OP and then often after ~2min, the program panics with the following log:

ydejonghe@UBUNTU:~/ethercat/ethercrab$ sudo RUST_LOG=debug target/debug/examples/ec400
[2023-03-14T13:55:29Z INFO  ec400] Starting SDO demo...
[2023-03-14T13:55:29Z DEBUG ethercrab::slave] Slave 0x1000 name R88D-1SN04H-ECT
[2023-03-14T13:55:29Z DEBUG ethercrab::client] Configuring topology/distributed clocks
[2023-03-14T13:55:29Z DEBUG ethercrab::dc] Slave 0x1000 R88D-1SN04H-ECT has DC
[2023-03-14T13:55:29Z DEBUG ethercrab::dc] --> Times 39335733 (2139694680) 2179030413 (0) 0 (1819436374) 1819436374
[2023-03-14T13:55:29Z DEBUG ethercrab::dc] --> Propagation time None ns, child delay 0 ns
[2023-03-14T13:55:29Z DEBUG ethercrab::dc] Distributed clock config complete
[2023-03-14T13:55:29Z DEBUG ethercrab::dc] Performing static drift compensation using slave 0x1000 R88D-1SN04H-ECT as reference. This can take some time...
[2023-03-14T13:55:31Z DEBUG ethercrab::dc] Static drift compensation complete
[2023-03-14T13:55:31Z DEBUG ethercrab::slave_group::configurator] Going to configure group, starting PDI offset 0x000000
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered sync managers:
    [
        SyncManager {
            start_addr: 0x1800,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxWrite,
        },
        SyncManager {
            start_addr: 0x1c00,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxRead,
        },
        SyncManager {
            start_addr: 0x1100,
            length: 0x000c,
            control: Control {
                operation_mode: Normal,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: true,
            },
            enable: ENABLE,
            usage_type: ProcessDataWrite,
        },
        SyncManager {
            start_addr: 0x1200,
            length: 0x001c,
            control: Control {
                operation_mode: Normal,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: ProcessDataRead,
        },
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 SM0: start 0x1800, size 0x0100 (256), direction MasterWrite, mode Mailbox, enabled
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 SM1: start 0x1c00, size 0x0100 (256), direction MasterRead, mode Mailbox, enabled
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::slave_client] Set state Pre-Operational for slave address 0x1000
[2023-03-14T13:55:31Z INFO  ec400] Found "R88D-1SN04H-ECT\0\0\0\0\0"
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered sync managers:
    [
        SyncManager {
            start_addr: 0x1800,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxWrite,
        },
        SyncManager {
            start_addr: 0x1c00,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxRead,
        },
        SyncManager {
            start_addr: 0x1100,
            length: 0x000c,
            control: Control {
                operation_mode: Normal,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: true,
            },
            enable: ENABLE,
            usage_type: ProcessDataWrite,
        },
        SyncManager {
            start_addr: 0x1200,
            length: 0x001c,
            control: Control {
                operation_mode: Normal,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: ProcessDataRead,
        },
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered FMMUs:
    [
        Outputs,
        Inputs,
        SyncManagerStatus,
        Unused,
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] FMMU mappings: []
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 has CoE: true
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered PDOs:
    [
        Pdo {
            index: 0x1b01,
            num_entries: 9,
            sync_manager: 3,
            dc_sync: 0,
            name_string_idx: 3,
            flags: PDO_DEFAULT | PDO_FIXED_CONTENT,
            entries: [
                PdoEntry {
                    index: 0x603f,
                    sub_index: 0,
                    name_string_idx: 4,
                    data_type: U16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x6041,
                    sub_index: 0,
                    name_string_idx: 5,
                    data_type: U16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x6064,
                    sub_index: 0,
                    name_string_idx: 6,
                    data_type: I32,
                    data_length_bits: 32,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x6077,
                    sub_index: 0,
                    name_string_idx: 7,
                    data_type: I16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60f4,
                    sub_index: 0,
                    name_string_idx: 8,
                    data_type: I32,
                    data_length_bits: 32,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60b9,
                    sub_index: 0,
                    name_string_idx: 9,
                    data_type: U16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60ba,
                    sub_index: 0,
                    name_string_idx: 10,
                    data_type: I32,
                    data_length_bits: 32,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60bc,
                    sub_index: 0,
                    name_string_idx: 11,
                    data_type: I32,
                    data_length_bits: 32,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60fd,
                    sub_index: 0,
                    name_string_idx: 12,
                    data_type: U32,
                    data_length_bits: 32,
                    flags: 0,
                },
            ],
        },
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 SM3: start 0x1200, size 0x001c (28), direction MasterRead, mode Normal, enabled
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 FMMU1: logical start 0x00000000:0, size 28, logical end bit 7, physical start 0x1200:0, RO, enabled
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 PDI inputs: PdiSegment { bytes: 0..28, bit_len: 224 } (28 bytes), outputs: PdiSegment { bytes: 0..0, bit_len: 0 } (0 bytes)
[2023-03-14T13:55:31Z DEBUG ethercrab::slave_group::configurator] Slave mailboxes configured and init hooks called
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered sync managers:
    [
        SyncManager {
            start_addr: 0x1800,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxWrite,
        },
        SyncManager {
            start_addr: 0x1c00,
            length: 0x0100,
            control: Control {
                operation_mode: Mailbox,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: MailboxRead,
        },
        SyncManager {
            start_addr: 0x1100,
            length: 0x000c,
            control: Control {
                operation_mode: Normal,
                direction: MasterWrite,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: true,
            },
            enable: ENABLE,
            usage_type: ProcessDataWrite,
        },
        SyncManager {
            start_addr: 0x1200,
            length: 0x001c,
            control: Control {
                operation_mode: Normal,
                direction: MasterRead,
                ecat_event_enable: false,
                dls_user_event_enable: true,
                watchdog_enable: false,
            },
            enable: ENABLE,
            usage_type: ProcessDataRead,
        },
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] Discovered FMMUs:
    [
        Outputs,
        Inputs,
        SyncManagerStatus,
        Unused,
    ]
[2023-03-14T13:55:31Z DEBUG ethercrab::eeprom] FMMU mappings: []
[2023-03-14T13:55:31Z DEBUG ethercrab::slave::configurator] Slave 0x1000 has CoE: true
[2023-03-14T13:55:32Z DEBUG ethercrab::eeprom] Discovered PDOs:
    [
        Pdo {
            index: 0x1701,
            num_entries: 4,
            sync_manager: 2,
            dc_sync: 0,
            name_string_idx: 13,
            flags: PDO_DEFAULT | PDO_FIXED_CONTENT,
            entries: [
                PdoEntry {
                    index: 0x6040,
                    sub_index: 0,
                    name_string_idx: 14,
                    data_type: U16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x607a,
                    sub_index: 0,
                    name_string_idx: 15,
                    data_type: I32,
                    data_length_bits: 32,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60b8,
                    sub_index: 0,
                    name_string_idx: 16,
                    data_type: U16,
                    data_length_bits: 16,
                    flags: 0,
                },
                PdoEntry {
                    index: 0x60fe,
                    sub_index: 1,
                    name_string_idx: 17,
                    data_type: U32,
                    data_length_bits: 32,
                    flags: 0,
                },
            ],
        },
    ]
[2023-03-14T13:55:32Z DEBUG ethercrab::slave::configurator] Slave 0x1000 SM2: start 0x1100, size 0x000c (12), direction MasterWrite, mode Normal, enabled
[2023-03-14T13:55:32Z DEBUG ethercrab::slave::configurator] Slave 0x1000 FMMU0: logical start 0x0000001c:0, size 12, logical end bit 7, physical start 0x1100:0, W, enabled
[2023-03-14T13:55:32Z DEBUG ethercrab::slave::configurator] Slave 0x1000 PDI inputs: PdiSegment { bytes: 0..28, bit_len: 224 } (28 bytes), outputs: PdiSegment { bytes: 28..40, bit_len: 96 } (12 bytes)
[2023-03-14T13:55:32Z DEBUG ethercrab::slave::slave_client] Set state Safe-Operational for slave address 0x1000
[2023-03-14T13:55:32Z DEBUG ethercrab::slave_group::configurator] Slave FMMUs configured for group. Able to move to SAFE-OP
[2023-03-14T13:55:32Z DEBUG ethercrab::slave_group::configurator] Group PDI length: start 0, 40 total bytes (28 input bytes)
[2023-03-14T13:55:32Z DEBUG ethercrab::client] After group #0 offset: PdiOffset { start_address: 40, start_bit: 0 }
[2023-03-14T13:55:32Z DEBUG ethercrab::client] Total PDI 40 bytes
[2023-03-14T13:55:32Z INFO  ec400] Slaves moved to OP state
[2023-03-14T13:55:32Z INFO  ec400] Group has 1 slaves
[2023-03-14T13:55:32Z INFO  ec400] -> Slave 4096 R88D-1SN04H-ECT inputs: 28 bytes, outputs: 12 bytes
[2023-03-14T13:55:32Z INFO  ec400] Cycle time: 2 ms
[2023-03-14T13:55:32Z DEBUG ethercrab::ds402] Edge READY_TO_SWITCH_ON | SWITCHED_ON | WARNING | STO | REMOTE | TARGET_REACHED | INTERNAL_LIMIT | OP_SPECIFIC_1 | OP_SPECIFIC_2 | MAN_SPECIFIC_1 | MAN_SPECIFIC_2

<2min>

thread 'main' panicked at 'TX/RX: Pdu(InvalidFrameState)', examples/ec400.rs:188:36
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Here is the code I'm running:

use async_io::Timer;
use ethercrab::{
    ds402::{Ds402, Ds402Sm},
    error::Error,
    std::tx_rx_task,
    Client, PduLoop, PduStorage, SlaveGroup, SlaveState, SubIndex, Timeouts,
};
use futures_lite::StreamExt;
use smol::LocalExecutor;
use std::{
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
    time::Duration,
};

#[cfg(target_os = "windows")]
// ASRock NIC
// const INTERFACE: &str = "TODO";
// // USB NIC
// const INTERFACE: &str = "\\Device\\NPF_{DCEDC919-0A20-47A2-9788-FC57D0169EDB}";
// Lenovo USB-C NIC
const INTERFACE: &str = "\\Device\\NPF_{CC0908D5-3CB8-46D6-B8A2-575D0578008D}";
// Silver USB NIC
// const INTERFACE: &str = "\\Device\\NPF_{CC0908D5-3CB8-46D6-B8A2-575D0578008D}";
#[cfg(not(target_os = "windows"))]
const INTERFACE: &str = "eno1";

const MAX_SLAVES: usize = 16;
const MAX_PDU_DATA: usize = 1100;
const MAX_FRAMES: usize = 16;
const PDI_LEN: usize = 256;

static PDU_STORAGE: PduStorage<MAX_FRAMES, MAX_PDU_DATA> = PduStorage::new();
static PDU_LOOP: PduLoop = PduLoop::new(PDU_STORAGE.as_ref());

async fn main_inner(ex: &LocalExecutor<'static>) -> Result<(), Error> {
    log::info!("Starting SDO demo...");

    let client = Arc::new(Client::new(&PDU_LOOP, Timeouts::default()));

    ex.spawn(tx_rx_task(INTERFACE, &client).unwrap()).detach();

    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();

    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
    })
    .expect("Error setting Ctrl-C handler");

    // let num_slaves = client.num_slaves();

    let groups = SlaveGroup::<MAX_SLAVES, PDI_LEN>::new(|slave| {
        Box::pin(async {
            // --- Reads ---

            // // Name
            // dbg!(slave
            //     .read_sdo::<heapless::String<64>>(0x1008, SdoAccess::Index(0))
            //     .await
            //     .unwrap());

            // // Software version. For AKD, this should equal "M_01-20-00-003"
            // dbg!(slave
            //     .read_sdo::<heapless::String<64>>(0x100a, SdoAccess::Index(0))
            //     .await
            //     .unwrap());

            // --- Writes ---

            log::info!("Found {:?}", slave.name());

//             if slave.name() == "R88D-1SN04H-ECT\0\0\0\0\0" {
			if slave.name() == "ZeroErr Driver" {
				log::info!("  using it");
                // CSV described a bit better in section 7.6.2.2 Related Objects of the manual
                slave.write_sdo(0x1600, SubIndex::Index(0), 0u8).await?;
                // Control word, u16
                // NOTE: The lower word specifies the field length
                slave
                    .write_sdo(0x1600, SubIndex::Index(1), 0x6040_0010u32)
                    .await?;
                // Target velocity, i32
                slave
                    .write_sdo(0x1600, SubIndex::Index(2), 0x60ff_0020u32)
                    .await?;
                slave.write_sdo(0x1600, SubIndex::Index(0), 2u8).await?;

                slave.write_sdo(0x1a00, SubIndex::Index(0), 0u8).await?;
                // Status word, u16
                slave
                    .write_sdo(0x1a00, SubIndex::Index(1), 0x6041_0010u32)
                    .await?;
                // Actual position, i32
                slave
                    .write_sdo(0x1a00, SubIndex::Index(2), 0x6064_0020u32)
                    .await?;
                // Actual velocity, i32
                slave
                    .write_sdo(0x1a00, SubIndex::Index(3), 0x606c_0020u32)
                    .await?;
                slave.write_sdo(0x1a00, SubIndex::Index(0), 0x03u8).await?;

               // commented to avoid too big frame issue
//                 slave.write_sdo(0x1c12, SubIndex::Index(0), 0u8).await?;
//                 slave.write_sdo(0x1c12, SubIndex::Index(1), 0x1600).await?;
//                 slave.write_sdo(0x1c12, SubIndex::Index(0), 1u8).await?;
// 
//                 slave.write_sdo(0x1c13, SubIndex::Index(0), 0u8).await?;
//                 slave.write_sdo(0x1c13, SubIndex::Index(1), 0x1a00).await?;
//                 slave.write_sdo(0x1c13, SubIndex::Index(0), 1u8).await?;

                // Opmode - Cyclic Synchronous Position
                // slave.write_sdo(0x6060, SubIndex::Index(0), 0x08).await?;
                // Opmode - Cyclic Synchronous Velocity
                slave.write_sdo(0x6060, SubIndex::Index(0), 0x09u8).await?;
            }

            Ok(())
        })
    });

    let group = client
        .init::<16, _>(groups, |groups, slave| groups.push(slave))
        .await
        .expect("Init");

    client
        .request_slave_state(SlaveState::Op)
        .await
        .expect("OP");

    log::info!("Slaves moved to OP state");

    log::info!("Group has {} slaves", group.len());

    for slave in group.slaves() {
        let (i, o) = slave.io();

        log::info!(
            "-> Slave {} {} inputs: {} bytes, outputs: {} bytes",
            slave.configured_address,
            slave.name,
            i.len(),
            o.len(),
        );
    }

    // Run twice to prime PDI
    group.tx_rx(&client).await.expect("TX/RX");

    let cycle_time = {
        let slave = group.slave(0).unwrap();

        let base = slave
            .read_sdo::<u8>(&client, 0x60c2, SubIndex::Index(1))
            .await?;
        let x10 = slave
            .read_sdo::<i8>(&client, 0x60c2, SubIndex::Index(2))
            .await?;

        let base = f32::from(base);
        let x10 = 10.0f32.powi(i32::from(x10));

        let cycle_time_ms = (base * x10) * 1000.0;

        Duration::from_millis(unsafe { cycle_time_ms.round().to_int_unchecked() })
    };

    log::info!("Cycle time: {} ms", cycle_time.as_millis());

    // AKD will error with F706 if cycle time is not 2ms or less
    let mut cyclic_interval = Timer::interval(cycle_time);

    let slave = group.slave(0).expect("No servo!");
    let mut servo = Ds402Sm::new(Ds402::new(slave).expect("Failed to gather DS402"));

    let mut velocity: i32 = 0;

    // let mut slave = group.slave(0, &client).unwrap();

    let accel = 300;

    while let Some(_) = cyclic_interval.next().await {
        group.tx_rx(&client).await.expect("TX/RX");

        if servo.tick() {
            // // Opmode - Cyclic Synchronous Position
            // servo
            //     .sm
            //     .context()
            //     .slave
            //     .write_sdo(&client, 0x6060, SubIndex::Index(0), 0x08u8)
            //     .await?;

            let status = servo.status_word();
            let (i, o) = servo.slave().io();

            let (pos, vel) = {
                let pos = i32::from_le_bytes(i[2..=5].try_into().unwrap());
                let vel = i32::from_le_bytes(i[6..=9].try_into().unwrap());

                (pos, vel)
            };

            println!(
                "Position: {pos}, velocity: {vel}, status: {status:?} | {:?}",
                o
            );

            let pos_cmd = &mut o[2..=5];

            pos_cmd.copy_from_slice(&velocity.to_le_bytes());

            if running.load(Ordering::SeqCst) {
                if vel < 200_000 {
                    velocity += accel;
                }
            } else if vel > 0 {
                velocity -= accel;
            } else {
                break;
            }
        }
    }

    log::info!("Servo stopped, shutting drive down");

    while let Some(_) = cyclic_interval.next().await {
        group.tx_rx(&client).await.expect("TX/RX");

        if servo.tick_shutdown() {
            break;
        }

        let status = servo.status_word();
        let (i, o) = servo.slave().io();

        let (pos, vel) = {
            let pos = i32::from_le_bytes(i[2..=5].try_into().unwrap());
            let vel = i32::from_le_bytes(i[6..=9].try_into().unwrap());

            (pos, vel)
        };

        println!(
            "Position: {pos}, velocity: {vel}, status: {status:?} | {:?}",
            o
        );
    }

    Ok(())
}

fn main() -> Result<(), Error> {
    env_logger::init();
    let local_ex = LocalExecutor::new();

    futures_lite::future::block_on(local_ex.run(main_inner(&local_ex))).unwrap();

    Ok(())
}

Do you think I am doing something wrong ?

ek1100.rs example: thread 'main' panicked at 'Init: WorkingCounter ...

I just started learning how to use this library by running the ek1100.rs example.

I have a simple topology:
Ubuntu 22.04 Server minimized and:

  • Network card (Eth0):
    • EK1100
      • EL2004

Cloning this repo to have stable support as commented in the matrix channel (very quick response! Thanks for that!) and running the mentioned example I get the next error:

[2023-07-11T02:09:06Z INFO  ek1100] Starting EK1100 demo...
[2023-07-11T02:09:06Z INFO  ek1100] Ensure an EK1100 is the first slave, with any number of modules connected after
[2023-07-11T02:09:06Z INFO  ek1100] Run with RUST_LOG=ethercrab=debug or =trace for debug information
[2023-07-11T02:09:06Z DEBUG ethercrab::slave] Slave 0x1000 name EK1100
[2023-07-11T02:09:07Z DEBUG ethercrab::slave] Slave 0x1001 name EL2004
[2023-07-11T02:09:07Z DEBUG ethercrab::client] Configuring topology/distributed clocks
thread 'main' panicked at 'Init: WorkingCounter { expected: 2, received: 1, context: Some("Broadcast time") }', examples/ek1100.rs:83:10

I build the example with cargo build --example ek1100 --release.

After that run it by sudo RUST_BACKTRACE=full RUST_LOG=debug ./target/release/examples/ek1100 eth0 where eth0 is the name of the NIC where the EtherCAT equipment is plugged.

For some more context this is the full stack trace:

thread 'main' panicked at 'Init: WorkingCounter { expected: 2, received: 1, context: Some("Broadcast time") }', examples/ek1100.rs:83:10
stack backtrace:
   0:     0x560e53c1e23a - std::backtrace_rs::backtrace::libunwind::trace::h9a6b80bbf328ba5d
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
   1:     0x560e53c1e23a - std::backtrace_rs::backtrace::trace_unsynchronized::hd162ec543a11886b
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x560e53c1e23a - std::sys_common::backtrace::_print_fmt::h78a5099be12f51a6
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/sys_common/backtrace.rs:65:5
   3:     0x560e53c1e23a - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::ha1c5390454d74f71
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/sys_common/backtrace.rs:44:22
   4:     0x560e53c3a8af - core::fmt::write::h9ffde816c577717b
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/fmt/mod.rs:1254:17
   5:     0x560e53c1b9d5 - std::io::Write::write_fmt::h88186074961638e4
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/io/mod.rs:1698:15
   6:     0x560e53c1e005 - std::sys_common::backtrace::_print::h184198273ed08d59
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/sys_common/backtrace.rs:47:5
   7:     0x560e53c1e005 - std::sys_common::backtrace::print::h1b4d8e7add699453
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/sys_common/backtrace.rs:34:9
   8:     0x560e53c1f6ae - std::panicking::default_hook::{{closure}}::h393bcea75423915a
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:269:22
   9:     0x560e53c1f455 - std::panicking::default_hook::h48c64f31d8b3fd03
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:288:9
  10:     0x560e53c1fc0e - std::panicking::rust_panic_with_hook::hafdc493a79370062
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:691:13
  11:     0x560e53c1fb09 - std::panicking::begin_panic_handler::{{closure}}::h0a64bc82e36bedc7
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:582:13
  12:     0x560e53c1e6a6 - std::sys_common::backtrace::__rust_end_short_backtrace::hc203444fb7416a16
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/sys_common/backtrace.rs:150:18
  13:     0x560e53c1f862 - rust_begin_unwind
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:578:5
  14:     0x560e53a69193 - core::panicking::panic_fmt::h0f6ef0178afce4f2
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/panicking.rs:67:14
  15:     0x560e53a695e3 - core::result::unwrap_failed::h8090202169109f9c
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/result.rs:1687:5
  16:     0x560e53a78798 - tokio::runtime::park::CachedParkThread::block_on::he68682098847e7b5
  17:     0x560e53a78c36 - tokio::runtime::context::runtime::enter_runtime::h57a07d4b664d4d90
  18:     0x560e53adb101 - tokio::runtime::runtime::Runtime::block_on::h29109ff0d856e13d
  19:     0x560e53a6e78f - ek1100::main::h1550c917b3a86bf5
  20:     0x560e53adc756 - std::sys_common::backtrace::__rust_begin_short_backtrace::h15abfc1a7efe2366
  21:     0x560e53a70511 - std::rt::lang_start::{{closure}}::h5e04481cc6f084b8
  22:     0x560e53c176fe - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::hb1327dc2ef3fecdf
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/ops/function.rs:287:13
  23:     0x560e53c176fe - std::panicking::try::do_call::h4044173225fe83dd
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:485:40
  24:     0x560e53c176fe - std::panicking::try::hd8a722c09d156a53
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:449:19
  25:     0x560e53c176fe - std::panic::catch_unwind::hd2ca07971cf0119b
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panic.rs:140:14
  26:     0x560e53c176fe - std::rt::lang_start_internal::{{closure}}::h26d89d595cf47b70
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/rt.rs:148:48
  27:     0x560e53c176fe - std::panicking::try::do_call::hf47aa1aa005e5f1a
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:485:40
  28:     0x560e53c176fe - std::panicking::try::h73d246b2423eaf4e
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:449:19
  29:     0x560e53c176fe - std::panic::catch_unwind::hbaaeae8f1b2f9915
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panic.rs:140:14
  30:     0x560e53c176fe - std::rt::lang_start_internal::h76f3e81e6b8f13f9
                               at /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/rt.rs:148:20
  31:     0x560e53a6e865 - main
  32:     0x7f3343d77d90 - <unknown>
  33:     0x7f3343d77e40 - __libc_start_main
  34:     0x560e53a69775 - _start
  35:                0x0 - <unknown>

I am also testing the c wrapper ethercat-rs so I had to install the IgH modules but all of that is disabled and stopped. I tried the example in interfaces previously configured as ec_master and others unassigned to ethercat with the same results.

I will post more information with more testing.

Create EEPROM test harness

It should be possible to use an EEPROM dump file (either from a slave or created manually) or otherwise generate one so it's possible to unit test the EEPROM module.

More accessible docs

Hi,
I wanted to take a look at the topology diagram in the docs folder, but it seems like Monodraw, the tool used to view it, is only available for Mac.
Unfortunately, I don't currently have access to a Mac.
Do you mind adding an image of the diagram for those who can't use Mac with Monodraw?
Thanks!

Add methods to transition out of OP back to INIT

A subset for now:

  • OP -> SAFE-OP
  • SAFE-OP -> PRE-OP
  • PRE-OP -> INIT

These methods will allow for graceful shutdown of the EtherCAT network. Some users have been having issues with servo drives that only crop up when they're re-initialised after a Ctrl-C "shutdown" which just stops cyclic data and lets SubDevice watchdogs time out.

Transitioning to PreOpPdi doesn't make much sense from this direction, so this won't be supported (for now? Maybe someone needs this?).

Support reading of `std::String` when `std` feature is turned on

Currently we have to do a little dance to first read into a heapless::String:

let whatever= slave
    .sdo_read::<heapless::String<128>>(0x10e2, 0x01)
    .await
    .map(|s| s.to_string())?;

This will require refactoring of the (admitedly quite crappy) PduRead/PduData traits to support reading into dynamically sized containers.

Get AL status for more than one device as bitflags

This will cater for cases where e.g. the whole network is in OP but one slave is not. Currently, an enum is used which would result in this network state giving a SlaveState::Unknown.

When doing a BRD for status, the bits are ORed as the packet transits each device, meaning a bitflags struct is the best way to represent this.

EEPROM strings contain trailing null bytes

Issue #21 raised a bug where strings read from the slave device contain trailing null bytes (\0). These should be trimmed from the returned string, otherwise we get behaviour like a slave name of "R88D-1SN04H-ECT\0\0\0\0\0".

Presumably this slave device assumes a language with null-terminated strings is reading the EEPROM, so is a bit loose about what it returns after the string data. In this specific case, the slave device reports a length of 20 bytes for the string, where "R88D-1SN04H-ECT" is only 15 bytes long.

Allow configuration of DC static drift iterations

The current hard-coded value of 10000 iterations takes an age on Windows and makes it look like the program has hung. This should be configurable for those who either don't need as accurate DC static drift comp or those who don't care at all.

The config option should be an Option<u32 where None disables the static drift comp entirely.

What about tools to manipulate SDOs and PDOs

Hello @jamwaffles

I was working on a benchmark of ethercrab vs ethercat-rs as we mentioned, and I've found useful the following piece of API. I didn't fully implemented it due to some difficulties I will explain after.

API suggestion

base concepts

The idea is to have pointer-like variables pointing to fields in datagrams, and other to SDOs on slaves. However those would be perfectly safe because they would not be pointers but bare indices with runtime bounds checks.

/// an instance of this refers to some data in a datagram
struct Field<T: PduData> {
  pub byte: usize,   // start byte offset in the datagram
  pub bit: u8,    // start bit offset in the start byte
  pub bitlen: usize,  // bit length of the value after the start byte
}

/// an instance of this refers to an SDO item or subitem
struct Sdo<T: PduData> {
  pub index: u16,
  pub sub: u8,
}

After this there is two alternatives:

  1. they shall provide methods to get/set what they are refering to
impl Field<T> {
  fn get(&self, data: &[u8]) -> T
  fn set(&self, data: &mut [u8], value: T)
}

impl Sdo<T> {
  async fn get(&self, slave: &SlaveRef) -> Result<T> 
  async fn set(&self, slave: &SlaveRef, value: T) -> Result<()>
}
  1. what they are refering to shall provide methods to get/set them, as if the aformentioned structs were keys in a table
impl [u8] {
  fn get(&self, key: &Field<T>) -> T
  fn set(&mut self, key: &Field<T>, value: T)
}

impl Slave {
  async fn get(&self, sdo: &Sdo<T>) -> Result<T>
  async fn set(&self, sdo: &Sdo<T>, value: T) -> Result<()>
}

usage example

/// in a declaration file
let operation_mode = Sdo::<u16> {index: 0x6040, sub: 0};
let position = Sdo::<i32> {index: 0x6064, sub: 0};
/// in user code
operation_mode.set(slave, OperationMode::SynchronousPosition as u16).await?;
let p = position.get(slave);

an now, even more helper structs

In the same spirit, I'm also considering helper structs to configure the PDO mappings

struct ConfigurablePdo {
  index: u16,  // sdo address
  num: u8, // number of sdo subitems that can be mapped
}

// helper to init a sync manager mapping, compute and increment mapped fields offsets
struct SyncMapping {...}
impl SyncMapping {
  fn push(&mut self, sdo: &ConfigurablePdo) -> PdoMapping
  fn finish(&mut self)
}
// helper to init a pdo mapping, compute and increment mapped fields offsets
struct PdoMapping {...}
impl PdoMapping {
  // produce a Field pointing to where the sdo subitem will be in the received/transmitted datagram, so the user can use it to easily get/set the value in the datagram
  fn push(&mut self, sdo: &Sdo<T>) -> Field<T>
  fn finish(&mut self)
}

usage example

// in declaration file
let profile = { // some big nested struct of helpers and Sdo<T> ...
		mode: Sdo::<u16> {index: 0x6060, sub: 0},
		current: ProfileCurrent {
			status: Sdo::<u16> {index: 0x6041, sub: 0},
			error: Sdo::<u16> {index: 0x603f, sub: 0},
			position: Sdo::<i32> {index: 0x6064, sub: 0},
			velocity: Sdo::<i32> {index: 0x606c, sub: 0},
			force: Sdo::<i16> {index: 0x6077, sub: 0},
			},
		target: ProfileTarget {
			control: Sdo::<u16> {index: 0x6040, sub: 0},
			position: Sdo::<i32> {index: 0x607a, sub: 0},
			velocity: Sdo::<i32> {index: 0x60ff, sub: 0},
			force: Sdo::<i16> {index: 0x6071, sub: 0},
			},
		period: ProfilePeriod {
			digits: Sdo::<u8> {index: 0x60c2, sub: 1},
			exponent: Sdo::<i8> {index: 0x60c2, sub: 2},
			},
		pdo: [
			ConfigurablePdo {index: 0x1600, num: 3},
			ConfigurablePdo {index: 0x1a00, num: 3},
			],
		rx: SyncManager {index: 0x1c12, num: 3},
		tx: SyncManager {index: 0x1c13, num: 3},
  };

// in pre-op mode
let mapping = slave.mapping(profile.tx).await?;     // start configuring sync manager mapping
let pdo = mapping.push(&profile.pdo[0]).await?;    // add a PDO and start configure it
current_status = pdo.push(&profile.current.status).await?;   // set next (first) object to status SDO
current_position = pdo.count(&profile.current.position).await?;   // assume next (second) object is position SDO
pdo.finish().await?;
mapping.finish().await?;

// in op mode
let (i, o) = slave.io();    // i and o are actually datagrams, respectively &[u8] and &mut [u8]
let status = current_status.get(i);
let pos = current_position.get(i);
target_position.set(o, pos+some_increment);

state of work

I implemented it, but it only works for SlaveRef, since the get/set method have to take a slave as argument It cannot be both SlaveRef and GroupSlave.
By the way, what is the distinction between SlaveRef and GroupSlave ? They both access SDOs

Add RTIC example with multiple groups

I'd like to support RTIC as well as Embassy, so EtherCrab should have an example for it too. Needs to demonstrate multiple groups to ensure the lifetimes/Send bounds/whatever are correct to work with RTIC's architecture.

PRE OP -> SAFE OP hook should be a closure

Hello @jamwaffles

At the moment, the PRE OP -> SAFE OP hook is a function pointer (fn) so it cannot borrow data specifying the mapping to operate nor can return locally computed mapping offsets to the rest of the program.

Do you think the hook could be a closure (maybe a dyn Fn) instead ?

I thing beeing able to do the following would be great:

let config = {/* data defining the mapping to set */}
let mut offsets = {/* data containing the pdo entries offsets deduced from mapping */}
let groups = SlaveGroup::<MAX_SLAVES, PDI_LEN>::new(|slave| {
    Box::pin(async {
        // use config to write the correct SDOS
        something(config, slave)
        // compute the corresponding offsets
        offsets = something(config, slave)
});
// use offsets to extract entries from the process data

Remove `Send` bound and impl from `SlaveGroup`

  • Remove Send bound on HookFuture (the type returned by the PRE-OP/SAFE-OP hook function passed to a SlaveGroup)
  • Remove unsafe impl Send for SlaveGroup (and Sync if possible - groups shouldn't be movable between threads)

The examples compile fine without these two things, but it's definitely worth checking if a different kind of architecture someone might use is affected by these changes.

Optimise slave status read

Two PDUs, therefore two ethernet packets, are sent to read the AL status and code currently. This is wasteful. When multiple PDU sends are supported, these two items should be batched so only one ethernet packet is sent.

Is this ready for an R&D project

Hello there
Thanks for all the work done here

I am an engineer in robotics and software engineering and I'm developing a robot arm with ethercat enabled servodrives that will be directly controlled by a linux computer (not a small one but a big one responsible for a lot of vision processing as well a trajectory control).

I'm considering alternatives to control my ethercat devices in Rust. As regarding trajectory control, I need a fast and reliable implementation. It is also a project that shall go on the market in the coming 2 years.

I'm currently comparing the two rust implementations of ethercat I have found:

  • ethercat-rs wrapping etherlab's kernel module

    • has its own linux kernel module
    • ethercat can be shared by several processes
    • code can run with user-priviledges
    • implemented in C 😞
    • linux-only
    • might slower than ethercrab due to frequent calls to ioctl ?
    • the C part is production ready
  • ethercrab

    • everything in one process
    • one process owns the ethercat master, no other can access them
    • needs root priviledges (or linux capabilities) to open a raw socket
    • fast heap-less implementation
    • implemented in Rust 👍
    • cross-platform (even microships ?)
    • might be faster than etherlab's implementation ?
    • in early stage ?

Is my comparison correct ? Or is there other significant differences ?

There seems to be a lot of work done on ethercrab, but the crate is still v0.1. I'm also ready to help fix some eventual issues if needed for my usage.
Do you think ethercrab could be a good choice for my project ?

Storage initialization possibly not zeroing backing store properly

Hi,

I am currently reading a bit of your code because I am curious how it works and I crossed the following line in src/pdu_loop/storage.rs:

// MSRV: Use MaybeUninit::zeroed when `const_maybe_uninit_zeroed` is stabilised.
let frames = UnsafeCell::new(MaybeUninit::uninit());

This ideally wants to initialize the frames slice wrapped in the UnsafeCell and MaybeUninit, but because const_maybe_uninit_zeroed isn't stable yet, you treat it as completely uninitialized NOT ZEROED memory. So far, so good.

A bit further down in line 93, you are attempting to zero the memory of the slice by getting a mutable pointer out of the UnsafeCell and then out of the MaybeUninit and then you are writing ONE zero byte into that location using write_bytes(0u8, 1):

// MSRV: Remove when `const_maybe_uninit_zeroed` is stabilised. Rely on
// `MaybeUninit::zeroed` in `PduStorage::new()`.
unsafe { (&mut *self.frames.get()).as_mut_ptr().write_bytes(0u8, 1) };

Shouldn't that call rather write core::mem::size_of::<[PacketBuffer<DATA>; N]>() zero bytes to entirely zero the backing store? As far as I understand it, MaybeUninit::zeroed() should fill the whole slice with all its elements to zero bytes.

I might be wrong there, but I think these two approaches do different things. I also don't understand what the benefit of just zeroing the first byte of the slice has apart from setting the first byte of a FrameElement in the slice which should be in turn the first byte of a PduFrame which is the index as a u8.

Also, why do you do all this in as_ref() and not in new() when you would call MaybeUninit::zeroed() there anyway later? Why not place that pointer hack in new right now until const_maybe_uninit_zeroed is stable?

Redundant network interface support

EtherCAT supports redundant interfaces where a second NIC is attached to the other end of the network. EtherCrab should support this feature.

Add example showing tx/rx thread performance tricks

I'll need to create a single threaded executor in a thread::spawn so we have complete control over the thread the task runs in.

  • Unix only
  • Set core affinity (probably just to 0 for simplicity) so the thread isn't migrated between cores
  • Test for RT capability and set SCHED_FIFO
    • Set thread priority to 99 (highest for SCHED_FIFO).

Run MIRI on PDU loop, maybe group PDI accesses

https://github.com/rust-lang/miri

The PDU loop and group PDI access performs unsafe concurrent reads/writes into the same slice of data. The writes aren't (or shouldn't be anyway...) overlapping but this is still something that needs some close scrutiny, both with tests in #3 as well as MIRI.

Of note, currently EtherCrab hasn't been tested with threads - only a single-threaded async executor which is hardly concurrent.

This will need some refactoring to split the logic from syscalls as MIRI gets very unhappy about anything that isn't sandboxable but that's a good idea for testability anyway.

Does EK1100 stay in OP?

Since the beginning, my EK1100's Run light has blinked during what I thought was OP, however the spec sheet says:

image

Which seems to imply it is in SAFE-OP. Maybe it enters OP, EtherCrab is happy, then it falls back out of OP. This needs looking into.

Networks with more than one CoE device causes mailbox timeout

Network setup:

  • EK1100
  • EL3204
  • EL6910

OR

  • Leadshine EC400
  • Leadshine EC400

Running this command:

RUST_LOG=debug just linux-example-release ec400 enp2s0

Note: This is looking for an EC400, but the init is generic, and the error occurs during init.

Error received:

[2023-11-17T15:32:01Z ERROR ethercrab::slave] Response mailbox IN error for slave 0x1001: timeout

If I remove the EL6910, all works fine (although the example crashes because it's looking for the EC400). Replacing the EL6910 with a non-CoE device like EL2828 also fixes the mailbox IN error.

Add method to `SlaveGroup` to get all slave statuses at once

This requires internal support for sending multiple PDUs at a time (as many fit in an Ethernet packet) which is a useful feature to have for other purposes.

One option is to add SlaveState::Other(Flags) where Flags is a bitflags struct. This allows both easily checking if every slave in a group is in the same state, as well as collecting different states in some bit flags.

Get `defmt` working properly

Right now, ethercrab just uses log which, whilst supporting no_std, isn't very efficient about it. defmt is already a dependency but is barely integrated into the crate. This should be fixed for better/more performant no_std support.

Split network topology discovery code into unit testable/fuzzable module

Currently it is somewhat coupled with slave device structs, therefore ends up talking to the network. The actual topology algorithm doesn't need any of that, so it can be split out and unit tested properly.

It's reasonably complex code so is a good target for some proper unit tests, and getting to grips with proptest/cargo-fuzz as mentioned in #3.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.