Endüstriyel Ethernet
TEKNİK REHBER ENDÜSTRİYEL ETHERNET OPC UA 2026

OPC UA —
IT/OT köprüsü.

IEC 62541: platform bağımsız, güvenli ve genişletilebilir endüstriyel iletişim standardı. Nesne modeli, pub/sub ve open62541 ile gerçek üretim ortamında kullanım.

00 OPC UA nedir — IEC 62541 ve legacy farkı

OPC UA (OPC Unified Architecture), OPC Foundation'ın 2008'de yayımladığı ve IEC 62541 olarak standartlaştırılan endüstriyel iletişim mimarisidir. Eski OPC protokollerinin (DA/HDA/AE) Windows COM/DCOM bağımlılığını ortadan kaldırarak platform bağımsız, güvenli ve birleşik bir çerçeve sunar.

Legacy OPC vs OPC UA

ÖzellikOPC Classic (DA/HDA/AE)OPC UA
TransportDCOM (Windows COM)TCP/IP, HTTPS, WebSocket, MQTT, AMQP
PlatformSadece WindowsLinux, Windows, RTOS, mikrodenetleyici
GüvenlikWindows NTLM/KerberosX.509 sertifika, TLS 1.2/1.3
Veri modeliAyrı (DA/HDA/AE)Birleşik nesne modeli
KeşifOPC Foundation LDSDiscovery, LDS, GDS
Pub/SubYokPart 14 (UADP, Broker)
StandartOPC 1.x spesifikasyonuIEC 62541 Bölüm 1-22

IEC 62541 bölümleri

Part 1-3Kavramlar, güvenlik modeli, adres alanı.
Part 4Servisler (Read, Write, Browse, Subscribe…).
Part 6Eşleme: UA Binary, UA XML, UA JSON encoding.
Part 7Profiller ve uyum test gereksinimleri.
Part 14Pub/Sub: UADP mesaj formatı, MQTT/AMQP/UDP broker.

01 Information model — NodeId, ObjectType, Variable, Method

OPC UA Information Model, sunucu adres alanındaki tüm varlıkları bir grafik olarak temsil eder. Her varlık bir "Node"dur; node'lar arasındaki "Reference" ilişkileri nesne hiyerarşisini ve özelliklerini tanımlar.

Node sınıfları

ObjectGerçek dünya varlıklarını temsil eder. Örn: "Kazan1", "Pompa_A". Variable ve Method içerebilir.
VariableBir değeri tutar. DataType (Boolean, Int32, Float, String, ByteString…), AccessLevel, ValueRank ve ArrayDimensions özellikleri vardır.
MethodÇağrılabilir fonksiyon. Giriş ve çıkış argümanları tanımlanır. Örn: StartPump(speed: Float) → status: Int32.
ObjectTypeNesne şablonu. Object node'larını oluşturmak için kullanılan tip tanımı. Kalıtım desteklenir.
VariableTypeVariable için tip şablonu. Varsayılan değer ve DataType tanımı içerir.
DataTypeSkaler ve yapısal veri tipleri. Enum, Structure, Union tanımlanabilir.
ViewAdres alanının alt kümesi. İstemciye filtrelenmiş bakış açısı sunar.
ReferenceTypeNode'lar arası ilişki türü: HasComponent, HasProperty, Organizes, HasTypeDefinition, HasSubtype…

NodeId türleri

TürGösterimÖrnek
Numericns=<ns>;i=<id>ns=0;i=2253 (Server node)
Stringns=<ns>;s=<str>ns=2;s=Kazan1.Sicaklik
GUIDns=<ns>;g=<guid>ns=1;g=550e8400-e29b-41d4…
ByteStringns=<ns>;b=<base64>Üretici özel kullanım
Objects (ns=0;i=85)
└── Kazan1  [Object, ns=2;s=Kazan1]
    ├── Sicaklik  [Variable, ns=2;s=Kazan1.Sicaklik]
    │   DataType: Float, Unit: °C
    │   AccessLevel: Read | StatusWrite | TimestampWrite
    ├── Basinc  [Variable, ns=2;s=Kazan1.Basinc]
    │   DataType: Float, Unit: bar
    └── Calistir  [Method, ns=2;s=Kazan1.Calistir]
        InputArgs: hedefSicaklik (Float)
        OutputArgs: sonuc (Int32)

