OPC UA'ya giriş ── PLC ve cihaz verilerini bir Windows uygulamasıyla toplamak

· · Windows, OPC UA, Üretim, PLC, C#, .NET, İletişim, Sertifikalar

X'te paylaş
Değişiklik geçmişi (yalnızca ilk sürüm, 01.11.2026 tarihinde yayımlandı)
İlk yayın
Bu makaleye atıf yapın(DOI: 10.5281/zenodo.23968450)

Bu makale Zenodo'da arşivlenmiştir. Aşağıda, her zaman en son sürüme yönlenen DOI ile şu anda görüntülediğiniz sürüme sabitlenmiş DOI birlikte verilmiştir.

Go Komura (2026). OPC UA'ya giriş ── PLC ve cihaz verilerini bir Windows uygulamasıyla toplamak. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968450 https://comcomponent.com/tr/blog/opc-ua-windows-data-collection/

DOI (en son sürüm)
10.5281/zenodo.23968450
DOI (bu sürüm)
10.5281/zenodo.23968451

PLC’lerin veya cihazların verilerini bir Windows uygulamasına toplarken cihazın bir OPC UA sunucusu varsa, her üretici için ayrı bir iletişim yazmanız gerekmeyebilir. Ancak bağlanıp değer okumak yeterli değildir. Değerle birlikte dönen durumu ve zaman damgalarını, değişiklikleri bildiren aboneliği, sertifikayla bağlantıya izin verilmesini ve bağlantı koptuktan sonra nasıl toparlanılacağını bilmezseniz, topladığınız verilere sonradan güvenemezsiniz.

Bu makale, OPC UA’nın işleyişinin veri toplamak için gereken kısımlarını bir C# istemcisinin gerçek çalıştırma sonuçlarıyla birlikte açıklıyor. İstemci OPC Foundation’ın .NET kitaplığını kullanıyor, karşı tarafta ise benzetilmiş bir cihaz sunucusu var. Belirli bir PLC veya cihazla denemedim. Cihaza özgü ayarları o cihazın belgelerinden kontrol edin.

GigE Vision ve USB3 Vision kameralarından görüntü toplama konusunu “Dropped Frames with GigE Vision and USB3 Vision Cameras” makalesinde, seri iletişim kullanan cihazları ise “Serial Communication App Pitfalls” makalesinde ele aldım.

OPC UA ile OPC Classic arasındaki fark

OPC Classic belirtimleri, yazılım bileşenleri arasında veri alışverişi için Windows’un COM/DCOM teknolojisini kullanır. Süreç verilerini ele alan OPC DA, alarmları ele alan OPC A&E ve geçmiş verileri ele alan OPC HDA ayrı belirtimler olarak tanımlanmıştır1.

OPC Foundation, OPC UA’yı (Unified Architecture) 2008’de yayımladı. OPC UA, belirli bir platforma bağlı olmayan ve Classic’in her bir belirtiminin işlevlerini bir araya getiren, hizmet odaklı bir mimaridir. OPC Foundation yeniden tasarlamanın nedenleri olarak, Microsoft’un COM/DCOM yerine platformlar arası çözümlere daha fazla önem vermeye başlamasını, Microsoft dışındaki sistemlerde ve gömülü cihazlarda da çalışma gereksinimini ve güvenlik duvarlarının ötesine güvenli biçimde bağlanma gereksinimini gösteriyor1.

OPC UA belirtimi IEC tarafından da IEC 62541 serisi olarak standartlaştırılmıştır2. İletişim bağlantı noktası olarak IANA’da 4840/TCP, opcua-tcp adıyla kayıtlıdır3.

OPC Classic ile OPC UA arasındaki farkOPC Classic'in Windows'un COM ve DCOM teknolojisine dayandığını ve ayrı DA, A&E ve HDA belirtimlerine bölündüğünü, OPC UA'nın ise bunların işlevlerini platformdan bağımsız tek bir belirtimde topladığını gösteren diyagram.İşlevler bir araya getirilirOPC ClassicCOM/DCOM (Windows) üzerinde çalışırDA, A&E ve HDA olarak ayrılmış belirtimlerOPC UA (2008)Belirli bir platforma bağlı değildirIEC 62541 olarak standartlaştırıldı

Şekil 1: OPC Classic, COM/DCOM üzerindeki ayrı belirtimlerden oluşur; OPC UA ise bunları tek bir belirtimde toplar.

Mevcut cihazda yalnızca bir OPC DA sunucusu varsa, istemci tarafında COM/DCOM kullanan bir yapıya ihtiyaç vardır. Bu makale yalnızca OPC UA’yı ele alıyor.

Diyagramda düz çizgi her zaman geçerli olan bir ilişkiyi, kesikli çizgi ise koşullu bir ilişkiyi gösterir (koşullar, ayrıntı sayfasında her ilişki için verilmiştir). İlişkilerin tam listesi (toplam 13, kanıt ve kesinlik derecesiyle birlikte) ve ana kavramların tanımları bilgi haritası ayrıntı sayfasında (Japonca) toplanmıştır. Veri: JSON-LD / Turtle

Denediğim yapılandırma

Çalıştırdığım yapılandırma şöyledir.

Rol Kullandığım
İstemci C#, .NET 10 SDK 10.0.114, OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 NuGet paketi
Sunucu Python 3.13 ve asyncua 2.1.0 ile yapılmış benzetilmiş cihaz (aynı bilgisayarda 4840 numaralı bağlantı noktası)
İşletim sistemi Linux (Windows’ta çalıştırma denenmedi)

