เริ่มต้น OPC UA ── เก็บข้อมูลจาก PLC และอุปกรณ์ด้วยแอป Windows

· · Windows, OPC UA, การผลิต, PLC, C#, .NET, การสื่อสาร, ใบรับรอง

โพสต์บน X
ประวัติการแก้ไข (ฉบับแรก เผยแพร่เมื่อ 1 Nov 2026)
เผยแพร่ครั้งแรก
อ้างอิงบทความนี้(DOI: 10.5281/zenodo.23968413)

บทความนี้ถูกเก็บถาวรใน Zenodo ด้านล่างมีทั้ง DOI ที่ชี้ไปยังฉบับล่าสุดเสมอ และ DOI ที่ผูกกับฉบับที่คุณกำลังอ่าน

Go Komura (2026). เริ่มต้น OPC UA ── เก็บข้อมูลจาก PLC และอุปกรณ์ด้วยแอป Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968413 https://comcomponent.com/th/blog/opc-ua-windows-data-collection/

DOI (ฉบับล่าสุด)
10.5281/zenodo.23968413
DOI (ฉบับนี้)
10.5281/zenodo.23968414

เมื่อเก็บข้อมูลจาก PLC หรืออุปกรณ์เข้าแอป Windows ถ้าอุปกรณ์มีเซิร์ฟเวอร์ OPC UA ก็อาจไม่ต้องเขียนการสื่อสารแยกสำหรับผู้ผลิตแต่ละราย แต่แค่เชื่อมต่อแล้วอ่านค่ายังไม่พอ ถ้าไม่รู้เรื่องสถานะและเวลาประทับที่ส่งมาพร้อมค่า subscription ที่แจ้งการเปลี่ยนแปลง การอนุญาตการเชื่อมต่อด้วยใบรับรอง และวิธีกลับมาทำงานหลังการเชื่อมต่อขาด ข้อมูลที่เก็บมาก็จะเชื่อถือไม่ได้ในภายหลัง

บทความนี้อธิบายส่วนของ OPC UA ที่จำเป็นต่อการเก็บข้อมูล พร้อมผลที่ได้จากการรันไคลเอนต์ C# จริง ฝั่งไคลเอนต์ใช้ไลบรารี .NET ของ OPC Foundation และอีกฝั่งใช้เซิร์ฟเวอร์อุปกรณ์จำลอง ยังไม่ได้ทดสอบกับ PLC หรืออุปกรณ์รุ่นใดโดยเฉพาะ การตั้งค่าของแต่ละอุปกรณ์ให้ตรวจสอบจากเอกสารของอุปกรณ์นั้น

เรื่องการเก็บภาพจากกล้อง GigE Vision และ USB3 Vision อยู่ใน “Dropped Frames with GigE Vision and USB3 Vision Cameras” และเรื่องอุปกรณ์ที่สื่อสารแบบอนุกรมอยู่ใน “Serial Communication App Pitfalls”

ความแตกต่างระหว่าง OPC UA กับ OPC Classic

ข้อกำหนดของ OPC Classic ใช้ COM/DCOM ของ Windows ในการแลกเปลี่ยนข้อมูลระหว่างส่วนประกอบซอฟต์แวร์ โดยกำหนดเป็นข้อกำหนดแยกกัน ได้แก่ OPC DA สำหรับข้อมูลกระบวนการ OPC A&E สำหรับการแจ้งเตือน และ OPC HDA สำหรับข้อมูลย้อนหลัง1

OPC Foundation เผยแพร่ OPC UA (Unified Architecture) ในปี 2008 OPC UA เป็นสถาปัตยกรรมแบบ service-oriented ที่ไม่ขึ้นกับแพลตฟอร์มใดโดยเฉพาะ และรวมความสามารถของข้อกำหนดแต่ละฉบับของ Classic ไว้ด้วยกัน OPC Foundation ให้เหตุผลของการสร้างใหม่ไว้ เช่น Microsoft หันไปให้ความสำคัญกับกลไกข้ามแพลตฟอร์มมากกว่า COM/DCOM ความจำเป็นที่ต้องทำงานบนระบบที่ไม่ใช่ของ Microsoft และอุปกรณ์ฝังตัว และความจำเป็นที่ต้องเชื่อมต่ออย่างปลอดภัยข้ามไฟร์วอลล์1

ข้อกำหนดของ OPC UA ยังเป็นมาตรฐานของ IEC ในชุด IEC 62541 ด้วย2 สำหรับพอร์ตการสื่อสาร IANA ลงทะเบียน 4840/TCP ไว้ในชื่อ opcua-tcp3

ความแตกต่างระหว่าง OPC Classic กับ OPC UAแผนภาพแสดงว่า OPC Classic ทำงานบน COM และ DCOM ของ Windows และแยกเป็นข้อกำหนด DA, A&E และ HDA ส่วน OPC UA รวมความสามารถเหล่านั้นไว้ในข้อกำหนดเดียวที่ไม่ขึ้นกับแพลตฟอร์มรวมความสามารถOPC Classicทำงานบน COM/DCOM (Windows)ข้อกำหนดที่แยกเป็น DA, A&E และ HDAOPC UA (2008)ไม่ขึ้นกับแพลตฟอร์มใดโดยเฉพาะเป็นมาตรฐาน IEC 62541

ภาพ 1: OPC Classic เป็นข้อกำหนดแยกกันที่อยู่บน COM/DCOM และ OPC UA รวมข้อกำหนดเหล่านั้นเป็นหนึ่งเดียว

ถ้าอุปกรณ์ที่มีอยู่มีเพียงเซิร์ฟเวอร์ OPC DA ก็ต้องมีกลไกฝั่งที่ใช้ COM/DCOM บทความนี้กล่าวถึงเฉพาะ OPC UA

ในแผนภาพ เส้นทึบหมายถึงความสัมพันธ์ที่ถือเสมอ และเส้นประหมายถึงความสัมพันธ์ที่มีเงื่อนไข (เงื่อนไขอยู่ในการอธิบายของแต่ละความสัมพันธ์ในหน้าละเอียด) รายการความสัมพันธ์ทั้งหมด (รวม 13 รายการ พร้อมหลักฐานและระดับความเชื่อมั่น) และนิยามของแนวคิดหลักรวบรวมไว้ที่ หน้าละเอียดของแผนที่ความรู้ (เป็นภาษาญี่ปุ่น) ข้อมูล: JSON-LD / Turtle

