Kom i gang med OPC UA ── Innsamling av data fra PLS-er og utstyr i en Windows-app

· · Windows, OPC UA, Produksjon, PLC, C#, .NET, Kommunikasjon, Sertifikater

Del på X
Endringshistorikk (kun førsteversjonen, publisert 01.11.2026)
Første publisering
Siter denne artikkelen(DOI: 10.5281/zenodo.23968427)

Denne artikkelen er arkivert hos Zenodo. Nedenfor finner du både DOI-en som alltid peker til siste versjon, og DOI-en som er knyttet til versjonen du leser nå.

Go Komura (2026). Kom i gang med OPC UA ── Innsamling av data fra PLS-er og utstyr i en Windows-app. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968427 https://comcomponent.com/nb/blog/opc-ua-windows-data-collection/

DOI (siste versjon)
10.5281/zenodo.23968427
DOI (denne versjonen)
10.5281/zenodo.23968428

Når du samler inn data fra PLS-er eller utstyr i en Windows-app, og utstyret har en OPC UA-server, kan du kanskje unngå å skrive en egen protokoll for hver produsent. Det er likevel ikke nok bare å koble til og lese verdier. Hvis du ikke kjenner til statusen og tidsstemplene som følger med hver verdi, abonnementer som varsler om endringer, godkjenning av tilkoblinger med sertifikater og hvordan man kommer seg etter et brudd, kan du ikke stole på de innsamlede dataene i ettertid.

Denne artikkelen forklarer de delene av OPC UA som trengs for datainnsamling, sammen med resultater fra faktiske kjøringer av en C#-klient. Klienten bruker OPC Foundations .NET-bibliotek, og motparten er en simulert utstyrsserver. Jeg har ikke prøvd det mot noen bestemt PLS eller enhet. Kontroller enhetsspesifikke innstillinger i dokumentasjonen for den aktuelle enheten.

Innsamling av bilder fra GigE Vision- og USB3 Vision-kameraer omtales i «Dropped Frames with GigE Vision and USB3 Vision Cameras», og enheter med seriell kommunikasjon i «Serial Communication App Pitfalls».

Forskjellen mellom OPC UA og OPC Classic

OPC Classic-spesifikasjonene utveksler data mellom programvarekomponenter ved hjelp av Windows COM/DCOM. OPC DA for prosessdata, OPC A&E for alarmer og OPC HDA for historikk er definert som separate spesifikasjoner1.

OPC Foundation publiserte OPC UA (Unified Architecture) i 2008. OPC UA er en tjenesteorientert arkitektur som ikke er knyttet til en bestemt plattform, og som samler funksjonene fra hver av Classic-spesifikasjonene. Som grunner til omleggingen nevner OPC Foundation blant annet at Microsoft begynte å prioritere plattformuavhengige mekanismer framfor COM/DCOM, at løsningen måtte kunne kjøre på andre systemer enn Microsofts og på innebygde enheter, og at det måtte være mulig å koble til sikkert gjennom brannmurer1.

OPC UA-spesifikasjonen er også standardisert av IEC som IEC 62541-serien2. Som kommunikasjonsport har IANA registrert 4840/TCP som opcua-tcp3.

Forskjellen mellom OPC Classic og OPC UAEt diagram som viser at OPC Classic bygger på Windows COM og DCOM og er delt opp i separate DA-, A&E- og HDA-spesifikasjoner, mens OPC UA samler funksjonene deres i én plattformuavhengig spesifikasjon.Funksjonene samlesOPC ClassicKjører oppå COM/DCOM (Windows)Delt opp i DA-, A&E- og HDA-spesifikasjonerOPC UA (2008)Ikke knyttet til en bestemt plattformStandardisert som IEC 62541

Figur 1: OPC Classic består av separate spesifikasjoner oppå COM/DCOM, og OPC UA samler dem i én.

Hvis eksisterende utstyr bare har en OPC DA-server, trenger du en løsning som bruker COM/DCOM. Denne artikkelen omhandler bare OPC UA.