İstemci kitaplığı, OPC Foundation’ın yayımladığı .NET uygulamasıdır. 2.0 serisinin kullanımında aynı deponun Getting started belgesindeki yazım biçimini izledim4.

Denediğim yapılandırmaOPC Foundation'ın .NET kitaplığını kullanan bir C# istemcisinin, 4840 numaralı bağlantı noktasını dinleyen ve asyncua ile yapılmış benzetilmiş bir cihaz sunucusuna şifreli bir bağlantıyla bağlandığı yapılandırmayı gösteren diyagram.opc.tcp, SignAndEncryptC# istemcisi (LineCollector)Benzetilmiş cihaz sunucusu (asyncua)OPC Foundation .NET kitaplığı 2.0.1Line1'in dört değişkeni

Şekil 2: Aynı bilgisayarın içinde C# istemcisi benzetilmiş cihaz sunucusuna bağlandı.

Benzetilmiş cihaz sunucusu

Cihaz yerine asyncua ile küçük bir sunucu yaptım5. Line1 adlı bir nesnenin altında dört değişken bulunuyor: sıcaklık (Temperature), üretim adedi (PartCount), çalışma durumu (Running) ve ayar değeri (Setpoint). Sıcaklık 0,5 saniyede bir, üretim adedi 2 saniyede bir değişiyor. Güvenlik için iki seçenek hazırladım: şifrelemesiz (None) ile imzalama ve şifreleme (Basic256Sha256 ile SignAndEncrypt).

# Çalışmayı doğrulamak için benzetilmiş cihaz. Gerçek üretim cihazlarının veya PLC'lerin yerini tutmaz.
import asyncio, math, random, socket
from pathlib import Path
from cryptography.x509.oid import ExtendedKeyUsageOID
from asyncua import Server, ua
from asyncua.crypto.cert_gen import setup_self_signed_certificate

HERE = Path(__file__).parent
APP_URI = "urn:komurasoft:demo-plc"


async def main():
    key, cert = HERE / "server_key.pem", HERE / "server_cert.der"
    await setup_self_signed_certificate(
        key, cert, APP_URI, socket.gethostname(),
        [ExtendedKeyUsageOID.SERVER_AUTH], {"commonName": "DemoPlcServer"})

    server = Server()
    await server.init()
    server.set_endpoint("opc.tcp://0.0.0.0:4840/demo/")
    server.set_server_name("DemoPlcServer")
    await server.set_application_uri(APP_URI)
    server.set_security_policy([
        ua.SecurityPolicyType.NoSecurity,
        ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt,
    ])
    await server.load_certificate(str(cert))
    await server.load_private_key(str(key))

    ns = await server.register_namespace("urn:komurasoft:demo-plc:line1")
    line1 = await server.nodes.objects.add_object(ua.NodeId("Line1", ns), "Line1")
    temperature = await line1.add_variable(ua.NodeId("Line1.Temperature", ns), "Temperature", 25.0)
    part_count = await line1.add_variable(
        ua.NodeId("Line1.PartCount", ns), "PartCount", ua.Variant(0, ua.VariantType.UInt32))
    await line1.add_variable(ua.NodeId("Line1.Running", ns), "Running", True)
    setpoint = await line1.add_variable(ua.NodeId("Line1.Setpoint", ns), "Setpoint", 30.0)
    await setpoint.set_writable()

    async with server:
        print("listening", flush=True)
        t, count = 0, 0
        while True:
            await asyncio.sleep(0.5)
            t += 1
            value = 25.0 + 2.0 * math.sin(t / 4) + random.uniform(-0.05, 0.05)
            await temperature.write_value(round(value, 2))
            if t % 4 == 0:
                count += 1
                await part_count.write_value(ua.Variant(count, ua.VariantType.UInt32))


asyncio.run(main())

Bu sunucu, bağlanan istemcilerin sertifikalarını denetlemeden kabul ediyor. Bu bir test ayarıdır ve gerçek üretim cihazlarının davranışından farklıdır.

C# istemcisi

Proje dosyasına şu iki paketi ekledim.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="2.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
  </ItemGroup>

</Project>

İstemci sırasıyla bağlanır, göz atar, okur, yazar ve abone olur. Her bölümün ne anlama geldiğini sonraki bölümlerde sırayla açıklıyorum.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Client.Subscriptions;
using Opc.Ua.Client.Subscriptions.Streaming;
using MonitoredItemOptions = Opc.Ua.Client.Subscriptions.MonitoredItems.MonitoredItemOptions;

string url = args.Length > 0 ? args[0] : "opc.tcp://localhost:4840/demo/";
int notifications = args.Length > 1 ? int.Parse(args[1]) : 6;
const string LineNamespace = "urn:komurasoft:demo-plc:line1";

HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

builder.Services
    .AddOpcUa()
    .AddClient(options =>
    {
        options.ApplicationName = "LineCollector";
        options.ApplicationUri = "urn:localhost:LineCollector";
        options.ProductUri = "urn:komurasoft:LineCollector";
        options.PkiRoot = Path.Combine(AppContext.BaseDirectory, "pki");
    })
    .AddDiscoveryAndConnect(options =>
    {
        options.DiscoveryUrl = url;
        options.SecurityMode = MessageSecurityMode.SignAndEncrypt;
        options.SecurityPolicyUri = SecurityPolicies.Basic256Sha256;
    });

using IHost host = builder.Build();
await host.StartAsync();
using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2));
CancellationToken ct = timeout.Token;

