Controller Plugin Description (electricclawplugin-controller)
Development Environment
| Environment | Toolchain |
|---|---|
| linux X86 / aarch64 | gcc/g++ 5.4.0, cross-compilation |
| Build tool | cmake 3.5 |
| Dependency | xCore 3.2.1 |
Directory Structure
| Directory | Description |
|---|---|
| 3rdparty | Third-party dependency libraries |
| ElectricClawPlugin | Electric claw logic implementation code (project main body) |
| examples | Plugin usage examples; refer to these examples to understand how to use the API |
| xcore_api | xCore system API directory (core interfaces for platform integration) |
| CMakeLists.txt | CMake configuration file |
| Src | The example uses the ElectricClawPlugin directory as the code main body, so this directory has no actual function and is only kept as an empty code example |
For general directory descriptions of controller plugin projects (config, src, launch entry, etc.), see Controller Plugin Project.
Code Description
File Composition
The ElectricClawPlugin directory is the project main body. Responsibilities of each file:
| File | Responsibility |
|---|---|
| ElectricClawPlugin.h/.cpp | Plugin entry class: inherits LaunchAPI; registers RL instructions and plugin instruction processing when the plugin is loaded |
| ElectricClawAPI.h/.cpp | Core electric claw control interfaces: encapsulates start, stop, and get status based on Modbus |
| ElectricClawRLCmd.h/.cpp | RL instruction registration and implementation: ElectricClawStart, ElectricClawStop, ElectricClawStatus |
| ElectricClawService.h/.cpp | Registration and processing of plugin instructions (custom JSON protocol); responds to HMI requests |
| CMakeLists.txt | Subdirectory build configuration: collects source files and adds them to the main project build |
Collaboration between modules: RLCmd and Service are two entry points (RL program execution and HMI panel operations respectively); both ultimately call the API module to perform the actual claw control.
CMakeLists.txt
The final compilation result of this code is a dynamic library called by the controller, therefore:
- Third-party libraries in the project do not need dynamic library files; they are provided by the controller during loading;
CMakeLists.txtsets dynamic-library-related properties, such as C++11 support, external invisibility of methods (symbol visibility), and-fPIC.
The CMakeLists.txt under the ElectricClawPlugin subdirectory automatically collects all source files in this directory via FILE(GLOB_RECURSE ...), appends them to the main project build target, and sets the output artifact name to the project name. For reference development, you only need to replace the project name with your own.
Core Modules
| Module | Description |
|---|---|
| *API | Core electric claw control interfaces. The electric claw control/status acquisition functions in the process package overlap with those in RL instructions, so the three functions start, stop, and get status are placed in the API for unified calls from both sides |
| *Plugin | Plugin initialization entry class. Initializes the plugin when it is loaded, i.e., registers RL instructions and plugin instruction processing in the controller |
| *RLCmd | RL instruction registration and implementation. Provides an RL instruction registration function; you need to specify the instruction parameter format, the instruction implementation function (which directly calls the API for execution after obtaining parameters), and the instruction attributes (instruction lookahead method, task limit type, whether the instruction needs parentheses, return value type, step-back type) |
| *Service | Registers and implements the processing of plugin instructions sent from the HMI |
Plugin Entry (ElectricClawPlugin)
The entry class inherits xcore_api::launch::LaunchAPI and is declared as an initialization module via the LAUNCH_MODULE macro (without this declaration, the initialization logic will not be executed):
class ElectricClawPlugin : public xcore_api::launch::LaunchAPI {
public:
explicit ElectricClawPlugin();
virtual void Init();
virtual void Start();
virtual void Stop();
virtual ~ElectricClawPlugin();
};
LAUNCH_MODULE(ElectricClawPlugin);
Lifecycle of the inherited class:
- The constructor is called when the plugin is loaded (the construction order of plugins without dependencies is undefined; if plugin A depends on plugin B, A is guaranteed to load after B);
- After the plugin is loaded,
Init(),Start(), andStop()of each module are called in priority order; - The destructor is called on unload (destruction order is undefined, regardless of dependencies).
This project performs two registrations in Init(). The Start() phase begins only after all plugins have finished their Init():
void ElectricClawPlugin::Init()
{
ElectricClawCmd(); // Register RL instructions (see ElectricClawRLCmd)
ServiceProtocol(); // Register plugin instruction processing (see ElectricClawService)
}
Control Interfaces (ElectricClawAPI)
ElectricClawAPI encapsulates Modbus communication for the electric claw. It holds the underlying communication object xcore_api::endtool::ModbusRTUEndtoolAPI and exposes three interfaces:
| Interface | Function | Modbus Operation |
|---|---|---|
startElectricClaw(slave_id, width, force, speed, mode) | Start the claw | WriteRegister_10 (write multiple registers): writes 4 consecutive registers starting at 0x0000 — width, force, speed, mode |
stopElectricClaw(slave_id) | Stop the claw | WriteRegister_06 (write single register): writes value 3 (stop command) to 0x0003 |
getStatusElectricClaw(slave_id, status, external_width, internal_width, force) | Get claw status | ReadRegister_03 (read holding registers): reads 8 registers starting at 0x0100; data[0] status code, data[1] external width, data[2] internal width, data[7] actual force |
All three interfaces include a built-in retry mechanism: up to 3 retries with a 10 ms interval, logging the retry count via LOG(WARNING) on failure:
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);
}
In addition, ElectricClawAPI.cpp defines a global object ElectricClawUtil::ElectricClawAPI api, which is declared as extern in the headers of both RLCmd and Service, so that RL instruction execution and HMI plugin instruction processing share the same Modbus communication instance.
RL Instruction Implementation (ElectricClawRLCmd)
The registration flow for each RL instruction is: configure instruction attributes → define the parameter format → implement the execution logic → register the instruction. Taking ElectricClawStart as an example:
void RegElectricClawStart()
{
// 1. Configure instruction attributes
RLCmdConfigAPI cfg;
cfg.SetFuncName("ElectricClawStart"); // Instruction name
cfg.SetLookAhead(CmdLookAheadImplAPI::WAIT_MOVE_PAUSE); // Lookahead method
cfg.SetTaskLimit(TaskLimitAPI::TASK_LIMIT_MOVE); // Task limit type
cfg.SetStepBack(StepBackAPI::STEP_BACK_STOP); // Step-back type
BEGIN_COMMAND_API(cfg)
// 2. Define instruction parameters: ID, mode, width, force, speed
PARAMS_API(
ArgTypeAPI(ValueTypeAPI::VALUE_INT),
...
)
// 3. Instruction execution logic: extract parameters, then call the 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 the instruction
REGISTER_COMMAND_API
}
Comparison of the three instructions:
| Instruction | Parameters | Lookahead Method | Description |
|---|---|---|---|
ElectricClawStart | ID, mode, width, force, speed (5 INTs) | WAIT_MOVE_PAUSE | Executes after motion pauses; calls startElectricClaw |
ElectricClawStop | ID (1 INT) | ACTIVATE_AT_LOOKAHEAD | Activated during lookahead; calls stopElectricClaw |
ElectricClawStatus | ID, status, external width, internal width, force (5 INTs) | ACTIVATE_AT_LOOKAHEAD | Calls getStatusElectricClaw and writes the results back to the last 4 parameters |
The last 4 parameters of ElectricClawStatus serve as output parameters; the read values are written back via ResetInt() during execution so that the RL program can reference them later:
args[1]->ResetInt(status);
args[2]->ResetInt(external_width);
args[3]->ResetInt(internal_width);
args[4]->ResetInt(force);
At the end of the file, ElectricClawCmd() registers all three instructions into the system; it is called by the plugin entry in Init():
void ElectricClawCmd()
{
electric_claw_cmd::RegElectricClawStop();
electric_claw_cmd::RegElectricClawStart();
electric_claw_cmd::RegElectricClawStatus();
}
For the general mechanism of RL instruction registration, see Instruction Registration.
Plugin Instruction Processing (ElectricClawService)
Custom JSON protocols are registered via xcore_api::service::RegServiceAPI, used together with the client (HMI). Registration specifies the plugin name, instruction key, and callback function; the controller invokes the corresponding callback when it receives the matching JSON protocol:
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;
});
Three instruction keys are registered in this project:
| Key | Request Example | Processing | Response |
|---|---|---|---|
stop | {"stop":{"slave_id":1}} | Calls stopElectricClaw | {"return":true} |
start | {"start":{"slave_id":1,"width":50,"force":100,"speed":50,"mode":0}} | Extracts each parameter and calls startElectricClaw | {"return":true} |
get_status | {"get_status":{"slave_id":1}} | Reads and parses 8 registers starting at 0x0100 | {"status":...,"external_width":...,"internal_width":...,"force":...,"return":true} |
The get_status handler directly constructs a local ModbusRTUEndtoolAPI object to read registers instead of reusing the global api; start and stop call the API module through the global api. If fewer than 8 registers are read or the read fails, it returns {"return":false} directly.
json_serialize() is used for log output: it serializes JSON into a string and replaces the trailing \n with \r as the ending control character.
Compilation and Packaging
After the code is written, use cmake commands to compile:
mkdir build && cd build # Create build directory and enter
cmake .. # Generate Makefile
make # Build; the result is generated in the bin directory
Packaging requirements are the same as for the HMI plugin: compress the dynamic library generated under Linux together with the json description file and the lic file.