# Darra EtherCAT — Rust SDK

Rust 1.70+ 平台的 Darra EtherCAT Master 封装. 安全的 Rust API, FFI 绑定 `Darra.Core.dll` (Windows) / `libDarraCore.so` (Linux), 对齐 C#/Java/Python 语义.

## 安装

### crates.io

```toml
[dependencies]
darra-ethercat = "0.4"
```

### 可选 feature

```toml
darra-ethercat = { version = "0.4", features = ["async-tokio", "redundancy", "udp", "wdk"] }
```

| Feature | 作用 |
|---------|------|
| `async-tokio` | 启用 `*_async` 方法 (基于 `tokio::task::spawn_blocking`) |
| `redundancy` | 双网口 Cable Redundancy 支持 |
| `udp` | UDP 传输层 (非 Raw Ethernet) |
| `wdk` | WDK 内核 PDO Offload (仅 Windows) |

默认无 feature: 纯同步 + Raw Ethernet.

### 系统依赖

- Windows: 需 `Darra.Core.dll` 在可执行目录 / `PATH`
- Linux: 需 `libDarraCore.so` 在 `LD_LIBRARY_PATH`

## 快速 Hello World

```rust
use ethercat::{EtherCATMaster, EcState};
use ethercat::statics::network;

fn main() -> ethercat::Result<()> {
    // 1. 扫描网卡
    let adapters = network::get_network_info(true)?;
    for nic in &adapters {
        println!("{}  slaves={}", nic.description, nic.slave_count);
    }

    let target = adapters.iter().find(|n| n.slave_count > 0)
        .ok_or_else(|| ethercat::DarraError::Other("未找到带从站的网卡".into()))?;

    // 2. 构建主站 (builder)
    let master = EtherCATMaster::builder()
        .set_network(&target.name, "")
        .enable_auto_startup()
        .build()?;

    // 3. 扫描从站
    println!("主站 #{} 发现从站 {} 台",
        master.master_number(), master.slave_count());
    for s in master.slaves() {
        println!("  [{}] {}  State={:?}", s.index(), s.name(), s.state());
    }

    // 4. 进入 OP
    master.set_state_sequence(EcState::Operational, 10_000)?;

    // 5. 读取第一个从站 Statusword
    if master.slave_count() > 0 {
        let slave = master.slave(1);  // 1-based
        let data = slave.coe().sdo_read(0x6041, 0x00, false)?;
        let sw = u16::from_le_bytes([data[0], data[1]]);
        println!("Statusword=0x{:04X}", sw);
    }

    // 6. 退出 (master drop 时自动释放)
    master.set_state(EcState::Init)?;
    Ok(())
}
```

## API 要点

- **入口**: `EtherCATMaster::new()` / `EtherCATMaster::builder().set_network(...).build()` / `EtherCATMaster::from_json_file("config.xml")`
- **从站**: `master.slave(idx)` (1-based) / `master.slaves()` (Vec) / `master.slaves_in_group(g)`
- **协议实例**: `slave.coe()` / `.foe()` / `.soe()` / `.eoe()` / `.aoe()` / `.voe()` / `.fsoe()` / `.dc()` / `.pdo()`
- **诊断**: `master.diagnostics()`
- **状态机**: `master.set_state_sequence(EcState::Operational, 10_000)` 或 `master.set_state(target)`
- **错误**: `Result<T, DarraError>`, `DarraError` 携带 ETG abort code

## 异步模型

启用 `async-tokio` feature:

```toml
darra-ethercat = { version = "0.4", features = ["async-tokio"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

```rust
#[tokio::main]
async fn main() -> Result<()> {
    let data = slave.coe().sdo_read_async(0x6041, 0x00, false).await?;
    slave.coe().sdo_write_async(0x6040, 0x00, false, &[0x0F, 0x00]).await?;

    // 并发
    let (s1, s2, s3) = tokio::join!(
        slave.coe().sdo_read_async(0x6041, 0x00, false),
        slave.coe().sdo_read_async(0x6064, 0x00, false),
        slave.coe().sdo_read_async(0x606C, 0x00, false),
    );
    Ok(())
}
```

**未启用 async-tokio** 时, 使用 `*_blocking` 版本 (基于 `std::thread`):

```rust
let handle = slave.coe().sdo_read_blocking(0, 1, 0x6041, 0x00);
let data = handle.join().unwrap()?;  // JoinHandle
```

## 与 C# 对齐

| C# | Rust |
|---|---|
| `DarraEtherCAT` | `EtherCATMaster` |
| `SDORead(0x6041, 0x00)` | `sdo_read(0x6041, 0x00, false)` |
| `SetState(EcState.OP)` | `set_state_sequence(EcState::Operational, 10_000)` |
| `slave.CoE.LastSdoError` | `slave.coe().last_error()` |
| `master.Dispose()` | `drop(master)` (自动) |

命名风格: Rust `snake_case` (类型名 `PascalCase`).

## 错误处理

```rust
use ethercat::{DarraError, SDOError};

