OPC UA sissejuhatus ── PLC-de ja seadmete andmete kogumine Windowsi rakendusega

· · Windows, OPC UA, Tootmine, PLC, C#, .NET, Side, Sertifikaadid

Postita X-is
Muudatuste ajalugu (ainult esmaversioon, avaldatud 01.11.2026)
Esmakordselt avaldatud
Tsiteeri seda artiklit(DOI: 10.5281/zenodo.23968429)

See artikkel on arhiveeritud Zenodos. Allpool on nii DOI, mis viib alati uusima versioonini, kui ka DOI, mis on seotud praegu kuvatava versiooniga.

Go Komura (2026). OPC UA sissejuhatus ── PLC-de ja seadmete andmete kogumine Windowsi rakendusega. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968429 https://comcomponent.com/et/blog/opc-ua-windows-data-collection/

DOI (uusim versioon)
10.5281/zenodo.23968429
DOI (see versioon)
10.5281/zenodo.23968430

Kui kogute PLC-de või seadmete andmeid Windowsi rakendusse ja seadmel on OPC UA server, võib õnnestuda vältida eraldi sideprotokolli kirjutamist iga tootja jaoks. Ühendamisest ja väärtuste lugemisest siiski ei piisa. Kui te ei tea väärtusega koos tagastatavast olekust ja ajatemplitest, muutustest teavitavatest tellimustest, sertifikaadiga ühenduse lubamisest ega sellest, kuidas ühenduse katkemisest taastuda, ei saa kogutud andmeid hiljem usaldada.

See artikkel selgitab OPC UA neid osi, mida andmete kogumiseks vaja on, koos C#-kliendi tegelike käivitamistulemustega. Klient kasutab OPC Foundationi .NET-i teeki ja teisel pool on simuleeritud seadmeserver. Ühegi konkreetse PLC ega seadmega ma seda proovinud ei ole. Seadmepõhiseid sätteid kontrollige selle seadme dokumentatsioonist.

GigE Visioni ja USB3 Visioni kaameratest piltide kogumist käsitleb artikkel „Dropped Frames with GigE Vision and USB3 Vision Cameras“ ja jadaliidesega seadmeid artikkel „Serial Communication App Pitfalls“.

OPC UA ja OPC Classicu erinevus

OPC Classicu spetsifikatsioonid vahetavad andmeid tarkvarakomponentide vahel Windowsi COM/DCOM-i abil. Protsessiandmete jaoks OPC DA, alarmide jaoks OPC A&E ja ajaloo jaoks OPC HDA on määratletud eraldi spetsifikatsioonidena1.

OPC Foundation avaldas OPC UA (Unified Architecture) 2008. aastal. OPC UA on teenustele orienteeritud arhitektuur, mis ei sõltu kindlast platvormist ja koondab Classicu iga spetsifikatsiooni funktsioonid. Ümberkujundamise põhjustena nimetab OPC Foundation seda, et Microsoft hakkas COM/DCOM-i asemel rõhutama platvormiüleseid mehhanisme, et oli vaja töötada ka mitte-Microsofti süsteemides ja manussüsteemides ning et oli vaja turvaliselt ühenduda üle tulemüüride1.

OPC UA spetsifikatsioon on IEC poolt standarditud ka seeriana IEC 625412. Sideportiks on IANA registreerinud 4840/TCP nimega opcua-tcp3.

OPC Classicu ja OPC UA erinevusJoonis näitab, et OPC Classic põhineb Windowsi COM-il ja DCOM-il ning jaguneb eraldi spetsifikatsioonideks DA, A&E ja HDA, samas kui OPC UA koondab nende funktsioonid ühte platvormist sõltumatusse spetsifikatsiooni.Funktsioonid koondatakseOPC ClassicTöötab COM/DCOM-i peal (Windows)Jagunenud spetsifikatsioonideks DA, A&E ja HDAOPC UA (2008)Ei sõltu kindlast platvormistStandarditud kui IEC 62541

Joonis 1: OPC Classic koosneb COM/DCOM-i peal olevatest eraldi spetsifikatsioonidest ja OPC UA koondab need üheks.

