Qt+OPC开发笔记(三):OPC客户端订阅特点消息的Demo

发布于:2025-06-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/148868209

长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…

Qt开发专栏:三方库开发技术

上一篇:《Qt+OPC开发笔记(二):OPC客户端介绍与读取和写入bool类型Demo
下一篇:敬请期待…


前言

  本篇介绍opc客户端订阅消息,实现一个opc事件的订阅,当订阅的数据在服务器发生变化是,客户端能立即得到更新。


Demo

  请添加图片描述


OPC客户端

  OPC 客户端是一种利用OPC(OLE for Process Control)协议与 OPC 服务器进行通信的软件应用程序。

功能特点

  • 数据访问:提供一套简单易用的 API,使开发人员能轻松地创建、读取、更新和删除OPC服务器上的数据项,可从传感器、PLC、DCS 系统、过程分析仪等各种数据源获取实时数据。
  • 事件订阅(当前使用):支持实时数据变化订阅,当服务器端的数据发生变化时,客户端能够立即获取到更新,以便及时响应和处理数据变化。
  • 连接管理:负责建立和管理与 OPC 服务器的连接,包括连接的建立、监控连接状态以及在发生异常时进行重连或断开。
  • 数据展示与处理:允许用户创建和管理数据视图,通常以表格或图形的方式展示实时数据流,还能对采集到的数据进行分析、存储、归档等处理,为决策提供支持。

数据访问方式

  OPC 协议支持多种数据访问方式,以满足不同的应用场景需求:

  • 同步访问:客户端发送请求后会一直等待,直到服务器返回响应。这种方式适用于对实时性要求较高的场景,但如果服务器响应时间较长,可能会导致客户端程序阻塞。
  • 异步访问:客户端发送请求后不会等待服务器响应,而是继续执行后续操作。当服务器处理完请求后,会通过回调函数通知客户端。这种方式可以提高客户端程序的效率,避免阻塞。
  • 订阅访问(当前使用):客户端可以订阅特定的数据项,当这些数据项的值发生变化时,服务器会主动将更新后的数据推送给客户端。这种方式适用于需要实时监控数据变化的场景。

订阅服务器某个消息

步骤一:连接服务器

  在这里插入图片描述

步骤二:创建订阅

  在这里插入图片描述

  在这里插入图片描述

步骤三:创建监听项

  在这里插入图片描述

步骤四:处理回调函数

  这里是通过subId与监控id对应来确定是哪一个变量变化。
  在这里插入图片描述

步骤五:Qt兼容使用定时器定时调用

  在这里插入图片描述


Demo关键源码

创建订阅和监控项

bool OpcClientManager::createSubscriptionResponse()
{
    /*
       OPC UA中的订阅是异步的。也就是说,客户端向服务器发送多个PublishRequest。
       服务器返回带有通知的PublishResponses。但只有在生成通知时。客户端不会等待响应,而是继续正常操作。
       请注意订阅和受监视项目之间的区别。订阅用于报告通知。
       MonitoredItems用于生成通知。每个MonitoredItem只附加到一个订阅。订阅可以包含许多受监视的项目。
       客户端在后台自动处理PublishResponses(带回调),并在传输中保留足够的PublishRequests。
       ublishResponses可以在同步服务调用期间或在“UA_Client_run_iterate”中接收
    */

    // 步骤一:创建一个默认的订阅请求对象(有订阅再开放)
    _subscriptionRequest = UA_CreateSubscriptionRequest_default();
    _subscriptionRequest.requestedPublishingInterval = 1000; // 设置发布间隔为1000毫秒,即每秒发布一次数据
    _subscriptionRequest.requestedLifetimeCount = 300;       // 设置生命周期计数为300,即服务器在300个发布周期后会终止该订阅
    _subscriptionRequest.requestedMaxKeepAliveCount = 10;    // 设置最大保持活动计数为10,即服务器在10个发布周期内没有数据变化时,仍会发送空的通知以保持连接活跃
    _subscriptionRequest.maxNotificationsPerPublish = 0;     // 设置每个发布周期的最大通知数为0,表示不限制通知数量
    _subscriptionRequest.publishingEnabled = true;           // 启用发布功能,允许服务器主动推送数据
    _subscriptionRequest.priority = 0;                       // 设置订阅的优先级为0,数值越高优先级越高
    // 步骤二:设置订阅回复,设置状态改变通知回调和删除订阅回调
    _subscriptionResponse = UA_Client_Subscriptions_create(_pUAClient,
                                                           _subscriptionRequest,
                                                           NULL,
                                                           OpcClientManager::statusChangeNotificationCallback,
                                                           OpcClientManager::deleteSubscriptionCallback);
    if(_subscriptionResponse.responseHeader.serviceResult != UA_STATUSCODE_GOOD)
    {
        LOG << QString("Failed to UA_Client_Subscriptions_create, error code: 0x%1")
               .arg(UA_StatusCode_name(_subscriptionResponse.responseHeader.serviceResult));
        return false;
    }
    LOG << "Succeed to UA_Client_Subscriptions_create, id:" << _subscriptionResponse.subscriptionId;

    startTimer(100);

    return true;
}

