Úvod do OPC UA ── Sběr dat z PLC a zařízení v aplikaci pro Windows

· · Windows, OPC UA, Výroba, PLC, C#, .NET, Komunikace, Certifikáty

Sdílet na X
Historie změn (pouze první verze, zveřejněno 1. 11. 2026)
První zveřejnění
Citace tohoto článku(DOI: 10.5281/zenodo.23968431)

Tento článek je archivován na Zenodo. Níže uvádíme jak DOI, které vždy odkazuje na nejnovější verzi, tak DOI přiřazené právě zobrazené verzi.

Go Komura (2026). Úvod do OPC UA ── Sběr dat z PLC a zařízení v aplikaci pro Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968431 https://comcomponent.com/cs/blog/opc-ua-windows-data-collection/

DOI (nejnovější verze)
10.5281/zenodo.23968431
DOI (tato verze)
10.5281/zenodo.23968432

Když sbíráte data z PLC nebo zařízení do aplikace pro Windows a zařízení má server OPC UA, můžete se možná vyhnout psaní samostatné komunikace pro každého výrobce. Připojit se a číst hodnoty ale nestačí. Pokud neznáte stav a časová razítka, která se vracejí s každou hodnotou, subskripce, které oznamují změny, povolování připojení certifikáty a způsob, jak se zotavit z odpojení, nebudete moci nasbíraným datům později věřit.

Tento článek vysvětluje ty části OPC UA, které jsou potřeba pro sběr dat, spolu s výsledky skutečného spuštění klienta v C#. Klient používá knihovnu OPC Foundation pro .NET a na druhé straně je simulovaný server zařízení. Proti konkrétnímu PLC nebo zařízení jsem to nezkoušel. Nastavení specifická pro zařízení si ověřte v jeho dokumentaci.

Sběru obrázků z kamer GigE Vision a USB3 Vision se věnuje článek „Dropped Frames with GigE Vision and USB3 Vision Cameras“ a zařízením se sériovou komunikací článek „Serial Communication App Pitfalls“.

Rozdíl mezi OPC UA a OPC Classic

Specifikace OPC Classic vyměňují data mezi softwarovými komponentami pomocí COM/DCOM ve Windows. OPC DA pro procesní data, OPC A&E pro alarmy a OPC HDA pro historii jsou definovány jako samostatné specifikace1.

OPC Foundation zveřejnila OPC UA (Unified Architecture) v roce 2008. OPC UA je architektura orientovaná na služby, která nezávisí na konkrétní platformě a sjednocuje funkce jednotlivých specifikací Classic. Jako důvody přepracování OPC Foundation uvádí, že Microsoft začal místo COM/DCOM upřednostňovat multiplatformní mechanismy, že bylo potřeba běžet i na systémech jiných než od Microsoftu a ve vestavěných zařízeních a že bylo potřeba se bezpečně připojovat přes firewally1.

Specifikace OPC UA je normalizována také v IEC jako řada IEC 625412. Jako komunikační port má v IANA registrováno číslo 4840/TCP pod názvem opcua-tcp3.

Rozdíl mezi OPC Classic a OPC UAObrázek ukazuje, že OPC Classic stojí na COM a DCOM ve Windows a dělí se na samostatné specifikace DA, A&E a HDA, kdežto OPC UA sjednocuje jejich funkce do jedné specifikace nezávislé na platformě.Funkce se sjednocujíOPC ClassicBěží nad COM/DCOM (Windows)Rozděleno na specifikace DA, A&E a HDAOPC UA (2008)Nezávisí na konkrétní platforměNormalizováno jako IEC 62541

Obrázek 1: OPC Classic tvoří samostatné specifikace nad COM/DCOM a OPC UA je sjednocuje do jedné.

Pokud má stávající zařízení jen server OPC DA, potřebujete na své straně mechanismus, který používá COM/DCOM. Tento článek se zabývá pouze OPC UA.

Na diagramu plná čára označuje vztah, který platí vždy, a přerušovaná čára vztah podmíněný (podmínky jsou uvedeny u jednotlivých vztahů na podrobné stránce). Úplný seznam vztahů (celkem 13, s doklady a mírou jistoty) a definice hlavních pojmů najdete na podrobné stránce znalostní mapy (v japonštině). Data: JSON-LD / Turtle

Vyzkoušená sestava

Spustil jsem tuto sestavu.