Kui olemasoleval seadmel on ainult OPC DA server, on vaja lahendust, mis kasutab COM/DCOM-i. See artikkel käsitleb ainult OPC UA-d.

Diagrammil tähistab pidev joon seost, mis kehtib alati, ja katkendjoon tingimuslikku seost (tingimused on igal seosel toodud üksikasjalikul lehel). Seoste täielik loend (13 kokku, koos tõendite ja kindlusastmega) ning põhimõistete definitsioonid on koondatud teadmuskaardi üksikasjalikule lehele (jaapani keeles). Andmed: JSON-LD / Turtle

Proovitud seadistus

Käivitasin järgmise seadistuse.

Roll Mida kasutasin
Klient C#, .NET 10 SDK 10.0.114, NuGeti pakett OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Server Pythoni 3.13 ja asyncua 2.1.0 abil tehtud simuleeritud seade (sama arvuti port 4840)
OS Linux (Windowsis käivitamist ei proovitud)

Kliendi teek on OPC Foundationi avaldatud .NET-i teostus. 2.0 versioonirea kasutamisel järgisin sama hoidla juhendit Getting started4.

Proovitud seadistusJoonis näitab seadistust, kus OPC Foundationi .NET-i teeki kasutav C#-klient ühendub krüpteeritud ühendusega simuleeritud asyncua seadmeserveriga, mis kuulab porti 4840.opc.tcp, SignAndEncryptC#-klient (LineCollector)Simuleeritud seadmeserver (asyncua)OPC Foundationi .NET-i teek 2.0.1Neli muutujat objekti Line1 all

Joonis 2: Samas arvutis ühendus C#-klient simuleeritud seadmeserveriga.

Simuleeritud seadmeserver

Päris seadme asemel tegin asyncua abil väikese serveri5. Objekti Line1 all on neli muutujat: temperatuur (Temperature), toodangu arv (PartCount), töötab (Running) ja seadeväärtus (Setpoint). Temperatuur muutub iga 0,5 sekundi järel ja toodangu arv iga 2 sekundi järel. Turbe jaoks pakkusin kahte varianti: krüpteerimata (None) ning allkirjastamine koos krüpteerimisega (Basic256Sha256 režiimiga SignAndEncrypt).

# Testimiseks mõeldud simuleeritud seade. See ei asenda tootmisseadmeid ega PLC-sid.
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())

See server aktsepteerib ühenduvate klientide sertifikaadid neid kontrollimata. See on testimise säte ja erineb sellest, kuidas tootmisseadmed käituvad.

C#-klient

Lisasin projektifaili need kaks paketti.

<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 ühendub, sirvib, loeb, kirjutab ja tellib, selles järjekorras. Iga osa tähendust selgitavad järgmised jaotised.

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

        // Nimeruumi indeksi määrab server. Otsi see URI järgi ja koosta selle abil NodeId-d
        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);

        // Sirvimine: loetle Line1 all olevad muutujad
        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}");
        }

        // Lugemine: vaata lisaks väärtusele ka olekut ja kaht ajatemplit
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Olematu sõlme lugemisel tuleb väärtuse asemel Bad-olek
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Kirjutamine: sea Setpoint väärtuseks 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]}");

        // Tellimine: palu serveril diskreetida iga 500 ms järel ja teavitada muutustest
        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");

Esimesel käivitamisel ei õnnestunud ühendust luua. Põhjust selgitab jaotis „Ühenduse lubamine sertifikaatidega“. Allpool on väljund pärast serveri sertifikaadi usaldamist. Teegi hoiatuslogid on välja jäetud.

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

Kellaajad on UTC-s.

Seadme andmed paistavad aadressiruumi sõlmedena

OPC UA server avaldab oma andmed sõlmede kogumina. Seda kogumit nimetatakse aadressiruumiks. Sõlmi on eri liiki, näiteks objektid, mis esindavad kaustu või seadmeid, ja muutujad, millel on väärtus, ning sõlmed on omavahel seotud viidetega. Klient järgib viiteid teenusega Browse ja selgitab nii välja, millised sõlmed on olemas6.

Ülaltoodud väljundis järgib klient objektist Line1 hierarhilisi viiteid ja leiab neli muutujat.

