Last edited 2 years ago

How to install OPC UA

This stage explains how to properly install a library to use it on STM32MP1 boards.

1. Overview[edit source]

Refer to the OPC UA overview for more details about this communication protocol.

2. Installation process[edit source]

This section gives an overview of how to get the open62541 library for STM32MP1.

2.1. Install the SDK[edit source]

  • If the SDK is not yet installed, refer to Install the SDK before going further. It is essential later to cross-compile the library.

2.2. Create a new project[edit source]

It is not required to follow this step, however this tutorial is based on a fake project to give an example of how to proceed.

  • Create a directory to host the source codes
 mkdir $HOME/Documents/OPC_UA_first_project
 mkdir $HOME/Documents/OPC_UA_first_project/src
  • Create a directory to host the library and go to it
 mkdir $HOME/Documents/OPC_UA_first_project/lib
 cd $HOME/Documents/OPC_UA_first_project/lib

2.3. Clone open62541 git repository[edit source]

  • Clone the repository needed to compile the library (git commands must be enable on your Linux):
 git clone https://github.com/open62541/open62541.git
 cd open62541
 git submodule update --init --recursive
 mkdir build && cd build

2.4. Cross-building the library[edit source]

The SDK is useful as it builds a library usable with ARM binaries to execute the code on the cortex A7 of STM32MP1.

  • Source the shell to cross compile with the SDK:
 source <path_to_SDK>/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
  • To make sure that your source succeeds try "echo $CROSS_COMPILE" to get the following answer:
 echo $CROSS_COMPILE
arm-ostl-linux-gnueabi-
Warning white.png Warning
Be careful, your source operation is only available in this shell window
  • To cross compile the library use the cmake command. All compilation flags can be found here on the official documentation of open62541. The main ones have been chosen in this example to make both Client/Server and PubSub communication.
 cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DUA_ENABLE_PUBSUB=ON  -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_NAMESPACE_ZERO=MINIMAL UA_ENABLE_STATUSCODE_DESCRIPTIONS=OFF UA_ENABLE_TYPEDESCRIPTION=OFF UA_LOGLEVEL=200 ..
 make
  • The library is now built and put into the bin folder. Check it here:
 ls bin/
libopen62541.a
  • To be sure that the library is well cross-compiled, enter the following command:
 readelf -a bin/libopen62541.a

Many R_ARM notices are displayed with at the end, the following returns:

File Attributes
 Tag_CPU_name: "Cortex-A7"
 Tag_CPU_arch: v7
 Tag_CPU_arch_profile: Application
Info white.png Information
It is also possible to develop the OPC UA programs executable on your PC at a later stage (and then communicate with STM32MP1). However, this library cannot be used for x86 binaries, therefore the best way is to have another library which is not cross-compiled. The steps to follow are the same, just do not source your shell.

3. Building your first program[edit source]

As similar to other libraries, the Makefile must be edited with the path information where to find open62541 library, headers etc. As a first program the example used here is given by open62541 team, which is a simple OPC UA Client .

  • First, go to your src project folder
 cd $HOME/Documents/OPC_UA_first_project/src
  • Create a new file called myServer.c whose content can be found here. Do not compile the code as it is done on the webpage.
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>

#include <signal.h>
#include <stdlib.h>

static volatile UA_Boolean running = true;
static void stopHandler(int sig) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    running = false;
}

int main(void) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    UA_StatusCode retval = UA_Server_run(server, &running);

    UA_Server_delete(server);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
  • Now create a new file called Makefile whose content is:
# Makefile template for OPC UA

PROG = myServer.bin

SRCS = myServer.c #you can add here your other .c files

OBJS = $(SRCS:%.c=%.o)

#the path of open62541 library
PATHLIB = -L ../lib/open62541/build/bin/

#the name of the library
LIB = open62541

#path of headers files
INCLUDE += -I ../lib/open62541/include/
INCLUDE += -I ../lib/open62541/plugins/include/
INCLUDE += -I ../lib/open62541/build/src_generated/
INCLUDE += -I ../lib/open62541/arch/
INCLUDE += -I ../lib/open62541/deps/
INCLUDE += -I ../lib/open62541/src/pubsub/

CLEANFILES = $(PROG)

# Add / change option in CFLAGS and LDFLAGS
CFLAGS += -Wall 
CFLAGS += $(INCLUDE)

LDFLAGS += $(PATHLIB) -l$(LIB)

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

clean:
	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))
  • Now build the program:
 make 
  • The executable program appears in the folder:
 ls
Makefile  myServer.bin  myServer.c  myServer.o

4. Executing the program on STM32MP1[edit source]

 scp myServer.bin root@<IP of your board>:/usr/local
  • Then open another shell and connect to the board in ssh:
 ssh root@<IP of your board>
  • Go to the folder where the program is located and execute it:
 cd /usr/local
 ./myServer.bin
  • Output similar appears:
