How to install OPC UA

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

1 Overview[edit]

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

2 Installation process[edit]

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

2.1 Install the SDK[edit]

  • 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]

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
PC $> mkdir $HOME/Documents/OPC_UA_first_project
PC $> mkdir $HOME/Documents/OPC_UA_first_project/src
  • Create a directory to host the library and go to it
PC $> mkdir $HOME/Documents/OPC_UA_first_project/lib
PC $> cd $HOME/Documents/OPC_UA_first_project/lib

2.3 Clone open62541 git repository[edit]

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

2.4 Cross-building the library[edit]

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:
PC $> 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:
PC $> 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.
PC $> 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 ..
PC $> make
  • The library is now built and put into the bin folder. Check it here:
PC $> ls bin/
libopen62541.a
  • To be sure that the library is well cross-compiled, enter the following command:
PC $> 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]

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
PC $> 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.
 1 #include <open62541/plugin/log_stdout.h>
 2 #include <open62541/server.h>
 3 #include <open62541/server_config_default.h>
 4 
 5 #include <signal.h>
 6 #include <stdlib.h>
 7 
 8 static volatile UA_Boolean running = true;
 9 static void stopHandler(int sig) {
10     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
11     running = false;
12 }
13 
14 int main(void) {
15     signal(SIGINT, stopHandler);
16     signal(SIGTERM, stopHandler);
17 
18     UA_Server *server = UA_Server_new();
19     UA_ServerConfig_setDefault(UA_Server_getConfig(server));
20 
21     UA_StatusCode retval = UA_Server_run(server, &running);
22 
23     UA_Server_delete(server);
24     return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
25 }
  • 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:
PC $> make 
  • The executable program appears in the folder:
PC $> ls
Makefile  myServer.bin  myServer.c  myServer.o

4 Executing the program on STM32MP1[edit]

PC $> scp myServer.bin root@<IP of your board>:/usr/local
  • Then open another shell and connect to the board in ssh:
PC $> ssh root@<IP of your board>
  • Go to the folder where the program is located and execute it:
Board $> cd /usr/local
Board $> ./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]

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:
PC $> 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:
PC $> mkdir <your_project_path>/opc_ua_client_server && cd <your_project_path>/opc_ua_client_server
PC $> mkdir lib && cd lib
PC $> git clone https://github.com/open62541/open62541.git
PC $> cd open62541
PC $> git submodule update --init --recursive
PC $> mkdir build && cd build
PC $> 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 ..
PC $> make
  • Now, with the source code side add:
PC $> cd ../../..
PC $> mkdir src && cd src

  • Create a common file for both client and server common.h:
1 #ifndef COMMON_H
2 #define COMMON_H
3 
4 const char * IP_SERVER = "10.48.0.23"; //the address needs to be the IP of your board which is your server (ifconfig)
5 const int PORT_SERVER = 12345; //choose your port for the server
6 
7 #endif /* COMMON_H */
  • Now create files for the server:
