简介

通过第一个简单的服务器和客户端搭建,我们可以成功地将含有一个int变量的值从服务器显示到客户端上。但是从客户端如何将值赋值给服务器上面的变量?如何将服务器上的值进行刷新变化,或者与硬件信息连接呢?

初步了解OPCUA62541开源项目给定的一些接口,在此基础上完成一个带有变量和方法的对象型节点集。

细节

在基础的服务器main函数中添加一行代码:

manuallyDefinePump(server);

下面实现这个函数即可。

首先创建一个Object节点:

UA_NodeId pumpId;    //这个为Object的ID,接着会使用它添加变量和方法
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Pump");
UA_Server_addObjectNode(server, UA_NODEID_STRING(1,"Pump"),UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),UA_QUALIFIEDNAME(1, "Pump"),         UA_NODEID_NUMERIC(0,UA_NS0ID_BASEOBJECTTYPE),oAttr, NULL, &pumpId);

这里首先创建了一个节点和Object默认的属性实例,displayname很重要,推荐和UA_NODEID_STRING()中的名称设置相同,防止在使用客户端读取不知道是读取哪一个的问题。

UA_Server_addObjectNode():服务器添加Object的接口。

第一个参数为服务器实例。

第二个参数为指定的节点id:id格式分为数字型(ns=1;i=1000)与字符型(ns=1;s=Pump),还可以使用默认的UA_NODEID_NULL让服务器自己分配,本例子使用的为字符型NodeID;

第三,四个参数分别为父节点Id和类型节点id,这里暂时将Pump对象在Objects下。

第五个参数UA_QUALIFIEDNAME为浏览名称,是从客户端显示的节点名称,很重要,推荐与STRING简单一致。

第六个是typeDefinition,暂不清晰。

第七个参数为节点属性,第八个是用户自定义数据(在需要为节点对象的一些函数传递参数时使用),第九个为创建后的节点引用导出。

添加一个参数

上一节已经简单的创建使用过,这里直接代码:

UA_VariableAttributes actposAttr = UA_VariableAttributes_default;
actposAttr.displayName = UA_LOCALIZEDTEXT("en-US", "pos");
actposAttr.valueRank = -1;
//以上完成节点属性的基础定义,然后定义节点的值为double并可读可写
UA_Double actposValue = 0.0;
UA_Variant_setScalar(&actposAttr.value, &actposValue, &UA_TYPES[UA_TYPES_DOUBLE]);
actposAttr.accessLevel = UA_ACCESSLEVELMASK_READ|UA_ACCESSLEVELMASK_WRITE;
//单独制定节点字符型id
UA_NodeId actposNodeId = UA_NODEID_STRING(1, "pos");
//完成添加
UA_Server_addVariableNode(server, actposNodeId, pumpId,UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),UA_QUALIFIEDNAME(1, "pos"),UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),actposAttr, NULL, NULL);

这里只简单地看一下UA_Server_addVariableNode()接口的使用方式。

参数1为服务器实例,2为节点id,3为父节点id,5为浏览名称。。

添加参数回调

double类型的pos节点创建成功之后,只是一个固定的值。只能从客户端读写值操作。

要是想在服务器上将这一个值与硬件信息相互连接,本人是使用的共享内存,如何将一个共享内存中的double类型拿出来就不说了。

如下添加上面创建的Object内一个变量节点的回调。

    UA_ValueCallback callback ;callback.onRead = beforeReadActpos0;callback.onWrite = afterWriteTime;UA_Server_setVariableNode_valueCallback(server, actposNodeId, callback);

onRead指向的为客户端每次查看该值前服务器的操作,这里便可以将共享内存的值赋值过来。

onWrite指向的是客户端进行操作赋值之后的操作。

接着将该callback添加到指定的节点id中。

下面创建onRead指向的函数:

static void beforeReadActpos0(UA_Server* server,const UA_NodeId* sessionId, void * sessionContext,const UA_NodeId* nodeid, void * nodeContext,const UA_NumericRange* range, const UA_DataValue * data) {UA_Variant value;UA_Double now = XXXXXXXXXXXXX;UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DOUBLE]);UA_NodeId currentNodeId = UA_NODEID_STRING(1, "pos");UA_Server_writeValue(server, currentNodeId, value);
}

XXXXXXX所做的操作应该是共享内存中你要监控的一个double变量。

然后将值写入节点,客户端每次查看时便可以更新出共享内存的值变化。

onWrite的函数未做操作,可以自行尝试:

static void afterWriteTime(UA_Server* server,const UA_NodeId* sessionId, void * sessionContext,const UA_NodeId* nodeId, void * nodeContext,const UA_NumericRange* range, const UA_DataValue * data) {}#想要使用节点值的话:
*(UAtype *)(data->value.data) 即可获取写入节点的值