02 Session ve güvenlik modeli

OPC UA güvenlik mimarisi üç katmandan oluşur: taşıma güvenliği (TLS), uygulama kimlik doğrulama (X.509) ve kullanıcı kimlik doğrulama (parola/sertifika/token). Bu katmanlar bağımsız yapılandırılabilir.

Güvenlik politikaları (Security Policy)

Politika URIİmzaŞifrelemeKullanım
...NoneYokYokSadece test/geliştirme
...Basic128Rsa15RSA-SHA1AES-128Eski sistemler (önerilmez)
...Basic256RSA-SHA1AES-256Geçiş dönemi
...Basic256Sha256RSA-SHA256AES-256Önerilen minimum
...Aes128_Sha256_RsaOaepRSA-SHA256AES-128 + RSA-OAEPModern sistemler
...Aes256_Sha256_RsaPssRSA-PSS-SHA256AES-256 + RSA-PSSYüksek güvenlik

Bağlantı akışı

İstemci                           Sunucu
   │                                 │
   │── GetEndpoints ────────────────►│  (güvenlik politikaları listesi)
   │◄─ Endpoints ────────────────────│
   │                                 │
   │── OpenSecureChannel ───────────►│  (TLS benzeri handshake)
   │   (ClientCertificate, nonce)    │
   │◄─ OpenSecureChannelResponse ────│  (ServerCertificate, nonce)
   │                                 │
   │── CreateSession ───────────────►│
   │   (ApplicationDescription,      │
   │    ClientNonce)                 │
   │◄─ CreateSessionResponse ────────│  (SessionId, AuthToken)
   │                                 │
   │── ActivateSession ─────────────►│  (IdentityToken: user/pass)
   │◄─ ActivateSessionResponse ──────│
   │                                 │
   │   ─── Servisler (Read/Write/Subscribe) ───

Sertifika yönetimi (open62541)

# Sunucu self-signed sertifika oluşturma
openssl req -x509 -newkey rsa:2048 \
    -keyout server_key.pem -out server_cert.pem \
    -days 3650 -nodes \
    -subj "/CN=OPC UA Server/O=EmbeddedDeck/C=TR" \
    -extensions v3_req \
    -addext "subjectAltName=URI:urn:embedded-deck:opcua:server"

# DER formatına çevir (open62541 için)
openssl x509 -in server_cert.pem -outform DER -out server_cert.der
openssl rsa  -in server_key.pem  -outform DER -out server_key.der

03 Services — Read/Write/Browse/Call

OPC UA, istek-yanıt modelinde çalışan kapsamlı bir servis kümesi tanımlar. Tüm servisler Secure Channel üzerinden UA Binary veya UA JSON ile kodlanarak iletilir.

Temel servisler

ReadBir veya birden fazla attribute (Value, DataType, AccessLevel, Description…) okunur. MaxAge ile önbellek kontrolü yapılabilir.
WriteBir veya birden fazla attribute'e değer yazılır. Değer, StatusCode ve Timestamp ayrı ayrı yazılabilir.
BrowseNode'dan erişilebilen referanslar listelenir. ReferenceType filtresi ve BrowseDirection (Forward/Inverse/Both) ile gezinme yapılır.
BrowseNextBrowse sonuçları çok sayıda referans içeriyorsa, ContinuationPoint ile sayfalama yapılır.
TranslateBrowsePathsToNodeIdsYol tabanlı erişim: "Kazan1/Sicaklik" gibi yoldan NodeId elde etme.
CallMethod node'u çağırır. ObjectId + MethodId + InputArguments parametreleri gönderilir.
RegisterNodes / UnregisterNodesSık erişilen node'ları kaydet; sunucu internal handle kullanarak erişimi hızlandırır.

UA Binary encoding yapısı

Secure Channel üzerinde bir ReadRequest:
┌────────────────────────────────────────────────┐
│  Message Header (MessageType=MSG, 8 byte)      │
│  Secure Channel Id (4 byte)                    │
│  Security Token Id (4 byte)                    │
│  Sequence Number (4 byte)                      │
│  Request Id (4 byte)                           │
├────────────────────────────────────────────────┤
│  RequestHeader (authToken, timestamp, reqHandle)│
│  MaxAge (Double)                               │
│  TimestampsToReturn (Enum: Source/Server/Both) │
│  NodesToRead []:                               │
│    NodeId: ns=2;s=Kazan1.Sicaklik             │
│    AttributeId: 13 (Value)                    │
└────────────────────────────────────────────────┘

