OPC UA:n perusteet ── PLC:iden ja laitteiden tietojen keruu Windows-sovellukseen

· · Windows, OPC UA, Valmistava teollisuus, PLC, C#, .NET, Tietoliikenne, Varmenteet

Julkaise X:ssä
Muutoshistoria (vain ensimmäinen versio, julkaistu 1.11.2026)
Ensimmäinen julkaisu
Lainaa tätä artikkelia(DOI: 10.5281/zenodo.23968421)

Tämä artikkeli on arkistoitu Zenodoon. Alla on sekä DOI, joka johtaa aina uusimpaan versioon, että DOI, joka on sidottu parhaillaan näytettävään versioon.

Go Komura (2026). OPC UA:n perusteet ── PLC:iden ja laitteiden tietojen keruu Windows-sovellukseen. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968421 https://comcomponent.com/fi/blog/opc-ua-windows-data-collection/

DOI (uusin versio)
10.5281/zenodo.23968421
DOI (tämä versio)
10.5281/zenodo.23968422

Kun PLC:iden tai laitteiden tietoja kerätään Windows-sovellukseen ja laitteessa on OPC UA -palvelin, jokaiselle valmistajalle ei välttämättä tarvitse kirjoittaa omaa tiedonsiirtoa. Pelkkä yhdistäminen ja arvojen lukeminen ei kuitenkaan riitä. Jos et tunne arvon mukana palaavaa tilaa ja aikaleimoja, muutoksista ilmoittavia tilauksia, yhteyksien sallimista varmenteilla ja katkoksista toipumista, kerättyihin tietoihin ei voi myöhemmin luottaa.

Tämä artikkeli selittää ne OPC UA:n osat, joita tiedonkeruussa tarvitaan, ja näyttää tulokset C#-asiakkaan todellisista ajoista. Asiakas käyttää OPC Foundationin .NET-kirjastoa, ja vastapuolena on simuloitu laitepalvelin. En ole kokeillut tätä millään tietyllä PLC:llä tai laitteella. Tarkista laitekohtaiset asetukset kyseisen laitteen dokumentaatiosta.

Kuvien keruuta GigE Vision- ja USB3 Vision -kameroista käsitellään artikkelissa “Dropped Frames with GigE Vision and USB3 Vision Cameras” ja sarjaliikennettä käyttäviä laitteita artikkelissa “Serial Communication App Pitfalls”.

OPC UA:n ja OPC Classicin ero

OPC Classicin määritykset välittävät tietoja ohjelmistokomponenttien välillä Windowsin COM/DCOM-tekniikalla. Prosessitietoja käsittelevä OPC DA, hälytyksiä käsittelevä OPC A&E ja historiatietoja käsittelevä OPC HDA on määritelty erillisinä määrityksinä1.

OPC Foundation julkaisi OPC UA:n (Unified Architecture) vuonna 2008. OPC UA on palvelukeskeinen arkkitehtuuri, joka ei ole sidottu tiettyyn alustaan ja joka kokoaa yhteen kunkin Classic-määrityksen toiminnot. Uudelleensuunnittelun syiksi OPC Foundation mainitsee muun muassa sen, että Microsoft alkoi painottaa COM/DCOMia enemmän alustariippumattomia tekniikoita, että järjestelmän piti toimia myös muissa kuin Microsoftin järjestelmissä ja sulautetuissa laitteissa ja että yhteyksiä piti voida muodostaa turvallisesti palomuurien läpi1.

OPC UA -määritys on myös IEC:n standardoima IEC 62541 -sarjana2. Tietoliikenneportiksi IANA on rekisteröinyt 4840/TCP:n nimellä opcua-tcp3.

OPC Classicin ja OPC UA:n eroKaavio, joka näyttää, että OPC Classic perustuu Windowsin COM- ja DCOM-tekniikkaan ja jakautuu erillisiin DA-, A&E- ja HDA-määrityksiin, kun taas OPC UA kokoaa niiden toiminnot yhdeksi alustasta riippumattomaksi määritykseksi.Toiminnot kootaan yhteenOPC ClassicToimii COM/DCOM:n päällä (Windows)Jaettu DA-, A&E- ja HDA-määrityksiinOPC UA (2008)Ei sidottu tiettyyn alustaanStandardoitu nimellä IEC 62541

Kuva 1: OPC Classic koostuu erillisistä määrityksistä COM/DCOM:n päällä, ja OPC UA kokoaa ne yhdeksi.

Jos olemassa olevassa laitteistossa on vain OPC DA -palvelin, tarvitaan COM/DCOMia käyttävä ratkaisu. Tämä artikkeli käsittelee vain OPC UA:ta.