PC $> mkdir server && cd server 
  • Create myServer.c and Makefile
  1 /** DATA MODEL INFORMATION **
  2  * 
  3  * 
  4  * 
  5  * ------* Supermarket
  6  *       |
  7  *       |
  8  *       |--------* Products
  9  *       |        |
 10  *       |        |
 11  *       |        |----------* Apples (R/W)
 12  *       |        |
 13  *       |        |
 14  *       |        |----------* Bananas (R/W)
 15  *       |        
 16  *       |        
 17  *       |--------* Name (R)
 18  * 
 19  * 
 20  * The information model is based on a nodes model, and each endpoint of the tree has different values with Read/Write rights (R/W). 
 21  * The client will be able to manage the quantity of fruits, but only read the name of the market. 
 22  * 
 23  */
 24 
 25 #include <open62541/plugin/log_stdout.h>
 26 #include <open62541/server.h>
 27 #include <open62541/server_config_default.h>
 28 
 29 #include <signal.h>
 30 #include <stdlib.h>
 31 #include "../common.h"
 32 
 33 
 34 static UA_VariableAttributes vnAttr;
 35 static UA_VariableAttributes tpAttr;
 36 static UA_VariableAttributes bAttr;
 37 
 38 /* end tree node */
 39 static  UA_NodeId nodeApples;
 40 static  UA_NodeId nodeBananas;
 41 static  UA_NodeId nodeName;
 42 
 43 static volatile UA_Boolean running = true;
 44 
 45 static void stopHandler(int sig) {
 46     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
 47     running = false;
 48 }
 49 
 50 //display the data model tree
 51 static void display_tree();
 52 
 53 // Read callback
 54 static void
 55 readCallback(UA_Server *server,
 56                const UA_NodeId *sessionId, void *sessionContext,
 57                const UA_NodeId *nodeid, void *nodeContext,
 58                const UA_NumericRange *range, const UA_DataValue *data) {
 59     
 60 
 61     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "read from client on node : %.*s ",
 62      (int)nodeid->identifier.string.length, nodeid->identifier.string.data);
 63 
 64 }
 65 
 66 // Write callback
 67 static void
 68 writeCallback(UA_Server *server,
 69                const UA_NodeId *sessionId, void *sessionContext,
 70                const UA_NodeId *nodeid, void *nodeContext,
 71                const UA_NumericRange *range, const UA_DataValue *data) {
 72     
 73 
 74     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "write from client on node : %.*s", (int)nodeid->identifier.string.length, nodeid->identifier.string.data);
 75 
 76 }
 77 
 78 
 79 int main(int argc, char * argv[]) { 
 80     signal(SIGINT, stopHandler);
 81     signal(SIGTERM, stopHandler);
 82 
 83     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Initialisation ...\n");
 84 
 85     UA_Server * server = UA_Server_new();
 86 
 87     //get port number
 88     UA_Int16 port_number = (UA_Int16) PORT_SERVER;
 89 
 90     //Server config creation 
 91     UA_ServerConfig_setMinimal(UA_Server_getConfig(server), port_number, 0);
 92 
 93     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Configuration completed\n");
 94 
 95         
 96 
 97    
 98 
 99     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Server created\n");
100 
101     
102 
103     //--------------------------------------------DATA-------------------------------------------
104 
105     //Adding a new namepace to the server
106     UA_Int16 ns_supermarket = UA_Server_addNamespace(server,"NS_Supermarket");
107     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New Namespace added with the number : %d",ns_supermarket);
108 
109 
110     //adding a new Object Supermarket which is the major Node
111     UA_NodeId node_supermaret_id; /* get the nodeid assigned by the server */
112     UA_ObjectAttributes sAttr = UA_ObjectAttributes_default;
113     UA_Server_addObjectNode(server, UA_NODEID_STRING(2,"Node_Supermarket"),
114                             UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
115                             UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
116                             UA_QUALIFIEDNAME(2, "Supermarket"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
117                             sAttr, NULL, &node_supermaret_id);
118 
119 
120     //adding the variable Name to server
121     vnAttr = UA_VariableAttributes_default;
122     UA_String marketName = UA_STRING("Fruits_Market_And_Co.");
123     vnAttr.accessLevel = UA_ACCESSLEVELMASK_READ;
124     UA_Variant_setScalar(&vnAttr.value, &marketName, &UA_TYPES[UA_TYPES_STRING]);
125     UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Market_Name"), node_supermaret_id,
126                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
127                               UA_QUALIFIEDNAME(2, "Market_Name"),
128                               UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vnAttr, NULL, &nodeName);
129 
130     
131     //adding a new Object Product which is a child Node of Supermarket
132     UA_NodeId node_products_id; /* get the nodeid assigned by the server */
133     UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
134     UA_Server_addObjectNode(server, UA_NODEID_STRING(2,"Node_Products"),
135                             node_supermaret_id,
136                             UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
137                             UA_QUALIFIEDNAME(2, "Products"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
138                             oAttr, NULL, &node_products_id);
139 
140     //adding the variable node Apples under products
141     tpAttr = UA_VariableAttributes_default;
142     UA_UInt32 apples = 60;
143     tpAttr.accessLevel = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
144     UA_Variant_setScalar(&tpAttr.value, &apples, &UA_TYPES[UA_TYPES_UINT32]);
145     UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Apples"), node_products_id,
146                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
147                               UA_QUALIFIEDNAME(2, "Apples"),
148                               UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), tpAttr, NULL, &nodeApples);
149 
150 
151     //adding the variable node Bananas under products
152     bAttr = UA_VariableAttributes_default;
153     UA_UInt32 bananas = 34;
154     bAttr.accessLevel = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
155     UA_Variant_setScalar(&bAttr.value, &bananas, &UA_TYPES[UA_TYPES_UINT32]);
156     UA_Server_addVariableNode(server, UA_NODEID_STRING(2,"Node_Bananas"), node_products_id,
157                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
158                               UA_QUALIFIEDNAME(2, "Bananas"),
159                               UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), bAttr, NULL, &nodeBananas);
160 
161     
162 
163     //Adding Callback to all Nodes
164     UA_ValueCallback callback ;
165     callback.onRead = readCallback;
166     callback.onWrite = writeCallback;
167     UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Bananas"), callback);
168     UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Apples"), callback);
169     UA_Server_setVariableNode_valueCallback(server, UA_NODEID_STRING(2,"Node_Market_Name"), callback);
170     //-------------------------------------------------------------------------------------------
171     
172     display_tree(server);
173     UA_StatusCode retval = UA_Server_run(server, &running);
174     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "shutdown server ...");
175     display_tree(server);
176     UA_Server_delete(server);
177     return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
178 }
179 
180 //display the data model tree information (server side)
181 static void display_tree(UA_Server * serv) {
182 
183     UA_String str = * (UA_String *) vnAttr.value.data;
184     UA_Variant value;
185     UA_Variant_init(&value);
186     UA_StatusCode retval = UA_Server_readValue(serv, nodeApples ,&value);
187     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, ">>> %d", retval);
188     UA_UInt32 nA = * (UA_UInt32 *) value.data;
189     UA_Server_readValue(serv, nodeBananas ,&value);
190     UA_UInt32 nB = * (UA_UInt32 *) value.data;
191 
192     printf("\n\n");
193     printf("------* Supermarket\n");
194     printf("      |\n");
195     printf("      |\n");
196     printf("      |--------* Products\n");
197     printf("      |        |\n");
198     printf("      |        |\n");
199     printf("      |        |----------* Apples n = %d\n", (int) nA);
200     printf("      |        |\n");
201     printf("      |        |\n");
202     printf("      |        |----------* Bananas n = %d\n", (int) nB);
203     printf("      |\n");
204     printf("      |\n");
205     printf("      |--------* Name = %.*s", (int)str.length, str.data);
206     printf("\n\n");
207 
208     UA_Variant_clear(&value);
209 
210 }
 1 # Makefile template for OPC UA
 2 
 3 PROG = myServer.bin
 4 
 5 SRCS = myServer.c #you can add here your other .c files
 6 
 7 OBJS = $(SRCS:%.c=%.o)
 8 
 9 #the path of open62541 library
