Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# OPCAutomation を使って OPC DA クライアントクラスを作ってみた

0
Posted at

はじめに

備忘録です。たけびし (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("失敗しました。");
            }
        }
    }
}

おわりに

扱いやすさを重視して、辞書型で都度紐付けを行っていますが、処理速度の要件に応じて適宜アレンジしてください。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?