在工业树莓派中轻松部署您的OPC UA 服务器与客户端,


简介

虹科工业树莓派是一款基于树莓派计算模块的开源的模块化智能网关,其全名是RevolutionPi(简称RevPi)。RevPi在计算模块的基础上进行了工业级封装,以实现工业环境的适用性。它取消了不稳定的GPIO接口,通过模块化的DIO以及AIO模块进行扩展。另外它还提供有适用于大多数常见现场总线协议以及工业以太网的网关扩展模块,使得RevPi可以快速集成到您的工业网络中。在软件方面,RevPi基于树莓派的Raspbain系统,并添加了RT(RealTime)补丁, 支持C、python、Node-RED等高级语言编程,并且内置虚拟Modbus RTU/TCP主从站,无需额外扩展网关即可与您的Modbus设备进行连接。

OPC UA是OPC基金会推出的面向工业4.0的接口规范,它具有架构统一、平台独立、安全可靠、可扩展等特点。OPC UA主要解决了语义互操作性问题,在整个OICT融合中扮演了非常重要的角色。虹科工业树莓派作为一款模块化的边缘智能网关,默认是没有配备OPC UA功能的。但由于得益于其平台的开源性,我们可以自己在RevPi上部署OPC UA Server及Client。虹科Matrikon OPC UA SDK是一款允许您简单迅速地添加一个OPC UA服务器到您嵌入式产品中的软件开发工具包。但由于版权的原因,本文使用开源的open62541进行测试。作为开源项目,open62541相对于虹科Matrikon OPC UA SDK具有不标准、效率低等劣势。作为测试Demo,我们可以不考虑这一点,但在实际现场应用中,建议选择使用更加标准的虹科Matrikon OPC UA SDK进行开发。

1.准备

RevPi Core x1
路由器 x1
网线 x1
open62541源码(可从官网下载)
PC x1

2.编译open62541源码

2.1 编译

首先我们需要编译open62541源码,生成对应的.c和.h文件,才能很方便地把open62541集成到我们自己的代码中。
我下载的源码版本是open62541-1.1.2.zip,首先通过命令解压文件:

unzip open62541-1.1.2.zip

然后进入open62541-1.1.2文件夹,创建build目录并进入,输入以下命令调用cmake:

cmake .. -DUA_ENABLE_AMALGAMATION=ON

然后再调用make命令,完成后即可生成以下文件:

2.2 建立Demo

在上一部分,已经通过make命令生成了open62541.c和open62541.h文件。下面我们退出open62541-1.1.2,建立新的文件夹Demo,并将open62541.c和open62541.h文件复制到该文件夹。然后我们就可以在此文件夹中调用open62541编写server及client的程序了。为了方便后续程序编译调试,我们可以先把open62541.c编译一下,运行以下命令:

gcc -c open62541.c -o open62541.o

3.OPC UA Server

下面就正式开始编写server程序了,此部分可以参考open62541官方文档。本文的目的不仅仅是建立一个简单的server,还要结合RevPi特有的虚拟Modbus TCP主站的功能,将读取到的Modbus TCP Slave的数据放入OPC UA Server变量中,以实现一个简单的协议转换功能。

3.1 建立Server程序

新建server.c文件,并写入以下代码:

#include "piControlIf.h"  
#include "piControl.h"  
#include "open62541.h"  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <signal.h>  
#include <stdlib.h>  
  
static volatile UA_Boolean running = true;  
static void stopHandler(int sig) {  
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");  
    running = false;  
}  
  
uint16_t Read_i16u__Val(char *pszVariableName)  
{  
    int rc;  
    SPIVariable sPiVariable;  
    SPIValue sPIValue;  
    uint16_t i16uValue;  
  
    strncpy(sPiVariable.strVarName, pszVariableName, sizeof(sPiVariable.strVarName));  
    rc = piControlGetVariableInfo(&sPiVariable);  
    if (rc < 0) {  
        printf("Cannot find variable '%s'\n", pszVariableName);  
    }  
    if (sPiVariable.i16uLength == 16) {  
            rc = piControlRead(sPiVariable.i16uAddress, 4, (uint8_t *) & i16uValue);  
            if (rc < 0)  
                printf("Read error\n");  
            else  
            {  
                return i16uValue;  
            }  
    } else  
        printf("Could not read variable %s. Internal Error\n", pszVariableName);  
}  
  
static void addVariable(UA_Server *server) {  
    /* Define the attribute of the myInteger variable node */  
    UA_VariableAttributes attr = UA_VariableAttributes_default;  
    UA_Int16 myInteger = 0;  
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT16]);  
    attr.description = UA_LOCALIZEDTEXT("en-US","modbus data");  
    attr.displayName = UA_LOCALIZEDTEXT("en-US","modbus data");  
    attr.dataType = UA_TYPES[UA_TYPES_INT16].typeId;  
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;  
  
    /* Add the variable node to the information model */  
    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "modbus.data");  
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "modbus data");  
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);  
    UA_NodeId parent

相关内容