Back Midas Rome Roody Rootana
  Midas DAQ System  Not logged in ELOG logo
Entry  30 Sep 2021, Francesco Renga, Forum, OPC client within MIDAS 
    Reply  10 Feb 2022, Francesco Renga, Forum, OPC client within MIDAS opc.cxxopc.h
Message ID: 2334     Entry time: 10 Feb 2022     In reply to: 2282
Author: Francesco Renga 
Topic: Forum 
Subject: OPC client within MIDAS 
Dear all,
        I finally succeeded to get a working driver for the communication with an OPC 
UA server. It is based on the open62541 library and I use it in combination with the 
generic.h driver class. This is still a crude implementation, but let me post it here, 
maybe it can be useful to somebody else.

BTW, if there is somebody more skilled than me with OPC UA and MIDAS drivers, who is 
willing to give suggestions for improving the implementation, it would be extremely 
appreciated.

Best Regards,
      Francesco



> Dear all,
>      I need to integrate in my MIDAS project the communication with an OPC UA 
> server. My plan is to develop an OPC UA client as a "device" in 
> midas/drivers/device.
> 
> Two questions:
> 
> 1) Is anybody aware of some similar effort for some other project, so that I can 
> get some example?
> 
> 2) What could be the more appropriate driver's class to be used? generic.cxx? 
> multi.cxx?
> 
> Thank you for your help,
>            Francesco
Attachment 1: opc.cxx  10 kB  | Hide | Hide all | Show all
/********************************************************************\

  Name:         opc.cxx
  Created by:   Francesco Renga

  Contents:     Device Driver for generic OPC server

  $Id: mscbdev.c 3428 2006-12-06 08:49:38Z ritt $

\********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "midas.h"
#include "msystem.h"
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/client_subscriptions.h>
#include <open62541/plugin/log_stdout.h>


#ifndef _MSC_VER
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#endif

#include <vector>

using namespace std;

/*---- globals -----------------------------------------------------*/

typedef struct {
  char Name[32];                // System Name (duplication)
  char ip[32];                  // IP# for network access
  int nsIndex;
  char TagsGuid[64];
} OPC_SETTINGS;

#define OPC_SETTINGS_STR "\
System Name = STRING : [32] gassys\n\
IP = STRING : [32] 172.17.19.70:4870\n\
Namespace Index = INT : 3\n\
Tags Guid = STRING : [64] ecef81d5-c834-4379-ab79-c8fa3133a311\n\
"

typedef struct {
  OPC_SETTINGS opc_settings;
  UA_Client* client;
  int num_channels;
  vector<string> channel_name;
  vector<int> channel_type;
} OPC_INFO;

/*---- device driver routines --------------------------------------*/

INT opc_init(HNDLE hKey, void **pinfo, INT channels, INT(*bd) (INT cmd, ...))
{
  
   int status, size;
   OPC_INFO *info;
   HNDLE hDB;
   
   /* allocate info structure */
   info = (OPC_INFO *)calloc(1, sizeof(OPC_INFO));
   *pinfo = info;
   
   cm_get_experiment_database(&hDB, NULL);

   /* create PSI_BEAMBLOCKER settings record */
   status = db_create_record(hDB, hKey, "", OPC_SETTINGS_STR);
   if (status != DB_SUCCESS)
      return FE_ERR_ODB;

   size = sizeof(info->opc_settings);
   db_get_record(hDB, hKey, &info->opc_settings, &size, 0);

   info->client = UA_Client_new();
   UA_ClientConfig_setDefault(UA_Client_getConfig(info->client));

   char address[32];
   sprintf(address,"opc.tcp://%s",info->opc_settings.ip);
   printf("Connecting to %s...\n",address);
   UA_StatusCode retval = UA_Client_connect(info->client, address);
   if(retval != UA_STATUSCODE_GOOD) {
     UA_Client_delete(info->client);
     return FE_ERR_HW;
   }

   ////Scan of variables
   UA_BrowseRequest bReq;
   UA_BrowseRequest_init(&bReq);
   bReq.requestedMaxReferencesPerNode = 0;
   bReq.nodesToBrowse = UA_BrowseDescription_new();
   bReq.nodesToBrowseSize = 1;
   UA_Guid guid1;
   UA_Guid_parse(&guid1, UA_STRING(info->opc_settings.TagsGuid));
   bReq.nodesToBrowse[0].nodeId = UA_NODEID_GUID(info->opc_settings.nsIndex, guid1);
   bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
   
   UA_BrowseResponse bResp = UA_Client_Service_browse(info->client, bReq);
   
   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]);
       if ((ref->nodeClass == UA_NODECLASS_OBJECT || ref->nodeClass == UA_NODECLASS_VARIABLE||ref->nodeClass == UA_NODECLASS_METHOD)) {

	 if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
	   UA_NodeId dataType;
	   UA_Client_readDataTypeAttribute(info->client, UA_NODEID_STRING(info->opc_settings.nsIndex, ref->nodeId.nodeId.identifier.string.data), &dataType);
	   (info->channel_type).push_back(dataType.identifier.numeric-1);
	   (info->channel_name).push_back((char*)(ref->nodeId.nodeId.identifier.string.data));
	 }

       }

     }
     
   }
   
   UA_BrowseNextRequest bNextReq;
   UA_BrowseNextResponse bNextResp;				    
   UA_BrowseNextRequest_init(&bNextReq);
   
   bNextReq.releaseContinuationPoints = UA_FALSE;
   bNextReq.continuationPoints = &bResp.results[0].continuationPoint;
   bNextReq.continuationPointsSize = 1;
   
   bool hasRef;
   
   do {
     
     hasRef = false;
     
     bNextResp = UA_Client_Service_browseNext(info->client, bNextReq);
     
     for (size_t i = 0; i < bNextResp.resultsSize; i++) {
       for (size_t j = 0; j < bNextResp.results[i].referencesSize; j++) {
	     
	 hasRef = true;
	 
	 UA_ReferenceDescription *ref = &(bNextResp.results[i].references[j]);
	 if ((ref->nodeClass == UA_NODECLASS_OBJECT || ref->nodeClass == UA_NODECLASS_VARIABLE||ref->nodeClass == UA_NODECLASS_METHOD)) {
	   
	   if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
	     UA_NodeId dataType;
	     UA_Client_readDataTypeAttribute(info->client, UA_NODEID_STRING(info->opc_settings.nsIndex, ref->nodeId.nodeId.identifier.string.data), &dataType);
	     (info->channel_type).push_back(dataType.identifier.numeric-1);
	     (info->channel_name).push_back((char*)(ref->nodeId.nodeId.identifier.string.data));
	   }
	   
	 }
	 
       }

     }
       
     bNextReq.continuationPoints = &bNextResp.results[0].continuationPoint;
     bNextReq.continuationPointsSize = 1;
     
   } while(hasRef);

   info->num_channels = info->channel_name.size();
   
   UA_BrowseRequest_deleteMembers(&bReq);
   UA_BrowseResponse_deleteMembers(&bResp);
   
   UA_BrowseRequest_clear(&bReq);
   UA_BrowseResponse_clear(&bResp);

   return FE_SUCCESS;

}