Kaaviossa yhtenäinen viiva kuvaa suhdetta, joka pätee aina, ja katkoviiva ehdollista suhdetta (ehdot on kuvattu kunkin suhteen kohdalla yksityiskohtasivulla). Suhteiden täydellinen luettelo (yhteensä 13, todisteineen ja varmuustasoineen) sekä keskeisten käsitteiden määritelmät on koottu tietokartan yksityiskohtasivulle (japaniksi). Data: JSON-LD / Turtle

Kokeiltu kokoonpano

Ajoin seuraavan kokoonpanon.

Rooli Mitä käytin
Asiakas C#, .NET 10 SDK 10.0.114, NuGet-paketti OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1
Palvelin Pythonilla 3.13 ja asyncua 2.1.0:lla tehty simuloitu laite (portti 4840 samassa tietokoneessa)
Käyttöjärjestelmä Linux (ajoa Windowsissa ei kokeiltu)

Asiakaskirjasto on OPC Foundationin julkaisema .NET-toteutus. 2.0-sarjan käytössä noudatin saman tietovaraston Getting started -ohjeen tapaa4.

Kokeiltu kokoonpanoKaavio, joka näyttää C#-asiakkaan, joka käyttää OPC Foundationin .NET-kirjastoa ja yhdistää salatulla yhteydellä porttia 4840 kuuntelevaan asyncualla tehtyyn simuloituun laitepalvelimeen.opc.tcp, SignAndEncryptC#-asiakas (LineCollector)Simuloitu laitepalvelin (asyncua)OPC Foundationin .NET-kirjasto 2.0.1Neljä muuttujaa Line1:n alla

Kuva 2: C#-asiakas yhdisti simuloituun laitepalvelimeen saman tietokoneen sisällä.

Simuloitu laitepalvelin

Todellisen laitteen sijaan tein pienen palvelimen asyncualla5. Line1-nimisen objektin alla on neljä muuttujaa: lämpötila (Temperature), tuotemäärä (PartCount), käynnissä (Running) ja asetusarvo (Setpoint). Lämpötila muuttuu 0,5 sekunnin ja tuotemäärä 2 sekunnin välein. Suojaukseksi tarjosin kaksi vaihtoehtoa: ei salausta (None) sekä allekirjoitus ja salaus (Basic256Sha256:n SignAndEncrypt).

# Testaukseen tarkoitettu simuloitu laite. Se ei korvaa tuotantolaitteita tai PLC:itä.
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())

Tämä palvelin hyväksyy yhdistävien asiakkaiden varmenteet tarkistamatta niitä. Se on testiasetus, ja se poikkeaa tuotantolaitteiden toiminnasta.

C#-asiakas

Lisäsin projektitiedostoon nämä kaksi pakettia.

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

Asiakas yhdistää, selaa, lukee, kirjoittaa ja tilaa tässä järjestyksessä. Seuraavat luvut selittävät, mitä kukin osa tarkoittaa.

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

        // Palvelin päättää nimiavaruuden indeksin. Haetaan se URI:sta ja muodostetaan NodeIdit sen avulla
        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);

        // Selaus: luetellaan Line1:n alla olevat muuttujat
        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}");
        }

        // Luku: katsotaan arvon lisäksi tila ja kaksi aikaleimaa
        DataValue t = await session.ReadValueAsync(temperature, ct);
        Console.WriteLine($"Temperature = {t.WrappedValue}  状態 {t.StatusCode}");
        Console.WriteLine($"  Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");

        // Olemattoman solmun lukeminen palauttaa arvon sijaan Bad-tilan
        try
        {
            await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
        }
        catch (ServiceResultException ex)
        {
            Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
        }

        // Kirjoitus: asetetaan Setpoint arvoon 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]}");

        // Tilaus: pyydetään palvelinta ottamaan näyte 500 ms:n välein ja ilmoittamaan muutoksista
        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");

Ensimmäisellä ajokerralla yhteys ei onnistunut. Syy selitetään luvussa “Yhteyksien salliminen varmenteilla”. Alla on tuloste sen jälkeen, kun palvelimen varmenteeseen oli luotettu. Kirjaston varoituslokit on jätetty pois.

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

Ajat ovat UTC-aikaa.

Laitteen tiedot näkyvät osoiteavaruuden solmuina

OPC UA -palvelin julkaisee hallussaan olevat tiedot solmujen joukkona. Tätä joukkoa kutsutaan osoiteavaruudeksi. Solmuja on eri lajeja, kuten kansioita tai laitteita kuvaavia objekteja ja arvoja sisältäviä muuttujia, ja solmut on yhdistetty toisiinsa viittauksilla. Asiakas seuraa viittauksia Browse-palvelulla ja selvittää, mitä solmuja on olemassa6.

Yllä olevassa tulosteessa asiakas seuraa hierarkkisia viittauksia Line1:stä ja löytää neljä muuttujaa.

