FoE (File over EtherCAT)
FoE 协议用于 EtherCAT 从站的文件传输,支持固件更新、配置文件上传下载等场景。
通过 slave.FoE() 访问。
属性
| 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|
| DefaultTimeoutMs() / DefaultTimeoutMs(int) | int | 读写 | 默认超时时间(毫秒),默认 5000(5秒) |
| DefaultPassword() / DefaultPassword(int) | int | 读写 | 默认密码,默认 0 |
文件传输
Download(String filename, Integer password, Integer timeoutMs)
public byte[] Download(String filename)
public byte[] Download(String filename, Integer password, Integer timeoutMs)
从从站设备下载(读取)文件。
参数:
filename(String) -- 要下载的文件名password(Integer) -- FoE 密码(不指定则使用DefaultPassword())timeoutMs(Integer) -- 超时时间(毫秒,不指定则使用DefaultTimeoutMs())
返回值:
byte[]-- 文件内容字节数组,失败返回null
示例:
FoE foe = slave.FoE();
byte[] data = foe.Download("firmware.bin");
byte[] data2 = foe.Download("firmware.bin", 0x1234, 10000);
Upload(String filename, byte[] fileData, Integer password, Integer timeoutMs, boolean enableCrc)
public boolean Upload(String filename, byte[] fileData)
public boolean Upload(String filename, byte[] fileData, Integer password, Integer timeoutMs, boolean enableCrc)
上传(写入)文件到从站设备。
参数:
filename(String) -- 要上传的文件名fileData(byte[]) -- 要写入的文件数据password(Integer) -- FoE 密码(不指定则使用DefaultPassword())timeoutMs(Integer) -- 超时时间(毫秒,不指定则使用DefaultTimeoutMs())enableCrc(boolean) -- 启用 CRC32 校验(自动附加校验信息)
返回值:
boolean-- 成功返回true
示例:
FoE foe = slave.FoE();
byte[] fileData = Files.readAllBytes(Path.of("firmware.bin"));
boolean ok = foe.Upload("firmware.bin", fileData);
boolean ok2 = foe.Upload("firmware.bin", fileData, 0x1234, 10000, true);
传输进度
ProgressCallback 接口
@FunctionalInterface
public interface ProgressCallback {
void OnProgress(short slave, int packetNumber, int dataSize, int progressPercent);
}
FoE 传输进度回调接口(函数式接口,支持 Lambda 表达式)。
回调参数:
slave(short) — 从站编号packetNumber(int) — 当前数据包序号(从 1 开始递增)dataSize(int) — 当前数据包的数据大小(字节)progressPercent(int) — 估算的进度百分比 (0-100),需要通过EstimatePacketCount提供总包数才有意义
EnableProgressHook(ProgressCallback callback, int estimatedTotalPackets)
public void EnableProgressHook(ProgressCallback callback, int estimatedTotalPackets)
启用 FoE 进度回调。启用后,Download / Upload 操作会通过回调自动报告传输进度。
参数:
callback(ProgressCallback) — 进度回调函数estimatedTotalPackets(int) — 估算的总数据包数(用于计算progressPercent百分比,可通过EstimatePacketCount计算)
DisableProgressHook()
public void DisableProgressHook()
禁用 FoE 进度回调,清除已注册的回调函数。传输完成后应调用此方法释放回调引用。
EstimatePacketCount(int fileSize, int mailboxSize)
public int EstimatePacketCount(int fileSize)
public int EstimatePacketCount(int fileSize, int mailboxSize)
估算文件传输所需的数据包数量。计算公式: ceil(fileSize / (mailboxSize - 6)),其中 6 字节为 FoE 协议头开销。
参数:
fileSize(int) — 文件大小(字节)mailboxSize(int) — 邮箱大小(字节,默认 512)
返回值:
int— 估算的数据包数量,参数无效时返回 0
示例:
FoE foe = slave.FoE();
byte[] fileData = Files.readAllBytes(Path.of("firmware.bin"));
// 估算包数量
int packets = foe.EstimatePacketCount(fileData.length);
System.out.println("预计传输 " + packets + " 个数据包");
// 启用进度回调(Lambda 表达式)
foe.EnableProgressHook((slaveNo, packetNum, dataSize, percent) -> {
// 显示进度百分比和当前包信息
System.out.printf("\r传输进度: %d%% (包 %d/%d, 本包 %d 字节)",
percent, packetNum, packets, dataSize);
}, packets);
// 执行上传
boolean ok = foe.Upload("firmware.bin", fileData);
// 传输完成后禁用进度回调
foe.DisableProgressHook();
System.out.println(ok ? "\n上传完成!" : "\n上传失败!");
进度回调完整流程
FoE foe = slave.FoE();
foe.DefaultTimeoutMs(30000); // 大文件传输设置较长超时
byte[] firmware = Files.readAllBytes(Path.of("new_firmware.bin"));
// 1. 估算总包数
int totalPackets = foe.EstimatePacketCount(firmware.length);
// 2. 注册进度回调
foe.EnableProgressHook((slaveNo, packetNum, dataSize, percent) -> {
// 构建进度条
int barLen = 30;
int filled = barLen * percent / 100;
StringBuilder bar = new StringBuilder("[");
for (int i = 0; i < barLen; i++)
bar.append(i < filled ? "#" : "-");
bar.append("]");
System.out.printf("\r%s %d%% (包 %d/%d)", bar, percent, packetNum, totalPackets);
}, totalPackets);
// 3. 执行固件上传(带 CRC 校验)
boolean success = foe.Upload("firmware.bin", firmware, null, null, true);
// 4. 清理回调
foe.DisableProgressHook();
if (success) {
System.out.println("\n固件更新成功");
} else {
// 查看失败原因
FoEErrorCode error = foe.getLastError();
System.out.println("\n固件更新失败: " + FoE.getErrorDescription(error));
}
取消传输
用户侧可在传输进行中请求主动取消,DLL 在内部循环下一次迭代时退出。典型用途:用户点"取消"按钮、看门狗超限、上位机停机。
cancel()
public boolean cancel()
请求取消当前 FoE 传输。SDK 在传输主循环下一次迭代时退出。
返回值:
boolean—true表示 SDK 已接受取消请求
clearCancel()
public boolean clearCancel()
清除 FoE 取消标志。通常 Download / Upload 入口会自动清零,仅在异常手动复位路径调用。
返回值:
boolean—true表示清除成功
示例(CompletableFuture 异步配合 cancel):
FoE foe = slave.FoE();
// 启动异步上传
byte[] firmware = Files.readAllBytes(Path.of("firmware.bin"));
CompletableFuture<Boolean> task = CompletableFuture.supplyAsync(
() -> foe.Upload("firmware.bin", firmware));
// 用户点击取消按钮时
cancelButton.addActionListener(e -> {
if (foe.cancel()) {
System.out.println("已请求取消, 等待传输退出...");
}
});
// 等待任务结束(无论成功/失败/取消)
boolean ok = task.get();
if (!ok) {
FoEErrorCode err = foe.getLastError();
System.out.println("上传失败/取消: " + FoE.getErrorDescription(err));
}
cancel() 仅设置取消标志,不会立刻中断传输。实际退出时机取决于当前帧是否已经进入网卡发送队列。调用方应 get() future 等待任务真正返回。
BUSY 进度回调
FoE 协议(ETG.1000.6 Table 93)允许从站在烧写期间返回 BUSY 帧,汇报已完成量 / 总量 / 可选文本。setBusyHook 注册监听后,上位机可在每次 BUSY 帧到达时刷新进度条。
EnableProgressHook— 基于包序号的本地进度估算,不依赖从站上报,任意 FoE 从站通用setBusyHook— 基于从站实际上报 Done/Entire,精度更高,但需要从站实现 BUSY 帧(大多数固件烧写类设备支持)
两者可同时启用互补使用。
setBusyHook(FoEBusyListener listener)
public void setBusyHook(FoEBusyListener listener)
设置 FoE BUSY 回调。传 null 则禁用。
参数:
listener(FoEBusyListener) — BUSY 监听器;null清除 hook
FoEBusyListener 接口
@FunctionalInterface
public interface FoEBusyListener {
void onBusy(FoEBusyEventArgs args);
}
FoEBusyEventArgs
public static class FoEBusyEventArgs {
public short Slave; // 从站编号
public int Done; // 从站报告的已完成量
public int Entire; // 从站报告的总量
public String Text; // 可选 BusyText (可为空)
public int RetryIdx; // 重试序号
}
示例:
FoE foe = slave.FoE();
// 注册 BUSY 回调:按从站实际上报的 Done/Entire 计算百分比
foe.setBusyHook(args -> {
double percent = args.Entire > 0
? 100.0 * args.Done / args.Entire
: 0.0;
String text = (args.Text == null || args.Text.isEmpty())
? "烧写中..." : args.Text;
System.out.printf("\r[BUSY] %.1f%% (%d/%d, retry=%d) %s",
percent, args.Done, args.Entire, args.RetryIdx, text);
});
// 异步上传
byte[] firmware = Files.readAllBytes(Path.of("firmware.bin"));
CompletableFuture<Boolean> task = CompletableFuture.supplyAsync(
() -> foe.Upload("firmware.bin", firmware));
// 用户取消按钮
if (userWantsCancel) {
foe.cancel();
}
boolean ok = task.get();
foe.setBusyHook(null); // 清理回调
System.out.println(ok ? "\n上传完成" : "\n上传失败/取消");
- Java
cancel / clearCancel↔ C#FoE.Cancel() / ClearCancel() - Java
setBusyHook(FoEBusyListener)↔ C#FoE.ProgressChanged事件 +BusyText属性 - 事件字段 Java 用 PascalCase (
Slave / Done / Entire / Text / RetryIdx) 与 C# 保持一致
错误处理
FoE 协议定义的错误码 FoEErrorCode,用于诊断传输失败原因:
| 枚举值 | 值 | 说明 |
|---|---|---|
| NOT_DEFINED | 0x8000 | 未定义错误 |
| NOT_FOUND | 0x8001 | 文件未找到 |
| ACCESS_DENIED | 0x8002 | 访问被拒绝 |
| DISK_FULL | 0x8003 | 磁盘已满 |
| ILLEGAL | 0x8004 | 非法操作 |
| PACKET_NUMBER_WRONG | 0x8005 | 数据包序号错误 |
| ALREADY_EXISTS | 0x8006 | 文件已存在 |
| NO_USER | 0x8007 | 无此用户 |
| BOOTSTRAP_ONLY | 0x8008 | 仅 Bootstrap 模式可用 |
| NOT_BOOTSTRAP | 0x8009 | 不在 Bootstrap 模式 |
| NO_RIGHTS | 0x800A | 权限不足 |
| PROGRAM_ERROR | 0x800B | 程序错误 |
getErrorDescription(FoEErrorCode errorCode)
public static String getErrorDescription(FoEErrorCode errorCode)
获取 FoE 错误码的中文描述文本。
getLastError()
public FoEErrorCode getLastError()
获取最近一次 FoE 操作的错误码。当 Download / Upload 失败时,可通过此方法获取具体错误原因。
示例:
FoE foe = slave.FoE();
byte[] data = foe.Download("firmware.bin");
if (data == null) {
FoEErrorCode error = foe.getLastError();
String desc = FoE.getErrorDescription(error);
System.out.println("下载失败: " + desc);
}
完整示例
FoE foe = slave.FoE();
foe.DefaultTimeoutMs(10000);
// 备份固件
byte[] backup = foe.Download("firmware.bin");
if (backup != null)
Files.write(Path.of("backup.bin"), backup);
// 上传新固件
byte[] newFirmware = Files.readAllBytes(Path.of("new_firmware.bin"));
boolean ok = foe.Upload("firmware.bin", newFirmware);
System.out.println(ok ? "更新成功" : "更新失败");