04 Subscriptions ve MonitoredItems

OPC UA Subscription mekanizması, istemcinin belirli node'lardaki değişimleri periyodik olarak almasını sağlar. Polling'e göre çok daha verimlidir: sunucu yalnızca değişim olduğunda bildirim gönderir.

Subscription parametreleri

Publishing IntervalSunucunun bildirim gönderme periyodu (ms). İstemci bu aralıkta Publish isteği gönderir.
Lifetime CountKaç Publishing Interval boyunca bildirim olmazsa subscription iptal edilir. Bağlantı kesilmelerini yönetir.
Max Keep-Alive CountDeğişiklik olmazsa kaç Publishing Interval'da bir boş "keep-alive" mesajı gönderilir.
Max Notifications Per PublishTek bir Publish yanıtındaki maksimum bildirim sayısı. Ağ bant genişliği kontrolü için.

MonitoredItem parametreleri

Sampling IntervalSunucunun değeri örnekleme hızı (ms). Publishing Interval'dan daha hızlı olabilir; ara değerler queue'da birikir.
Queue SizeSampling ve Publishing arasındaki tampon boyutu. Overflow politikası: DiscardOldest veya DiscardNewest.
DataChange FilterDeadband: mutlak veya yüzde. Sadece belirli bir miktarın üzerinde değişimde bildirim gönderir.
TriggerDataChange (değer değişimi), StatusChange (kalite değişimi) veya EventNotification.
import asyncio
from asyncua import Client

async def subscribe_temperatures():
    url = "opc.tcp://localhost:4840"
    async with Client(url=url) as client:
        # Subscription oluştur (100 ms publishing interval)
        subscription = await client.create_subscription(
            period=100,
            handler=DataChangeHandler()
        )

        # Sıcaklık node'una MonitoredItem ekle
        node = client.get_node("ns=2;s=Kazan1.Sicaklik")
        handle = await subscription.subscribe_data_change(
            node,
            sampling_interval=50,   # 50 ms örnekleme
            queuesize=10
        )

        await asyncio.sleep(10)     # 10 saniye dinle
        await subscription.unsubscribe(handle)
        await subscription.delete()

class DataChangeHandler:
    def datachange_notification(self, node, val, data):
        print(f"Sıcaklık değişti: {val:.2f} °C")
        print(f"  StatusCode: {data.monitored_item.Value.StatusCode}")
        print(f"  SourceTimestamp: {data.monitored_item.Value.SourceTimestamp}")

asyncio.run(subscribe_temperatures())

05 Pub/Sub — UADP over MQTT/UDP

OPC UA Part 14 (Pub/Sub), subscription modelinin ötesinde, broker tabanlı veya broker-less veri yayımını tanımlar. UADP (UA Datagram Protocol) mesaj formatı MQTT, AMQP veya UDP üzerinden taşınabilir.

Pub/Sub kavramları

PublishedDataSetYayımlanacak değişken kümesi. DataSetField listesi içerir; her alan bir Variable node'una eşlenir.
DataSetWriterBelirli bir PublishedDataSet'i belirli aralıkta yayımlar. Her DataSetWriter benzersiz bir WriterGroupId taşır.
WriterGroupBirden fazla DataSetWriter'ı barındırır. Publishing interval ve transport ayarları burada tanımlanır.
DataSetReaderAbone tarafında, belirli bir WriterGroup+DataSetWriter'ın mesajlarını alır ve adres alanına yazar.
UADPUA Datagram Protocol: kompakt binary mesaj formatı. NetworkMessage, DataSetMessage ve DataSetFields hiyerarşisi.

MQTT transport ile yayım

OPC UA Publisher                MQTT Broker               OPC UA Subscriber
    │                               │                          │
    │── NetworkMessage ────────────►│                          │
    │   Topic: opcua/kazan1/data    │── NetworkMessage ───────►│
    │   UADP binary payload         │   (DataSetField decode)  │
    │                               │                          │
    │   [Header][GroupHeader][DataSetMessage[Fields...]]