Simuloidun laitteen osoiteavaruusKaavio, joka näyttää Objects-kansion alla olevan Line1-objektin ja sen alla neljä muuttujaa: Temperature, PartCount, Running ja Setpoint.ObjectsLine1 (objekti)Temperature (muuttuja)PartCount (muuttuja)Running (muuttuja)Setpoint (muuttuja, kirjoitettava)

Kuva 3: Simuloidun laitteen osoiteavaruus. Asiakas löytää solmut seuraamalla viittauksia Browse-palvelulla.

NodeId koostuu nimiavaruudesta ja tunnisteesta

Solmut tunnistetaan NodeIdillä. Tulosteen ns=2;s=Line1.Temperature tarkoittaa, että nimiavaruuden indeksi on 2 ja tunniste on merkkijono Line1.Temperature.

Nimiavaruus on pohjimmiltaan URI. Määrityksen mukaan nimiavaruuksien URI:t esitetään palveluissa numeroina, ja numero vastaa sijaintia palvelimen NamespaceArray-taulukossa. Indeksi 0 on OPC UA:n nimiavaruus http://opcfoundation.org/UA/7.

Siksi indeksin numeroa ei kannata kirjoittaa koodiin, vaan indeksi haetaan URI:sta. Yllä oleva koodi hakee URI:sta arvon 2 kutsulla session.NamespaceUris.GetIndex(LineNamespace) ja muodostaa NodeIdit sen avulla.

NodeIdin muodostaminen nimiavaruuden URI:staKaavio, joka näyttää, että asiakas hakee nimiavaruuden URI:n palvelimen NamespaceArray-taulukosta saadakseen indeksin ja yhdistää indeksin tunnisteeseen muodostaakseen NodeIdin.Nimiavaruuden URIPalvelimen NamespaceArrayIndeksi (tässä esimerkissä 2)NodeId ns=2;s=Line1.TemperatureTunniste Line1.Temperature

Kuva 4: Nimiavaruuden indeksi haetaan URI:sta ja yhdistetään tunnisteeseen NodeIdiksi.

Luku ja kirjoitus palauttavat arvon mukana tilan ja aikaleimat

Muuttujien arvoja luetaan ja kirjoitetaan Read- ja Write-palveluilla8. Luku palauttaa muutakin kuin arvon. Se palauttaa DataValue-nimisen rakenteen, jossa on arvo, tilakoodi, lähteen aikaleima ja palvelimen aikaleima9.

Kohta Merkitys
Arvo Muuttujan arvo. Kun tilakoodi ilmaisee virheen, arvo jätetään huomiotta ja palvelin asettaa sen null-arvoksi
Tilakoodi (StatusCode) Pystyikö palvelin hankkimaan ja toimittamaan arvon
SourceTimestamp Aika, jonka tiedon lähde liitti arvoon
ServerTimestamp Aika, jolloin palvelin vastaanotti arvon tai sai tietää sen olevan oikea

Tilakoodin kaksi ylintä bittiä ilmaisevat tilan Good (onnistui), Uncertain (onnistui osittain, joihinkin käyttötarkoituksiin ei välttämättä kelpaa) tai Bad (epäonnistui)10. Tulosteen Good [0x00000000] on Good.

Kun luin olematonta Line1.Pressure-solmua, arvon sijaan palautui BadNodeIdUnknown [0x80340000]. Tämän kirjaston ReadValueAsync heittää poikkeuksen, kun tila on Bad4. Jos arvoja tallentava järjestelmä tallentaa jokaisen arvon rinnalle myös tilakoodin, Bad-tilan aikana tallennetut arvot voidaan myöhemmin erottaa muista.

Mitä DataValue sisältääKaavio, joka näyttää, että luvun tai ilmoituksen palauttamassa DataValue-rakenteessa on neljä asiaa, arvo, tilakoodi, lähteen aikaleima ja palvelimen aikaleima, ja että arvo jätetään huomiotta, kun tilakoodi on Bad.Jos BadDataValueArvoTilakoodiSourceTimestamp (lähteen aika)ServerTimestamp (palvelimen aika)Arvo jätetään huomiotta

Kuva 5: Luvun tuloksessa on arvon lisäksi tilakoodi ja kaksi aikaleimaa.

Kirjoituksessa kirjoitin Setpoint-muuttujaan arvon 32.5, ja tuloksen tilakoodiksi palautui Good. Palvelin päättää, mitkä muuttujat ovat kirjoitettavissa. Tässä simuloidussa palvelimessa vain Setpoint on kirjoitettavissa. Se, saako laitteen asetusarvoja muuttaa ulkopuolelta, kuuluu laitteen turvallisuussuunnitteluun, joten tämä artikkeli ei käsittele sitä.

Ilmoitukset muutoksista: tilaukset ja MonitoredItemit

