Struct EipClient

Source
pub struct EipClient { /* private fields */ }
Expand description

High-performance EtherNet/IP client for PLC communication

This struct provides the core functionality for communicating with Allen-Bradley PLCs using the EtherNet/IP protocol. It handles connection management, session registration, and tag operations.

§Thread Safety

The EipClient is NOT thread-safe. For multi-threaded applications:

use std::sync::Arc;
use tokio::sync::Mutex;
use rust_ethernet_ip::EipClient;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // Create a thread-safe wrapper
    let client = Arc::new(Mutex::new(EipClient::connect("192.168.1.100:44818").await?));
 
    // Use in multiple threads
    let client_clone = client.clone();
    tokio::spawn(async move {
        let mut client = client_clone.lock().await;
        let _ = client.read_tag("Tag1").await?;
        Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
    });
    Ok(())
}

§Performance Characteristics

OperationLatencyThroughputMemory
Connect100-500msN/A~8KB
Read Tag1-5ms1,500+ ops/sec~2KB
Write Tag2-10ms600+ ops/sec~2KB
Batch Read5-20ms2,000+ ops/sec~4KB

§Error Handling

All operations return Result<T, EtherNetIpError>. Common errors include:

use rust_ethernet_ip::{EipClient, EtherNetIpError};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
    match client.read_tag("Tag1").await {
        Ok(value) => println!("Tag value: {:?}", value),
        Err(EtherNetIpError::Protocol(_)) => println!("Tag does not exist"),
        Err(EtherNetIpError::Connection(_)) => println!("Lost connection to PLC"),
        Err(EtherNetIpError::Timeout(_)) => println!("Operation timed out"),
        Err(e) => println!("Other error: {}", e),
    }
    Ok(())
}

§Examples

Basic usage:

use rust_ethernet_ip::{EipClient, PlcValue};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
 
    // Read a boolean tag
    let motor_running = client.read_tag("MotorRunning").await?;
 
    // Write an integer tag
    client.write_tag("SetPoint", PlcValue::Dint(1500)).await?;
 
    // Read multiple tags in sequence
    let tag1 = client.read_tag("Tag1").await?;
    let tag2 = client.read_tag("Tag2").await?;
    let tag3 = client.read_tag("Tag3").await?;
    Ok(())
}

Advanced usage with error recovery:

use rust_ethernet_ip::{EipClient, PlcValue, EtherNetIpError};
use tokio::time::Duration;
 
async fn read_with_retry(client: &mut EipClient, tag: &str, retries: u32) -> Result<PlcValue, EtherNetIpError> {
    for attempt in 0..retries {
        match client.read_tag(tag).await {
            Ok(value) => return Ok(value),
            Err(EtherNetIpError::Connection(_)) => {
                if attempt < retries - 1 {
                    tokio::time::sleep(Duration::from_secs(1)).await;
                    continue;
                }
            }
            Err(e) => return Err(e),
        }
    }
    Err(EtherNetIpError::Protocol("Max retries exceeded".to_string()))
}

Implementations§

Source§

impl EipClient

Source

pub async fn new(addr: &str) -> Result<Self>

Source

pub async fn connect(addr: &str) -> Result<Self>

Public async connect function for EipClient

Source

pub fn set_max_packet_size(&mut self, size: u32)

Sets the maximum packet size for communication

Source

pub async fn discover_tags(&mut self) -> Result<()>

Discovers all tags in the PLC

Source

pub async fn get_tag_metadata(&self, tag_name: &str) -> Option<TagMetadata>

Gets metadata for a tag

Source

pub async fn read_tag(&mut self, tag_name: &str) -> Result<PlcValue>

Reads a tag value from the PLC

This function performs a CIP read request for the specified tag. The tag’s data type is automatically determined from the PLC’s response.

§Arguments
  • tag_name - The name of the tag to read
§Returns

The tag’s value as a PlcValue enum

