学习open62541 --- [70] 深入理解变量监测

2023-05-16

本文深入探讨一下变量监测的用法和原理。


一 累积传输

先前写的关于变量监测的文章,都是设置一个采样时间,然后有变化了就通知一下,有时我们希望变化累积一段时间再一起传给client,这时如何设置呢?

直接上代码,如下,是client端,

#include <stdlib.h>
#include <signal.h>
#include "open62541.h"


UA_Boolean running = true;


UA_NodeId gTargetId = UA_NODEID_STRING(1, (char*)"the.answer");

void stopHandler(int sign) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}


static void handler_DataChanged(UA_Client *client, UA_UInt32 subId, 
                                    void *subContext, UA_UInt32 monId, 
                                    void *monContext, UA_DataValue *value) 
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Received Notification");

    UA_NodeId *ptr = (UA_NodeId*)monContext;

    if (UA_NodeId_equal(ptr, &gTargetId))
    {
        UA_Int32 currentValue = *(UA_Int32*)(value->value.data);

        UA_DateTimeStruct dts = UA_DateTime_toStruct(value->sourceTimestamp);

        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "SubId:%u, MonId:%u, Current Value: %d, date is: %u-%u-%u %u:%u:%u.%03u\n", 
                                                            subId, monId, currentValue, 
                                                            dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);


    }
}




void addMonitoredItemToVariable(UA_Client *client)
{
    /* Create a subscription */
    UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
    request.requestedPublishingInterval = 10000; // 发布时间间隔设置为10s


    UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
                                                                            NULL, NULL, NULL);

    UA_UInt32 subId = response.subscriptionId;
    if(response.responseHeader.serviceResult == UA_STATUSCODE_GOOD)
    {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Create subscription succeeded, id %u\n", subId);
    }

    
    UA_MonitoredItemCreateRequest monRequest = 
        UA_MonitoredItemCreateRequest_default(gTargetId);

    monRequest.requestedParameters.samplingInterval = 500; // 采样时间间隔设置500ms
    monRequest.requestedParameters.queueSize = 50; // 队列设置为50

    UA_MonitoredItemCreateResult monResponse =
        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
                                                  UA_TIMESTAMPSTORETURN_BOTH,
                                                  monRequest, &gTargetId, 
                                                  handler_DataChanged, NULL);
    if(monResponse.statusCode == UA_STATUSCODE_GOOD)
    {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, "Monitoring 'the.answer', id %u\n", 
                                                            monResponse.monitoredItemId);
    }
}

int main(void) 
{
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);


    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
    if(retval != UA_STATUSCODE_GOOD) 
    {
        UA_Client_delete(client);
        return (int)retval;
    }

    addMonitoredItemToVariable(client);

    while(running)
    {
        // send publish request, time out 1000ms
        UA_Client_run_iterate(client, 1000); 
    }

    UA_Client_delete(client); /* Disconnects the client internally */
    
    return EXIT_SUCCESS;
}

关键是函数addMonitoredItemToVariable(),里面先把发布的时间间隔设置为10s,然后把采样时间间隔设置为500ms,队列设置为50,也就是最多存放50个元素。如果超过50个,就会把旧的数据删除掉,这是依据UA_MonitoredItemCreateRequest_default()给的默认设置,如下,

static UA_INLINE UA_MonitoredItemCreateRequest
UA_MonitoredItemCreateRequest_default(UA_NodeId nodeId) {
    UA_MonitoredItemCreateRequest request;
    UA_MonitoredItemCreateRequest_init(&request);
    request.itemToMonitor.nodeId = nodeId;
    request.itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
    request.monitoringMode = UA_MONITORINGMODE_REPORTING;
    request.requestedParameters.samplingInterval = 250;
    request.requestedParameters.discardOldest = true;
    request.requestedParameters.queueSize = 1;
    return request;
}

discardOldest是true

下面是Server代码,

// server.c

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include "open62541.h"

UA_Boolean running = true;

void stopHandler(int sign) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}



UA_NodeId addTheAnswerVariable(UA_Server *server) 
{
    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 1;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId theAnswerNodeId = UA_NODEID_STRING(1, (char*)"the.answer");
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)"the answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
                              parentReferenceNodeId, myIntegerName,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
    
    return theAnswerNodeId;
}

UA_NodeId addTheAnswer2Variable(UA_Server *server) 
{
    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 1;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer2");
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer2");
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId theAnswerNodeId = UA_NODEID_STRING(1, (char*)"the.answer2");
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)"the answer2");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
                              parentReferenceNodeId, myIntegerName,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
    
    return theAnswerNodeId;
}

