Kom igång med OPC UA ── Samla in data från PLC:er och utrustning i en Windows-app

· · Windows, OPC UA, Tillverkning, PLC, C#, .NET, Kommunikation, Certifikat

Dela på X
Ändringshistorik (endast första versionen, publicerad 2026-11-01)
Först publicerad
Citera den här artikeln(DOI: 10.5281/zenodo.23968448)

Den här artikeln är arkiverad på Zenodo. Nedan visas både den DOI som alltid leder till den senaste versionen och den DOI som är knuten till den version du läser nu.

Go Komura (2026). Kom igång med OPC UA ── Samla in data från PLC:er och utrustning i en Windows-app. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968448 https://comcomponent.com/sv/blog/opc-ua-windows-data-collection/

DOI (senaste versionen)
10.5281/zenodo.23968448
DOI (den här versionen)
10.5281/zenodo.23968449

När du samlar in data från PLC:er eller utrustning till en Windows-app och utrustningen har en OPC UA-server kan du ibland slippa skriva separat kommunikation för varje tillverkare. Det räcker dock inte att ansluta och läsa värden. Om du inte känner till statusen och tidsstämplarna som kommer tillbaka med varje värde, prenumerationer som meddelar dig om förändringar, godkännande av anslutningar med certifikat och hur man återhämtar sig efter ett avbrott, kan du inte lita på de insamlade uppgifterna i efterhand.

Den här artikeln förklarar de delar av OPC UA som behövs för datainsamling, tillsammans med resultaten från körningar med en C#-klient. Klienten använder OPC Foundations .NET-bibliotek, och på andra sidan finns en simulerad utrustningsserver. Jag har inte provat mot någon specifik PLC eller utrustning. Kontrollera utrustningsspecifika inställningar i dokumentationen för respektive utrustning.

Insamling av bilder från GigE Vision- och USB3 Vision-kameror beskrivs i ”Dropped Frames with GigE Vision and USB3 Vision Cameras”, och utrustning med seriell kommunikation i ”Serial Communication App Pitfalls”.

Skillnaden mellan OPC UA och OPC Classic

Specifikationerna i OPC Classic använder COM/DCOM i Windows för att utbyta data mellan programvarukomponenter. OPC DA för processdata, OPC A&E för larm och OPC HDA för historik är definierade som separata specifikationer1.

OPC Foundation publicerade OPC UA (Unified Architecture) 2008. OPC UA är en tjänsteorienterad arkitektur som inte är beroende av en viss plattform och som samlar funktionerna i de olika Classic-specifikationerna. Som skäl till omarbetningen nämner OPC Foundation bland annat att Microsoft började prioritera plattformsoberoende lösningar framför COM/DCOM, att det behövdes stöd för system från andra än Microsoft och för inbyggda enheter, och att det behövdes säkra anslutningar genom brandväggar1.

OPC UA-specifikationen har också standardiserats av IEC som serien IEC 625412. För kommunikationsporten har IANA registrerat 4840/TCP som opcua-tcp3.

Skillnaden mellan OPC Classic och OPC UAEtt diagram som visar att OPC Classic bygger på COM och DCOM i Windows och är uppdelad i separata DA-, A&E- och HDA-specifikationer, medan OPC UA samlar deras funktioner i en enda plattformsoberoende specifikation.Funktionerna samlasOPC ClassicKörs ovanpå COM/DCOM (Windows)Uppdelad i DA, A&E och HDAOPC UA (2008)Inte bunden till en viss plattformStandardiserad som IEC 62541

Figur 1: OPC Classic består av separata specifikationer ovanpå COM/DCOM, och OPC UA samlar dem i en.

Om befintlig utrustning bara har en OPC DA-server behövs en lösning på klientsidan som använder COM/DCOM. Den här artikeln behandlar bara OPC UA.

I diagrammet markerar en heldragen linje en relation som alltid gäller och en streckad linje en villkorad relation (villkoren anges per relation på detaljsidan). Den fullständiga listan över relationer (totalt 13, med belägg och säkerhetsgrad) och definitionerna av huvudbegreppen finns samlade på detaljsidan för kunskapskartan (på japanska). Data: JSON-LD / Turtle

Konfigurationen jag provade

Det här är konfigurationen jag körde.