Role Co jsem použil
Klient C#, .NET 10 SDK 10.0.114, balíček NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Server Simulované zařízení vytvořené v Pythonu 3.13 a asyncua 2.1.0 (port 4840 na stejném počítači)
OS Linux (spuštění ve Windows jsem nezkoušel)

Knihovna klienta je implementace pro .NET, kterou zveřejňuje OPC Foundation. Při používání řady 2.0 jsem se řídil postupem z Getting started ve stejném repozitáři4.

Vyzkoušená sestavaObrázek ukazuje sestavu, v níž se klient v C# používající knihovnu OPC Foundation pro .NET připojuje šifrovaným spojením k simulovanému serveru zařízení asyncua, který naslouchá na portu 4840.opc.tcp, SignAndEncryptKlient v C# (LineCollector)Simulovaný server zařízení (asyncua)Knihovna OPC Foundation pro .NET 2.0.1Čtyři proměnné pod Line1

Obrázek 2: Na stejném počítači se klient v C# připojil k simulovanému serveru zařízení.

Simulovaný server zařízení

Místo skutečného zařízení jsem pomocí asyncua vytvořil malý server5. Pod objektem Line1 má čtyři proměnné: teplotu (Temperature), počet kusů (PartCount), běh (Running) a žádanou hodnotu (Setpoint). Teplota se mění každých 0,5 sekundy a počet kusů každé 2 sekundy. Pro zabezpečení jsem připravil dvě možnosti: bez šifrování (None) a podepisování se šifrováním (SignAndEncrypt s Basic256Sha256).

# Simulované zařízení pro ověření funkce. Nenahrazuje produkční zařízení ani 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())

Tento server přijímá certifikáty připojujících se klientů bez kontroly. Jde o testovací nastavení, které se liší od chování produkčních zařízení.

Klient v C#

Do projektového souboru jsem přidal tyto dva balíčky.

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

Klient se v tomto pořadí připojí, prochází, čte, zapisuje a přihlašuje k odběru. Význam jednotlivých částí vysvětlují následující oddíly.

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

        // Index jmenného prostoru určuje server. Zjisti ho podle URI a z něj sestav 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);

        // Procházení: vypiš proměnné pod 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}");
        }

        // Čtení: sleduj nejen hodnotu, ale i stav a dvě časová razítka
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Čtení neexistujícího uzlu vrátí místo hodnoty stav Bad
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Zápis: nastav Setpoint na 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]}");

        // Subskripce: požádej server o vzorkování každých 500 ms a oznamování změn
        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");

Při prvním spuštění se připojit nepodařilo. Důvod vysvětluje oddíl „Povolení připojení pomocí certifikátů“. Níže je výstup po tom, co jsem certifikátu serveru začal důvěřovat. Varovné protokoly knihovny jsou vynechány.

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

Časy jsou v UTC.

Data zařízení se zobrazují jako uzly v adresním prostoru

Server OPC UA zveřejňuje svá data jako soubor uzlů. Tento soubor se nazývá adresní prostor. Uzly jsou různých druhů, například objekty, které představují složky nebo zařízení, a proměnné, které mají hodnotu, a uzly jsou navzájem propojeny odkazy. Klient sleduje odkazy pomocí služby Browse a zjišťuje, jaké uzly existují6.

Ve výstupu výše klient prochází hierarchické odkazy od Line1 a nachází čtyři proměnné.

Adresní prostor simulovaného zařízeníObrázek ukazuje, že pod složkou Objects je objekt Line1 a pod ním čtyři proměnné Temperature, PartCount, Running a Setpoint.ObjectsLine1 (objekt)Temperature (proměnná)PartCount (proměnná)Running (proměnná)Setpoint (proměnná, zapisovatelná)

Obrázek 3: Adresní prostor simulovaného zařízení. Klient nachází uzly sledováním odkazů pomocí služby Browse.

NodeId se skládá z jmenného prostoru a identifikátoru

Uzly se identifikují pomocí NodeId. Ve výstupu ns=2;s=Line1.Temperature znamená, že index jmenného prostoru je 2 a identifikátor je řetězec Line1.Temperature.

Jmenný prostor je ve skutečnosti URI. Specifikace stanoví, že URI jmenných prostorů se ve službách vyjadřují čísly a že toto číslo odpovídá pozici v poli NamespaceArray serveru. Index 0 je jmenný prostor OPC UA http://opcfoundation.org/UA/7.

