Ä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.
flowchart TB
accTitle: Skillnaden mellan OPC Classic och OPC UA
accDescr: Ett 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.
C["OPC Classic"] --> D["Körs ovanpå COM/DCOM (Windows)"]
D --> DA["Uppdelad i DA, A&E och HDA"]
DA -->|"Funktionerna samlas"| U["OPC UA (2008)"]
U --> P["Inte bunden till en viss plattform"]
P --> S["Standardiserad 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.
flowchart TB
accTitle: Konfigurationen jag provade
accDescr: Ett 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.
A["C#-klient (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Simulerad utrustningsserver (asyncua)"]
A -.-> L["OPC Foundations .NET-bibliotek 2.0.1"]
B -.-> V["Fyra 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.
flowchart TB
accTitle: Den simulerade utrustningens adressrymd
accDescr: Ett diagram som visar ett Line1-objekt under mappen Objects, med fyra variabler under sig: Temperature, PartCount, Running och 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 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.
flowchart TB
accTitle: Att bygga en NodeId från namnrymdens URI
accDescr: Ett 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.
U["Namnrymdens URI"] --> A["Serverns NamespaceArray"]
A --> I["Index (2 i det här exemplet)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identifierare Line1.Temperature"] --> N
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.
flowchart TB
accTitle: Vad en DataValue innehåller
accDescr: Ett 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.
D["DataValue"] --> V["Värde"]
D --> S["Statuskod"]
D --> TS["SourceTimestamp (tid vid källan)"]
D --> TV["ServerTimestamp (tid vid servern)"]
S -->|"Om Bad"| X["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.
flowchart TB
accTitle: Hur prenumerationer och MonitoredItem fungerar
accDescr: Ett 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.
S["Källa (utrustningens värde)"] --> M["MonitoredItem"]
M -->|"Samplingsintervall"| F["Filter (standard: ändring av värde eller status)"]
F --> Q["Kö (Overflow när den är full)"]
Q --> SUB["Prenumeration"]
SUB -->|"Publiceringsintervall"| C["Klient"]
SUB -.->|"Om inga meddelanden"| K["Keep-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.
flowchart TB
accTitle: Skillnaden mellan upprepad läsning och prenumeration
accDescr: Ett 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.
A["Följa förändringar i värden"] --> R["Upprepa läsningar"]
A --> S["Prenumerera"]
R --> R1["Klienten bestämmer intervallet och frågar varje gång"]
S --> S1["Servern samplar och skickar bara förändringar"]
R1 --> X["Förändringar kortare än intervallet kan missas"]
S1 --> X
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.
flowchart TB
accTitle: Stegen för att lita på ett certifikat
accDescr: Ett 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.
A["Första anslutningen"] --> B["BadCertificateUntrusted"]
B --> C["Certifikatet sparas i rejected-arkivet"]
C --> D["Bekräfta tumavtrycket med serverns administratör"]
D --> E["Flytta det till trusted-arkivet"]
E --> F["Nä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”.
flowchart TB
accTitle: Registrering när servern stoppas
accDescr: Ett 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.
A["Prenumeration pågår"] --> B["Servern stoppas"]
B --> C["Klienten försöker upprepade gånger återansluta"]
C --> D["Servern kommer tillbaka och meddelandena återupptas"]
B -.-> E["Värden från stoppet kommer inte fram"]
E -.-> F["Registrera 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”.
flowchart TB
accTitle: Där datainsamlingen går fel
accDescr: Ett 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).
A["Där datainsamlingen går fel"] --> R["Innehållet i registreringen"]
A --> N["Förberedelser för anslutningen"]
R --> B["Värden från ett stopp kommer inte fram"]
B ~~~ C["Vilken tidsstämpel"]
C ~~~ D["Hantering av värden som inte är Good"]
N --> E["Brandväggsportar"]
E ~~~ F["Certifikatens 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”.
flowchart TB
accTitle: Välja hur man kommunicerar med utrustning
accDescr: Ett 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.
A["Kommunikation som utrustningen erbjuder"] --> B{"Vad finns?"}
B -->|"OPC UA-server"| C["Bläddra bland och samla in variablerna"]
B -->|"Bara Modbus/TCP"| D["Kontrollera adresser och format i dokumentationen"]
B -->|"Bara tillverkarens kommunikation"| E["Följ tillverkarens dokumentation och SDK"]
D --> F["Läs registren och omvandla dem till värden"]
Figur 11: Vad du samlar in med avgörs ofta av vad utrustningen erbjuder.
Relaterade artiklar
- Dropped Frames with GigE Vision and USB3 Vision Cameras — Locating the Cause and the Windows Settings to Review
- Serial Communication App Pitfalls - Through Reconnection and Log Design
- 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
Referenser
-
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
-
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). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Registreringarna av
opcua-tcppå 4840/TCP ochmbappå 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Hur en klient i 2.0-serien skrivs, att
ReadValueAsynckastar ett undantag vid Bad, arkiven för avvisade och betrodda certifikat, att servrar som standard inte erbjuderNone, och portnumren i exemplen. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). Det Python-bibliotek för OPC UA som användes för den simulerade utrustningsservern. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Tjänsten som följer en nods referenser. ↩
-
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. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Tjänsterna Read och Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definitionerna av värde, statuskod, SourceTimestamp och ServerTimestamp. ↩ ↩2 ↩3
-
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
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Att samplingsintervallet är en ”best effort”-cykel. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Att ett meddelande utan filter skickas när värdet eller statusen ändras. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Köstorleken, hur poster kastas och Overflow-biten. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publiceringsintervallet och keep-alive. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsyncsamt samplingsintervall och köstorlek iMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Betydelsen av None, Sign och SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Rutinen att flytta certifikat från rejected-arkivet till det betrodda arkivet, och att
AutoAcceptUntrustedCertificatesinte ska användas i produktion. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De fyra slagen: anonym, användarnamn, X.509 och utfärdade token. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Listan över användartoken som en slutpunkt accepterar (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Att
ManagedSessionhar en inbyggd återanslutningsmekanism. ↩ -
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. ↩
-
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). ↩
Relaterade artiklar
De senaste artiklarna med samma taggar. Fördjupa dig i närliggande ämnen.
Vad stannar när du aktiverar ”Anslutning med datapriser”? – Effekten på Windows Update, OneDrive och appar från Store
Med anslutning med datapriser hämtar Windows Update bara prioriterade uppdateringar och OneDrive pausar synkroniseringen. Store, inställn...
Kan man ta bort Windows.old och ”Tillfälliga filer”? ── När du ska använda Lagringsassistenten och när Diskrensning
Om Windows.old och tillfälliga filer kan tas bort avgörs av om du förlorar något som inte går att få tillbaka. Utifrån Microsofts officie...
Vad ändrar Windows energiläge? ── Skillnaden mellan Bästa prestanda, Balanserad och Bästa energieffektivitet, och hur du väljer
Vad Windows 11:s energilägen Bästa prestanda, Balanserad och Bästa energieffektivitet ändrar: CPU-inställningarna bakom, energischeman, n...
Vad är spelläget i Windows? ── Vad ändras när du slår på det, och påverkar det något annat än spel?
Spelläget i Windows stoppar drivrutinsinstallationer och omstartsaviseringar från Windows Update medan ett spel körs, för en stabilare bi...
Vad är ”Dedikerat GPU-minne” och ”Delat GPU-minne” i Aktivitetshanteraren? ── Varför siffrorna skiljer sig så mycket på integrerade GPU:er, och vad som händer när minnet inte räcker
Vi förklarar för vanliga användare vad dedikerat och delat GPU-minne betyder i Aktivitetshanteraren: varför integrerade GPU:er visar lite...
Relaterade ämnen
Ämnessidor som ligger nära det här temat. Utgå från artikeln och gå vidare till relaterade tjänster och andra artiklar.
Tekniska Windows-ämnen
Ingång till tekniska ämnen om Windows-utveckling, felsökning och återanvändning av befintliga tillgångar.
Tjänster som hänger ihop med det här ämnet
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.
Teknisk rådgivning och designgranskning
Om du vill avgöra om du ska samla in med OPC UA eller använda Modbus/TCP eller tillverkarens egen kommunikation, och hur certifikaten ska hanteras, kan det hanteras som teknisk rådgivning och designgranskning.
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.