การตั้งค่าที่ใช้ทดสอบ

ทดสอบด้วยการตั้งค่าต่อไปนี้

บทบาท สิ่งที่ใช้
ไคลเอนต์ C#, .NET 10 SDK 10.0.114, แพ็กเกจ NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
เซิร์ฟเวอร์ อุปกรณ์จำลองที่สร้างด้วย Python 3.13 และ asyncua 2.1.0 (พอร์ต 4840 บน PC เครื่องเดียวกัน)
ระบบปฏิบัติการ Linux (ยังไม่ได้ทดสอบการรันบน Windows)

ไลบรารีฝั่งไคลเอนต์เป็นการ implement สำหรับ .NET ที่ OPC Foundation เผยแพร่ วิธีใช้ของรุ่น 2.0 เขียนตามแบบใน Getting started ของ repository เดียวกัน4

การตั้งค่าที่ใช้ทดสอบแผนภาพแสดงว่าไคลเอนต์ C# ใช้ไลบรารี .NET ของ OPC Foundation และเชื่อมต่อแบบเข้ารหัสไปยังเซิร์ฟเวอร์อุปกรณ์จำลองของ asyncua ที่รอรับอยู่บนพอร์ต 4840opc.tcp, SignAndEncryptไคลเอนต์ C# (LineCollector)เซิร์ฟเวอร์อุปกรณ์จำลอง (asyncua)ไลบรารี .NET ของ OPC Foundation 2.0.1ตัวแปร 4 ตัวของ Line1

ภาพ 2: เชื่อมต่อจากไคลเอนต์ C# ไปยังเซิร์ฟเวอร์อุปกรณ์จำลองภายใน PC เครื่องเดียวกัน

เซิร์ฟเวอร์อุปกรณ์จำลอง

แทนที่จะใช้อุปกรณ์จริง ได้สร้างเซิร์ฟเวอร์ขนาดเล็กด้วย asyncua5 ใต้ออบเจ็กต์ชื่อ Line1 มีตัวแปร 4 ตัว ได้แก่ อุณหภูมิ (Temperature) จำนวนชิ้นงานที่ผลิต (PartCount) สถานะกำลังทำงาน (Running) และค่าที่ตั้งไว้ (Setpoint) อุณหภูมิเปลี่ยนทุก 0.5 วินาที และจำนวนชิ้นงานเปลี่ยนทุก 2 วินาที ด้านความปลอดภัยเตรียมไว้ 2 แบบ คือไม่เข้ารหัส (None) และลงนามพร้อมเข้ารหัส (SignAndEncrypt ของ Basic256Sha256)

# อุปกรณ์จำลองสำหรับทดสอบ ไม่ได้ใช้แทนอุปกรณ์หรือ PLC จริง
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())

เซิร์ฟเวอร์นี้รับใบรับรองของไคลเอนต์ที่เชื่อมต่อเข้ามาโดยไม่ตรวจสอบ ซึ่งเป็นการตั้งค่าสำหรับทดสอบ และต่างจากพฤติกรรมของอุปกรณ์จริง

ไคลเอนต์ C#

ในไฟล์โปรเจกต์ใส่แพ็กเกจ 2 ตัวต่อไปนี้

<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>

ไคลเอนต์ทำการเชื่อมต่อ browse อ่าน เขียน และ subscribe ตามลำดับ ความหมายของแต่ละส่วนจะอธิบายทีละส่วนในหัวข้อถัดไป

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}");

        // เซิร์ฟเวอร์เป็นผู้กำหนดดัชนีของเนมสเปซ จึงค้นจาก URI แล้วสร้าง NodeId
        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);

        // Browse: แสดงรายการตัวแปรที่อยู่ใต้ Line1
        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}");
        }

        // Read: ดูไม่เฉพาะค่า แต่ดูสถานะและเวลาประทับทั้งสองด้วย
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // เมื่ออ่านโหนดที่ไม่มีอยู่ จะได้สถานะ Bad แทนค่า
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Write: ตั้ง Setpoint เป็น 32.5
        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]}");

        // Subscribe: ขอให้เซิร์ฟเวอร์สุ่มตัวอย่างทุก 500 ms และแจ้งเมื่อมีการเปลี่ยนแปลง
        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");

ครั้งแรกที่รันนั้นเชื่อมต่อไม่ได้ เหตุผลอธิบายไว้ในหัวข้อ “อนุญาตการเชื่อมต่อด้วยใบรับรอง” ผลลัพธ์หลังจากเชื่อถือใบรับรองของเซิร์ฟเวอร์แล้วเป็นดังนี้ โดยตัดล็อกคำเตือนของไลบรารีออก

接続: 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]

เวลาเป็น UTC

ข้อมูลของอุปกรณ์ปรากฏเป็นโหนดใน address space

เซิร์ฟเวอร์ OPC UA เปิดเผยข้อมูลที่มีอยู่ในรูปของกลุ่มโหนด กลุ่มนี้เรียกว่า address space โหนดมีหลายชนิด เช่น ออบเจ็กต์ที่แทนโฟลเดอร์หรืออุปกรณ์ และตัวแปรที่เก็บค่า โหนดเชื่อมกันด้วย reference ไคลเอนต์ใช้บริการ Browse เพื่อไล่ตาม reference และตรวจดูว่ามีโหนดอะไรบ้าง6

ในผลลัพธ์ข้างต้น ไคลเอนต์ไล่ตาม reference แบบลำดับชั้นจาก Line1 และพบตัวแปร 4 ตัว

Address space ของอุปกรณ์จำลองแผนภาพแสดงว่าใต้โฟลเดอร์ Objects มีออบเจ็กต์ Line1 และใต้ Line1 มีตัวแปร 4 ตัว ได้แก่ Temperature, PartCount, Running และ SetpointObjectsLine1 (ออบเจ็กต์)Temperature (ตัวแปร)PartCount (ตัวแปร)Running (ตัวแปร)Setpoint (ตัวแปร เขียนได้)

