Historie změn (pouze první verze, zveřejněno 1. 11. 2026)
- První zveřejnění
Citace tohoto článku(DOI: 10.5281/zenodo.23968431)
Tento článek je archivován na Zenodo. Níže uvádíme jak DOI, které vždy odkazuje na nejnovější verzi, tak DOI přiřazené právě zobrazené verzi.
Go Komura (2026). Úvod do OPC UA ── Sběr dat z PLC a zařízení v aplikaci pro Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968431 https://comcomponent.com/cs/blog/opc-ua-windows-data-collection/
- DOI (nejnovější verze)
- 10.5281/zenodo.23968431
- DOI (tato verze)
- 10.5281/zenodo.23968432
Když sbíráte data z PLC nebo zařízení do aplikace pro Windows a zařízení má server OPC UA, můžete se možná vyhnout psaní samostatné komunikace pro každého výrobce. Připojit se a číst hodnoty ale nestačí. Pokud neznáte stav a časová razítka, která se vracejí s každou hodnotou, subskripce, které oznamují změny, povolování připojení certifikáty a způsob, jak se zotavit z odpojení, nebudete moci nasbíraným datům později věřit.
Tento článek vysvětluje ty části OPC UA, které jsou potřeba pro sběr dat, spolu s výsledky skutečného spuštění klienta v C#. Klient používá knihovnu OPC Foundation pro .NET a na druhé straně je simulovaný server zařízení. Proti konkrétnímu PLC nebo zařízení jsem to nezkoušel. Nastavení specifická pro zařízení si ověřte v jeho dokumentaci.
Sběru obrázků z kamer GigE Vision a USB3 Vision se věnuje článek „Dropped Frames with GigE Vision and USB3 Vision Cameras“ a zařízením se sériovou komunikací článek „Serial Communication App Pitfalls“.
Rozdíl mezi OPC UA a OPC Classic
Specifikace OPC Classic vyměňují data mezi softwarovými komponentami pomocí COM/DCOM ve Windows. OPC DA pro procesní data, OPC A&E pro alarmy a OPC HDA pro historii jsou definovány jako samostatné specifikace1.
OPC Foundation zveřejnila OPC UA (Unified Architecture) v roce 2008. OPC UA je architektura orientovaná na služby, která nezávisí na konkrétní platformě a sjednocuje funkce jednotlivých specifikací Classic. Jako důvody přepracování OPC Foundation uvádí, že Microsoft začal místo COM/DCOM upřednostňovat multiplatformní mechanismy, že bylo potřeba běžet i na systémech jiných než od Microsoftu a ve vestavěných zařízeních a že bylo potřeba se bezpečně připojovat přes firewally1.
Specifikace OPC UA je normalizována také v IEC jako řada IEC 625412. Jako komunikační port má v IANA registrováno číslo 4840/TCP pod názvem opcua-tcp3.
flowchart TB
accTitle: Rozdíl mezi OPC Classic a OPC UA
accDescr: Obrázek ukazuje, že OPC Classic stojí na COM a DCOM ve Windows a dělí se na samostatné specifikace DA, A&E a HDA, kdežto OPC UA sjednocuje jejich funkce do jedné specifikace nezávislé na platformě.
C["OPC Classic"] --> D["Běží nad COM/DCOM (Windows)"]
D --> DA["Rozděleno na specifikace DA, A&E a HDA"]
DA -->|"Funkce se sjednocují"| U["OPC UA (2008)"]
U --> P["Nezávisí na konkrétní platformě"]
P --> S["Normalizováno jako IEC 62541"]
Obrázek 1: OPC Classic tvoří samostatné specifikace nad COM/DCOM a OPC UA je sjednocuje do jedné.
Pokud má stávající zařízení jen server OPC DA, potřebujete na své straně mechanismus, který používá COM/DCOM. Tento článek se zabývá pouze OPC UA.
Na diagramu plná čára označuje vztah, který platí vždy, a přerušovaná čára vztah podmíněný (podmínky jsou uvedeny u jednotlivých vztahů na podrobné stránce). Úplný seznam vztahů (celkem 13, s doklady a mírou jistoty) a definice hlavních pojmů najdete na podrobné stránce znalostní mapy (v japonštině). Data: JSON-LD / Turtle
Vyzkoušená sestava
Spustil jsem tuto sestavu.
| Role | Co jsem použil |
|---|---|
| Klient | C#, .NET 10 SDK 10.0.114, balíček NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| Server | Simulované zařízení vytvořené v Pythonu 3.13 a asyncua 2.1.0 (port 4840 na stejném počítači) |
| OS | Linux (spuštění ve Windows jsem nezkoušel) |
Knihovna klienta je implementace pro .NET, kterou zveřejňuje OPC Foundation. Při používání řady 2.0 jsem se řídil postupem z Getting started ve stejném repozitáři4.
flowchart TB
accTitle: Vyzkoušená sestava
accDescr: Obrázek ukazuje sestavu, v níž se klient v C# používající knihovnu OPC Foundation pro .NET připojuje šifrovaným spojením k simulovanému serveru zařízení asyncua, který naslouchá na portu 4840.
A["Klient v C# (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Simulovaný server zařízení (asyncua)"]
A -.-> L["Knihovna OPC Foundation pro .NET 2.0.1"]
B -.-> V["Čtyři proměnné pod Line1"]
Obrázek 2: Na stejném počítači se klient v C# připojil k simulovanému serveru zařízení.
Simulovaný server zařízení
Místo skutečného zařízení jsem pomocí asyncua vytvořil malý server5. Pod objektem Line1 má čtyři proměnné: teplotu (Temperature), počet kusů (PartCount), běh (Running) a žádanou hodnotu (Setpoint). Teplota se mění každých 0,5 sekundy a počet kusů každé 2 sekundy. Pro zabezpečení jsem připravil dvě možnosti: bez šifrování (None) a podepisování se šifrováním (SignAndEncrypt s Basic256Sha256).
# Simulované zařízení pro ověření funkce. Nenahrazuje produkční zařízení ani PLC.
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())
Tento server přijímá certifikáty připojujících se klientů bez kontroly. Jde o testovací nastavení, které se liší od chování produkčních zařízení.
Klient v C#
Do projektového souboru jsem přidal tyto dva balíčky.
<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>
Klient se v tomto pořadí připojí, prochází, čte, zapisuje a přihlašuje k odběru. Význam jednotlivých částí vysvětlují následující oddíly.
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}");
// Index jmenného prostoru určuje server. Zjisti ho podle URI a z něj sestav NodeId
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);
// Procházení: vypiš proměnné pod 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}");
}
// Čtení: sleduj nejen hodnotu, ale i stav a dvě časová razítka
DataValue t = await session.ReadValueAsync(temperature, ct);
Console.WriteLine($"Temperature = {t.WrappedValue} 状態 {t.StatusCode}");
Console.WriteLine($" Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");
// Čtení neexistujícího uzlu vrátí místo hodnoty stav Bad
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// Zápis: nastav Setpoint na 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]}");
// Subskripce: požádej server o vzorkování každých 500 ms a oznamování změn
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");
Při prvním spuštění se připojit nepodařilo. Důvod vysvětluje oddíl „Povolení připojení pomocí certifikátů“. Níže je výstup po tom, co jsem certifikátu serveru začal důvěřovat. Varovné protokoly knihovny jsou vynechány.
接続: 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]
Časy jsou v UTC.
Data zařízení se zobrazují jako uzly v adresním prostoru
Server OPC UA zveřejňuje svá data jako soubor uzlů. Tento soubor se nazývá adresní prostor. Uzly jsou různých druhů, například objekty, které představují složky nebo zařízení, a proměnné, které mají hodnotu, a uzly jsou navzájem propojeny odkazy. Klient sleduje odkazy pomocí služby Browse a zjišťuje, jaké uzly existují6.
Ve výstupu výše klient prochází hierarchické odkazy od Line1 a nachází čtyři proměnné.
flowchart TB
accTitle: Adresní prostor simulovaného zařízení
accDescr: Obrázek ukazuje, že pod složkou Objects je objekt Line1 a pod ním čtyři proměnné Temperature, PartCount, Running a Setpoint.
O["Objects"] --> L["Line1 (objekt)"]
L --> T["Temperature (proměnná)"]
L --> P["PartCount (proměnná)"]
L --> R["Running (proměnná)"]
L --> S["Setpoint (proměnná, zapisovatelná)"]
Obrázek 3: Adresní prostor simulovaného zařízení. Klient nachází uzly sledováním odkazů pomocí služby Browse.
NodeId se skládá z jmenného prostoru a identifikátoru
Uzly se identifikují pomocí NodeId. Ve výstupu ns=2;s=Line1.Temperature znamená, že index jmenného prostoru je 2 a identifikátor je řetězec Line1.Temperature.
Jmenný prostor je ve skutečnosti URI. Specifikace stanoví, že URI jmenných prostorů se ve službách vyjadřují čísly a že toto číslo odpovídá pozici v poli NamespaceArray serveru. Index 0 je jmenný prostor OPC UA http://opcfoundation.org/UA/7.
Proto číslo indexu nepište do kódu, ale zjišťujte index podle URI. Kód výše získá z URI pomocí session.NamespaceUris.GetIndex(LineNamespace) hodnotu 2 a z ní sestaví NodeId.
flowchart TB
accTitle: Sestavení NodeId z URI jmenného prostoru
accDescr: Obrázek ukazuje, že klient vyhledá URI jmenného prostoru v poli NamespaceArray serveru, získá index a spojením tohoto indexu s identifikátorem sestaví NodeId.
U["URI jmenného prostoru"] --> A["NamespaceArray serveru"]
A --> I["Index (v tomto příkladu 2)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identifikátor Line1.Temperature"] --> N
Obrázek 4: Index jmenného prostoru zjistěte podle URI a spojte ho s identifikátorem do NodeId.
Čtení a zápis vracejí s hodnotou i stav a časová razítka
Hodnoty proměnných se čtou a zapisují službami Read a Write8. Čtení nevrací jen hodnotu. Vrací strukturu DataValue, která obsahuje hodnotu, stavový kód, časové razítko zdroje a časové razítko serveru9.
| Položka | Význam |
|---|---|
| Hodnota | Hodnota proměnné. Pokud stavový kód signalizuje chybu, hodnota se ignoruje a server ji nastaví na null |
| Stavový kód (StatusCode) | Zda server dokázal hodnotu získat a poskytnout |
| SourceTimestamp | Čas, který k hodnotě připojil zdroj dat |
| ServerTimestamp | Čas, kdy server hodnotu přijal nebo zjistil, že je správná |
Horní dva bity stavového kódu vyjadřují Good (úspěch), Uncertain (částečný úspěch, pro některé účely nemusí být použitelné) nebo Bad (neúspěch)10. Good [0x00000000] ve výstupu je Good.
Když jsem četl neexistující Line1.Pressure, vrátilo se místo hodnoty BadNodeIdUnknown [0x80340000]. Metoda ReadValueAsync této knihovny při stavu Bad vyvolá výjimku4. Pokud systém, který hodnoty zaznamenává, ukládá ke každé hodnotě i stavový kód, můžete později rozlišit hodnoty zaznamenané v době, kdy byl stav Bad.
flowchart TB
accTitle: Co obsahuje DataValue
accDescr: Obrázek ukazuje, že DataValue vrácená čtením nebo oznámením obsahuje čtyři položky, tedy hodnotu, stavový kód, časové razítko zdroje a časové razítko serveru, a že při stavovém kódu Bad se hodnota ignoruje.
D["DataValue"] --> V["Hodnota"]
D --> S["Stavový kód"]
D --> TS["SourceTimestamp (čas zdroje)"]
D --> TV["ServerTimestamp (čas serveru)"]
S -->|"Pokud Bad"| X["Hodnotu ignorovat"]
Obrázek 5: Výsledek čtení obsahuje nejen hodnotu, ale i stavový kód a dvě časová razítka.
Při zápisu jsem do Setpoint zapsal 32,5 a jako výsledný stavový kód se vrátil Good. O tom, které proměnné jsou zapisovatelné, rozhoduje server. V tomto simulovaném serveru je zapisovatelná jen Setpoint. Zda umožnit přepisování žádaných hodnot zařízení zvenčí, je otázkou bezpečnostního návrhu zařízení, a proto se jí tento článek nezabývá.
Oznamování změn: subskripce a MonitoredItem
Ke sledování změn hodnot může klient opakovat Read v pevném intervalu. OPC UA má kromě toho mechanismus, který sledování hodnot přenechá serveru a nechá si od něj změny oznamovat. Tímto mechanismem jsou subskripce a MonitoredItem.
MonitoredItem je jedna sledovaná položka a má vzorkovací interval. Vzorkovací interval je nejrychlejší interval, ve kterém server vzorkuje hodnoty ze zdroje, a specifikace ho popisuje jako cyklus „best effort“11. Když vzorek splní podmínky filtru, vytvoří se oznámení. Pokud filtr není zadán, oznámení se odešle, když se změní hodnota nebo stav12.
Oznámení se nejprve dostanou do fronty MonitoredItem. Předem se rozhodne, zda se při přetečení fronty zahodí nejstarší, nebo nejnovější položka, a pokud je velikost fronty 2 nebo více, nastaví se ve stavovém kódu bit Overflow, který zahození signalizuje13.
Subskripce sdružuje několik MonitoredItem a v každém publikačním intervalu posílá klientovi oznámení, která ještě nebyla odeslána. Když uplynou cykly bez oznámení k odeslání, pošle keep-alive, aby ukázala, že subskripce stále žije14.
flowchart TB
accTitle: Jak fungují subskripce a MonitoredItem
accDescr: Obrázek ukazuje, že MonitoredItem vzorkuje hodnotu zdroje ve vzorkovacím intervalu a oznámení, která projdou filtrem, ukládá do fronty, a že subskripce je v publikačním intervalu posílá společně klientovi a při absenci oznámení posílá keep-alive.
S["Zdroj (hodnota zařízení)"] --> M["MonitoredItem"]
M -->|"Vzorkovací interval"| F["Filtr (výchozí: změna hodnoty nebo stavu)"]
F --> Q["Fronta (při zaplnění Overflow)"]
Q --> SUB["Subskripce"]
SUB -->|"Publikační interval"| C["Klient"]
SUB -.->|"Pokud nejsou oznámení"| K["Keep-alive"]
Obrázek 6: MonitoredItem vzorkuje a ukládá do fronty a subskripce posílá oznámení klientovi společně.
Kód výše se přihlašuje k odběru Temperature a PartCount se vzorkovacím intervalem 500 milisekund a velikostí fronty 515. Ve výstupu přichází teplota přibližně každých 0,5 sekundy a počet kusů přibližně každé 2 sekundy. První dvě oznámení byla hodnoty, které měly jednotlivé proměnné v okamžiku zahájení subskripce. Čas 13:40:50.801 u počtu kusů je okamžik, kdy se jeho hodnota změnila, ještě před zahájením subskripce. Protože oznámení zobrazují časové razítko zdroje, ukazují čas, kdy se hodnota změnila, ne čas, kdy oznámení přišlo.
Volba mezi opakovaným čtením a subskripcí
Při opakovaném čtení si interval určuje klient a mechanismus je jednoduchý. Na druhou stranu se požadavek a odpověď posílají pokaždé, i když se hodnota nezměnila, a změny kratší než interval se přehlédnou. U subskripce server vzorkuje a posílá jen změny, takže provoz příliš neroste ani u hodnot, které se mění zřídka. I u subskripce se však změny kratší než vzorkovací interval nemusí oznámit. Specifikace vzorkování rychlejší, než je požadováno, dovoluje, ale nevyžaduje11.
flowchart TB
accTitle: Rozdíl mezi opakovaným čtením a subskripcí
accDescr: Obrázek ukazuje, že při opakovaném čtení určuje interval klient a pokaždé posílá požadavek, že při subskripci server vzorkuje ve vzorkovacím intervalu a posílá jen změny a že obě metody mohou přehlédnout změny kratší než interval.
A["Sledování změn hodnot"] --> R["Opakované čtení"]
A --> S["Subskripce"]
R --> R1["Klient určuje interval a pokaždé žádá"]
S --> S1["Server vzorkuje a posílá jen změny"]
R1 --> X["Změny kratší než interval se mohou ztratit"]
S1 --> X
Obrázek 7: U obou metod se mohou změny kratší než interval ztratit.
Povolení připojení pomocí certifikátů
V připojení OPC UA se míra ochrany zpráv volí pomocí MessageSecurityMode. Hodnoty jsou None, která neposkytuje ochranu, Sign, která jen podepisuje, a SignAndEncrypt, která podepisuje a šifruje16. Kombinaci algoritmů použitých k podepisování a šifrování určuje zásada zabezpečení a tento článek používá Basic256Sha256.
K podepisování a šifrování se používá certifikát každé aplikace. Klient a server si navzájem kontrolují certifikáty a odmítají připojení od stran, kterým nedůvěřují. Server vytvořený pomocí knihovny OPC Foundation pro .NET ve výchozím nastavení nenabízí nešifrovanou zásadu None4.
První připojení skončilo chybou BadCertificateUntrusted
Při prvním spuštění klient nedůvěřoval certifikátu serveru, takže bylo připojení odmítnuto. Zde je část výstupu.
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.'
Tato knihovna ukládá odmítnuté certifikáty do úložiště rejected. Když správce certifikát přesune odtud do úložiště trusted, knihovna začne této straně důvěřovat17. Tentokrát jsem certifikát serveru přesunul z pki/rejected/certs do pki/trusted/certs, spustil jsem klienta znovu a připojení se podařilo. Před přesunutím ověřte se správcem serveru kryptografický otisk, který je také součástí názvu souboru4.
flowchart TB
accTitle: Postup, jak začít důvěřovat certifikátu
accDescr: Obrázek ukazuje, že při prvním připojení klient certifikát serveru odmítne a uloží ho do úložiště rejected a že po ověření kryptografického otisku a přesunutí do úložiště trusted mu další připojení důvěřuje a uspěje.
A["První připojení"] --> B["BadCertificateUntrusted"]
B --> C["Certifikát se uloží do úložiště rejected"]
C --> D["Ověřit otisk se správcem serveru"]
D --> E["Přesunout do úložiště trusted"]
E --> F["Další připojení důvěřuje a uspěje"]
Obrázek 8: Po ověření odmítnutého certifikátu a jeho přesunutí do trusted klient této straně důvěřuje.
Existuje také nastavení AutoAcceptUntrustedCertificates, které nedůvěryhodné certifikáty přijímá automaticky. Výchozí hodnota je false a dokumentace knihovny uvádí, že je užitečné ve vývojových prostředích, ale v produkci by se nemělo používat17.
NotAfter=2027-11-01 v protokolu je datum vypršení platnosti certifikátu simulovaného serveru. Jde o certifikát podepsaný sám sebou, vytvořený funkcí asyncua, jehož platnost vyprší rok po dni vytvoření. Platnost certifikátů zařízení také vyprší, proto si v rámci provozu stanovte, jak je před vypršením vyměnit a zajistit, aby druhá strana důvěřovala novým. Práci s úložištěm certifikátů ve Windows popisuje článek „The Windows Certificate Store in Practice“.
Ověřování uživatelů
Certifikáty ověřují aplikace. Nezávisle na tom se při otevírání relace předává token, který identifikuje uživatele. Specifikace definuje čtyři druhy: anonymní, uživatelské jméno a heslo, certifikát X.509 a externě vydaný token18. Klient v tomto článku se připojil anonymně. Které tokeny server přijímá, je uvedeno v informacích o koncovém bodu serveru19.
Kde sběr dat v provozu selhává
Dále uvádím problémy, které v systémech pro sběr dat ze zařízení často vznikají, na základě tohoto experimentu a specifikace.
Hodnoty z doby, kdy server stál, nedorazí
Během subskripce jsem server zastavil a asi po 15 sekundách ho znovu spustil. ManagedSession této knihovny se při výpadku spojení automaticky znovu připojuje20. Zatímco server stál, klient se opakovaně pokoušel znovu připojit.
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.
Když se server vrátil, vrátila se i oznámení. Zde je část výstupu subskripce.
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]
Z doby mezi 13:41:13 a 13:41:34 nedorazila ani jedna hodnota. Počet kusů se vrátil ze 78 na 2. Je to proto, že restart simulovaného serveru způsobil, že začal počítat znovu od 0.
V tomto příkladu stál samotný server, takže hodnoty z této doby nikde nezůstaly. Mechanismus opětovného připojení obnoví spojení, ale hodnoty z doby, kdy server stál, dodatečně nedoplní.
Jiná situace nastává, když se přeruší jen komunikace a server dál běží. Specifikace obsahuje službu Republish, pomocí které lze server požádat, aby z fronty pro opakované odeslání znovu poslal zprávy s oznámeními, které nedorazily21. Oznámení se hromadí i ve frontě MonitoredItem, ale co přesáhne velikost fronty, se zahodí13. Tento případ jsem v tomto experimentu nezkoušel. V obou případech zaznamenávejte časy odpojení a opětovného připojení, abyste poznali, v jakém období hodnoty nepřicházely. Opětovnému připojení a návrhu protokolů u zařízení se sériovou komunikací se věnuje také článek „Serial Communication App Pitfalls“.
flowchart TB
accTitle: Záznam při zastavení serveru
accDescr: Obrázek ukazuje, že když se server během subskripce zastaví, klient se opakovaně pokouší znovu připojit a po návratu serveru se vrátí i oznámení, ale hodnoty z doby zastavení nedorazí, a proto je potřeba zaznamenat časy odpojení a opětovného připojení.
A["Subskripce běží"] --> B["Server se zastaví"]
B --> C["Klient se opakovaně pokouší připojit"]
C --> D["Server se vrátí a oznámení pokračují"]
B -.-> E["Hodnoty z doby zastavení nedorazí"]
E -.-> F["Zaznamenat časy odpojení a připojení"]
Obrázek 9: Opětovné připojení obnoví spojení, ale hodnoty z doby, kdy server stál, nedorazí.
Které časové razítko zaznamenávat
Podle specifikace je SourceTimestamp čas, který k hodnotě připojil zdroj dat, a měl by udávat, kdy se hodnota změnila. ServerTimestamp je čas, kdy server hodnotu přijal nebo zjistil, že je správná9. Simulovaný server v tomto článku generuje hodnoty sám, takže oba časy byly stejné.
Pokud chcete vědět, kdy se hodnota v zařízení změnila, použijte SourceTimestamp. Tento čas se však přiděluje podle hodin zdroje, takže pokud jdou hodiny zařízení nebo serveru špatně, je posunutý i záznam. Pokud budete nasbíraná data porovnávat s jinými záznamy, ukládejte vedle nich i čas přijímajícího počítače, abyste mohli posun později ověřit. Synchronizaci času na straně Windows popisuje článek „Windows Time Synchronization (w32time) and Business Systems“.
Hodnoty se stavem jiným než Good nezpracovávejte jako prostá čísla
Pokud je stavový kód Bad, hodnota se má ignorovat9. Pokud je Uncertain, hodnota nemusí být pro některé účely použitelná10. Než hodnoty zahrnete do grafů nebo souhrnů, roztřiďte je podle stavového kódu a rozhodněte, jak naložit s hodnotami, které nejsou Good. Bit Overflow při přetečení fronty se také nastavuje ve stavovém kódu13. Zobrazování stavu zařízení na obrazovce popisuje článek „Best Practices for Checking and Displaying External Device State“.
Otevření portu ve firewallu
Port OPC UA registrovaný v IANA je 4840/TCP3. Ne všechny servery však toto číslo používají. Ukázky OPC Foundation pro .NET používají například port 625414. Číslo portu ověřte v adrese URL koncového bodu zařízení a povolte připojení k tomuto portu. Pokud server běží ve Windows, povolte ho v bráně firewall systému Windows na daném počítači, a pokud je mezi nimi síťové zařízení, povolte ho i na něm. Vytváření pravidel pro příchozí komunikaci popisuje článek „The Windows Firewall and Business Applications“.
flowchart TB
accTitle: Kde sběr dat selhává
accDescr: Obrázek ukazuje místa, kde sběr dat ze zařízení selhává, rozdělená na obsah záznamů (hodnoty z doby zastavení serveru nedorazí, které časové razítko zaznamenávat, zacházení s hodnotami, které nejsou Good) a přípravu připojení (porty ve firewallu, platnost certifikátů).
A["Kde sběr dat selhává"] --> R["Obsah záznamů"]
A --> N["Příprava připojení"]
R --> B["Hodnoty ze zastavení nedorazí"]
B ~~~ C["Které časové razítko"]
C ~~~ D["Hodnoty, které nejsou Good"]
N --> E["Porty ve firewallu"]
E ~~~ F["Platnost certifikátů"]
Obrázek 10: I když se připojení podaří, kontrolujte mezery v záznamech, časy, stav, porty a certifikáty.
Volba mezi OPC UA, Modbus/TCP a vlastní komunikací výrobců
Pro komunikaci s PLC a zařízeními existuje kromě OPC UA také Modbus/TCP a komunikace, které poskytují výrobci PLC.
Specifikace Modbus pracuje s daty ve čtyřech tabulkách: Discrete Input, jednobitová hodnota jen pro čtení; Coil, jednobitová hodnota pro čtení i zápis; Input Register, 16bitové slovo jen pro čtení; a Holding Register, 16bitové slovo pro čtení i zápis22. V IANA je pro aplikační protokol Modbus (mbap) registrován port 502/TCP3.
Do těchto tabulek patří bity a 16bitová slova, takže jaká hodnota je na které adrese a v jakém formátu, je nutné ověřit v dokumentaci zařízení a zapsat na straně klienta. U OPC UA, jak jsme viděli, server zveřejňuje v adresním prostoru pojmenované proměnné a klient je může zjistit pomocí služby Browse. Hodnoty mají stavový kód a časová razítka a připojení se povolují certifikáty.
Co použít, často rozhodne to, co zařízení nabízí. Pokud má zařízení server OPC UA, použijte ho; pokud má jen Modbus/TCP, vytvořte tabulku adres a čtěte podle ní. Pokud je k dispozici jen komunikace nebo SDK výrobce, řiďte se jeho dokumentací. Volbu způsobu komunikace se zařízeními USB popisuje článek „How to Work with USB Devices from a Windows App“.
flowchart TB
accTitle: Volba způsobu komunikace se zařízením
accDescr: Obrázek ukazuje, že pokud má zařízení server OPC UA, procházejí se a sbírají proměnné přes OPC UA, pokud má jen Modbus/TCP, ověří se adresy a formáty v dokumentaci a čtou se registry, a pokud má jen komunikaci výrobce, postupuje se podle jeho dokumentace.
A["Komunikace, kterou zařízení nabízí"] --> B{"Co je k dispozici?"}
B -->|"Server OPC UA"| C["Procházet a sbírat proměnné"]
B -->|"Jen Modbus/TCP"| D["Ověřit adresy a formáty v dokumentaci"]
B -->|"Jen komunikace výrobce"| E["Řídit se dokumentací a SDK výrobce"]
D --> F["Číst registry a převádět na hodnoty"]
Obrázek 11: Čím data sbírat, často rozhodne to, co zařízení nabízí.
Související články
- 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
Reference
-
OPC Foundation, Classic. Že OPC Classic vychází z COM/DCOM, specifikace DA, A&E a HDA, zveřejnění OPC UA v roce 2008 a důvody přepracování. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Část 1 řady IEC 62541, která se zabývá koncepty a přehledem OPC UA (nahrazena pozdějším vydáním). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Registrace
opcua-tcpna 4840/TCP ambapna 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Jak napsat klienta řady 2.0, že
ReadValueAsyncpři stavu Bad vyvolá výjimku, úložiště rejected a trusted, že servery ve výchozím nastavení nenabízejíNone, a čísla portů použitá v ukázkách. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). Knihovna OPC UA pro Python, kterou jsem použil pro simulovaný server zařízení. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Služba, která sleduje odkazy uzlu. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Že URI jmenných prostorů se ve službách vyjadřují čísly a že toto číslo odpovídá pozici v poli NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Služby Read a Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Definice hodnoty, stavového kódu, SourceTimestamp a ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain a Bad vyjádřené horními dvěma bity. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Že vzorkovací interval je cyklus „best effort“. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Že bez filtru se oznámení posílá při změně hodnoty nebo stavu. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Velikost fronty, způsob zahazování a bit Overflow. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Publikační interval a keep-alive. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsynca vzorkovací interval a velikost fronty vMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Význam None, Sign a SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Postup přesouvání certifikátů z úložiště rejected do důvěryhodného úložiště a nepoužívání
AutoAcceptUntrustedCertificatesv produkci. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Čtyři druhy: anonymní, uživatelské jméno, X.509 a vydaný token. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Seznam uživatelských tokenů, které koncový bod přijímá (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Že
ManagedSessionmá vestavěný mechanismus opětovného připojení. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Služba, kterou subskripce z fronty pro opakované odeslání znovu pošle zprávy s oznámeními. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (kopie uložená na serveru Chicagské univerzity). Datový model v oddílu 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩
Související články
Nejnovější články se stejnými štítky. Prohloubte si znalosti v příbuzných tématech.
Co se zastaví po zapnutí „připojení účtovaného podle objemu dat“? ── Vliv na Windows Update, OneDrive a aplikace ze Storu
Na připojení účtovaném podle objemu dat stahuje Windows Update jen prioritní aktualizace a OneDrive pozastaví synchronizaci. Store, nasta...
Lze smazat Windows.old a „Dočasné soubory“? ── Kdy použít Inteligentní úložiště a kdy Vyčištění disku
O smazání Windows.old a dočasných souborů rozhoduje, zda tím nepřijdete o něco, co už nepůjde obnovit. Podle oficiálních materiálů vysvět...
Co mění režim napájení ve Windows? ── Nejlepší výkon, vyvážený a nejlepší energetická účinnost: rozdíly a jak vybrat
Co mění režimy napájení Windows 11 Nejlepší výkon, Vyvážený a Nejlepší energetická účinnost: nastavení procesoru v pozadí, vztah ke schém...
Co je herní režim ve Windows? ── Co se po zapnutí změní a ovlivňuje i něco jiného než hry?
Herní režim ve Windows zastaví během hraní instalaci ovladačů a oznámení o restartu ze služby Windows Update a snaží se o stabilnější sní...
Co je „vyhrazená paměť GPU“ a „sdílená paměť GPU“ ve Správci úloh? ── Proč se čísla u integrovaných GPU tolik liší a co se stane, když paměť nestačí
Co znamená vyhrazená a sdílená paměť GPU ve Správci úloh, proč integrované GPU ukazují málo vyhrazené a hodně sdílené paměti, jak se urču...
Související témata
Stránky s tématy blízkými tomuto článku. Od článku můžete přejít k souvisejícím službám a dalším článkům.
Technická témata pro Windows
Rozcestník k technickým tématům o vývoji pro Windows, vyšetřování chyb a využití stávajících řešení.
Služby k tomuto tématu
Tento článek navazuje na následující stránky služeb. Vyberte si nejbližší vstup.
Vývoj aplikací pro Windows
Aplikaci pro Windows, která sbírá data ze zařízení, včetně nastavení subskripcí, opětovného připojení a záznamu časových razítek a stavu, lze řešit jako vývoj aplikací pro Windows.
Technické konzultace a revize návrhu
Pokud se chcete rozhodnout, zda sbírat data přes OPC UA, nebo použít Modbus/TCP či vlastní komunikaci výrobce, a jak spravovat certifikáty, lze to řešit jako technické konzultace a revizi návrhu.
Často kladené otázky
Otázky, které se při konzultacích k tématu tohoto článku objevují nejčastěji.
- Jaký je rozdíl mezi OPC UA a OPC Classic (OPC DA)?
- OPC Classic je specifikace, která vyměňuje data přes COM/DCOM ve Windows. OPC UA je specifikace zveřejněná v roce 2008, která nezávisí na konkrétním operačním systému a sjednocuje funkce Classic. Klienti OPC UA běží i mimo Windows a nastavení DCOM není potřeba.
- Stačí při sběru hodnot přes OPC UA číst je v pevném intervalu?
- Pokud je hodnot málo a interval je dlouhý, lze je sbírat i čtením. Ke sledování změn hodnot lze místo toho použít subskripci, která vzorkování přenechá serveru a nechá si od něj změny oznamovat. V obou případech zaznamenávejte stavový kód a časová razítka, která se vracejí spolu s hodnotou.
- Proč se při připojení objeví BadCertificateUntrusted?
- Protože klient zatím nedůvěřuje certifikátu serveru. Knihovna OPC Foundation pro .NET ukládá odmítnuté certifikáty do úložiště rejected. Ověřte kryptografický otisk certifikátu se správcem serveru a teprve potom ho přesuňte do úložiště trusted. Nastavení, které certifikáty přijímá automaticky, používejte jen ve vývojovém prostředí.
- Jaké číslo portu OPC UA používá?
- V registru IANA je port 4840/TCP zapsán jako opcua-tcp. Skutečné servery však mohou používat i jiný port, proto to ověřte v nastavení zařízení. Pokud server běží ve Windows, je potřeba komunikaci na tento port povolit v bráně firewall systému Windows, a pokud je mezi nimi síťové zařízení, tak i v něm.
O autorovi
Stránka s profilem autora článku.
Go Komura
Zástupce společnosti KomuraSoft LLC
Specializuje se na vývoj softwaru pro Windows, technické konzultace a vyšetřování chyb. Silnou stránkou jsou zakázky, v nichž zůstává stávající software, a vyšetřování poruch s nejasnou příčinou.