Simuleeritud seadme aadressiruumJoonis näitab, et kausta Objects all on objekt Line1 ja selle all neli muutujat: Temperature, PartCount, Running ja Setpoint.ObjectsLine1 (objekt)Temperature (muutuja)PartCount (muutuja)Running (muutuja)Setpoint (muutuja, kirjutatav)

Joonis 3: Simuleeritud seadme aadressiruum. Klient leiab sõlmed, järgides teenusega Browse viiteid.

NodeId koosneb nimeruumist ja identifikaatorist

Sõlmi tuvastatakse NodeId abil. Väljundis tähendab ns=2;s=Line1.Temperature, et nimeruumi indeks on 2 ja identifikaator on string Line1.Temperature.

Nimeruum on tegelikult URI. Spetsifikatsiooni järgi esitatakse nimeruumi URI-d teenustes arvudena ja see arv vastab positsioonile serveri NamespaceArray-s. Indeks 0 on OPC UA nimeruum http://opcfoundation.org/UA/7.

Seetõttu ärge kirjutage indeksi arvu koodi, vaid otsige indeks URI järgi. Ülaltoodud kood saab URI-st meetodiga session.NamespaceUris.GetIndex(LineNamespace) väärtuse 2 ja koostab selle abil NodeId-d.

NodeId koostamine nimeruumi URI-stJoonis näitab, et klient otsib nimeruumi URI serveri NamespaceArray-st, saab indeksi ja koostab selle indeksi ning identifikaatori ühendamisel NodeId.Nimeruumi URIServeri NamespaceArrayIndeks (selles näites 2)NodeId ns=2;s=Line1.TemperatureIdentifikaator Line1.Temperature

Joonis 4: Otsige nimeruumi indeks URI järgi ja ühendage see identifikaatoriga NodeId-ks.

Lugemine ja kirjutamine tagastavad väärtusega koos oleku ja ajatemplid

Muutujate väärtusi loetakse ja kirjutatakse teenustega Read ja Write8. Lugemine tagastab rohkem kui ainult väärtuse. See tagastab struktuuri DataValue, milles on väärtus, olekukood, allika ajatempel ja serveri ajatempel9.

Väli Tähendus
Väärtus Muutuja väärtus. Kui olekukood näitab viga, väärtust ignoreeritakse ja server seab selle väärtuseks null
Olekukood (StatusCode) Kas server suutis väärtuse hankida ja edastada
SourceTimestamp Aeg, mille andmete allikas väärtusele lisas
ServerTimestamp Aeg, mil server väärtuse sai või teada sai, et see on õige

Olekukoodi kaks kõrgemat bitti näitavad olekut Good (õnnestus), Uncertain (osaliselt õnnestus, ei pruugi mõneks otstarbeks sobida) või Bad (ebaõnnestus)10. Väljundis olev Good [0x00000000] on Good.

Kui lugesin olematut Line1.Pressure, tuli väärtuse asemel BadNodeIdUnknown [0x80340000]. Selle teegi ReadValueAsync viskab oleku Bad korral erandi4. Kui väärtusi salvestav süsteem jätab iga väärtuse kõrvale ka olekukoodi, saab hiljem eristada väärtusi, mis salvestati ajal, mil olek oli Bad.

Mida DataValue sisaldabJoonis näitab, et lugemisel või teavitusel tagastataval DataValue'l on neli osa: väärtus, olekukood, allika ajatempel ja serveri ajatempel, ning et oleku Bad korral väärtust ignoreeritakse.Kui BadDataValueVäärtusOlekukoodSourceTimestamp (allika aeg)ServerTimestamp (serveri aeg)Ignoreeri väärtust

Joonis 5: Lugemise tulemuses on lisaks väärtusele ka olekukood ja kaks ajatemplit.

Kirjutamisel kirjutasin muutujasse Setpoint väärtuse 32,5 ja tulemuse olekukoodina tuli Good. Milliseid muutujaid saab kirjutada, otsustab server. Selles simuleeritud serveris on kirjutatav ainult Setpoint. See, kas lubada seadme seadeväärtusi väljastpoolt muuta, on seadme ohutuse disaini küsimus, seega seda artiklis ei käsitleta.

