Уводзіны ў OPC UA ── Збор даных з PLC і абсталявання ў праграме для Windows

· · Windows, OPC UA, Вытворчасць, PLC, C#, .NET, Сувязь, Сертыфікаты

Апублікаваць у X
Гісторыя змяненняў (толькі першая версія, апублікавана 01.11.2026)
Першая публікацыя
Як цытаваць гэты артыкул(DOI: 10.5281/zenodo.23968436)

Гэты артыкул заархіваваны ў Zenodo. Ніжэй пададзены DOI, які заўсёды вядзе да апошняй версіі, і DOI, замацаваны за версіяй, якую вы чытаеце.

Го Комура (2026). Уводзіны ў OPC UA ── Збор даных з PLC і абсталявання ў праграме для Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968436 https://comcomponent.com/be/blog/opc-ua-windows-data-collection/

DOI (апошняя версія)
10.5281/zenodo.23968436
DOI (гэтая версія)
10.5281/zenodo.23968437

Калі вы збіраеце даныя з PLC або абсталявання ў праграму для Windows, а ў абсталявання ёсць сервер OPC UA, часам можна не пісаць асобную сувязь для кожнага вытворцы. Але падключыцца і прачытаць значэнні недастаткова. Калі вы не ведаеце пра стан і пазнакі часу, якія прыходзяць разам са значэннем, пра падпіскі, якія паведамляюць пра змены, пра дазвол падключэння праз сертыфікаты і пра аднаўленне пасля адключэння, вы потым не зможаце давяраць сабраным даным.

Гэты артыкул тлумачыць тую частку OPC UA, якая патрэбна для збору даных, разам з вынікамі рэальнага запуску кліента на C#. Для кліента я выкарыстаў бібліятэку .NET ад OPC Foundation, а на другім баку — сервер, які імітуе абсталяванне. З пэўнымі PLC або абсталяваннем я не правяраў. Налады кожнага абсталявання правярайце ў яго дакументацыі.

Пра збор відарысаў з камер GigE Vision і USB3 Vision гаворыцца ў артыкуле «Dropped Frames with GigE Vision and USB3 Vision Cameras», а пра абсталяванне з паслядоўнай сувяззю — у артыкуле «Serial Communication App Pitfalls».

Адрозненне OPC UA ад OPC Classic

Спецыфікацыі OPC Classic абменьваюцца данымі паміж праграмнымі кампанентамі праз COM/DCOM у Windows. OPC DA для даных працэсу, OPC A&E для сігналаў трывогі і OPC HDA для гісторыі вызначаны як асобныя спецыфікацыі1.

OPC Foundation апублікавала OPC UA (Unified Architecture) у 2008 годзе. OPC UA — сэрвісна-арыентаваная архітэктура, якая не залежыць ад пэўнай платформы і аб’ядноўвае функцыі кожнай са спецыфікацый Classic. Сярод прычын перапраектавання OPC Foundation называе тое, што Microsoft стала аддаваць перавагу крос-платформавым механізмам перад COM/DCOM, што трэба было працаваць і на сістэмах па-за Microsoft і на ўбудаваных прыладах і што трэба было бяспечна падключацца праз брандмаўэры1.

Спецыфікацыя OPC UA стандартызавана і ў IEC як серыя IEC 625412. Для порта сувязі ў IANA зарэгістраваны 4840/TCP як opcua-tcp3.

Адрозненне OPC Classic ад OPC UAДыяграма, якая паказвае, што OPC Classic абапіраецца на COM і DCOM у Windows і падзелены на асобныя спецыфікацыі DA, A&E і HDA, а OPC UA аб'ядноўвае іх функцыі ў адну спецыфікацыю, незалежную ад платформы.Аб'яднанне функцыйOPC ClassicПрацуе на COM/DCOM (Windows)Спецыфікацыі падзелены на DA, A&E і HDAOPC UA (2008)Не залежыць ад пэўнай платформыСтандартызавана як IEC 62541

Малюнак 1: OPC Classic складаецца з асобных спецыфікацый над COM/DCOM, а OPC UA аб’ядноўвае іх у адну.

Калі ў існуючага абсталявання ёсць толькі сервер OPC DA, на баку кліента патрэбны механізм, які выкарыстоўвае COM/DCOM. Гэты артыкул разглядае толькі OPC UA.

