Creating Your Custom Controller Plugin

A step-by-step guide for developing your own controller plugin.

Set up the development environment

Gantner Instruments offers a Software Development Kit (SDK) for all devices, which includes all the necessary files and examples to kickstart your development. The SDK is dependent on the controller version and can be accessed in the public download area.

  • The SDK is designed to operate exclusively on a Linux PC. If you don't have access to a Linux PC, you can utilize a virtual machine, such as VirtualBox on Debian, as an alternative.

  • The controller firmware and the corresponding SDK undergo the same build step, utilizing identical source files and libraries. Therefore, it is crucial to develop plugins with the SDK that align with the device firmware version.

  • To work with the SDK, install Eclipse with CDT (C/C++ Development Tooling). The CDT offers a comprehensive C and C++ Integrated Development Environment based on the Eclipse platform.


In the following example, SDK v2.14.9 is used.

Installing the SDK

  1. Copy the SDK installation script (e.g., gins-qstation-sdk-toolchain-2.14.9.sh) into a working directory on your Linux PC. The "2.14.9" in the script name represents the SDK version and should match the controller firmware version.
  2. Execute the SDK installation script.
    root@GInsSDK:/home/user1/work# ./gins-qstation-sdk-toolchain-2.14.9.sh

    Gantner Instruments Distro SDK installer version 2.14.9

    =======================================================

    Enter target directory for SDK (default: /opt/gins-qstation-sdk/2.14.9):

    The directory "/opt/gins-qstation-sdk/2.14.9" already contains a SDK for this architecture.

    If you continue, existing files will be overwritten! Proceed[y/N]? y

    Extracting SDK..........................................................

    ......................................................done

    Setting it up...done

    Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.

    $ . /opt/gins-qstation-sdk/2.14.9/environment-setup-core2-32-gins-linux

    root@GInsSDK:/home/user1/work#
  3. If the installation is successful, the SDK is installed in the directory /opt/gins-qstation-sdk/2.14.9/.

    root@GInsSDK:/home/user1/work# ls -l /opt/gins-qstation-sdk/2.14.9/

    total 28

    -rw-r--r-- 1 root root 3535 Nov 6 12:16 environment-setup-core2-32-gins-linux

    -rw-r--r-- 1 root root 14666 Nov 6 12:16 site-config-core2-32-gins-linux

    drwxr-xr-x 4 root root 4096 Oct 8 11:43 sysroots

    -rw-r--r-- 1 root root 122 Nov 6 12:16 version-core2-32-gins-linux
  4. Open a terminal and execute the environment setup for the SDK (run as a user, not as root).

    user1@GInsSDK:/home/user1/work# source /opt/gins-qstation-sdk/2.14.9/environment-setup-core2-32-gins-linux 

    The environment variables are only active in the current terminal session. To make these environment variables available in Eclipse, you need to launch Eclipse from this terminal. For instance, you can use the following command:

    user1@GInsSDK:/home/user1/work# /home/user1/eclipse/eclipse

    👉 It is assumed that Eclipse is installed in directory /home/user1/eclipse.

  5. To prepare the SDK for kernel development, the Linux kernel sources have to be prepared first:

    1. root rights are required (login as root)

    2. the environment has to be sourced as root

    3. navigate to /opt/gins-qstation-sdk/2.14.9/sysroots/core2-32-gins-linux/usr/src/kernel/

    4. run make prepare

    5. run make scripts

    6. kernel-source should be ready for kernel module compilation

Plugin basics

The fundamental class for a plugin is GIns::CPlugin.

The class GIns::CPlugin is specified in /opt/gins-qstation-sdk/2.14.9/sysroots/core2-32-gins-linux/usr/include/GInsSystemGlobals/GInsPlugin.hpp. Every plugin must inherit from this class to function as a controller plugin. It encompasses all the necessary functionality for a plugin, including installation, communication with the device firmware, and additional useful functions.