Roll Det jag använde
Klient C#, .NET 10 SDK 10.0.114, NuGet-paketet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Server En simulerad utrustning byggd med Python 3.13 och asyncua 2.1.0 (port 4840 på samma dator)
Operativsystem Linux (körning på Windows har inte provats)

Klientbiblioteket är den .NET-implementation som OPC Foundation publicerar. För hur 2.0-serien används följde jag beskrivningen i Getting started i samma repository4.

Konfigurationen jag provadeEtt diagram som visar en C#-klient som använder OPC Foundations .NET-bibliotek och ansluter via en krypterad anslutning till en simulerad utrustningsserver byggd med asyncua som lyssnar på port 4840.opc.tcp, SignAndEncryptC#-klient (LineCollector)Simulerad utrustningsserver (asyncua)OPC Foundations .NET-bibliotek 2.0.1Fyra variabler under Line1

Figur 2: På samma dator anslöt C#-klienten till den simulerade utrustningsservern.

Den simulerade utrustningsservern

I stället för verklig utrustning byggde jag en liten server med asyncua5. Under ett objekt som heter Line1 finns fyra variabler: temperatur (Temperature), antal tillverkade delar (PartCount), i drift (Running) och börvärde (Setpoint). Temperaturen ändras var 0,5 sekund och antalet delar varannan sekund. För säkerheten erbjöd jag två alternativ: ingen kryptering (None) samt signering och kryptering (SignAndEncrypt med Basic256Sha256).

# En simulerad utrustning för att kontrollera funktionen. Den ersätter inte verklig utrustning eller PLC: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())

Den här servern accepterar anslutande klienters certifikat utan att kontrollera dem. Det är en testinställning och skiljer sig från hur verklig utrustning beter sig.

C#-klienten

Jag lade till följande två paket i projektfilen.

<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 ansluter, bläddrar, läser, skriver och prenumererar, i den ordningen. Vad varje del betyder förklaras i tur och ordning i följande avsnitt.

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

        // Namnrymdsindexet bestäms av servern. Slå upp det från URI:n och bygg NodeId utifrån det
        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);

        // Bläddra: lista variablerna 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}");
        }

        // Läsning: titta inte bara på värdet utan även på statusen och de två tidsstämplarna
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Läser man en nod som inte finns kommer en Bad-status tillbaka i stället för ett värde
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Skrivning: sätt Setpoint till 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]}");

        // Prenumeration: be servern sampla var 500:e ms och meddela oss om förändringar
        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örsta gången jag körde programmet gick det inte att ansluta. Orsaken förklaras i avsnittet ”Godkänna anslutningar med certifikat”. Så här ser utdata ut efter att serverns certifikat har gjorts betrott. Bibliotekets varningsloggar är utelämnade.

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

Tiderna anges i UTC.

Utrustningens data syns som noder i adressrymden

En OPC UA-server exponerar de data den har som en samling noder. Den samlingen kallas adressrymden (address space). Noder finns i olika slag, till exempel objekt som representerar mappar eller utrustning och variabler som har värden, och noderna är kopplade till varandra genom referenser. Klienten följer referenserna med tjänsten Browse för att ta reda på vilka noder som finns6.

I utdata ovan följer klienten hierarkiska referenser från Line1 och hittar fyra variabler.

Den simulerade utrustningens adressrymdEtt diagram som visar ett Line1-objekt under mappen Objects, med fyra variabler under sig: Temperature, PartCount, Running och Setpoint.ObjectsLine1 (objekt)Temperature (variabel)PartCount (variabel)Running (variabel)Setpoint (variabel, skrivbar)

Figur 3: Den simulerade utrustningens adressrymd. Klienten hittar noder genom att följa referenser med Browse.

En NodeId består av en namnrymd och en identifierare

Noder identifieras med NodeId. I utdata betyder ns=2;s=Line1.Temperature att namnrymdsindexet är 2 och att identifieraren är strängen Line1.Temperature.

En namnrymd är egentligen en URI. Specifikationen anger att namnrymdens URI representeras med ett tal i tjänsterna och att talet motsvarar positionen i serverns NamespaceArray. Index 0 är OPC UA:s namnrymd, http://opcfoundation.org/UA/7.