Muutustest teavitamine: tellimused ja MonitoredItem

Väärtuste muutuste jälgimiseks võib klient kindla intervalliga korduvalt Readi kutsuda. Peale selle on OPC UA-s mehhanism, mis jätab väärtuste jälgimise serverile ja laseb serveril muutustest teavitada. See mehhanism on tellimused (subscription) ja MonitoredItem.

MonitoredItem on üks jälgitav objekt ja sellel on diskreetimisintervall. Diskreetimisintervall on kiireim intervall, millega server allikast väärtusi diskreedib, ja spetsifikatsioon kirjeldab seda kui „parima võimaliku“ (best effort) tsüklit11. Kui diskreet vastab filtri tingimustele, luuakse teavitus. Kui filtrit pole määratud, saadetakse teavitus siis, kui väärtus või olek muutub12.

Teavitused lähevad kõigepealt MonitoredItemi järjekorda. Eelnevalt otsustatakse, kas järjekorra ületäitumisel visatakse ära vanim või uusim, ja kui järjekorra suurus on 2 või rohkem, seatakse olekukoodis äraviskamist näitav bitt Overflow13.

Tellimus koondab mitu MonitoredItemi ja saadab igal avaldamisintervallil kliendile seni saatmata teavitused. Kui tsüklid mööduvad ilma saadetavate teavitusteta, saadab see keep-alive’i, et näidata, et tellimus on endiselt elus14.

Kuidas tellimused ja MonitoredItem töötavadJoonis näitab, et MonitoredItem diskreedib allika väärtust diskreetimisintervalliga ja paneb filtri läbinud teavitused järjekorda, et tellimus saadab need avaldamisintervallil koos kliendile ja et teavituste puudumisel saadetakse keep-alive.DiskreetimisintervallAvaldamisintervallKui teavitusi poleAllikas (seadme väärtus)MonitoredItemFilter (vaikimisi väärtuse või oleku muutus)Järjekord (täitumisel Overflow)TellimusKlientKeep-alive

Joonis 6: MonitoredItem diskreedib ja paneb järjekorda ning tellimus saadab teavitused koos kliendile.

Ülaltoodud kood tellib muutujad Temperature ja PartCount 500-millisekundilise diskreetimisintervalli ja järjekorra suurusega 515. Väljundis saabub temperatuur umbes iga 0,5 sekundi ja toodangu arv umbes iga 2 sekundi järel. Kaks esimest teavitust olid väärtused, mis kummalgi muutujal tellimuse alguses olid. Toodangu arvu 13:40:50.801 on aeg, mil selle väärtus muutus, enne kui tellimus algas. Kuna teavitused näitavad allika ajatemplit, näitavad need aega, mil väärtus muutus, mitte aega, mil teavitus saabus.

Korduva lugemise ja tellimuse valimine

Korduva lugemise puhul saab klient intervalli ise määrata ja mehhanism on lihtne. Samas liiguvad päring ja vastus edasi-tagasi iga kord, isegi kui väärtus pole muutunud, ning intervallist lühemad muutused jäävad märkamata. Tellimuse puhul diskreedib server ja saadab ainult muutused, nii et harva muutuvate väärtuste korral liiklus palju ei kasva. Ka tellimuse puhul ei pruugita diskreetimisintervallist lühemaid muutusi teavitada. Spetsifikatsioon lubab nõutust kiiremat diskreetimist, kuid ei nõua seda11.

Korduva lugemise ja tellimuse erinevusJoonis näitab, et korduva lugemise puhul määrab klient intervalli ja saadab iga kord päringu, et tellimuse puhul diskreedib server diskreetimisintervalliga ja saadab ainult muutused ning et mõlemal juhul võivad intervallist lühemad muutused märkamata jääda.Väärtuste muutuste jälgimineKorduv lugemineTellimineKlient määrab intervalli ja pärib iga kordServer diskreedib ja saadab ainult muutusedIntervallist lühemad muutused võivad kaduma minna

Joonis 7: Kummagi meetodi puhul võivad intervallist lühemad muutused märkamata jääda.

Ühenduse lubamine sertifikaatidega