Arvojen muutosten seuraamiseksi asiakas voi toistaa Read-palvelua tasaisin väliajoin. Sen lisäksi OPC UA:ssa on mekanismi, joka jättää arvojen valvonnan palvelimelle ja pyytää sitä ilmoittamaan muutoksista. Tämä mekanismi on tilaukset (Subscription) ja MonitoredItemit.

MonitoredItem on yksi valvottava kohde, ja sillä on näytteenottoväli. Näytteenottoväli on nopein tahti, jolla palvelin ottaa lähteestä näytteitä arvoista, ja määritys kuvaa sitä “parhaan yrityksen” (best effort) jaksoksi11. Kun näyte täyttää suodattimen ehdot, syntyy ilmoitus. Jos suodatinta ei ole määritetty, ilmoitus lähetetään, kun arvo tai tila muuttuu12.

Ilmoitukset menevät ensin MonitoredItemin jonoon. Etukäteen päätetään, hylätäänkö jonon täyttyessä vanhin vai uusin ilmoitus, ja jos jonon koko on 2 tai enemmän, tilakoodiin asetetaan hylkäämisestä kertova Overflow-bitti13.

Tilaus kokoaa useita MonitoredItemejä ja lähettää jokaisella julkaisuvälillä asiakkaalle ne ilmoitukset, joita ei vielä ole lähetetty. Kun jaksoja kuluu ilman lähetettäviä ilmoituksia, se lähettää keep-alive-viestin osoittaakseen, että tilaus on yhä voimassa14.

Miten tilaukset ja MonitoredItemit toimivatKaavio, joka näyttää, että MonitoredItem ottaa näytteitä lähteen arvosta näytteenottovälin mukaan ja lisää suodattimen läpäisseet ilmoitukset jonoon ja että tilaus lähettää ne yhdessä asiakkaalle julkaisuvälin mukaan ja lähettää keep-alive-viestin, kun ilmoituksia ei ole.NäytteenottoväliJulkaisuväliJos ilmoituksia ei oleLähde (laitteen arvo)MonitoredItemSuodatin (oletus: arvon tai tilan muutos)Jono (Overflow, kun täynnä)TilausAsiakasKeep-alive

Kuva 6: MonitoredItem ottaa näytteet ja jonottaa ne, ja tilaus lähettää ilmoitukset asiakkaalle yhdessä.

Yllä oleva koodi tilaa Temperature- ja PartCount-muuttujat 500 millisekunnin näytteenottovälillä ja jonon koolla 515. Tulosteessa lämpötila saapuu noin 0,5 sekunnin ja tuotemäärä noin 2 sekunnin välein. Kaksi ensimmäistä ilmoitusta olivat arvot, jotka muuttujilla oli tilauksen alkaessa. Tuotemäärän 13:40:50.801 on aika, jolloin sen arvo muuttui ennen tilauksen alkua. Koska ilmoituksissa näytetään lähteen aikaleima, niissä näkyy arvon muuttumisen aika eikä ilmoituksen vastaanottamisen aika.

Toistuvan luvun ja tilauksen valinta

Toistuvassa luvussa asiakas voi itse päättää välin, ja mekanismi on yksinkertainen. Toisaalta pyyntö ja vastaus kulkevat edestakaisin joka kerta, vaikka arvo ei olisi muuttunut, ja väliä lyhyemmät muutokset jäävät huomaamatta. Tilauksessa palvelin ottaa näytteet ja lähettää vain muutokset, joten liikenne ei juuri kasva harvoin muuttuvillakaan arvoilla. Tilauksessakaan näytteenottoväliä lyhyemmistä muutoksista ei kuitenkaan välttämättä ilmoiteta. Määritys sallii pyydettyä nopeamman näytteenoton mutta ei vaadi sitä11.

Toistuvan luvun ja tilauksen eroKaavio, joka näyttää, että toistuvassa luvussa asiakas päättää välin ja lähettää pyynnön joka kerta, että tilauksessa palvelin ottaa näytteet näytteenottovälin mukaan ja lähettää vain muutokset ja että kumpikin voi ohittaa väliä lyhyemmät muutokset.Arvojen muutosten seurantaToistuva lukuTilausAsiakas päättää välin ja pyytää joka kertaPalvelin ottaa näytteet ja lähettää vain muutoksetVäliä lyhyemmät muutokset voivat jäädä huomaamatta

Kuva 7: Kummallakin tavalla väliä lyhyemmät muutokset voivat jäädä huomaamatta.

Yhteyksien salliminen varmenteilla

OPC UA -yhteydessä viestien suojauksen taso valitaan MessageSecurityMode-asetuksella. Arvot ovat None, joka ei suojaa, Sign, joka vain allekirjoittaa, ja SignAndEncrypt, joka allekirjoittaa ja salaa16. Allekirjoituksessa ja salauksessa käytettävien algoritmien yhdistelmä määräytyy suojauskäytännön (security policy) mukaan, ja tässä artikkelissa käytetään Basic256Sha256:ta.