try
{
    var connect = host.Services
        .GetRequiredService<Func<CancellationToken, Task<ManagedSession>>>();
    ManagedSession session = await connect(ct);
    await using (session)
    {
        Console.WriteLine($"接続: {session.ConfiguredEndpoint?.EndpointUrl}");

        // Ad alanı dizinini sunucu belirler. URI'den bulup NodeId'leri ondan oluşturuyoruz
        ushort ns = (ushort)session.NamespaceUris.GetIndex(LineNamespace);
        var line1 = new NodeId("Line1", ns);
        var temperature = new NodeId("Line1.Temperature", ns);
        var partCount = new NodeId("Line1.PartCount", ns);
        var setpoint = new NodeId("Line1.Setpoint", ns);

        // Göz atma: Line1'in altındaki değişkenleri listeler
        var browser = new Browser(session)
        {
            BrowseDirection = BrowseDirection.Forward,
            ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
            IncludeSubtypes = true,
            NodeClassMask = (uint)NodeClass.Variable,
        };
        Console.WriteLine("Line1 の変数:");
        foreach (ReferenceDescription child in await browser.BrowseAsync(line1, ct))
        {
            Console.WriteLine($"  {child.DisplayName}  {child.NodeId}");
        }

        // Okuma: yalnızca değere değil, duruma ve iki zaman damgasına da bakar
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Var olmayan bir düğüm okunduğunda değer yerine Bad durumu döner
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Yazma: Setpoint'i 32,5 yapar
        var write = new WriteValue
        {
            NodeId = setpoint,
            AttributeId = Attributes.Value,
            Value = new DataValue(new Variant(32.5)),
        };
        WriteResponse wr = await session.WriteAsync(null, new[] { write }, ct);
        Console.WriteLine($"Setpoint の書き込み: {wr.Results[0]}");

        // Abonelik: sunucudan 500 ms'de bir örnekleme yapmasını ve değişiklikleri bildirmesini ister
        Console.WriteLine($"購読({notifications}件まで):");
        await foreach (DataValueChange change in session.DefaultStreaming
            .SubscribeDataChangesAsync(new[] { temperature, partCount }, options: new MonitoredItemOptions
            {
                SamplingInterval = TimeSpan.FromMilliseconds(500),
                QueueSize = 5,
            }, ct: ct)
            .TakeAsync(notifications)
            .WithCancellation(ct))
        {
            DataValue v = change.Value;
            string name = change.MonitoredItem?.Name.Split("s=")[^1] ?? "?";
            Console.WriteLine($"  {Hms(v.SourceTimestamp)}  {name,-17} {v.WrappedValue,6}  {v.StatusCode}");
        }
    }
}
catch (ServiceResultException ex)
{
    Console.Error.WriteLine($"OPC UA エラー {ex.StatusCode}: {ex.Message}");
    Environment.ExitCode = 1;
}
finally
{
    await host.StopAsync();
}

static string Hms(DateTimeUtc time) => time.ToDateTime().ToString("HH:mm:ss.fff");

İlk çalıştırmada bağlanamadım. Nedenini “Bağlantıya sertifikayla izin vermek” bölümünde açıklıyorum. Sunucunun sertifikasına güvendikten sonraki çıktı şöyledir. Kitaplığın uyarı günlükleri çıkarılmıştır.

接続: opc.tcp://localhost:4840/demo/
Line1 の変数:
  Temperature  ns=2;s=Line1.Temperature
  PartCount  ns=2;s=Line1.PartCount
  Running  ns=2;s=Line1.Running
  Setpoint  ns=2;s=Line1.Setpoint
Temperature = 23.09  状態 Good [0x00000000]
  Source 13:40:51.302 / Server 13:40:51.302
Line1.Pressure の読み取り: BadNodeIdUnknown [0x80340000]
Setpoint の書き込み: Good [0x00000000]
購読(6件まで):
  13:40:51.302  Line1.Temperature  23.09  Good [0x00000000]
  13:40:50.801  Line1.PartCount       67  Good [0x00000000]
  13:40:51.802  Line1.Temperature  23.05  Good [0x00000000]
  13:40:52.304  Line1.Temperature  23.07  Good [0x00000000]
  13:40:52.806  Line1.Temperature   23.2  Good [0x00000000]
  13:40:52.806  Line1.PartCount       68  Good [0x00000000]

Saatler UTC’dir.

Cihaz verileri adres alanında düğümler olarak görünür

OPC UA sunucusu, elindeki verileri düğümlerden (node) oluşan bir küme olarak yayımlar. Bu kümeye adres alanı (address space) denir. Düğümlerin klasörleri veya cihazları temsil eden nesneler, değer tutan değişkenler gibi türleri vardır ve düğümler birbirine başvurularla bağlanır. İstemci, Browse hizmetiyle başvuruları izleyerek hangi düğümlerin bulunduğunu araştırır6.

Yukarıdaki çıktıda istemci Line1‘den hiyerarşik başvuruları izleyerek dört değişkeni buluyor.

Benzetilmiş cihazın adres alanıObjects klasörünün altında bir Line1 nesnesi ve onun altında dört değişkenin, Temperature, PartCount, Running ve Setpoint değişkenlerinin bulunduğunu gösteren diyagram.ObjectsLine1 (nesne)Temperature (değişken)PartCount (değişken)Running (değişken)Setpoint (değişken, yazılabilir)

Şekil 3: Benzetilmiş cihazın adres alanı. İstemci, Browse ile başvuruları izleyerek düğümleri bulur.

NodeId, ad alanı ve tanımlayıcıdan oluşur

Düğümler NodeId ile tanımlanır. Çıktıdaki ns=2;s=Line1.Temperature, ad alanı dizininin 2, tanımlayıcının ise Line1.Temperature dizesi olduğunu gösterir.