Within the plugin, there are virtual methods that need to be implemented:

Virtual methods Description
void PluginInit() Will be called by the plugin interface after it is correctly started.
This function is called before PluginMain() and should init all tasks.
void PluginMain() Will be called after PluginInit(). The main thread should stay in this function for the entire life cycle.
bool PluginStart() Data communication can be started until PluginStop() or PluginFinished() is called.
bool PluginStop() Stop the main activity like data communication and wait until PluginStart() is called again.
bool PluginFinish() Stop the main activity of the plugin and also stop the control body of the plugin. Wait until the main body has left. Be sure that all dynamically allocated resources are freed.
bool PluginUninstall() This method will be called before the plugin gets uninstalled (clean anything that is not located inside the plugin installation path).
std::string GetStateInfoText() Use this method to publish a plugin's state info which is displayed in XML-RPC “GetStates” method of GInsPluginSystemAPI.
 

Some other helpful non-virtual methods are:

Method name Description
void Error(int64_t, const char *fmt)
void Warning(int64_t, const char *fmt)
void Info(int64_t, const char *fmt)
Sends a plugin Message (Error, Warning, or Info) + state code + description to the Q.station firmware.
Q.station will display the messages in #actual.sta or via XML-RPC “DiagnosticAPI → GetErrorStates”.
(Common Gantner Instruments states + descriptions can be generated using “ginsstate.h”)
void ClearEvents() Clears all Errors, Warnings, or Info messages.
bool StartPluginTimer(int ms) Set plugin cycle time in milli seconds → after each cycle a call to WaitForPluginTimerExpired() will return.
bool SaveConfig(const std::string &name,const std::string &config)
bool LoadConfig(const std::string &name,const std::string &config)
Save/load a configuration file. The file will be stored next to the plugin-executable.


A plugin project should be linked to the libraries 'GInsQStationGlobals' and 'GInsCommon,' both included in the SDK. Additionally, the libraries "PocoFoundation" and "PocoNet" are necessary. The controller heavily depends on this robust cross-platform C++ framework, also provided in the SDK.

Plugin examples

Some plugin examples are installed with the SDK. You can import these examples directly into your Eclipse workspace from /opt/gins-qstation-sdk/2.14.9/sysroots/core2-32-gins-linux/gins/qstation101/examples/.

Example: QStationPluginExample

This example is the most basic plugin, merely sending some messages to the Q.station. Its purpose is to illustrate the interaction of a plugin with the Q.Station firmware and demonstrate the proper utilization of the configuration system. This example is included in the SDK.


QStationPluginExample.cpp
The main project file contains the main loop. It does nothing else than call the infinite main loop of the plugin.

#include "PluginExample.hpp"
using namespace std;
 
int main(int argc, char** argv)
{
/**
* The program flow has to be directed to the run function of the plugin singleton
* Command-line arguments have to be forwarded to the plugin class to interpret some built-in arguments
* The PluginType is always "PluginType_User"
* The version should be the version information of this plugin and has no further impact on the functionality
*/
if(PluginExample().Run(argc,argv,GIns::CPlugin::PluginType_User,"V1.01"))
{
return (0);
}
return (1);
}


PluginExample.hpp/cpp
The user-defined plugin class is:

class CPluginExample: public GIns::CPlugin
  • It follows the singleton design pattern
  • It inherits the basic plugin capabilities from GIns::CPlugin
  • It contains a state machine (main plugin activity) that can be remotely controlled through the plugin interface

PluginMain()
The main thread of the plugin should stay in this function for the entire life cycle. The PluginMain() implements a state machine to control the activities:

void CPluginExample::PluginMain()
{
// The GIns::CPlugin class provides timer functionality
this->StartPluginTimer(100);
while (!this->m_CtrlLeaveBody) {
this->IncrementLoopCounter();
if (this->WaitForPluginTimerExpired()) {
// Process State Machine states
switch (this->m_SMState) {
case SMState_Init:
SMHandler_State_Init();
break;
case SMState_Stop:
SMHandler_State_Stop();
break;
case SMState_CheckConfig:
SMHandler_State_CheckConfig();
break;
case SMState_ProcessData:
SMHandler_State_ProcessData();
break;
default:
break;
}
}
}
this->m_CtrlBodyLeft = true;
}


PluginAPI.model
This file contains the model for the plugin interface (XML-RPC interface). It is the input for the generator GInsXmlRpcIFGen and can be modified considering some conventions.

<?xml version="1.0" encoding="utf-8"?>
<model>
<XmlRpcInterface Name="PluginAPI" ID="" Description="no" Revision="0.1">
<FileInclude Name="GInsXmlRpcStdAPI_Types.h" Global="true"/>
<Struct Name="TypeGInsVariable">
<Item Name="GInsVariableName" Type="std::string"></Item>
<Item Name="Value" Type="double"></Item>
</Struct>
<Struct Name="TypeConfig">
<Item Name="GInsVariables" Type="TypeGInsVariable[]"></Item>
</Struct>
<Method Name="Configure">
<Params>
<Item Name="Configuration" Type="TypeConfig"></Item>
<Item Name="SetDefault" Type="bool"></Item>
</Params>
<Results>
<Item Name="ReturnState" Type="GInsXmlRpcStdAPI::GIns_Info_State"></Item>
</Results>
</Method>
</XmlRpcInterface>
</model>
  • Structs:
    Use structs to model objects with several child elements.
    Child elements can be basic types or structs or arrays of both.
    Structs are accessible in the code as classes of the API, which provide “set” and “get” methods to access the items. As from the previous “PluginAPI.model” example:
    PluginAPI::CTypeConfig          Config;
    PluginAPI::CTypeGInsVariable GInsVariable;

    Each item in the class object “CTypeConfig” can be accessed now using “Set_” or “Get_” methods. For example:

    std::string VarName = "Name";
    PluginAPI::CTypeGInsVariable GInsVariable;
     
    //set an item:
    GInsVariable.Set_GInsVariableName(VarName);
    GInsVariable.Set_Value(1234);
     
    //get an item:
    if (GInsVariable.Get_GInsVariableName(VarName)) {
    //success
    }
     
    // OR
     
    VarName = GInsVariable.Get_GInsVariableName();
  • Methods:
    Create Methods to generate a Remote Procedure Call - functions to transport parameters and results from and to the plugin. For each method, a new source file (class) is generated and provides 'execute' and 'help' functions which can be accessed via an XML-RPC client (see Plugin-API source files).
  • Types:
    You can use standard types for your items, for example std::string, int, double, bool, and so on. But also commonly used types (defined by Gantner Instruments) included in GInsXmlRpcStdAPI_Types (find details in:GInsXmlRpcStdAPI_Types).
  • ReturnStates:
    It is recommended to use ReturnStates in methods, to give the client information about the execution state of the method. It is free to use any type. Gantner Instruments provides a type called GIns_Info_State, which is a struct of an int64 code and a string for the description.
    There are also predefined error codes (+ descriptions) available in ginsstate.h.

Putting all configuration values into one struct makes it very easy to store the entire plugin configuration in a single file.

GInsXmlRpcIFGenEng.sh

  • This script calls the code generator GInsXmlIFGenEng.jar for the model file PluginAPI.model.
  • This script should be executed in the build configuration before compiling the plugin!
  • The code generator GInsXmlIFGenEng.jar must be located at …/GInsCommon/Tools. It is referenced by the relative path; so it is very important not to change the directory structure!
  • The generator automatically generates source files as “.cpp”, “.h”, “.cs” and “.pas”.
  • For each new method defined in PluginAPI.model file, a new source file will be generated.
  • The naming of the generated files is defined by the attribute 'Name=' in the element XmlRpcInterface of the PluginAPI.model file. So the naming of generated files for a method will be <Name>_<MethodName>.
  • Old source files will be deleted if a method is deleted in the PluginAPI.model file.
  • Added code in generated files will not be changed when this code is placed in specially marked sections, e.g.
    //+++---------------------------------------------------------
    /*Add_Own_<type>*/
    //+++---------------------------------------------------------