Allekirjoituksessa ja salauksessa käytetään sovelluskohtaista varmennetta. Asiakas ja palvelin tarkistavat toistensa varmenteet ja hylkäävät yhteydet osapuolilta, joihin ne eivät luota. OPC Foundationin .NET-kirjastolla tehty palvelin ei oletuksena tarjoa salaamatonta None-käytäntöä4.

Ensimmäinen yhteys pysähtyi virheeseen BadCertificateUntrusted

Ensimmäisellä ajokerralla asiakas ei luottanut palvelimen varmenteeseen, joten yhteys hylättiin. Tässä osa tulosteesta.

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

Tämä kirjasto tallentaa hylätyt varmenteet rejected-säilöön. Kun ylläpitäjä siirtää varmenteen sieltä trusted-säilöön, kirjasto alkaa luottaa kyseiseen osapuoleen17. Tällä kertaa siirsin palvelimen varmenteen kansiosta pki/rejected/certs kansioon pki/trusted/certs, ajoin ohjelman uudelleen ja sain yhteyden. Ennen siirtoa tarkista palvelimen ylläpitäjän kanssa tunnistearvo (thumbprint), joka on myös osa tiedostonimeä4.

Varmenteeseen luottamisen vaiheetKaavio, joka näyttää, että ensimmäisessä yhteydessä asiakas hylkää palvelimen varmenteen ja tallentaa sen rejected-säilöön ja että kun tunnistearvo on tarkistettu ja varmenne siirretty trusted-säilöön, seuraava yhteys luottaa siihen ja onnistuu.Ensimmäinen yhteysBadCertificateUntrustedVarmenne tallennetaan rejected-säilöönTunnistearvo tarkistetaan palvelimen ylläpitäjän kanssaVarmenne siirretään trusted-säilöönSeuraava yhteys luottaa siihen ja onnistuu

Kuva 8: Kun hylätty varmenne on tarkistettu ja siirretty trusted-säilöön, asiakas luottaa kyseiseen osapuoleen.

On myös asetus AutoAcceptUntrustedCertificates, joka hyväksyy epäluotetut varmenteet automaattisesti. Sen oletusarvo on false, ja kirjaston dokumentaation mukaan se on hyödyllinen kehitysympäristöissä mutta sitä ei pidä käyttää tuotannossa17.

Lokin NotAfter=2027-11-01 on simuloidun palvelimen varmenteen viimeinen voimassaolopäivä. Se on asyncuan funktiolla luotu itse allekirjoitettu varmenne, jonka voimassaolo päättyi vuoden kuluttua luontipäivästä. Myös laitteiden varmenteet vanhenevat, joten osana käyttöä kannattaa päättää, miten ne vaihdetaan ennen vanhenemista ja miten vastapuoli saadaan luottamaan uusiin. Windowsin varmennesäilön käsittelyä käsitellään artikkelissa “The Windows Certificate Store in Practice”.

Käyttäjän todentaminen

Varmenteilla tarkistetaan sovellukset. Sen lisäksi istuntoa avattaessa välitetään käyttäjän tunnistava tunnus (token). Määritys määrittelee neljä lajia: anonyymi, käyttäjänimi ja salasana, X.509-varmenne sekä ulkoisesti myönnetty tunnus18. Tämän artikkelin asiakas yhdisti anonyymisti. Se, mitä tunnuksia palvelin hyväksyy, sisältyy palvelimen päätepisteen tietoihin19.

Missä tiedonkeruu kentällä menee pieleen

Seuraavaksi luettelen ongelmia, joita laitteiden tietoja keräävissä järjestelmissä usein syntyy, tämän kokeen ja määrityksen perusteella.

Palvelimen pysähdyksen aikaiset arvot eivät saavu

Tilauksen aikana pysäytin palvelimen ja käynnistin sen uudelleen noin 15 sekunnin kuluttua. Tämän kirjaston ManagedSession yhdistää automaattisesti uudelleen, kun yhteys katkeaa20. Palvelimen ollessa pysähdyksissä asiakas yritti toistuvasti yhdistää uudelleen.

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.

Kun palvelin palasi, myös ilmoitukset palasivat. Tässä osa tilauksen tulosteesta.

  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]

Ajalta 13.41.13–13.41.34 ei saapunut yhtään arvoa. Tuotemäärä palasi arvosta 78 arvoon 2. Tämä johtuu siitä, että simuloitu palvelin aloitti laskemisen uudelleenkäynnistyksen jälkeen alusta nollasta.