Proto číslo indexu nepište do kódu, ale zjišťujte index podle URI. Kód výše získá z URI pomocí session.NamespaceUris.GetIndex(LineNamespace) hodnotu 2 a z ní sestaví NodeId.

Sestavení NodeId z URI jmenného prostoruObrázek ukazuje, že klient vyhledá URI jmenného prostoru v poli NamespaceArray serveru, získá index a spojením tohoto indexu s identifikátorem sestaví NodeId.URI jmenného prostoruNamespaceArray serveruIndex (v tomto příkladu 2)NodeId ns=2;s=Line1.TemperatureIdentifikátor Line1.Temperature

Obrázek 4: Index jmenného prostoru zjistěte podle URI a spojte ho s identifikátorem do NodeId.

Čtení a zápis vracejí s hodnotou i stav a časová razítka

Hodnoty proměnných se čtou a zapisují službami Read a Write8. Čtení nevrací jen hodnotu. Vrací strukturu DataValue, která obsahuje hodnotu, stavový kód, časové razítko zdroje a časové razítko serveru9.

Položka Význam
Hodnota Hodnota proměnné. Pokud stavový kód signalizuje chybu, hodnota se ignoruje a server ji nastaví na null
Stavový kód (StatusCode) Zda server dokázal hodnotu získat a poskytnout
SourceTimestamp Čas, který k hodnotě připojil zdroj dat
ServerTimestamp Čas, kdy server hodnotu přijal nebo zjistil, že je správná

Horní dva bity stavového kódu vyjadřují Good (úspěch), Uncertain (částečný úspěch, pro některé účely nemusí být použitelné) nebo Bad (neúspěch)10. Good [0x00000000] ve výstupu je Good.

Když jsem četl neexistující Line1.Pressure, vrátilo se místo hodnoty BadNodeIdUnknown [0x80340000]. Metoda ReadValueAsync této knihovny při stavu Bad vyvolá výjimku4. Pokud systém, který hodnoty zaznamenává, ukládá ke každé hodnotě i stavový kód, můžete později rozlišit hodnoty zaznamenané v době, kdy byl stav Bad.

Co obsahuje DataValueObrázek ukazuje, že DataValue vrácená čtením nebo oznámením obsahuje čtyři položky, tedy hodnotu, stavový kód, časové razítko zdroje a časové razítko serveru, a že při stavovém kódu Bad se hodnota ignoruje.Pokud BadDataValueHodnotaStavový kódSourceTimestamp (čas zdroje)ServerTimestamp (čas serveru)Hodnotu ignorovat

Obrázek 5: Výsledek čtení obsahuje nejen hodnotu, ale i stavový kód a dvě časová razítka.

Při zápisu jsem do Setpoint zapsal 32,5 a jako výsledný stavový kód se vrátil Good. O tom, které proměnné jsou zapisovatelné, rozhoduje server. V tomto simulovaném serveru je zapisovatelná jen Setpoint. Zda umožnit přepisování žádaných hodnot zařízení zvenčí, je otázkou bezpečnostního návrhu zařízení, a proto se jí tento článek nezabývá.

Oznamování změn: subskripce a MonitoredItem

Ke sledování změn hodnot může klient opakovat Read v pevném intervalu. OPC UA má kromě toho mechanismus, který sledování hodnot přenechá serveru a nechá si od něj změny oznamovat. Tímto mechanismem jsou subskripce a MonitoredItem.

MonitoredItem je jedna sledovaná položka a má vzorkovací interval. Vzorkovací interval je nejrychlejší interval, ve kterém server vzorkuje hodnoty ze zdroje, a specifikace ho popisuje jako cyklus „best effort“11. Když vzorek splní podmínky filtru, vytvoří se oznámení. Pokud filtr není zadán, oznámení se odešle, když se změní hodnota nebo stav12.

Oznámení se nejprve dostanou do fronty MonitoredItem. Předem se rozhodne, zda se při přetečení fronty zahodí nejstarší, nebo nejnovější položka, a pokud je velikost fronty 2 nebo více, nastaví se ve stavovém kódu bit Overflow, který zahození signalizuje13.

Subskripce sdružuje několik MonitoredItem a v každém publikačním intervalu posílá klientovi oznámení, která ještě nebyla odeslána. Když uplynou cykly bez oznámení k odeslání, pošle keep-alive, aby ukázala, že subskripce stále žije14.

