Histórico de revisões (apenas a primeira versão, publicada em 01/11/2026)
- Primeira publicação
Citar este artigo(DOI: 10.5281/zenodo.23968403)
Este artigo está arquivado no Zenodo. Abaixo estão tanto o DOI que sempre aponta para a versão mais recente quanto o DOI fixado na versão que você está lendo.
Go Komura (2026). “Introdução ao OPC UA ── Coletar dados de CLPs e equipamentos em um aplicativo Windows”. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968403 https://comcomponent.com/pt/blog/opc-ua-windows-data-collection/
- DOI (versão mais recente)
- 10.5281/zenodo.23968403
- DOI (esta versão)
- 10.5281/zenodo.23968404
Ao coletar dados de CLPs ou equipamentos em um aplicativo Windows, se o equipamento tiver um servidor OPC UA, às vezes é possível evitar programar uma comunicação diferente para cada fabricante. Mas não basta conectar e ler valores. Sem conhecer o status e os carimbos de data/hora que vêm com cada valor, as assinaturas que notificam as mudanças, a autorização da conexão por certificados e a forma de se recuperar de uma desconexão, depois não será possível confiar nos dados coletados.
Este artigo explica as partes do OPC UA necessárias para a coleta de dados, junto com os resultados de executar de fato um cliente em C#. Como cliente foi usada a biblioteca .NET da OPC Foundation e, do outro lado, um servidor que simula um equipamento. Não foi testado com nenhum CLP ou equipamento específico. Confira as configurações próprias de cada equipamento na documentação dele.
A coleta de imagens de câmeras GigE Vision e USB3 Vision está em “Dropped Frames with GigE Vision and USB3 Vision Cameras”, e os equipamentos com comunicação serial, em “Serial Communication App Pitfalls”.
Diferenças entre OPC UA e OPC Classic
As especificações do OPC Classic trocam dados entre componentes de software usando o COM/DCOM do Windows. O OPC DA, para dados de processo, o OPC A&E, para alarmes, e o OPC HDA, para histórico, são definidos como especificações separadas1.
A OPC Foundation publicou o OPC UA (Unified Architecture) em 2008. O OPC UA é uma arquitetura orientada a serviços que não depende de uma plataforma específica e reúne as funções de cada especificação do Classic. Como motivos da reformulação, a OPC Foundation cita que a Microsoft passou a valorizar mais os mecanismos multiplataforma do que o COM/DCOM, que era preciso rodar também em sistemas que não fossem da Microsoft e em dispositivos embarcados, e que era preciso conectar com segurança através de firewalls1.
A especificação do OPC UA também foi padronizada pela IEC como a série IEC 625412. Quanto à porta de comunicação, a IANA tem registrada a 4840/TCP como opcua-tcp3.
flowchart TB
accTitle: Diferenças entre OPC Classic e OPC UA
accDescr: Diagrama que mostra que o OPC Classic se baseia no COM e no DCOM do Windows e se divide nas especificações separadas DA, A&E e HDA, enquanto o OPC UA reúne essas funções em uma única especificação independente de plataforma.
C["OPC Classic"] --> D["Roda sobre COM/DCOM (Windows)"]
D --> DA["Especificações separadas DA, A&E e HDA"]
DA -->|"Reúne as funções"| U["OPC UA (2008)"]
U --> P["Não depende de uma plataforma específica"]
P --> S["Padronizado como IEC 62541"]
Figura 1: O OPC Classic é formado por especificações separadas sobre COM/DCOM, e o OPC UA as reúne em uma só.
Se uma instalação existente só tiver um servidor OPC DA, é preciso um mecanismo do lado que usa COM/DCOM. Este artigo trata apenas do OPC UA.
No diagrama, uma linha contínua indica uma relação que sempre se aplica e uma linha tracejada indica uma relação condicional (as condições aparecem em cada relação, na página de detalhes). A lista completa das relações (13 no total, com evidências e grau de certeza) e as definições dos principais conceitos estão reunidas na página de detalhes do mapa de conhecimento (em japonês). Dados: JSON-LD / Turtle
A configuração testada
Esta é a configuração executada.
| Papel | O que foi usado |
|---|---|
| Cliente | C#, .NET 10 SDK 10.0.114, pacote NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| Servidor | Um equipamento simulado feito com Python 3.13 e asyncua 2.1.0 (porta 4840 do mesmo PC) |
| Sistema operacional | Linux (a execução no Windows não foi testada) |
A biblioteca do cliente é a implementação .NET publicada pela OPC Foundation. Para o uso da série 2.0, foi seguido o que está em Getting started, no mesmo repositório4.
flowchart TB
accTitle: A configuração testada
accDescr: Diagrama que mostra um cliente em C# que usa a biblioteca .NET da OPC Foundation e se conecta, por uma conexão criptografada, a um servidor de equipamento simulado com asyncua que escuta na porta 4840.
A["Cliente em C# (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Servidor de equipamento simulado (asyncua)"]
A -.-> L["Biblioteca .NET da OPC Foundation 2.0.1"]
B -.-> V["Quatro variáveis de Line1"]
Figura 2: No mesmo PC, o cliente em C# se conectou ao servidor de equipamento simulado.
O servidor de equipamento simulado
No lugar de um equipamento real, foi criado um pequeno servidor com asyncua5. Sob um objeto chamado Line1 há quatro variáveis: a temperatura (Temperature), a quantidade produzida (PartCount), se está em operação (Running) e o setpoint (Setpoint). A temperatura muda a cada 0,5 segundo e a quantidade produzida a cada 2 segundos. Para a segurança, foram preparadas duas opções: sem criptografia (None) e com assinatura e criptografia (SignAndEncrypt de Basic256Sha256).
# Equipamento simulado para testes. Não substitui equipamentos nem CLPs de produção.
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())
Este servidor aceita o certificado dos clientes que se conectam sem verificá-lo. É uma configuração de teste e não se comporta como um equipamento de produção.
O cliente em C#
No arquivo de projeto foram incluídos estes dois pacotes.
<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>
O cliente conecta, navega, lê, escreve e assina, nessa ordem. O significado de cada parte é explicado nas seções seguintes.
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}");
// Quem define o índice do namespace é o servidor. Obtém-se o índice pela URI e com ele se criam os NodeIds
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);
// Navegação: lista as variáveis que estão sob 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}");
}
// Leitura: olha-se não só o valor, mas também o status e os dois carimbos de data/hora
DataValue t = await session.ReadValueAsync(temperature, ct);
Console.WriteLine($"Temperature = {t.WrappedValue} 状態 {t.StatusCode}");
Console.WriteLine($" Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");
// Ao ler um nó que não existe, volta um status Bad em vez de um valor
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// Escrita: define Setpoint como 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]}");
// Assinatura: pede-se ao servidor que faça a amostragem a cada 500 ms e notifique as mudanças
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");
Na primeira execução não foi possível conectar. O motivo é explicado na seção “Autorizar a conexão por certificados”. Esta é a saída depois de confiar no certificado do servidor. Os logs de aviso da biblioteca foram omitidos.
接続: 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]
Os horários estão em UTC.
Os dados do equipamento aparecem como nós do espaço de endereços
Um servidor OPC UA publica os dados que tem como um conjunto de nós. Esse conjunto é chamado de espaço de endereços. Há tipos de nós, como os objetos, que representam pastas ou equipamentos, e as variáveis, que têm valores, e os nós se ligam entre si por referências. O cliente percorre as referências com o serviço Browse para descobrir quais nós existem6.
Na saída acima, o cliente percorre as referências hierárquicas a partir de Line1 e encontra quatro variáveis.
flowchart TB
accTitle: Espaço de endereços do equipamento simulado
accDescr: Diagrama que mostra que sob a pasta Objects há um objeto Line1 e que sob ele há quatro variáveis, Temperature, PartCount, Running e Setpoint.
O["Objects"] --> L["Line1 (objeto)"]
L --> T["Temperature (variável)"]
L --> P["PartCount (variável)"]
L --> R["Running (variável)"]
L --> S["Setpoint (variável, gravável)"]
Figura 3: Espaço de endereços do equipamento simulado. O cliente encontra os nós percorrendo as referências com Browse.
Um NodeId é formado por um namespace e um identificador
Os nós são identificados por um NodeId. O ns=2;s=Line1.Temperature da saída indica que o índice do namespace é 2 e que o identificador é a string Line1.Temperature.
O que está por trás de um namespace é uma URI. A especificação determina que, dentro dos serviços, a URI do namespace é representada por um número, e que esse número corresponde a uma posição no NamespaceArray do servidor. O índice 0 é o namespace do OPC UA, http://opcfoundation.org/UA/7.
Por isso, em vez de escrever o número do índice no código, obtém-se o índice a partir da URI. No código acima, session.NamespaceUris.GetIndex(LineNamespace) obtém o 2 a partir da URI, e com ele são criados os NodeIds.
flowchart TB
accTitle: Como criar um NodeId a partir da URI do namespace
accDescr: Diagrama que mostra que o cliente procura a URI do namespace no NamespaceArray do servidor para obter o índice, e combina esse índice com o identificador para criar o NodeId.
U["URI do namespace"] --> A["NamespaceArray do servidor"]
A --> I["Índice (2 neste exemplo)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Identificador Line1.Temperature"] --> N
Figura 4: O índice do namespace é obtido pela URI e combinado com o identificador para formar o NodeId.
Na leitura e na escrita, o valor vem com um status e carimbos de data/hora
Os valores das variáveis são lidos e escritos com os serviços Read e Write8. A leitura não retorna só o valor. Ela retorna uma estrutura chamada DataValue, com o valor, o código de status, o carimbo de data/hora de origem e o carimbo de data/hora do servidor9.
| Item | Significado |
|---|---|
| Valor | O valor da variável. Quando o código de status indica erro, o valor é ignorado e o servidor o define como null |
| Código de status (StatusCode) | Se o servidor conseguiu obter e fornecer esse valor |
| SourceTimestamp | O horário que a origem dos dados atribuiu ao valor |
| ServerTimestamp | O horário em que o servidor recebeu o valor ou soube que ele estava correto |
Os dois bits superiores do código de status indicam Good (sucesso), Uncertain (sucesso parcial; conforme o uso, pode não servir) ou Bad (falha)10. O Good [0x00000000] da saída é Good.
Ao ler Line1.Pressure, que não existe, voltou BadNodeIdUnknown [0x80340000] em vez de um valor. O ReadValueAsync desta biblioteca lança uma exceção quando o status é Bad4. Em um sistema que registra valores, guardar o código de status ao lado do valor permite distinguir depois os valores registrados enquanto o status era Bad.
flowchart TB
accTitle: O conteúdo de um DataValue
accDescr: Diagrama que mostra que o DataValue retornado pelas leituras e notificações tem quatro itens, o valor, o código de status, o carimbo de data/hora de origem e o carimbo de data/hora do servidor, e que o valor é ignorado se o código de status for Bad.
D["DataValue"] --> V["Valor"]
D --> S["Código de status"]
D --> TS["SourceTimestamp (horário da origem)"]
D --> TV["ServerTimestamp (horário do servidor)"]
S -->|"Se for Bad"| X["O valor é ignorado"]
Figura 5: O resultado de uma leitura tem não só o valor, mas também um código de status e dois carimbos de data/hora.
Na escrita, foi gravado 32.5 em Setpoint, e o código de status do resultado foi Good. Se uma variável pode ser gravada ou não é o servidor que decide. Neste servidor simulado, apenas Setpoint é gravável. Permitir ou não que os setpoints de um equipamento sejam alterados de fora é uma questão do projeto de segurança do equipamento e não é tratada neste artigo.
Receber notificações das mudanças: assinaturas e MonitoredItem
Para acompanhar as mudanças de um valor, o cliente pode repetir Read em intervalos fixos. O OPC UA oferece também outro mecanismo, em que a vigilância do valor fica a cargo do servidor e ele notifica as mudanças. São as assinaturas (subscriptions) e os MonitoredItem.
Um MonitoredItem é um item vigiado e tem um intervalo de amostragem. O intervalo de amostragem é o intervalo mais rápido com que o servidor faz a amostragem do valor na origem, e a especificação o define como um período de “melhor esforço”11. Quando uma amostra atende à condição do filtro, uma notificação é gerada. Se nenhum filtro for especificado, a notificação ocorre quando o valor ou o status muda12.
As notificações entram primeiro na fila do MonitoredItem. Para quando a fila transbordar, define-se se serão descartadas as mais antigas ou as mais novas, e, se o tamanho da fila for 2 ou mais, o bit Overflow, que indica que houve descarte, é ativado no código de status13.
Uma assinatura agrupa vários MonitoredItem e, a cada intervalo de publicação, envia ao cliente as notificações ainda não enviadas. Se houver ciclos seguidos sem notificações para enviar, ela envia um keep-alive para indicar que a assinatura continua ativa14.
flowchart TB
accTitle: O fluxo das assinaturas e dos MonitoredItem
accDescr: Diagrama que mostra que um MonitoredItem faz a amostragem do valor da origem no intervalo de amostragem, coloca na fila as notificações que passam pelo filtro, a assinatura as envia agrupadas ao cliente a cada intervalo de publicação e, se não houver notificações, envia um keep-alive.
S["Origem (valor do equipamento)"] --> M["MonitoredItem"]
M -->|"Intervalo de amostragem"| F["Filtro (padrão: mudança de valor ou de status)"]
F --> Q["Fila (se transbordar, Overflow)"]
Q --> SUB["Assinatura"]
SUB -->|"Intervalo de publicação"| C["Cliente"]
SUB -.->|"Se não houver notificações"| K["Keep-alive"]
Figura 6: O MonitoredItem faz a amostragem e acumula na fila, e a assinatura envia tudo agrupado ao cliente.
No código acima, Temperature e PartCount foram assinados com intervalo de amostragem de 500 milissegundos e tamanho de fila 515. Na saída, a temperatura chega mais ou menos a cada 0,5 segundo e a quantidade produzida mais ou menos a cada 2 segundos. As duas primeiras notificações eram os valores que cada variável tinha no momento em que a assinatura começou. O 13:40:50.801 da quantidade produzida é o horário em que o valor mudou antes do início da assinatura. Como as notificações exibem o carimbo de data/hora de origem, aparece o horário em que o valor mudou, e não o horário em que a notificação foi recebida.
Quando repetir leituras e quando assinar
Repetir leituras permite ao cliente decidir o intervalo, e o mecanismo é simples. Em compensação, mesmo que o valor não tenha mudado, a cada vez vão e voltam uma requisição e uma resposta, e as mudanças mais curtas que o intervalo se perdem. Com a assinatura, o servidor faz a amostragem e envia só as mudanças, então a comunicação cresce pouco mesmo com valores que mudam pouco. Ainda assim, mesmo com a assinatura, as mudanças mais curtas que o intervalo de amostragem podem não ser notificadas. A especificação permite fazer a amostragem mais rápido do que o indicado, mas não exige isso11.
flowchart TB
accTitle: Diferenças entre repetir leituras e assinar
accDescr: Diagrama que mostra que, ao repetir leituras, o cliente decide o intervalo e faz uma requisição a cada vez, que, na assinatura, o servidor faz a amostragem no intervalo de amostragem e envia só as mudanças, e que nos dois casos as mudanças mais curtas que o intervalo podem se perder.
A["Acompanhar as mudanças do valor"] --> R["Repetir leituras"]
A --> S["Assinar"]
R --> R1["O cliente decide o intervalo e requisita a cada vez"]
S --> S1["O servidor faz a amostragem e envia só as mudanças"]
R1 --> X["Mudanças mais curtas que o intervalo podem se perder"]
S1 --> X
Figura 7: Com qualquer um dos métodos, as mudanças mais curtas que o intervalo podem se perder.
Autorizar a conexão por certificados
Em uma conexão OPC UA, o grau de proteção das mensagens é escolhido com o MessageSecurityMode. Os valores são None, sem proteção; Sign, apenas com assinatura digital; e SignAndEncrypt, com assinatura digital e criptografia16. A combinação de algoritmos usados na assinatura e na criptografia é definida pela política de segurança, e neste artigo foi usada a Basic256Sha256.
Para a assinatura e a criptografia, usa-se um certificado próprio de cada aplicativo. O cliente e o servidor verificam os certificados um do outro e recusam a conexão com quem não é confiável. Os servidores criados com a biblioteca .NET da OPC Foundation não oferecem, por padrão, a política None, sem criptografia4.
A primeira conexão parou em BadCertificateUntrusted
Na primeira execução, o cliente não confiava no certificado do servidor, e a conexão foi recusada. Este é um trecho da saída.
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.'
Esta biblioteca salva os certificados rejeitados no repositório rejected. Quando um administrador os move de lá para o repositório trusted, a biblioteca passa a confiar nesse interlocutor17. Neste teste, o certificado do servidor que estava em pki/rejected/certs foi movido para pki/trusted/certs e, na nova execução, a conexão funcionou. Antes de mover, confira com o administrador do servidor a impressão digital, que também aparece no nome do arquivo4.
flowchart TB
accTitle: O fluxo até confiar no certificado
accDescr: Diagrama que mostra que, na primeira conexão, o cliente recusa o certificado do servidor e o salva no repositório rejected, e que, depois de conferir a impressão digital e movê-lo para o repositório trusted, na conexão seguinte ele confia no certificado e consegue conectar.
A["Primeira conexão"] --> B["BadCertificateUntrusted"]
B --> C["O certificado é salvo no repositório rejected"]
C --> D["Conferir a impressão digital com o administrador do servidor"]
D --> E["Mover para o repositório trusted"]
E --> F["Na conexão seguinte, confia e conecta"]
Figura 8: Depois de conferir o certificado recusado e movê-lo para trusted, esse interlocutor passa a ser confiável.
Existe também a opção AutoAcceptUntrustedCertificates, que aceita automaticamente certificados não confiáveis. O padrão é false, e a documentação da biblioteca diz que ela é útil em ambientes de desenvolvimento, mas não deve ser usada em produção17.
O NotAfter=2027-11-01 do log é a data de validade do certificado do servidor simulado. É um certificado autoassinado criado com uma função do asyncua, e vencia um ano depois do dia em que foi criado. Os certificados dos equipamentos também vencem, então defina na operação o procedimento para substituí-los antes do vencimento e fazer o outro lado confiar no novo. O uso do repositório de certificados do Windows está resumido em “The Windows Certificate Store in Practice”.
A autenticação do usuário
O que os certificados verificam é a relação entre aplicativos. Além disso, ao abrir uma sessão, é entregue um token que identifica o usuário. A especificação prevê quatro tipos: anônimo, nome de usuário e senha, certificado X.509 e token emitido externamente18. O cliente deste teste se conectou de forma anônima. Quais tokens um servidor aceita está nas informações dos endpoints do servidor19.
Onde se tropeça na coleta de dados no chão de fábrica
A partir daqui, são listados os problemas que costumam aparecer em sistemas que coletam dados de equipamentos, com base neste experimento e na especificação.
Os valores do período em que o servidor ficou parado não chegam
No meio da assinatura, o servidor foi parado e reiniciado cerca de 15 segundos depois. O ManagedSession desta biblioteca se reconecta automaticamente quando a conexão cai20. Enquanto o servidor estava parado, o cliente ficou tentando se reconectar.
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.
Quando o servidor voltou, as notificações também voltaram. Este é um trecho da saída da assinatura.
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]
Não chegou nenhum valor entre 13:41:13 e 13:41:34. A quantidade produzida voltou de 78 para 2. Isso aconteceu porque, ao reiniciar o servidor simulado, a contagem recomeçou do 0.
Neste exemplo, o próprio servidor estava parado, então os valores desse período não ficaram guardados em lugar nenhum. O mecanismo de reconexão restabelece a conexão, mas não preenche depois os valores do período em que tudo ficou parado.
Se apenas a comunicação cair e o servidor continuar funcionando, a situação é outra. A especificação tem o serviço Republish, que pede ao servidor que reenvie, a partir da fila de retransmissão, as mensagens de notificação que não chegaram21. As notificações também se acumulam na fila do MonitoredItem, mas o que passar do tamanho da fila é descartado13. Neste experimento, esse caso não foi testado. Em ambos os casos, registre os horários de desconexão e de reconexão, para que se saiba em que período os valores não chegaram. A reconexão e o projeto de logs em equipamentos com comunicação serial também são tratados em “Serial Communication App Pitfalls”.
flowchart TB
accTitle: O registro quando o servidor para
accDescr: Diagrama que mostra que, se o servidor para durante a assinatura, o cliente repete a reconexão e, quando o servidor volta, as notificações também voltam, mas os valores do período parado não chegam, por isso se registram os horários de desconexão e reconexão.
A["Durante a assinatura"] --> B["O servidor para"]
B --> C["O cliente repete a reconexão"]
C --> D["O servidor volta e as notificações também"]
B -.-> E["Os valores do período parado não chegam"]
E -.-> F["Registrar os horários de desconexão e reconexão"]
Figura 9: A reconexão restabelece a conexão, mas os valores do período parado não chegam.
Qual carimbo de data/hora registrar
Pela especificação, o SourceTimestamp é o horário que a origem dos dados atribuiu ao valor e deve indicar o horário em que o valor mudou. O ServerTimestamp é o horário em que o servidor recebeu o valor ou soube que ele estava correto9. O servidor simulado deste teste gera os próprios valores, então os dois horários eram iguais.
Para saber o horário em que o valor mudou no equipamento, usa-se o SourceTimestamp. Mas esse horário é atribuído pelo relógio da origem, então, se o relógio do equipamento ou do servidor estiver adiantado ou atrasado, o registro também estará. Se os dados coletados forem cruzados com outros registros, guardar também o horário do PC que os recebeu permite verificar a diferença depois. A sincronização de horário no Windows está resumida em “Windows Time Synchronization (w32time) and Business Systems”.
Não tratar como números, sem mais, os valores cujo status não é Good
Quando o código de status é Bad, o valor deve ser ignorado9. Quando é Uncertain, conforme o uso, o valor pode não servir10. Antes de colocar os valores em gráficos ou totais, separe-os pelo código de status e defina como tratar os que não forem Good. O bit Overflow de quando a fila transborda também é ativado dentro do código de status13. Como exibir na tela o estado dos equipamentos é tratado em “Best Practices for Checking and Displaying External Device State”.
Liberar a porta no firewall
A porta do OPC UA registrada na IANA é a 4840/TCP3. Porém, nem todos os servidores usam esse número. Os exemplos .NET da OPC Foundation usam portas como a 625414. Confira o número da porta na URL do endpoint do equipamento e libere as conexões para essa porta. Se o servidor roda no Windows, libere no Firewall do Windows dessa máquina e, se houver equipamentos de rede no caminho, nesses equipamentos. A criação das regras de entrada está resumida em “The Windows Firewall and Business Applications”.
flowchart TB
accTitle: Onde se tropeça na coleta de dados
accDescr: Diagrama que divide os pontos onde se tropeça na coleta de dados de equipamentos entre o conteúdo do registro (os valores do período em que o servidor ficou parado não chegam, qual carimbo de data/hora registrar, como tratar os valores que não são Good) e a preparação da conexão (a porta do firewall, a validade do certificado).
A["Onde se tropeça na coleta de dados"] --> R["Conteúdo do registro"]
A --> N["Preparação da conexão"]
R --> B["Os valores do período parado não chegam"]
B ~~~ C["Qual carimbo de data/hora"]
C ~~~ D["Valores que não são Good"]
N --> E["A porta do firewall"]
E ~~~ F["A validade do certificado"]
Figura 10: Mesmo depois de conectar, confira as lacunas do registro, os horários, o status, a porta e o certificado.
Quando usar Modbus/TCP ou a comunicação própria do fabricante
Para se comunicar com CLPs e equipamentos, além do OPC UA existem o Modbus/TCP e as comunicações oferecidas pelo fabricante do CLP.
A especificação do Modbus trata os dados em quatro tabelas: Discrete Input, de 1 bit e somente leitura; Coil, de 1 bit e leitura e escrita; Input Register, de 16 bits e somente leitura; e Holding Register, de 16 bits e leitura e escrita22. Na IANA, a 502/TCP está registrada como o protocolo de aplicação do Modbus (mbap)3.
O que cabe nessas tabelas são bits e palavras de 16 bits, então qual valor está em qual endereço e em qual formato é conferido na documentação do equipamento e escrito no lado do cliente. No OPC UA, como vimos, o servidor publica variáveis com nome no espaço de endereços, e o cliente pode descobri-las com Browse. Os valores vêm com código de status e carimbos de data/hora, e a conexão é autorizada por certificados.
O que usar costuma ser decidido pelo que o equipamento oferece. Se o equipamento tiver um servidor OPC UA, use-o; se tiver só Modbus/TCP, crie uma tabela de correspondência de endereços e leia. Se houver só uma comunicação ou um SDK do fabricante, siga a documentação dele. Como escolher o método de comunicação com dispositivos USB está resumido em “How to Work with USB Devices from a Windows App”.
flowchart TB
accTitle: Como escolher a comunicação com o equipamento
accDescr: Diagrama que mostra que, se o equipamento tem um servidor OPC UA, coleta-se navegando pelas variáveis com OPC UA; se tem só Modbus/TCP, conferem-se na documentação os endereços e os formatos e leem-se os registradores; e se tem só a comunicação do fabricante, segue-se a documentação dele.
A["Comunicação que o equipamento oferece"] --> B{"O que existe?"}
B -->|"Servidor OPC UA"| C["Navegar pelas variáveis e coletar"]
B -->|"Só Modbus/TCP"| D["Conferir endereços e formatos na documentação"]
B -->|"Só a comunicação do fabricante"| E["Seguir a documentação e o SDK do fabricante"]
D --> F["Ler os registradores e converter em valores"]
Figura 11: Com o que coletar costuma ser decidido pelo que o equipamento oferece.
Artigos relacionados
- 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
Referências
-
OPC Foundation, Classic. Que o OPC Classic se baseia em COM/DCOM, as especificações DA, A&E e HDA, a publicação do OPC UA em 2008 e os motivos da reformulação. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Primeira parte da série IEC 62541, sobre os conceitos e a visão geral do OPC UA (substituída por uma edição posterior). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Os registros de
opcua-tcpna 4840/TCP e dembapna 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Como escrever um cliente da série 2.0, que
ReadValueAsynclança exceção com Bad, os repositórios de rejeitados e de confiáveis, que o servidor não ofereceNonepor padrão e os números de porta dos exemplos. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). A biblioteca OPC UA para Python usada no servidor de equipamento simulado. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. O serviço que percorre as referências dos nós. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Que, dentro dos serviços, a URI do namespace é representada por um número e que esse número corresponde a uma posição no NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Os serviços Read e Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. As definições do valor, do código de status, do SourceTimestamp e do ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain e Bad, indicados pelos dois bits superiores. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Que o intervalo de amostragem é um período de melhor esforço. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Que, sem filtro, a notificação ocorre quando o valor ou o status muda. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. O tamanho da fila, a forma de descarte e o bit Overflow. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. O intervalo de publicação e o keep-alive. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsynce o intervalo de amostragem e o tamanho de fila deMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. O significado de None, Sign e SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. A prática de mover os certificados do repositório rejected para o repositório de confiáveis, e que
AutoAcceptUntrustedCertificatesnão deve ser usada em produção. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Os quatro tipos: anônimo, nome de usuário, X.509 e token emitido. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. A lista de tokens de usuário que um endpoint aceita (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Que o
ManagedSessiontem o mecanismo de reconexão embutido. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. O serviço que pede o reenvio das mensagens de notificação a partir da fila de retransmissão da assinatura. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (cópia hospedada em um servidor da Universidade de Chicago). O modelo de dados da seção 4.3 (Discrete Input, Coil, Input Register e Holding Register). ↩
Artigos relacionados
Artigos recentes que compartilham as mesmas tags. Aprofunde seu conhecimento com temas próximos.
O que para quando você ativa a "conexão limitada"? ── Os efeitos no Windows Update, no OneDrive e nos aplicativos da Store
Em uma conexão limitada, o Windows Update baixa só as atualizações prioritárias e o OneDrive pausa a sincronização. Efeitos nos aplicativ...
Posso excluir o Windows.old e os “Arquivos temporários”? ── Quando usar o Sensor de Armazenamento e quando usar a Limpeza de Disco
Para saber se o Windows.old ou os arquivos temporários podem ser excluídos, veja se algo deixará de ser recuperável. Os 10 dias para volt...
O que muda com o modo de energia do Windows? ── Diferenças entre Melhor desempenho, Equilibrado e Melhor eficiência energética, e como escolher
O que muda com os modos de energia do Windows 11 “Melhor desempenho”, “Equilibrado” e “Melhor eficiência energética”: as configurações da...
O que é o Modo de Jogo do Windows? ── O que muda ao ativá-lo e se ele afeta algo além dos jogos
O Modo de Jogo do Windows impede que o Windows Update instale drivers e mostre notificações de reinicialização enquanto um jogo está em e...
O que são a “Memória de GPU dedicada” e a “Memória de GPU compartilhada” do Gerenciador de Tarefas? ── Por que os números diferem tanto em uma GPU integrada e o que acontece quando falta memória
O que significam a memória de GPU dedicada e a compartilhada do Gerenciador de Tarefas, por que uma GPU integrada mostra pouca dedicada e...
Tópicos relacionados
Páginas de tópicos próximos a este tema. A partir do artigo, você pode seguir para serviços relacionados e outros artigos.
Tópicos técnicos de Windows
Página de entrada que reúne os tópicos técnicos sobre desenvolvimento para Windows, investigação de falhas e reaproveitamento de ativos existentes.
Serviços ligados a este tema
Este artigo se conecta às páginas de serviço abaixo. Escolha a porta de entrada mais próxima.
Desenvolvimento de aplicativos Windows
Se você quer criar um aplicativo Windows que colete dados dos equipamentos, incluindo a configuração das assinaturas, a reconexão e o registro dos carimbos de data/hora e do status, podemos tratar isso como desenvolvimento de aplicativos Windows.
Consultoria técnica e revisão de projeto
Se você quer decidir entre coletar com OPC UA ou usar Modbus/TCP ou a comunicação própria do fabricante, e como gerenciar os certificados, podemos tratar isso como consultoria técnica e revisão de projeto.
Perguntas frequentes
Reunimos as perguntas mais comuns, em consultas, sobre o tema deste artigo.
- Qual é a diferença entre OPC UA e OPC Classic (OPC DA)?
- O OPC Classic é um conjunto de especificações que trocam dados por meio do COM/DCOM do Windows. O OPC UA é uma especificação publicada em 2008 que não depende de um sistema operacional específico e reúne as funções do Classic. Os clientes OPC UA funcionam também fora do Windows e não precisam de configuração de DCOM.
- Para coletar valores com OPC UA, basta lê-los em intervalos fixos?
- Se forem poucos valores e o intervalo for longo, dá para coletar lendo. Para acompanhar as mudanças dos valores, pode-se usar uma assinatura, que deixa a amostragem a cargo do servidor e faz com que ele notifique as mudanças. Em ambos os casos, registre o código de status e os carimbos de data/hora que vêm junto com o valor.
- Por que aparece BadCertificateUntrusted ao conectar?
- Porque o cliente ainda não confia no certificado do servidor. A biblioteca .NET da OPC Foundation salva os certificados rejeitados no repositório rejected. Confira a impressão digital do certificado com o administrador do servidor e depois mova-o para o repositório trusted. Limite aos ambientes de desenvolvimento a opção de aceitar certificados automaticamente.
- Qual é o número da porta do OPC UA?
- A IANA tem registrada a porta 4840/TCP como opcua-tcp. Porém, os servidores reais podem usar outra porta, então confira nas configurações do equipamento. Se o servidor roda no Windows, é preciso liberar o tráfego para essa porta no Firewall do Windows e, se houver equipamentos de rede no caminho, também nesses equipamentos.
Perfil do autor
Página de perfil do autor do artigo.
Go Komura
Representante da KomuraSoft LLC
Atua principalmente com desenvolvimento de software para Windows, consultoria técnica e investigação de falhas, com especial força em projetos em que ativos existentes permanecem e em investigações de falhas cuja causa é difícil de enxergar.