Tässä esimerkissä itse palvelin oli pysähdyksissä, joten sen ajan arvoja ei ole tallessa missään. Uudelleenyhdistämisen mekanismi palauttaa yhteyden, mutta se ei täydennä jälkikäteen arvoja ajalta, jolloin palvelin oli poissa käytöstä.

Tilanne on eri, kun vain tietoliikenne katkeaa ja palvelin jatkaa toimintaansa. Määrityksessä on Republish-palvelu, jolla palvelin lähettää perille saapumattomat ilmoitusviestit uudelleen uudelleenlähetysjonostaan21. Ilmoituksia kertyy myös MonitoredItemin jonoon, mutta jonon koon ylittävä osa hylätään13. En kokeillut tätä tapausta tässä kokeessa. Kummassakin tapauksessa yhteyden katkeamisen ja uudelleenyhdistämisen ajat kannattaa tallentaa, jotta tiedetään, minä aikana arvoja ei saapunut. Sarjaliikennelaitteiden uudelleenyhdistämistä ja lokien suunnittelua käsitellään myös artikkelissa “Serial Communication App Pitfalls”.

Tallentaminen, kun palvelin pysähtyyKaavio, joka näyttää, että kun palvelin pysähtyy tilauksen aikana, asiakas yrittää toistuvasti yhdistää uudelleen ja ilmoitukset palaavat palvelimen palatessa, mutta pysähdyksen aikaiset arvot eivät saavu, joten katkeamisen ja uudelleenyhdistämisen ajat tallennetaan.Tilaus voimassaPalvelin pysähtyyAsiakas yrittää toistuvasti yhdistää uudelleenPalvelin palaa ja ilmoitukset jatkuvatPysähdyksen aikaiset arvot eivät saavuKatkeamisen ja uudelleenyhdistämisen ajat tallennetaan

Kuva 9: Uudelleenyhdistäminen palauttaa yhteyden, mutta palvelimen pysähdyksen aikaiset arvot eivät saavu.

Kumpi aikaleima tallennetaan

Määrityksen mukaan SourceTimestamp on aika, jonka tiedon lähde liitti arvoon, ja sen pitäisi ilmaista, milloin arvo muuttui. ServerTimestamp on aika, jolloin palvelin vastaanotti arvon tai sai tietää sen olevan oikea9. Tämän artikkelin simuloitu palvelin tuottaa arvot itse, joten molemmat ajat olivat samat.

Jos haluat tietää, milloin arvo muuttui laitteessa, käytä SourceTimestampia. Aika merkitään kuitenkin lähteen kellon mukaan, joten jos laitteen tai palvelimen kello on väärässä, myös tallenne on väärässä. Jos aiot verrata kerättyjä tietoja muihin tallenteisiin, tallenna niiden rinnalle myös vastaanottavan tietokoneen aika, jotta poikkeama voidaan tarkistaa myöhemmin. Windows-puolen ajan synkronointia käsitellään artikkelissa “Windows Time Synchronization (w32time) and Business Systems”.

Älä käsittele arvoja, joiden tila ei ole Good, pelkkinä lukuina

Kun tilakoodi on Bad, arvo on jätettävä huomiotta9. Kun se on Uncertain, arvo ei välttämättä kelpaa joihinkin käyttötarkoituksiin10. Ennen kuin arvot viedään kaavioihin tai koosteisiin, lajittele ne tilakoodin mukaan ja päätä, miten muut kuin Good-arvot käsitellään. Täyttyneen jonon Overflow-bitti asetetaan myös tilakoodiin13. Laitteen tilan näyttämistä näytöllä käsitellään artikkelissa “Best Practices for Checking and Displaying External Device State”.

Avaa portti palomuurissa

IANA:n rekisterissä oleva OPC UA:n portti on 4840/TCP3. Kaikki palvelimet eivät kuitenkaan käytä tätä numeroa. OPC Foundationin .NET-esimerkit käyttävät muun muassa porttia 625414. Tarkista porttinumero laitteen päätepisteen URL-osoitteesta ja salli yhteydet kyseiseen porttiin. Jos palvelin toimii Windowsissa, salli yhteys sen Windowsin palomuurissa, ja jos välissä on verkkolaitteita, salli se niissä. Saapuvan liikenteen sääntöjen luomista käsitellään artikkelissa “The Windows Firewall and Business Applications”.

Missä tiedonkeruu menee pieleenKaavio, joka näyttää kohdat, joissa laitetietojen keruu menee pieleen, jaettuna tallenteiden sisältöön (palvelimen pysähdyksen aikaiset arvot eivät saavu, kumpi aikaleima tallennetaan, muiden kuin Good-arvojen käsittely) ja yhteyden valmisteluun (palomuurin portit, varmenteiden vanheneminen).Missä tiedonkeruu menee pieleenTallenteiden sisältöYhteyden valmisteluPysähdyksen aikaiset arvot eivät saavuKumpi aikaleimaMuiden kuin Good-arvojen käsittelyPalomuurin portitVarmenteiden vanheneminen