ภาพ 3: Address space ของอุปกรณ์จำลอง ไคลเอนต์ใช้ Browse ไล่ตาม reference เพื่อหาโหนด

NodeId ประกอบด้วยเนมสเปซและตัวระบุ

โหนดระบุด้วย NodeId ค่า ns=2;s=Line1.Temperature ในผลลัพธ์หมายความว่าดัชนีของเนมสเปซคือ 2 และตัวระบุคือสตริง Line1.Temperature

แท้จริงแล้วเนมสเปซคือ URI ข้อกำหนดระบุว่าภายในบริการต่าง ๆ จะแทน URI ของเนมสเปซด้วยตัวเลข และตัวเลขนั้นคือตำแหน่งใน NamespaceArray ของเซิร์ฟเวอร์ ดัชนี 0 คือเนมสเปซของ OPC UA http://opcfoundation.org/UA/7

ดังนั้นแทนที่จะเขียนตัวเลขดัชนีไว้ในโค้ด ให้ค้นดัชนีจาก URI ในโค้ดข้างต้นใช้ session.NamespaceUris.GetIndex(LineNamespace) หาเลข 2 จาก URI แล้วนำไปสร้าง NodeId

ขั้นตอนการสร้าง NodeId จาก URI ของเนมสเปซแผนภาพแสดงว่าไคลเอนต์นำ URI ของเนมสเปซไปค้นใน NamespaceArray ของเซิร์ฟเวอร์เพื่อได้ดัชนี แล้วรวมดัชนีนั้นกับตัวระบุเป็น NodeIdURI ของเนมสเปซNamespaceArray ของเซิร์ฟเวอร์ดัชนี (ในตัวอย่างนี้คือ 2)NodeId ns=2;s=Line1.Temperatureตัวระบุ Line1.Temperature

ภาพ 4: ค้นดัชนีของเนมสเปซจาก URI แล้วรวมกับตัวระบุเป็น NodeId

การอ่านและเขียนจะได้สถานะและเวลาประทับมาพร้อมค่า

ค่าของตัวแปรอ่านและเขียนด้วยบริการ Read และ Write8 สิ่งที่ได้จากการอ่านไม่ได้มีแค่ค่า แต่ได้โครงสร้างชื่อ DataValue ซึ่งมีค่า รหัสสถานะ source timestamp และ server timestamp9

รายการ ความหมาย
ค่า ค่าของตัวแปร เมื่อรหัสสถานะบ่งบอกข้อผิดพลาดให้ไม่สนใจค่านี้ และเซิร์ฟเวอร์จะตั้งเป็น null
รหัสสถานะ (StatusCode) เซิร์ฟเวอร์ได้มาและส่งค่านั้นได้หรือไม่
SourceTimestamp เวลาที่ต้นทางของข้อมูลประทับให้กับค่า
ServerTimestamp เวลาที่เซิร์ฟเวอร์ได้รับค่า หรือรู้ว่าค่านั้นถูกต้อง

บิตบนสุด 2 บิตของรหัสสถานะบอก Good (สำเร็จ) Uncertain (สำเร็จบางส่วน อาจใช้ไม่ได้ขึ้นกับการใช้งาน) และ Bad (ล้มเหลว)10 ค่า Good [0x00000000] ในผลลัพธ์คือ Good

เมื่ออ่าน Line1.Pressure ที่ไม่มีอยู่ ได้ BadNodeIdUnknown [0x80340000] แทนค่า ReadValueAsync ของไลบรารีนี้จะโยน exception เมื่อสถานะเป็น Bad4 ในระบบที่บันทึกค่า ถ้าเก็บรหัสสถานะไว้คู่กับค่าด้วย ภายหลังก็แยกได้ว่าค่าใดถูกบันทึกในช่วงที่เป็น Bad

สิ่งที่อยู่ใน DataValueแผนภาพแสดงว่า DataValue ที่ได้จากการอ่านหรือการแจ้งเตือนมี 4 อย่าง ได้แก่ ค่า รหัสสถานะ source timestamp และ server timestamp และถ้ารหัสสถานะเป็น Bad จะไม่สนใจค่าถ้าเป็น BadDataValueค่ารหัสสถานะSourceTimestamp (เวลาของต้นทาง)ServerTimestamp (เวลาของเซิร์ฟเวอร์)ไม่สนใจค่า

ภาพ 5: ผลการอ่านไม่ได้มีแค่ค่า แต่มีรหัสสถานะและเวลาประทับ 2 ค่า

ในการเขียน ได้เขียน 32.5 ลงใน Setpoint และได้ Good เป็นรหัสสถานะของผลลัพธ์ ตัวแปรใดเขียนได้นั้นเซิร์ฟเวอร์เป็นผู้กำหนด เซิร์ฟเวอร์จำลองนี้ให้เขียนได้เฉพาะ Setpoint การจะยอมให้เปลี่ยนค่าที่ตั้งไว้ของอุปกรณ์จากภายนอกหรือไม่ เกี่ยวข้องกับการออกแบบด้านความปลอดภัยของอุปกรณ์ จึงอยู่นอกขอบเขตของบทความนี้

ให้แจ้งการเปลี่ยนแปลง: subscription และ MonitoredItem

เมื่อต้องการติดตามการเปลี่ยนแปลงของค่า ไคลเอนต์อาจ Read ซ้ำเป็นช่วงเวลาคงที่ก็ได้ แต่ OPC UA ยังมีอีกกลไกหนึ่ง คือฝากการเฝ้าดูค่าไว้กับเซิร์ฟเวอร์และให้แจ้งการเปลี่ยนแปลงกลับมา นั่นคือ subscription และ MonitoredItem

MonitoredItem คือเป้าหมายที่เฝ้าดูหนึ่งรายการ และมี sampling interval sampling interval คือช่วงเวลาที่เร็วที่สุดที่เซิร์ฟเวอร์สุ่มตัวอย่างค่าจากต้นทาง และข้อกำหนดเรียกว่าเป็นรอบแบบ “best effort”11 เมื่อตัวอย่างผ่านเงื่อนไขของตัวกรอง จะเกิดการแจ้งเตือน ถ้าไม่ระบุตัวกรอง จะแจ้งเมื่อค่าหรือสถานะเปลี่ยน12

