//! A performant, `async`-first EtherCAT MainDevice written in pure Rust. //! //! # Crate features //! //! - `std` (enabled by default) - exposes the [`std`] module, containing helpers to run the TX/RX //! loop on desktop operating systems. //! - `defmt` - enable logging with the [`defmt`](https://docs.rs/defmt) crate. //! - `log` - enable logging with the [`log`](https://docs.rs/log) crate. This is enabled by default //! when the `std` feature is enabled. //! - `serde` - enable `serde` impls for some public items. //! - `xdp` - enable support for XDP on some (currently only Linux) systems. //! //! For `no_std` targets, it is recommended to add this crate with //! //! ```bash //! cargo add --no-default-features --features defmt //! ``` //! //! # Examples //! //! This example increments the output bytes of all detected SubDevices every tick. It is tested on an //! EK1100 with output modules but may work on other basic SubDevices. //! //! Run with e.g. //! //! Linux //! //! ```bash //! RUST_LOG=debug cargo run --example ek1100 --release -- eth0 //! ``` //! //! Windows //! //! ```ps //! $env:RUST_LOG="debug" ; cargo run --example ek1100 --release -- '\Device\NPF_{FF0ACEE6-E8CD-48D5-A399-619CD2340465}' //! ``` //! //! ```rust,no_run //! use env_logger::Env; //! use ethercrab::{ //! error::Error, std::{ethercat_now, tx_rx_task}, MainDevice, MainDeviceConfig, PduStorage, Timeouts //! }; //! use std::{sync::Arc, time::Duration}; //! use tokio::time::MissedTickBehavior; //! //! /// Maximum number of SubDevices that can be stored. This must be a power of 2 greater than 1. //! const MAX_SUBDEVICES: usize = 16; //! /// Maximum PDU data payload size - set this to the max PDI size or higher. //! const MAX_PDU_DATA: usize = 1100; //! /// Maximum number of EtherCAT frames that can be in flight at any one time. //! const MAX_FRAMES: usize = 16; //! /// Maximum total PDI length. //! const PDI_LEN: usize = 64; //! //! static PDU_STORAGE: PduStorage = PduStorage::new(); //! //! #[tokio::main] //! async fn main() -> Result<(), Error> { //! env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); //! //! let interface = std::env::args() //! .nth(1) //! .expect("Provide network interface as first argument."); //! //! log::info!("Starting EK1100 demo..."); //! log::info!("Ensure an EK1100 is the first SubDevice, with any number of modules connected after"); //! log::info!("Run with RUST_LOG=ethercrab=debug or =trace for debug information"); //! //! let (tx, rx, pdu_loop) = PDU_STORAGE.try_split().expect("can only split once"); //! //! let maindevice = Arc::new(MainDevice::new( //! pdu_loop, //! Timeouts { //! wait_loop_delay: Duration::from_millis(2), //! mailbox_response: Duration::from_millis(1000), //! ..Default::default() //! }, //! MainDeviceConfig::default(), //! )); //! //! tokio::spawn(tx_rx_task(&interface, tx, rx).expect("spawn TX/RX task")); //! //! let mut group = maindevice //! .init_single_group::(ethercat_now) //! .await //! .expect("Init"); //! //! log::info!("Discovered {} SubDevices", group.len()); //! //! for subdevice in group.iter(&maindevice) { //! // Special case: if an EL3004 module is discovered, it needs some specific config during //! // init to function properly //! if subdevice.name() == "EL3004" { //! log::info!("Found EL3004. Configuring..."); //! //! subdevice.sdo_write(0x1c12, 0, 0u8).await?; //! subdevice.sdo_write(0x1c13, 0, 0u8).await?; //! //! subdevice.sdo_write(0x1c13, 1, 0x1a00u16).await?; //! subdevice.sdo_write(0x1c13, 2, 0x1a02u16).await?; //! subdevice.sdo_write(0x1c13, 3, 0x1a04u16).await?; //! subdevice.sdo_write(0x1c13, 4, 0x1a06u16).await?; //! subdevice.sdo_write(0x1c13, 0, 4u8).await?; //! } //! } //! //! let mut group = group.into_op(&maindevice).await.expect("PRE-OP -> OP"); //! //! for subdevice in group.iter(&maindevice) { //! let io = subdevice.io_raw(); //! //! log::info!( //! "-> SubDevice {:#06x} {} inputs: {} bytes, outputs: {} bytes", //! subdevice.configured_address(), //! subdevice.name(), //! io.inputs().len(), //! io.outputs().len() //! ); //! } //! //! let mut tick_interval = tokio::time::interval(Duration::from_millis(5)); //! tick_interval.set_missed_tick_behavior(MissedTickBehavior::Skip); //! //! loop { //! group.tx_rx(&maindevice).await.expect("TX/RX"); //! //! // Increment every output byte for every SubDevice by one //! for mut subdevice in group.iter(&maindevice) { //! let mut io = subdevice.io_raw_mut(); //! //! for byte in io.outputs().iter_mut() { //! *byte = byte.wrapping_add(1); //! } //! } //! //! tick_interval.tick().await; //! } //! } //! ``` #![cfg_attr(not(feature = "std"), no_std)] #![deny(missing_docs)] #![deny(missing_copy_implementations)] #![deny(trivial_casts)] #![deny(trivial_numeric_casts)] #![deny(unused_import_braces)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] #![deny(clippy::missing_safety_doc)] #![allow(clippy::uninlined_format_args)] #![doc( html_logo_url = "https://raw.githubusercontent.com/ethercrab-rs/ethercrab/5185a914f49abad8b0f71a1b3b689500077e7c2b/ethercrab-logo-docsrs.svg" )] #![doc( html_favicon_url = "https://raw.githubusercontent.com/ethercrab-rs/ethercrab/5185a914f49abad8b0f71a1b3b689500077e7c2b/ethercrab-logo-docsrs.svg" )] // MUST go first so everything else can see the macros inside pub(crate) mod fmt; mod al_control; mod al_status_code; mod base_data_types; mod command; mod dc; mod dl_status; mod eeprom; pub mod error; mod ethernet; mod fmmu; mod generate; mod mailbox; mod maindevice; mod maindevice_config; mod pdi; mod pdu_loop; mod register; mod subdevice; pub mod subdevice_group; mod subdevice_state; mod sync_manager_channel; mod timer_factory; mod vendors; #[cfg(feature = "std")] pub mod std; pub use al_status_code::AlStatusCode; pub use command::{Command, Reads, WrappedRead, WrappedWrite, Writes}; pub use ethercrab_wire::{ EtherCrabWireRead, EtherCrabWireReadSized, EtherCrabWireReadWrite, EtherCrabWireSized, EtherCrabWireWrite, EtherCrabWireWriteSized, }; use ethernet::EthernetAddress; pub use mailbox::coe::SubIndex; pub use maindevice::MainDevice; pub use maindevice_config::{MainDeviceConfig, RetryBehaviour}; pub use pdu_loop::{PduLoop, PduRx, PduStorage, PduTx, ReceiveAction, SendableFrame}; pub use register::{DcSupport, RegisterAddress}; pub use subdevice::{ DcSync, ObjectDescriptionListQuery, ObjectDescriptionListQueryCounts, SubDevice, SubDeviceIdentity, SubDevicePdi, SubDeviceRef, }; pub use subdevice_group::{GroupId, SubDeviceGroup, SubDeviceGroupHandle, TxRxResponse}; pub use subdevice_state::SubDeviceState; pub use timer_factory::Timeouts; const LEN_MASK: u16 = 0b0000_0111_1111_1111; const ETHERCAT_ETHERTYPE: u16 = 0x88a4; const MAINDEVICE_ADDR: EthernetAddress = EthernetAddress([0x10, 0x10, 0x10, 0x10, 0x10, 0x10]); /// Starting address for discovered subdevices. const BASE_SUBDEVICE_ADDRESS: u16 = 0x1000; /// The default lock provided by EtherCrab. /// /// This is a [`spin::RwLock`] containing [`spin::Yield`] when the `std` feature is enabled. For /// non-`std` environments, it is [`spin::Spin`]. #[cfg(feature = "std")] pub type DefaultLock = spin::rwlock::RwLock<(), spin::Yield>; /// The default lock provided by EtherCrab. /// /// This is a [`spin::RwLock`] containing [`spin::Yield`] when the `std` feature is enabled. For /// non-`std` environments, it is [`spin::Spin`]. #[cfg(not(feature = "std"))] pub type DefaultLock = spin::rwlock::RwLock<(), spin::Spin>; #[allow(unused)] fn test_logger() { #[cfg(all(not(miri), test))] let _ = env_logger::builder().is_test(true).try_init(); #[cfg(all(miri, test))] let _ = simple_logger::init_with_level(log::Level::Debug); }