学习open62541 --- [73] 数据源造成无法监测变量的问题解决

news2024/11/18 15:44:32

本人最近遇到一个问题:给一个变量添加数据源后,使用监测项去监测变量变化,如果采样时间为0,会发现无法监测到变量的变化。

本文讲述这种情况的发生原因以及解决办法。


一 Server例子

首先准备server例子,如下,

// server.c

#include <signal.h>
#include <stdlib.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;
}


int32_t gData = 0;

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


void addVariable(UA_Server * server)
{
   /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 0;
    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_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, gTargetNodeId, parentNodeId,
                              parentReferenceNodeId, myIntegerName,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
}


UA_StatusCode onReadCallback(UA_Server *server, const UA_NodeId *sessionId,
                          void *sessionContext, const UA_NodeId *nodeId,
                          void *nodeContext, UA_Boolean includeSourceTimeStamp,
                          const UA_NumericRange *range, UA_DataValue *value) 
{
	UA_Variant_setScalarCopy(&(value->value), &gData, &UA_TYPES[UA_TYPES_INT32]);
    value->hasValue = UA_TRUE;

    return UA_STATUSCODE_GOOD;

}

UA_StatusCode onWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
                           void *sessionContext, const UA_NodeId *nodeId,
                           void *nodeContext, const UA_NumericRange *range,
                           const UA_DataValue *value) 
{
    return UA_STATUSCODE_GOOD;
}

void addDataSourceToVariable(UA_Server *server) 
{
    UA_DataSource varDataSource;
    varDataSource.read = onReadCallback;
    varDataSource.write = onWriteCallback;
    UA_Server_setVariableNode_dataSource(server, gTargetNodeId, varDataSource);
}

void cycleCallback(UA_Server *server, void *data) 
{
    if ((++gData) == 10000)
    {
        gData = 0;
    }
}


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

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    addVariable(server);
    addDataSourceToVariable(server);

    UA_UInt64 callbackId = 0;
	UA_Server_addRepeatedCallback(server, cycleCallback, NULL, 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;
}


代码里添加了一个变量叫“the answer”,并添加了数据源用于读写操作,代码里有个全局变量gData,是“the answer”的源,每隔2秒会加1,当用户读取变量值时,会调用读回调onReadCallback,然后把gData的值传给用户。


二 测试

a)使用UaExpert的默认配置进行监测

编译server后运行,然后使用UaExpert连接server,
在这里插入图片描述
然后把变量“the answer”拖到Data Access View里,
在这里插入图片描述
可以看到值每隔1s就会加1,和预期一样,在server这边,会打印监测项的配置,如下,
在这里插入图片描述
可以看到采样时间是250ms,这个是UaExpert的默认配置

b)使用有变化才通知的监测设置

有时用户希望有变化才通知,不想按照一定时间间隔去采样,这样就需要对UaExpert进行配置,点击Setting,然后点击Configure UaExpert,如下,
在这里插入图片描述
在弹出的设置框里把采样时间的默认值改为0,
在这里插入图片描述
然后点击右下角的OK进行保存。

最后重启一下server,然后再使用UaExpert进行连接,连接成功后再把变量"the answer"拖拽到Data Access View窗口里,此时会发现数据不在变化了。
在这里插入图片描述
在Server这端可以看到这个监测项的设置,如下,采样时间是0
在这里插入图片描述


三 原因分析

由上可以看出,设置250ms采样,就可以监测到数据变化,而设置为有变化再通知(采样时间为0),就无法监测到数据的变化,其根本原因是:

  • 250ms采样,server内部会每隔250ms去读一下变量的值,这就会调用onReadCallback,这样就能拿到变化的值
  • 0ms采样,server内部不会去调用读接口去读取变量值,而是在对变量进行写的时候才去检查一下变量值是否发生变化,这样才能做到有变化才通知

server源码中创建监测项的的函数如下,

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


四 解决办法