การแจ้งเตือนจะเข้าคิวของ MonitoredItem ก่อน เมื่อคิวเต็มจะทิ้งรายการเก่าหรือใหม่ตามที่กำหนดไว้ และถ้าขนาดคิวตั้งแต่ 2 ขึ้นไป บิต Overflow ที่บอกว่ามีการทิ้งจะถูกตั้งในรหัสสถานะ13

Subscription รวม MonitoredItem หลายตัวไว้ด้วยกัน และส่งการแจ้งเตือนที่ยังไม่ได้ส่งไปให้ไคลเอนต์ทุก publishing interval ถ้ารอบที่ไม่มีการแจ้งเตือนจะส่งเกิดต่อเนื่องกัน ก็จะส่ง keep-alive เพื่อบอกว่า subscription ยังทำงานอยู่14

ขั้นตอนของ subscription และ MonitoredItemแผนภาพแสดงว่า MonitoredItem สุ่มตัวอย่างค่าจากต้นทางตาม sampling interval ใส่การแจ้งเตือนที่ผ่านตัวกรองลงคิว จากนั้น subscription รวมส่งไปยังไคลเอนต์ตาม publishing interval และถ้าไม่มีการแจ้งเตือนจะส่ง keep-aliveSampling intervalPublishing intervalถ้าไม่มีการแจ้งเตือนต้นทาง (ค่าของอุปกรณ์)MonitoredItemตัวกรอง (ค่าเริ่มต้นคือค่าหรือสถานะเปลี่ยน)คิว (ถ้าเต็มจะเกิด Overflow)Subscriptionไคลเอนต์Keep-alive

ภาพ 6: MonitoredItem สุ่มตัวอย่างและเก็บไว้ในคิว แล้ว subscription รวมส่งไปยังไคลเอนต์

ในโค้ดข้างต้นได้ subscribe Temperature และ PartCount ด้วย sampling interval 500 มิลลิวินาที และขนาดคิว 515 ในผลลัพธ์ อุณหภูมิมาถึงประมาณทุก 0.5 วินาที และจำนวนชิ้นงานมาถึงประมาณทุก 2 วินาที 2 รายการแรกคือค่าที่ตัวแปรแต่ละตัวมีอยู่ ณ เวลาที่เริ่ม subscribe ส่วน 13:40:50.801 ของจำนวนชิ้นงานคือเวลาที่ค่าเปลี่ยนก่อนเริ่ม subscribe เวลาของการแจ้งเตือนแสดงด้วย source timestamp จึงเป็นเวลาที่ค่าเปลี่ยน ไม่ใช่เวลาที่ได้รับการแจ้งเตือน

เลือกใช้การอ่านซ้ำหรือ subscription

การอ่านซ้ำทำให้ฝั่งไคลเอนต์กำหนดช่วงเวลาได้ และกลไกเรียบง่าย แต่ถึงค่าไม่เปลี่ยน คำขอและคำตอบก็ยังวิ่งไปมาทุกครั้ง และพลาดการเปลี่ยนแปลงที่สั้นกว่าช่วงเวลา ส่วน subscription เซิร์ฟเวอร์สุ่มตัวอย่างแล้วส่งเฉพาะการเปลี่ยนแปลง การสื่อสารจึงไม่ค่อยเพิ่มแม้ค่าจะเปลี่ยนน้อย อย่างไรก็ตาม แม้ใช้ subscription การเปลี่ยนแปลงที่สั้นกว่า sampling interval ก็อาจไม่ถูกแจ้ง ข้อกำหนดอนุญาตให้สุ่มตัวอย่างเร็วกว่าที่ระบุ แต่ไม่ได้บังคับ11

ความแตกต่างระหว่างการอ่านซ้ำกับ subscriptionแผนภาพแสดงว่าการอ่านซ้ำนั้นไคลเอนต์กำหนดช่วงเวลาและส่งคำขอทุกครั้ง ส่วน subscription เซิร์ฟเวอร์สุ่มตัวอย่างตาม sampling interval และส่งเฉพาะการเปลี่ยนแปลง และทั้งสองวิธีอาจเก็บการเปลี่ยนแปลงที่สั้นกว่าช่วงเวลาไม่ได้ติดตามการเปลี่ยนแปลงของค่าอ่านซ้ำSubscribeไคลเอนต์กำหนดช่วงเวลาและขอทุกครั้งเซิร์ฟเวอร์สุ่มตัวอย่างและส่งเฉพาะการเปลี่ยนแปลงอาจเก็บการเปลี่ยนแปลงที่สั้นกว่าช่วงเวลาไม่ได้

ภาพ 7: ไม่ว่าวิธีใด ก็อาจเก็บการเปลี่ยนแปลงที่สั้นกว่าช่วงเวลาไม่ได้

อนุญาตการเชื่อมต่อด้วยใบรับรอง

ในการเชื่อมต่อ OPC UA ระดับการป้องกันข้อความเลือกด้วย MessageSecurityMode ค่าที่มีคือ None ที่ไม่ป้องกัน Sign ที่ลงนามอย่างเดียว และ SignAndEncrypt ที่ลงนามและเข้ารหัส16 ชุดอัลกอริทึมที่ใช้ลงนามและเข้ารหัสกำหนดด้วย security policy และในบทความนี้ใช้ Basic256Sha256

การลงนามและการเข้ารหัสใช้ใบรับรองของแต่ละแอปพลิเคชัน ไคลเอนต์และเซิร์ฟเวอร์ตรวจสอบใบรับรองของกันและกัน และปฏิเสธการเชื่อมต่อกับฝ่ายที่ไม่เชื่อถือ เซิร์ฟเวอร์ที่สร้างด้วยไลบรารี .NET ของ OPC Foundation จะไม่เปิดให้ใช้นโยบาย None ที่ไม่เข้ารหัสโดยค่าเริ่มต้น4

การเชื่อมต่อครั้งแรกหยุดที่ BadCertificateUntrusted

ในการรันครั้งแรก ไคลเอนต์ยังไม่เชื่อถือใบรับรองของเซิร์ฟเวอร์ การเชื่อมต่อจึงถูกปฏิเสธ นี่คือส่วนหนึ่งของผลลัพธ์

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.'