OPC UA ühenduses valitakse sõnumite kaitse tase parameetriga MessageSecurityMode. Väärtused on None, mis ei kaitse, Sign, mis ainult allkirjastab, ja SignAndEncrypt, mis allkirjastab ja krüpteerib16. Allkirjastamiseks ja krüpteerimiseks kasutatavate algoritmide kombinatsiooni määrab turbepoliitika ja selles artiklis kasutatakse poliitikat Basic256Sha256.

Allkirjastamiseks ja krüpteerimiseks kasutatakse iga rakenduse sertifikaati. Klient ja server kontrollivad teineteise sertifikaate ja keelduvad ühendusest osapooltega, keda nad ei usalda. OPC Foundationi .NET-i teegiga tehtud server ei paku vaikimisi krüpteerimata poliitikat None4.

Esimene ühendus peatus veaga BadCertificateUntrusted

Esimesel käivitamisel ei usaldanud klient serveri sertifikaati, seega lükati ühendus tagasi. Siin on osa väljundist.

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

See teek salvestab tagasi lükatud sertifikaadid hoidlasse rejected. Kui administraator teisaldab sertifikaadi sealt hoidlasse trusted, hakkab teek seda osapoolt usaldama17. Seekord teisaldasin serveri sertifikaadi kaustast pki/rejected/certs kausta pki/trusted/certs, käivitasin uuesti ja ühendus õnnestus. Enne teisaldamist kontrollige koos serveri administraatoriga sõrmejälge, mis on ka faili nimes4.

Sertifikaadi usaldamise sammudJoonis näitab, et esimesel ühendamisel lükkab klient serveri sertifikaadi tagasi ja salvestab selle hoidlasse rejected ning et pärast sõrmejälje kontrollimist ja hoidlasse trusted teisaldamist usaldab järgmine ühendus seda ja õnnestub.Esimene ühendusBadCertificateUntrustedSertifikaat salvestatakse hoidlasse rejectedKontrolli sõrmejälge serveri administraatorigaTeisalda see hoidlasse trustedJärgmine ühendus usaldab ja õnnestub

Joonis 8: Kui tagasi lükatud sertifikaat on kontrollitud ja teisaldatud hoidlasse trusted, usaldab klient seda osapoolt.

On olemas ka säte AutoAcceptUntrustedCertificates, mis aktsepteerib mitteusaldatud sertifikaadid automaatselt. Vaikimisi on see false ja teegi dokumentatsioon ütleb, et see on arenduskeskkonnas kasulik, kuid tootmises ei tohiks seda kasutada17.

Logis olev NotAfter=2027-11-01 on simuleeritud serveri sertifikaadi aegumiskuupäev. See on asyncua funktsiooniga loodud iseallkirjastatud sertifikaat, mis aegus aasta pärast loomise päeva. Ka seadmete sertifikaadid aeguvad, seega otsustage käitamise osana, kuidas need enne aegumist välja vahetada ja panna teine pool uusi usaldama. Windowsi sertifikaadihoidla käsitlemist kirjeldab artikkel „The Windows Certificate Store in Practice“.

Kasutaja autentimine

Sertifikaadid kontrollivad rakendusi. Peale selle antakse seansi avamisel üle kasutajat tuvastav luba (token). Spetsifikatsioon määratleb neli liiki: anonüümne, kasutajanimi ja parool, X.509 sertifikaat ning väliselt väljastatud luba18. Selle artikli klient ühendus anonüümselt. Milliseid lube server aktsepteerib, sisaldub serveri lõpp-punkti teabes19.

Kus andmete kogumine praktikas komistab

Siit edasi loetlen probleeme, mis tekivad sageli seadmete andmeid koguvates süsteemides, tuginedes sellele katsele ja spetsifikatsioonile.

Väärtused ajast, mil server seisis, ei saabu

Tellimuse ajal peatasin serveri ja käivitasin selle umbes 15 sekundi pärast uuesti. Selle teegi ManagedSession ühendub ühenduse katkemisel automaatselt uuesti20. Sel ajal, kui server seisis, proovis klient korduvalt uuesti ühenduda.

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.