§Examples
use rust_ethernet_ip::{EipClient, PlcValue};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
     
    // Read different data types
    let bool_val = client.read_tag("MotorRunning").await?;
    let int_val = client.read_tag("Counter").await?;
    let real_val = client.read_tag("Temperature").await?;
     
    // Handle the result
    match bool_val {
        PlcValue::Bool(true) => println!("Motor is running"),
        PlcValue::Bool(false) => println!("Motor is stopped"),
        _ => println!("Unexpected data type"),
    }
    Ok(())
}
§Performance
  • Latency: 1-5ms typical
  • Throughput: 1,500+ ops/sec
  • Network: 1 request/response cycle
§Error Handling

Common errors:

  • Protocol: Tag doesn’t exist or invalid format
  • Connection: Lost connection to PLC
  • Timeout: Operation timed out
Source

pub async fn write_tag(&mut self, tag_name: &str, value: PlcValue) -> Result<()>

Writes a value to a PLC tag

This method automatically determines the best communication method based on the data type:

  • STRING values use unconnected explicit messaging with proper AB STRING format
  • Other data types use standard unconnected messaging
§Arguments
  • tag_name - The name of the tag to write to
  • value - The value to write
§Example
use rust_ethernet_ip::PlcValue;
 
client.write_tag("Counter", PlcValue::Dint(42)).await?;
client.write_tag("Message", PlcValue::String("Hello PLC".to_string())).await?;
Source

pub fn build_list_tags_request(&self) -> Vec<u8>

Source

pub async fn check_health(&self) -> bool

Checks the health of the connection

Source

pub async fn check_health_detailed(&mut self) -> Result<bool>

Performs a more thorough health check by actually communicating with the PLC

Source

pub async fn send_cip_request(&self, cip_request: &[u8]) -> Result<Vec<u8>>

Sends a CIP request wrapped in EtherNet/IP SendRRData command

Source

pub async fn unregister_session(&mut self) -> Result<()>

Unregisters the EtherNet/IP session with the PLC

Source

pub async fn execute_batch( &mut self, operations: &[BatchOperation], ) -> Result<Vec<BatchResult>>

Executes a batch of read and write operations

This is the main entry point for batch operations. It takes a slice of BatchOperation items and executes them efficiently by grouping them into optimal CIP packets based on the current BatchConfig.

§Arguments
  • operations - A slice of operations to execute
§Returns

A vector of BatchResult items, one for each input operation. Results are returned in the same order as the input operations.

§Performance
  • Throughput: 5,000-15,000+ operations/second (vs 1,500 individual)
  • Latency: 5-20ms per batch (vs 1-3ms per individual operation)
  • Network efficiency: 1-5 packets vs N packets for N operations
§Examples
use rust_ethernet_ip::{EipClient, BatchOperation, PlcValue};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
     
    let operations = vec![
        BatchOperation::Read { tag_name: "Motor1_Speed".to_string() },
        BatchOperation::Read { tag_name: "Motor2_Speed".to_string() },
        BatchOperation::Write { 
            tag_name: "SetPoint".to_string(), 
            value: PlcValue::Dint(1500) 
        },
    ];
     
    let results = client.execute_batch(&operations).await?;
     
    for result in results {
        match result.result {
            Ok(Some(value)) => println!("Read value: {:?}", value),
            Ok(None) => println!("Write successful"),
            Err(e) => println!("Operation failed: {}", e),
        }
    }
     
    Ok(())
}
Source

pub async fn read_tags_batch( &mut self, tag_names: &[&str], ) -> Result<Vec<(String, Result<PlcValue, BatchError>)>>

Reads multiple tags in a single batch operation

This is a convenience method for read-only batch operations. It’s optimized for reading many tags at once.

§Arguments
  • tag_names - A slice of tag names to read
§Returns

A vector of tuples containing (tag_name, result) pairs