На схеме суцэльная лінія пазначае сувязь, якая выконваецца заўсёды, а пункцірная — умоўную сувязь (умовы пададзены ў апісанні кожнай сувязі на старонцы з падрабязнасцямі). Поўны спіс сувязей (усяго 13, з падставамі і ступенню пэўнасці) і азначэнні асноўных паняццяў сабраны на старонцы з падрабязнасцямі карты ведаў (па-японску). Даныя: JSON-LD / Turtle

Правераная канфігурацыя

Я запускаў наступную канфігурацыю.

Роля Што выкарыстоўвалася
Кліент C#, .NET 10 SDK 10.0.114, пакет NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Сервер Імітацыя абсталявання на Python 3.13 і asyncua 2.1.0 (порт 4840 на тым жа ПК)
АС Linux (запуск у Windows не правяраўся)

Бібліятэка кліента — гэта рэалізацыя .NET, якую публікуе OPC Foundation. Для версіі 2.0 я трымаўся спосабу напісання з дакумента Getting started у тым жа рэпазітарыі4.

Правераная канфігурацыяДыяграма канфігурацыі, у якой кліент на C# праз бібліятэку .NET ад OPC Foundation падключаецца зашыфраваным злучэннем да сервера asyncua, які імітуе абсталяванне і слухае порт 4840.opc.tcp, SignAndEncryptКліент на C# (LineCollector)Сервер-імітацыя абсталявання (asyncua)Бібліятэка .NET ад OPC Foundation 2.0.1Чатыры зменныя Line1

Малюнак 2: На адным ПК я падключыўся з кліента на C# да сервера, які імітуе абсталяванне.

Сервер, які імітуе абсталяванне

Замест абсталявання я зрабіў невялікі сервер на asyncua5. Пад аб’ектам Line1 размешчаны чатыры зменныя: тэмпература (Temperature), колькасць вырабленых дэталей (PartCount), працуе ці не (Running) і зададзенае значэнне (Setpoint). Тэмпература мяняецца кожныя 0,5 секунды, колькасць дэталей — кожныя 2 секунды. Я падрыхтаваў два ўзроўні бяспекі: без шыфравання (None) і з подпісам і шыфраваннем (SignAndEncrypt з палітыкай Basic256Sha256).

# Імітацыя абсталявання для праверкі работы. Не замяняе рэальнае абсталяванне або 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())

Гэты сервер прымае сертыфікаты кліентаў, якія падключаюцца, без праверкі. Гэта налада для тэсціравання, і яна адрозніваецца ад паводзін рэальнага абсталявання.

Кліент на C#

У файл праекта я дадаў наступныя два пакеты.

<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>

Кліент па чарзе выконвае падключэнне, прагляд (Browse), чытанне, запіс і падпіску. Сэнс кожнай часткі я тлумачу па чарзе ў наступных раздзелах.

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}");

        // Індэкс прасторы імёнаў вызначае сервер. Знаходзім яго па URI і ствараем з яго 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);

        // Прагляд: пералічыць зменныя пад 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}");
        }

        // Чытанне: глядзім не толькі на значэнне, але і на стан і дзве пазнакі часу
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Пры чытанні неіснуючага вузла замест значэння вяртаецца стан Bad
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Запіс: устанаўлівае Setpoint у 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]}");

        // Падпіска: просім сервер браць адлікі кожныя 500 мс і паведамляць пра змены
        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");

Пры першым запуску падключыцца не ўдалося. Прычыну я тлумачу ў раздзеле «Дазвол падключэння праз сертыфікаты». Пасля таго як я пачаў давяраць сертыфікату сервера, вывад быў такі. Запісы папярэджанняў бібліятэкі я прапусціў.

接続: 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]

Час паказаны ў UTC.

Даныя абсталявання бачныя як вузлы адраснай прасторы

Сервер OPC UA публікуе даныя, якія ў яго ёсць, як набор вузлоў. Гэты набор называецца адраснай прасторай. Вузлы бываюць розных відаў, напрыклад аб’екты, якія прадстаўляюць папкі або абсталяванне, і зменныя са значэннямі, а паміж сабой вузлы злучаны спасылкамі. Кліент праходзіць па спасылках праз службу Browse і высвятляе, якія вузлы ёсць6.

У вывадзе вышэй кліент прайшоў ад Line1 па іерархічных спасылках і знайшоў чатыры зменныя.

Адрасная прастора імітацыі абсталяванняДыяграма, якая паказвае, што пад папкай Objects ёсць аб'ект Line1, а пад ім чатыры зменныя Temperature, PartCount, Running і Setpoint.ObjectsLine1 (аб'ект)Temperature (зменная)PartCount (зменная)Running (зменная)Setpoint (зменная, даступная для запісу)