void cycleCallback(UA_Server *server, void *data) 
{
    static UA_Int32 update = 2;
    static UA_Int32 update2 = 8;
        
    UA_NodeId * idArray = (UA_NodeId*)data;

    UA_Variant myVar;
    UA_Variant_init(&myVar);
    UA_Variant_setScalar(&myVar, &update, &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_writeValue(server, idArray[0], myVar);

    UA_Variant_init(&myVar);
    UA_Variant_setScalar(&myVar, &update2, &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_writeValue(server, idArray[1], myVar);
    
    update++;
    update2++;
    
    if (update == 100)
    {
        update = 2;
    }

    if (update2 == 200)
    {
        update = 8;
    }
}


int main(void) 
{    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    
    UA_NodeId targetNodeId = addTheAnswerVariable(server);
    UA_NodeId target2NodeId = addTheAnswer2Variable(server);

    UA_NodeId idArr[2] = {targetNodeId, target2NodeId};

    UA_UInt64 callbackId = 0;
	UA_Server_addRepeatedCallback(server, cycleCallback, idArr, 1000, &callbackId); // call every 1s
    
  
    UA_StatusCode retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

server中添加一个变量,然后每隔1s去把其值加1。

编译运行后,可以在client这边看到打印信息如下,
在这里插入图片描述
可以看出每隔10s会发送多个变化值过来,在client的变化回调里打印其值,同时打印值变化的时间,可以看到变化的时间间隔是1s,和Server端的设置一样。注意,每个变化值都会调用一次Client的回调函数。


二 设置有变化就通知

有时希望被监测的变量一有变化就通知,不希望等到发布时间到了再发送过来,如何做呢?可以把采样时间设置为0,同时把发布时间也设置为0,这样就可以达到这个目的了。

源码分析

创建监测项时会调用UA_MonitoredItem_registerSampling(),这里会根据采样时间分别进行处理,如下,

UA_StatusCode
UA_MonitoredItem_registerSampling(UA_Server *server, UA_MonitoredItem *mon) {
    UA_LOCK_ASSERT(&server->serviceMutex, 1);
    if(mon->sampleCallbackIsRegistered)
        return UA_STATUSCODE_GOOD;

    UA_assert(mon->next == (UA_MonitoredItem*)~0); /* Not registered in a node */

    /* Only DataChange MonitoredItems with a positive sampling interval have a
     * repeated callback. Other MonitoredItems are attached to the Node in a
     * linked list of backpointers. */
    UA_StatusCode res;
    if(mon->itemToMonitor.attributeId == UA_ATTRIBUTEID_EVENTNOTIFIER ||
       mon->parameters.samplingInterval == 0.0) {
        UA_Subscription *sub = mon->subscription;
        UA_Session *session = &server->adminSession;
        if(sub)
            session = sub->session;
        res = UA_Server_editNode(server, session, &mon->itemToMonitor.nodeId,
                                 addMonitoredItemBackpointer, mon);
    } else {
        res = addRepeatedCallback(server,
                                  (UA_ServerCallback)UA_MonitoredItem_sampleCallback,
                                  mon, mon->parameters.samplingInterval,
                                  &mon->sampleCallbackId);
    }

    if(res == UA_STATUSCODE_GOOD)
        mon->sampleCallbackIsRegistered = true;
    return res;
}

如果采样时间不为0,就会添加一个定时任务去做采样;如果是0,那么就会把这个监测项添加到node里的一个单链表里,该链表存放所有对该node创建的检测项(因为代码里可能会对同一个变量添加多个监测项),一旦有变化就会逐个进行通知,此时所使用的函数如下,

/* Trigger sampling if a MonitoredItem surveils the attribute with no sampling
 * interval */
#ifdef UA_ENABLE_SUBSCRIPTIONS
static void
triggerImmediateDataChange(UA_Server *server, UA_Session *session,
                           UA_Node *node, const UA_WriteValue *wvalue) {
    for(UA_MonitoredItem *mon = node->head.monitoredItems; mon != NULL; mon = mon->next) {
        if(mon->itemToMonitor.attributeId != wvalue->attributeId)
            continue;
        UA_DataValue value;
        UA_DataValue_init(&value);
        ReadWithNode(node, server, session, mon->timestampsToReturn,
                     &mon->itemToMonitor, &value);
        UA_Subscription *sub = mon->subscription;
        UA_StatusCode res = sampleCallbackWithValue(server, sub, mon, &value);
        if(res != UA_STATUSCODE_GOOD) {
            UA_DataValue_clear(&value);
            UA_LOG_WARNING_SUBSCRIPTION(&server->config.logger, sub,
                                        "MonitoredItem %" PRIi32 " | "
                                        "Sampling returned the statuscode %s",
                                        mon->monitoredItemId,
                                        UA_StatusCode_name(res));
        }
    }
}
#endif

该函数会遍历这个单链表,然后执行回调,注意这里是server端,其回调执行中会把内容发给client端。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

学习open62541 --- [70] 深入理解变量监测 的相关文章

  • .LDS 文件详解

    最近在研究uboot xff0c 红色部分为我加上的注解 转载地址 xff1a http blog chinaunix net space php uid 61 23373524 amp do 61 blog amp cuid 61 232
  • 13 select的优化一

    1 上个例子中 xff0c select通过for循环轮询client套接字 xff0c 轮询的范围比较大 xff0c 有优化的地方 2 优化代码 xff1a 通过数组存储client的套接字 xff0c 达到少轮询的效果 xff0c 可以
  • 二.手写迷你版Tomcat-minicat2.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0需求 xff1a 封装Request和Response对象 xff0c 返回html静态资源文件 封装Request对象 想要封
  • 三.手写迷你版Tomcat-minicat3.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0封装Request和Response对象 xff0c 返回html静态资源文件 minicat 3 0需求 xff1a 请求se
  • python爬取全国五级行政区

    以前爬过国家统计局的四级行政区 xff08 http www stats gov cn tjsj tjbz tjyqhdmhcxhfdm 2017 xff09 xff0c 但是对于五级数据效果不是很好 偶然间发现这个网站 xff1a htt
  • ElasticSearch使用elasticsearchTemplate聚合查询

    这两天正好做个需求 xff0c 需要用到聚合查询 前几篇文章只是简单的提到过 xff0c 并没有真正的运用到实际产出中 xff0c 本篇结合实际代码 xff0c 专项学习ES的聚合查询 1 业务背景 有一张地址索引表 xff1a hisAd
  • Java字节码

    Java最黑科技的玩法就是字节码编程 xff0c 也就是动态修改或是动态生成 Java 字节码 使用字节码可以玩出很多高级的玩法 xff0c 最高级的还是在 Java 程序运行时进行字节码修改和代码注入 听起来是不是一些很黑客 xff0c
  • TCP/IP (一) accept建立连接

    七层网络协议 三次握手 四次分手 xff0c 这些大家都比较熟知 xff0c 这里主要是带着一些问题来思考整个TCP IP流程 1 三次握手的具体流程是怎么样的 xff1f 2 socket编程中int listen int fd int
  • http 的认证模式

    周海汉 2006 7 11 ablozhou 64 gmail com SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Base模式和摘要模式 xff08 Digest sche
  • Java Agent

    在 Java 字节码 一文中有提到 xff0c 使用 Java Agent 操控字节码 xff0c 本文将讨论 Java Agent xff0c 这是普通 Java 开发人员的真正的黑魔法 Java Agent 能够通过执行字节码的直接修改
  • 通过gitlab远程统计git代码量

    git的代码量大多数都是根据命令行统计 xff0c 或者根据第三方插件统计 但是都不满足我的需求 xff0c 因为我们代码都由gitlab管理 xff0c 于是想到了通过gitlab暴露出来的接口获取数据 第一步 xff0c 生成私钥 登录
  • Qt第二十二章:将控件放到另一个控件的后面或前面

    话不多说 xff1a 看图
  • 缓存行填充与@sun.misc.Contended注解

    1 缓存模型 CPU和主内存之间有好几层缓存 xff0c 因为与cpu的速度相比 xff0c 访问主内存的速度是非常慢的 如果频繁对同一个数据做运算 xff0c 每次都从内存中加载 xff0c 运算完之后再写回到主内存中 xff0c 将会严

随机推荐

  • ThreadLocal那点事

    目录 1 ThreadLocal原理 2 ThreadLocal内存泄漏 3 ThreadLocal最佳实践 4 FastThreadLocal原理 5 FastThreadLocal最佳实践 6 ThreadLocal与FastThrea
  • 关于雪花算法的设计与思考

    2017年的时候项目组在开发一款大区游戏 xff0c 由于之前demo阶段的玩家id都是单服生成的 xff0c 只能保证单进程中的唯一 xff0c 而无法保证在分布式服务器端的唯一性 随着项目的开发进展 xff0c 需要设计能保证在分布式的
  • java反射之Method的invoke方法实现

    在框架中经常会会用到method invoke 方法 xff0c 用来执行某个的对象的目标方法 以前写代码用到反射时 xff0c 总是获取先获取Method xff0c 然后传入对应的Class实例对象执行方法 然而前段时间研究invoke
  • A*寻路算法之解决路径多拐点问题

    1 问题描述 最近公司正在开发的游戏涉及到了寻路算法 xff0c 然后我从网上找了一份A 算法代码 xff0c 整理了一下写了一个A 算法基础实现 然而 xff0c 在真正实用时A 寻路时 xff0c 却发现了几个问题 xff1a 基础实现
  • 代理模式与委托模式的异同点

    在 设计模式之禅 xff08 第二版 xff09 中 xff0c 作者说 代理模式也叫做委托模式 xff0c 显然是认为代理模式和委托模式是毫无差别的 然而在实际开发中 xff0c 我们通常可以很明确的知道一个模式究竟是代理模式还是委托模式
  • TCP/IP编程之select函数详解

    前述 xff1a linux下的I O复用模型目前很多都已经不用select函数了 xff0c 而是用epoll xff0c 但是为什么还需要了解select编程呢 xff0c 其实是从两个方面考虑的 xff1a 一是为了通过select去
  • 堆栈的详细解释

    一 在c中分为这几个存储区 1 栈 由编译器自动分配释放 2 堆 一般由程序员分配释放 xff0c 若程序员不释放 xff0c 程序结束时可能由OS回收 3 全局区 xff08 静态区 xff09 xff0c 全局变量和静态变量的存储是放在
  • Gmapping、hector、Cartographer三种激光SLAM算法简单对比

    文章目录 一 Gmapping是基于粒子滤波的算法 二 Hector SLAM三 Cartographer 一 Gmapping是基于粒子滤波的算法 缺点 xff1a 严重依赖里程计 xff0c 无法适应无人机及地面不平坦的区域 xff0c
  • TCP连接建立过程

    TCP连接建立过程 浏览器访问网站 xff0c 通过域名解析找到ip地址后会与服务器端建立连接 其中TCP xff08 Transmission Control Protocol xff0c 传输控制协议 xff09 是一种面向连接的 可靠
  • 海康威视错误代码文档大全【完整版】

    简介 本文收录了海康各大设备错误码 xff0c 按ctrl 43 f查询 xff1b 包含网络通讯库错误码 阵列错误码 安全激活相关错误码 智能设备错误码 RTSP通讯库错误码 软解码库错误码 转封装库错误码 语音对讲库错误码 Qos流控库
  • lighttpd http响应报文(Response)增加安全头Referrer-Policy和X-Permitted-Cross-Domain-Policies方法

    X Permitted Cross Domain Policies和Referrer Policy说明 X Permitted Cross Domain Policies X Permitted Cross Domain Policies
  • ROS使用ARUCO识别二维码获取位置信息做定位使用

    使用ARUCO识别二维码获取位置信息 1 安装软件 cd catkin ws src git clone b kinetic devel https github com pal robotics aruco ros cd catkin m
  • Keil 编译时无法找到头文件解决

    Keil 编译时无法找到头文件解决方法 1 背景 Keil 编译的时候无法找到头文件 xff0c 搜了下相关问题及解决方法 xff0c 有介绍说是因为文件夹中有数字 xff0c 无法搜到头文件 xff0c 进行了更改 xff0c 还是找不到
  • 学习open62541 --- [71] Alarm and Condition

    本文讲述Alarm and Condition的用法 xff0c 主要以源码里提供的例子为基础进行讲解和演示 xff0c 即tutorial server alarms conditions c xff0c 该例子写的有点乱 xff0c 本
  • 学习open62541 ---[68] 使用Wireshark观察通信消息

    Wireshark是强大的网络协议分析工具 xff0c 而open62541也是基于socket的 xff0c 所以也可以用其来观察OPCUA通信消息 一 安装Wireshark 去https www wireshark org 去下载并安
  • UART、IIC、SPI、CAN通信的区别与应用

    文章目录 1 通信的基本知识1 1 数据通信的种类1 1 1 串行通信1 1 2 并行通信1 1 3 总结 1 2 数据通信的传输方向1 2 1 单工1 2 2 半双工1 2 3 全双工1 2 4 总结 1 3 数据通信的方式1 3 1 同
  • 学习open62541 --- [69] Client监测多个变量值

    有读者问Client如何监测多个变量值 xff0c 这篇文章给了提示 xff0c 但是没给例子 xff0c 本文给出详细例子 xff0c 使用的open62541版本是1 3 3 xff0c 运行环境debian10 5 一 准备Serve
  • 学习CANopen --- [6] 自定义对象字典

    在前面几篇文章中 xff0c 我们运行例子时都需要一个eds文件 xff0c 比较麻烦 xff0c 本文讲述如何在代码中自定义对象字典 xff0c 只添加自己需要的OD项 如果是作为master来使用 xff0c 还是比较方便的 自定义对象
  • 学习CANopen --- [7] 使用块(Block)下载

    对于一次传输超过4字节的情形 xff0c SDO可以使用Segment传输或者Block传输 xff0c Segment传输在第6篇文章中已经介绍 xff0c 本文讲解Block传输中的下载情况 一 与Segment传输的比较 相比于Seg
  • 学习open62541 --- [70] 深入理解变量监测

    本文深入探讨一下变量监测的用法和原理 一 累积传输 先前写的关于变量监测的文章 xff0c 都是设置一个采样时间 xff0c 然后有变化了就通知一下 xff0c 有时我们希望变化累积一段时间再一起传给client xff0c 这时如何设置呢