PluginAPI.h/.cpp

The PluginAPI Interface class. Normally this class shouldn't be modified. If some interface-global members are needed, they may only be placed inside the marked comment areas:

//+++---------------------------------------------------------
/*Add_Own_Definitions*/
//+++---------------------------------------------------------

After changing the model file, the code generator will rebuild this file and throw out anything that is not inside these marked areas!

Include statements:

//+++---------------------------------------------------------
/*Add_Own_Includes*/
//+++---------------------------------------------------------

PluginAPI_Types.h / .cs

This is the generated client interface code based on the model file PluginAPI.model. This code contains classes for all PluginAPI.model - structs and methods.

It is available for C++ and C# and can be used directly on the controller or on the PC.

This client code is used for:

  • Accessing the Plugin config through a convenient class interface inside the plugin class
  • In the PluginAPI Server side code (PluginAPI_*.cpp)
  • For Remote Procedure Calls from programs connected to the plugin via XML-RPC (Network or local) → use GI.monitor for testing!

PluginAPI_*.h / .cpp / .pas / .cs

This is the automatically generated API code based on the model file “PluginAPI.model”. For each method in the “PluginAPI.model” file, the code generates a separate class as a source file named like

PluginAPI_<method name from model> + .h / .cpp / .cs / .pas

These files have to be filled with the plugin XML-Rpc server functionality between the marked comment areas as seen in the section "GInsXmlRpcIFGenEng.sh".

Each class provides an execute and help method:

help method:

  • This method is used to send server information to the client, for example reading the actual plugin configuration from the main plugin class and filling it to the method parameters (MethodParams) of the method help call. Example from “OnlineExample”:
    std::string CConfigure::help(void)
    {
    try {
    OnlineExample_PluginAPI::Configure::CParams MethodParams;
     
    //+++---------------------------------------------------------
    /*Add_Own_Parameters*/
    //+++---------------------------------------------------------
     
    return MethodParams.toXml();
    }
    catch (...) {
    }
    return std::string("NoParams");
    }

execute method:

  • The execute method is used to receive parameters from a client and to send them to the main plugin class. For example, receiving the plugin configuration from a client:
    void CConfigure::execute(GInsXmlRpc::XmlRpcValue& Params, GInsXmlRpc::XmlRpcValue& Results, GInsXmlRpc::XmlRpcExecutor& Executor)
    {
    try {
    OnlineExample_PluginAPI::Configure::CResults MethodResults;
    //+++---------------------------------------------------------
    /*Add_Own_Method_Variables*/
    //+++---------------------------------------------------------
    do {
    // method ID
    std::string MethodID;
    if (GInsXmlRpc::XmlRpcServerMethod::GetMethodID(Params, MethodID))
    {
    if (MethodID != "PluginAPI::Configure")
    throw GInsXmlRpc::XmlRpcException("Invalid MethodID (" + MethodID + " instead of PluginAPI::Configure)",
    TEnumGInsStateType::eGInsStateType__XMLRPC_UNKNOWNMETHOD);
    }
    OnlineExample_PluginAPI::Configure::CParams MethodParams(Params[0]);
    //+++---------------------------------------------------------
    /*Add_Own_Method_Body*/
    //+++---------------------------------------------------------
    } while(false);
    //+++---------------------------------------------------------
    /*Add_Own_Method_Return_Values*/
    //+++---------------------------------------------------------
    Results = MethodResults;
    return;
    }
    catch (const GInsXmlRpc::XmlRpcException&) {
    throw;
    }
    catch (...) {
    throw;
    }
    } // CConfigure::execute()