Därför ska du inte skriva in indexets tal i koden, utan slå upp indexet från URI:n. Koden ovan använder session.NamespaceUris.GetIndex(LineNamespace) för att få fram 2 från URI:n och bygger NodeId utifrån det.

Att bygga en NodeId från namnrymdens URIEtt diagram som visar att klienten slår upp namnrymdens URI i serverns NamespaceArray för att få indexet, och kombinerar indexet med identifieraren för att bygga en NodeId.Namnrymdens URIServerns NamespaceArrayIndex (2 i det här exemplet)NodeId ns=2;s=Line1.TemperatureIdentifierare Line1.Temperature

Figur 4: Slå upp namnrymdsindexet från URI:n och kombinera det med identifieraren till en NodeId.

Läsning och skrivning returnerar status och tidsstämplar tillsammans med värdet

Variablernas värden läses och skrivs med tjänsterna Read och Write8. En läsning returnerar mer än bara värdet. Den returnerar en struktur som heter DataValue med värdet, en statuskod, en källtidsstämpel och en servertidsstämpel9.

Post Betydelse
Värde Variabelns värde. När statuskoden anger ett fel ska värdet ignoreras, och servern sätter det till null
Statuskod (StatusCode) Om servern kunde hämta och tillhandahålla värdet
SourceTimestamp Den tid som datakällan satte på värdet
ServerTimestamp Den tid då servern tog emot värdet eller fick veta att det var korrekt

Statuskodens två högsta bitar anger Good (lyckat), Uncertain (delvis lyckat, kan vara oanvändbart för vissa ändamål) eller Bad (misslyckat)10. Good [0x00000000] i utdata är Good.

När jag läste Line1.Pressure, som inte finns, kom BadNodeIdUnknown [0x80340000] tillbaka i stället för ett värde. Det här bibliotekets ReadValueAsync kastar ett undantag när statusen är Bad4. Om ett system som registrerar värden även sparar statuskoden bredvid varje värde kan du i efterhand skilja ut de värden som registrerades medan statusen var Bad.

Vad en DataValue innehållerEtt diagram som visar att den DataValue som returneras vid en läsning eller ett meddelande innehåller fyra saker, värdet, statuskoden, källtidsstämpeln och servertidsstämpeln, och att värdet ignoreras när statuskoden är Bad.Om BadDataValueVärdeStatuskodSourceTimestamp (tid vid källan)ServerTimestamp (tid vid servern)Ignorera värdet

Figur 5: Resultatet av en läsning innehåller inte bara värdet utan även en statuskod och två tidsstämplar.

Vid skrivningen skrev jag 32,5 till Setpoint, och Good kom tillbaka som resulterande statuskod. Det är servern som avgör vilka variabler som är skrivbara. I den här simulerade servern är bara Setpoint skrivbar. Om utrustningens börvärden ska kunna skrivas om utifrån är en fråga om utrustningens säkerhetsdesign, så den här artikeln tar inte upp det.

Få meddelanden om förändringar: prenumerationer och MonitoredItem

För att följa förändringar i värden kan klienten upprepa Read med jämna mellanrum. Därutöver har OPC UA en mekanism som överlåter övervakningen av värden till servern och låter den meddela dig om förändringar. Det är prenumerationer (subscription) och MonitoredItem.

En MonitoredItem är ett enskilt objekt som övervakas, och den har ett samplingsintervall. Samplingsintervallet är den snabbaste takt med vilken servern samplar värden från källan, och specifikationen beskriver det som en ”best effort”-cykel11. När ett sampel uppfyller filtrets villkor skapas ett meddelande. Om inget filter anges skickas ett meddelande när värdet eller statusen ändras12.

Meddelandena hamnar först i MonitoredItems kö. Du bestämmer i förväg om det äldsta eller det nyaste ska kastas när kön blir full, och om köstorleken är 2 eller större sätts Overflow-biten, som visar att något har kastats, i statuskoden13.

En prenumeration samlar flera MonitoredItem och skickar vid varje publiceringsintervall de meddelanden som ännu inte har skickats till klienten. När det under flera cykler i följd inte finns några meddelanden att skicka, skickar den ett keep-alive-meddelande för att visa att prenumerationen fortfarande lever14.

