跳到主要内容

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 在传输主循环下一次迭代时退出。

返回值:

  • booleantrue 表示 SDK 已接受取消请求

clearCancel()

public boolean clearCancel()

清除 FoE 取消标志。通常 Download / Upload 入口会自动清零,仅在异常手动复位路径调用。

返回值:

  • booleantrue 表示清除成功

示例(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() 是请求式

cancel() 仅设置取消标志,不会立刻中断传输。实际退出时机取决于当前帧是否已经进入网卡发送队列。调用方应 get() future 等待任务真正返回。

BUSY 进度回调

FoE 协议(ETG.1000.6 Table 93)允许从站在烧写期间返回 BUSY 帧,汇报已完成量 / 总量 / 可选文本setBusyHook 注册监听后,上位机可在每次 BUSY 帧到达时刷新进度条。

BUSY vs ProgressCallback
  • 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上传失败/取消");
与 C# 的对齐
  • 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_DEFINED0x8000未定义错误
NOT_FOUND0x8001文件未找到
ACCESS_DENIED0x8002访问被拒绝
DISK_FULL0x8003磁盘已满
ILLEGAL0x8004非法操作
PACKET_NUMBER_WRONG0x8005数据包序号错误
ALREADY_EXISTS0x8006文件已存在
NO_USER0x8007无此用户
BOOTSTRAP_ONLY0x8008仅 Bootstrap 模式可用
NOT_BOOTSTRAP0x8009不在 Bootstrap 模式
NO_RIGHTS0x800A权限不足
PROGRAM_ERROR0x800B程序错误

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 ? "更新成功" : "更新失败");