I diagrammet markerer en heldekkende linje en relasjon som alltid gjelder, og en stiplet linje en betinget relasjon (vilkårene står for hver relasjon på detaljsiden). Den fullstendige listen over relasjoner (13 i alt, med belegg og sikkerhet) og definisjonene av de viktigste begrepene finner du på detaljsiden for kunnskapskartet (på japansk). Data: JSON-LD / Turtle

Oppsettet jeg prøvde

Dette er oppsettet jeg kjørte.

Rolle Det jeg brukte
Klient C#, .NET 10 SDK 10.0.114, NuGet-pakken OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Server En simulert enhet laget med Python 3.13 og asyncua 2.1.0 (port 4840 på samme PC)
Operativsystem Linux (kjøring på Windows ble ikke prøvd)

Klientbiblioteket er .NET-implementeringen som OPC Foundation har publisert. For bruken av 2.0-serien fulgte jeg framgangsmåten i Getting started i det samme repositoriet4.

Oppsettet jeg prøvdeEt diagram som viser en C#-klient som bruker OPC Foundations .NET-bibliotek og kobler seg via en kryptert tilkobling til en simulert utstyrsserver laget med asyncua, som lytter på port 4840.opc.tcp, SignAndEncryptC#-klient (LineCollector)Simulert utstyrsserver (asyncua)OPC Foundations .NET-bibliotek 2.0.1Fire variabler under Line1

Figur 2: C#-klienten koblet seg til den simulerte utstyrsserveren på samme PC.

Den simulerte utstyrsserveren

I stedet for ekte utstyr laget jeg en liten server med asyncua5. Under et objekt med navnet Line1 er det fire variabler: temperatur (Temperature), antall deler (PartCount), i drift (Running) og settpunkt (Setpoint). Temperaturen endres hvert 0,5 sekund og antall deler hvert 2. sekund. Som sikkerhet tilbød jeg to alternativer: ingen kryptering (None) og signering pluss kryptering (SignAndEncrypt med Basic256Sha256).

# En simulert enhet for testing. Den erstatter ikke produksjonsutstyr eller PLS-er.
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())

Denne serveren godtar sertifikatene til klienter som kobler seg til, uten å kontrollere dem. Det er en testinnstilling og skiller seg fra hvordan produksjonsutstyr oppfører seg.

C#-klienten

Jeg la til disse to pakkene i prosjektfilen.

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

Klienten kobler til, blar gjennom, leser, skriver og abonnerer, i den rekkefølgen. De neste avsnittene forklarer hva hver del betyr.

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

        // Serveren bestemmer navneromsindeksen. Slå den opp fra URI-en og bygg NodeId-er ut fra den
        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);

        // Gjennomsøking: list opp variablene under 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}");
        }

        // Lesing: se på statusen og de to tidsstemplene, ikke bare verdien
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Lesing av en node som ikke finnes, returnerer en Bad-status i stedet for en verdi
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Skriving: sett Setpoint til 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]}");

        // Abonnement: be serveren sample hvert 500. ms og varsle om endringer
        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");

Første gang jeg kjørte den, kunne den ikke koble til. Grunnen forklares i avsnittet «Godkjenning av tilkoblinger med sertifikater». Her er utdataene etter at serverens sertifikat var godkjent. Bibliotekets advarselslogger er utelatt.

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

Tidspunktene er i UTC.

Utstyrets data vises som noder i adresserommet

En OPC UA-server eksponerer dataene den har, som en samling noder. Denne samlingen kalles adresserommet. Noder finnes i ulike typer, for eksempel objekter som representerer mapper eller utstyr, og variabler som inneholder verdier, og nodene er koblet til hverandre med referanser. Klienten følger referansene med Browse-tjenesten for å finne ut hvilke noder som finnes6.

I utdataene ovenfor følger klienten hierarkiske referanser fra Line1 og finner fire variabler.