知道了原因,解决办法也比较简单,就是当源变化时,要更新一下对应变量的值,对应到代码里,就是在cycleCallback()里把gData的当前值写入到变量里,如下,

void cycleCallback(UA_Server *server, void *data) 
{
    if ((++gData) == 10000)
    {
        gData = 0;
    }

    UA_Variant value;
    UA_Variant_setScalarCopy(&(value), &gData, &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_writeValue(server, gTargetNodeId, value);
}

这样就可以触发通知了,重新编译运行,就会发现可以监测到变量的变化了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/148203.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WSL 下载服务器加速

网络下载加速&#xff0c;这里使用修改 hosts 文件 &#xff0c;地址映射 方法&#xff0c;所有网址适用&#xff0c;这里以 WSL 下载服务器为例子 命令 wsl -l -o 访问的地址&#xff1a; https://raw.githubusercontent.com/microsoft/WSL/master/distributions/Distributi…

Java设计模式中组合模式是什么/树形结构怎么组合或显示存储,编程怎么实现树形结构

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 5.7 组合模式 5.7.1 概述 又名整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次属于…

基于asp.net+vbscript+wsc编写网站

1、前言 asp大家应该都比较熟悉&#xff0c;就是一个动态服务器页面&#xff0c;有点类似于jsp。只是不同的是asp可以在IIS服务器上创建&#xff0c;并且如果配置了.net环境的话&#xff0c;那么就可以在asp里面<%%>写vbscript。vbscript是一种脚本语言&#xff0c;其实就…

因果推断5--DML(个人笔记)

目录 1论文介绍 1.1论文 1.2摘要 1.3DML思路 2价格需求曲线 2.1价格需求弹性 2.2价格需求弹性计算DML代码 2.3价格需求弹性例子--数据集 2.4建模过程 2.5回归结果 1论文介绍 1.1论文 V. Chernozhukov, D. Chetverikov, M. Demirer, E. Duflo, C. Hansen, and a. W.…

(强制)类型转换方法

目录 一、C语言中的类型转换 二、C中的强制类型转换 2.1 static_cast 2.2 reinterpret_cast 2.3 const_cast 2.4 dynamic_cast 2.5 总结 一、C语言中的类型转换 在C语言中&#xff0c;若赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或…

[PyTorch笔记]深度学习计算

[PyTorch笔记]深度学习计算1 层和块1.1自定义神经网络块1.2顺序块1.3在前向传播中执行代码2.参数管理2.1 访问参数、用于调试、诊断和可视化2.2 参数初始化2.2.1内置初始化&#xff1a;2.2.2 自定义初始化2.3 在不同模型组件间共享参数3.延后初始化4.自定义层4.1 不带参数的层4…

深入理解数据结构 —— 跳表

什么是跳表 只要是平衡搜索二叉树能实现的功能&#xff0c;跳表都能实现&#xff0c;且时间复杂度都相同 例如&#xff1a; 哈希表的功能&#xff1a;插入&#xff0c;查找&#xff0c;删除有序表的功能&#xff1a;查找大于某值最小的数&#xff0c;小于某值最大的数&#…

干货 | 数据跨境传输合规体系的构建思路

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;研究背景随着经济活动数字化转型加快&#xff0c;“数据”对生产、流通、分配和消费活动产生重要影响&#xff0c;成为新的生产要素。地区之间数据流通愈发频繁&#xff0c;…

图像频域滤波(理想低通滤波)

图像变换是对图像信息进行变换&#xff0c;是能量保持但重新分配&#xff0c;利于加工处理。这里主要介绍傅里叶变换的图像频域滤波。 图像从空间域变换到频域后&#xff0c;其低频分量对应图像中灰度值变化较为缓慢的区域&#xff0c;高频分量表征图像中物体的边缘和随机噪声等…

基于yolov5的钢材表面缺陷识别(pycharm连接远程服务器,老版本yolov5运行遇到的问题)

时间&#xff1a;2023年1月 1 pycharm远程连接服务器 提示&#xff1a;需要下载pycharm专业版。 参考文献&#xff1a; [1] [2] [3] [4] 设置解释器的界面有一些不同&#xff0c;在此截图记录一下。 &#xff08;这是已经弄好了之后回头截图的&#xff0c;假设它不存在哈) …

【寒假每日一题】洛谷 P6206 [USACO06OCT] Another Cow Number Game G

题目链接&#xff1a;P6206 [USACO06OCT] Another Cow Number Game G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 奶牛们在玩一种数字游戏&#xff0c;Bessie 想让你帮她预测一下结果。游戏开始时&#xff0c;Bessie 将得到一个正整数 N。此时她的分数为 0。 奶…

C 程序设计教程(07)—— 数据类型转换

C 程序设计教程&#xff08;07&#xff09;—— 数据类型转换 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用户阅读。 目录C 程序设计教程&#xff08;07&#xff…

mysql学习总结(一)

总结一下近期学习的mysql内容&#xff1a;这里主要总结一下mysql的底层数据结构索引的本质是什么&#xff1f;索引的本质就是排好序的一种数据结构&#xff0c;通过索引我们能干什么呢&#xff1f;&#xff0c;快速的去定位到我们想要查找的数据&#xff0c;就像是你看书&#…

Ansible 介绍与实战操作演示

文章目录一、概述二、Ansible 架构三、Ansible 工作原理四、Ansible 安装与基础配置1&#xff09;开启记录日志2&#xff09;去掉第一次连接ssh ask确认五、Ansible 的七个命令1&#xff09;ansible2&#xff09;ansible-doc3&#xff09;ansible-playbook4&#xff09;ansible…

非线性系统辨识:非线性 ARX 和 Hammerstein-Wiener

1. 系统辨识 系统辨识是根据系统的输入输出时间函数来确定描述系统行为的数学模型。现代控制理论中的一个分支。通过辨识建立数学模型的目的是估计表征系统行为的重要参数&#xff0c;建立一个能模仿真实系统行为的模型&#xff0c;用当前可测量的系统的输入和输出预测系统输出…

Js逆向教程25-BOM DOM过检测

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; Js逆向教程25-BOM DOM过检测 一、JS BOM 检测 它是一种浏览器环境&#xff0c;脱离了浏览器在外部不能直接调用的就是BOM 在浏览器中…

SAP工作流对象类

目录 1. 实现IF_WORKFLOW接口 2. 创建流程属性 3. 接口方法参考 4. 定义事件 5. 工作流触发&#xff08;事件抛出&#xff09; 业务对象作为工作流的数据内核&#xff0c;也是联系业务流程和工作流的重要核心&#xff0c;体现形式一般为BOR或者业务对象类&#xff0c;用来标识不…

SpringCloud从入门到精通(六)

Hystrix-熔断器 Hystrix-概述 • Hystix 是Netflix 开源的一个延迟和容错库&#xff0c;用于隔离访问远程服务、第三方库&#xff0c;防止出现级联失败&#xff08;雪崩&#xff09;。• 雪崩&#xff1a;一个服务失败&#xff0c;导致整条链路的服务都失败的情形 Hystix 主要功…

【Neo4j构建知识图谱】:cypher操作语言加载 CSV电影人数据集链接文件

这目录 数据链接来源1、创建约束2、从 CSV 文件添加节点3、从 CSV 文件添加关系4、运行cypher查询5、清理数据库参考CSV 文件可以使用LOAD CSV密码条款。出于安全原因,无法加载本地CSV文件,这些文件必须在HTTP或HTTPS服务器(如GitHub,Google Drive和Dropbox)上公开访问。使…

Python 中将列表中的每个元素除以一个数字

Python 中将列表中的每个元素除以一个数字&#xff1a; 使用列表理解来遍历列表。在每次迭代中&#xff0c;将当前列表元素除以数字。新列表将包含除法结果。 my_list [8, 12, 20]# ✅ divide each element in list by number new_list [item / 2 for item in my_list] pri…