ChimeraTK-ControlSystemAdapter-OPCUAAdapter  04.00.01
csa_processvariable.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 
22 #include "csa_processvariable.h"
23 
24 #include "csa_config.h"
25 
26 extern "C" {
27 #include "csa_namespace.h"
28 }
29 
30 #include "open62541/plugin/log_stdout.h"
31 #include "ua_map_types.h"
32 
33 #include <iostream>
34 #include <utility>
35 #include <vector>
36 namespace ChimeraTK {
37  ua_processvariable::ua_processvariable(UA_Server* server, UA_NodeId basenodeid, const string& namePV,
38  boost::shared_ptr<ControlSystemPVManager> csManager, const UA_Logger* logger, string overwriteNodeString)
39  : namePV(namePV), nameNew(namePV), csManager(std::move(csManager)),
40  nodeStringIdOverwrite(std::move(overwriteNodeString)), array(false), ua_mapped_class(server, basenodeid) {
41  this->mapSelfToNamespace(logger);
42  }
43 
45  //* Our ua_mapped_class destructor will take care of deleting our opcua footprint as long as all variables are mapped
46  // in this->ownedNodes
47  UA_NodeId_clear(&this->ownNodeId);
48  }
49 
51  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
52  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* /*range*/, UA_DataValue* value) {
53  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
54 
55  UA_String ua_val = UA_String_fromChars((char*)thisObj->getName().c_str());
56  UA_Variant_setScalarCopy(&value->value, &ua_val, &UA_TYPES[UA_TYPES_STRING]);
57  UA_String_clear(&ua_val);
58  value->hasValue = true;
59  if(includeSourceTimeStamp) {
60  value->sourceTimestamp = thisObj->getSourceTimeStamp();
61  value->hasSourceTimestamp = true;
62  }
63  return UA_STATUSCODE_GOOD;
64  }
65 
67  return this->namePV;
68  }
69 
70  // EngineeringUnit
72  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
73  const UA_NumericRange* /*range*/, const UA_DataValue* value) {
74  auto* theClass = static_cast<ua_processvariable*>(nodeContext);
75  std::string cpps;
76  char* s;
77  s = (char*)malloc(((UA_String) * ((UA_String*)value->value.data)).length + 1);
78  memset(s, 0, ((UA_String) * ((UA_String*)value->value.data)).length + 1);
79  memcpy(s, ((UA_String) * ((UA_String*)value->value.data)).data,
80  ((UA_String) * ((UA_String*)value->value.data)).length);
81  cpps.assign(s, ((UA_String) * ((UA_String*)value->value.data)).length);
82  free(s);
83  theClass->setEngineeringUnit(cpps);
84  return UA_STATUSCODE_GOOD;
85  }
86 
87  void ua_processvariable::setEngineeringUnit(string engineeringUnit) {
88  this->engineeringUnit = std::move(engineeringUnit);
89  }
90 
92  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
93  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* /*range*/, UA_DataValue* value) {
94  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
95 
96  UA_String ua_val = UA_String_fromChars((char*)thisObj->getEngineeringUnit().c_str());
97  UA_Variant_setScalarCopy(&value->value, &ua_val, &UA_TYPES[UA_TYPES_STRING]);
98  UA_String_clear(&ua_val);
99  value->hasValue = true;
100  if(includeSourceTimeStamp) {
101  value->sourceTimestamp = thisObj->getSourceTimeStamp();
102  value->hasSourceTimestamp = true;
103  }
104  return UA_STATUSCODE_GOOD;
105  }
106 
108  if(!this->engineeringUnit.empty()) {
109  return this->engineeringUnit;
110  }
111  else {
112  this->engineeringUnit = this->csManager->getProcessVariable(this->namePV)->getUnit();
113  return this->engineeringUnit;
114  }
115  }
116 
117  // Description
119  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
120  const UA_NumericRange* /*range*/, const UA_DataValue* value) {
121  auto* theClass = static_cast<ua_processvariable*>(nodeContext);
122  std::string cpps;
123  char* s;
124  s = (char*)malloc(((UA_String) * ((UA_String*)value->value.data)).length + 1);
125  memset(s, 0, ((UA_String) * ((UA_String*)value->value.data)).length + 1);
126  memcpy(s, ((UA_String) * ((UA_String*)value->value.data)).data,
127  ((UA_String) * ((UA_String*)value->value.data)).length);
128  cpps.assign(s, ((UA_String) * ((UA_String*)value->value.data)).length);
129  free(s);
130  theClass->setDescription(cpps);
131  return UA_STATUSCODE_GOOD;
132  }
133 
134  void ua_processvariable::setDescription(string description) {
135  this->description = std::move(description);
136  }
137 
139  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
140  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* /*range*/, UA_DataValue* value) {
141  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
142 
143  UA_String ua_val = UA_String_fromChars((char*)thisObj->getDescription().c_str());
144  UA_Variant_setScalarCopy(&value->value, &ua_val, &UA_TYPES[UA_TYPES_STRING]);
145  UA_String_clear(&ua_val);
146  value->hasValue = true;
147  if(includeSourceTimeStamp) {
148  value->sourceTimestamp = thisObj->getSourceTimeStamp();
149  value->hasSourceTimestamp = true;
150  }
151  return UA_STATUSCODE_GOOD;
152  }
153 
155  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
156  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* /*range*/, UA_DataValue* value) {
157  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
158  DataValidity dv = thisObj->csManager->getProcessVariable(thisObj->namePV)->dataValidity();
159  UA_Int32 validity;
160  switch(dv) {
161  case DataValidity::ok:
162  validity = 1;
163  break;
164  case DataValidity::faulty:
165  validity = 0;
166  break;
167  default:
168  validity = -1;
169  }
170  UA_Variant_setScalarCopy(&value->value, &validity, &UA_TYPES[UA_TYPES_INT32]);
171  value->hasValue = true;
172  if(includeSourceTimeStamp) {
173  value->sourceTimestamp = thisObj->getSourceTimeStamp();
174  value->hasSourceTimestamp = true;
175  }
176  return UA_STATUSCODE_GOOD;
177  }
178 
180  if(!this->description.empty()) {
181  return this->description;
182  }
183  else {
184  this->description = this->csManager->getProcessVariable(this->namePV)->getDescription();
185  return this->description;
186  }
187  }
188 
189  // Type
191  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
192  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* /*range*/, UA_DataValue* value) {
193  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
194 
195  UA_String ua_val = UA_String_fromChars((char*)thisObj->getType().c_str());
196  UA_Variant_setScalarCopy(&value->value, &ua_val, &UA_TYPES[UA_TYPES_STRING]);
197  UA_String_clear(&ua_val);
198  value->hasValue = true;
199  if(includeSourceTimeStamp) {
200  value->sourceTimestamp = thisObj->getSourceTimeStamp();
201  value->hasSourceTimestamp = true;
202  }
203  return UA_STATUSCODE_GOOD;
204  }
205 
207  // Note: typeid().name() may return the name; may as well return the symbol's name from the binary though...
208  std::type_info const& valueType = this->csManager->getProcessVariable(this->namePV)->getValueType();
209  if(valueType == typeid(int8_t))
210  return "int8_t";
211  else if(valueType == typeid(uint8_t))
212  return "uint8_t";
213  else if(valueType == typeid(int16_t))
214  return "int16_t";
215  else if(valueType == typeid(uint16_t))
216  return "uint16_t";
217  else if(valueType == typeid(int32_t))
218  return "int32_t";
219  else if(valueType == typeid(uint32_t))
220  return "uint32_t";
221  else if(valueType == typeid(int64_t))
222  return "int64_t";
223  else if(valueType == typeid(uint64_t))
224  return "uint64_t";
225  else if(valueType == typeid(float))
226  return "float";
227  else if(valueType == typeid(double))
228  return "double";
229  else if(valueType == typeid(string))
230  return "string";
231  else if(valueType == typeid(Boolean))
232  return "Boolean";
233  else if(valueType == typeid(Void))
234  return "Void";
235  else
236  return "Unsupported type";
237  }
238 
239  template<typename T>
241  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
242  UA_Boolean includeSourceTimeStamp, const UA_NumericRange* range, UA_DataValue* value) {
243  auto* thisObj = static_cast<ua_processvariable*>(nodeContext);
244  UA_StatusCode rv = UA_STATUSCODE_GOOD;
245  rv = thisObj->getValue<T>(&value->value, range);
246  if(rv == UA_STATUSCODE_GOOD) {
247  value->hasValue = true;
248  if(includeSourceTimeStamp) {
249  value->sourceTimestamp = thisObj->getSourceTimeStamp();
250  value->hasSourceTimestamp = true;
251  }
252  }
253  return rv;
254  }
255 
256  template<typename T>
257  UA_StatusCode ua_processvariable::getValue(UA_Variant* v, const UA_NumericRange* range) {
258  UA_StatusCode rv = UA_STATUSCODE_BADINTERNALERROR;
259  if(this->csManager->getProcessVariable(this->namePV)->isReadable()) {
260  this->csManager->getProcessArray<T>(this->namePV)->readLatest();
261  }
262  if(this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0).size() == 1) {
263  T ival = this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0).at(0);
264  rv = UA_Variant_setScalarCopy(v, &ival, &UA_TYPES[fusion::at_key<T>(typesMap)]);
265  }
266  else {
267  std::vector<T> iarr = this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0);
268  v->type = &UA_TYPES[fusion::at_key<T>(typesMap)];
269  if(range != nullptr) {
270  // v->type = &UA_TYPES[fusion::at_key<T>(typesMap)];
271  // rv = UA_Variant_setRangeCopy(v, iarr.data(), iarr.size(), *range);
272  UA_Variant tmpVariant;
273  UA_Variant_setArray(&tmpVariant, iarr.data(), iarr.size(), &UA_TYPES[fusion::at_key<T>(typesMap)]);
274  rv = UA_Variant_copyRange(&tmpVariant, v, *range);
275  }
276  else {
277  rv = UA_Variant_setArrayCopy(v, iarr.data(), iarr.size(), &UA_TYPES[fusion::at_key<T>(typesMap)]);
278  }
279  }
280  return rv;
281  }
282 
283  template<>
284  UA_StatusCode ua_processvariable::getValue<string>(UA_Variant* v, const UA_NumericRange* /*range*/) {
285  UA_StatusCode rv = UA_STATUSCODE_BADINTERNALERROR;
286  if(this->csManager->getProcessVariable(this->namePV)->isReadable()) {
287  this->csManager->getProcessArray<string>(this->namePV)->readLatest();
288  }
289  if(this->csManager->getProcessArray<string>(this->namePV)->accessChannel(0).size() == 1) {
290  string sval = this->csManager->getProcessArray<string>(this->namePV)->accessChannel(0).at(0);
291  UA_String ua_val = UA_String_fromChars((char*)sval.c_str());
292  rv = UA_Variant_setScalarCopy(v, &ua_val, &UA_TYPES[UA_TYPES_STRING]);
293  UA_String_clear(&ua_val);
294  }
295  else {
296  std::vector<string> sarr = this->csManager->getProcessArray<string>(this->namePV)->accessChannel(0);
297  auto* sarrayval = new UA_String[sarr.size()];
298  for(size_t i = 0; i < sarr.size(); i++) {
299  sarrayval[i] = UA_String_fromChars((char*)sarr[i].c_str());
300  }
301  rv = UA_Variant_setArrayCopy(v, sarrayval, sarr.size(), &UA_TYPES[UA_TYPES_STRING]);
302  delete[] sarrayval;
303  }
304  return rv;
305  }
306 
307  template<typename T>
309  const UA_NodeId* /*sessionId*/, void* /*sessionContext*/, const UA_NodeId* /*nodeId*/, void* nodeContext,
310  const UA_NumericRange* /*range*/, const UA_DataValue* value) {
311  UA_StatusCode retval = UA_STATUSCODE_BADINTERNALERROR;
312  auto* theClass = static_cast<ua_processvariable*>(nodeContext);
313  retval = theClass->setValue<T>(&value->value);
314  return retval;
315  }
316 
317  template<typename T>
318  UA_StatusCode ua_processvariable::setValue(const UA_Variant* data) {
319  UA_StatusCode retval = UA_STATUSCODE_BADINTERNALERROR;
320 
321  if(this->csManager->getProcessVariable(this->namePV)->isWriteable()) {
322  vector<T> valueArray;
323  if(UA_Variant_isScalar(data) && (!array)) {
324  T value = *((T*)data->data);
325  valueArray.push_back(value);
326  }
327  else if((!UA_Variant_isScalar(data)) && array) {
328  auto* v = (T*)data->data;
329  valueArray.resize(data->arrayLength);
330  for(size_t i = 0; i < valueArray.size(); i++) {
331  valueArray.at(i) = v[i];
332  }
333  if(this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0).size() != data->arrayLength) {
334  return UA_STATUSCODE_BADINVALIDARGUMENT;
335  }
336  }
337  this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0) = valueArray;
338  this->csManager->getProcessArray<T>(this->namePV)->write();
339  retval = UA_STATUSCODE_GOOD;
340  }
341  else {
342  retval = UA_STATUSCODE_BADNOTWRITABLE;
343  }
344 
345  return retval;
346  }
347 
348  template<>
349  UA_StatusCode ua_processvariable::setValue<std::string>(const UA_Variant* data) {
350  UA_StatusCode retval = UA_STATUSCODE_BADINTERNALERROR;
351 
352  if(this->csManager->getProcessVariable(this->namePV)->isWriteable()) {
353  vector<string> valueArray;
354  if(UA_Variant_isScalar(data) && (!array)) {
355  string cpps;
356  UASTRING_TO_CPPSTRING(((UA_String) * ((UA_String*)data->data)), cpps)
357  // string value = *((string *)data->data);
358  valueArray.push_back(cpps);
359  }
360  else if((!UA_Variant_isScalar(data)) && array) {
361  // Array
362  auto* vdata = (UA_String*)data->data;
363  valueArray.resize(data->arrayLength);
364  for(uint32_t i = 0; i < valueArray.size(); i++) {
365  string cpps;
366  UASTRING_TO_CPPSTRING(vdata[i], cpps)
367  valueArray.at(i) = cpps;
368  }
369  if(this->csManager->getProcessArray<string>(this->namePV)->accessChannel(0).size() != data->arrayLength) {
370  return UA_STATUSCODE_BADINVALIDARGUMENT;
371  }
372  }
373  this->csManager->getProcessArray<string>(this->namePV)->accessChannel(0) = valueArray;
374  this->csManager->getProcessArray<string>(this->namePV)->write();
375  retval = UA_STATUSCODE_GOOD;
376  }
377  else {
378  retval = UA_STATUSCODE_BADNOTWRITABLE;
379  }
380 
381  return retval;
382  }
383 
384  UA_StatusCode ua_processvariable::mapSelfToNamespace(const UA_Logger* logger) {
385  UA_StatusCode retval = UA_STATUSCODE_GOOD;
386  UA_NodeId createdNodeId = UA_NODEID_NULL;
387  if(!nodeStringIdOverwrite.empty()) this->nameNew = nodeStringIdOverwrite;
388 
389  if(UA_NodeId_equal(&this->baseNodeId, &createdNodeId))
390  return UA_STATUSCODE_GOOD; // initializer should have set this!
391 
392  UA_LocalizedText description;
393  description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"),
394  const_cast<char*>(this->csManager->getProcessVariable(this->namePV)->getDescription().c_str()));
395 
396  // Create our toplevel instance
397  UA_VariableAttributes attr;
398  UA_VariableAttributes_init(&attr);
399  attr = UA_VariableAttributes_default;
400  // Allow negative sampling intervals -> used by Labview and handled by open62541-interface 1.3.3.-2
401  attr.minimumSamplingInterval = -1.;
402  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(this->nameNew.c_str()));
403  attr.description = description;
404  attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
405 
406  if(this->csManager->getProcessVariable(this->namePV)->isWriteable()) {
407  attr.writeMask = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
408  attr.accessLevel = UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE;
409  }
410 
411  // Append the app and application folder names to the string nodeId, this is needed
412  // because the mapping allows one pv linked in more folder -> only pv path is not unique
413  string baseNodeIdName;
414  if(this->baseNodeId.identifierType == UA_NODEIDTYPE_STRING) {
415  UA_STRING_TO_CPPSTRING_COPY(&this->baseNodeId.identifier.string, &baseNodeIdName)
416  }
417  if(!baseNodeIdName.empty()) {
418  baseNodeIdName.resize(baseNodeIdName.size() - 3);
419  }
420 
421  // check if the nodeId is used by another mapping and find next free NodeId
422  UA_NodeId result;
423  if(UA_Server_readDataType(this->mappedServer,
424  UA_NODEID_STRING(1, const_cast<char*>((baseNodeIdName + "/" + this->nameNew).c_str())),
425  &result) == UA_STATUSCODE_GOOD) {
426  return UA_STATUSCODE_BADNODEIDEXISTS;
427  }
428  retval = UA_Server_addVariableNode(this->mappedServer,
429  UA_NODEID_STRING(1, const_cast<char*>((baseNodeIdName + "/" + this->nameNew).c_str())), this->baseNodeId,
430  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, const_cast<char*>(this->nameNew.c_str())),
431  UA_NODEID_NUMERIC(CSA_NSID, 1001), attr, (void*)this, &createdNodeId);
432  UA_NodeId_copy(&createdNodeId, &this->ownNodeId);
433  ua_mapInstantiatedNodes(createdNodeId, UA_NODEID_NUMERIC(CSA_NSID, 1001), &this->ownedNodes);
434 
435  /* Use a datasource map to map any local getter/setter functions to OPC UA variables nodes */
436  UA_DataSource_Map mapDs;
437  this->addPVChildNodes(createdNodeId, baseNodeIdName, mapDs);
438 
439  UA_Variant arrayDimensions;
440  UA_Variant_init(&arrayDimensions);
441 
442  std::type_info const& valueType = this->csManager->getProcessVariable(this->namePV)->getValueType();
443 
445  mapElem.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE);
446  mapElem.description = description;
447  // Read is possible for all elements
448  UA_Variant uaArrayDimensions;
449  UA_UInt32 arrayDims[1];
450 
451  if(valueType == typeid(int8_t)) {
452  arrayDims[0] = typeSpecificSetup<int8_t>(mapElem, createdNodeId);
453  }
454  else if(valueType == typeid(uint8_t)) {
455  arrayDims[0] = typeSpecificSetup<uint8_t>(mapElem, createdNodeId);
456  }
457  else if(valueType == typeid(int16_t)) {
458  arrayDims[0] = typeSpecificSetup<int16_t>(mapElem, createdNodeId);
459  }
460  else if(valueType == typeid(uint16_t)) {
461  arrayDims[0] = typeSpecificSetup<uint16_t>(mapElem, createdNodeId);
462  }
463  else if(valueType == typeid(int32_t)) {
464  arrayDims[0] = typeSpecificSetup<int32_t>(mapElem, createdNodeId);
465  }
466  else if(valueType == typeid(uint32_t)) {
467  arrayDims[0] = typeSpecificSetup<uint32_t>(mapElem, createdNodeId);
468  }
469  else if(valueType == typeid(int64_t)) {
470  arrayDims[0] = typeSpecificSetup<int64_t>(mapElem, createdNodeId);
471  }
472  else if(valueType == typeid(uint64_t)) {
473  arrayDims[0] = typeSpecificSetup<uint64_t>(mapElem, createdNodeId);
474  }
475  else if(valueType == typeid(float)) {
476  arrayDims[0] = typeSpecificSetup<float>(mapElem, createdNodeId);
477  }
478  else if(valueType == typeid(double)) {
479  arrayDims[0] = typeSpecificSetup<double>(mapElem, createdNodeId);
480  }
481  else if(valueType == typeid(string)) {
482  arrayDims[0] = typeSpecificSetup<std::string>(mapElem, createdNodeId);
483  }
484  else if(valueType == typeid(Boolean)) {
485  arrayDims[0] = typeSpecificSetup<Boolean>(mapElem, createdNodeId);
486  }
487  else if(valueType != typeid(Void)) {
488  int status;
489  auto* demangledName = abi::__cxa_demangle(valueType.name(), nullptr, nullptr, &status);
490 
491  if(status == 0) {
492  UA_LOG_WARNING(logger, UA_LOGCATEGORY_USERLAND, "Cannot proxy unknown type %s for variable %s", demangledName,
493  this->namePV.c_str());
494  free(demangledName);
495  }
496  else {
497  UA_LOG_WARNING(logger, UA_LOGCATEGORY_USERLAND,
498  "Cannot proxy unknown type %s (demangling failed) for variable %s", valueType.name(), this->namePV.c_str());
499  }
500  }
501 
502  if(!this->csManager->getProcessVariable(this->namePV)->isWriteable()) {
503  mapElem.dataSource.write = nullptr;
504  }
505  if(this->array) {
506  UA_Server_writeValueRank(this->mappedServer, createdNodeId, UA_VALUERANK_ONE_DIMENSION);
507  UA_Variant_setArray(&uaArrayDimensions, arrayDims, 1, &UA_TYPES[UA_TYPES_UINT32]);
508  UA_Server_writeArrayDimensions(this->mappedServer, createdNodeId, uaArrayDimensions);
509  }
510  else {
511  UA_Server_writeValueRank(this->mappedServer, createdNodeId, UA_VALUERANK_SCALAR);
512  }
513 
514  // add variable data source map element to the list
515  UA_NodeId_copy(&createdNodeId, &mapElem.concreteNodeId);
516  mapDs.push_back(mapElem);
517 
518  UA_NodeId nodeIdVariableType = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE);
519  NODE_PAIR_PUSH(this->ownedNodes, nodeIdVariableType, createdNodeId)
520 
521  for(auto i : mapDs) {
522  retval |= UA_Server_setVariableNode_dataSource(this->mappedServer, i.concreteNodeId, i.dataSource);
523  UA_NodeId_clear(&i.concreteNodeId);
524  UA_NodeId_clear(&i.typeTemplateId);
525  }
526  UA_NodeId_clear(&createdNodeId);
527  return retval;
528  }
529 
530  UA_StatusCode ua_processvariable::addPVChildNodes(
531  UA_NodeId pvNodeId, const string& baseNodePath, UA_DataSource_Map& map) {
532  UA_NodeId createdNodeId = UA_NODEID_NULL;
533  UA_VariableAttributes attr;
534  UA_StatusCode addResult;
535 
536  // Adding the 'name' node to the PV
537  UA_DataSource_Map_Element mapElemName;
538  mapElemName.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_NAME);
539  mapElemName.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
540  mapElemName.dataSource.read = ua_readproxy_ua_processvariable_getName;
541  mapElemName.dataSource.write = nullptr;
542 
543  UA_VariableAttributes_init(&attr);
544  attr = UA_VariableAttributes_default;
545  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Name"));
546  attr.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
547  attr.valueRank = UA_VALUERANK_SCALAR;
548  attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
549  UA_String opcua_node_variable_t_ns_2_i_6004_variant_DataContents = UA_STRING_ALLOC(const_cast<char*>(""));
550  UA_Variant_setScalar(
551  &attr.value, &opcua_node_variable_t_ns_2_i_6004_variant_DataContents, &UA_TYPES[UA_TYPES_STRING]);
552  addResult = UA_Server_addVariableNode(this->mappedServer,
553  UA_NODEID_STRING(1, const_cast<char*>((baseNodePath + "/" + this->nameNew + "/Name").c_str())), pvNodeId,
554  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, const_cast<char*>("Name")),
555  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, this, &createdNodeId);
556  if(addResult == UA_STATUSCODE_GOOD) {
557  UA_NodeId nameVariable = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_NAME);
558  NODE_PAIR_PUSH(this->ownedNodes, nameVariable, createdNodeId)
559  UA_NodeId_copy(&createdNodeId, &mapElemName.concreteNodeId);
560  map.push_back(mapElemName);
561  }
562  else {
563  UA_NodeId_clear(&createdNodeId);
564  return addResult;
565  }
566  UA_NodeId_clear(&createdNodeId);
567  createdNodeId = UA_NODEID_NULL;
568 
569  // Adding the 'Description' node to the PV
570  UA_DataSource_Map_Element mapElemDesc;
571  mapElemDesc.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_DESC);
572  mapElemDesc.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
573  mapElemDesc.dataSource.read = ua_readproxy_ua_processvariable_getDescription;
574  mapElemDesc.dataSource.write = nullptr;
575 
576  UA_VariableAttributes_init(&attr);
577  attr = UA_VariableAttributes_default;
578  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Description"));
579  attr.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
580  attr.accessLevel = UA_ACCESSLEVELMASK_READ;
581  attr.userAccessLevel = UA_ACCESSLEVELMASK_READ;
582  attr.valueRank = UA_VALUERANK_SCALAR;
583  attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
584  UA_String opcua_node_variable_t_ns_2_i_6001_variant_DataContents = UA_STRING(const_cast<char*>(""));
585  UA_Variant_setScalar(
586  &attr.value, &opcua_node_variable_t_ns_2_i_6001_variant_DataContents, &UA_TYPES[UA_TYPES_STRING]);
587  addResult = UA_Server_addVariableNode(this->mappedServer,
588  UA_NODEID_STRING(1, const_cast<char*>((baseNodePath + "/" + this->nameNew + "/Description").c_str())), pvNodeId,
589  UA_NODEID_NUMERIC(0, 47), UA_QUALIFIEDNAME(1, const_cast<char*>("Description")), UA_NODEID_NUMERIC(0, 63), attr,
590  this, &createdNodeId);
591  if(addResult == UA_STATUSCODE_GOOD) {
592  UA_NodeId descVariable = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_DESC);
593  NODE_PAIR_PUSH(this->ownedNodes, descVariable, createdNodeId)
594  UA_NodeId_copy(&createdNodeId, &mapElemDesc.concreteNodeId);
595  map.push_back(mapElemDesc);
596  }
597  else {
598  UA_NodeId_clear(&createdNodeId);
599  return addResult;
600  }
601 
602  UA_NodeId_clear(&createdNodeId);
603  createdNodeId = UA_NODEID_NULL;
604 
605  // Adding the 'EngineeringUnit' node to the PV
606  UA_DataSource_Map_Element mapElemEU;
607  mapElemEU.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_UNIT);
608  mapElemEU.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
609  mapElemEU.dataSource.read = ua_readproxy_ua_processvariable_getEngineeringUnit;
610  mapElemEU.dataSource.write = nullptr;
611  UA_VariableAttributes_init(&attr);
612  attr = UA_VariableAttributes_default;
613  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("EngineeringUnit"));
614  attr.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
615  attr.accessLevel = UA_ACCESSLEVELMASK_READ;
616  attr.userAccessLevel = UA_ACCESSLEVELMASK_READ;
617  attr.valueRank = UA_VALUERANK_SCALAR;
618  attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
619  UA_String defaultEngineeringUnit = UA_STRING(const_cast<char*>(""));
620  UA_Variant_setScalar(&attr.value, &defaultEngineeringUnit, &UA_TYPES[UA_TYPES_STRING]);
621  addResult = UA_Server_addVariableNode(this->mappedServer,
622  UA_NODEID_STRING(1, const_cast<char*>((baseNodePath + "/" + this->nameNew + "/EngineeringUnit").c_str())),
623  pvNodeId, UA_NODEID_NUMERIC(0, 47), UA_QUALIFIEDNAME(1, const_cast<char*>("EngineeringUnit")),
624  UA_NODEID_NUMERIC(0, 63), attr, this, &createdNodeId);
625  if(addResult == UA_STATUSCODE_GOOD) {
626  UA_NodeId engineeringunitVariable = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_UNIT);
627  NODE_PAIR_PUSH(this->ownedNodes, engineeringunitVariable, createdNodeId)
628  UA_NodeId_copy(&createdNodeId, &mapElemEU.concreteNodeId);
629  map.push_back(mapElemEU);
630  }
631  else {
632  UA_NodeId_clear(&createdNodeId);
633  return addResult;
634  }
635 
636  UA_NodeId_clear(&createdNodeId);
637  createdNodeId = UA_NODEID_NULL;
638 
639  // Adding the 'Type' node to the PV
640  UA_DataSource_Map_Element mapElemType;
641  mapElemType.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_TYPE);
642  mapElemType.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
643  mapElemType.dataSource.read = ua_readproxy_ua_processvariable_getType;
644  mapElemType.dataSource.write = nullptr;
645  UA_VariableAttributes_init(&attr);
646  attr = UA_VariableAttributes_default;
647  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Type"));
648  attr.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Data type used in ChimeraTK"));
649  attr.accessLevel = UA_ACCESSLEVELMASK_READ;
650  attr.userAccessLevel = UA_ACCESSLEVELMASK_READ;
651  attr.valueRank = UA_VALUERANK_SCALAR;
652  attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
653  UA_String opcua_node_variable_t_ns_2_i_6012_variant_DataContents = UA_STRING(const_cast<char*>(""));
654  UA_Variant_setScalar(
655  &attr.value, &opcua_node_variable_t_ns_2_i_6012_variant_DataContents, &UA_TYPES[UA_TYPES_STRING]);
656  UA_NodeId nodeId = UA_NODEID_NUMERIC(2, 6012);
657  UA_NodeId typeDefinition = UA_NODEID_NUMERIC(0, 63);
658  UA_NodeId parentNodeId = UA_NODEID_NUMERIC(2, 1001);
659  addResult = UA_Server_addVariableNode(this->mappedServer,
660  UA_NODEID_STRING(1, const_cast<char*>((baseNodePath + "/" + this->nameNew + "/Type").c_str())), pvNodeId,
661  UA_NODEID_NUMERIC(0, 47), UA_QUALIFIEDNAME(1, const_cast<char*>("Type")), UA_NODEID_NUMERIC(0, 63), attr, this,
662  &createdNodeId);
663  if(addResult == UA_STATUSCODE_GOOD) {
664  UA_NodeId typeVariable = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_TYPE);
665  NODE_PAIR_PUSH(this->ownedNodes, typeVariable, createdNodeId)
666  UA_NodeId_copy(&createdNodeId, &mapElemType.concreteNodeId);
667  map.push_back(mapElemType);
668  }
669  else {
670  UA_NodeId_clear(&createdNodeId);
671  return addResult;
672  }
673 
674  UA_NodeId_clear(&createdNodeId);
675  createdNodeId = UA_NODEID_NULL;
676 
677  // Adding the Validity node to the PV
678  UA_DataSource_Map_Element mapElemValidity;
679  mapElemValidity.typeTemplateId = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_VALIDITY);
680  mapElemValidity.description = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>(""));
681  mapElemValidity.dataSource.read = ua_readproxy_ua_processvariable_getValidity;
682  mapElemValidity.dataSource.write = nullptr;
683  UA_VariableAttributes_init(&attr);
684  attr = UA_VariableAttributes_default;
685  attr.displayName = UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Validity"));
686  attr.description =
687  UA_LOCALIZEDTEXT(const_cast<char*>("en_US"), const_cast<char*>("Data validity. 0: faulty, 1: ok"));
688  attr.accessLevel = UA_ACCESSLEVELMASK_READ;
689  attr.userAccessLevel = UA_ACCESSLEVELMASK_READ;
690  attr.valueRank = UA_VALUERANK_SCALAR;
691  attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
692  UA_Int32 defaultValidity = -1;
693  UA_Variant_setScalar(&attr.value, &defaultValidity, &UA_TYPES[UA_TYPES_INT32]);
694  addResult = UA_Server_addVariableNode(this->mappedServer,
695  UA_NODEID_STRING(1, const_cast<char*>((baseNodePath + "/" + this->nameNew + "/Validity").c_str())), pvNodeId,
696  UA_NODEID_NUMERIC(0, 47), UA_QUALIFIEDNAME(1, const_cast<char*>("Validity")), UA_NODEID_NUMERIC(0, 63), attr,
697  this, &createdNodeId);
698  if(addResult == UA_STATUSCODE_GOOD) {
699  UA_NodeId vadilityVariable = UA_NODEID_NUMERIC(CSA_NSID, CSA_NSID_VARIABLE_VALIDITY);
700  NODE_PAIR_PUSH(this->ownedNodes, vadilityVariable, createdNodeId)
701  UA_NodeId_copy(&createdNodeId, &mapElemValidity.concreteNodeId);
702  map.push_back(mapElemValidity);
703  }
704  else {
705  UA_NodeId_clear(&createdNodeId);
706  return addResult;
707  }
708 
709  UA_NodeId_clear(&createdNodeId);
710 
711  return addResult;
712  }
713 
718  auto t = this->csManager->getProcessVariable(this->namePV)->getVersionNumber().getTime();
719  auto microseconds = std::chrono::time_point_cast<std::chrono::microseconds>(t).time_since_epoch().count();
720  // For the initial value from ChimeraTK microseconds will be 0 -> use current time in this case
721  if(microseconds == 0) {
722  microseconds = std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now())
723  .time_since_epoch()
724  .count();
725  }
726  return (microseconds * UA_DATETIME_USEC) + UA_DATETIME_UNIX_EPOCH;
727  }
728 
730  UA_NodeId outputNode;
731  UA_NodeId_copy(&this->ownNodeId, &outputNode);
732  return outputNode;
733  }
734 
735  template<typename T>
736  UA_UInt32 ua_processvariable::typeSpecificSetup(UA_DataSource_Map_Element& mapElem, const UA_NodeId nodeId) {
737  UA_Int32 arrayDims;
738  mapElem.dataSource.read = ua_processvariable::ua_readproxy_ua_processvariable_getValue<T>;
739  if(this->csManager->getProcessVariable(this->namePV)->isWriteable()) {
740  mapElem.dataSource.write = ua_processvariable::ua_writeproxy_ua_processvariable_setValue<T>;
741  }
742  if(this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0).size() == 1) {
743  this->array = false;
744  }
745  else {
746  this->array = true;
747  arrayDims = this->csManager->getProcessArray<T>(this->namePV)->accessChannel(0).size();
748  }
749  UA_Server_writeDataType(this->mappedServer, nodeId, UA_TYPES[fusion::at_key<T>(typesMap)].typeId);
750  return arrayDims;
751  }
752 } // namespace ChimeraTK
ChimeraTK::ua_mapInstantiatedNodes
UA_StatusCode ua_mapInstantiatedNodes(UA_NodeId objectId, UA_NodeId definitionId, void *handle)
Node function and proxy mapping for new nodes.
Definition: ua_map_types.cpp:30
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getValidity
static UA_StatusCode ua_readproxy_ua_processvariable_getValidity(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Get vadility of processvariable.
Definition: csa_processvariable.cpp:154
ChimeraTK::UA_DataSource_Map_Element_t::typeTemplateId
UA_NodeId typeTemplateId
Definition: ua_map_types.h:51
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getValue
static UA_StatusCode ua_readproxy_ua_processvariable_getValue(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Definition: csa_processvariable.cpp:240
ChimeraTK::ua_mapped_class::mappedServer
UA_Server * mappedServer
Definition: ua_mapped_class.h:39
ChimeraTK::ua_processvariable::getEngineeringUnit
string getEngineeringUnit()
Get engineering unit of processvariable.
Definition: csa_processvariable.cpp:107
ChimeraTK::ua_processvariable::setValue
UA_StatusCode setValue(const UA_Variant *data)
Definition: csa_processvariable.cpp:318
ChimeraTK::ua_mapped_class
This class mapped all inforamtion into the opca server.
Definition: ua_mapped_class.h:33
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getType
static UA_StatusCode ua_readproxy_ua_processvariable_getType(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Definition: csa_processvariable.cpp:190
ChimeraTK::ua_mapped_class::baseNodeId
UA_NodeId baseNodeId
Definition: ua_mapped_class.h:36
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getDescription
static UA_StatusCode ua_readproxy_ua_processvariable_getDescription(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Definition: csa_processvariable.cpp:138
NODE_PAIR_PUSH
#define NODE_PAIR_PUSH(_p_listname, _p_srcId, _p_targetId)
Definition: ua_map_types.h:58
csa_namespace.h
ChimeraTK::ua_processvariable::getName
string getName()
Get name of processvariable.
Definition: csa_processvariable.cpp:66
ChimeraTK::UA_DataSource_Map_Element
struct ChimeraTK::UA_DataSource_Map_Element_t UA_DataSource_Map_Element
ChimeraTK::ua_processvariable::getOwnNodeId
UA_NodeId getOwnNodeId()
Get node id of this processvariable instance.
Definition: csa_processvariable.cpp:729
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getName
static UA_StatusCode ua_readproxy_ua_processvariable_getName(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Definition: csa_processvariable.cpp:50
ChimeraTK::UA_DataSource_Map
std::list< UA_DataSource_Map_Element > UA_DataSource_Map
Definition: ua_map_types.h:56
csa_processvariable.h
ChimeraTK::ua_processvariable::ua_writeproxy_ua_processvariable_setEngineeringUnit
static UA_StatusCode ua_writeproxy_ua_processvariable_setEngineeringUnit(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *value)
Definition: csa_processvariable.cpp:71
ChimeraTK::ua_processvariable::getSourceTimeStamp
UA_DateTime getSourceTimeStamp()
Reimplement the sourcetimestamp for every processvariable.
Definition: csa_processvariable.cpp:717
csManager
boost::shared_ptr< ChimeraTK::ControlSystemPVManager > csManager
Definition: csa_opcua_application.cpp:59
ChimeraTK::ua_processvariable::ua_processvariable
ua_processvariable(UA_Server *server, UA_NodeId basenodeid, const string &namePV, boost::shared_ptr< ControlSystemPVManager > csManager, const UA_Logger *logger, string overwriteNodeString="")
Constructor from ua_processvaribale for generic creation.
Definition: csa_processvariable.cpp:37
ChimeraTK::ua_processvariable::typeSpecificSetup
UA_UInt32 typeSpecificSetup(UA_DataSource_Map_Element &mapElem, const UA_NodeId nodeId)
Definition: csa_processvariable.cpp:736
ChimeraTK::ua_processvariable::setDescription
void setDescription(string description)
Get description unit of processvariable.
Definition: csa_processvariable.cpp:134
ChimeraTK::ua_processvariable::ua_readproxy_ua_processvariable_getEngineeringUnit
static UA_StatusCode ua_readproxy_ua_processvariable_getEngineeringUnit(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean includeSourceTimeStamp, const UA_NumericRange *range, UA_DataValue *value)
Definition: csa_processvariable.cpp:91
ChimeraTK::ua_processvariable::getDescription
string getDescription()
Get description unit of processvariable.
Definition: csa_processvariable.cpp:179
ChimeraTK::ua_processvariable::ua_writeproxy_ua_processvariable_setDescription
static UA_StatusCode ua_writeproxy_ua_processvariable_setDescription(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *value)
Definition: csa_processvariable.cpp:118
ChimeraTK::ua_processvariable::getType
string getType()
Get type of processvariable.
Definition: csa_processvariable.cpp:206
UASTRING_TO_CPPSTRING
#define UASTRING_TO_CPPSTRING(_p_uastring, _p_cppstring)
Definition: ua_typeconversion.h:53
ChimeraTK::ua_processvariable::setEngineeringUnit
void setEngineeringUnit(string engineeringUnit)
Set engineering unit of processvariable.
Definition: csa_processvariable.cpp:87
ChimeraTK::ua_mapped_class::ownedNodes
nodePairList ownedNodes
Definition: ua_mapped_class.h:35
ua_map_types.h
ChimeraTK::UA_DataSource_Map_Element_t
For generic callback use, this sturct contains the methode pointer and a NodeId of the model....
Definition: ua_map_types.h:50
ChimeraTK
Definition: csa_additionalvariable.h:28
ChimeraTK::ua_processvariable::~ua_processvariable
~ua_processvariable()
Destructor for ua_processvariable.
Definition: csa_processvariable.cpp:44
UA_STRING_TO_CPPSTRING_COPY
#define UA_STRING_TO_CPPSTRING_COPY(_p_uastring, _p_cppstring)
Definition: ua_typeconversion.h:28
generate_open62541CCode.logger
logger
Definition: generate_open62541CCode.py:29
ChimeraTK::ua_processvariable::ua_writeproxy_ua_processvariable_setValue
static UA_StatusCode ua_writeproxy_ua_processvariable_setValue(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *value)
Definition: csa_processvariable.cpp:308
ChimeraTK::UA_DataSource_Map_Element_t::dataSource
UA_DataSource dataSource
Definition: ua_map_types.h:54
ChimeraTK::ua_processvariable::getValue
UA_StatusCode getValue(UA_Variant *v, const UA_NumericRange *range)
Definition: csa_processvariable.cpp:257
ChimeraTK::ua_processvariable
This class represent a processvariable of the controlsystemadapter in the information model of a OPC ...
Definition: csa_processvariable.h:49