两个回调函数的参数是不能改变的,除非你修改OPCUA的源码,想要添加自己的参数就要使用到之前所说的自定义数据了,在多Objects时会有讲到。

添加方法

方法method为服务器创建的方法节点,提供输入与输出数组,客户端可以直接调用服务器的方法节点,传入输入数组,服务器调用完该接口后,获取输出数组。

这里创建一个输入为3个bool,输出为一个int值的方法如下:

    UA_Argument inputArgument;UA_Argument_init(&inputArgument);inputArgument.description = UA_LOCALIZEDTEXT("en-US", "tree argu"); inputArgument.name = UA_STRING("MyBoolInput");inputArgument.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId; inputArgument.valueRank = 1; /* scalar *///这里添加,定义输入为3个bool的数组UA_UInt32 pInputDimension = 3;inputArgument.arrayDimensionsSize = 1;inputArgument.arrayDimensions = &pInputDimension;UA_Argument outputArgument;UA_Argument_init(&outputArgument);outputArgument.description = UA_LOCALIZEDTEXT("en-US", "power on"); outputArgument.name = UA_STRING("MyOutput");outputArgument.dataType = UA_TYPES[UA_TYPES_INT32].typeId; outputArgument.valueRank = -1; /* scalar */UA_MethodAttributes helloAttr = UA_MethodAttributes_default;helloAttr.description = UA_LOCALIZEDTEXT("en-US","setpos");helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","setpos");helloAttr.executable = true;helloAttr.userExecutable = true;UA_Server_addMethodNode(server, UA_NODEID_STRING(1,"setpos"),pumpId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT), UA_QUALIFIEDNAME(1, "setpos"),helloAttr, &McPower0MethodCallback,1, &inputArgument, 1, &outputArgument, NULL, NULL);

以上代码首先创建了输入数组和输出数组,接着创建方法节点。

然后创建其回调函数用于处理输入数组和返回输出数组:

static UA_StatusCode McPower0MethodCallback(UA_Server *server,const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *methodId, void *methodContext, const UA_NodeId *objectId, void *objectContext, size_t inputSize, const UA_Variant *input, size_t outputSize, UA_Variant *output) {UA_Boolean *inputArray = (UA_Boolean*)input->data;//获取输入数组UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Show: %d-%d-%d",inputArray[0],inputArray[1],inputArray[2]);//操作//返回输出数组UA_Int32 tmp = (UA_Int32)XXXXXXXXXXX;UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_INT32]);return UA_STATUSCODE_GOOD;
}

如此便完成了一个方法的添加。

如何添加多个不同类型的输入参数呢?

与一个输入参数的区别就是要使用数组,然后创建方法节点的时候使用输入参数的数目:

回调函数也是在取输入参数的有些不同而已:

提供另外一个官方文档上的例子,他上面是定义了两个参数的实现方式:

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. *//*** Adding Methods to Objects* -------------------------** An object in an OPC UA information model may contain methods similar to* objects in a programming language. Methods are represented by a MethodNode.* Note that several objects may reference the same MethodNode. When an object* type is instantiated, a reference to the method is added instead of copying* the MethodNode. Therefore, the identifier of the context object is always* explicitly stated when a method is called.** The method callback takes as input a custom data pointer attached to the* method node, the identifier of the object from which the method is called,* and two arrays for the input and output arguments. The input and output* arguments are all of type :ref:`variant`. Each variant may in turn contain a* (multi-dimensional) array or scalar of any data type.** Constraints for the method arguments are defined in terms of data type, value* rank and array dimension (similar to variable definitions). The argument* definitions are stored in child VariableNodes of the MethodNode with the* respective BrowseNames ``(0, "InputArguments")`` and ``(0,* "OutputArguments")``.** Example: Hello World Method* ^^^^^^^^^^^^^^^^^^^^^^^^^^^* The method takes a string scalar and returns a string scalar with "Hello "* prepended. The type and length of the input arguments is checked internally* by the SDK, so that we don't have to verify the arguments in the callback. */#include "open62541.h"
#include <signal.h>static UA_StatusCode
helloWorldMethodCallback(UA_Server *server,const UA_NodeId *sessionId, void *sessionHandle,const UA_NodeId *methodId, void *methodContext,const UA_NodeId *objectId, void *objectContext,size_t inputSize, const UA_Variant *input,size_t outputSize, UA_Variant *output) {UA_String *inputStr = (UA_String*)input->data;UA_String tmp = UA_STRING_ALLOC("Hello ");if(inputStr->length > 0) {tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);tmp.length += inputStr->length;}UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);UA_String_deleteMembers(&tmp);UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");return UA_STATUSCODE_GOOD;
}static void
addHellWorldMethod(UA_Server *server) {UA_Argument inputArgument;UA_Argument_init(&inputArgument);inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");inputArgument.name = UA_STRING("MyInput");inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;inputArgument.valueRank = UA_VALUERANK_SCALAR;UA_Argument outputArgument;UA_Argument_init(&outputArgument);outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");outputArgument.name = UA_STRING("MyOutput");outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;outputArgument.valueRank = UA_VALUERANK_SCALAR;UA_MethodAttributes helloAttr = UA_MethodAttributes_default;helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");helloAttr.executable = true;helloAttr.userExecutable = true;UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT),UA_QUALIFIEDNAME(1, "hello world"),helloAttr, &helloWorldMethodCallback,1, &inputArgument, 1, &outputArgument, NULL, NULL);
}/*** Increase Array Values Method* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^* The method takes an array of 5 integers and a scalar as input. It returns a* copy of the array with every entry increased by the scalar. */static UA_StatusCode
IncInt32ArrayMethodCallback(UA_Server *server,const UA_NodeId *sessionId, void *sessionContext,const UA_NodeId *methodId, void *methodContext,const UA_NodeId *objectId, void *objectContext,size_t inputSize, const UA_Variant *input,size_t outputSize, UA_Variant *output) {UA_Int32 *inputArray = (UA_Int32*)input[0].data;UA_Int32 delta = *(UA_Int32*)input[1].data;/* Copy the input array */UA_StatusCode retval = UA_Variant_setArrayCopy(output, inputArray, 5,&UA_TYPES[UA_TYPES_INT32]);if(retval != UA_STATUSCODE_GOOD)return retval;/* Increate the elements */UA_Int32 *outputArray = (UA_Int32*)output->data;for(size_t i = 0; i < input->arrayLength; i++)outputArray[i] = inputArray[i] + delta;return UA_STATUSCODE_GOOD;
}static void
addIncInt32ArrayMethod(UA_Server *server) {/* Two input arguments */UA_Argument inputArguments[2];UA_Argument_init(&inputArguments[0]);inputArguments[0].description = UA_LOCALIZEDTEXT("en-US", "int32[5] array");inputArguments[0].name = UA_STRING("int32 array");inputArguments[0].dataType = UA_TYPES[UA_TYPES_INT32].typeId;inputArguments[0].valueRank = UA_VALUERANK_ONE_DIMENSION;UA_UInt32 pInputDimension = 5;inputArguments[0].arrayDimensionsSize = 1;inputArguments[0].arrayDimensions = &pInputDimension;UA_Argument_init(&inputArguments[1]);inputArguments[1].description = UA_LOCALIZEDTEXT("en-US", "int32 delta");inputArguments[1].name = UA_STRING("int32 delta");inputArguments[1].dataType = UA_TYPES[UA_TYPES_INT32].typeId;inputArguments[1].valueRank = UA_VALUERANK_SCALAR;/* One output argument */UA_Argument outputArgument;UA_Argument_init(&outputArgument);outputArgument.description = UA_LOCALIZEDTEXT("en-US", "int32[5] array");outputArgument.name = UA_STRING("each entry is incremented by the delta");outputArgument.dataType = UA_TYPES[UA_TYPES_INT32].typeId;outputArgument.valueRank = UA_VALUERANK_ONE_DIMENSION;UA_UInt32 pOutputDimension = 5;outputArgument.arrayDimensionsSize = 1;outputArgument.arrayDimensions = &pOutputDimension;/* Add the method node */UA_MethodAttributes incAttr = UA_MethodAttributes_default;incAttr.description = UA_LOCALIZEDTEXT("en-US", "IncInt32ArrayValues");incAttr.displayName = UA_LOCALIZEDTEXT("en-US", "IncInt32ArrayValues");incAttr.executable = true;incAttr.userExecutable = true;UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "IncInt32ArrayValues"),UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),UA_QUALIFIEDNAME(1, "IncInt32ArrayValues"),incAttr, &IncInt32ArrayMethodCallback,2, inputArguments, 1, &outputArgument,NULL, NULL);
}/** It follows the main server code, making use of the above definitions. */UA_Boolean running = true;
static void stopHandler(int sign) {UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");running = false;
}int main(void) {signal(SIGINT, stopHandler);signal(SIGTERM, stopHandler);UA_ServerConfig *config = UA_ServerConfig_new_default();UA_Server *server = UA_Server_new(config);addHellWorldMethod(server);addIncInt32ArrayMethod(server);UA_StatusCode retval = UA_Server_run(server, &running);UA_Server_delete(server);UA_ServerConfig_delete(config);return (int)retval;
}

