ChimeraTK-ControlSystemAdapter-OPCUAAdapter 04.00.05
Loading...
Searching...
No Matches
ua_adapter.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) 2016 Chris Iatrou <Chris_Paul.Iatrou@tu-dresden.de>
18 * Copyright (c) 2016 Julian Rahm <Julian.Rahm@tu-dresden.de>
19 * Copyright (c) 2018-2023 Andreas Ebner <Andreas.Ebner@iosb.fraunhofer.de>
20 */
21#include "void_type.h"
22
23#include <open62541/plugin/historydata/history_data_backend_memory.h>
24#include <open62541/plugin/historydata/history_data_gathering_default.h>
25#include <open62541/plugin/historydata/history_database_default.h>
26#include <open62541/plugin/historydatabase.h>
27#include <open62541/plugin/log_stdout.h>
28#include <open62541/server.h>
29
30extern "C" {
31#include "csa_namespace.h"
32#include "loggingLevel_type.h"
33#include "unistd.h"
34
35#include <dirent.h>
36#include <stdio.h>
37#include <stdlib.h>
38}
39
41#include "csa_config.h"
42#include "csa_processvariable.h"
43#include "ua_adapter.h"
44#include "ua_map_types.h"
45#include "xml_file_handler.h"
46
47#include <functional>
48#include <regex>
49#include <string>
50
51// These defines allow the use of an define as a string
52#define xstr(a) str(a)
53#define str(a) #a
54
55using namespace std;
56
57namespace ChimeraTK {
58 ua_uaadapter::ua_uaadapter(const string& configFile) : ua_mapped_class() {
59 this->mappingExceptions = UA_FALSE;
60 this->fileHandler = std::make_shared<xml_file_handler>(configFile);
61 this->readConfig();
62 this->constructServer();
63 this->mapSelfToNamespace();
64 }
65
67 UA_Server_delete(this->mappedServer);
68 for(auto ptr : variables) delete ptr;
69 for(auto ptr : additionalVariables) delete ptr;
70 for(auto ptr : mappedVariables) delete ptr;
71 for(auto dir : folderVector) {
72 UA_NodeId_clear(&dir.folderNodeId);
73 }
74 UA_NodeId_clear(&this->ownNodeId);
75 UA_NodeId_clear(&this->configNodeId);
76 }
77
78 static string cleanUri(string s) {
79 // replace multiple with a single space
80 std::regex r1("\\s+");
81 s = std::regex_replace(s, r1, " ");
82 s = std::regex_replace(s, r1, "_");
83 // remove all not printable basic ASCII character
84 std::regex r2("[^ -~]+");
85 s = std::regex_replace(s, r2, "");
86 return s;
87 }
88
89 static UA_ByteString loadFile(const char* const path) {
90 UA_ByteString fileContents = UA_STRING_NULL;
91
92 /* Open the file */
93 FILE* fp = fopen(path, "rb");
94 if(!fp) {
95 errno = 0; /* We read errno also from the tcp layer... */
96 return fileContents;
97 }
98
99 /* Get the file length, allocate the data and read */
100 fseek(fp, 0, SEEK_END);
101 fileContents.length = (size_t)ftell(fp);
102 fileContents.data = (UA_Byte*)UA_malloc(fileContents.length * sizeof(UA_Byte));
103 if(fileContents.data) {
104 fseek(fp, 0, SEEK_SET);
105 size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
106 if(read != fileContents.length) UA_ByteString_clear(&fileContents);
107 }
108 else {
109 fileContents.length = 0;
110 }
111 fclose(fp);
112
113 return fileContents;
114 }
115
116 void ua_uaadapter::fillBuildInfo(UA_ServerConfig* config) const {
117 /*get hostname */
118 char hostname[HOST_NAME_MAX];
119 gethostname(hostname, HOST_NAME_MAX);
120 string hostname_uri = "urn:";
121 hostname_uri.append(hostname);
122 hostname_uri.append(":ChimeraTK:" + this->serverConfig.applicationName);
123
124 config->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;
125 UA_String* hostName = UA_String_new();
126 *hostName = UA_STRING_ALLOC(hostname);
127 config->applicationDescription.discoveryUrls = hostName;
128 config->applicationDescription.discoveryUrlsSize = 1;
129 UA_String_clear(&config->applicationDescription.applicationUri);
130 config->applicationDescription.applicationUri = UA_STRING_ALLOC(const_cast<char*>(cleanUri(hostname_uri).c_str()));
131 UA_String_clear(&config->buildInfo.manufacturerName);
132 config->buildInfo.manufacturerName = UA_STRING_ALLOC(const_cast<char*>("ChimeraTK Team"));
133 std::string
134 versionString =
135 "open62541: " xstr(UA_OPEN62541_VER_MAJOR) "." xstr(UA_OPEN62541_VER_MINOR) "." xstr(UA_OPEN62541_VER_PATCH) ", ControlSystemAdapter-OPC-UA-Adapter: " xstr(
136 PROJECT_VER_MAJOR) "." xstr(PROJECT_VER_MINOR) "." xstr(PTOJECT_VER_PATCH) "";
137 UA_String_clear(&config->buildInfo.softwareVersion);
138 config->buildInfo.softwareVersion = UA_STRING_ALLOC(const_cast<char*>(versionString.c_str()));
139 config->buildInfo.buildDate = UA_DateTime_now();
140 UA_String_clear(&config->buildInfo.buildNumber);
141 config->buildInfo.buildNumber = UA_STRING_ALLOC(const_cast<char*>(""));
142
143 UA_String_clear(&config->buildInfo.productName);
144 config->buildInfo.productName = UA_STRING_ALLOC(const_cast<char*>(this->serverConfig.applicationName.c_str()));
145 string product_urn = "urn:ChimeraTK:" + this->serverConfig.applicationName;
146 UA_String_clear(&config->buildInfo.productUri);
147 config->buildInfo.productUri = UA_STRING_ALLOC(const_cast<char*>(cleanUri(product_urn).c_str()));
148 UA_LocalizedText_clear(&config->applicationDescription.applicationName);
149 config->applicationDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC(
150 const_cast<char*>("en_US"), const_cast<char*>(this->serverConfig.applicationName.c_str()));
151 }
152
153 void ua_uaadapter::constructServer() {
154 auto config = (UA_ServerConfig*)UA_calloc(1, sizeof(UA_ServerConfig));
155 config->logging = &logger;
156 if(!this->serverConfig.enableSecurity) {
157 UA_ServerConfig_setMinimal(config, this->serverConfig.opcuaPort, nullptr);
158 }
159 config->eventLoop->logger = &logger;
160 if(this->serverConfig.enableSecurity) {
161 UA_ByteString certificate = UA_BYTESTRING_NULL;
162 UA_ByteString privateKey = UA_BYTESTRING_NULL;
163 size_t trustListSize = 0;
164 UA_ByteString* trustList = nullptr;
165 size_t issuerListSize = 0;
166 UA_ByteString* issuerList = nullptr;
167 size_t blockListSize = 0;
168 UA_ByteString* blockList = nullptr;
169
170 /* Load certificate and private key */
171 certificate = loadFile(this->serverConfig.certPath.c_str());
172 privateKey = loadFile(this->serverConfig.keyPath.c_str());
173 if(UA_ByteString_equal(&certificate, &UA_BYTESTRING_NULL) ||
174 UA_ByteString_equal(&privateKey, &UA_BYTESTRING_NULL)) {
175 UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
176 "Invalid security configuration. Can't load private key or certificate.");
177 }
178
179 /* Load trust list */
180 struct dirent* entry = nullptr;
181 DIR* dp = nullptr;
182 dp = opendir(this->serverConfig.allowListFolder.c_str());
183 if(dp != nullptr) {
184 while((entry = readdir(dp))) {
185 if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
186 trustListSize++;
187 trustList = (UA_ByteString*)UA_realloc(trustList, sizeof(UA_ByteString) * trustListSize);
188 char sbuf[1024];
189 sprintf(sbuf, "%s/%s", this->serverConfig.allowListFolder.c_str(), entry->d_name);
190 printf("Trust List entry: %s\n", entry->d_name);
191 trustList[trustListSize - 1] = loadFile(sbuf);
192 }
193 }
194 closedir(dp);
195
196 /* Load Issuer list */
197 dp = nullptr;
198 dp = opendir(this->serverConfig.issuerListFolder.c_str());
199 if(dp != nullptr) {
200 while((entry = readdir(dp))) {
201 if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
202 issuerListSize++;
203 issuerList = (UA_ByteString*)UA_realloc(issuerList, sizeof(UA_ByteString) * issuerListSize);
204 char sbuf[1024];
205 sprintf(sbuf, "%s/%s", this->serverConfig.issuerListFolder.c_str(), entry->d_name);
206 printf("Issuer List entry: %s\n", entry->d_name);
207 issuerList[issuerListSize - 1] = loadFile(sbuf);
208 }
209 }
210 closedir(dp);
211
212 /* Load Block list */
213 dp = nullptr;
214 dp = opendir(this->serverConfig.blockListFolder.c_str());
215 if(dp != nullptr) {
216 while((entry = readdir(dp))) {
217 if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
218 blockListSize++;
219 blockList = (UA_ByteString*)UA_realloc(blockList, sizeof(UA_ByteString) * blockListSize);
220 char sbuf[1024];
221 sprintf(sbuf, "%s/%s", this->serverConfig.blockListFolder.c_str(), entry->d_name);
222 printf("Block List entry: %s\n", entry->d_name);
223 blockList[blockListSize - 1] = loadFile(sbuf);
224 }
225 }
226 closedir(dp);
227
228 // setup encrypted endpoints
229 UA_StatusCode retval = UA_ServerConfig_setDefaultWithSecurityPolicies(config, this->serverConfig.opcuaPort,
230 &certificate, &privateKey, trustList, trustListSize, issuerList, issuerListSize, blockList, blockListSize);
231
232 if(retval != UA_STATUSCODE_GOOD) {
233 throw std::runtime_error("Failed setting up server endpoints.");
234 }
235
236 if(!this->serverConfig.unsecure) {
237 for(size_t i = 0; i < config->endpointsSize; i++) {
238 UA_EndpointDescription* ep = &config->endpoints[i];
239 if(ep->securityMode != UA_MESSAGESECURITYMODE_NONE) continue;
240
241 UA_EndpointDescription_clear(ep);
242 // Move the last to this position
243 if(i + 1 < config->endpointsSize) {
244 config->endpoints[i] = config->endpoints[config->endpointsSize - 1];
245 i--;
246 }
247 config->endpointsSize--;
248 }
249 // Delete the entire array if the last Endpoint was removed
250 if(config->endpointsSize == 0) {
251 UA_free(config->endpoints);
252 config->endpoints = nullptr;
253 }
254 }
255
256 UA_ByteString_clear(&certificate);
257 UA_ByteString_clear(&privateKey);
258 for(size_t i = 0; i < trustListSize; i++) UA_ByteString_clear(&trustList[i]);
259 }
260 fillBuildInfo(config);
261 for(size_t i = 0; i < config->endpointsSize; ++i) {
262 UA_ApplicationDescription_clear(&config->endpoints[i].server);
263 UA_ApplicationDescription_copy(&config->applicationDescription, &config->endpoints[i].server);
264 }
265
266 this->mappedServer = UA_Server_newWithConfig(config);
267 this->server_config = UA_Server_getConfig(this->mappedServer);
268
269 customType[0] = LoggingLevelType;
270 this->customDataTypes.reset(new UA_DataTypeArray{this->server_config->customDataTypes, 1, customType, UA_FALSE});
271 this->server_config->customDataTypes = customDataTypes.get();
272
273 // Username/Password handling
274 auto* usernamePasswordLogins = new UA_UsernamePasswordLogin;
275 usernamePasswordLogins->password = UA_STRING_ALLOC(const_cast<char*>(this->serverConfig.password.c_str()));
276 usernamePasswordLogins->username = UA_STRING_ALLOC(const_cast<char*>(this->serverConfig.username.c_str()));
277 UA_AccessControl_default(this->server_config, !this->serverConfig.UsernamePasswordLogin,
278 &this->server_config->securityPolicies[this->server_config->securityPoliciesSize - 1].policyUri, 1,
279 usernamePasswordLogins);
280
281 this->baseNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
283 UA_free(config);
284 UA_String_clear(&usernamePasswordLogins->password);
285 UA_String_clear(&usernamePasswordLogins->username);
286 delete usernamePasswordLogins;
287 }
288
290 string xpath = "//config";
291 xmlXPathObjectPtr result = this->fileHandler->getNodeSet(xpath);
292 string placeHolder;
293 if(result) {
294 xmlNodeSetPtr nodeset = result->nodesetval;
295 if(nodeset->nodeNr > 1) {
296 throw std::runtime_error("To many <config>-Tags in config file");
297 }
298
299 /*
300 * result = this->fileHandler->getNodeSet(xpath + "//process_variable_hierarchy");
301 if(result) {
302 xmlNodeSetPtr nodeset = result->nodesetval;
303 vector<xmlNodePtr> nodeVectorUnrollPathPV =
304 xml_file_handler::getNodesByName(nodeset->nodeTab[0]->children, "unroll");
305 for(auto nodeUnrollPath : nodeVectorUnrollPathPV) {
306 string unrollSepEnabled = xml_file_handler::getContentFromNode(nodeUnrollPath);
307 transform(unrollSepEnabled.begin(), unrollSepEnabled.end(), unrollSepEnabled.begin(), ::toupper);
308 if(unrollSepEnabled == "TRUE") {
309 this->pvSeperator += xml_file_handler::getAttributeValueFromNode(nodeUnrollPath, "pathSep");
310 }
311 }
312 */
313 vector<xmlNodePtr> mappingExceptionsVector =
314 xml_file_handler::getNodesByName(nodeset->nodeTab[0]->children, "mapping_exceptions");
315 if(mappingExceptionsVector.empty()) {
316 this->mappingExceptions = UA_FALSE;
317 }
318 else {
319 placeHolder = xml_file_handler::getContentFromNode(mappingExceptionsVector[0]);
320 transform(placeHolder.begin(), placeHolder.end(), placeHolder.begin(), ::toupper);
321 if(placeHolder == "TRUE") {
322 this->mappingExceptions = UA_TRUE;
323 }
324 else {
325 this->mappingExceptions = UA_FALSE;
326 }
327 }
328
329 xmlXPathObjectPtr sub_result = this->fileHandler->getNodeSet(xpath + "//server");
330 if(result) {
331 xmlNodeSetPtr nodeset = sub_result->nodesetval;
332 if(nodeset->nodeNr > 1) {
333 throw std::runtime_error("To many <server>-Tags in config file");
334 }
335
336 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "logLevel");
337 if(!placeHolder.empty()) {
338 transform(placeHolder.begin(), placeHolder.end(), placeHolder.begin(), ::toupper);
339 if(placeHolder.compare("TRACE") == 0) {
340 this->serverConfig.logLevel = UA_LOGLEVEL_TRACE;
341 }
342 else if(placeHolder.compare("DEBUG") == 0) {
343 this->serverConfig.logLevel = UA_LOGLEVEL_DEBUG;
344 }
345 else if(placeHolder.compare("INFO") == 0) {
346 this->serverConfig.logLevel = UA_LOGLEVEL_INFO;
347 }
348 else if(placeHolder.compare("WARNING") == 0) {
349 this->serverConfig.logLevel = UA_LOGLEVEL_WARNING;
350 }
351 else if(placeHolder.compare("ERROR") == 0) {
352 this->serverConfig.logLevel = UA_LOGLEVEL_ERROR;
353 }
354 else if(placeHolder.compare("FATAL") == 0) {
355 this->serverConfig.logLevel = UA_LOGLEVEL_FATAL;
356 }
357 else {
358 // See default set in ServerConfig
359 // Use UA_Log_Stdout because the logger does not yet exist, but log level INFO is used so
360 // writing a warning is ok here.
361 UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
362 "Unknown logLevel (\"%s\") found in config file. INFO is used instead!", placeHolder.c_str());
363 }
364 }
365 else {
366 // Use UA_Log_Stdout because the logger does not yet exist, but log level INFO is used so
367 // writing a warning is ok here.
368 UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
369 "No 'logLevel'-Attribute set in config file. Use default logging level: info");
370 }
371 logger = UA_Log_Stdout_withLevel(this->serverConfig.logLevel);
372
373 string opcuaPort = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "port");
374 if(!opcuaPort.empty()) {
375 this->serverConfig.opcuaPort = std::stoi(opcuaPort);
376 }
377 else {
378 UA_LOG_WARNING(
379 &logger, UA_LOGCATEGORY_USERLAND, "No 'port'-Attribute in config file is set. Use default Port: 16664");
380 }
381
382 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "applicationName");
383 if(!placeHolder.empty()) {
384 this->serverConfig.applicationName = placeHolder;
385 if(this->serverConfig.rootFolder.empty()) {
386 this->serverConfig.rootFolder = placeHolder;
387 }
388 }
389 else {
390 string applicationName;
391 try {
392 string applicationName = ApplicationBase::getInstance().getName();
393 this->serverConfig.applicationName = applicationName;
394 }
395 catch(ChimeraTK::logic_error) {
396 }
397 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
398 "No 'applicationName'-Attribute is set in config file. Use default application-name.");
399 }
400
401 // if no root folder name is set, use application name
402 if(this->serverConfig.rootFolder.empty()) {
403 this->serverConfig.rootFolder = this->serverConfig.applicationName;
404 }
405 xmlXPathFreeObject(sub_result);
406 }
407 else {
408 logger = UA_Log_Stdout_withLevel(this->serverConfig.logLevel);
409 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
410 "No <server>-Tag in config file. Use default port 16664 and application name configuration.");
411 this->serverConfig.rootFolder = this->serverConfig.applicationName;
412 }
413
414 // check if historizing is configured
415 vector<xmlNodePtr> historizing = xml_file_handler::getNodesByName(nodeset->nodeTab[0]->children, "historizing");
416 if(!historizing.empty()) {
417 xmlXPathObjectPtr sub_sub_result = this->fileHandler->getNodeSet("//historizing");
418 if(sub_sub_result) {
419 xmlNodeSetPtr node_set = sub_sub_result->nodesetval;
420 for(int32_t i = 0; i < node_set->nodeNr; i++) {
421 vector<xmlNodePtr> nodeVectorhistorizingSetUp =
422 xml_file_handler::getNodesByName(node_set->nodeTab[i]->children, "setup");
423 string val;
424 for(auto& nodeHistorizingPath : nodeVectorhistorizingSetUp) {
425 string history_name = xml_file_handler::getAttributeValueFromNode(nodeHistorizingPath, "name");
427 char* c;
428 bool incomplete = false;
429 if(!history_name.empty()) {
430 temp.name = history_name;
431 }
432 else {
433 raiseError("Missing history parameter 'name'.",
434 "History configuration is not added to the list of available history configurations",
435 nodeHistorizingPath->line);
436 incomplete = true;
437 }
438 string history_buffer_length =
439 xml_file_handler::getAttributeValueFromNode(nodeHistorizingPath, "buffer_length");
440 if(!history_buffer_length.empty()) {
441 sscanf(history_buffer_length.c_str(), "%zu", &temp.buffer_length);
442 }
443 else {
444 raiseError("Missing history parameter 'buffer_length'.",
445 "History configuration " + history_name +
446 " is not added to the list of available history configurations",
447 nodeHistorizingPath->line);
448 incomplete = true;
449 }
450 string history_entries_per_response =
451 xml_file_handler::getAttributeValueFromNode(nodeHistorizingPath, "entries_per_response");
452 if(!history_entries_per_response.empty()) {
453 sscanf(history_entries_per_response.c_str(), "%zu", &temp.entries_per_response);
454 }
455 else {
456 raiseError("Missing history parameter 'entries_per_response'.",
457 "History configuration " + history_name +
458 " is not added to the list of available history configurations",
459 nodeHistorizingPath->line);
460 incomplete = true;
461 }
462 string history_interval = xml_file_handler::getAttributeValueFromNode(nodeHistorizingPath, "interval");
463 if(!history_interval.empty()) {
464 sscanf(history_interval.c_str(), "%zu", &temp.interval);
465 }
466 else {
467 raiseError("Missing history parameter 'interval'.",
468 "History configuration " + history_name +
469 " is not added to the list of available history configurations",
470 nodeHistorizingPath->line);
471 incomplete = true;
472 }
473
474 // check if a configuration is redefined
475 bool existing = false;
476 for(size_t j = 0; j < this->serverConfig.history.size(); j++) {
477 if(history_name == this->serverConfig.history[j].name) {
478 existing = true;
479 }
480 }
481 if(!existing) {
482 if(!incomplete) {
483 this->serverConfig.history.insert(this->serverConfig.history.end(), temp);
484 }
485 }
486 else {
487 raiseError("Redefinition of history configuration.",
488 "History configuration " + history_name +
489 " is not added to the list of available history configurations");
490 }
491 }
492 }
493 }
494 }
495
496 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "rootFolder");
497 if(!placeHolder.empty()) {
498 this->serverConfig.rootFolder = placeHolder;
499 }
500
501 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "description");
502 if(!placeHolder.empty()) {
503 this->serverConfig.descriptionFolder = placeHolder;
504 }
505 xmlXPathFreeObject(result);
506 }
507 else {
508 logger = UA_Log_Stdout_withLevel(this->serverConfig.logLevel);
509 }
510 result = this->fileHandler->getNodeSet(xpath + "//login");
511 if(result) {
512 xmlNodeSetPtr nodeset = result->nodesetval;
513 if(nodeset->nodeNr > 1) {
514 throw std::runtime_error("To many <login>-Tags in config file");
515 }
516 this->serverConfig.UsernamePasswordLogin = UA_TRUE;
517 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "password");
518 if(!placeHolder.empty()) {
519 this->serverConfig.password = placeHolder;
520 }
521 else {
522 throw std::runtime_error("<login>-Tag requires username");
523 }
524
525 placeHolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "username");
526 if(!placeHolder.empty()) {
527 this->serverConfig.username = placeHolder;
528 }
529 else {
530 throw std::runtime_error("<login>-Tag requires password");
531 }
532
533 xmlXPathFreeObject(result);
534 }
535 else {
536 this->serverConfig.UsernamePasswordLogin = UA_FALSE;
537 }
538
539 result = this->fileHandler->getNodeSet(xpath + "//security");
540 if(result) {
541 xmlNodeSetPtr nodeset = result->nodesetval;
542 if(nodeset->nodeNr > 1) {
543 throw std::runtime_error("To many <security>-Tags in config file");
544 }
545 this->serverConfig.enableSecurity = true;
546 string unsecure = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "unsecure");
547 if(!unsecure.empty()) {
548 transform(unsecure.begin(), unsecure.end(), unsecure.begin(), ::toupper);
549 if(unsecure.compare("TRUE") == 0) {
550 this->serverConfig.unsecure = true;
551 }
552 else {
553 this->serverConfig.unsecure = false;
554 }
555 }
556 else {
557 this->serverConfig.unsecure = false;
558 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
559 "No 'unsecure'-Attribute in config file is set. Disable unsecure endpoints");
560 }
561 string certPath = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "certificate");
562 if(!certPath.empty()) {
563 this->serverConfig.certPath = certPath;
564 }
565 else {
566 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
567 "Invalid security configuration. No 'certificate'-Attribute in config file is set.");
568 }
569 string keyPath = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "privatekey");
570 if(!keyPath.empty()) {
571 this->serverConfig.keyPath = keyPath;
572 }
573 else {
574 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
575 "Invalid security configuration. No 'privatekey'-Attribute in config file is set.");
576 }
577 string allowListFolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "trustlist");
578 if(!allowListFolder.empty()) {
579 this->serverConfig.allowListFolder = allowListFolder;
580 }
581 else {
582 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
583 "Invalid security configuration. No 'trustlist'-Attribute in config file is set.");
584 }
585 string blockListFolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "blocklist");
586 if(!blockListFolder.empty()) {
587 this->serverConfig.blockListFolder = blockListFolder;
588 }
589 else {
590 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND, "No 'blockListFolder'-Attribute in config file is set.");
591 }
592 string issuerListFolder = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[0], "issuerlist");
593 if(!issuerListFolder.empty()) {
594 this->serverConfig.issuerListFolder = issuerListFolder;
595 }
596 else {
597 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND, "No 'issuerListFolder'-Attribute in config file is set.");
598 }
599 xmlXPathFreeObject(result);
600 }
601 else {
602 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
603 "No <security>-Tag in config file. Use default configuration with unsecure endpoint.");
604 this->serverConfig.enableSecurity = false;
605 }
606 result = this->fileHandler->getNodeSet(xpath + "//process_variable_hierarchy");
607 if(result) {
608 xmlNodeSetPtr nodeset = result->nodesetval;
609 vector<xmlNodePtr> nodeVectorUnrollPathPV =
610 xml_file_handler::getNodesByName(nodeset->nodeTab[0]->children, "unroll");
611 for(auto nodeUnrollPath : nodeVectorUnrollPathPV) {
612 string unrollSepEnabled = xml_file_handler::getContentFromNode(nodeUnrollPath);
613 transform(unrollSepEnabled.begin(), unrollSepEnabled.end(), unrollSepEnabled.begin(), ::toupper);
614 if(unrollSepEnabled == "TRUE") {
615 this->pvSeperator += xml_file_handler::getAttributeValueFromNode(nodeUnrollPath, "pathSep");
616 }
617 }
618 xmlXPathFreeObject(result);
619 }
620 else {
621 UA_LOG_WARNING(&logger, UA_LOGCATEGORY_USERLAND,
622 "No <process_variable_hierarchy>-Tag in config file. Use default hierarchical mapping with '/'.");
623 this->pvSeperator = "/";
624 }
625
626 xmlXPathObjectPtr result_exclude = this->fileHandler->getNodeSet("//exclude");
627 xmlNodeSetPtr nodeset;
628 if(result_exclude) {
629 nodeset = result_exclude->nodesetval;
630 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
631 string exclude_string = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "sourceName");
632 if(!exclude_string.empty()) {
633 this->exclude.insert(this->exclude.begin(), "/" + exclude_string);
634 }
635 }
636 }
637 xmlXPathFreeObject(result_exclude);
638
639 xmlXPathObjectPtr folder_with_his = this->fileHandler->getNodeSet("//folder");
640 xmlNodeSetPtr nodeset_folder_with_history;
641 if(folder_with_his) {
642 nodeset_folder_with_history = folder_with_his->nodesetval;
643 for(int32_t i = 0; i < nodeset_folder_with_history->nodeNr; i++) {
644 string folder =
645 xml_file_handler::getAttributeValueFromNode(nodeset_folder_with_history->nodeTab[i], "sourceName");
646 string history =
647 xml_file_handler::getAttributeValueFromNode(nodeset_folder_with_history->nodeTab[i], "history");
648 if(!folder.empty() && !history.empty()) {
649 this->folder_with_history.insert(this->folder_with_history.begin(), "/" + folder);
650 }
651 }
652 }
653 xmlXPathFreeObject(folder_with_his);
654 }
655
657 return this->serverConfig;
658 }
659
660 void ua_uaadapter::applyMapping(const boost::shared_ptr<ControlSystemPVManager>& csManager) {
661 // build folder structure
662 this->buildFolderStructure(csManager);
663 // start explicit mapping
664 this->explicitVarMapping(csManager);
665 // add configured additional variables
667 }
668
670 if(this->mappedServer == nullptr) {
671 UA_LOG_DEBUG(server_config->logging, UA_LOGCATEGORY_USERLAND, "No server mapped");
672 return;
673 }
674
675 /*for(auto & variable : this->variables){
676 check_void_type(variable);
677 }*/
678
679 vector<UA_NodeId> historizing_nodes;
680 vector<string> historizing_setup;
681 UA_HistoryDataGathering gathering =
682 add_historizing_nodes(historizing_nodes, historizing_setup, this->mappedServer, this->server_config,
683 this->serverConfig.history, this->serverConfig.historyfolders, this->serverConfig.historyvariables);
684 UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND, "Starting the server worker thread");
685 UA_Server_run_startup(this->mappedServer);
686 this->running = true;
687 while(this->running) {
688 UA_Server_run_iterate(this->mappedServer, true);
689 }
690 clear_history(gathering, historizing_nodes, historizing_setup, this->mappedServer,
691 this->serverConfig.historyfolders, this->serverConfig.historyvariables, this->server_config);
692
693 UA_Server_run_shutdown(this->mappedServer);
694 UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND, "Stopped the server worker thread");
695 }
696
697 UA_NodeId ua_uaadapter::enrollFolderPathFromString(const string& path, const string& seperator) {
698 vector<string> varPathVector;
699 if(!seperator.empty()) {
700 vector<string> newPathVector = xml_file_handler::parseVariablePath(path, seperator);
701 varPathVector.insert(varPathVector.end(), newPathVector.begin(), newPathVector.end());
702 }
703 if(!varPathVector.empty()) { // last element is the variable name itself
704 varPathVector.pop_back();
705 return this->createFolderPath(this->ownNodeId, varPathVector);
706 }
707 return UA_NODEID_NULL;
708 }
709
711 const std::string& varName, const boost::shared_ptr<ControlSystemPVManager>& csManager) {
712 UA_NodeId folderPathNodeId = enrollFolderPathFromString(varName, this->pvSeperator);
713 ua_processvariable* processvariable;
714 if(!UA_NodeId_isNull(&folderPathNodeId)) {
715 processvariable =
716 new ua_processvariable(this->mappedServer, folderPathNodeId, varName.substr(1, varName.size() - 1), csManager,
717 server_config->logging, xml_file_handler::parseVariablePath(varName, this->pvSeperator).back());
718 }
719 else {
720 processvariable = new ua_processvariable(this->mappedServer, this->ownNodeId,
721 varName.substr(1, varName.size() - 1), csManager, server_config->logging);
722 }
723 this->variables.push_back(processvariable);
724 UA_NodeId tmpNodeId = processvariable->getOwnNodeId();
725 UA_Server_writeDisplayName(this->mappedServer, tmpNodeId,
726 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"),
727 const_cast<char*>(xml_file_handler::parseVariablePath(varName, this->pvSeperator).back().c_str())));
728 UA_NodeId_clear(&tmpNodeId);
729 }
730
732 const boost::shared_ptr<ControlSystemPVManager>& csManager, UA_NodeId layer, UA_NodeId target) {
733 // copy pv's of current layer
734 UA_BrowseDescription bd;
735 bd.includeSubtypes = false;
736 bd.nodeId = layer;
737 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
738 bd.resultMask = UA_BROWSERESULTMASK_NONE;
739 bd.nodeClassMask = UA_NODECLASS_VARIABLE;
740 bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
741 UA_BrowseResult br = UA_Server_browse(this->mappedServer, UA_UINT32_MAX, &bd);
742 for(size_t j = 0; j < br.referencesSize; ++j) {
743 UA_ReferenceDescription rd = br.references[j];
744 // get unit, desc, ..
745 UA_LocalizedText foundPVName;
746 string foundPVNameCPP;
747 UA_Server_readDisplayName(this->mappedServer, rd.nodeId.nodeId, &foundPVName);
748 UASTRING_TO_CPPSTRING(foundPVName.text, foundPVNameCPP)
749
750 string pvSourceNameid;
751 UA_String foundPVSourceName;
752 string foundPVSourceNameCPP;
753 UA_Variant value;
754 UA_STRING_TO_CPPSTRING_COPY(&rd.nodeId.nodeId.identifier.string, &pvSourceNameid)
755 UA_Server_readValue(
756 this->mappedServer, UA_NODEID_STRING(1, const_cast<char*>((pvSourceNameid + "/Name").c_str())), &value);
757 foundPVSourceName = *((UA_String*)value.data);
758 UASTRING_TO_CPPSTRING(foundPVSourceName, foundPVSourceNameCPP)
759 string varName = xml_file_handler::parseVariablePath(foundPVNameCPP, "/").back();
760 auto* processvariable = new ua_processvariable(
761 this->mappedServer, target, foundPVSourceNameCPP, csManager, server_config->logging, foundPVNameCPP);
762 this->variables.push_back(processvariable);
763 UA_Variant_clear(&value);
764 UA_LocalizedText_clear(&foundPVName);
765 }
766 UA_BrowseResult_clear(&br);
767 // copy folders of current layer
768 bd.includeSubtypes = false;
769 bd.nodeId = layer;
770 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
771 bd.resultMask = UA_BROWSERESULTMASK_NONE;
772 bd.nodeClassMask = UA_NODECLASS_OBJECT;
773 bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
774 br = UA_Server_browse(this->mappedServer, UA_UINT32_MAX, &bd);
775 for(size_t j = 0; j < br.referencesSize; ++j) {
776 UA_ReferenceDescription rd = br.references[j];
777 UA_String folderName, description;
778 UA_LocalizedText foundFolderName, foundFolderDescription;
779 UA_LocalizedText_init(&foundFolderDescription);
780 string foundFolderNameCPP;
781 UA_Server_readDescription(this->mappedServer, rd.nodeId.nodeId, &foundFolderDescription);
782 UA_StatusCode result = UA_Server_readDisplayName(this->mappedServer, rd.nodeId.nodeId, &foundFolderName);
783 UASTRING_TO_CPPSTRING(foundFolderName.text, foundFolderNameCPP)
784 if(result != UA_STATUSCODE_GOOD) {
785 // error handling
786 continue;
787 }
788 UA_NodeId newRootFolder = createFolder(target, foundFolderNameCPP);
789 if(foundFolderDescription.text.length > 0)
790 UA_Server_writeDescription(this->mappedServer, newRootFolder, foundFolderDescription);
791 deepCopyHierarchicalLayer(csManager, rd.nodeId.nodeId, newRootFolder);
792 UA_LocalizedText_clear(&foundFolderName);
793 UA_LocalizedText_clear(&foundFolderDescription);
794 }
795 UA_BrowseResult_clear(&br);
796 }
797
798 void ua_uaadapter::buildFolderStructure(const boost::shared_ptr<ControlSystemPVManager>& csManager) {
799 xmlXPathObjectPtr result = this->fileHandler->getNodeSet("//folder");
800 xmlNodeSetPtr nodeset;
801 if(result) {
802 nodeset = result->nodesetval;
803
804 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
805 UA_NodeId folderPathNodeId;
806 string destination, description, folder;
807 vector<xmlNodePtr> nodeFolderPath =
808 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "destination");
809 vector<xmlNodePtr> nodeFolder = xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "name");
810 vector<xmlNodePtr> nodeDescription =
811 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "description");
812 string sourceName = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "sourceName");
813 string copy = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "copy");
814 transform(copy.begin(), copy.end(), copy.begin(), ::toupper);
815 if(!nodeFolderPath.empty()) {
816 destination = xml_file_handler::getContentFromNode(nodeFolderPath[0]);
817 }
818 if(!nodeDescription.empty()) {
819 description = xml_file_handler::getContentFromNode(nodeDescription[0]);
820 }
821 if(!nodeFolder.empty()) {
822 folder = xml_file_handler::getContentFromNode(nodeFolder[0]);
823 }
824 string history = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "history");
825 if(!history.empty()) {
826 // After discussion we only allow history if source name is set.
827 // Else the exclude logic would become more complex!
828 if(sourceName.empty()) {
829 raiseError("Can not add history if no sourceName attribute is set.", "", nodeset->nodeTab[i]->line);
830 }
831 if(!nodeFolder.empty()) {
832 string folderNodeId;
833 if(!nodeFolderPath.empty()) {
834 if(strlen(destination.c_str()) == 0) {
835 folderNodeId = this->serverConfig.rootFolder + "/" + folder + "Dir";
836 }
837 else {
838 folderNodeId = this->serverConfig.rootFolder + "/" + destination + "/" + folder + "Dir";
839 }
840 }
841 else {
842 folderNodeId = this->serverConfig.rootFolder + "/" + folder + "Dir";
843 }
844
846 temp.folder_historizing = history;
847 UA_NodeId id = UA_NODEID_STRING(1, const_cast<char*>(folderNodeId.c_str()));
848 UA_NodeId_copy(&id, &temp.folder_id);
849 this->serverConfig.historyfolders.insert(this->serverConfig.historyfolders.end(), temp);
850 UA_String out = UA_STRING_NULL;
851 UA_print(&temp.folder_id, &UA_TYPES[UA_TYPES_NODEID], &out);
852 UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND,
853 "Adding history for folder from destination and name %.*s ", (int)out.length, out.data);
854 UA_String_clear(&out);
855 }
856 else if(copy.empty() || copy == "FALSE") {
858 temp.folder_historizing = history;
859 string folderNodeId = this->serverConfig.rootFolder + "/" + sourceName + "Dir";
860 UA_NodeId id = UA_NODEID_STRING(1, const_cast<char*>(folderNodeId.c_str()));
861 UA_NodeId_copy(&id, &temp.folder_id);
862 this->serverConfig.historyfolders.insert(this->serverConfig.historyfolders.end(), temp);
863 UA_String out = UA_STRING_NULL;
864 UA_print(&temp.folder_id, &UA_TYPES[UA_TYPES_NODEID], &out);
865 UA_LOG_INFO(server_config->logging, UA_LOGCATEGORY_USERLAND,
866 "Adding history for folder from source name %.*s ", (int)out.length, out.data);
867 UA_String_clear(&out);
868 }
869 else {
870 raiseError(
871 "Can not add history if copy is true and no source name is given.", "", nodeset->nodeTab[i]->line);
872 }
873 }
874
875 if(folder.empty()) {
876 // only if no history is set raise error/warning
877 if(history.empty()) {
878 raiseError("Folder creation failed. Name is missing.", "Skipping Folder.", nodeset->nodeTab[i]->line);
879 }
880 continue;
881 }
882 // check if a pv with the requested node id exists
883 UA_NodeId tmpOutput = UA_NODEID_NULL;
884 string pvNodeString;
885 if(destination.empty()) {
886 pvNodeString += this->serverConfig.rootFolder + "/" + folder + "Dir";
887 }
888 else {
889 pvNodeString += this->serverConfig.rootFolder + "/" + destination + "/" + folder + "Dir";
890 }
891 UA_NodeId pvNode = UA_NODEID_STRING(1, const_cast<char*>(pvNodeString.c_str()));
892 UA_Server_readNodeId(this->mappedServer, pvNode, &tmpOutput);
893 if(!UA_NodeId_isNull(&tmpOutput)) {
894 UA_NodeId_clear(&tmpOutput);
895 // set folder description
896 if(sourceName.empty() && !description.empty()) {
897 UA_Server_writeDescription(this->mappedServer, pvNode,
898 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(description.c_str())));
899 continue;
900 }
901 raiseError("Folder with source mapping failed. Target folder node id exists.",
902 std::string("Skipping folder: ") + sourceName, nodeset->nodeTab[i]->line);
903 continue;
904 }
905 if(!copy.empty() && sourceName.empty()) {
906 raiseError(std::string("Source 'name: ") + folder + "' folder missing.", "Skipping Folder.",
907 nodeset->nodeTab[i]->line);
908 continue;
909 }
910 folderPathNodeId = enrollFolderPathFromString(destination + "/replacePart", "/");
911 if(UA_NodeId_isNull(&folderPathNodeId)) {
912 folderPathNodeId = UA_NODEID_STRING(1, const_cast<char*>((this->serverConfig.rootFolder + "Dir").c_str()));
913 }
914 // check if source name is set -> map complete hierarchical structure to the destination
915 if(!sourceName.empty()) {
916 if((destination.empty() && sourceName == folder) ||
917 (!destination.empty() && sourceName == destination + "/" + folder)) {
918 raiseError(
919 "Folder creation failed. Source and Destination equal.", "Skipping Folder.", nodeset->nodeTab[i]->line);
920 continue;
921 }
922 // check if the src is a folder
923 string sourceFolder = this->serverConfig.rootFolder + "/" + sourceName + "Dir";
924 bool isFolderType = false;
925 UA_NodeId sourceFolderId = UA_NODEID_STRING(1, const_cast<char*>(sourceFolder.c_str()));
926
927 UA_BrowseDescription bd;
928 bd.includeSubtypes = false;
929 bd.nodeId = sourceFolderId;
930 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASTYPEDEFINITION);
931 bd.resultMask = UA_BROWSERESULTMASK_ALL;
932 bd.nodeClassMask = UA_NODECLASS_OBJECTTYPE;
933 bd.browseDirection = UA_BROWSEDIRECTION_BOTH;
934 UA_BrowseResult br = UA_Server_browse(this->mappedServer, 1000, &bd);
935 for(size_t j = 0; j < br.referencesSize; ++j) {
936 UA_ReferenceDescription rd = br.references[j];
937 UA_NodeId folderType = UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE);
938 if(UA_NodeId_equal(&rd.nodeId.nodeId, &folderType)) {
939 isFolderType = true;
940 }
941 }
942 UA_BrowseResult_clear(&br);
943 if(!isFolderType) {
944 raiseError(std::string("Folder creation failed. No corresponding source folder: ") + sourceName,
945 "Skipping Folder.", nodeset->nodeTab[i]->line);
946 continue;
947 }
948 // enroll path destination -> copy / link the complete tree to this place
949 if(copy == "TRUE") {
950 bool sourceAndDestinationEqual = false;
951 if(!destination.empty()) {
952 if(sourceName == destination + "/" + folder) sourceAndDestinationEqual = true;
953 }
954 else {
955 if(sourceName == folder) sourceAndDestinationEqual = true;
956 }
957 if(sourceAndDestinationEqual) {
958 raiseError(std::string("Folder creation failed. Source and destination must be different for '") +
959 sourceName + "' folder.",
960 "Skipping Folder.", nodeset->nodeTab[i]->line);
961 continue;
962 }
963 // folder structure must be copied, pv nodes must be added
964 UA_LocalizedText foundFolderName;
965 UA_NodeId copyRoot = createFolder(folderPathNodeId, folder);
966 if(UA_NodeId_isNull(&copyRoot)) {
967 string existingDestinationFolderString;
968 if(destination.empty()) {
969 existingDestinationFolderString += this->serverConfig.rootFolder + "/" + folder + "Dir";
970 }
971 else {
972 existingDestinationFolderString = this->serverConfig.rootFolder +=
973 "/" + destination + "/" + folder + "Dir";
974 }
975 copyRoot = UA_NODEID_STRING(1, const_cast<char*>(existingDestinationFolderString.c_str()));
976 }
977 deepCopyHierarchicalLayer(csManager, sourceFolderId, copyRoot);
978 if(!description.empty()) {
979 UA_Server_writeDescription(this->mappedServer, copyRoot,
980 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(description.c_str())));
981 }
982 }
983 else {
984 string existingDestinationFolderString;
985 UA_NodeId copyRoot = createFolder(folderPathNodeId, folder);
986 if(UA_NodeId_isNull(&copyRoot)) {
987 if(destination.empty()) {
988 existingDestinationFolderString = this->serverConfig.rootFolder + "/" + folder;
989 }
990 else {
991 existingDestinationFolderString = this->serverConfig.rootFolder + "/" + destination + "/" + folder;
992 }
993 copyRoot = UA_NODEID_STRING(1, const_cast<char*>(existingDestinationFolderString.c_str()));
994 }
995 else {
996 if(!description.empty()) {
997 UA_Server_writeDescription(this->mappedServer, copyRoot,
998 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(description.c_str())));
999 }
1000 }
1001 UA_BrowseDescription bd;
1002 bd.includeSubtypes = false;
1003 bd.nodeId = sourceFolderId;
1004 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
1005 bd.resultMask = UA_BROWSERESULTMASK_ALL;
1006 bd.nodeClassMask = UA_NODECLASS_OBJECT;
1007 bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
1008 UA_BrowseResult br = UA_Server_browse(this->mappedServer, 1000, &bd);
1009 for(size_t j = 0; j < br.referencesSize; ++j) {
1010 UA_ExpandedNodeId enid;
1011 enid.serverIndex = 0;
1012 enid.namespaceUri = UA_STRING_NULL;
1013 enid.nodeId = br.references[j].nodeId.nodeId;
1014 UA_Server_addReference(
1015 this->mappedServer, copyRoot, UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), enid, UA_TRUE);
1016 }
1017 UA_BrowseResult_clear(&br);
1018 bd.nodeId = sourceFolderId;
1019 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
1020 bd.resultMask = UA_BROWSERESULTMASK_ALL;
1021 bd.nodeClassMask = UA_NODECLASS_VARIABLE;
1022 bd.browseDirection = UA_BROWSEDIRECTION_FORWARD;
1023 br = UA_Server_browse(this->mappedServer, 1000, &bd);
1024 for(size_t j = 0; j < br.referencesSize; ++j) {
1025 UA_ExpandedNodeId enid;
1026 enid.serverIndex = 0;
1027 enid.namespaceUri = UA_STRING_NULL;
1028 enid.nodeId = br.references[j].nodeId.nodeId;
1029 UA_Server_addReference(
1030 this->mappedServer, copyRoot, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), enid, UA_TRUE);
1031 }
1032 UA_BrowseResult_clear(&br);
1033 }
1034 continue;
1035 }
1036
1037 UA_NodeId retnode = createFolder(folderPathNodeId, folder);
1038 // set folder description
1039 if(copy.empty() && sourceName.empty() && !description.empty()) {
1040 UA_Server_writeDescription(this->mappedServer, retnode,
1041 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(description.c_str())));
1042 }
1043 }
1044
1045 xmlXPathFreeObject(result);
1046 }
1047 }
1048
1049 void ua_uaadapter::explicitVarMapping(const boost::shared_ptr<ControlSystemPVManager>& csManager) {
1050 xmlXPathObjectPtr result = this->fileHandler->getNodeSet("//process_variable");
1051 xmlNodeSetPtr nodeset;
1052 if(result) {
1053 nodeset = result->nodesetval;
1054 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
1055 string sourceName, copy, destination, name, unit, description, unrollPath, history;
1056 vector<xmlNodePtr> nodeDestination =
1057 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "destination");
1058 vector<xmlNodePtr> nodeName = xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "name");
1059
1060 vector<xmlNodePtr> nodeUnit = xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "unit");
1061 vector<xmlNodePtr> nodeDescription =
1062 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "description");
1063
1064 copy = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "copy");
1065 transform(copy.begin(), copy.end(), copy.begin(), ::toupper);
1066
1067 sourceName = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "sourceName");
1068 history = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "history");
1069 if(!history.empty()) {
1071 temp.variable_historizing = history;
1072 string targetNodeId;
1073 // get the name of the variable
1074 if(!nodeName.empty()) {
1075 if(nodeDestination.empty()) {
1076 targetNodeId = this->serverConfig.rootFolder + "/" + xml_file_handler::getContentFromNode(nodeName[0]);
1077 UA_NodeId id = UA_NODEID_STRING(1, const_cast<char*>(targetNodeId.c_str()));
1078 UA_NodeId_copy(&id, &temp.variable_id);
1079 this->serverConfig.historyvariables.insert(this->serverConfig.historyvariables.end(), temp);
1080 }
1081 else {
1082 targetNodeId = this->serverConfig.rootFolder + "/" +
1083 xml_file_handler::getContentFromNode(nodeDestination[0]) + "/" +
1085 UA_NodeId id = UA_NODEID_STRING(1, const_cast<char*>(targetNodeId.c_str()));
1086 UA_NodeId_copy(&id, &temp.variable_id);
1087 this->serverConfig.historyvariables.insert(this->serverConfig.historyvariables.end(), temp);
1088 }
1089 }
1090 if(!sourceName.empty() && (copy.empty() || copy == "FALSE")) {
1091 name = sourceName;
1092 targetNodeId = this->serverConfig.rootFolder + "/" + sourceName;
1093 UA_NodeId id = UA_NODEID_STRING(1, const_cast<char*>(targetNodeId.c_str()));
1094 UA_NodeId_copy(&id, &temp.variable_id);
1095 this->serverConfig.historyvariables.insert(this->serverConfig.historyvariables.end(), temp);
1096 }
1097 }
1098
1099 if(!nodeDestination.empty()) {
1100 destination = xml_file_handler::getContentFromNode(nodeDestination[0]);
1101 unrollPath = xml_file_handler::getAttributeValueFromNode(nodeDestination[0], "unrollPath");
1102 }
1103 if(!nodeName.empty()) {
1104 name = xml_file_handler::getContentFromNode(nodeName[0]);
1105 }
1106 if(!nodeUnit.empty()) {
1107 unit = xml_file_handler::getContentFromNode(nodeUnit[0]);
1108 }
1109 if(!nodeDescription.empty()) {
1110 description = xml_file_handler::getContentFromNode(nodeDescription[0]);
1111 }
1112
1113 if(sourceName.empty()) {
1114 if(!name.empty()) {
1115 raiseError(std::string("PV mapping failed. SourceName missing for 'name' : '") + name,
1116 "Skipping PV mapping.", nodeset->nodeTab[i]->line);
1117 }
1118 else {
1119 raiseError("PV mapping failed. SourceName is missing.", "Skipping PV mapping.", nodeset->nodeTab[i]->line);
1120 }
1121 continue;
1122 }
1123 // check if the pv still exists -> update requested
1124 if(((destination + "/" + name) == sourceName) && copy == "FALSE") {
1125 // check if the source var exists
1126 string parentSourceFolder = this->serverConfig.rootFolder + "/" +
1127 (sourceName.substr(
1128 0, sourceName.length() - xml_file_handler::parseVariablePath(sourceName, "/").back().length() - 1));
1129 UA_NodeId parentSourceFolderId = UA_NODEID_STRING(1, const_cast<char*>(parentSourceFolder.c_str()));
1130 UA_NodeId pvNodeId = UA_NODEID_NULL;
1131 UA_BrowseDescription bd;
1132 bd.includeSubtypes = false;
1133 bd.nodeId = parentSourceFolderId;
1134 bd.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
1135 bd.resultMask = UA_BROWSERESULTMASK_ALL;
1136 bd.nodeClassMask = UA_NODECLASS_VARIABLE;
1137 bd.browseDirection = UA_BROWSEDIRECTION_BOTH;
1138 UA_BrowseResult br = UA_Server_browse(this->mappedServer, 20, &bd);
1139 for(size_t j = 0; j < br.referencesSize; ++j) {
1140 UA_ReferenceDescription rd = br.references[j];
1141 string name;
1142 UASTRING_TO_CPPSTRING(rd.displayName.text, name)
1143 if(name == xml_file_handler::parseVariablePath(sourceName, "/").back()) {
1144 UA_NodeId_copy(&br.references[j].nodeId.nodeId, &pvNodeId);
1145 }
1146 }
1147 UA_BrowseResult_clear(&br);
1148 if(UA_NodeId_isNull(&pvNodeId)) {
1149 if(!name.empty()) {
1150 raiseError(std::string("PV mapping failed. No corresponding source pv for 'name' : '") + name,
1151 "Skipping PV mapping.", nodeset->nodeTab[i]->line);
1152 }
1153 else {
1154 raiseError(
1155 "PV mapping failed. No corresponding source pv.", "Skipping PV mapping.", nodeset->nodeTab[i]->line);
1156 }
1157 continue;
1158 }
1159 if(!unit.empty()) {
1160 // update unit
1161 for(auto& variable : this->variables) {
1162 UA_NodeId tmpNodeId = variable->getOwnNodeId();
1163 if(UA_NodeId_equal(&tmpNodeId, &pvNodeId)) {
1164 variable->setEngineeringUnit(unit);
1165 }
1166 UA_NodeId_clear(&tmpNodeId);
1167 }
1168 }
1169 if(!description.empty()) {
1170 // update description
1171 for(auto& variable : this->variables) {
1172 UA_NodeId tmpNodeId = variable->getOwnNodeId();
1173 if(UA_NodeId_equal(&tmpNodeId, &pvNodeId)) {
1174 variable->setDescription(description);
1175 }
1176 UA_NodeId_clear(&tmpNodeId);
1177 }
1178 }
1179
1180 if(!UA_NodeId_isNull(&pvNodeId)) {
1181 UA_NodeId_clear(&pvNodeId);
1182 }
1183 continue;
1184 }
1185 // check if the source pv exists
1186 string parentSourceString = this->serverConfig.rootFolder + "/" + sourceName;
1187 UA_NodeId parentSourceId = UA_NODEID_STRING(1, const_cast<char*>(parentSourceString.c_str()));
1188 UA_NodeId tmpOutput = UA_NODEID_NULL;
1189 UA_Server_readNodeId(this->mappedServer, parentSourceId, &tmpOutput);
1190 if(UA_NodeId_isNull(&tmpOutput)) {
1191 if(!name.empty()) {
1192 raiseError(std::string("PV mapping failed. Source PV not found 'name' : '") + name, "Skipping PV mapping.",
1193 nodeset->nodeTab[i]->line);
1194 }
1195 else {
1196 raiseError("PV mapping failed. Source PV not found", "Skipping PV mapping.", nodeset->nodeTab[i]->line);
1197 }
1198 continue;
1199 }
1200 else {
1201 UA_NodeId_clear(&tmpOutput);
1202 UA_NodeId_init(&tmpOutput);
1203 }
1204 // check the pv copy attribute -> copy of pv requested; false -> reference to original pv requested
1205 UA_NodeId createdNodeId = UA_NODEID_NULL;
1206 if(copy == "TRUE") {
1207 if(sourceName == (destination + "/" + name)) {
1208 raiseError("PV mapping failed. Source and destination must be different if copy='true'.",
1209 std::string("Skipping PV ") + sourceName, nodeset->nodeTab[i]->line);
1210 continue;
1211 }
1212 UA_NodeId destinationFolderNodeId = UA_NODEID_NULL;
1213 if(destination.empty()) {
1214 destinationFolderNodeId = this->ownNodeId;
1215 }
1216 else {
1217 if(unrollPath.empty()) {
1218 destinationFolderNodeId = enrollFolderPathFromString(destination + "/removedPart", "/");
1219 }
1220 else {
1221 // legacy code to make old mapping possible
1222 if(destination.find("Variables" + unrollPath) == 0) {
1223 string requestedBrowseName = destination;
1224 requestedBrowseName.erase(0, ("Variables" + unrollPath).length());
1225 // check if the requested path still exists in the Variables folder
1226 string requestedPVBrowseName = this->serverConfig.rootFolder + "/Variables/" + requestedBrowseName;
1227 UA_NodeId requestedPVBrowseId = UA_NODEID_STRING(1, const_cast<char*>(requestedPVBrowseName.c_str()));
1228 UA_NodeId tmpOutput = UA_NODEID_NULL;
1229 UA_Server_readNodeId(this->mappedServer, requestedPVBrowseId, &tmpOutput);
1230 if(!UA_NodeId_isNull(&tmpOutput)) {
1231 UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND,
1232 "Using legacy code. Mapped PV is used as PV mapping target");
1233 UA_NodeId_copy(&requestedPVBrowseId, &destinationFolderNodeId);
1234 }
1235 else {
1236 destinationFolderNodeId =
1237 enrollFolderPathFromString(destination + unrollPath + "removedPart", unrollPath);
1238 }
1239 }
1240 else {
1241 destinationFolderNodeId =
1242 enrollFolderPathFromString(destination + unrollPath + "removedPart", unrollPath);
1243 }
1244 }
1245 }
1246 if(name.empty()) {
1247 name = sourceName;
1248 }
1249 ua_processvariable* processvariable;
1250 if(!UA_NodeId_isNull(&destinationFolderNodeId)) {
1251 processvariable = new ua_processvariable(
1252 this->mappedServer, destinationFolderNodeId, sourceName, csManager, server_config->logging, name);
1253 UA_NodeId tmpProcessVariableNodeId = processvariable->getOwnNodeId();
1254 if(UA_NodeId_isNull(&tmpProcessVariableNodeId)) {
1255 raiseError("PV creation failed. PV with same name mapped.", std::string("Skipping PV ") + sourceName,
1256 nodeset->nodeTab[i]->line);
1257 continue;
1258 }
1259 else {
1260 UA_NodeId_clear(&tmpProcessVariableNodeId);
1261 }
1262 this->variables.push_back(processvariable);
1263 }
1264 else {
1265 raiseError("Folder creation failed.", std::string("Skipping PV ") + sourceName, nodeset->nodeTab[i]->line);
1266 continue;
1267 }
1268 UA_NodeId tmpPVNodeId = processvariable->getOwnNodeId();
1269 UA_NodeId_copy(&tmpPVNodeId, &createdNodeId);
1270 UA_NodeId_clear(&tmpPVNodeId);
1271 }
1272 else {
1273 // get node id of the source node
1274 string sourceVarName = xml_file_handler::parseVariablePath(sourceName, "/").back();
1275 if(name.empty()) {
1276 name = sourceVarName;
1277 }
1278 if(sourceVarName != name) {
1279 if(history.empty()) {
1280 raiseError("PV mapping failed. The pv name can't changed if copy is false.",
1281 std::string("Skipping PV mapping of pv ") + name);
1282 }
1283 continue;
1284 }
1285 // create destination folder
1286 UA_NodeId destinationFolder = enrollFolderPathFromString(destination + "/removedpart", "/");
1287 UA_ExpandedNodeId enid;
1288 enid.serverIndex = 0;
1289 enid.namespaceUri = UA_STRING_NULL;
1290 enid.nodeId = parentSourceId;
1291 // add reference to the source node
1292 UA_StatusCode addRef = UA_Server_addReference(
1293 this->mappedServer, destinationFolder, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), enid, true);
1294 if(sourceVarName != name) {
1295 raiseError("PV mapping failed. Can't create reference to original pv.",
1296 std::string("Skipping PV mapping of pv ") + name);
1297 continue;
1298 }
1299 UA_NodeId_copy(&parentSourceId, &createdNodeId);
1300 }
1301 if(!unit.empty()) {
1302 for(auto& variable : this->variables) {
1303 UA_NodeId tmpNodeId = variable->getOwnNodeId();
1304 if(UA_NodeId_equal(&tmpNodeId, &createdNodeId)) {
1305 variable->setEngineeringUnit(unit);
1306 }
1307 UA_NodeId_clear(&tmpNodeId);
1308 }
1309 }
1310 if(!description.empty()) {
1311 for(auto& variable : this->variables) {
1312 UA_NodeId tmpNodeId = variable->getOwnNodeId();
1313 if(UA_NodeId_equal(&tmpNodeId, &createdNodeId)) {
1314 variable->setDescription(description);
1315 }
1316 UA_NodeId_clear(&tmpNodeId);
1317 }
1318 }
1319 UA_Server_writeDisplayName(this->mappedServer, createdNodeId,
1320 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(name.c_str())));
1321 UA_NodeId_clear(&createdNodeId);
1322 }
1323
1324 xmlXPathFreeObject(result);
1325 }
1326 }
1327
1329 xmlXPathObjectPtr result = this->fileHandler->getNodeSet("//additional_variable");
1330 xmlNodeSetPtr nodeset;
1331 if(result) {
1332 nodeset = result->nodesetval;
1333 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
1334 string destination, name, description, value;
1335 vector<xmlNodePtr> nodeDestination =
1336 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "destination");
1337 vector<xmlNodePtr> nodeName = xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "name");
1338 vector<xmlNodePtr> nodeDescription =
1339 xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "description");
1340 vector<xmlNodePtr> nodeValue = xml_file_handler::getNodesByName(nodeset->nodeTab[i]->children, "value");
1341
1342 if(!nodeDestination.empty()) {
1343 destination = xml_file_handler::getContentFromNode(nodeDestination[0]);
1344 }
1345 if(!nodeName.empty()) {
1346 name = xml_file_handler::getContentFromNode(nodeName[0]);
1347 }
1348 if(!nodeDescription.empty()) {
1349 description = xml_file_handler::getContentFromNode(nodeDescription[0]);
1350 }
1351 if(!nodeValue.empty()) {
1352 value = xml_file_handler::getContentFromNode(nodeValue[0]);
1353 }
1354 // check if name is empty
1355 if(name.empty()) {
1356 raiseError("Additional variable node creation failed. Additional variable name is mandatory.",
1357 "Skipping additional variable.", nodeset->nodeTab[i]->line);
1358 continue;
1359 }
1360 // check if the av node still exists
1361 UA_NodeId tmpOutput = UA_NODEID_NULL;
1362 string avNodeString;
1363 if(destination.empty()) {
1364 avNodeString = this->serverConfig.rootFolder + "/" + name + "AdditionalVariable";
1365 }
1366 else {
1367 avNodeString = this->serverConfig.rootFolder + "/" + destination + "/" + name + "AdditionalVariable";
1368 }
1369 UA_NodeId avNode = UA_NODEID_STRING(1, const_cast<char*>(avNodeString.c_str()));
1370 UA_Server_readNodeId(this->mappedServer, avNode, &tmpOutput);
1371 if(!UA_NodeId_isNull(&tmpOutput)) {
1372 UA_NodeId_clear(&tmpOutput);
1373 UA_NodeId_init(&tmpOutput);
1374 raiseError("Additional variable node creation failed. Additional variable already exists.",
1375 std::string("Skipping additional variable ") + name, nodeset->nodeTab[i]->line);
1376 continue;
1377 }
1378 // check if pv with same name exists in the target folder
1379 UA_NodeId_clear(&tmpOutput);
1380 string pvNodeString;
1381 if(destination.empty()) {
1382 pvNodeString = this->serverConfig.rootFolder + "/" + name + "Value";
1383 }
1384 else {
1385 pvNodeString = this->serverConfig.rootFolder + "/" + destination + "/" + name + "Value";
1386 }
1387 UA_NodeId pvNode = UA_NODEID_STRING(1, const_cast<char*>(pvNodeString.c_str()));
1388 UA_Server_readNodeId(this->mappedServer, pvNode, &tmpOutput);
1389 if(!UA_NodeId_isNull(&tmpOutput)) {
1390 UA_NodeId_clear(&tmpOutput);
1391 UA_NodeId_init(&tmpOutput);
1392 raiseError("Additional variable node creation failed. PV with same name already exists.",
1393 std::string("Skipping additional variable ") + name, nodeset->nodeTab[i]->line);
1394 continue;
1395 }
1396
1397 UA_NodeId additionalVarFolderPath = UA_NODEID_NULL;
1398 string additionalVarFolderPathNodeId;
1399 if(!destination.empty()) {
1400 additionalVarFolderPath = enrollFolderPathFromString(destination + "/removePart", "/");
1401 }
1402 else {
1403 additionalVarFolderPathNodeId += this->serverConfig.rootFolder + "Dir";
1404 additionalVarFolderPath = UA_NODEID_STRING_ALLOC(1, const_cast<char*>(additionalVarFolderPathNodeId.c_str()));
1405 }
1406 if(UA_NodeId_isNull(&additionalVarFolderPath)) {
1407 raiseError("Creation of additional variable folder failed.", "Skipping additional variable.",
1408 nodeset->nodeTab[i]->line);
1409 continue;
1410 }
1411 auto* additionalvariable =
1412 new ua_additionalvariable(this->mappedServer, additionalVarFolderPath, name, value, description);
1413 this->additionalVariables.push_back(additionalvariable);
1414 if(destination.empty()) {
1415 UA_NodeId_clear(&additionalVarFolderPath);
1416 }
1417 }
1418
1419 xmlXPathFreeObject(result);
1420 }
1421 }
1422
1423 vector<ua_processvariable*> ua_uaadapter::getVariables() {
1424 return this->variables;
1425 }
1426
1427 UA_NodeId ua_uaadapter::createUAFolder(
1428 UA_NodeId basenodeid, const std::string& folderName, const std::string& description) {
1429 // FIXME: Check if folder name a possible name or should it be escaped (?!"§%-:, etc)
1430 UA_StatusCode retval = UA_STATUSCODE_GOOD;
1431 UA_NodeId createdNodeId = UA_NODEID_NULL;
1432
1433 if(UA_NodeId_equal(&baseNodeId, &createdNodeId) == UA_TRUE) {
1434 return createdNodeId; // Something went wrong (initializer should set this!)
1435 }
1436
1437 // Create our toplevel instance
1438 UA_ObjectAttributes oAttr;
1439 UA_ObjectAttributes_init(&oAttr);
1440 // Classcast to prevent Warnings
1441 oAttr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(folderName.c_str()));
1442 oAttr.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(description.c_str()));
1443
1444 string parentNodeIdString;
1445 if(basenodeid.identifierType == UA_NODEIDTYPE_STRING) {
1446 UASTRING_TO_CPPSTRING(basenodeid.identifier.string, parentNodeIdString)
1447 if(!parentNodeIdString.empty()) {
1448 parentNodeIdString.resize(parentNodeIdString.size() - 3);
1449 }
1450 parentNodeIdString += '/' + folderName + "Dir";
1451 }
1452 else if(basenodeid.identifierType == UA_NODEIDTYPE_NUMERIC) {
1453 parentNodeIdString += '/' + std::to_string(basenodeid.identifier.numeric) + "Dir";
1454 }
1455
1456 UA_Server_addObjectNode(this->mappedServer,
1457 UA_NODEID_STRING(1, const_cast<char*>(parentNodeIdString.c_str())), // UA_NODEID_NUMERIC(1,0)
1458 basenodeid, UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
1459 UA_QUALIFIEDNAME(1, const_cast<char*>(folderName.c_str())), UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE), oAttr,
1460 &this->ownedNodes, &createdNodeId);
1461
1462 ua_mapInstantiatedNodes(createdNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE), &this->ownedNodes);
1463 return createdNodeId;
1464 }
1465
1466 void ua_uaadapter::raiseError(std::string errorMesssage, std::string consequenceMessage, const int& line) {
1467 std::string lineMessage("");
1468 if(line > 0) {
1469 lineMessage = std::string(" Mapping line number: ") + std::to_string(line) + ".";
1470 }
1471 std::string tmp[2] = {errorMesssage, consequenceMessage};
1472 if(errorMesssage.back() != '.') {
1473 errorMesssage += ".";
1474 }
1475 if(consequenceMessage.back() != '.') {
1476 consequenceMessage += ".";
1477 }
1478 if(this->mappingExceptions) {
1479 throw std::runtime_error(std::string("Error! ") + errorMesssage + lineMessage);
1480 }
1481 if(server_config) {
1482 UA_LOG_WARNING(server_config->logging, UA_LOGCATEGORY_USERLAND, "%s %s", errorMesssage.c_str(),
1483 (consequenceMessage + lineMessage).c_str());
1484 }
1485 else {
1486 UA_LOG_WARNING(
1487 &logger, UA_LOGCATEGORY_USERLAND, "%s %s", errorMesssage.c_str(), (consequenceMessage + lineMessage).c_str());
1488 }
1489 }
1490
1491 UA_StatusCode ua_uaadapter::mapSelfToNamespace() {
1492 UA_StatusCode retval = UA_STATUSCODE_GOOD;
1493 UA_NodeId createdNodeId = UA_NODEID_NULL;
1494
1495 if(UA_NodeId_equal(&this->baseNodeId, &createdNodeId) == UA_TRUE) {
1496 return 0;
1497 }
1498
1499 {
1500 // Create our toplevel instance
1501 UA_ObjectAttributes oAttr;
1502 UA_ObjectAttributes_init(&oAttr);
1503 // Classcast to prevent Warnings
1504 oAttr.displayName =
1505 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(this->serverConfig.rootFolder.c_str()));
1506 oAttr.description =
1507 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(this->serverConfig.descriptionFolder.c_str()));
1508
1509 UA_Server_addObjectNode(this->mappedServer,
1510 UA_NODEID_STRING(1, (const_cast<char*>((this->serverConfig.rootFolder + "Dir").c_str()))),
1511 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
1512 UA_QUALIFIEDNAME(1, const_cast<char*>(this->serverConfig.rootFolder.c_str())),
1513 UA_NODEID_NUMERIC(CSA_NSID, UA_NS2ID_CTKMODULE), oAttr, &ownedNodes, &createdNodeId);
1514
1515 this->ownNodeId = createdNodeId;
1516 ua_mapInstantiatedNodes(this->ownNodeId, UA_NODEID_NUMERIC(CSA_NSID, UA_NS2ID_CTKMODULE), &ownedNodes);
1517 }
1518 {
1519 // Create our toplevel instance for configuration
1520 UA_ObjectAttributes oAttr;
1521 UA_ObjectAttributes_init(&oAttr);
1522 // Classcast to prevent Warnings
1523 oAttr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("ServerConfiguration"));
1524 oAttr.description =
1525 UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Here adapter configurations are placed."));
1526 UA_Server_addObjectNode(this->mappedServer, UA_NODEID_STRING(1, const_cast<char*>("ServerConfigurationDir")),
1527 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
1528 UA_QUALIFIEDNAME(1, const_cast<char*>("ServerConfiguration")),
1529 UA_NODEID_NUMERIC(CSA_NSID, UA_NS2ID_CTKMODULE), oAttr, &ownedNodes, &createdNodeId);
1530 configNodeId = createdNodeId;
1531 ua_mapInstantiatedNodes(configNodeId, UA_NODEID_NUMERIC(CSA_NSID, UA_NS2ID_CTKMODULE), &ownedNodes);
1532
1533 // create log level node
1534 UA_VariableAttributes attr = UA_VariableAttributes_default;
1535 attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en-US"), const_cast<char*>("logLevel"));
1536 attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
1537 attr.valueRank = -1;
1538 attr.dataType = LoggingLevelType.typeId;
1540 auto status = UA_Variant_setScalarCopy(&attr.value, &l, &LoggingLevelType);
1541
1542 UA_NodeId currentNodeId = UA_NODEID_STRING(1, const_cast<char*>("logLevel"));
1543 UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, const_cast<char*>("logLevel"));
1544 UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
1545 UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
1546
1547 UA_DataSource logLevelDataSource;
1548 logLevelDataSource.read = readLogLevel;
1549 logLevelDataSource.write = writeLogLevel;
1550 UA_Server_addDataSourceVariableNode(this->mappedServer, currentNodeId, createdNodeId, parentReferenceNodeId,
1551 currentName, variableTypeNodeId, attr, logLevelDataSource, this, NULL);
1552 UA_Variant_clear(&attr.value);
1553 }
1554
1555 return UA_STATUSCODE_GOOD;
1556 }
1557
1559 return this->ownNodeId;
1560 }
1561
1562 UA_NodeId ua_uaadapter::existFolderPath(UA_NodeId basenodeid, const std::vector<string>& folderPath) {
1563 UA_NodeId lastNodeId = basenodeid;
1564 for(const std::string& t : folderPath) {
1565 lastNodeId = this->existFolder(lastNodeId, t);
1566 if(UA_NodeId_isNull(&lastNodeId)) {
1567 return UA_NODEID_NULL;
1568 }
1569 }
1570 return lastNodeId;
1571 }
1572
1573 UA_NodeId ua_uaadapter::existFolder(UA_NodeId basenodeid, const string& folder) {
1574 UA_NodeId lastNodeId = UA_NODEID_NULL;
1575 for(auto& i : this->folderVector) {
1576 if((i.folderName == folder) && (UA_NodeId_equal(&i.prevFolderNodeId, &basenodeid))) {
1577 return i.folderNodeId;
1578 }
1579 }
1580 return UA_NODEID_NULL;
1581 }
1582
1583 UA_NodeId ua_uaadapter::createFolderPath(UA_NodeId basenodeid, std::vector<string> folderPath) {
1584 if(UA_NodeId_isNull(&basenodeid)) {
1585 return UA_NODEID_NULL;
1586 }
1587 // Check if path exist
1588 UA_NodeId toCheckNodeId = existFolderPath(basenodeid, folderPath);
1589 int32_t starter4Folder = 0;
1590 UA_NodeId nextNodeId = basenodeid;
1591 UA_NodeId startNodeId = basenodeid;
1592 if(UA_NodeId_isNull(&toCheckNodeId)) {
1593 bool setted = false;
1594 // Check if path exist partly
1595 for(uint32_t m = 0; m < folderPath.size(); m++) {
1596 for(auto& i : this->folderVector) {
1597 // get correct folder NodeId from first folderPath element
1598 if(!setted && (folderPath.at(m) == i.folderName) && (UA_NodeId_equal(&i.prevFolderNodeId, &nextNodeId)) &&
1599 ((m + 1) < folderPath.size())) {
1600 // remember on witch position the folder still exist
1601 setted = true;
1602 starter4Folder = m + 1;
1603 nextNodeId = i.folderNodeId;
1604 }
1605 if(setted) {
1606 break;
1607 }
1608 }
1609 if(!setted) break;
1610 setted = false;
1611 }
1612 }
1613 else {
1614 // Path exist nothing to do
1615 return toCheckNodeId;
1616 }
1617
1618 UA_NodeId prevNodeId = nextNodeId;
1619 // use the remembered position to start the loop
1620 for(uint32_t m = starter4Folder; m < folderPath.size(); m++) {
1621 prevNodeId = this->createFolder(prevNodeId, folderPath.at(m));
1622 }
1623 // return last created folder UA_NodeId
1624 return prevNodeId;
1625 }
1626
1627 UA_NodeId ua_uaadapter::createFolder(UA_NodeId basenodeid, const string& folderName, const string& description) {
1628 if(UA_NodeId_isNull(&basenodeid)) {
1629 return UA_NODEID_NULL;
1630 }
1631 // Check if path exist
1632 UA_NodeId toCheckNodeId = existFolder(basenodeid, folderName);
1633 FolderInfo newFolder;
1634 if(UA_NodeId_isNull(&toCheckNodeId)) {
1635 newFolder.folderName = folderName;
1636 newFolder.folderNodeId = this->createUAFolder(basenodeid, folderName, description);
1637 newFolder.prevFolderNodeId = basenodeid;
1638 this->folderVector.push_back(newFolder);
1639 }
1640 return newFolder.folderNodeId;
1641 }
1642
1644 vector<string> mappedPvSources;
1645 xmlXPathObjectPtr result = this->fileHandler->getNodeSet("//process_variable");
1646 if(result) {
1647 xmlNodeSetPtr nodeset = result->nodesetval;
1648 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
1649 mappedPvSources.insert(
1650 mappedPvSources.begin(), xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "sourceName"));
1651 }
1652 }
1653 xmlXPathFreeObject(result);
1654 return mappedPvSources;
1655 }
1656
1658 vector<string> notMappableVariablesNames;
1659 xmlXPathObjectPtr result = this->fileHandler->getNodeSet("//process_variable");
1660
1661 if(result) {
1662 xmlNodeSetPtr nodeset = result->nodesetval;
1663 for(int32_t i = 0; i < nodeset->nodeNr; i++) {
1664 // for(auto var:this->variables) {
1665 bool mapped = false;
1666 string mappedVar = xml_file_handler::getAttributeValueFromNode(nodeset->nodeTab[i], "sourceName");
1667 for(auto var : this->getVariables()) {
1668 if(var->getName() == mappedVar) {
1669 mapped = true;
1670 break;
1671 }
1672 }
1673 if(!mapped) {
1674 for(auto histVar : this->serverConfig.historyvariables) {
1675 string mappedVarHist = serverConfig.rootFolder + "/" + mappedVar;
1676 std::string tmp;
1677 UASTRING_TO_CPPSTRING(histVar.variable_id.identifier.string, tmp);
1678 if(tmp.compare(mappedVarHist) == 0) {
1679 mapped = true;
1680 break;
1681 }
1682 }
1683 }
1684 if(!mapped) {
1685 notMappableVariablesNames.push_back(mappedVar);
1686 }
1687 }
1688
1689 xmlXPathFreeObject(result);
1690 }
1691
1692 return notMappableVariablesNames;
1693 }
1694
1696 return UA_DateTime_now();
1697 }
1698
1700 return this->mappedServer;
1701 }
1702
1703 UA_StatusCode ua_uaadapter::readLogLevel(UA_Server* /*server*/, const UA_NodeId* /*sessionId*/,
1704 void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext, UA_Boolean /*sourceTimeStamp*/,
1705 const UA_NumericRange* /*range*/, UA_DataValue* dataValue) {
1706 auto adapter = (ua_uaadapter*)nodeContext;
1707 UA_LoggingLevel l = toLoggingLevel(adapter->serverConfig.logLevel);
1708 UA_Variant_setScalarCopy(&dataValue->value, &l, &LoggingLevelType);
1709 dataValue->hasValue = true;
1710 return UA_STATUSCODE_GOOD;
1711 }
1712
1713 UA_StatusCode ua_uaadapter::writeLogLevel(UA_Server* /*server*/, const UA_NodeId* /*sessionId*/,
1714 void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext, const UA_NumericRange* /*range*/,
1715 const UA_DataValue* data) {
1716 auto adapter = (ua_uaadapter*)nodeContext;
1717 const UA_Variant* var = &data->value;
1718 adapter->serverConfig.logLevel = toLogLevel(*((UA_LoggingLevel*)var->data));
1719 adapter->logger = UA_Log_Stdout_withLevel(adapter->serverConfig.logLevel);
1720 adapter->server_config->logging = &adapter->logger;
1721 UA_LOG_INFO(adapter->server_config->logging, UA_LOGCATEGORY_USERLAND, "Set log level to %d",
1722 adapter->serverConfig.logLevel);
1723 return UA_STATUSCODE_GOOD;
1724 }
1725
1726} // namespace ChimeraTK
This class represent a additional variable from <variableMap.xml> in the information model of a OPC U...
This class mapped all inforamtion into the opca server.
This class represent a processvariable of the controlsystemadapter in the information model of a OPC ...
UA_NodeId getOwnNodeId()
Get node id of this processvariable instance.
This class provide the opcua server and manage the variable mapping.
Definition ua_adapter.h:101
void fillBuildInfo(UA_ServerConfig *config) const
Fill server build information.
void workerThread()
Create and start a thread for the opcua server instance.
void implicitVarMapping(const std::string &varName, const boost::shared_ptr< ControlSystemPVManager > &csManager)
Start implicit mapping process.
UA_NodeId createFolder(UA_NodeId basenodeid, const string &folderName, const string &description="")
Creates a folder in the given parent node.
static UA_StatusCode writeLogLevel(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data)
callback function used to change the servers logging level.
vector< ua_processvariable * > getVariables()
Methode that returns all <ua_processvariable> of the class.
UA_NodeId existFolder(UA_NodeId basenodeid, const string &folderName)
Check if a folder exist in opcua server.
void buildFolderStructure(const boost::shared_ptr< ControlSystemPVManager > &csManager)
Read mapping file and and add contained folders to the server.
void addAdditionalVariables()
Read mapping file and add contained additional variables to the server.
UA_NodeId getOwnNodeId()
Methode that returns the node id of the instanced class.
UA_DateTime getSourceTimeStamp()
Return the timestamp of the node.
void deepCopyHierarchicalLayer(const boost::shared_ptr< ControlSystemPVManager > &csManager, UA_NodeId layer, UA_NodeId target)
Copy (recursively) the content of a folder to a new location.
UA_Server * getMappedServer()
Return the OPC UA Server instance.
void explicitVarMapping(const boost::shared_ptr< ControlSystemPVManager > &csManager)
Read mapping file and apply contained PV mappings.
UA_NodeId existFolderPath(UA_NodeId basenodeid, const vector< string > &folderPath)
Check if a folder path exist in opcua server.
void readConfig()
This Methode reads the config-tag form the given <variableMap.xml>.
UA_NodeId createFolderPath(UA_NodeId basenodeid, vector< string > folderPathVector)
Create a path of folders in the given parent node.
vector< string > getAllMappedPvSourceNames()
UA_NodeId enrollFolderPathFromString(const string &path, const string &seperator)
Create folder structure based on the given path.
void applyMapping(const boost::shared_ptr< ControlSystemPVManager > &csManager)
Read mapping file and apply the contained folders, additional variables and pv mappings.
vector< string > folder_with_history
Definition ua_adapter.h:163
vector< string > exclude
Definition ua_adapter.h:162
ua_uaadapter(const string &configPath)
Constructor of the class.
virtual ~ua_uaadapter()
Destrructor of the class.
vector< string > getAllNotMappableVariablesNames()
Methode to get all names from all potential VarableNodes from XML-Mappingfile which could not allocat...
static UA_StatusCode readLogLevel(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean sourceTimeStamp, const UA_NumericRange *range, UA_DataValue *dataValue)
callback function used to read the servers logging level.
ServerConfig get_server_config()
get ServerConfig.
static std::vector< xmlNodePtr > getNodesByName(xmlNodePtr startNode, const std::string &nodeName)
This methode return a list of all nodes with the given name nodeName starting by the given startNode.
static std::string getContentFromNode(xmlNode *node)
This methode returns the value between a xml tag.
static std::vector< std::string > parseVariablePath(const std::string &variablePath, const std::string &seperator="/")
This methode splitt a given string bey the given seperators.
static std::string getAttributeValueFromNode(xmlNode *node, const std::string &attributeName)
This methode returns a value of the given attribute from the given node you want to know.
UA_StatusCode csa_namespace_init(UA_Server *server)
boost::shared_ptr< ChimeraTK::ControlSystemPVManager > csManager
UA_LoggingLevel
@ UA_LOGGINGLEVEL_INFO
UA_StatusCode ua_mapInstantiatedNodes(UA_NodeId objectId, UA_NodeId definitionId, void *handle)
Node function and proxy mapping for new nodes.
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)
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)
This struct represents a folder in OPCUA with its own node id and with his parent and child node id.
Definition ua_adapter.h:66
string folderName
Name of the folder.
Definition ua_adapter.h:70
UA_NodeId folderNodeId
NodeId from the folder from opcua server.
Definition ua_adapter.h:72
UA_NodeId prevFolderNodeId
NodeId from the parent folder.
Definition ua_adapter.h:76
This struct represents a server config.
Definition ua_adapter.h:37
UA_Boolean UsernamePasswordLogin
Definition ua_adapter.h:40
vector< AdapterPVHistorySetup > historyvariables
Definition ua_adapter.h:55
vector< AdapterHistorySetup > history
Definition ua_adapter.h:53
vector< AdapterFolderHistorySetup > historyfolders
Definition ua_adapter.h:54
#define xstr(a)
#define UASTRING_TO_CPPSTRING(_p_uastring, _p_cppstring)
#define UA_STRING_TO_CPPSTRING_COPY(_p_uastring, _p_cppstring)