سجل التعديلات (النسخة الأولى، نُشرت في 1 Nov، 2026)
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.23968385)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). مدخل إلى OPC UA ── جمع بيانات وحدات PLC والأجهزة في تطبيق Windows. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.23968385 https://comcomponent.com/ar/blog/opc-ua-windows-data-collection/
- DOI (أحدث نسخة)
- 10.5281/zenodo.23968385
- DOI (هذه النسخة)
- 10.5281/zenodo.23968386
عندما تجمع بيانات وحدة PLC أو جهاز في تطبيق Windows، ويكون لدى الجهاز خادم OPC UA، فقد تستغني عن كتابة اتصال مختلف لكل شركة مصنّعة. لكنّ الاتصال وقراءة القيم وحدهما لا يكفيان. إن لم تعرف الحالة والطوابع الزمنيّة التي تعود مع كل قيمة، والاشتراك الذي يُخطرك بالتغييرات، والسماح بالاتصال عبر الشهادات، وكيفيّة التعافي من انقطاع الاتصال، فلن تستطيع لاحقاً أن تثق بالبيانات التي جمعتها.
يشرح هذا المقال أجزاء OPC UA التي تلزم لجمع البيانات، مع نتائج تشغيل عميل مكتوب بلغة C# فعلياً. يستخدم العميل مكتبة OPC Foundation لـ.NET، والطرف الآخر خادم يحاكي جهازاً. لم أجرّب أيّ وحدة PLC أو جهاز بعينه. تحقّق من إعدادات كل جهاز في وثائقه.
جمع الصور من كاميرات GigE Vision وUSB3 Vision مشروح في «Dropped Frames with GigE Vision and USB3 Vision Cameras»، والأجهزة التي تتواصل عبر الاتصال التسلسليّ في «مزالق تطبيقات الاتصالات التسلسلية».
الفرق بين OPC UA وOPC Classic
تتبادل مواصفات OPC Classic البيانات بين مكوّنات البرمجيات باستخدام COM/DCOM في Windows. وقد عُرِّفت OPC DA لبيانات العمليّات، وOPC A&E للإنذارات، وOPC HDA للبيانات التاريخيّة، كمواصفات منفصلة1.
نشرت OPC Foundation عام 2008 مواصفة OPC UA (Unified Architecture). وOPC UA معماريّة موجّهة نحو الخدمات لا تعتمد على منصّة بعينها، وتجمع وظائف كل واحدة من مواصفات Classic. ومن الأسباب التي تذكرها OPC Foundation لإعادة البناء أنّ Microsoft صارت تقدّم الآليّات متعدّدة المنصّات على COM/DCOM، وأنّه كان لا بدّ من العمل على أنظمة غير أنظمة Microsoft وعلى الأجهزة المدمجة، وأنّه كان لا بدّ من الاتصال الآمن عبر جدران الحماية1.
ومواصفة OPC UA معتمدة أيضاً معياراً لدى IEC ضمن السلسلة IEC 625412. أمّا منفذ الاتصال، فقد سجّلت IANA المنفذ 4840/TCP باسم opcua-tcp3.
flowchart TB
accTitle: الفرق بين OPC Classic وOPC UA
accDescr: مخطّط يبيّن أنّ OPC Classic مبنيّ على COM وDCOM في Windows ومقسّم إلى مواصفات منفصلة هي DA وA&E وHDA، وأنّ OPC UA يجمع وظائفها في مواصفة واحدة لا تعتمد على منصّة بعينها.
C["OPC Classic"] --> D["يعمل فوق COM/DCOM (Windows)"]
D --> DA["مواصفات منفصلة: DA وA&E وHDA"]
DA -->|"يجمع الوظائف"| U["OPC UA (2008)"]
U --> P["لا يعتمد على منصّة بعينها"]
P --> S["معيار IEC 62541"]
الشكل 1: OPC Classic مواصفات منفصلة فوق COM/DCOM، وOPC UA يجمعها في مواصفة واحدة.
إذا لم يكن لدى جهاز قائم سوى خادم OPC DA، فيلزم في جهة العميل آليّة تستخدم COM/DCOM. ولا يتناول هذا المقال إلّا OPC UA.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 13، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
الإعداد الذي جرّبته
هذا هو الإعداد الذي شغّلته.
| الدور | ما استخدمته |
|---|---|
| العميل | C#، و.NET 10 SDK 10.0.114، وحزمة NuGet OPCFoundation.NetStandard.Opc.Ua.Client 2.0.1 |
| الخادم | جهاز محاكى مبنيّ بـPython 3.13 وasyncua 2.1.0 (المنفذ 4840 على الحاسوب نفسه) |
| نظام التشغيل | Linux (لم أجرّب التشغيل على Windows) |
مكتبة العميل هي تنفيذ .NET الذي تنشره OPC Foundation. وفي طريقة استخدام إصدارات 2.0 اتّبعت الأسلوب الوارد في Getting started في المستودع نفسه4.
flowchart TB
accTitle: الإعداد الذي جرّبته
accDescr: مخطّط يبيّن عميلاً بلغة C# يستخدم مكتبة OPC Foundation لـ.NET ويتصل اتصالاً مشفّراً بخادم asyncua يحاكي جهازاً ويستمع على المنفذ 4840.
A["عميل C# (LineCollector)"] -->|"opc.tcp، SignAndEncrypt"| B["خادم جهاز محاكى (asyncua)"]
A -.-> L["مكتبة OPC Foundation لـ.NET 2.0.1"]
B -.-> V["أربعة متغيّرات تحت Line1"]
الشكل 2: على الحاسوب نفسه، اتصل عميل C# بخادم الجهاز المحاكى.
خادم الجهاز المحاكى
بدلاً من جهاز حقيقيّ، بنيت خادماً صغيراً باستخدام asyncua5. تحت كائن اسمه Line1 توجد أربعة متغيّرات: درجة الحرارة (Temperature)، وعدد القطع المنتجة (PartCount)، وحالة التشغيل (Running)، والقيمة المضبوطة (Setpoint). تتغيّر درجة الحرارة كل 0.5 ثانية، وعدد القطع كل ثانيتين. وللأمان وفّرت خيارين: بلا تشفير (None)، وتوقيع مع تشفير (SignAndEncrypt من Basic256Sha256).
# جهاز محاكى للتحقّق من التشغيل. ليس بديلاً عن الأجهزة أو وحدات PLC في بيئة الإنتاج.
import asyncio, math, random, socket
from pathlib import Path
from cryptography.x509.oid import ExtendedKeyUsageOID
from asyncua import Server, ua
from asyncua.crypto.cert_gen import setup_self_signed_certificate
HERE = Path(__file__).parent
APP_URI = "urn:komurasoft:demo-plc"
async def main():
key, cert = HERE / "server_key.pem", HERE / "server_cert.der"
await setup_self_signed_certificate(
key, cert, APP_URI, socket.gethostname(),
[ExtendedKeyUsageOID.SERVER_AUTH], {"commonName": "DemoPlcServer"})
server = Server()
await server.init()
server.set_endpoint("opc.tcp://0.0.0.0:4840/demo/")
server.set_server_name("DemoPlcServer")
await server.set_application_uri(APP_URI)
server.set_security_policy([
ua.SecurityPolicyType.NoSecurity,
ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt,
])
await server.load_certificate(str(cert))
await server.load_private_key(str(key))
ns = await server.register_namespace("urn:komurasoft:demo-plc:line1")
line1 = await server.nodes.objects.add_object(ua.NodeId("Line1", ns), "Line1")
temperature = await line1.add_variable(ua.NodeId("Line1.Temperature", ns), "Temperature", 25.0)
part_count = await line1.add_variable(
ua.NodeId("Line1.PartCount", ns), "PartCount", ua.Variant(0, ua.VariantType.UInt32))
await line1.add_variable(ua.NodeId("Line1.Running", ns), "Running", True)
setpoint = await line1.add_variable(ua.NodeId("Line1.Setpoint", ns), "Setpoint", 30.0)
await setpoint.set_writable()
async with server:
print("listening", flush=True)
t, count = 0, 0
while True:
await asyncio.sleep(0.5)
t += 1
value = 25.0 + 2.0 * math.sin(t / 4) + random.uniform(-0.05, 0.05)
await temperature.write_value(round(value, 2))
if t % 4 == 0:
count += 1
await part_count.write_value(ua.Variant(count, ua.VariantType.UInt32))
asyncio.run(main())
يقبل هذا الخادم شهادات العملاء المتّصلين به دون فحصها. هذا إعداد للتجربة، ويختلف عن سلوك الأجهزة في بيئة الإنتاج.
عميل C#
أضفت إلى ملفّ المشروع الحزمتين التاليتين.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
</ItemGroup>
</Project>
يتصل العميل، ثم يستعرض، ثم يقرأ، ثم يكتب، ثم يشترك، بهذا الترتيب. وتشرح الأقسام التالية معنى كل جزء.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Client.Subscriptions;
using Opc.Ua.Client.Subscriptions.Streaming;
using MonitoredItemOptions = Opc.Ua.Client.Subscriptions.MonitoredItems.MonitoredItemOptions;
string url = args.Length > 0 ? args[0] : "opc.tcp://localhost:4840/demo/";
int notifications = args.Length > 1 ? int.Parse(args[1]) : 6;
const string LineNamespace = "urn:komurasoft:demo-plc:line1";
HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder.Logging.SetMinimumLevel(LogLevel.Warning);
builder.Services
.AddOpcUa()
.AddClient(options =>
{
options.ApplicationName = "LineCollector";
options.ApplicationUri = "urn:localhost:LineCollector";
options.ProductUri = "urn:komurasoft:LineCollector";
options.PkiRoot = Path.Combine(AppContext.BaseDirectory, "pki");
})
.AddDiscoveryAndConnect(options =>
{
options.DiscoveryUrl = url;
options.SecurityMode = MessageSecurityMode.SignAndEncrypt;
options.SecurityPolicyUri = SecurityPolicies.Basic256Sha256;
});
using IHost host = builder.Build();
await host.StartAsync();
using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2));
CancellationToken ct = timeout.Token;
try
{
var connect = host.Services
.GetRequiredService<Func<CancellationToken, Task<ManagedSession>>>();
ManagedSession session = await connect(ct);
await using (session)
{
Console.WriteLine($"接続: {session.ConfiguredEndpoint?.EndpointUrl}");
// الخادم هو الذي يحدّد فهرس فضاء الأسماء. نستخرجه من الـURI ونبني منه NodeId
ushort ns = (ushort)session.NamespaceUris.GetIndex(LineNamespace);
var line1 = new NodeId("Line1", ns);
var temperature = new NodeId("Line1.Temperature", ns);
var partCount = new NodeId("Line1.PartCount", ns);
var setpoint = new NodeId("Line1.Setpoint", ns);
// الاستعراض (Browse): سرد المتغيّرات الموجودة تحت Line1
var browser = new Browser(session)
{
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
IncludeSubtypes = true,
NodeClassMask = (uint)NodeClass.Variable,
};
Console.WriteLine("Line1 の変数:");
foreach (ReferenceDescription child in await browser.BrowseAsync(line1, ct))
{
Console.WriteLine($" {child.DisplayName} {child.NodeId}");
}
// القراءة: ننظر إلى الحالة والطابعين الزمنيّين، لا إلى القيمة وحدها
DataValue t = await session.ReadValueAsync(temperature, ct);
Console.WriteLine($"Temperature = {t.WrappedValue} 状態 {t.StatusCode}");
Console.WriteLine($" Source {Hms(t.SourceTimestamp)} / Server {Hms(t.ServerTimestamp)}");
// قراءة عقدة غير موجودة تعيد حالة Bad بدلاً من قيمة
try
{
await session.ReadValueAsync(new NodeId("Line1.Pressure", ns), ct);
}
catch (ServiceResultException ex)
{
Console.WriteLine($"Line1.Pressure の読み取り: {ex.StatusCode}");
}
// الكتابة: نجعل Setpoint يساوي 32.5
var write = new WriteValue
{
NodeId = setpoint,
AttributeId = Attributes.Value,
Value = new DataValue(new Variant(32.5)),
};
WriteResponse wr = await session.WriteAsync(null, new[] { write }, ct);
Console.WriteLine($"Setpoint の書き込み: {wr.Results[0]}");
// الاشتراك: نطلب من الخادم أخذ عيّنة كل 500ms وإخطارنا بالتغييرات
Console.WriteLine($"購読({notifications}件まで):");
await foreach (DataValueChange change in session.DefaultStreaming
.SubscribeDataChangesAsync(new[] { temperature, partCount }, options: new MonitoredItemOptions
{
SamplingInterval = TimeSpan.FromMilliseconds(500),
QueueSize = 5,
}, ct: ct)
.TakeAsync(notifications)
.WithCancellation(ct))
{
DataValue v = change.Value;
string name = change.MonitoredItem?.Name.Split("s=")[^1] ?? "?";
Console.WriteLine($" {Hms(v.SourceTimestamp)} {name,-17} {v.WrappedValue,6} {v.StatusCode}");
}
}
}
catch (ServiceResultException ex)
{
Console.Error.WriteLine($"OPC UA エラー {ex.StatusCode}: {ex.Message}");
Environment.ExitCode = 1;
}
finally
{
await host.StopAsync();
}
static string Hms(DateTimeUtc time) => time.ToDateTime().ToString("HH:mm:ss.fff");
في التشغيل الأوّل لم يتمّ الاتصال. والسبب مشروح في قسم «السماح بالاتصال عبر الشهادات». وهذا هو الناتج بعد الوثوق بشهادة الخادم، مع حذف سجلّات التحذير الخاصّة بالمكتبة.
接続: opc.tcp://localhost:4840/demo/
Line1 の変数:
Temperature ns=2;s=Line1.Temperature
PartCount ns=2;s=Line1.PartCount
Running ns=2;s=Line1.Running
Setpoint ns=2;s=Line1.Setpoint
Temperature = 23.09 状態 Good [0x00000000]
Source 13:40:51.302 / Server 13:40:51.302
Line1.Pressure の読み取り: BadNodeIdUnknown [0x80340000]
Setpoint の書き込み: Good [0x00000000]
購読(6件まで):
13:40:51.302 Line1.Temperature 23.09 Good [0x00000000]
13:40:50.801 Line1.PartCount 67 Good [0x00000000]
13:40:51.802 Line1.Temperature 23.05 Good [0x00000000]
13:40:52.304 Line1.Temperature 23.07 Good [0x00000000]
13:40:52.806 Line1.Temperature 23.2 Good [0x00000000]
13:40:52.806 Line1.PartCount 68 Good [0x00000000]
الأوقات بتوقيت UTC.
بيانات الجهاز تظهر عقداً في فضاء العناوين
يعرض خادم OPC UA البيانات التي يحملها مجموعةً من العقد. وتُسمّى هذه المجموعة فضاء العناوين (address space). وللعقد أنواع، منها الكائنات التي تمثّل المجلّدات أو الأجهزة، والمتغيّرات التي تحمل القيم، وترتبط العقد بعضها ببعض عبر المراجع (references). ويتتبّع العميل المراجع بخدمة Browse ليعرف ما العقد الموجودة6.
في الناتج أعلاه، يتتبّع العميل المراجع الهرميّة انطلاقاً من Line1 فيجد أربعة متغيّرات.
flowchart TB
accTitle: فضاء العناوين في الجهاز المحاكى
accDescr: مخطّط يبيّن وجود كائن Line1 تحت المجلّد Objects، وتحته أربعة متغيّرات هي Temperature وPartCount وRunning وSetpoint.
O["Objects"] --> L["Line1 (كائن)"]
L --> T["Temperature (متغيّر)"]
L --> P["PartCount (متغيّر)"]
L --> R["Running (متغيّر)"]
L --> S["Setpoint (متغيّر، قابل للكتابة)"]
الشكل 3: فضاء العناوين في الجهاز المحاكى. يجد العميل العقد بتتبّع المراجع عبر Browse.
يتكوّن NodeId من فضاء أسماء ومعرّف
تُعرَّف العقد بواسطة NodeId. وفي الناتج يعني ns=2;s=Line1.Temperature أنّ فهرس فضاء الأسماء هو 2، وأنّ المعرّف هو السلسلة النصّيّة Line1.Temperature.
فضاء الأسماء (namespace) في حقيقته URI. وتنصّ المواصفة على أنّ URI فضاء الأسماء يُمثَّل داخل الخدمات برقم، وأنّ هذا الرقم يقابل الموضع في NamespaceArray الخاصّ بالخادم. والفهرس 0 هو فضاء أسماء OPC UA http://opcfoundation.org/UA/7.
لذلك لا تكتب رقم الفهرس في الشيفرة، بل استخرجه من الـURI. تحصل الشيفرة أعلاه على الرقم 2 من الـURI عبر session.NamespaceUris.GetIndex(LineNamespace)، ثم تبني منه NodeId.
flowchart TB
accTitle: بناء NodeId من URI فضاء الأسماء
accDescr: مخطّط يبيّن أنّ العميل يبحث عن URI فضاء الأسماء في NamespaceArray الخاصّ بالخادم ليحصل على الفهرس، ثم يجمع الفهرس مع المعرّف ليبني NodeId.
U["URI فضاء الأسماء"] --> A["NamespaceArray الخاصّ بالخادم"]
A --> I["الفهرس (2 في هذا المثال)"]
I --> N["NodeId ns=2;s=Line1.Temperature"]
K["المعرّف Line1.Temperature"] --> N
الشكل 4: يُستخرج فهرس فضاء الأسماء من الـURI، ثم يُجمع مع المعرّف ليصبح NodeId.
القراءة والكتابة تعيدان الحالة والطوابع الزمنيّة مع القيمة
تُقرأ قيم المتغيّرات وتُكتب بخدمتي Read وWrite8. ولا تعيد القراءة القيمة وحدها، بل تعيد بنية تُسمّى DataValue تضمّ القيمة، ورمز الحالة، والطابع الزمنيّ للمصدر، والطابع الزمنيّ للخادم9.
| العنصر | المعنى |
|---|---|
| القيمة | قيمة المتغيّر. عندما يدلّ رمز الحالة على خطأ تُهمَل القيمة، ويجعلها الخادم null |
| رمز الحالة (StatusCode) | هل تمكّن الخادم من الحصول على القيمة وتقديمها |
| SourceTimestamp | الوقت الذي أرفقه مصدر البيانات بالقيمة |
| ServerTimestamp | الوقت الذي استلم فيه الخادم القيمة، أو عرف فيه أنّها صحيحة |
يدلّ البتّان الأعليان في رمز الحالة على Good (نجاح)، أو Uncertain (نجاح جزئيّ، وقد لا تصلح القيمة لبعض الاستخدامات)، أو Bad (فشل)10. وGood [0x00000000] في الناتج تعني Good.
عندما قرأت Line1.Pressure غير الموجود، عاد BadNodeIdUnknown [0x80340000] بدلاً من قيمة. والدالّة ReadValueAsync في هذه المكتبة ترمي استثناءً عندما تكون الحالة Bad4. وإذا كان نظام تسجيل القيم يحفظ رمز الحالة بجانب كل قيمة، فيمكن لاحقاً تمييز القيم التي سُجّلت أثناء كون الحالة Bad.
flowchart TB
accTitle: محتوى DataValue
accDescr: مخطّط يبيّن أنّ DataValue العائدة من القراءة أو الإخطار تضمّ أربعة عناصر هي القيمة ورمز الحالة والطابع الزمنيّ للمصدر والطابع الزمنيّ للخادم، وأنّ القيمة تُهمَل عندما يكون رمز الحالة Bad.
D["DataValue"] --> V["القيمة"]
D --> S["رمز الحالة"]
D --> TS["SourceTimestamp (وقت المصدر)"]
D --> TV["ServerTimestamp (وقت الخادم)"]
S -->|"إن كانت Bad"| X["تُهمَل القيمة"]
الشكل 5: نتيجة القراءة لا تضمّ القيمة وحدها، بل رمز الحالة وطابعين زمنيّين أيضاً.
في الكتابة كتبت 32.5 في Setpoint، فعاد رمز الحالة Good. والخادم هو الذي يقرّر أيّ المتغيّرات قابلة للكتابة. وفي هذا الخادم المحاكى جعلت Setpoint وحده قابلاً للكتابة. أمّا السماح بتغيير القيم المضبوطة للجهاز من الخارج أو عدمه فيتعلّق بتصميم سلامة الجهاز، ولذلك لا يتناوله هذا المقال.
تلقّي الإخطار بالتغييرات: الاشتراك وMonitoredItem
لمتابعة تغيّر القيم، يمكن للعميل أن يكرّر Read على فترات ثابتة. وإلى جانب ذلك، يوفّر OPC UA آليّة تترك للخادم مراقبة القيم وتجعله يُخطرك بالتغييرات، وهي الاشتراك (subscription) وMonitoredItem.
MonitoredItem عنصر واحد قيد المراقبة، وله فترة أخذ عيّنات (sampling interval). وفترة أخذ العيّنات هي أسرع معدّل يأخذ به الخادم عيّنات القيم من المصدر، وتصفها المواصفة بأنّها دورة «بأفضل جهد» (best effort)11. وعندما تستوفي العيّنة شروط المرشّح يُنشأ إخطار. وإذا لم يُحدَّد مرشّح، يُرسَل الإخطار عندما تتغيّر القيمة أو الحالة12.
تدخل الإخطارات أوّلاً في طابور MonitoredItem. ويُحدَّد مسبقاً هل يُتخلَّص من الأقدم أم من الأحدث عندما يمتلئ الطابور، وإذا كان حجم الطابور 2 أو أكثر يُضبط في رمز الحالة بتّ Overflow الذي يدلّ على التخلّص13.
يجمع الاشتراك عدّة عناصر MonitoredItem، ويرسل إلى العميل في كل فترة نشر (publishing interval) الإخطارات التي لم تُرسل بعد. وعندما تمرّ دورات دون إخطارات تُرسل، يرسل keep-alive ليدلّ على أنّ الاشتراك ما زال قائماً14.
flowchart TB
accTitle: آليّة الاشتراك وMonitoredItem
accDescr: مخطّط يبيّن أنّ MonitoredItem يأخذ عيّنات قيمة المصدر وفق فترة أخذ العيّنات، ويضع الإخطارات التي تجتاز المرشّح في طابور، وأنّ الاشتراك يرسلها مجتمعة إلى العميل وفق فترة النشر، ويرسل keep-alive إذا لم تكن هناك إخطارات.
S["المصدر (قيمة الجهاز)"] --> M["MonitoredItem"]
M -->|"فترة أخذ العيّنات"| F["المرشّح (الافتراضيّ: تغيّر القيمة أو الحالة)"]
F --> Q["الطابور (Overflow عند الامتلاء)"]
Q --> SUB["الاشتراك"]
SUB -->|"فترة النشر"| C["العميل"]
SUB -.->|"إن لم تكن هناك إخطارات"| K["Keep-alive"]
الشكل 6: يأخذ MonitoredItem العيّنات ويضعها في طابور، ويرسل الاشتراك الإخطارات مجتمعة إلى العميل.
اشتركت الشيفرة أعلاه في Temperature وPartCount بفترة أخذ عيّنات قدرها 500 ملّي ثانية وحجم طابور 515. في الناتج، تصل درجة الحرارة كل 0.5 ثانية تقريباً، وعدد القطع كل ثانيتين تقريباً. أوّل إخطارين كانا القيمتين اللتين كانتا لدى كل متغيّر عند بدء الاشتراك. و13:40:50.801 لعدد القطع هو الوقت الذي تغيّرت فيه القيمة قبل بدء الاشتراك. ولأنّ الإخطارات تعرض الطابع الزمنيّ للمصدر، فالظاهر هو وقت تغيّر القيمة لا وقت استلام الإخطار.
متى تكرّر القراءة ومتى تستخدم الاشتراك
تكرار القراءة يتيح للعميل أن يحدّد الفترة، والآليّة بسيطة. في المقابل، يتبادل الطرفان طلباً واستجابة في كل مرّة حتى لو لم تتغيّر القيمة، وتفوت التغييرات الأقصر من الفترة. أمّا في الاشتراك فيأخذ الخادم العيّنات ولا يرسل إلّا التغييرات، فلا تزداد حركة الاتصال كثيراً حتى للقيم قليلة التغيّر. ومع ذلك، قد لا يُخطَر بالتغييرات الأقصر من فترة أخذ العيّنات حتى في الاشتراك. فالمواصفة تسمح بأخذ العيّنات أسرع ممّا طُلب، لكنّها لا تفرضه11.
flowchart TB
accTitle: الفرق بين تكرار القراءة والاشتراك
accDescr: مخطّط يبيّن أنّ تكرار القراءة يجعل العميل يحدّد الفترة ويرسل طلباً في كل مرّة، وأنّ الاشتراك يجعل الخادم يأخذ العيّنات وفق فترة أخذ العيّنات ويرسل التغييرات فقط، وأنّ كلتا الطريقتين قد تفوّتان التغييرات الأقصر من الفترة.
A["متابعة تغيّر القيم"] --> R["تكرار القراءة"]
A --> S["الاشتراك"]
R --> R1["العميل يحدّد الفترة ويطلب كل مرّة"]
S --> S1["الخادم يأخذ العيّنات ويرسل التغييرات فقط"]
R1 --> X["قد تفوت التغييرات الأقصر من الفترة"]
S1 --> X
الشكل 7: في كلتا الطريقتين، قد تفوت التغييرات الأقصر من الفترة.
السماح بالاتصال عبر الشهادات
في اتصال OPC UA تختار درجة حماية الرسائل بواسطة MessageSecurityMode. والقيم هي None بلا حماية، وSign للتوقيع فقط، وSignAndEncrypt للتوقيع والتشفير16. وتحدّد سياسة الأمان (security policy) مجموعة الخوارزميّات المستخدمة في التوقيع والتشفير، واستخدمت في هذا المقال Basic256Sha256.
يُستخدم في التوقيع والتشفير شهادة لكل تطبيق. ويتحقّق العميل والخادم كلّ منهما من شهادة الآخر، ويرفضان الاتصال بطرف لا يثقان به. والخادم المبنيّ بمكتبة OPC Foundation لـ.NET لا يوفّر افتراضياً السياسة None غير المشفّرة4.
توقّف الاتصال الأوّل عند BadCertificateUntrusted
في التشغيل الأوّل لم يكن العميل يثق بشهادة الخادم، فرُفض الاتصال. وهذا جزء من الناتج.
fail: Opc.Ua.CertificateValidationCore[91]
Certificate [Subject=CN=DemoPlcServer, CN=urn:komurasoft:demo-plc, Thumbprint=4E236DDA629FE2AB32D185BF681609FD6C525363, NotBefore=2026-11-01, NotAfter=2027-11-01, KeyAlgorithm=1.2.840.113549.1.1.1] validation failed with suppressible errors but was rejected. Reason=Id: BadCertificateUntrusted
...
OPC UA エラー BadCertificateUntrusted [0x801A0000]: [801A0000] (BadCertificateUntrusted) 'Could not verify security on OpenSecureChannel response.'
تحفظ هذه المكتبة الشهادات المرفوضة في مخزن rejected. وعندما ينقل المسؤول الشهادة من هناك إلى مخزن trusted، تصبح المكتبة تثق بذلك الطرف17. وفي هذه المرّة نقلت شهادة الخادم من pki/rejected/certs إلى pki/trusted/certs، ثم شغّلت العميل مرّة أخرى فتمّ الاتصال. وقبل النقل، تحقّق مع مسؤول الخادم من البصمة (thumbprint)، وهي موجودة أيضاً في اسم الملفّ4.
flowchart TB
accTitle: خطوات الوثوق بالشهادة
accDescr: مخطّط يبيّن أنّ العميل يرفض شهادة الخادم في الاتصال الأوّل ويحفظها في مخزن rejected، وأنّه بعد التحقّق من البصمة ونقلها إلى مخزن trusted يثق بها في الاتصال التالي فيتمّ الاتصال.
A["الاتصال الأوّل"] --> B["BadCertificateUntrusted"]
B --> C["حفظ الشهادة في مخزن rejected"]
C --> D["التحقّق من البصمة مع مسؤول الخادم"]
D --> E["النقل إلى مخزن trusted"]
E --> F["الثقة بها والاتصال في المرّة التالية"]
الشكل 8: بعد التحقّق من الشهادة المرفوضة ونقلها إلى trusted، يثق العميل بذلك الطرف.
يوجد أيضاً إعداد اسمه AutoAcceptUntrustedCertificates يقبل الشهادات غير الموثوق بها تلقائياً. قيمته الافتراضيّة false، وتذكر وثائق المكتبة أنّه مفيد في بيئات التطوير، لكن لا ينبغي استخدامه في بيئة الإنتاج17.
وNotAfter=2027-11-01 في السجلّ هو تاريخ انتهاء صلاحيّة شهادة الخادم المحاكى. وهي شهادة موقّعة ذاتياً أُنشئت بدالّة من asyncua، وكان تاريخ انتهائها بعد سنة من يوم إنشائها. ولشهادات الأجهزة أيضاً تاريخ انتهاء، فحدّد ضمن التشغيل خطوات استبدالها قبل انتهاء صلاحيّتها وجعل الطرف الآخر يثق بالجديدة. أمّا التعامل مع مخزن الشهادات في Windows فمشروح في «دليل مخزن شهادات Windows العمليّ».
مصادقة المستخدم
ما تتحقّق منه الشهادات هو التطبيقات بعضها من بعض. وبمعزل عن ذلك، يُمرَّر عند فتح الجلسة رمز (token) يدلّ على المستخدم. وتعرّف المواصفة أربعة أنواع: المجهول، واسم المستخدم وكلمة المرور، وشهادة X.509، والرمز الصادر من جهة خارجيّة18. واتصل العميل في هذا المقال بوصفه مجهولاً. أمّا الرموز التي يقبلها الخادم فموجودة في معلومات نقاط النهاية (endpoints) الخاصّة به19.
أين يتعثّر جمع البيانات في الميدان
من هنا أذكر المشكلات التي يكثر حدوثها في أنظمة جمع بيانات الأجهزة، استناداً إلى هذه التجربة وإلى المواصفة.
القيم التي تقع أثناء توقّف الخادم لا تصل
أثناء الاشتراك أوقفت الخادم، ثم أعدت تشغيله بعد نحو 15 ثانية. وتعيد ManagedSession في هذه المكتبة الاتصال تلقائياً عند انقطاعه20. وطوال مدّة التوقّف ظلّ العميل يكرّر محاولة إعادة الاتصال.
warn: Opc.Ua.ChannelManager[6]
Reconnect failed. Endpoint=opc.tcp://localhost:4840/demo/, Attempt=3, Outcome=transient-failure, Status=BadNotConnected, ErrorMessage=Could not connect to the remote endpoint.
عندما عاد الخادم عادت الإخطارات أيضاً. وهذا جزء من ناتج الاشتراك.
13:41:13.364 Line1.Temperature 25.61 Good [0x00000000]
13:41:13.865 Line1.Temperature 25.12 Good [0x00000000]
13:41:34.285 Line1.Temperature 26.55 Good [0x00000000]
13:41:33.784 Line1.PartCount 2 Good [0x00000000]
13:41:34.786 Line1.Temperature 26.16 Good [0x00000000]
لم تصل أيّ قيمة بين الساعة 13:41:13 والساعة 13:41:34. وعاد عدد القطع من 78 إلى 2، لأنّ إعادة تشغيل الخادم المحاكى جعلته يبدأ العدّ من 0 من جديد.
في هذا المثال كان الخادم نفسه متوقّفاً، فلم تُحفَظ قيم تلك الفترة في أيّ مكان. تعيد آليّة إعادة الاتصال الاتصالَ، لكنّها لا تملأ لاحقاً قيم الفترة التي كان فيها الخادم متوقّفاً.
ويختلف الوضع إذا انقطع الاتصال وحده واستمرّ الخادم في العمل. ففي المواصفة خدمة Republish تجعل الخادم يعيد إرسال رسائل الإخطار التي لم تصل من طابور إعادة الإرسال الخاصّ به21. وتتراكم الإخطارات أيضاً في طابور MonitoredItem، لكنّ ما يتجاوز حجم الطابور يُتخلَّص منه13. ولم أجرّب هذه الحالة في هذه التجربة. وفي الحالتين، احفظ في السجلّ أوقات الانقطاع وإعادة الاتصال لتعرف الفترة التي لم تصل فيها القيم. وتصميم إعادة الاتصال والسجلّات للأجهزة التي تتواصل تسلسلياً مشروح أيضاً في «مزالق تطبيقات الاتصالات التسلسلية».
flowchart TB
accTitle: ما يُسجَّل عند توقّف الخادم
accDescr: مخطّط يبيّن أنّه عندما يتوقّف الخادم أثناء الاشتراك يكرّر العميل محاولة إعادة الاتصال، وتعود الإخطارات عند عودة الخادم، لكنّ قيم فترة التوقّف لا تصل، ولذلك تُسجَّل أوقات الانقطاع وإعادة الاتصال.
A["أثناء الاشتراك"] --> B["يتوقّف الخادم"]
B --> C["يكرّر العميل إعادة الاتصال"]
C --> D["يعود الخادم وتعود الإخطارات"]
B -.-> E["قيم فترة التوقّف لا تصل"]
E -.-> F["تسجيل أوقات الانقطاع وإعادة الاتصال"]
الشكل 9: تعيد إعادة الاتصال الاتصالَ، لكنّ القيم التي وقعت أثناء توقّف الخادم لا تصل.
أيّ الطابعين الزمنيّين تسجّل
تنصّ المواصفة على أنّ SourceTimestamp هو الوقت الذي أرفقه مصدر البيانات بالقيمة، وينبغي أن يدلّ على وقت تغيّر القيمة. أمّا ServerTimestamp فهو الوقت الذي استلم فيه الخادم القيمة، أو عرف فيه أنّها صحيحة9. والخادم المحاكى في هذا المقال يولّد القيم بنفسه، ولذلك كان الوقتان متطابقين.
إذا أردت أن تعرف متى تغيّرت القيمة في الجهاز فاستخدم SourceTimestamp. لكنّ هذا الوقت يُختم بساعة المصدر، فإذا انحرفت ساعة الجهاز أو الخادم انحرف السجلّ أيضاً. وإذا كنت ستطابق البيانات المجموعة مع سجلّات أخرى، فاحفظ بجانبها وقت الحاسوب الذي استلمها، لتتمكّن لاحقاً من التحقّق من الانحراف. أمّا مزامنة الوقت في جهة Windows فمشروحة في «Windows Time Synchronization (w32time) and Business Systems».
لا تعامل القيم التي حالتها ليست Good كأرقام عاديّة
عندما يكون رمز الحالة Bad تُهمَل القيمة9. وعندما يكون Uncertain فقد لا تصلح القيمة لبعض الاستخدامات10. وقبل إدخال القيم في الرسوم البيانيّة أو الإحصاءات، صنّفها حسب رمز الحالة، وحدّد كيف تتعامل مع القيم التي ليست Good. ويُضبط بتّ Overflow عند امتلاء الطابور داخل رمز الحالة أيضاً13. أمّا كيف تعرض حالة الجهاز على الشاشة فمشروح في «أفضل الممارسات للتحقّق من حالة الأجهزة الخارجيّة وعرضها».
افتح المنفذ في جدار الحماية
منفذ OPC UA المسجّل لدى IANA هو 4840/TCP3. لكن ليست كل الخوادم تستخدم هذا الرقم. فأمثلة OPC Foundation لـ.NET تستخدم منافذ مثل 625414. تحقّق من رقم المنفذ في عنوان URL لنقطة نهاية الجهاز، واسمح بالاتصال بذلك المنفذ. إذا كان الخادم يعمل على Windows فاسمح به في جدار حماية Windows على ذلك الحاسوب، وإذا كانت بينهما أجهزة شبكة فاسمح به عليها. أمّا طريقة إنشاء القواعد الواردة فمشروحة في «جدار حماية Windows وتطبيقات الأعمال».
flowchart TB
accTitle: أين يتعثّر جمع البيانات
accDescr: مخطّط يبيّن مواضع تعثّر جمع بيانات الأجهزة مقسّمة إلى محتوى السجلّات (عدم وصول القيم أثناء توقّف الخادم، وأيّ الطابعين الزمنيّين يُسجَّل، والتعامل مع القيم التي ليست Good) وتهيئة الاتصال (منافذ جدار الحماية، وانتهاء صلاحيّة الشهادات).
A["أين يتعثّر جمع البيانات"] --> R["محتوى السجلّات"]
A --> N["تهيئة الاتصال"]
R --> B["قيم فترة التوقّف لا تصل"]
B ~~~ C["أيّ الطابعين الزمنيّين"]
C ~~~ D["التعامل مع القيم غير Good"]
N --> E["منافذ جدار الحماية"]
E ~~~ F["انتهاء صلاحيّة الشهادات"]
الشكل 10: حتى بعد نجاح الاتصال، تحقّق من فجوات السجلّات والأوقات والحالة والمنافذ والشهادات.
متى تستخدم OPC UA أو Modbus/TCP أو بروتوكول الشركة المصنّعة
للاتصال بوحدات PLC والأجهزة، يوجد إلى جانب OPC UA بروتوكول Modbus/TCP والبروتوكولات التي توفّرها الشركات المصنّعة لوحدات PLC.
تتعامل مواصفة Modbus مع البيانات في أربعة جداول: Discrete Input، وهي بتّات مفردة للقراءة فقط؛ وCoil، وهي بتّات مفردة للقراءة والكتابة؛ وInput Register، وهي كلمات بطول 16 بتّاً للقراءة فقط؛ وHolding Register، وهي كلمات بطول 16 بتّاً للقراءة والكتابة22. وتسجّل IANA المنفذ 502/TCP بروتوكولاً تطبيقياً لـModbus (mbap)3.
ما يدخل في هذه الجداول بتّات وكلمات بطول 16 بتّاً، ولذلك يجب التحقّق في وثائق الجهاز من أيّ قيمة توجد في أيّ عنوان وبأيّ صيغة، ثم كتابة ذلك في جهة العميل. أمّا في OPC UA، فكما رأينا، يعرض الخادم متغيّرات لها أسماء في فضاء العناوين، ويستطيع العميل اكتشافها عبر Browse. وترافق القيمَ رموزُ الحالة والطوابعُ الزمنيّة، ويُسمح بالاتصال عبر الشهادات.
وكثيراً ما يتحدّد الاختيار بما يوفّره الجهاز. فإذا كان لدى الجهاز خادم OPC UA فاستخدمه، وإذا لم يكن لديه إلّا Modbus/TCP فأنشئ جدول مطابقة للعناوين واقرأ وفقه. وإذا لم يكن هناك إلّا بروتوكول الشركة المصنّعة أو حزمة SDK الخاصّة بها، فاتّبع وثائقها. أمّا اختيار طريقة الاتصال بأجهزة USB فمشروح في «How to Work with USB Devices from a Windows App».
flowchart TB
accTitle: اختيار طريقة الاتصال بالجهاز
accDescr: مخطّط يبيّن أنّه إذا كان لدى الجهاز خادم OPC UA تُستعرض المتغيّرات وتُجمع عبر OPC UA، وإذا لم يكن لديه إلّا Modbus/TCP يُتحقّق من العناوين والصيغ في الوثائق وتُقرأ السجلّات، وإذا لم يكن لديه إلّا بروتوكول الشركة المصنّعة تُتّبع وثائقها.
A["الاتصال الذي يوفّره الجهاز"] --> B{"ما المتاح؟"}
B -->|"خادم OPC UA"| C["استعراض المتغيّرات وجمعها"]
B -->|"Modbus/TCP فقط"| D["التحقّق من العناوين والصيغ في الوثائق"]
B -->|"بروتوكول الشركة المصنّعة فقط"| E["اتّباع وثائق الشركة المصنّعة وSDK"]
D --> F["قراءة السجلّات وتحويلها إلى قيم"]
الشكل 11: كثيراً ما يتحدّد ما تجمع به البيانات بما يوفّره الجهاز.
مقالات ذات صلة
- Dropped Frames with GigE Vision and USB3 Vision Cameras — Locating the Cause and the Windows Settings to Review
- مزالق تطبيقات الاتصالات التسلسلية - حتى إعادة الاتصال وتصميم السجلّ
- أفضل الممارسات للتحقّق من حالة الأجهزة الخارجيّة وعرضها
- How to Work with USB Devices from a Windows App
- جدار حماية Windows وتطبيقات الأعمال
- دليل مخزن شهادات Windows العمليّ
- Windows Time Synchronization (w32time) and Business Systems
روابط مرجعيّة
-
OPC Foundation, Classic. أنّ OPC Classic مبنيّ على COM/DCOM، ومواصفات DA وA&E وHDA، ونشر OPC UA عام 2008، وأسباب إعادة البناء. ↩ ↩2
-
IEC, IEC TR 62541-1:2020 OPC Unified Architecture - Part 1: Overview and concepts. الجزء الأوّل من سلسلة IEC 62541، ويتناول مفاهيم OPC UA ونظرة عامّة عليه (حلّت محلّه طبعة لاحقة). ↩
-
IANA, Service Name and Transport Protocol Port Number Registry. تسجيل
opcua-tcpعلى 4840/TCP، وmbapعلى 502/TCP. ↩ ↩2 ↩3 -
OPC Foundation, UA-.NETStandard 2.0.1: Getting started. طريقة كتابة عميل لإصدارات 2.0، وأنّ
ReadValueAsyncترمي استثناءً عند Bad، ومخزنا rejected وtrusted، وأنّ الخوادم لا توفّرNoneافتراضياً، وأرقام المنافذ في الأمثلة. ↩ ↩2 ↩3 ↩4 ↩5 -
FreeOpcUa, asyncua (PyPI). مكتبة OPC UA للغة Python التي استُخدمت لخادم الجهاز المحاكى. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.9.2 Browse. الخدمة التي تتتبّع مراجع العقدة. ↩
-
OPC Foundation, OPC 10000-3 v1.05, 8.2.2 NamespaceIndex. أنّ URI فضاء الأسماء يُمثَّل داخل الخدمات برقم، وأنّ هذا الرقم يقابل الموضع في NamespaceArray. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.11 Attribute Service Set. خدمتا Read وWrite. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.11 DataValue. تعريفات القيمة ورمز الحالة وSourceTimestamp وServerTimestamp. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 7.38.1 StatusCode General. Good وUncertain وBad كما يدلّ عليها البتّان الأعليان. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.2 Sampling interval. أنّ فترة أخذ العيّنات دورة بأفضل جهد. ↩ ↩2
-
OPC Foundation, OPC 10000-4 v1.05, 7.22.2 DataChangeFilter. أنّ الإخطار يُرسَل عند تغيّر القيمة أو الحالة إذا لم يكن هناك مرشّح. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 5.13.1.5 Queue parameters. حجم الطابور، وطريقة التخلّص، وبتّ Overflow. ↩ ↩2 ↩3
-
OPC Foundation, OPC 10000-4 v1.05, 5.14.1.1 Subscription model. فترة النشر وkeep-alive. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Subscriptions and Monitored Items Service Set.
SubscribeDataChangesAsync، وفترة أخذ العيّنات وحجم الطابور فيMonitoredItemOptions. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 7.20 MessageSecurityMode. معاني None وSign وSignAndEncrypt. ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Certificates. ممارسة نقل الشهادات من مخزن rejected إلى المخزن الموثوق، وعدم استخدام
AutoAcceptUntrustedCertificatesفي بيئة الإنتاج. ↩ ↩2 -
OPC Foundation, OPC 10000-4 v1.05, 7.40 UserIdentityToken parameters. الأنواع الأربعة: المجهول، واسم المستخدم، وX.509، والرمز الصادر. ↩
-
OPC Foundation, OPC 10000-4 v1.05, 7.14 EndpointDescription. قائمة رموز المستخدمين التي تقبلها نقطة النهاية (userIdentityTokens). ↩
-
OPC Foundation, UA-.NETStandard 2.0.1: Sessions, Reconnection, and Subscription Engines. أنّ
ManagedSessionتتضمّن آليّة مدمجة لإعادة الاتصال. ↩ -
OPC Foundation, OPC 10000-4 v1.05, 5.14.6.1 Republish. الخدمة التي تجعل رسائل الإخطار تُرسل مرّة أخرى من طابور إعادة الإرسال في الاشتراك. ↩
-
Modbus-IDA, MODBUS Application Protocol Specification V1.1b (نسخة موضوعة على خادم تابع لجامعة شيكاغو). نموذج البيانات في القسم 4.3 (Discrete Input وCoil وInput Register وHolding Register). ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
لماذا تتعطّل الوسائط ── قواعد وسائط سطر أوامر Windows
في Windows لا توجد مصفوفة وسائط؛ يصل إلى CreateProcess سلسلة واحدة ويتولّى الطرف المستقبل تقسيمها. نشرح قواعد CommandLineToArgvW وCRT و.N...
أفضل الممارسات العمليّة لتعدّد مؤشّرات الترابط: نسخة .NET ── ما تقرِّره قبل إضافة مؤشّرات ترابط
قواعد تصميم عمليّة تمنع شيفرة .NET/C# متعدّدة مؤشّرات الترابط من الانهيار أو التجمّد أحياناً: اركب على Task بدل إنشاء المؤشّرات بنفسك، قل...
استخدام WMI/CIM من C# وPowerShell ── دليل عمليّ لاسترجاع معلومات العتاد ومراقبة العمليّات والاستعلام عن بُعد
الإجابة الكلاسيكيّة للحصول على الرقم التسلسليّ للحاسوب، ومراقبة المساحة الحرّة على القرص، وكشف بدء عمليّة هي WMI/CIM. يشرح المقال أوامر C...
أعماق إدخال/إخراج Windows (الجزء 4) ── مدير الذاكرة المؤقّتة: متى يصل WriteFile فعلاً إلى القرص؟
الجزء 4 من سلسلة تشرح مدير الذاكرة المؤقّتة في Windows بالرسوم. نرتّب الذاكرة المؤقّتة المنفَّذة كتعيين ملفّ، والقراءة الاستباقيّة والكتا...
كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً
نوضح مفهوم «الجلسة» الذي يربك كثيراً من مطوّري تطبيقات Windows. نشرح عملياً سبب عزل Session 0 الذي يمنع الخدمة من عرض واجهة، وسلوك الجلسا...
مواضيع ذات صلة
صفحات مواضيع قريبة من موضوع هذه المقالة. وانطلاقًا من المقالة يمكنكم الانتقال إلى الخدمات ذات الصلة وإلى مقالات أخرى.
مواضيع ويندوز التقنية
مدخل إلى المواضيع التقنية في تطوير ويندوز والتحقيق في الأخطاء وإعادة استخدام الأصول القائمة.
الخدمات المرتبطة بهذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
يمكن تناول بناء تطبيق Windows يجمع البيانات من الأجهزة، بما في ذلك إعداد الاشتراكات وإعادة الاتصال وتسجيل الطوابع الزمنيّة والحالة، ضمن تطوير تطبيقات Windows.
الاستشارات التقنية ومراجعة التصميم
إذا أردت أن تقرّر هل تجمع البيانات عبر OPC UA أم تستخدم Modbus/TCP أو بروتوكول الشركة المصنّعة، وكيف تدير الشهادات، فيمكننا تناول ذلك كاستشارة تقنيّة ومراجعة للتصميم.
الأسئلة الشائعة
أسئلة شائعة نتلقّاها عند الاستشارة حول موضوع هذه المقالة.
- ما الفرق بين OPC UA وOPC Classic (OPC DA)؟
- OPC Classic مجموعة مواصفات تتبادل البيانات عبر COM/DCOM في Windows. أمّا OPC UA الذي نُشر عام 2008 فلا يعتمد على نظام تشغيل بعينه، ويجمع وظائف Classic في مواصفة واحدة. تعمل عملاء OPC UA خارج Windows أيضاً، ولا تحتاج إلى إعداد DCOM.
- عند جمع القيم عبر OPC UA، هل تكفي قراءتها على فترات ثابتة؟
- إذا كانت القيم قليلة والفترة طويلة فالقراءة تكفي. ولمتابعة تغيّر القيم يمكن استخدام الاشتراك: تترك أخذ العيّنات للخادم، فيُخطرك بالتغييرات. وفي الحالتين، سجّل رمز الحالة والطوابع الزمنيّة التي تعود مع كل قيمة.
- لماذا يظهر BadCertificateUntrusted عند الاتصال؟
- لأنّ العميل لا يثق بعدُ بشهادة الخادم. تحفظ مكتبة OPC Foundation لـ.NET الشهادات المرفوضة في مخزن rejected. تحقّق من بصمة الشهادة مع مسؤول الخادم، ثم انقلها إلى مخزن trusted. اقصر الإعداد الذي يقبل الشهادات تلقائياً على بيئات التطوير.
- ما رقم المنفذ الذي يستخدمه OPC UA؟
- تسجّل IANA المنفذ 4840/TCP باسم opcua-tcp. لكنّ الخوادم الفعليّة تستخدم أحياناً منفذاً آخر، فتحقّق منه في إعدادات الجهاز. إذا كان الخادم يعمل على Windows فيلزم السماح بالاتصال بذلك المنفذ في جدار حماية Windows، وإذا كانت بينهما أجهزة شبكة فعليها أيضاً.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
ممثل شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.