Ændringshistorik (kun første version, udgivet 01.11.2026)
- Første udgivelse
Citér denne artikel(DOI: 10.5281/zenodo.23968424)
Denne artikel er arkiveret på Zenodo. Nedenfor ser du både den DOI, der altid peger på den nyeste version, og den DOI, der er knyttet til den version, du læser nu.
Go Komura (2026). Kom i gang med OPC UA ── Indsamling af data fra PLC'er og udstyr i en Windows-app. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968424 https://comcomponent.com/da/blog/opc-ua-windows-data-collection/
- DOI (seneste version)
- 10.5281/zenodo.23968424
- DOI (denne version)
- 10.5281/zenodo.23968425
Når du indsamler data fra PLC’er eller udstyr i en Windows-app, og udstyret har en OPC UA-server, kan du måske undgå at skrive en separat protokol for hver producent. Det er dog ikke nok bare at oprette forbindelse og læse værdier. Hvis du ikke kender til den status og de tidsstempler, der følger med hver værdi, abonnementer, der melder ændringer, godkendelse af forbindelser med certifikater og hvordan man kommer sig efter en afbrydelse, kan du ikke stole på de indsamlede data bagefter.
Denne artikel forklarer de dele af OPC UA, der er nødvendige for dataindsamling, sammen med resultater fra faktiske kørsler af en C#-klient. Klienten bruger OPC Foundations .NET-bibliotek, og modparten er en simuleret udstyrsserver. Jeg har ikke afprøvet det mod en bestemt PLC eller enhed. Kontrollér enhedsspecifikke indstillinger i den pågældende enheds dokumentation.
Indsamling af billeder fra GigE Vision- og USB3 Vision-kameraer behandles i “Dropped Frames with GigE Vision and USB3 Vision Cameras”, og enheder med seriel kommunikation i “Serial Communication App Pitfalls”.
Forskellen mellem OPC UA og OPC Classic
OPC Classic-specifikationerne udveksler data mellem softwarekomponenter ved hjælp af Windows COM/DCOM. OPC DA til procesdata, OPC A&E til alarmer og OPC HDA til historik er defineret som separate specifikationer1.
OPC Foundation offentliggjorde OPC UA (Unified Architecture) i 2008. OPC UA er en serviceorienteret arkitektur, der ikke er bundet til en bestemt platform, og som samler funktionerne fra hver af Classic-specifikationerne. Som årsager til omlægningen nævner OPC Foundation blandt andet, at Microsoft begyndte at prioritere platformsuafhængige mekanismer frem for COM/DCOM, at løsningen skulle kunne køre på andre systemer end Microsofts og på indlejrede enheder, og at der skulle kunne oprettes sikre forbindelser gennem firewalls1.
OPC UA-specifikationen er også standardiseret af IEC som IEC 62541-serien2. Som kommunikationsport har IANA registreret 4840/TCP som opcua-tcp3.
flowchart TB
accTitle: Forskellen mellem OPC Classic og OPC UA
accDescr: Et diagram, der viser, at OPC Classic bygger på Windows COM og DCOM og er opdelt i separate DA-, A&E- og HDA-specifikationer, mens OPC UA samler deres funktioner i én platformsuafhængig specifikation.
C["OPC Classic"] --> D["Kører oven på COM/DCOM (Windows)"]
D --> DA["Opdelt i DA-, A&E- og HDA-specifikationer"]
DA -->|"Funktionerne samles"| U["OPC UA (2008)"]
U --> P["Ikke bundet til en bestemt platform"]
P --> S["Standardiseret som IEC 62541"]
Figur 1: OPC Classic består af separate specifikationer oven på COM/DCOM, og OPC UA samler dem i én.
Hvis eksisterende udstyr kun har en OPC DA-server, skal du bruge en løsning, der anvender COM/DCOM. Denne artikel behandler kun OPC UA.
I diagrammet markerer en fuldt optrukket linje en relation, der altid gælder, og en stiplet linje en betinget relation (betingelserne står for hver relation på detaljesiden). Den fulde liste over relationer (13 i alt, med belæg og sikkerhed) og definitionerne af de vigtigste begreber er samlet på detaljesiden for vidensoversigten (på japansk). Data: JSON-LD / Turtle
Den opsætning, jeg afprøvede
Dette er den opsætning, jeg kørte.
| Rolle | Det brugte jeg |
|---|---|
| Klient | C#, .NET 10 SDK 10.0.114, NuGet-pakken OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| Server | En simuleret enhed lavet med Python 3.13 og asyncua 2.1.0 (port 4840 på samme pc) |
| Operativsystem | Linux (kørsel på Windows blev ikke afprøvet) |
Klientbiblioteket er den .NET-implementering, som OPC Foundation har offentliggjort. Til brugen af 2.0-serien fulgte jeg fremgangsmåden i Getting started i samme repository4.
flowchart TB
accTitle: Den opsætning, jeg afprøvede
accDescr: Et diagram, der viser en C#-klient, som bruger OPC Foundations .NET-bibliotek og via en krypteret forbindelse opretter forbindelse til en simuleret udstyrsserver lavet med asyncua, der lytter på port 4840.
A["C#-klient (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Simuleret udstyrsserver (asyncua)"]
A -.-> L["OPC Foundations .NET-bibliotek 2.0.1"]
B -.-> V["Fire variabler under Line1"]
Figur 2: C#-klienten oprettede forbindelse til den simulerede udstyrsserver på samme pc.
Den simulerede udstyrsserver
I stedet for rigtigt udstyr lavede jeg en lille server med asyncua5. Under et objekt ved navn Line1 er der fire variabler: temperatur (Temperature), antal emner (PartCount), kører (Running) og sætpunkt (Setpoint). Temperaturen ændres hvert 0,5 sekund og antallet af emner hvert 2. sekund. Som sikkerhed tilbød jeg to muligheder: ingen kryptering (None) og signering plus kryptering (SignAndEncrypt med Basic256Sha256).
# En simuleret enhed til test. Den erstatter ikke produktionsudstyr 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())
Denne server accepterer klienters certifikater uden at kontrollere dem. Det er en testindstilling og adskiller sig fra, hvordan produktionsudstyr opfører sig.
C#-klienten
Jeg tilføjede disse to pakker til 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 opretter forbindelse, gennemser, læser, skriver og abonnerer i den rækkefølge. De følgende afsnit forklarer, hvad hver del betyder.
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 navnerummets indeks. Slå det op ud fra URI'en, og byg NodeIds ud fra 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);
// Gennemsøgning: list variablerne 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: se på status og de to tidsstempler, ikke kun værdien
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æsning af en node, der ikke findes, returnerer en Bad-status i stedet for en værdi
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// Skrivning: sæt 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: bed serveren om at sample hvert 500. ms og melde ændringer
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 kørte den, kunne den ikke oprette forbindelse. Årsagen forklares i afsnittet “Godkendelse af forbindelser med certifikater”. Her er outputtet, efter at serverens certifikat var godkendt. Bibliotekets advarselslogge er udeladt.
接続: 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]
Tidspunkterne er i UTC.
Udstyrets data vises som noder i adresserummet
En OPC UA-server udstiller de data, den har, som en samling af noder. Denne samling kaldes adresserummet. Noder findes i forskellige typer, f.eks. objekter, der repræsenterer mapper eller udstyr, og variabler, der indeholder værdier, og noderne er forbundet med hinanden via referencer. Klienten følger referencerne med Browse-tjenesten for at finde ud af, hvilke noder der findes6.
I outputtet ovenfor følger klienten hierarkiske referencer fra Line1 og finder fire variabler.
flowchart TB
accTitle: Den simulerede enheds adresserum
accDescr: Et diagram, der viser et Line1-objekt under Objects-mappen med fire variabler under sig: Temperature, PartCount, Running og Setpoint.
O["Objects"] --> L["Line1 (objekt)"]
L --> T["Temperature (variabel)"]
L --> P["PartCount (variabel)"]
L --> R["Running (variabel)"]
L --> S["Setpoint (variabel, skrivbar)"]
Figur 3: Den simulerede enheds adresserum. Klienten finder noder ved at følge referencer med Browse.
Et NodeId består af et navnerum og en identifikator
Noder identificeres med et NodeId. I outputtet betyder ns=2;s=Line1.Temperature, at navnerummets indeks er 2, og at identifikatoren er strengen Line1.Temperature.
Et navnerum er i virkeligheden en URI. Ifølge specifikationen repræsenteres navnerums-URI’er med tal i tjenesterne, og tallet svarer til positionen i serverens NamespaceArray. Indeks 0 er OPC UA-navnerummet http://opcfoundation.org/UA/7.
Derfor skal du ikke skrive indeksnummeret ind i koden, men slå indekset op ud fra URI’en. Koden ovenfor bruger session.NamespaceUris.GetIndex(LineNamespace) til at få 2 ud fra URI’en og bygger sine NodeIds ud fra det.
flowchart TB
accTitle: Opbygning af et NodeId ud fra en navnerums-URI
accDescr: Et diagram, der viser, at klienten slår navnerums-URI'en op i serverens NamespaceArray for at få indekset og kombinerer indekset med identifikatoren for at bygge et NodeId.
U["Navnerums-URI"] --> A["Serverens NamespaceArray"]
A --> I["Indeks (2 i dette eksempel)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identifikator Line1.Temperature"] --> N
Figur 4: Navnerummets indeks slås op ud fra URI’en og kombineres med identifikatoren til et NodeId.
Læsning og skrivning returnerer status og tidsstempler sammen med værdien
Variablernes værdier læses og skrives med tjenesterne Read og Write8. En læsning returnerer mere end blot værdien. Den returnerer en struktur kaldet DataValue, som indeholder værdien, en statuskode, et kildetidsstempel og et servertidsstempel9.
| Element | Betydning |
|---|---|
| Værdi | Variablens værdi. Når statuskoden angiver en fejl, ignoreres værdien, og serveren sætter den til null |
| Statuskode (StatusCode) | Om serveren kunne hente og levere værdien |
| SourceTimestamp | Det tidspunkt, datakilden knyttede til værdien |
| ServerTimestamp | Det tidspunkt, serveren modtog værdien eller fik at vide, at den var korrekt |
Statuskodens to øverste bit angiver Good (lykkedes), Uncertain (lykkedes delvist og kan være uegnet til visse formål) eller Bad (mislykkedes)10. Good [0x00000000] i outputtet er Good.
Da jeg læste Line1.Pressure, som ikke findes, kom BadNodeIdUnknown [0x80340000] tilbage i stedet for en værdi. Dette biblioteks ReadValueAsync kaster en undtagelse, når status er Bad4. Hvis et system, der registrerer værdier, også gemmer statuskoden ved siden af hver værdi, kan du bagefter skelne de værdier, der blev registreret, mens status var Bad.
flowchart TB
accTitle: Hvad en DataValue indeholder
accDescr: Et diagram, der viser, at den DataValue, en læsning eller en notifikation returnerer, indeholder fire ting, værdien, statuskoden, kildetidsstemplet og servertidsstemplet, og at værdien ignoreres, når statuskoden er Bad.
D["DataValue"] --> V["Værdi"]
D --> S["Statuskode"]
D --> TS["SourceTimestamp (tid ved kilden)"]
D --> TV["ServerTimestamp (tid ved serveren)"]
S -->|"Hvis Bad"| X["Ignorér værdien"]
Figur 5: Resultatet af en læsning indeholder ikke kun værdien, men også en statuskode og to tidsstempler.
Ved skrivningen skrev jeg 32.5 til Setpoint, og Good kom tilbage som statuskode. Serveren bestemmer, hvilke variabler der kan skrives til. I denne simulerede server kan der kun skrives til Setpoint. Om udstyrets sætpunkter må ændres udefra, er et spørgsmål om udstyrets sikkerhedsdesign, så det behandler denne artikel ikke.
Få besked om ændringer: abonnementer og MonitoredItems
For at følge ændringer i værdier kan klienten gentage Read med et fast interval. Derudover har OPC UA en mekanisme, hvor overvågningen af værdier overlades til serveren, som så melder ændringerne. Den mekanisme er abonnementer (Subscription) og MonitoredItems.
Et MonitoredItem er ét element, der overvåges, og det har et samplingsinterval. Samplingsintervallet er den hurtigste takt, hvormed serveren sampler værdier fra kilden, og specifikationen beskriver det som en “best effort”-cyklus11. Når en sample opfylder filterets betingelser, oprettes en notifikation. Hvis der ikke er angivet noget filter, sendes en notifikation, når værdien eller status ændres12.
Notifikationerne lægges først i MonitoredItem’ets kø. Du bestemmer på forhånd, om den ældste eller den nyeste kasseres, når køen løber over, og hvis køstørrelsen er 2 eller mere, sættes Overflow-bitten, der angiver kassering, i statuskoden13.
Et abonnement samler flere MonitoredItems og sender ved hvert publiceringsinterval de notifikationer, der endnu ikke er sendt, til klienten. Når der går cyklusser uden notifikationer at sende, sender det en keep-alive for at vise, at abonnementet stadig er aktivt14.
flowchart TB
accTitle: Sådan fungerer abonnementer og MonitoredItems
accDescr: Et diagram, der viser, at et MonitoredItem sampler kildens værdi ved samplingsintervallet og lægger notifikationer, der passerer filteret, i en kø, og at abonnementet sender dem samlet til klienten ved publiceringsintervallet og sender en keep-alive, når der ikke er notifikationer.
S["Kilde (udstyrets værdi)"] --> M["MonitoredItem"]
M -->|"Samplingsinterval"| F["Filter (standard: ændring i værdi eller status)"]
F --> Q["Kø (Overflow, når den er fuld)"]
Q --> SUB["Abonnement"]
SUB -->|"Publiceringsinterval"| C["Klient"]
SUB -.->|"Hvis der ikke er notifikationer"| K["Keep-alive"]
Figur 6: MonitoredItem’et sampler og sætter i kø, og abonnementet sender notifikationerne samlet til klienten.
Koden ovenfor abonnerer på Temperature og PartCount med et samplingsinterval på 500 millisekunder og en køstørrelse på 515. I outputtet kommer temperaturen ca. hvert 0,5 sekund og antallet af emner ca. hvert 2. sekund. De første to notifikationer var de værdier, hver variabel havde, da abonnementet startede. Antallet af emners 13:40:50.801 er det tidspunkt, hvor værdien ændrede sig, før abonnementet startede. Da notifikationerne viser kildetidsstemplet, viser de det tidspunkt, værdien ændrede sig, ikke det tidspunkt, notifikationen blev modtaget.
Valget mellem gentagne læsninger og abonnementer
Ved gentagne læsninger bestemmer klienten selv intervallet, og mekanismen er enkel. Til gengæld går en forespørgsel og et svar frem og tilbage hver gang, også når værdien ikke har ændret sig, og ændringer, der er kortere end intervallet, overses. Med et abonnement sampler serveren og sender kun ændringer, så trafikken vokser ikke ret meget, selv for værdier, der sjældent ændres. Men også med et abonnement bliver ændringer, der er kortere end samplingsintervallet, måske ikke meldt. Specifikationen tillader hurtigere sampling end ønsket, men kræver det ikke11.
flowchart TB
accTitle: Forskellen mellem gentagne læsninger og abonnementer
accDescr: Et diagram, der viser, at ved gentagne læsninger bestemmer klienten intervallet og sender en forespørgsel hver gang, at ved abonnementer sampler serveren ved samplingsintervallet og sender kun ændringer, og at begge kan overse ændringer, der er kortere end intervallet.
A["Følg ændringer i værdier"] --> R["Gentag læsninger"]
A --> S["Abonnér"]
R --> R1["Klienten bestemmer intervallet og spørger hver gang"]
S --> S1["Serveren sampler og sender kun ændringer"]
R1 --> X["Ændringer kortere end intervallet kan overses"]
S1 --> X
Figur 7: Med begge metoder kan ændringer, der er kortere end intervallet, blive overset.
Godkendelse af forbindelser med certifikater
I en OPC UA-forbindelse vælger du graden af beskyttelse af meddelelser med MessageSecurityMode. Værdierne er None, som ikke beskytter, Sign, som kun signerer, og SignAndEncrypt, som signerer og krypterer16. Kombinationen af algoritmer til signering og kryptering bestemmes af sikkerhedspolitikken (security policy), og denne artikel bruger Basic256Sha256.
Signering og kryptering bruger et certifikat for hver applikation. Klienten og serveren kontrollerer hinandens certifikater og afviser forbindelser fra parter, de ikke har tillid til. En server, der er lavet med OPC Foundations .NET-bibliotek, tilbyder som standard ikke den ukrypterede None-politik4.
Den første forbindelse stoppede med BadCertificateUntrusted
Ved den første kørsel havde klienten ikke tillid til serverens certifikat, så forbindelsen blev afvist. Her er en del af outputtet.
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 bibliotek gemmer afviste certifikater i rejected-lageret. Når en administrator flytter et certifikat derfra til trusted-lageret, får biblioteket tillid til den pågældende part17. Denne gang flyttede jeg serverens certifikat fra pki/rejected/certs til pki/trusted/certs, kørte programmet igen og kunne oprette forbindelse. Før du flytter det, skal du bekræfte aftrykket (thumbprint), som også indgår i filnavnet, med serverens administrator4.
flowchart TB
accTitle: Trin til at få tillid til et certifikat
accDescr: Et diagram, der viser, at klienten ved den første forbindelse afviser serverens certifikat og gemmer det i rejected-lageret, og at når aftrykket er bekræftet og certifikatet flyttet til trusted-lageret, har den næste forbindelse tillid til det og lykkes.
A["Første forbindelse"] --> B["BadCertificateUntrusted"]
B --> C["Certifikatet gemmes i rejected-lageret"]
C --> D["Bekræft aftrykket med serverens administrator"]
D --> E["Flyt det til trusted-lageret"]
E --> F["Den næste forbindelse har tillid og lykkes"]
Figur 8: Når et afvist certifikat er kontrolleret og flyttet til trusted, har klienten tillid til den part.
Der findes også indstillingen AutoAcceptUntrustedCertificates, som automatisk accepterer certifikater, der ikke er tillid til. Standardværdien er false, og ifølge bibliotekets dokumentation er den nyttig i udviklingsmiljøer, men den bør ikke bruges i produktion17.
NotAfter=2027-11-01 i loggen er udløbsdatoen for den simulerede servers certifikat. Det er et selvsigneret certifikat, der er oprettet med en asyncua-funktion, og det udløb et år efter oprettelsesdagen. Udstyrets certifikater udløber også, så fastlæg som en del af driften, hvordan de udskiftes, før de udløber, og hvordan modparten får tillid til de nye. Håndtering af Windows’ certifikatlager behandles i “The Windows Certificate Store in Practice”.
Brugergodkendelse
Det, certifikaterne verificerer, er applikationerne. Derudover overføres et token, der identificerer brugeren, når en session åbnes. Specifikationen definerer fire typer: anonym, brugernavn og adgangskode, X.509-certifikat og eksternt udstedte tokens18. Klienten i denne artikel oprettede forbindelse anonymt. Hvilke tokens en server accepterer, fremgår af serverens endpointoplysninger19.
Hvor dataindsamling i praksis går galt
Herfra gennemgår jeg problemer, der ofte opstår i systemer, der indsamler udstyrsdata, baseret på dette eksperiment og specifikationen.
Værdier fra den periode, hvor serveren var stoppet, kommer ikke frem
Under abonnementet stoppede jeg serveren og genstartede den ca. 15 sekunder senere. Dette biblioteks ManagedSession genopretter automatisk forbindelsen, når den afbrydes20. Mens serveren var stoppet, blev klienten ved med at forsøge at genoprette forbindelsen.
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 tilbage, kom notifikationerne også tilbage. Her er en del af abonnementets output.
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]
Der kom ikke en eneste værdi fra tidsrummet 13:41:13 til 13:41:34. Antallet af emner gik tilbage fra 78 til 2. Det skyldes, at den simulerede server efter genstarten begyndte at tælle forfra fra 0.
I dette eksempel var selve serveren stoppet, så værdierne fra den periode er ikke gemt nogen steder. Genforbindelsesmekanismen genopretter forbindelsen, men den udfylder ikke bagefter værdierne fra den periode, hvor serveren var nede.
Situationen er en anden, når kun kommunikationen afbrydes, og serveren kører videre. Specifikationen har en Republish-tjeneste, som får serveren til at sende notifikationsmeddelelser, der ikke kom frem, igen fra sin genudsendelseskø21. Notifikationer ophobes også i MonitoredItem’ets kø, men alt ud over køstørrelsen kasseres13. Jeg afprøvede ikke dette tilfælde i eksperimentet. I begge tilfælde bør du registrere tidspunkterne for afbrydelse og genforbindelse, så du kan se, i hvilken periode der ikke kom værdier. Genforbindelse og logdesign for udstyr med seriel kommunikation behandles også i “Serial Communication App Pitfalls”.
flowchart TB
accTitle: Registrering, når serveren stopper
accDescr: Et diagram, der viser, at når serveren stopper under et abonnement, bliver klienten ved med at forsøge at genoprette forbindelsen, og notifikationerne kommer tilbage, når serveren vender tilbage, men værdierne fra den periode, hvor den var stoppet, kommer ikke frem, så tidspunkterne for afbrydelse og genforbindelse bør registreres.
A["Abonnement aktivt"] --> B["Serveren stopper"]
B --> C["Klienten forsøger gentagne gange at genoprette forbindelsen"]
C --> D["Serveren vender tilbage, og notifikationerne genoptages"]
B -.-> E["Værdier fra perioden, hvor den var stoppet, kommer ikke frem"]
E -.-> F["Registrér tidspunkterne for afbrydelse og genforbindelse"]
Figur 9: Genforbindelse genopretter forbindelsen, men værdierne fra den periode, hvor serveren var stoppet, kommer ikke frem.
Hvilket tidsstempel skal registreres
Ifølge specifikationen er SourceTimestamp det tidspunkt, datakilden knyttede til værdien, og det bør angive, hvornår værdien ændrede sig. ServerTimestamp er det tidspunkt, serveren modtog værdien eller fik at vide, at den var korrekt9. Den simulerede server i denne artikel genererer selv værdierne, så de to tidspunkter var de samme.
Hvis du vil vide, hvornår en værdi ændrede sig i udstyret, skal du bruge SourceTimestamp. Tidspunktet sættes dog af kildens ur, så hvis udstyrets eller serverens ur går forkert, er registreringen også forkert. Hvis du vil sammenholde de indsamlede data med andre registreringer, så gem også tidspunktet på den modtagende pc ved siden af, så du senere kan kontrollere afvigelser. Tidssynkronisering på Windows-siden behandles i “Windows Time Synchronization (w32time) and Business Systems”.
Behandl ikke værdier, hvis status ikke er Good, som almindelige tal
Når statuskoden er Bad, skal værdien ignoreres9. Når den er Uncertain, kan værdien være uegnet til visse formål10. Før værdierne føres ind i grafer eller sammentællinger, så sortér dem efter statuskode, og fastlæg, hvordan værdier, der ikke er Good, skal håndteres. Overflow-bitten for en kø, der løber over, sættes også i statuskoden13. Hvordan udstyrets tilstand vises på skærmen, behandles i “Best Practices for Checking and Displaying External Device State”.
Åbn porten i firewallen
Den OPC UA-port, der er registreret hos IANA, er 4840/TCP3. Ikke alle servere bruger dog dette nummer. OPC Foundations .NET-eksempler bruger blandt andet port 625414. Kontrollér portnummeret i udstyrets endpoint-URL, og tillad forbindelser til den port. Hvis serveren kører på Windows, skal det tillades i den maskines Windows Firewall, og hvis der er netværksudstyr imellem, på det udstyr. Oprettelse af indgående regler behandles i “The Windows Firewall and Business Applications”.
flowchart TB
accTitle: Hvor dataindsamling går galt
accDescr: Et diagram, der viser de steder, hvor indsamling af udstyrsdata går galt, opdelt i registreringernes indhold (værdier fra den periode, hvor serveren var stoppet, kommer ikke frem, hvilket tidsstempel der registreres, håndtering af værdier, der ikke er Good) og forberedelse af forbindelsen (firewallporte, certifikaters udløb).
A["Hvor dataindsamling går galt"] --> R["Registreringernes indhold"]
A --> N["Forberedelse af forbindelsen"]
R --> B["Værdier fra et stop kommer ikke frem"]
B ~~~ C["Hvilket tidsstempel"]
C ~~~ D["Håndtering af værdier, der ikke er Good"]
N --> E["Firewallporte"]
E ~~~ F["Certifikaters udløb"]
Figur 10: Selv når forbindelsen virker, skal du kontrollere huller i registreringerne, tidspunkterne, status, portene og certifikaterne.
Valget mellem OPC UA, Modbus/TCP og producentspecifikke protokoller
Til kommunikation med PLC’er og udstyr findes der ud over OPC UA også Modbus/TCP samt protokoller, som PLC-producenterne stiller til rådighed.
Modbus-specifikationen håndterer data i fire tabeller: Discrete Input (en enkelt bit, der kun kan læses), Coil (en enkelt bit, der kan læses og skrives), Input Register (et 16-bit ord, der kun kan læses) og Holding Register (et 16-bit ord, der kan læses og skrives)22. Hos IANA er 502/TCP registreret som Modbus’ applikationsprotokol (mbap)3.
Det, der ligger i disse tabeller, er bit og 16-bit ord, så hvilken værdi der ligger på hvilken adresse og i hvilket format, skal kontrolleres i udstyrets dokumentation og skrives på klientsiden. Med OPC UA udstiller serveren, som vi har set, navngivne variabler i sit adresserum, og klienten kan finde dem med Browse. Værdierne følges af en statuskode og tidsstempler, og forbindelser godkendes med certifikater.
Hvad du skal bruge, afgøres ofte af, hvad udstyret tilbyder. Hvis udstyret har en OPC UA-server, så brug den. Hvis det kun har Modbus/TCP, så lav en adressetabel og læs ud fra den. Hvis der kun er producentens protokol eller SDK, så følg dokumentationen for den. Valg af kommunikationsmetode til USB-enheder behandles i “How to Work with USB Devices from a Windows App”.
flowchart TB
accTitle: Valg af kommunikationsmetode til udstyret
accDescr: Et diagram, der viser, at hvis udstyret har en OPC UA-server, gennemser og indsamler du variabler med OPC UA, hvis det kun har Modbus/TCP, kontrollerer du adresser og formater i dokumentationen og læser registre, og hvis det kun har producentens protokol, følger du dens dokumentation.
A["Kommunikation, som udstyret tilbyder"] --> B{"Hvad er der?"}
B -->|"OPC UA-server"| C["Gennemse og indsaml variablerne"]
B -->|"Kun Modbus/TCP"| D["Kontrollér adresser og formater i dokumentationen"]
B -->|"Kun producentens protokol"| E["Følg producentens dokumentation og SDK"]
D --> F["Læs registrene, og omregn dem til værdier"]
Figur 11: Hvad du indsamler med, afgøres ofte af, hvad udstyret tilbyder.
Relaterede artikler
- Dropped Frames with GigE Vision and USB3 Vision Cameras
- Serial Communication App Pitfalls
- Best Practices for Checking and Displaying External Device State
- How to Work with USB Devices from a Windows App
- The Windows Firewall and Business Applications
- The Windows Certificate Store in Practice
- Windows Time Synchronization (w32time) and Business Systems
Referencer
-
OPC Foundation, Classic. At OPC Classic bygger på COM/DCOM, specifikationerne DA, A&E og HDA, offentliggørelsen af OPC UA i 2008 og årsagerne til omlægningen. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Del 1 af IEC 62541-serien, som behandler begreber og overblik for OPC UA (siden erstattet af en nyere udgave). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Registreringerne af
opcua-tcppå 4840/TCP ogmbappå 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Hvordan en klient i 2.0-serien skrives, at
ReadValueAsynckaster en undtagelse ved Bad, rejected- og trusted-lagrene, at servere som standard ikke tilbyderNone, og de portnumre, eksemplerne bruger. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). Det Python-OPC UA-bibliotek, der blev brugt til den simulerede udstyrsserver. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Tjenesten, der følger en nodes referencer. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. At navnerums-URI’er repræsenteres med tal i tjenesterne, og at tallet svarer til en position i NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Tjenesterne Read og Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definitionerne af værdi, statuskode, SourceTimestamp og ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain og Bad, som angives af de to øverste bit. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. At samplingsintervallet er en best effort-cyklus. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. At der uden filter sendes en notifikation ved ændring i værdi eller status. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Køstørrelsen, kasseringspolitikken og Overflow-bitten. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publiceringsintervallet og keep-alives. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsyncsamt samplingsinterval og køstørrelse iMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Betydningerne af None, Sign og SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Praksis med at flytte certifikater fra rejected-lageret til trusted-lageret, og at
AutoAcceptUntrustedCertificatesikke bruges i produktion. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De fire typer: anonym, brugernavn, X.509 og udstedte tokens. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Listen over brugertokens, som et endpoint accepterer (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. At
ManagedSessionhar en indbygget genforbindelsesmekanisme. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Tjenesten, der får et abonnements genudsendelseskø til at sende notifikationsmeddelelser igen. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (en kopi på en server hos University of Chicago). Datamodellen i afsnit 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩
Relaterede artikler
De nyeste artikler med de samme tags. Gå dybere ind i emnet med nært beslægtede artikler.
Hvad stopper, når du slår »forbrugsbaseret forbindelse« til? ── Virkningen på Windows Update, OneDrive og Store-apps
Når Windows' forbrugsbaserede forbindelse er slået til, downloader Windows Update kun opdateringer med høj prioritet, og OneDrive sætter ...
Må man slette Windows.old og »midlertidige filer«? – Hvornår du bruger Lagerassistent, og hvornår du bruger Diskoprydning
Om Windows.old og midlertidige filer kan slettes, afgøres af, om du mister noget, du ikke kan få tilbage. Ud fra Microsofts dokumentation...
Hvad ændrer strømtilstanden i Windows? ── Bedste ydeevne, Balanceret og Bedste strømeffektivitet sammenlignet, og sådan vælger du
Hvad Windows 11's strømtilstande Bedste ydeevne, Balanceret og Bedste strømeffektivitet ændrer: CPU-indstillingerne bagved, forholdet til...
Hvad er spiltilstand i Windows? ── Hvad ændres, når du slår den til, og påvirker den andet end spil?
Spiltilstand i Windows forhindrer Windows Update i at installere drivere og sende genstartsbeskeder, mens et spil kører, og sigter mod en...
Hvad er ”Dedikeret GPU-hukommelse” og ”Delt GPU-hukommelse” i Jobliste? ── Hvorfor tallene er så forskellige på en integreret GPU, og hvad der sker, når hukommelsen ikke slår til
Hvad dedikeret og delt GPU-hukommelse i Jobliste betyder, hvorfor en integreret GPU viser lidt dedikeret og meget delt hukommelse, hvorda...
Relaterede emner
Emnesider, der ligger tæt på dette tema. Med artiklen som udgangspunkt kan du gå videre til relaterede ydelser og andre artikler.
Tekniske emner om Windows
Indgang til tekniske emner om Windows-udvikling, fejlfinding og genbrug af eksisterende løsninger.
Ydelser, der hænger sammen med emnet
Denne artikel er knyttet til følgende ydelsessider. Vælg den indgang, der ligger tættest på din sag.
Windows-appudvikling
Udvikling af en Windows-app, der indsamler data fra udstyr, herunder abonnementsindstillinger, genforbindelse og registrering af tidsstempler og status, kan håndteres som udvikling af Windows-apps.
Teknisk rådgivning og designreview
Hvis du vil afgøre, om data skal indsamles med OPC UA, eller om Modbus/TCP eller producentens egen protokol skal bruges, og hvordan certifikater skal administreres, kan det håndteres som teknisk rådgivning og designgennemgang.
Ofte stillede spørgsmål
Her er de spørgsmål, der ofte kommer op ved rådgivning om artiklens emne.
- Hvad er forskellen på OPC UA og OPC Classic (OPC DA)?
- OPC Classic er et sæt specifikationer, der udveksler data via Windows COM/DCOM. OPC UA, der blev offentliggjort i 2008, er ikke bundet til et bestemt operativsystem og samler funktionerne fra Classic. OPC UA-klienter kører også uden for Windows, og der kræves ingen DCOM-konfiguration.
- Er det nok at læse værdierne med et fast interval, når de indsamles med OPC UA?
- Hvis der er få værdier, og intervallet er langt, kan du indsamle dem ved at læse. Hvis du vil følge ændringer i værdierne, kan du i stedet bruge et abonnement, hvor serveren står for samplingen og melder ændringerne. Uanset metode skal du registrere den statuskode og de tidsstempler, der følger med hver værdi.
- Hvorfor får jeg BadCertificateUntrusted, når jeg opretter forbindelse?
- Fordi klienten endnu ikke har tillid til serverens certifikat. OPC Foundations .NET-bibliotek gemmer afviste certifikater i rejected-lageret. Bekræft certifikatets aftryk (thumbprint) med serverens administrator, og flyt det derefter til trusted-lageret. Brug kun indstillinger, der automatisk accepterer certifikater, i udviklingsmiljøer.
- Hvilket portnummer bruger OPC UA?
- Hos IANA er 4840/TCP registreret som opcua-tcp. Rigtige servere bruger dog nogle gange en anden port, så kontrollér udstyrets indstillinger. Hvis serveren kører på Windows, skal trafik til porten tillades i Windows Firewall, og hvis der er netværksudstyr imellem, også på det udstyr.
Forfatterprofil
Profilsiden for artiklens forfatter.
Go Komura
Repræsentant for KomuraSoft LLC
Fokus på udvikling af Windows-software, teknisk rådgivning og fejlfinding, med særlig erfaring i projekter, hvor eksisterende løsninger skal bevares, og i undersøgelse af fejl, hvor årsagen er svær at spore.