ไลบรารีนี้เก็บใบรับรองที่ปฏิเสธไว้ในที่เก็บ rejected เมื่อผู้ดูแลย้ายใบรับรองจากที่นั่นไปยังที่เก็บ trusted ไลบรารีจะเชื่อถือฝ่ายนั้น17 ครั้งนี้ได้ย้ายใบรับรองของเซิร์ฟเวอร์ที่อยู่ใน pki/rejected/certs ไปยัง pki/trusted/certs แล้วรันใหม่ จึงเชื่อมต่อได้ ก่อนย้ายให้ตรวจ thumbprint ซึ่งอยู่ในชื่อไฟล์ด้วย กับผู้ดูแลเซิร์ฟเวอร์4

ขั้นตอนจนถึงการเชื่อถือใบรับรองแผนภาพแสดงว่าในการเชื่อมต่อครั้งแรก ไคลเอนต์ปฏิเสธใบรับรองของเซิร์ฟเวอร์และเก็บไว้ในที่เก็บ rejected และเมื่อตรวจ thumbprint แล้วย้ายไปยังที่เก็บ trusted การเชื่อมต่อครั้งถัดไปจะเชื่อถือและเชื่อมต่อได้การเชื่อมต่อครั้งแรกBadCertificateUntrustedเก็บใบรับรองไว้ในที่เก็บ rejectedตรวจ thumbprint กับผู้ดูแลเซิร์ฟเวอร์ย้ายไปยังที่เก็บ trustedครั้งถัดไปเชื่อถือและเชื่อมต่อได้

ภาพ 8: เมื่อตรวจใบรับรองที่ถูกปฏิเสธแล้วย้ายไปยัง trusted ไคลเอนต์จะเชื่อถือฝ่ายนั้น

ยังมีการตั้งค่าชื่อ AutoAcceptUntrustedCertificates ที่รับใบรับรองที่ไม่เชื่อถือโดยอัตโนมัติ ค่าเริ่มต้นคือ false และเอกสารของไลบรารีเขียนว่ามีประโยชน์ในสภาพแวดล้อมการพัฒนา แต่ไม่ควรใช้ในระบบจริง17

NotAfter=2027-11-01 ในล็อกคือวันหมดอายุของใบรับรองของเซิร์ฟเวอร์จำลอง เป็นใบรับรองแบบ self-signed ที่สร้างด้วยฟังก์ชันของ asyncua และหมดอายุ 1 ปีหลังวันที่สร้าง ใบรับรองของอุปกรณ์ก็มีวันหมดอายุเช่นกัน จึงควรกำหนดขั้นตอนในการปฏิบัติงานไว้ว่าจะเปลี่ยนใบรับรองก่อนหมดอายุและให้อีกฝ่ายเชื่อถืออย่างไร การจัดการที่เก็บใบรับรองของ Windows อยู่ใน “คู่มือปฏิบัติที่เก็บใบรับรองของ Windows”

การยืนยันตัวตนของผู้ใช้

สิ่งที่ตรวจสอบด้วยใบรับรองคือแอปพลิเคชันกับแอปพลิเคชัน นอกจากนั้น เมื่อเปิดเซสชันจะส่งโทเค็นที่แทนผู้ใช้ด้วย ข้อกำหนดมี 4 แบบ ได้แก่ anonymous ชื่อผู้ใช้และรหัสผ่าน ใบรับรอง X.509 และโทเค็นที่ออกโดยภายนอก18 ไคลเอนต์ครั้งนี้เชื่อมต่อแบบ anonymous เซิร์ฟเวอร์รับโทเค็นแบบใดนั้นอยู่ในข้อมูล endpoint ของเซิร์ฟเวอร์19

จุดที่มักติดขัดในการเก็บข้อมูลหน้างาน

จากนี้จะยกปัญหาที่มักเกิดในระบบเก็บข้อมูลจากอุปกรณ์ โดยอิงจากการทดลองครั้งนี้และข้อกำหนด

ค่าในช่วงที่เซิร์ฟเวอร์หยุดทำงานจะไม่มาถึง

ระหว่าง subscribe ได้หยุดเซิร์ฟเวอร์ แล้วเริ่มใหม่หลังจากประมาณ 15 วินาที ManagedSession ของไลบรารีนี้จะเชื่อมต่อใหม่โดยอัตโนมัติเมื่อการเชื่อมต่อขาด20 ระหว่างที่เซิร์ฟเวอร์หยุด ไคลเอนต์พยายามเชื่อมต่อใหม่ซ้ำไปเรื่อย ๆ

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.

เมื่อเซิร์ฟเวอร์กลับมา การแจ้งเตือนก็กลับมาด้วย นี่คือส่วนหนึ่งของผลลัพธ์ของ subscription

  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 ถึง 13:41:34 ไม่มาถึงเลยแม้แต่รายการเดียว จำนวนชิ้นงานกลับจาก 78 เป็น 2 เนื่องจากเซิร์ฟเวอร์จำลองถูกเริ่มใหม่และนับใหม่ตั้งแต่ 0

ในตัวอย่างนี้ตัวเซิร์ฟเวอร์เองหยุดทำงาน ค่าในช่วงนั้นจึงไม่เหลืออยู่ที่ใดเลย กลไกการเชื่อมต่อใหม่จะคืนการเชื่อมต่อให้ แต่ไม่ได้เติมค่าในช่วงที่หยุดย้อนหลังให้

ถ้าขาดเพียงการสื่อสารและเซิร์ฟเวอร์ยังทำงานอยู่ จะต่างออกไป ข้อกำหนดมีบริการ Republish ที่ขอให้เซิร์ฟเวอร์ส่งข้อความแจ้งเตือนที่ส่งไม่ถึงซ้ำจากคิวสำหรับส่งซ้ำ21 คิวของ MonitoredItem ก็สะสมการแจ้งเตือนไว้ แต่ส่วนที่เกินขนาดคิวจะถูกทิ้ง13 การทดลองครั้งนี้ไม่ได้ทดสอบกรณีนี้ ไม่ว่ากรณีใด ให้บันทึกเวลาที่การเชื่อมต่อขาดและเวลาที่เชื่อมต่อใหม่ไว้ เพื่อให้รู้ว่าค่าไม่มาในช่วงเวลาใด การเชื่อมต่อใหม่และการออกแบบล็อกของอุปกรณ์ที่สื่อสารแบบอนุกรมกล่าวถึงใน “Serial Communication App Pitfalls” ด้วย