Adresserommet til den simulerte enhetenEt diagram som viser et Line1-objekt under Objects-mappen med fire variabler under seg: Temperature, PartCount, Running og Setpoint.ObjectsLine1 (objekt)Temperature (variabel)PartCount (variabel)Running (variabel)Setpoint (variabel, skrivbar)

Figur 3: Adresserommet til den simulerte enheten. Klienten finner noder ved å følge referanser med Browse.

En NodeId består av et navnerom og en identifikator

Noder identifiseres med en NodeId. I utdataene betyr ns=2;s=Line1.Temperature at navneromsindeksen er 2, og at identifikatoren er strengen Line1.Temperature.

Et navnerom er egentlig en URI. Ifølge spesifikasjonen representeres navneroms-URI-er med tall i tjenestene, og tallet tilsvarer posisjonen i serverens NamespaceArray. Indeks 0 er OPC UA-navnerommet http://opcfoundation.org/UA/7.

Derfor bør du ikke skrive indeksnummeret inn i koden, men slå opp indeksen fra URI-en. Koden ovenfor bruker session.NamespaceUris.GetIndex(LineNamespace) for å få 2 fra URI-en og bygger NodeId-ene ut fra den.

Bygge en NodeId fra en navneroms-URIEt diagram som viser at klienten slår opp navneroms-URI-en i serverens NamespaceArray for å få indeksen, og kombinerer indeksen med identifikatoren for å bygge en NodeId.Navneroms-URIServerens NamespaceArrayIndeks (2 i dette eksemplet)NodeId ns=2;s=Line1.TemperatureIdentifikator Line1.Temperature

Figur 4: Navneromsindeksen slås opp fra URI-en og kombineres med identifikatoren til en NodeId.

Lesing og skriving returnerer status og tidsstempler sammen med verdien

Variablenes verdier leses og skrives med tjenestene Read og Write8. En lesing returnerer mer enn bare verdien. Den returnerer en struktur som heter DataValue, og som inneholder verdien, en statuskode, et kildetidsstempel og et servertidsstempel9.

Element Betydning
Verdi Variabelens verdi. Når statuskoden angir en feil, ignoreres verdien, og serveren setter den til null
Statuskode (StatusCode) Om serveren klarte å hente og levere verdien
SourceTimestamp Tidspunktet datakilden knyttet til verdien
ServerTimestamp Tidspunktet serveren mottok verdien eller fikk vite at den var korrekt

Statuskodens to øverste biter angir Good (vellykket), Uncertain (delvis vellykket og kanskje ikke brukbar til alle formål) eller Bad (mislykket)10. Good [0x00000000] i utdataene er Good.

Da jeg leste Line1.Pressure, som ikke finnes, kom BadNodeIdUnknown [0x80340000] tilbake i stedet for en verdi. Dette bibliotekets ReadValueAsync kaster et unntak når statusen er Bad4. Hvis et system som registrerer verdier, også lagrer statuskoden ved siden av hver verdi, kan du i ettertid skille ut verdiene som ble registrert mens statusen var Bad.

Hva en DataValue inneholderEt diagram som viser at DataValue-en som en lesing eller et varsel returnerer, inneholder fire ting, verdien, statuskoden, kildetidsstempelet og servertidsstempelet, og at verdien ignoreres når statuskoden er Bad.Hvis BadDataValueVerdiStatuskodeSourceTimestamp (tid ved kilden)ServerTimestamp (tid ved serveren)Ignorer verdien

Figur 5: Resultatet av en lesing inneholder ikke bare verdien, men også en statuskode og to tidsstempler.

Ved skrivingen skrev jeg 32.5 til Setpoint, og Good kom tilbake som statuskode. Serveren bestemmer hvilke variabler som er skrivbare. I denne simulerte serveren er bare Setpoint skrivbar. Om utstyrets settpunkter skal kunne endres utenfra, er et spørsmål om utstyrets sikkerhetsdesign, så det omhandler ikke denne artikkelen.