// open62541 PubSub JSON konfigürasyon örneği
{
  "publishedDataSets": [{
    "name": "KazanDataSet",
    "fields": [
      {"name": "Sicaklik", "nodeId": "ns=2;s=Kazan1.Sicaklik"},
      {"name": "Basinc",   "nodeId": "ns=2;s=Kazan1.Basinc"}
    ]
  }],
  "connections": [{
    "name": "MQTTBaglantisi",
    "transportProfileUri": "http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-json",
    "address": "mqtt://broker.local:1883",
    "writerGroups": [{
      "name": "KazanGroup",
      "publishingInterval": 500,
      "writerGroupId": 1,
      "dataSetWriters": [{
        "name": "KazanWriter",
        "dataSetName": "KazanDataSet",
        "mqttTopic": "opcua/kazan1/data"
      }]
    }]
  }]
}

06 open62541 C kütüphanesi

open62541, Apache 2.0 lisansıyla yayımlanan, C99 ile yazılmış taşınabilir bir OPC UA implementasyonudur. Tek header (amalgamate) veya kütüphane olarak kullanılabilir; gömülü sistemlerden sunuculara kadar geniş yelpazeyi destekler.

Derleme ve kurulum

# Bağımlılıklar
sudo apt install cmake python3 python3-sphinx mbedtls-dev

# Kaynak kodu
git clone https://github.com/open62541/open62541.git
cd open62541
git checkout v1.3.8

mkdir build && cd build
cmake .. \
    -DCMAKE_BUILD_TYPE=RelWithDebInfo \
    -DUA_ENABLE_AMALGAMATION=ON \
    -DUA_ENABLE_PUBSUB=ON \
    -DUA_ENABLE_ENCRYPTION=MBEDTLS \
    -DUA_ENABLE_SUBSCRIPTIONS=ON \
    -DUA_LOGLEVEL=300    # UA_LOGLEVEL_INFO

make -j$(nproc)
sudo make install

OPC UA server oluşturma — temel yapı

/* opcua_server.c — open62541 ile minimal OPC UA server */
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include <stdio.h>

static volatile UA_Boolean running = true;
static void stopHandler(int sig) { running = false; }

/* Custom nesne tipi: SicaklikSensoru */
static void addTemperatureSensorType(UA_Server *server)
{
    UA_ObjectTypeAttributes otAttr = UA_ObjectTypeAttributes_default;
    otAttr.displayName = UA_LOCALIZEDTEXT("tr", "Sıcaklık Sensörü");
    UA_NodeId typeId = UA_NODEID_STRING(2, "SicaklikSensoru");

    UA_Server_addObjectTypeNode(server,
        typeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(2, "SicaklikSensoru"),
        otAttr, NULL, NULL);

    /* Sıcaklık değişkeni ekle */
    UA_VariableAttributes vAttr = UA_VariableAttributes_default;
    vAttr.displayName = UA_LOCALIZEDTEXT("tr", "Sıcaklık");
    vAttr.dataType    = UA_TYPES[UA_TYPES_FLOAT].typeId;
    vAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Float initVal  = 0.0f;
    UA_Variant_setScalar(&vAttr.value, &initVal, &UA_TYPES[UA_TYPES_FLOAT]);

    UA_Server_addVariableNode(server,
        UA_NODEID_STRING(2, "SicaklikSensoru.Sicaklik"),
        typeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(2, "Sicaklik"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        vAttr, NULL, NULL);
}

/* Nesne örneği oluştur */
static void addSensorInstance(UA_Server *server,
                               const char *name, float initTemp)
{
    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    oAttr.displayName = UA_LOCALIZEDTEXT("tr", (char*)name);

    UA_Server_addObjectNode(server,
        UA_NODEID_STRING(2, (char*)name),
        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(2, (char*)name),
        UA_NODEID_STRING(2, "SicaklikSensoru"),
        oAttr, NULL, NULL);
}

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

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

    /* Namespace 2 ekle */
    UA_Server_addNamespace(server, "urn:embedded-deck:opcua");

    /* Tip ve örnekler oluştur */
    addTemperatureSensorType(server);
    addSensorInstance(server, "Kazan1", 25.0f);
    addSensorInstance(server, "Kazan2", 30.0f);

    printf("OPC UA Server başlatılıyor: opc.tcp://localhost:4840\n");
    UA_StatusCode sc = UA_Server_runUntilInterrupt(server);

    UA_Server_delete(server);
    return sc == UA_STATUSCODE_GOOD ? 0 : 1;
}
gcc -o opcua_server opcua_server.c \
    $(pkg-config --cflags --libs open62541)