Jak fungují subskripce a MonitoredItemObrázek ukazuje, že MonitoredItem vzorkuje hodnotu zdroje ve vzorkovacím intervalu a oznámení, která projdou filtrem, ukládá do fronty, a že subskripce je v publikačním intervalu posílá společně klientovi a při absenci oznámení posílá keep-alive.Vzorkovací intervalPublikační intervalPokud nejsou oznámeníZdroj (hodnota zařízení)MonitoredItemFiltr (výchozí: změna hodnoty nebo stavu)Fronta (při zaplnění Overflow)SubskripceKlientKeep-alive

Obrázek 6: MonitoredItem vzorkuje a ukládá do fronty a subskripce posílá oznámení klientovi společně.

Kód výše se přihlašuje k odběru Temperature a PartCount se vzorkovacím intervalem 500 milisekund a velikostí fronty 515. Ve výstupu přichází teplota přibližně každých 0,5 sekundy a počet kusů přibližně každé 2 sekundy. První dvě oznámení byla hodnoty, které měly jednotlivé proměnné v okamžiku zahájení subskripce. Čas 13:40:50.801 u počtu kusů je okamžik, kdy se jeho hodnota změnila, ještě před zahájením subskripce. Protože oznámení zobrazují časové razítko zdroje, ukazují čas, kdy se hodnota změnila, ne čas, kdy oznámení přišlo.

Volba mezi opakovaným čtením a subskripcí

Při opakovaném čtení si interval určuje klient a mechanismus je jednoduchý. Na druhou stranu se požadavek a odpověď posílají pokaždé, i když se hodnota nezměnila, a změny kratší než interval se přehlédnou. U subskripce server vzorkuje a posílá jen změny, takže provoz příliš neroste ani u hodnot, které se mění zřídka. I u subskripce se však změny kratší než vzorkovací interval nemusí oznámit. Specifikace vzorkování rychlejší, než je požadováno, dovoluje, ale nevyžaduje11.

Rozdíl mezi opakovaným čtením a subskripcíObrázek ukazuje, že při opakovaném čtení určuje interval klient a pokaždé posílá požadavek, že při subskripci server vzorkuje ve vzorkovacím intervalu a posílá jen změny a že obě metody mohou přehlédnout změny kratší než interval.Sledování změn hodnotOpakované čteníSubskripceKlient určuje interval a pokaždé žádáServer vzorkuje a posílá jen změnyZměny kratší než interval se mohou ztratit

Obrázek 7: U obou metod se mohou změny kratší než interval ztratit.

Povolení připojení pomocí certifikátů

V připojení OPC UA se míra ochrany zpráv volí pomocí MessageSecurityMode. Hodnoty jsou None, která neposkytuje ochranu, Sign, která jen podepisuje, a SignAndEncrypt, která podepisuje a šifruje16. Kombinaci algoritmů použitých k podepisování a šifrování určuje zásada zabezpečení a tento článek používá Basic256Sha256.

K podepisování a šifrování se používá certifikát každé aplikace. Klient a server si navzájem kontrolují certifikáty a odmítají připojení od stran, kterým nedůvěřují. Server vytvořený pomocí knihovny OPC Foundation pro .NET ve výchozím nastavení nenabízí nešifrovanou zásadu None4.

První připojení skončilo chybou BadCertificateUntrusted

Při prvním spuštění klient nedůvěřoval certifikátu serveru, takže bylo připojení odmítnuto. Zde je část výstupu.

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

Tato knihovna ukládá odmítnuté certifikáty do úložiště rejected. Když správce certifikát přesune odtud do úložiště trusted, knihovna začne této straně důvěřovat17. Tentokrát jsem certifikát serveru přesunul z pki/rejected/certs do pki/trusted/certs, spustil jsem klienta znovu a připojení se podařilo. Před přesunutím ověřte se správcem serveru kryptografický otisk, který je také součástí názvu souboru4.

Postup, jak začít důvěřovat certifikátuObrázek ukazuje, že při prvním připojení klient certifikát serveru odmítne a uloží ho do úložiště rejected a že po ověření kryptografického otisku a přesunutí do úložiště trusted mu další připojení důvěřuje a uspěje.První připojeníBadCertificateUntrustedCertifikát se uloží do úložiště rejectedOvěřit otisk se správcem serveruPřesunout do úložiště trustedDalší připojení důvěřuje a uspěje

Obrázek 8: Po ověření odmítnutého certifikátu a jeho přesunutí do trusted klient této straně důvěřuje.