Малюнак 3: Адрасная прастора імітацыі абсталявання. Кліент знаходзіць вузлы, праходзячы па спасылках праз Browse.

NodeId складаецца з прасторы імёнаў і ідэнтыфікатара

Вузлы ідэнтыфікуюцца праз NodeId. ns=2;s=Line1.Temperature у вывадзе азначае, што індэкс прасторы імёнаў роўны 2, а ідэнтыфікатар — радок Line1.Temperature.

Прастора імёнаў насамрэч ёсць URI. Спецыфікацыя вызначае, што ўнутры службаў URI прасторы імёнаў выражаецца лікам і што гэты лік адпавядае пазіцыі ў NamespaceArray сервера. Індэкс 0 — гэта прастора імёнаў OPC UA http://opcfoundation.org/UA/7.

Таму не пішыце лік індэкса проста ў код, а знаходзьце індэкс па URI. Код вышэй атрымлівае 2 з URI праз session.NamespaceUris.GetIndex(LineNamespace) і з гэтага стварае NodeId.

Стварэнне NodeId з URI прасторы імёнаўДыяграма, якая паказвае, што кліент шукае URI прасторы імёнаў у NamespaceArray сервера, атрымлівае індэкс і, спалучаючы гэты індэкс з ідэнтыфікатарам, стварае NodeId.URI прасторы імёнаўNamespaceArray сервераІндэкс (у гэтым прыкладзе 2)NodeId ns=2;s=Line1.TemperatureІдэнтыфікатар Line1.Temperature

Малюнак 4: Індэкс прасторы імёнаў знаходзяць па URI і спалучаюць з ідэнтыфікатарам у NodeId.

Чытанне і запіс вяртаюць разам са значэннем стан і пазнакі часу

Значэнні зменных чытаюцца і запісваюцца службамі Read і Write8. Чытанне вяртае не толькі значэнне. Яно вяртае структуру DataValue, у якой ёсць значэнне, код стану, пазнака часу крыніцы і пазнака часу сервера9.

Элемент Сэнс
Значэнне Значэнне зменнай. Калі код стану паказвае памылку, значэнне ігнаруецца, а сервер робіць яго null
Код стану (StatusCode) Ці змог сервер атрымаць і даць значэнне
SourceTimestamp Час, які крыніца даных прыпісала значэнню
ServerTimestamp Час, калі сервер атрымаў значэнне або высветліў, што яно правільнае

Два старшыя біты кода стану азначаюць Good (поспех), Uncertain (частковы поспех, для некаторых мэт непрыдатна) або Bad (няўдача)10. Good [0x00000000] у вывадзе — гэта Good.

Пры чытанні неіснуючага Line1.Pressure замест значэння вярнулася BadNodeIdUnknown [0x80340000]. Метад ReadValueAsync гэтай бібліятэкі пры стане Bad выкідвае выключэнне4. Калі механізм, які запісвае значэнні, захоўвае побач са значэннем і код стану, потым можна адрозніць значэнні, запісаныя падчас стану Bad.

Што змяшчае DataValueДыяграма, якая паказвае, што DataValue, які вяртаецца пры чытанні або паведамленні, мае чатыры элементы — значэнне, код стану, пазнаку часу крыніцы і пазнаку часу сервера — і што пры кодзе стану Bad значэнне ігнаруецца.Калі BadDataValueЗначэннеКод стануSourceTimestamp (час крыніцы)ServerTimestamp (час сервера)Ігнараваць значэнне

Малюнак 5: Вынік чытання змяшчае не толькі значэнне, але і код стану і дзве пазнакі часу.

Пры запісе я запісаў 32,5 у Setpoint, і ў выніку вярнуўся код стану Good. Якія зменныя даступныя для запісу, вызначае сервер. У гэтым серверы-імітацыі для запісу даступная толькі Setpoint. Ці дазваляць мяняць зададзеныя значэнні абсталявання звонку — пытанне праектавання бяспекі абсталявання, таму гэты артыкул яго не разглядае.

Паведамленні пра змены: падпіскі і MonitoredItem

Каб сачыць за зменамі значэнняў, кліент можа паўтараць Read праз пэўны інтэрвал. Акрамя гэтага, у OPC UA ёсць механізм, пры якім вы пакідаеце назіранне за значэннямі серверу, а ён паведамляе вам пра змены. Гэты механізм — падпіскі (subscription) і MonitoredItem.