Kui server tagasi tuli, tulid tagasi ka teavitused. Siin on osa tellimuse väljundist.

  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]

Ajavahemikust 13:41:13 kuni 13:41:34 ei saabunud ühtegi väärtust. Toodangu arv läks 78-lt tagasi 2-le. See on nii, sest simuleeritud serveri taaskäivitamine pani selle uuesti 0-st lugema.

Selles näites seisis server ise, seega selle aja väärtusi pole kuskil säilinud. Taasühendamise mehhanism taastab ühenduse, kuid ei täida tagantjärele väärtusi ajast, mil server seisis.

Olukord on teistsugune, kui katkeb ainult side ja server töötab edasi. Spetsifikatsioonis on teenus Republish, millega saab paluda serveril saata kohale jõudmata teavitussõnumid uuesti tema taasedastusjärjekorrast21. Teavitused kogunevad ka MonitoredItemi järjekorda, kuid kõik, mis ületab järjekorra suuruse, visatakse ära13. Seda juhtumit ma selles katses ei testinud. Mõlemal juhul salvestage ühenduse katkemise ja taastumise ajad, et saaks aru, millisel perioodil väärtusi ei saabunud. Jadaliidesega seadmete taasühendamist ja logide disaini käsitleb ka artikkel „Serial Communication App Pitfalls“.

Salvestamine, kui server peatubJoonis näitab, et kui server tellimuse ajal peatub, proovib klient korduvalt uuesti ühenduda ja serveri naastes tulevad teavitused tagasi, kuid peatuse ajal tekkinud väärtused ei saabu, seega tuleb salvestada ühenduse katkemise ja taastumise ajad.Tellimus käibServer peatubKlient proovib korduvalt uuesti ühendudaServer naaseb ja teavitused jätkuvadPeatuse ajal tekkinud väärtused ei saabuSalvesta katkemise ja taasühendamise ajad

Joonis 9: Taasühendamine taastab ühenduse, kuid väärtused ajast, mil server seisis, ei saabu.

Millist ajatemplit salvestada

Spetsifikatsiooni järgi on SourceTimestamp aeg, mille andmete allikas väärtusele lisas, ja see peaks näitama, millal väärtus muutus. ServerTimestamp on aeg, mil server väärtuse sai või teada sai, et see on õige9. Selle artikli simuleeritud server genereerib väärtused ise, seega olid need kaks aega samad.

Kui soovite teada, millal väärtus seadmes muutus, kasutage SourceTimestampi. See aeg pannakse aga allika kella järgi, nii et kui seadme või serveri kell on nihkes, on nihkes ka salvestis. Kui kavatsete kogutud andmeid teiste salvestistega võrrelda, salvestage kõrvale ka vastuvõtva arvuti aeg, et saaksite nihet hiljem kontrollida. Windowsi poolset ajasünkroonimist käsitleb artikkel „Windows Time Synchronization (w32time) and Business Systems“.

Ärge käsitlege väärtusi, mille olek pole Good, lihtsalt arvudena

Kui olekukood on Bad, tuleb väärtust ignoreerida9. Kui see on Uncertain, ei pruugi väärtus mõneks otstarbeks sobida10. Enne väärtuste lisamist graafikutesse või koondandmetesse sorteerige need olekukoodi järgi ja otsustage, kuidas käsitleda väärtusi, mis pole Good. Ka järjekorra ületäitumise bitt Overflow seatakse olekukoodis13. Seadme oleku kuvamist ekraanil käsitleb artikkel „Best Practices for Checking and Displaying External Device State“.

Avage port tulemüüris

IANA-s registreeritud OPC UA port on 4840/TCP3. Kõik serverid seda numbrit siiski ei kasuta. OPC Foundationi .NET-i näidised kasutavad näiteks porti 625414. Kontrollige pordinumbrit seadme lõpp-punkti URL-ist ja lubage ühendused sellesse porti. Kui server töötab Windowsis, lubage see selle arvuti Windowsi tulemüüris, ja kui vahepeal on võrguseadmeid, lubage see neis. Sissetulevate reeglite loomist käsitleb artikkel „The Windows Firewall and Business Applications“.