These examples are very basic! The methods can be filled with more logic, validating parameters, setting “ReturnStates” (return code + descriptions) of the method results, and much more!

Save and load plugin configuration

In the examples, the struct “TypeConfig” (→ see PluginAPI.model) is used to save some parameters of the plugin, which are transferred via the PluginAPI (XML-RPC), to an XML file on the flash drive. The struct is received in the “Configure” method of the API and is set as PluginConfig using:

void CPluginExample::SetPluginConfig(const PluginAPI::CTypeConfig &config)
{
m_PluginConfig=config;
this->m_SMState = SMState_CheckConfig; //-> to check new config, change state
}

Using the function “SaveConfig” from GIns::CPlugin class, the plugin configuration will be saved as an XML file in the plugin installation directory:

bool CPluginExample::SavePluginConfig()
{
return this->SaveConfig("PluginConfiguration.xml",this->m_PluginConfig.toXml());
}

On startup, the plugin configuration can be loaded in order to run with the last settings:

bool CPluginExample::LoadPluginConfig()
{
std::string config;
if(this->LoadConfig("PluginConfiguration.xml",config))
{
size_t offset=0;
if(this->m_PluginConfig.fromXml(config,&offset))
{
this->m_SMState = SMState_CheckConfig;
return true;
}
}
return false;
}

Example: BufferStream

Using this example plugin, the previous example “QStationPluginExample” was extended to a simple program for

  • accessing buffer data stream and header information from the controller using the GIns::Data interface and,
  • a simple example of using an "ElementSelectionList" for channel selection via XML-RPC (GInsXmlRpcStdAPI Types).

Buffer Interface

Create an interface object “IGInsDataSource” using the factory method which takes the buffer index as a parameter. If the returned shared pointer is valid, the connection is successful.

The connection is closed automatically when no copy of the shared pointer is in scope anymore.

Examples:

GIns::Data::GInsDataSourcePtr DataSource = GIns::Data::IGInsDataSource::CreateIGInsDataSourcePtr(0);

The IGInsDataSource class provides read access to different types of buffered data and various other information from the device (see “GInsDataSource.hpp”) as well as from DataHeader (→ IGInsDataHeader class), for example:

std::string location   = DataSource->HeaderInterface()->GetLocation();
std::string firmware = DataSource->HeaderInterface()->GetFirmwareVersion();
uint16_t channelcnt = DataSource->HeaderInterface()->GetVariableCount();

Using IGInsDataHeader from a connected IGInsDataSource gives you valid variable objects for this interface. It allows you to read/find IGInsVariables using the name, ID, or index, or gives you a vector of all available variables:

GIns::Data::IGInsVariablePtr TempVariable;
DataSource->HeaderInterface()->FindGInsVariable("VarName",TempVariable);

or a vector of all variables:

std::vector<GIns::Data::IGInsVariablePtr> vars;
DataSource->HeaderInterface()->GetGInsVariables(vars);

The IGInsVariable class represents a variable on a device or data source. It provides all required information to decode, encode, or transfer variable data (see “GInsDataVariable.hpp”). Reading buffered data of the IGInsVariable “TempVariable” can look like this for example (within the loop “ProcessData”):

GIns::Data::FrameBuffer buffer(DataSource->GetFrameWidth());
 
size_t dataLength = 0;
DataSource->GetNextFrames_All(buffer, dataLength); //read all available buffer dataframes
 
for (GIns::Data::Frame fr = buffer.begin(); fr != buffer.end(); ++fr)
{
//process data here -> loop through all frames
InputChannel = TempVariable->ConvertFromBufferToDouble(*fr);
}

Example: OnlineData

With this example plugin, the previous example “QStationPluginExample” was extended to a simple program for reading/writing online values from/to the process image of Q.Station using the GIns::Data interface.

Examples:

GIns::Data::GInsDevicePtr ProcessImage = GIns::Data::IGInsDevice::CreateIGInsDevicePtr();

The IGInsDevice class provides read and write access to the online process image of a device. It also provides system and diagnostic information (see “GInsDevice.hpp”) as well as information from the DataHeader (→ IGInsDataHeader class) as mentioned for IGInsDataSource.

Read Input Variable:

GIns::Data::IGInsVariablePtr InputVariable;
ProcessImage->HeaderInterface()->FindGInsVariable("InputVarName",InputVariable);
if (InputVariable->IsReadable()) {
double value = 0;
if (!ProcessImage->GetInputVariableValue_Double(*InputVariable,value)) {
this->Error(GINSERR_Plugin_Runtime_NotSpecified,"could not read from variable");
return;
}
}

Write Output Variable:

GIns::Data::IGInsVariablePtr OutputVariable;
ProcessImage->HeaderInterface()->FindGInsVariable("OutputVarName",OutputVariable);
if (OutputVariable->IsWritable()) {
double value = 5;
if(!ProcessImage->SetOutputVariableValue_Double(*OutputVariable,value)) {
this->Error(GINSERR_Plugin_Runtime_NotSpecified,"could not write to variable");
}
}

Q.station RPC-API

The Q.station RPC-API (as well as other Gantner Instruments XML-RPC-based APIs) can also be accessed directly from a plugin. Therefore add:

  • an XmlRpcClient and
  • include the desired API header (s)

The procedure is similar to that described on page XML-RPC API

XmlRpc client

Add an XmlRpc client to your plugin using:

GInsXmlRpc::XmlRpcClientTransportHTTP   XmlRpcTransport;
GInsXmlRpc::XmlRpcClient XmlRpcClient(XmlRpcTransport);

Initialize the client for example:

char HostName[1024] = "127.0.0.1";
int PortXMLRpc = 1200;
TGInsState Ret = GINSSTATE_BuildNone();
 
//Configure HTTP Transport
GInsXmlRpc::XmlRpcValue XmlRpcTransportCfg;
XmlRpcTransportCfg[GInsXmlRpc::XmlRpcClientTransportHTTP::CfgParam_ServerIP] = std::string(HostName);
XmlRpcTransportCfg[GInsXmlRpc::XmlRpcClientTransportHTTP::CfgParam_ServerPort] = PortXMLRpc;
 
XmlRpcTransport.SetCfg(XmlRpcTransportCfg);
 
Ret = XmlRpcTransport.Connect();
 
if (GINSSTATE_IsLevel_Error(Ret)) {
this->Error(GINSSTATE_Build(eGInsStateLevel__ERROR,eGInsStateType__Plugin_Runtime_NotSpecified), "Failed to connect to XML-RPC server \" %s \" via HTTP transport!", HostName);
this->m_CtrlLeaveBody = true; // terminate plugin
}

APIs

All available Q.station RPC-API headers can be found in the SDK directory ${SDKTARGETSYSROOT}/usr/include/GInsQStation/.

These are the same APIs, which can also be seen and tested via GI.monitor when connecting to the controller on port 1200. Add the desired API paths as include paths to the plugin project and include the “<API-name>_Types.h” file (for example “SummaryAPI_Types.h”).

Example: Read DeviceName via SummeryAPI method “GetGlobalSysInfos”

std::string CPluginExample::GetLocation()
{
SummaryAPI::GetGlobalSysInfos::CResults GResults;
GInsXmlRpc::XmlRpcValue GParams;
TGInsState Ret = GINSSTATE_BuildNone();
 
Ret = XmlRpcClient.execute(GResults.MethodName(), GParams, GResults);
 
if (GINSSTATE_IsLevel_Error(Ret)) {
this->Error(GINSSTATE_Build(eGInsStateLevel__ERROR,eGInsStateType__Plugin_Runtime_NotSpecified), "DeviceAPI function \"GetSystemInfos\" failed... ");
return "";
}
 
return GResults.Get_DeviceName();
}