Kuva 10: Vaikka yhteys toimisi, tarkista tallenteiden aukot, ajat, tilat, portit ja varmenteet.

OPC UA:n, Modbus/TCP:n ja valmistajakohtaisten protokollien valinta

PLC:iden ja laitteiden kanssa viestimiseen on OPC UA:n lisäksi Modbus/TCP sekä PLC-valmistajien tarjoamia protokollia.

Modbus-määritys käsittelee tietoja neljänä taulukkona: Discrete Input (vain luettava yksittäinen bitti), Coil (luettava ja kirjoitettava yksittäinen bitti), Input Register (vain luettava 16-bittinen sana) ja Holding Register (luettava ja kirjoitettava 16-bittinen sana)22. IANA:n rekisterissä 502/TCP on rekisteröity Modbusin sovellusprotokollaksi (mbap)3.

Näihin taulukoihin tallennetaan bittejä ja 16-bittisiä sanoja, joten se, mikä arvo on missäkin osoitteessa ja missä muodossa, on tarkistettava laitteen dokumentaatiosta ja kirjoitettava asiakkaan puolelle. OPC UA:ssa, kuten edellä nähtiin, palvelin julkaisee nimettyjä muuttujia osoiteavaruudessaan, ja asiakas voi löytää ne Browse-palvelulla. Arvojen mukana tulee tilakoodi ja aikaleimat, ja yhteydet sallitaan varmenteilla.

Käytettävä tapa määräytyy usein sen mukaan, mitä laite tarjoaa. Jos laitteessa on OPC UA -palvelin, käytä sitä. Jos siinä on vain Modbus/TCP, laadi osoitekartta ja lue sen mukaan. Jos tarjolla on vain valmistajan protokolla tai SDK, noudata sen dokumentaatiota. USB-laitteiden tiedonsiirtotavan valintaa käsitellään artikkelissa “How to Work with USB Devices from a Windows App”.

Laitteen kanssa viestimisen tavan valintaKaavio, joka näyttää, että jos laitteessa on OPC UA -palvelin, muuttujat selataan ja kerätään OPC UA:lla, jos siinä on vain Modbus/TCP, osoitteet ja muodot tarkistetaan dokumentaatiosta ja rekisterit luetaan, ja jos siinä on vain valmistajan protokolla, noudatetaan sen dokumentaatiota.OPC UA -palvelinVain Modbus/TCPVain valmistajan protokollaLaitteen tarjoama tiedonsiirtoMitä on saatavilla?Muuttujat selataan ja kerätäänOsoitteet ja muodot tarkistetaan dokumentaatiostaNoudatetaan valmistajan dokumentaatiota ja SDK:taRekisterit luetaan ja muunnetaan arvoiksi

Kuva 11: Keruutapa määräytyy usein sen mukaan, mitä laite tarjoaa.

Aiheeseen liittyviä artikkeleita

Lähteet

  1. OPC Foundation, Classic. OPC Classicin perustuminen COM/DCOMiin, DA-, A&E- ja HDA-määritykset, OPC UA:n julkaisu vuonna 2008 ja uudelleensuunnittelun syyt. ↩ ↩2

  2. IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. IEC 62541 -sarjan osa 1, joka käsittelee OPC UA:n käsitteitä ja yleiskuvaa (korvattu sittemmin uudemmalla painoksella). ↩

  3. IANA, Service Name and Transport Protocol Port Number Registry. opcua-tcp-rekisteröinti porttiin 4840/TCP ja mbap-rekisteröinti porttiin 502/TCP. ↩ ↩2 ↩3

  4. OPC Foundation, UA-.NETStandard 2.0.1: Getting started. 2.0-sarjan asiakkaan kirjoittaminen, ReadValueAsync heittää poikkeuksen Bad-tilassa, rejected- ja trusted-säilöt, palvelimet eivät oletuksena tarjoa None-käytäntöä ja esimerkeissä käytetyt porttinumerot. ↩ ↩2 ↩3 ↩4 ↩5

  5. FreeOpcUa, asyncua (PyPI). Simuloidussa laitepalvelimessa käytetty Pythonin OPC UA -kirjasto. ↩

  6. OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Palvelu, joka seuraa solmun viittauksia. ↩

  7. OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Nimiavaruuksien URI:t esitetään palveluissa numeroina, ja numero vastaa sijaintia NamespaceArray-taulukossa. ↩

  8. OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Read- ja Write-palvelut. ↩

  9. OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Arvon, tilakoodin, SourceTimestampin ja ServerTimestampin määritelmät. ↩ ↩2 ↩3

  10. OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Kahden ylimmän bitin ilmaisemat tilat Good, Uncertain ja Bad. ↩ ↩2

  11. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Näytteenottoväli on parhaan yrityksen jakso. ↩ ↩2

  12. OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Ilman suodatinta ilmoitus lähetetään arvon tai tilan muuttuessa. ↩

  13. OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Jonon koko, hylkäämiskäytäntö ja Overflow-bitti. ↩ ↩2 ↩3

  14. OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Julkaisuväli ja keep-alive-viestit. ↩

  15. OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set. SubscribeDataChangesAsync sekä MonitoredItemOptions-asetusten näytteenottoväli ja jonon koko. ↩

  16. OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Arvojen None, Sign ja SignAndEncrypt merkitykset. ↩

  17. OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Käytäntö siirtää varmenteet rejected-säilöstä trusted-säilöön sekä se, ettei AutoAcceptUntrustedCertificates-asetusta käytetä tuotannossa. ↩ ↩2

  18. OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Neljä lajia: anonyymi, käyttäjänimi, X.509 ja myönnetty tunnus. ↩

  19. OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Luettelo käyttäjätunnuksista, jotka päätepiste hyväksyy (userIdentityTokens). ↩

  20. OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. ManagedSession-luokassa on sisäänrakennettu uudelleenyhdistämisen mekanismi. ↩

  21. OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Palvelu, jolla tilauksen uudelleenlähetysjono lähettää ilmoitusviestit uudelleen. ↩

  22. Modbus-IDA, MODBUS Application Protocol Specification V1.1b (Chicagon yliopiston palvelimella oleva kopio). Luvun 4.3 tietomalli (Discrete Input, Coil, Input Register, Holding Register). ↩

