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 原始字节数据。默认超时 500ms。
参数:
index(short) — 对象索引subIndex(byte) — 子索引timeoutMs(int) — 超时(毫秒)
返回值:
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 原始字节数据。
类型化写入
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.getValue());
}
SDOError 枚举
SDO 操作错误代码(CANopen 标准 / ETG.1020)。
| 名称 | 值 | 说明 |
|---|---|---|
| NO_ERROR | 0x00000000 | 无错误 |
| TOGGLE_BIT_NOT_CHANGED | 0x05030000 | Toggle 位未改变 |
| SDO_PROTOCOL_TIMEOUT | 0x05040000 | SDO 协议超时 |
| INVALID_COMMAND_SPECIFIER | 0x05040001 | 无效的命令标识符 |
| OUT_OF_MEMORY | 0x05040005 | 内存不足 |
| UNSUPPORTED_ACCESS | 0x06010000 | 不支持的访问 |
| READ_WRITE_ONLY_ERROR | 0x06010001 | 尝试读取只写对象 |
| WRITE_READ_ONLY_ERROR | 0x06010002 | 尝试写入只读对象 |
| SUBINDEX_WRITE_ERROR | 0x06010003 | 子索引不允许写入 |
| COMPLETE_ACCESS_NOT_SUPPORTED | 0x06010004 | 不支持完全访问 |
| OBJECT_LENGTH_EXCEEDED | 0x06010005 | 对象长度超限 |
| OBJECT_MAPPED_TO_RXPDO | 0x06010006 | 对象已映射到 RxPDO |
| OBJECT_DOES_NOT_EXIST | 0x06020000 | 对象不存在 |
| CANNOT_BE_MAPPED_TO_PDO | 0x06040041 | 不可映射到 PDO |
| EXCEEDS_PDO_LENGTH | 0x06040042 | 超出 PDO 长度 |
| PARAMETER_INCOMPATIBILITY | 0x06040043 | 参数不兼容 |
| INTERNAL_INCOMPATIBILITY | 0x06040047 | 内部不兼容 |
| HARDWARE_ACCESS_ERROR | 0x06060000 | 硬件访问错误 |
| DATA_TYPE_MISMATCH | 0x06070010 | 数据类型不匹配 |
| DATA_TYPE_TOO_HIGH | 0x06070012 | 数据类型长度过大 |
| DATA_TYPE_TOO_LOW | 0x06070013 | 数据类型长度过小 |
| SUBINDEX_DOES_NOT_EXIST | 0x06090011 | 子索引不存在 |
| VALUE_RANGE_EXCEEDED | 0x06090030 | 值超出范围 |
| VALUE_TOO_HIGH | 0x06090031 | 值过大 |
| VALUE_TOO_LOW | 0x06090032 | 值过小 |
| MODULE_IDENT_LIST_MISMATCH | 0x06090033 | 模块列表不匹配 |
| MAX_LESS_THAN_MIN | 0x06090036 | 最大值小于最小值 |
| GENERAL_ERROR | 0x08000000 | 一般错误 |
| DATA_TRANSFER_ERROR | 0x08000020 | 数据传输错误 |
| LOCAL_CONTROL_ERROR | 0x08000021 | 本地控制错误 |
| DEVICE_STATE_ERROR | 0x08000022 | 设备状态错误 |
| DICTIONARY_ERROR | 0x08000023 | 字典错误 |
| UNKNOWN_ERROR | 0xFFFFFFFF | 未知错误 |
EcDataType 枚举
EtherCAT 数据类型枚举(基于 ETG.1000.6),决定类型化读取的目标类型。
| 枚举值 | 值 | Java 类型 | 说明 |
|---|---|---|---|
| BOOLEAN | 0x0001 | boolean | 布尔 |
| INTEGER8 | 0x0002 | byte | int8 |
| INTEGER16 | 0x0003 | short | int16 |
| INTEGER32 | 0x0004 | int | int32 |
| UNSIGNED8 | 0x0005 | byte | uint8 (Java 无无符号,使用 & 0xFF) |
| UNSIGNED16 | 0x0006 | int | uint16 (Java 使用 int 存储) |
| UNSIGNED32 | 0x0007 | long | uint32 (Java 使用 long 存储) |
| REAL32 | 0x0008 | float | 单精度浮点 |
| VISIBLE_STRING | 0x0009 | String | ASCII 字符串 |
| OCTET_STRING | 0x000A | byte[] | 字节串 |
| UNICODE_STRING | 0x000B | String | Unicode 字符串 |
| REAL64 | 0x0011 | double | 双精度浮点 |
| INTEGER64 | 0x0015 | long | int64 |
| UNSIGNED64 | 0x001B | long | uint64 |
| DOMAIN | 0x000F | byte[] | 原始数据 |
CoEObjectAccess 标志
对象访问权限标志位。
| 标志 | 值 | 说明 |
|---|---|---|
| READ_PREOP | 0x01 | PreOp 可读 |
| READ_SAFEOP | 0x02 | SafeOp 可读 |
| READ_OP | 0x04 | OP 可读 |
| WRITE_PREOP | 0x08 | PreOp 可写 |
| WRITE_SAFEOP | 0x10 | SafeOp 可写 |
| WRITE_OP | 0x20 | OP 可写 |
对象字典
ODList()
public Map<Short, List<ObjectEntry>> ODList()
获取从站完整对象字典。
相关结构:
public static class ObjectEntry {
public short ODIndex; // 对象索引
public byte SubIndex; // 子索引
public String Name; // 名称
public short DataType; // 数据类型(见 EcDataType 枚举)
public int BitLength; // 位长度
public 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());
}
诊断消息
readDiagnosticMessages()
public List<DiagnosticMessage> readDiagnosticMessages()
读取 ETG.1510 诊断历史消息(0x10F3)。一次性读取全量历史,适合离线诊断或故障现场快照。
相关结构:
public static class DiagnosticMessage {
public byte SubIndex; // 子索引
public int DiagCode; // 诊断码
public short Flags; // 标志位
public short TextIndex; // 文本索引
public byte[] RawData; // 原始数据
}
新版诊断历史 API (0x10F3)
基于 ETG.1020 §16 定义,用四个轻量原语(coe_diag_*)实现增量轮询的诊断历史访问,相比 readDiagnosticMessages() 一次读全量更适合运行时高频监听场景。
readDiagnosticMessages()— 一次性读取全量诊断历史,适合离线分析、故障现场转储pollHasNewDiagnostic+readDiagnosticMeta+readDiagnosticMessage+acknowledgeDiagnostic— 轮询 + 按需读取 + 逐条确认,适合运行时监听和环形缓冲管理
pollHasNewDiagnostic()
public int pollHasNewDiagnostic()
快速轮询 0x10F3:04 NewAvailable 标志。无新消息时不会读取完整历史,适合 1~10Hz 高频轮询。
返回值:
int—1有新消息;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 字段按 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);
}
}
Java 的 pollHasNewDiagnostic / readDiagnosticMeta / readDiagnosticMessage / acknowledgeDiagnostic 与 C# 的 CoE.PollHasNewDiagnostic / ReadDiagnosticMeta / ReadDiagnosticMessage / AcknowledgeDiagnostic 语义一致,仅遵循 Java camelCase 命名惯例。
访问权限
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();
完整示例
基本读写
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);
// 错误处理
byte[] data = coe.SDORead((short) 0x6041, (byte) 0);
if (data == null) {
CoE.SDOError error = coe.getLastSdoError();
System.out.printf("SDO 读取失败: %s (0x%08X)%n", error.name(), error.getValue());
}
遍历对象字典
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);
}