[2021-01-14 09:41:22.659 (UTC+0100)] warn/server	AccessControl: Unconfigured AccessControl. Users have all permissions.
[2021-01-14 09:41:22.659 (UTC+0100)] info/server	AccessControl: Anonymous login is enabled
[2021-01-14 09:41:22.659 (UTC+0100)] warn/server	Username/Password configured, but no encrypting SecurityPolicy. This can leak 
credentials on the network.
[2021-01-14 09:41:22.659 (UTC+0100)] warn/userland	AcceptAll Certificate Verification. Any remote certificate will be accepted.
[2021-01-14 09:41:22.659 (UTC+0100)] info/network	TCP network layer listening on <TCP port> 

The first OPC UA program is now running on your board

5. Going further: make your first OPC UA Client/Server[edit source]

As an example to illustrate this article, let's make an OPC UA Client/Server run on two different STM32MP1 (it can also be done with two shells on the PC, or two different PCs, or one PC and one board. Just make sure to have the right version of the library, cross-compiled or not, to do this). The two STM32MP1 are linked by Ethernet to the same network. The PC used for the development of the project is also in the same network (this helps to easily send the files by scp command later).

  • Since two boards are used in this example, in order to not forget source the shell:
 source <path_to_SDK>/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
  • Then create all the folders needed for this little project, with the library side:
 mkdir <your_project_path>/opc_ua_client_server && cd <your_project_path>/opc_ua_client_server
 mkdir lib && cd lib
 git clone https://github.com/open62541/open62541.git
 cd open62541
 git submodule update --init --recursive
 mkdir build && cd build
 cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DUA_ENABLE_PUBSUB=ON  -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_NAMESPACE_ZERO=MINIMAL UA_ENABLE_STATUSCODE_DESCRIPTIONS=OFF UA_ENABLE_TYPEDESCRIPTION=OFF UA_LOGLEVEL=200 ..
 make
  • Now, with the source code side add:
 cd ../../..
 mkdir src && cd src

  • Create a common file for both client and server common.h:
#ifndef COMMON_H
#define COMMON_H

const char * IP_SERVER = "10.48.0.23"; //the address needs to be the IP of your board which is your server (ifconfig)
const int PORT_SERVER = 12345; //choose your port for the server

#endif /* COMMON_H */
  • Now create files for the server:
 mkdir server && cd server 
  • Create myServer.c and Makefile
/** DATA MODEL INFORMATION **
 * 
 * 
 * 
 * ------* Supermarket
 *       |
 *       |
 *       |--------* Products
 *       |        |
 *       |        |
 *       |        |----------* Apples (R/W)
 *       |        |
 *       |        |
 *       |        |----------* Bananas (R/W)
 *       |        
 *       |        
 *       |--------* Name (R)
 * 
 * 
 * The information model is based on a nodes model, and each endpoint of the tree has different values with Read/Write rights (R/W). 
 * The client will be able to manage the quantity of fruits, but only read the name of the market. 
 * 
 */

#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>

#include <signal.h>
#include <stdlib.h>
#include "../common.h"


static UA_VariableAttributes vnAttr;
static UA_VariableAttributes tpAttr;
static UA_VariableAttributes bAttr;

/* end tree node */
static  UA_NodeId nodeApples;
static  UA_NodeId nodeBananas;
static  UA_NodeId nodeName;

static volatile UA_Boolean running = true;

static void stopHandler(int sig) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    running = false;
}

//display the data model tree
static void display_tree();

// Read callback
static void
readCallback(UA_Server *server,
               const UA_NodeId *sessionId, void *sessionContext,
               const UA_NodeId *nodeid, void *nodeContext,
               const UA_NumericRange *range, const UA_DataValue *data) {
    

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "read from client on node : %.*s ",
     (int)nodeid->identifier.string.length, nodeid->identifier.string.data);

}

// Write callback
static void
writeCallback(UA_Server *server,
               const UA_NodeId *sessionId, void *sessionContext,
               const UA_NodeId *nodeid, void *nodeContext,
               const UA_NumericRange *range, const UA_DataValue *data) {
    

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "write from client on node : %.*s", (int)nodeid->identifier.string.length, nodeid->identifier.string.data);

}