Generate Plugin API

This is possible by using the GInsXmlRpc Interface Generator that comes along with the QStation Library Package. It generates C++ classes compatible with the plugin's built-in API.

Plugin Interface and Configuration

Preconditions

  1. Java is required on the development system to generate the plugin interface code:  http://www.oracle.com/technetwork/java/javase/downloads/index.html
  2. Unpack Java (e.g., the directory jdk1.8.0) to /usr/java/ (e.g., /usr/java/jdk1.8.0)
  3. Ensure that the generator script "GInsXmlRpcIFGenEng.sh" points to the correct Java directory. In the example above, the variable "JAVA_HOME" must be set to "/usr/java/jdk1.8.0" (the SDK already provides environment variables that need to be sourced).
  4. Set the variable "HUGO" to the location of "GInsHugo.jar" in the Q.Station Library Package, and "GENERATOR_PATH" must be set to the directory containing "GInsHugo.jar" and "GInsXmlRpcIFGenEng.jar."

Generate a Configuration

  • Define methods in the *.model file (see example in PluginAPI.model)
  • Call “./GInsXmlRpcIFGenEng.sh”
  • The generator will create client and server code for C++, C# and Pascal

Use the Project “QStationPluginExample”, it demonstrates how to generate the config code automatically.

Run and Debug Plugin

Normally, plugins are installed by USB Stick + script or with a DeviceAPI function call (see Install Plugin). When developing plugins, it should be possible to deploy the plugin to the Q.station by starting the remote debugger.

Enable controller plugin mode

👉 Use the same version for the controller firmware and for the SDK.

  1. Activate the Plugin mode on the controller and set it to Debug!
    test.commander


    GI.bench
  2. Use “GI.monitor” (connect to Q.Station port 1200).

    Show installed plugins and get plugin information: PluginAPI → GetInstalledPlugins

    Remove installed plugin: PluginAPI → RemovePlugin

Eclipse IDE

Import Project

  1. Select “File → Import…”
  2. Select “General → Existing Projects into Workspace”
  3. Select Folder
  4. Select “Finish”

Debug Configurations

To install/debug plugins directly on the controller → add “Debug Configurations…”

Add “New”:

Install

Every plugin has to be installed before it can be started for debugging, otherwise, no interaction with the Q.Station firmware will be possible! To make this possible directly from Eclipse, a separate debug configuration can be defined only to install the plugin.

Follow this procedure:

Add an install configuration for your plugin:

  • Set “Name” to a reasonable name.
  • Change “Remote Absolute File Path for C/C++ Application” to
    • Q.station with firmware version V2.14 or newer: /gins/fs/online/tmp/<ApplicationName>
    • Q.station with firmware version V2.12 or older: “/home/user1/fs/online/tmp/<ApplicationName>” or “/tmp/<ApplicationName>”

Add a new connection.

  • Choose as connection type “SSH”:
  • Host: use the address of your Q.station controller
  •  User: “root”
  • Password: <SerialNumber of your Q.Station>
  • Arguments:
    • -install
    • -clientport=<port of the Q.station DeviceAPI>
    • -name=<application name>
  • Debugger:
    • GDB debugger:
      • since version V2.14: “/opt/gins-qstation-sdk/2.14.9/sysroots/x86_64-ginssdk-linux/usr/bin/i686-gins-linux/i686-gins-linux-gdb”
      • older versions: “/opt/poky/1.8.2/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux/i586-poky-linux-gdb”

Run the configuration to install the plugin

If asked for the password, use <SerialNumber> of your Q.Station

What happens after starting the debug session:

  • the remote gdb copies the plugin executable to the specified directory of the Q.Station
  • it calls the executable with -install, clientport=port of the Q.station DeviceAPI and
    -name which is the name of the installation
  • the plugin initiates the installation of the firmware
  • The firmware registers the plugin and creates the installation path (which is in fact the installation name)
  • The plugin exits because with the argument -install it does nothing else than install itself