OPCUA协议: 复杂服务器C语言初步实践(回调与方法)相关推荐

  1. OPCUA 复杂服务器对应的Python客户端(回调与方法)

    在创建了C服务器之后,部署在linux端.接着要在自己客户端的界面上添加关于节点变量的刷新和方法的调用.而OPCUA的客户端有比较多的通用版本,在第一节简单示例中便有一个WIndow版本的客户端,可以 ...

  2. opcua协议服务器端口号,opc ua服务器 数据配置

    opc ua服务器 数据配置 内容精选 换一换 配置OPC-UA服务端的用户身份,添加一个用户名和密码,如用户名:edge-test密码:1234 OPC-UA(OPC Unified Archite ...

  3. SkyWalking调研与初步实践

    APM和调用链跟踪 随着企业经营规模的扩大,以及对内快速诊断效率和对外SLA(服务品质协议,service-level agreement)的追求,对于业务系统的掌控度的要求越来越高,主要体现在: 对 ...

  4. Python3初步实践教程概要

    "人生苦短,快学Python" "Life is short, you need Python!" ---- Bruce Eckel Python作为一门脚本语 ...

  5. daytime协议的服务器和客户端程序,用Socket套接字实现DAYTIME协议的服务器和客户端程序.doc...

    用Socket套接字实现DAYTIME协议的服务器和客户端程序.doc 一. 设计目的 为了提高同学的自主动手能力,把理论知识运用于实践中,从实践中更好的领悟所学的知识 . 二. 题目要求及需求分析 ...

  6. 邹萍:工业互联网平台IDNICS及其开源生态初步实践

    近年来,云计算开源技术逐渐成为云计算发展的重要支撑和导向,改变了以往的信息技术进化模式,引领软件技术标准的发展和创新,深刻影响着整个信息技术产业的发展格局.为进一步探索我国云计算开源技术发展模式,加速 ...

  7. 高德渲染网关Go语言重构实践

    1.导读 高德启动Go业务建设已经有段时间了,主要包含Go应用落地,Go中间件建设,云原生三个部分.经过持续的发力,在这些方面取得了不错的进展.高德Go业务落地过程是如何实现的,遇到过哪些问题,如何解 ...

  8. c语言程序设计实践课选题,c语言程序设计实践实验题目

    c语言程序设计实践实验题目 绥化学院程序设计实践实验报告范例 参考1实验题目:循环结构程序设计实验目的:1.熟悉 VC++6.0 的运行环境,掌握 C 程序的执行方法:2.掌握三种基本数据类型.部分运 ...

  9. 邮件服务器两种协议,邮件服务器协议

    邮件服务器协议 内容精选 换一换 浏览器发送邮件:在使用浏览器登录邮箱时直接使用HTTP协议,其端口号默认为80,邮件服务器之间还是使用邮件发送协议:SMTP协议.通过浏览器发送邮件,则需要开放TCP ...

最新文章

  1. git stage 暂存_Git撤销暂存区stage中的内容
  2. sgu 126 Boxes
  3. 了解linux服务器,教你快速了解一台Linux系统服务器的方法
  4. Asp.net MVC2.0系列文章-编辑和删除新闻操作
  5. 网贷,高利贷,套路贷为什么必须铲除?
  6. Linux 用户和组
  7. SonarQube+Jenkins,搭建持续交付平台
  8. eclipse 下编写java code 比较好的设置和快捷键
  9. 脱离微信,在硬件设备运行小程序?小程序硬件框架大揭秘!
  10. jmeter线程说明_jmeter 线程组
  11. git 遇到fatal:multiple stage entries for merged file
  12. Git小乌龟的安装及使用
  13. CSDN获取C币方法
  14. AODV协议的仿真研究
  15. 如何理解js中的this
  16. html明月几时有古诗,古诗词赏析|苏轼《水调歌头·明月几时有》
  17. 开发者大会优先谈云,对于微软Win10还重要吗
  18. Kyan网络监控设备账号密码泄露漏洞
  19. 五险一金重要吗?还是趁年轻多赚钱比较重要?
  20. xshell 免费版申请

热门文章

  1. HTTPS/数字证书/数字签名
  2. 精美素材:10套最新出炉的免费扁平图标下载
  3. Claus Hansen加入Entrust Datacard,担任亚太地区和日本销售副总裁
  4. Wilcoxon 检验之 rank-sum 与 signed-rank
  5. office@word官方文档查看@审阅@批注@修订
  6. python3.7运行 skimage 报错ImportError: DLL load failed:找不到指定模块
  7. 数学符号“s.t.”的意义
  8. 公众号推送长图最佳尺寸_公众号10W 排版攻略,长图无缝拼接一步做到!
  9. SEO的道与术,因果关系的选择
  10. 文件夹配额linux,Linux磁盘配额