ChimeraTK-ControlSystemAdapter-OPCUAAdapter  04.00.01
node_historizing.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ChimeraTKs ControlSystem-OPC-UA-Adapter.
3  *
4  * ChimeraTKs ControlSystem-OPC-UA-Adapter is free software: you can
5  * redistribute it and/or modify it under the terms of the Lesser GNU
6  * General Public License as published by the Free Software Foundation,
7  * either version 3 of the License, or (at your option) any later version.
8  *
9  * ChimeraTKs ControlSystem-OPC-UA-Adapter is distributed in the hope
10  * that it will be useful, but WITHOUT ANY WARRANTY; without even the
11  * implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the Lesser GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Foobar. If not, see https://www.gnu.org/licenses/lgpl.html
16  *
17  * Copyright (c) 2023-2024 Fraunhofer IOSB (Author: Florian Düwel)
18  */
19 
20 #include "node_historizing.h"
21 
22 #include "open62541/plugin/log_stdout.h"
23 
24 namespace ChimeraTK {
25  typedef struct {
26  UA_Server* server;
27  string history;
28  vector<UA_NodeId>* historizing_nodes;
29  vector<string>* historizing_setup;
31 
32  UA_StatusCode get_child_variables(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void* handle) {
33  if(isInverse) return UA_STATUSCODE_GOOD;
34  auto* handler = (HandleFolderVariables*)handle;
35  UA_NodeId organizes = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
36  // get variables of nested folders
37  if(UA_NodeId_equal(&referenceTypeId, &organizes)) {
38  UA_Server_forEachChildNodeCall(handler->server, childId, get_child_variables, handler);
39  }
40  UA_NodeClass outNodeClass;
41  UA_NodeClass_init(&outNodeClass);
42  UA_Server_readNodeClass(handler->server, childId, &outNodeClass);
43  if(outNodeClass == UA_NODECLASS_VARIABLE) {
44  UA_NodeId* temp = UA_NodeId_new();
45  UA_NodeId_copy(&childId, temp);
46  handler->historizing_nodes->insert(handler->historizing_nodes->end(), *temp);
47  handler->historizing_setup->insert(handler->historizing_setup->end(), handler->history);
48  }
49  return UA_STATUSCODE_GOOD;
50  }
51 
52  void add_folder_historizing(vector<UA_NodeId>* historizing_nodes, vector<string>* historizing_setup,
53  vector<AdapterFolderHistorySetup> historyfolders, UA_Server* mappedServer, UA_ServerConfig* server_config) {
54  for(size_t i = 0; i < historyfolders.size(); i++) {
55  UA_NodeId* temp = UA_NodeId_new();
56  UA_StatusCode retval = UA_Server_readNodeId(mappedServer, historyfolders[i].folder_id, temp);
57  UA_NodeId_clear(temp);
58  if(retval == UA_STATUSCODE_GOOD) {
59  HandleFolderVariables handle;
60  handle.server = mappedServer;
61  handle.history = historyfolders[i].folder_historizing;
62  handle.historizing_nodes = historizing_nodes;
63  handle.historizing_setup = historizing_setup;
64  UA_Server_forEachChildNodeCall(mappedServer, historyfolders[i].folder_id, get_child_variables, &handle);
65  }
66  else {
67  UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
68  "Warning! Folder is not mapped in the server. StatusCode: %s ", UA_StatusCode_name(retval));
69  }
70  }
71  }
72 
73  void add_variable_historizing(vector<UA_NodeId>* historizing_nodes, vector<string>* historizing_setup,
74  vector<AdapterPVHistorySetup> historyvariables, UA_Server* mappedServer, UA_ServerConfig* server_config) {
75  for(size_t i = 0; i < historyvariables.size(); i++) {
76  UA_NodeId* temp = UA_NodeId_new();
77  UA_NodeId id = historyvariables[i].variable_id;
78  UA_StatusCode retval = UA_Server_readNodeId(mappedServer, id, temp);
79  if(retval == UA_STATUSCODE_GOOD) {
80  UA_String out = UA_STRING_NULL;
81  UA_print(&id, &UA_TYPES[UA_TYPES_NODEID], &out);
82  UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND, "add Node %.*s ", (int)out.length, out.data);
83  UA_String_clear(&out);
84  historizing_nodes->insert(historizing_nodes->end(), historyvariables[i].variable_id);
85  historizing_setup->insert(historizing_setup->end(), historyvariables[i].variable_historizing);
86  }
87  else {
88  UA_String out = UA_STRING_NULL;
89  UA_print(&id, &UA_TYPES[UA_TYPES_NODEID], &out);
90  UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
91  "Warning! Node %.*s has a history configuration but is not mapped to the server. StatusCode: %s ",
92  (int)out.length, out.data, UA_StatusCode_name(retval));
93  UA_String_clear(&out);
94  }
95  UA_NodeId_clear(temp);
96  }
97  }
98 
99  void set_variable_access_level_historizing(UA_NodeId id, UA_Server* mappedServer) {
100  UA_Byte temp;
101  UA_Byte_init(&temp);
102  UA_Server_readAccessLevel(mappedServer, id, &temp);
103  if(temp & UA_ACCESSLEVELMASK_READ) {
104  temp |= UA_ACCESSLEVELMASK_HISTORYREAD;
105  UA_Server_writeAccessLevel(mappedServer, id, temp);
106  }
107  UA_Server_writeHistorizing(mappedServer, id, true);
108  UA_Byte_clear(&temp);
109  }
110 
112  vector<UA_NodeId>& historizing_nodes, vector<string>& historizing_setup, UA_ServerConfig* server_config) {
113  bool repeat = true;
114  /*size_t position = 0;*/
115  while(repeat) {
116  repeat = false;
117  for(size_t i = /*position*/ 0; i < historizing_nodes.size(); i++) {
118  for(size_t j = i + 1; j < historizing_nodes.size(); j++) {
119  if(UA_NodeId_equal(reinterpret_cast<UA_NodeId*>(&historizing_nodes[i]),
120  reinterpret_cast<UA_NodeId*>(&historizing_nodes[j]))) {
121  UA_String out = UA_STRING_NULL;
122  UA_print(reinterpret_cast<UA_NodeId*>(&historizing_nodes[j]), &UA_TYPES[UA_TYPES_NODEID], &out);
123  UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND,
124  "Node %.*s has multiple history settings. The first folder/process_variable setting in the mapping "
125  "file is considered if multiple exists/overlap. "
126  "process_variable settings are preferred over folder settings.",
127  (int)out.length, out.data);
128  UA_String_clear(&out);
129  UA_NodeId_clear(reinterpret_cast<UA_NodeId*>(&historizing_nodes[j]));
130  historizing_nodes.erase(historizing_nodes.begin() + j);
131  historizing_setup.erase(historizing_setup.begin() + j);
132  repeat = true;
133  /*position = i;*/
134  break;
135  }
136  }
137  }
138  }
139  }
140 
141  void remove_nodes_with_incomplete_historizing_setup(vector<UA_NodeId>& historizing_nodes,
142  vector<string>& historizing_setup, UA_ServerConfig* server_config, vector<AdapterHistorySetup> history) {
143  bool repeat = true;
144  size_t position = 0;
145  while(repeat) {
146  repeat = false;
147  for(size_t i = position; i < historizing_nodes.size(); i++) {
148  bool found = false;
149  for(auto& j : history) {
150  if(historizing_setup[i] == j.name) {
151  found = true;
152  break;
153  }
154  }
155  if(!found) {
156  UA_String out = UA_STRING_NULL;
157  UA_print(reinterpret_cast<UA_NodeId*>(&historizing_nodes[i]), &UA_TYPES[UA_TYPES_NODEID], &out);
158  UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
159  "Warning! Remove node %.*s from historizing because the setup %s is missing.", (int)out.length, out.data,
160  historizing_setup[i].c_str());
161  UA_String_clear(&out);
162  // UA_NodeId_clear(&historizing_nodes[j]);
163  historizing_nodes.erase(historizing_nodes.begin() + i);
164  historizing_setup.erase(historizing_setup.begin() + i);
165  repeat = true;
166  position = i;
167  break;
168  }
169  }
170  }
171  }
172 
173  UA_HistoryDataGathering add_historizing_nodes(vector<UA_NodeId>& historizing_nodes, vector<string>& historizing_setup,
174  UA_Server* mappedServer, UA_ServerConfig* server_config, vector<AdapterHistorySetup> history,
175  vector<AdapterFolderHistorySetup> historyfolders, vector<AdapterPVHistorySetup> historyvariables) {
176  add_variable_historizing(&historizing_nodes, &historizing_setup, historyvariables, mappedServer, server_config);
177  add_folder_historizing(&historizing_nodes, &historizing_setup, historyfolders, mappedServer, server_config);
178  check_historizing_nodes(historizing_nodes, historizing_setup, server_config);
179  // search nodes with incomplete history config
180  remove_nodes_with_incomplete_historizing_setup(historizing_nodes, historizing_setup, server_config, history);
181  UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Default(historizing_nodes.size());
182  server_config->historyDatabase = UA_HistoryDatabase_default(gathering);
183  for(size_t i = 0; i < historizing_nodes.size(); i++) {
184  UA_HistorizingNodeIdSettings setting;
185  setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_POLL;
186  AdapterHistorySetup hist;
187  for(auto& j : history) {
188  if(historizing_setup[i] == j.name) {
189  hist = j;
190  }
191  }
192  set_variable_access_level_historizing(historizing_nodes[i], mappedServer);
193  setting.historizingBackend = UA_HistoryDataBackend_Memory_Circular(historizing_nodes.size(), hist.buffer_length);
194  setting.maxHistoryDataResponseSize = hist.entries_per_response;
195  setting.pollingInterval = hist.interval;
196  UA_StatusCode retval = gathering.registerNodeId(mappedServer, gathering.context, &historizing_nodes[i], setting);
197  if(retval != UA_STATUSCODE_GOOD) {
198  UA_String out = UA_STRING_NULL;
199  UA_print(&historizing_nodes[i], &UA_TYPES[UA_TYPES_NODEID], &out);
200  UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
201  "Failed to add historizing for Node %.*s with StatusCode %s", (int)out.length, out.data,
202  UA_StatusCode_name(retval));
203  UA_String_clear(&out);
204  }
205  retval = gathering.startPoll(mappedServer, gathering.context, &historizing_nodes[i]);
206  if(retval != UA_STATUSCODE_GOOD) {
207  UA_String out = UA_STRING_NULL;
208  UA_print(&historizing_nodes[i], &UA_TYPES[UA_TYPES_NODEID], &out);
209  UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
210  "Failed to start the poll for Node %.*s with StatusCode %s", (int)out.length, out.data,
211  UA_StatusCode_name(retval));
212  UA_String_clear(&out);
213  }
214  }
215  return gathering;
216  }
217 
218  void clear_history(UA_HistoryDataGathering gathering, vector<UA_NodeId>& historizing_nodes,
219  vector<string>& historizing_setup, UA_Server* mappedServer, vector<AdapterFolderHistorySetup> historyfolders,
220  vector<AdapterPVHistorySetup> historyvariables, UA_ServerConfig* server_config) {
221  // stop the poll for all historizing variables
222  for(auto& historizing_node : historizing_nodes) {
223  UA_StatusCode retval = gathering.stopPoll(mappedServer, gathering.context, &historizing_node);
224  UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_SERVER,
225  "Stopping polling thread used by the historizing -> %s", UA_StatusCode_name(retval));
226  }
227  // clear the list of valid historizing nodes
228  historizing_nodes.clear();
229  historizing_setup.clear();
230  // clear the nodeis lists (variables + server from the xml config)
231  for(auto& historyfolder : historyfolders) {
232  UA_NodeId_clear(&historyfolder.folder_id);
233  }
234  for(auto& historyvariable : historyvariables) {
235  UA_NodeId_clear(&historyvariable.variable_id);
236  }
237  }
238 } // namespace ChimeraTK
ChimeraTK::HandleFolderVariables::historizing_setup
vector< string > * historizing_setup
Definition: node_historizing.cpp:29
ChimeraTK::clear_history
void clear_history(UA_HistoryDataGathering gathering, vector< UA_NodeId > &historizing_nodes, vector< string > &historizing_setup, UA_Server *mappedServer, vector< AdapterFolderHistorySetup > historyfolders, vector< AdapterPVHistorySetup > historyvariables, UA_ServerConfig *server_config)
Definition: node_historizing.cpp:218
ChimeraTK::add_historizing_nodes
UA_HistoryDataGathering add_historizing_nodes(vector< UA_NodeId > &historizing_nodes, vector< string > &historizing_setup, UA_Server *mappedServer, UA_ServerConfig *server_config, vector< AdapterHistorySetup > history, vector< AdapterFolderHistorySetup > historyfolders, vector< AdapterPVHistorySetup > historyvariables)
Definition: node_historizing.cpp:173
ChimeraTK::HandleFolderVariables::server
UA_Server * server
Definition: node_historizing.cpp:26
ChimeraTK::remove_nodes_with_incomplete_historizing_setup
void remove_nodes_with_incomplete_historizing_setup(vector< UA_NodeId > &historizing_nodes, vector< string > &historizing_setup, UA_ServerConfig *server_config, vector< AdapterHistorySetup > history)
Definition: node_historizing.cpp:141
node_historizing.h
ChimeraTK::HandleFolderVariables::historizing_nodes
vector< UA_NodeId > * historizing_nodes
Definition: node_historizing.cpp:28
ChimeraTK::AdapterHistorySetup::buffer_length
size_t buffer_length
Definition: node_historizing.h:47
ChimeraTK::get_child_variables
UA_StatusCode get_child_variables(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void *handle)
Definition: node_historizing.cpp:32
ChimeraTK::set_variable_access_level_historizing
void set_variable_access_level_historizing(UA_NodeId id, UA_Server *mappedServer)
Definition: node_historizing.cpp:99
ChimeraTK::add_folder_historizing
void add_folder_historizing(vector< UA_NodeId > *historizing_nodes, vector< string > *historizing_setup, vector< AdapterFolderHistorySetup > historyfolders, UA_Server *mappedServer, UA_ServerConfig *server_config)
Definition: node_historizing.cpp:52
ChimeraTK::check_historizing_nodes
void check_historizing_nodes(vector< UA_NodeId > &historizing_nodes, vector< string > &historizing_setup, UA_ServerConfig *server_config)
This assumes both lists have the same size.
Definition: node_historizing.cpp:111
ChimeraTK::AdapterHistorySetup::entries_per_response
size_t entries_per_response
Definition: node_historizing.h:48
ChimeraTK::HandleFolderVariables
Definition: node_historizing.cpp:25
ChimeraTK::HandleFolderVariables::history
string history
Definition: node_historizing.cpp:27
ChimeraTK
Definition: csa_additionalvariable.h:28
ChimeraTK::AdapterHistorySetup
Definition: node_historizing.h:45
ChimeraTK::add_variable_historizing
void add_variable_historizing(vector< UA_NodeId > *historizing_nodes, vector< string > *historizing_setup, vector< AdapterPVHistorySetup > historyvariables, UA_Server *mappedServer, UA_ServerConfig *server_config)
Definition: node_historizing.cpp:73
ChimeraTK::AdapterHistorySetup::interval
size_t interval
Definition: node_historizing.h:49