Ad alanı aslında bir URI’dir. Belirtim, ad alanı URI’sinin hizmetlerin içinde bir sayıyla gösterildiğini ve bu sayının sunucunun NamespaceArray dizisindeki konuma karşılık geldiğini belirtir. 0 dizini OPC UA’nın ad alanı olan http://opcfoundation.org/UA/‘dir7.

Bu nedenle dizin numarasını koda yazmak yerine, dizini URI’den bulursunuz. Yukarıdaki kod, session.NamespaceUris.GetIndex(LineNamespace) ile URI’den 2’yi bulup NodeId’leri ondan oluşturuyor.

Ad alanı URI'sinden NodeId oluşturma akışıİstemcinin ad alanı URI'sini sunucunun NamespaceArray dizisinde arayarak dizini bulduğunu ve bu dizini tanımlayıcıyla birleştirerek NodeId oluşturduğunu gösteren diyagram.Ad alanı URI'siSunucunun NamespaceArray dizisiDizin (bu örnekte 2)NodeId ns=2;s=Line1.TemperatureTanımlayıcı Line1.Temperature

Şekil 4: Ad alanı dizini URI’den bulunur ve tanımlayıcıyla birleştirilerek NodeId elde edilir.

Okuma ve yazmada değerle birlikte durum ve zaman damgaları döner

Değişkenlerin değerleri Read ve Write hizmetleriyle okunup yazılır8. Okumada yalnızca değer dönmez. DataValue adlı bir yapı içinde değer, durum kodu, kaynak zaman damgası ve sunucu zaman damgası döner9.

Öğe Anlamı
Değer Değişkenin değeri. Durum kodu bir hata gösterdiğinde değer yok sayılır ve sunucu onu null yapar
Durum kodu (StatusCode) Sunucunun değeri alıp sağlayabilip sağlayamadığı
SourceTimestamp Verinin kaynağının değere eklediği zaman
ServerTimestamp Sunucunun değeri aldığı veya değerin doğru olduğunu öğrendiği zaman

Durum kodunun en üstteki iki biti Good (başarılı), Uncertain (kısmen başarılı; bazı amaçlar için kullanılamayabilir) veya Bad (başarısız) durumunu gösterir10. Çıktıdaki Good [0x00000000], Good durumudur.

Var olmayan Line1.Pressure okunduğunda değer yerine BadNodeIdUnknown [0x80340000] döndü. Bu kitaplığın ReadValueAsync yöntemi, durum Bad olduğunda bir özel durum oluşturur4. Değerleri kaydeden bir sistem, her değerin yanında durum kodunu da saklarsa Bad durumundayken kaydedilen değerleri sonradan ayırt edebilirsiniz.

DataValue'nun içeriğiOkumada veya bildirimde dönen DataValue'nun değer, durum kodu, kaynak zaman damgası ve sunucu zaman damgası olmak üzere dört öğe içerdiğini ve durum kodu Bad ise değerin yok sayıldığını gösteren diyagram.Bad iseDataValueDeğerDurum koduSourceTimestamp (kaynaktaki zaman)ServerTimestamp (sunucudaki zaman)Değer yok sayılır

Şekil 5: Okuma sonucunda yalnızca değer değil, durum kodu ve iki zaman damgası da bulunur.

Yazmada Setpoint değişkenine 32,5 yazdım ve sonuç durum kodu olarak Good döndü. Hangi değişkenlerin yazılabilir olduğuna sunucu karar verir. Bu benzetilmiş sunucuda yalnızca Setpoint yazılabilir. Cihazın ayar değerlerinin dışarıdan değiştirilebilmesine izin verilip verilmeyeceği cihazın güvenlik tasarımıyla ilgili bir konu olduğundan bu makalenin kapsamında ele alınmıyor.

Değişikliklerin bildirilmesi: abonelik ve MonitoredItem

Değerlerdeki değişiklikleri izlemek için istemcinin belirli aralıklarla Read’i tekrarlaması da bir yöntemdir. OPC UA’da bunun dışında, değerlerin izlenmesini sunucuya bırakıp değişikliklerin bildirilmesini sağlayan bir mekanizma da vardır. Bu mekanizma abonelik (subscription) ve MonitoredItem’dır.

MonitoredItem izlenen tek bir öğedir ve bir örnekleme aralığına sahiptir. Örnekleme aralığı, sunucunun kaynaktan değer örneklediği en hızlı aralıktır ve belirtimde “best effort” bir döngü olarak tanımlanır11. Örnek, filtrenin koşullarını karşıladığında bir bildirim oluşturulur. Filtre belirtilmezse değer veya durum değiştiğinde bildirim gönderilir12.

Bildirimler önce MonitoredItem’ın kuyruğuna girer. Kuyruk taştığında en eskinin mi en yeninin mi atılacağını önceden belirlersiniz; kuyruk boyutu 2 veya daha büyükse, atıldığını gösteren Overflow biti durum kodunda ayarlanır13.

Abonelik birden fazla MonitoredItem’ı bir araya getirir ve her yayımlama aralığında henüz gönderilmemiş bildirimleri istemciye gönderir. Gönderilecek bildirimin olmadığı döngüler art arda gelirse, aboneliğin hâlâ canlı olduğunu bildiren bir keep-alive gönderir14.

Abonelik ve MonitoredItem'ın işleyişiMonitoredItem'ın kaynağın değerini örnekleme aralığıyla örneklediğini ve filtreden geçen bildirimleri kuyruğa koyduğunu, aboneliğin bunları yayımlama aralığıyla toplu olarak istemciye gönderdiğini ve bildirim yoksa keep-alive gönderdiğini gösteren diyagram.Örnekleme aralığıYayımlama aralığıBildirim yoksaKaynak (cihazın değeri)MonitoredItemFiltre (varsayılan: değer veya durum değişikliği)Kuyruk (taşarsa Overflow)AbonelikİstemciKeep-alive