Få varsel om endringer: abonnementer og MonitoredItems

For å følge endringer i verdier kan klienten gjenta Read med et fast intervall. I tillegg har OPC UA en mekanisme der overvåkingen av verdier overlates til serveren, som så varsler om endringene. Den mekanismen er abonnementer (Subscription) og MonitoredItems.

Et MonitoredItem er ett element som overvåkes, og det har et samplingsintervall. Samplingsintervallet er den raskeste takten serveren sampler verdier fra kilden med, og spesifikasjonen beskriver det som en «best effort»-syklus11. Når en sample oppfyller filterets betingelser, opprettes et varsel. Hvis det ikke er angitt noe filter, sendes et varsel når verdien eller statusen endres12.

Varslene legges først i MonitoredItem-ets kø. Du bestemmer på forhånd om det eldste eller det nyeste forkastes når køen renner over, og hvis køstørrelsen er 2 eller mer, settes Overflow-biten, som angir forkasting, i statuskoden13.

Et abonnement samler flere MonitoredItems og sender ved hvert publiseringsintervall varslene som ennå ikke er sendt, til klienten. Når det går sykluser uten varsler å sende, sender det en keep-alive for å vise at abonnementet fortsatt er aktivt14.

Slik fungerer abonnementer og MonitoredItemsEt diagram som viser at et MonitoredItem sampler kildens verdi ved samplingsintervallet og legger varsler som passerer filteret, i en kø, og at abonnementet sender dem samlet til klienten ved publiseringsintervallet og sender en keep-alive når det ikke er varsler.SamplingsintervallPubliseringsintervallHvis det ikke er varslerKilde (utstyrets verdi)MonitoredItemFilter (standard: endring i verdi eller status)Kø (Overflow når den er full)AbonnementKlientKeep-alive

Figur 6: MonitoredItem-et sampler og legger i kø, og abonnementet sender varslene samlet til klienten.

Koden ovenfor abonnerer på Temperature og PartCount med et samplingsintervall på 500 millisekunder og en køstørrelse på 515. I utdataene kommer temperaturen omtrent hvert 0,5 sekund og antall deler omtrent hvert 2. sekund. De to første varslene var verdiene hver variabel hadde da abonnementet startet. Antall deler sitt 13:40:50.801 er tidspunktet verdien endret seg, før abonnementet startet. Fordi varslene viser kildetidsstempelet, viser de tidspunktet verdien endret seg, ikke tidspunktet varselet ble mottatt.

Valget mellom gjentatte lesinger og abonnementer

Ved gjentatte lesinger bestemmer klienten selv intervallet, og mekanismen er enkel. Til gjengjeld går en forespørsel og et svar fram og tilbake hver gang, også når verdien ikke har endret seg, og endringer som er kortere enn intervallet, går tapt. Med et abonnement sampler serveren og sender bare endringer, så trafikken øker ikke mye, selv for verdier som sjelden endres. Men også med et abonnement blir endringer som er kortere enn samplingsintervallet, kanskje ikke varslet. Spesifikasjonen tillater raskere sampling enn ønsket, men krever det ikke11.

Forskjellen mellom gjentatte lesinger og abonnementerEt diagram som viser at ved gjentatte lesinger bestemmer klienten intervallet og sender en forespørsel hver gang, at ved abonnementer sampler serveren ved samplingsintervallet og sender bare endringer, og at begge kan gå glipp av endringer som er kortere enn intervallet.Følge endringer i verdierGjenta lesingerAbonnereKlienten bestemmer intervallet og spør hver gangServeren sampler og sender bare endringerEndringer kortere enn intervallet kan gå tapt

Figur 7: Med begge metodene kan endringer som er kortere enn intervallet, gå tapt.

Godkjenning av tilkoblinger med sertifikater

