跳到主要内容

控制器插件说明(electricclawplugin-controller)

开发环境​

环境工具链
linux X86 / aarch64gcc/g++ 5.4.0,采取交叉编译的方式执行
构建工具cmake 3.5
依赖xCore 3.2.1

目录结构​

目录说明
3rdparty第三方依赖库
ElectricClawPlugin电爪逻辑实现代码(项目主体)
examples插件使用示例,可参考了解 API 的使用方式
xcore_apixCore 系统 API 目录(对接平台的核心接口)
CMakeLists.txtCMake 配置文件
Src本项目以 ElectricClawPlugin 目录作为代码主体,此目录仅保留空示例

控制器插件工程的通用目录说明(config、src、launch 入口等)见 控制器插件工程。

代码说明​

文件组成​

ElectricClawPlugin 目录为项目主体,各文件职责如下:

文件职责
ElectricClawPlugin.h/.cpp插件入口类:继承 LaunchAPI,在插件加载时完成 RL 指令与插件指令的注册
ElectricClawAPI.h/.cpp电爪控制核心接口:基于 Modbus 封装 启动、停止、获取状态 三个功能
ElectricClawRLCmd.h/.cppRL 指令注册与实现: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 指令和插件指令处理
*RLCmdRL 指令注册与实现。提供 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);

继承类的生命周期:

  1. 加载插件时调用 构造函数(无依赖关系的插件构造顺序未定义;若插件 A 依赖插件 B,保证 A 比 B 后加载);
  2. 插件加载完毕,按优先级依次调用各模块的 Init()、Start()、Stop();
  3. 卸载时调用 析构函数(析构顺序未定义,无论是否有依赖关系)。

本项目在 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
}

三条指令的定义对比:

指令参数前瞻方式说明
ElectricClawStartID、模式、宽度、力度、速度(5 个 INT)WAIT_MOVE_PAUSE等待运动暂停后执行,调用 startElectricClaw
ElectricClawStopID(1 个 INT)ACTIVATE_AT_LOOKAHEAD前瞻时即激活执行,调用 stopElectricClaw
ElectricClawStatusID、状态、外宽、内宽、力度(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 文件放入文件夹后压缩。