跳到主要内容

CoE (CANopen over EtherCAT)

通过 slave.CoE() 访问。

快速开始

CoE 使用 SDORead/SDOWrite 方法直接按索引和子索引读写对象字典:

CoE coe = slave.CoE();

// 原始字节读写
byte[] data = coe.SDORead((short) 0x6041, (byte) 0);
coe.SDOWrite((short) 0x6040, (byte) 0, new byte[]{0x06, 0x00});

// 类型化读取
Short status = coe.SDOReadInt16((short) 0x6041, (byte) 0);
Integer position = coe.SDOReadInt32((short) 0x6064, (byte) 0);
String name = coe.SDOReadString((short) 0x1008, (byte) 0);

// 类型化写入
coe.SDOWriteInt16((short) 0x6040, (byte) 0, (short) 0x0006);
coe.SDOWriteInt32((short) 0x607A, (byte) 0, 100000);

SDO 读取

SDORead(short index, byte subIndex)

public byte[] SDORead(short index, byte subIndex)
public byte[] SDORead(short index, byte subIndex, int timeoutMs)

读取 SDO 原始字节数据。默认超时 5000ms(与 C# / Python SDK 对齐)。

参数:

  • index (short) — 对象索引
  • subIndex (byte) — 子索引
  • timeoutMs (int) — 超时(毫秒)

CompleteAccess 重载:

public byte[] SDORead(short index, byte subIndex, boolean completeAccess)
public byte[] SDORead(short index, byte subIndex, boolean completeAccess, int timeoutMs)

completeAccess=true 时一次读取该对象的所有子索引(ETG.1000.6 Complete Access),返回打包的原始字节,可用 parseCompleteAccessData(...) 拆分。

返回值:

  • byte[] — 数据字节数组,失败返回 null

类型化读取

Byte val8 = coe.SDOReadInt8(index, subIndex);
Short val16 = coe.SDOReadInt16(index, subIndex);
Integer val32 = coe.SDOReadInt32(index, subIndex);
Integer uval16 = coe.SDOReadUInt16(index, subIndex);
Long uval32 = coe.SDOReadUInt32(index, subIndex);
Float fval = coe.SDOReadFloat(index, subIndex);
Double dval = coe.SDOReadDouble(index, subIndex);
String sval = coe.SDOReadString(index, subIndex);

所有类型化读取方法失败时返回 null

SDO 写入

SDOWrite(short index, byte subIndex, byte[] data)

public boolean SDOWrite(short index, byte subIndex, byte[] data)
public boolean SDOWrite(short index, byte subIndex, byte[] data, int timeoutMs)

写入 SDO 原始字节数据。默认超时 5000ms。

CompleteAccess 重载:

public boolean SDOWrite(short index, byte subIndex, byte[] data, boolean completeAccess)
public boolean SDOWrite(short index, byte subIndex, byte[] data, boolean completeAccess, int timeoutMs)

异步 SDO 读写

public CompletableFuture<byte[]>  sdoReadAsync(short index, byte subIndex)
public CompletableFuture<byte[]> sdoReadAsync(short index, byte subIndex, boolean completeAccess)
public CompletableFuture<Boolean> sdoWriteAsync(short index, byte subIndex, byte[] data)
public CompletableFuture<Boolean> sdoWriteAsync(short index, byte subIndex, byte[] data, boolean completeAccess)

异步读写 SDO(对齐 C# SDOReadAsync / SDOWriteAsync)。在 ForkJoinPool.commonPool() 上执行;底层失败时返回的 CompletableFutureCoEAbortException 异常完成(承载 SDO Abort 码与从站索引)。

示例:

CoE coe = slave.CoE();

coe.sdoReadAsync((short) 0x6041, (byte) 0)
.thenAccept(data -> System.out.printf("状态字: 0x%04X%n",
(data[0] & 0xFF) | ((data[1] & 0xFF) << 8)))
.exceptionally(ex -> {
if (ex.getCause() instanceof CoE.CoEAbortException) {
CoE.CoEAbortException ab = (CoE.CoEAbortException) ex.getCause();
System.err.printf("SDO Abort 0x%08X (slave=%d)%n", ab.abortCode, ab.slaveIndex);
}
return null;
});

类型化写入

coe.SDOWriteByte(index, subIndex, value);
coe.SDOWriteInt16(index, subIndex, value);
coe.SDOWriteInt32(index, subIndex, value);
coe.SDOWriteUInt16(index, subIndex, value);
coe.SDOWriteUInt32(index, subIndex, value);
coe.SDOWriteFloat(index, subIndex, value);
coe.SDOWriteDouble(index, subIndex, value);

批量读取

ReadMultipleAsync(List<int[]> entries)

public CompletableFuture<Map<Long, byte[]>> ReadMultipleAsync(List<int[]> entries)

批量 SDO 读取 — 一次调用异步读取多个对象。适合需要同时读取多个 SDO 的场景,避免逐个阻塞调用。

参数:

  • entries (List<int[]>) — 要读取的 (index, subindex) 列表,每个元素为 int[2]

返回值:

  • CompletableFuture<Map<Long, byte[]>> — 结果 Map,key 为 (index << 8 | subindex) 编码,value 为数据(失败为 null

示例:

CoE coe = slave.CoE();

List<int[]> entries = new ArrayList<>();
entries.add(new int[]{0x6041, 0}); // StatusWord
entries.add(new int[]{0x6064, 0}); // ActualPosition
entries.add(new int[]{0x1008, 0}); // DeviceName

Map<Long, byte[]> results = coe.ReadMultipleAsync(entries).get();
for (Map.Entry<Long, byte[]> e : results.entrySet()) {
long key = e.getKey();
int index = (int) ((key >> 8) & 0xFFFF);
int sub = (int) (key & 0xFF);
System.out.printf("0x%04X:%d = %s%n", index, sub,
e.getValue() != null ? e.getValue().length + " bytes" : "失败");
}

属性

属性类型读写说明
getLastSdoError()SDOError只读最后一次 SDO 操作的错误码

getLastSdoError()

public SDOError getLastSdoError()

最后一次 SDO 操作的错误码(CANopen Abort Code,ETG.1020)。每次 SDORead* / SDOWrite* 调用结束后由 SDK 缓存:成功 → SDOError.NO_ERROR,失败 → 具体 Abort 枚举。在 null 返回值后调用,可拿到从站为何拒绝该 SDO 请求的细节。

示例:

byte[] data = coe.SDORead((short) 0x6041, (byte) 0);
if (data == null) {
SDOError error = coe.getLastSdoError();
System.out.printf("SDO 读取失败: %s (0x%08X)%n", error.name(), error.value);
}

邮箱统计

CoE 实现统一的 IMailboxProtocol 接口,提供邮箱事务统计。2.5.x 起,统计经真实导出的 native EcxMbxGetStats 拉取(此前为占位空快照)。

类别方法类型读写说明
协议标识getProtocolType()byte只读协议类型常量(CoE=0x03)
getProtocolName()String只读协议名称("CoE"
isSupported()boolean只读从站是否支持本协议
事务状态getLastStatus()MailboxStatus只读最近一次邮箱事务的统一状态
getLastErrorCode()long只读最近一次协议层错误码(0=无错误)
统计getStatistics()MailboxStatistics只读邮箱统计快照
resetStatistics()void重置邮箱统计计数器

MailboxStatistics 结构(不可变快照):

字段类型说明
totalSentlong累计发送事务数
totalReceivedlong累计收到响应数(含错误帧)
totalTimeoutlong累计超时次数
totalMailboxErrorlong累计邮箱错误帧数(MBX ERROR / SDO Abort 等)
totalProtocolErrorlong累计协议错误次数(类型 / counter / WKC 不匹配)
totalCancelledlong累计被取消的事务数
lastErrorCodelong最近一次错误码(0=无)
lastErrorTimeInstant最近一次错误时刻(UTC,无错误为 null
averageLatencyUsdouble平均事务延迟(微秒)

示例:

CoE coe = slave.CoE();

// 执行一批 SDO 操作 ...
coe.SDORead((short) 0x6041, (byte) 0);

// 查询邮箱统计
MailboxStatistics stats = coe.getStatistics();
System.out.printf("CoE 邮箱: 发送=%d, 接收=%d, 超时=%d, 平均延迟=%.1fus%n",
stats.totalSent, stats.totalReceived, stats.totalTimeout, stats.averageLatencyUs);

// 检查最近事务状态
if (coe.getLastStatus() != MailboxStatus.SUCCESS) {
System.out.printf("最近事务异常: %s (code=0x%X)%n",
coe.getLastStatus(), coe.getLastErrorCode());
}

// 重置统计
coe.resetStatistics();

MailboxStatus 枚举

邮箱事务统一状态码(所有邮箱协议共用,与底层邮箱状态码一一对应)。

名称说明
PENDING0待处理(事务尚未发起或仍在进行中)
SUCCESS1成功完成
TIMEOUT-1超时(对端未在规定时间内响应)
CANCELLED-2已取消
MAILBOX_ERROR-3邮箱错误帧(MBX ERROR 或协议层 Abort)
PROTO_MISMATCH-4响应协议类型不匹配
COUNTER_FAIL-5响应 counter 不匹配
WKC_FAIL-6底层工作计数器(WKC)不符合预期
NO_MEMORY-7内存不足
INVALID_ARG-8非法参数
NOT_IMPLEMENTED-9未实现 / 不支持

SDOError 枚举

SDO 操作错误代码(CANopen 标准 / ETG.1020)。

名称说明
NO_ERROR0x00000000无错误
TOGGLE_BIT_NOT_CHANGED0x05030000Toggle 位未改变
SDO_PROTOCOL_TIMEOUT0x05040000SDO 协议超时
INVALID_COMMAND_SPECIFIER0x05040001无效的命令标识符
OUT_OF_MEMORY0x05040005内存不足
UNSUPPORTED_ACCESS0x06010000不支持的访问
READ_WRITE_ONLY_ERROR0x06010001尝试读取只写对象
WRITE_READ_ONLY_ERROR0x06010002尝试写入只读对象
SUBINDEX_WRITE_ERROR0x06010003子索引不允许写入
COMPLETE_ACCESS_NOT_SUPPORTED0x06010004不支持完全访问
OBJECT_LENGTH_EXCEEDED0x06010005对象长度超限
OBJECT_MAPPED_TO_RXPDO0x06010006对象已映射到 RxPDO
OBJECT_DOES_NOT_EXIST0x06020000对象不存在
CANNOT_BE_MAPPED_TO_PDO0x06040041不可映射到 PDO
EXCEEDS_PDO_LENGTH0x06040042超出 PDO 长度
PARAMETER_INCOMPATIBILITY0x06040043参数不兼容
INTERNAL_INCOMPATIBILITY0x06040047内部不兼容
HARDWARE_ACCESS_ERROR0x06060000硬件访问错误
DATA_TYPE_MISMATCH0x06070010数据类型不匹配
DATA_TYPE_TOO_HIGH0x06070012数据类型长度过大
DATA_TYPE_TOO_LOW0x06070013数据类型长度过小
SUBINDEX_DOES_NOT_EXIST0x06090011子索引不存在
VALUE_RANGE_EXCEEDED0x06090030值超出范围
VALUE_TOO_HIGH0x06090031值过大
VALUE_TOO_LOW0x06090032值过小
MODULE_IDENT_LIST_MISMATCH0x06090033模块列表不匹配
MAX_LESS_THAN_MIN0x06090036最大值小于最小值
GENERAL_ERROR0x08000000一般错误
DATA_TRANSFER_ERROR0x08000020数据传输错误
LOCAL_CONTROL_ERROR0x08000021本地控制错误
DEVICE_STATE_ERROR0x08000022设备状态错误
DICTIONARY_ERROR0x08000023字典错误
UNKNOWN_ERROR0xFFFFFFFF未知错误

EcDataType 枚举

EtherCAT 数据类型枚举(基于 ETG.1000.6),决定类型化读取的目标类型。

枚举值Java 类型说明
BOOLEAN0x0001boolean布尔
INTEGER80x0002byteint8
INTEGER160x0003shortint16
INTEGER320x0004intint32
UNSIGNED80x0005byteuint8 (Java 无无符号,使用 & 0xFF)
UNSIGNED160x0006intuint16 (Java 使用 int 存储)
UNSIGNED320x0007longuint32 (Java 使用 long 存储)
REAL320x0008float单精度浮点
VISIBLE_STRING0x0009StringASCII 字符串
OCTET_STRING0x000Abyte[]字节串
UNICODE_STRING0x000BStringUnicode 字符串
TIME_OF_DAY0x000Cbyte[]时间(4 字节原始)
TIME_DIFFERENCE0x000Dbyte[]时间差(4 字节原始)
DOMAIN0x000Fbyte[]原始数据
REAL640x0011double双精度浮点
INTEGER640x0015longint64
UNSIGNED640x001Blonguint64

CoEObjectAccess 标志

对象访问权限标志位。

标志说明
READ_PREOP0x01PreOp 可读
READ_SAFEOP0x02SafeOp 可读
READ_OP0x04OP 可读
WRITE_PREOP0x08PreOp 可写
WRITE_SAFEOP0x10SafeOp 可写
WRITE_OP0x20OP 可写

对象字典

ODList()

public Map<Short, List<ObjectEntry>> ODList()

获取从站完整对象字典。

相关结构:

public class ObjectEntry {
public final short ODIndex; // 对象索引
public final byte SubIndex; // 子索引
public final String Name; // 名称
public final short DataType; // 数据类型(见 EcDataType 枚举)
public final int BitLength; // 位长度
public final int ObjAccess; // 访问权限标志(见 CoEObjectAccess 标志)
}

相关属性方法:

  • ObjAccessFlags.canRead(int access) (boolean) — 是否可读
  • ObjAccessFlags.canWrite(int access) (boolean) — 是否可写
  • ObjAccessFlags.canWritePreOp(int access) (boolean) — PreOP 下是否可写
  • ObjAccessFlags.canWriteSafeOp(int access) (boolean) — SafeOP 下是否可写
  • ObjAccessFlags.canWriteOp(int access) (boolean) — OP 下是否可写
  • ObjAccessFlags.getAccessDescription(int access) (String) — 获取访问权限中文描述

示例:

Map<Short, List<CoE.ObjectEntry>> od = coe.ODList();
for (Map.Entry<Short, List<CoE.ObjectEntry>> entry : od.entrySet()) {
List<CoE.ObjectEntry> subEntries = entry.getValue();
if (!subEntries.isEmpty()) {
CoE.ObjectEntry first = subEntries.get(0);
System.out.printf("0x%04X: %s%n", entry.getKey() & 0xFFFF, first.Name);
// 检查访问权限
for (CoE.ObjectEntry sub : subEntries) {
boolean readable = CoE.ObjAccessFlags.canRead(sub.ObjAccess);
boolean writable = CoE.ObjAccessFlags.canWrite(sub.ObjAccess);
System.out.printf(" [%d] %s (R=%b, W=%b)%n",
sub.SubIndex, sub.Name, readable, writable);
}
}
}

对象字典树结构

loadODList()

public EcODList loadODList()

加载完整对象字典树结构(同步)。首次调用从 DLL 读取,后续返回缓存。

loadODListAsync()

public CompletableFuture<EcODList> loadODListAsync()

异步加载对象字典。

EcODList 方法:

  • getCount() (int) — 对象数量
  • get(short index) (ObjectDictionary) — 按 OD 索引获取
  • get(String key) (ObjectDictionary) — 按名称或十六进制索引获取
  • containsKey(short) (boolean) — 检查索引是否存在
  • getFullList() (List) — 获取完整列表

ObjectDictionary 字段/方法:

  • Index (short) — 对象索引
  • Name (String) — 对象名称
  • ObjectCode (byte) — 对象类型 (0x07=VAR, 0x08=ARRAY, 0x09=RECORD)
  • MaxSub (byte) — 最大子索引
  • getOEList() (OEDictionary) — 子索引条目列表
  • readAll() (Map) — 批量读取所有子索引数据

示例:

CoE.EcODList odList = coe.loadODList();
for (CoE.ObjectDictionary od : odList) {
System.out.printf("0x%04X: %s (子索引=%d)%n",
od.Index & 0xFFFF, od.Name, od.getCount());
}

辅助方法:

  • isODListLoading() (boolean) — 对象字典是否正在异步加载
  • getODList() (EcODList) — 获取已加载的 OD 列表(未加载返回 null
  • getCount() (int) — 已加载的对象数量
  • containsKey(short index) / containsKey(String key) (boolean) — 检查给定索引 / 名称是否存在
  • getSlaveIndex() (short) — 获取从站索引

SDO Information 直读对象字典

以下方法通过 CoE SDO Information Service 直接从从站实时拉取 OD 树,不依赖 GetSlaveSDOList 缓存路径(对齐 C# GetObjectDescription / GetEntryDescription)。适合需要从从站实时读取 OD 元数据的场景。

loadObjectDictionary()

public List<ObjectDictionary> loadObjectDictionary()

直接通过 SDO Information Service 加载完整对象字典(每次实时从从站拉 OD 索引 + 详情)。失败返回 null

getObjectDescription(short index)

public ObjectDictionary getObjectDescription(short index)

读取单个对象的描述(SDO Info Service 0x03)。返回包含 Index / Name / Datatype / ObjectCode / MaxSubObjectDictionary 元数据(不含子条目)。对象不存在或从站不支持 SDO Info 时返回 null

示例:

CoE.ObjectDictionary desc = coe.getObjectDescription((short) 0x6041);
if (desc != null)
System.out.printf("%s type=0x%04X subs=%d%n",
desc.Name, desc.Datatype & 0xFFFF, desc.MaxSub & 0xFF);

getEntryDescription(short index, byte subindex)

public ObjectEntry getEntryDescription(short index, byte subindex)

读取单个子索引的描述(SDO Info Service 0x05)。返回包含 DataType / BitLength / ObjAccess / NameObjectEntry。失败返回 null

示例:

CoE.ObjectEntry entry = coe.getEntryDescription((short) 0x1A00, (byte) 1);
if (entry != null)
System.out.printf("sub1: %s, bits=%d%n", entry.Name, entry.BitLength);

设备协议

getDeviceProfile()

public int getDeviceProfile()

读取设备协议编号(0x1000 Device Type 低 16 位)。例如 CiA 402 伺服返回 402,CiA 401 I/O 模块返回 401。读取失败返回 0

getCiA402() / getCiA401()

public Object getCiA402()
public Object getCiA401()

getDeviceProfile() 匹配 402 / 401 时返回当前 CoE 实例(可继续用 SDO 操作 CiA 对象),否则返回 null。专用 CiA 接口见 CiA402 / CiA401

Complete Access 数据解析

parseCompleteAccessData(byte[] caData, ObjectDictionary od)

public static Map<Integer, byte[]> parseCompleteAccessData(byte[] caData, ObjectDictionary od)

SDORead(..., completeAccess=true) 读取的打包原始字节按子索引拆分(ETG.1000.6 §5.6.2)。规则:SubIndex 0 始终填充到 16 位(2 字节);BIT 类型连续打包,下一个非 BIT 类型从下一个字节边界开始。

参数:

  • caData (byte[]) — Complete Access 读取的原始字节
  • od (ObjectDictionary) — 对应对象字典条目(需已加载 OE 列表)

返回值:

  • Map<Integer, byte[]> — 按子索引(key = 实际子索引号)拆分的字节数组

诊断消息

readDiagnosticMessages()

public List<DiagnosticMessage> readDiagnosticMessages()

读取 ETG.1510 诊断历史消息(0x10F3)。一次性读取全量历史,适合离线诊断或故障现场快照。

相关结构:

public static class DiagnosticMessage {
public final byte SubIndex; // 子索引
public final int DiagCode; // 诊断码
public final short Flags; // 标志位
public final short TextIndex; // 文本索引
public final byte[] RawData; // 原始数据
}

新版诊断历史 API (0x10F3)

基于 ETG.1020 §16 定义,用"是否有新消息 / 元数据 / 单条消息 / 确认已处理"四类轻量操作实现增量轮询的诊断历史访问,相比 readDiagnosticMessages() 一次读全量更适合运行时高频监听场景。

新旧 API 选型
  • readDiagnosticMessages() — 一次性读取全量诊断历史,适合离线分析、故障现场转储
  • pollHasNewDiagnostic + readDiagnosticMeta + readDiagnosticMessage + acknowledgeDiagnostic — 轮询 + 按需读取 + 逐条确认,适合运行时监听和环形缓冲管理

pollHasNewDiagnostic()

public int pollHasNewDiagnostic()

快速轮询 0x10F3:04 NewAvailable 标志。无新消息时不会读取完整历史,适合 1~10Hz 高频轮询。

返回值:

  • int1 有新消息;0 无新消息;-1 通信失败

readDiagnosticMeta()

public DiagMeta readDiagnosticMeta()

读取 0x10F3 元数据(MaxMessages / NewestMessage / NewestAcknowledged / Flags)。

返回值:

  • DiagMeta — 元数据对象;通信失败返回 null

DiagMeta 结构:

public static class DiagMeta {
public int maxMessages; // 0x10F3:01 从站支持的最大消息数
public int newestMessage; // 0x10F3:02 最新消息所在 subindex
public int newestAcknowledged; // 0x10F3:03 用户已确认的最新 subindex
public int flags; // 0x10F3:05 bit4=Ring/Linear, bit5=Overrun
}
Flags 位含义

flags 字段按 ETG.1020 Table 49:

  • bit4 = 0 → 线性缓冲,填满后丢新;bit4 = 1 → 环形缓冲,覆盖旧消息
  • bit5 = 1 → 发生过溢出(有消息被覆盖丢失)

readDiagnosticMessage(int msgIdx, int bufSize)

public byte[] readDiagnosticMessage(int msgIdx, int bufSize)

读取指定 subindex(6..255)对应的诊断消息原始字节。

参数:

  • msgIdx (int) — 消息 subindex(6..255)
  • bufSize (int) — 接收缓冲区大小(字节),超出范围时回落到 256

返回值:

  • byte[] — 实际读取的字节数组;失败返回 null

acknowledgeDiagnostic(int msgIdx)

public boolean acknowledgeDiagnostic(int msgIdx)

确认指定 subindex 之前的消息已处理。从站在 Ring 模式下会清理 <= msgIdx 的消息。

参数:

  • msgIdx (int) — 要确认的最后一个 subindex

返回值:

  • boolean — 成功返回 true

完整示例(运行时轮询):

CoE coe = slave.CoE();

// 1. 查询缓冲模式
CoE.DiagMeta meta = coe.readDiagnosticMeta();
if (meta == null) return;
boolean isRing = ((meta.flags >> 4) & 0x01) != 0;
boolean overrun = ((meta.flags >> 5) & 0x01) != 0;
System.out.printf("MaxMsg=%d, Ring=%b, Overrun=%b%n",
meta.maxMessages, isRing, overrun);

// 2. 周期性轮询(例如每 100ms 调用一次)
int hasNew = coe.pollHasNewDiagnostic();
if (hasNew == 1) {
CoE.DiagMeta m2 = coe.readDiagnosticMeta();
if (m2 != null && m2.newestMessage > m2.newestAcknowledged) {
// 从 (acked+1) 扫到 newest, 逐条读 + 确认
for (int idx = m2.newestAcknowledged + 1; idx <= m2.newestMessage; idx++) {
byte[] raw = coe.readDiagnosticMessage(idx, 256);
if (raw != null && raw.length >= 8) {
int diagCode = (raw[0] & 0xFF) | ((raw[1] & 0xFF) << 8)
| ((raw[2] & 0xFF) << 16) | ((raw[3] & 0xFF) << 24);
System.out.printf("Diag[%d]: code=0x%08X, %d 字节%n",
idx, diagCode, raw.length);
}
}
coe.acknowledgeDiagnostic(m2.newestMessage);
}
}
与 C# 的对齐

Java 的 pollHasNewDiagnostic / readDiagnosticMeta / readDiagnosticMessage / acknowledgeDiagnostic 与 C# 的 CoE.PollHasNewDiagnostic / ReadDiagnosticMeta / ReadDiagnosticMessage / AcknowledgeDiagnostic 语义一致,仅遵循 Java camelCase 命名惯例。

Java 特有语法糖

Java 可用 Stream<DiagnosticMessage> 链式过滤 / 排序 / 收集诊断消息, 比 for 循环更短, 配合 DiagnosticStream sugar 还能与 0x10F3 增量轮询合流. 详见 Stream API.

访问权限

ObjAccessFlags

提供对象访问权限判断的静态方法:

  • canRead(int access) — 是否可读
  • canWrite(int access) — 是否可写
  • canWritePreOp(int access) — PreOP 下是否可写
  • canWriteSafeOp(int access) — SafeOP 下是否可写
  • getAccessDescription(int access) — 获取访问权限中文描述

紧急消息

GetEmergencyHistory()

public List<EmergencyMessage> GetEmergencyHistory()

获取紧急消息历史。

ClearEmergencyHistory()

public void ClearEmergencyHistory()

清除紧急消息历史。

相关结构:

public static class EmergencyMessage {
public short ErrorCode; // 错误代码
public byte ErrorRegister; // 错误寄存器
public byte[] Data; // 附加数据
public long Timestamp; // 时间戳
}

示例:

List<CoE.EmergencyMessage> history = coe.GetEmergencyHistory();
for (CoE.EmergencyMessage msg : history) {
System.out.println(msg);
}
coe.ClearEmergencyHistory();

异常类型

CoEAccessDeniedException

public static class CoEAccessDeniedException extends RuntimeException {
public final int Index; // 对象索引
public final int SubIndex; // 子索引
public final String EntryName; // 条目名称
public final boolean IsReadOperation; // true=读, false=写
public final int ObjAccess; // 访问权限标志
}

CoE 对象访问被拒时由 ObjectEntry.getBytes() / setBytes(...) 路径主动抛出(对齐 C# CoE_AccessDeniedException)。通常意味着 PDO 映射对象在 OP 状态下被尝试改写,或 ObjAccess 标记禁止当前状态访问。

示例:

try {
CoE.ObjectEntry entry = coe.getEntryDescription((short) 0x1C12, (byte) 0);
// entry.setBytes(...) 在权限不足时抛出
} catch (CoE.CoEAccessDeniedException ex) {
System.out.printf("对象 0x%04X:%02X (%s) 当前不可访问%n",
ex.Index, ex.SubIndex, ex.EntryName);
}

CoEAbortException

public static class CoEAbortException extends RuntimeException {
public final int slaveIndex; // 从站索引
public final long abortCode; // SDO Abort 码
}

sdoReadAsync / sdoWriteAsync 在失败时抛出(承载 SDO Abort 码与从站索引)。异步 future 以此异常完成,可通过 ex.getCause() 取得。

完整示例

基本读写

CoE coe = slave.CoE();

// 读取设备信息
String deviceName = coe.SDOReadString((short) 0x1008, (byte) 0);
System.out.println("设备名称: " + deviceName);

// 读取/写入控制字
Short statusWord = coe.SDOReadInt16((short) 0x6041, (byte) 0);
System.out.printf("状态字: 0x%04X%n", statusWord & 0xFFFF);

coe.SDOWriteInt16((short) 0x6040, (byte) 0, (short) 0x0006);

// 读取实际位置
Integer actualPos = coe.SDOReadInt32((short) 0x6064, (byte) 0);
System.out.println("实际位置: " + actualPos);

// 错误处理(SDOError 为 EtherCATTypes.SDOError)
byte[] data = coe.SDORead((short) 0x6041, (byte) 0);
if (data == null) {
SDOError error = coe.getLastSdoError();
System.out.printf("SDO 读取失败: %s (0x%08X)%n", error.name(), error.value);
}

遍历对象字典

CoE coe = slave.CoE();

// 使用树结构遍历
CoE.EcODList odList = coe.loadODList();
for (CoE.ObjectDictionary od : odList) {
System.out.printf("0x%04X: %s (子索引=%d)%n",
od.Index & 0xFFFF, od.Name, od.getCount());

// 遍历子索引
for (CoE.ObjectEntry entry : od.getOEList()) {
boolean readable = CoE.ObjAccessFlags.canRead(entry.ObjAccess);
boolean writable = CoE.ObjAccessFlags.canWrite(entry.ObjAccess);
String accessDesc = CoE.ObjAccessFlags.getAccessDescription(entry.ObjAccess);
System.out.printf(" [%d] %s (类型=0x%04X, 位长=%d, %s)%n",
entry.SubIndex, entry.Name, entry.DataType, entry.BitLength, accessDesc);
}
}

// 按索引访问
CoE.ObjectDictionary controlWord = odList.get((short) 0x6040);
if (controlWord != null) {
System.out.println("控制字: " + controlWord.Name);
}

// 按名称访问
CoE.ObjectDictionary devType = odList.get("Device Type");
if (devType != null) {
System.out.printf("Device Type: 0x%04X%n", devType.Index & 0xFFFF);
}

// 批量读取所有子索引数据
Map<Byte, byte[]> allData = controlWord.readAll();
for (Map.Entry<Byte, byte[]> e : allData.entrySet()) {
System.out.printf(" 子索引 %d: %d 字节%n", e.getKey(), e.getValue().length);
}