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.
flowchart TB
accTitle: Forskjellen mellom OPC Classic og OPC UA
accDescr: Et 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.
C["OPC Classic"] --> D["Kjører oppå COM/DCOM (Windows)"]
D --> DA["Delt opp i DA-, A&E- og HDA-spesifikasjoner"]
DA -->|"Funksjonene samles"| U["OPC UA (2008)"]
U --> P["Ikke knyttet til en bestemt plattform"]
P --> S["Standardisert 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.
flowchart TB
accTitle: Oppsettet jeg prøvde
accDescr: Et 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.
A["C#-klient (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Simulert utstyrsserver (asyncua)"]
A -.-> L["OPC Foundations .NET-bibliotek 2.0.1"]
B -.-> V["Fire 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.
flowchart TB
accTitle: Adresserommet til den simulerte enheten
accDescr: Et diagram som viser et Line1-objekt under Objects-mappen med fire variabler under seg: 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: 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.
flowchart TB
accTitle: Bygge en NodeId fra en navneroms-URI
accDescr: Et 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.
U["Navneroms-URI"] --> A["Serverens NamespaceArray"]
A --> I["Indeks (2 i dette eksemplet)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identifikator Line1.Temperature"] --> N
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.
flowchart TB
accTitle: Hva en DataValue inneholder
accDescr: Et 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.
D["DataValue"] --> V["Verdi"]
D --> S["Statuskode"]
D --> TS["SourceTimestamp (tid ved kilden)"]
D --> TV["ServerTimestamp (tid ved serveren)"]
S -->|"Hvis Bad"| X["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.
flowchart TB
accTitle: Slik fungerer abonnementer og MonitoredItems
accDescr: Et 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.
S["Kilde (utstyrets verdi)"] --> M["MonitoredItem"]
M -->|"Samplingsintervall"| F["Filter (standard: endring i verdi eller status)"]
F --> Q["Kø (Overflow når den er full)"]
Q --> SUB["Abonnement"]
SUB -->|"Publiseringsintervall"| C["Klient"]
SUB -.->|"Hvis det ikke er varsler"| K["Keep-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.
flowchart TB
accTitle: Forskjellen mellom gjentatte lesinger og abonnementer
accDescr: Et 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.
A["Følge endringer i verdier"] --> R["Gjenta lesinger"]
A --> S["Abonnere"]
R --> R1["Klienten bestemmer intervallet og spør hver gang"]
S --> S1["Serveren sampler og sender bare endringer"]
R1 --> X["Endringer kortere enn intervallet kan gå tapt"]
S1 --> X
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.
flowchart TB
accTitle: Trinn for å stole på et sertifikat
accDescr: Et 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.
A["Første tilkobling"] --> B["BadCertificateUntrusted"]
B --> C["Sertifikatet lagres i rejected-lageret"]
C --> D["Bekreft avtrykket med serverens administrator"]
D --> E["Flytt det til trusted-lageret"]
E --> F["Den 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».
flowchart TB
accTitle: Registrering når serveren stopper
accDescr: Et 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.
A["Abonnement aktivt"] --> B["Serveren stopper"]
B --> C["Klienten prøver gjentatte ganger å koble til på nytt"]
C --> D["Serveren er tilbake, og varslene fortsetter"]
B -.-> E["Verdier fra perioden den var stoppet, kommer ikke fram"]
E -.-> F["Registrer 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».
flowchart TB
accTitle: Der datainnsamling går galt
accDescr: Et 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).
A["Der datainnsamling går galt"] --> R["Innholdet i registreringene"]
A --> N["Forberedelse av tilkoblingen"]
R --> B["Verdier fra et stopp kommer ikke fram"]
B ~~~ C["Hvilket tidsstempel"]
C ~~~ D["Håndtering av verdier som ikke er Good"]
N --> E["Brannmurporter"]
E ~~~ F["Sertifikaters 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».
flowchart TB
accTitle: Valg av kommunikasjonsmetode med utstyret
accDescr: Et 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.
A["Kommunikasjon utstyret tilbyr"] --> B{"Hva finnes?"}
B -->|"OPC UA-server"| C["Bla gjennom og samle inn variablene"]
B -->|"Bare Modbus/TCP"| D["Kontroller adresser og formater i dokumentasjonen"]
B -->|"Bare produsentens protokoll"| E["Følg produsentens dokumentasjon og SDK"]
D --> F["Les registrene og gjør dem om til verdier"]
Figur 11: Hva du samler inn med, avgjøres ofte av hva utstyret tilbyr.
Relaterte 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
Referanser
-
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
-
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). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Registreringene av
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 et unntak ved Bad, rejected- og trusted-lagrene, at servere som standard ikke tilbyrNone, og portnumrene eksemplene bruker. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). Python-biblioteket for OPC UA som ble brukt til den simulerte utstyrsserveren. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Tjenesten som følger en nodes referanser. ↩
-
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. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Tjenestene Read og Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definisjonene av verdi, statuskode, SourceTimestamp og ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain og Bad, som angis av de to øverste bitene. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. At samplingsintervallet er en best effort-syklus. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. At det uten filter sendes et varsel ved endring i verdi eller status. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Køstørrelsen, forkastingspolicyen og Overflow-biten. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publiseringsintervallet og keep-alives. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsyncog samplingsintervallet og køstørrelsen iMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Betydningene av None, Sign og SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Praksisen med å flytte sertifikater fra rejected-lageret til trusted-lageret, og at
AutoAcceptUntrustedCertificatesikke skal brukes i produksjon. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De fire typene: anonym, brukernavn, X.509 og utstedte tokener. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Listen over brukertokener et endepunkt godtar (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. At
ManagedSessionhar en innebygd gjenoppkoblingsmekanisme. ↩ -
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. ↩
-
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). ↩
Relaterte artikler
Nyere artikler med de samme taggene. Fordyp deg i beslektede emner.
Hva stopper når du slår på «forbruksmålt tilkobling»? ── Virkningen på Windows Update, OneDrive og Store-apper
Når forbruksmålt tilkobling er slått på i Windows, laster Windows Update bare ned oppdateringer med høy prioritet, og OneDrive stanser sy...
Kan man slette Windows.old og «midlertidige filer»? – Når du bruker Lagringssensor, og når du bruker Diskopprydding
Om Windows.old og midlertidige filer kan slettes, avgjøres av om du mister noe du ikke kan få tilbake. Ut fra Microsofts dokumentasjon gå...
Hva endrer strømmodusen i Windows? ── Best ytelse, Balansert og Beste energieffektivitet sammenlignet, og slik velger du
Hva strømmodusene i Windows 11, Best ytelse, Balansert og Beste energieffektivitet, endrer: CPU-innstillingene bak, forholdet til strømst...
Hva er spillmodus i Windows? ── Hva endres når du slår den på, og påvirker den noe annet enn spill?
Spillmodus i Windows hindrer Windows Update i å installere drivere og sende omstartsvarsler mens et spill kjører, og sikter mot en mer st...
Hva er «Dedikert GPU-minne» og «Delt GPU-minne» i Oppgavebehandling? ── Hvorfor tallene er så forskjellige på en integrert GPU, og hva som skjer når minnet ikke strekker til
Hva dedikert og delt GPU-minne i Oppgavebehandling betyr, hvorfor en integrert GPU viser lite dedikert og mye delt minne, hvordan grensen...
Relaterte temaer
Dette er temasider som ligger nær temaet for artikkelen. Med artikkelen som utgangspunkt kan du gå videre til relaterte tjenester og andre artikler.
Tekniske Windows-temaer
Inngang til KomuraSoft LLCs tekniske temaer om Windows-utvikling, feilsøking og gjenbruk av eksisterende løsninger.
Tjenester knyttet til dette temaet
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.