Existuje také nastavení AutoAcceptUntrustedCertificates, které nedůvěryhodné certifikáty přijímá automaticky. Výchozí hodnota je false a dokumentace knihovny uvádí, že je užitečné ve vývojových prostředích, ale v produkci by se nemělo používat17.

NotAfter=2027-11-01 v protokolu je datum vypršení platnosti certifikátu simulovaného serveru. Jde o certifikát podepsaný sám sebou, vytvořený funkcí asyncua, jehož platnost vyprší rok po dni vytvoření. Platnost certifikátů zařízení také vyprší, proto si v rámci provozu stanovte, jak je před vypršením vyměnit a zajistit, aby druhá strana důvěřovala novým. Práci s úložištěm certifikátů ve Windows popisuje článek „The Windows Certificate Store in Practice“.

Ověřování uživatelů

Certifikáty ověřují aplikace. Nezávisle na tom se při otevírání relace předává token, který identifikuje uživatele. Specifikace definuje čtyři druhy: anonymní, uživatelské jméno a heslo, certifikát X.509 a externě vydaný token18. Klient v tomto článku se připojil anonymně. Které tokeny server přijímá, je uvedeno v informacích o koncovém bodu serveru19.

Kde sběr dat v provozu selhává

Dále uvádím problémy, které v systémech pro sběr dat ze zařízení často vznikají, na základě tohoto experimentu a specifikace.

Hodnoty z doby, kdy server stál, nedorazí

Během subskripce jsem server zastavil a asi po 15 sekundách ho znovu spustil. ManagedSession této knihovny se při výpadku spojení automaticky znovu připojuje20. Zatímco server stál, klient se opakovaně pokoušel znovu připojit.

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.

Když se server vrátil, vrátila se i oznámení. Zde je část výstupu subskripce.

  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]

Z doby mezi 13:41:13 a 13:41:34 nedorazila ani jedna hodnota. Počet kusů se vrátil ze 78 na 2. Je to proto, že restart simulovaného serveru způsobil, že začal počítat znovu od 0.

V tomto příkladu stál samotný server, takže hodnoty z této doby nikde nezůstaly. Mechanismus opětovného připojení obnoví spojení, ale hodnoty z doby, kdy server stál, dodatečně nedoplní.

Jiná situace nastává, když se přeruší jen komunikace a server dál běží. Specifikace obsahuje službu Republish, pomocí které lze server požádat, aby z fronty pro opakované odeslání znovu poslal zprávy s oznámeními, které nedorazily21. Oznámení se hromadí i ve frontě MonitoredItem, ale co přesáhne velikost fronty, se zahodí13. Tento případ jsem v tomto experimentu nezkoušel. V obou případech zaznamenávejte časy odpojení a opětovného připojení, abyste poznali, v jakém období hodnoty nepřicházely. Opětovnému připojení a návrhu protokolů u zařízení se sériovou komunikací se věnuje také článek „Serial Communication App Pitfalls“.

Záznam při zastavení serveruObrázek ukazuje, že když se server během subskripce zastaví, klient se opakovaně pokouší znovu připojit a po návratu serveru se vrátí i oznámení, ale hodnoty z doby zastavení nedorazí, a proto je potřeba zaznamenat časy odpojení a opětovného připojení.Subskripce běžíServer se zastavíKlient se opakovaně pokouší připojitServer se vrátí a oznámení pokračujíHodnoty z doby zastavení nedorazíZaznamenat časy odpojení a připojení

Obrázek 9: Opětovné připojení obnoví spojení, ale hodnoty z doby, kdy server stál, nedorazí.

Které časové razítko zaznamenávat

Podle specifikace je SourceTimestamp čas, který k hodnotě připojil zdroj dat, a měl by udávat, kdy se hodnota změnila. ServerTimestamp je čas, kdy server hodnotu přijal nebo zjistil, že je správná9. Simulovaný server v tomto článku generuje hodnoty sám, takže oba časy byly stejné.

Pokud chcete vědět, kdy se hodnota v zařízení změnila, použijte SourceTimestamp. Tento čas se však přiděluje podle hodin zdroje, takže pokud jdou hodiny zařízení nebo serveru špatně, je posunutý i záznam. Pokud budete nasbíraná data porovnávat s jinými záznamy, ukládejte vedle nich i čas přijímajícího počítače, abyste mohli posun později ověřit. Synchronizaci času na straně Windows popisuje článek „Windows Time Synchronization (w32time) and Business Systems“.