การบันทึกเมื่อหยุดเซิร์ฟเวอร์แผนภาพแสดงว่าเมื่อเซิร์ฟเวอร์หยุดระหว่าง subscribe ไคลเอนต์จะพยายามเชื่อมต่อใหม่ซ้ำ เมื่อเซิร์ฟเวอร์กลับมาการแจ้งเตือนก็กลับมา แต่ค่าในช่วงที่หยุดไม่มาถึง จึงต้องบันทึกเวลาที่ขาดและเวลาที่เชื่อมต่อใหม่กำลัง subscribeเซิร์ฟเวอร์หยุดไคลเอนต์พยายามเชื่อมต่อใหม่ซ้ำเซิร์ฟเวอร์กลับมาและการแจ้งเตือนกลับมาค่าในช่วงที่หยุดไม่มาถึงบันทึกเวลาที่ขาดและเวลาที่เชื่อมต่อใหม่

ภาพ 9: การเชื่อมต่อใหม่คืนการเชื่อมต่อให้ แต่ค่าในช่วงที่เซิร์ฟเวอร์หยุดไม่มาถึง

ควรบันทึกเวลาประทับตัวใด

ข้อกำหนดระบุว่า SourceTimestamp คือเวลาที่ต้นทางของข้อมูลประทับให้กับค่า และควรแสดงเวลาที่ค่าเปลี่ยน ส่วน ServerTimestamp คือเวลาที่เซิร์ฟเวอร์ได้รับค่าหรือรู้ว่าค่านั้นถูกต้อง9 เซิร์ฟเวอร์จำลองครั้งนี้สร้างค่าขึ้นเอง ทั้งสองเวลาจึงตรงกัน

ถ้าต้องการรู้เวลาที่ค่าเปลี่ยนในอุปกรณ์ ให้ใช้ SourceTimestamp แต่เวลานั้นประทับด้วยนาฬิกาของต้นทาง ถ้านาฬิกาของอุปกรณ์หรือเซิร์ฟเวอร์คลาดเคลื่อน บันทึกก็คลาดเคลื่อนตาม ถ้าจะนำข้อมูลที่เก็บมาไปเทียบกับบันทึกอื่น ให้เก็บเวลาของ PC ที่รับข้อมูลไว้คู่กันด้วย เพื่อตรวจความคลาดเคลื่อนได้ภายหลัง การซิงค์เวลาฝั่ง Windows อยู่ใน “Windows Time Synchronization (w32time) and Business Systems”

อย่าใช้ค่าที่สถานะไม่ใช่ Good เป็นตัวเลขโดยตรง

เมื่อรหัสสถานะเป็น Bad ให้ไม่สนใจค่า9 เมื่อเป็น Uncertain ค่าอาจใช้ไม่ได้ขึ้นกับการใช้งาน10 ก่อนนำไปใส่กราฟหรือรวมผล ให้คัดแยกด้วยรหัสสถานะ และกำหนดว่าจะจัดการค่าที่ไม่ใช่ Good อย่างไร บิต Overflow เมื่อคิวเต็มก็ถูกตั้งไว้ในรหัสสถานะด้วย13 การแสดงสถานะของอุปกรณ์บนหน้าจอกล่าวถึงใน “Best Practices for Checking and Displaying External Device State”

เปิดพอร์ตในไฟร์วอลล์

พอร์ตของ OPC UA ที่ลงทะเบียนกับ IANA คือ 4840/TCP3 แต่ไม่ใช่ทุกเซิร์ฟเวอร์ที่ใช้หมายเลขนี้ ตัวอย่าง .NET ของ OPC Foundation ใช้พอร์ตอย่าง 625414 ให้ตรวจหมายเลขพอร์ตจาก URL ของ endpoint ของอุปกรณ์ และอนุญาตการเชื่อมต่อไปยังพอร์ตนั้น ถ้าเซิร์ฟเวอร์ทำงานบน Windows ให้อนุญาตใน Windows Firewall ของเครื่องนั้น และถ้ามีอุปกรณ์เครือข่ายอยู่ระหว่างทาง ให้อนุญาตที่อุปกรณ์นั้น วิธีสร้างกฎขาเข้าอยู่ใน “ไฟร์วอลล์ Windows กับแอปธุรกิจ”

จุดที่มักติดขัดในการเก็บข้อมูลแผนภาพแบ่งจุดที่มักติดขัดในการเก็บข้อมูลจากอุปกรณ์เป็น 2 กลุ่ม คือเนื้อหาของบันทึก (ค่าในช่วงที่เซิร์ฟเวอร์หยุดไม่มาถึง ควรบันทึกเวลาประทับตัวใด และการจัดการค่าที่ไม่ใช่ Good) กับการเตรียมการเชื่อมต่อ (พอร์ตของไฟร์วอลล์ และวันหมดอายุของใบรับรอง)จุดที่มักติดขัดในการเก็บข้อมูลเนื้อหาของบันทึกการเตรียมการเชื่อมต่อค่าในช่วงที่หยุดไม่มาถึงเวลาประทับตัวใดการจัดการค่าที่ไม่ใช่ Goodพอร์ตของไฟร์วอลล์วันหมดอายุของใบรับรอง

ภาพ 10: แม้เชื่อมต่อได้แล้ว ก็ยังต้องตรวจช่องว่างของบันทึก เวลา สถานะ พอร์ต และใบรับรอง

การเลือกระหว่าง Modbus/TCP กับการสื่อสารเฉพาะของผู้ผลิต

การสื่อสารกับ PLC และอุปกรณ์ นอกจาก OPC UA แล้วยังมี Modbus/TCP และการสื่อสารที่ผู้ผลิต PLC จัดเตรียมไว้

ข้อกำหนดของ Modbus จัดการข้อมูลเป็น 4 ตาราง ได้แก่ Discrete Input ขนาด 1 บิตที่อ่านได้อย่างเดียว Coil ขนาด 1 บิตที่อ่านและเขียนได้ Input Register ขนาด 16 บิตที่อ่านได้อย่างเดียว และ Holding Register ขนาด 16 บิตที่อ่านและเขียนได้22 IANA ลงทะเบียน 502/TCP ไว้สำหรับโปรโตคอลแอปพลิเคชันของ Modbus (mbap)3

