はじめに
備忘録です。たけびし (TAKEBISHI) 製の OPC サーバーである、デバイスエクスプローラ (DeviceXPlorer) に接続する OPC DA クライアントクラスを作ってみました。
改訂履歴
- 2026/05/31 : 初版公開。
本文
1. 環境
- .NET Framework 4.8.1
- Visual Studio Community 2022
- たけびし デバイスエクスプローラ OPC サーバー Ver.6.8.2.1 32 bit 版
2. 実装
接続・切断・読取・書込の機能を持った OPCAutomation のラッパークラスです。
OpcClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using OPCAutomation;
namespace TakebishiOpcClientExample
{
/// <summary>
/// OPC サーバーへの接続や操作を提供します。
/// </summary>
public sealed class OpcClient
{
private readonly object _lock = new object();
private readonly ILogger _logger;
private readonly Dictionary<int, string> _handleToTagId = new Dictionary<int, string>();
private readonly Dictionary<string, int> _tagIdToHandle = new Dictionary<string, int>();
private OPCServer _opcServer;
private OPCGroup _opcGroup;
/// <summary>
/// 接続中かどうかを示す値を取得します。
/// </summary>
public bool IsConnected { get; private set; } = false;
/// <summary>
/// <see cref="OpcClient"/> クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="logger">ログ出力を行う <see cref="ILogger"/> インスタンス。</param>
public OpcClient(ILogger logger)
{
_logger = logger;
}
/// <summary>
/// OPC サーバーへの接続を行います。
/// </summary>
/// <param name="opcServer">接続する OPC サーバーの名前。</param>
/// <param name="opcConnection">接続先の OPC サーバーのアドレス。</param>
/// <param name="opcGroup">OPC グループ。</param>
/// <param name="tagIds">追加するタグの ID のリスト。</param>
/// <returns>接続が成功した場合は true、それ以外の場合は false。</returns>
public bool Connect(
string opcServer,
string opcConnection,
string opcGroup,
List<string> tagIds)
{
try
{
lock (_lock)
{
if (tagIds == null || tagIds.Count == 0)
{
return false;
}
if (IsConnected)
{
Disconnect();
}
// OPC サーバーに接続する。
_opcServer = new OPCServer();
_opcServer.Connect(opcServer, opcConnection);
// 通信単位を追加する。
_opcGroup = _opcServer.OPCGroups.Add(opcGroup);
_opcGroup.UpdateRate = 50;
// OPC タグを管理するクラスを取得する。
OPCItems items = _opcGroup.OPCItems;
// OPC サーバーへ追加するタグのリストを作成する。
var tagIdsForOpc = new List<string> { string.Empty };
tagIds.ForEach(x => tagIdsForOpc.Add(x));
// 引数のタグからクライアントハンドルを作成する。
var clientHandles = new List<int>();
for (var i = 0; i < tagIdsForOpc.Count; i++)
{
clientHandles.Add(i);
}
// 引数のタグとクライアントハンドルから、サーバーハンドルを取得する。
items.AddItems(
tagIdsForOpc.Count - 1,
tagIdsForOpc.ToArray(),
clientHandles.ToArray(),
out Array serverHandles,
out Array errors);
// エラーの出力を行う。
var errorList = errors.Cast<int>().ToList();
for (var i = 0; i < errorList.Count; i++)
{
if (errorList[i] != 0)
{
_logger.LogError("An error occurred. Failed to add tag: {tag}", tagIdsForOpc[i]);
}
}
// エラーが 1 件でも発生した場合は、異常中断する。
if (errorList.Any(e => e != 0))
{
Disconnect();
return false;
}
// サーバーハンドルとタグの紐付けを行う。
var serverHandleList = serverHandles.Cast<int>().ToList();
for (var i = 0; i < serverHandleList.Count; i++)
{
_tagIdToHandle[tagIdsForOpc[i + 1]] = serverHandleList[i];
_handleToTagId[serverHandleList[i]] = tagIdsForOpc[i + 1];
}
IsConnected = true;
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
return false;
}
}
/// <summary>
/// 接続を切断します。
/// </summary>
/// <returns>切断が成功した場合は true、それ以外の場合は false。</returns>
public bool Disconnect()
{
try
{
lock (_lock)
{
if (_opcServer is null)
{
IsConnected = false;
return true;
}
try
{
if (_opcServer.OPCGroups != null)
{
// 各 OPC グループの通信を非アクティブにする。
foreach (OPCGroup group in _opcServer.OPCGroups)
{
if (group is null)
{
continue;
}
group.IsActive = false;
group.IsSubscribed = false;
}
// OPC グループを一括削除する。
_opcServer.OPCGroups.RemoveAll();
}
// OPC サーバーを切断する。
_opcServer.Disconnect();
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
}
finally
{
if (_opcGroup != null)
{
// OPC グループの解放を行う。
Marshal.ReleaseComObject(_opcGroup);
_opcGroup = null;
}
if (_opcServer != null)
{
// OPC サーバーの解放を行う。
Marshal.ReleaseComObject(_opcServer);
_opcServer = null;
}
// タグとハンドルの紐付けをクリアする。
_handleToTagId.Clear();
_tagIdToHandle.Clear();
// 切断状態に更新する。
IsConnected = false;
}
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
return false;
}
}
/// <summary>
/// OPC サーバーから値を読み取ります。
/// </summary>
/// <param name="tagIds">読み取るタグ ID のリスト。</param>
/// <param name="opcTags">読み取った値を格納する <see cref="OpcTag"/> のリスト。</param>
/// <returns>読み取りが成功した場合は true、それ以外の場合は false。</returns>
public bool ReadValue(List<string> tagIds, out List<OpcTag> opcTags)
{
opcTags = new List<OpcTag>();
try
{
lock (_lock)
{
if (!IsConnected || _opcGroup == null)
{
return false;
}
if (tagIds == null || tagIds.Count == 0)
{
return true;
}
// 引数のタグからサーバーハンドルを作成する。
var serverHandles = new List<int>() { 0 };
for (var i = 0; i < tagIds.Count; i++)
{
if (_tagIdToHandle.TryGetValue(tagIds[i], out var handle))
{
serverHandles.Add(handle);
}
else
{
_logger.LogError("An error occurred. Failed to get tag: {tagId}", tagIds[i]);
return false;
}
}
// OPC サーバーから値を読み取る。
_opcGroup.SyncRead(
(short)OPCDataSource.OPCCache,
serverHandles.Count - 1,
serverHandles.ToArray(),
out Array values,
out Array errors,
out var q,
out _);
// エラーの出力と戻り値の作成を行う。
var errorList = errors.Cast<int>().ToList();
var valueList = values.Cast<object>().ToList();
for (var i = 0; i < tagIds.Count; i++)
{
if (errorList[i] != 0)
{
_logger.LogError("An error occurred. Failed to read tag: {tag}", tagIds[i]);
}
opcTags.Add(new OpcTag
{
TagId = tagIds[i],
Value = valueList[i]
});
}
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
return false;
}
}
/// <summary>
/// OPC サーバーへ値を書き込みます。
/// </summary>
/// <param name="opcTags">書き込む <see cref="OpcTag"/> のリスト。</param>
/// <returns>書き込みが成功した場合は true、それ以外の場合は false。</returns>
public bool WriteValue(List<OpcTag> opcTags)
{
try
{
lock (_lock)
{
if (!IsConnected || _opcGroup == null)
{
return false;
}
if (opcTags == null || opcTags.Count == 0)
{
return true;
}
// 引数のタグからサーバーハンドルと書込値を作成する。
var serverHandles = new List<int> { 0 };
var values = new List<object> { null };
for (var i = 0; i < opcTags.Count; i++)
{
if (_tagIdToHandle.TryGetValue(opcTags[i].TagId, out int handle))
{
serverHandles.Add(handle);
values.Add(opcTags[i].Value);
}
else
{
_logger.LogError("An error occurred. Failed to get tag: {tagId}", opcTags[i].TagId);
return false;
}
}
// OPC サーバーへ値を書き込む。
_opcGroup.SyncWrite(
opcTags.Count,
serverHandles.ToArray(),
values.ToArray(),
out Array errors);
// エラーの出力を行う。
var errorList = errors.Cast<int>().ToList();
for (var i = 0; i < opcTags.Count; i++)
{
if (errorList[i] != 0)
{
_logger.LogError("An error occurred. Failed to write tag: {tag}", opcTags[i].TagId);
return false;
}
}
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
return false;
}
}
}
}
OpcTag.cs
namespace TakebishiOpcClientExample
{
/// <summary>
/// <see cref="OpcClient"/> クラスで使用される OPC タグ情報を表します。
/// </summary>
public class OpcTag
{
/// <summary>
/// OPC タグの識別子を取得または設定します。
/// </summary>
public string TagId { get; set; }
/// <summary>
/// OPC タグの値を取得または設定します。
/// </summary>
public object Value { get; set; }
}
}
3. 使用例
ざっくりと Windows Forms での使い方です。
MainForm.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace TakebishiOpcClientExample
{
public partial class MainForm : Form
{
OpcClient _opcClient;
public MainForm()
{
InitializeComponent();
}
// 接続
private void ConnectButton_Click(object sender, System.EventArgs e)
{
if (_opcClient == null)
{
_opcClient = new OpcClient(null);
}
if (!_opcClient.IsConnected)
{
_opcClient.Connect(
opcServer: "Takebishi.Dxp",
opcConnection: "localhost",
opcGroup: "Group1",
tagIds: new List<string>
{
"Device1.Tag00001",
"Device1.Tag00002",
"Device1.Tag00003",
"Device1.Tag00004",
});
}
}
// 切断
private void DisconnectButton_Click(object sender, EventArgs e)
{
_opcClient?.Disconnect();
}
// 読取
private void ReadButton_Click(object sender, EventArgs e)
{
var tagIds = new List<string>
{
"Device1.Tag00001",
"Device1.Tag00002",
"Device1.Tag00003",
"Device1.Tag00004",
};
if (_opcClient.ReadValue(tagIds, out List<OpcTag> result))
{
ValueBox1.Text = result[0].Value.ToString();
ValueBox2.Text = result[1].Value.ToString();
ValueBox3.Text = result[2].Value.ToString();
ValueBox4.Text = result[3].Value.ToString();
MessageBox.Show("成功しました。");
}
else
{
MessageBox.Show("失敗しました。");
}
}
// 書込
private void WriteButton_Click(object sender, EventArgs e)
{
var opcTags = new List<OpcTag>
{
new OpcTag { TagId = "Device1.Tag00001", Value = ValueBox1.Text },
new OpcTag { TagId = "Device1.Tag00002", Value = ValueBox2.Text },
new OpcTag { TagId = "Device1.Tag00003", Value = ValueBox3.Text },
new OpcTag { TagId = "Device1.Tag00004", Value = ValueBox4.Text }
};
if (_opcClient.WriteValue(opcTags))
{
MessageBox.Show("成功しました。");
}
else
{
MessageBox.Show("失敗しました。");
}
}
}
}
おわりに
扱いやすさを重視して、辞書型で都度紐付けを行っていますが、処理速度の要件に応じて適宜アレンジしてください。