bool OpcClientManager::createMonitoredItemRequest(int ns, int i)
{
    // 前置:有一个订阅实例
    // 步骤三:创建监控项请求,需要传入监控的节点
    LOG << ns << i;
    UA_NodeId nodeId = UA_NODEID_NUMERIC(ns, i);
    UA_MonitoredItemCreateRequest monitoredItemCreateRequest = UA_MonitoredItemCreateRequest_default(nodeId);
    monitoredItemCreateRequest.requestedParameters.samplingInterval = 100;  // 采样间隔(单位:毫秒),指定服务器多久读取一次被监控变量的实际值。
    monitoredItemCreateRequest.requestedParameters.discardOldest = true;    // 当监控项的队列(Queue)已满时,是否丢弃最早的数据。
    monitoredItemCreateRequest.requestedParameters.queueSize = 10;          // 服务器为该监控项保留的历史值队列大小。queueSize = 10 表示服务器最多保存10个未发送给客户端的值

    // 添加监控项到订阅
    UA_MonitoredItemCreateResult monResult = UA_Client_MonitoredItems_createDataChange(_pUAClient,
                                                                                       _subscriptionResponse.subscriptionId,
                                                                                       UA_TIMESTAMPSTORETURN_BOTH,
                                                                                       monitoredItemCreateRequest,
                                                                                       NULL,
                                                                                       OpcClientManager::dataChangeNotificationCallback,
                                                                                       NULL);
    if(monResult.statusCode != UA_STATUSCODE_GOOD)
    {
        LOG << "监控项创建失败 error:" << QString(UA_StatusCode_name(monResult.statusCode));
        return false;
    }else
    {
       LOG << "成功监控节点 MonId: " << monResult.monitoredItemId;
       return true;
    }

}

回调函数

void OpcClientManager::statusChangeNotificationCallback(UA_Client *client, UA_UInt32 subId, void *subContext, UA_StatusChangeNotification *notification)
{
    LOG << __FUNCTION__ << client << subId;
}

void OpcClientManager::deleteSubscriptionCallback(UA_Client *client, UA_UInt32 subId, void *subContext)
{
    LOG << __FUNCTION__ << client << subId;
}

void OpcClientManager::dataChangeNotificationCallback(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value)
{
    LOG << __FUNCTION__ << client << subId;
    LOG << "数据变化通知 - 监控项ID: " << monId;
   if(value->hasValue && value->value.type)
   {
       UA_Variant *var = &value->value;
       if(var->type == &UA_TYPES[UA_TYPES_BOOLEAN])
       {
            LOG << *static_cast<bool *>(var->data);
       }else{
           LOG << "other types";
       }
   }
}

定时器处理

void OpcClientManager::timerEvent(QTimerEvent *event)
{
    if(_pUAClient)
    {
        UA_Client_run_iterate(_pUAClient, 100);
    }
}

工程模板v1.2.0

  在这里插入图片描述


入坑

入坑一:订阅变量后未通知

问题

  订阅变量后未通知
  在这里插入图片描述

尝试

  检查代码没有发现任何问题,考虑是否有其他问题。
  更换第三方单文件全代码订阅后,变化 也无通知:
  在这里插入图片描述

  使用uaexpert测试,订阅看起来是成了:
  在这里插入图片描述

  修改成5秒,发现就是5秒了,所以这里订阅是成功了。
  继续考虑代码问题了,再次查看,发现可能是打印缓存的问题,Qt输出printf需要设置stdout为0:
  在这里插入图片描述

  那么这个代码是没问题的。
  回到封装的代码,对比检查,发下关键性代码:
  在这里插入图片描述

  在这里插入图片描述

  所以open ua这个代码,收到订阅通知需要跑这个循环才可以收到。
在OPC UA通信中,客户端需要持续运行并处理服务器推送的通知,而UA_Client_run_iterate函数正是用于实现这一点的关键机制。
  然后查看了其他一边监听一边写入的代码,跟想象中一样,间隔写入(PS:就是单片机的单路径一样)
  在这里插入图片描述

解决

  本意是用Qt的消息循环替代:
  在这里插入图片描述
  这个靠Qt循环的不是那么准确,还需要完善这个流程,有可能处理会有2次一同处理。


上一篇:《Qt+OPC开发笔记(二):OPC客户端介绍与读取和写入bool类型Demo
下一篇:敬请期待…


本文章博客地址:https://hpzwl.blog.csdn.net/article/details/148868209


网站公告

今日签到

点亮在社区的每一天
去签到