sudo ./opcua_server

07 asyncua Python kütüphanesi

asyncua (FreeOpcUa projesinin asyncio tabanlı halefi), Python ile OPC UA istemci ve sunucu geliştirmek için en popüler kütüphanedir. asyncio tabanlı yapısı sayesinde çok sayıda subscription bile tek thread'de verimli şekilde yönetilebilir.

pip install asyncua

İstemci: okuma ve yazma

import asyncio
from asyncua import Client
from asyncua.ua import DataValue, Variant, VariantType

async def main():
    async with Client("opc.tcp://localhost:4840") as client:
        # Namespace indeksi bul
        nsidx = await client.get_namespace_index(
            "urn:embedded-deck:opcua")

        # Node'u al ve oku
        node = await client.nodes.root.get_child([
            "0:Objects", f"{nsidx}:Kazan1",
            f"{nsidx}:Sicaklik"
        ])
        value = await node.read_value()
        print(f"Sıcaklık: {value:.2f} °C")

        # Değer yaz
        await node.write_value(
            DataValue(Variant(72.5, VariantType.Float))
        )
        print("Yeni değer yazıldı: 72.5 °C")

asyncio.run(main())

Method çağırma

import asyncio
from asyncua import Client

async def call_method():
    async with Client("opc.tcp://localhost:4840") as client:
        nsidx = await client.get_namespace_index(
            "urn:embedded-deck:opcua")

        parent = await client.nodes.root.get_child([
            "0:Objects", f"{nsidx}:Kazan1"
        ])

        # Calistir metodunu çağır
        # InputArgs: hedefSicaklik=80.0
        result = await parent.call_method(
            f"{nsidx}:Calistir",
            80.0   # hedefSicaklik parametresi
        )
        print(f"Metod sonucu: {result}")

asyncio.run(call_method())

Subscription ile olay izleme

import asyncio
from asyncua import Client
from asyncua.common.subscription import SubHandler

class MyHandler(SubHandler):
    def datachange_notification(self, node, val, data):
        print(f"[DEĞİŞİM] {node}: {val}")

    def event_notification(self, event):
        print(f"[OLAY] {event.EventType}: {event.Message}")

async def subscribe():
    async with Client("opc.tcp://localhost:4840") as client:
        handler = MyHandler()
        sub = await client.create_subscription(200, handler)

        nsidx = await client.get_namespace_index(
            "urn:embedded-deck:opcua")

        nodes = [
            client.get_node(f"ns={nsidx};s=Kazan1.Sicaklik"),
            client.get_node(f"ns={nsidx};s=Kazan1.Basinc"),
        ]
        handles = await sub.subscribe_data_change(nodes)

        print("Abonelik aktif — Ctrl+C ile durdur")
        try:
            await asyncio.sleep(float('inf'))
        except KeyboardInterrupt:
            pass
        finally:
            await sub.unsubscribe(handles)
            await sub.delete()

asyncio.run(subscribe())

08 Pratik: Raspberry Pi + DS18B20 + OPC UA server

Bu bölümde Raspberry Pi'ye bağlı DS18B20 dijital sıcaklık sensöründen veri okuyan ve bu veriyi OPC UA server üzerinden yayımlayan eksiksiz bir sistem kurulur.

Donanım bağlantısı

Raspberry Pi 4           DS18B20
  3.3V (Pin 1) ─────────── VDD
  GND  (Pin 6) ─────────── GND
  GPIO4 (Pin 7) ──┬──────── DATA
                  │
               4.7 kΩ
                  │
  3.3V (Pin 1) ───┘   (pull-up direnci)
# /boot/config.txt (veya /boot/firmware/config.txt)
dtoverlay=w1-gpio,gpiopin=4

# Modülleri yükle
sudo modprobe w1-gpio
sudo modprobe w1-therm

# Sensörü doğrula
ls /sys/bus/w1/devices/
# 28-XXXXXXXXXXXX  w1_bus_master1

cat /sys/bus/w1/devices/28-*/w1_slave
# 50 05 55 05 7f a5 a5 66 8a : crc=8a YES
# 50 05 55 05 7f a5 a5 66 8a t=21250
# t=21250 → 21.250 °C

OPC UA server + DS18B20 entegrasyonu

