ethercat-linux/vendor/ethercrab/src/lib.rs

237 lines
8.1 KiB
Rust
Raw Normal View History

//! 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<MAX_FRAMES, MAX_PDU_DATA> = 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::<MAX_SUBDEVICES, PDI_LEN>(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);
}