Uusimmat artikkelit, joilla on samoja tunnisteita. Niiden avulla aihetta voi syventää läheisten teemojen kautta.

Tähän teemaan läheisesti liittyviä aihesivuja. Artikkelista voi siirtyä aiheeseen liittyviin palveluihin ja muihin artikkeleihin.

Tämä artikkeli liittyy seuraaviin palvelusivuihin. Voitte tutustua lähimpään niistä.

Windows-sovelluskehitys

Laitteista tietoja keräävän Windows-sovelluksen rakentaminen tilausasetuksineen, uudelleenyhdistämisineen sekä aikaleimojen ja tilojen tallennuksineen voidaan hoitaa Windows-sovelluskehityksenä.

Usein kysytyt kysymykset

Tämän artikkelin aiheesta konsultoinnin yhteydessä usein esitettyjä kysymyksiä.

Mitä eroa on OPC UA:lla ja OPC Classicilla (OPC DA)?
OPC Classic on joukko määrityksiä, jotka välittävät tietoja Windowsin COM/DCOM-tekniikalla. Vuonna 2008 julkaistu OPC UA ei ole sidottu tiettyyn käyttöjärjestelmään, ja se kokoaa Classicin toiminnot yhteen. OPC UA -asiakkaat toimivat myös muualla kuin Windowsissa, eikä DCOM-asetuksia tarvita.
Riittääkö, että arvot luetaan OPC UA:lla tasaisin väliajoin?
Jos arvoja on vähän ja väli on pitkä, lukeminen riittää. Jos haluat seurata arvojen muutoksia, voit käyttää tilausta: palvelin hoitaa näytteenoton ja ilmoittaa muutoksista. Kummassakin tapauksessa tallenna arvon mukana palaava tilakoodi ja aikaleimat.
Miksi yhdistäminen päättyy virheeseen BadCertificateUntrusted?
Koska asiakas ei vielä luota palvelimen varmenteeseen. OPC Foundationin .NET-kirjasto tallentaa hylätyt varmenteet rejected-säilöön. Tarkista varmenteen tunnistearvo (thumbprint) palvelimen ylläpitäjän kanssa ja siirrä varmenne sitten trusted-säilöön. Käytä varmenteet automaattisesti hyväksyviä asetuksia vain kehitysympäristöissä.
Mitä porttinumeroa OPC UA käyttää?
IANA:n rekisterissä 4840/TCP on rekisteröity nimellä opcua-tcp. Todelliset palvelimet käyttävät kuitenkin joskus muuta porttia, joten tarkista se laitteen asetuksista. Jos palvelin toimii Windowsissa, liikenne kyseiseen porttiin on sallittava Windowsin palomuurissa, ja jos välissä on verkkolaitteita, myös niissä.

Kirjoittajan profiili

Artikkelin kirjoittajan profiilisivu.

Go Komura

KomuraSoft LLC:n edustaja

Keskittyy Windows-ohjelmistokehitykseen, tekniseen konsultointiin ja vianetsintään. Erityisosaamista ovat hankkeet, joissa on olemassa olevia ratkaisuja, sekä vikojen tutkiminen silloin, kun syy ei ole helposti havaittavissa.

Takaisin blogin etusivulle