blob: c3ebb2045e22be5774597b338c937961cee0cc6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015-2019 Florian Froschermeier <florian.froschermeier@tum.de>,
* fortiss GmbH
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Florian Froschermeier
* - initial integration of the OPC-UA protocol
* Stefan Profanter
* - refactoring and adaption to new concept
* Jose Cabral:
* - refactoring to cleaner architecture
*******************************************************************************/
#include "../../core/devexec.h"
#include "../../core/iec61131_functions.h"
#include "../../core/cominfra/basecommfb.h"
#include "../../core/utils/parameterParser.h"
#include "../../core/utils/string_utils.h"
#include <criticalregion.h>
#include <forte_printer.h>
#include "../../arch/utils/mainparam_utils.h"
#include "opcua_local_handler.h"
#ifndef FORTE_COM_OPC_UA_CUSTOM_HOSTNAME
#include <sockhand.h>
#endif
const char * const COPC_UA_Local_Handler::mEnglishLocaleForNodes = "en-US";
const char * const COPC_UA_Local_Handler::mDefaultDescriptionForVariableNodes = "Digital port of Function Block";
TForteUInt16 gOpcuaServerPort = FORTE_COM_OPC_UA_PORT;
using namespace forte::com_infra;
DEFINE_HANDLER(COPC_UA_Local_Handler);
COPC_UA_Local_Handler::COPC_UA_Local_Handler(CDeviceExecution &paDeviceExecution) :
COPC_UA_HandlerAbstract(paDeviceExecution), mUaServer(0), mUaServerRunningFlag(UA_FALSE)
#ifdef FORTE_COM_OPC_UA_MULTICAST
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
//do nothing
# else //FORTE_COM_OPC_UA_MASTER_BRANCH
, mServerConfig(0)
# endif //FORTE_COM_OPC_UA_MASTER_BRANCH
#endif //FORTE_COM_OPC_UA_MULTICAST
{
}
COPC_UA_Local_Handler::~COPC_UA_Local_Handler() {
stopServer();
CCriticalRegion criticalRegion(mNodesReferencesMutex);
for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iter = mNodesReferences.begin(); iter != mNodesReferences.end(); ++iter) {
UA_NodeId_delete(const_cast<UA_NodeId*>((*iter)->mNodeId));
delete *iter;
}
mNodesReferences.clearAll();
#ifdef FORTE_COM_OPC_UA_MULTICAST
for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
UA_String_delete(*iter);
}
#endif //FORTE_COM_OPC_UA_MULTICAST
}
void COPC_UA_Local_Handler::enableHandler(void) {
startServer();
}
void COPC_UA_Local_Handler::disableHandler(void) {
COPC_UA_Local_Handler::stopServer();
end();
}
void COPC_UA_Local_Handler::run() {
DEVLOG_INFO("[OPC UA LOCAL]: Starting OPC UA Server: opc.tcp://localhost:%d\n", gOpcuaServerPort);
UA_ServerConfig *uaServerConfig = 0;
UA_ServerStrings serverStrings;
generateServerStrings(gOpcuaServerPort, serverStrings);
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
mUaServer = UA_Server_new();
if(mUaServer) {
uaServerConfig = UA_Server_getConfig(mUaServer);
UA_ServerConfig_setMinimal(uaServerConfig, gOpcuaServerPort, 0);
configureUAServer(serverStrings, *uaServerConfig);
#else
uaServerConfig = UA_ServerConfig_new_minimal(gOpcuaServerPort, 0);
configureUAServer(serverStrings, *uaServerConfig);
mUaServer = UA_Server_new(uaServerConfig);
if(mUaServer) {
#endif
if(initializeNodesets(*mUaServer)) {
#ifdef FORTE_COM_OPC_UA_MULTICAST
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
//do nothing
# else
mServerConfig = uaServerConfig;
# endif
UA_Server_setServerOnNetworkCallback(mUaServer, serverOnNetworkCallback, this);
#endif //FORTE_COM_OPC_UA_MULTICAST
mUaServerRunningFlag = UA_TRUE;
mServerStarted.inc();
UA_StatusCode retVal = UA_Server_run(mUaServer, &mUaServerRunningFlag); // server keeps iterating as long as running is true
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Server exited. Error: %s\n", UA_StatusCode_name(retVal));
} else {
DEVLOG_INFO("[OPC UA LOCAL]: Server successfully stopped\n");
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: Couldn't initialize Nodesets\n", gOpcuaServerPort);
}
UA_Server_delete(mUaServer);
mUaServer = 0;
}
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
//nothing to do in master branch. Config is deleted with the server
#else
UA_ServerConfig_delete(uaServerConfig);
#endif
mServerStarted.inc(); //this will avoid locking startServer() for all cases where the starting of server failed
}
void COPC_UA_Local_Handler::startServer() {
if(!isAlive()) {
start();
mServerStarted.waitIndefinitely();
}
}
void COPC_UA_Local_Handler::stopServer() {
mUaServerRunningFlag = UA_FALSE;
end();
}
void COPC_UA_Local_Handler::generateServerStrings(TForteUInt16 paUAServerPort, UA_ServerStrings &paServerStrings) {
char helperBuffer[scmMaxServerNameLength + 1];
forte_snprintf(helperBuffer, scmMaxServerNameLength, "forte_%d", paUAServerPort);
char hostname[scmMaxServerNameLength + 1];
#ifdef FORTE_COM_OPC_UA_CUSTOM_HOSTNAME
forte_snprintf(hostname, scmMaxServerNameLength, "%s-%s", FORTE_COM_OPC_UA_CUSTOM_HOSTNAME, helperBuffer);
#else
if(gethostname(hostname, scmMaxServerNameLength) == 0) {
size_t offset = strlen(hostname);
size_t nameLen = strlen(helperBuffer);
if(offset + nameLen + 1 > scmMaxServerNameLength) {
offset = MAX(scmMaxServerNameLength - nameLen - 1, (size_t) 0);
}
forte_snprintf(hostname + offset, scmMaxServerNameLength - offset, "-%s", helperBuffer);
}
#endif
forte_snprintf(helperBuffer, scmMaxServerNameLength, "org.eclipse.4diac.%s", hostname);
paServerStrings.mAppURI = helperBuffer;
paServerStrings.mHostname = hostname;
}
void COPC_UA_Local_Handler::configureUAServer(UA_ServerStrings &paServerStrings, UA_ServerConfig &paUaServerConfig) {
paUaServerConfig.logger = COPC_UA_HandlerAbstract::getLogger();
#ifdef FORTE_COM_OPC_UA_MULTICAST
paUaServerConfig.applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER;
// hostname will be added by mdns library
# ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
UA_String_deleteMembers(&paUaServerConfig.discovery.mdns.mdnsServerName);
paUaServerConfig.discovery.mdns.mdnsServerName = UA_String_fromChars(helperBuffer);
# else //FORTE_COM_OPC_UA_MASTER_BRANCH
UA_String_deleteMembers(&paUaServerConfig.mdnsServerName);
paUaServerConfig.mdnsServerName = UA_String_fromChars(helperBuffer);
# endif//FORTE_COM_OPC_UA_MASTER_BRANCH
#endif //FORTE_COM_OPC_UA_MULTICAST
UA_String customHost = UA_STRING(paServerStrings.mHostname.getValue());
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
UA_ServerConfig_setCustomHostname(&paUaServerConfig, customHost);
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
UA_ServerConfig_set_customHostname(&paUaServerConfig, customHost);
#endif//FORTE_COM_OPC_UA_MASTER_BRANCH
// delete pre-initialized values
UA_LocalizedText_deleteMembers(&paUaServerConfig.applicationDescription.applicationName);
UA_String_deleteMembers(&paUaServerConfig.applicationDescription.applicationUri);
paUaServerConfig.applicationDescription.applicationUri = UA_String_fromChars(paServerStrings.mAppURI.getValue());
paUaServerConfig.applicationDescription.applicationName.locale = UA_STRING_NULL;
paUaServerConfig.applicationDescription.applicationName.text = UA_String_fromChars(paServerStrings.mHostname.getValue());
paUaServerConfig.publishingIntervalLimits.min = FORTE_COM_OPC_UA_SERVER_PUB_INTERVAL;
for(size_t i = 0; i < paUaServerConfig.endpointsSize; i++) {
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
UA_String_deleteMembers(&paUaServerConfig.endpoints[i].server.applicationUri);
UA_LocalizedText_deleteMembers(&paUaServerConfig.endpoints[i].server.applicationName);
UA_String_copy(&paUaServerConfig.applicationDescription.applicationUri, &paUaServerConfig.endpoints[i].server.applicationUri);
UA_LocalizedText_copy(&paUaServerConfig.applicationDescription.applicationName, &paUaServerConfig.endpoints[i].server.applicationName);
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
UA_String_deleteMembers(&paUaServerConfig.endpoints[i].endpointDescription.server.applicationUri);
UA_LocalizedText_deleteMembers(&paUaServerConfig.endpoints[i].endpointDescription.server.applicationName);
UA_String_copy(&paUaServerConfig.applicationDescription.applicationUri, &paUaServerConfig.endpoints[i].endpointDescription.server.applicationUri);
UA_LocalizedText_copy(&paUaServerConfig.applicationDescription.applicationName, &paUaServerConfig.endpoints[i].endpointDescription.server.applicationName);
#endif //FORTE_COM_OPC_UA_MASTER_BRANCH
}
}
void COPC_UA_Local_Handler::referencedNodesIncrement(const CSinglyLinkedList<UA_NodeId *> &paNodes, CActionInfo &paActionInfo) {
CCriticalRegion criticalRegion(mNodesReferencesMutex);
for(CSinglyLinkedList<UA_NodeId *>::Iterator iterNode = paNodes.begin(); iterNode != paNodes.end(); ++iterNode) {
bool found = false;
for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
if(UA_NodeId_equal((*iterRef)->mNodeId, (*iterNode))) {
found = true;
(*iterRef)->mActionsReferencingIt.pushFront(&paActionInfo);
break;
}
}
if(!found) {
nodesReferencedByActions *newRef = new nodesReferencedByActions();
UA_NodeId *newNode = UA_NodeId_new();
UA_NodeId_copy((*iterNode), newNode);
newRef->mNodeId = newNode;
newRef->mActionsReferencingIt = CSinglyLinkedList<CActionInfo*>();
newRef->mActionsReferencingIt.pushFront(&paActionInfo);
mNodesReferences.pushFront(newRef);
}
}
}
void COPC_UA_Local_Handler::referencedNodesDecrement(const CActionInfo &paActionInfo) {
CCriticalRegion criticalRegion(mNodesReferencesMutex);
CSinglyLinkedList<const UA_NodeId *> nodesReferencedByAction;
getNodesReferencedByAction(paActionInfo, nodesReferencedByAction);
for(CSinglyLinkedList<const UA_NodeId *>::Iterator iterNode = nodesReferencedByAction.begin(); iterNode != nodesReferencedByAction.end(); ++iterNode) {
CSinglyLinkedList<nodesReferencedByActions *>::Iterator nodeReferencedToDelete = mNodesReferences.end();
for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
if(UA_NodeId_equal((*iterRef)->mNodeId, (*iterNode))) {
bool stillSomethingThere = true;
while(stillSomethingThere) {
CSinglyLinkedList<CActionInfo *>::Iterator itActionToDelete = (*iterRef)->mActionsReferencingIt.end();
for(CSinglyLinkedList<CActionInfo *>::Iterator itAction = (*iterRef)->mActionsReferencingIt.begin();
itAction != (*iterRef)->mActionsReferencingIt.end(); ++itAction) {
if((*itAction) == &paActionInfo) {
itActionToDelete = itAction;
break;
}
}
if((*iterRef)->mActionsReferencingIt.end() == itActionToDelete) {
stillSomethingThere = false;
} else {
(*iterRef)->mActionsReferencingIt.erase(*itActionToDelete);
}
}
if((*iterRef)->mActionsReferencingIt.isEmpty()) {
nodeReferencedToDelete = iterRef;
if(0 != (*iterRef)->mNodeId->namespaceIndex && mUaServer) {
UA_Server_deleteNode(mUaServer, *(*iterRef)->mNodeId, UA_TRUE);
}
}
break;
}
}
if(mNodesReferences.end() != nodeReferencedToDelete) {
nodesReferencedByActions *toDelete = *nodeReferencedToDelete;
mNodesReferences.erase(toDelete);
UA_NodeId_delete(const_cast<UA_NodeId*>(toDelete->mNodeId));
delete toDelete;
}
}
}
void COPC_UA_Local_Handler::getNodesReferencedByAction(const CActionInfo &paActionInfo, CSinglyLinkedList<const UA_NodeId *> &paNodes) {
for(CSinglyLinkedList<nodesReferencedByActions *>::Iterator iterRef = mNodesReferences.begin(); iterRef != mNodesReferences.end(); ++iterRef) {
for(CSinglyLinkedList<CActionInfo *>::Iterator iterAction = (*iterRef)->mActionsReferencingIt.begin();
iterAction != (*iterRef)->mActionsReferencingIt.end(); ++iterAction) {
if((*iterAction) == &paActionInfo) {
paNodes.pushFront((*iterRef)->mNodeId);
break;
}
}
}
}
#ifdef FORTE_COM_OPC_UA_MULTICAST
const UA_String* COPC_UA_Local_Handler::getDiscoveryUrl() const {
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
UA_ServerConfig *mServerConfig = UA_Server_getConfig(mUaServer); //change mServerConfig to serverConfig when only master branch is present
#else
//do nothing
#endif
if(0 == mServerConfig->networkLayersSize) {
return 0;
}
return &mServerConfig->networkLayers[0].discoveryUrl;
}
void COPC_UA_Local_Handler::serverOnNetworkCallback(const UA_ServerOnNetwork *paServerOnNetwork, UA_Boolean paIsServerAnnounce, UA_Boolean paIsTxtReceived,
void *paData) { //NOSONAR
COPC_UA_Local_Handler *handler = static_cast<COPC_UA_Local_Handler*>(paData);
const UA_String *ownDiscoverUrl = handler->getDiscoveryUrl();
if(!ownDiscoverUrl || UA_String_equal(&paServerOnNetwork->discoveryUrl, ownDiscoverUrl)) {
// skip self
return;
}
if(!paIsTxtReceived) {
return; // we wait until the corresponding TXT record is announced.
}
DEVLOG_DEBUG("[OPC UA LOCAL]: mDNS %s '%.*s' with url '%.*s'\n", paIsServerAnnounce ? "announce" : "remove", paServerOnNetwork->serverName.length,
paServerOnNetwork->serverName.data, paServerOnNetwork->discoveryUrl.length, paServerOnNetwork->discoveryUrl.data);
// check if server is LDS, and then register
UA_String ldsStr = UA_String_fromChars("LDS");
for(unsigned int i = 0; i < paServerOnNetwork->serverCapabilitiesSize; i++) {
if(UA_String_equal(&paServerOnNetwork->serverCapabilities[i], &ldsStr)) {
if(paIsServerAnnounce) {
handler->registerWithLds(&paServerOnNetwork->discoveryUrl);
} else {
handler->removeLdsRegister(&paServerOnNetwork->discoveryUrl);
}
break;
}
}
UA_String_deleteMembers(&ldsStr);
}
void COPC_UA_Local_Handler::registerWithLds(const UA_String *paDiscoveryUrl) {
// check if already registered with the given LDS
for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
if(UA_String_equal(paDiscoveryUrl, *iter)) {
return;
}
}
// will be freed when removed from list
UA_String *discoveryUrlChar = 0;
UA_String_copy(paDiscoveryUrl, discoveryUrlChar);
mRegisteredWithLds.pushFront(discoveryUrlChar);
DEVLOG_INFO("[OPC UA LOCAL]: Registering with LDS '%.*s'\n", paDiscoveryUrl->length, paDiscoveryUrl->data);
UA_StatusCode retVal = UA_Server_addPeriodicServerRegisterCallback(mUaServer,
#ifdef FORTE_COM_OPC_UA_MASTER_BRANCH
0,
#else //FORTE_COM_OPC_UA_MASTER_BRANCH
//nothing here
#endif //FORTE_COM_OPC_UA_MASTER_BRANCH
reinterpret_cast<const char*>(discoveryUrlChar->data), 10 * 60 * 1000, 500, 0);
if( UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not register with LDS. Error: %s\n", UA_StatusCode_name(retVal));
}
}
void COPC_UA_Local_Handler::removeLdsRegister(const UA_String *paDiscoveryUrl) {
UA_String *toDelete = 0;
for(CSinglyLinkedList<UA_String*>::Iterator iter = mRegisteredWithLds.begin(); iter != mRegisteredWithLds.end(); ++iter) {
if(UA_String_equal(paDiscoveryUrl, *iter)) {
toDelete = *iter;
break;
}
}
if(toDelete) {
mRegisteredWithLds.erase(toDelete);
UA_String_delete(toDelete);
}
}
#endif //FORTE_COM_OPC_UA_MULTICAST
UA_StatusCode COPC_UA_Local_Handler::initializeAction(CActionInfo &paActionInfo) {
enableHandler();
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
if(mUaServer) { //if the server failed at starting, nothing will be initialized
// other thread may currently create nodes, thus mutex
CCriticalRegion criticalRegion(mCreateNodesMutex);
switch(paActionInfo.getAction()){
case CActionInfo::eRead:
retVal = initializeVariable(paActionInfo, false);
break;
case CActionInfo::eWrite:
retVal = initializeVariable(paActionInfo, true);
break;
case CActionInfo::eCreateMethod:
retVal = initializeCreateMethod(paActionInfo);
break;
case CActionInfo::eCreateObject:
retVal = initializeCreateObject(paActionInfo);
break;
case CActionInfo::eDeleteObject:
retVal = initializeDeleteObject(paActionInfo);
break;
case CActionInfo::eCallMethod:
case CActionInfo::eSubscribe:
DEVLOG_ERROR("[OPC UA LOCAL]: Cannot perform action %s locally. Initialization failed\n", CActionInfo::mActionNames[paActionInfo.getAction()]);
break;
default:
DEVLOG_ERROR("[OPC UA LOCAL]: Unknown action %d to be initialized\n", paActionInfo.getAction());
break;
}
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::executeAction(CActionInfo &paActionInfo) {
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
switch(paActionInfo.getAction()){
case CActionInfo::eWrite:
retVal = executeWrite(paActionInfo);
break;
case CActionInfo::eCreateMethod:
retVal = executeCreateMethod(paActionInfo);
break;
case CActionInfo::eCreateObject:
retVal = executeCreateObject(paActionInfo);
break;
case CActionInfo::eDeleteObject:
retVal = executeDeleteObject(paActionInfo);
break;
default: //eCallMethod, eSubscribe will never reach here since they weren't initialized. eRead is a Subscribe FB
DEVLOG_ERROR("[OPC UA LOCAL]: Action %d to be executed is unknown or invalid\n", paActionInfo.getAction());
break;
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::uninitializeAction(CActionInfo &paActionInfo) {
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
switch(paActionInfo.getAction()){
case CActionInfo::eRead:
case CActionInfo::eWrite:
case CActionInfo::eCreateMethod:
case CActionInfo::eCreateObject:
case CActionInfo::eDeleteObject:
referencedNodesDecrement(paActionInfo);
retVal = UA_STATUSCODE_GOOD;
break;
default:
DEVLOG_ERROR("[OPC UA LOCAL]: Action %d to be uninitialized is unknown or invalid\n", paActionInfo.getAction());
break;
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::initializeVariable(CActionInfo &paActionInfo, bool paWrite) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
size_t indexOfNodePair = 0;
CSinglyLinkedList<UA_NodeId*> referencedNodes;
const CIEC_ANY *variables = paWrite ? paActionInfo.getDataToSend() : paActionInfo.getDataToReceive();
for(CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itMain = paActionInfo.getNodePairInfo().begin();
itMain != paActionInfo.getNodePairInfo().end() && UA_STATUSCODE_GOOD == retVal; ++itMain, indexOfNodePair++) {
CSinglyLinkedList<UA_NodeId*> presentNodes;
bool isNodePresent = false;
retVal = getNode(**itMain, presentNodes, &isNodePresent);
if(UA_STATUSCODE_GOOD == retVal) {
if(isNodePresent) {
retVal = handleExistingVariable(paActionInfo, **itMain, variables[indexOfNodePair], indexOfNodePair, paWrite);
handlePresentNodes(presentNodes, referencedNodes, UA_STATUSCODE_GOOD != retVal);
} else { //node does not exist
//presentNodes shouldn't have any allocated NodeId at this point
retVal = handleNonExistingVariable(paActionInfo, **itMain, variables[indexOfNodePair], indexOfNodePair, referencedNodes, paWrite);
}
}
}
//we add the references first even if it fails, since some nodes might have been created,
//and/or some might have been already there, so deleting them will be taken care of by
//the referencedNodesDecrement function later
referencedNodesIncrement(referencedNodes, paActionInfo);
if(UA_STATUSCODE_GOOD != retVal) {
referencedNodesDecrement(paActionInfo);
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
UA_NodeId_delete(*itRerencedNodes);
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::handleExistingVariable(CActionInfo &paActionInfo, CActionInfo::CNodePairInfo &paNodePairInfo, const CIEC_ANY &paVariable,
size_t paIndexOfNodePair, bool paWrite) {
UA_NodeId outDataType;
UA_StatusCode retVal = UA_Server_readDataType(mUaServer, *paNodePairInfo.mNodeId, &outDataType);
if(UA_STATUSCODE_GOOD == retVal) {
if(UA_NodeId_equal(&outDataType, &COPC_UA_Helper::getOPCUATypeFromAny(paVariable)->typeId)) {
if(!paWrite) { //If we are reading a variable, it should be writable from the outside
retVal = addWritePermission(*paNodePairInfo.mNodeId);
if(UA_STATUSCODE_GOOD == retVal) {
void *handle = 0;
retVal = UA_Server_getNodeContext(mUaServer, *paNodePairInfo.mNodeId, &handle);
if(UA_STATUSCODE_GOOD == retVal) {
if(!handle) {
retVal = registerVariableCallBack(*paNodePairInfo.mNodeId, paActionInfo, paIndexOfNodePair);
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s RD_%d the node %s has already a FB who is reading from it. Cannot add another one\n",
paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, paNodePairInfo.mBrowsePath.getValue());
retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s RD_%d the node %s could not retrieve context. Error: %s\n",
paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, paNodePairInfo.mBrowsePath.getValue(), UA_StatusCode_name(retVal));
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: Cannot set write permission of node for port %d. Error: %s\n", paIndexOfNodePair, UA_StatusCode_name(retVal));
}
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s index %d there was the type of the existing node doesn't match the new one\n",
paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair);
retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: At FB %s index %d there was a problem reading the type of the existing node. Error: %s\n",
paActionInfo.getLayer().getCommFB()->getInstanceName(), paIndexOfNodePair, UA_StatusCode_name(retVal));
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::handleNonExistingVariable(CActionInfo &paActionInfo, CActionInfo::CNodePairInfo &paNodePairInfo,
const CIEC_ANY &paVariable, size_t paIndexOfNodePair, CSinglyLinkedList<UA_NodeId*> &paReferencedNodes, bool paWrite) {
CIEC_STRING nodeName;
UA_StatusCode retVal = splitAndCreateFolders(paNodePairInfo.mBrowsePath, nodeName, paReferencedNodes);
if(UA_STATUSCODE_GOOD == retVal) {
CCreateVariableInfo variableInformation;
initializeCreateInfo(nodeName, paNodePairInfo, paReferencedNodes.isEmpty() ? 0 : *(paReferencedNodes.back()), variableInformation);
variableInformation.mTypeConvert = COPC_UA_Helper::getOPCUATypeFromAny(paVariable);
variableInformation.mInitData = &paVariable;
variableInformation.mAllowWrite = !paWrite; // write FB here means that from the outside should not be possible to write and the other way around for read
retVal = createVariableNode(variableInformation);
if(UA_STATUSCODE_GOOD == retVal) {
UA_NodeId *tmp = UA_NodeId_new();
UA_NodeId_copy(variableInformation.mReturnedNodeId, tmp);
paReferencedNodes.pushBack(tmp);
if(!paWrite) {
retVal = registerVariableCallBack(*variableInformation.mReturnedNodeId, paActionInfo, paIndexOfNodePair);
}
if(UA_STATUSCODE_GOOD == retVal) {
if(!paNodePairInfo.mNodeId) {
paNodePairInfo.mNodeId = UA_NodeId_new();
UA_NodeId_copy(variableInformation.mReturnedNodeId, paNodePairInfo.mNodeId);
}
}
}
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::createVariableNode(CCreateVariableInfo &paCreateVariableInfo) {
UA_NodeId requestedNodeId;
if(paCreateVariableInfo.mRequestedNodeId) {
UA_NodeId_copy(paCreateVariableInfo.mRequestedNodeId, &requestedNodeId);
} else {
requestedNodeId = UA_NODEID_NUMERIC(paCreateVariableInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
}
UA_NodeId parentNodeId;
if(paCreateVariableInfo.mParentNodeId) {
UA_NodeId_copy(paCreateVariableInfo.mParentNodeId, &parentNodeId);
} else {
parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
DEVLOG_WARNING(
"[OPC UA LOCAL]: You are creating a variable %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
paCreateVariableInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data));
}
void *paVarValue = UA_new(paCreateVariableInfo.mTypeConvert);
UA_init(paVarValue, paCreateVariableInfo.mTypeConvert);
COPC_UA_Helper::convertToOPCUAType(*paCreateVariableInfo.mInitData, paVarValue);
// create variable attributes
UA_VariableAttributes variableAttributes;
UA_VariableAttributes_init(&variableAttributes);
variableAttributes.dataType = paCreateVariableInfo.mTypeConvert->typeId;
variableAttributes.valueRank = -1; // value is a scalar
variableAttributes.displayName.locale = UA_STRING_ALLOC(mEnglishLocaleForNodes);
UA_String_copy(&paCreateVariableInfo.mBrowseName->name, &variableAttributes.displayName.text);
variableAttributes.description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, mDefaultDescriptionForVariableNodes);
variableAttributes.userAccessLevel = UA_ACCESSLEVELMASK_READ;
if(paCreateVariableInfo.mAllowWrite) {
variableAttributes.userAccessLevel |= UA_ACCESSLEVELMASK_WRITE;
}
variableAttributes.accessLevel = variableAttributes.userAccessLevel;
UA_Variant_setScalar(&variableAttributes.value, paVarValue, paCreateVariableInfo.mTypeConvert);
UA_StatusCode retVal = UA_Server_addVariableNode(mUaServer, // server
requestedNodeId, // requestedNewNodeId
parentNodeId, // parentNodeId
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), // referenceTypeId Reference to the type definition for the variable node
*paCreateVariableInfo.mBrowseName, // browseName
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), // typeDefinition
variableAttributes, // Variable attributes
0, // instantiation callback
paCreateVariableInfo.mReturnedNodeId); // return Node Id
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: AddressSpace adding Variable Node %s failed. Error: %s\n", paCreateVariableInfo.mBrowseName->name.data,
UA_StatusCode_name(retVal));
}
UA_NodeId_deleteMembers(&parentNodeId);
UA_NodeId_deleteMembers(&requestedNodeId);
UA_VariableAttributes_deleteMembers(&variableAttributes);
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::updateNodeValue(const UA_NodeId &paNodeId, const CIEC_ANY *paData) {
UA_Variant *nodeValue = UA_Variant_new();
UA_Variant_init(nodeValue);
const UA_DataType* dataType = COPC_UA_Helper::getOPCUATypeFromAny(*paData);
void *varValue = UA_new(dataType);
COPC_UA_Helper::convertToOPCUAType(*paData, varValue);
UA_Variant_setScalarCopy(nodeValue, varValue, dataType);
UA_StatusCode retVal = UA_Server_writeValue(mUaServer, paNodeId, *nodeValue);
UA_delete(varValue, dataType);
UA_Variant_delete(nodeValue);
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::registerVariableCallBack(const UA_NodeId &paNodeId, CActionInfo &paActionInfo, size_t paPortIndex) {
UA_StatusCode retVal = UA_Server_setVariableNode_valueCallback(mUaServer, paNodeId, { 0, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onWrite });
if(UA_STATUSCODE_GOOD == retVal) {
UA_VariableContext_Handle variableContext(paActionInfo, paPortIndex);
mNodeCallbackHandles.pushFront(variableContext);
CSinglyLinkedList<UA_VariableContext_Handle>::Iterator contextIterator = mNodeCallbackHandles.begin();
retVal = UA_Server_setNodeContext(mUaServer, paNodeId, &(*contextIterator));
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback context for node. Error: %s\n", UA_StatusCode_name(retVal));
mNodeCallbackHandles.popFront();
retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback function for node. Error: %s\n", UA_StatusCode_name(retVal));
retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::addWritePermission(const UA_NodeId &paNodeId) {
UA_StatusCode retVal = UA_Server_writeAccessLevel(mUaServer, paNodeId, UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE);
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_WARNING("[OPC UA LOCAL]: Cannot set write permission of node. Error: %s\n", UA_StatusCode_name(retVal));
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::initializeCreateMethod(CActionInfo &paActionInfo) {
CSinglyLinkedList<UA_NodeId*> referencedNodes;
CSinglyLinkedList<UA_NodeId*> presentNodes;
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itMethodNodePairInfo = paActionInfo.getNodePairInfo().begin();
bool isNodePresent = false;
UA_StatusCode retVal = getNode(**itMethodNodePairInfo, presentNodes, &isNodePresent);
if(UA_STATUSCODE_GOOD == retVal) {
if(isNodePresent) {
UA_NodeId parent = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
//look for parent object
for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresentNodes = presentNodes.begin(); itPresentNodes != presentNodes.end();) {
CSinglyLinkedList<UA_NodeId*>::Iterator currentIterator = itPresentNodes;
++itPresentNodes;
if(itPresentNodes == presentNodes.back()) {
parent = **currentIterator;
break;
}
}
retVal = handleExistingMethod(paActionInfo, parent);
handlePresentNodes(presentNodes, referencedNodes, UA_STATUSCODE_GOOD != retVal);
} else { //node does not exist
//presentNodes shouldn't have any allocated NodeId at this point
CIEC_STRING nodeName;
retVal = splitAndCreateFolders((*itMethodNodePairInfo)->mBrowsePath, nodeName, referencedNodes);
if(UA_STATUSCODE_GOOD == retVal) {
CCreateMethodInfo methodInformation(static_cast<CLocalMethodInfo&>(paActionInfo));
initializeCreateInfo(nodeName, (**itMethodNodePairInfo), referencedNodes.isEmpty() ? 0 : *(referencedNodes.back()), methodInformation);
methodInformation.mOutputSize = paActionInfo.getSendSize();
methodInformation.mInputSize = paActionInfo.getReceiveSize();
createMethodArguments(paActionInfo, methodInformation);
retVal = createMethodNode(methodInformation, &(*itMethodNodePairInfo)->mNodeId);
if(UA_STATUSCODE_GOOD == retVal) {
UA_NodeId *tmp = UA_NodeId_new();
UA_NodeId_copy(methodInformation.mReturnedNodeId, tmp);
referencedNodes.pushBack(tmp);
}
}
}
}
//we add the references first even if it fails, since some nodes might have been created,
//and/or some might have been already there, so deleting them will be taken care of by
//the referencedNodesDecrement function later
referencedNodesIncrement(referencedNodes, paActionInfo);
if(UA_STATUSCODE_GOOD != retVal) {
referencedNodesDecrement(paActionInfo);
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
UA_NodeId_delete(*itRerencedNodes);
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::handleExistingMethod(CActionInfo &paActionInfo, UA_NodeId &paParentNode) {
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin();
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
DEVLOG_INFO("[OPC UA LOCAL]: Adding a callback for an existing method at %s\n", (*it)->mBrowsePath.getValue());
//check if the method was already referenced by another FB
for(CSinglyLinkedList<UA_ParentNodeHandler>::Iterator iter = mMethodsContexts.begin(); iter != mMethodsContexts.end(); ++iter) {
if(UA_NodeId_equal(&paParentNode, (*iter).mParentNodeId) && UA_NodeId_equal((*it)->mNodeId, (*iter).mMethodNodeId)) {
DEVLOG_ERROR(
"[OPC UA LOCAL]: The FB %s is trying to reference a local method at %s which has already a FB who is referencing it. Cannot add another one\n",
paActionInfo.getLayer().getCommFB()->getInstanceName(), (*it)->mBrowsePath.getValue());
retVal = UA_STATUSCODE_BADINTERNALERROR;
break;
}
}
//TODO: check types of existing method to this layer
if(UA_STATUSCODE_GOOD == retVal) {
retVal = UA_Server_setMethodNode_callback(mUaServer, *(*it)->mNodeId, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall);
if(UA_STATUSCODE_GOOD == retVal) {
retVal = UA_Server_setNodeContext(mUaServer, *(*it)->mNodeId, &mMethodsContexts);
if(UA_STATUSCODE_GOOD == retVal) {
UA_ParentNodeHandler parentNodeContext(paParentNode, (*it)->mNodeId, static_cast<CLocalMethodInfo&>(paActionInfo));
mMethodsContexts.pushBack(parentNodeContext);
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not set context function for method at %s. Error: %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName(),
UA_StatusCode_name(retVal));
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not set callback function for method at %s. Error: %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName(),
UA_StatusCode_name(retVal));
}
}
return retVal;
}
void COPC_UA_Local_Handler::createMethodArguments(CActionInfo &paActionInfo, CCreateMethodInfo &paCreateMethodInfo) {
const SFBInterfaceSpec *interfaceFB = paActionInfo.getLayer().getCommFB()->getFBInterfaceSpec();
const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
CIEC_ANY *dataToReceive = paActionInfo.getDataToReceive();
paCreateMethodInfo.mOutputArguments = static_cast<UA_Argument *>(UA_Array_new(paCreateMethodInfo.mOutputSize, &UA_TYPES[UA_TYPES_ARGUMENT]));
paCreateMethodInfo.mInputArguments = static_cast<UA_Argument *>(UA_Array_new(paCreateMethodInfo.mInputSize, &UA_TYPES[UA_TYPES_ARGUMENT]));
for(size_t i = 0; i < paCreateMethodInfo.mOutputSize + paCreateMethodInfo.mInputSize; i++) {
UA_Argument *arg;
if(i < paCreateMethodInfo.mOutputSize) {
arg = &(paCreateMethodInfo.mOutputArguments)[i];
UA_Argument_init(arg);
arg->name = UA_STRING_ALLOC(CStringDictionary::getInstance().get(interfaceFB->m_aunDINames[i + 2])); //we store the names of the SDs/RDs as names for the arguments names. Not so nice. + 2 skips the QI and ID
arg->dataType = COPC_UA_Helper::getOPCUATypeFromAny(dataToSend[i])->typeId;
} else {
arg = &(paCreateMethodInfo.mInputArguments)[i - paCreateMethodInfo.mOutputSize];
UA_Argument_init(arg);
arg->name = UA_STRING_ALLOC(CStringDictionary::getInstance().get(interfaceFB->m_aunDONames[i - paCreateMethodInfo.mOutputSize + 2])); // + 2 skips the QO and STATUS
arg->dataType = COPC_UA_Helper::getOPCUATypeFromAny(dataToReceive[i - paCreateMethodInfo.mOutputSize])->typeId;
}
arg->arrayDimensionsSize = 0;
arg->arrayDimensions = 0;
arg->description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, "Method parameter");
arg->valueRank = -1;
}
}
UA_StatusCode COPC_UA_Local_Handler::createMethodNode(CCreateMethodInfo &paCreateMethodInfo, UA_NodeId **paNodeId) {
UA_NodeId requestedNodeId;
if(paCreateMethodInfo.mRequestedNodeId) {
UA_NodeId_copy(paCreateMethodInfo.mRequestedNodeId, &requestedNodeId);
} else {
requestedNodeId = UA_NODEID_NUMERIC(paCreateMethodInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
}
UA_NodeId parentNodeId;
if(paCreateMethodInfo.mParentNodeId) {
UA_NodeId_copy(paCreateMethodInfo.mParentNodeId, &parentNodeId);
} else {
parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
DEVLOG_WARNING(
"[OPC UA LOCAL]: You are creating a Method %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
paCreateMethodInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateMethodInfo.mBrowseName->name.data));
}
UA_MethodAttributes methodAttributes;
UA_MethodAttributes_init(&methodAttributes);
methodAttributes.description = UA_LOCALIZEDTEXT_ALLOC(mEnglishLocaleForNodes, "Method which can be called");
methodAttributes.executable = true;
methodAttributes.userExecutable = true;
methodAttributes.displayName.locale = UA_STRING_ALLOC(mEnglishLocaleForNodes);
UA_String_copy(&paCreateMethodInfo.mBrowseName->name, &methodAttributes.displayName.text);
UA_StatusCode retVal = UA_Server_addMethodNode(mUaServer, requestedNodeId, parentNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
*paCreateMethodInfo.mBrowseName, methodAttributes, COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall, paCreateMethodInfo.mInputSize,
paCreateMethodInfo.mInputArguments, paCreateMethodInfo.mOutputSize, paCreateMethodInfo.mOutputArguments, &mMethodsContexts,
paCreateMethodInfo.mReturnedNodeId);
if(UA_STATUSCODE_GOOD == retVal) {
if(!*paNodeId) {
*paNodeId = UA_NodeId_new();
UA_NodeId_copy(paCreateMethodInfo.mReturnedNodeId, *paNodeId);
}
UA_ParentNodeHandler parentNodeContext(parentNodeId, *paNodeId, paCreateMethodInfo.mLocalMethodInfo);
mMethodsContexts.pushBack(parentNodeContext);
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: OPC UA could not create method at %s. Error: %s\n",
paCreateMethodInfo.mLocalMethodInfo.getLayer().getCommFB()->getInstanceName(), UA_StatusCode_name(retVal));
}
UA_NodeId_deleteMembers(&parentNodeId);
UA_NodeId_deleteMembers(&requestedNodeId);
UA_MethodAttributes_deleteMembers(&methodAttributes);
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::initializeCreateObject(CActionInfo &paActionInfo) {
//The main process is done in the execution
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin();
++it;
if("" == (*it)->mBrowsePath) { //the browsename of the instance is mandatory
retVal = UA_STATUSCODE_BADINTERNALERROR;
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::initializeDeleteObject(CActionInfo &) {
//nothing to do here
return UA_STATUSCODE_GOOD;
}
UA_StatusCode COPC_UA_Local_Handler::executeWrite(CActionInfo &paActionInfo) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
size_t indexOfNodePair = 0;
for(CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator it = paActionInfo.getNodePairInfo().begin(); it != paActionInfo.getNodePairInfo().end();
++it, indexOfNodePair++) {
retVal = updateNodeValue(*(*it)->mNodeId, &dataToSend[indexOfNodePair]);
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not convert value to write for port %d at FB %s. Error: %s\n", indexOfNodePair,
paActionInfo.getLayer().getCommFB()->getInstanceName(), UA_StatusCode_name(retVal));
break;
}
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::executeCreateMethod(CActionInfo &paActionInfo) {
//This is the return of a local method call, when RSP is triggered
UA_StatusCode retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
CLocalMethodCall *localMethodCall = getLocalMethodCall(static_cast<CLocalMethodInfo&>(paActionInfo));
if(localMethodCall) {
const CIEC_ANY *dataToSend = paActionInfo.getDataToSend();
// copy SD values to output
for(size_t i = 0; i < localMethodCall->mSendHandle.mSize; i++) {
COPC_UA_Helper::fillVariant(*localMethodCall->mSendHandle.mData[i], dataToSend[i]);
}
localMethodCall->mActionInfo.getResultReady().inc();
retVal = UA_STATUSCODE_GOOD;
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: The method being returned hasn't been called before of FB %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName());
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::executeCreateObject(CActionInfo &paActionInfo) {
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itTypeNodePairInfo = paActionInfo.getNodePairInfo().begin();
//look for type first
if(isTypeOfObjectPresent(**itTypeNodePairInfo)) {
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itInstance = paActionInfo.getNodePairInfo().begin();
++itInstance;
CSinglyLinkedList<UA_NodeId*> referencedNodes;
bool isNodePresent = false;
//check if an instance is already present
retVal = getNode(**itInstance, referencedNodes, &isNodePresent);
if(UA_STATUSCODE_GOOD == retVal) {
if(!isNodePresent) {
CIEC_STRING nodeName;
retVal = splitAndCreateFolders((*itInstance)->mBrowsePath, nodeName, referencedNodes);
if(UA_STATUSCODE_GOOD == retVal) {
CCreateObjectInfo createInformation;
initializeCreateInfo(nodeName, (**itInstance), referencedNodes.isEmpty() ? 0 : *(referencedNodes.back()), createInformation);
createInformation.mTypeNodeId = (*itTypeNodePairInfo)->mNodeId;
retVal = createObjectNode(createInformation);
if(UA_STATUSCODE_GOOD == retVal) {
UA_NodeId *tmp = UA_NodeId_new();
UA_NodeId_copy(createInformation.mReturnedNodeId, tmp);
referencedNodes.pushBack(tmp);
}
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: You are trying to create a node which already exists at FB %s\n", paActionInfo.getLayer().getCommFB()->getInstanceName());
retVal = UA_STATUSCODE_BADINTERNALERROR;
}
}
//we add the references first even if itType fails, since some nodes might have been created,
//and/or some might have been already there, so deleting them will be taken care of by
//the referencedNodesDecrement function later
referencedNodesIncrement(referencedNodes, paActionInfo);
if(UA_STATUSCODE_GOOD != retVal) {
referencedNodesDecrement(paActionInfo);
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator itRerencedNodes = referencedNodes.begin(); itRerencedNodes != referencedNodes.end(); ++itRerencedNodes) {
UA_NodeId_delete(*itRerencedNodes);
}
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: The type of the object to create could not be found \n", paActionInfo.getLayer().getCommFB()->getInstanceName());
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::createObjectNode(CCreateObjectInfo &paCreateVariableInfo) {
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
UA_NodeId requestedNodeId;
if(paCreateVariableInfo.mRequestedNodeId) {
UA_NodeId_copy(paCreateVariableInfo.mRequestedNodeId, &requestedNodeId);
} else {
requestedNodeId = UA_NODEID_NUMERIC(paCreateVariableInfo.mBrowseName->namespaceIndex, 0); //if not provided, let the server generate the NodeId using the namespace from the browsename
}
UA_NodeId parentNodeId;
if(paCreateVariableInfo.mParentNodeId) {
UA_NodeId_copy(paCreateVariableInfo.mParentNodeId, &parentNodeId);
} else {
parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
DEVLOG_WARNING(
"[OPC UA LOCAL]: You are creating an Object %.*s in the /Root folder. This is not a good practice. Try to create them in the /Root/Objects folder\n",
paCreateVariableInfo.mBrowseName->name.length, reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data));
}
CIEC_STRING nodeName;
nodeName.assign(reinterpret_cast<const char*>(paCreateVariableInfo.mBrowseName->name.data),
static_cast<TForteUInt16>(paCreateVariableInfo.mBrowseName->name.length));
UA_ObjectAttributes oAttr;
UA_ObjectAttributes_init(&oAttr);
oAttr.description = UA_LOCALIZEDTEXT_ALLOC("", nodeName.getValue());
oAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("", nodeName.getValue());
retVal = UA_Server_addObjectNode(mUaServer, requestedNodeId, parentNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), *paCreateVariableInfo.mBrowseName,
*paCreateVariableInfo.mTypeNodeId, oAttr, 0, paCreateVariableInfo.mReturnedNodeId);
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not addObjectNode. Error: %s\n", UA_StatusCode_name(retVal));
}
UA_NodeId_deleteMembers(&requestedNodeId);
UA_ObjectAttributes_deleteMembers(&oAttr);
return retVal;
}
bool COPC_UA_Local_Handler::isTypeOfObjectPresent(CActionInfo::CNodePairInfo &paNodePairInfo) {
bool isNodePresent = false;
CSinglyLinkedList<UA_NodeId*> referencedNodes;
UA_StatusCode retVal = getNode(paNodePairInfo, referencedNodes, &isNodePresent);
if(UA_STATUSCODE_GOOD == retVal) {
if(isNodePresent) {
isNodePresent = true;
}
//we don't need the nodes from the type
for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresentNodes = referencedNodes.begin(); itPresentNodes != referencedNodes.end(); ++itPresentNodes) {
UA_NodeId_delete(*itPresentNodes);
}
referencedNodes.clearAll();
}
return isNodePresent;
}
UA_StatusCode COPC_UA_Local_Handler::executeDeleteObject(CActionInfo &paActionInfo) {
bool isNodePresent = false;
CSinglyLinkedList<CActionInfo::CNodePairInfo*>::Iterator itInstance = paActionInfo.getNodePairInfo().begin();
CSinglyLinkedList<UA_NodeId*> referencedNodes;
//look for instance
UA_StatusCode retVal = getNode(**itInstance, referencedNodes, &isNodePresent);
if(UA_STATUSCODE_GOOD == retVal) {
if(isNodePresent) {
retVal = UA_Server_deleteNode(mUaServer, *(*itInstance)->mNodeId, true);
} else {
DEVLOG_ERROR("[OPC UA LOCAL]: The instance of the object to delete could not be found for FB %s\n",
paActionInfo.getLayer().getCommFB()->getInstanceName());
retVal = UA_STATUSCODE_BADINTERNALERROR;
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresendNodes = referencedNodes.begin(); itPresendNodes != referencedNodes.end(); ++itPresendNodes) {
UA_NodeId_delete(*itPresendNodes);
}
referencedNodes.clearAll();
}
return retVal;
}
void COPC_UA_Local_Handler::initializeCreateInfo(CIEC_STRING &paNodeName, CActionInfo::CNodePairInfo &paNodePairInfo, const UA_NodeId *paParentNodeId,
CCreateInfo &paResult) {
COPC_UA_Helper::getBrowsenameFromNodeName(paNodeName.getValue(), scmDefaultBrowsenameNameSpace, *paResult.mBrowseName); //this cannot fail here anymore, since it was checked already with getNode
paResult.mRequestedNodeId = paNodePairInfo.mNodeId;
paResult.mParentNodeId = paParentNodeId;
}
UA_StatusCode COPC_UA_Local_Handler::getNode(CActionInfo::CNodePairInfo &paNodePairInfo, CSinglyLinkedList<UA_NodeId*> &paFoundNodeIds, bool *paIsPresent) {
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
*paIsPresent = false;
if("" != paNodePairInfo.mBrowsePath) {
UA_BrowsePath *browsePaths = 0;
size_t pathCount = 0;
size_t firstNonExistingNode = 0;
CSinglyLinkedList<UA_NodeId*> existingNodeIds;
retVal = translateBrowseNameAndStore(paNodePairInfo.mBrowsePath.getValue(), &browsePaths, &pathCount, &firstNonExistingNode, existingNodeIds);
if(UA_STATUSCODE_GOOD == retVal) {
if(firstNonExistingNode == pathCount) { //all nodes exist
*paIsPresent = true;
if(paNodePairInfo.mNodeId) { //nodeID was provided
if(!UA_NodeId_equal(paNodePairInfo.mNodeId, *existingNodeIds.back())) {
*paIsPresent = false; // the found Node has not the same NodeId.
}
} else { //if no nodeID was provided, the found Node is stored in the nodePairInfo
paNodePairInfo.mNodeId = UA_NodeId_new();
UA_NodeId_copy(*existingNodeIds.back(), paNodePairInfo.mNodeId);
}
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator it = existingNodeIds.begin(); it != existingNodeIds.end(); ++it) {
if(!*paIsPresent) {
UA_NodeId_delete(*it);
} else {
paFoundNodeIds.pushBack((*it));
}
}
COPC_UA_Helper::releaseBrowseArgument(*browsePaths, pathCount);
}
} else {
void *handle = 0;
//we use UA_Server_getNodeContext just to check if the node exist in the addressspace
if(UA_STATUSCODE_BADNODEIDUNKNOWN != UA_Server_getNodeContext(mUaServer, *paNodePairInfo.mNodeId, &handle)) {
*paIsPresent = true;
}
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::translateBrowseNameAndStore(const char *paBrowsePath, UA_BrowsePath **paBrowsePaths, size_t *paFoldercount,
size_t *paFirstNonExistingNode, CSinglyLinkedList<UA_NodeId*> &paFoundNodeIds) {
UA_StatusCode retVal = COPC_UA_Helper::prepareBrowseArgument(paBrowsePath, paBrowsePaths, paFoldercount);
if(UA_STATUSCODE_GOOD == retVal) {
UA_BrowsePathResult *browsePathsResults = 0;
CSinglyLinkedList<UA_NodeId*> storedNodeIds;
browsePathsResults = static_cast<UA_BrowsePathResult *>(UA_Array_new(*paFoldercount * 2, &UA_TYPES[UA_TYPES_BROWSEPATHRESULT]));
for(unsigned int i = 0; i < *paFoldercount * 2; i++) {
browsePathsResults[i] = UA_Server_translateBrowsePathToNodeIds(mUaServer, &(*paBrowsePaths)[i]);
}
retVal = storeAlreadyExistingNodes(browsePathsResults, *paFoldercount, paFirstNonExistingNode, storedNodeIds);
for(CSinglyLinkedList<UA_NodeId*>::Iterator it = storedNodeIds.begin(); it != storedNodeIds.end(); ++it) {
if(UA_STATUSCODE_GOOD != retVal) {
UA_NodeId_delete(*it);
} else {
paFoundNodeIds.pushBack(*it);
}
}
UA_Array_delete(browsePathsResults, *paFoldercount * 2, &UA_TYPES[UA_TYPES_BROWSEPATHRESULT]);
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::storeAlreadyExistingNodes(UA_BrowsePathResult *paBrowsePathsResults, size_t paFolderCnt, size_t *paFirstNonExistingNode,
CSinglyLinkedList<UA_NodeId*> &paCreatedNodeIds) {
size_t foundFolderOffset = 0;
int retVal; // no unsigned, because we need to check for -1
bool offsetFound = false;
for(retVal = static_cast<int>(paFolderCnt) - 1; retVal >= 0; retVal--) {
if(UA_STATUSCODE_GOOD == paBrowsePathsResults[retVal].statusCode) { // find first existing node. Check for isInverse = TRUE
foundFolderOffset = 0;
offsetFound = true;
} else if(UA_STATUSCODE_GOOD == paBrowsePathsResults[paFolderCnt + retVal].statusCode) { // and for isInverse = FALSE
foundFolderOffset = paFolderCnt;
offsetFound = true;
}
if(offsetFound) {
break;
}
}
if(-1 != retVal) {
if(paBrowsePathsResults[foundFolderOffset + retVal].targetsSize == 0) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not translate browse paths to node IDs. Target size is 0.\n");
return UA_STATUSCODE_BADINTERNALERROR;
} else {
if(paBrowsePathsResults[foundFolderOffset + retVal].targetsSize > 1) {
DEVLOG_WARNING("[OPC UA LOCAL]: The given browse path has multiple results for the same path. Taking the first result.\n");
}
}
}
retVal++;
for(int j = 0; j < retVal; j++) {
UA_NodeId *tmp = UA_NodeId_new();
UA_NodeId_copy(&paBrowsePathsResults[foundFolderOffset + j].targets[0].targetId.nodeId, tmp);
paCreatedNodeIds.pushBack(tmp);
}
*paFirstNonExistingNode = static_cast<size_t>(retVal);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode COPC_UA_Local_Handler::createFolders(const char *paFolders, CSinglyLinkedList<UA_NodeId*> &paCreatedNodeIds) {
UA_BrowsePath *browsePaths = 0;
size_t folderCnt = 0;
size_t firstNonExistingNode;
CSinglyLinkedList<UA_NodeId*> existingNodeIds;
UA_StatusCode retVal = translateBrowseNameAndStore(paFolders, &browsePaths, &folderCnt, &firstNonExistingNode, existingNodeIds);
if(UA_STATUSCODE_GOOD == retVal) {
UA_NodeId parentNodeId;
if(existingNodeIds.isEmpty()) {
UA_NodeId objectsNode = UA_NODEID_NUMERIC(0, UA_NS0ID_ROOTFOLDER);
UA_NodeId_copy(&objectsNode, &parentNodeId);
} else {
UA_NodeId_copy(*existingNodeIds.back(), &parentNodeId);
}
CSinglyLinkedList<UA_NodeId*> createdNodeIds;
// create all the nodes on the way
for(size_t j = firstNonExistingNode; j < folderCnt; j++) {
UA_NodeId type = UA_NODEID_NUMERIC(0, UA_NS0ID_FOLDERTYPE);
CCreateObjectInfo createInformation;
createInformation.mRequestedNodeId = 0;
createInformation.mParentNodeId = &parentNodeId;
UA_QualifiedName_copy(&browsePaths[folderCnt - 1].relativePath.elements[j].targetName, createInformation.mBrowseName);
createInformation.mTypeNodeId = &type;
retVal = createObjectNode(createInformation);
if(UA_STATUSCODE_GOOD != retVal) {
DEVLOG_ERROR("[OPC UA LOCAL]: Could not create folder %.*s in path %s. Error: %s\n", createInformation.mBrowseName->name.length,
reinterpret_cast<const char*>(createInformation.mBrowseName->name.data), paFolders, UA_StatusCode_name(retVal));
break;
}
UA_NodeId *tmp = UA_NodeId_new();
UA_NodeId_copy(createInformation.mReturnedNodeId, tmp);
createdNodeIds.pushBack(tmp);
UA_NodeId_deleteMembers(&parentNodeId);
UA_NodeId_copy(createInformation.mReturnedNodeId, &parentNodeId);
}
UA_NodeId_deleteMembers(&parentNodeId);
for(CSinglyLinkedList<UA_NodeId*>::Iterator it = existingNodeIds.begin(); it != existingNodeIds.end(); ++it) {
if(UA_STATUSCODE_GOOD != retVal) {
UA_NodeId_delete(*it);
} else {
paCreatedNodeIds.pushBack(*it);
}
}
for(CSinglyLinkedList<UA_NodeId*>::Iterator it = createdNodeIds.begin(); it != createdNodeIds.end(); ++it) {
paCreatedNodeIds.pushBack(*it); //store the NodeId because the object was created, and if the next fails, the node should be referenced
}
COPC_UA_Helper::releaseBrowseArgument(*browsePaths, folderCnt);
}
return retVal;
}
UA_StatusCode COPC_UA_Local_Handler::splitAndCreateFolders(CIEC_STRING &paBrowsePath, CIEC_STRING &paNodeName,
CSinglyLinkedList<UA_NodeId*> &paRreferencedNodes) {
CIEC_STRING folders;
UA_StatusCode retVal = UA_STATUSCODE_BADINTERNALERROR;
if(splitFoldersFromNode(paBrowsePath, folders, paNodeName)) {
retVal = UA_STATUSCODE_GOOD;
if("" != folders) {
retVal = createFolders(folders.getValue(), paRreferencedNodes);
}
}
return retVal;
}
bool COPC_UA_Local_Handler::splitFoldersFromNode(const CIEC_STRING &paOriginal, CIEC_STRING &paFolder, CIEC_STRING &paNodeName) {
bool retVal = false;
if(COPC_UA_Helper::isBrowsePathValid(paOriginal.getValue())) {
retVal = true;
CIEC_STRING copyOfOriginal(paOriginal);
char *begin = copyOfOriginal.getValue();
char *runner = begin + copyOfOriginal.length() - 1;
if('/' == *runner) { // remove tailing slash
*runner = '\0';
runner--;
}
while('/' != *runner) {
runner--;
}
*runner = '\0';
paNodeName = runner + 1;
if(begin != runner) {
paFolder = begin;
}
}
return retVal;
}
void COPC_UA_Local_Handler::handlePresentNodes(CSinglyLinkedList<UA_NodeId*> &paPresentNodes, CSinglyLinkedList<UA_NodeId*> &paReferencedNodes, bool paFailed) {
for(CSinglyLinkedList<UA_NodeId*>::Iterator itPresendNodes = paPresentNodes.begin(); itPresendNodes != paPresentNodes.end(); ++itPresendNodes) {
if(paFailed) {
UA_NodeId_delete(*itPresendNodes);
} else {
paReferencedNodes.pushBack(*itPresendNodes);
}
}
}
COPC_UA_Local_Handler::CLocalMethodCall& COPC_UA_Local_Handler::addMethodCall(CLocalMethodInfo &paActionInfo,
COPC_UA_Helper::UA_SendVariable_handle &paHandleRecv) {
CCriticalRegion criticalRegion(mMethodCallsMutex);
mMethodCalls.pushBack(CLocalMethodCall(paActionInfo, paHandleRecv));
CSinglyLinkedList<CLocalMethodCall>::Iterator justPushed = mMethodCalls.back();
return *justPushed;
}
void COPC_UA_Local_Handler::removeMethodCall(CLocalMethodCall &toRemove) {
CCriticalRegion criticalRegion(mMethodCallsMutex);
mMethodCalls.erase(toRemove);
}
COPC_UA_Local_Handler::CLocalMethodCall *COPC_UA_Local_Handler::getLocalMethodCall(CLocalMethodInfo &paActionInfo) {
CCriticalRegion criticalRegion(mMethodCallsMutex);
CLocalMethodCall *retVal = 0;
for(CSinglyLinkedList<CLocalMethodCall>::Iterator it = mMethodCalls.begin(); it != mMethodCalls.end(); ++it) {
if(&(*it).mActionInfo == &paActionInfo) {
retVal = &(*it);
break;
}
}
return retVal;
}
// ******************** CALLBACKS *************************
UA_StatusCode COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onServerMethodCall(UA_Server *, const UA_NodeId *, void *, const UA_NodeId *paMethodNodeId,
void *paMethodContext, //NOSONAR
const UA_NodeId *paParentNodeId, void *, //NOSONAR
size_t paInputSize, const UA_Variant *paInput, size_t paOutputSize, UA_Variant *paOutput) {
UA_StatusCode retVal = UA_STATUSCODE_BADUNEXPECTEDERROR;
CLocalMethodInfo *localMethodHandle = 0;
CSinglyLinkedList<UA_ParentNodeHandler> *methodsContexts = static_cast<CSinglyLinkedList<UA_ParentNodeHandler>*>(paMethodContext);
for(CSinglyLinkedList<UA_ParentNodeHandler>::Iterator iter = methodsContexts->begin(); iter != methodsContexts->end(); ++iter) {
if(UA_NodeId_equal(paParentNodeId, (*iter).mParentNodeId) && UA_NodeId_equal(paMethodNodeId, (*iter).mMethodNodeId)) {
localMethodHandle = &(*iter).mActionInfo;
break;
}
}
if(!localMethodHandle) {
DEVLOG_ERROR("[OPC UA LOCAL]: Method doesn't have any FB referencing it\n");
} else {
if(paInputSize != localMethodHandle->getReceiveSize() || paOutputSize != localMethodHandle->getSendSize()) {
DEVLOG_ERROR("[OPC UA LOCAL]: method call got invalid number of arguments. In: %d==%d, Out: %d==%d\n", localMethodHandle->getReceiveSize(), paInputSize,
localMethodHandle->getSendSize(), paOutput);
} else {
// other thread may currently create nodes for the same path, thus mutex
CCriticalRegion criticalRegion(localMethodHandle->getMutex()); //TODO: do we need this mutex?
COPC_UA_Helper::UA_SendVariable_handle sendHandle(paOutputSize);
COPC_UA_Helper::UA_RecvVariable_handle recvHandle(paInputSize);
for(size_t i = 0; i < paInputSize; i++) {
recvHandle.mData[i] = &paInput[i];
}
for(size_t i = 0; i < paOutputSize; i++) {
sendHandle.mData[i] = &paOutput[i];
}
// Handle return of receive mData
if(e_ProcessDataOk == localMethodHandle->getLayer().recvData(static_cast<const void *>(&recvHandle), 0)) { //TODO: add multidimensional mData handling with 'range'.
localMethodHandle->getLayer().getCommFB()->interruptCommFB(&localMethodHandle->getLayer());
//when the method finishes, and RSP is triggered, sendHandle will be filled with the right information
CLocalMethodCall &localMethodCall = ::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).addMethodCall(
*localMethodHandle, sendHandle);
::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).startNewEventChain(localMethodHandle->getLayer().getCommFB());
//wait For semaphore, which will be released by execute Local Method in this handler
if(!localMethodHandle->getResultReady().timedWait(scmMethodCallTimeoutInNanoSeconds)) {
DEVLOG_ERROR("[OPC UA LOCAL]: method call did not get result values within timeout of %u nanoseconds.\n", scmMethodCallTimeoutInNanoSeconds);
retVal = UA_STATUSCODE_BADTIMEOUT;
} else {
retVal = sendHandle.mFailed ? UA_STATUSCODE_BADUNEXPECTEDERROR : UA_STATUSCODE_GOOD;
}
::getExtEvHandler<COPC_UA_Local_Handler>(*localMethodHandle->getLayer().getCommFB()).removeMethodCall(localMethodCall);
}
}
}
return retVal;
}
void COPC_UA_Local_Handler::CUA_LocalCallbackFunctions::onWrite(UA_Server *, const UA_NodeId *, void *, const UA_NodeId *, void *nodeContext, //NOSONAR
const UA_NumericRange *, const UA_DataValue *data) {
UA_VariableContext_Handle *variableCallbackHandle = static_cast<UA_VariableContext_Handle *>(nodeContext);
COPC_UA_Helper::UA_RecvVariable_handle handleRecv(1);
handleRecv.mData[0] = data->hasValue ? &data->value : 0; //TODO: check this empty data
handleRecv.mOffset = variableCallbackHandle->mPortIndex;
EComResponse retVal = variableCallbackHandle->mActionInfo.getLayer().recvData(static_cast<const void *>(&handleRecv), 0); //TODO: add multidimensional mData handling with 'range'.
if(e_Nothing != retVal) {
variableCallbackHandle->mActionInfo.getLayer().getCommFB()->interruptCommFB(&variableCallbackHandle->mActionInfo.getLayer());
::getExtEvHandler<COPC_UA_Local_Handler>(*variableCallbackHandle->mActionInfo.getLayer().getCommFB()).startNewEventChain(
variableCallbackHandle->mActionInfo.getLayer().getCommFB());
}
}