MonitoredItem — гэта адзін аб’ект назірання, у яго ёсць інтэрвал узяцця адлікаў. Інтэрвал узяцця адлікаў — гэта самы хуткі інтэрвал, з якім сервер бярэ адлікі значэнняў ад крыніцы, і спецыфікацыя называе яго цыклам тыпу «best effort»11. Калі адлік адпавядае ўмовам фільтра, ствараецца паведамленне. Калі фільтр не зададзены, паведамленне адпраўляецца пры змене значэння або стану12.

Паведамленні спачатку трапляюць у чаргу MonitoredItem. Загадзя вызначаецца, што адкідаць пры перапаўненні чаргі — самае старое ці самае новае паведамленне, а калі памер чаргі 2 або больш, у кодзе стану ўсталёўваецца біт Overflow, які паказвае на адкіданне13.

Падпіска групуе некалькі MonitoredItem і ў кожным інтэрвале публікацыі адпраўляе кліенту паведамленні, якія яшчэ не адпраўляліся. Калі некалькі цыклаў запар няма чаго адпраўляць, яна адпраўляе keep-alive, які паказвае, што падпіска яшчэ жывая14.

Як працуюць падпіскі і MonitoredItemДыяграма, якая паказвае, што MonitoredItem бярэ адлікі значэння крыніцы з інтэрвалам узяцця адлікаў і кладзе ў чаргу паведамленні, якія прайшлі фільтр, а падпіска адпраўляе іх разам кліенту з інтэрвалам публікацыі, а калі паведамленняў няма, адпраўляе keep-alive.Інтэрвал узяцця адлікаўІнтэрвал публікацыіКалі паведамленняў нямаКрыніца (значэнне абсталявання)MonitoredItemФільтр (па змаўчанні змена значэння або стану)Чарга (пры перапаўненні Overflow)ПадпіскаКліентKeep-alive

Малюнак 6: MonitoredItem бярэ адлікі і кладзе ў чаргу, а падпіска адпраўляе паведамленні кліенту разам.

Код вышэй падпісваецца на Temperature і PartCount з інтэрвалам узяцця адлікаў 500 мілісекунд і памерам чаргі 515. У вывадзе тэмпература прыходзіць прыблізна кожныя 0,5 секунды, а колькасць дэталей — прыблізна кожныя 2 секунды. Першыя два паведамленні былі значэннямі, якія мелі зменныя ў момант пачатку падпіскі. Час 13:40:50.801 у колькасці дэталей — гэта момант, калі значэнне змянілася яшчэ да пачатку падпіскі. Паколькі ў паведамленнях паказваецца пазнака часу крыніцы, бачны час змены значэння, а не час атрымання паведамлення.

Калі паўторнае чытанне, а калі падпіска

Пры паўторным чытанні інтэрвал вызначае кліент, і механізм просты. Затое запыт і адказ ходзяць кожны раз, нават калі значэнне не змянілася, а змены, карацейшыя за інтэрвал, прапускаюцца. Пры падпісцы сервер бярэ адлікі і адпраўляе толькі змены, таму трафік мала расце нават для значэнняў, якія мяняюцца рэдка. Аднак і пры падпісцы змены, карацейшыя за інтэрвал узяцця адлікаў, могуць не трапіць у паведамленні. Спецыфікацыя дазваляе браць адлікі хутчэй, чым запытана, але не патрабуе гэтага11.

Адрозненне паўторнага чытання ад падпіскіДыяграма, якая паказвае, што пры паўторным чытанні кліент вызначае інтэрвал і кожны раз адпраўляе запыт, пры падпісцы сервер бярэ адлікі з інтэрвалам узяцця адлікаў і адпраўляе толькі змены і што ў абодвух выпадках змены, карацейшыя за інтэрвал, могуць быць прапушчаны.Сачыць за зменамі значэнняўПаўтараць чытаннеПадпісаццаКліент вызначае інтэрвал і кожны раз робіць запытСервер бярэ адлікі і адпраўляе толькі зменыЗмены, карацейшыя за інтэрвал, могуць быць прапушчаны

Малюнак 7: Пры любым спосабе змены, карацейшыя за інтэрвал, могуць быць прапушчаны.

Дазвол падключэння праз сертыфікаты

