控制器插件说明(electricclawplugin-controller)
开发环境
| 环境 | 工具链 |
|---|---|
| linux X86 / aarch64 | gcc/g++ 5.4.0,采取交叉编译的方式执行 |
| 构建工具 | cmake 3.5 |
| 依赖 | xCore 3.2.1 |
目录结构
| 目录 | 说明 |
|---|---|
| 3rdparty | 第三方依赖库 |
| ElectricClawPlugin | 电爪逻辑实现代码(项目主体) |
| examples | 插件使用示例,可参考了解 API 的使用方式 |
| xcore_api | xCore 系统 API 目录(对接平台的核心接口) |
| CMakeLists.txt | CMake 配置文件 |
| Src | 本项目以 ElectricClawPlugin 目录作为代码主体,此目录仅保留空示例 |
控制器插件工程的通用目录说明(config、src、launch 入口等)见 控制器插件工程。
代码说明
文件组成
ElectricClawPlugin 目录为项目主体,各文件职责如下:
| 文件 | 职责 |
|---|---|
| ElectricClawPlugin.h/.cpp | 插件入口类:继承 LaunchAPI,在插件加载时完成 RL 指令与插件指令的注册 |
| ElectricClawAPI.h/.cpp | 电爪控制核心接口:基于 Modbus 封装 启动、停止、获取状态 三个功能 |
| ElectricClawRLCmd.h/.cpp | RL 指令注册与实现:ElectricClawStart、ElectricClawStop、ElectricClawStatus |
| ElectricClawService.h/.cpp | 插件指令(自定义 JSON 协议)的注册与处理,响应 HMI 端请求 |
| CMakeLists.txt | 子目录构建配置:收集源文件并加入主工程编译 |
模块间的协作关系:RLCmd 与 Service 是两条入口(分别对应 RL 程序执行和 HMI 界面操作),二者最终都调用 API 模块完成电爪的实际控制。
CMakeLists.txt
该代码的最终编译成果为 动态库,由控制器调用使用,因此:
- 项目中的三方库不需要动态库文件,在加载时由控制器提供;
CMakeLists.txt设置了动态库相关的属性,如 C++11 支持、方法外部不可见(符号可见性)、-fPIC等。
ElectricClawPlugin 子目录下的 CMakeLists.txt 通过 FILE(GLOB_RECURSE ...) 自动收集本目录全部源文件,追加到主工程编译目标中,并将编译产物名称设置为工程名。进行参考开发时只需将项目名称替换为自己的即可。
核心模块
| 模块 | 说明 |
|---|---|
| *API | 电爪控制核心接口。工艺包中的电爪控制/状态获取与 RL 指令中的功能存在重合,因此提取 开始、停止、获取状态 三个功能放在 API 中统一供两端调用 |
| *Plugin | 插件初始化入口类。在加载该插件时进行初始化,即在控制器中注册 RL 指令和插件指令处理 |
| *RLCmd | RL 指令注册与实现。提供 RL 指令注册函数,需要指定:指令参数形式、指令实现函数(获取到参数后直接调用 API 执行)以及指令属性(指令前瞻方式、任务限制类型、指令是否需要括号、返回值类型、步退的类型) |
| *Service | 注册和实现 HMI 发来的插件指令的处理过程 |
插件入口(ElectricClawPlugin)
入口类继承 xcore_api::launch::LaunchAPI,并通过 LAUNCH_MODULE 宏声明为初始化模块(不声明则不会执行初始化逻辑):
class ElectricClawPlugin : public xcore_api::launch::LaunchAPI {
public:
explicit ElectricClawPlugin();
virtual void Init();
virtual void Start();
virtual void Stop();
virtual ~ElectricClawPlugin();
};
LAUNCH_MODULE(ElectricClawPlugin);
继承类的生命周期:
- 加载插件时调用 构造函数(无依赖关系的插件构造顺序未定义;若插件 A 依赖插件 B,保证 A 比 B 后加载);
- 插件加载完毕,按优先级依次调用各模块的
Init()、Start()、Stop(); - 卸载时调用 析构函数(析构顺序未定义,无论是否有依赖关系)。
本项目在 Init() 中完成两项注册,所有插件的 Init() 执行完毕后才会进入 Start() 阶段:
void ElectricClawPlugin::Init()
{
ElectricClawCmd(); // 注册 RL 指令(见 ElectricClawRLCmd)
ServiceProtocol(); // 注册插件指令处理(见 ElectricClawService)
}
控制接口(ElectricClawAPI)
ElectricClawAPI 是对电动夹爪 Modbus 通信的封装类,内部持有底层通信对象 xcore_api::endtool::ModbusRTUEndtoolAPI,对外提供三个接口:
| 接口 | 功能 | Modbus 操作 |
|---|---|---|
startElectricClaw(slave_id, width, force, speed, mode) | 启动夹爪 | WriteRegister_10(写多寄存器):从 0x0000 起连续写入 4 个寄存器——宽度、力度、速度、模式 |
stopElectricClaw(slave_id) | 停止夹爪 | WriteRegister_06(写单寄存器):向 0x0003 写入值 3(停止命令) |
getStatusElectricClaw(slave_id, status, external_width, internal_width, force) | 获取夹爪状态 | ReadRegister_03(读保持寄存器):从 0x0100 起读 8 个寄存器,data[0] 状态码、data[1] 外宽、data[2] 内宽、data[7] 实际力度 |
三个接口均内置 失败重试机制:最多重试 3 次,每次间隔 10 ms,失败时通过 LOG(WARNING) 记录重试次数:
for (int attempt = 1; attempt <= maxRetries; ++attempt)
{
ret = api.WriteRegister_10(slave_id, registerAddress, dataLength, {...});
if (ret)
break;
LOG(WARNING) << "WriteRegister_10 failed. Retry " << attempt << "/" << maxRetries;
if (attempt < maxRetries)
usleep(retryDelayUs);
}
此外,ElectricClawAPI.cpp 中定义了全局对象 ElectricClawUtil::ElectricClawAPI api,并在 RLCmd 与 Service 的头文件中以 extern 声明共享,使 RL 指令执行与 HMI 插件指令处理复用同一个 Modbus 通信实例。
RL 指令实现(ElectricClawRLCmd)
每条 RL 指令的注册流程为:配置指令属性 → 定义参数形式 → 实现执行逻辑 → 注册指令。以 ElectricClawStart 为例:
void RegElectricClawStart()
{
// 1. 配置指令属性
RLCmdConfigAPI cfg;
cfg.SetFuncName("ElectricClawStart"); // 指令名
cfg.SetLookAhead(CmdLookAheadImplAPI::WAIT_MOVE_PAUSE); // 前瞻方式
cfg.SetTaskLimit(TaskLimitAPI::TASK_LIMIT_MOVE); // 任务限制类型
cfg.SetStepBack(StepBackAPI::STEP_BACK_STOP); // 步退类型
BEGIN_COMMAND_API(cfg)
// 2. 定义指令参数:ID、模式、宽度、力度、速度
PARAMS_API(
ArgTypeAPI(ValueTypeAPI::VALUE_INT),
...
)
// 3. 指令执行逻辑:取出参数后调用 API
RLCmdActionAPI action = [=](std::shared_ptr<RLCallFrameAPI> frame,
std::vector<std::shared_ptr<SymbolVarBaseAPI>> args) -> CommandExecuteAPI {
int claw_id = args[0]->GetIntVal();
...
CommandExecuteAPI cmd_exec = [=]() {
bool ret = api.startElectricClaw(claw_id, width, force, speed, mode);
return ret ? CommandProcessResultAPI::CMD_PROCESS_SUCCESS
: CommandProcessResultAPI::CMD_PROCESS_ERROR;
};
return cmd_exec;
};
EXECUTE_API(action);
// 4. 注册指令
REGISTER_COMMAND_API
}
三条指令的定义对比:
| 指令 | 参数 | 前瞻方式 | 说明 |
|---|---|---|---|
ElectricClawStart | ID、模式、宽度、力度、速度(5 个 INT) | WAIT_MOVE_PAUSE | 等待运动暂停后执行,调用 startElectricClaw |
ElectricClawStop | ID(1 个 INT) | ACTIVATE_AT_LOOKAHEAD | 前瞻时即激活执行,调用 stopElectricClaw |
ElectricClawStatus | ID、状态、外宽、内宽、力度(5 个 INT) | ACTIVATE_AT_LOOKAHEAD | 调用 getStatusElectricClaw,并将结果回写到后 4 个参数 |
ElectricClawStatus 的后 4 个参数作为 输出参数 使用,执行时通过 ResetInt() 将读取到的状态回写,供 RL 程序后续引用:
args[1]->ResetInt(status);
args[2]->ResetInt(external_width);
args[3]->ResetInt(internal_width);
args[4]->ResetInt(force);
文件末尾的 ElectricClawCmd() 将三条指令统一注册到系统中,供插件入口在 Init() 中调用:
void ElectricClawCmd()
{
electric_claw_cmd::RegElectricClawStop();
electric_claw_cmd::RegElectricClawStart();
electric_claw_cmd::RegElectricClawStatus();
}
RL 指令注册的通用机制见 指令注册。
插件指令处理(ElectricClawService)
通过 xcore_api::service::RegServiceAPI 注册自定义 JSON 协议,需搭配客户端(HMI)使用。注册时指定 插件名称、指令 key、回调函数,控制器收到对应 JSON 协议即回调处理:
xcore_api::service::RegServiceAPI("ElectricClawPlugin", "stop",
[](const Json::Value &in)
{
int slave_id = in["stop"]["slave_id"].asInt();
bool ret = api.stopElectricClaw(slave_id);
Json::Value out;
out["return"] = ret;
return out;
});
本项目注册了三个指令 key:
| key | 请求示例 | 处理 | 响应 |
|---|---|---|---|
stop | {"stop":{"slave_id":1}} | 调用 stopElectricClaw | {"return":true} |
start | {"start":{"slave_id":1,"width":50,"force":100,"speed":50,"mode":0}} | 取出各参数后调用 startElectricClaw | {"return":true} |
get_status | {"get_status":{"slave_id":1}} | 读取 0x0100 起 8 个寄存器并解析 | {"status":...,"external_width":...,"internal_width":...,"force":...,"return":true} |
get_status 的处理中直接构造了本地 ModbusRTUEndtoolAPI 对象读取寄存器,未复用全局 api;start、stop 则通过全局 api 调用 API 模块。读取结果不足 8 个寄存器或读取失败时直接返回 {"return":false}。
json_serialize() 用于日志输出:将 JSON 序列化为字符串,并把结尾的 \n 替换为 \r 作为结束控制符。
编译打包
在代码编写完成后使用 cmake 相关命令进行编译:
mkdir build && cd build # 创建 build 目录并进入
cmake .. # 生成 makefile 文件
make # 构建最终结果,生成在 bin 目录下
打包要求与 HMI 插件相同:将 linux 下生成的动态库与 json 说明文件、lic 文件放入文件夹后压缩。