After this, the plugin installation name should be shown in the #actual.sta file, in the section “PLUGIN STATES” (but with Act=0). The file can be read via FTP, user: “6”, PW: “6”.

Debug

Use the debug configuration without “install” to start a regular debug configuration.

You can just use the “install” configuration, “duplicate” it, and adjust these settings:

  • Set “Remote Absolute File Path for C/C++ Application” to
    • Q.station with firmware version V2.14 or newer: /gins/fs/firmware/plugins/<installation name>/<executable name>
    • Q.station with firmware version V2.12 or older: /home/user1/fs/firmware/plugins/<installation name>/<executable name>
  • 👉 The command line argument “-install” may not be set but “-clientport” and “-name” must be set correctly!
  • The plugin doesn't start automatically during debugging. Initially, the "Start" method of the "GInsPluginSystemAPI" must be executed. Ordinarily, this is handled by the Q.Station firmware on startup when the plugin is installed. However, during debugging, this step needs to be performed manually using GI.monitor (refer to the screenshot).
  • The plugin is assigned its own XML-RPC port, and you can connect to it using GI.monitor. If no other plugin is running, the port will be 1201 (1200 for Q.Station). To determine the plugin port, use GI.monitor and call Q.Station → PluginAPI → GetInstalledPlugins.

GI.monitor

Use GI.monitor as a client to access your own PluginAPI, which you defined in your plugin (PluginAPI.model).

Create and install the plugin release version

To create a release version of the plugin example, use the Release Build Configuration.

  • Open the properties of the project
  • Select “C/C++ Build”
  • Click on the “Manage Configurations…” button.
  • Select “Release” and click on “Set Active”
  • Re-build your project with that build configuration.

If the build configuration doesn't work immediately, please compare the configuration settings between your "debug" and "release" configurations to identify potential reasons.

Upon successful completion of the build process, you will find new files in the "Release" directory of your project. In Eclipse, it might be necessary to "Refresh" the project to view the newly generated files.

Among these files is a "*_usb_install.zip" file, which contains the plugin executable along with an installation script, "autorun.sh," for installing it on a "Q.station" (Plugin-Mode needs to be "Activated" on Q.station - refer to Plugin Activation).

Depending on the firmware version, it may be necessary to modify the installation path in the installation script "autorun.sh":

  • Q.station with firmware version V2.14 or newer: /gins/fs/firmware/plugins/
  • Q.station with firmware version V2.12 or older: /home/user1/fs/firmware/plugins/

Miscellaneous 

C++ dialect "C++11"

Q.station controller libraries heavily rely on the C++ dialect "C++11" (utilizing features like std::shared_ptr, lambda expressions, etc.). Certain compiler and code analysis settings are necessary, particularly if Eclipse CDT is not updated to the latest version.

It may not always be apparent that missing C++11 settings are causing errors. A common indication is the appearance of "semantic error: unresolved reference" for references that specifically require C++11.

For newer versions of Eclipse CDT, these settings are typically supported (refer to project properties "C/C++ Build → Settings → GCC C++ Compiler → Dialect").

For older versions of Eclipse CDT:

Compiler:

  • Add the compiler flag "-std=c++0x" to "Other flags" in "Project Properties → C/C++ Build → Settings → GCC C++ Compiler → Miscellaneous".
Code Analysis:
  • Navigate to "Project Properties → C/C++ General → Preprocessor Include Paths, Macros, etc. → Tab [Providers] → your Built-in Compiler Settings provider (toolchain dependent).
  • Click on the "Workspace Settings" link to access the "Settings" property page, select the [Discovery] tab, and choose your provider.
  • In the "Command to get compiler specs," add "-std=c++0x". Then, refresh the indexer.