Пры падключэнні OPC UA ступень абароны паведамленняў выбіраецца праз MessageSecurityMode. Значэнні — None без абароны, Sign, які толькі падпісвае, і SignAndEncrypt, які падпісвае і шыфруе16. Спалучэнне алгарытмаў для подпісу і шыфравання вызначае палітыка бяспекі; у гэтым артыкуле я выкарыстаў Basic256Sha256.

Для подпісу і шыфравання выкарыстоўваецца сертыфікат кожнай праграмы. Кліент і сервер правяраюць сертыфікаты адзін аднаго і адмаўляюць у падключэнні таму, каму не давяраюць. Сервер, створаны бібліятэкай .NET ад OPC Foundation, па змаўчанні не прапануе палітыку None без шыфравання4.

Першае падключэнне спынілася на BadCertificateUntrusted

Пры першым запуску кліент не давяраў сертыфікату сервера, таму ў падключэнні было адмоўлена. Вось частка вываду.

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.'

Гэтая бібліятэка захоўвае адхіленыя сертыфікаты ў сховішча rejected. Калі адміністратар перамяшчае іх адтуль у сховішча trusted, бібліятэка пачынае давяраць гэтаму боку17. На гэты раз я перамясціў сертыфікат сервера з pki/rejected/certs у pki/trusted/certs, запусціў яшчэ раз, і падключыцца ўдалося. Перад перамяшчэннем праверце з адміністратарам сервера адбітак, які ёсць і ў назве файла4.

Парадак да даверу сертыфікатуДыяграма, якая паказвае, што пры першым падключэнні кліент адхіляе сертыфікат сервера і захоўвае яго ў сховішча rejected, а пасля праверкі адбітка і перамяшчэння ў сховішча trusted пры наступным падключэнні давярае яму і падключаецца.Першае падключэннеBadCertificateUntrustedСертыфікат захоўваецца ў сховішча rejectedПраверыць адбітак з адміністратарам сервераПерамясціць у сховішча trustedПры наступным падключэнні давярае і падключаецца

Малюнак 8: Калі праверыць адхілены сертыфікат і перамясціць яго ў trusted, кліент давярае гэтаму боку.

Ёсць і налада AutoAcceptUntrustedCertificates, якая аўтаматычна прымае сертыфікаты без даверу. Па змаўчанні яна false, і дакументацыя бібліятэкі піша, што ў асяроддзі распрацоўкі яна карысная, але ў рабочым асяроддзі яе не варта выкарыстоўваць17.

NotAfter=2027-11-01 у журнале — гэта тэрмін дзеяння сертыфіката сервера-імітацыі. Гэта самападпісаны сертыфікат, створаны функцыяй asyncua, і яго тэрмін сканчаўся праз год пасля дня стварэння. У сертыфікатаў абсталявання таксама ёсць тэрмін дзеяння, таму вызначце ў рамках эксплуатацыі парадак, як замяніць іх да заканчэння тэрміну і дамагчыся даверу другога боку. Пра работу са сховішчам сертыфікатаў Windows гаворыцца ў артыкуле «The Windows Certificate Store in Practice».

Аўтэнтыфікацыя карыстальніка

Сертыфікатамі правяраюць адна адну праграмы. Акрамя гэтага, пры адкрыцці сеанса перадаецца токен, які паказвае карыстальніка. Спецыфікацыя мае чатыры віды: ананімны, імя карыстальніка і пароль, сертыфікат X.509 і токен, выдадзены звонку18. Кліент у гэтым артыкуле падключаўся ананімна. Якія токены прымае сервер, указана ў звестках аб канцавой кропцы сервера19.

Дзе збор даных на практыцы спатыкаецца

Далей я пералічваю праблемы, якія часта ўзнікаюць у сістэмах збору даных з абсталявання, на падставе гэтага эксперыменту і спецыфікацыі.

Значэнні за той час, калі сервер стаяў, не прыходзяць

Падчас падпіскі я спыніў сервер і праз каля 15 секунд запусціў яго зноў. ManagedSession гэтай бібліятэкі пры разрыве злучэння аўтаматычна падключаецца зноў20. Пакуль сервер стаяў, кліент раз за разам спрабаваў падключыцца зноў.

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.

Калі сервер вярнуўся, вярнуліся і паведамленні. Вось частка вываду падпіскі.

  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]

З 13:41:13 да 13:41:34 не прыйшло ніводнага значэння. Колькасць дэталей вярнулася з 78 да 2. Гэта таму, што пасля перазапуску сервер-імітацыя пачаў лічыць зноў ад 0.