I en OPC UA-tilkobling velger du graden av beskyttelse av meldinger med MessageSecurityMode. Verdiene er None, som ikke beskytter, Sign, som bare signerer, og SignAndEncrypt, som signerer og krypterer16. Kombinasjonen av algoritmer for signering og kryptering bestemmes av sikkerhetspolicyen (security policy), og denne artikkelen bruker Basic256Sha256.

Signering og kryptering bruker et sertifikat for hver applikasjon. Klienten og serveren kontrollerer hverandres sertifikater og avviser tilkoblinger fra parter de ikke stoler på. En server som er laget med OPC Foundations .NET-bibliotek, tilbyr som standard ikke den ukrypterte None-policyen4.

Den første tilkoblingen stoppet med BadCertificateUntrusted

Ved den første kjøringen stolte ikke klienten på serverens sertifikat, så tilkoblingen ble avvist. Her er en del av utdataene.

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

Dette biblioteket lagrer avviste sertifikater i rejected-lageret. Når en administrator flytter et sertifikat derfra til trusted-lageret, begynner biblioteket å stole på den aktuelle parten17. Denne gangen flyttet jeg serverens sertifikat fra pki/rejected/certs til pki/trusted/certs, kjørte programmet på nytt og fikk koblet til. Før du flytter det, bør du bekrefte avtrykket (thumbprint), som også inngår i filnavnet, med serverens administrator4.

Trinn for å stole på et sertifikatEt diagram som viser at klienten ved den første tilkoblingen avviser serverens sertifikat og lagrer det i rejected-lageret, og at når avtrykket er bekreftet og sertifikatet er flyttet til trusted-lageret, stoler den neste tilkoblingen på det og lykkes.Første tilkoblingBadCertificateUntrustedSertifikatet lagres i rejected-lageretBekreft avtrykket med serverens administratorFlytt det til trusted-lageretDen neste tilkoblingen stoler på det og lykkes

Figur 8: Når et avvist sertifikat er kontrollert og flyttet til trusted, stoler klienten på den parten.

Det finnes også innstillingen AutoAcceptUntrustedCertificates, som automatisk godtar sertifikater det ikke er tillit til. Standardverdien er false, og ifølge bibliotekets dokumentasjon er den nyttig i utviklingsmiljøer, men den bør ikke brukes i produksjon17.

NotAfter=2027-11-01 i loggen er utløpsdatoen for den simulerte serverens sertifikat. Det er et selvsignert sertifikat som er laget med en asyncua-funksjon, og det utløp ett år etter dagen det ble laget. Utstyrets sertifikater utløper også, så fastsett som en del av driften hvordan de skal byttes ut før de utløper, og hvordan motparten skal få tillit til de nye. Håndtering av sertifikatlageret i Windows omtales i «The Windows Certificate Store in Practice».

Brukerautentisering

Det sertifikatene verifiserer, er applikasjonene. I tillegg overføres et token som identifiserer brukeren, når en økt åpnes. Spesifikasjonen definerer fire typer: anonym, brukernavn og passord, X.509-sertifikat og eksternt utstedte tokener18. Klienten i denne artikkelen koblet til anonymt. Hvilke tokener en server godtar, går fram av serverens endepunktinformasjon19.

Der datainnsamling i praksis går galt

Herfra går jeg gjennom problemer som ofte oppstår i systemer som samler inn utstyrsdata, basert på dette eksperimentet og spesifikasjonen.

Verdier fra perioden serveren var stoppet, kommer ikke fram

Under abonnementet stoppet jeg serveren og startet den på nytt omtrent 15 sekunder senere. Dette bibliotekets ManagedSession kobler automatisk til på nytt når tilkoblingen brytes20. Mens serveren var stoppet, fortsatte klienten å prøve å koble til på nytt.

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.

Da serveren kom tilbake, kom også varslene tilbake. Her er en del av abonnementets utdata.

  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]

Det kom ikke én eneste verdi fra tidsrommet 13:41:13 til 13:41:34. Antall deler gikk tilbake fra 78 til 2. Det skyldes at den simulerte serveren begynte å telle fra 0 igjen etter omstarten.