int main(int argc, char * argv[]) { 
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Initialisation ...\n");

    UA_Server * server = UA_Server_new();

    //get port number
    UA_Int16 port_number = (UA_Int16) PORT_SERVER;

    //Server config creation 
    UA_ServerConfig_setMinimal(UA_Server_getConfig(server), port_number, 0);

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Configuration completed\n");

        

   

    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Server created\n");

    

    //--------------------------------------------DATA-------------------------------------------

    //Adding a new namepace to the server
    UA_Int16 ns_supermarket = UA_Server_addNamespace(server,"NS_Supermarket");
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New Namespace added with the number : %d",ns_supermarket);


    //adding a new Object Supermarket which is the major Node
    UA_NodeId node_supermaret_id; /* get the nodeid assigned by the server */
    UA_ObjectAttributes sAttr = UA_ObjectAttributes_default;
    UA_Server_addObjectNode(server, UA_NODEID_STRING(2,"Node_Supermarket"),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(2, "Supermarket"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                            sAttr, NULL, &node_supermaret_id);


    //adding the variable Name to server
    vnAttr = UA_VariableAttributes_default;
    UA_String marketName = UA_STRING("Fruits_Market_And_Co.");
    vnAttr.accessLevel = UA_ACCESSLEVELMASK_READ;
    UA_Variant_setScalar(&vnAttr.value, &marketName, &UA_TYPES[UA_TYPES_STRING]);
    UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Market_Name"), node_supermaret_id,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(2, "Market_Name"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vnAttr, NULL, &nodeName);

    
    //adding a new Object Product which is a child Node of Supermarket
    UA_NodeId node_products_id; /* get the nodeid assigned by the server */
    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    UA_Server_addObjectNode(server, UA_NODEID_STRING(2,"Node_Products"),
                            node_supermaret_id,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(2, "Products"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                            oAttr, NULL, &node_products_id);

    //adding the variable node Apples under products
    tpAttr = UA_VariableAttributes_default;
    UA_UInt32 apples = 60;
    tpAttr.accessLevel = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&tpAttr.value, &apples, &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Apples"), node_products_id,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(2, "Apples"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), tpAttr, NULL, &nodeApples);


    //adding the variable node Bananas under products
    bAttr = UA_VariableAttributes_default;
    UA_UInt32 bananas = 34;
    bAttr.accessLevel = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&bAttr.value, &bananas, &UA_TYPES[UA_TYPES_UINT32]);
    UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Bananas"), node_products_id,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(2, "Bananas"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), bAttr, NULL, &nodeBananas);

    

    //Adding Callback to all Nodes
    UA_ValueCallback callback ;
    callback.onRead = readCallback;
    callback.onWrite = writeCallback;
    UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Bananas"), callback);
    UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Apples"), callback);
    UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Market_Name"), callback);
    //-------------------------------------------------------------------------------------------
    
    display_tree(server);
    UA_StatusCode retval = UA_Server_run(server, &running);
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "shutdown server ...");
    display_tree(server);
    UA_Server_delete(server);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

//display the data model tree information (server side)
static void display_tree(UA_Server * serv) {

    UA_String str = * (UA_String *) vnAttr.value.data;
    UA_Variant value;
    UA_Variant_init(&value);
    UA_StatusCode retval = UA_Server_readValue(serv, nodeApples ,&value);
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, ">>> %d", retval);
    UA_UInt32 nA = * (UA_UInt32 *) value.data;
    UA_Server_readValue(serv, nodeBananas ,&value);
    UA_UInt32 nB = * (UA_UInt32 *) value.data;

    printf("\n\n");
    printf("------* Supermarket\n");
    printf("      |\n");
    printf("      |\n");
    printf("      |--------* Products\n");
    printf("      |        |\n");
    printf("      |        |\n");
    printf("      |        |----------* Apples n = %d\n", (int) nA);
    printf("      |        |\n");
    printf("      |        |\n");
    printf("      |        |----------* Bananas n = %d\n", (int) nB);
    printf("      |\n");
    printf("      |\n");
    printf("      |--------* Name = %.*s", (int)str.length, str.data);
    printf("\n\n");

    UA_Variant_clear(&value);

}
# Makefile template for OPC UA

PROG = myServer.bin

SRCS = myServer.c #you can add here your other .c files

OBJS = $(SRCS:%.c=%.o)

#the path of open62541 library
PATHLIB = -L ../../lib/open62541/build/bin/

#the name of the library
LIB = open62541

#path of headers files
INCLUDE += -I ../../lib/open62541/include/
INCLUDE += -I ../../lib/open62541/plugins/include/
INCLUDE += -I ../../lib/open62541/build/src_generated/
INCLUDE += -I ../../lib/open62541/arch/
INCLUDE += -I ../../lib/open62541/deps/
CLEANFILES = $(PROG)

# Add / change option in CFLAGS and LDFLAGS
CFLAGS += -Wall 
CFLAGS += $(INCLUDE)

LDFLAGS += $(PATHLIB) -l$(LIB)

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

