ua_subscription.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #ifdef ENABLE_SUBSCRIPTIONS
  2. #include "ua_subscription.h"
  3. /****************/
  4. /* Subscription */
  5. /****************/
  6. UA_Subscription *UA_Subscription_new(UA_Int32 SubscriptionID) {
  7. UA_Subscription *new = (UA_Subscription *) UA_malloc(sizeof(UA_Subscription));
  8. new->SubscriptionID = SubscriptionID;
  9. new->LastPublished = 0;
  10. new->SequenceNumber = 0;
  11. LIST_INIT(&new->MonitoredItems);
  12. LIST_INITENTRY(new, listEntry);
  13. LIST_INIT(&new->unpublishedNotifications);
  14. return new;
  15. }
  16. UA_UInt32 Subscription_queuedNotifications(UA_Subscription *subscription) {
  17. if (!subscription)
  18. return 0;
  19. UA_UInt32 j = 0;
  20. UA_unpublishedNotification *i;
  21. LIST_FOREACH(i, &subscription->unpublishedNotifications, listEntry)
  22. j++;
  23. return j;
  24. }
  25. void Subscription_generateKeepAlive(UA_Subscription *subscription) {
  26. UA_unpublishedNotification *msg = NULL;
  27. if (subscription->KeepAliveCount.currentValue <= subscription->KeepAliveCount.minValue || subscription->KeepAliveCount.currentValue > subscription->KeepAliveCount.maxValue) {
  28. msg = (UA_unpublishedNotification *) UA_malloc(sizeof(UA_unpublishedNotification));
  29. LIST_INITENTRY(msg, listEntry);
  30. INITPOINTER(msg->notification);
  31. msg->notification = (UA_NotificationMessage *) UA_malloc(sizeof(UA_NotificationMessage));
  32. INITPOINTER(msg->notification->notificationData);
  33. msg->notification->sequenceNumber = (subscription->SequenceNumber)+1; // KeepAlive uses next message, but does not increment counter
  34. msg->notification->publishTime = UA_DateTime_now();
  35. msg->notification->notificationDataSize = 0;
  36. LIST_INSERT_HEAD(&subscription->unpublishedNotifications, msg, listEntry);
  37. subscription->KeepAliveCount.currentValue = subscription->KeepAliveCount.maxValue;
  38. }
  39. return;
  40. }
  41. void Subscription_updateNotifications(UA_Subscription *subscription) {
  42. UA_MonitoredItem *mon;
  43. //MonitoredItem_queuedValue *queuedValue;
  44. UA_unpublishedNotification *msg = NULL;
  45. UA_UInt32 monItemsChangeT = 0, monItemsStatusT = 0, monItemsEventT = 0;
  46. UA_DataChangeNotification *changeNotification;
  47. size_t notificationOffset;
  48. if(!subscription)
  49. return;
  50. if((subscription->LastPublished + subscription->PublishingInterval) > UA_DateTime_now())
  51. return;
  52. // Make sure there is data to be published and establish which message types
  53. // will need to be generated
  54. LIST_FOREACH(mon, &subscription->MonitoredItems, listEntry) {
  55. // Check if this MonitoredItems Queue holds data and how much data is held in total
  56. if (!mon->queue.lh_first)
  57. continue;
  58. if((mon->MonitoredItemType & MONITOREDITEM_TYPE_CHANGENOTIFY) != 0)
  59. monItemsChangeT+=mon->QueueSize.currentValue;
  60. else if((mon->MonitoredItemType & MONITOREDITEM_TYPE_STATUSNOTIFY) != 0)
  61. monItemsStatusT+=mon->QueueSize.currentValue;
  62. else if ((mon->MonitoredItemType & MONITOREDITEM_TYPE_EVENTNOTIFY) != 0)
  63. monItemsEventT+=mon->QueueSize.currentValue;
  64. }
  65. // FIXME: This is hardcoded to 100 because it is not covered by the spec but we need to protect the server!
  66. if (Subscription_queuedNotifications(subscription) >= 10) {
  67. // Remove last entry
  68. LIST_FOREACH(msg, &subscription->unpublishedNotifications, listEntry)
  69. LIST_REMOVE(msg, listEntry);
  70. UA_free(msg);
  71. }
  72. if (monItemsChangeT == 0 && monItemsEventT == 0 && monItemsStatusT == 0) {
  73. // Decrement KeepAlive
  74. subscription->KeepAliveCount.currentValue--;
  75. // +- Generate KeepAlive msg if counter overruns
  76. Subscription_generateKeepAlive(subscription);
  77. return;
  78. }
  79. msg = (UA_unpublishedNotification *) UA_malloc(sizeof(UA_unpublishedNotification));
  80. LIST_INITENTRY(msg, listEntry);
  81. msg->notification = UA_malloc(sizeof(UA_NotificationMessage));
  82. INITPOINTER(msg->notification->notificationData);
  83. msg->notification->sequenceNumber = subscription->SequenceNumber++;
  84. msg->notification->publishTime = UA_DateTime_now();
  85. // NotificationData is an array of Change, Status and Event messages, each containing the appropriate
  86. // list of Queued values from all monitoredItems of that type
  87. msg->notification->notificationDataSize = ISNOTZERO(monItemsChangeT);// + ISNOTZERO(monItemsEventT) + ISNOTZERO(monItemsStatusT);
  88. msg->notification->notificationData = (UA_ExtensionObject *) UA_malloc(sizeof(UA_ExtensionObject) * msg->notification->notificationDataSize);
  89. for(int notmsgn=0; notmsgn < msg->notification->notificationDataSize; notmsgn++) {
  90. // Set the notification message type and encoding for each of
  91. // the three possible NotificationData Types
  92. (msg->notification->notificationData)[notmsgn].encoding = 1; // Encoding is always binary
  93. (msg->notification->notificationData)[notmsgn].typeId = UA_NODEID_NUMERIC(0, 811);
  94. if(notmsgn == 0) {
  95. // Construct a DataChangeNotification
  96. changeNotification = (UA_DataChangeNotification *) UA_malloc(sizeof(UA_DataChangeNotification));
  97. // Create one DataChangeNotification for each queue item held in each monitoredItems queue:
  98. changeNotification->monitoredItems = (UA_MonitoredItemNotification *) UA_malloc(sizeof(UA_MonitoredItemNotification) * monItemsChangeT);
  99. // Scan all monitoredItems in this subscription and have their queue transformed into an Array of
  100. // the propper NotificationMessageType (Status, Change, Event)
  101. monItemsChangeT = 0;
  102. for(mon=subscription->MonitoredItems.lh_first; mon != NULL; mon=mon->listEntry.le_next) {
  103. if (mon->MonitoredItemType != MONITOREDITEM_TYPE_CHANGENOTIFY || mon->queue.lh_first == NULL ) continue;
  104. // Note: Monitored Items might not return a queuedValue if there is a problem encoding it.
  105. monItemsChangeT += MonitoredItem_QueueToDataChangeNotifications( &((changeNotification->monitoredItems)[monItemsChangeT]), mon);
  106. MonitoredItem_ClearQueue(mon);
  107. }
  108. changeNotification->monitoredItemsSize = monItemsChangeT;
  109. changeNotification->diagnosticInfosSize = 0;
  110. changeNotification->diagnosticInfos = NULL;
  111. (msg->notification->notificationData[notmsgn]).body.length = UA_calcSizeBinary(changeNotification, &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION]);
  112. (msg->notification->notificationData[notmsgn]).body.data = UA_malloc((msg->notification->notificationData[notmsgn]).body.length);
  113. notificationOffset = 0;
  114. UA_encodeBinary((const void *) changeNotification, &UA_TYPES[UA_TYPES_DATACHANGENOTIFICATION], &(msg->notification->notificationData[notmsgn].body), &notificationOffset);
  115. // FIXME: Not properly freed!
  116. for(unsigned int i=0; i<monItemsChangeT; i++) {
  117. UA_MonitoredItemNotification *thisNotification = &(changeNotification->monitoredItems[i]);
  118. UA_DataValue_deleteMembers(&(thisNotification->value));
  119. }
  120. UA_free(changeNotification->monitoredItems);
  121. UA_free(changeNotification);
  122. }
  123. else if (notmsgn == 1) {
  124. // FIXME: Constructing a StatusChangeNotification is not implemented
  125. }
  126. else if (notmsgn == 2) {
  127. // FIXME: Constructing a EventListNotification is not implemented
  128. }
  129. }
  130. LIST_INSERT_HEAD(&subscription->unpublishedNotifications, msg, listEntry);
  131. }
  132. UA_UInt32 *Subscription_getAvailableSequenceNumbers(UA_Subscription *sub) {
  133. UA_UInt32 *seqArray;
  134. int i;
  135. UA_unpublishedNotification *not;
  136. if(!sub)
  137. return NULL;
  138. seqArray = (UA_UInt32 *) UA_malloc(sizeof(UA_UInt32) * Subscription_queuedNotifications(sub));
  139. if (seqArray == NULL ) return NULL;
  140. i = 0;
  141. for(not = sub->unpublishedNotifications.lh_first; not != NULL; not=(not->listEntry).le_next) {
  142. seqArray[i] = not->notification->sequenceNumber;
  143. i++;
  144. }
  145. return seqArray;
  146. }
  147. void Subscription_copyTopNotificationMessage(UA_NotificationMessage *dst, UA_Subscription *sub) {
  148. UA_NotificationMessage *latest;
  149. if (dst == NULL) return;
  150. if (Subscription_queuedNotifications(sub) == 0) {
  151. dst->notificationDataSize = 0;
  152. dst->publishTime = UA_DateTime_now();
  153. dst->sequenceNumber = 0;
  154. return;
  155. }
  156. latest = sub->unpublishedNotifications.lh_first->notification;
  157. dst->notificationDataSize = latest->notificationDataSize;
  158. dst->publishTime = latest->publishTime;
  159. dst->sequenceNumber = latest->sequenceNumber;
  160. if (latest->notificationDataSize == 0) return;
  161. dst->notificationData = (UA_ExtensionObject *) UA_malloc(sizeof(UA_ExtensionObject));
  162. dst->notificationData->encoding = latest->notificationData->encoding;
  163. dst->notificationData->typeId = latest->notificationData->typeId;
  164. UA_ByteString_copy((UA_String *) &(latest->notificationData->body),
  165. (UA_String *) &(dst->notificationData->body));
  166. }
  167. UA_UInt32 Subscription_deleteUnpublishedNotification(UA_UInt32 seqNo, UA_Subscription *sub) {
  168. UA_unpublishedNotification *not;
  169. UA_UInt32 deletedItems = 0;
  170. for(not=sub->unpublishedNotifications.lh_first; not != NULL; not=not->listEntry.le_next) {
  171. if(not->notification->sequenceNumber != seqNo)
  172. continue;
  173. LIST_REMOVE(not, listEntry);
  174. if(not->notification != NULL) {
  175. if (not->notification->notificationData != NULL) {
  176. if (not->notification->notificationData->body.data != NULL)
  177. UA_free(not->notification->notificationData->body.data);
  178. UA_free(not->notification->notificationData);
  179. }
  180. UA_free(not->notification);
  181. }
  182. UA_free(not);
  183. deletedItems++;
  184. }
  185. return deletedItems;
  186. }
  187. /*****************/
  188. /* MonitoredItem */
  189. /*****************/
  190. UA_MonitoredItem *UA_MonitoredItem_new() {
  191. UA_MonitoredItem *new = (UA_MonitoredItem *) UA_malloc(sizeof(UA_MonitoredItem));
  192. new->QueueSize = (UA_UInt32_BoundedValue) { .minValue = 0, .maxValue = 0, .currentValue = 0};
  193. new->LastSampled = 0;
  194. // FIXME: This is currently hardcoded;
  195. new->MonitoredItemType = MONITOREDITEM_TYPE_CHANGENOTIFY;
  196. LIST_INIT(&new->queue);
  197. LIST_INITENTRY(new, listEntry);
  198. INITPOINTER(new->monitoredNode);
  199. INITPOINTER(new->LastSampledValue.data );
  200. return new;
  201. }
  202. void MonitoredItem_delete(UA_MonitoredItem *monitoredItem) {
  203. if (monitoredItem == NULL) return;
  204. // Delete Queued Data
  205. MonitoredItem_ClearQueue(monitoredItem);
  206. // Remove from subscription list
  207. LIST_REMOVE(monitoredItem, listEntry);
  208. // Release comparison sample
  209. if(monitoredItem->LastSampledValue.data != NULL) {
  210. UA_free(monitoredItem->LastSampledValue.data);
  211. }
  212. UA_free(monitoredItem);
  213. }
  214. int MonitoredItem_QueueToDataChangeNotifications(UA_MonitoredItemNotification *dst,
  215. UA_MonitoredItem *monitoredItem) {
  216. int queueSize = 0;
  217. MonitoredItem_queuedValue *queueItem;
  218. // Count instead of relying on the items currentValue
  219. LIST_FOREACH(queueItem, &monitoredItem->queue, listEntry) {
  220. dst[queueSize].clientHandle = monitoredItem->ClientHandle;
  221. dst[queueSize].value.hasServerPicoseconds = UA_FALSE;
  222. dst[queueSize].value.hasServerTimestamp = UA_FALSE;
  223. dst[queueSize].value.serverTimestamp = UA_FALSE;
  224. dst[queueSize].value.hasSourcePicoseconds = UA_FALSE;
  225. dst[queueSize].value.hasSourceTimestamp = UA_FALSE;
  226. dst[queueSize].value.hasValue = UA_TRUE;
  227. dst[queueSize].value.status = UA_STATUSCODE_GOOD;
  228. UA_Variant_copy(&(queueItem->value), &(dst[queueSize].value.value));
  229. // Do not create variants with no type -> will make calcSizeBinary() segfault.
  230. if(dst[queueSize].value.value.type)
  231. queueSize++;
  232. }
  233. return queueSize;
  234. }
  235. void MonitoredItem_ClearQueue(UA_MonitoredItem *monitoredItem) {
  236. MonitoredItem_queuedValue *val;
  237. if (monitoredItem == NULL)
  238. return;
  239. while(monitoredItem->queue.lh_first) {
  240. val = monitoredItem->queue.lh_first;
  241. LIST_REMOVE(val, listEntry);
  242. if(val->value.data != NULL) {
  243. UA_Variant_deleteMembers(&(val->value));
  244. UA_free(val->value.data);
  245. }
  246. UA_free(val);
  247. }
  248. monitoredItem->QueueSize.currentValue = 0;
  249. }
  250. UA_Boolean MonitoredItem_CopyMonitoredValueToVariant(UA_UInt32 AttributeID, const UA_Node *src,
  251. UA_Variant *dst) {
  252. UA_Boolean samplingError = UA_TRUE;
  253. UA_DataValue sourceDataValue;
  254. const UA_VariableNode *srcAsVariableNode = (const UA_VariableNode *) src;
  255. // FIXME: Not all AttributeIDs can be monitored yet
  256. switch(AttributeID) {
  257. case UA_ATTRIBUTEID_NODEID:
  258. UA_Variant_setScalarCopy(dst, (const UA_NodeId *) &(src->nodeId), &UA_TYPES[UA_TYPES_NODEID]);
  259. samplingError = UA_FALSE;
  260. break;
  261. case UA_ATTRIBUTEID_NODECLASS:
  262. UA_Variant_setScalarCopy(dst, (const UA_Int32 *) &(src->nodeClass), &UA_TYPES[UA_TYPES_INT32]);
  263. samplingError = UA_FALSE;
  264. break;
  265. case UA_ATTRIBUTEID_BROWSENAME:
  266. UA_Variant_setScalarCopy(dst, (const UA_String *) &(src->browseName), &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
  267. samplingError = UA_FALSE;
  268. break;
  269. case UA_ATTRIBUTEID_DISPLAYNAME:
  270. UA_Variant_setScalarCopy(dst, (const UA_String *) &(src->displayName), &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
  271. samplingError = UA_FALSE;
  272. break;
  273. case UA_ATTRIBUTEID_DESCRIPTION:
  274. UA_Variant_setScalarCopy(dst, (const UA_String *) &(src->displayName), &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
  275. samplingError = UA_FALSE;
  276. break;
  277. case UA_ATTRIBUTEID_WRITEMASK:
  278. UA_Variant_setScalarCopy(dst, (const UA_String *) &(src->writeMask), &UA_TYPES[UA_TYPES_UINT32]);
  279. samplingError = UA_FALSE;
  280. break;
  281. case UA_ATTRIBUTEID_USERWRITEMASK:
  282. UA_Variant_setScalarCopy(dst, (const UA_String *) &(src->writeMask), &UA_TYPES[UA_TYPES_UINT32]);
  283. samplingError = UA_FALSE;
  284. break;
  285. case UA_ATTRIBUTEID_ISABSTRACT:
  286. break;
  287. case UA_ATTRIBUTEID_SYMMETRIC:
  288. break;
  289. case UA_ATTRIBUTEID_INVERSENAME:
  290. break;
  291. case UA_ATTRIBUTEID_CONTAINSNOLOOPS:
  292. break;
  293. case UA_ATTRIBUTEID_EVENTNOTIFIER:
  294. break;
  295. case UA_ATTRIBUTEID_VALUE:
  296. if (src->nodeClass == UA_NODECLASS_VARIABLE) {
  297. if ( srcAsVariableNode->valueSource == UA_VALUESOURCE_VARIANT) {
  298. UA_Variant_copy( (const UA_Variant *) &((const UA_VariableNode *) src)->value, dst);
  299. samplingError = UA_FALSE;
  300. }
  301. else if (srcAsVariableNode->valueSource == UA_VALUESOURCE_DATASOURCE) {
  302. // todo: handle numeric ranges
  303. if (srcAsVariableNode->value.dataSource.read(((const UA_VariableNode *) src)->value.dataSource.handle, (UA_Boolean) UA_TRUE, UA_NULL, &sourceDataValue) == UA_STATUSCODE_GOOD) {
  304. UA_Variant_copy( (const UA_Variant *) &(sourceDataValue.value), dst);
  305. if (sourceDataValue.value.data != NULL) {
  306. UA_deleteMembers(sourceDataValue.value.data, sourceDataValue.value.type);
  307. UA_free(sourceDataValue.value.data);
  308. sourceDataValue.value.data = NULL;
  309. }
  310. UA_DataValue_deleteMembers(&sourceDataValue);
  311. samplingError = UA_FALSE;
  312. }
  313. }
  314. }
  315. break;
  316. case UA_ATTRIBUTEID_DATATYPE:
  317. break;
  318. case UA_ATTRIBUTEID_VALUERANK:
  319. break;
  320. case UA_ATTRIBUTEID_ARRAYDIMENSIONS:
  321. break;
  322. case UA_ATTRIBUTEID_ACCESSLEVEL:
  323. break;
  324. case UA_ATTRIBUTEID_USERACCESSLEVEL:
  325. break;
  326. case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
  327. break;
  328. case UA_ATTRIBUTEID_HISTORIZING:
  329. break;
  330. case UA_ATTRIBUTEID_EXECUTABLE:
  331. break;
  332. case UA_ATTRIBUTEID_USEREXECUTABLE:
  333. break;
  334. default:
  335. break;
  336. }
  337. return samplingError;
  338. }
  339. void MonitoredItem_QueuePushDataValue(UA_MonitoredItem *monitoredItem) {
  340. MonitoredItem_queuedValue *newvalue = NULL, *queueItem = NULL;
  341. UA_Boolean samplingError = UA_TRUE;
  342. UA_ByteString newValueAsByteString = { .length=0, .data=NULL };
  343. size_t encodingOffset = 0;
  344. if(!monitoredItem || monitoredItem->LastSampled + monitoredItem->SamplingInterval > UA_DateTime_now())
  345. return;
  346. // FIXME: Actively suppress non change value based monitoring. There should be
  347. // another function to handle status and events.
  348. if (monitoredItem->MonitoredItemType != MONITOREDITEM_TYPE_CHANGENOTIFY)
  349. return;
  350. newvalue = (MonitoredItem_queuedValue *) UA_malloc(sizeof(MonitoredItem_queuedValue));
  351. LIST_INITENTRY(newvalue,listEntry);
  352. newvalue->value.arrayLength = 0;
  353. newvalue->value.arrayDimensionsSize = 0;
  354. newvalue->value.arrayDimensions = NULL;
  355. newvalue->value.data = NULL;
  356. newvalue->value.type = NULL;
  357. samplingError = MonitoredItem_CopyMonitoredValueToVariant(monitoredItem->AttributeID, monitoredItem->monitoredNode, &(newvalue->value));
  358. if ((monitoredItem->QueueSize).currentValue >= (monitoredItem->QueueSize).maxValue) {
  359. if (newvalue->value.type != NULL && monitoredItem->DiscardOldest == UA_TRUE && monitoredItem->queue.lh_first != NULL ) {
  360. for(queueItem = monitoredItem->queue.lh_first; queueItem->listEntry.le_next != NULL; queueItem = queueItem->listEntry.le_next) {}
  361. LIST_REMOVE(queueItem, listEntry);
  362. UA_free(queueItem);
  363. (monitoredItem->QueueSize).currentValue--;
  364. }
  365. else {
  366. // We cannot remove the oldest value and theres no queue space left. We're done here.
  367. UA_free(newvalue);
  368. return;
  369. }
  370. }
  371. // Only add a value if we have sampled it correctly and it fits into the queue;
  372. if ( samplingError != UA_FALSE || newvalue->value.type == NULL || (monitoredItem->QueueSize).currentValue >= (monitoredItem->QueueSize).maxValue) {
  373. if (newvalue->value.data != NULL ) {
  374. UA_Variant_deleteMembers(&(newvalue->value));
  375. UA_free(&(newvalue->value));
  376. }
  377. UA_free(newvalue);
  378. return;
  379. }
  380. newValueAsByteString.length = UA_calcSizeBinary((const void *) &(newvalue->value), &UA_TYPES[UA_TYPES_VARIANT]);
  381. newValueAsByteString.data = UA_malloc(newValueAsByteString.length);
  382. UA_encodeBinary((const void *) &(newvalue->value), &UA_TYPES[UA_TYPES_VARIANT], &(newValueAsByteString), &encodingOffset );
  383. if(monitoredItem->LastSampledValue.data == NULL) {
  384. UA_ByteString_copy((UA_String *) &newValueAsByteString, (UA_String *) &(monitoredItem->LastSampledValue));
  385. LIST_INSERT_HEAD(&monitoredItem->queue, newvalue, listEntry);
  386. (monitoredItem->QueueSize).currentValue++;
  387. monitoredItem->LastSampled = UA_DateTime_now();
  388. UA_free(newValueAsByteString.data);
  389. }
  390. else {
  391. if (UA_String_equal((UA_String *) &newValueAsByteString, (UA_String *) &(monitoredItem->LastSampledValue)) == UA_TRUE) {
  392. if (newvalue->value.data != NULL ) {
  393. UA_Variant_deleteMembers(&(newvalue->value));
  394. UA_free(&(newvalue->value)); // Same addr as newvalue
  395. }
  396. else {
  397. UA_free(newvalue); // Same address as newvalue->value
  398. }
  399. UA_free(newValueAsByteString.data);
  400. return;
  401. }
  402. UA_ByteString_copy((UA_String *) &newValueAsByteString, (UA_String *) &(monitoredItem->LastSampledValue));
  403. LIST_INSERT_HEAD(&monitoredItem->queue, newvalue, listEntry);
  404. (monitoredItem->QueueSize).currentValue++;
  405. monitoredItem->LastSampled = UA_DateTime_now();
  406. UA_free(newValueAsByteString.data);
  407. }
  408. return;
  409. }
  410. #endif