Şekil 6: MonitoredItem örnekler ve kuyruğa alır; abonelik de bildirimleri toplu olarak istemciye gönderir.

Yukarıdaki kod, Temperature ve PartCount değişkenlerine 500 milisaniyelik örnekleme aralığı ve 5’lik kuyruk boyutuyla abone oluyor15. Çıktıda sıcaklık yaklaşık 0,5 saniyede bir, üretim adedi ise yaklaşık 2 saniyede bir geliyor. İlk iki bildirim, abonelik başladığında her değişkenin sahip olduğu değerlerdi. Üretim adedindeki 13:40:50.801, abonelik başlamadan önce değerin değiştiği zamandır. Bildirimlerde kaynak zaman damgası gösterildiğinden, bildirimin alındığı zaman değil, değerin değiştiği zaman görünür.

Tekrarlanan okuma ile aboneliğin seçimi

Okumayı tekrarlama yönteminde aralığı istemci tarafında belirleyebilirsiniz ve mekanizma basittir. Buna karşılık değer değişmemiş olsa bile her seferinde bir istek ve yanıt gidip gelir ve aralıktan kısa süren değişiklikler kaçırılır. Abonelikte sunucu örnekleme yapar ve yalnızca değişiklikleri gönderir; bu nedenle seyrek değişen değerlerde bile trafik pek artmaz. Ancak abonelikte de örnekleme aralığından kısa süren değişiklikler bildirilmeyebilir. Belirtim, istenenden daha hızlı örneklemeye izin verir ama bunu zorunlu tutmaz11.

Tekrarlanan okuma ile abonelik arasındaki farkTekrarlanan okumada istemcinin aralığı belirleyip her seferinde istek gönderdiğini, abonelikte sunucunun örnekleme aralığıyla örnekleme yapıp yalnızca değişiklikleri gönderdiğini ve her ikisinin de aralıktan kısa süren değişiklikleri kaçırabileceğini gösteren diyagram.Değer değişikliklerini izlemekOkumayı tekrarlamakAbone olmakİstemci aralığı belirler ve her seferinde isterSunucu örnekler ve yalnızca değişiklikleri gönderirAralıktan kısa değişiklikler kaçırılabilir

Şekil 7: Her iki yöntemde de aralıktan kısa süren değişiklikler kaçırılabilir.

Bağlantıya sertifikayla izin vermek

OPC UA bağlantısında iletilerin korunma düzeyi MessageSecurityMode ile seçilir. Değerleri, koruma sağlamayan None, yalnızca imzalayan Sign ve imzalayıp şifreleyen SignAndEncrypt‘tir16. İmzalama ve şifrelemede kullanılan algoritmaların birleşimi güvenlik ilkesiyle (security policy) belirlenir; bu makalede Basic256Sha256 kullanıldı.

İmzalama ve şifreleme için her uygulamanın kendi sertifikası kullanılır. İstemci ve sunucu birbirlerinin sertifikalarını doğrular ve güvenmedikleri taraflarla bağlantıyı reddeder. OPC Foundation’ın .NET kitaplığıyla yapılan sunucular, varsayılan olarak şifrelemesiz None ilkesini sunmaz4.

İlk bağlantı BadCertificateUntrusted ile durdu

İlk çalıştırmada istemci sunucunun sertifikasına güvenmediği için bağlantı reddedildi. Çıktının bir bölümü:

fail: Opc.Ua.CertificateValidationCore[91]
      Certificate [Subject=CN=DemoPlcServer, CN=urn:komurasoft:demo-plc, Thumbprint=4E236DDA629FE2AB32D185BF681609FD6C525363, NotBefore=2026-11-01, NotAfter=2027-11-01, KeyAlgorithm=1.2.840.113549.1.1.1] validation failed with suppressible errors but was rejected. Reason=Id: BadCertificateUntrusted
...
OPC UA エラー BadCertificateUntrusted [0x801A0000]: [801A0000] (BadCertificateUntrusted) 'Could not verify security on OpenSecureChannel response.'

Bu kitaplık reddettiği sertifikaları rejected deposuna kaydeder. Yönetici bir sertifikayı oradan trusted deposuna taşıdığında kitaplık o tarafa güvenmeye başlar17. Bu kez pki/rejected/certs klasöründeki sunucu sertifikasını pki/trusted/certs klasörüne taşıdım, programı yeniden çalıştırdım ve bağlanabildim. Taşımadan önce dosya adında da bulunan parmak izini sunucunun yöneticisiyle doğrulayın4.

Bir sertifikaya güvenene kadarki akışİlk bağlantıda istemcinin sunucunun sertifikasını reddedip rejected deposuna kaydettiğini, parmak izi doğrulanıp sertifika trusted deposuna taşındıktan sonra bir sonraki bağlantıda güvenilerek bağlanılabildiğini gösteren diyagram.İlk bağlantıBadCertificateUntrustedSertifika rejected deposuna kaydedilirParmak izi sunucunun yöneticisiyle doğrulanırtrusted deposuna taşınırBir sonraki bağlantıda güvenilir ve bağlanılır

Şekil 8: Reddedilen sertifika doğrulanıp trusted deposuna taşındıktan sonra istemci o tarafa güvenir.

Güvenilmeyen sertifikaları otomatik olarak kabul eden AutoAcceptUntrustedCertificates adlı bir ayar da vardır. Varsayılan değeri false’tur ve kitaplığın belgeleri bu ayarın geliştirme ortamlarında yararlı olduğunu ancak üretimde kullanılmaması gerektiğini belirtir17.

