/src/open62541/src/pubsub/ua_pubsub_ns0.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 (c) 2017-2025 Fraunhofer IOSB (Author: Andreas Ebner) |
6 | | * Copyright (c) 2019-2021 Kalycito Infotech Private Limited |
7 | | * Copyright (c) 2020 Yannick Wallerer, Siemens AG |
8 | | * Copyright (c) 2020-2022 Thomas Fischer, Siemens AG |
9 | | * Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil) |
10 | | * Copyright (c) 2025 Fraunhofer IOSB (Author: Julius Pfrommer) |
11 | | */ |
12 | | |
13 | | #include "ua_pubsub_internal.h" |
14 | | |
15 | | #ifdef UA_ENABLE_PUBSUB_INFORMATIONMODEL /* conditional compilation */ |
16 | | |
17 | | typedef struct { |
18 | | UA_NodeId parentNodeId; |
19 | | UA_UInt32 parentClassifier; |
20 | | UA_UInt32 elementClassiefier; |
21 | | } UA_NodePropertyContext; |
22 | | |
23 | | static UA_StatusCode |
24 | | writePubSubNs0VariableArray(UA_Server *server, const UA_NodeId id, void *v, |
25 | 553 | size_t length, const UA_DataType *type) { |
26 | 553 | UA_LOCK_ASSERT(&server->serviceMutex); |
27 | 553 | UA_Variant var; |
28 | 553 | UA_Variant_init(&var); |
29 | 553 | UA_Variant_setArray(&var, v, length, type); |
30 | 553 | return writeValueAttribute(server, id, &var); |
31 | 553 | } |
32 | | |
33 | | static UA_NodeId |
34 | | findSingleChildNode(UA_Server *server, UA_QualifiedName targetName, |
35 | 0 | UA_NodeId referenceTypeId, UA_NodeId startingNode){ |
36 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
37 | 0 | UA_NodeId resultNodeId; |
38 | 0 | UA_RelativePathElement rpe; |
39 | 0 | UA_RelativePathElement_init(&rpe); |
40 | 0 | rpe.referenceTypeId = referenceTypeId; |
41 | 0 | rpe.isInverse = false; |
42 | 0 | rpe.includeSubtypes = false; |
43 | 0 | rpe.targetName = targetName; |
44 | 0 | UA_BrowsePath bp; |
45 | 0 | UA_BrowsePath_init(&bp); |
46 | 0 | bp.startingNode = startingNode; |
47 | 0 | bp.relativePath.elementsSize = 1; |
48 | 0 | bp.relativePath.elements = &rpe; |
49 | 0 | UA_BrowsePathResult bpr = translateBrowsePathToNodeIds(server, &bp); |
50 | 0 | if(bpr.statusCode != UA_STATUSCODE_GOOD || |
51 | 0 | bpr.targetsSize < 1) |
52 | 0 | return UA_NODEID_NULL; |
53 | 0 | UA_StatusCode res = UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, &resultNodeId); |
54 | 0 | if(res != UA_STATUSCODE_GOOD){ |
55 | 0 | UA_BrowsePathResult_clear(&bpr); |
56 | 0 | return UA_NODEID_NULL; |
57 | 0 | } |
58 | 0 | UA_BrowsePathResult_clear(&bpr); |
59 | 0 | return resultNodeId; |
60 | 0 | } |
61 | | |
62 | | static UA_StatusCode |
63 | | findPubSubComponentFromStatus(UA_Server *server, const UA_NodeId *statusObjectId, |
64 | | UA_NodeId *componentNodeId, UA_PubSubComponentType *componentType, |
65 | 0 | void **component, UA_Boolean *isPublishSubscribeObject) { |
66 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
67 | |
|
68 | 0 | *isPublishSubscribeObject = false; |
69 | | /* Find the parent PubSub component by browsing up from the Status object */ |
70 | 0 | UA_BrowseDescription bd; |
71 | 0 | UA_BrowseDescription_init(&bd); |
72 | 0 | bd.nodeId = *statusObjectId; |
73 | 0 | bd.browseDirection = UA_BROWSEDIRECTION_INVERSE; |
74 | 0 | bd.referenceTypeId = UA_NS0ID(HASCOMPONENT); |
75 | 0 | bd.includeSubtypes = false; |
76 | 0 | bd.nodeClassMask = UA_NODECLASS_OBJECT; |
77 | 0 | bd.resultMask = UA_BROWSERESULTMASK_REFERENCETYPEID | UA_BROWSERESULTMASK_TYPEDEFINITION; |
78 | |
|
79 | 0 | UA_BrowseResult br = UA_Server_browse(server, 0, &bd); |
80 | 0 | if(br.statusCode != UA_STATUSCODE_GOOD || br.referencesSize == 0) { |
81 | 0 | UA_BrowseResult_clear(&br); |
82 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
83 | 0 | } |
84 | | |
85 | 0 | *componentNodeId = br.references[0].nodeId.nodeId; |
86 | 0 | UA_NodeId parentTypeId = br.references[0].typeDefinition.nodeId; |
87 | 0 | UA_BrowseResult_clear(&br); |
88 | |
|
89 | 0 | UA_PubSubManager *psm = getPSM(server); |
90 | 0 | if(!psm) |
91 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
92 | | |
93 | | /* Identify component type and find the component */ |
94 | 0 | UA_NodeId pubsubconnectionTypeId = UA_NS0ID(PUBSUBCONNECTIONTYPE); |
95 | 0 | UA_NodeId writergroupTypeId = UA_NS0ID(WRITERGROUPTYPE); |
96 | 0 | UA_NodeId readergroupTypeId = UA_NS0ID(READERGROUPTYPE); |
97 | 0 | UA_NodeId datasetreaderTypeId = UA_NS0ID(DATASETREADERTYPE); |
98 | 0 | UA_NodeId datasetwriterTypeId = UA_NS0ID(DATASETWRITERTYPE); |
99 | 0 | UA_NodeId publishsubscribeTypeId = UA_NS0ID(PUBLISHSUBSCRIBETYPE); |
100 | |
|
101 | 0 | if(UA_NodeId_equal(&parentTypeId, &publishsubscribeTypeId)) { |
102 | 0 | *isPublishSubscribeObject = true; |
103 | 0 | *componentType = UA_PUBSUBCOMPONENT_CONNECTION; |
104 | 0 | *component = psm; |
105 | 0 | return UA_STATUSCODE_GOOD; |
106 | 0 | } else if(UA_NodeId_equal(&parentTypeId, &pubsubconnectionTypeId)) { |
107 | 0 | *componentType = UA_PUBSUBCOMPONENT_CONNECTION; |
108 | 0 | *component = UA_PubSubConnection_find(psm, *componentNodeId); |
109 | 0 | } else if(UA_NodeId_equal(&parentTypeId, &writergroupTypeId)) { |
110 | 0 | *componentType = UA_PUBSUBCOMPONENT_WRITERGROUP; |
111 | 0 | *component = UA_WriterGroup_find(psm, *componentNodeId); |
112 | 0 | } else if(UA_NodeId_equal(&parentTypeId, &readergroupTypeId)) { |
113 | 0 | *componentType = UA_PUBSUBCOMPONENT_READERGROUP; |
114 | 0 | *component = UA_ReaderGroup_find(psm, *componentNodeId); |
115 | 0 | } else if(UA_NodeId_equal(&parentTypeId, &datasetreaderTypeId)) { |
116 | 0 | *componentType = UA_PUBSUBCOMPONENT_DATASETREADER; |
117 | 0 | *component = UA_DataSetReader_find(psm, *componentNodeId); |
118 | 0 | } else if(UA_NodeId_equal(&parentTypeId, &datasetwriterTypeId)) { |
119 | 0 | *componentType = UA_PUBSUBCOMPONENT_DATASETWRITER; |
120 | 0 | *component = UA_DataSetWriter_find(psm, *componentNodeId); |
121 | 0 | } else { |
122 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
123 | 0 | } |
124 | | |
125 | 0 | if(!*component) |
126 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
127 | | |
128 | 0 | return UA_STATUSCODE_GOOD; |
129 | 0 | } |
130 | | |
131 | | static UA_StatusCode |
132 | | pubSubStateVariableDataSourceRead(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, |
133 | | const UA_NodeId *nodeid, void *context, UA_Boolean includeSourceTimeStamp, |
134 | 0 | const UA_NumericRange *range, UA_DataValue *value) { |
135 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
136 | | |
137 | | /* Find the parent Status object by browsing inverse from State variable */ |
138 | 0 | UA_BrowseDescription bd; |
139 | 0 | UA_BrowseDescription_init(&bd); |
140 | 0 | bd.nodeId = *nodeid; |
141 | 0 | bd.browseDirection = UA_BROWSEDIRECTION_INVERSE; |
142 | 0 | bd.referenceTypeId = UA_NS0ID(HASCOMPONENT); |
143 | 0 | bd.includeSubtypes = false; |
144 | 0 | bd.nodeClassMask = UA_NODECLASS_OBJECT; |
145 | 0 | bd.resultMask = UA_BROWSERESULTMASK_REFERENCETYPEID; |
146 | |
|
147 | 0 | UA_BrowseResult br = UA_Server_browse(server, 0, &bd); |
148 | 0 | if(br.statusCode != UA_STATUSCODE_GOOD || br.referencesSize == 0) { |
149 | 0 | UA_BrowseResult_clear(&br); |
150 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
151 | 0 | } |
152 | | |
153 | 0 | UA_NodeId statusObjectId = br.references[0].nodeId.nodeId; |
154 | 0 | UA_BrowseResult_clear(&br); |
155 | |
|
156 | 0 | UA_NodeId componentNodeId; |
157 | 0 | UA_PubSubComponentType componentType; |
158 | 0 | void *component = NULL; |
159 | 0 | UA_Boolean isPublishSubscribeObject = false; |
160 | | |
161 | 0 | UA_StatusCode retVal = findPubSubComponentFromStatus(server, &statusObjectId, |
162 | 0 | &componentNodeId, &componentType, &component, &isPublishSubscribeObject); |
163 | 0 | if(retVal != UA_STATUSCODE_GOOD) |
164 | 0 | return retVal; |
165 | | |
166 | 0 | UA_PubSubState state = UA_PUBSUBSTATE_DISABLED; |
167 | | |
168 | 0 | if(isPublishSubscribeObject) { |
169 | 0 | UA_PubSubManager *psm = (UA_PubSubManager*)component; |
170 | 0 | state = (psm->sc.state == UA_LIFECYCLESTATE_STARTED) ? |
171 | 0 | UA_PUBSUBSTATE_OPERATIONAL : UA_PUBSUBSTATE_DISABLED; |
172 | 0 | } else { |
173 | 0 | switch(componentType) { |
174 | 0 | case UA_PUBSUBCOMPONENT_CONNECTION: |
175 | 0 | state = ((UA_PubSubConnection*)component)->head.state; |
176 | 0 | break; |
177 | 0 | case UA_PUBSUBCOMPONENT_WRITERGROUP: |
178 | 0 | state = ((UA_WriterGroup*)component)->head.state; |
179 | 0 | break; |
180 | 0 | case UA_PUBSUBCOMPONENT_READERGROUP: |
181 | 0 | state = ((UA_ReaderGroup*)component)->head.state; |
182 | 0 | break; |
183 | 0 | case UA_PUBSUBCOMPONENT_DATASETREADER: |
184 | 0 | state = ((UA_DataSetReader*)component)->head.state; |
185 | 0 | break; |
186 | 0 | case UA_PUBSUBCOMPONENT_DATASETWRITER: |
187 | 0 | state = ((UA_DataSetWriter*)component)->head.state; |
188 | 0 | break; |
189 | 0 | default: |
190 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | 0 | value->hasValue = true; |
195 | 0 | return UA_Variant_setScalarCopy(&value->value, &state, &UA_TYPES[UA_TYPES_PUBSUBSTATE]); |
196 | 0 | } |
197 | | |
198 | | static UA_StatusCode |
199 | | enablePubSubObjectAction(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, |
200 | | const UA_NodeId *methodId, void *methodContext, |
201 | | const UA_NodeId *objectId, void *objectContext, |
202 | | size_t inputSize, const UA_Variant *input, |
203 | 0 | size_t outputSize, UA_Variant *output) { |
204 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
205 | | |
206 | | /* Find the State variable within the Status object */ |
207 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
208 | 0 | UA_NS0ID(HASCOMPONENT), *objectId); |
209 | 0 | if(UA_NodeId_isNull(&stateNodeId)) |
210 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
211 | | |
212 | | /* Use helper function to identify and find the PubSub component */ |
213 | 0 | UA_NodeId componentNodeId; |
214 | 0 | UA_PubSubComponentType componentType; |
215 | 0 | void *component = NULL; |
216 | 0 | UA_Boolean isPublishSubscribeObject = false; |
217 | | |
218 | 0 | UA_StatusCode retVal = findPubSubComponentFromStatus(server, objectId, |
219 | 0 | &componentNodeId, &componentType, &component, &isPublishSubscribeObject); |
220 | 0 | if(retVal != UA_STATUSCODE_GOOD) |
221 | 0 | return retVal; |
222 | | |
223 | 0 | UA_PubSubManager *psm = getPSM(server); |
224 | 0 | if(!psm) |
225 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
226 | | |
227 | 0 | if(isPublishSubscribeObject) { |
228 | 0 | if(psm->sc.state != UA_LIFECYCLESTATE_STOPPED) |
229 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
230 | 0 | UA_PubSubManager_setState(psm, UA_LIFECYCLESTATE_STARTED); |
231 | 0 | return UA_STATUSCODE_GOOD; |
232 | 0 | } |
233 | | |
234 | 0 | switch(componentType) { |
235 | 0 | case UA_PUBSUBCOMPONENT_CONNECTION: { |
236 | 0 | UA_PubSubConnection *conn = (UA_PubSubConnection*)component; |
237 | | /* OPC UA Standard: "The Server shall reject Enable Method calls if the current State is not Disabled." */ |
238 | 0 | if(conn->head.state != UA_PUBSUBSTATE_DISABLED) |
239 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
240 | 0 | retVal = UA_PubSubConnection_setPubSubState(psm, conn, UA_PUBSUBSTATE_OPERATIONAL); |
241 | 0 | break; |
242 | 0 | } |
243 | 0 | case UA_PUBSUBCOMPONENT_WRITERGROUP: { |
244 | 0 | UA_WriterGroup *wg = (UA_WriterGroup*)component; |
245 | 0 | if(wg->head.state != UA_PUBSUBSTATE_DISABLED) |
246 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
247 | 0 | retVal = UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_OPERATIONAL); |
248 | 0 | break; |
249 | 0 | } |
250 | 0 | case UA_PUBSUBCOMPONENT_READERGROUP: { |
251 | 0 | UA_ReaderGroup *rg = (UA_ReaderGroup*)component; |
252 | 0 | if(rg->head.state != UA_PUBSUBSTATE_DISABLED) |
253 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
254 | 0 | retVal = UA_ReaderGroup_setPubSubState(psm, rg, UA_PUBSUBSTATE_OPERATIONAL); |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | case UA_PUBSUBCOMPONENT_DATASETREADER: { |
258 | 0 | UA_DataSetReader *dsr = (UA_DataSetReader*)component; |
259 | 0 | if(dsr->head.state != UA_PUBSUBSTATE_DISABLED) |
260 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
261 | 0 | retVal = UA_DataSetReader_setPubSubState(psm, dsr, UA_PUBSUBSTATE_OPERATIONAL, UA_STATUSCODE_GOOD); |
262 | 0 | break; |
263 | 0 | } |
264 | 0 | case UA_PUBSUBCOMPONENT_DATASETWRITER: { |
265 | 0 | UA_DataSetWriter *dsw = (UA_DataSetWriter*)component; |
266 | 0 | if(dsw->head.state != UA_PUBSUBSTATE_DISABLED) |
267 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
268 | 0 | retVal = UA_DataSetWriter_setPubSubState(psm, dsw, UA_PUBSUBSTATE_OPERATIONAL); |
269 | 0 | break; |
270 | 0 | } |
271 | 0 | default: |
272 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
273 | 0 | } |
274 | | |
275 | 0 | return retVal; |
276 | 0 | } |
277 | | |
278 | | static UA_StatusCode |
279 | | disablePubSubObjectAction(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, |
280 | | const UA_NodeId *methodId, void *methodContext, |
281 | | const UA_NodeId *objectId, void *objectContext, |
282 | | size_t inputSize, const UA_Variant *input, |
283 | 0 | size_t outputSize, UA_Variant *output) { |
284 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
285 | | |
286 | | /* Find the State variable within the Status object */ |
287 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
288 | 0 | UA_NS0ID(HASCOMPONENT), *objectId); |
289 | 0 | if(UA_NodeId_isNull(&stateNodeId)) |
290 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
291 | | |
292 | | /* Use helper function to identify and find the PubSub component */ |
293 | 0 | UA_NodeId componentNodeId; |
294 | 0 | UA_PubSubComponentType componentType; |
295 | 0 | void *component = NULL; |
296 | 0 | UA_Boolean isPublishSubscribeObject = false; |
297 | | |
298 | 0 | UA_StatusCode retVal = findPubSubComponentFromStatus(server, objectId, |
299 | 0 | &componentNodeId, &componentType, &component, &isPublishSubscribeObject); |
300 | 0 | if(retVal != UA_STATUSCODE_GOOD) |
301 | 0 | return retVal; |
302 | | |
303 | 0 | UA_PubSubManager *psm = getPSM(server); |
304 | 0 | if(!psm) |
305 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
306 | | |
307 | | /* Handle PublishSubscribe object separately */ |
308 | 0 | if(isPublishSubscribeObject) { |
309 | | /* For PublishSubscribe object, check PubSubManager lifecycle state */ |
310 | 0 | if(psm->sc.state == UA_LIFECYCLESTATE_STOPPED) |
311 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
312 | | /* Disable the PubSubManager by stopping it */ |
313 | 0 | UA_PubSubManager_setState(psm, UA_LIFECYCLESTATE_STOPPED); |
314 | 0 | return UA_STATUSCODE_GOOD; |
315 | 0 | } |
316 | | |
317 | | /* Disable the appropriate PubSub component with state validation */ |
318 | 0 | switch(componentType) { |
319 | 0 | case UA_PUBSUBCOMPONENT_CONNECTION: { |
320 | 0 | UA_PubSubConnection *conn = (UA_PubSubConnection*)component; |
321 | | /* OPC UA Standard: "The Server shall reject Disable Method calls if the current State is Disabled." */ |
322 | 0 | if(conn->head.state == UA_PUBSUBSTATE_DISABLED) |
323 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
324 | 0 | retVal = UA_PubSubConnection_setPubSubState(psm, conn, UA_PUBSUBSTATE_DISABLED); |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | case UA_PUBSUBCOMPONENT_WRITERGROUP: { |
328 | 0 | UA_WriterGroup *wg = (UA_WriterGroup*)component; |
329 | 0 | if(wg->head.state == UA_PUBSUBSTATE_DISABLED) |
330 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
331 | 0 | retVal = UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_DISABLED); |
332 | 0 | break; |
333 | 0 | } |
334 | 0 | case UA_PUBSUBCOMPONENT_READERGROUP: { |
335 | 0 | UA_ReaderGroup *rg = (UA_ReaderGroup*)component; |
336 | 0 | if(rg->head.state == UA_PUBSUBSTATE_DISABLED) |
337 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
338 | 0 | retVal = UA_ReaderGroup_setPubSubState(psm, rg, UA_PUBSUBSTATE_DISABLED); |
339 | 0 | break; |
340 | 0 | } |
341 | 0 | case UA_PUBSUBCOMPONENT_DATASETREADER: { |
342 | 0 | UA_DataSetReader *dsr = (UA_DataSetReader*)component; |
343 | 0 | if(dsr->head.state == UA_PUBSUBSTATE_DISABLED) |
344 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
345 | 0 | retVal = UA_DataSetReader_setPubSubState(psm, dsr, UA_PUBSUBSTATE_DISABLED, UA_STATUSCODE_GOOD); |
346 | 0 | break; |
347 | 0 | } |
348 | 0 | case UA_PUBSUBCOMPONENT_DATASETWRITER: { |
349 | 0 | UA_DataSetWriter *dsw = (UA_DataSetWriter*)component; |
350 | 0 | if(dsw->head.state == UA_PUBSUBSTATE_DISABLED) |
351 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
352 | 0 | retVal = UA_DataSetWriter_setPubSubState(psm, dsw, UA_PUBSUBSTATE_DISABLED); |
353 | 0 | break; |
354 | 0 | } |
355 | 0 | default: |
356 | 0 | return UA_STATUSCODE_BADNOTSUPPORTED; |
357 | 0 | } |
358 | | |
359 | 0 | return retVal; |
360 | 0 | } |
361 | | |
362 | | static UA_StatusCode |
363 | | ReadCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, |
364 | | const UA_NodeId *nodeid, void *context, UA_Boolean includeSourceTimeStamp, |
365 | 0 | const UA_NumericRange *range, UA_DataValue *value) { |
366 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
367 | |
|
368 | 0 | UA_PubSubManager *psm = getPSM(server); |
369 | 0 | if(!psm) |
370 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
371 | | |
372 | 0 | const UA_NodePropertyContext *nodeContext = (const UA_NodePropertyContext*)context; |
373 | 0 | const UA_NodeId *myNodeId = &nodeContext->parentNodeId; |
374 | |
|
375 | 0 | switch(nodeContext->parentClassifier) { |
376 | 0 | case UA_NS0ID_PUBSUBCONNECTIONTYPE: { |
377 | 0 | UA_PubSubConnection *pubSubConnection = UA_PubSubConnection_find(psm, *myNodeId); |
378 | 0 | if(!pubSubConnection) |
379 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
380 | 0 | if(nodeContext->elementClassiefier == UA_NS0ID_PUBSUBCONNECTIONTYPE_PUBLISHERID) { |
381 | 0 | UA_Variant tmp; |
382 | 0 | UA_PublisherId_toVariant(&pubSubConnection->config.publisherId, &tmp); |
383 | 0 | value->hasValue = true; |
384 | 0 | return UA_Variant_copy(&tmp, &value->value); |
385 | 0 | } |
386 | 0 | break; |
387 | 0 | } |
388 | 0 | case UA_NS0ID_READERGROUPTYPE: { |
389 | 0 | UA_ReaderGroup *readerGroup = UA_ReaderGroup_find(psm, *myNodeId); |
390 | 0 | if(!readerGroup) |
391 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
392 | 0 | if(nodeContext->elementClassiefier == UA_NS0ID_PUBSUBGROUPTYPE_STATUS_STATE) { |
393 | 0 | value->hasValue = true; |
394 | 0 | return UA_Variant_setScalarCopy(&value->value, &readerGroup->head.state, |
395 | 0 | &UA_TYPES[UA_TYPES_PUBSUBSTATE]); |
396 | 0 | } |
397 | 0 | break; |
398 | 0 | } |
399 | 0 | case UA_NS0ID_DATASETREADERTYPE: { |
400 | 0 | UA_DataSetReader *dataSetReader = UA_DataSetReader_find(psm, *myNodeId); |
401 | 0 | if(!dataSetReader) |
402 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
403 | 0 | switch(nodeContext->elementClassiefier) { |
404 | 0 | case UA_NS0ID_DATASETREADERTYPE_PUBLISHERID: { |
405 | 0 | UA_Variant tmp; |
406 | 0 | UA_PublisherId_toVariant(&dataSetReader->config.publisherId, &tmp); |
407 | 0 | value->hasValue = true; |
408 | 0 | return UA_Variant_copy(&tmp, &value->value); |
409 | 0 | } |
410 | 0 | case UA_NS0ID_DATASETREADERTYPE_STATUS_STATE: |
411 | 0 | value->hasValue = true; |
412 | 0 | return UA_Variant_setScalarCopy(&value->value, &dataSetReader->head.state, |
413 | 0 | &UA_TYPES[UA_TYPES_PUBSUBSTATE]); |
414 | 0 | default: break; |
415 | 0 | } |
416 | 0 | break; |
417 | 0 | } |
418 | 0 | case UA_NS0ID_WRITERGROUPTYPE: { |
419 | 0 | UA_WriterGroup *writerGroup = UA_WriterGroup_find(psm, *myNodeId); |
420 | 0 | if(!writerGroup) |
421 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
422 | 0 | switch(nodeContext->elementClassiefier){ |
423 | 0 | case UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL: |
424 | 0 | value->hasValue = true; |
425 | 0 | return UA_Variant_setScalarCopy(&value->value, |
426 | 0 | &writerGroup->config.publishingInterval, |
427 | 0 | &UA_TYPES[UA_TYPES_DURATION]); |
428 | 0 | break; |
429 | 0 | case UA_NS0ID_PUBSUBGROUPTYPE_STATUS_STATE: |
430 | 0 | value->hasValue = true; |
431 | 0 | return UA_Variant_setScalarCopy(&value->value, &writerGroup->head.state, |
432 | 0 | &UA_TYPES[UA_TYPES_PUBSUBSTATE]); |
433 | 0 | break; |
434 | 0 | default: break; |
435 | 0 | } |
436 | 0 | break; |
437 | 0 | } |
438 | 0 | case UA_NS0ID_DATASETWRITERTYPE: { |
439 | 0 | UA_DataSetWriter *dataSetWriter = UA_DataSetWriter_find(psm, *myNodeId); |
440 | 0 | if(!dataSetWriter) |
441 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
442 | 0 | switch(nodeContext->elementClassiefier) { |
443 | 0 | case UA_NS0ID_DATASETWRITERTYPE_DATASETWRITERID: |
444 | 0 | value->hasValue = true; |
445 | 0 | return UA_Variant_setScalarCopy(&value->value, &dataSetWriter->config.dataSetWriterId, |
446 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
447 | 0 | case UA_NS0ID_DATASETWRITERTYPE_STATUS_STATE: |
448 | 0 | value->hasValue = true; |
449 | 0 | return UA_Variant_setScalarCopy(&value->value, &dataSetWriter->head.state, |
450 | 0 | &UA_TYPES[UA_TYPES_PUBSUBSTATE]); |
451 | 0 | default: break; |
452 | 0 | } |
453 | 0 | break; |
454 | 0 | } |
455 | 0 | case UA_NS0ID_PUBLISHEDDATAITEMSTYPE: { |
456 | 0 | UA_PublishedDataSet *publishedDataSet = UA_PublishedDataSet_find(psm, *myNodeId); |
457 | 0 | if(!publishedDataSet) |
458 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
459 | 0 | switch(nodeContext->elementClassiefier) { |
460 | 0 | case UA_NS0ID_PUBLISHEDDATAITEMSTYPE_PUBLISHEDDATA: { |
461 | 0 | UA_PublishedVariableDataType *pvd = (UA_PublishedVariableDataType *) |
462 | 0 | UA_calloc(publishedDataSet->fieldSize, sizeof(UA_PublishedVariableDataType)); |
463 | 0 | size_t counter = 0; |
464 | 0 | UA_DataSetField *field; |
465 | 0 | TAILQ_FOREACH(field, &publishedDataSet->fields, listEntry) { |
466 | 0 | pvd[counter].attributeId = UA_ATTRIBUTEID_VALUE; |
467 | 0 | pvd[counter].publishedVariable = |
468 | 0 | field->config.field.variable.publishParameters.publishedVariable; |
469 | 0 | UA_NodeId_copy(&field->config.field.variable.publishParameters.publishedVariable, |
470 | 0 | &pvd[counter].publishedVariable); |
471 | 0 | counter++; |
472 | 0 | } |
473 | 0 | value->hasValue = true; |
474 | 0 | UA_Variant_setArray(&value->value, pvd, publishedDataSet->fieldSize, |
475 | 0 | &UA_TYPES[UA_TYPES_PUBLISHEDVARIABLEDATATYPE]); |
476 | 0 | return UA_STATUSCODE_GOOD; |
477 | 0 | } |
478 | 0 | case UA_NS0ID_PUBLISHEDDATASETTYPE_DATASETMETADATA: |
479 | 0 | value->hasValue = true; |
480 | 0 | return UA_Variant_setScalarCopy(&value->value, &publishedDataSet->dataSetMetaData, |
481 | 0 | &UA_TYPES[UA_TYPES_DATASETMETADATATYPE]); |
482 | 0 | case UA_NS0ID_PUBLISHEDDATASETTYPE_CONFIGURATIONVERSION: |
483 | 0 | value->hasValue = true; |
484 | 0 | return UA_Variant_setScalarCopy(&value->value, |
485 | 0 | &publishedDataSet->dataSetMetaData.configurationVersion, |
486 | 0 | &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE]); |
487 | 0 | default: break; |
488 | 0 | } |
489 | 0 | break; |
490 | 0 | } |
491 | 0 | case UA_NS0ID_STANDALONESUBSCRIBEDDATASETREFDATATYPE: { |
492 | 0 | UA_SubscribedDataSet *sds = UA_SubscribedDataSet_find(psm, *myNodeId); |
493 | 0 | if(!sds) |
494 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
495 | 0 | switch(nodeContext->elementClassiefier) { |
496 | 0 | case UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE_ISCONNECTED: { |
497 | 0 | UA_Boolean isConnected = (sds->connectedReader != NULL); |
498 | 0 | value->hasValue = true; |
499 | 0 | return UA_Variant_setScalarCopy(&value->value, &isConnected, |
500 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
501 | 0 | } |
502 | 0 | case UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE_DATASETMETADATA: { |
503 | 0 | value->hasValue = true; |
504 | 0 | return UA_Variant_setScalarCopy(&value->value, &sds->config.dataSetMetaData, |
505 | 0 | &UA_TYPES[UA_TYPES_DATASETMETADATATYPE]); |
506 | 0 | } |
507 | 0 | default: break; |
508 | 0 | } |
509 | 0 | break; |
510 | 0 | } |
511 | 0 | default: break; |
512 | 0 | } |
513 | | |
514 | 0 | UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER, |
515 | 0 | "Read error! Unknown property"); |
516 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
517 | 0 | } |
518 | | |
519 | | static UA_StatusCode |
520 | | WriteCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, |
521 | | const UA_NodeId *nodeId, void *nodeContext, |
522 | 0 | const UA_NumericRange *range, const UA_DataValue *data) { |
523 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
524 | |
|
525 | 0 | UA_NodePropertyContext *npc = (UA_NodePropertyContext *)nodeContext; |
526 | |
|
527 | 0 | UA_PubSubManager *psm = getPSM(server); |
528 | 0 | if(!psm) |
529 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
530 | | |
531 | 0 | UA_WriterGroup *writerGroup = NULL; |
532 | 0 | UA_StatusCode res = UA_STATUSCODE_BADNOTWRITABLE; |
533 | 0 | switch(npc->parentClassifier) { |
534 | 0 | case UA_NS0ID_PUBSUBCONNECTIONTYPE: |
535 | 0 | break; |
536 | 0 | case UA_NS0ID_WRITERGROUPTYPE: { |
537 | 0 | writerGroup = UA_WriterGroup_find(psm, npc->parentNodeId); |
538 | 0 | if(!writerGroup) |
539 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
540 | 0 | switch(npc->elementClassiefier) { |
541 | 0 | case UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL: { |
542 | 0 | if(!UA_Variant_hasScalarType(&data->value, &UA_TYPES[UA_TYPES_DURATION]) && |
543 | 0 | !UA_Variant_hasScalarType(&data->value, &UA_TYPES[UA_TYPES_DOUBLE])) |
544 | 0 | return UA_STATUSCODE_BADTYPEMISMATCH; |
545 | 0 | UA_Duration interval = *((UA_Duration *) data->value.data); |
546 | 0 | if(interval <= 0.0) |
547 | 0 | return UA_STATUSCODE_BADOUTOFRANGE; |
548 | 0 | writerGroup->config.publishingInterval = interval; |
549 | 0 | if(writerGroup->head.state == UA_PUBSUBSTATE_OPERATIONAL) { |
550 | 0 | UA_WriterGroup_removePublishCallback(psm, writerGroup); |
551 | 0 | UA_WriterGroup_addPublishCallback(psm, writerGroup); |
552 | 0 | } |
553 | 0 | return UA_STATUSCODE_GOOD; |
554 | 0 | } |
555 | 0 | default: break; |
556 | 0 | } |
557 | 0 | break; |
558 | 0 | } |
559 | 0 | default: break; |
560 | 0 | } |
561 | | |
562 | 0 | UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER, |
563 | 0 | "Changing the ReaderGroupConfig failed"); |
564 | 0 | return res; |
565 | 0 | } |
566 | | |
567 | | static UA_StatusCode |
568 | | setVariableValueSource(UA_Server *server, const UA_CallbackValueSource evs, |
569 | 553 | UA_NodeId node, UA_NodePropertyContext *context){ |
570 | 553 | UA_LOCK_ASSERT(&server->serviceMutex); |
571 | 553 | setNodeContext(server, node, context); |
572 | 553 | return setVariableNode_callbackValueSource(server, node, evs); |
573 | 553 | } |
574 | | |
575 | | static UA_StatusCode |
576 | | addPubSubConnectionConfig(UA_Server *server, UA_PubSubConnectionDataType *pubsubConnection, |
577 | 0 | UA_NodeId *connectionId) { |
578 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
579 | |
|
580 | 0 | UA_PubSubManager *psm = getPSM(server); |
581 | 0 | if(!psm) |
582 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
583 | | |
584 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
585 | 0 | UA_NetworkAddressUrlDataType networkAddressUrl; |
586 | 0 | memset(&networkAddressUrl, 0, sizeof(networkAddressUrl)); |
587 | 0 | UA_ExtensionObject *eo = &pubsubConnection->address; |
588 | 0 | if(eo->encoding == UA_EXTENSIONOBJECT_DECODED && |
589 | 0 | eo->content.decoded.type == &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]) { |
590 | 0 | void *data = eo->content.decoded.data; |
591 | 0 | retVal = |
592 | 0 | UA_NetworkAddressUrlDataType_copy((UA_NetworkAddressUrlDataType *)data, |
593 | 0 | &networkAddressUrl); |
594 | 0 | if(retVal != UA_STATUSCODE_GOOD) |
595 | 0 | return retVal; |
596 | 0 | } |
597 | | |
598 | 0 | UA_PubSubConnectionConfig connectionConfig; |
599 | 0 | memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig)); |
600 | 0 | connectionConfig.transportProfileUri = pubsubConnection->transportProfileUri; |
601 | 0 | connectionConfig.name = pubsubConnection->name; |
602 | 0 | UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, |
603 | 0 | &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]); |
604 | |
|
605 | 0 | retVal |= UA_PublisherId_fromVariant(&connectionConfig.publisherId, |
606 | 0 | &pubsubConnection->publisherId); |
607 | 0 | retVal |= UA_PubSubConnection_create(psm, &connectionConfig, connectionId); |
608 | 0 | UA_NetworkAddressUrlDataType_clear(&networkAddressUrl); |
609 | 0 | return retVal; |
610 | 0 | } |
611 | | |
612 | | /** |
613 | | * **WriterGroup handling** |
614 | | * |
615 | | * The WriterGroup (WG) is part of the connection and contains the primary |
616 | | * configuration parameters for the message creation. */ |
617 | | static UA_StatusCode |
618 | | addWriterGroupConfig(UA_Server *server, UA_NodeId connectionId, |
619 | 0 | UA_WriterGroupDataType *writerGroup, UA_NodeId *writerGroupId){ |
620 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
621 | |
|
622 | 0 | UA_PubSubManager *psm = getPSM(server); |
623 | 0 | if(!psm) |
624 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
625 | | |
626 | | /* Now we create a new WriterGroupConfig and add the group to the existing |
627 | | * PubSubConnection. */ |
628 | 0 | UA_WriterGroupConfig writerGroupConfig; |
629 | 0 | memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig)); |
630 | 0 | writerGroupConfig.name = writerGroup->name; |
631 | 0 | writerGroupConfig.publishingInterval = writerGroup->publishingInterval; |
632 | 0 | writerGroupConfig.writerGroupId = writerGroup->writerGroupId; |
633 | 0 | writerGroupConfig.priority = writerGroup->priority; |
634 | |
|
635 | 0 | UA_ExtensionObject *eoWG = &writerGroup->messageSettings; |
636 | 0 | UA_UadpWriterGroupMessageDataType uadpWriterGroupMessage; |
637 | 0 | UA_JsonWriterGroupMessageDataType jsonWriterGroupMessage; |
638 | 0 | if(eoWG->encoding == UA_EXTENSIONOBJECT_DECODED){ |
639 | 0 | writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED; |
640 | 0 | if(eoWG->content.decoded.type == &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]){ |
641 | 0 | writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP; |
642 | 0 | if(UA_UadpWriterGroupMessageDataType_copy( |
643 | 0 | (UA_UadpWriterGroupMessageDataType *)eoWG->content.decoded.data, |
644 | 0 | &uadpWriterGroupMessage) != UA_STATUSCODE_GOOD) { |
645 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
646 | 0 | } |
647 | 0 | writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]; |
648 | 0 | writerGroupConfig.messageSettings.content.decoded.data = &uadpWriterGroupMessage; |
649 | 0 | } else if(eoWG->content.decoded.type == &UA_TYPES[UA_TYPES_JSONWRITERGROUPMESSAGEDATATYPE]) { |
650 | 0 | writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_JSON; |
651 | 0 | if(UA_JsonWriterGroupMessageDataType_copy( |
652 | 0 | (UA_JsonWriterGroupMessageDataType *)eoWG->content.decoded.data, |
653 | 0 | &jsonWriterGroupMessage) != UA_STATUSCODE_GOOD) { |
654 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
655 | 0 | } |
656 | 0 | writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_JSONWRITERGROUPMESSAGEDATATYPE]; |
657 | 0 | writerGroupConfig.messageSettings.content.decoded.data = &jsonWriterGroupMessage; |
658 | 0 | } |
659 | 0 | } |
660 | | |
661 | 0 | eoWG = &writerGroup->transportSettings; |
662 | 0 | UA_BrokerWriterGroupTransportDataType brokerWriterGroupTransport; |
663 | 0 | UA_DatagramWriterGroupTransportDataType datagramWriterGroupTransport; |
664 | 0 | if(eoWG->encoding == UA_EXTENSIONOBJECT_DECODED) { |
665 | 0 | writerGroupConfig.transportSettings.encoding = UA_EXTENSIONOBJECT_DECODED; |
666 | 0 | if(eoWG->content.decoded.type == &UA_TYPES[UA_TYPES_BROKERWRITERGROUPTRANSPORTDATATYPE]) { |
667 | 0 | if(UA_BrokerWriterGroupTransportDataType_copy( |
668 | 0 | (UA_BrokerWriterGroupTransportDataType*)eoWG->content.decoded.data, |
669 | 0 | &brokerWriterGroupTransport) != UA_STATUSCODE_GOOD) { |
670 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
671 | 0 | } |
672 | 0 | writerGroupConfig.transportSettings.content.decoded.type = &UA_TYPES[UA_TYPES_BROKERWRITERGROUPTRANSPORTDATATYPE]; |
673 | 0 | writerGroupConfig.transportSettings.content.decoded.data = &brokerWriterGroupTransport; |
674 | 0 | } else if(eoWG->content.decoded.type == &UA_TYPES[UA_TYPES_DATAGRAMWRITERGROUPTRANSPORTDATATYPE]) { |
675 | 0 | if(UA_DatagramWriterGroupTransportDataType_copy( |
676 | 0 | (UA_DatagramWriterGroupTransportDataType *)eoWG->content.decoded.data, |
677 | 0 | &datagramWriterGroupTransport) != UA_STATUSCODE_GOOD) { |
678 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
679 | 0 | } |
680 | 0 | writerGroupConfig.transportSettings.content.decoded.type = &UA_TYPES[UA_TYPES_DATAGRAMWRITERGROUPTRANSPORTDATATYPE]; |
681 | 0 | writerGroupConfig.transportSettings.content.decoded.data = &datagramWriterGroupTransport; |
682 | 0 | } |
683 | 0 | } |
684 | 0 | if (writerGroupConfig.encodingMimeType == UA_PUBSUB_ENCODING_JSON |
685 | 0 | && (writerGroupConfig.transportSettings.encoding != UA_EXTENSIONOBJECT_DECODED || |
686 | 0 | writerGroupConfig.transportSettings.content.decoded.type != |
687 | 0 | &UA_TYPES[UA_TYPES_BROKERWRITERGROUPTRANSPORTDATATYPE])) { |
688 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
689 | 0 | "JSON encoding is supported only for MQTT transport"); |
690 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
691 | 0 | } |
692 | | |
693 | 0 | return UA_WriterGroup_create(psm, connectionId, &writerGroupConfig, writerGroupId); |
694 | 0 | } |
695 | | |
696 | | /** |
697 | | * **DataSetWriter handling** |
698 | | * |
699 | | * A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is |
700 | | * linked to exactly one PDS and contains additional informations for the |
701 | | * message generation. */ |
702 | | static UA_StatusCode |
703 | | addDataSetWriterConfig(UA_Server *server, const UA_NodeId *writerGroupId, |
704 | | UA_DataSetWriterDataType *dataSetWriter, |
705 | 0 | UA_NodeId *dataSetWriterId) { |
706 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
707 | |
|
708 | 0 | UA_PubSubManager *psm = getPSM(server); |
709 | 0 | if(!psm) |
710 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
711 | | |
712 | 0 | UA_NodeId publishedDataSetId = UA_NODEID_NULL; |
713 | 0 | UA_PublishedDataSet *tmpPDS; |
714 | 0 | TAILQ_FOREACH(tmpPDS, &psm->publishedDataSets, listEntry){ |
715 | 0 | if(UA_String_equal(&dataSetWriter->dataSetName, &tmpPDS->config.name)) { |
716 | 0 | publishedDataSetId = tmpPDS->head.identifier; |
717 | 0 | break; |
718 | 0 | } |
719 | 0 | } |
720 | |
|
721 | 0 | if(UA_NodeId_isNull(&publishedDataSetId)) |
722 | 0 | return UA_STATUSCODE_BADPARENTNODEIDINVALID; |
723 | | |
724 | | /* We need now a DataSetWriter within the WriterGroup. This means we must |
725 | | * create a new DataSetWriterConfig and add call the addWriterGroup function. */ |
726 | 0 | UA_DataSetWriterConfig dataSetWriterConfig; |
727 | 0 | memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig)); |
728 | 0 | dataSetWriterConfig.name = dataSetWriter->name; |
729 | 0 | dataSetWriterConfig.dataSetWriterId = dataSetWriter->dataSetWriterId; |
730 | 0 | dataSetWriterConfig.keyFrameCount = dataSetWriter->keyFrameCount; |
731 | 0 | dataSetWriterConfig.dataSetFieldContentMask = dataSetWriter->dataSetFieldContentMask; |
732 | 0 | return UA_DataSetWriter_create(psm, *writerGroupId, publishedDataSetId, |
733 | 0 | &dataSetWriterConfig, dataSetWriterId); |
734 | 0 | } |
735 | | |
736 | | /** |
737 | | * **ReaderGroup** |
738 | | * |
739 | | * ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are |
740 | | * created within a PubSubConnection and automatically deleted if the connection |
741 | | * is removed. All network message related filters are only available in the DataSetReader. */ |
742 | | /* Add ReaderGroup to the created connection */ |
743 | | static UA_StatusCode |
744 | | addReaderGroupConfig(UA_Server *server, UA_NodeId connectionId, |
745 | | UA_ReaderGroupDataType *readerGroup, |
746 | 0 | UA_NodeId *readerGroupId) { |
747 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
748 | |
|
749 | 0 | UA_PubSubManager *psm = getPSM(server); |
750 | 0 | if(!psm) |
751 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
752 | | |
753 | 0 | UA_ReaderGroupConfig readerGroupConfig; |
754 | 0 | memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig)); |
755 | 0 | readerGroupConfig.name = readerGroup->name; |
756 | 0 | return UA_ReaderGroup_create(psm, connectionId, |
757 | 0 | &readerGroupConfig, readerGroupId); |
758 | 0 | } |
759 | | |
760 | | /** |
761 | | * **SubscribedDataSet** |
762 | | * |
763 | | * Set SubscribedDataSet type to TargetVariables data type. |
764 | | * Add subscribedvariables to the DataSetReader */ |
765 | | static UA_StatusCode |
766 | | addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId, |
767 | | UA_DataSetReaderDataType *dataSetReader, |
768 | 0 | UA_DataSetMetaDataType *pMetaData) { |
769 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
770 | |
|
771 | 0 | UA_PubSubManager *psm = getPSM(server); |
772 | 0 | if(!psm) |
773 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
774 | | |
775 | 0 | UA_ExtensionObject *eoTargetVar = &dataSetReader->subscribedDataSet; |
776 | 0 | if(eoTargetVar->encoding != UA_EXTENSIONOBJECT_DECODED || |
777 | 0 | eoTargetVar->content.decoded.type != &UA_TYPES[UA_TYPES_TARGETVARIABLESDATATYPE]) |
778 | 0 | return UA_STATUSCODE_BADUNEXPECTEDERROR; |
779 | | |
780 | 0 | const UA_TargetVariablesDataType *targetVars = |
781 | 0 | (UA_TargetVariablesDataType*)eoTargetVar->content.decoded.data; |
782 | |
|
783 | 0 | UA_DataSetReader *dsr = UA_DataSetReader_find(psm, dataSetReaderId); |
784 | 0 | if(!dsr) |
785 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
786 | | |
787 | 0 | UA_NodeId folderId; |
788 | 0 | UA_String folderName = pMetaData->name; |
789 | 0 | UA_ObjectAttributes oAttr = UA_ObjectAttributes_default; |
790 | 0 | UA_QualifiedName folderBrowseName; |
791 | 0 | if(folderName.length > 0) { |
792 | 0 | oAttr.displayName.locale = UA_STRING(""); |
793 | 0 | oAttr.displayName.text = folderName; |
794 | 0 | folderBrowseName.namespaceIndex = 1; |
795 | 0 | folderBrowseName.name = folderName; |
796 | 0 | } else { |
797 | 0 | oAttr.displayName = UA_LOCALIZEDTEXT("", "Subscribed Variables"); |
798 | 0 | folderBrowseName = UA_QUALIFIEDNAME(1, "Subscribed Variables"); |
799 | 0 | } |
800 | |
|
801 | 0 | addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NULL, |
802 | 0 | UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(ORGANIZES), |
803 | 0 | folderBrowseName, UA_NS0ID(BASEOBJECTTYPE), |
804 | 0 | &oAttr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
805 | 0 | NULL, &folderId); |
806 | | |
807 | | /* The SubscribedDataSet option TargetVariables defines a list of Variable |
808 | | * mappings between received DataSet fields and target Variables in the |
809 | | * Subscriber AddressSpace. The values subscribed from the Publisher are |
810 | | * updated in the value field of these variables */ |
811 | | |
812 | | /* Add variable for the fields */ |
813 | 0 | for(size_t i = 0; i < targetVars->targetVariablesSize; i++) { |
814 | 0 | UA_VariableAttributes vAttr = UA_VariableAttributes_default; |
815 | 0 | vAttr.description = pMetaData->fields[i].description; |
816 | 0 | vAttr.displayName.locale = UA_STRING(""); |
817 | 0 | vAttr.displayName.text = pMetaData->fields[i].name; |
818 | 0 | vAttr.dataType = pMetaData->fields[i].dataType; |
819 | 0 | UA_QualifiedName varname = {1, pMetaData->fields[i].name}; |
820 | 0 | addNode(server, UA_NODECLASS_VARIABLE, |
821 | 0 | targetVars->targetVariables[i].targetNodeId, folderId, |
822 | 0 | UA_NS0ID(HASCOMPONENT), varname, UA_NS0ID(BASEDATAVARIABLETYPE), |
823 | 0 | &vAttr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], |
824 | 0 | NULL, &targetVars->targetVariables[i].targetNodeId); |
825 | 0 | } |
826 | | |
827 | | /* Set the TargetVariables in the DSR config */ |
828 | 0 | return DataSetReader_createTargetVariables(psm, dsr, |
829 | 0 | targetVars->targetVariablesSize, |
830 | 0 | targetVars->targetVariables); |
831 | 0 | } |
832 | | |
833 | | /** |
834 | | * **DataSetReader** |
835 | | * |
836 | | * DataSetReader can receive NetworkMessages with the DataSetMessage |
837 | | * of interest sent by the Publisher. DataSetReader provides |
838 | | * the configuration necessary to receive and process DataSetMessages |
839 | | * on the Subscriber side. DataSetReader must be linked with a |
840 | | * SubscribedDataSet and be contained within a ReaderGroup. */ |
841 | | static UA_StatusCode |
842 | | addDataSetReaderConfig(UA_Server *server, UA_NodeId readerGroupId, |
843 | | UA_DataSetReaderDataType *dataSetReader, |
844 | 0 | UA_NodeId *dataSetReaderId) { |
845 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
846 | |
|
847 | 0 | UA_PubSubManager *psm = getPSM(server); |
848 | 0 | if(!psm) |
849 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
850 | | |
851 | 0 | UA_DataSetReaderConfig readerConfig; |
852 | 0 | memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig)); |
853 | |
|
854 | 0 | UA_StatusCode retVal = |
855 | 0 | UA_PublisherId_fromVariant(&readerConfig.publisherId, |
856 | 0 | &dataSetReader->publisherId); |
857 | 0 | readerConfig.name = dataSetReader->name; |
858 | 0 | readerConfig.writerGroupId = dataSetReader->writerGroupId; |
859 | 0 | readerConfig.dataSetWriterId = dataSetReader->dataSetWriterId; |
860 | | |
861 | | /* Setting up Meta data configuration in DataSetReader */ |
862 | 0 | UA_DataSetMetaDataType *pMetaData; |
863 | 0 | pMetaData = &readerConfig.dataSetMetaData; |
864 | 0 | UA_DataSetMetaDataType_init (pMetaData); |
865 | 0 | pMetaData->name = dataSetReader->dataSetMetaData.name; |
866 | 0 | pMetaData->fieldsSize = dataSetReader->dataSetMetaData.fieldsSize; |
867 | 0 | pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize, |
868 | 0 | &UA_TYPES[UA_TYPES_FIELDMETADATA]); |
869 | 0 | if(pMetaData->fieldsSize > 0 && !pMetaData->fields) { |
870 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
871 | 0 | "Failed to allocate memory for DataSetReader MetaData fields"); |
872 | 0 | UA_PublisherId_clear(&readerConfig.publisherId); |
873 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
874 | 0 | } |
875 | 0 | for(size_t i = 0; i < pMetaData->fieldsSize; i++){ |
876 | 0 | UA_FieldMetaData_init (&pMetaData->fields[i]); |
877 | 0 | UA_NodeId_copy(&dataSetReader->dataSetMetaData.fields[i].dataType, |
878 | 0 | &pMetaData->fields[i].dataType); |
879 | 0 | pMetaData->fields[i].builtInType = dataSetReader->dataSetMetaData.fields[i].builtInType; |
880 | 0 | pMetaData->fields[i].name = dataSetReader->dataSetMetaData.fields[i].name; |
881 | 0 | pMetaData->fields[i].valueRank = dataSetReader->dataSetMetaData.fields[i].valueRank; |
882 | 0 | } |
883 | |
|
884 | 0 | retVal |= UA_DataSetReader_create(psm, readerGroupId, |
885 | 0 | &readerConfig, dataSetReaderId); |
886 | 0 | UA_PublisherId_clear(&readerConfig.publisherId); |
887 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
888 | 0 | UA_free(pMetaData->fields); |
889 | 0 | return retVal; |
890 | 0 | } |
891 | | |
892 | 0 | retVal |= addSubscribedVariables(server, *dataSetReaderId, dataSetReader, pMetaData); |
893 | 0 | UA_free(pMetaData->fields); |
894 | 0 | return retVal; |
895 | 0 | } |
896 | | |
897 | | /*************************************************/ |
898 | | /* PubSubConnection */ |
899 | | /*************************************************/ |
900 | | |
901 | | UA_StatusCode |
902 | 0 | addPubSubConnectionRepresentation(UA_Server *server, UA_PubSubConnection *connection) { |
903 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
904 | 0 | if(connection->config.name.length > 512) |
905 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
906 | 0 | char connectionName[513]; |
907 | 0 | memcpy(connectionName, connection->config.name.data, connection->config.name.length); |
908 | 0 | connectionName[connection->config.name.length] = '\0'; |
909 | |
|
910 | 0 | UA_ObjectAttributes attr = UA_ObjectAttributes_default; |
911 | 0 | attr.displayName = UA_LOCALIZEDTEXT("", connectionName); |
912 | 0 | retVal |= addNode_begin(server, UA_NODECLASS_OBJECT, |
913 | 0 | UA_NODEID_NUMERIC(1, 0), /* Generate a new id */ |
914 | 0 | UA_NS0ID(PUBLISHSUBSCRIBE), |
915 | 0 | UA_NS0ID(HASPUBSUBCONNECTION), |
916 | 0 | UA_QUALIFIEDNAME(0, connectionName), |
917 | 0 | UA_NS0ID(PUBSUBCONNECTIONTYPE), |
918 | 0 | (const UA_NodeAttributes*)&attr, |
919 | 0 | &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
920 | 0 | NULL, &connection->head.identifier); |
921 | |
|
922 | 0 | attr.displayName = UA_LOCALIZEDTEXT("", "Address"); |
923 | 0 | retVal |= addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), |
924 | 0 | connection->head.identifier, UA_NS0ID(HASCOMPONENT), |
925 | 0 | UA_QUALIFIEDNAME(0, "Address"), UA_NS0ID(NETWORKADDRESSURLTYPE), |
926 | 0 | &attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, NULL); |
927 | |
|
928 | 0 | retVal |= addNode_finish(server, &server->adminSession, &connection->head.identifier); |
929 | |
|
930 | 0 | UA_NodeId addressNode = |
931 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Address"), |
932 | 0 | UA_NS0ID(HASCOMPONENT), connection->head.identifier); |
933 | 0 | UA_NodeId urlNode = |
934 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Url"), |
935 | 0 | UA_NS0ID(HASCOMPONENT), addressNode); |
936 | 0 | UA_NodeId interfaceNode = |
937 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "NetworkInterface"), |
938 | 0 | UA_NS0ID(HASCOMPONENT), addressNode); |
939 | 0 | UA_NodeId publisherIdNode = |
940 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublisherId"), |
941 | 0 | UA_NS0ID(HASPROPERTY), connection->head.identifier); |
942 | 0 | UA_NodeId connectionPropertyNode = |
943 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "ConnectionProperties"), |
944 | 0 | UA_NS0ID(HASPROPERTY), connection->head.identifier); |
945 | 0 | UA_NodeId transportProfileUri = |
946 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "TransportProfileUri"), |
947 | 0 | UA_NS0ID(HASCOMPONENT), connection->head.identifier); |
948 | 0 | UA_NodeId statusIdNode = |
949 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Status"), |
950 | 0 | UA_NS0ID(HASCOMPONENT), connection->head.identifier); |
951 | |
|
952 | 0 | if(UA_NodeId_isNull(&addressNode) || UA_NodeId_isNull(&urlNode) || |
953 | 0 | UA_NodeId_isNull(&interfaceNode) || UA_NodeId_isNull(&publisherIdNode) || |
954 | 0 | UA_NodeId_isNull(&connectionPropertyNode) || |
955 | 0 | UA_NodeId_isNull(&transportProfileUri)) { |
956 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
957 | 0 | } |
958 | | |
959 | 0 | retVal |= writePubSubNs0VariableArray(server, connectionPropertyNode, |
960 | 0 | connection->config.connectionProperties.map, |
961 | 0 | connection->config.connectionProperties.mapSize, |
962 | 0 | &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); |
963 | |
|
964 | 0 | UA_NetworkAddressUrlDataType *networkAddressUrl= |
965 | 0 | ((UA_NetworkAddressUrlDataType*)connection->config.address.data); |
966 | 0 | UA_Variant value; |
967 | 0 | UA_Variant_init(&value); |
968 | |
|
969 | 0 | UA_Variant_setScalar(&value, &networkAddressUrl->url, &UA_TYPES[UA_TYPES_STRING]); |
970 | 0 | writeValueAttribute(server, urlNode, &value); |
971 | |
|
972 | 0 | UA_Variant_setScalar(&value, &networkAddressUrl->networkInterface, &UA_TYPES[UA_TYPES_STRING]); |
973 | 0 | writeValueAttribute(server, interfaceNode, &value); |
974 | |
|
975 | 0 | UA_Variant_setScalar(&value, &connection->config.transportProfileUri, &UA_TYPES[UA_TYPES_STRING]); |
976 | 0 | writeValueAttribute(server, transportProfileUri, &value); |
977 | |
|
978 | 0 | UA_NodePropertyContext *connectionPublisherIdContext = |
979 | 0 | (UA_NodePropertyContext *)UA_malloc(sizeof(UA_NodePropertyContext)); |
980 | 0 | connectionPublisherIdContext->parentNodeId = connection->head.identifier; |
981 | 0 | connectionPublisherIdContext->parentClassifier = UA_NS0ID_PUBSUBCONNECTIONTYPE; |
982 | 0 | connectionPublisherIdContext->elementClassiefier = UA_NS0ID_PUBSUBCONNECTIONTYPE_PUBLISHERID; |
983 | |
|
984 | 0 | UA_CallbackValueSource valueCallback; |
985 | 0 | valueCallback.read = ReadCallback; |
986 | 0 | valueCallback.write = NULL; |
987 | 0 | retVal |= setVariableValueSource(server, valueCallback, publisherIdNode, |
988 | 0 | connectionPublisherIdContext); |
989 | |
|
990 | 0 | if(!UA_NodeId_isNull(&statusIdNode)) { |
991 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
992 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
993 | 0 | if(!UA_NodeId_isNull(&stateNodeId)) { |
994 | 0 | UA_DataSource stateDataSource; |
995 | 0 | stateDataSource.read = pubSubStateVariableDataSourceRead; |
996 | 0 | stateDataSource.write = NULL; |
997 | 0 | retVal |= UA_Server_setVariableNode_dataSource(server, stateNodeId, stateDataSource); |
998 | 0 | } |
999 | 0 | } |
1000 | |
|
1001 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1002 | 0 | retVal |= addRef(server, connection->head.identifier, UA_NS0ID(HASCOMPONENT), |
1003 | 0 | UA_NS0ID(PUBSUBCONNECTIONTYPE_ADDWRITERGROUP), true); |
1004 | 0 | retVal |= addRef(server, connection->head.identifier, UA_NS0ID(HASCOMPONENT), |
1005 | 0 | UA_NS0ID(PUBSUBCONNECTIONTYPE_ADDREADERGROUP), true); |
1006 | 0 | retVal |= addRef(server, connection->head.identifier, UA_NS0ID(HASCOMPONENT), |
1007 | 0 | UA_NS0ID(PUBSUBCONNECTIONTYPE_REMOVEGROUP), true); |
1008 | | |
1009 | 0 | if(!UA_NodeId_isNull(&statusIdNode)) { |
1010 | 0 | retVal |= addRef(server, statusIdNode, |
1011 | 0 | UA_NS0ID(HASCOMPONENT), |
1012 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
1013 | 0 | retVal |= addRef(server, statusIdNode, |
1014 | 0 | UA_NS0ID(HASCOMPONENT), |
1015 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 | return retVal; |
1019 | 0 | } |
1020 | | |
1021 | | static UA_StatusCode |
1022 | | addPubSubConnectionAction(UA_Server *server, |
1023 | | const UA_NodeId *sessionId, void *sessionContext, |
1024 | | const UA_NodeId *methodId, void *methodContext, |
1025 | | const UA_NodeId *objectId, void *objectContext, |
1026 | | size_t inputSize, const UA_Variant *input, |
1027 | 0 | size_t outputSize, UA_Variant *output) { |
1028 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1029 | |
|
1030 | 0 | UA_PubSubManager *psm = getPSM(server); |
1031 | 0 | if(!psm) |
1032 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1033 | | |
1034 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1035 | 0 | UA_PubSubConnectionDataType *pubSubConnection = |
1036 | 0 | (UA_PubSubConnectionDataType *) input[0].data; |
1037 | | |
1038 | | //call API function and create the connection |
1039 | 0 | UA_NodeId connectionId; |
1040 | 0 | retVal |= addPubSubConnectionConfig(server, pubSubConnection, &connectionId); |
1041 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1042 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1043 | 0 | "addPubSubConnection failed"); |
1044 | 0 | return retVal; |
1045 | 0 | } |
1046 | | |
1047 | 0 | for(size_t i = 0; i < pubSubConnection->writerGroupsSize; i++) { |
1048 | 0 | UA_NodeId writerGroupId; |
1049 | 0 | UA_WriterGroupDataType *writerGroup = &pubSubConnection->writerGroups[i]; |
1050 | 0 | retVal |= addWriterGroupConfig(server, connectionId, writerGroup, &writerGroupId); |
1051 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1052 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1053 | 0 | "addWriterGroup failed"); |
1054 | 0 | return retVal; |
1055 | 0 | } |
1056 | | |
1057 | 0 | for(size_t j = 0; j < writerGroup->dataSetWritersSize; j++) { |
1058 | 0 | UA_DataSetWriterDataType *dataSetWriter = &writerGroup->dataSetWriters[j]; |
1059 | 0 | retVal |= addDataSetWriterConfig(server, &writerGroupId, dataSetWriter, NULL); |
1060 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1061 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1062 | 0 | "addDataSetWriter failed"); |
1063 | 0 | return retVal; |
1064 | 0 | } |
1065 | 0 | } |
1066 | | |
1067 | | /* TODO: Need to handle the UA_Server_setWriterGroupOperational based on |
1068 | | * the status variable in information model */ |
1069 | 0 | UA_WriterGroup *wg = UA_WriterGroup_find(psm, writerGroupId); |
1070 | 0 | if(!wg) |
1071 | 0 | continue; |
1072 | 0 | if(pubSubConnection->enabled) { |
1073 | 0 | UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_OPERATIONAL); |
1074 | 0 | } else { |
1075 | 0 | UA_WriterGroup_setPubSubState(psm, wg, UA_PUBSUBSTATE_DISABLED); |
1076 | 0 | } |
1077 | 0 | } |
1078 | | |
1079 | 0 | for(size_t i = 0; i < pubSubConnection->readerGroupsSize; i++){ |
1080 | 0 | UA_NodeId readerGroupId; |
1081 | 0 | UA_ReaderGroupDataType *readerGroup = &pubSubConnection->readerGroups[i]; |
1082 | 0 | retVal |= addReaderGroupConfig(server, connectionId, readerGroup, &readerGroupId); |
1083 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1084 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1085 | 0 | "addReaderGroup failed"); |
1086 | 0 | return retVal; |
1087 | 0 | } |
1088 | | |
1089 | 0 | for(size_t j = 0; j < readerGroup->dataSetReadersSize; j++) { |
1090 | 0 | UA_NodeId dataSetReaderId; |
1091 | 0 | UA_DataSetReaderDataType *dataSetReader = &readerGroup->dataSetReaders[j]; |
1092 | 0 | retVal |= addDataSetReaderConfig(server, readerGroupId, |
1093 | 0 | dataSetReader, &dataSetReaderId); |
1094 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1095 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1096 | 0 | "addDataSetReader failed"); |
1097 | 0 | return retVal; |
1098 | 0 | } |
1099 | |
|
1100 | 0 | } |
1101 | | |
1102 | | /* TODO: Need to handle the UA_Server_setReaderGroupOperational based on |
1103 | | * the status variable in information model */ |
1104 | 0 | UA_ReaderGroup *rg = UA_ReaderGroup_find(psm, readerGroupId); |
1105 | 0 | if(!rg) |
1106 | 0 | continue; |
1107 | 0 | if(pubSubConnection->enabled) { |
1108 | 0 | UA_ReaderGroup_setPubSubState(psm, rg, UA_PUBSUBSTATE_OPERATIONAL); |
1109 | 0 | } else { |
1110 | 0 | UA_ReaderGroup_setPubSubState(psm, rg, UA_PUBSUBSTATE_DISABLED); |
1111 | 0 | } |
1112 | 0 | } |
1113 | | |
1114 | | /* Set ouput value */ |
1115 | 0 | UA_Variant_setScalarCopy(output, &connectionId, &UA_TYPES[UA_TYPES_NODEID]); |
1116 | 0 | return UA_STATUSCODE_GOOD; |
1117 | 0 | } |
1118 | | |
1119 | | static UA_StatusCode |
1120 | | removeConnectionAction(UA_Server *server, |
1121 | | const UA_NodeId *sessionId, void *sessionContext, |
1122 | | const UA_NodeId *methodId, void *methodContext, |
1123 | | const UA_NodeId *objectId, void *objectContext, |
1124 | | size_t inputSize, const UA_Variant *input, |
1125 | 0 | size_t outputSize, UA_Variant *output){ |
1126 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1127 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *) input[0].data); |
1128 | 0 | retVal |= UA_Server_removePubSubConnection(server, nodeToRemove); |
1129 | 0 | if(retVal == UA_STATUSCODE_BADNOTFOUND) |
1130 | 0 | retVal = UA_STATUSCODE_BADNODEIDUNKNOWN; |
1131 | 0 | return retVal; |
1132 | 0 | } |
1133 | | |
1134 | | /**********************************************/ |
1135 | | /* DataSetReader */ |
1136 | | /**********************************************/ |
1137 | | |
1138 | | UA_StatusCode |
1139 | 0 | addDataSetReaderRepresentation(UA_Server *server, UA_DataSetReader *dataSetReader){ |
1140 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1141 | |
|
1142 | 0 | if(dataSetReader->config.name.length > 512) |
1143 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
1144 | | |
1145 | 0 | char dsrName[513]; |
1146 | 0 | memcpy(dsrName, dataSetReader->config.name.data, dataSetReader->config.name.length); |
1147 | 0 | dsrName[dataSetReader->config.name.length] = '\0'; |
1148 | |
|
1149 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1150 | 0 | UA_NodeId publisherIdNode, writerGroupIdNode, dataSetwriterIdNode, statusIdNode, stateIdNode; |
1151 | |
|
1152 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1153 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", dsrName); |
1154 | 0 | retVal = addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), /* create an id */ |
1155 | 0 | dataSetReader->linkedReaderGroup->head.identifier, |
1156 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASDATASETREADER), |
1157 | 0 | UA_QUALIFIEDNAME(0, dsrName), |
1158 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_DATASETREADERTYPE), |
1159 | 0 | &object_attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1160 | 0 | NULL, &dataSetReader->head.identifier); |
1161 | | |
1162 | | /* Add childNodes such as PublisherId, WriterGroupId and DataSetWriterId in |
1163 | | * DataSetReader object */ |
1164 | 0 | publisherIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublisherId"), |
1165 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), |
1166 | 0 | dataSetReader->head.identifier); |
1167 | 0 | writerGroupIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "WriterGroupId"), |
1168 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), |
1169 | 0 | dataSetReader->head.identifier); |
1170 | 0 | dataSetwriterIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetWriterId"), |
1171 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), |
1172 | 0 | dataSetReader->head.identifier); |
1173 | 0 | statusIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Status"), |
1174 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), |
1175 | 0 | dataSetReader->head.identifier); |
1176 | |
|
1177 | 0 | if(UA_NodeId_isNull(&statusIdNode)) |
1178 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1179 | | |
1180 | 0 | stateIdNode = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
1181 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), |
1182 | 0 | statusIdNode); |
1183 | |
|
1184 | 0 | if(UA_NodeId_isNull(&publisherIdNode) || |
1185 | 0 | UA_NodeId_isNull(&writerGroupIdNode) || |
1186 | 0 | UA_NodeId_isNull(&dataSetwriterIdNode) || |
1187 | 0 | UA_NodeId_isNull(&stateIdNode)) |
1188 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1189 | | |
1190 | 0 | UA_NodePropertyContext *dataSetReaderPublisherIdContext = |
1191 | 0 | (UA_NodePropertyContext *) UA_malloc(sizeof(UA_NodePropertyContext)); |
1192 | 0 | dataSetReaderPublisherIdContext->parentNodeId = dataSetReader->head.identifier; |
1193 | 0 | dataSetReaderPublisherIdContext->parentClassifier = UA_NS0ID_DATASETREADERTYPE; |
1194 | 0 | dataSetReaderPublisherIdContext->elementClassiefier = UA_NS0ID_DATASETREADERTYPE_PUBLISHERID; |
1195 | 0 | UA_CallbackValueSource valueCallback; |
1196 | 0 | valueCallback.read = ReadCallback; |
1197 | 0 | valueCallback.write = NULL; |
1198 | 0 | retVal |= setVariableValueSource(server, valueCallback, publisherIdNode, |
1199 | 0 | dataSetReaderPublisherIdContext); |
1200 | |
|
1201 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
1202 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
1203 | 0 | if(!UA_NodeId_isNull(&stateNodeId)) { |
1204 | 0 | UA_DataSource stateDataSource; |
1205 | 0 | stateDataSource.read = pubSubStateVariableDataSourceRead; |
1206 | 0 | stateDataSource.write = NULL; |
1207 | 0 | retVal |= UA_Server_setVariableNode_dataSource(server, stateNodeId, stateDataSource); |
1208 | 0 | } |
1209 | | |
1210 | | /* Update childNode with values from Publisher */ |
1211 | 0 | UA_Variant value; |
1212 | 0 | UA_Variant_init(&value); |
1213 | 0 | UA_Variant_setScalar(&value, &dataSetReader->config.writerGroupId, |
1214 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
1215 | 0 | writeValueAttribute(server, writerGroupIdNode, &value); |
1216 | 0 | UA_Variant_setScalar(&value, &dataSetReader->config.dataSetWriterId, |
1217 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
1218 | 0 | writeValueAttribute(server, dataSetwriterIdNode, &value); |
1219 | |
|
1220 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1221 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1222 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
1223 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1224 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
1225 | 0 | } |
1226 | | |
1227 | 0 | return retVal; |
1228 | 0 | } |
1229 | | |
1230 | | static UA_StatusCode |
1231 | | addDataSetReaderAction(UA_Server *server, |
1232 | | const UA_NodeId *sessionId, void *sessionContext, |
1233 | | const UA_NodeId *methodId, void *methodContext, |
1234 | | const UA_NodeId *objectId, void *objectContext, |
1235 | | size_t inputSize, const UA_Variant *input, |
1236 | 0 | size_t outputSize, UA_Variant *output) { |
1237 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1238 | |
|
1239 | 0 | UA_NodeId dataSetReaderId; |
1240 | 0 | UA_DataSetReaderDataType *dataSetReader= (UA_DataSetReaderDataType *) input[0].data; |
1241 | 0 | UA_StatusCode retVal = |
1242 | 0 | addDataSetReaderConfig(server, *objectId, dataSetReader, &dataSetReaderId); |
1243 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1244 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1245 | 0 | "AddDataSetReader failed"); |
1246 | 0 | return retVal; |
1247 | 0 | } |
1248 | | |
1249 | 0 | UA_Variant_setScalarCopy(output, &dataSetReaderId, &UA_TYPES[UA_TYPES_NODEID]); |
1250 | 0 | return retVal; |
1251 | 0 | } |
1252 | | |
1253 | | static UA_StatusCode |
1254 | | removeDataSetReaderAction(UA_Server *server, |
1255 | | const UA_NodeId *sessionId, void *sessionContext, |
1256 | | const UA_NodeId *methodId, void *methodContext, |
1257 | | const UA_NodeId *objectId, void *objectContext, |
1258 | | size_t inputSize, const UA_Variant *input, |
1259 | 0 | size_t outputSize, UA_Variant *output){ |
1260 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *)input[0].data); |
1261 | 0 | return UA_Server_removeDataSetReader(server, nodeToRemove); |
1262 | 0 | } |
1263 | | |
1264 | | /*************************************************/ |
1265 | | /* PublishedDataSet */ |
1266 | | /*************************************************/ |
1267 | | |
1268 | | static UA_StatusCode |
1269 | | addDataSetFolderAction(UA_Server *server, |
1270 | | const UA_NodeId *sessionId, void *sessionContext, |
1271 | | const UA_NodeId *methodId, void *methodContext, |
1272 | | const UA_NodeId *objectId, void *objectContext, |
1273 | | size_t inputSize, const UA_Variant *input, |
1274 | 0 | size_t outputSize, UA_Variant *output){ |
1275 | | /* defined in R 1.04 9.1.4.5.7 */ |
1276 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1277 | 0 | UA_String newFolderName = *((UA_String *) input[0].data); |
1278 | 0 | UA_NodeId generatedId; |
1279 | 0 | UA_ObjectAttributes objectAttributes = UA_ObjectAttributes_default; |
1280 | 0 | UA_LocalizedText name = {UA_STRING(""), newFolderName}; |
1281 | 0 | objectAttributes.displayName = name; |
1282 | 0 | retVal |= UA_Server_addObjectNode(server, UA_NODEID_NULL, *objectId, |
1283 | 0 | UA_NS0ID(ORGANIZES), |
1284 | 0 | UA_QUALIFIEDNAME(0, "DataSetFolder"), |
1285 | 0 | UA_NS0ID(DATASETFOLDERTYPE), |
1286 | 0 | objectAttributes, NULL, &generatedId); |
1287 | 0 | UA_Variant_setScalarCopy(output, &generatedId, &UA_TYPES[UA_TYPES_NODEID]); |
1288 | |
|
1289 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1290 | 0 | retVal |= UA_Server_addReference(server, generatedId, UA_NS0ID(HASCOMPONENT), |
1291 | 0 | UA_NS0EXID(DATASETFOLDERTYPE_ADDPUBLISHEDDATAITEMS), true); |
1292 | 0 | retVal |= UA_Server_addReference(server, generatedId, UA_NS0ID(HASCOMPONENT), |
1293 | 0 | UA_NS0EXID(DATASETFOLDERTYPE_REMOVEPUBLISHEDDATASET), true); |
1294 | 0 | retVal |= UA_Server_addReference(server, generatedId, UA_NS0ID(HASCOMPONENT), |
1295 | 0 | UA_NS0EXID(DATASETFOLDERTYPE_ADDDATASETFOLDER), true); |
1296 | 0 | retVal |= UA_Server_addReference(server, generatedId, UA_NS0ID(HASCOMPONENT), |
1297 | 0 | UA_NS0EXID(DATASETFOLDERTYPE_REMOVEDATASETFOLDER), true); |
1298 | 0 | } |
1299 | 0 | return retVal; |
1300 | 0 | } |
1301 | | |
1302 | | static UA_StatusCode |
1303 | | removeDataSetFolderAction(UA_Server *server, |
1304 | | const UA_NodeId *sessionId, void *sessionContext, |
1305 | | const UA_NodeId *methodId, void *methodContext, |
1306 | | const UA_NodeId *objectId, void *objectContext, |
1307 | | size_t inputSize, const UA_Variant *input, |
1308 | 0 | size_t outputSize, UA_Variant *output) { |
1309 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *) input[0].data); |
1310 | 0 | return UA_Server_deleteNode(server, nodeToRemove, true); |
1311 | 0 | } |
1312 | | |
1313 | | UA_StatusCode |
1314 | | addPublishedDataItemsRepresentation(UA_Server *server, |
1315 | 0 | UA_PublishedDataSet *publishedDataSet) { |
1316 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1317 | |
|
1318 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1319 | 0 | if(publishedDataSet->config.name.length > 512) |
1320 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1321 | 0 | char pdsName[513]; |
1322 | 0 | memcpy(pdsName, publishedDataSet->config.name.data, publishedDataSet->config.name.length); |
1323 | 0 | pdsName[publishedDataSet->config.name.length] = '\0'; |
1324 | |
|
1325 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1326 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", pdsName); |
1327 | 0 | retVal = addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), /* Create a new id */ |
1328 | 0 | UA_NS0ID(PUBLISHSUBSCRIBE_PUBLISHEDDATASETS), |
1329 | 0 | UA_NS0ID(HASCOMPONENT), |
1330 | 0 | UA_QUALIFIEDNAME(0, pdsName), |
1331 | 0 | UA_NS0ID(PUBLISHEDDATAITEMSTYPE), |
1332 | 0 | &object_attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1333 | 0 | NULL, &publishedDataSet->head.identifier); |
1334 | 0 | UA_CHECK_STATUS(retVal, return retVal); |
1335 | | |
1336 | 0 | UA_CallbackValueSource valueCallback; |
1337 | 0 | valueCallback.read = ReadCallback; |
1338 | 0 | valueCallback.write = NULL; |
1339 | | //ToDo: Need to move the browse name from namespaceindex 0 to 1 |
1340 | 0 | UA_NodeId configurationVersionNode = |
1341 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "ConfigurationVersion"), |
1342 | 0 | UA_NS0ID(HASPROPERTY), publishedDataSet->head.identifier); |
1343 | 0 | if(UA_NodeId_isNull(&configurationVersionNode)) |
1344 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1345 | | |
1346 | 0 | UA_NodePropertyContext *configurationVersionContext = (UA_NodePropertyContext *) |
1347 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1348 | 0 | configurationVersionContext->parentNodeId = publishedDataSet->head.identifier; |
1349 | 0 | configurationVersionContext->parentClassifier = UA_NS0ID_PUBLISHEDDATAITEMSTYPE; |
1350 | 0 | configurationVersionContext->elementClassiefier = |
1351 | 0 | UA_NS0ID_PUBLISHEDDATASETTYPE_CONFIGURATIONVERSION; |
1352 | 0 | retVal |= setVariableValueSource(server, valueCallback, configurationVersionNode, |
1353 | 0 | configurationVersionContext); |
1354 | |
|
1355 | 0 | UA_NodeId publishedDataNode = |
1356 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishedData"), |
1357 | 0 | UA_NS0ID(HASPROPERTY), publishedDataSet->head.identifier); |
1358 | 0 | if(UA_NodeId_isNull(&publishedDataNode)) |
1359 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1360 | | |
1361 | 0 | UA_NodePropertyContext * publishingIntervalContext = (UA_NodePropertyContext *) |
1362 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1363 | 0 | publishingIntervalContext->parentNodeId = publishedDataSet->head.identifier; |
1364 | 0 | publishingIntervalContext->parentClassifier = UA_NS0ID_PUBLISHEDDATAITEMSTYPE; |
1365 | 0 | publishingIntervalContext->elementClassiefier = UA_NS0ID_PUBLISHEDDATAITEMSTYPE_PUBLISHEDDATA; |
1366 | 0 | retVal |= setVariableValueSource(server, valueCallback, publishedDataNode, |
1367 | 0 | publishingIntervalContext); |
1368 | |
|
1369 | 0 | UA_NodeId dataSetMetaDataNode = |
1370 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
1371 | 0 | UA_NS0ID(HASPROPERTY), publishedDataSet->head.identifier); |
1372 | 0 | if(UA_NodeId_isNull(&dataSetMetaDataNode)) |
1373 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1374 | | |
1375 | 0 | UA_NodePropertyContext *metaDataContext = (UA_NodePropertyContext *) |
1376 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1377 | 0 | metaDataContext->parentNodeId = publishedDataSet->head.identifier; |
1378 | 0 | metaDataContext->parentClassifier = UA_NS0ID_PUBLISHEDDATAITEMSTYPE; |
1379 | 0 | metaDataContext->elementClassiefier = UA_NS0ID_PUBLISHEDDATASETTYPE_DATASETMETADATA; |
1380 | 0 | retVal |= setVariableValueSource(server, valueCallback, |
1381 | 0 | dataSetMetaDataNode, metaDataContext); |
1382 | |
|
1383 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1384 | 0 | retVal |= addRef(server, publishedDataSet->head.identifier, UA_NS0ID(HASCOMPONENT), |
1385 | 0 | UA_NS0ID(PUBLISHEDDATAITEMSTYPE_ADDVARIABLES), true); |
1386 | 0 | retVal |= addRef(server, publishedDataSet->head.identifier, UA_NS0ID(HASCOMPONENT), |
1387 | 0 | UA_NS0ID(PUBLISHEDDATAITEMSTYPE_REMOVEVARIABLES), true); |
1388 | 0 | } |
1389 | 0 | return retVal; |
1390 | 0 | } |
1391 | | |
1392 | | static UA_StatusCode |
1393 | | addPublishedDataItemsAction(UA_Server *server, |
1394 | | const UA_NodeId *sessionId, void *sessionContext, |
1395 | | const UA_NodeId *methodId, void *methodContext, |
1396 | | const UA_NodeId *objectId, void *objectContext, |
1397 | | size_t inputSize, const UA_Variant *input, |
1398 | 0 | size_t outputSize, UA_Variant *output){ |
1399 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1400 | 0 | size_t fieldNameAliasesSize = input[1].arrayLength; |
1401 | 0 | UA_String * fieldNameAliases = (UA_String *) input[1].data; |
1402 | 0 | size_t fieldFlagsSize = input[2].arrayLength; |
1403 | 0 | UA_DataSetFieldFlags * fieldFlags = (UA_DataSetFieldFlags *) input[2].data; |
1404 | 0 | size_t variablesToAddSize = input[3].arrayLength; |
1405 | 0 | UA_PublishedVariableDataType *eoAddVar = |
1406 | 0 | (UA_PublishedVariableDataType *)input[3].data; |
1407 | |
|
1408 | 0 | if(fieldNameAliasesSize != fieldFlagsSize || |
1409 | 0 | fieldFlagsSize != variablesToAddSize) |
1410 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
1411 | | |
1412 | 0 | UA_PublishedDataSetConfig publishedDataSetConfig; |
1413 | 0 | memset(&publishedDataSetConfig, 0, sizeof(publishedDataSetConfig)); |
1414 | 0 | publishedDataSetConfig.name = *((UA_String *) input[0].data); |
1415 | 0 | publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS; |
1416 | |
|
1417 | 0 | UA_NodeId dataSetItemsNodeId; |
1418 | 0 | retVal |= UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, |
1419 | 0 | &dataSetItemsNodeId).addResult; |
1420 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1421 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1422 | 0 | "addPublishedDataset failed"); |
1423 | 0 | return retVal; |
1424 | 0 | } |
1425 | | |
1426 | 0 | UA_DataSetFieldConfig dataSetFieldConfig; |
1427 | 0 | for(size_t j = 0; j < variablesToAddSize; ++j) { |
1428 | | /* Prepare the config */ |
1429 | 0 | memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig)); |
1430 | 0 | dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE; |
1431 | 0 | dataSetFieldConfig.field.variable.fieldNameAlias = fieldNameAliases[j]; |
1432 | 0 | dataSetFieldConfig.field.variable.publishParameters = eoAddVar[j]; |
1433 | 0 | if(fieldFlags[j] == UA_DATASETFIELDFLAGS_PROMOTEDFIELD) |
1434 | 0 | dataSetFieldConfig.field.variable.promotedField = true; |
1435 | 0 | retVal |= UA_Server_addDataSetField(server, dataSetItemsNodeId, |
1436 | 0 | &dataSetFieldConfig, NULL).result; |
1437 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1438 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
1439 | 0 | "addDataSetField failed"); |
1440 | 0 | return retVal; |
1441 | 0 | } |
1442 | 0 | } |
1443 | | |
1444 | 0 | UA_Variant_setScalarCopy(output, &dataSetItemsNodeId, &UA_TYPES[UA_TYPES_NODEID]); |
1445 | 0 | return retVal; |
1446 | 0 | } |
1447 | | |
1448 | | static UA_StatusCode |
1449 | | addVariablesAction(UA_Server *server, |
1450 | | const UA_NodeId *sessionId, void *sessionContext, |
1451 | | const UA_NodeId *methodId, void *methodContext, |
1452 | | const UA_NodeId *objectId, void *objectContext, |
1453 | | size_t inputSize, const UA_Variant *input, |
1454 | 0 | size_t outputSize, UA_Variant *output){ |
1455 | 0 | return UA_STATUSCODE_GOOD; |
1456 | 0 | } |
1457 | | |
1458 | | static UA_StatusCode |
1459 | | removeVariablesAction(UA_Server *server, |
1460 | | const UA_NodeId *sessionId, void *sessionContext, |
1461 | | const UA_NodeId *methodId, void *methodContext, |
1462 | | const UA_NodeId *objectId, void *objectContext, |
1463 | | size_t inputSize, const UA_Variant *input, |
1464 | 0 | size_t outputSize, UA_Variant *output){ |
1465 | 0 | return UA_STATUSCODE_GOOD; |
1466 | 0 | } |
1467 | | |
1468 | | static UA_StatusCode |
1469 | | removePublishedDataSetAction(UA_Server *server, |
1470 | | const UA_NodeId *sessionId, void *sessionContext, |
1471 | | const UA_NodeId *methodId, void *methodContext, |
1472 | | const UA_NodeId *objectId, void *objectContext, |
1473 | | size_t inputSize, const UA_Variant *input, |
1474 | 0 | size_t outputSize, UA_Variant *output){ |
1475 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *) input[0].data); |
1476 | 0 | return UA_Server_removePublishedDataSet(server, nodeToRemove); |
1477 | 0 | } |
1478 | | |
1479 | | /*********************/ |
1480 | | /* SubscribedDataSet */ |
1481 | | /*********************/ |
1482 | | |
1483 | | UA_StatusCode |
1484 | | addSubscribedDataSetRepresentation(UA_Server *server, |
1485 | 0 | UA_SubscribedDataSet *subscribedDataSet) { |
1486 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1487 | |
|
1488 | 0 | UA_StatusCode ret = UA_STATUSCODE_GOOD; |
1489 | 0 | if(subscribedDataSet->config.name.length > 512) |
1490 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
1491 | | |
1492 | 0 | UA_STACKARRAY(char, sdsName, sizeof(char) * subscribedDataSet->config.name.length +1); |
1493 | 0 | memcpy(sdsName, subscribedDataSet->config.name.data, subscribedDataSet->config.name.length); |
1494 | 0 | sdsName[subscribedDataSet->config.name.length] = '\0'; |
1495 | |
|
1496 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1497 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", sdsName); |
1498 | 0 | addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), /* Create a new id */ |
1499 | 0 | UA_NS0ID(PUBLISHSUBSCRIBE_SUBSCRIBEDDATASETS), |
1500 | 0 | UA_NS0ID(HASCOMPONENT), |
1501 | 0 | UA_QUALIFIEDNAME(0, sdsName), |
1502 | 0 | UA_NS0ID(STANDALONESUBSCRIBEDDATASETTYPE), |
1503 | 0 | &object_attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1504 | 0 | NULL, &subscribedDataSet->head.identifier); |
1505 | 0 | UA_NodeId sdsObjectNode = |
1506 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "SubscribedDataSet"), |
1507 | 0 | UA_NS0ID(HASCOMPONENT), subscribedDataSet->head.identifier); |
1508 | 0 | UA_NodeId metaDataId = |
1509 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
1510 | 0 | UA_NS0ID(HASPROPERTY), subscribedDataSet->head.identifier); |
1511 | 0 | UA_NodeId connectedId = |
1512 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "IsConnected"), |
1513 | 0 | UA_NS0ID(HASPROPERTY), subscribedDataSet->head.identifier); |
1514 | |
|
1515 | 0 | if(UA_NodeId_equal(&sdsObjectNode, &UA_NODEID_NULL) || |
1516 | 0 | UA_NodeId_equal(&metaDataId, &UA_NODEID_NULL) || |
1517 | 0 | UA_NodeId_equal(&connectedId, &UA_NODEID_NULL)) { |
1518 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1519 | 0 | } |
1520 | 0 | if(subscribedDataSet->config.subscribedDataSetType == UA_PUBSUB_SDS_TARGET){ |
1521 | 0 | UA_VariableAttributes attr = UA_VariableAttributes_default; |
1522 | 0 | UA_NodeId targetVarsId; |
1523 | 0 | attr.displayName = UA_LOCALIZEDTEXT("", "TargetVariables"); |
1524 | 0 | attr.dataType = UA_TYPES[UA_TYPES_FIELDTARGETDATATYPE].typeId; |
1525 | 0 | attr.valueRank = UA_VALUERANK_ONE_DIMENSION; |
1526 | 0 | attr.arrayDimensionsSize = 1; |
1527 | 0 | UA_UInt32 arrayDimensions[1]; |
1528 | 0 | arrayDimensions[0] = (UA_UInt32) |
1529 | 0 | subscribedDataSet->config.subscribedDataSet.target.targetVariablesSize; |
1530 | 0 | attr.arrayDimensions = arrayDimensions; |
1531 | 0 | attr.accessLevel = UA_ACCESSLEVELMASK_READ; |
1532 | 0 | UA_Variant_setArray(&attr.value, |
1533 | 0 | subscribedDataSet->config.subscribedDataSet.target.targetVariables, |
1534 | 0 | subscribedDataSet->config.subscribedDataSet.target.targetVariablesSize, |
1535 | 0 | &UA_TYPES[UA_TYPES_FIELDTARGETDATATYPE]); |
1536 | 0 | ret |= addNode(server, UA_NODECLASS_VARIABLE, UA_NODEID_NULL, sdsObjectNode, |
1537 | 0 | UA_NS0ID(HASPROPERTY), UA_QUALIFIEDNAME(0, "TargetVariables"), |
1538 | 0 | UA_NS0ID(PROPERTYTYPE), &attr, &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES], |
1539 | 0 | NULL, &targetVarsId); |
1540 | 0 | } |
1541 | |
|
1542 | 0 | UA_NodePropertyContext *isConnectedNodeContext = (UA_NodePropertyContext *) |
1543 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1544 | 0 | isConnectedNodeContext->parentNodeId = subscribedDataSet->head.identifier; |
1545 | 0 | isConnectedNodeContext->parentClassifier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETREFDATATYPE; |
1546 | 0 | isConnectedNodeContext->elementClassiefier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE_ISCONNECTED; |
1547 | |
|
1548 | 0 | UA_CallbackValueSource valueCallback; |
1549 | 0 | valueCallback.read = ReadCallback; |
1550 | 0 | valueCallback.write = NULL; |
1551 | 0 | ret |= setVariableValueSource(server, valueCallback, connectedId, isConnectedNodeContext); |
1552 | |
|
1553 | 0 | UA_NodePropertyContext *metaDataContext = (UA_NodePropertyContext *) |
1554 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1555 | 0 | metaDataContext->parentNodeId = subscribedDataSet->head.identifier; |
1556 | 0 | metaDataContext->parentClassifier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETREFDATATYPE; |
1557 | 0 | metaDataContext->elementClassiefier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE_DATASETMETADATA; |
1558 | 0 | ret |= setVariableValueSource(server, valueCallback, metaDataId, metaDataContext); |
1559 | |
|
1560 | 0 | return ret; |
1561 | 0 | } |
1562 | | |
1563 | | /**********************************************/ |
1564 | | /* WriterGroup */ |
1565 | | /**********************************************/ |
1566 | | |
1567 | | static UA_StatusCode |
1568 | | readContentMask(UA_Server *server, const UA_NodeId *sessionId, |
1569 | | void *sessionContext, const UA_NodeId *nodeId, |
1570 | | void *nodeContext, UA_Boolean includeSourceTimeStamp, |
1571 | 0 | const UA_NumericRange *range, UA_DataValue *value) { |
1572 | 0 | UA_WriterGroup *writerGroup = (UA_WriterGroup*)nodeContext; |
1573 | 0 | if((writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED && |
1574 | 0 | writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) || |
1575 | 0 | writerGroup->config.messageSettings.content.decoded.type != |
1576 | 0 | &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]) |
1577 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1578 | 0 | UA_UadpWriterGroupMessageDataType *wgm = (UA_UadpWriterGroupMessageDataType*) |
1579 | 0 | writerGroup->config.messageSettings.content.decoded.data; |
1580 | |
|
1581 | 0 | UA_Variant_setScalarCopy(&value->value, &wgm->networkMessageContentMask, |
1582 | 0 | &UA_TYPES[UA_TYPES_UADPNETWORKMESSAGECONTENTMASK]); |
1583 | 0 | value->hasValue = true; |
1584 | 0 | return UA_STATUSCODE_GOOD; |
1585 | 0 | } |
1586 | | |
1587 | | static UA_StatusCode |
1588 | | writeContentMask(UA_Server *server, const UA_NodeId *sessionId, |
1589 | | void *sessionContext, const UA_NodeId *nodeId, |
1590 | | void *nodeContext, const UA_NumericRange *range, |
1591 | 0 | const UA_DataValue *value) { |
1592 | 0 | UA_WriterGroup *writerGroup = (UA_WriterGroup*)nodeContext; |
1593 | 0 | if((writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED && |
1594 | 0 | writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) || |
1595 | 0 | writerGroup->config.messageSettings.content.decoded.type != |
1596 | 0 | &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]) |
1597 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1598 | 0 | UA_UadpWriterGroupMessageDataType *wgm = (UA_UadpWriterGroupMessageDataType*) |
1599 | 0 | writerGroup->config.messageSettings.content.decoded.data; |
1600 | |
|
1601 | 0 | if(!value->value.type) |
1602 | 0 | return UA_STATUSCODE_BADTYPEMISMATCH; |
1603 | 0 | if(value->value.type->typeKind != UA_DATATYPEKIND_ENUM && |
1604 | 0 | value->value.type->typeKind != UA_DATATYPEKIND_INT32) |
1605 | 0 | return UA_STATUSCODE_BADTYPEMISMATCH; |
1606 | | |
1607 | 0 | wgm->networkMessageContentMask = *(UA_UadpNetworkMessageContentMask*)value->value.data; |
1608 | 0 | return UA_STATUSCODE_GOOD; |
1609 | 0 | } |
1610 | | |
1611 | | static UA_StatusCode |
1612 | | readGroupVersion(UA_Server *server, const UA_NodeId *sessionId, |
1613 | | void *sessionContext, const UA_NodeId *nodeId, |
1614 | | void *nodeContext, UA_Boolean includeSourceTimeStamp, |
1615 | 0 | const UA_NumericRange *range, UA_DataValue *value) { |
1616 | 0 | UA_WriterGroup *writerGroup = (UA_WriterGroup*)nodeContext; |
1617 | 0 | if((writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED && |
1618 | 0 | writerGroup->config.messageSettings.encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) || |
1619 | 0 | writerGroup->config.messageSettings.content.decoded.type != |
1620 | 0 | &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]) |
1621 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1622 | 0 | UA_UadpWriterGroupMessageDataType *wgm = (UA_UadpWriterGroupMessageDataType*) |
1623 | 0 | writerGroup->config.messageSettings.content.decoded.data; |
1624 | |
|
1625 | 0 | UA_Variant_setScalarCopy(&value->value, &wgm->groupVersion, |
1626 | 0 | &UA_TYPES[UA_DATATYPEKIND_UINT32]); |
1627 | 0 | value->hasValue = true; |
1628 | 0 | return UA_STATUSCODE_GOOD; |
1629 | 0 | } |
1630 | | |
1631 | | UA_StatusCode |
1632 | 0 | addWriterGroupRepresentation(UA_Server *server, UA_WriterGroup *writerGroup) { |
1633 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1634 | |
|
1635 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1636 | 0 | if(writerGroup->config.name.length > 512) |
1637 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1638 | 0 | char wgName[513]; |
1639 | 0 | memcpy(wgName, writerGroup->config.name.data, writerGroup->config.name.length); |
1640 | 0 | wgName[writerGroup->config.name.length] = '\0'; |
1641 | |
|
1642 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1643 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", wgName); |
1644 | 0 | retVal = addNode(server, UA_NODECLASS_OBJECT, |
1645 | 0 | UA_NODEID_NUMERIC(1, 0), /* create a new id */ |
1646 | 0 | writerGroup->linkedConnection->head.identifier, UA_NS0ID(HASCOMPONENT), |
1647 | 0 | UA_QUALIFIEDNAME(0, wgName), UA_NS0ID(WRITERGROUPTYPE), &object_attr, |
1648 | 0 | &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], NULL, &writerGroup->head.identifier); |
1649 | |
|
1650 | 0 | UA_NodeId keepAliveNode = |
1651 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "KeepAliveTime"), |
1652 | 0 | UA_NS0ID(HASPROPERTY), writerGroup->head.identifier); |
1653 | 0 | UA_NodeId publishingIntervalNode = |
1654 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"), |
1655 | 0 | UA_NS0ID(HASPROPERTY), writerGroup->head.identifier); |
1656 | 0 | UA_NodeId statusIdNode = |
1657 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Status"), |
1658 | 0 | UA_NS0ID(HASCOMPONENT), writerGroup->head.identifier); |
1659 | |
|
1660 | 0 | if(UA_NodeId_isNull(&statusIdNode)) |
1661 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1662 | | |
1663 | 0 | UA_NodeId stateIdNode = |
1664 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
1665 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
1666 | |
|
1667 | 0 | if(UA_NodeId_isNull(&keepAliveNode) || |
1668 | 0 | UA_NodeId_isNull(&publishingIntervalNode) || |
1669 | 0 | UA_NodeId_isNull(&stateIdNode)) |
1670 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1671 | | |
1672 | 0 | UA_NodePropertyContext * publishingIntervalContext = (UA_NodePropertyContext *) |
1673 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
1674 | 0 | publishingIntervalContext->parentNodeId = writerGroup->head.identifier; |
1675 | 0 | publishingIntervalContext->parentClassifier = UA_NS0ID_WRITERGROUPTYPE; |
1676 | 0 | publishingIntervalContext->elementClassiefier = UA_NS0ID_WRITERGROUPTYPE_PUBLISHINGINTERVAL; |
1677 | 0 | UA_CallbackValueSource valueCallback; |
1678 | 0 | valueCallback.read = ReadCallback; |
1679 | 0 | valueCallback.write = WriteCallback; |
1680 | 0 | retVal |= setVariableValueSource(server, valueCallback, |
1681 | 0 | publishingIntervalNode, publishingIntervalContext); |
1682 | 0 | writeAccessLevelAttribute(server, publishingIntervalNode, |
1683 | 0 | UA_ACCESSLEVELMASK_READ ^ UA_ACCESSLEVELMASK_WRITE); |
1684 | |
|
1685 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
1686 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
1687 | 0 | if(!UA_NodeId_isNull(&stateNodeId)) { |
1688 | 0 | UA_DataSource stateDataSource; |
1689 | 0 | stateDataSource.read = pubSubStateVariableDataSourceRead; |
1690 | 0 | stateDataSource.write = NULL; |
1691 | 0 | retVal |= UA_Server_setVariableNode_dataSource(server, stateNodeId, stateDataSource); |
1692 | 0 | } |
1693 | |
|
1694 | 0 | UA_NodeId priorityNode = |
1695 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Priority"), |
1696 | 0 | UA_NS0ID(HASPROPERTY), writerGroup->head.identifier); |
1697 | 0 | UA_NodeId writerGroupIdNode = |
1698 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "WriterGroupId"), |
1699 | 0 | UA_NS0ID(HASPROPERTY), writerGroup->head.identifier); |
1700 | |
|
1701 | 0 | UA_Variant value; |
1702 | 0 | UA_Variant_init(&value); |
1703 | 0 | UA_Variant_setScalar(&value, &writerGroup->config.publishingInterval, &UA_TYPES[UA_TYPES_DURATION]); |
1704 | 0 | writeValueAttribute(server, publishingIntervalNode, &value); |
1705 | 0 | UA_Variant_setScalar(&value, &writerGroup->config.keepAliveTime, &UA_TYPES[UA_TYPES_DURATION]); |
1706 | 0 | writeValueAttribute(server, keepAliveNode, &value); |
1707 | 0 | UA_Variant_setScalar(&value, &writerGroup->config.priority, &UA_TYPES[UA_TYPES_BYTE]); |
1708 | 0 | writeValueAttribute(server, priorityNode, &value); |
1709 | 0 | UA_Variant_setScalar(&value, &writerGroup->config.writerGroupId, &UA_TYPES[UA_TYPES_UINT16]); |
1710 | 0 | writeValueAttribute(server, writerGroupIdNode, &value); |
1711 | |
|
1712 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", "MessageSettings"); |
1713 | 0 | retVal |= addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), |
1714 | 0 | writerGroup->head.identifier, UA_NS0ID(HASCOMPONENT), |
1715 | 0 | UA_QUALIFIEDNAME(0, "MessageSettings"), |
1716 | 0 | UA_NS0ID(UADPWRITERGROUPMESSAGETYPE), &object_attr, |
1717 | 0 | &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1718 | 0 | NULL, NULL); |
1719 | | |
1720 | | /* Find the variable with the content mask */ |
1721 | |
|
1722 | 0 | UA_NodeId messageSettingsId = |
1723 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "MessageSettings"), |
1724 | 0 | UA_NS0ID(HASCOMPONENT), writerGroup->head.identifier); |
1725 | 0 | UA_NodeId contentMaskId = |
1726 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "NetworkMessageContentMask"), |
1727 | 0 | UA_NS0ID(HASPROPERTY), messageSettingsId); |
1728 | 0 | if(!UA_NodeId_isNull(&contentMaskId)) { |
1729 | | /* Set the callback */ |
1730 | 0 | UA_CallbackValueSource ds; |
1731 | 0 | ds.read = readContentMask; |
1732 | 0 | ds.write = writeContentMask; |
1733 | 0 | setVariableNode_callbackValueSource(server, contentMaskId, ds); |
1734 | 0 | setNodeContext(server, contentMaskId, writerGroup); |
1735 | | |
1736 | | /* Make writable */ |
1737 | 0 | writeAccessLevelAttribute(server, contentMaskId, |
1738 | 0 | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_READ); |
1739 | |
|
1740 | 0 | } |
1741 | 0 | UA_NodeId groupVersionId = |
1742 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "GroupVersion"), |
1743 | 0 | UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY), messageSettingsId); |
1744 | 0 | if(!UA_NodeId_isNull(&groupVersionId)) { |
1745 | | /* Set the callback */ |
1746 | 0 | UA_CallbackValueSource ds; |
1747 | 0 | ds.read = readGroupVersion; |
1748 | 0 | ds.write = NULL; |
1749 | 0 | setVariableNode_callbackValueSource(server, groupVersionId, ds); |
1750 | 0 | setNodeContext(server, groupVersionId, writerGroup); |
1751 | | |
1752 | | /* Read only */ |
1753 | 0 | writeAccessLevelAttribute(server, groupVersionId, |
1754 | 0 | UA_ACCESSLEVELMASK_READ); |
1755 | |
|
1756 | 0 | } |
1757 | | |
1758 | | /* Add reference to methods */ |
1759 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1760 | 0 | retVal |= addRef(server, writerGroup->head.identifier, |
1761 | 0 | UA_NS0ID(HASCOMPONENT), |
1762 | 0 | UA_NS0ID(WRITERGROUPTYPE_ADDDATASETWRITER), true); |
1763 | 0 | retVal |= addRef(server, writerGroup->head.identifier, |
1764 | 0 | UA_NS0ID(HASCOMPONENT), |
1765 | 0 | UA_NS0ID(WRITERGROUPTYPE_REMOVEDATASETWRITER), true); |
1766 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1767 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
1768 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1769 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
1770 | 0 | } |
1771 | 0 | return retVal; |
1772 | 0 | } |
1773 | | |
1774 | | static UA_StatusCode |
1775 | | addWriterGroupAction(UA_Server *server, |
1776 | | const UA_NodeId *sessionId, void *sessionContext, |
1777 | | const UA_NodeId *methodId, void *methodContext, |
1778 | | const UA_NodeId *objectId, void *objectContext, |
1779 | | size_t inputSize, const UA_Variant *input, |
1780 | 0 | size_t outputSize, UA_Variant *output) { |
1781 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1782 | |
|
1783 | 0 | UA_NodeId writerGroupId; |
1784 | 0 | UA_WriterGroupDataType *writerGroup = (UA_WriterGroupDataType *)input->data; |
1785 | 0 | UA_StatusCode retVal = addWriterGroupConfig(server, *objectId, writerGroup, &writerGroupId); |
1786 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1787 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, "addWriterGroup failed"); |
1788 | 0 | return retVal; |
1789 | 0 | } |
1790 | | // TODO: Need to handle the UA_Server_setWriterGroupOperational based on the |
1791 | | // status variable in information model |
1792 | | |
1793 | 0 | UA_Variant_setScalarCopy(output, &writerGroupId, &UA_TYPES[UA_TYPES_NODEID]); |
1794 | 0 | return retVal; |
1795 | 0 | } |
1796 | | |
1797 | | static UA_StatusCode |
1798 | | removeGroupAction(UA_Server *server, |
1799 | | const UA_NodeId *sessionId, void *sessionContext, |
1800 | | const UA_NodeId *methodId, void *methodContext, |
1801 | | const UA_NodeId *objectId, void *objectContext, |
1802 | | size_t inputSize, const UA_Variant *input, |
1803 | 0 | size_t outputSize, UA_Variant *output){ |
1804 | 0 | UA_PubSubManager *psm = getPSM(server); |
1805 | 0 | if(!psm) |
1806 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1807 | | |
1808 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *)input->data); |
1809 | 0 | if(UA_WriterGroup_find(psm, nodeToRemove)) { |
1810 | 0 | return UA_Server_removeWriterGroup(server, nodeToRemove); |
1811 | 0 | } else { |
1812 | 0 | return UA_Server_removeReaderGroup(server, nodeToRemove); |
1813 | 0 | } |
1814 | 0 | } |
1815 | | |
1816 | | /**********************************************/ |
1817 | | /* ReserveIds */ |
1818 | | /**********************************************/ |
1819 | | |
1820 | | static UA_StatusCode |
1821 | | addReserveIdsAction(UA_Server *server, |
1822 | | const UA_NodeId *sessionId, void *sessionContext, |
1823 | | const UA_NodeId *methodId, void *methodContext, |
1824 | | const UA_NodeId *objectId, void *objectContext, |
1825 | | size_t inputSize, const UA_Variant *input, |
1826 | 0 | size_t outputSize, UA_Variant *output){ |
1827 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1828 | |
|
1829 | 0 | UA_PubSubManager *psm = getPSM(server); |
1830 | 0 | if(!psm) |
1831 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1832 | | |
1833 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1834 | 0 | UA_String transportProfileUri = *((UA_String *)input[0].data); |
1835 | 0 | UA_UInt16 numRegWriterGroupIds = *((UA_UInt16 *)input[1].data); |
1836 | 0 | UA_UInt16 numRegDataSetWriterIds = *((UA_UInt16 *)input[2].data); |
1837 | |
|
1838 | 0 | UA_UInt16 *writerGroupIds; |
1839 | 0 | UA_UInt16 *dataSetWriterIds; |
1840 | |
|
1841 | 0 | retVal |= UA_PubSubManager_reserveIds(psm, *sessionId, numRegWriterGroupIds, |
1842 | 0 | numRegDataSetWriterIds, transportProfileUri, |
1843 | 0 | &writerGroupIds, &dataSetWriterIds); |
1844 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1845 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, "addReserveIds failed"); |
1846 | 0 | return retVal; |
1847 | 0 | } |
1848 | | |
1849 | | /* Check the transportProfileUri */ |
1850 | 0 | UA_String profile_1 = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-uadp"); |
1851 | 0 | UA_String profile_2 = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-json"); |
1852 | |
|
1853 | 0 | if(UA_String_equal(&transportProfileUri, &profile_1) || |
1854 | 0 | UA_String_equal(&transportProfileUri, &profile_2)) { |
1855 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_SERVER, "ApplicationUri: %S", |
1856 | 0 | server->config.applicationDescription.applicationUri); |
1857 | 0 | retVal |= UA_Variant_setScalarCopy(&output[0], |
1858 | 0 | &server->config.applicationDescription.applicationUri, |
1859 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1860 | 0 | } else { |
1861 | 0 | retVal |= UA_Variant_setScalarCopy(&output[0], &psm->defaultPublisherId, |
1862 | 0 | &UA_TYPES[UA_TYPES_UINT64]); |
1863 | 0 | } |
1864 | |
|
1865 | 0 | UA_Variant_setArray(&output[1], writerGroupIds, |
1866 | 0 | numRegWriterGroupIds, &UA_TYPES[UA_TYPES_UINT16]); |
1867 | 0 | UA_Variant_setArray(&output[2], dataSetWriterIds, |
1868 | 0 | numRegDataSetWriterIds, &UA_TYPES[UA_TYPES_UINT16]); |
1869 | |
|
1870 | 0 | return retVal; |
1871 | 0 | } |
1872 | | |
1873 | | /**********************************************/ |
1874 | | /* ReaderGroup */ |
1875 | | /**********************************************/ |
1876 | | |
1877 | | UA_StatusCode |
1878 | 0 | addReaderGroupRepresentation(UA_Server *server, UA_ReaderGroup *readerGroup) { |
1879 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1880 | 0 | if(readerGroup->config.name.length > 512) |
1881 | 0 | return UA_STATUSCODE_BADCONFIGURATIONERROR; |
1882 | 0 | char rgName[513]; |
1883 | 0 | memcpy(rgName, readerGroup->config.name.data, readerGroup->config.name.length); |
1884 | 0 | rgName[readerGroup->config.name.length] = '\0'; |
1885 | |
|
1886 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1887 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", rgName); |
1888 | 0 | UA_StatusCode retVal = |
1889 | 0 | addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), /* create an id */ |
1890 | 0 | readerGroup->linkedConnection->head.identifier, |
1891 | 0 | UA_NS0ID(HASCOMPONENT), |
1892 | 0 | UA_QUALIFIEDNAME(0, rgName), UA_NS0ID(READERGROUPTYPE), |
1893 | 0 | &object_attr, &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1894 | 0 | NULL, &readerGroup->head.identifier); |
1895 | |
|
1896 | 0 | UA_NodeId statusIdNode = |
1897 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Status"), |
1898 | 0 | UA_NS0ID(HASCOMPONENT), readerGroup->head.identifier); |
1899 | |
|
1900 | 0 | if(UA_NodeId_isNull(&statusIdNode)) |
1901 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1902 | | |
1903 | 0 | UA_NodeId stateIdNode = |
1904 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
1905 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
1906 | |
|
1907 | 0 | if(UA_NodeId_isNull(&stateIdNode)) |
1908 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1909 | | |
1910 | 0 | UA_DataSource stateDataSource; |
1911 | 0 | stateDataSource.read = pubSubStateVariableDataSourceRead; |
1912 | 0 | stateDataSource.write = NULL; |
1913 | 0 | retVal |= UA_Server_setVariableNode_dataSource(server, stateIdNode, stateDataSource); |
1914 | |
|
1915 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
1916 | 0 | retVal |= addRef(server, readerGroup->head.identifier, UA_NS0ID(HASCOMPONENT), |
1917 | 0 | UA_NS0ID(READERGROUPTYPE_ADDDATASETREADER), true); |
1918 | 0 | retVal |= addRef(server, readerGroup->head.identifier, UA_NS0ID(HASCOMPONENT), |
1919 | 0 | UA_NS0ID(READERGROUPTYPE_REMOVEDATASETREADER), true); |
1920 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1921 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
1922 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
1923 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
1924 | 0 | } |
1925 | 0 | return retVal; |
1926 | 0 | } |
1927 | | |
1928 | | static UA_StatusCode |
1929 | | addReaderGroupAction(UA_Server *server, |
1930 | | const UA_NodeId *sessionId, void *sessionContext, |
1931 | | const UA_NodeId *methodId, void *methodContext, |
1932 | | const UA_NodeId *objectId, void *objectContext, |
1933 | | size_t inputSize, const UA_Variant *input, |
1934 | 0 | size_t outputSize, UA_Variant *output) { |
1935 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1936 | |
|
1937 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1938 | 0 | UA_ReaderGroupDataType *readerGroup = ((UA_ReaderGroupDataType *) input->data); |
1939 | 0 | UA_NodeId readerGroupId; |
1940 | 0 | retVal |= addReaderGroupConfig(server, *objectId, readerGroup, &readerGroupId); |
1941 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
1942 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, "addReaderGroup failed"); |
1943 | 0 | return retVal; |
1944 | 0 | } |
1945 | | // TODO: Need to handle the UA_Server_setReaderGroupOperational based on the |
1946 | | // status variable in information model |
1947 | | |
1948 | 0 | UA_Variant_setScalarCopy(output, &readerGroupId, &UA_TYPES[UA_TYPES_NODEID]); |
1949 | 0 | return retVal; |
1950 | 0 | } |
1951 | | |
1952 | | /**********************************************/ |
1953 | | /* DataSetWriter */ |
1954 | | /**********************************************/ |
1955 | | |
1956 | | UA_StatusCode |
1957 | 0 | addDataSetWriterRepresentation(UA_Server *server, UA_DataSetWriter *dataSetWriter) { |
1958 | |
|
1959 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
1960 | |
|
1961 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
1962 | 0 | if(dataSetWriter->config.name.length > 512) |
1963 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1964 | | |
1965 | 0 | char dswName[513]; |
1966 | 0 | memcpy(dswName, dataSetWriter->config.name.data, dataSetWriter->config.name.length); |
1967 | 0 | dswName[dataSetWriter->config.name.length] = '\0'; |
1968 | |
|
1969 | 0 | UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; |
1970 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", dswName); |
1971 | 0 | retVal = |
1972 | 0 | addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), /* create an id */ |
1973 | 0 | dataSetWriter->linkedWriterGroup->head.identifier, UA_NS0ID(HASDATASETWRITER), |
1974 | 0 | UA_QUALIFIEDNAME(0, dswName), UA_NS0ID(DATASETWRITERTYPE), &object_attr, |
1975 | 0 | &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
1976 | 0 | NULL, &dataSetWriter->head.identifier); |
1977 | | //if connected dataset is null this means it's configured for heartbeats |
1978 | 0 | if(dataSetWriter->connectedDataSet) { |
1979 | 0 | retVal |= addRef(server, dataSetWriter->connectedDataSet->head.identifier, |
1980 | 0 | UA_NS0ID(DATASETTOWRITER), dataSetWriter->head.identifier, true); |
1981 | 0 | } |
1982 | |
|
1983 | 0 | UA_NodeId dataSetWriterIdNode = |
1984 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetWriterId"), |
1985 | 0 | UA_NS0ID(HASPROPERTY), dataSetWriter->head.identifier); |
1986 | 0 | UA_NodeId keyFrameNode = |
1987 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "KeyFrameCount"), |
1988 | 0 | UA_NS0ID(HASPROPERTY), dataSetWriter->head.identifier); |
1989 | 0 | UA_NodeId dataSetFieldContentMaskNode = |
1990 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetFieldContentMask"), |
1991 | 0 | UA_NS0ID(HASPROPERTY), dataSetWriter->head.identifier); |
1992 | |
|
1993 | 0 | UA_NodeId statusIdNode = |
1994 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "Status"), |
1995 | 0 | UA_NS0ID(HASCOMPONENT), dataSetWriter->head.identifier); |
1996 | | |
1997 | 0 | if(UA_NodeId_isNull(&statusIdNode)) |
1998 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
1999 | | |
2000 | 0 | UA_NodeId stateIdNode = |
2001 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
2002 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
2003 | | |
2004 | | // TODO: The keyFrameNode is NULL here, should be check |
2005 | | // does not depend on the pubsub changes |
2006 | 0 | if(UA_NodeId_isNull(&dataSetWriterIdNode) || |
2007 | 0 | UA_NodeId_isNull(&dataSetFieldContentMaskNode) || |
2008 | 0 | UA_NodeId_isNull(&stateIdNode)) |
2009 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
2010 | | |
2011 | 0 | UA_NodePropertyContext *dataSetWriterIdContext = (UA_NodePropertyContext *) |
2012 | 0 | UA_malloc(sizeof(UA_NodePropertyContext)); |
2013 | 0 | dataSetWriterIdContext->parentNodeId = dataSetWriter->head.identifier; |
2014 | 0 | dataSetWriterIdContext->parentClassifier = UA_NS0ID_DATASETWRITERTYPE; |
2015 | 0 | dataSetWriterIdContext->elementClassiefier = UA_NS0ID_DATASETWRITERTYPE_DATASETWRITERID; |
2016 | 0 | UA_CallbackValueSource valueCallback; |
2017 | 0 | valueCallback.read = ReadCallback; |
2018 | 0 | valueCallback.write = NULL; |
2019 | 0 | retVal |= setVariableValueSource(server, valueCallback, |
2020 | 0 | dataSetWriterIdNode, dataSetWriterIdContext); |
2021 | |
|
2022 | 0 | UA_NodeId stateNodeId = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "State"), |
2023 | 0 | UA_NS0ID(HASCOMPONENT), statusIdNode); |
2024 | 0 | if(!UA_NodeId_isNull(&stateNodeId)) { |
2025 | 0 | UA_DataSource stateDataSource; |
2026 | 0 | stateDataSource.read = pubSubStateVariableDataSourceRead; |
2027 | 0 | stateDataSource.write = NULL; |
2028 | 0 | retVal |= UA_Server_setVariableNode_dataSource(server, stateNodeId, stateDataSource); |
2029 | 0 | } |
2030 | |
|
2031 | 0 | UA_Variant value; |
2032 | 0 | UA_Variant_init(&value); |
2033 | 0 | UA_Variant_setScalar(&value, &dataSetWriter->config.dataSetWriterId, |
2034 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
2035 | 0 | writeValueAttribute(server, dataSetWriterIdNode, &value); |
2036 | |
|
2037 | 0 | UA_Variant_setScalar(&value, &dataSetWriter->config.keyFrameCount, |
2038 | 0 | &UA_TYPES[UA_TYPES_UINT32]); |
2039 | 0 | writeValueAttribute(server, keyFrameNode, &value); |
2040 | |
|
2041 | 0 | UA_Variant_setScalar(&value, &dataSetWriter->config.dataSetFieldContentMask, |
2042 | 0 | &UA_TYPES[UA_TYPES_DATASETFIELDCONTENTMASK]); |
2043 | 0 | writeValueAttribute(server, dataSetFieldContentMaskNode, &value); |
2044 | |
|
2045 | 0 | object_attr.displayName = UA_LOCALIZEDTEXT("", "MessageSettings"); |
2046 | 0 | retVal |= addNode(server, UA_NODECLASS_OBJECT, UA_NODEID_NUMERIC(1, 0), |
2047 | 0 | dataSetWriter->head.identifier, UA_NS0ID(HASCOMPONENT), |
2048 | 0 | UA_QUALIFIEDNAME(0, "MessageSettings"), |
2049 | 0 | UA_NS0ID(UADPDATASETWRITERMESSAGETYPE), &object_attr, |
2050 | 0 | &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES], |
2051 | 0 | NULL, NULL); |
2052 | 0 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
2053 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
2054 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
2055 | 0 | retVal |= addRef(server, statusIdNode, UA_NS0ID(HASCOMPONENT), |
2056 | 0 | UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
2057 | 0 | } |
2058 | |
|
2059 | 0 | return retVal; |
2060 | 0 | } |
2061 | | |
2062 | | static UA_StatusCode |
2063 | | addDataSetWriterAction(UA_Server *server, |
2064 | | const UA_NodeId *sessionId, void *sessionContext, |
2065 | | const UA_NodeId *methodId, void *methodContext, |
2066 | | const UA_NodeId *objectId, void *objectContext, |
2067 | | size_t inputSize, const UA_Variant *input, |
2068 | 0 | size_t outputSize, UA_Variant *output) { |
2069 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2070 | |
|
2071 | 0 | UA_NodeId dataSetWriterId; |
2072 | 0 | UA_DataSetWriterDataType *dataSetWriterData = (UA_DataSetWriterDataType *)input->data; |
2073 | 0 | UA_StatusCode retVal = |
2074 | 0 | addDataSetWriterConfig(server, objectId, dataSetWriterData, &dataSetWriterId); |
2075 | 0 | if(retVal != UA_STATUSCODE_GOOD) { |
2076 | 0 | UA_LOG_ERROR(server->config.logging, UA_LOGCATEGORY_SERVER, |
2077 | 0 | "addDataSetWriter failed"); |
2078 | 0 | return retVal; |
2079 | 0 | } |
2080 | | |
2081 | 0 | UA_Variant_setScalarCopy(output, &dataSetWriterId, &UA_TYPES[UA_TYPES_NODEID]); |
2082 | 0 | return UA_STATUSCODE_GOOD; |
2083 | 0 | } |
2084 | | |
2085 | | static UA_StatusCode |
2086 | | removeDataSetWriterAction(UA_Server *server, |
2087 | | const UA_NodeId *sessionId, void *sessionContext, |
2088 | | const UA_NodeId *methodId, void *methodContext, |
2089 | | const UA_NodeId *objectId, void *objectContext, |
2090 | | size_t inputSize, const UA_Variant *input, |
2091 | 0 | size_t outputSize, UA_Variant *output){ |
2092 | 0 | UA_NodeId nodeToRemove = *((UA_NodeId *) input[0].data); |
2093 | 0 | return UA_Server_removeDataSetWriter(server, nodeToRemove); |
2094 | 0 | } |
2095 | | |
2096 | | /**********************************************/ |
2097 | | /* Destructors */ |
2098 | | /**********************************************/ |
2099 | | |
2100 | | static void |
2101 | | connectionTypeDestructor(UA_Server *server, |
2102 | | const UA_NodeId *sessionId, void *sessionContext, |
2103 | | const UA_NodeId *typeId, void *typeContext, |
2104 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2105 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2106 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2107 | 0 | "Connection destructor called!"); |
2108 | 0 | UA_NodeId publisherIdNode = |
2109 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublisherId"), |
2110 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2111 | 0 | UA_NodePropertyContext *ctx; |
2112 | 0 | getNodeContext(server, publisherIdNode, (void **)&ctx); |
2113 | 0 | if(!UA_NodeId_isNull(&publisherIdNode)) |
2114 | 0 | UA_free(ctx); |
2115 | 0 | } |
2116 | | |
2117 | | static void |
2118 | | writerGroupTypeDestructor(UA_Server *server, |
2119 | | const UA_NodeId *sessionId, void *sessionContext, |
2120 | | const UA_NodeId *typeId, void *typeContext, |
2121 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2122 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2123 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2124 | 0 | "WriterGroup destructor called!"); |
2125 | 0 | UA_NodeId intervalNode = |
2126 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishingInterval"), |
2127 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2128 | |
|
2129 | 0 | UA_NodePropertyContext *ctx; |
2130 | 0 | getNodeContext(server, intervalNode, (void **)&ctx); |
2131 | 0 | if(!UA_NodeId_isNull(&intervalNode)) |
2132 | 0 | UA_free(ctx); |
2133 | 0 | } |
2134 | | |
2135 | | static void |
2136 | | readerGroupTypeDestructor(UA_Server *server, |
2137 | | const UA_NodeId *sessionId, void *sessionContext, |
2138 | | const UA_NodeId *typeId, void *typeContext, |
2139 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2140 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2141 | 0 | } |
2142 | | |
2143 | | static void |
2144 | | dataSetWriterTypeDestructor(UA_Server *server, |
2145 | | const UA_NodeId *sessionId, void *sessionContext, |
2146 | | const UA_NodeId *typeId, void *typeContext, |
2147 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2148 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2149 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2150 | 0 | "DataSetWriter destructor called!"); |
2151 | |
|
2152 | 0 | UA_NodeId dataSetWriterIdNode = |
2153 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetWriterId"), |
2154 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2155 | |
|
2156 | 0 | UA_NodePropertyContext *ctx; |
2157 | 0 | getNodeContext(server, dataSetWriterIdNode, (void **)&ctx); |
2158 | 0 | if(!UA_NodeId_isNull(&dataSetWriterIdNode)) |
2159 | 0 | UA_free(ctx); |
2160 | 0 | } |
2161 | | |
2162 | | static void |
2163 | | dataSetReaderTypeDestructor(UA_Server *server, |
2164 | | const UA_NodeId *sessionId, void *sessionContext, |
2165 | | const UA_NodeId *typeId, void *typeContext, |
2166 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2167 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2168 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2169 | 0 | "DataSetReader destructor called!"); |
2170 | 0 | UA_NodeId publisherIdNode = |
2171 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublisherId"), |
2172 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2173 | |
|
2174 | 0 | UA_NodePropertyContext *ctx; |
2175 | 0 | getNodeContext(server, publisherIdNode, (void **)&ctx); |
2176 | 0 | if(!UA_NodeId_isNull(&publisherIdNode)) |
2177 | 0 | UA_free(ctx); |
2178 | 0 | } |
2179 | | |
2180 | | static void |
2181 | | publishedDataItemsTypeDestructor(UA_Server *server, |
2182 | | const UA_NodeId *sessionId, void *sessionContext, |
2183 | | const UA_NodeId *typeId, void *typeContext, |
2184 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2185 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2186 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2187 | 0 | "PublishedDataItems destructor called!"); |
2188 | 0 | void *childContext; |
2189 | 0 | UA_NodeId node = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "PublishedData"), |
2190 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2191 | 0 | getNodeContext(server, node, (void**)&childContext); |
2192 | 0 | if(!UA_NodeId_isNull(&node)) |
2193 | 0 | UA_free(childContext); |
2194 | |
|
2195 | 0 | node = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "ConfigurationVersion"), |
2196 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2197 | 0 | getNodeContext(server, node, (void**)&childContext); |
2198 | 0 | if(!UA_NodeId_isNull(&node)) |
2199 | 0 | UA_free(childContext); |
2200 | |
|
2201 | 0 | node = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
2202 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2203 | 0 | getNodeContext(server, node, (void**)&childContext); |
2204 | 0 | if(!UA_NodeId_isNull(&node)) |
2205 | 0 | UA_free(childContext); |
2206 | 0 | } |
2207 | | |
2208 | | static void |
2209 | | subscribedDataSetTypeDestructor(UA_Server *server, |
2210 | | const UA_NodeId *sessionId, void *sessionContext, |
2211 | | const UA_NodeId *typeId, void *typeContext, |
2212 | 0 | const UA_NodeId *nodeId, void **nodeContext) { |
2213 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2214 | 0 | UA_LOG_INFO(server->config.logging, UA_LOGCATEGORY_PUBSUB, |
2215 | 0 | "Standalone SubscribedDataSet destructor called!"); |
2216 | 0 | void *childContext; |
2217 | 0 | UA_NodeId node = |
2218 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
2219 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2220 | 0 | getNodeContext(server, node, (void**)&childContext); |
2221 | 0 | if(!UA_NodeId_equal(&UA_NODEID_NULL , &node)) |
2222 | 0 | UA_free(childContext); |
2223 | 0 | node = findSingleChildNode(server, UA_QUALIFIEDNAME(0, "IsConnected"), |
2224 | 0 | UA_NS0ID(HASPROPERTY), *nodeId); |
2225 | 0 | getNodeContext(server, node, (void**)&childContext); |
2226 | 0 | if(!UA_NodeId_equal(&UA_NODEID_NULL , &node)) |
2227 | 0 | UA_free(childContext); |
2228 | 0 | } |
2229 | | |
2230 | | /*************************************/ |
2231 | | /* PubSub configurator */ |
2232 | | /*************************************/ |
2233 | | |
2234 | | #ifdef UA_ENABLE_PUBSUB_FILE_CONFIG |
2235 | | |
2236 | | /* Callback function that will be executed when the method "PubSub configurator |
2237 | | * (replace config)" is called. */ |
2238 | | static UA_StatusCode |
2239 | | UA_loadPubSubConfigMethodCallback(UA_Server *server, |
2240 | | const UA_NodeId *sessionId, void *sessionContext, |
2241 | | const UA_NodeId *methodId, void *methodContext, |
2242 | | const UA_NodeId *objectId, void *objectContext, |
2243 | | size_t inputSize, const UA_Variant *input, |
2244 | | size_t outputSize, UA_Variant *output) { |
2245 | | UA_LOCK_ASSERT(&server->serviceMutex); |
2246 | | if(inputSize == 1) { |
2247 | | UA_ByteString *inputStr = (UA_ByteString*)input->data; |
2248 | | return UA_Server_loadPubSubConfigFromByteString(server, *inputStr); |
2249 | | } else if(inputSize > 1) { |
2250 | | return UA_STATUSCODE_BADTOOMANYARGUMENTS; |
2251 | | } else { |
2252 | | return UA_STATUSCODE_BADARGUMENTSMISSING; |
2253 | | } |
2254 | | } |
2255 | | |
2256 | | static void |
2257 | | deletePubSubConfigMethodFinalize(void *application, void *context) { |
2258 | | UA_PubSubManager *manager = (UA_PubSubManager *) application; |
2259 | | UA_Server *server = manager->sc.server; |
2260 | | lockServer(manager->sc.server); |
2261 | | UA_PubSubManager_clear(manager); |
2262 | | unlockServer(server); |
2263 | | UA_free(context); |
2264 | | } |
2265 | | |
2266 | | /* Callback function that will be executed when the method "PubSub configurator |
2267 | | * (delete config)" is called. */ |
2268 | | static UA_StatusCode |
2269 | | UA_deletePubSubConfigMethodCallback(UA_Server *server, |
2270 | | const UA_NodeId *sessionId, void *sessionContext, |
2271 | | const UA_NodeId *methodId, void *methodContext, |
2272 | | const UA_NodeId *objectId, void *objectContext, |
2273 | | size_t inputSize, const UA_Variant *input, |
2274 | | size_t outputSize, UA_Variant *output) { |
2275 | | UA_LOCK_ASSERT(&server->serviceMutex); |
2276 | | UA_PubSubManager *psm = getPSM(server); |
2277 | | if(psm) { |
2278 | | psm->sc.stop(&psm->sc); |
2279 | | UA_DelayedCallback *dc = (UA_DelayedCallback*)UA_calloc(1, sizeof(UA_DelayedCallback)); |
2280 | | if(!dc) |
2281 | | return UA_STATUSCODE_BADOUTOFMEMORY; |
2282 | | dc->callback = deletePubSubConfigMethodFinalize; |
2283 | | dc->application = psm; |
2284 | | dc->context = dc; |
2285 | | server->config.eventLoop->addDelayedCallback(psm->sc.server->config.eventLoop, dc); |
2286 | | } |
2287 | | |
2288 | | return UA_STATUSCODE_GOOD; |
2289 | | } |
2290 | | |
2291 | | #endif |
2292 | | |
2293 | | UA_StatusCode |
2294 | 553 | initPubSubNS0(UA_Server *server) { |
2295 | 553 | UA_LOCK_ASSERT(&server->serviceMutex); |
2296 | | |
2297 | 553 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
2298 | 553 | UA_String profileArray[1]; |
2299 | 553 | profileArray[0] = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp"); |
2300 | | |
2301 | 553 | retVal |= writePubSubNs0VariableArray(server, |
2302 | 553 | UA_NS0ID(PUBLISHSUBSCRIBE_SUPPORTEDTRANSPORTPROFILES), |
2303 | 553 | profileArray, 1, &UA_TYPES[UA_TYPES_STRING]); |
2304 | | |
2305 | | /* Set read callback for PublishSubscribeType Status State (mandatory) */ |
2306 | 553 | UA_CallbackValueSource statusCallback; |
2307 | 553 | statusCallback.read = pubSubStateVariableDataSourceRead; |
2308 | 553 | statusCallback.write = NULL; |
2309 | 553 | retVal |= setVariableValueSource(server, statusCallback, |
2310 | 553 | UA_NS0ID(PUBLISHSUBSCRIBE_STATUS_STATE), NULL); |
2311 | | |
2312 | 553 | if(server->config.pubSubConfig.enableInformationModelMethods) { |
2313 | | /* Add missing references */ |
2314 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_PUBLISHEDDATASETS), |
2315 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(DATASETFOLDERTYPE_ADDDATASETFOLDER), true); |
2316 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_PUBLISHEDDATASETS), |
2317 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(DATASETFOLDERTYPE_ADDPUBLISHEDDATAITEMS), true); |
2318 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_PUBLISHEDDATASETS), |
2319 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(DATASETFOLDERTYPE_REMOVEPUBLISHEDDATASET), true); |
2320 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_PUBLISHEDDATASETS), |
2321 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(DATASETFOLDERTYPE_REMOVEDATASETFOLDER), true); |
2322 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_STATUS), |
2323 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), true); |
2324 | 553 | retVal |= addRef(server, UA_NS0ID(PUBLISHSUBSCRIBE_STATUS), |
2325 | 553 | UA_NS0ID(HASCOMPONENT), UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), true); |
2326 | | |
2327 | | /* Set method callbacks */ |
2328 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBLISHSUBSCRIBE_ADDCONNECTION), addPubSubConnectionAction); |
2329 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBLISHSUBSCRIBE_REMOVECONNECTION), removeConnectionAction); |
2330 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(DATASETFOLDERTYPE_ADDDATASETFOLDER), addDataSetFolderAction); |
2331 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(DATASETFOLDERTYPE_REMOVEDATASETFOLDER), removeDataSetFolderAction); |
2332 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(DATASETFOLDERTYPE_ADDPUBLISHEDDATAITEMS), addPublishedDataItemsAction); |
2333 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(DATASETFOLDERTYPE_REMOVEPUBLISHEDDATASET), removePublishedDataSetAction); |
2334 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBLISHEDDATAITEMSTYPE_ADDVARIABLES), addVariablesAction); |
2335 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBLISHEDDATAITEMSTYPE_REMOVEVARIABLES), removeVariablesAction); |
2336 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBSUBCONNECTIONTYPE_ADDWRITERGROUP), addWriterGroupAction); |
2337 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBSUBCONNECTIONTYPE_ADDREADERGROUP), addReaderGroupAction); |
2338 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBSUBCONNECTIONTYPE_REMOVEGROUP), removeGroupAction); |
2339 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(WRITERGROUPTYPE_ADDDATASETWRITER), addDataSetWriterAction); |
2340 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(WRITERGROUPTYPE_REMOVEDATASETWRITER), removeDataSetWriterAction); |
2341 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(READERGROUPTYPE_ADDDATASETREADER), addDataSetReaderAction); |
2342 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(READERGROUPTYPE_REMOVEDATASETREADER), removeDataSetReaderAction); |
2343 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBLISHSUBSCRIBE_PUBSUBCONFIGURATION_RESERVEIDS), addReserveIdsAction); |
2344 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBSUBSTATUSTYPE_ENABLE), enablePubSubObjectAction); |
2345 | 553 | retVal |= setMethodNode_callback(server, UA_NS0ID(PUBSUBSTATUSTYPE_DISABLE), disablePubSubObjectAction); |
2346 | | |
2347 | | #ifdef UA_ENABLE_PUBSUB_FILE_CONFIG |
2348 | | /* Adds method node to server. This method is used to load binary files for |
2349 | | * PubSub configuration and delete / replace old PubSub configurations. */ |
2350 | | UA_Argument inputArgument; |
2351 | | UA_Argument_init(&inputArgument); |
2352 | | inputArgument.description = UA_LOCALIZEDTEXT("", "PubSub config binfile"); |
2353 | | inputArgument.name = UA_STRING("BinFile"); |
2354 | | inputArgument.dataType = UA_TYPES[UA_TYPES_BYTESTRING].typeId; |
2355 | | inputArgument.valueRank = UA_VALUERANK_SCALAR; |
2356 | | |
2357 | | UA_MethodAttributes configAttr = UA_MethodAttributes_default; |
2358 | | configAttr.description = UA_LOCALIZEDTEXT("","Load binary configuration file"); |
2359 | | configAttr.displayName = UA_LOCALIZEDTEXT("","LoadPubSubConfigurationFile"); |
2360 | | configAttr.executable = true; |
2361 | | configAttr.userExecutable = true; |
2362 | | retVal |= addMethodNode(server, UA_NODEID_NULL, |
2363 | | UA_NS0ID(PUBLISHSUBSCRIBE), UA_NS0ID(HASORDEREDCOMPONENT), |
2364 | | UA_QUALIFIEDNAME(1, "PubSub configuration"), |
2365 | | &configAttr, UA_loadPubSubConfigMethodCallback, |
2366 | | 1, &inputArgument, UA_NODEID_NULL, NULL, |
2367 | | 0, NULL, UA_NODEID_NULL, NULL, |
2368 | | NULL, NULL); |
2369 | | |
2370 | | /* Adds method node to server. This method is used to delete the current |
2371 | | * PubSub configuration. */ |
2372 | | configAttr.description = UA_LOCALIZEDTEXT("","Delete current PubSub configuration"); |
2373 | | configAttr.displayName = UA_LOCALIZEDTEXT("","DeletePubSubConfiguration"); |
2374 | | configAttr.executable = true; |
2375 | | configAttr.userExecutable = true; |
2376 | | retVal |= addMethodNode(server, UA_NODEID_NULL, |
2377 | | UA_NS0ID(PUBLISHSUBSCRIBE), UA_NS0ID(HASORDEREDCOMPONENT), |
2378 | | UA_QUALIFIEDNAME(1, "Delete PubSub config"), |
2379 | | &configAttr, UA_deletePubSubConfigMethodCallback, |
2380 | | 0, NULL, UA_NODEID_NULL, NULL, |
2381 | | 0, NULL, UA_NODEID_NULL, NULL, |
2382 | | NULL, NULL); |
2383 | | #endif |
2384 | 553 | } else { |
2385 | | /* Remove methods */ |
2386 | 0 | retVal |= deleteReference(server, UA_NS0ID(PUBLISHSUBSCRIBE), |
2387 | 0 | UA_NS0ID(HASCOMPONENT), true, |
2388 | 0 | UA_NS0EXID(PUBLISHSUBSCRIBE_ADDCONNECTION), false); |
2389 | 0 | retVal |= deleteReference(server, UA_NS0ID(PUBLISHSUBSCRIBE), |
2390 | 0 | UA_NS0ID(HASCOMPONENT), true, |
2391 | 0 | UA_NS0EXID(PUBLISHSUBSCRIBE_REMOVECONNECTION), false); |
2392 | 0 | } |
2393 | | |
2394 | | /* Set the object-type destructors */ |
2395 | 553 | UA_NodeTypeLifecycle lifeCycle; |
2396 | 553 | lifeCycle.constructor = NULL; |
2397 | | |
2398 | 553 | lifeCycle.destructor = connectionTypeDestructor; |
2399 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(PUBSUBCONNECTIONTYPE), lifeCycle); |
2400 | | |
2401 | 553 | lifeCycle.destructor = writerGroupTypeDestructor; |
2402 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(WRITERGROUPTYPE), lifeCycle); |
2403 | | |
2404 | 553 | lifeCycle.destructor = readerGroupTypeDestructor; |
2405 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(READERGROUPTYPE), lifeCycle); |
2406 | | |
2407 | 553 | lifeCycle.destructor = dataSetWriterTypeDestructor; |
2408 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(DATASETWRITERTYPE), lifeCycle); |
2409 | | |
2410 | 553 | lifeCycle.destructor = publishedDataItemsTypeDestructor; |
2411 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(PUBLISHEDDATAITEMSTYPE), lifeCycle); |
2412 | | |
2413 | 553 | lifeCycle.destructor = dataSetReaderTypeDestructor; |
2414 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(DATASETREADERTYPE), lifeCycle); |
2415 | | |
2416 | 553 | lifeCycle.destructor = subscribedDataSetTypeDestructor; |
2417 | 553 | retVal |= setNodeTypeLifecycle(server, UA_NS0ID(STANDALONESUBSCRIBEDDATASETTYPE), lifeCycle); |
2418 | | |
2419 | | #ifdef UA_ENABLE_PUBSUB_SKS |
2420 | | /* Initialize SKS-specific functionality */ |
2421 | | retVal |= initPubSubNS0_SKS(server); |
2422 | | #endif |
2423 | | |
2424 | 553 | return retVal; |
2425 | 553 | } |
2426 | | |
2427 | | /* Remove the Metadata nodes of the DSR and reference the Metadata nodes of the |
2428 | | * SDS instead */ |
2429 | | UA_StatusCode |
2430 | 0 | connectDataSetReaderToDataSet(UA_Server *server, UA_NodeId dsrId, UA_NodeId sdsId) { |
2431 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2432 | |
|
2433 | 0 | UA_NodeId dataSetMetaDataOnSdsId = |
2434 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
2435 | 0 | UA_NS0ID(HASPROPERTY), sdsId); |
2436 | 0 | UA_NodeId subscribedDataSetOnSdsId = |
2437 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "SubscribedDataSet"), |
2438 | 0 | UA_NS0ID(HASCOMPONENT), sdsId); |
2439 | |
|
2440 | 0 | if(UA_NodeId_isNull(&dataSetMetaDataOnSdsId) || |
2441 | 0 | UA_NodeId_isNull(&subscribedDataSetOnSdsId)) |
2442 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
2443 | | |
2444 | 0 | UA_NodeId dataSetMetaDataOnDsrId = |
2445 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
2446 | 0 | UA_NS0ID(HASPROPERTY), dsrId); |
2447 | 0 | UA_NodeId subscribedDataSetOnDsrId = |
2448 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "SubscribedDataSet"), |
2449 | 0 | UA_NS0ID(HASCOMPONENT), dsrId); |
2450 | |
|
2451 | 0 | UA_NODESTORE_REMOVE(server, &dataSetMetaDataOnDsrId); |
2452 | 0 | UA_NODESTORE_REMOVE(server, &subscribedDataSetOnDsrId); |
2453 | |
|
2454 | 0 | UA_StatusCode retVal = UA_STATUSCODE_GOOD; |
2455 | 0 | retVal |= addRef(server, dsrId, UA_NS0ID(HASPROPERTY), |
2456 | 0 | dataSetMetaDataOnSdsId, true); |
2457 | 0 | retVal |= addRef(server, dsrId, UA_NS0ID(HASPROPERTY), |
2458 | 0 | subscribedDataSetOnSdsId, true); |
2459 | 0 | return retVal; |
2460 | 0 | } |
2461 | | |
2462 | | /* Remove the references to the SDS */ |
2463 | | void |
2464 | 0 | disconnectDataSetReaderToDataSet(UA_Server *server, UA_NodeId dsrId) { |
2465 | 0 | UA_LOCK_ASSERT(&server->serviceMutex); |
2466 | |
|
2467 | 0 | UA_NodeId dataSetMetaDataOnDsrId = |
2468 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "DataSetMetaData"), |
2469 | 0 | UA_NS0ID(HASPROPERTY), dsrId); |
2470 | 0 | UA_NodeId subscribedDataSetOnDsrId = |
2471 | 0 | findSingleChildNode(server, UA_QUALIFIEDNAME(0, "SubscribedDataSet"), |
2472 | 0 | UA_NS0ID(HASCOMPONENT), dsrId); |
2473 | |
|
2474 | 0 | deleteReference(server, dsrId, UA_NS0ID(HASPROPERTY), true, |
2475 | 0 | UA_NODEID2EXPANDEDNODEID(dataSetMetaDataOnDsrId), true); |
2476 | |
|
2477 | 0 | deleteReference(server, dsrId, UA_NS0ID(HASCOMPONENT), true, |
2478 | 0 | UA_NODEID2EXPANDEDNODEID(subscribedDataSetOnDsrId), true); |
2479 | 0 | } |
2480 | | |
2481 | | #endif /* UA_ENABLE_PUBSUB_INFORMATIONMODEL */ |