10 PATHLIB = -L ../../lib/open62541/build/bin/
11 
12 #the name of the library
13 LIB = open62541
14 
15 #path of headers files
16 INCLUDE += -I ../../lib/open62541/include/
17 INCLUDE += -I ../../lib/open62541/plugins/include/
18 INCLUDE += -I ../../lib/open62541/build/src_generated/
19 INCLUDE += -I ../../lib/open62541/arch/
20 INCLUDE += -I ../../lib/open62541/deps/
21 CLEANFILES = $(PROG)
22 
23 # Add / change option in CFLAGS and LDFLAGS
24 CFLAGS += -Wall 
25 CFLAGS += $(INCLUDE)
26 
27 LDFLAGS += $(PATHLIB) -l$(LIB)
28 
29 all: $(PROG)
30 
31 $(PROG): $(OBJS)
32 	$(CC) -o $@ $^ $(LDFLAGS)
33 
34 clean:
35 	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))
  • Build the server part:
PC $> make
  • The binary myServer.bin is now cross-compiled and can be sent on the server STM32MP1 board.
PC $> scp myServer.bin root@<IP of your server board>:/usr/local
  • Now do the same with client side:
PC $> cd ..
PC $> mkdir client && cd client
  • Create myClient.c and Makefile:
  1 #include <open62541/client_config_default.h>
  2 #include <open62541/client_highlevel.h>
  3 #include <open62541/plugin/log_stdout.h>
  4 
  5 #include <stdlib.h>
  6 #include <stdio.h>
  7 #include <time.h>
  8 #include "../common.h"
  9 
 10 static volatile UA_Boolean running = true;
 11 
 12 static void stopHandler(int sig) {
 13     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
 14     running = false;
 15 }
 16 
 17 int main(void) {
 18     signal(SIGINT, stopHandler);
 19     signal(SIGTERM, stopHandler);
 20 
 21     /* Create the client wich will listen on the port described in common.h */
 22     UA_Client *client = UA_Client_new();
 23     UA_ClientConfig_setDefault(UA_Client_getConfig(client));
 24     char address[64];
 25     char header[sizeof("opc.tcp://")] = "opc.tcp://";
 26     strcat(address, header);
 27     strcat(address, IP_SERVER);
 28     strcat(address, ":");
 29     char port[12];
 30     sprintf(port, "%d", PORT_SERVER);
 31     strcat(address, port);
 32     UA_StatusCode retval = UA_Client_connect(client, address); //IP / port of your server
 33 
 34     if(retval != UA_STATUSCODE_GOOD) {
 35         UA_Client_delete(client);
 36         return (int)retval;
 37     }
 38 
 39 
 40     //begin routine ---------------------
 41 
 42     while (running == true){
 43         
 44         //Variable to read from Server
 45         UA_String marketName;
 46         UA_UInt32 applesNumber;
 47         UA_UInt32 bananasNumber;
 48 
 49         //Variant that is used as buffer 
 50         UA_Variant value;
 51         UA_Variant_init(&value);
 52 
 53         //We read the name of the Supermarket
 54         retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Market_Name"), &value);
 55         if(retval == UA_STATUSCODE_GOOD &&
 56             UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_STRING])) {
 57             marketName = *(UA_String *) value.data;
 58             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The Market Name is : %.*s \n",(int)marketName.length, marketName.data);  
 59         }
 60 
 61         sleep(1);
 62 
 63         //We try to modify the name of Supermarket (should not work because we do not have writing rights)
 64         UA_String newName = UA_STRING("My_New_Fruit_Market.");
 65         UA_Variant_setScalar(&value, &newName, &UA_TYPES[UA_TYPES_STRING]);
 66         retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Market_Name"), &value);
 67         if(retval != UA_STATUSCODE_GOOD){
 68             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "You do not have rights to modify the market name\n");
 69         }
 70 
 71         sleep(1);
 72 
 73         //We read the number of apples in the market 
 74         retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Apples"), &value);
 75         if(retval == UA_STATUSCODE_GOOD &&
 76             UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT32])) {
 77             applesNumber = *(UA_UInt32 *) value.data;
 78             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The number of Apples is : %.d \n",(int)applesNumber);  
 79         }
 80 
 81         sleep(1);
 82 
 83         //We try to change the number of apples in the market (should work because we have writing rights)
 84         UA_UInt32 valA = applesNumber + 2;
 85         UA_Variant_setScalar(&value, &valA, &UA_TYPES[UA_TYPES_UINT32]);
 86         retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Apples"), &value);
 87         if(retval != UA_STATUSCODE_GOOD){
 88             UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error when trying to change Apples number\n");
 89         }
 90 
 91         sleep(1);
 92 
 93         //We read the number of bananas in the market 
 94         retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2,"Node_Bananas"), &value);
 95         if(retval == UA_STATUSCODE_GOOD &&
 96             UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_UINT32])) {
 97             bananasNumber = *(UA_UInt32 *) value.data;
 98             UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"The number of Bananas is : %.d \n",(int)bananasNumber);  
 99         }