ตารางเหล่านี้เก็บได้เพียงบิตและเวิร์ดขนาด 16 บิต จึงต้องตรวจจากเอกสารของอุปกรณ์ว่าแอดเดรสใดเก็บค่าอะไรในรูปแบบใด แล้วเขียนไว้ฝั่งไคลเอนต์ ส่วน OPC UA อย่างที่เห็นมา เซิร์ฟเวอร์เปิดเผยตัวแปรที่มีชื่อไว้ใน address space และไคลเอนต์ตรวจดูได้ด้วย Browse ค่ามีรหัสสถานะและเวลาประทับมาด้วย และการเชื่อมต่อมีการอนุญาตด้วยใบรับรอง

จะใช้แบบใดมักขึ้นอยู่กับว่าอุปกรณ์มีอะไรให้ ถ้าอุปกรณ์มีเซิร์ฟเวอร์ OPC UA ก็ใช้ OPC UA ถ้ามีแต่ Modbus/TCP ก็ทำตารางจับคู่แอดเดรสแล้วอ่าน ถ้ามีเพียงการสื่อสารหรือ SDK ของผู้ผลิต ก็ทำตามเอกสารนั้น การเลือกวิธีสื่อสารกับอุปกรณ์ USB อยู่ใน “How to Work with USB Devices from a Windows App”

วิธีเลือกการสื่อสารกับอุปกรณ์แผนภาพแสดงว่าถ้าอุปกรณ์มีเซิร์ฟเวอร์ OPC UA ให้ browse ตัวแปรด้วย OPC UA แล้วเก็บข้อมูล ถ้ามีแต่ Modbus/TCP ให้ตรวจแอดเดรสและรูปแบบจากเอกสารแล้วอ่านรีจิสเตอร์ และถ้ามีแต่การสื่อสารของผู้ผลิตให้ทำตามเอกสารของผู้ผลิตเซิร์ฟเวอร์ OPC UAModbus/TCP เท่านั้นการสื่อสารของผู้ผลิตเท่านั้นการสื่อสารที่อุปกรณ์มีให้มีอะไรบ้างBrowse ตัวแปรแล้วเก็บข้อมูลตรวจแอดเดรสและรูปแบบจากเอกสารทำตามเอกสารและ SDK ของผู้ผลิตอ่านรีจิสเตอร์แล้วแปลงเป็นค่า

ภาพ 11: จะเก็บข้อมูลด้วยวิธีใด มักขึ้นอยู่กับว่าอุปกรณ์มีอะไรให้

บทความที่เกี่ยวข้อง

เอกสารอ้างอิง

  1. OPC Foundation, Classic. OPC Classic อยู่บนพื้นฐานของ COM/DCOM ข้อกำหนด DA, A&E และ HDA การเผยแพร่ OPC UA ในปี 2008 และเหตุผลของการสร้างใหม่ ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. ส่วนที่ 1 ของชุด IEC 62541 ที่กล่าวถึงแนวคิดและภาพรวมของ OPC UA (ถูกแทนที่ด้วยฉบับใหม่แล้ว) ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. การลงทะเบียน opcua-tcp บน 4840/TCP และ mbap บน 502/TCP ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. วิธีเขียนไคลเอนต์รุ่น 2.0 การที่ ReadValueAsync โยน exception เมื่อเป็น Bad ที่เก็บ rejected และ trusted การที่เซิร์ฟเวอร์ไม่เปิด None โดยค่าเริ่มต้น และหมายเลขพอร์ตของตัวอย่าง ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). ไลบรารี OPC UA ของ Python ที่ใช้สร้างเซิร์ฟเวอร์อุปกรณ์จำลอง ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. บริการที่ไล่ตาม reference ของโหนด ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. การแทน URI ของเนมสเปซด้วยตัวเลขภายในบริการ และตัวเลขนั้นคือตำแหน่งใน NamespaceArray ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. บริการ Read และ Write ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. นิยามของค่า รหัสสถานะ SourceTimestamp และ ServerTimestamp ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain และ Bad ที่บอกด้วยบิตบนสุด 2 บิต ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. sampling interval เป็นรอบแบบ best effort ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. ถ้าไม่มีตัวกรองจะแจ้งเมื่อค่าหรือสถานะเปลี่ยน ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. ขนาดคิว วิธีทิ้ง และบิต Overflow ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publishing interval และ keep-alive ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync และ sampling interval กับขนาดคิวใน MonitoredItemOptions ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. ความหมายของ None, Sign และ SignAndEncrypt ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. การปฏิบัติที่ย้ายใบรับรองจากที่เก็บ rejected ไปยังที่เก็บที่เชื่อถือ และการไม่ใช้ AutoAcceptUntrustedCertificates ในระบบจริง ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. 4 แบบ ได้แก่ anonymous ชื่อผู้ใช้ X.509 และโทเค็นที่ออกให้ ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. รายการโทเค็นผู้ใช้ที่ endpoint รับ (userIdentityTokens) ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. ManagedSession มีกลไกการเชื่อมต่อใหม่ในตัว ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. บริการที่ขอให้ส่งข้อความแจ้งเตือนซ้ำจากคิวสำหรับส่งซ้ำของ subscription ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (สำเนาที่วางไว้บนเซิร์ฟเวอร์ของมหาวิทยาลัยชิคาโก). โมเดลข้อมูลในหัวข้อ 4.3 (Discrete Input, Coil, Input Register, Holding Register) ↩

บทความล่าสุดที่มีแท็กเดียวกัน เพื่อเจาะลึกหัวข้อใกล้เคียง

แนวปฏิบัติมัลติเธรดเชิงปฏิบัติ: ฉบับ .NET — สิ่งที่ต้องตัดสินก่อนเพิ่มเธรด

เรียบเรียงหลักออกแบบที่กันไม่ให้โค้ดมัลติเธรด .NET/C# «แครชหรือค้างเป็นครั้งคราว»: อย่าสร้างเธรดเอง ให้ขี่ Task ลดสถานะที่เปลี่ยนแปลงร่วม...