Hodnoty se stavem jiným než Good nezpracovávejte jako prostá čísla

Pokud je stavový kód Bad, hodnota se má ignorovat9. Pokud je Uncertain, hodnota nemusí být pro některé účely použitelná10. Než hodnoty zahrnete do grafů nebo souhrnů, roztřiďte je podle stavového kódu a rozhodněte, jak naložit s hodnotami, které nejsou Good. Bit Overflow při přetečení fronty se také nastavuje ve stavovém kódu13. Zobrazování stavu zařízení na obrazovce popisuje článek „Best Practices for Checking and Displaying External Device State“.

Otevření portu ve firewallu

Port OPC UA registrovaný v IANA je 4840/TCP3. Ne všechny servery však toto číslo používají. Ukázky OPC Foundation pro .NET používají například port 625414. Číslo portu ověřte v adrese URL koncového bodu zařízení a povolte připojení k tomuto portu. Pokud server běží ve Windows, povolte ho v bráně firewall systému Windows na daném počítači, a pokud je mezi nimi síťové zařízení, povolte ho i na něm. Vytváření pravidel pro příchozí komunikaci popisuje článek „The Windows Firewall and Business Applications“.

Kde sběr dat selháváObrázek ukazuje místa, kde sběr dat ze zařízení selhává, rozdělená na obsah záznamů (hodnoty z doby zastavení serveru nedorazí, které časové razítko zaznamenávat, zacházení s hodnotami, které nejsou Good) a přípravu připojení (porty ve firewallu, platnost certifikátů).Kde sběr dat selháváObsah záznamůPříprava připojeníHodnoty ze zastavení nedorazíKteré časové razítkoHodnoty, které nejsou GoodPorty ve firewalluPlatnost certifikátů

Obrázek 10: I když se připojení podaří, kontrolujte mezery v záznamech, časy, stav, porty a certifikáty.

Volba mezi OPC UA, Modbus/TCP a vlastní komunikací výrobců

Pro komunikaci s PLC a zařízeními existuje kromě OPC UA také Modbus/TCP a komunikace, které poskytují výrobci PLC.

Specifikace Modbus pracuje s daty ve čtyřech tabulkách: Discrete Input, jednobitová hodnota jen pro čtení; Coil, jednobitová hodnota pro čtení i zápis; Input Register, 16bitové slovo jen pro čtení; a Holding Register, 16bitové slovo pro čtení i zápis22. V IANA je pro aplikační protokol Modbus (mbap) registrován port 502/TCP3.

Do těchto tabulek patří bity a 16bitová slova, takže jaká hodnota je na které adrese a v jakém formátu, je nutné ověřit v dokumentaci zařízení a zapsat na straně klienta. U OPC UA, jak jsme viděli, server zveřejňuje v adresním prostoru pojmenované proměnné a klient je může zjistit pomocí služby Browse. Hodnoty mají stavový kód a časová razítka a připojení se povolují certifikáty.

Co použít, často rozhodne to, co zařízení nabízí. Pokud má zařízení server OPC UA, použijte ho; pokud má jen Modbus/TCP, vytvořte tabulku adres a čtěte podle ní. Pokud je k dispozici jen komunikace nebo SDK výrobce, řiďte se jeho dokumentací. Volbu způsobu komunikace se zařízeními USB popisuje článek „How to Work with USB Devices from a Windows App“.

Volba způsobu komunikace se zařízenímObrázek ukazuje, že pokud má zařízení server OPC UA, procházejí se a sbírají proměnné přes OPC UA, pokud má jen Modbus/TCP, ověří se adresy a formáty v dokumentaci a čtou se registry, a pokud má jen komunikaci výrobce, postupuje se podle jeho dokumentace.Server OPC UAJen Modbus/TCPJen komunikace výrobceKomunikace, kterou zařízení nabízíCo je k dispozici?Procházet a sbírat proměnnéOvěřit adresy a formáty v dokumentaciŘídit se dokumentací a SDK výrobceČíst registry a převádět na hodnoty

Obrázek 11: Čím data sbírat, často rozhodne to, co zařízení nabízí.

Související články