Hur prenumerationer och MonitoredItem fungerarEtt diagram som visar att en MonitoredItem samplar källans värde med samplingsintervallet och lägger meddelanden som passerar filtret i en kö, och att prenumerationen skickar dem samlat till klienten med publiceringsintervallet och skickar keep-alive när det inte finns några meddelanden.SamplingsintervallPubliceringsintervallOm inga meddelandenKälla (utrustningens värde)MonitoredItemFilter (standard: ändring av värde eller status)Kö (Overflow när den är full)PrenumerationKlientKeep-alive

Figur 6: MonitoredItem samplar och köar, och prenumerationen skickar meddelandena samlat till klienten.

Koden ovan prenumererar på Temperature och PartCount med ett samplingsintervall på 500 millisekunder och en köstorlek på 515. I utdata kommer temperaturen ungefär var 0,5 sekund och antalet delar ungefär varannan sekund. De två första meddelandena var de värden som respektive variabel hade när prenumerationen startade. Tiden 13:40:50.801 för antalet delar är den tid då värdet ändrades, innan prenumerationen startade. Eftersom meddelandena visar källtidsstämpeln visas tiden då värdet ändrades, inte tiden då meddelandet togs emot.

Upprepad läsning eller prenumeration

Med upprepad läsning kan klienten själv bestämma intervallet, och mekanismen är enkel. I gengäld skickas en begäran och ett svar fram och tillbaka varje gång, även när värdet inte har ändrats, och förändringar som är kortare än intervallet missas. Med en prenumeration samplar servern och skickar bara förändringar, så trafiken ökar inte nämnvärt ens för värden som sällan ändras. Även med en prenumeration meddelas dock förändringar som är kortare än samplingsintervallet inte alltid. Specifikationen tillåter att servern samplar snabbare än begärt men kräver det inte11.

Skillnaden mellan upprepad läsning och prenumerationEtt diagram som visar att klienten vid upprepad läsning bestämmer intervallet och skickar en begäran varje gång, att servern vid prenumeration samplar med samplingsintervallet och bara skickar förändringar, och att båda kan missa förändringar som är kortare än intervallet.Följa förändringar i värdenUpprepa läsningarPrenumereraKlienten bestämmer intervallet och frågar varje gångServern samplar och skickar bara förändringarFörändringar kortare än intervallet kan missas

Figur 7: Med båda metoderna kan förändringar som är kortare än intervallet missas.

Godkänna anslutningar med certifikat

I en OPC UA-anslutning väljer du med MessageSecurityMode hur väl meddelandena ska skyddas. Värdena är None, som inte ger något skydd, Sign, som bara signerar, och SignAndEncrypt, som signerar och krypterar16. Kombinationen av algoritmer för signering och kryptering bestäms av säkerhetspolicyn (security policy), och den här artikeln använder Basic256Sha256.

För signering och kryptering används ett certifikat för varje applikation. Klienten och servern kontrollerar varandras certifikat och avvisar anslutningar från parter de inte litar på. En server som byggs med OPC Foundations .NET-bibliotek erbjuder som standard inte den okrypterade policyn None4.

Den första anslutningen stoppades med BadCertificateUntrusted

Vid den första körningen litade klienten inte på serverns certifikat, så anslutningen avvisades. Här är en del av utdata.

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

Det här biblioteket sparar avvisade certifikat i rejected-arkivet. När en administratör flyttar ett certifikat därifrån till trusted-arkivet börjar biblioteket lita på den parten17. Den här gången flyttade jag serverns certifikat från pki/rejected/certs till pki/trusted/certs, körde programmet igen och kunde ansluta. Bekräfta före flytten tumavtrycket, som också ingår i filnamnet, med serverns administratör4.

Stegen för att lita på ett certifikatEtt diagram som visar att klienten vid den första anslutningen avvisar serverns certifikat och sparar det i rejected-arkivet, och att nästa anslutning litar på certifikatet och lyckas efter att tumavtrycket har bekräftats och certifikatet flyttats till trusted-arkivet.Första anslutningenBadCertificateUntrustedCertifikatet sparas i rejected-arkivetBekräfta tumavtrycket med serverns administratörFlytta det till trusted-arkivetNästa anslutning litar på det och lyckas

Figur 8: När ett avvisat certifikat har kontrollerats och flyttats till trusted litar klienten på den parten.