ใช้ WMI/CIM จาก C# และ PowerShell — คู่มือปฏิบัติการดึงข้อมูลฮาร์ดแวร์ เฝ้าโพรเซส และสอบถามระยะไกล

หมายเลขซีเรียลของพีซี การเฝ้าพื้นที่ว่างดิสก์ และการตรวจจับการเริ่มโพรเซส คำตอบมาตรฐานคือ WMI/CIM บทความนี้อธิบายการใช้ cmdlet CIM เช่น G...

เชิงลึกของ Windows I/O (ตอนที่ 4) — Cache Manager: WriteFile ของคุณไปถึงดิสก์เมื่อใด

ตอนที่ 4 ของซีรีส์ที่อธิบาย Cache Manager ของ Windows ด้วยแผนภาพ จัดแคชที่อิมพลีเมนต์เป็นการแมปไฟล์ การอ่านล่วงหน้ากับการเขียนแบบหน่วง กา...

Named Pipe ในทางปฏิบัติ — IPC มาตรฐานของ Windows ตั้งแต่การออกแบบถึงความปลอดภัย

คู่มือเชิงปฏิบัติเรื่อง named pipe ซึ่งเป็นการสื่อสารระหว่างโปรเซสมาตรฐานของ Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิ ทั้งการเลือกระหว่...

Spurious Wakeup — ทำไม condition variable จึงตื่น "โดยไม่ถูกแจ้ง" และวิธีรออย่างถูกต้องบน Windows

การรอของ condition variable สามารถกลับได้แม้ไม่มีการแจ้งมาถึง (spurious wakeup) บทความนี้อธิบายจากการอิมพลีเมนต์ของ Windows ว่าทำไมข้อกำห...

นี่คือหน้าหัวข้อที่ใกล้เคียงกับเรื่องนี้ จากบทความนี้ คุณสามารถไปต่อยังบริการที่เกี่ยวข้องและบทความอื่น ๆ ได้

บทความนี้เชื่อมโยงกับหน้าบริการต่อไปนี้ โปรดเริ่มดูจากหน้าที่ใกล้เคียงที่สุด

พัฒนาแอป Windows

หากต้องการสร้างแอป Windows ที่เก็บข้อมูลจากอุปกรณ์ รวมถึงการตั้งค่า subscription การเชื่อมต่อใหม่ และการบันทึกเวลาประทับกับสถานะ สามารถรับเป็นงานพัฒนาแอป Windows ได้

ที่ปรึกษาเทคนิคและรีวิวการออกแบบ

หากต้องการตัดสินใจว่าจะเก็บข้อมูลด้วย OPC UA หรือใช้ Modbus/TCP หรือการสื่อสารเฉพาะของผู้ผลิต และจะจัดการใบรับรองอย่างไร สามารถรับเป็นการให้คำปรึกษาทางเทคนิคและการรีวิวการออกแบบได้

คำถามที่พบบ่อย

คำถามที่มักพบในการปรึกษาเกี่ยวกับหัวข้อของบทความนี้

OPC UA กับ OPC Classic (OPC DA) ต่างกันอย่างไร
OPC Classic เป็นข้อกำหนดที่แลกเปลี่ยนข้อมูลผ่าน COM/DCOM ของ Windows ส่วน OPC UA เป็นข้อกำหนดที่เผยแพร่ในปี 2008 ไม่ขึ้นกับระบบปฏิบัติการใดโดยเฉพาะ และรวมความสามารถของ Classic ไว้ด้วยกัน ไคลเอนต์ OPC UA ทำงานนอก Windows ได้ด้วย และไม่ต้องตั้งค่า DCOM
เมื่อเก็บค่าด้วย OPC UA อ่านเป็นช่วงเวลาคงที่ก็พอหรือไม่
ถ้าค่ามีจำนวนน้อยและช่วงเวลายาว การอ่านก็เก็บข้อมูลได้ หากต้องการติดตามการเปลี่ยนแปลงของค่า ยังมีวิธีใช้ subscription เพื่อให้เซิร์ฟเวอร์เป็นผู้สุ่มตัวอย่างและแจ้งการเปลี่ยนแปลงให้ ไม่ว่าแบบไหน ให้บันทึกรหัสสถานะและเวลาประทับที่ส่งมาพร้อมค่าด้วย
ทำไมเชื่อมต่อแล้วได้ BadCertificateUntrusted
เพราะไคลเอนต์ยังไม่เชื่อถือใบรับรองของเซิร์ฟเวอร์ ไลบรารี .NET ของ OPC Foundation จะเก็บใบรับรองที่ปฏิเสธไว้ในที่เก็บ rejected ให้ตรวจ thumbprint ของใบรับรองกับผู้ดูแลเซิร์ฟเวอร์ก่อน แล้วจึงย้ายไปยังที่เก็บ trusted การตั้งค่าที่รับใบรับรองโดยอัตโนมัติให้ใช้เฉพาะในสภาพแวดล้อมการพัฒนาเท่านั้น
OPC UA ใช้พอร์ตหมายเลขอะไร
IANA ลงทะเบียน 4840/TCP ไว้ในชื่อ opcua-tcp แต่เซิร์ฟเวอร์จริงบางครั้งใช้พอร์ตอื่น จึงต้องตรวจในการตั้งค่าของอุปกรณ์ ถ้าเซิร์ฟเวอร์ทำงานบน Windows ต้องอนุญาตการสื่อสารไปยังพอร์ตนั้นใน Windows Firewall และถ้ามีอุปกรณ์เครือข่ายอยู่ระหว่างทาง ก็ต้องอนุญาตที่อุปกรณ์นั้นด้วย

โปรไฟล์ผู้เขียน

หน้าแนะนำผู้เขียนบทความ

Go Komura

ผู้แทนของ KomuraSoft LLC

เชี่ยวชาญด้านการพัฒนาซอฟต์แวร์ Windows ที่ปรึกษาเทคนิค และการตรวจสอบบั๊ก โดยเฉพาะงานที่มีระบบเดิมอยู่ และการตรวจสอบความขัดข้องที่มองเห็นสาเหตุได้ยาก

ลิงก์สาธารณะ

กลับไปยังบล็อก