Lịch sử sửa đổi (bản đầu tiên, đăng ngày 1 Nov 2026)
- Đăng lần đầu
Trích dẫn bài viết này(DOI: 10.5281/zenodo.23968415)
Bài viết này được lưu trữ trên Zenodo. Bên dưới là DOI luôn dẫn tới phiên bản mới nhất, và DOI gắn với phiên bản bạn đang đọc.
Go Komura (2026). Nhập môn OPC UA ── Thu thập dữ liệu PLC và thiết bị bằng ứng dụng Windows. KomuraSoft LLC. https://doi.org/10.5281/zenodo.23968415 https://comcomponent.com/vi/blog/opc-ua-windows-data-collection/
- DOI (phiên bản mới nhất)
- 10.5281/zenodo.23968415
- DOI (phiên bản này)
- 10.5281/zenodo.23968416
Khi thu thập dữ liệu PLC hay thiết bị vào ứng dụng Windows, nếu thiết bị có máy chủ OPC UA thì đôi khi bạn không phải tự viết phần giao tiếp riêng cho từng nhà sản xuất. Tuy vậy, chỉ kết nối và đọc giá trị là chưa đủ. Nếu không nắm được trạng thái và dấu thời gian trả về cùng giá trị, cơ chế đăng ký nhận thông báo khi có thay đổi, việc cho phép kết nối bằng chứng chỉ, và cách phục hồi sau khi mất kết nối, thì về sau bạn sẽ không thể tin cậy dữ liệu đã thu thập.
Bài viết này giải thích những phần của OPC UA cần cho việc thu thập dữ liệu, kèm theo kết quả chạy thực tế bằng một máy khách C#. Máy khách dùng thư viện .NET của OPC Foundation, còn phía bên kia là một máy chủ giả lập thiết bị. Tôi chưa thử với PLC hay thiết bị cụ thể nào. Hãy kiểm tra cấu hình riêng của từng thiết bị trong tài liệu của thiết bị đó.
Việc thu thập ảnh từ camera GigE Vision và USB3 Vision được trình bày trong “Dropped Frames with GigE Vision and USB3 Vision Cameras”, còn thiết bị dùng truyền thông nối tiếp được trình bày trong “Serial Communication App Pitfalls”.
Khác biệt giữa OPC UA và OPC Classic
Các đặc tả OPC Classic dùng COM/DCOM của Windows để trao đổi dữ liệu giữa các thành phần phần mềm. OPC DA cho dữ liệu quy trình, OPC A&E cho cảnh báo và OPC HDA cho dữ liệu lịch sử được định nghĩa thành các đặc tả riêng biệt1.
Năm 2008, OPC Foundation công bố OPC UA (Unified Architecture). OPC UA là kiến trúc hướng dịch vụ không phụ thuộc vào một nền tảng cụ thể, gộp chức năng của từng đặc tả Classic lại. OPC Foundation nêu lý do thiết kế lại gồm: Microsoft chuyển sang coi trọng cơ chế đa nền tảng hơn COM/DCOM, cần chạy được trên hệ thống không phải của Microsoft và trên thiết bị nhúng, và cần kết nối an toàn xuyên qua tường lửa1.
Đặc tả OPC UA cũng đã được IEC chuẩn hóa thành bộ tiêu chuẩn IEC 625412. Về cổng truyền thông, IANA đã đăng ký 4840/TCP với tên opcua-tcp3.
flowchart TB
accTitle: Khác biệt giữa OPC Classic và OPC UA
accDescr: Sơ đồ cho thấy OPC Classic dựa trên COM và DCOM của Windows và chia thành các đặc tả DA, A&E, HDA riêng biệt, còn OPC UA gộp các chức năng đó vào một đặc tả duy nhất không phụ thuộc nền tảng.
C["OPC Classic"] --> D["Chạy trên COM/DCOM (Windows)"]
D --> DA["Đặc tả chia thành DA, A&E, HDA"]
DA -->|"Gộp chức năng lại"| U["OPC UA (2008)"]
U --> P["Không phụ thuộc nền tảng cụ thể"]
P --> S["Chuẩn hóa thành IEC 62541"]
Hình 1: OPC Classic gồm các đặc tả riêng biệt trên COM/DCOM, còn OPC UA gộp chúng lại thành một.
Nếu thiết bị hiện có chỉ có máy chủ OPC DA, bạn cần một cơ chế dùng COM/DCOM ở phía mình. Bài viết này chỉ đề cập đến OPC UA.
Trong sơ đồ, đường liền nét biểu thị quan hệ luôn đúng và đường nét đứt biểu thị quan hệ có điều kiện (điều kiện nằm trong phần giải thích từng quan hệ trên trang chi tiết). Danh sách đầy đủ các quan hệ (tổng 13, kèm bằng chứng và mức chắc chắn) cùng định nghĩa các khái niệm chính được tập hợp tại trang chi tiết bản đồ tri thức (bằng tiếng Nhật). Dữ liệu: JSON-LD / Turtle
Cấu hình đã thử
Tôi đã chạy với cấu hình sau.
| Vai trò | Thành phần đã dùng |
|---|---|
| Máy khách | C#, .NET 10 SDK 10.0.114, gói NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| Máy chủ | Thiết bị giả lập viết bằng Python 3.13 và asyncua 2.1.0 (cổng 4840 trên cùng một PC) |
| Hệ điều hành | Linux (chưa thử chạy trên Windows) |
Thư viện phía máy khách là bản triển khai .NET do OPC Foundation công bố. Về cách dùng dòng 2.0, tôi làm theo cách viết trong Getting started của cùng kho mã4.
flowchart TB
accTitle: Cấu hình đã thử
accDescr: Sơ đồ cho thấy một máy khách C# dùng thư viện .NET của OPC Foundation, kết nối bằng kết nối mã hóa tới máy chủ thiết bị giả lập asyncua đang lắng nghe ở cổng 4840.
A["Máy khách C# (LineCollector)"] -->|"opc.tcp, SignAndEncrypt"| B["Máy chủ thiết bị giả lập (asyncua)"]
A -.-> L["Thư viện .NET của OPC Foundation 2.0.1"]
B -.-> V["Bốn biến dưới Line1"]
Hình 2: Trong cùng một PC, máy khách C# kết nối tới máy chủ thiết bị giả lập.
Máy chủ thiết bị giả lập
Thay cho thiết bị thật, tôi viết một máy chủ nhỏ bằng asyncua5. Dưới đối tượng tên Line1 có bốn biến: nhiệt độ (Temperature), số sản phẩm (PartCount), đang chạy (Running) và giá trị đặt (Setpoint). Nhiệt độ thay đổi mỗi 0,5 giây, số sản phẩm thay đổi mỗi 2 giây. Về bảo mật, tôi chuẩn bị hai lựa chọn: không mã hóa (None) và ký kèm mã hóa (SignAndEncrypt của Basic256Sha256).
# Thiết bị giả lập để kiểm tra hoạt động. Không thay thế cho thiết bị hay PLC thật.
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())
Máy chủ này chấp nhận chứng chỉ của máy khách kết nối đến mà không kiểm tra. Đây là thiết lập dùng cho thử nghiệm và khác với cách hoạt động của thiết bị thật.
Máy khách C#
Tôi thêm hai gói sau vào tệp dự án.
<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>
Máy khách lần lượt kết nối, duyệt (browse), đọc, ghi và đăng ký theo dõi. Ý nghĩa của từng phần được giải thích trong các mục tiếp theo.
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}");
// Chỉ số không gian tên do máy chủ quyết định. Tra từ URI rồi tạo 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);
// Duyệt: liệt kê các biến nằm dưới 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}");
}
// Đọc: xem cả trạng thái và hai dấu thời gian, không chỉ giá trị
DataValue t = await session.ReadValueAsync(temperature, ct);
Console.WriteLine($"Temperature = {t.WrappedValue} 状態 {t.StatusCode}");
Console.WriteLine($" Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");
// Đọc một nút không tồn tại thì nhận về trạng thái Bad thay vì giá trị
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// Ghi: đặt Setpoint thành 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]}");
// Đăng ký theo dõi: nhờ máy chủ lấy mẫu mỗi 500ms và thông báo khi có thay đổi
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");
Lần chạy đầu tiên không kết nối được. Lý do được giải thích trong mục “Cho phép kết nối bằng chứng chỉ”. Dưới đây là kết quả sau khi đã tin cậy chứng chỉ của máy chủ. Các nhật ký cảnh báo của thư viện đã được lược bỏ.
接続: 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]
Thời gian là giờ UTC.
Dữ liệu thiết bị hiện ra dưới dạng các nút trong không gian địa chỉ
Máy chủ OPC UA công bố dữ liệu nó có dưới dạng một tập hợp các nút. Tập hợp này được gọi là không gian địa chỉ (address space). Nút có nhiều loại, như đối tượng biểu diễn thư mục hay thiết bị, và biến chứa giá trị; các nút liên kết với nhau bằng tham chiếu (reference). Máy khách lần theo tham chiếu bằng dịch vụ Browse để tìm hiểu có những nút nào6.
Trong kết quả ở trên, máy khách lần theo các tham chiếu phân cấp từ Line1 và tìm thấy bốn biến.
flowchart TB
accTitle: Không gian địa chỉ của thiết bị giả lập
accDescr: Sơ đồ cho thấy đối tượng Line1 nằm dưới thư mục Objects, và dưới nó có bốn biến Temperature, PartCount, Running và Setpoint.
O["Objects"] --> L["Line1 (đối tượng)"]
L --> T["Temperature (biến)"]
L --> P["PartCount (biến)"]
L --> R["Running (biến)"]
L --> S["Setpoint (biến, ghi được)"]
Hình 3: Không gian địa chỉ của thiết bị giả lập. Máy khách lần theo tham chiếu bằng Browse để tìm nút.
NodeId gồm không gian tên và định danh
Nút được nhận diện bằng NodeId. Chuỗi ns=2;s=Line1.Temperature trong kết quả có nghĩa là chỉ số không gian tên là 2 và định danh là chuỗi Line1.Temperature.
Bản chất của không gian tên là một URI. Đặc tả quy định rằng trong các dịch vụ, URI không gian tên được biểu diễn bằng một số, và số đó ứng với vị trí trong NamespaceArray của máy chủ. Chỉ số 0 là không gian tên của OPC UA, http://opcfoundation.org/UA/7.
Vì vậy, thay vì ghi cứng con số chỉ số vào mã, hãy tra chỉ số từ URI. Đoạn mã ở trên dùng session.NamespaceUris.GetIndex(LineNamespace) để lấy số 2 từ URI rồi tạo NodeId từ đó.
flowchart TB
accTitle: Quy trình tạo NodeId từ URI không gian tên
accDescr: Sơ đồ cho thấy máy khách tra URI không gian tên trong NamespaceArray của máy chủ để lấy chỉ số, rồi kết hợp chỉ số đó với định danh để tạo NodeId.
U["URI không gian tên"] --> A["NamespaceArray của máy chủ"]
A --> I["Chỉ số (trong ví dụ này là 2)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["Định danh Line1.Temperature"] --> N
Hình 4: Tra chỉ số không gian tên từ URI, rồi kết hợp với định danh để có NodeId.
Khi đọc và ghi, trạng thái và dấu thời gian được trả về cùng giá trị
Giá trị của biến được đọc và ghi bằng các dịch vụ Read và Write8. Kết quả đọc không chỉ có giá trị. Nó trả về một cấu trúc gọi là DataValue, gồm giá trị, mã trạng thái, dấu thời gian nguồn và dấu thời gian máy chủ9.
| Mục | Ý nghĩa |
|---|---|
| Giá trị | Giá trị của biến. Khi mã trạng thái báo lỗi thì bỏ qua giá trị, và máy chủ đặt nó thành null |
| Mã trạng thái (StatusCode) | Máy chủ có lấy và cung cấp được giá trị đó hay không |
| SourceTimestamp | Thời điểm mà nguồn phát sinh dữ liệu gắn cho giá trị |
| ServerTimestamp | Thời điểm máy chủ nhận giá trị, hoặc biết rằng giá trị là đúng |
Hai bit cao nhất của mã trạng thái biểu thị Good (thành công), Uncertain (thành công một phần, có thể không dùng được cho một số mục đích) hoặc Bad (thất bại)10. Good [0x00000000] trong kết quả là Good.
Khi đọc Line1.Pressure, một nút không tồn tại, kết quả trả về là BadNodeIdUnknown [0x80340000] thay vì giá trị. ReadValueAsync của thư viện này ném ngoại lệ khi trạng thái là Bad4. Trong hệ thống ghi lại giá trị, nếu lưu mã trạng thái song song với giá trị, về sau bạn có thể phân biệt được những giá trị đã ghi trong lúc trạng thái là Bad.
flowchart TB
accTitle: Nội dung của DataValue
accDescr: Sơ đồ cho thấy DataValue trả về khi đọc hoặc khi nhận thông báo có bốn thành phần là giá trị, mã trạng thái, dấu thời gian nguồn và dấu thời gian máy chủ, và nếu mã trạng thái là Bad thì bỏ qua giá trị.
D["DataValue"] --> V["Giá trị"]
D --> S["Mã trạng thái"]
D --> TS["SourceTimestamp (thời điểm ở nguồn)"]
D --> TV["ServerTimestamp (thời điểm ở máy chủ)"]
S -->|"Nếu là Bad"| X["Bỏ qua giá trị"]
Hình 5: Kết quả đọc không chỉ có giá trị mà còn có mã trạng thái và hai dấu thời gian.
Khi ghi, tôi ghi 32,5 vào Setpoint và nhận về mã trạng thái Good. Biến nào ghi được là do máy chủ quyết định. Trong máy chủ giả lập này, chỉ Setpoint được phép ghi. Có cho phép ghi đè giá trị đặt của thiết bị từ bên ngoài hay không là vấn đề thuộc thiết kế an toàn của thiết bị, nên bài viết này không đề cập.
Nhận thông báo khi có thay đổi: subscription và MonitoredItem
Để theo dõi sự thay đổi của giá trị, máy khách có thể lặp lại Read theo chu kỳ cố định. Ngoài cách đó, OPC UA còn có cơ chế giao việc giám sát giá trị cho máy chủ và nhận thông báo khi có thay đổi. Đó là subscription và MonitoredItem.
MonitoredItem là một đối tượng được giám sát và có một khoảng lấy mẫu (sampling interval). Khoảng lấy mẫu là tốc độ nhanh nhất mà máy chủ lấy mẫu giá trị từ nguồn, và đặc tả coi nó là chu kỳ “nỗ lực tối đa” (best effort)11. Khi một mẫu thỏa điều kiện của bộ lọc thì thông báo được tạo ra. Nếu không chỉ định bộ lọc, thông báo được gửi khi giá trị hoặc trạng thái thay đổi12.
Thông báo trước hết được đưa vào hàng đợi của MonitoredItem. Bạn quyết định trước sẽ bỏ thông báo cũ hay mới khi hàng đợi đầy, và nếu kích thước hàng đợi từ 2 trở lên thì bit Overflow cho biết đã có thông báo bị bỏ sẽ được bật trong mã trạng thái13.
Subscription gom nhiều MonitoredItem lại, và sau mỗi khoảng phát hành (publishing interval) sẽ gửi cho máy khách những thông báo chưa gửi. Khi nhiều chu kỳ liên tiếp không có thông báo nào để gửi, nó gửi keep-alive để báo rằng subscription vẫn còn hoạt động14.
flowchart TB
accTitle: Luồng hoạt động của subscription và MonitoredItem
accDescr: Sơ đồ cho thấy MonitoredItem lấy mẫu giá trị của nguồn theo khoảng lấy mẫu, đưa các thông báo đã qua bộ lọc vào hàng đợi, rồi subscription gom lại gửi cho máy khách theo khoảng phát hành, và nếu không có thông báo thì gửi keep-alive.
S["Nguồn (giá trị của thiết bị)"] --> M["MonitoredItem"]
M -->|"Khoảng lấy mẫu"| F["Bộ lọc (mặc định là thay đổi giá trị hoặc trạng thái)"]
F --> Q["Hàng đợi (đầy thì Overflow)"]
Q --> SUB["Subscription"]
SUB -->|"Khoảng phát hành"| C["Máy khách"]
SUB -.->|"Nếu không có thông báo"| K["Keep-alive"]
Hình 6: MonitoredItem lấy mẫu và đưa vào hàng đợi, rồi subscription gom lại gửi cho máy khách.
Trong đoạn mã ở trên, tôi đăng ký theo dõi Temperature và PartCount với khoảng lấy mẫu 500 mili giây và kích thước hàng đợi 515. Trong kết quả, nhiệt độ đến khoảng mỗi 0,5 giây, còn số sản phẩm đến khoảng mỗi 2 giây. Hai thông báo đầu tiên là giá trị mà mỗi biến đang có lúc bắt đầu đăng ký. Thời điểm 13:40:50.801 của số sản phẩm là lúc giá trị thay đổi, trước khi bắt đầu đăng ký. Vì thời gian của thông báo hiển thị dấu thời gian nguồn, nên nó cho thấy lúc giá trị thay đổi chứ không phải lúc nhận được thông báo.
Chọn giữa đọc lặp lại và đăng ký theo dõi
Cách đọc lặp lại cho phép máy khách tự quyết định chu kỳ, và cơ chế đơn giản. Đổi lại, mỗi lần đều có yêu cầu và phản hồi đi lại dù giá trị không đổi, và những thay đổi ngắn hơn chu kỳ sẽ bị bỏ lỡ. Với đăng ký theo dõi, máy chủ lấy mẫu và chỉ gửi những thay đổi, nên lưu lượng khó tăng lên ngay cả với giá trị ít thay đổi. Tuy nhiên, ngay cả khi đăng ký theo dõi, những thay đổi ngắn hơn khoảng lấy mẫu có thể không được thông báo. Đặc tả cho phép lấy mẫu nhanh hơn mức chỉ định nhưng không bắt buộc11.
flowchart TB
accTitle: Khác biệt giữa đọc lặp lại và đăng ký theo dõi
accDescr: Sơ đồ cho thấy cách đọc lặp lại do máy khách quyết định chu kỳ và gửi yêu cầu mỗi lần, cách đăng ký theo dõi do máy chủ lấy mẫu theo khoảng lấy mẫu và chỉ gửi thay đổi, và cả hai đều có thể không bắt được thay đổi ngắn hơn chu kỳ.
A["Theo dõi thay đổi của giá trị"] --> R["Đọc lặp lại"]
A --> S["Đăng ký theo dõi"]
R --> R1["Máy khách quyết định chu kỳ, gửi yêu cầu mỗi lần"]
S --> S1["Máy chủ lấy mẫu, chỉ gửi thay đổi"]
R1 --> X["Có thể không bắt được thay đổi ngắn hơn chu kỳ"]
S1 --> X
Hình 7: Với cả hai cách, những thay đổi ngắn hơn chu kỳ đều có thể không bắt được.
Cho phép kết nối bằng chứng chỉ
Trong kết nối OPC UA, mức độ bảo vệ thông điệp được chọn bằng MessageSecurityMode. Các giá trị gồm None không bảo vệ, Sign chỉ ký, và SignAndEncrypt vừa ký vừa mã hóa16. Tổ hợp thuật toán dùng cho ký và mã hóa do chính sách bảo mật (security policy) quyết định; bài viết này dùng Basic256Sha256.
Việc ký và mã hóa dùng chứng chỉ riêng cho từng ứng dụng. Máy khách và máy chủ kiểm tra chứng chỉ của nhau và từ chối kết nối với bên mình không tin cậy. Máy chủ xây dựng bằng thư viện .NET của OPC Foundation mặc định không cung cấp chính sách None không mã hóa4.
Kết nối đầu tiên dừng lại ở BadCertificateUntrusted
Ở lần chạy đầu tiên, máy khách chưa tin cậy chứng chỉ của máy chủ nên kết nối bị từ chối. Đây là một phần kết quả.
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.'
Thư viện này lưu chứng chỉ bị từ chối vào kho rejected. Khi quản trị viên chuyển chứng chỉ từ đó sang kho trusted, thư viện sẽ tin cậy bên đó17. Lần này, tôi chuyển chứng chỉ của máy chủ từ pki/rejected/certs sang pki/trusted/certs, chạy lại và đã kết nối được. Trước khi chuyển, hãy xác nhận thumbprint, vốn cũng nằm trong tên tệp, với quản trị viên máy chủ4.
flowchart TB
accTitle: Quy trình tin cậy một chứng chỉ
accDescr: Sơ đồ cho thấy ở kết nối đầu tiên, máy khách từ chối chứng chỉ của máy chủ và lưu nó vào kho rejected, rồi sau khi xác nhận thumbprint và chuyển sang kho trusted, kết nối lần sau sẽ tin cậy và kết nối được.
A["Kết nối đầu tiên"] --> B["BadCertificateUntrusted"]
B --> C["Lưu chứng chỉ vào kho rejected"]
C --> D["Xác nhận thumbprint với quản trị viên máy chủ"]
D --> E["Chuyển sang kho trusted"]
E --> F["Kết nối lần sau tin cậy và kết nối được"]
Hình 8: Kiểm tra chứng chỉ bị từ chối rồi chuyển sang trusted thì máy khách sẽ tin cậy bên đó.
Ngoài ra còn có thiết lập AutoAcceptUntrustedCertificates để tự động chấp nhận chứng chỉ chưa được tin cậy. Giá trị mặc định là false, và tài liệu của thư viện viết rằng nó hữu ích trong môi trường phát triển nhưng không nên dùng trong môi trường thực tế17.
NotAfter=2027-11-01 trong nhật ký là ngày hết hạn của chứng chỉ máy chủ giả lập. Đây là chứng chỉ tự ký tạo bằng một hàm của asyncua, có hạn là một năm kể từ ngày tạo. Chứng chỉ của thiết bị cũng có thời hạn, vì vậy hãy đưa vào quy trình vận hành các bước thay chứng chỉ trước khi hết hạn và làm cho bên kia tin cậy chứng chỉ mới. Cách xử lý kho chứng chỉ của Windows được trình bày trong “Hướng dẫn thực tế kho chứng chỉ Windows”.
Xác thực người dùng
Thứ được kiểm tra bằng chứng chỉ là các ứng dụng với nhau. Tách biệt với điều đó, khi mở phiên (session), máy khách gửi một token cho biết người dùng. Đặc tả có bốn loại: ẩn danh, tên người dùng và mật khẩu, chứng chỉ X.509, và token do bên ngoài cấp18. Máy khách lần này kết nối ẩn danh. Máy chủ chấp nhận những token nào được ghi trong thông tin endpoint của máy chủ19.
Những chỗ dễ vấp khi thu thập dữ liệu tại hiện trường
Từ đây, tôi liệt kê những vấn đề dễ xảy ra trong hệ thống thu thập dữ liệu thiết bị, dựa trên thử nghiệm lần này và đặc tả.
Giá trị trong lúc máy chủ dừng sẽ không đến
Trong lúc đang đăng ký theo dõi, tôi dừng máy chủ rồi khởi động lại sau khoảng 15 giây. ManagedSession của thư viện này tự động kết nối lại khi mất kết nối20. Trong lúc máy chủ dừng, máy khách liên tục thử kết nối lại.
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.
Khi máy chủ hoạt động trở lại, thông báo cũng trở lại. Đây là một phần kết quả của việc đăng ký theo dõi.
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]
Không có giá trị nào trong khoảng từ 13 giờ 41 phút 13 giây đến 34 giây được gửi đến. Số sản phẩm quay từ 78 về 2. Đó là vì khởi động lại máy chủ giả lập khiến nó đếm lại từ 0.
Trong ví dụ này chính máy chủ đã dừng, nên giá trị trong khoảng thời gian đó không được lưu ở đâu cả. Cơ chế kết nối lại khôi phục kết nối, nhưng không lấp lại giá trị của khoảng thời gian máy chủ dừng.
Trường hợp chỉ có đường truyền bị ngắt còn máy chủ vẫn chạy thì khác. Đặc tả có dịch vụ Republish để yêu cầu máy chủ gửi lại các thông điệp thông báo chưa đến được, từ hàng đợi gửi lại của máy chủ21. Thông báo cũng được tích lại trong hàng đợi của MonitoredItem, nhưng phần vượt quá kích thước hàng đợi sẽ bị bỏ13. Trong thử nghiệm lần này, tôi chưa thử trường hợp đó. Trong cả hai trường hợp, hãy ghi lại thời điểm mất kết nối và kết nối lại để biết được khoảng thời gian nào không có giá trị. Thiết kế kết nối lại và nhật ký cho thiết bị truyền thông nối tiếp cũng được đề cập trong “Serial Communication App Pitfalls”.
flowchart TB
accTitle: Ghi nhận khi máy chủ dừng
accDescr: Sơ đồ cho thấy khi máy chủ dừng trong lúc đang đăng ký theo dõi, máy khách liên tục kết nối lại, khi máy chủ trở lại thì thông báo cũng trở lại, nhưng giá trị trong lúc dừng không đến, nên cần ghi lại thời điểm mất kết nối và kết nối lại.
A["Đang đăng ký theo dõi"] --> B["Máy chủ dừng"]
B --> C["Máy khách liên tục kết nối lại"]
C --> D["Máy chủ trở lại, thông báo cũng trở lại"]
B -.-> E["Giá trị trong lúc dừng không đến"]
E -.-> F["Ghi lại thời điểm mất kết nối và kết nối lại"]
Hình 9: Kết nối lại khôi phục kết nối, nhưng giá trị trong lúc máy chủ dừng không đến.
Ghi lại dấu thời gian nào
Theo đặc tả, SourceTimestamp là thời điểm mà nguồn phát sinh dữ liệu gắn cho giá trị và nên biểu thị lúc giá trị thay đổi. ServerTimestamp là thời điểm máy chủ nhận giá trị, hoặc biết rằng giá trị là đúng9. Máy chủ giả lập lần này tự tạo giá trị nên hai thời điểm là như nhau.
Nếu muốn biết lúc giá trị thay đổi trên thiết bị, hãy dùng SourceTimestamp. Tuy nhiên, thời điểm đó được gắn theo đồng hồ của nguồn, nên nếu đồng hồ của thiết bị hay máy chủ bị lệch thì bản ghi cũng lệch theo. Nếu cần đối chiếu dữ liệu đã thu thập với các bản ghi khác, hãy lưu song song cả thời gian của PC nhận dữ liệu, để về sau có thể kiểm tra độ lệch. Việc đồng bộ thời gian ở phía Windows được trình bày trong “Windows Time Synchronization (w32time) and Business Systems”.
Đừng xử lý giá trị có trạng thái khác Good như một con số thông thường
Khi mã trạng thái là Bad, giá trị được coi là phải bỏ qua9. Khi là Uncertain, đó là giá trị có thể không dùng được cho một số mục đích10. Trước khi đưa vào biểu đồ hay số liệu tổng hợp, hãy phân loại theo mã trạng thái và quyết định trước cách xử lý giá trị khác Good. Bit Overflow khi hàng đợi bị tràn cũng được bật trong mã trạng thái13. Cách hiển thị trạng thái thiết bị lên màn hình được đề cập trong “Best Practices for Checking and Displaying External Device State”.
Mở cổng trên tường lửa
Cổng OPC UA được đăng ký với IANA là 4840/TCP3. Tuy nhiên, không phải máy chủ nào cũng dùng số cổng này. Mẫu .NET của OPC Foundation dùng các cổng như 625414. Hãy kiểm tra số cổng trong URL endpoint của thiết bị và cho phép kết nối đến cổng đó. Nếu máy chủ chạy trên Windows thì cho phép trong Windows Firewall của máy đó, và nếu có thiết bị mạng ở giữa thì cho phép trên thiết bị đó. Cách tạo quy tắc nhận được trình bày trong “Windows Firewall và ứng dụng nghiệp vụ”.
flowchart TB
accTitle: Những chỗ dễ vấp khi thu thập dữ liệu
accDescr: Sơ đồ chia những chỗ dễ vấp khi thu thập dữ liệu thiết bị thành nội dung bản ghi (giá trị trong lúc máy chủ dừng không đến, ghi lại dấu thời gian nào, cách xử lý giá trị khác Good) và chuẩn bị kết nối (cổng tường lửa, hạn của chứng chỉ).
A["Những chỗ dễ vấp khi thu thập dữ liệu"] --> R["Nội dung bản ghi"]
A --> N["Chuẩn bị kết nối"]
R --> B["Giá trị lúc dừng không đến"]
B ~~~ C["Dấu thời gian nào"]
C ~~~ D["Xử lý giá trị khác Good"]
N --> E["Cổng tường lửa"]
E ~~~ F["Hạn của chứng chỉ"]
Hình 10: Ngay cả khi đã kết nối được, hãy kiểm tra chỗ thiếu trong bản ghi, thời gian, trạng thái, cổng và chứng chỉ.
Chọn giữa OPC UA, Modbus/TCP và giao thức riêng của nhà sản xuất
Để giao tiếp với PLC và thiết bị, ngoài OPC UA còn có Modbus/TCP và các giao thức do nhà sản xuất PLC cung cấp.
Đặc tả Modbus xử lý dữ liệu trong bốn bảng: Discrete Input 1 bit chỉ đọc, Coil 1 bit đọc ghi được, Input Register 16 bit chỉ đọc và Holding Register 16 bit đọc ghi được22. IANA đăng ký 502/TCP cho giao thức ứng dụng Modbus (mbap)3.
Thứ nằm trong các bảng này là bit và từ 16 bit, nên địa chỉ nào chứa giá trị gì theo định dạng nào phải được kiểm tra trong tài liệu của thiết bị và viết ở phía máy khách. Với OPC UA, như đã thấy ở trên, máy chủ công bố các biến có tên trong không gian địa chỉ, và máy khách có thể tìm hiểu chúng bằng Browse. Giá trị đi kèm mã trạng thái và dấu thời gian, còn kết nối được cho phép bằng chứng chỉ.
Dùng cách nào thường được quyết định bởi thiết bị cung cấp gì. Nếu thiết bị có máy chủ OPC UA thì dùng nó; nếu chỉ có Modbus/TCP thì lập bảng tương ứng địa chỉ rồi đọc. Nếu chỉ có giao thức hoặc SDK của nhà sản xuất thì làm theo tài liệu đó. Cách chọn phương thức giao tiếp với thiết bị USB được trình bày trong “How to Work with USB Devices from a Windows App”.
flowchart TB
accTitle: Cách chọn phương thức giao tiếp với thiết bị
accDescr: Sơ đồ cho thấy nếu thiết bị có máy chủ OPC UA thì duyệt và thu thập biến bằng OPC UA, nếu chỉ có Modbus/TCP thì kiểm tra địa chỉ và định dạng trong tài liệu rồi đọc thanh ghi, và nếu chỉ có giao thức của nhà sản xuất thì làm theo tài liệu đó.
A["Giao tiếp mà thiết bị cung cấp"] --> B{"Có những gì?"}
B -->|"Máy chủ OPC UA"| C["Duyệt và thu thập các biến"]
B -->|"Chỉ có Modbus/TCP"| D["Kiểm tra địa chỉ và định dạng trong tài liệu"]
B -->|"Chỉ có giao thức của nhà sản xuất"| E["Làm theo tài liệu và SDK của nhà sản xuất"]
D --> F["Đọc thanh ghi và đổi thành giá trị"]
Hình 11: Thu thập bằng cách nào thường được quyết định bởi thiết bị cung cấp gì.
Bài viết liên quan
- Dropped Frames with GigE Vision and USB3 Vision Cameras
- Serial Communication App Pitfalls
- Best Practices for Checking and Displaying External Device State
- How to Work with USB Devices from a Windows App
- Windows Firewall và ứng dụng nghiệp vụ
- Hướng dẫn thực tế kho chứng chỉ Windows
- Windows Time Synchronization (w32time) and Business Systems
Tài liệu tham khảo
-
OPC Foundation, Classic. Việc OPC Classic dựa trên COM/DCOM, các đặc tả DA, A&E, HDA, việc công bố OPC UA năm 2008 và lý do thiết kế lại. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. Phần 1 của bộ IEC 62541, trình bày khái niệm và tổng quan về OPC UA (đã được thay bằng phiên bản sau). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. Đăng ký
opcua-tcpcho 4840/TCP vàmbapcho 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. Cách viết máy khách dòng 2.0, việc
ReadValueAsyncném ngoại lệ khi gặp Bad, kho rejected và kho trusted, việc máy chủ mặc định không cung cấpNone, và số cổng trong các mẫu. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). Thư viện OPC UA cho Python dùng để làm máy chủ thiết bị giả lập. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. Dịch vụ lần theo tham chiếu của nút. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. Việc URI không gian tên được biểu diễn bằng số trong các dịch vụ, và số đó ứng với vị trí trong NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. Các dịch vụ Read và Write. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. Định nghĩa giá trị, mã trạng thái, SourceTimestamp và ServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good, Uncertain và Bad do hai bit cao nhất biểu thị. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. Việc khoảng lấy mẫu là chu kỳ nỗ lực tối đa. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. Việc khi không có bộ lọc thì thông báo khi giá trị hoặc trạng thái thay đổi. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. Kích thước hàng đợi, cách bỏ thông báo và bit Overflow. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. Khoảng phát hành và keep-alive. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsync, khoảng lấy mẫu và kích thước hàng đợi trongMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. Ý nghĩa của None, Sign và SignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. Cách vận hành chuyển chứng chỉ từ kho rejected sang kho tin cậy, và việc không dùng
AutoAcceptUntrustedCertificatestrong môi trường thực tế. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. Bốn loại: ẩn danh, tên người dùng, X.509 và token đã được cấp. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. Danh sách token người dùng mà endpoint chấp nhận (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. Việc
ManagedSessiontích hợp sẵn cơ chế kết nối lại. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. Dịch vụ yêu cầu gửi lại thông điệp thông báo từ hàng đợi gửi lại của subscription. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (bản sao đặt trên máy chủ của Đại học Chicago). Mô hình dữ liệu ở mục 4.3 (Discrete Input, Coil, Input Register, Holding Register). ↩
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
Thực hành tốt nhất về đa luồng: ấn bản .NET — Những gì cần quyết trước khi thêm luồng
Tổng hợp các định thức thiết kế ngăn «thêm luồng rồi thỉnh thoảng crash hoặc treo» cho .NET/C#. Bài viết đi từ việc dựa trên Task thay vì...
Dùng WMI/CIM từ C# và PowerShell — hướng dẫn thực hành lấy thông tin phần cứng, giám sát tiến trình và truy vấn từ xa
Cách chuẩn để lấy số serial PC, giám sát dung lượng đĩa trống và phát hiện tiến trình khởi chạy là WMI/CIM. Bài viết trình bày cmdlet CIM...
Named pipe trong thực tiễn — IPC chuẩn của Windows, từ thiết kế đến bảo mật
Hướng dẫn thực tiễn về named pipe, cơ chế giao tiếp giữa các tiến trình chuẩn của Windows. Bài viết sắp xếp, từ nguồn gốc, việc chọn giữa...
Thức tỉnh giả — Vì sao biến điều kiện thức "mà không được thông báo" và cách chờ đúng trên Windows
Lần chờ của biến điều kiện có thể trở về ngay cả khi không có thông báo nào tới (thức tỉnh giả). Bài viết này giải thích, từ triển khai W...
Proxy doanh nghiệp và ứng dụng Windows — sắp xếp việc phân giải proxy trong WinINET, WinHTTP và .NET
Trình duyệt đi được, nhưng chỉ ứng dụng nghiệp vụ không vượt proxy doanh nghiệp. Nguyên nhân thường là lệch nhau về thiết lập proxy mà Wi...
Chủ đề liên quan
Đây là các trang chủ đề gần với chủ đề này. Từ bài viết, bạn có thể đi tiếp đến các dịch vụ liên quan và những bài viết khác.
Chủ đề kỹ thuật Windows
Cổng vào tổng hợp các chủ đề kỹ thuật về phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Dịch vụ liên quan đến chủ đề này
Bài viết này dẫn đến các trang dịch vụ sau. Hãy xem từ lối vào gần nhất với bạn.
Phát triển ứng dụng Windows
Việc xây dựng ứng dụng Windows thu thập dữ liệu từ thiết bị, bao gồm cả thiết lập đăng ký theo dõi, kết nối lại, ghi lại dấu thời gian và trạng thái, có thể được thực hiện như một dự án phát triển ứng dụng Windows.
Tư vấn kỹ thuật và đánh giá thiết kế
Nếu bạn muốn quyết định nên thu thập bằng OPC UA hay dùng Modbus/TCP hoặc giao thức của nhà sản xuất, và nên quản lý chứng chỉ thế nào, việc đó có thể được thực hiện như tư vấn kỹ thuật và đánh giá thiết kế.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- OPC UA và OPC Classic (OPC DA) khác nhau thế nào?
- OPC Classic là đặc tả trao đổi dữ liệu qua COM/DCOM của Windows. OPC UA được công bố năm 2008, không phụ thuộc vào một hệ điều hành cụ thể và gộp các chức năng của Classic lại. Máy khách OPC UA chạy được cả ngoài Windows và không cần cấu hình DCOM.
- Khi thu thập giá trị bằng OPC UA, chỉ cần đọc theo chu kỳ cố định là đủ phải không?
- Nếu số giá trị ít và chu kỳ dài thì đọc theo chu kỳ cũng thu thập được. Nếu muốn theo dõi sự thay đổi của giá trị, có cách dùng subscription để giao việc lấy mẫu cho máy chủ và nhận thông báo khi có thay đổi. Dù dùng cách nào, hãy ghi lại mã trạng thái và dấu thời gian được trả về cùng giá trị.
- Vì sao kết nối lại báo BadCertificateUntrusted?
- Vì máy khách chưa tin cậy chứng chỉ của máy chủ. Thư viện .NET của OPC Foundation lưu chứng chỉ bị từ chối vào kho rejected. Hãy xác nhận thumbprint của chứng chỉ với quản trị viên máy chủ rồi chuyển nó sang kho trusted. Chỉ dùng thiết lập tự động chấp nhận chứng chỉ trong môi trường phát triển.
- OPC UA dùng cổng số mấy?
- IANA đăng ký 4840/TCP với tên opcua-tcp. Tuy nhiên máy chủ thực tế có thể dùng cổng khác, nên hãy kiểm tra trong cấu hình của thiết bị. Nếu máy chủ chạy trên Windows thì cần cho phép kết nối đến cổng đó trong Windows Firewall, và nếu có thiết bị mạng ở giữa thì cần cho phép trên thiết bị đó.
Hồ sơ tác giả
Trang giới thiệu tác giả bài viết.
Go Komura
Đại diện của KomuraSoft LLC
Chuyên về phát triển phần mềm Windows, tư vấn kỹ thuật và điều tra lỗi, với thế mạnh ở các dự án còn tài sản hiện có và việc điều tra những sự cố khó nhìn ra nguyên nhân.