| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| * Copyright 2015-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2015 (c) Oleksiy Vasylyev |
| * Copyright 2017 (c) Florian Palm |
| * Copyright 2016 (c) Chris Iatrou |
| * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
| * Copyright 2018 (c) Fabian Arndt |
| * Copyright 2018 (c) Peter Rustler, basyskom GmbH |
| */ |
| |
| #include <open62541/client_highlevel.h> |
| #include <open62541/client_highlevel_async.h> |
| |
| #include "ua_client_internal.h" |
| |
| UA_StatusCode |
| UA_Client_NamespaceGetIndex(UA_Client *client, UA_String *namespaceUri, |
| UA_UInt16 *namespaceIndex) { |
| UA_ReadRequest request; |
| UA_ReadRequest_init(&request); |
| UA_ReadValueId id; |
| UA_ReadValueId_init(&id); |
| id.attributeId = UA_ATTRIBUTEID_VALUE; |
| id.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACEARRAY); |
| request.nodesToRead = &id; |
| request.nodesToReadSize = 1; |
| |
| UA_ReadResponse response = UA_Client_Service_read(client, request); |
| |
| UA_StatusCode retval = UA_STATUSCODE_GOOD; |
| if(response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) |
| retval = response.responseHeader.serviceResult; |
| else if(response.resultsSize != 1 || !response.results[0].hasValue) |
| retval = UA_STATUSCODE_BADNODEATTRIBUTESINVALID; |
| else if(response.results[0].value.type != &UA_TYPES[UA_TYPES_STRING]) |
| retval = UA_STATUSCODE_BADTYPEMISMATCH; |
| |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| retval = UA_STATUSCODE_BADNOTFOUND; |
| UA_String *ns = (UA_String *)response.results[0].value.data; |
| for(size_t i = 0; i < response.results[0].value.arrayLength; ++i) { |
| if(UA_String_equal(namespaceUri, &ns[i])) { |
| *namespaceIndex = (UA_UInt16)i; |
| retval = UA_STATUSCODE_GOOD; |
| break; |
| } |
| } |
| |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_Client_forEachChildNodeCall(UA_Client *client, UA_NodeId parentNodeId, |
| UA_NodeIteratorCallback callback, void *handle) { |
| UA_BrowseRequest bReq; |
| UA_BrowseRequest_init(&bReq); |
| bReq.requestedMaxReferencesPerNode = 0; |
| bReq.nodesToBrowse = UA_BrowseDescription_new(); |
| bReq.nodesToBrowseSize = 1; |
| UA_NodeId_copy(&parentNodeId, &bReq.nodesToBrowse[0].nodeId); |
| bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; //return everything |
| bReq.nodesToBrowse[0].browseDirection = UA_BROWSEDIRECTION_BOTH; |
| |
| UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq); |
| |
| UA_StatusCode retval = bResp.responseHeader.serviceResult; |
| if(retval == UA_STATUSCODE_GOOD) { |
| for(size_t i = 0; i < bResp.resultsSize; ++i) { |
| for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) { |
| UA_ReferenceDescription *ref = &bResp.results[i].references[j]; |
| retval |= callback(ref->nodeId.nodeId, !ref->isForward, |
| ref->referenceTypeId, handle); |
| } |
| } |
| } |
| |
| UA_BrowseRequest_deleteMembers(&bReq); |
| UA_BrowseResponse_deleteMembers(&bResp); |
| return retval; |
| } |
| |
| /*******************/ |
| /* Node Management */ |
| /*******************/ |
| |
| UA_StatusCode |
| UA_Client_addReference(UA_Client *client, const UA_NodeId sourceNodeId, |
| const UA_NodeId referenceTypeId, UA_Boolean isForward, |
| const UA_String targetServerUri, |
| const UA_ExpandedNodeId targetNodeId, |
| UA_NodeClass targetNodeClass) { |
| UA_AddReferencesItem item; |
| UA_AddReferencesItem_init(&item); |
| item.sourceNodeId = sourceNodeId; |
| item.referenceTypeId = referenceTypeId; |
| item.isForward = isForward; |
| item.targetServerUri = targetServerUri; |
| item.targetNodeId = targetNodeId; |
| item.targetNodeClass = targetNodeClass; |
| UA_AddReferencesRequest request; |
| UA_AddReferencesRequest_init(&request); |
| request.referencesToAdd = &item; |
| request.referencesToAddSize = 1; |
| UA_AddReferencesResponse response = UA_Client_Service_addReferences(client, request); |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_AddReferencesResponse_deleteMembers(&response); |
| return retval; |
| } |
| if(response.resultsSize != 1) { |
| UA_AddReferencesResponse_deleteMembers(&response); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| retval = response.results[0]; |
| UA_AddReferencesResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_Client_deleteReference(UA_Client *client, const UA_NodeId sourceNodeId, |
| const UA_NodeId referenceTypeId, UA_Boolean isForward, |
| const UA_ExpandedNodeId targetNodeId, |
| UA_Boolean deleteBidirectional) { |
| UA_DeleteReferencesItem item; |
| UA_DeleteReferencesItem_init(&item); |
| item.sourceNodeId = sourceNodeId; |
| item.referenceTypeId = referenceTypeId; |
| item.isForward = isForward; |
| item.targetNodeId = targetNodeId; |
| item.deleteBidirectional = deleteBidirectional; |
| UA_DeleteReferencesRequest request; |
| UA_DeleteReferencesRequest_init(&request); |
| request.referencesToDelete = &item; |
| request.referencesToDeleteSize = 1; |
| UA_DeleteReferencesResponse response = UA_Client_Service_deleteReferences(client, request); |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_DeleteReferencesResponse_deleteMembers(&response); |
| return retval; |
| } |
| if(response.resultsSize != 1) { |
| UA_DeleteReferencesResponse_deleteMembers(&response); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| retval = response.results[0]; |
| UA_DeleteReferencesResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_Client_deleteNode(UA_Client *client, const UA_NodeId nodeId, |
| UA_Boolean deleteTargetReferences) { |
| UA_DeleteNodesItem item; |
| UA_DeleteNodesItem_init(&item); |
| item.nodeId = nodeId; |
| item.deleteTargetReferences = deleteTargetReferences; |
| UA_DeleteNodesRequest request; |
| UA_DeleteNodesRequest_init(&request); |
| request.nodesToDelete = &item; |
| request.nodesToDeleteSize = 1; |
| UA_DeleteNodesResponse response = UA_Client_Service_deleteNodes(client, request); |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_DeleteNodesResponse_deleteMembers(&response); |
| return retval; |
| } |
| if(response.resultsSize != 1) { |
| UA_DeleteNodesResponse_deleteMembers(&response); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| retval = response.results[0]; |
| UA_DeleteNodesResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| UA_StatusCode |
| __UA_Client_addNode(UA_Client *client, const UA_NodeClass nodeClass, |
| const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId, |
| const UA_NodeId referenceTypeId, const UA_QualifiedName browseName, |
| const UA_NodeId typeDefinition, const UA_NodeAttributes *attr, |
| const UA_DataType *attributeType, UA_NodeId *outNewNodeId) { |
| UA_AddNodesRequest request; |
| UA_AddNodesRequest_init(&request); |
| UA_AddNodesItem item; |
| UA_AddNodesItem_init(&item); |
| item.parentNodeId.nodeId = parentNodeId; |
| item.referenceTypeId = referenceTypeId; |
| item.requestedNewNodeId.nodeId = requestedNewNodeId; |
| item.browseName = browseName; |
| item.nodeClass = nodeClass; |
| item.typeDefinition.nodeId = typeDefinition; |
| item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE; |
| item.nodeAttributes.content.decoded.type = attributeType; |
| item.nodeAttributes.content.decoded.data = (void*)(uintptr_t)attr; // hack. is not written into. |
| request.nodesToAdd = &item; |
| request.nodesToAddSize = 1; |
| UA_AddNodesResponse response = UA_Client_Service_addNodes(client, request); |
| |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_AddNodesResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| if(response.resultsSize != 1) { |
| UA_AddNodesResponse_deleteMembers(&response); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| |
| /* Move the id of the created node */ |
| retval = response.results[0].statusCode; |
| if(retval == UA_STATUSCODE_GOOD && outNewNodeId) { |
| *outNewNodeId = response.results[0].addedNodeId; |
| UA_NodeId_init(&response.results[0].addedNodeId); |
| } |
| |
| UA_AddNodesResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| /********/ |
| /* Call */ |
| /********/ |
| |
| #ifdef UA_ENABLE_METHODCALLS |
| |
| UA_StatusCode |
| UA_Client_call(UA_Client *client, const UA_NodeId objectId, |
| const UA_NodeId methodId, size_t inputSize, |
| const UA_Variant *input, size_t *outputSize, |
| UA_Variant **output) { |
| /* Set up the request */ |
| UA_CallRequest request; |
| UA_CallRequest_init(&request); |
| UA_CallMethodRequest item; |
| UA_CallMethodRequest_init(&item); |
| item.methodId = methodId; |
| item.objectId = objectId; |
| item.inputArguments = (UA_Variant *)(void*)(uintptr_t)input; // cast const... |
| item.inputArgumentsSize = inputSize; |
| request.methodsToCall = &item; |
| request.methodsToCallSize = 1; |
| |
| /* Call the service */ |
| UA_CallResponse response = UA_Client_Service_call(client, request); |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval == UA_STATUSCODE_GOOD) { |
| if(response.resultsSize == 1) |
| retval = response.results[0].statusCode; |
| else |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_CallResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| /* Move the output arguments */ |
| if(output != NULL && outputSize != NULL) { |
| *output = response.results[0].outputArguments; |
| *outputSize = response.results[0].outputArgumentsSize; |
| response.results[0].outputArguments = NULL; |
| response.results[0].outputArgumentsSize = 0; |
| } |
| UA_CallResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| #endif |
| |
| /********************/ |
| /* Write Attributes */ |
| /********************/ |
| |
| UA_StatusCode |
| __UA_Client_writeAttribute(UA_Client *client, const UA_NodeId *nodeId, |
| UA_AttributeId attributeId, const void *in, |
| const UA_DataType *inDataType) { |
| if(!in) |
| return UA_STATUSCODE_BADTYPEMISMATCH; |
| |
| UA_WriteValue wValue; |
| UA_WriteValue_init(&wValue); |
| wValue.nodeId = *nodeId; |
| wValue.attributeId = attributeId; |
| if(attributeId == UA_ATTRIBUTEID_VALUE) |
| wValue.value.value = *(const UA_Variant*)in; |
| else |
| /* hack. is never written into. */ |
| UA_Variant_setScalar(&wValue.value.value, (void*)(uintptr_t)in, inDataType); |
| wValue.value.hasValue = true; |
| UA_WriteRequest wReq; |
| UA_WriteRequest_init(&wReq); |
| wReq.nodesToWrite = &wValue; |
| wReq.nodesToWriteSize = 1; |
| |
| UA_WriteResponse wResp = UA_Client_Service_write(client, wReq); |
| |
| UA_StatusCode retval = wResp.responseHeader.serviceResult; |
| if(retval == UA_STATUSCODE_GOOD) { |
| if(wResp.resultsSize == 1) |
| retval = wResp.results[0]; |
| else |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| |
| UA_WriteResponse_deleteMembers(&wResp); |
| return retval; |
| } |
| |
| UA_StatusCode |
| UA_Client_writeArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId, |
| size_t newArrayDimensionsSize, |
| const UA_UInt32 *newArrayDimensions) { |
| if(!newArrayDimensions) |
| return UA_STATUSCODE_BADTYPEMISMATCH; |
| |
| UA_WriteValue wValue; |
| UA_WriteValue_init(&wValue); |
| wValue.nodeId = nodeId; |
| wValue.attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS; |
| UA_Variant_setArray(&wValue.value.value, (void*)(uintptr_t)newArrayDimensions, |
| newArrayDimensionsSize, &UA_TYPES[UA_TYPES_UINT32]); |
| wValue.value.hasValue = true; |
| UA_WriteRequest wReq; |
| UA_WriteRequest_init(&wReq); |
| wReq.nodesToWrite = &wValue; |
| wReq.nodesToWriteSize = 1; |
| |
| UA_WriteResponse wResp = UA_Client_Service_write(client, wReq); |
| |
| UA_StatusCode retval = wResp.responseHeader.serviceResult; |
| if(retval == UA_STATUSCODE_GOOD) { |
| if(wResp.resultsSize == 1) |
| retval = wResp.results[0]; |
| else |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| UA_WriteResponse_deleteMembers(&wResp); |
| return retval; |
| } |
| |
| /*******************/ |
| /* Read Attributes */ |
| /*******************/ |
| |
| UA_StatusCode |
| __UA_Client_readAttribute(UA_Client *client, const UA_NodeId *nodeId, |
| UA_AttributeId attributeId, void *out, |
| const UA_DataType *outDataType) { |
| UA_ReadValueId item; |
| UA_ReadValueId_init(&item); |
| item.nodeId = *nodeId; |
| item.attributeId = attributeId; |
| UA_ReadRequest request; |
| UA_ReadRequest_init(&request); |
| request.nodesToRead = &item; |
| request.nodesToReadSize = 1; |
| UA_ReadResponse response = UA_Client_Service_read(client, request); |
| UA_StatusCode retval = response.responseHeader.serviceResult; |
| if(retval == UA_STATUSCODE_GOOD) { |
| if(response.resultsSize == 1) |
| retval = response.results[0].status; |
| else |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| /* Set the StatusCode */ |
| UA_DataValue *res = response.results; |
| if(res->hasStatus) |
| retval = res->status; |
| |
| /* Return early of no value is given */ |
| if(!res->hasValue) { |
| if(retval == UA_STATUSCODE_GOOD) |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| /* Copy value into out */ |
| if(attributeId == UA_ATTRIBUTEID_VALUE) { |
| memcpy(out, &res->value, sizeof(UA_Variant)); |
| UA_Variant_init(&res->value); |
| } else if(attributeId == UA_ATTRIBUTEID_NODECLASS) { |
| memcpy(out, (UA_NodeClass*)res->value.data, sizeof(UA_NodeClass)); |
| } else if(UA_Variant_isScalar(&res->value) && |
| res->value.type == outDataType) { |
| memcpy(out, res->value.data, res->value.type->memSize); |
| UA_free(res->value.data); |
| res->value.data = NULL; |
| } else { |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| static UA_StatusCode |
| processReadArrayDimensionsResult(UA_ReadResponse *response, |
| UA_UInt32 **outArrayDimensions, |
| size_t *outArrayDimensionsSize) { |
| UA_StatusCode retval = response->responseHeader.serviceResult; |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| if(response->resultsSize != 1) |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| |
| retval = response->results[0].status; |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| UA_DataValue *res = &response->results[0]; |
| if(!res->hasValue || |
| UA_Variant_isScalar(&res->value) || |
| res->value.type != &UA_TYPES[UA_TYPES_UINT32]) |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| |
| /* Move results */ |
| *outArrayDimensions = (UA_UInt32*)res->value.data; |
| *outArrayDimensionsSize = res->value.arrayLength; |
| res->value.data = NULL; |
| res->value.arrayLength = 0; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| UA_StatusCode |
| UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId, |
| size_t *outArrayDimensionsSize, |
| UA_UInt32 **outArrayDimensions) { |
| UA_ReadValueId item; |
| UA_ReadValueId_init(&item); |
| item.nodeId = nodeId; |
| item.attributeId = UA_ATTRIBUTEID_ARRAYDIMENSIONS; |
| UA_ReadRequest request; |
| UA_ReadRequest_init(&request); |
| request.nodesToRead = &item; |
| request.nodesToReadSize = 1; |
| |
| UA_ReadResponse response = UA_Client_Service_read(client, request); |
| UA_StatusCode retval = processReadArrayDimensionsResult(&response, outArrayDimensions, |
| outArrayDimensionsSize); |
| UA_ReadResponse_deleteMembers(&response); |
| return retval; |
| } |
| |
| /*********************/ |
| /* Historical Access */ |
| /*********************/ |
| #ifdef UA_ENABLE_HISTORIZING |
| static UA_HistoryReadResponse |
| __UA_Client_HistoryRead(UA_Client *client, const UA_NodeId *nodeId, |
| UA_ExtensionObject* details, UA_String indexRange, |
| UA_TimestampsToReturn timestampsToReturn, |
| UA_ByteString continuationPoint, UA_Boolean releaseConti) { |
| |
| UA_HistoryReadValueId item; |
| UA_HistoryReadValueId_init(&item); |
| |
| item.nodeId = *nodeId; |
| item.indexRange = indexRange; |
| item.continuationPoint = continuationPoint; |
| item.dataEncoding = UA_QUALIFIEDNAME(0, "Default Binary"); |
| |
| UA_HistoryReadRequest request; |
| UA_HistoryReadRequest_init(&request); |
| |
| request.nodesToRead = &item; |
| request.nodesToReadSize = 1; |
| request.timestampsToReturn = timestampsToReturn; // Defaults to Source |
| request.releaseContinuationPoints = releaseConti; // No values are returned, if true |
| |
| /* Build ReadDetails */ |
| request.historyReadDetails = *details; |
| |
| return UA_Client_Service_historyRead(client, request); |
| } |
| |
| static UA_StatusCode |
| __UA_Client_HistoryRead_service(UA_Client *client, const UA_NodeId *nodeId, |
| const UA_HistoricalIteratorCallback callback, |
| UA_ExtensionObject *details, UA_String indexRange, |
| UA_TimestampsToReturn timestampsToReturn, |
| void *callbackContext) { |
| |
| UA_ByteString continuationPoint = UA_BYTESTRING_NULL; |
| UA_Boolean continuationAvail = false; |
| UA_Boolean fetchMore = false; |
| UA_StatusCode retval = UA_STATUSCODE_GOOD; |
| |
| do { |
| /* We release the continuation point, if no more data is requested by the user */ |
| UA_Boolean cleanup = !fetchMore && continuationAvail; |
| UA_HistoryReadResponse response = |
| __UA_Client_HistoryRead(client, nodeId, details, indexRange, timestampsToReturn, continuationPoint, cleanup); |
| |
| if (cleanup) { |
| retval = response.responseHeader.serviceResult; |
| cleanup: UA_HistoryReadResponse_deleteMembers(&response); |
| UA_ByteString_deleteMembers(&continuationPoint); |
| return retval; |
| } |
| |
| retval = response.responseHeader.serviceResult; |
| if (retval == UA_STATUSCODE_GOOD) { |
| if (response.resultsSize == 1) |
| retval = response.results[0].statusCode; |
| else |
| retval = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| if (retval != UA_STATUSCODE_GOOD) |
| goto cleanup; |
| |
| UA_HistoryReadResult *res = response.results; |
| |
| /* Clear old and check / store new continuation point */ |
| UA_ByteString_deleteMembers(&continuationPoint); |
| UA_ByteString_copy(&res->continuationPoint, &continuationPoint); |
| continuationAvail = !UA_ByteString_equal(&continuationPoint, &UA_BYTESTRING_NULL); |
| |
| /* Client callback with possibility to request further values */ |
| fetchMore = callback(client, nodeId, continuationAvail, &res->historyData, callbackContext); |
| |
| /* Regular cleanup */ |
| UA_HistoryReadResponse_deleteMembers(&response); |
| } while (continuationAvail); |
| |
| return retval; |
| } |
| |
| #ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING |
| UA_StatusCode |
| UA_Client_HistoryRead_events(UA_Client *client, const UA_NodeId *nodeId, |
| const UA_HistoricalIteratorCallback callback, |
| UA_DateTime startTime, UA_DateTime endTime, |
| UA_String indexRange, const UA_EventFilter filter, UA_UInt32 numValuesPerNode, |
| UA_TimestampsToReturn timestampsToReturn, void *callbackContext) { |
| |
| UA_ReadEventDetails details; |
| UA_ReadEventDetails_init(&details); |
| details.filter = filter; |
| |
| // At least two of the following parameters must be set |
| details.numValuesPerNode = numValuesPerNode; // 0 = return all / max server is capable of |
| details.startTime = startTime; |
| details.endTime = endTime; |
| |
| UA_ExtensionObject detailsExtensionObject; |
| UA_ExtensionObject_init(&detailsExtensionObject); |
| detailsExtensionObject.content.decoded.type = &UA_TYPES[UA_TYPES_READEVENTDETAILS]; |
| detailsExtensionObject.content.decoded.data = &details; |
| detailsExtensionObject.encoding = UA_EXTENSIONOBJECT_DECODED; |
| |
| return __UA_Client_HistoryRead_service(client, nodeId, callback, &detailsExtensionObject, |
| indexRange, timestampsToReturn, callbackContext); |
| } |
| #endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING |
| |
| static UA_StatusCode |
| __UA_Client_HistoryRead_service_rawMod(UA_Client *client, const UA_NodeId *nodeId, |
| const UA_HistoricalIteratorCallback callback, |
| UA_DateTime startTime,UA_DateTime endTime, |
| UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode, |
| UA_Boolean readModified, UA_TimestampsToReturn timestampsToReturn, |
| void *callbackContext) { |
| |
| UA_ReadRawModifiedDetails details; |
| UA_ReadRawModifiedDetails_init(&details); |
| details.isReadModified = readModified; // Return only modified values |
| details.returnBounds = returnBounds; // Return values pre / post given range |
| |
| // At least two of the following parameters must be set |
| details.numValuesPerNode = numValuesPerNode; // 0 = return all / max server is capable of |
| details.startTime = startTime; |
| details.endTime = endTime; |
| |
| UA_ExtensionObject detailsExtensionObject; |
| UA_ExtensionObject_init(&detailsExtensionObject); |
| detailsExtensionObject.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS]; |
| detailsExtensionObject.content.decoded.data = &details; |
| detailsExtensionObject.encoding = UA_EXTENSIONOBJECT_DECODED; |
| |
| return __UA_Client_HistoryRead_service(client, nodeId, callback, |
| &detailsExtensionObject, indexRange, |
| timestampsToReturn, callbackContext); |
| } |
| |
| UA_StatusCode |
| UA_Client_HistoryRead_raw(UA_Client *client, const UA_NodeId *nodeId, |
| const UA_HistoricalIteratorCallback callback, |
| UA_DateTime startTime, UA_DateTime endTime, |
| UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode, |
| UA_TimestampsToReturn timestampsToReturn, void *callbackContext) { |
| |
| return __UA_Client_HistoryRead_service_rawMod(client, nodeId, callback, startTime, endTime, indexRange, returnBounds, |
| numValuesPerNode, false, timestampsToReturn, callbackContext); |
| } |
| |
| #ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING |
| UA_StatusCode |
| UA_Client_HistoryRead_modified(UA_Client *client, const UA_NodeId *nodeId, |
| const UA_HistoricalIteratorCallback callback, |
| UA_DateTime startTime, UA_DateTime endTime, |
| UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 maxItems, |
| UA_TimestampsToReturn timestampsToReturn, void *callbackContext) { |
| |
| return __UA_Client_HistoryRead_service_rawMod(client, nodeId, callback, startTime, endTime, indexRange, returnBounds, |
| maxItems, true, timestampsToReturn, callbackContext); |
| } |
| #endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING |
| |
| static UA_HistoryUpdateResponse |
| __UA_Client_HistoryUpdate(UA_Client *client, |
| void *details, |
| size_t typeId) |
| { |
| UA_HistoryUpdateRequest request; |
| UA_HistoryUpdateRequest_init(&request); |
| |
| UA_ExtensionObject extension; |
| UA_ExtensionObject_init(&extension); |
| request.historyUpdateDetailsSize = 1; |
| request.historyUpdateDetails = &extension; |
| |
| extension.encoding = UA_EXTENSIONOBJECT_DECODED; |
| extension.content.decoded.type = &UA_TYPES[typeId]; |
| extension.content.decoded.data = details; |
| |
| UA_HistoryUpdateResponse response; |
| response = UA_Client_Service_historyUpdate(client, request); |
| //UA_HistoryUpdateRequest_deleteMembers(&request); |
| return response; |
| } |
| |
| static UA_StatusCode |
| __UA_Client_HistoryUpdate_updateData(UA_Client *client, |
| const UA_NodeId *nodeId, |
| UA_PerformUpdateType type, |
| UA_DataValue *value) |
| { |
| UA_StatusCode ret = UA_STATUSCODE_GOOD; |
| UA_UpdateDataDetails details; |
| UA_UpdateDataDetails_init(&details); |
| |
| details.performInsertReplace = type; |
| details.updateValuesSize = 1; |
| details.updateValues = value; |
| UA_NodeId_copy(nodeId, &details.nodeId); |
| |
| UA_HistoryUpdateResponse response; |
| response = __UA_Client_HistoryUpdate(client, &details, UA_TYPES_UPDATEDATADETAILS); |
| if (response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| ret = response.responseHeader.serviceResult; |
| goto cleanup; |
| } |
| if (response.resultsSize != 1 || response.results[0].operationResultsSize != 1) { |
| ret = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| goto cleanup; |
| } |
| if (response.results[0].statusCode != UA_STATUSCODE_GOOD) { |
| ret = response.results[0].statusCode; |
| goto cleanup; |
| } |
| ret = response.results[0].operationResults[0]; |
| cleanup: |
| UA_HistoryUpdateResponse_deleteMembers(&response); |
| UA_NodeId_deleteMembers(&details.nodeId); |
| return ret; |
| } |
| |
| UA_StatusCode |
| UA_Client_HistoryUpdate_insert(UA_Client *client, |
| const UA_NodeId *nodeId, |
| UA_DataValue *value) |
| { |
| return __UA_Client_HistoryUpdate_updateData(client, |
| nodeId, |
| UA_PERFORMUPDATETYPE_INSERT, |
| value); |
| } |
| |
| UA_StatusCode |
| UA_Client_HistoryUpdate_replace(UA_Client *client, |
| const UA_NodeId *nodeId, |
| UA_DataValue *value) |
| { |
| return __UA_Client_HistoryUpdate_updateData(client, |
| nodeId, |
| UA_PERFORMUPDATETYPE_REPLACE, |
| value); |
| } |
| |
| UA_StatusCode |
| UA_Client_HistoryUpdate_update(UA_Client *client, |
| const UA_NodeId *nodeId, |
| UA_DataValue *value) |
| { |
| return __UA_Client_HistoryUpdate_updateData(client, |
| nodeId, |
| UA_PERFORMUPDATETYPE_UPDATE, |
| value); |
| } |
| |
| UA_StatusCode |
| UA_Client_HistoryUpdate_deleteRaw(UA_Client *client, |
| const UA_NodeId *nodeId, |
| UA_DateTime startTimestamp, |
| UA_DateTime endTimestamp) |
| { |
| UA_StatusCode ret = UA_STATUSCODE_GOOD; |
| |
| UA_DeleteRawModifiedDetails details; |
| UA_DeleteRawModifiedDetails_init(&details); |
| |
| details.isDeleteModified = false; |
| details.startTime = startTimestamp; |
| details.endTime = endTimestamp; |
| UA_NodeId_copy(nodeId, &details.nodeId); |
| |
| UA_HistoryUpdateResponse response; |
| response = __UA_Client_HistoryUpdate(client, &details, UA_TYPES_DELETERAWMODIFIEDDETAILS); |
| if (response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| ret = response.responseHeader.serviceResult; |
| goto cleanup; |
| } |
| if (response.resultsSize != 1) { |
| ret = UA_STATUSCODE_BADUNEXPECTEDERROR; |
| goto cleanup; |
| } |
| |
| ret = response.results[0].statusCode; |
| |
| cleanup: |
| UA_HistoryUpdateResponse_deleteMembers(&response); |
| UA_NodeId_deleteMembers(&details.nodeId); |
| return ret; |
| } |
| #endif // UA_ENABLE_HISTORIZING |
| |
| /* Async Functions */ |
| |
| static |
| void ValueAttributeRead(UA_Client *client, void *userdata, |
| UA_UInt32 requestId, void *response) { |
| if(!response) |
| return; |
| |
| /* Find the callback for the response */ |
| CustomCallback *cc; |
| LIST_FOREACH(cc, &client->customCallbacks, pointers) { |
| if(cc->callbackId == requestId) |
| break; |
| } |
| if(!cc) |
| return; |
| |
| UA_ReadResponse *rr = (UA_ReadResponse *) response; |
| UA_DataValue *res = rr->results; |
| UA_Boolean done = false; |
| if(rr->resultsSize == 1 && res != NULL && res->hasValue) { |
| if(cc->attributeId == UA_ATTRIBUTEID_VALUE) { |
| /* Call directly with the variant */ |
| cc->callback(client, userdata, requestId, &res->value); |
| done = true; |
| } else if(UA_Variant_isScalar(&res->value) && |
| res->value.type == cc->outDataType) { |
| /* Unpack the value */ |
| UA_STACKARRAY(UA_Byte, value, cc->outDataType->memSize); |
| memcpy(&value, res->value.data, cc->outDataType->memSize); |
| cc->callback(client, userdata, requestId, &value); |
| done = true; |
| } |
| } |
| |
| /* Could not process, delete the callback anyway */ |
| if(!done) |
| UA_LOG_INFO(&client->config.logger, UA_LOGCATEGORY_CLIENT, |
| "Cannot process the response to the async read " |
| "request %u", requestId); |
| |
| LIST_REMOVE(cc, pointers); |
| UA_free(cc); |
| } |
| |
| /*Read Attributes*/ |
| UA_StatusCode __UA_Client_readAttribute_async(UA_Client *client, |
| const UA_NodeId *nodeId, UA_AttributeId attributeId, |
| const UA_DataType *outDataType, UA_ClientAsyncServiceCallback callback, |
| void *userdata, UA_UInt32 *reqId) { |
| UA_ReadValueId item; |
| UA_ReadValueId_init(&item); |
| item.nodeId = *nodeId; |
| item.attributeId = attributeId; |
| UA_ReadRequest request; |
| UA_ReadRequest_init(&request); |
| request.nodesToRead = &item; |
| request.nodesToReadSize = 1; |
| |
| __UA_Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_READREQUEST], |
| ValueAttributeRead, &UA_TYPES[UA_TYPES_READRESPONSE], |
| userdata, reqId); |
| |
| CustomCallback *cc = (CustomCallback*) UA_malloc(sizeof(CustomCallback)); |
| if (!cc) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| cc->callback = callback; |
| cc->callbackId = *reqId; |
| |
| cc->attributeId = attributeId; |
| cc->outDataType = outDataType; |
| |
| LIST_INSERT_HEAD(&client->customCallbacks, cc, pointers); |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*Write Attributes*/ |
| UA_StatusCode __UA_Client_writeAttribute_async(UA_Client *client, |
| const UA_NodeId *nodeId, UA_AttributeId attributeId, const void *in, |
| const UA_DataType *inDataType, UA_ClientAsyncServiceCallback callback, |
| void *userdata, UA_UInt32 *reqId) { |
| if (!in) |
| return UA_STATUSCODE_BADTYPEMISMATCH; |
| |
| UA_WriteValue wValue; |
| UA_WriteValue_init(&wValue); |
| wValue.nodeId = *nodeId; |
| wValue.attributeId = attributeId; |
| if (attributeId == UA_ATTRIBUTEID_VALUE) |
| wValue.value.value = *(const UA_Variant*) in; |
| else |
| /* hack. is never written into. */ |
| UA_Variant_setScalar(&wValue.value.value, (void*) (uintptr_t) in, |
| inDataType); |
| wValue.value.hasValue = true; |
| UA_WriteRequest wReq; |
| UA_WriteRequest_init(&wReq); |
| wReq.nodesToWrite = &wValue; |
| wReq.nodesToWriteSize = 1; |
| |
| return __UA_Client_AsyncService(client, &wReq, |
| &UA_TYPES[UA_TYPES_WRITEREQUEST], callback, |
| &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata, reqId); |
| } |
| |
| /*Node Management*/ |
| |
| UA_StatusCode UA_EXPORT |
| __UA_Client_addNode_async(UA_Client *client, const UA_NodeClass nodeClass, |
| const UA_NodeId requestedNewNodeId, const UA_NodeId parentNodeId, |
| const UA_NodeId referenceTypeId, const UA_QualifiedName browseName, |
| const UA_NodeId typeDefinition, const UA_NodeAttributes *attr, |
| const UA_DataType *attributeType, UA_NodeId *outNewNodeId, |
| UA_ClientAsyncServiceCallback callback, void *userdata, |
| UA_UInt32 *reqId) { |
| UA_AddNodesRequest request; |
| UA_AddNodesRequest_init(&request); |
| UA_AddNodesItem item; |
| UA_AddNodesItem_init(&item); |
| item.parentNodeId.nodeId = parentNodeId; |
| item.referenceTypeId = referenceTypeId; |
| item.requestedNewNodeId.nodeId = requestedNewNodeId; |
| item.browseName = browseName; |
| item.nodeClass = nodeClass; |
| item.typeDefinition.nodeId = typeDefinition; |
| item.nodeAttributes.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE; |
| item.nodeAttributes.content.decoded.type = attributeType; |
| item.nodeAttributes.content.decoded.data = (void*) (uintptr_t) attr; // hack. is not written into. |
| request.nodesToAdd = &item; |
| request.nodesToAddSize = 1; |
| |
| return __UA_Client_AsyncService(client, &request, |
| &UA_TYPES[UA_TYPES_ADDNODESREQUEST], callback, |
| &UA_TYPES[UA_TYPES_ADDNODESRESPONSE], userdata, reqId); |
| |
| } |
| |
| /* Misc Highlevel Functions */ |
| #ifdef UA_ENABLE_METHODCALLS |
| UA_StatusCode __UA_Client_call_async(UA_Client *client, |
| const UA_NodeId objectId, const UA_NodeId methodId, size_t inputSize, |
| const UA_Variant *input, UA_ClientAsyncServiceCallback callback, |
| void *userdata, UA_UInt32 *reqId) { |
| |
| UA_CallRequest request; |
| UA_CallRequest_init(&request); |
| UA_CallMethodRequest item; |
| UA_CallMethodRequest_init(&item); |
| item.methodId = methodId; |
| item.objectId = objectId; |
| item.inputArguments = (UA_Variant *) (void*) (uintptr_t) input; // cast const... |
| item.inputArgumentsSize = inputSize; |
| request.methodsToCall = &item; |
| request.methodsToCallSize = 1; |
| |
| return __UA_Client_AsyncService(client, &request, |
| &UA_TYPES[UA_TYPES_CALLREQUEST], callback, |
| &UA_TYPES[UA_TYPES_CALLRESPONSE], userdata, reqId); |
| } |
| #endif |
| |
| UA_StatusCode __UA_Client_translateBrowsePathsToNodeIds_async(UA_Client *client, |
| char *paths[], UA_UInt32 ids[], size_t pathSize, |
| UA_ClientAsyncServiceCallback callback, void *userdata, |
| UA_UInt32 *reqId) { |
| |
| UA_BrowsePath browsePath; |
| UA_BrowsePath_init(&browsePath); |
| browsePath.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); |
| browsePath.relativePath.elements = (UA_RelativePathElement*) UA_Array_new( |
| pathSize, &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]); |
| if (!browsePath.relativePath.elements) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| browsePath.relativePath.elementsSize = pathSize; |
| |
| UA_TranslateBrowsePathsToNodeIdsRequest request; |
| UA_TranslateBrowsePathsToNodeIdsRequest_init(&request); |
| request.browsePaths = &browsePath; |
| request.browsePathsSize = 1; |
| |
| UA_StatusCode retval = __UA_Client_AsyncService(client, &request, |
| &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], callback, |
| &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE], userdata, |
| reqId); |
| if (retval != UA_STATUSCODE_GOOD) { |
| UA_Array_delete(browsePath.relativePath.elements, |
| browsePath.relativePath.elementsSize, |
| &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT]); |
| return retval; |
| } |
| UA_BrowsePath_deleteMembers(&browsePath); |
| return retval; |
| } |