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# McpX を使って MC プロトコル通信クラスを作ってみた

0
Posted at

はじめに

C# McpX を使って MC プロトコル通信クラスを作ってみました。

改訂履歴

  • 2026/08/19 : 初版公開。

本文

1. 環境

  • .NET 8.0
  • McpX 0.8.0
  • Microsoft.Extensions.Logging 10.0.11
  • Microsoft.Extensions.Logging.Abstractions 10.0.11
  • Microsoft.Extensions.Logging.Console 10.0.11
  • Visual Studio Community 2022

2. 実装

ソースは少し長いです。またデバイス割付周りの処理は適宜変更してください。

McpClient.cs
using McpXLib;
using McpXLib.Enums;
using Microsoft.Extensions.Logging;

namespace McpXExample;

/// <summary>
/// MC プロトコルを使用したクライアント接続を提供します。
/// </summary>
public interface IMcpClient : IDisposable
{
    /// <summary>
    /// 接続を開きます。
    /// </summary>
    /// <returns>接続に成功した場合は <see langword="true"/>、それ以外の場合は <see langword="false"/>。</returns>
    bool Open();

    /// <summary>
    /// 接続を閉じます。
    /// </summary>
    void Close();

    /// <summary>
    /// 監視対象のデバイスの値を取得します。
    /// </summary>
    /// <param name="devices">取得した <see cref="McpDevice"/> の一覧。</param>
    /// <returns>取得に成功した場合は <see langword="true"/>、それ以外の場合は <see langword="false"/>。</returns>
    bool Read(out List<McpDevice> devices);

    /// <summary>
    /// デバイスに値を書き込みます。
    /// </summary>
    /// <param name="device">書き込み対象のデバイス。</param>
    /// <returns>書き込みに成功した場合は <see langword="true"/>、それ以外の場合は <see langword="false"/>。</returns>
    bool Write(McpDevice device);
}

/// <summary>
/// MC プロトコルを使用したクライアント接続を提供します。
/// </summary>
/// <param name="logger">ロガー。</param>
/// <param name="options">接続に使用する <see cref="McpClientOptions"/>。</param>
public sealed class McpClient(ILogger<McpClient> logger, McpClientOptions options) : IMcpClient
{
    private const string ErrorMessage = "An error occurred.";

    private static readonly (Prefix Prefix, string Address)[] s_wordAddresses =
    [
        (Prefix.D, "8000"),
        (Prefix.D, "8010")
    ];

    private static readonly (Prefix Prefix, string Address)[] s_doubleWordAddresses =
    [
        (Prefix.D, "8020"),
        (Prefix.D, "8030")
    ];

    private readonly ILogger<McpClient> _logger = logger;
    private readonly McpClientOptions _options = options;
    private McpX? _mcpx;

    /// <inheritdoc/>
    public bool Open()
    {
        if (_mcpx is not null)
        {
            return true;
        }

        try
        {
            // McpX クラスのインスタンスを作成する。
            var mcpx = new McpX(
                _options.Ip,
                _options.Port,
                _options.Password,
                _options.IsAscii,
                _options.IsUdp,
                _options.RequestFrame,
                _options.TimeoutMilliseconds);

            try
            {
                // モニタ登録要求 (0801) で監視対象のアドレスを登録する。
                mcpx.MonitorRegist(
                    wordAddresses: s_wordAddresses,
                    doubleWordAddresses: s_doubleWordAddresses);

                // 処理が完了した場合のみ、メンバ変数に格納する。
                _mcpx = mcpx;
                return true;
            }
            catch
            {
                mcpx.Dispose();
                throw;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ErrorMessage);
            return false;
        }
    }

    /// <inheritdoc/>
    public void Close()
    {
        _mcpx?.Dispose();
        _mcpx = null;
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        Close();
    }

