属性与状态机
属性
| 类别 | 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 基本信息 | MasterIndex | short (public final) | 只读 | 主站编号(构造后不变) |
| MasterNumber() | short | 只读 | 主站编号(MasterIndex 别名) | |
| SlaveCount() | int | 只读 | 网络中检测到的从站数量 | |
| Slaves() | List<Slave> | 只读 | 从站列表(不可修改) | |
| IsDisposed() | boolean | 只读 | 主站是否已被释放 | |
| IsStarted() | boolean | 只读 | PDO 循环是否已启动 | |
| 网络与链路 | PrimaryNicName() | String | 只读 | 主网卡名称 |
| RedundantNicName() | String | 只读 | 冗余网卡名称 | |
| LinkStatus() | EcLinkState | 只读 | 网络链路状态 | |
| LinkState() | EcLinkState | 只读 | 链路状态枚举值 | |
| 错误码 | ErrorCode() | EcALState | 只读 | 异常时的自动报警错误码(AL Status Code) |
| ErrorCnt() | int | 只读 | 错误计数 | |
| 从站分组 | ActiveGroupCount() | byte | 只读 | 活跃组数量(映射后有效) |
| IO 映射 | Obytes() / Ibytes() | int | 只读 | 总输出/输入字节数 |
| Obits() / Ibits() | short | 只读 | 总输出/输入位数 | |
| DC | HasDC() | boolean | 只读 | 网络中是否有支持 DC 的从站 |
| DCtime() | long | 只读 | DC 时间戳(纳秒) |
EcLinkState 枚举
public enum EcLinkState {
DISCONNECTED(0), // 无连接
CONNECTED(1), // 主网口和冗余网口都连接
PRIMARY_ONLY(3), // 仅主网口连接
SECONDARY_ONLY(4); // 仅冗余网口连接
}
EcALState 枚举
public enum EcALState {
NO_ERROR(0x0000), // 无错误
UNSPECIFIED_ERROR(0x0001), // 未指定错误
NO_MEMORY(0x0002), // 内存不足
INVALID_DEVICE_SETUP(0x0003), // 无效设备设置
INVALID_REVISION(0x0004), // 版本不匹配
SII_EEPROM_MISMATCH(0x0006), // SII/EEPROM 不匹配
FIRMWARE_UPDATE_FAILED(0x0007), // 固件更新失败
LICENSE_ERROR(0x000E), // 许可证错误
INVALID_STATE_CHANGE(0x0011), // 无效状态切换
UNKNOWN_REQUESTED_STATE(0x0012), // 未知状态请求
BOOTSTRAP_NOT_SUPPORTED(0x0013), // 不支持 Bootstrap
NO_VALID_FIRMWARE(0x0014), // 无有效固件
INVALID_MAILBOX_CONFIG(0x0015), // 无效邮箱配置 (SM0/SM1)
INVALID_SYNC_MANAGER_CONFIG(0x0017), // 无效同步管理器配置
NO_VALID_INPUTS(0x0018), // 无有效输入
NO_VALID_OUTPUTS(0x0019), // 无有效输出
SYNC_ERROR(0x001A), // 同步错误
SYNC_MANAGER_WATCHDOG(0x001B), // SM 看门狗
INVALID_OUTPUT_CONFIG(0x001D), // 无效输出配置
INVALID_INPUT_CONFIG(0x001E), // 无效输入配置
INVALID_WATCHDOG_CONFIG(0x001F), // 无效看门狗配置
NEEDS_COLD_START(0x0020), // 需要冷启动
NEEDS_INIT(0x0021), // 需要 Init
NEEDS_PREOP(0x0022), // 需要 PreOp
NEEDS_SAFEOP(0x0023), // 需要 SafeOp
INVALID_INPUT_MAPPING(0x0024), // 无效输入映射
INVALID_OUTPUT_MAPPING(0x0025), // 无效输出映射
INCONSISTENT_SETTINGS(0x0026), // 设置不一致
FREERUN_NOT_SUPPORTED(0x0027), // 不支持自由运行
SYNC_NOT_SUPPORTED(0x0028), // 不支持同步
CYCLE_TIME_TOO_SMALL(0x002E), // 周期时间过小
INVALID_DC_SYNC_CONFIG(0x0030), // 无效 DC 同步配置
PLL_ERROR(0x0032), // PLL 错误
DC_SYNC_IO_ERROR(0x0033), // DC 同步 IO 错误
DC_SYNC_TIMEOUT(0x0034), // DC 同步超时
MAILBOX_AOE(0x0041), // AoE 邮箱错误
MAILBOX_COE(0x0043), // CoE 邮箱错误
MAILBOX_FOE(0x0044), // FoE 邮箱错误
MAILBOX_SOE(0x0045), // SoE 邮箱错误
EEPROM_NO_ACCESS(0x0050), // EEPROM 无法访问
EEPROM_ERROR(0x0051), // EEPROM 错误
EXTERNAL_HARDWARE_NOT_READY(0x0052), // 外部硬件未就绪
SLAVE_RESTARTED(0x0060), // 从站已重启
UNKNOWN_ERROR(0xFFFF); // 未知错误
}
状态机管理
State()
EtherCAT 当前状态 (只读)。切换状态请调用 setState(...)。
相关结构:
public enum EcState {
NONE(0x00), // 无状态
INIT(0x01), // 初始化
PRE_OP(0x02), // 预操作
BOOT(0x03), // 引导
SAFE_OP(0x04), // 安全操作
OP(0x08), // 运行
ACK(0x10), // 确认
ERROR(0x10); // 错误
}
示例:
if (master.State() == EcState.OP) {
System.out.println("主站处于运行状态");
}
- SDK 中的所有切换状态机方法,均符合 EtherCAT 标准状态机流程,协议层自动处理。
- 请在启动前导入或设置正确的配置。
- 如需返回更详细的错误信息,请使用
setState(state, errorOut)重载。
setState(EcState state)
public boolean setState(EcState state)
切换主站到目标状态。内部自动按 ETG 状态机链 (INIT↔PRE_OP↔SAFE_OP↔OP) 跨多级一次到位,自动应用各步启动参数。进入 SAFE_OP/OP 时自动启动 PDO 线程,每步默认 5s 超时。
参数:
state(EcState) — 目标状态
返回值:
boolean—true切换成功,false失败
SDK 在状态切换内部自动执行 3 次重试 (每次失败后等待 1.5s),用于吸收硬件偶发抖动 (PHY PLL 抖动等)。用户不需要自行包装重试逻辑。
切换到 SAFE_OP 时,SDK 自动按 ETG 标准为非 DC 从站配置同步循环计数阈值与 SyncManager 同步类型 (FreeRun 兜底),应用层无需手动写。
DC 从站跳过此自动配置 (避免与 DC 时基冲突)。如需自定义同步策略,应在切到 SAFE_OP 之后再覆盖。写入失败时容忍继续,不阻塞 SAFE_OP 转换。
示例:
if (!master.setState(EcState.OP)) {
System.out.println("状态切换失败");
}
setState(EcState state, String[] errorOut)
public boolean setState(EcState state, String[] errorOut)
切换主站到目标状态,并通过 errorOut[0] 输出失败原因(用于诊断)。成功时 errorOut[0] 不写入。重试与 SAFE_OP 自动同步行为同上。
参数:
state(EcState) — 目标状态errorOut(String[]) — 长度 ≥ 1 的数组,失败时errorOut[0]写中文错误描述;传 null 跳过
返回值:
boolean—true切换成功,false失败
示例:
String[] err = new String[1];
if (!master.setState(EcState.OP, err)) {
System.out.println("状态切换失败: " + err[0]);
}
setStateAsync(EcState state)
public CompletableFuture<Boolean> setStateAsync(EcState state)
异步版状态切换,不阻塞调用线程。重试与 SAFE_OP 自动同步行为同上。
参数:
state(EcState) — 目标状态
返回值:
CompletableFuture<Boolean>— 完成时返回true(成功)/false(失败)
示例:
master.setStateAsync(EcState.OP).thenAccept(ok -> {
if (!ok) System.out.println("状态切换失败");
});
// 阻塞等待结果
boolean ok = master.setStateAsync(EcState.OP).get();
Stop()
public void Stop()
停止主站,切换到 PRE_OP 状态。
Start()
public void Start()
启动 PDO 循环。setState(SAFE_OP) 与 setState(OP) 内部已自动启动,通常无需显式调用。
State.abort()
public static void abort()
中断协议层所有阻塞操作(如 setState(...)、build() 中的 setNetwork 等)。可安全地从任意线程调用。
示例:
import com.darra.ethercat.master.State;
// 用户点击取消按钮时中断正在进行的状态切换
State.abort();
waitForState(EcState target, int timeoutMs, int pollIntervalMs)
public boolean waitForState(EcState target, int timeoutMs, int pollIntervalMs)
public boolean waitForState(EcState target) // 默认 timeoutMs=10000, pollIntervalMs=50
轮询等待主站到达指定状态。不发起状态切换,仅等待已请求的转换完成 / PDO 流稳定确认。常用于 setStateAsync 异步触发后的确认窗口,或 PDO 启动后短暂等待 OP 稳定。
参数:
target(EcState) — 目标状态timeoutMs(int) — 最大等待时间(毫秒),默认 10000mspollIntervalMs(int) — 轮询间隔(毫秒),默认 50ms
返回值:
boolean— 在超时前到达目标状态返回true
示例:
master.setState(EcState.OP);
// 最多等 5s 主站进入 OP
if (!master.waitForState(EcState.OP, 5000, 50)) {
System.out.println("主站未在 5s 内进入 OP");
}
waitForSlaveState(int slaveIndex, EcState target, int timeoutMs, int pollIntervalMs)
public boolean waitForSlaveState(int slaveIndex, EcState target, int timeoutMs, int pollIntervalMs)
轮询等待单个从站到达指定状态。适用于 PDO 启动后对关键从站的状态确认。
参数:
slaveIndex(int) — 从站编号(1-based)target(EcState) — 目标状态timeoutMs(int) — 最大等待时间(毫秒)pollIntervalMs(int) — 轮询间隔(毫秒)
返回值:
boolean— 在超时前到达目标状态返回true
示例:
// 等从站 3 进入 OP, 最多 3s
if (!master.waitForSlaveState(3, EcState.OP, 3000, 50)) {
System.out.println("从站 3 当前: " + master.getSlave(3).State());
}
过程数据看门狗
setAllSlaveWatchdog(int timeoutMs)
public int setAllSlaveWatchdog(int timeoutMs)
批量设置所有从站的过程数据看门狗超时。
参数:
timeoutMs(int) — 超时时间(毫秒),0 = 禁用,最大 6553ms
返回值:
int— 成功设置的从站数量
单个从站的看门狗配置请使用 slave.SetWatchdog()。
示例:
int count = master.setAllSlaveWatchdog(100);
System.out.println("已设置 " + count + " 个从站看门狗超时为 100ms");
setAllSlavePdiWatchdog(int timeoutMs)
public int setAllSlavePdiWatchdog(int timeoutMs)
批量设置所有从站的 PDI 看门狗超时。
参数:
timeoutMs(int) — 超时时间(毫秒),0 = 禁用,最大 6553ms
返回值:
int— 成功设置的从站数量
单个从站的看门狗配置请使用 slave.SetPdiWatchdog()。
示例:
int count = master.setAllSlavePdiWatchdog(100);
System.out.println("已设置 " + count + " 个从站 PDI 看门狗超时为 100ms");
诊断控制方法
resetDiagnostics()
public void resetDiagnostics()
重置主站诊断计数器。等同于调用 master.Diagnostics().Reset() 中的诊断重置部分。
resetSlavePortErrorCounters(short slaveIndex)
public boolean resetSlavePortErrorCounters(short slaveIndex)
重置指定从站的端口错误计数器。
参数:
slaveIndex(short) — 从站索引(1-based)
返回值:
boolean— 成功返回true
master.Diagnostics().Reset() 会同时重置主站诊断统计、所有从站端口错误计数器和 PDO 丢帧统计。
热插拔自修复
在断电重插/更换从站的场景下,SDK 自动识别身份不符并进入保护状态,避免错误设备被误纳入控制循环。事件流程见 SlaveIdentityMismatch 事件。
AcknowledgeSlaveReplacement(int slaveNum)
public boolean AcknowledgeSlaveReplacement(int slaveNum)
用户确认从站替换完毕,触发 EtherCAT 识别状态机重新探测该从站。
调用时机: 订阅 master.Events().addSlaveIdentityMismatchListener 接收到身份不符报警后,操作员检查/更换设备完毕,调用本方法让 SDK 重新检测。
行为:
- 若身份已纠正(换回正确设备 / 同型号升级 Revision)→ 自动恢复并触发
SlaveOnline事件 - 若身份仍不匹配 → 再次触发
SlaveIdentityMismatch,回到IDENT_REJECTED状态
参数:
slaveNum(int) — 从站编号(1-based,与配置一致)
返回值:
boolean—true=已接受并复位 FSM;false=参数无效,或从站当前不在IDENT_REJECTED/FAILED状态
在 SlaveIdentityMismatch 事件触发之前调用本方法无效。从站未进入保护状态时 SDK 会持续正常探测,无需手动确认。
示例:
master.Events().addSlaveIdentityMismatchListener((args) -> {
System.out.printf("从站 %d 身份不符: 期望 Vendor=0x%08X, 实际 Vendor=0x%08X%n",
args.slaveIndex, args.expectedVendor, args.actualVendor);
// UI 弹窗: "请检查从站 N 的设备, 换回正确型号后点击确认"
if (showReplacementDialog(args.slaveIndex)) {
boolean ok = master.AcknowledgeSlaveReplacement(args.slaveIndex);
if (!ok) {
System.out.println("确认失败: 从站不在 IDENT_REJECTED 状态");
}
}
});
SlaveOffline— 从站断电/断线(身份仍匹配),自动恢复并触发SlaveOnlineSlaveIdentityMismatch— 从站身份变了(换错设备 / 换同型号旧版本固件),需手动调AcknowledgeSlaveReplacement
主站身份与拓扑诊断 (Phase 2)
GetMasterIdentity()
public DarraCore.MasterIdentity GetMasterIdentity()
读取主站自身的身份信息(ETG.1510 对象 0x1018),包含厂商号、产品号、Revision、序列号。配置面板/远程运维使用本接口区分多主机环境下的多个 EtherCATMaster 实例。
返回值:
DarraCore.MasterIdentity— 身份结构体,失败返回null
示例:
DarraCore.MasterIdentity id = master.GetMasterIdentity();
if (id != null) {
System.out.printf("主站 Vendor=0x%08X Product=0x%08X%n", id.VendorId, id.ProductCode);
}
GetMasterDiagData()
public DarraCore.MasterDiagData GetMasterDiagData()
读取主站诊断数据(ETG.1510 对象 0xF120),含网络层错误计数、PDO 周期统计、链路状态摘要。与 master.Diagnostics() 的差异:本接口对应 ETG.1510 标准对象,便于和 EtherCAT 配置工具(TwinCAT/EC-Engineer)对齐字段语义。
返回值:
DarraCore.MasterDiagData— 诊断结构体,失败返回null
rebuildTopology()
public void rebuildTopology()
重建主站持有的拓扑视图(重新解析 slave[i].topology / parentport / activeports 生成父子关系)。
调用时机:
- 热插拔/SlaveIdentityMismatch 修复后,确认
topologyRoots()/topologyChildren(parent)返回的列表与最新链路一致 - 切换冗余模式(环网 ↔ 线性)后
示例:
master.rebuildTopology();
List<Integer> roots = master.TopologyRoots();
System.out.println("拓扑根: " + roots);
静态: enablePDOMonitoring / isPDOMonitoringEnabled
public static void enablePDOMonitoring(boolean enable)
public static boolean isPDOMonitoringEnabled()
进程级 PDO 监控开关,开启后 SDK 在每周期记录 WKC / actual vs expected / 帧时延 并写入 Diagnostics 计数器。默认关闭(生产环境零开销),仅在调试 / IDE 监视场景开启。
示例:
EtherCATMaster.enablePDOMonitoring(true); // 调试模式
// ... 运行 ...
boolean on = EtherCATMaster.isPDOMonitoringEnabled();
EtherCATMaster.enablePDOMonitoring(false); // 还原
资源生命周期
close() — try-with-resources
@Override
public void close()
EtherCATMaster 实现了 AutoCloseable,推荐用 try-with-resources 自动释放:
try (EtherCATMaster master = EtherCATMaster.create()) {
master.SetNetwork("\\Device\\NPF_{...}", "");
master.setENI("config.xml").build();
master.setStateSequence(EcState.OP, 10000);
master.start();
// ... PDO 操作 ...
} // close() 自动调用:Stop → Dispose → 注销回调 → 清空从站列表
close() 流程:
- 标记 disposed,避免后续方法被调用
- 若已
start():调用 DLLStop - 停止
MasterDiagnosticsInfo采样线程、邮箱网关 - 释放 DLL 主站槽位(
Dispose) - 清空所有 JNA 回调引用(防止 GC 在释放后被调)
释放后调用任何方法(除 IsDisposed())会抛 IllegalStateException。请勿对已 close() 的实例调用 start() / setNetwork()。
静态: EmergencyCleanup()
public static void EmergencyCleanup()
紧急释放所有主站资源,跳过常规 stop/dispose 流程,直接关闭网卡句柄。仅用于异常退出钩子(如 Runtime.getRuntime().addShutdownHook / 未捕获异常处理器),常规释放请使用 close()。
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
EtherCATMaster.EmergencyCleanup();
}));
启动配置验证
State.VerifyStartupConfiguration()
public Map<String, Object> VerifyStartupConfiguration()
State 子模块上的方法,遍历所有从站,逐项检查启动配置的完整性:主站状态/链路 → 各从站 ESC 状态 → AL 状态码 → DLL 配置校验。在 start() 前后均可调用,建议在 setStateSequence(OP) 失败后调用以定位卡点。
返回值: Map<String, Object>
| Key | 类型 | 含义 |
|---|---|---|
valid | boolean | 是否全部通过 |
slave_count | int | 已检测到的从站数量 |
issues | List<String> | 失败明细(中文,可直接展示给用户) |
示例:
State stateMgr = new State(master.MasterIndex);
Map<String, Object> result = stateMgr.VerifyStartupConfiguration();
if (!(boolean) result.get("valid")) {
@SuppressWarnings("unchecked")
List<String> issues = (List<String>) result.get("issues");
issues.forEach(System.err::println);
}
master.Validate()— 仅检查 SDK 调用链(ENI 路径/网口已配),尚未启动 SDK 时使用State.VerifyStartupConfiguration()— 必须 SDK 已build(),检查实际硬件状态
ESM 状态超时常量
EtherCATTypes.EsmTimeouts
public static final class EsmTimeouts {
public static final int INIT_TO_PREOP = 3000;
public static final int PREOP_TO_SAFEOP = 10000;
public static final int SAFEOP_TO_OP = 3000;
public static final int OP_TO_SAFEOP = 200;
public static final int SAFEOP_TO_PREOP = 200;
public static final int PREOP_TO_INIT = 200;
public static final int TO_BOOT = 3000;
public static final int BOOT_TO_INIT = 3000;
public static final int SDO_TIMEOUT = 3000;
public static final int MAILBOX_TIMEOUT = 5000;
public static final int STATE_STABILIZE_DELAY = 100;
}
ETG.1020 推荐的状态机超时常量(毫秒)。在调用 slave.SetState(target, timeoutMs) 时建议优先用本表的常量,而不是硬编码数值,便于未来跟随标准调整。
单从站设置自定义超时:
import com.darra.ethercat.data.EtherCATTypes.EsmTimeouts;
slave.SetEsmTimeouts(
EsmTimeouts.INIT_TO_PREOP,
EsmTimeouts.PREOP_TO_SAFEOP,
EsmTimeouts.SAFEOP_TO_OP,
EsmTimeouts.OP_TO_SAFEOP,
EsmTimeouts.SAFEOP_TO_PREOP,
EsmTimeouts.PREOP_TO_INIT,
EsmTimeouts.BOOT_TO_INIT,
EsmTimeouts.TO_BOOT
);
授权验证
verifyLicense / isLicenseValid / invalidateLicense
public boolean verifyLicense() // 校验许可证(启动前 / 长跑期间巡检)
public boolean isLicenseValid() // 当前授权是否仍然有效(缓存判断, 不发起检查)
public void invalidateLicense() // 主动作废本地缓存, 下次 verifyLicense 重新校验
verifyLicense() 触发完整的本地缓冲完整性检查。运行时建议在 PLC 任务空闲期周期性巡检(≥ 4 小时一次)。isLicenseValid() 不重新计算,仅返回缓存状态,可在 PDO 周期内安全调用。
示例:
if (!master.verifyLicense()) {
System.err.println("授权失败, 请联系管理员");
return;
}
ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
timer.scheduleAtFixedRate(() -> {
if (!master.isLicenseValid()) master.verifyLicense();
}, 4, 4, TimeUnit.HOURS);
主站构建器辅助 (Builder)
Validate()
public ValidationResult Validate()
public static class ValidationResult {
public final boolean IsValid;
public final List<String> Errors;
}
在调用 build() 前预检查 Builder 链是否完整(SetNetwork / setENI 已设置)。失败时 Errors 包含中文错误说明,可直接显示在 UI。
EtherCATMaster master = EtherCATMaster.create()
.SetNetwork("\\Device\\NPF_{...}")
.setENI("config.xml")
.enableAutoStartup();
ValidationResult vr = master.Validate();
if (!vr.IsValid) {
System.err.println(vr); // "配置无效: ENI路径为空字符串; ..."
master.close();
return;
}
master.build();
Deconstruct() / BuildResult
public BuildResult Deconstruct()
public static class BuildResult {
public final boolean success;
public final int slaveCount;
public final short masterIndex;
public final String message;
public final EtherCATMaster master;
}
返回主站构建摘要(对应 C# Deconstruct 元组解构),便于日志/上报:
EtherCATMaster.BuildResult r = master.Deconstruct();
System.out.printf("master=%d slaves=%d ok=%s%n",
r.masterIndex, r.slaveCount, r.success);