Det finns också en inställning, AutoAcceptUntrustedCertificates, som accepterar ej betrodda certifikat automatiskt. Standardvärdet är false, och bibliotekets dokumentation säger att den är användbar i utvecklingsmiljöer men inte bör användas i produktion17.

NotAfter=2027-11-01 i loggen är utgångsdatumet för den simulerade serverns certifikat. Det är ett självsignerat certifikat som skapades med en asyncua-funktion, och det gick ut ett år efter den dag det skapades. Även utrustningens certifikat har ett utgångsdatum, så bestäm som en del av driften hur de ska bytas ut innan de går ut och hur motparten ska fås att lita på de nya. Hantering av certifikatarkivet i Windows beskrivs i ”The Windows Certificate Store in Practice”.

Autentisering av användare

Det som certifikaten verifierar är applikationerna sinsemellan. Utöver det skickas en token som identifierar användaren när en session öppnas. Specifikationen definierar fyra slag: anonym, användarnamn och lösenord, X.509-certifikat och externt utfärdade token18. Klienten i den här artikeln anslöt anonymt. Vilka token en server accepterar ingår i serverns slutpunktsinformation19.

Där datainsamlingen i fält går fel

Härifrån räknar jag upp problem som ofta uppstår i system som samlar in utrustningsdata, utifrån det här experimentet och specifikationen.

Värden från tiden då servern var stoppad kommer inte fram

Under prenumerationen stoppade jag servern och startade om den ungefär 15 sekunder senare. Det här bibliotekets ManagedSession återansluter automatiskt när anslutningen bryts20. Medan servern var stoppad försökte klienten upprepade gånger att återansluta.

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.

När servern kom tillbaka kom också meddelandena tillbaka. Här är en del av prenumerationens 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]

Inte ett enda värde från tiden mellan 13:41:13 och 13:41:34 kom fram. Antalet delar gick tillbaka från 78 till 2. Det beror på att den simulerade servern startades om och började räkna från 0 igen.

I det här exemplet var det själva servern som var stoppad, så värdena från den perioden finns inte sparade någonstans. Återanslutningsmekanismen återställer anslutningen, men den fyller inte i efterhand i värdena från den tid då servern var nere.

Situationen är en annan när bara kommunikationen bryts och servern fortsätter att köra. Specifikationen har tjänsten Republish, som låter servern skicka meddelanden som inte kom fram en gång till från sin kö för återsändning21. Meddelanden samlas också i MonitoredItems kö, men det som överskrider köstorleken kastas13. Det fallet testade jag inte i det här experimentet. Registrera i båda fallen tiderna för avbrott och återanslutning, så att du kan se under vilken period inga värden kom fram. Återanslutning och loggdesign för utrustning med seriell kommunikation tas också upp i ”Serial Communication App Pitfalls”.

Registrering när servern stoppasEtt diagram som visar att när servern stoppas under en prenumeration försöker klienten upprepade gånger att återansluta och meddelandena kommer tillbaka när servern är tillbaka, men värdena från tiden då den var stoppad kommer inte fram, så tiderna för avbrott och återanslutning bör registreras.Prenumeration pågårServern stoppasKlienten försöker upprepade gånger återanslutaServern kommer tillbaka och meddelandena återupptasVärden från stoppet kommer inte framRegistrera tiderna för avbrott och återanslutning

Figur 9: Återanslutningen återställer anslutningen, men värdena från tiden då servern var stoppad kommer inte fram.

Vilken tidsstämpel ska registreras?

Specifikationen säger att SourceTimestamp är den tid som datakällan satte på värdet och att den bör ange när värdet ändrades. ServerTimestamp är den tid då servern tog emot värdet eller fick veta att det var korrekt9. Den simulerade servern i den här artikeln genererar själv värdena, så de två tiderna var desamma.

Om du vill veta när ett värde ändrades i utrustningen använder du SourceTimestamp. Den tiden sätts dock av källans klocka, så om utrustningens eller serverns klocka går fel blir även registreringen fel. Om du ska jämföra de insamlade uppgifterna med andra register, spara även tiden på den mottagande datorn bredvid, så att du senare kan kontrollera avvikelsen. Tidssynkronisering på Windows-sidan beskrivs i ”Windows Time Synchronization (w32time) and Business Systems”.