I dette eksemplet var selve serveren stoppet, så verdiene fra den perioden er ikke lagret noe sted. Gjenoppkoblingsmekanismen gjenoppretter tilkoblingen, men den fyller ikke i ettertid inn verdiene fra perioden serveren var nede.

Situasjonen er en annen når bare kommunikasjonen brytes og serveren fortsetter å kjøre. Spesifikasjonen har en Republish-tjeneste som får serveren til å sende varselmeldinger som ikke kom fram, på nytt fra sin kø for gjensending21. Varsler samler seg også i MonitoredItem-ets kø, men alt utover køstørrelsen forkastes13. Jeg prøvde ikke dette tilfellet i eksperimentet. I begge tilfeller bør du registrere tidspunktene for brudd og gjenoppkobling, slik at du kan se i hvilken periode det ikke kom verdier. Gjenoppkobling og loggdesign for utstyr med seriell kommunikasjon omtales også i «Serial Communication App Pitfalls».

Registrering når serveren stopperEt diagram som viser at når serveren stopper under et abonnement, fortsetter klienten å prøve å koble til på nytt, og varslene kommer tilbake når serveren er tilbake, men verdiene fra perioden den var stoppet, kommer ikke fram, så tidspunktene for brudd og gjenoppkobling bør registreres.Abonnement aktivtServeren stopperKlienten prøver gjentatte ganger å koble til på nyttServeren er tilbake, og varslene fortsetterVerdier fra perioden den var stoppet, kommer ikke framRegistrer tidspunktene for brudd og gjenoppkobling

Figur 9: Gjenoppkobling gjenoppretter tilkoblingen, men verdiene fra perioden serveren var stoppet, kommer ikke fram.

Hvilket tidsstempel som skal registreres

Ifølge spesifikasjonen er SourceTimestamp tidspunktet datakilden knyttet til verdien, og det bør angi når verdien endret seg. ServerTimestamp er tidspunktet serveren mottok verdien eller fikk vite at den var korrekt9. Den simulerte serveren i denne artikkelen genererer verdiene selv, så de to tidspunktene var like.

Hvis du vil vite når en verdi endret seg i utstyret, bruker du SourceTimestamp. Tidspunktet settes likevel av kildens klokke, så hvis klokken i utstyret eller serveren går feil, blir også registreringen feil. Hvis du skal sammenholde de innsamlede dataene med andre registreringer, bør du også lagre tidspunktet på den mottakende PC-en ved siden av, slik at du senere kan kontrollere avvik. Tidssynkronisering på Windows-siden omtales i «Windows Time Synchronization (w32time) and Business Systems».

Ikke behandle verdier med en annen status enn Good som vanlige tall

Når statuskoden er Bad, skal verdien ignoreres9. Når den er Uncertain, er verdien kanskje ikke brukbar til alle formål10. Før verdiene føres inn i grafer eller summeringer, bør du sortere dem etter statuskode og fastsette hvordan verdier som ikke er Good, skal håndteres. Overflow-biten for en kø som renner over, settes også i statuskoden13. Hvordan utstyrets tilstand vises på skjermen, omtales i «Best Practices for Checking and Displaying External Device State».

Åpne porten i brannmuren

OPC UA-porten som er registrert hos IANA, er 4840/TCP3. Ikke alle servere bruker likevel dette nummeret. OPC Foundations .NET-eksempler bruker blant annet port 625414. Kontroller portnummeret i utstyrets endepunkt-URL, og tillat tilkoblinger til den porten. Hvis serveren kjører på Windows, må det tillates i den maskinens Windows-brannmur, og hvis det er nettverksutstyr imellom, på det utstyret. Oppretting av innkommende regler omtales i «The Windows Firewall and Business Applications».

