若該文為原創文章,轉載請注明原文出處
本文章博客地址: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