ethercat-devices (0.1.4)
Published 2026-06-12 12:29:11 +00:00 by madsludvig
Installation
[registry]
default = "forgejo"
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add ethercat-devices@0.1.4About this package
Typed catalog of EtherCAT terminals + declarative topology macro on top of ethercrab.
ethercat-devices
Typed EtherCAT bus access on top of ethercrab,
plus a topology! proc-macro: declare the physical chain and name the bits you
care about, get a typed struct back — no string lookups or channel numbers in
application code.
On connect() every declared slot is validated against the hardware
(vendor/product id, position by position), so a miswired bus fails loudly at
startup instead of silently driving the wrong terminal.
Example
use ethercat_devices::{Ek1100, El1012, El2002, El7031, el7031, topology};
topology! {
pub struct MyBus;
Ek1100; // declared + validated, no accessor
El1012 { 0 => photocell, 1 => part_present }; // named input bits
El2002 { 0 => gripper, 1 => lamp }; // named output bits
El7031 => stepper; // typed handle: bus.stepper()
skip(8); // unknown terminal, 8 PDI bytes
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let bus = MyBus::connect("enp7s0").await?;
// PRE-OP: per-device configuration through the slot field.
bus.bus()
.configure_stepper(bus.stepper, &el7031::Config::default())
.await?;
let bus = bus.into_op().await?;
loop {
bus.tx_rx().await?; // one process-data exchange
if bus.photocell().read() { // input bit
bus.gripper().write(true); // output bit
bus.stepper().command(|c| { // specialised handle
c.set_enable(true);
c.set_velocity(1800);
});
}
}
}
Everything static is checked at compile time:
bus.photocell().write(true)— doesn't compile: the EL1012 is an input terminal (.writeonly exists forDigitalOutDevices).El1012 { 5 => foo }— doesn't compile when used: bit 5 is out of range for a 2-channel terminal.- A typo'd signal name is a plain missing-method error.
Forms
| Line | Meaning |
|---|---|
Device; |
On the chain, identity-validated, no accessor. |
Device { N => name, ... }; |
One bus.name() per bit, returning a BitRef with .read() / .write(bool) per the device's direction. |
Device => name; |
Typed per-cycle handle via the HasHandle trait (e.g. the EL7031 stepper). |
skip(N); |
Unknown/unsupported terminal: no identity check, but its PDI must total N bytes (verified at OP transition). |
In OP the generated struct also exposes park() — zero every output image on
the bus (all bits low, motion terminals disabled) as the one-call safe state.
Adding a device
No macro edits needed:
- New digital terminal → marker type +
impl Device(identity) +impl DigitalInDevice/impl DigitalOutDevice(channel count). - New specialised terminal →
impl HasHandlemapping it to a per-cycle handle type.
Workspace
.— device catalog and bus (ethercat-devices)macros/— thetopology!proc-macro (ethercat-devices-macros)
EtherCAT uses raw Ethernet frames, so binaries need
sudo setcap cap_net_raw,cap_net_admin=eip <binary>.
Dependencies
| ID | Version |
|---|---|
| anyhow | ^1 |
| ethercat-devices-macros | =0.1.4 |
| ethercrab | ^0.7.1 |
| log | ^0.4 |
| tokio | ^1 |