§Examples
use rust_ethernet_ip::EipClient;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
     
    let tags = ["Motor1_Speed", "Motor2_Speed", "Temperature", "Pressure"];
    let results = client.read_tags_batch(&tags).await?;
     
    for (tag_name, result) in results {
        match result {
            Ok(value) => println!("{}: {:?}", tag_name, value),
            Err(e) => println!("{}: Error - {}", tag_name, e),
        }
    }
     
    Ok(())
}
Source

pub async fn write_tags_batch( &mut self, tag_values: &[(&str, PlcValue)], ) -> Result<Vec<(String, Result<(), BatchError>)>>

Writes multiple tag values in a single batch operation

This is a convenience method for write-only batch operations. It’s optimized for writing many values at once.

§Arguments
  • tag_values - A slice of (tag_name, value) tuples to write
§Returns

A vector of tuples containing (tag_name, result) pairs

§Examples
use rust_ethernet_ip::{EipClient, PlcValue};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
     
    let writes = vec![
        ("SetPoint1", PlcValue::Bool(true)),
        ("SetPoint2", PlcValue::Dint(2000)),
        ("EnableFlag", PlcValue::Bool(true)),
    ];
     
    let results = client.write_tags_batch(&writes).await?;
     
    for (tag_name, result) in results {
        match result {
            Ok(_) => println!("{}: Write successful", tag_name),
            Err(e) => println!("{}: Write failed - {}", tag_name, e),
        }
    }
     
    Ok(())
}
Source

pub fn configure_batch_operations(&mut self, config: BatchConfig)

Configures batch operation settings

This method allows fine-tuning of batch operation behavior, including performance optimizations and error handling.

§Arguments
  • config - The new batch configuration to use
§Examples
use rust_ethernet_ip::{EipClient, BatchConfig};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut client = EipClient::connect("192.168.1.100:44818").await?;
     
    let config = BatchConfig {
        max_operations_per_packet: 50,
        max_packet_size: 1500,
        packet_timeout_ms: 5000,
        continue_on_error: false,
        optimize_packet_packing: true,
    };
     
    client.configure_batch_operations(config);
     
    Ok(())
}
Source

pub fn get_batch_config(&self) -> &BatchConfig

Gets current batch operation configuration

Source

pub async fn write_ab_string_components( &mut self, tag_name: &str, value: &str, ) -> Result<()>

Writes a string value using Allen-Bradley UDT component access This writes to TestString.LEN and TestString.DATA separately

Source

pub async fn write_ab_string_udt( &mut self, tag_name: &str, value: &str, ) -> Result<()>

Writes a string using a single UDT write with proper AB STRING format

Source

pub async fn write_string_connected( &mut self, tag_name: &str, value: &str, ) -> Result<()>

Writes a string using connected explicit messaging

Source

pub async fn write_string_unconnected( &mut self, tag_name: &str, value: &str, ) -> Result<()>

Writes a string using unconnected explicit messaging with proper AB STRING format

This method uses standard unconnected messaging instead of connected messaging and implements the proper Allen-Bradley STRING structure as described in the provided information about Len, MaxLen, and Data[82] format.

Source

pub async fn write_string(&mut self, tag_name: &str, value: &str) -> Result<()>

Write a string value to a PLC tag using unconnected messaging

§Arguments
  • tag_name - The name of the tag to write to
  • value - The string value to write (max 82 characters)
§Returns
  • Ok(()) if the write was successful
  • Err(EtherNetIpError) if the write failed
§Errors
  • StringTooLong - If the string is longer than 82 characters
  • InvalidString - If the string contains invalid characters
  • TagNotFound - If the tag doesn’t exist
  • WriteError - If the write operation fails
Source

pub async fn subscribe_to_tag( &self, tag_path: &str, options: SubscriptionOptions, ) -> Result<()>

Subscribes to a tag for real-time updates

Source

pub async fn subscribe_to_tags( &self, tags: &[(&str, SubscriptionOptions)], ) -> Result<()>

Trait Implementations§

Source§

impl Clone for EipClient

Source§

fn clone(&self) -> EipClient

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EipClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Ungil for T
where T: Send,