FoE (File over EtherCAT)
FoE 协议用于 EtherCAT 从站的文件传输,支持固件更新、配置文件上传下载等场景。
通过 slave.GetFoE() 获取 FoE& 引用。
文件读取(下载)
Download()
std::vector<uint8_t> Download(const std::string& filename,
uint32_t password = 0,
int timeoutMs = 0) const;
从从站设备读取(下载)文件。失败返回空 vector。
参数:
filename— 要读取的文件名password— FoE 密码(0 = 使用默认密码)timeoutMs— 超时(0 = 使用默认超时)
示例:
auto& foe = master.GetSlave(1).GetFoE();
// 读取配置文件
auto config = foe.Download("config.xml");
if (!config.empty()) {
printf("配置内容 (%zu 字节):\n%.*s\n",
config.size(), (int)config.size(), config.data());
}
文件写入(上传)
Upload()
bool Upload(const std::string& filename, const uint8_t* data, int dataLen,
uint32_t password = 0, int timeoutMs = 0) const;
bool Upload(const std::string& filename, const std::vector<uint8_t>& data,
uint32_t password = 0, int timeoutMs = 0) const;
上传(写入)文件到从站设备。支持原始指针和 std::vector 两种方式。
示例:
auto& foe = master.GetSlave(1).GetFoE();
// 写入配置
std::string new_config = "<Config><Param1>100</Param1></Config>";
std::vector<uint8_t> data(new_config.begin(), new_config.end());
bool ok = foe.Upload("config.xml", data, 0x12345678);
printf("%s\n", ok ? "写入成功" : "写入失败");
默认参数
auto& foe = master.GetSlave(1).GetFoE();
// 默认超时
foe.DefaultTimeoutMs(10000); // 10秒
printf("默认超时: %d ms\n", foe.DefaultTimeoutMs());
// 默认密码
foe.DefaultPassword(0x12345678);
printf("默认密码: 0x%08X\n", foe.DefaultPassword());
传输进度
SetProgressCallback()
void SetProgressCallback(FoEProgressCallback cb);
设置文件传输进度回调。设置后,Download / Upload / DownloadEx / UploadEx 操作会自动触发回调,每传输一个数据包调用一次。
回调类型:
using FoEProgressCallback = std::function<void(
uint16_t slave, int packetNumber, int dataSize, int progressPercent)>;
回调参数:
slave(uint16_t) — 从站编号packetNumber(int) — 当前数据包序号(从 1 开始递增)dataSize(int) — 当前包传输的数据大小(字节)progressPercent(int) — 估算的进度百分比 (0-100)
EstimatePacketCount()
static int EstimatePacketCount(int fileSize, int mailboxSize = 512);
估算文件传输所需的数据包数量。FoE 协议头占 6 字节,因此每包有效数据为 mailboxSize - 6 字节。可用于进度百分比计算。
参数:
fileSize(int) — 文件大小(字节)mailboxSize(int) — 邮箱大小(字节,默认 512)
返回值:
int— 估算的数据包数量
基本用法
auto& foe = master.GetSlave(1).GetFoE();
// 设置进度回调
foe.SetProgressCallback([](uint16_t slave, int pkt, int sz, int percent) {
printf("\r从站 %d 传输进度: %d%% (包 %d, %d 字节)", slave, percent, pkt, sz);
fflush(stdout);
});
// 上传文件
bool ok = foe.Upload("firmware.bin", firmware);
printf("\n%s\n", ok ? "上传完成" : "上传失败");
完整示例: 带进度和错误处理的固件更新
#include "ethercat.hpp"
#include <fstream>
#include <iterator>
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();
auto& foe = master.GetSlave(1).GetFoE();
// 设置超时和密码
foe.DefaultTimeoutMs(30000);
// 加载固件文件
std::ifstream file("new_firmware.bin", std::ios::binary);
std::vector<uint8_t> firmware(
(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
// 估算包数量
int totalPackets = FoE::EstimatePacketCount(static_cast<int>(firmware.size()));
printf("文件大小: %zu 字节, 预计 %d 个数据包\n", firmware.size(), totalPackets);
// 设置进度回调 (带百分比计算)
foe.SetProgressCallback([totalPackets](uint16_t slave, int pkt, int sz, int percent) {
// percent 由 SDK 自动计算; 也可以手动计算:
// int manualPercent = totalPackets > 0 ? (pkt * 100) / totalPackets : 0;
printf("\r固件上传: %d%% (包 %d/%d)", percent, pkt, totalPackets);
fflush(stdout);
});
// 上传固件
bool ok = foe.Upload("firmware.bin", firmware);
printf("\n");
if (ok) {
printf("固件更新成功\n");
} else {
// 获取错误详情
auto error = foe.LastErrorCode();
auto desc = FoE::GetErrorDescription(error);
printf("固件更新失败: %s (0x%04X)\n", desc.c_str(), static_cast<uint16_t>(error));
}
getchar();
return 0;
}
带 CRC 校验的下载
auto& foe = master.GetSlave(1).GetFoE();
// 设置进度回调
foe.SetProgressCallback([](uint16_t slave, int pkt, int sz, int percent) {
printf("\r下载进度: %d%% (包 %d)", percent, pkt);
fflush(stdout);
});
// 使用 CRC 校验下载文件
auto data = foe.Download("config.bin", -1, 0, true /* enableCrc */);
printf("\n");
if (!data.empty()) {
printf("下载成功: %zu 字节\n", data.size());
} else {
printf("下载失败: %s\n",
FoE::GetErrorDescription(foe.LastErrorCode()).c_str());
}
异步 API (std::future)
长时间的 FoE 传输(固件升级动辄数 MB / 数分钟)会阻塞调用线程,C++ SDK 在同步 Download / Upload / DownloadEx / UploadEx 之外提供 4 个 std::future 风格的异步入口,内部用 std::async(std::launch::async) 把同步调用甩到后台线程。语义对齐 C# FoEInstance.DownloadAsync / UploadAsync / Java downloadAsync / uploadAsync / Rust download_async / upload_async。
std::future 本身不能强制中断后台线程。要真正提前结束传输,需配合 Cancel() — 后台 std::async 线程下一次 mailbox 迭代退出时 future.get() 才会立即返回。SetBusyHook() 注册的回调在 *Async 路径上同样生效。
DownloadAsync()
std::future<std::vector<uint8_t>> DownloadAsync(
const std::string& filename,
uint32_t password = 0,
int timeoutMs = 0) const;
异步从从站读取(下载)文件。失败时 future 持有空 vector,可继续通过 LastErrorCode() 查询错误码。
UploadAsync()
std::future<bool> UploadAsync(
const std::string& filename,
std::vector<uint8_t> data,
uint32_t password = 0,
int timeoutMs = 0) const;
异步上传(写入)文件到从站。data 按值捕获,可在调用后立即析构原对象。
DownloadExAsync() / UploadExAsync()
std::future<std::vector<uint8_t>> DownloadExAsync(
const std::string& filename, uint32_t password = 0,
int timeoutMs = 0, bool enableCrc = false) const;
std::future<bool> UploadExAsync(
const std::string& filename, std::vector<uint8_t> data,
uint32_t password = 0, int timeoutMs = 0, bool enableCrc = false) const;
*Ex 系列的异步版本,额外暴露 enableCrc 参数(启用 CRC 段校验)。
示例 (并行升级多个从站):
#include <future>
#include <vector>
auto& foeA = master.GetSlave(1).GetFoE();
auto& foeB = master.GetSlave(2).GetFoE();
auto fa = foeA.UploadAsync("firmware.bin", LoadFirmware("a.bin"));
auto fb = foeB.UploadAsync("firmware.bin", LoadFirmware("b.bin"));
bool okA = fa.get(); // 阻塞等待从站 1 完成
bool okB = fb.get(); // 阻塞等待从站 2 完成
printf("A=%d B=%d\n", okA, okB);
// 配合 std::future_status::ready 做轮询 / UI 进度
auto fut = foeA.DownloadAsync("config.bin");
while (fut.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) {
UpdateUiProgressBar();
}
auto data = fut.get();
错误处理
FoEErrorCode 枚举定义传输失败的诊断错误码:
| 枚举值 | 值 | 说明 |
|---|---|---|
| NotDefined | 0x8000 | 未定义错误 |
| NotFound | 0x8001 | 文件未找到 |
| AccessDenied | 0x8002 | 访问被拒绝 |
| DiskFull | 0x8003 | 磁盘已满 |
| Illegal | 0x8004 | 非法操作 |
| PacketNumberWrong | 0x8005 | 数据包序号错误 |
| AlreadyExists | 0x8006 | 文件已存在 |
| NoUser | 0x8007 | 无此用户 |
| BootstrapOnly | 0x8008 | 仅 Bootstrap 模式可用 |
| NotBootstrap | 0x8009 | 不在 Bootstrap 模式 |
| NoRights | 0x800A | 权限不足 |
| ProgramError | 0x800B | 程序错误 |
GetErrorDescription()
static std::string GetErrorDescription(FoEErrorCode errorCode);
获取 FoE 错误码的中文描述文本。
LastErrorCode() / GetLastError()
FoEErrorCode LastErrorCode() const;
FoEErrorCode GetLastError() const;
获取最近一次 FoE 操作的错误码。当 Download / Upload 失败时,可通过此方法获取具体错误原因。
示例:
auto& foe = master.GetSlave(1).GetFoE();
auto data = foe.Download("firmware.bin");
if (data.empty()) {
auto error = foe.LastErrorCode();
auto desc = FoE::GetErrorDescription(error);
printf("下载失败: %s\n", desc.c_str());
}
完整示例
固件更新
#include "ethercat.hpp"
#include <fstream>
#include <iterator>
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();
auto& foe = master.GetSlave(1).GetFoE();
// 设置超时
foe.DefaultTimeoutMs(30000); // 30秒
// 加载固件文件
std::ifstream file("new_firmware.bin", std::ios::binary);
std::vector<uint8_t> firmware(
(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
// 上传固件
bool ok = foe.Upload("firmware.bin", firmware);
printf("%s\n", ok ? "固件更新成功" : "固件更新失败");
getchar();
return 0;
}
FoE Cancel / BusyHook
长时间的 FoE 传输(固件升级可达数分钟)需要两个能力:用户取消(关闭对话框、超时、应急停止时中止传输)与细粒度进度(含当前包位置、重试计数、文本描述)。SDK 提供 Cancel() / ClearCancel() / SetBusyHook() 三个 API 组合使用。
FoEBusyEvent
struct FoEBusyEvent {
int slave_index; // 从站索引
uint16_t done; // 已完成包数
uint16_t entire; // 总包数
std::string text; // 状态描述 ("Downloading" / "Erasing flash" / "Verifying" 等)
int retry_index; // 重试计数 (0 表示首次, >0 表示第 N 次重试)
double Percent() const {
return entire > 0 ? 100.0 * done / entire : 0.0;
}
};
Cancel()
class FoE {
public:
bool Cancel();
};
请求取消当前正在进行的 FoE 传输。设置内部取消标志,SDK 在下一次包边界检测到标志后中止传输并返回错误。返回 true 表示取消标志已设置(并不代表传输已实际停止,需等待传输线程响应)。
ClearCancel()
bool ClearCancel();
清除取消标志,为下一次传输做准备。通常在取消后、启动新的 Download / Upload 前调用。
SetBusyHook()
using BusyCallback = std::function<void(const FoEBusyEvent&)>;
void SetBusyHook(BusyCallback cb);
注册 Busy 回调。与 SetProgressCallback 不同,BusyHook 提供更细粒度的状态信息(包括重试计数和文本描述),适合显示在 UI 进度条上。每次包传输前后均会触发。
示例 (C++20 std::jthread + stop_token 取消):
using namespace std::chrono_literals;
auto& foe = master.GetSlave(1).GetFoE();
// 设置 BusyHook 显示详细进度
foe.SetBusyHook([](const FoEBusyEvent& e) {
std::cout << "[从站 " << e.slave_index << "] "
<< e.Percent() << "% "
<< e.text;
if (e.retry_index > 0)
std::cout << " (重试 " << e.retry_index << ")";
std::cout << std::endl;
});
// 清除旧的取消标志
foe.ClearCancel();
// 用 std::jthread 启动固件上传,支持外部取消
std::vector<uint8_t> firmware = LoadFirmware();
std::jthread worker([&](std::stop_token st) {
auto fut = std::async(std::launch::async, [&] {
return foe.Upload("firmware.bin", firmware);
});
// 轮询 stop_token, 触发 cancel
while (fut.wait_for(100ms) == std::future_status::timeout) {
if (st.stop_requested()) {
foe.Cancel();
break;
}
}
bool ok = fut.get();
std::cout << (ok ? "上传完成" : "上传取消或失败") << "\n";
});
// 用户点击取消按钮 / 超时等场景
std::this_thread::sleep_for(30s);
worker.request_stop(); // jthread 自动 join
Cancel()仅设置标志,实际中止发生在下一次包边界(最多延迟一个包的传输时间,通常 <100ms)- 取消后的
Upload/Download返回false,LastErrorCode()可查询具体原因 - 取消不会破坏从站 Flash:SDK 仅在接收到从站 ACK 后递增已完成包数,中途取消从站会自动丢弃未完成的写入块
- ProgressCallback (
SetProgressCallback): 简单百分比 + 包号 + 字节数,适合命令行工具 - BusyHook (
SetBusyHook): 增加文本描述和重试计数,适合 GUI 显示状态栏或日志 - 两者可同时注册,互不影响
ETG.1020 §15 FoE 取消语义; ETG.5003 §8 固件升级最佳实践。