Günlükteki NotAfter=2027-11-01, benzetilmiş sunucunun sertifikasının son kullanma tarihidir. Bu, asyncua’nın bir işleviyle oluşturulmuş kendinden imzalı bir sertifikadır ve oluşturulduğu günden bir yıl sonra sona eriyordu. Cihazların sertifikalarının da son kullanma tarihi olduğundan, süreleri dolmadan önce bunları değiştirip karşı tarafın yenilerine güvenmesini sağlama yöntemini işletim süreçlerinin bir parçası olarak önceden belirleyin. Windows sertifika deposunun yönetimini “The Windows Certificate Store in Practice” makalesinde ele aldım.

Kullanıcı kimlik doğrulaması

Sertifikalar uygulamaların birbirini doğrulamasını sağlar. Bundan ayrı olarak oturum açılırken kullanıcıyı tanıtan bir belirteç iletilir. Belirtim dört tür tanımlar: anonim, kullanıcı adı ve parola, X.509 sertifikası ve dışarıda verilmiş belirteç18. Bu makaledeki istemci anonim olarak bağlandı. Sunucunun hangi belirteçleri kabul ettiği, sunucunun uç nokta bilgilerinde yer alır19.

Sahada veri toplamada takılınan noktalar

Bundan sonra, cihaz verilerini toplayan sistemlerde sık karşılaşılan sorunları bu deneye ve belirtime dayanarak sıralıyorum.

Sunucunun durduğu süredeki değerler gelmez

Abonelik sırasında sunucuyu durdurdum ve yaklaşık 15 saniye sonra yeniden başlattım. Bu kitaplığın ManagedSession sınıfı, bağlantı koptuğunda otomatik olarak yeniden bağlanır20. Sunucu dururken istemci yeniden bağlanmayı tekrar tekrar denedi.

warn: Opc.Ua.ChannelManager[6]
      Reconnect failed. Endpoint=opc.tcp://localhost:4840/demo/, Attempt=3, Outcome=transient-failure, Status=BadNotConnected, ErrorMessage=Could not connect to the remote endpoint.

Sunucu geri geldiğinde bildirimler de geri geldi. Abonelik çıktısının bir bölümü:

  13:41:13.364  Line1.Temperature  25.61  Good [0x00000000]
  13:41:13.865  Line1.Temperature  25.12  Good [0x00000000]
  13:41:34.285  Line1.Temperature  26.55  Good [0x00000000]
  13:41:33.784  Line1.PartCount        2  Good [0x00000000]
  13:41:34.786  Line1.Temperature  26.16  Good [0x00000000]

13:41:13 ile 13:41:34 arasındaki değerlerden tek biri bile gelmedi. Üretim adedi 78’den 2’ye geri döndü. Bunun nedeni, benzetilmiş sunucu yeniden başlatıldığında sayımın 0’dan yeniden başlamasıdır.

Bu örnekte sunucunun kendisi durduğu için o süredeki değerler hiçbir yerde kalmadı. Yeniden bağlanma mekanizması bağlantıyı geri getirir, ancak sunucunun durduğu süredeki değerleri sonradan doldurmaz.

Yalnızca iletişim koptuğunda ve sunucu çalışmaya devam ettiğinde durum farklıdır. Belirtimde, ulaşmayan bildirim iletilerini sunucunun yeniden gönderim kuyruğundan tekrar göndermesini sağlayan Republish hizmeti vardır21. Bildirimler MonitoredItem’ın kuyruğunda da birikir, ancak kuyruk boyutunu aşan kısım atılır13. Bu deneyde bu durumu denemedim. Her iki durumda da değerlerin gelmediği zaman aralığı anlaşılabilsin diye bağlantının kopma ve yeniden kurulma zamanlarını kayda geçirin. Seri iletişim kullanan cihazlarda yeniden bağlanma ve günlük tasarımı “Serial Communication App Pitfalls” makalesinde de ele alınıyor.

Sunucu durduğunda kayıtAbonelik sırasında sunucu durduğunda istemcinin yeniden bağlanmayı tekrar tekrar denediğini, sunucu geri gelince bildirimlerin de geri geldiğini, ancak durduğu süredeki değerlerin gelmediğini ve bu yüzden kopma ve yeniden bağlanma zamanlarının kaydedilmesi gerektiğini gösteren diyagram.Abonelik sürüyorSunucu dururİstemci yeniden bağlanmayı tekrar tekrar denerSunucu geri gelir, bildirimler de geri gelirDurduğu süredeki değerler gelmezKopma ve yeniden bağlanma zamanları kaydedilir

Şekil 9: Yeniden bağlanma bağlantıyı geri getirir, ancak sunucunun durduğu süredeki değerler gelmez.

Hangi zaman damgası kaydedilmeli?

Belirtime göre SourceTimestamp, verinin kaynağının değere eklediği zamandır ve değerin değiştiği zamanı göstermelidir. ServerTimestamp ise sunucunun değeri aldığı veya değerin doğru olduğunu öğrendiği zamandır9. Bu deneydeki benzetilmiş sunucu değerleri kendisi ürettiği için iki zaman aynıydı.

Cihazda bir değerin ne zaman değiştiğini bilmek istiyorsanız SourceTimestamp’i kullanın. Ancak bu zaman kaynağın saatiyle verildiğinden, cihazın veya sunucunun saati kaymışsa kayıt da kayar. Toplanan verileri başka kayıtlarla karşılaştıracaksanız, alıcı bilgisayarın saatini de yanına kaydederseniz kaymayı sonradan kontrol edebilirsiniz. Windows tarafındaki saat eşitlemesini “Windows Time Synchronization (w32time) and Business Systems” makalesinde ele aldım.