Reference

  1. OPC Foundation, Classic. Že OPC Classic vychází z COM/DCOM, specifikace DA, A&E a HDA, zveřejnění OPC UA v roce 2008 a důvody přepracování. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Část 1 řady IEC 62541, která se zabývá koncepty a přehledem OPC UA (nahrazena pozdějším vydáním). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. Registrace opcua-tcp na 4840/TCP a mbap na 502/TCP. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Jak napsat klienta řady 2.0, že ReadValueAsync při stavu Bad vyvolá výjimku, úložiště rejected a trusted, že servery ve výchozím nastavení nenabízejí None, a čísla portů použitá v ukázkách. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Knihovna OPC UA pro Python, kterou jsem použil pro simulovaný server zařízení. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Služba, která sleduje odkazy uzlu. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Že URI jmenných prostorů se ve službách vyjadřují čísly a že toto číslo odpovídá pozici v poli NamespaceArray. ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Služby Read a Write. ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definice hodnoty, stavového kódu, SourceTimestamp a ServerTimestamp. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain a Bad vyjádřené horními dvěma bity. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Že vzorkovací interval je cyklus „best effort“. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Že bez filtru se oznámení posílá při změně hodnoty nebo stavu. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Velikost fronty, způsob zahazování a bit Overflow. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publikační interval a keep-alive. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync a vzorkovací interval a velikost fronty v MonitoredItemOptions. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Význam None, Sign a SignAndEncrypt. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Postup přesouvání certifikátů z úložiště rejected do důvěryhodného úložiště a nepoužívání AutoAcceptUntrustedCertificates v produkci. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Čtyři druhy: anonymní, uživatelské jméno, X.509 a vydaný token. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Seznam uživatelských tokenů, které koncový bod přijímá (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Že ManagedSession má vestavěný mechanismus opětovného připojení. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Služba, kterou subskripce z fronty pro opakované odeslání znovu pošle zprávy s oznámeními. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (kopie uložená na serveru Chicagské univerzity). Datový model v oddílu 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩

Nejnovější články se stejnými štítky. Prohloubte si znalosti v příbuzných tématech.

Stránky s tématy blízkými tomuto článku. Od článku můžete přejít k souvisejícím službám a dalším článkům.

Tento článek navazuje na následující stránky služeb. Vyberte si nejbližší vstup.

Vývoj aplikací pro Windows

Aplikaci pro Windows, která sbírá data ze zařízení, včetně nastavení subskripcí, opětovného připojení a záznamu časových razítek a stavu, lze řešit jako vývoj aplikací pro Windows.

Často kladené otázky

Otázky, které se při konzultacích k tématu tohoto článku objevují nejčastěji.

Jaký je rozdíl mezi OPC UA a OPC Classic (OPC DA)?
OPC Classic je specifikace, která vyměňuje data přes COM/DCOM ve Windows. OPC UA je specifikace zveřejněná v roce 2008, která nezávisí na konkrétním operačním systému a sjednocuje funkce Classic. Klienti OPC UA běží i mimo Windows a nastavení DCOM není potřeba.
Stačí při sběru hodnot přes OPC UA číst je v pevném intervalu?
Pokud je hodnot málo a interval je dlouhý, lze je sbírat i čtením. Ke sledování změn hodnot lze místo toho použít subskripci, která vzorkování přenechá serveru a nechá si od něj změny oznamovat. V obou případech zaznamenávejte stavový kód a časová razítka, která se vracejí spolu s hodnotou.
Proč se při připojení objeví BadCertificateUntrusted?
Protože klient zatím nedůvěřuje certifikátu serveru. Knihovna OPC Foundation pro .NET ukládá odmítnuté certifikáty do úložiště rejected. Ověřte kryptografický otisk certifikátu se správcem serveru a teprve potom ho přesuňte do úložiště trusted. Nastavení, které certifikáty přijímá automaticky, používejte jen ve vývojovém prostředí.
Jaké číslo portu OPC UA používá?
V registru IANA je port 4840/TCP zapsán jako opcua-tcp. Skutečné servery však mohou používat i jiný port, proto to ověřte v nastavení zařízení. Pokud server běží ve Windows, je potřeba komunikaci na tento port povolit v bráně firewall systému Windows, a pokud je mezi nimi síťové zařízení, tak i v něm.

O autorovi

Stránka s profilem autora článku.

Go Komura

Zástupce společnosti KomuraSoft LLC

Specializuje se na vývoj softwaru pro Windows, technické konzultace a vyšetřování chyb. Silnou stránkou jsou zakázky, v nichž zůstává stávající software, a vyšetřování poruch s nejasnou příčinou.

Zpět na přehled blogu