跳到主要内容

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_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 字符串
REAL640x0011double双精度浮点
INTEGER640x0015longint64
UNSIGNED640x001Blonguint64
DOMAIN0x000Fbyte[]原始数据

CoEObjectAccess 标志

对象访问权限标志位。

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

对象字典

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() 一次读全量更适合运行时高频监听场景。

新旧 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 命名惯例。

访问权限

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);
}