在 .NET 中使用 OPC UA 協議

张高兴發表於2024-03-25

目錄
  • 什麼是 OPC UA
  • UaExpert 的使用
    • 下載 UaExpert
    • 首次啟動
    • 新增 OPC UA 伺服器
    • 連線 OPC UA 伺服器
    • 檢視 PLC 資料
  • 使用 C# 讀寫 OPC UA 資料
    • 連線到 OPC UA 伺服器
    • 獲取節點的值
    • 寫入節點的值

什麼是 OPC UA

OPC UA(OPC Unified Architecture,開放平臺通訊統一架構)是 OPC 基金會應用在自動化技術的機器對機器網路傳輸協定。OPC UA 不依賴於特定的作業系統或平臺,可以在 Windows、Mac、Linux 等多種系統上執行,而傳統的 OPC(如 OPC DA)通常只能在 Windows 上使用。該協議提供了一個更為先進、安全和靈活的解決方案,適用於現代工業自動化和物聯網環境中的裝置間通訊。

OPC UA 透過一個統一的資訊模型來實現裝置間的無縫資料交換,資訊模型來源於物件導向程式設計,使用了物件作為過程系統表示資料和活動的基礎。這個模型由節點組成,節點可以是物件、變數或方法,它們透過引用相互連線,構成了一個複雜的網路。每個節點都有一組屬性和引用,用於描述資料和定義節點間的關係。OPC UA 的地址空間就是這樣一個節點網路,它為客戶端提供了一種標準化的方式來訪問伺服器上的物件。OPC UA 還提供了一系列服務,使客戶端能夠執行讀取、寫入和訂閱等操作。安全性也是 OPC UA 設計的核心,內建了多種安全機制,包括認證、授權、加密和訊息簽名,以確保資料傳輸的安全性。

UaExpert 的使用

UaExpert 是一款 OPC UA 客戶端軟體,用於連線 OPC UA 伺服器並與之互動。UaExpert 支援 OPC UA 的所有特性,包括資料檢視、報警檢視、歷史趨勢檢視和診斷檢視等功能。使用者可以透過 UaExpert 訪問伺服器上的節點,如裝置和感測器,以及它們的屬性,例如溫度、壓力等資料。UaExpert 還提供了模擬、配置、歷史功能測試和匯出節點的功能,大多數功能都是免費使用的。

下載 UaExpert

訪問 Unified Automation 的官網下載 UaExpert,未註冊使用者則需要先註冊才能下載。

首次啟動

安裝完成後,首次執行 UaExpert 會提示建立一個應用程式證書,填寫一些相關資訊即可。

啟動後的介面如下。

新增 OPC UA 伺服器

依次單擊選單欄 Server - Add,或者直接單擊工具欄的 圖示,會彈出新增伺服器對話方塊。雙擊 Custom Discovery 下面的文字,輸入 OPC UA 伺服器的地址和埠號。

完成後會看到新新增的 OPC UA 伺服器資訊,選中開鎖狀 🔓 圖示,並單擊 OK 按鈕,即完成伺服器新增的操作。

連線 OPC UA 伺服器

伺服器新增完成後,在左側專案樹的 Servers 會顯示相關資訊,此時伺服器尚未連線。單擊工具欄的插頭 🔌 圖示,或者右擊伺服器點選 Connect,即可連線伺服器。

檢視 PLC 資料

成功連線後,會在介面左下側 Address Space 顯示 PLC 中的相關資料。找到想要監控(訂閱)的資料,將其直接拖放到介面中間的 Data Access View 就可以實時觀察資料的變化。介面右側 Attributes 可以顯示選中節點的相關屬性。

使用 C# 讀寫 OPC UA 資料

首先需要在專案中引用 NuGet 包 OPCFoundation.NetStandard.Opc.Ua

連線到 OPC UA 伺服器

  1. 建立一個應用配置物件,用於設定應用名稱、唯一標識、型別、證書和安全策略;
    // 建立一個應用配置物件,用於設定應用名稱、唯一標識、型別、證書和安全策略
    var config = new ApplicationConfiguration()
    {
        ApplicationName = "MyClient",
        ApplicationUri = Utils.Format(@"urn:{0}:MyClient", System.Net.Dns.GetHostName()),
        ApplicationType = ApplicationType.Client,
        SecurityConfiguration = new SecurityConfiguration
        {
            ApplicationCertificate = new CertificateIdentifier { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\MachineDefault", SubjectName = "MyClientSubjectName" },
            TrustedIssuerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Certificate Authorities" },
            TrustedPeerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Applications" },
            RejectedCertificateStore = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates" },
            AutoAcceptUntrustedCertificates = true,
            RejectSHA1SignedCertificates = false,
            MinimumCertificateKeySize = 1024,
            NonceLength = 32,
        },
        TransportConfigurations = new TransportConfigurationCollection(),
        TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
        ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
        TraceConfiguration = new TraceConfiguration()
    };
    
    // 驗證應用配置物件
    await config.Validate(ApplicationType.Client);
    
    // 設定證書驗證事件,用於自動接受不受信任的證書
    if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
    {
        config.CertificateValidator.CertificateValidation += (s, e) => { e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted); };
    }
    
  2. 檢查應用例項物件的證書;
    // 建立一個應用例項物件,用於檢查證書
    var application = new ApplicationInstance(config);
    
    // 檢查應用例項物件的證書
    bool check = await application.CheckApplicationInstanceCertificate(false, 2048);
    
  3. 建立一個會話物件,用於連線到 OPC UA 伺服器;
    // 建立一個會話物件,用於連線到 OPC UA 伺服器
    EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint("opc.tcp://192.168.0.100:4840", true);
    EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(config);
    ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
    Session session = await Session.Create(config, endpoint, false, false, "DataCollector", 60000, new UserIdentity(), null);
    

獲取節點的值

  1. 單次讀取節點的值
    DataValue value = session.ReadValue(nodeId: "ns=6;s=::MyNode");
    Console.WriteLine("{0}, {1}, {2}", value.Value, value.SourceTimestamp, value.StatusCode);
    
  2. 訂閱讀取節點的值
    // 建立一個訂閱物件,用於訂閱節點的值
    var subscription = new Subscription(session.DefaultSubscription) { PublishingInterval = 1000 };
    session.AddSubscription(subscription);
    subscription.Create();
    
    // 建立一個監視項物件,用於指定要訂閱的節點
    MonitoredItem monitoredItem = new MonitoredItem()
    {
        DisplayName = "MyNode",
        StartNodeId = "ns=6;s=::MyNode"
    };
    
    // 新增一個通知事件,用於處理節點值的變化
    monitoredItem.Notification += (item, e) =>
    {
        foreach (var value in item.DequeueValues())
        {
            Console.WriteLine("{0}: {1}, {2}, {3}", item.DisplayName, value.Value, value.SourceTimestamp, value.StatusCode);
        }
    };
    
    // 將監視項物件新增到訂閱物件中
    subscription.AddItem(monitoredItem);
    
    // 應用訂閱的變化
    subscription.ApplyChanges();
    

寫入節點的值

// 寫入資料到節點
WriteValue value = new WriteValue()
{
    NodeId = "ns=6;s=::MyNode",
    Value = new DataValue(new Variant(123))
};

ResponseHeader response = session.Write(null, new WriteValueCollection { value }, out StatusCodeCollection statuses, out DiagnosticInfoCollection diagnostics);

相關文章