Behandla inte värden vars status inte är Good som vanliga tal

När statuskoden är Bad ska värdet ignoreras9. När den är Uncertain kan värdet vara oanvändbart för vissa ändamål10. Sortera värdena efter statuskod innan de används i diagram eller sammanställningar, och bestäm hur värden som inte är Good ska hanteras. Overflow-biten för en full kö sätts också i statuskoden13. Hur utrustningens status ska visas på skärmen tas upp i ”Best Practices for Checking and Displaying External Device State”.

Öppna porten i brandväggen

Den OPC UA-port som är registrerad hos IANA är 4840/TCP3. Alla servrar använder dock inte det numret. OPC Foundations .NET-exempel använder till exempel port 625414. Kontrollera portnumret i URL:en för utrustningens slutpunkt och tillåt anslutningar till den porten. Om servern körs på Windows tillåter du det i den datorns Windows-brandvägg, och om det finns nätverksutrustning emellan tillåter du det på den utrustningen. Hur du skapar regler för inkommande trafik beskrivs i ”The Windows Firewall and Business Applications”.

Där datainsamlingen går felEtt diagram som visar var insamlingen av utrustningsdata går fel, uppdelat på innehållet i registreringen (värden från tiden då servern var stoppad kommer inte fram, vilken tidsstämpel som ska registreras, hantering av värden som inte är Good) och förberedelser för anslutningen (brandväggsportar, certifikatens utgångsdatum).Där datainsamlingen går felInnehållet i registreringenFörberedelser för anslutningenVärden från ett stopp kommer inte framVilken tidsstämpelHantering av värden som inte är GoodBrandväggsportarCertifikatens utgångsdatum

Figur 10: Även när anslutningen fungerar bör du kontrollera luckor i registreringen, tiderna, statusen, portarna och certifikaten.

Välja mellan OPC UA, Modbus/TCP och tillverkarens egen kommunikation

För kommunikation med PLC:er och utrustning finns förutom OPC UA även Modbus/TCP och kommunikation som PLC-tillverkarna tillhandahåller.

Modbus-specifikationen hanterar data i fyra tabeller: Discrete Input, skrivskyddade enskilda bitar; Coil, enskilda bitar som kan läsas och skrivas; Input Register, skrivskyddade 16-bitarsord; och Holding Register, 16-bitarsord som kan läsas och skrivas22. IANA har registrerat 502/TCP för Modbus applikationsprotokoll (mbap)3.

Det som ryms i tabellerna är bitar och 16-bitarsord, så vilket värde som ligger på vilken adress och i vilket format måste kontrolleras i utrustningens dokumentation och skrivas in på klientsidan. I OPC UA exponerar servern, som vi har sett, namngivna variabler i sin adressrymd, och klienten kan ta reda på dem med Browse. Värdena har statuskod och tidsstämplar, och anslutningar godkänns med certifikat.

Vilken som ska användas avgörs ofta av vad utrustningen erbjuder. Om utrustningen har en OPC UA-server använder du den, och om den bara har Modbus/TCP skapar du en adresstabell och läser utifrån den. Om det bara finns tillverkarens egen kommunikation eller SDK följer du dess dokumentation. Hur du väljer kommunikationsmetod för USB-enheter beskrivs i ”How to Work with USB Devices from a Windows App”.

Välja hur man kommunicerar med utrustningEtt diagram som visar att om utrustningen har en OPC UA-server bläddrar du bland och samlar in variablerna med OPC UA, om den bara har Modbus/TCP kontrollerar du adresser och format i dokumentationen och läser register, och om den bara har tillverkarens egen kommunikation följer du dess dokumentation.OPC UA-serverBara Modbus/TCPBara tillverkarens kommunikationKommunikation som utrustningen erbjuderVad finns?Bläddra bland och samla in variablernaKontrollera adresser och format i dokumentationenFölj tillverkarens dokumentation och SDKLäs registren och omvandla dem till värden

Figur 11: Vad du samlar in med avgörs ofta av vad utrustningen erbjuder.

Relaterade artiklar

