C#/. NET産業オートメーション開発完全ガイド:エンタープライズクラスの産業用アプリケーションの構築
産業オートメーションにおけるC#/. NETの利点
C#/. NETプラットフォームは、強力な型システム、豊富なクラスライブラリ、優れたパフォーマンス、マイクロソフトの包括的なサポートにより、エンタープライズクラスの産業オートメーションの世界で重要な地位を占めています。従来のWindowsデスクトップアプリケーションから最新のクラウドネイティブマイクロサービスまで、C#/. NETは完全なソリューションスタックを提供し、特に高信頼性、高性能、長期メンテナンスを必要とする産業用アプリケーションに最適です。
この記事では、NModbusライブラリの使用、WPF/Win Formsインターフェイスの開発、エンタープライズアーキテクチャの設計、プロフェッショナルグレードの産業オートメーションシステムの構築方法を示す実践的なケーススタディに焦点を当て、産業オートメーションにおけるC#/. NETの使用について深く掘り下げます。
C#/. NET産業開発環境の設定
1.1開発環境の要件
<!-- . NET SDKのバージョン要件->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroupの略。>
<TargetFramework Net 8.0/TargetFramework>
<OutputType Exe/OutputType>
<Nullable/Nullable>
</PropertyGroup>
<プロジェクト/Project>
1.2プロジェクト構造と依存構成
<!-- IndustrialAutomation.csproj-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroupの略。>
<WinExe/OutputTypeの設定>
<TargetFramework net 8.0-Windows/TargetFramework>
<UseWPF true/UseWPF>
<Nullable/Nullable>
</PropertyGroup>
<ItemGroup>
<!-- ModbusCommunication ->
<PackageReference Include="NModbus" Version="3.0.73" />
<PackageReference Include="NModbus.Serial" Version="3.0.73" />
<!--シリアルCommunication-->
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<!--ログ--->
<PackageReference Include="Serilog"バージョン="3.1.1" />
<PackageReference Include="Serilog. Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<!--依存性注入 ->
<PackageReference Include="Microsoft. Extension.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<!--構成管理 ->
<PackageReference Include="Microsoft. Extension.Configuration" Version="8.0.0" />
<PackageIn =". Extons.ation.Json" Version="8.0.0" />
<!--データ検証->
<PackageReference Include="FluentValidation" Version="11.8.0" />
<!--ユニットテスト-->
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" />
<PackageReference Include="Moq" Version="4.20.69" />
</ItemGroup>
</Project>
1.3プロジェクトのディレクトリ構造
産業オートメーション/
略称はSRC。
│ │ │ カテゴリー:Industrial Automation.Core/ コアビジネスロジック
│ │ │ │ │ │ あなたは-モデル。 #データモデル
│ │ │ │ │ │ あなた-サービス/ #ビジネスサービス
│ │ │ │ │ │ 略称は“インターフェイス”。 #インターフェースの定義
│ │ │ │ │ │ 3-例外とは #例外の定義
│ │ │ │ │ │
│ │ │ Industrial Automation.Modbusの略。 # ModbusCommunicationモジュール
│ │ │ │ │ │ クライアント-クライアント。 # Modbusクライアント
│ │ │ │ │ │ あなたはサーバーです。 # Modbusサーバ
│ │ │ │ │ │ そして、Converters/ #データコンバータ
│ │ │ │ │ │
│ │ │ カテゴリー:Industrial Automation.UI #ユーザーインターフェース
│ │ │ │ │ │ あなたの意見-ビュー # WPFビュー
│ │ │ │ │ │ タイトル:ViewModels # MVVMビューモデル
│ │ │ │ │ │ あなたはConvertersです。 #値変換器
│ │ │ │ │ │ 10.“Be haviors” #振る舞いは
│ │ │ │ │ │
│ │ │ 6--Industrial Automation.Host/ #アプリケーションホスト
│ │ │
あなた-テスト/テスト
│ │ │ カテゴリー:Industrial Automation.Core.Tests
│ │ │ カテゴリー:Industrial Automation.Modbus.Tests
│ │ │ 産業オートメーション-統合-テスト/
│ │ │
>> config/とは
│ │ │ >> appsettings.json #設定の適用
│ │ │ appsettings.development.json #開発環境の設定
│ │ │ >> devices.json #装備の配置
│ │ │
それは...ドキュメント。 #プロジェクトファイル
2. NModbusライブラリの深い使用
2.1 Modbus TCPクライアント
NModbusの使用
NModbus.Deviceを使用する
System.Net.Socketsの使用
名前空間産業オートメーション.Modbus.Clients
{
//summary>
// Modbus TCPクライアント
////summary>
public class ModbusTcpClient IModbusClient、IDisposable
{
private readonly ILogger ModbusTcpclient_logger;
private readonly Tcpclient_tcpClient;
ate modbusmaster;
private bool_disposed;
//summary>
///デバイス接続ステータス
////summary>
public bool IsConnected =_tcpClient. Connected()偽である。
//summary>
///デバイスのIPアドレス
////summary>
public string IpAddress { get; }
//summary>
//デバイスのポート
////summary>
public int Port { get; }
//summary>
Modbus TCPクライアントの初期化
////summary>
public ModbusTcpClient
string ipAddress、
int port = 502
ILogger ModbusTcpClient logger = null
{
IpAddress = ipAddress throw new ArgumentNullException nameof ipAddress;
ポート=ポート;
_logger = logger NullLogger ModbusTcpClient.Instance;
_tcpClient = new TcpClient();
}
/// <summary>
/// 连接到设备
/// </summary>
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("正在连接到设备 {IpAddress}:{Port}", IpAddress, Port);
await _tcpClient.ConnectAsync(IpAddress, Port, cancellationToken);
var factory = new ModbusFactory();
_modbusMaster = factory.CreateMaster(_tcpClient);
// 设置超时时间
_modbusMaster.Transport.ReadTimeout = 3000;
_modbusMaster.Transport.WriteTimeout = 3000;
_logger.LogInformation("成功连接到设备 {IpAddress}:{Port}", IpAddress, Port);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "连接设备 {IpAddress}:{Port} 失败", IpAddress, Port);
return false;
}
}
/// <summary>
/// Read and hold register
/// </summary>
public async Task<ushort[]> ReadHoldingRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Read and hold register - slave: {SlaveAddress}, address: {StartAddress}, Quantity: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"读取保持寄存器成功 - slave: {SlaveAddress}, address: {StartAddress}, data: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"读取保持寄存器失败 - slave: {SlaveAddress}, address: {StartAddress}, Quantity: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"读取保持寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入单个保持寄存器
/// </summary>
public async Task WriteSingleRegisterAsync(
byte slaveAddress,
ushort registerAddress,
ushort value,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Write to a single register - slave: {SlaveAddress}, address: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
await Task.Run(() =>
_modbusMaster!.WriteSingleRegister(slaveAddress, registerAddress, value),
cancellationToken);
_logger.LogDebug(
"写入单个寄存器成功 - slave: {SlaveAddress}, address: {RegisterAddress}",
slaveAddress, registerAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"写入单个寄存器失败 - slave: {SlaveAddress}, address: {RegisterAddress}, 值: {Value}",
slaveAddress, registerAddress, value);
throw new ModbusCommunicationException(
$"写入单个寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入多个保持寄存器
/// </summary>
public async Task WriteMultipleRegistersAsync(
byte slaveAddress,
ushort startAddress,
ushort[] data,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"Write multiple registers - slave: {SlaveAddress}, address: {StartAddress}, 数据长度: {Length}",
slaveAddress, startAddress, data.Length);
await Task.Run(() =>
_modbusMaster!.WriteMultipleRegisters(slaveAddress, startAddress, data),
cancellationToken);
_logger.LogDebug(
"写入多个寄存器成功 - slave: {SlaveAddress}, address: {StartAddress}",
slaveAddress, startAddress);
}
catch (Exception ex)
{
_logger.LogError(ex,
"写入多个寄存器失败 - slave: {SlaveAddress}, address: {StartAddress}, 数据长度: {Length}",
slaveAddress, startAddress, data.Length);
throw new ModbusCommunicationException(
$"写入多个寄存器失败: {ex.Message}", ex);
}
}
/// <summary>
/// Read coil status
/// </summary>
public async Task<bool[]> ReadCoilsAsync(
byte slaveAddress,
ushort startAddress,
ushort numberOfPoints,
CancellationToken cancellationToken = default)
{
ValidateConnection();
try
{
_logger.LogDebug(
"读取线圈 - slave: {SlaveAddress}, address: {StartAddress}, Quantity: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
var result = await Task.Run(() =>
_modbusMaster!.ReadCoils(slaveAddress, startAddress, numberOfPoints),
cancellationToken);
_logger.LogDebug(
"读取线圈成功 - slave: {SlaveAddress}, address: {StartAddress}, data: {Data}",
slaveAddress, startAddress, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,
"读取线圈失败 - slave: {SlaveAddress}, address: {StartAddress}, Quantity: {NumberOfPoints}",
slaveAddress, startAddress, numberOfPoints);
throw new ModbusCommunicationException(
$"读取线圈失败: {ex.Message}", ex);
}
}
/// <summary>
/// 批量读取优化
/// </summary>
public async Task<Dictionary<ushort, ushort[]>> BatchReadRegistersAsync(
byte slaveAddress,
IEnumerable<RegisterReadRequest> requests,
CancellationToken cancellationToken = default)
{
ValidateConnection();
var results = new Dictionary<ushort, ushort[]>();
foreach (var request in requests)
{
try
{
var data = await ReadHoldingRegistersAsync(
slaveAddress,
request.StartAddress,
request.NumberOfPoints,
cancellationToken);
results[request.StartAddress] = data;
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"批量读取失败 - address: {StartAddress}, Quantity: {NumberOfPoints}",
request.StartAddress, request.NumberOfPoints);
results[request.StartAddress] = Array.Empty<ushort>();
}
}
return results;
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
try
{
_modbusMaster?.Dispose();
_modbusMaster = null;
if (_tcpClient.Connected)
{
_tcpClient.Close();
}
_logger.LogInformation("已断开与设备 {IpAddress}:{Port} 的连接", IpAddress, Port);
}
catch (Exception ex)
{
_logger.LogError(ex, "断开连接时发生错误");
}
}
/// <summary>
/// 验证连接状态
/// </summary>
private void ValidateConnection()
{
if (!IsConnected || _modbusMaster == null)
{
throw new InvalidOperationException("设备未连接");
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize this;
}
protected virtual void Dispose bool disposing
{
if!_ disposed
{
if disposing
{
Disconnect Disconnect nParticle nParticle;
_tcpClient.Dispose;
}
disposed = true;
}
}
}
//summary>
///レジスタ読み取り要求
////summary>
public record RegisterReadRequest
ushort StartAddress、
ushort NumberOfPoints;
}
2.2 Modbus RTUクライアント
```csharp
using NModbus;
using NModbus.Serial;
using System.IO.Ports;
namespace IndustrialAutomation.Modbus.Clients
{
///
// Modbus RTUクライアント
///public class ModbusRtuClient : IModbusClient, IDisposable
{
private readonly ILogger
_logger;
private readonly SerialPort _serialPort;
private IModbusMaster? _modbusMaster;
private bool _disposed;//summary>
///シリアル接続ステータス
////summary>
public bool IsConnected =_serialPort. IsOpen偽である。
//summary>
///シリアル名
////summary>
public string PortName { get; }
//summary>
///ボーレート
////summary>
public int baud rate {}
//summary>
Modbus RTUクライアントの初期化
////summary>
public ModbusRClient
string portName、
baudRate = 96 00です。
ILogger ModbusRtuClient logger = null
{
portName = portName throw new ArgumentNullException nameof portName;
BaudRate = baudRate;
_logger = logger NullLogger ModbusRtuClient.Instance;
_serialPort = new SerialPort portName
{
BaudRate = baudRate
DataBits = 8、
Parity = Parity.
StopBits = StopBits.One
ReadTimeout = 1000
WriteTimeout = 1000
};
}
//summary>
///シリアルデバイスに接続
////summary>
public async bool Async ellationcancellation =
{
try tryより
{
_logger.LogInformation "シリアルポート{PortName}ボーレート{BaudRate}" PortName BaudRate;
await Task.Run =>
{
if!_ serialPort.IsOpen
{
_serialPort.Open Open nParticle nParticle;
}
} cancellationToken;
var factory = new ModbusFactory;
var adapter = new SerialPortAdapter_serialPort;
_modbusMaster = factory. RMaster adapter;
//Modbusパラメータの設定
_modbusMaster.Transport.ReadTimeout = 1000;
_modbusMaster.Transport.WriteTimeout = 1000;
_modbusMaster.Transport.Retries = 3;
_modbusMaster.Transport.WaitToRetryMilliseconds = 100;
_logger.LogInformation "シリアルポート{PortName}が正常にオープンされました" PortName;
真の帰還;
}
catch Exception ex
{
_logger.LogError ex "シリアルポート{PortName}のオープンに失敗しました" PortName;
偽を返す。
}
}
//summary>
///シリアルデバイスのスキャン
////summary>
public async List byte DevicesAsync
byte startAddress = 1
byte endAddress = 247、
ellation
{
var found = new List byte;
_logger.LogInformation "スキャンデバイスアドレス范囲{StartAddress}-{EndAddress}" Address endAddress;
for byte slAddress = Address; slAddress = endAddress; slAddress ++
{
if cancellationToken.IsCancellationRequested
{
休憩を取る。
}
try tryより
{
//デバイスIDの読み取りを試みる
var result = await Task.Run(() =>
_modbusMaster!.ReadDeviceIdentification(slaveAddress, 0x01),
cancellationToken);
if (result != null)
{
foundDevices.Add(slaveAddress);
_logger.LogInformation("
Leave a Reply