Wijzigingsgeschiedenis (alleen de eerste versie, gepubliceerd op 01-11-2026)
- Eerste publicatie
Dit artikel citeren(DOI: 10.5281/zenodo.23968419)
Dit artikel is gearchiveerd op Zenodo. Hieronder staan twee DOI’s: een die altijd naar de nieuwste versie verwijst en een die vastligt op de versie die u nu leest.
Go Komura (2026). Kennismaken met OPC UA ── Gegevens van PLC's en apparaten verzamelen in een Windows-app. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968419 https://comcomponent.com/nl/blog/opc-ua-windows-data-collection/
- DOI (nieuwste versie)
- 10.5281/zenodo.23968419
- DOI (deze versie)
- 10.5281/zenodo.23968420
Als u gegevens van een PLC of apparaat in een Windows-app verzamelt en het apparaat een OPC UA-server heeft, hoeft u soms niet voor elke fabrikant een eigen communicatie te schrijven. Alleen verbinden en waarden lezen is echter niet genoeg. Als u niet weet hoe het zit met de status en de tijdstempels die samen met de waarde terugkomen, met abonnementen die veranderingen melden, met het toestaan van verbindingen via certificaten en met het herstel na een verbroken verbinding, kunt u de verzamelde gegevens later niet vertrouwen.
Dit artikel legt de onderdelen van OPC UA uit die nodig zijn voor het verzamelen van gegevens, samen met de resultaten van een C#-client die ik echt heb uitgevoerd. De client gebruikt de .NET-bibliotheek van de OPC Foundation, en aan de andere kant draait een gesimuleerde apparaatserver. Ik heb het niet geprobeerd met een bepaalde PLC of een bepaald apparaat. Controleer de apparaatspecifieke instellingen in de documentatie van dat apparaat.
Het verzamelen van beelden van GigE Vision- en USB3 Vision-camera’s staat in “Dropped Frames with GigE Vision and USB3 Vision Cameras”, en apparaten met seriële communicatie in “Serial Communication App Pitfalls”.
Het verschil tussen OPC UA en OPC Classic
De specificaties van OPC Classic wisselen gegevens tussen softwareonderdelen uit via COM/DCOM van Windows. OPC DA voor procesgegevens, OPC A&E voor alarmen en OPC HDA voor historische gegevens zijn als afzonderlijke specificaties vastgelegd1.
In 2008 publiceerde de OPC Foundation OPC UA (Unified Architecture). OPC UA is een servicegerichte architectuur die niet afhankelijk is van een bepaald platform en de functies van de afzonderlijke Classic-specificaties samenbrengt. Als redenen voor het herontwerp noemt de OPC Foundation onder meer dat Microsoft platformonafhankelijke mechanismen belangrijker ging vinden dan COM/DCOM, dat de specificatie ook op systemen buiten Microsoft en op embedded apparaten moest draaien, en dat veilig verbinden over firewalls heen nodig was1.
De OPC UA-specificatie is ook door de IEC genormaliseerd als de reeks IEC 625412. Voor de communicatiepoort is bij IANA 4840/TCP geregistreerd als opcua-tcp3.
flowchart TB
accTitle: Het verschil tussen OPC Classic en OPC UA
accDescr: Een diagram dat laat zien dat OPC Classic gebaseerd is op COM en DCOM van Windows en is opgesplitst in afzonderlijke specificaties DA, A&E en HDA, terwijl OPC UA die functies samenbrengt in één platformonafhankelijke specificatie.
C["OPC Classic"] --> D["Draait op COM/DCOM (Windows)"]
D --> DA["Opgesplitst in DA, A&E en HDA"]
DA -->|"Functies samengebracht"| U["OPC UA (2008)"]
U --> P["Niet gebonden aan een bepaald platform"]
P --> S["Genormaliseerd als IEC 62541"]
Afbeelding 1: OPC Classic bestaat uit afzonderlijke specificaties op COM/DCOM, en OPC UA brengt ze samen tot één geheel.
Heeft bestaande apparatuur alleen een OPC DA-server, dan hebt u aan uw kant iets nodig dat COM/DCOM gebruikt. Dit artikel behandelt alleen OPC UA.
In het diagram geeft een doorgetrokken lijn een relatie aan die altijd geldt en een stippellijn een voorwaardelijke relatie (de voorwaarden staan per relatie op de detailpagina). Het volledige overzicht van de relaties (13 in totaal, met onderbouwing en zekerheid) en de definities van de belangrijkste concepten vindt u op de detailpagina van de kennisgrafiek (in het Japans). Gegevens: JSON-LD / Turtle
De geteste opstelling
Ik heb de volgende opstelling uitgevoerd.
| Rol | Wat ik gebruikte |
|---|---|
| Client | C#, .NET 10 SDK 10.0.114, NuGet-pakket OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| Server | Een gesimuleerd apparaat gemaakt met Python 3.13 en asyncua 2.1.0 (poort 4840 op dezelfde pc) |
| Besturingssysteem | Linux (uitvoeren op Windows is niet geprobeerd) |
De clientbibliotheek is de .NET-implementatie die de OPC Foundation publiceert. Voor het gebruik van de 2.0-reeks heb ik me gehouden aan de schrijfwijze in Getting started in dezelfde repository4.
flowchart TB
accTitle: De geteste opstelling
accDescr: Een diagram met een C#-client die de .NET-bibliotheek van de OPC Foundation gebruikt en via een versleutelde verbinding verbindt met een gesimuleerde asyncua-apparaatserver die op poort 4840 luistert.
A["C#-client (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Gesimuleerde apparaatserver (asyncua)"]
A -.-> L[".NET-bibliotheek van de OPC Foundation 2.0.1"]
B -.-> V["Vier variabelen onder Line1"]
Afbeelding 2: Op dezelfde pc verbond de C#-client met de gesimuleerde apparaatserver.
De gesimuleerde apparaatserver
In plaats van echte apparatuur heb ik met asyncua een kleine server gemaakt5. Onder een object met de naam Line1 staan vier variabelen: temperatuur (Temperature), aantal geproduceerde stuks (PartCount), in bedrijf (Running) en instelwaarde (Setpoint). De temperatuur verandert elke 0,5 seconde en het aantal stuks elke 2 seconden. Voor de beveiliging heb ik twee opties voorzien: geen versleuteling (None) en ondertekenen plus versleutelen (SignAndEncrypt met Basic256Sha256).
# Een gesimuleerd apparaat om de werking te testen. Het vervangt geen echte apparatuur of PLC's.
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())
Deze server accepteert de certificaten van verbindende clients zonder ze te controleren. Dat is een testinstelling en wijkt af van het gedrag van echte apparatuur.
De C#-client
Ik heb de volgende twee pakketten aan het projectbestand toegevoegd.
<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>
De client verbindt, bladert (browse), leest, schrijft en abonneert zich, in die volgorde. De volgende paragrafen leggen uit wat elk onderdeel betekent.
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}");
// De server bepaalt de naamruimte-index. Zoek die op via de URI en maak daarmee de NodeId's
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);
// Bladeren: geef de variabelen onder Line1 weer
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}");
}
// Lezen: bekijk niet alleen de waarde, maar ook de status en de twee tijdstempels
DataValue t = await session.ReadValueAsync(temperature, ct);
Console.WriteLine($"Temperature = {t.WrappedValue} 状態 {t.StatusCode}");
Console.WriteLine($" Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");
// Een niet-bestaande node lezen geeft een Bad-status terug in plaats van een waarde
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// Schrijven: zet Setpoint op 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]}");
// Abonneren: vraag de server om elke 500 ms te bemonsteren en veranderingen te melden
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");
De eerste keer dat ik het uitvoerde, lukte de verbinding niet. De reden staat in de paragraaf “Verbindingen toestaan met certificaten”. Hieronder de uitvoer nadat het certificaat van de server werd vertrouwd. De waarschuwingslogs van de bibliotheek zijn weggelaten.
接続: 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]
De tijden zijn in UTC.
Apparaatgegevens verschijnen als nodes in de adresruimte
Een OPC UA-server stelt de gegevens die hij heeft beschikbaar als een verzameling nodes. Die verzameling heet de adresruimte (address space). Nodes zijn er in soorten, zoals objecten die mappen of apparaten voorstellen en variabelen die waarden bevatten, en nodes zijn met elkaar verbonden via verwijzingen (references). De client volgt verwijzingen met de Browse-service om uit te zoeken welke nodes er zijn6.
In de uitvoer hierboven volgt de client de hiërarchische verwijzingen vanaf Line1 en vindt hij vier variabelen.
flowchart TB
accTitle: De adresruimte van het gesimuleerde apparaat
accDescr: Een diagram dat een object Line1 onder de map Objects laat zien, met daaronder vier variabelen: Temperature, PartCount, Running en Setpoint.
O["Objects"] --> L["Line1 (object)"]
L --> T["Temperature (variabele)"]
L --> P["PartCount (variabele)"]
L --> R["Running (variabele)"]
L --> S["Setpoint (variabele, beschrijfbaar)"]
Afbeelding 3: De adresruimte van het gesimuleerde apparaat. De client vindt nodes door met Browse verwijzingen te volgen.
Een NodeId bestaat uit een naamruimte en een identificator
Nodes worden geïdentificeerd met een NodeId. ns=2;s=Line1.Temperature in de uitvoer betekent dat de naamruimte-index 2 is en de identificator de tekenreeks Line1.Temperature.
Een naamruimte is in werkelijkheid een URI. De specificatie bepaalt dat naamruimte-URI’s binnen services als getal worden weergegeven, en dat dat getal overeenkomt met de positie in de NamespaceArray van de server. Index 0 is de OPC UA-naamruimte http://opcfoundation.org/UA/7.
Schrijf het indexnummer daarom niet vast in uw code, maar zoek de index op via de URI. De code hierboven haalt met session.NamespaceUris.GetIndex(LineNamespace) de waarde 2 op uit de URI en maakt daarmee de NodeId’s.
flowchart TB
accTitle: Een NodeId maken vanuit een naamruimte-URI
accDescr: Een diagram dat laat zien dat de client de naamruimte-URI opzoekt in de NamespaceArray van de server om de index te krijgen, en die index combineert met de identificator om een NodeId te maken.
U["Naamruimte-URI"] --> A["De NamespaceArray van de server"]
A --> I["Index (2 in dit voorbeeld)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identificator Line1.Temperature"] --> N
Afbeelding 4: Zoek de naamruimte-index op via de URI en combineer die met de identificator tot de NodeId.
Lezen en schrijven geven samen met de waarde een status en tijdstempels terug
Waarden van variabelen worden gelezen en geschreven met de services Read en Write8. Een leesbewerking geeft meer terug dan alleen de waarde. Ze geeft een structuur terug die DataValue heet, met de waarde, een statuscode, een brontijdstempel en een servertijdstempel9.
| Onderdeel | Betekenis |
|---|---|
| Waarde | De waarde van de variabele. Als de statuscode een fout aangeeft, wordt de waarde genegeerd en zet de server haar op null |
| Statuscode (StatusCode) | Of de server de waarde kon verkrijgen en leveren |
| SourceTimestamp | Het tijdstip dat de bron van de gegevens aan de waarde meegaf |
| ServerTimestamp | Het tijdstip waarop de server de waarde ontving of vaststelde dat ze juist was |
De hoogste twee bits van de statuscode geven Good (geslaagd), Uncertain (gedeeltelijk geslaagd, voor sommige doeleinden mogelijk niet bruikbaar) of Bad (mislukt) aan10. Good [0x00000000] in de uitvoer is Good.
Toen ik het niet-bestaande Line1.Pressure las, kwam er BadNodeIdUnknown [0x80340000] terug in plaats van een waarde. ReadValueAsync van deze bibliotheek gooit een uitzondering als de status Bad is4. Als een systeem dat waarden vastlegt naast de waarde ook de statuscode bewaart, kunt u later de waarden onderscheiden die zijn vastgelegd terwijl de status Bad was.
flowchart TB
accTitle: Wat een DataValue bevat
accDescr: Een diagram dat laat zien dat de DataValue die een leesbewerking of melding teruggeeft vier onderdelen heeft, de waarde, de statuscode, de brontijdstempel en de servertijdstempel, en dat de waarde wordt genegeerd als de statuscode Bad is.
D["DataValue"] --> V["Waarde"]
D --> S["Statuscode"]
D --> TS["SourceTimestamp (tijd bij de bron)"]
D --> TV["ServerTimestamp (tijd bij de server)"]
S -->|"Als Bad"| X["Waarde negeren"]
Afbeelding 5: Een leesresultaat bevat niet alleen de waarde, maar ook een statuscode en twee tijdstempels.
Bij het schrijven heb ik 32,5 naar Setpoint geschreven, en als resulterende statuscode kwam Good terug. Welke variabelen beschrijfbaar zijn, bepaalt de server. In deze gesimuleerde server is alleen Setpoint beschrijfbaar. Of u instelwaarden van het apparaat van buitenaf laat overschrijven, hoort bij het veiligheidsontwerp van het apparaat; dit artikel gaat daar daarom niet op in.
Veranderingen laten melden: abonnementen en MonitoredItems
Om veranderingen in waarden te volgen, kan de client Read met een vaste interval herhalen. Daarnaast heeft OPC UA een mechanisme waarbij de server het bewaken van waarden overneemt en veranderingen meldt. Dat zijn abonnementen (subscriptions) en MonitoredItems.
Een MonitoredItem is één bewaakt item en heeft een bemonsteringsinterval (sampling interval). Het bemonsteringsinterval is het hoogste tempo waarin de server waarden van de bron bemonstert, en de specificatie noemt het een “best effort”-cyclus11. Als een monster aan de voorwaarden van het filter voldoet, wordt een melding gemaakt. Is er geen filter opgegeven, dan wordt een melding verstuurd als de waarde of de status verandert12.
Meldingen komen eerst in de wachtrij van het MonitoredItem. U legt vooraf vast of bij een volle wachtrij de oudste of de nieuwste melding wordt weggegooid, en als de wachtrij 2 of groter is, wordt in de statuscode de Overflow-bit gezet die aangeeft dat er iets is weggegooid13.
Een abonnement groepeert meerdere MonitoredItems en stuurt per publicatie-interval (publishing interval) de meldingen naar de client die nog niet zijn verstuurd. Als er een aantal cycli achter elkaar niets te versturen is, stuurt het een keep-alive om te laten zien dat het abonnement nog actief is14.
flowchart TB
accTitle: Hoe abonnementen en MonitoredItems werken
accDescr: Een diagram dat laat zien dat een MonitoredItem de waarde van de bron bemonstert met het bemonsteringsinterval en meldingen die het filter passeren in een wachtrij zet, en dat het abonnement ze per publicatie-interval samen naar de client stuurt, en een keep-alive stuurt als er geen meldingen zijn.
S["Bron (de waarde van het apparaat)"] --> M["MonitoredItem"]
M -->|"Bemonsteringsinterval"| F["Filter (standaard: verandering van waarde of status)"]
F --> Q["Wachtrij (Overflow als die vol is)"]
Q --> SUB["Abonnement"]
SUB -->|"Publicatie-interval"| C["Client"]
SUB -.->|"Als er geen meldingen zijn"| K["Keep-alive"]
Afbeelding 6: Het MonitoredItem bemonstert en verzamelt in de wachtrij, en het abonnement stuurt de meldingen samen naar de client.
De code hierboven abonneert zich op Temperature en PartCount met een bemonsteringsinterval van 500 milliseconden en een wachtrijgrootte van 515. In de uitvoer komt de temperatuur ongeveer elke 0,5 seconde binnen en het aantal stuks ongeveer elke 2 seconden. De eerste twee meldingen waren de waarden die elke variabele had toen het abonnement begon. De 13:40:50.801 bij het aantal stuks is het tijdstip waarop de waarde veranderde, nog vóór de start van het abonnement. Omdat bij de meldingen de brontijdstempel wordt weergegeven, ziet u het tijdstip waarop de waarde veranderde, niet het tijdstip waarop de melding binnenkwam.
Kiezen tussen herhaald lezen en abonneren
Bij herhaald lezen bepaalt de client zelf de interval, en het mechanisme is eenvoudig. Daar staat tegenover dat er elke keer een verzoek en een antwoord heen en weer gaan, ook als de waarde niet is veranderd, en dat veranderingen die korter duren dan de interval worden gemist. Bij een abonnement bemonstert de server en stuurt hij alleen veranderingen, zodat het verkeer nauwelijks toeneemt, zelfs bij waarden die zelden veranderen. Ook bij een abonnement worden veranderingen die korter duren dan het bemonsteringsinterval echter mogelijk niet gemeld. De specificatie staat sneller bemonsteren dan gevraagd toe, maar vereist het niet11.
flowchart TB
accTitle: Het verschil tussen herhaald lezen en abonneren
accDescr: Een diagram dat laat zien dat bij herhaald lezen de client de interval bepaalt en elke keer een verzoek stuurt, dat bij abonneren de server met het bemonsteringsinterval bemonstert en alleen veranderingen stuurt, en dat beide veranderingen die korter duren dan de interval kunnen missen.
A["Veranderingen in waarden volgen"] --> R["Herhaald lezen"]
A --> S["Abonneren"]
R --> R1["Client bepaalt de interval, verzoekt elke keer"]
S --> S1["Server bemonstert, stuurt alleen veranderingen"]
R1 --> X["Kan veranderingen korter dan de interval missen"]
S1 --> X
Afbeelding 7: Met beide methoden kunnen veranderingen die korter duren dan de interval worden gemist.
Verbindingen toestaan met certificaten
Bij een OPC UA-verbinding kiest u de mate van berichtbeveiliging met MessageSecurityMode. De waarden zijn None zonder beveiliging, Sign dat alleen ondertekent, en SignAndEncrypt dat ondertekent en versleutelt16. Welke combinatie van algoritmen voor ondertekenen en versleutelen wordt gebruikt, bepaalt het beveiligingsbeleid (security policy); dit artikel gebruikt Basic256Sha256.
Voor ondertekenen en versleutelen gebruikt elke applicatie een eigen certificaat. Client en server controleren elkaars certificaat en weigeren verbindingen met partijen die ze niet vertrouwen. Een server die met de .NET-bibliotheek van de OPC Foundation is gebouwd, biedt het onversleutelde beleid None standaard niet aan4.
De eerste verbinding strandde op BadCertificateUntrusted
Bij de eerste uitvoering vertrouwde de client het certificaat van de server niet, dus werd de verbinding geweigerd. Hier een deel van de uitvoer.
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.'
Deze bibliotheek slaat geweigerde certificaten op in het archief rejected. Als een beheerder een certificaat van daar naar het archief trusted verplaatst, vertrouwt de bibliotheek die partij17. Deze keer verplaatste ik het certificaat van de server van pki/rejected/certs naar pki/trusted/certs, voerde het opnieuw uit en kon ik verbinden. Controleer vóór het verplaatsen de vingerafdruk, die ook in de bestandsnaam staat, met de beheerder van de server4.
flowchart TB
accTitle: Stappen om een certificaat te vertrouwen
accDescr: Een diagram dat laat zien dat de client bij de eerste verbinding het certificaat van de server weigert en in het archief rejected opslaat, en dat na het controleren van de vingerafdruk en het verplaatsen naar het archief trusted de volgende verbinding het vertrouwt en slaagt.
A["Eerste verbinding"] --> B["BadCertificateUntrusted"]
B --> C["Certificaat opgeslagen in het archief rejected"]
C --> D["Vingerafdruk controleren met de beheerder van de server"]
D --> E["Naar het archief trusted verplaatsen"]
E --> F["De volgende verbinding vertrouwt het en slaagt"]
Afbeelding 8: Na het controleren van een geweigerd certificaat en het verplaatsen naar trusted vertrouwt de client die partij.
Er is ook een instelling, AutoAcceptUntrustedCertificates, die niet-vertrouwde certificaten automatisch accepteert. De standaardwaarde is false, en de documentatie van de bibliotheek schrijft dat die nuttig is in een ontwikkelomgeving, maar niet in productie gebruikt moet worden17.
De NotAfter=2027-11-01 in de log is de vervaldatum van het certificaat van de gesimuleerde server. Het is een zelfondertekend certificaat dat met een functie van asyncua is gemaakt, en het verliep een jaar na de aanmaakdatum. Ook certificaten van apparaten verlopen, dus leg in de beheerprocedures vast hoe u ze vóór het verlopen vervangt en de andere kant de nieuwe laat vertrouwen. Het werken met het certificaatarchief van Windows staat in “The Windows Certificate Store in Practice”.
Gebruikersauthenticatie
Wat certificaten controleren, zijn de applicaties. Los daarvan wordt bij het openen van een sessie een token meegegeven dat de gebruiker aangeeft. De specificatie kent vier soorten: anoniem, gebruikersnaam en wachtwoord, X.509-certificaat en extern uitgegeven tokens18. De client in dit artikel verbond anoniem. Welke tokens een server accepteert, staat in de endpointinformatie van de server19.
Waar gegevensverzameling in de praktijk misgaat
Vanaf hier noem ik problemen die vaak optreden in systemen die apparaatgegevens verzamelen, gebaseerd op dit experiment en op de specificatie.
Waarden van terwijl de server stillag, komen niet binnen
Tijdens het abonnement heb ik de server gestopt en ongeveer 15 seconden later opnieuw gestart. ManagedSession van deze bibliotheek verbindt automatisch opnieuw als de verbinding wegvalt20. Terwijl de server stil lag, bleef de client opnieuw proberen te verbinden.
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.
Toen de server terugkwam, kwamen ook de meldingen terug. Hier een deel van de abonnementsuitvoer.
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]
Er kwam geen enkele waarde binnen van tussen 13:41:13 en 13:41:34. Het aantal stuks ging van 78 terug naar 2. Dat komt doordat de gesimuleerde server na de herstart weer vanaf 0 begon te tellen.
In dit voorbeeld lag de server zelf stil, dus de waarden uit die periode zijn nergens bewaard. Het mechanisme voor opnieuw verbinden herstelt de verbinding, maar vult de waarden uit de periode waarin de server stil lag niet achteraf aan.
Anders is het als alleen de communicatie wegvalt en de server blijft draaien. De specificatie kent de service Republish, waarmee de server meldingsberichten die niet zijn aangekomen opnieuw verstuurt vanuit zijn wachtrij voor herverzending21. Ook in de wachtrij van het MonitoredItem stapelen meldingen zich op, maar wat de wachtrijgrootte overschrijdt, wordt weggegooid13. Dit geval heb ik in dit experiment niet getest. Leg in beide gevallen de tijdstippen van het verbreken en het opnieuw verbinden vast, zodat u kunt zien in welke periode er geen waarden binnenkwamen. Opnieuw verbinden en logontwerp voor apparatuur met seriële communicatie komen ook aan bod in “Serial Communication App Pitfalls”.
flowchart TB
accTitle: Vastleggen als de server stopt
accDescr: Een diagram dat laat zien dat als de server tijdens een abonnement stopt, de client blijft proberen opnieuw te verbinden en de meldingen terugkomen als de server terug is, maar dat de waarden van terwijl hij stil lag niet binnenkomen, zodat de tijdstippen van verbreken en opnieuw verbinden moeten worden vastgelegd.
A["Geabonneerd"] --> B["De server stopt"]
B --> C["De client blijft opnieuw verbinden"]
C --> D["De server is terug, meldingen ook"]
B -.-> E["Waarden van tijdens de stop komen niet binnen"]
E -.-> F["Tijdstippen van verbreken en opnieuw verbinden vastleggen"]
Afbeelding 9: Opnieuw verbinden herstelt de verbinding, maar waarden van terwijl de server stil lag, komen niet binnen.
Welke tijdstempel vastleggen
Volgens de specificatie is SourceTimestamp het tijdstip dat de bron van de gegevens aan de waarde meegaf, en hoort die aan te geven wanneer de waarde veranderde. ServerTimestamp is het tijdstip waarop de server de waarde ontving of vaststelde dat ze juist was9. De gesimuleerde server in dit artikel maakt de waarden zelf, dus beide tijdstippen waren gelijk.
Wilt u weten wanneer een waarde op het apparaat veranderde, gebruik dan SourceTimestamp. Dat tijdstip wordt echter met de klok van de bron gezet, dus als de klok van het apparaat of de server afwijkt, wijkt ook het record af. Gaat u de verzamelde gegevens vergelijken met andere records, bewaar dan ook de tijd van de ontvangende pc ernaast, zodat u later de afwijking kunt controleren. Tijdsynchronisatie aan de Windows-kant staat in “Windows Time Synchronization (w32time) and Business Systems”.
Behandel waarden met een andere status dan Good niet als gewone getallen
Als de statuscode Bad is, hoort de waarde te worden genegeerd9. Is ze Uncertain, dan is de waarde voor sommige doeleinden mogelijk niet bruikbaar10. Sorteer waarden op statuscode voordat ze in grafieken of tellingen terechtkomen, en leg vast hoe u waarden behandelt die niet Good zijn. Ook de Overflow-bit bij een overgelopen wachtrij wordt in de statuscode gezet13. Hoe u de status van apparaten op het scherm toont, komt aan bod in “Best Practices for Checking and Displaying External Device State”.
Open de poort in de firewall
De bij IANA geregistreerde OPC UA-poort is 4840/TCP3. Niet elke server gebruikt echter dit nummer. De .NET-voorbeelden van de OPC Foundation gebruiken poorten zoals 625414. Controleer het poortnummer in de endpoint-URL van het apparaat en sta verbindingen naar die poort toe. Draait de server op Windows, sta het dan toe in Windows Firewall op die machine, en als er netwerkapparatuur tussen zit, op die apparatuur. Hoe u regels voor binnenkomend verkeer maakt, staat in “The Windows Firewall and Business Applications”.
flowchart TB
accTitle: Waar gegevensverzameling misgaat
accDescr: Een diagram dat de plekken waar het verzamelen van apparaatgegevens misgaat verdeelt in de inhoud van de records (waarden van terwijl de server stil lag die niet binnenkomen, welke tijdstempel u vastlegt, de behandeling van waarden die niet Good zijn) en de voorbereiding van de verbinding (firewallpoorten, verlopen van certificaten).
A["Waar gegevensverzameling misgaat"] --> R["Inhoud van de records"]
A --> N["Voorbereiding van de verbinding"]
R --> B["Waarden van een stop komen niet binnen"]
B ~~~ C["Welke tijdstempel"]
C ~~~ D["Waarden die niet Good zijn"]
N --> E["Firewallpoorten"]
E ~~~ F["Verlopen van certificaten"]
Afbeelding 10: Ook als u kunt verbinden, controleert u gaten in de records, de tijden, de status, de poorten en de certificaten.
Kiezen tussen OPC UA, Modbus/TCP en eigen protocollen van fabrikanten
Voor communicatie met PLC’s en apparaten zijn er naast OPC UA ook Modbus/TCP en de protocollen die PLC-fabrikanten zelf aanbieden.
De Modbus-specificatie behandelt gegevens in vier tabellen: Discrete Inputs (alleen-lezen, 1 bit), Coils (lezen en schrijven, 1 bit), Input Registers (alleen-lezen, 16 bits) en Holding Registers (lezen en schrijven, 16 bits)22. Bij IANA is 502/TCP geregistreerd voor het Modbus-applicatieprotocol (mbap)3.
Wat in deze tabellen staat, zijn bits en 16-bits woorden. Welke waarde op welk adres in welk formaat staat, moet u dus in de documentatie van het apparaat nagaan en aan de clientkant vastleggen. Bij OPC UA stelt de server, zoals we hebben gezien, benoemde variabelen beschikbaar in zijn adresruimte, en kan de client die met Browse ontdekken. Waarden hebben een statuscode en tijdstempels, en verbindingen worden toegestaan met certificaten.
Welke u gebruikt, hangt vaak af van wat het apparaat biedt. Heeft het apparaat een OPC UA-server, gebruik die dan; heeft het alleen Modbus/TCP, maak dan een adrestabel en lees aan de hand daarvan. Is er alleen het protocol of de SDK van de fabrikant, volg dan die documentatie. Hoe u een communicatiemethode kiest voor USB-apparaten, staat in “How to Work with USB Devices from a Windows App”.
flowchart TB
accTitle: Kiezen hoe u met apparatuur communiceert
accDescr: Een diagram dat laat zien dat u, als het apparaat een OPC UA-server heeft, de variabelen met OPC UA doorbladert en verzamelt, als het alleen Modbus/TCP heeft, de adressen en formaten in de documentatie nagaat en registers leest, en als het alleen het protocol van de fabrikant heeft, die documentatie volgt.
A["Communicatie die het apparaat biedt"] --> B{"Wat is er beschikbaar?"}
B -->|"OPC UA-server"| C["Variabelen doorbladeren en verzamelen"]
B -->|"Alleen Modbus/TCP"| D["Adressen en formaten nagaan in de documentatie"]
B -->|"Alleen protocol van de fabrikant"| E["Documentatie en SDK van de fabrikant volgen"]
D --> F["Registers lezen en omzetten naar waarden"]
Afbeelding 11: Waarmee u verzamelt, hangt vaak af van wat het apparaat biedt.
Gerelateerde artikelen
- 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
Bronnen
-
OPC Foundation, Classic. Dat OPC Classic gebaseerd is op COM/DCOM, de specificaties DA, A&E en HDA, de publicatie van OPC UA in 2008 en de redenen voor het herontwerp. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Deel 1 van de reeks IEC 62541, over de concepten en het overzicht van OPC UA (inmiddels vervangen door een latere editie). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. De registraties van
opcua-tcpop 4840/TCP enmbapop 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Hoe u een client in de 2.0-reeks schrijft, dat
ReadValueAsyncbij Bad een uitzondering gooit, de archieven voor geweigerde en vertrouwde certificaten, dat serversNonestandaard niet aanbieden, en de poortnummers in de voorbeelden. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). De Python-bibliotheek voor OPC UA die is gebruikt voor de gesimuleerde apparaatserver. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. De service die de verwijzingen van een node volgt. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Dat naamruimte-URI’s binnen services als getal worden weergegeven en dat dat getal overeenkomt met een positie in de NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. De services Read en Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. De definities van waarde, statuscode, SourceTimestamp en ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain en Bad zoals aangegeven door de hoogste twee bits. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Dat het bemonsteringsinterval een best-effortcyclus is. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Dat zonder filter een melding wordt verstuurd bij een verandering van waarde of status. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. De wachtrijgrootte, de manier van weggooien en de Overflow-bit. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Het publicatie-interval en keep-alives. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsync, en het bemonsteringsinterval en de wachtrijgrootte inMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. De betekenis van None, Sign en SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. De werkwijze om certificaten van het archief voor geweigerde naar het archief voor vertrouwde certificaten te verplaatsen, en
AutoAcceptUntrustedCertificatesniet in productie te gebruiken. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. De vier soorten: anoniem, gebruikersnaam, X.509 en uitgegeven tokens. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. De lijst van gebruikerstokens die een endpoint accepteert (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Dat
ManagedSessioneen ingebouwd mechanisme voor opnieuw verbinden heeft. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. De service waarmee meldingsberichten opnieuw worden verstuurd vanuit de wachtrij voor herverzending van een abonnement. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (een kopie op een server van de Universiteit van Chicago). Het datamodel in paragraaf 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩
Gerelateerde artikelen
Recente artikelen met dezelfde tags. Verdiep uw kennis met nauw verwante onderwerpen.
Wat stopt er als je een "verbinding met een datalimiet" inschakelt? ── Gevolgen voor Windows Update, OneDrive en Store-apps
Met een verbinding met een datalimiet downloadt Windows Update alleen updates met hoge prioriteit en pauzeert OneDrive de synchronisatie....
Mag u Windows.old en „Tijdelijke bestanden” verwijderen? ── Wanneer u Opslaginzicht gebruikt en wanneer Schijfopruiming
Of u Windows.old of tijdelijke bestanden mag verwijderen, hangt ervan af of u daarna iets kwijt bent dat u niet terugkrijgt. Op basis van...
Wat verandert de energiemodus van Windows? ── Beste prestaties, Gebalanceerd en Beste energie-efficiëntie vergeleken, en hoe je kiest
Wat verandert er met de energiemodi Beste prestaties, Gebalanceerd en Beste energie-efficiëntie van Windows 11? Uitleg over de CPU-instel...
Wat is de gamemodus van Windows? ── Wat verandert er als je hem inschakelt, en heeft hij ook invloed buiten games?
De gamemodus van Windows voorkomt dat Windows Update stuurprogramma's installeert en meldingen voor opnieuw opstarten stuurt terwijl een ...
Wat zijn „Toegewezen GPU-geheugen” en „Gedeeld GPU-geheugen” in Taakbeheer? ── Waarom de getallen bij een geïntegreerde GPU zo verschillen en wat er gebeurt als het geheugen tekortschiet
Wat toegewezen en gedeeld GPU-geheugen in Taakbeheer betekenen, uitgelegd voor gewone gebruikers: waarom een geïntegreerde GPU weinig toe...
Gerelateerde onderwerpen
Onderwerppagina’s die dicht bij dit thema liggen. Vanuit dit artikel kunt u doorklikken naar verwante diensten en andere artikelen.
Windows-technologieonderwerpen
Overzichtspagina met technische onderwerpen over Windows-ontwikkeling, foutanalyse en het hergebruik van bestaande assets.
Diensten bij dit thema
Dit artikel sluit aan op de volgende dienstpagina’s. Kies een ingang die dicht bij uw vraag ligt.
Windows-applicatieontwikkeling
Het bouwen van een Windows-app die gegevens van apparaten verzamelt, inclusief de abonnementsinstellingen, het opnieuw verbinden en het vastleggen van tijdstempels en status, kan worden opgepakt als ontwikkeling van een Windows-app.
Technisch advies en ontwerpreview
Als u wilt bepalen of u met OPC UA verzamelt of Modbus/TCP of het protocol van de fabrikant gebruikt, en hoe u certificaten beheert, kan dat worden opgepakt als technisch advies en ontwerpreview.
Veelgestelde vragen
Vragen die tijdens een adviesgesprek vaak over het thema van dit artikel worden gesteld.
- Wat is het verschil tussen OPC UA en OPC Classic (OPC DA)?
- OPC Classic is een specificatie die gegevens uitwisselt via COM/DCOM van Windows. OPC UA, gepubliceerd in 2008, is niet afhankelijk van een bepaald besturingssysteem en brengt de functies van Classic samen. OPC UA-clients draaien ook buiten Windows, en DCOM-configuratie is niet nodig.
- Is het bij het verzamelen van waarden met OPC UA genoeg om ze met een vaste interval te lezen?
- Bij weinig waarden en een lange interval kunt u ze met lezen verzamelen. Wilt u veranderingen in waarden volgen, dan kunt u een abonnement gebruiken: de server neemt de bemonstering over en meldt veranderingen. Leg in beide gevallen de statuscode en de tijdstempels vast die samen met de waarde terugkomen.
- Waarom krijg ik BadCertificateUntrusted bij het verbinden?
- Omdat de client het certificaat van de server nog niet vertrouwt. De .NET-bibliotheek van de OPC Foundation slaat geweigerde certificaten op in het archief rejected. Controleer de vingerafdruk (thumbprint) van het certificaat met de beheerder van de server en verplaats het daarna naar het archief trusted. Gebruik instellingen die certificaten automatisch accepteren alleen in een ontwikkelomgeving.
- Welk poortnummer gebruikt OPC UA?
- Bij IANA is 4840/TCP geregistreerd als opcua-tcp. Echte servers gebruiken soms een andere poort, dus controleer dat in de instellingen van het apparaat. Draait de server op Windows, dan moet u verkeer naar die poort toestaan in Windows Firewall, en als er netwerkapparatuur tussen zit, ook op die apparatuur.
Profiel van de auteur
De profielpagina van de auteur van dit artikel.
Go Komura
Vertegenwoordiger van KomuraSoft LLC
Richt zich op Windows-softwareontwikkeling, technisch advies en foutanalyse, en is vooral sterk in projecten waarin bestaande assets behouden blijven en in storingsonderzoek waarbij de oorzaak moeilijk zichtbaar is.