#!/usr/bin/env python3
"""
ds18b20_opcua_server.py
DS18B20 sıcaklık sensörünü OPC UA üzerinden yayımlayan server.
Gereksinim: pip install asyncua
"""
import asyncio
import glob
import logging
from asyncua import Server
from asyncua.ua import VariantType

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

W1_BASE = "/sys/bus/w1/devices/"

def read_ds18b20() -> float:
    """DS18B20'den °C cinsinden sıcaklık oku."""
    sensor_dirs = glob.glob(W1_BASE + "28-*")
    if not sensor_dirs:
        raise RuntimeError("DS18B20 sensörü bulunamadı!")

    with open(sensor_dirs[0] + "/w1_slave") as f:
        lines = f.readlines()

    if "YES" not in lines[0]:
        raise RuntimeError("CRC hatası — sensör yanıt vermiyor")

    temp_str = lines[1].split("t=")[1].strip()
    return float(temp_str) / 1000.0

async def update_sensor(server, temp_node, interval: float = 1.0):
    """Periyodik olarak sensörden oku ve OPC UA node'unu güncelle."""
    while True:
        try:
            temp = read_ds18b20()
            await temp_node.write_value(temp)
            logger.debug(f"Sıcaklık güncellendi: {temp:.3f} °C")
        except Exception as e:
            logger.warning(f"Sensör okuma hatası: {e}")
            await temp_node.write_value(float('nan'))
        await asyncio.sleep(interval)

async def main():
    server = Server()
    await server.init()
    server.set_endpoint("opc.tcp://0.0.0.0:4840/")
    server.set_server_name("Raspberry Pi DS18B20 OPC UA Server")

    # Namespace oluştur
    uri = "urn:raspberry-pi:ds18b20:server"
    nsidx = await server.register_namespace(uri)

    # Objects altına sensör node'u ekle
    objects = server.nodes.objects
    sensor_obj = await objects.add_object(nsidx, "SicaklikSensoru")

    # Sıcaklık değişkeni
    temp_node = await sensor_obj.add_variable(
        nsidx, "Sicaklik", 0.0
    )
    await temp_node.set_writable(False)

    # Birim string'i
    unit_node = await sensor_obj.add_variable(
        nsidx, "Birim", "°C"
    )

    # Sensör ID'si
    sensor_dirs = glob.glob(W1_BASE + "28-*")
    sensor_id = sensor_dirs[0].split("/")[-1] if sensor_dirs else "N/A"
    id_node = await sensor_obj.add_variable(
        nsidx, "SensorID", sensor_id
    )

    async with server:
        logger.info(f"OPC UA Server başlatıldı: opc.tcp://localhost:4840/")
        logger.info(f"Sensör ID: {sensor_id}")
        logger.info(f"Node: ns={nsidx};s=SicaklikSensoru.Sicaklik")

        # Arka planda sensör güncelleme görevi
        update_task = asyncio.create_task(
            update_sensor(server, temp_node, interval=1.0)
        )
        try:
            await asyncio.sleep(float('inf'))
        except asyncio.CancelledError:
            update_task.cancel()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("Server durduruldu.")

Python istemcisi ile veriyi izleme

#!/usr/bin/env python3
"""Raspberry Pi OPC UA server'dan sıcaklık oku."""
import asyncio
from asyncua import Client

async def monitor():
    uri = "urn:raspberry-pi:ds18b20:server"
    async with Client("opc.tcp://raspberry-pi.local:4840/") as c:
        nsidx = await c.get_namespace_index(uri)

        sub = await c.create_subscription(500, handler=TempHandler())
        node = c.get_node(f"ns={nsidx};s=SicaklikSensoru.Sicaklik")
        await sub.subscribe_data_change(node)

        print(f"İzleniyor: ns={nsidx};s=SicaklikSensoru.Sicaklik")
        await asyncio.sleep(30)

class TempHandler:
    def datachange_notification(self, node, val, data):
        ts = data.monitored_item.Value.SourceTimestamp
        print(f"[{ts:%H:%M:%S}] Sıcaklık: {val:.3f} °C")

asyncio.run(monitor())
NOT

Üretim ortamında server sertifikası ile Basic256Sha256 güvenlik politikasını etkinleştirin. asyncua, sertifika bazlı bağlantı için load_client_certificate() ve load_private_key() metodlarını sağlar.