はじめに
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;
}
}
}
}
}
}
おわりに
手元に実機はないので、細かくは確認できませんでした。