100 
101         sleep(1);
102 
103         //We try to change the number of apples in the market (should work because we have writing rights)
104         UA_UInt32 valB = bananasNumber + 1;
105         UA_Variant_setScalar(&value, &valB, &UA_TYPES[UA_TYPES_UINT32]);
106         retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2,"Node_Bananas"), &value);
107         if(retval != UA_STATUSCODE_GOOD){
108             UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error when trying to change Bananas number\n");
109         }
110 
111         sleep(1);
112 
113     }
114 
115     //-----------------------------------
116     
117 
118     /* Clean up */
119     UA_Client_delete(client); /* Disconnects the client  */
120     return EXIT_SUCCESS;
121 }
 1 # Makefile template for OPC UA
 2 
 3 PROG = myClient.bin
 4 
 5 SRCS = myClient.c #you can add here your other .c files
 6 
 7 OBJS = $(SRCS:%.c=%.o)
 8 
 9 #the path of open62541 library
10 PATHLIB = -L ../../lib/open62541/build/bin/
11 
12 #the name of the library
13 LIB = open62541
14 
15 #path of headers files
16 INCLUDE += -I ../../lib/open62541/include/
17 INCLUDE += -I ../../lib/open62541/plugins/include/
18 INCLUDE += -I ../../lib/open62541/build/src_generated/
19 INCLUDE += -I ../../lib/open62541/arch/
20 INCLUDE += -I ../../lib/open62541/deps/
21 CLEANFILES = $(PROG)
22 
23 # Add / change option in CFLAGS and LDFLAGS
24 CFLAGS += -Wall 
25 CFLAGS += $(INCLUDE)
26 
27 LDFLAGS += $(PATHLIB) -l$(LIB)
28 
29 all: $(PROG)
30 
31 $(PROG): $(OBJS)
32 	$(CC) -o $@ $^ $(LDFLAGS)
33 
34 clean:
35 	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))
  • Build the client part:
PC $> make
  • The binary myClient.bin is now cross-compiled and it can sent on the client STM32MP1 board.
PC $> 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.