Der datainnsamling går galtEt diagram som viser stedene der innsamling av utstyrsdata går galt, delt inn i innholdet i registreringene (verdier fra perioden serveren var stoppet, kommer ikke fram, hvilket tidsstempel som registreres, håndtering av verdier som ikke er Good) og forberedelse av tilkoblingen (brannmurporter, sertifikaters utløp).Der datainnsamling går galtInnholdet i registreringeneForberedelse av tilkoblingenVerdier fra et stopp kommer ikke framHvilket tidsstempelHåndtering av verdier som ikke er GoodBrannmurporterSertifikaters utløp

Figur 10: Selv når tilkoblingen fungerer, bør du kontrollere hull i registreringene, tidspunktene, statusen, portene og sertifikatene.

Valget mellom OPC UA, Modbus/TCP og produsentspesifikke protokoller

For kommunikasjon med PLS-er og utstyr finnes det i tillegg til OPC UA også Modbus/TCP og protokoller som PLS-produsentene tilbyr.

Modbus-spesifikasjonen håndterer data i fire tabeller: Discrete Input (en enkelt bit som bare kan leses), Coil (en enkelt bit som kan leses og skrives), Input Register (et 16-biters ord som bare kan leses) og Holding Register (et 16-biters ord som kan leses og skrives)22. Hos IANA er 502/TCP registrert som Modbus’ applikasjonsprotokoll (mbap)3.

Det som ligger i disse tabellene, er biter og 16-biters ord, så hvilken verdi som ligger på hvilken adresse og i hvilket format, må kontrolleres i utstyrets dokumentasjon og skrives på klientsiden. Med OPC UA eksponerer serveren, som vi har sett, navngitte variabler i adresserommet sitt, og klienten kan finne dem med Browse. Verdiene følges av en statuskode og tidsstempler, og tilkoblinger godkjennes med sertifikater.

Hva du bør bruke, avgjøres ofte av hva utstyret tilbyr. Hvis utstyret har en OPC UA-server, bruker du den. Hvis det bare har Modbus/TCP, lager du en adressetabell og leser ut fra den. Hvis det bare finnes produsentens protokoll eller SDK, følger du dokumentasjonen for den. Valg av kommunikasjonsmetode for USB-enheter omtales i «How to Work with USB Devices from a Windows App».

Valg av kommunikasjonsmetode med utstyretEt diagram som viser at hvis utstyret har en OPC UA-server, blar du gjennom og samler inn variabler med OPC UA, hvis det bare har Modbus/TCP, kontrollerer du adresser og formater i dokumentasjonen og leser registre, og hvis det bare har produsentens protokoll, følger du dokumentasjonen for den.OPC UA-serverBare Modbus/TCPBare produsentens protokollKommunikasjon utstyret tilbyrHva finnes?Bla gjennom og samle inn variableneKontroller adresser og formater i dokumentasjonenFølg produsentens dokumentasjon og SDKLes registrene og gjør dem om til verdier

Figur 11: Hva du samler inn med, avgjøres ofte av hva utstyret tilbyr.

Relaterte artikler