match slave.coe().sdo_read(0x6041, 0x00, false) {
    Ok(data) => { /* use data */ }
    Err(DarraError::Sdo(SDOError::AccessDenied)) => { /* ETG 0x06010000 */ }
    Err(DarraError::Timeout) => { /* 网络超时 */ }
    Err(e) => eprintln!("SDO 失败: {}", e),
}
```

`DarraError` 实现 `thiserror::Error`, 可与 `anyhow` / `eyre` 无缝集成.

## 日志

SDK 通过 DLL callback 上报日志, 默认写到 `stderr`. 可接入 `log` / `tracing`:

```rust
use ethercat::logging;
logging::set_callback(|level, module, msg| {
    tracing::info!(target: module, "[{}] {}", level, msg);
});
```

## 协议支持

以 C# SDK 为对齐基准, 通过 `extern "C"` FFI 直调 Darra Core DLL. 详细对比见 [`../SDK_ALIGNMENT.md`](../SDK_ALIGNMENT.md).

| 协议 / 能力 | 状态 | 说明 |
|------|------|------|
| CoE | ✓ 完整 | SDO `sdo_read`/`sdo_write`, async-tokio, PDO, OD, CiA402 |
| EoE | ✓ 完整 | IP/MAC 配置 |
| FoE | ✓ 完整 | 上下载, Cancel, 进度回调 |
| SoE | ✓ 完整 | IDN 读写 |
| AoE | ✓ 完整 | ADS 透传 |
| VoE | ✓ 完整 | 厂商邮箱透传 |
| FSoE | ✓ 完整 | SIL3, 多连接 |
| DC | ✓ 完整 | sync0/sync1, drift 补偿 |
| Cable Redundancy | ✓ 完整 | feature `redundancy` 启用, 双网口 failover |
| Hot-Connect | ✓ 完整 | 热插拔 |
| Mailbox Gateway | ✓ 完整 | ETG.8200 TCP 网关 |
| ESI 解析 | ✓ 完整 | XML 自动加载 |
| ENI 配置 | ✓ 完整 | ETG.1510 ENI init |
| ETG.1510 Diagnostics | ✓ 完整 | 0x10F3 历史 + 聚合状态 + Emergency Recorder `tokio::Stream` 适配 (`master.emergency_stream()`) |

**对齐度: 100% vs C#** (Phase 4 后, `tokio::Stream` 适配在 managed 层实装).

## 字节级一致性保证

Rust SDK 通过 `extern "C"` FFI 转发到 `Darra.Core.dll`, 与 C# 基准做字节级对齐:

- **CRC16 (FSoE)**: `ethercat::fsoe::crc16(data: &[u8]) -> u16` 直调 C ABI, byte-by-byte 与 C# 输出一致
- **IDN 编码 (SoE)**: `SoeIdn::encode(set, type_, block) -> u16` 与 C# `SoeIdn.Encode()` 同值, `to_le_bytes()` 与线缆字节序一致
- **枚举值**: `#[repr(u8)] enum EcState { Init = 0x01, ..., Operational = 0x08 }` 与 C/C#/Java/Python 同值, `bytemuck::Pod` 派生保证内存布局
- **结构体**: `#[repr(C, packed)]` 与 C `#pragma pack(1)` / C# `Pack=1` 字段偏移一致
- **`&[u8]` 语义**: SDO 数据按 EtherCAT 帧字节序透传, FFI 边界不做字节交换 (利用 `Pin<&[u8]>` 防止移动)
- **`Result<T, DarraError>`**: 错误码值与 C `EC_ERR_*` / C# `EtgAbortCode` 同值, `From<i32>` 转换无信息损失

字节级一致性由 `Darra_EtherCAT_SDKTest_Rust` 测试套件 + `cargo test` 与 C# 基准每次 CI 比对.

## 已知限制

下列能力在 `Darra.Core.dll` 中未导出 native symbol, Rust SDK 在 managed (safe Rust) 层提供 fallback, 用户 API 不变:

- **ENI 回写** (`master.export_eni(path)`): DLL 无 export, fallback 用 `quick-xml` 序列化 `MasterConfig` 重建 ENI XML
- **Emergency Recorder 历史深度**: DLL 默认 256 项, fallback 用 `VecDeque<EmergencyEntry>` 容量可配
- **Diagnostics 变化推送**: DLL 仅支持轮询, fallback 用 `tokio::time::interval` + `tokio::sync::watch` 推送, 暴露为 `impl Stream<Item = DiagnosticsChange>`
- **MailboxGateway 会话上限**: DLL 默认 16 路, fallback 用 `tokio::sync::Semaphore` 限流

> Rust fallback 全部 safe Rust 实现, 不引入 `unsafe`, 字节输出与 native 一致. 启用 `async-tokio` feature 时延迟 < 100 ms, 可通过 `MasterBuilder::poll_interval(Duration)` 调整.

## 如何升级到完整功能

DLL 端补以下 export 后, FFI 绑定切换为 native 路径, managed fallback 自动停用 (通过 `cfg!(feature = "native-<x>")` 切换):

| 待补 export | Rust SDK 受益 |
|-------------|---------------|
| `Master_ExportENI` | `master.export_eni()` 走 native, 移除 `quick-xml` 依赖 |
| `EmergencyRecorder_SetMaxHistory` | 历史下沉 DLL, 释放 `VecDeque` heap |
| `Diagnostics_RegisterChangeCallback` | `extern "C" fn` 回调推送, 延迟从 100 ms 降至 < 1 ms (注意 `Send + Sync`) |
| `MailboxGateway_SetSessionLimit` | DLL 限流, 移除 `tokio::sync::Semaphore` |

切换无需修改用户代码: `build.rs` 探测 native symbol, 自动启用 `cfg(native_<feature>)`, fallback 模块由 `cfg(not(native_<feature>))` 编译.

## 相关文档

- [SDK 对齐对比](../SDK_ALIGNMENT.md)
- [统一 API 参考](../docs/api-reference.md)
- [7 邮箱协议](../docs/mailbox-protocols.md)
- [快速入门](../docs/quick-start.md)
- [docs.rs](https://docs.rs/darra-ethercat) (rustdoc 自动生成)

## License

Proprietary — Darra Technology.