    /// <inheritdoc/>
    public bool Read(out List<McpDevice> devices)
    {
        if (_mcpx is null)
        {
            devices = [];
            return false;
        }

        try
        {
            devices = CreateDevices();
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ErrorMessage);
            devices = [];
            return false;
        }
    }

    /// <inheritdoc/>
    public bool Write(McpDevice device)
    {
        if (_mcpx is null)
        {
            return false;
        }

        try
        {
            switch (device.DataType)
            {
                case McpDeviceDataType.Int16:
                    _mcpx.Write(
                        device.Prefix,
                        device.Address,
                        short.Parse(device.Value));
                    break;

                case McpDeviceDataType.Int32:
                    _mcpx.Write(
                        device.Prefix,
                        device.Address,
                        int.Parse(device.Value));
                    break;

                default:
                    throw new ArgumentOutOfRangeException(
                        nameof(device),
                        device.DataType,
                        null);
            }

            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ErrorMessage);
            return false;
        }
    }

    /// <summary>
    /// <see cref="McpDevice"/> の一覧を作成します。
    /// </summary>
    /// <returns>作成した <see cref="McpDevice"/> の一覧。</returns>
    private List<McpDevice> CreateDevices()
    {
        // モニタ取得要求 (0802) で監視対象のアドレスから値を取得する。
        (short[] wordValues, int[] doubleWordValues) =
            _mcpx!.Monitor<short, int>(
                wordAddresses: s_wordAddresses,
                doubleWordAddresses: s_doubleWordAddresses);

        var devices = new List<McpDevice>(s_wordAddresses.Length + s_doubleWordAddresses.Length);

        // 取得した 1 WORD をリストに追加する。
        for (int i = 0; i < s_wordAddresses.Length; i++)
        {
            devices.Add(new McpDevice
            {
                Prefix = s_wordAddresses[i].Prefix,
                Address = s_wordAddresses[i].Address,
                DataType = McpDeviceDataType.Int16,
                Value = wordValues[i].ToString(),
            });
        }

        // 取得した 2 WORD をリストに追加する。
        for (int i = 0; i < s_doubleWordAddresses.Length; i++)
        {
            devices.Add(new McpDevice
            {
                Prefix = s_doubleWordAddresses[i].Prefix,
                Address = s_doubleWordAddresses[i].Address,
                DataType = McpDeviceDataType.Int32,
                Value = doubleWordValues[i].ToString(),
            });
        }

        // リストをアドレス順にソートして返す。
        devices.Sort(static (x, y) => string.CompareOrdinal(x.Address, y.Address));
        return devices;
    }
}
McpDevice.cs
using McpXLib.Enums;

namespace McpXExample;

/// <summary>
/// <see cref="McpDevice"/> で使用するデータ型を指定します。
/// </summary>
public enum McpDeviceDataType
{
    /// <summary>
    /// 16 ビット整数型。
    /// </summary>
    Int16,

    /// <summary>
    /// 32 ビット整数型。
    /// </summary>
    Int32
}

/// <summary>
/// <see cref="McpClient"/> で使用するデバイスを表します。
/// </summary>
public sealed class McpDevice
{
    /// <summary>
    /// デバイスの接頭辞を取得します。
    /// </summary>
    public required Prefix Prefix { get; init; }
    
    /// <summary>
    /// デバイスのアドレスを取得します。
    /// </summary>
    public required string Address { get; init; }

    /// <summary>
    /// デバイスの <see cref="McpDeviceDataType"/> を取得します。
    /// </summary>
    public required McpDeviceDataType DataType { get; init; }

    /// <summary>
    /// デバイスの値を取得します。
    /// </summary>
    public required string Value { get; init; }
}
McpClientOptions.cs
using McpXLib.Enums;

namespace McpXExample;

/// <summary>
/// <see cref="McpClientOptions"/> を提供します。
/// </summary>
public interface IMcpClientOptionsProvider
{
    /// <summary>
    /// <see cref="McpClientOptions"/> を読み込みます。
    /// </summary>
    /// <returns>読み込んだ <see cref="McpClientOptions"/>。</returns>
    McpClientOptions Load();
}