У гэтым прыкладзе стаяў сам сервер, таму значэнні за гэты час нідзе не захаваліся. Механізм паўторнага падключэння аднаўляе злучэнне, але не дапаўняе потым значэнні за час прастою.

Іншая сітуацыя, калі рвецца толькі сувязь, а сервер працягвае працаваць. У спецыфікацыі ёсць служба Republish, праз якую сервер паўторна адпраўляе з чаргі для паўторнай перадачы паведамленні, што не дайшлі21. Паведамленні назапашваюцца і ў чарзе MonitoredItem, але тое, што перавышае памер чаргі, адкідаецца13. Гэты выпадак у дадзеным эксперыменце я не правяраў. У абодвух выпадках запісвайце час адключэння і паўторнага падключэння, каб было відаць, у які перыяд значэнні не прыходзілі. Пра паўторнае падключэнне і праектаванне журналаў для абсталявання з паслядоўнай сувяззю гаворыцца і ў артыкуле «Serial Communication App Pitfalls».

Запіс пры спыненні сервераДыяграма, якая паказвае, што калі сервер спыняецца падчас падпіскі, кліент раз за разам спрабуе падключыцца зноў і пасля вяртання сервера вяртаюцца і паведамленні, але значэнні за час прастою не прыходзяць, таму трэба запісваць час адключэння і паўторнага падключэння.Падчас падпіскіСервер спыняеццаКліент раз за разам спрабуе падключыцца зноўСервер вяртаецца, вяртаюцца і паведамленніЗначэнні за час прастою не прыходзяцьЗапісваць час адключэння і паўторнага падключэння

Малюнак 9: Паўторнае падключэнне аднаўляе злучэнне, але значэнні за час, калі сервер стаяў, не прыходзяць.

Якую пазнаку часу запісваць

Паводле спецыфікацыі, SourceTimestamp — гэта час, які крыніца даных прыпісала значэнню, і ён павінен паказваць час змены значэння. ServerTimestamp — гэта час, калі сервер атрымаў значэнне або высветліў, што яно правільнае9. Сервер-імітацыя ў гэтым артыкуле сам стварае значэнні, таму абодва часы супадалі.

Калі вы хочаце ведаць, калі значэнне змянілася на абсталяванні, выкарыстоўвайце SourceTimestamp. Але гэты час ставіць гадзіннік крыніцы, таму калі гадзіннік абсталявання або сервера разыходзіцца, разыдзецца і запіс. Калі вы будзеце супастаўляць сабраныя даныя з іншымі запісамі, захоўвайце побач і час ПК, які іх атрымаў, каб потым праверыць разыходжанне. Пра сінхранізацыю часу на баку Windows гаворыцца ў артыкуле «Windows Time Synchronization (w32time) and Business Systems».

Не апрацоўвайце значэнні са станам, адрозным ад Good, як звычайныя лікі

Калі код стану Bad, значэнне трэба ігнараваць9. Пры Uncertain значэнне можа быць непрыдатным для некаторых мэт10. Перад тым як уключаць значэнні ў графікі або зводкі, рассартуйце іх па кодзе стану і загадзя вызначце, што рабіць са значэннямі, якія не Good. Біт Overflow пры перапаўненні чаргі таксама ўсталёўваецца ў кодзе стану13. Як паказваць стан абсталявання на экране, разглядаецца ў артыкуле «Best Practices for Checking and Displaying External Device State».

Адкрыць порт у брандмаўэры

Зарэгістраваны ў IANA порт OPC UA — 4840/TCP3. Але не ўсе серверы выкарыстоўваюць гэты нумар. Прыклады .NET ад OPC Foundation выкарыстоўваюць такія парты, як 625414. Праверце нумар порта ў URL канцавой кропкі абсталявання і дазвольце падключэнне да гэтага порта. Калі сервер працуе ў Windows, дазвольце яго ў Windows Firewall гэтага камп’ютара, а калі паміж імі ёсць сеткавае абсталяванне, то і на ім. Пра стварэнне правілаў для ўваходнага трафіку гаворыцца ў артыкуле «The Windows Firewall and Business Applications».

Дзе збор даных спатыкаеццаДыяграма, якая дзеліць месцы, дзе спатыкаецца збор даных з абсталявання, на змест запісаў (значэнні за час спынення сервера не прыходзяць, якую пазнаку часу запісваць, што рабіць са значэннямі, якія не Good) і падрыхтоўку падключэння (парты ў брандмаўэры, тэрмін дзеяння сертыфікатаў).Дзе збор даных спатыкаеццаЗмест запісаўПадрыхтоўка падключэнняЗначэнні за час спынення не прыходзяцьЯкая пазнака часуЗначэнні, якія не GoodПарты ў брандмаўэрыТэрмін дзеяння сертыфікатаў