Durumu Good olmayan değerleri sıradan sayılar gibi ele almayın

Durum kodu Bad olduğunda değer yok sayılır9. Uncertain olduğunda ise değer bazı amaçlar için kullanılamayabilir10. Değerleri grafiklere veya toplamlara katmadan önce durum koduna göre ayırın ve Good olmayan değerlerin nasıl ele alınacağını belirleyin. Kuyruk taştığında ayarlanan Overflow biti de durum kodunun içinde yer alır13. Cihazın durumunun ekranda nasıl gösterileceğini “Best Practices for Checking and Displaying External Device State” makalesinde ele alıyorum.

Güvenlik duvarında bağlantı noktasını açın

IANA’da kayıtlı OPC UA bağlantı noktası 4840/TCP’dir3. Ancak her sunucu bu numarayı kullanmaz. OPC Foundation’ın .NET örnekleri, 62541 gibi bağlantı noktalarını kullanıyor4. Cihazın uç nokta URL’sindeki bağlantı noktası numarasını kontrol edin ve o bağlantı noktasına yapılan bağlantılara izin verin. Sunucu Windows’ta çalışıyorsa o makinenin Windows Güvenlik Duvarı’nda, arada ağ cihazı varsa o cihazda izin verin. Gelen trafik kurallarının nasıl oluşturulacağını “The Windows Firewall and Business Applications” makalesinde ele aldım.

Veri toplamada takılınan noktalarCihaz verilerini toplamada takılınan noktaları, kaydın içeriği (sunucunun durduğu süredeki değerlerin gelmemesi, hangi zaman damgasının kaydedileceği, Good olmayan değerlerin ele alınması) ve bağlantı hazırlığı (güvenlik duvarı bağlantı noktaları, sertifikaların son kullanma tarihi) olarak ikiye ayırarak gösteren diyagram.Veri toplamada takılınan noktalarKaydın içeriğiBağlantı hazırlığıDurma süresindeki değerler gelmezHangi zaman damgasıGood olmayan değerlerin ele alınmasıGüvenlik duvarı bağlantı noktalarıSertifikaların son kullanma tarihi

Şekil 10: Bağlanabildikten sonra da kayıtlardaki boşlukları, zamanları, durumu, bağlantı noktalarını ve sertifikaları kontrol edin.

OPC UA, Modbus/TCP ve üreticiye özgü iletişim arasında seçim

PLC’ler ve cihazlarla iletişim için OPC UA’nın yanı sıra Modbus/TCP ve PLC üreticilerinin sağladığı iletişim yöntemleri de vardır.

Modbus belirtimi verileri dört tabloda ele alır: salt okunur 1 bitlik Discrete Input, okunup yazılabilen 1 bitlik Coil, salt okunur 16 bitlik Input Register ve okunup yazılabilen 16 bitlik Holding Register22. IANA’da Modbus uygulama protokolü (mbap) için 502/TCP kayıtlıdır3.

Bu tablolara bitler ve 16 bitlik sözcükler girdiğinden, hangi adreste hangi değerin hangi biçimde bulunduğunu cihazın belgelerinden doğrulayıp istemci tarafında yazmanız gerekir. OPC UA’da ise şimdiye kadar gördüğümüz gibi sunucu adlandırılmış değişkenleri adres alanında yayımlar ve istemci bunları Browse ile araştırabilir. Değerlerle birlikte durum kodu ve zaman damgaları gelir, bağlantıya da sertifikayla izin verilir.

Hangisinin kullanılacağını çoğu zaman cihazın ne sunduğu belirler. Cihazın bir OPC UA sunucusu varsa onu kullanın; yalnızca Modbus/TCP varsa bir adres eşleme tablosu oluşturup ona göre okuyun. Yalnızca üreticinin iletişimi veya SDK’sı varsa onun belgelerini izleyin. USB cihazlarıyla iletişim yönteminin seçimini “How to Work with USB Devices from a Windows App” makalesinde ele aldım.

Cihazla iletişim yönteminin seçimiCihazın bir OPC UA sunucusu varsa OPC UA ile değişkenlere göz atılıp toplandığını, yalnızca Modbus/TCP varsa belgelerden adreslerin ve biçimlerin doğrulanıp yazmaçların okunduğunu, yalnızca üreticinin iletişimi varsa onun belgelerinin izlendiğini gösteren diyagram.OPC UA sunucusuYalnızca Modbus/TCPYalnızca üreticinin iletişimiCihazın sunduğu iletişimNe var?Değişkenlere göz atıp toplamakAdresleri ve biçimleri belgelerden doğrulamakÜreticinin belgelerini ve SDK'sını izlemekYazmaçları okuyup değere dönüştürmek

Şekil 11: Neyle toplanacağını çoğu zaman cihazın ne sunduğu belirler.

İlgili makaleler