/// <summary>
/// <see cref="McpClientOptions"/> を提供します。
/// </summary>
public sealed class McpClientOptionsProvider() : IMcpClientOptionsProvider
{
    /// <inheritdoc/>
    public McpClientOptions Load()
    {
        // ※ここでファイル等から取得する。
        return new McpClientOptions
        {
            Ip = "127.0.0.1",
            Port = 5000
        };
    }
}

/// <summary>
/// <see cref="McpClient"/> で使用する構成を提供します。
/// </summary>
public sealed class McpClientOptions
{
    /// <summary>
    /// PLC の IP アドレスを取得します。
    /// </summary>
    public required string Ip { get; init; }

    /// <summary>
    /// PLC のポート番号を取得します。
    /// </summary>
    public required int Port { get; init; }

    /// <summary>
    /// PLC への接続に使用するパスワードを取得します。
    /// </summary>
    public string? Password { get; init; }

    /// <summary>
    /// ASCII 形式を使用するかどうかを示す値を取得します。
    /// </summary>
    public bool IsAscii { get; init; }

    /// <summary>
    /// UDP 通信を使用するかどうかを示す値を取得します。
    /// </summary>
    public bool IsUdp { get; init; }

    /// <summary>
    /// 使用する要求フレーム形式を取得します。
    /// </summary>
    public RequestFrame RequestFrame { get; init; } = RequestFrame.E3;

    /// <summary>
    /// タイムアウト時間をミリ秒単位で取得します。
    /// </summary>
    public ushort TimeoutMilliseconds { get; init; } = 5000;
}

3. 使用例

使用例です。メインループの最初で READ 値のスナップショットを取得し、その取得内容に応じて、処理を行うイメージです。

Program.cs
using McpXLib.Enums;
using Microsoft.Extensions.Logging;

namespace McpXExample;

/// <summary>
/// アプリケーションのエントリーポイントを定義します。
/// </summary>
internal static class Program
{
    /// <summary>
    /// アプリケーションのメインエントリポイントです。
    /// </summary>
    [STAThread]
    internal static void Main()
    {
        // サンプルのため LoggerFactory を直接生成して ILogger<McpClient> を取得する。
        using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger<McpClient> logger = loggerFactory.CreateLogger<McpClient>();

        // McpClient を生成する。
        IMcpClientOptionsProvider optionsProvider = new McpClientOptionsProvider();
        McpClientOptions options = optionsProvider.Load();
        using IMcpClient client = new McpClient(logger, options);

        // メインループの開始。
        while (true)
        {
            Thread.Sleep(1000);

            // 未接続の場合、接続を開く。
            if (!client.Open())
            {
                continue;
            }

            // デバイス情報を読み取る。
            if (!client.Read(out List<McpDevice> devices))
            {
                client.Close();
                continue;
            }

            Console.WriteLine($"Succeeded to read {devices.Count} devices.");

            foreach (McpDevice device in devices)
            {
                Console.WriteLine(
                    $"Prefix: {device.Prefix}, " +
                    $"Address: {device.Address}, " +
                    $"DataType: {device.DataType}, " +
                    $"Value: {device.Value}");

                // D8000 の値が 0 の場合、D8000 に 1 を書き込む。
                if (device.Prefix == Prefix.D
                    && device.Address == "8000"
                    && device.DataType == McpDeviceDataType.Int16
                    && device.Value == "0")
                {
                    var writeDevice = new McpDevice
                    {
                        Prefix = device.Prefix,
                        Address = device.Address,
                        DataType = device.DataType,
                        Value = "1",
                    };

                    if (!client.Write(writeDevice))
                    {
                        client.Close();
                        break;
                    }
                }
            }
        }
    }
}

おわりに

手元に実機はないので、細かくは確認できませんでした。

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?