Малюнак 10: Нават пасля паспяховага падключэння праверце прапускі ў запісах, час, стан, парты і сертыфікаты.

Калі OPC UA, а калі Modbus/TCP або сувязь вытворцы

Для сувязі з PLC і абсталяваннем, акрамя OPC UA, ёсць таксама Modbus/TCP і сувязь, якую прапануе вытворца PLC.

Спецыфікацыя Modbus апрацоўвае даныя ў чатырох табліцах: Discrete Input (толькі чытанне, 1 біт), Coil (чытанне і запіс, 1 біт), Input Register (толькі чытанне, 16 бітаў) і Holding Register (чытанне і запіс, 16 бітаў)22. У IANA порт 502/TCP зарэгістраваны як прыкладны пратакол Modbus (mbap)3.

У гэтыя табліцы трапляюць біты і 16-бітавыя словы, таму на якім адрасе якое значэнне і ў якім фармаце, трэба праверыць у дакументацыі абсталявання і запісаць на баку кліента. У OPC UA, як мы бачылі, сервер публікуе ў адраснай прасторы зменныя з імёнамі, і кліент можа высветліць іх праз Browse. Да значэнняў дадаюцца код стану і пазнакі часу, а падключэнне дазваляецца праз сертыфікаты.

Што выкарыстоўваць, часта вырашае тое, што прапануе абсталяванне. Калі ў абсталявання ёсць сервер OPC UA, выкарыстоўвайце яго; калі ёсць толькі Modbus/TCP, складзіце табліцу адпаведнасці адрасоў і чытайце па ёй. Калі ёсць толькі сувязь або SDK вытворцы, трымайцеся іх дакументацыі. Пра выбар спосабу сувязі з USB-прыладамі гаворыцца ў артыкуле «How to Work with USB Devices from a Windows App».

Выбар спосабу сувязі з абсталяваннемДыяграма, якая паказвае, што калі ў абсталявання ёсць сервер OPC UA, зменныя збіраюцца праглядам праз OPC UA, калі ёсць толькі Modbus/TCP, у дакументацыі правяраюцца адрасы і фарматы і чытаюцца рэгістры, а калі ёсць толькі сувязь вытворцы, трымаюцца яго дакументацыі.Сервер OPC UAТолькі Modbus/TCPТолькі сувязь вытворцыСувязь, якую прапануе абсталяваннеШто даступна?Збіраць зменныя праглядамПраверыць адрасы і фарматы ў дакументацыіТрымацца дакументацыі і SDK вытворцыЧытаць рэгістры і пераводзіць у значэнні

Малюнак 11: Чым збіраць, часта вырашае тое, што прапануе абсталяванне.

Звязаныя артыкулы

