/src/open62541_15/src/server/ua_subscription_datachange.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2017-2020 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
7 | | * Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH |
8 | | * Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA |
9 | | * Copyright 2018 (c) Fabian Arndt, Root-Core |
10 | | */ |
11 | | |
12 | | #include "ua_server_internal.h" |
13 | | #include "ua_subscription.h" |
14 | | #include "../ua_types_encoding_binary.h" |
15 | | |
16 | | #ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */ |
17 | | |
18 | | /* Detect value changes outside the deadband. |
19 | | * |
20 | | * Integer types: compute the absolute difference in a wide unsigned type |
21 | | * (UA_UInt64) first, then widen only the *difference* to UA_Double for the |
22 | | * deadband comparison. This avoids both signed integer overflow (e.g. |
23 | | * (UA_SByte)(100 - (-50)) wraps to -106 instead of 150) and the 2^53 |
24 | | * precision loss that comes from casting the operands to UA_Double before |
25 | | * subtracting: two huge but close Int64/UInt64 values would each round to a |
26 | | * nearby representable double and the subtraction would accumulate the |
27 | | * rounding error. Computing the exact integer magnitude first keeps the |
28 | | * difference exact (it fits in UA_Double's 53-bit mantissa for any realistic |
29 | | * deadband), so only the (usually small) delta is widened. The (UA_UInt64) |
30 | | * cast of a negative signed value yields the correct unsigned magnitude on |
31 | | * every platform. */ |
32 | 0 | #define UA_DETECT_DEADBAND(TYPE) do { \ |
33 | 0 | TYPE v1 = *(const TYPE*)data1; \ |
34 | 0 | TYPE v2 = *(const TYPE*)data2; \ |
35 | 0 | UA_UInt64 mag = (v1 > v2) ? (UA_UInt64)v1 - (UA_UInt64)v2 \ |
36 | 0 | : (UA_UInt64)v2 - (UA_UInt64)v1; \ |
37 | 0 | UA_Double diff = (UA_Double)mag; \ |
38 | 0 | return (diff > deadband); \ |
39 | 0 | } while(false) |
40 | | |
41 | | /* Floating-point types: the integer magnitude approach would truncate the |
42 | | * fractional part, so subtract directly in UA_Double. This is safe from |
43 | | * overflow and exact (Float widens to Double without loss). */ |
44 | 0 | #define UA_DETECT_DEADBAND_FLOAT(TYPE) do { \ |
45 | 0 | TYPE v1 = *(const TYPE*)data1; \ |
46 | 0 | TYPE v2 = *(const TYPE*)data2; \ |
47 | 0 | UA_Double diff = (v1 > v2) ? (UA_Double)v1 - (UA_Double)v2 \ |
48 | 0 | : (UA_Double)v2 - (UA_Double)v1; \ |
49 | 0 | return (diff > deadband); \ |
50 | 0 | } while(false) |
51 | | |
52 | | static UA_Boolean |
53 | | detectScalarDeadBand(const void *data1, const void *data2, |
54 | 0 | const UA_DataType *type, const UA_Double deadband) { |
55 | 0 | if(type->typeKind == UA_DATATYPEKIND_SBYTE) { |
56 | 0 | UA_DETECT_DEADBAND(UA_SByte); |
57 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_BYTE) { |
58 | 0 | UA_DETECT_DEADBAND(UA_Byte); |
59 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_INT16) { |
60 | 0 | UA_DETECT_DEADBAND(UA_Int16); |
61 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_UINT16) { |
62 | 0 | UA_DETECT_DEADBAND(UA_UInt16); |
63 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_INT32) { |
64 | 0 | UA_DETECT_DEADBAND(UA_Int32); |
65 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_UINT32) { |
66 | 0 | UA_DETECT_DEADBAND(UA_UInt32); |
67 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_INT64) { |
68 | 0 | UA_DETECT_DEADBAND(UA_Int64); |
69 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_UINT64) { |
70 | 0 | UA_DETECT_DEADBAND(UA_UInt64); |
71 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_FLOAT) { |
72 | 0 | UA_DETECT_DEADBAND_FLOAT(UA_Float); |
73 | 0 | } else if(type->typeKind == UA_DATATYPEKIND_DOUBLE) { |
74 | 0 | UA_DETECT_DEADBAND_FLOAT(UA_Double); |
75 | 0 | } else { |
76 | 0 | return false; /* Not a known numerical type */ |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | | static UA_Boolean |
81 | | detectVariantDeadband(const UA_Variant *value, const UA_Variant *oldValue, |
82 | 0 | const UA_Double deadbandValue) { |
83 | | /* Be careful to avoid a NULL access. We could have the value a scalar and |
84 | | * oldValue an empty array. Both define a type and arrayLength == 0. */ |
85 | 0 | if(value->arrayLength != oldValue->arrayLength) |
86 | 0 | return true; |
87 | 0 | if(value->type != oldValue->type) |
88 | 0 | return true; |
89 | 0 | if(UA_Variant_isScalar(value) != UA_Variant_isScalar(oldValue)) |
90 | 0 | return true; |
91 | | |
92 | | /* Treat scalars as an array of length 1 and iterate */ |
93 | 0 | size_t length = 1; |
94 | 0 | if(!UA_Variant_isScalar(value)) |
95 | 0 | length = value->arrayLength; |
96 | |
|
97 | 0 | uintptr_t data = (uintptr_t)value->data; |
98 | 0 | uintptr_t oldData = (uintptr_t)oldValue->data; |
99 | 0 | UA_UInt32 memSize = value->type->memSize; |
100 | 0 | for(size_t i = 0; i < length; ++i) { |
101 | 0 | if(detectScalarDeadBand((const void*)data, (const void*)oldData, |
102 | 0 | value->type, deadbandValue)) |
103 | 0 | return true; |
104 | 0 | data += memSize; |
105 | 0 | oldData += memSize; |
106 | 0 | } |
107 | 0 | return false; |
108 | 0 | } |
109 | | |
110 | | static UA_Boolean |
111 | 0 | detectValueChange(UA_Server *server, UA_MonitoredItem *mon, const UA_DataValue *dv) { |
112 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
113 | | |
114 | | /* Status changes are always reported */ |
115 | 0 | if(dv->hasStatus != mon->lastValue.hasStatus || |
116 | 0 | dv->status != mon->lastValue.status) { |
117 | 0 | return true; |
118 | 0 | } |
119 | | |
120 | | /* Default trigger is Status + Value */ |
121 | 0 | UA_DataChangeTrigger trigger = UA_DATACHANGETRIGGER_STATUSVALUE; |
122 | | |
123 | | /* Use the configured trigger */ |
124 | 0 | const UA_DataChangeFilter *dcf = NULL; |
125 | 0 | const UA_ExtensionObject *filter = &mon->parameters.filter; |
126 | 0 | if(filter->content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) { |
127 | 0 | dcf = (UA_DataChangeFilter*)filter->content.decoded.data; |
128 | 0 | trigger = dcf->trigger; |
129 | 0 | } |
130 | | |
131 | | /* The status was already tested above */ |
132 | 0 | if(trigger == UA_DATACHANGETRIGGER_STATUS) |
133 | 0 | return false; |
134 | | |
135 | 0 | UA_assert(trigger == UA_DATACHANGETRIGGER_STATUSVALUE || |
136 | 0 | trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP); |
137 | | |
138 | | /* Can we compare values? */ |
139 | 0 | if(dv->hasValue != mon->lastValue.hasValue) |
140 | 0 | return true; |
141 | | |
142 | | /* Test absolute deadband */ |
143 | 0 | if(dcf && dcf->deadbandType == UA_DEADBANDTYPE_ABSOLUTE && |
144 | 0 | dv->value.type != NULL && UA_DataType_isNumeric(dv->value.type)) |
145 | 0 | return detectVariantDeadband(&dv->value, &mon->lastValue.value, |
146 | 0 | dcf->deadbandValue); |
147 | | |
148 | | /* Compare the source timestamp if the trigger requires that */ |
149 | 0 | if(trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) { |
150 | 0 | if(dv->hasSourceTimestamp != mon->lastValue.hasSourceTimestamp) |
151 | 0 | return true; |
152 | 0 | if(dv->hasSourceTimestamp && |
153 | 0 | dv->sourceTimestamp != mon->lastValue.sourceTimestamp) |
154 | 0 | return true; |
155 | 0 | } |
156 | | |
157 | | /* Has the value changed? */ |
158 | 0 | return !UA_equal(&dv->value, &mon->lastValue.value, |
159 | 0 | &UA_TYPES[UA_TYPES_VARIANT]); |
160 | 0 | } |
161 | | |
162 | | UA_StatusCode |
163 | | UA_MonitoredItem_createDataChangeNotification(UA_Server *server, UA_MonitoredItem *mon, |
164 | 0 | const UA_DataValue *dv) { |
165 | | /* Copy the value */ |
166 | 0 | UA_DataValue valueCopy; |
167 | 0 | UA_StatusCode retval = UA_DataValue_copy(dv, &valueCopy); |
168 | 0 | if(retval != UA_STATUSCODE_GOOD) |
169 | 0 | return retval; |
170 | | |
171 | | /* Allocate a new notification */ |
172 | 0 | UA_Notification *n = UA_Notification_new(); |
173 | 0 | if(!n) { |
174 | 0 | UA_DataValue_clear(&valueCopy); |
175 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
176 | 0 | } |
177 | | |
178 | | /* Prepare and enqueue the notification */ |
179 | 0 | n->mon = mon; |
180 | 0 | n->data.dataChange.value = valueCopy; |
181 | 0 | n->data.dataChange.clientHandle = mon->parameters.clientHandle; |
182 | 0 | UA_Notification_enqueueAndTrigger(server, n); |
183 | 0 | return UA_STATUSCODE_GOOD; |
184 | 0 | } |
185 | | |
186 | | void |
187 | | UA_MonitoredItem_processSampledValue(UA_Server *server, UA_MonitoredItem *mon, |
188 | 0 | UA_DataValue *value) { |
189 | 0 | UA_assert(mon->itemToMonitor.attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER); |
190 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
191 | | |
192 | | /* Has the value changed (with the filters applied)? */ |
193 | 0 | UA_Boolean changed = detectValueChange(server, mon, value); |
194 | 0 | if(!changed) { |
195 | 0 | UA_LOG_DEBUG_SUBSCRIPTION(server->config.logging, mon->subscription, |
196 | 0 | "MonitoredItem %" PRIi32 " | " |
197 | 0 | "The value has not changed", mon->monitoredItemId); |
198 | 0 | UA_DataValue_clear(value); |
199 | 0 | return; |
200 | 0 | } |
201 | | |
202 | | /* Prepare a notification and enqueue it */ |
203 | 0 | UA_StatusCode res = |
204 | 0 | UA_MonitoredItem_createDataChangeNotification(server, mon, value); |
205 | 0 | if(res != UA_STATUSCODE_GOOD) { |
206 | 0 | UA_LOG_WARNING_SUBSCRIPTION(server->config.logging, mon->subscription, |
207 | 0 | "MonitoredItem %" PRIi32 " | " |
208 | 0 | "Processing the sample returned the statuscode %s", |
209 | 0 | mon->monitoredItemId, UA_StatusCode_name(res)); |
210 | 0 | UA_DataValue_clear(value); |
211 | 0 | return; |
212 | 0 | } |
213 | | |
214 | | /* Move/store the value for filter comparison and TransferSubscription */ |
215 | 0 | UA_DataValue_clear(&mon->lastValue); |
216 | 0 | mon->lastValue = *value; |
217 | | |
218 | | /* Call the local callback if the MonitoredItem is not attached to a |
219 | | * subscription. Do this at the very end. Because the callback might delete |
220 | | * the subscription. */ |
221 | 0 | if(!mon->subscription) { |
222 | 0 | UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*) mon; |
223 | 0 | void *nodeContext = NULL; |
224 | 0 | getNodeContext(server, mon->itemToMonitor.nodeId, &nodeContext); |
225 | 0 | localMon->callback.dataChangeCallback(server, |
226 | 0 | mon->monitoredItemId, localMon->context, |
227 | 0 | &mon->itemToMonitor.nodeId, nodeContext, |
228 | 0 | mon->itemToMonitor.attributeId, value); |
229 | 0 | } |
230 | 0 | } |
231 | | |
232 | | /* We know the result is a deep-copy. So we can abuse the const result-pointer |
233 | | * and take ownership of the value. */ |
234 | | static void |
235 | | processMonitoredItemAsyncRead(UA_Server *server, UA_MonitoredItem *mon, |
236 | 0 | const UA_DataValue *result) { |
237 | 0 | mon->outstandingAsyncReads--; |
238 | 0 | UA_DataValue *mut_result = (UA_DataValue*)(uintptr_t)result; |
239 | 0 | if(mut_result->status == UA_STATUSCODE_BADREQUESTCANCELLEDBYREQUEST) |
240 | 0 | return; /* Controlled shut-down */ |
241 | 0 | UA_MonitoredItem_processSampledValue(server, mon, mut_result); |
242 | 0 | UA_DataValue_init(mut_result); |
243 | 0 | } |
244 | | |
245 | | void |
246 | 0 | UA_MonitoredItem_sample(UA_Server *server, UA_MonitoredItem *mon) { |
247 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
248 | 0 | UA_assert(mon->itemToMonitor.attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER); |
249 | | |
250 | 0 | UA_Subscription *sub = mon->subscription; |
251 | 0 | UA_LOG_DEBUG_SUBSCRIPTION(server->config.logging, sub, "MonitoredItem %" PRIi32 |
252 | 0 | " | Sample callback called", mon->monitoredItemId); |
253 | | |
254 | | /* Sample the current value. |
255 | | * sub->session can be NULL when the subscription is detached. Then |
256 | | * readWithSession returns the error-code BADUSERACCESSDENIED. */ |
257 | 0 | UA_Session *session = (sub) ? sub->session : &server->adminSession; |
258 | | |
259 | | /* Read the value possibly asynchronous */ |
260 | 0 | UA_StatusCode res = UA_STATUSCODE_BADTOOMANYOPERATIONS; |
261 | 0 | if(UA_LIKELY(mon->outstandingAsyncReads < UA_MONITOREDITEM_ASYNC_MAX)) { |
262 | 0 | res = read_async(server, session, &mon->itemToMonitor, mon->timestampsToReturn, |
263 | 0 | (UA_ServerAsyncReadResultCallback)processMonitoredItemAsyncRead, mon, 0); |
264 | 0 | } |
265 | 0 | if(res == UA_STATUSCODE_GOOD) { |
266 | 0 | mon->outstandingAsyncReads++; |
267 | 0 | } else { |
268 | | /* Reading failed, process with the StatusCode */ |
269 | 0 | UA_DataValue dv; |
270 | 0 | UA_DataValue_init(&dv); |
271 | | dv.hasStatus = true; |
272 | 0 | dv.status = res; |
273 | 0 | UA_MonitoredItem_processSampledValue(server, mon, &dv); |
274 | 0 | } |
275 | 0 | } |
276 | | |
277 | | #endif /* UA_ENABLE_SUBSCRIPTIONS */ |