Referanser

  1. OPC Foundation, Classic. At OPC Classic bygger på COM/DCOM, spesifikasjonene DA, A&E og HDA, publiseringen av OPC UA i 2008 og grunnene til omleggingen. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Del 1 av IEC 62541-serien, som omhandler begreper og oversikt for OPC UA (senere erstattet av en nyere utgave). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. Registreringene av opcua-tcp på 4840/TCP og mbap på 502/TCP. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Hvordan en klient i 2.0-serien skrives, at ReadValueAsync kaster et unntak ved Bad, rejected- og trusted-lagrene, at servere som standard ikke tilbyr None, og portnumrene eksemplene bruker. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Python-biblioteket for OPC UA som ble brukt til den simulerte utstyrsserveren. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Tjenesten som følger en nodes referanser. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. At navneroms-URI-er representeres med tall i tjenestene, og at tallet tilsvarer en posisjon i NamespaceArray. ↩

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

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definisjonene av verdi, statuskode, SourceTimestamp og ServerTimestamp. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain og Bad, som angis av de to øverste bitene. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. At samplingsintervallet er en best effort-syklus. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. At det uten filter sendes et varsel ved endring i verdi eller status. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Køstørrelsen, forkastingspolicyen og Overflow-biten. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publiseringsintervallet og keep-alives. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync og samplingsintervallet og køstørrelsen i MonitoredItemOptions. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Betydningene av None, Sign og SignAndEncrypt. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Praksisen med å flytte sertifikater fra rejected-lageret til trusted-lageret, og at AutoAcceptUntrustedCertificates ikke skal brukes i produksjon. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De fire typene: anonym, brukernavn, X.509 og utstedte tokener. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Listen over brukertokener et endepunkt godtar (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. At ManagedSession har en innebygd gjenoppkoblingsmekanisme. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Tjenesten som får et abonnements kø for gjensending til å sende varselmeldinger på nytt. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (en kopi på en server ved University of Chicago). Datamodellen i avsnitt 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩

Nyere artikler med de samme taggene. Fordyp deg i beslektede emner.

Dette er temasider som ligger nær temaet for artikkelen. Med artikkelen som utgangspunkt kan du gå videre til relaterte tjenester og andre artikler.

Denne artikkelen henger sammen med følgende tjenestesider. Se dem gjennom den inngangen som ligger nærmest.

Windows-apputvikling

Utvikling av en Windows-app som samler inn data fra utstyr, inkludert abonnementsinnstillinger, gjenoppkobling og registrering av tidsstempler og status, kan håndteres som utvikling av Windows-apper.

Teknisk rådgivning og designgjennomgang

Hvis du vil avgjøre om data skal samles inn med OPC UA, eller om Modbus/TCP eller produsentens egen protokoll skal brukes, og hvordan sertifikater skal administreres, kan det håndteres som teknisk rådgivning og designgjennomgang.

Ofte stilte spørsmål

Vanlige spørsmål ved rådgivning om temaet for denne artikkelen.

Hva er forskjellen på OPC UA og OPC Classic (OPC DA)?
OPC Classic er et sett spesifikasjoner som utveksler data via Windows COM/DCOM. OPC UA, som ble publisert i 2008, er ikke knyttet til et bestemt operativsystem og samler funksjonene fra Classic. OPC UA-klienter kjører også utenfor Windows, og det trengs ingen DCOM-konfigurasjon.
Holder det å lese verdiene med et fast intervall når de samles inn med OPC UA?
Hvis det er få verdier og intervallet er langt, kan du samle dem inn ved å lese. Hvis du vil følge endringer i verdiene, kan du i stedet bruke et abonnement, der serveren står for samplingen og varsler om endringene. Uansett metode bør du registrere statuskoden og tidsstemplene som følger med hver verdi.
Hvorfor får jeg BadCertificateUntrusted når jeg kobler til?
Fordi klienten ennå ikke stoler på serverens sertifikat. OPC Foundations .NET-bibliotek lagrer avviste sertifikater i rejected-lageret. Bekreft sertifikatets avtrykk (thumbprint) med serverens administrator, og flytt det deretter til trusted-lageret. Bruk innstillinger som godtar sertifikater automatisk, bare i utviklingsmiljøer.
Hvilket portnummer bruker OPC UA?
Hos IANA er 4840/TCP registrert som opcua-tcp. Ekte servere bruker likevel noen ganger en annen port, så kontroller utstyrets innstillinger. Hvis serveren kjører på Windows, må trafikk til porten tillates i Windows-brannmuren, og hvis det er nettverksutstyr imellom, også på det utstyret.

Om forfatteren

Profilside for artikkelens forfatter.

Go Komura

Representant for KomuraSoft LLC

Arbeider særlig med Windows-programvareutvikling, teknisk rådgivning og feilsøking, og har sin styrke i prosjekter med eksisterende systemer og i undersøkelse av feil der årsaken er vanskelig å se.

Tilbake til bloggen