跳到主要内容

CoE (CANopen over EtherCAT)

通过 slave.CoE() 访问。​

快速开始​

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

CoE coe = slave.CoE();

// 原始字节读写 — 失败 / WKC=0 返回 null,必须先判空再组整数
byte[] data = coe.SDORead((short) 0x6041, (byte) 0);
if (data == null) {
System.out.printf("SDO 失败: %s%n", coe.getLastSdoError()); // GENERAL_ERROR ≠ 对象不存在
return;
}
int statusWord = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8);
coe.SDOWrite((short) 0x6040, (byte) 0, new byte[]{0x06, 0x00});

// 类型化读取 — 失败同样返回 null
Short status = coe.SDOReadInt16((short) 0x6041, (byte) 0);
if (status == null) return;
Integer position = coe.SDOReadInt32((short) 0x6064, (byte) 0);
if (position == null) return;
String name = coe.SDOReadString((short) 0x1008, (byte) 0);

// 类型化写入
coe.SDOWriteInt16((short) 0x6040, (byte) 0, (short) 0x0006);
coe.SDOWriteInt32((short) 0x607A, (byte) 0, 100000);
SDORead 失败 / WKC=0 返回 null

不要对 SDORead 的返回值直接拼 int / long。getLastSdoError() == GENERAL_ERROR 是邮箱没回来(WKC=0 / 超时),不是「对象不存在」。真 abort(OBJECT_DOES_NOT_EXIST 0x06020000 / SUBINDEX_DOES_NOT_EXIST 0x06090011 / 0x06090012)才进 NotPresent 缓存。

SDO 读取​

SDORead(short index, byte subIndex)​

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

读取 SDO 原始字节数据。默认超时 5000ms。

失败 / WKC=0 返回 null。 空 byte[0] + NO_ERROR 才是空字符串成功。GENERAL_ERROR (0x08000000) 不是「对象不存在」,也不写入 NotPresent;真 abort 才缓存。

参数:

  • 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
  • 单次读取长度上限 64 KB:SDO 读取与 OD 枚举的应答数据超过 64 KB 时按读失败返回 null,超大对象请拆分读取

SDOReadEx()​

public CoE.SdoReadResult SDOReadEx(short index, byte subIndex)
public CoE.SdoReadResult SDOReadEx(short index, byte subIndex, boolean completeAccess)

一次返回 ok / data / abortCode / wkc。WKC≤0 为失败(GENERAL_ERROR 不进 NotPresent)。热插拔后调 resetNotPresentCache()。清除 OD 缓存时会同步清除 SDO 缓存与未命中对象缓存。应答数据超过 64 KB 时按失败返回(ok=false,data=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。在 ForkJoinPool.commonPool() 上执行;底层失败时返回的 CompletableFuture 以 CoEAbortException 异常完成(承载 SDO Abort 码与从站索引)。

示例:

CoE coe = slave.CoE();

coe.sdoReadAsync((short) 0x6041, (byte) 0)
.thenAccept(data -> {
if (data == null || data.length < 2) {
System.err.printf("SDO 失败: %s (GENERAL_ERROR 不是对象不存在)%n",
coe.getLastSdoError());
return;
}
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 请求的细节。

GENERAL_ERROR 不是「对象不存在」
  • SDOError.GENERAL_ERROR (0x08000000) — WKC=0 / 邮箱超时 / 通信未回,abort 可能是上一次残留,不当对象不存在,也 不写入 NotPresent。
  • 真 abort 才进 NotPresent:OBJECT_DOES_NOT_EXIST (0x06020000)、SUBINDEX_DOES_NOT_EXIST (0x06090011)、0x06090012。

示例:

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);
if (error == SDOError.GENERAL_ERROR)
System.out.println("WKC=0 / 超时,不是对象不存在");
}

邮箱统计​

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 标志​

对象访问权限标志位(常量挂在 CoE.ObjAccessFlags 上)。

常量值说明
READ_PRE0x01PreOp 可读
READ_SAFE0x02SafeOp 可读
READ_OP0x04OP 可读
WRITE_PRE0x08PreOp 可写
WRITE_SAFE0x10SafeOp 可写
WRITE_OP0x20OP 可写

辅助常量:MAPPABLE / BACKUP / SETTINGS(属性位)、READ_ANY = 0x07、WRITE_ANY = 0x38。

对象字典​

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()

异步加载对象字典。默认真发 SDO Information(不走 ESI / 预加载缓存)。

行为:

  • 正在加载中 → 不重复启动,返回 null
  • 空字典不是成功:SDO Information / WKC=0 扫出 0 个对象时返回 null,getCount()==0 不要当已加载成功
  • OP / SafeOp 只发 SDO Information Basic 0x01(GetSlaveSDOListBasic,仅对象索引),不全量读对象描述 0x03
  • PreOp 才完整加载子索引

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();
if (odList == null || odList.getCount() == 0) {
System.out.printf("对象字典加载失败: %s%n", coe.getLastSdoError());
return;
}
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 缓存路径。适合需要从从站实时读取 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 / MaxSub 的 ObjectDictionary 元数据(不含子条目)。对象不存在或从站不支持 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 / Name 的 ObjectEntry。失败返回 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 高频轮询。

返回值:

  • 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 位含义

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 特有语法糖

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

访问权限​

ObjAccessFlags​

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

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

紧急消息​

GetEmergencyHistory()​

public List<EmergencyMessage> GetEmergencyHistory()

获取紧急消息历史。

ClearEmergencyHistory()​

public void ClearEmergencyHistory()

清除紧急消息历史。

相关结构:

public static class EmergencyMessage {
public final short ErrorCode; // 错误代码
public final byte ErrorRegister; // 错误寄存器
public final byte[] Data; // 附加数据
public final short SlaveIndex; // 从站索引
public final 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(...) 路径主动抛出。通常意味着 PDO 映射对象在 OP 状态下被尝试改写,或条目明确只读(OD 表只在明确只读时才拦,残缺 OD 缓存放行、由从站 SDO abort 当真相源,ECM-SDDOC1)。SDO 写门槛为 PreOp 及以上(Init 拒)。

示例:

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);
if (deviceName != null)
System.out.println("设备名称: " + deviceName);

// 读取/写入控制字
Short statusWord = coe.SDOReadInt16((short) 0x6041, (byte) 0);
if (statusWord == null) {
SDOError error = coe.getLastSdoError();
System.out.printf("SDO 读取失败: %s (0x%08X)%n", error.name(), error.value);
return;
}
System.out.printf("状态字: 0x%04X%n", statusWord & 0xFFFF);

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

// 读取实际位置
Integer actualPos = coe.SDOReadInt32((short) 0x6064, (byte) 0);
if (actualPos != null)
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);
if (error == SDOError.GENERAL_ERROR)
System.out.println("WKC=0 / 超时,不是对象不存在");
}

遍历对象字典​

CoE coe = slave.CoE();

// 使用树结构遍历(空字典不是成功)
CoE.EcODList odList = coe.loadODList();
if (odList == null || odList.getCount() == 0) {
System.out.printf("对象字典加载失败: %s%n", coe.getLastSdoError());
return;
}
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);
}