/*----------------------------------------------------------------------------*/

INT opc_exit(OPC_INFO * info)
{

   UA_Client_disconnect(info->client);
   UA_Client_delete(info->client);

   if (info)
      free(info);

   return FE_SUCCESS;
   
}

/*----------------------------------------------------------------------------*/

INT opc_get(OPC_INFO * info, INT channel, float *pvalue)
{

  ////Read value
  UA_Variant *val = UA_Variant_new();
  UA_StatusCode retval = UA_Client_readValueAttribute(info->client, UA_NODEID_STRING(info->opc_settings.nsIndex, info->channel_name[channel].c_str()), val);
  if (retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
      val->type == &UA_TYPES[info->channel_type[channel]]
      ) {

    if(info->channel_type[channel] == UA_TYPES_FLOAT){
      *pvalue = *(UA_Float*)val->data;
    }

    else if(info->channel_type[channel] == UA_TYPES_DOUBLE){
      *pvalue = *(UA_Double*)val->data;
    }

    else if(info->channel_type[channel] == UA_TYPES_INT16){
      *pvalue = *(UA_Int16*)val->data;
    }
    
    else if(info->channel_type[channel] == UA_TYPES_INT32){
      *pvalue = *(UA_Int32*)val->data;
    }
    
    else if(info->channel_type[channel] == UA_TYPES_INT64){
      *pvalue = *(UA_Int64*)val->data;
    }
    
    else if(info->channel_type[channel] == UA_TYPES_BOOLEAN){
      *pvalue = (*(UA_Boolean*)val->data) ? 1.0 : 0.0;
    }

    else if(info->channel_type[channel] == UA_TYPES_BYTE){
      *pvalue = *(UA_Byte*)val->data;
    }

    else {
      *pvalue = 0.;
    }

  }

  UA_Variant_delete(val);
  
  return FE_SUCCESS;

}


INT opc_set(OPC_INFO * info, INT channel, float value)
{

  ////Write value
  UA_Variant *myVariant = UA_Variant_new();

  if(info->channel_type[channel] == UA_TYPES_FLOAT){
    UA_Float typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_DOUBLE){
    UA_Double typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_INT16){
    UA_Int16 typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_INT32){
    UA_Int32 typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_INT64){
    UA_Int64 typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_BOOLEAN){
    UA_Boolean typevalue = (value != 0) ? true : false;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }

  else if(info->channel_type[channel] == UA_TYPES_BYTE){
    UA_Byte typevalue = value;
    UA_Variant_setScalarCopy(myVariant, &typevalue, &UA_TYPES[info->channel_type[channel]]);
  }
  
  else return FE_SUCCESS;
  
  UA_Client_writeValueAttribute(info->client, UA_NODEID_STRING(info->opc_settings.nsIndex, info->channel_name[channel].c_str()), myVariant);
  UA_Variant_delete(myVariant);
  
  return FE_SUCCESS;

}

/*---- device driver entry point -----------------------------------*/

... 62 more lines ...
Attachment 2: opc.h  333 Bytes  | Show | Hide all | Show all
ELOG V3.1.4-2e1708b5