Kus andmete kogumine komistabJoonis näitab kohti, kus seadmete andmete kogumine komistab, jagatuna salvestiste sisuks (peatuse ajal tekkinud väärtused ei saabu, millist ajatemplit salvestada, väärtuste käsitlemine, mille olek pole Good) ja ühenduse ettevalmistuseks (tulemüüri pordid, sertifikaatide aegumine).Kus andmete kogumine komistabSalvestiste sisuÜhenduse ettevalmistusPeatuse ajal väärtusi ei saabuMilline ajatempelMitte-Good väärtuste käsitlemineTulemüüri pordidSertifikaatide aegumine

Joonis 10: Ka pärast ühenduse loomist kontrollige salvestiste lünki, aegu, olekut, porte ja sertifikaate.

OPC UA, Modbus/TCP ja tootjate oma protokollide valimine

PLC-de ja seadmetega suhtlemiseks on peale OPC UA ka Modbus/TCP ning PLC-tootjate pakutavad protokollid.

Modbusi spetsifikatsioon käsitleb andmeid nelja tabelina: Discrete Input, ainult loetav 1-bitine väärtus; Coil, loetav ja kirjutatav 1-bitine väärtus; Input Register, ainult loetav 16-bitine sõna; ning Holding Register, loetav ja kirjutatav 16-bitine sõna22. IANA-s on Modbusi rakendusprotokolliks (mbap) registreeritud 502/TCP3.

Nendesse tabelitesse mahuvad bitid ja 16-bitised sõnad, seega tuleb seadme dokumentatsioonist kontrollida, milline väärtus millisel aadressil ja millises vormingus on, ning kirjutada see kliendi poolele. OPC UA puhul, nagu nägime, avaldab server oma aadressiruumis nimega muutujad ja klient saab need teenusega Browse üles leida. Väärtustega kaasnevad olekukood ja ajatemplid ning ühendused lubatakse sertifikaatidega.

Mida kasutada, sõltub sageli sellest, mida seade pakub. Kui seadmel on OPC UA server, kasutage seda; kui sellel on ainult Modbus/TCP, koostage aadresside vastavustabel ja lugege selle järgi. Kui on ainult tootja protokoll või SDK, järgige selle dokumentatsiooni. USB-seadmetega suhtlemise meetodi valimist käsitleb artikkel „How to Work with USB Devices from a Windows App“.

Seadmega suhtlemise viisi valimineJoonis näitab, et kui seadmel on OPC UA server, sirvitakse ja kogutakse muutujad OPC UA abil, kui sellel on ainult Modbus/TCP, kontrollitakse aadressid ja vormingud dokumentatsioonist ning loetakse registreid, ja kui on ainult tootja protokoll, järgitakse selle dokumentatsiooni.OPC UA serverAinult Modbus/TCPAinult tootja protokollSeadme pakutav sideMis on olemas?Sirvi ja kogu muutujadKontrolli aadresse ja vorminguid dokumentatsioonistJärgi tootja dokumentatsiooni ja SDK-dLoe registrid ja teisenda väärtusteks

Joonis 11: Millega andmeid koguda, sõltub sageli sellest, mida seade pakub.

Seotud artiklid