Referenser

  1. OPC Foundation, Classic. Att OPC Classic bygger på COM/DCOM, specifikationerna DA, A&E och HDA, publiceringen av OPC UA 2008 och skälen till omarbetningen. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Del 1 i serien IEC 62541, som behandlar begreppen och en översikt över OPC UA (sedan dess ersatt av en senare utgåva). ↩

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

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Hur en klient i 2.0-serien skrivs, att ReadValueAsync kastar ett undantag vid Bad, arkiven för avvisade och betrodda certifikat, att servrar som standard inte erbjuder None, och portnumren i exemplen. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Det Python-bibliotek för OPC UA som användes för den simulerade utrustningsservern. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Tjänsten som följer en nods referenser. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Att namnrymdens URI representeras med ett tal i tjänsterna och att talet motsvarar en position i NamespaceArray. ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Tjänsterna Read och Write. ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definitionerna av värde, statuskod, SourceTimestamp och ServerTimestamp. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain och Bad som anges av de två högsta bitarna. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Att samplingsintervallet är en ”best effort”-cykel. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Att ett meddelande utan filter skickas när värdet eller statusen ändras. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Köstorleken, hur poster kastas och Overflow-biten. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publiceringsintervallet och keep-alive. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync samt samplingsintervall och köstorlek i MonitoredItemOptions. ↩

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

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Rutinen att flytta certifikat från rejected-arkivet till det betrodda arkivet, och att AutoAcceptUntrustedCertificates inte ska användas i produktion. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De fyra slagen: anonym, användarnamn, X.509 och utfärdade token. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Listan över användartoken som en slutpunkt accepterar (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Att ManagedSession har en inbyggd återanslutningsmekanism. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Tjänsten som låter en prenumerations kö för återsändning skicka meddelanden en gång till. ↩

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

De senaste artiklarna med samma taggar. Fördjupa dig i närliggande ämnen.

Ämnessidor som ligger nära det här temat. Utgå från artikeln och gå vidare till relaterade tjänster och andra artiklar.

Den här artikeln hör ihop med följande tjänstesidor. Välj den ingång som ligger närmast.

Windows-apputveckling

Att bygga en Windows-app som samlar in data från utrustning, inklusive inställningar för prenumerationer, återanslutning och registrering av tidsstämplar och status, kan hanteras som Windows-apputveckling.

Vanliga frågor

Vanliga frågor om artikelns ämne som brukar komma upp när man rådfrågar oss.

Vad är skillnaden mellan OPC UA och OPC Classic (OPC DA)?
OPC Classic är en specifikation som utbyter data via COM/DCOM i Windows. OPC UA, som publicerades 2008, är en specifikation som inte är beroende av ett visst operativsystem och som samlar funktionerna i Classic. OPC UA-klienter fungerar även utanför Windows, och ingen DCOM-konfiguration behövs.
Räcker det att läsa värdena med jämna mellanrum när jag samlar in dem med OPC UA?
Om det är få värden och intervallet är långt går det bra att samla in dem genom läsning. Om du vill följa förändringar i värdena kan du i stället använda en prenumeration, som överlåter samplingen till servern och låter den meddela dig om förändringar. Registrera i båda fallen statuskoden och tidsstämplarna som kommer tillbaka tillsammans med värdet.
Varför får jag BadCertificateUntrusted när jag ansluter?
Det beror på att klienten ännu inte litar på serverns certifikat. OPC Foundations .NET-bibliotek sparar avvisade certifikat i rejected-arkivet. Bekräfta certifikatets tumavtryck med serverns administratör och flytta det sedan till trusted-arkivet. Använd inställningar som accepterar certifikat automatiskt endast i utvecklingsmiljöer.
Vilket portnummer använder OPC UA?
IANA har registrerat 4840/TCP som opcua-tcp. Verkliga servrar kan dock använda en annan port, så kontrollera det i utrustningens inställningar. Om servern körs på Windows måste trafik till den porten tillåtas i Windows-brandväggen, och om det finns nätverksutrustning emellan måste den tillåtas där också.

Författarprofil

Profilsidan för artikelns författare.

Go Komura

Företrädare för KomuraSoft LLC

Arbetar med Windows-programvaruutveckling, teknisk rådgivning och felsökning, med särskild styrka i projekt med befintliga tillgångar och i utredning av fel vars orsak är svår att se.

Tillbaka till bloggen