ethercrab-rs / ethercrab Goto Github PK
View Code? Open in Web Editor NEWEtherCAT master written in pure Rust
EtherCAT master written in pure Rust
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.
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:
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<()>
}
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<()>
}
/// 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);
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)
}
// 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);
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
Currently EtherCrab just times out when no devices are on the network. This should fail gracefully with a device count of 0.
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)
The current Embassy example only uses a single group which doesn't ensure EtherCrab can run multiple groups in embedded.
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>,
}
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.
Send
bound on HookFuture
(the type returned by the PRE-OP/SAFE-OP hook function passed to a SlaveGroup
)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.
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.
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.
@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
, etcThis would be a breaking change.
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!
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.
Confirm/check that:
FrameElement
slot is made available again.FrameElement
is made available again.EtherCAT supports redundant interfaces where a second NIC is attached to the other end of the network. EtherCrab should support this feature.
A subset for now:
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?).
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.
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:
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.
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.
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.
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!
Since the beginning, my EK1100's Run
light has blinked during what I thought was OP
, however the spec sheet says:
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.
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
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.
Hi! Thanks for opening an issue. Please provide some specific details to make it easier to debug any
problems.
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
I can't quite understand how TxPdos are ordered in SlaveRef.inputs_raw
. Take the EL1809 ESI file description for input channels 1 and 2
<TxPdo Fixed="1" Mandatory="1" Sm="0">
<Index>#x1a00</Index>
<Name>Channel 1</Name>
<Entry>
<Index>#x6000</Index>
<SubIndex>1</SubIndex>
<BitLen>1</BitLen>
<Name>Input</Name>
<DataType>BOOL</DataType>
</Entry>
</TxPdo>
<TxPdo Fixed="1" Mandatory="1" Sm="0">
<Index>#x1a01</Index>
<Name>Channel 2</Name>
<Entry>
<Index>#x6010</Index>
<SubIndex>1</SubIndex>
<BitLen>1</BitLen>
<Name>Input</Name>
<DataType>BOOL</DataType>
</Entry>
</TxPdo>
Their entries are in addresses 0x6000
and 0x6010
. How come they are one bit apart in the SlaveRef.inputs_raw
bytes?
SlaveRef.inputs_raw[0] // 0b00000001 -> Channel 1
SlaveRef.inputs_raw[0] // 0b00000010 -> Channel 2
What am i missing? Does ethercrab provide some way to acces entries via their index and subindexes?
Look into using:
To check the reliability and safety of the PDU loop machinery in terms of creating and/or parsing of PDU packets.
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 ?
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.
0
for simplicity) so the thread isn't migrated between coresSCHED_FIFO
SCHED_FIFO
).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.
Any idea ?
Network setup:
OR
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.
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.
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
ethercrab
due to frequent calls to ioctl ?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 ?
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?
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.
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
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?
The replay harness needs to be a bit smarter now as now that #114 has landed it's possible to not have pure TX/RX/TX/RX packet flow.
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.
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.
Terrible description - more of a note to self.
It's possible to hold multiple mutable references to the same slave's outputs PDI.
Also, the PDI is written to by SlaveGroup::tx_rx()
which provides the potential for race conditions.
A way around this might be to use closures in the API more, or make more methods &mut
on various things.
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)
// 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.
In writing https://github.com/ethercrab-rs/dump-analyser, I need to parse PDUs. Right now I've just copypasta'd the code, but it could be useful to expose at least the ability to parse a PDU in the public API.
Would need work to refactor the types to be cleaner and more friendly.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.