Kaynaklar

  1. OPC Foundation, Classic. OPC Classic’in COM/DCOM’a dayandığı, DA, A&E ve HDA belirtimleri, OPC UA’nın 2008’de yayımlanması ve yeniden tasarlanmasının nedenleri. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. OPC UA’nın kavramlarını ve genel görünümünü ele alan IEC 62541 serisinin 1. bölümü (daha sonraki bir baskıyla değiştirilmiştir). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. 4840/TCP’deki opcua-tcp ve 502/TCP’deki mbap kayıtları. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. 2.0 serisi bir istemcinin nasıl yazılacağı, ReadValueAsync‘in Bad durumunda özel durum oluşturduğu, reddedilen ve güvenilen sertifika depoları, sunucuların varsayılan olarak None sunmadığı ve örneklerde kullanılan bağlantı noktası numaraları. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Benzetilmiş cihaz sunucusu için kullanılan Python OPC UA kitaplığı. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Düğümlerin başvurularını izleyen hizmet. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Ad alanı URI’sinin hizmetlerin içinde bir sayıyla gösterildiği ve bu sayının NamespaceArray’deki bir konuma karşılık geldiği. ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Read ve Write hizmetleri. ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Değer, durum kodu, SourceTimestamp ve ServerTimestamp tanımları. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. En üstteki iki bitin gösterdiği Good, Uncertain ve Bad. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Örnekleme aralığının “best effort” bir döngü olduğu. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Filtre yoksa değer veya durum değiştiğinde bildirim gönderildiği. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Kuyruk boyutu, atma ilkesi ve Overflow biti. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Yayımlama aralığı ve keep-alive. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync ile MonitoredItemOptions içindeki örnekleme aralığı ve kuyruk boyutu. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. None, Sign ve SignAndEncrypt’in anlamları. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Sertifikaları rejected deposundan güvenilen depoya taşıma uygulaması ve AutoAcceptUntrustedCertificates‘in üretimde kullanılmaması. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Dört tür: anonim, kullanıcı adı, X.509 ve verilmiş belirteç. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Bir uç noktanın kabul ettiği kullanıcı belirteçlerinin listesi (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. ManagedSession‘ın yerleşik bir yeniden bağlanma mekanizmasına sahip olduğu. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Aboneliğin yeniden gönderim kuyruğundan bildirim iletilerinin tekrar gönderilmesini sağlayan hizmet. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (Chicago Üniversitesi’nin sunucusunda barındırılan bir kopya). 4.3. bölümdeki veri modeli (Discrete Input, Coil, Input Register, Holding Register). ↩

Aynı etiketleri paylaşan en yeni makaleler. Birbirine yakın konularla bilginizi derinleştirebilirsiniz.

Bu konuya yakın konu sayfaları. Makaleden yola çıkarak ilgili hizmetlere ve diğer makalelere geçebilirsiniz.

Windows Teknik Konuları

Windows geliştirme, hata araştırması ve mevcut varlıkların kullanımına ilişkin teknik konuları bir araya getiren giriş sayfası.

Bu makale aşağıdaki hizmet sayfalarıyla ilişkilidir. Size en yakın başlangıç noktasından göz atabilirsiniz.

Windows Uygulama Geliştirme

Cihazlardan veri toplayan bir Windows uygulamasını, abonelik ayarları, yeniden bağlanma ve zaman damgaları ile durumun kaydedilmesi dâhil olmak üzere geliştirmek Windows uygulama geliştirme kapsamında ele alınabilir.

Teknik Danışmanlık ve Tasarım İncelemesi

Verileri OPC UA ile mi toplayacağınıza, yoksa Modbus/TCP'yi veya üreticinin kendi iletişimini mi kullanacağınıza ve sertifikaları nasıl yöneteceğinize karar vermek istiyorsanız, bu konu teknik danışmanlık ve tasarım incelemesi olarak ele alınabilir.

Sık Sorulan Sorular

Bu makalenin konusuyla ilgili olarak danışmanlık sırasında sık sorulan soruları derledik.

OPC UA ile OPC Classic (OPC DA) arasındaki fark nedir?
OPC Classic, verileri Windows'un COM/DCOM teknolojisi üzerinden alıp veren bir belirtimdir. OPC UA ise 2008'de yayımlanan, belirli bir işletim sistemine bağlı olmayan ve Classic'in işlevlerini bir araya getiren bir belirtimdir. OPC UA istemcileri Windows dışında da çalışır ve DCOM ayarı gerektirmez.
OPC UA ile değer toplarken belirli aralıklarla okumak yeterli mi?
Değer sayısı az ve aralık uzunsa okuyarak da toplayabilirsiniz. Değerlerdeki değişiklikleri izlemek istiyorsanız, örneklemeyi sunucuya bırakıp değişikliklerin size bildirilmesini sağlayan abonelik yöntemini kullanabilirsiniz. Her iki durumda da değerle birlikte dönen durum kodunu ve zaman damgalarını kaydedin.
Bağlanırken neden BadCertificateUntrusted hatası alıyorum?
Çünkü istemci henüz sunucunun sertifikasına güvenmiyor. OPC Foundation'ın .NET kitaplığı reddettiği sertifikaları rejected deposuna kaydeder. Sertifikanın parmak izini sunucunun yöneticisiyle doğruladıktan sonra trusted deposuna taşıyın. Sertifikaları otomatik olarak kabul eden ayarı yalnızca geliştirme ortamlarında kullanın.
OPC UA hangi bağlantı noktası numarasını kullanır?
IANA'da 4840/TCP, opcua-tcp adıyla kayıtlıdır. Ancak gerçek sunucular başka bir bağlantı noktası da kullanabildiğinden cihazın ayarlarından kontrol edin. Sunucu Windows'ta çalışıyorsa Windows Güvenlik Duvarı'nda, arada ağ cihazı varsa o cihazda bu bağlantı noktasına giden trafiğe izin vermeniz gerekir.

Yazar Profili

Makale yazarının profil sayfası.

Go Komura

KomuraSoft LLC Temsilcisi

Windows yazılım geliştirme, teknik danışmanlık ve hata araştırması odaklı çalışır; mevcut varlıkların korunduğu projelerde ve nedeni kolay anlaşılamayan arızaların araştırılmasında güçlüdür.

Herkese açık bağlantılar

Blog Listesine Dön