Спасылкі на крыніцы

  1. OPC Foundation, Classic. Тое, што OPC Classic абапіраецца на COM/DCOM, спецыфікацыі DA, A&E і HDA, публікацыя OPC UA ў 2008 годзе і прычыны перапраектавання. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Частка 1 серыі IEC 62541 пра канцэпцыі і агляд OPC UA (ужо заменена больш новым выданнем). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. Рэгістрацыя opcua-tcp на 4840/TCP і mbap на 502/TCP. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Як пісаць кліент версіі 2.0, тое, што ReadValueAsync пры Bad выкідвае выключэнне, сховішчы адхіленых і давераных сертыфікатаў, тое, што сервер па змаўчанні не прапануе None, і нумары партоў у прыкладах. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Бібліятэка OPC UA для Python, выкарыстаная для сервера-імітацыі абсталявання. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Служба, якая праходзіць па спасылках вузлоў. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Тое, што ўнутры службаў URI прасторы імёнаў выражаецца лікам і што гэты лік адпавядае пазіцыі ў NamespaceArray. ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Службы Read і Write. ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Азначэнні значэння, кода стану, SourceTimestamp і ServerTimestamp. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain і Bad паводле двух старшых бітаў. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Тое, што інтэрвал узяцця адлікаў — гэта цыкл тыпу best effort. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Тое, што без фільтра паведамленне адпраўляецца пры змене значэння або стану. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Памер чаргі, спосаб адкідання і біт Overflow. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Інтэрвал публікацыі і keep-alive. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync і інтэрвал узяцця адлікаў і памер чаргі ў MonitoredItemOptions. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Сэнс None, Sign і SignAndEncrypt. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Практыка перамяшчэння сертыфікатаў са сховішча rejected у сховішча даверу і тое, што AutoAcceptUntrustedCertificates не варта выкарыстоўваць у рабочым асяроддзі. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Чатыры віды: ананімны, імя карыстальніка, X.509 і выдадзены токен. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Спіс токенаў карыстальнікаў, якія прымае канцавая кропка (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Тое, што ў ManagedSession убудаваны механізм паўторнага падключэння. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Служба, праз якую чарга падпіскі для паўторнай перадачы зноў адпраўляе паведамленні. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (копія на серверы Чыкагскага ўніверсітэта). Мадэль даных у раздзеле 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩

Апошнія артыкулы з такімі ж тэгамі. Паглыбляйце веды на блізкіх тэмах.

Што такое «Dedicated GPU memory» і «Shared GPU memory» у Task Manager? ── Чаму лічбы на ўбудаваным GPU так моцна адрозніваюцца і што адбываецца, калі памяці не хапае

Тлумачым для звычайных карыстальнікаў, што азначаюць выдзеленая і агульная памяць GPU ў Task Manager. Падсумоўваем, чаму на ўбудаваным GP...

Гэта тэматычныя старонкі, блізкія да гэтай тэмы. Ад артыкула можна перайсці да звязаных паслуг і іншых артыкулаў.

Гэты артыкул вядзе да наступных старонак паслуг. Пачніце з бліжэйшай.

Тэхнічныя кансультацыі і рэцэнзаванне праектных рашэнняў

Калі вы хочаце вырашыць, ці збіраць даныя праз OPC UA, ці выкарыстоўваць Modbus/TCP або сувязь вытворцы, і як кіраваць сертыфікатамі, гэта можна разглядаць як тэхнічную кансультацыю і рэвізію праекта.

Частыя пытанні

Частыя пытанні падчас кансультацый па тэме гэтага артыкула.

Чым адрозніваюцца OPC UA і OPC Classic (OPC DA)?
OPC Classic — гэта спецыфікацыі, якія абменьваюцца данымі праз COM/DCOM у Windows. OPC UA — апублікаваная ў 2008 годзе спецыфікацыя, якая не залежыць ад пэўнай АС і аб'ядноўвае функцыі Classic. Кліенты OPC UA працуюць і па-за Windows, і налада DCOM ім не патрэбна.
Ці дастаткова пры зборы значэнняў праз OPC UA чытаць іх праз пэўны інтэрвал?
Калі значэнняў мала і інтэрвал доўгі, іх можна збіраць і чытаннем. Каб сачыць за зменамі значэнняў, можна выкарыстаць падпіску: вы пакідаеце серверу ўзяцце адлікаў, а ён паведамляе вам пра змены. У абодвух выпадках запісвайце таксама код стану і пазнакі часу, якія прыходзяць разам са значэннем.
Чаму пры падключэнні з'яўляецца BadCertificateUntrusted?
Таму што кліент яшчэ не давярае сертыфікату сервера. Бібліятэка .NET ад OPC Foundation захоўвае адхіленыя сертыфікаты ў сховішча rejected. Праверце адбітак сертыфіката з адміністратарам сервера, а потым перамясціце яго ў сховішча trusted. Наладу, якая аўтаматычна прымае сертыфікаты, выкарыстоўвайце толькі ў асяроддзі распрацоўкі.
Які нумар порта выкарыстоўвае OPC UA?
У IANA порт 4840/TCP зарэгістраваны як opcua-tcp. Аднак рэальныя серверы часам выкарыстоўваюць іншы порт, таму праверце яго ў наладах абсталявання. Калі сервер працуе ў Windows, сувязь з гэтым портам трэба дазволіць у Windows Firewall, а калі паміж імі ёсць сеткавае абсталяванне, то і на ім.

Профіль аўтара

Старонка з профілем аўтара артыкула.

Го Комура

Прадстаўнік KomuraSoft LLC

Займаецца распрацоўкай ПЗ для Windows, тэхнічнымі кансультацыямі і расследаваннем дэфектаў; асабліва моцны ў праектах з наяўнымі распрацоўкамі і ў пошуку прычын збояў, якія цяжка выявіць.

Публічныя спасылкі

Назад да блога