Viited

  1. OPC Foundation, Classic. Et OPC Classic põhineb COM/DCOM-il, spetsifikatsioonid DA, A&E ja HDA, OPC UA avaldamine 2008. aastal ning ümberkujundamise põhjused. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. IEC 62541 seeria 1. osa, mis käsitleb OPC UA mõisteid ja ülevaadet (asendatud hilisema väljaandega). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. opcua-tcp registreering pordil 4840/TCP ja mbap registreering pordil 502/TCP. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Kuidas kirjutada 2.0 versioonirea klienti, et ReadValueAsync viskab Bad-oleku korral erandi, hoidlad rejected ja trusted, et serverid ei paku vaikimisi poliitikat None, ning näidistes kasutatavad pordinumbrid. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Pythoni OPC UA teek, mida kasutati simuleeritud seadmeserveri jaoks. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Teenus, mis järgib sõlme viiteid. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Et nimeruumi URI-d esitatakse teenustes arvudena ja et see arv vastab positsioonile NamespaceArray-s. ↩

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

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Väärtuse, olekukoodi, SourceTimestampi ja ServerTimestampi definitsioonid. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Kahe kõrgema bitiga näidatavad Good, Uncertain ja Bad. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Et diskreetimisintervall on parima võimaliku (best effort) tsükkel. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Et filtri puudumisel saadetakse teavitus väärtuse või oleku muutumisel. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Järjekorra suurus, äraviskamise põhimõte ja bitt Overflow. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Avaldamisintervall ja keep-alive’id. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync ning MonitoredItemOptions diskreetimisintervall ja järjekorra suurus. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. None, Sign ja SignAndEncrypt tähendused. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Tava teisaldada sertifikaadid hoidlast rejected usaldatud hoidlasse ning mitte kasutada tootmises sätet AutoAcceptUntrustedCertificates. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Neli liiki: anonüümne, kasutajanimi, X.509 ja väljastatud luba. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Lõpp-punkti aktsepteeritavate kasutajalubade loend (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Et ManagedSession-il on sisseehitatud taasühendamise mehhanism. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Teenus, millega tellimuse taasedastusjärjekorrast saadetakse teavitussõnumid uuesti. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (Chicago ülikooli serveris asuv koopia). Jaotise 4.3 andmemudel (Discrete Input, Coil, Input Register, Holding Register). ↩

Viimased samade siltidega artiklid. Nende abil saate teemasse süveneda.

Sellega lähedased teemalehed. Artiklist saate liikuda edasi seotud teenuste ja teiste artiklite juurde.

See artikkel seondub järgmiste teenuslehtedega. Alustage sobivaimast.

Windowsi rakenduste arendus

Windowsi rakendust, mis kogub seadmetest andmeid, koos tellimuste seadistamise, taasühendamise ning ajatemplite ja oleku salvestamisega, saab käsitleda Windowsi rakenduste arendusena.

Korduma kippuvad küsimused

Küsimused, mida nõustamisel selle artikli teemal sageli esitatakse.

Mis vahe on OPC UA-l ja OPC Classicul (OPC DA)?
OPC Classic on spetsifikatsioon, mis vahetab andmeid Windowsi COM/DCOM-i kaudu. OPC UA on 2008. aastal avaldatud spetsifikatsioon, mis ei sõltu kindlast operatsioonisüsteemist, ja koondab Classicu funktsioonid. OPC UA kliendid töötavad ka väljaspool Windowsi ning DCOM-i seadistamist pole vaja.
Kas OPC UA abil väärtusi kogudes piisab nende lugemisest kindla intervalliga?
Kui väärtusi on vähe ja intervall on pikk, saab neid koguda ka lugemise teel. Väärtuste muutuste jälgimiseks saab kasutada tellimust: diskreetimine jäetakse serverile ja server teavitab muutustest. Mõlemal juhul salvestage väärtusega koos tagastatav olekukood ja ajatemplid.
Miks ühendamisel tekib BadCertificateUntrusted?
Sest klient ei usalda veel serveri sertifikaati. OPC Foundationi .NET-i teek salvestab tagasi lükatud sertifikaadid hoidlasse rejected. Kontrollige sertifikaadi sõrmejälge koos serveri administraatoriga ja teisaldage see siis hoidlasse trusted. Sertifikaatide automaatse aktsepteerimise sätet kasutage ainult arenduskeskkonnas.
Millist pordinumbrit OPC UA kasutab?
IANA-s on registreeritud 4840/TCP nimega opcua-tcp. Tegelikud serverid võivad aga kasutada ka muud porti, nii et kontrollige seda seadme sätetest. Kui server töötab Windowsis, tuleb selle pordi liiklus lubada Windowsi tulemüüris, ja kui vahepeal on võrguseadmeid, siis ka neis.

Autori profiil

Artikli autori profiilileht.

Go Komura

KomuraSoft LLC esindaja

Tegeleb peamiselt Windowsi tarkvara arenduse, tehnilise nõustamise ja veaotsinguga. Tugevus on projektid, kus on säilinud olemasolevaid lahendusi, ning raskesti jälgitavate põhjustega tõrgete uurimine.

Tagasi blogisse