clean:
	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))
  • Build the server part:
 make
  • The binary myServer.bin is now cross-compiled and can be sent on the server STM32MP1 board.
 scp myServer.bin root@<IP of your server board>:/usr/local
  • Now do the same with client side:
 cd ..
 mkdir client && cd client
  • Create myClient.c and Makefile:
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "../common.h"

static volatile UA_Boolean running = true;

static void stopHandler(int sig) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    running = false;
}

int main(void) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    /* Create the client wich will listen on the port described in common.h */
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    char address[64];
    char header[sizeof("opc.tcp://")] = "opc.tcp://";
    strcat(address, header);
    strcat(address, IP_SERVER);
    strcat(address, ":");
    char port[12];
    sprintf(port, "%d", PORT_SERVER);
    strcat(address, port);
    UA_StatusCode retval = UA_Client_connect(client, address); //IP / port of your server

    if(retval != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client);
        return (int)retval;
    }


    //begin routine ---------------------

    while (running == true){
        
        //Variable to read from Server
        UA_String marketName;
        UA_UInt32 applesNumber;
        UA_UInt32 bananasNumber;

        //Variant that is used as buffer 
        UA_Variant value;
        UA_Variant_init(&value);

        //We read the name of the Supermarket
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Market_Name"), &value);
        if(retval == UA_STATUSCODE_GOOD &&
            UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_STRING])) {
            marketName = *(UA_String *) value.data;
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The Market Name is : %.*s \n",(int)marketName.length, marketName.data);  
        }

        sleep(1);

        //We try to modify the name of Supermarket (should not work because we do not have writing rights)
        UA_String newName = UA_STRING("My_New_Fruit_Market.");
        UA_Variant_setScalar(&value, &newName, &UA_TYPES[UA_TYPES_STRING]);
        retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Market_Name"), &value);
        if(retval != UA_STATUSCODE_GOOD){
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "You do not have rights to modify the market name\n");
        }

        sleep(1);

        //We read the number of apples in the market 
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Apples"), &value);
        if(retval == UA_STATUSCODE_GOOD &&
            UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT32])) {
            applesNumber = *(UA_UInt32 *) value.data;
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The number of Apples is : %.d \n",(int)applesNumber);  
        }

        sleep(1);

        //We try to change the number of apples in the market (should work because we have writing rights)
        UA_UInt32 valA = applesNumber + 2;
        UA_Variant_setScalar(&value, &valA, &UA_TYPES[UA_TYPES_UINT32]);
        retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Apples"), &value);
        if(retval != UA_STATUSCODE_GOOD){
            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error when trying to change Apples number\n");
        }

        sleep(1);

        //We read the number of bananas in the market 
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Bananas"), &value);
        if(retval == UA_STATUSCODE_GOOD &&
            UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT32])) {
            bananasNumber = *(UA_UInt32 *) value.data;
            UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The number of Bananas is : %.d \n",(int)bananasNumber);  
        }

        sleep(1);

        //We try to change the number of apples in the market (should work because we have writing rights)
        UA_UInt32 valB = bananasNumber + 1;
        UA_Variant_setScalar(&value, &valB, &UA_TYPES[UA_TYPES_UINT32]);
        retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Bananas"), &value);
        if(retval != UA_STATUSCODE_GOOD){
            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error when trying to change Bananas number\n");
        }

        sleep(1);

    }

    //-----------------------------------
    

    /* Clean up */
    UA_Client_delete(client); /* Disconnects the client  */
    return EXIT_SUCCESS;
}
# Makefile template for OPC UA

PROG = myClient.bin

SRCS = myClient.c #you can add here your other .c files

OBJS = $(SRCS:%.c=%.o)

#the path of open62541 library
PATHLIB = -L ../../lib/open62541/build/bin/

#the name of the library
LIB = open62541

#path of headers files
INCLUDE += -I ../../lib/open62541/include/
INCLUDE += -I ../../lib/open62541/plugins/include/
INCLUDE += -I ../../lib/open62541/build/src_generated/
INCLUDE += -I ../../lib/open62541/arch/
INCLUDE += -I ../../lib/open62541/deps/
CLEANFILES = $(PROG)

# Add / change option in CFLAGS and LDFLAGS
CFLAGS += -Wall 
CFLAGS += $(INCLUDE)

LDFLAGS += $(PATHLIB) -l$(LIB)

all: $(PROG)

$(PROG): $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

clean:
	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))
  • Build the client part:
 make
  • The binary myClient.bin is now cross-compiled and it can sent on the client STM32MP1 board.
 scp myClient.bin root@<IP of your client board>:/usr/local

Now, open a shell for both of your boards, and go to /usr/local/ folder, find binaries and execute them starting with the server.

Info white.png Information
Find here the official open62541 website which give you access both to the most recent documentation and to the official Github of this stack.