How to include an open-source library in your own C++ project

Print

When developing components and programs for PLCnext in C++, it is often required to use functions provided by external, open source libraries. Open source libraries are available for applications including:

PLCnext Technology allows functions in external libraries to be called directly from real-time, deterministic control programs.

This guide describes how to use a third-party open-source library in a C++ project that is built for PLCnext Control using Eclipse.

NOTE: When using third-party software, it is the responsibility of the user to ensure that all license conditions are complied with.

Prerequisites

This example uses the open-source Ne10 library, which provides maths, signal processing, image processing, and physics functions.

This example uses a linux host. On Windows, a bourne-again shell (bash) is required, or else the bash script must be replaced with a suitable batch file or PowerShell script. WinSCP can be used to deploy the NE10 files to the target.

Procedure

1. In Eclipse, create a new PLCnext C++ project

2. Build the external library

3. Install the external library

4. Modify the PLCnext project build environment to reference the external library

5. Write the C++ program

6. Build, deploy and run your project

7. Check that the program executed successfully

Note on blocking calls

External functions may introduce execution delays that can affect the performance of the PLC, for example by blocking a program for long enough to trigger the Task Watchdog timer. In these cases, consider the following possibilities when designing your program:

Appendix - Program source code

Sample program header file

#pragma once
#include "Arp/System/Core/Arp.h"
#include "Arp/Plc/Commons/Esm/ProgramBase.hpp"
#include "Arp/System/Commons/Logging.h"
#include "MyProjectComponent.hpp"
#include "NE10.h"

#define MATRICES 3

namespace MyCompany { namespace MyProject
{

using namespace Arp;
using namespace Arp::System::Commons::Diagnostics::Logging;
using namespace Arp::Plc::Commons::Esm;

//#program
//#component(MyCompany::MyProject::MyProjectComponent)
class MyProjectProgram : public ProgramBase, private Loggable<MyProjectProgram>
{
public: // typedefs

public: // construction/destruction
    MyProjectProgram(MyCompany::MyProject::MyProjectComponent& myProjectComponentArg, const String& name);
    MyProjectProgram(const MyProjectProgram& arg) = delete;
    virtual ~MyProjectProgram() = default;

public: // operators
    MyProjectProgram&  operator=(const MyProjectProgram& arg) = delete;

public: // properties

public: // operations
    void    Execute() override;

public: /* Ports
           =====
           Ports are defined in the following way:
           //#port
           //#attributes(Input|Retain)
           //#name(NameOfPort)
           bool portField;

           The attributes comment define the port attributes and is optional.
           The name comment defines the name of the port and is optional. Default is the name of the field.
        */
    //#port
    //#attributes(Input)
    //#name(Calculate)
    bool calculate = false;

private: // fields
    MyCompany::MyProject::MyProjectComponent& myProjectComponent;
    bool prevCalc = false;  // remembers value from previous call

};

///////////////////////////////////////////////////////////////////////////////
// inline methods of class ProgramBase
inline MyProjectProgram::MyProjectProgram(MyCompany::MyProject::MyProjectComponent& myProjectComponentArg, const String& name)
: ProgramBase(name)
, myProjectComponent(myProjectComponentArg)
{
}

}} // end of namespace MyCompany.MyProject

Sample program source file

#include "MyProjectProgram.hpp"
#include "Arp/System/Commons/Logging.h"
#include "Arp/System/Core/ByteConverter.hpp"
#include <sstream>
#include <iomanip>

namespace MyCompany { namespace MyProject
{

void initialise_matrix_column(ne10_mat_row3f *col)
{
    col->r1 = (ne10_float32_t)rand() / RAND_MAX * 5.0f;
    col->r2 = (ne10_float32_t)rand() / RAND_MAX * 5.0f;
    col->r3 = (ne10_float32_t)rand() / RAND_MAX * 5.0f;
}

void initialise_matrix(ne10_mat3x3f_t *mat)
{
    initialise_matrix_column(&mat->c1);
    initialise_matrix_column(&mat->c2);
    initialise_matrix_column(&mat->c3);
}

void MyProjectProgram::Execute()
{
    if (calculate && !prevCalc)
    {
        ne10_mat3x3f_t src[MATRICES]; // A source array of `MATRICES` input matrices
        ne10_mat3x3f_t mul[MATRICES]; // An array of matrices to multiply those in `src` by
        ne10_mat3x3f_t dst[MATRICES]; // A destination array for the multiplication results

        // Initialise Ne10, using hardware auto-detection to set library function pointers
        if (ne10_init() != NE10_OK)
        {
            this->log.Info("Failed to initialise Ne10.\n");
        }
        else
        {
            // Generate test input values
            for (int i = 0; i < MATRICES; i++)
            {
                initialise_matrix(&src[i]);
                initialise_matrix(&mul[i]);
            }

            // Perform the multiplication of the matrices in `src` by those in `mul`
            ne10_mulmat_3x3f(dst, src, mul, MATRICES);

            // Display the results (src[i] * mul[i] == dst[i])
            std::ostringstream ss;
            ss << "\n\nResult of matrix multiplication:\n";
            for (int i = 0; i < MATRICES; i++)
            {
                ss << fixed << setprecision(2) 
                    << "\n[ "<<src[i].c1.r1<<" "<<src[i].c2.r1<<" "<<src[i].c3.r1
                    <<"     [ "<<mul[i].c1.r1<<" "<<mul[i].c2.r1<<" "<<mul[i].c3.r1
                    <<"     [ "<<dst[i].c1.r1<<" "<<dst[i].c2.r1<<" "<<dst[i].c3.r1
                    <<"\n  "<<src[i].c1.r2<<" "<<src[i].c2.r2<<" "<<src[i].c3.r2
                    <<"   *   "<<mul[i].c1.r2<<" "<<mul[i].c2.r2<<" "<<mul[i].c3.r2
                    <<"   =   "<<dst[i].c1.r2<<" "<<dst[i].c2.r2<<" "<<dst[i].c3.r2
                    <<"\n  "<<src[i].c1.r3<<" "<<src[i].c2.r3<<" "<<src[i].c3.r3
                    <<" ]     "<<mul[i].c1.r3<<" "<<mul[i].c2.r3<<" "<<mul[i].c3.r3
                    <<" ]     "<<dst[i].c1.r3<<" "<<dst[i].c2.r3<<" "<<dst[i].c3.r3<<" ]\n";
            }
            this->log.Info(ss.str().c_str());
        }
    }

    // Remember the input value for next time
    prevCalc = calculate;
}

}} // end of namespace MyCompany.MyProject