https://mp.weixin.qq.com/s/gZWIn-eNkq1Y_8UZgLjgOg
在工业自动化领域,Modbus协议凭借其简单易用、通信稳定的特点,得到了广泛的应用。然而,对于许多.NET开发者来说,如何快速、高效地实现Modbus协议的功能却是一个不小的挑战。今天,我们要为大家介绍一个强大的开源项目——FluentModbus,它使用C#编写,支持Modbus TCP/RTU协议,并且是一个轻量级、快速的.NET Standard库,可以无缝对接.net core项目,助力开发者轻松实现工业自动化应用。
FluentModbus简介
FluentModbus是一个使用C#编写的支持Modbus协议(TCP/RTU)的轻量级和快速客户端和服务端实现的开源项目。旨在提供一个简单、易用的Modbus协议实现方案。它采用C#语言编写,支持Modbus TCP和RTU两种通信方式,适用于各种工业自动化场景。作为一个.NET Standard库,FluentModbus可以轻松集成到.NET Core项目中,为开发者提供强大的Modbus通信能力。
FluentModbus项目结构
图片
FluentModbus功能特点
轻量级与快速:FluentModbus采用轻量级设计,代码简洁高效,运行速度快,能够满足工业自动化应用对实时性的要求。
支持多种功能码:FluentModbus服务端和客户端均实现了多种Modbus功能码,包括读取保持寄存器、写入多个寄存器、读取线圈、读取离散量、读取输入寄存器、写入单线圈、写入单个寄存器以及读取多个寄存器等,满足各种工业自动化通信需求。
易于使用:FluentModbus提供简洁易懂的API接口,开发者无需深入了解Modbus协议细节,即可快速上手并应用到项目中。
跨平台支持:作为一个.NET Standard库,FluentModbus支持在多种平台上运行,包括Windows、Linux和macOS等,为开发者提供更大的灵活性。
FluentModbus应用场景
FluentModbus适用于各种工业自动化应用场景,如PLC与上位机通信、传感器数据采集、设备监控与控制等。通过使用FluentModbus,开发者可以更加高效地实现Modbus协议的通信功能,提高工业自动化系统的稳定性和可靠性。
如何使用
nuget包安装
PS> dotnet add package FluentModbus
modbusTCP案例
首先,创建服务端和客户端实例以及记录器以将进度打印到控制台:
static async Task Main(string[] args)
{
/* create logger */
var loggerFactory = LoggerFactory.Create(loggingBuilder =>
{
loggingBuilder.SetMinimumLevel(LogLevel.Debug);
loggingBuilder.AddConsole();
});
var serverLogger = loggerFactory.CreateLogger("Server");
var clientLogger = loggerFactory.CreateLogger("Client");
/* create Modbus TCP server */
using var server = new ModbusTcpServer(serverLogger)
{
// see 'RegistersChanged' event below
EnableRaisingEvents = true
};
/* subscribe to the 'RegistersChanged' event (in case you need it) */
server.RegistersChanged += (sender, registerAddresses) =>
{
// the variable 'registerAddresses' contains a list of modified register addresses
};
/* create Modbus TCP client */
var client = new ModbusTcpClient();
图片
一切准备就绪后,首先启动服务器...
/* run Modbus TCP server */
var cts = new CancellationTokenSource();
var task_server = Task.Run(async () =>
{
server.Start();
serverLogger.LogInformation("Server started.");
while (!cts.IsCancellationRequested)
{
// lock is required to synchronize buffer access between this application and one or more Modbus clients
lock (server.Lock)
{
DoServerWork(server);
}
// update server buffer content once per second
await Task.Delay(TimeSpan.FromSeconds(1));
}
}, cts.Token);
...然后是客户端:
/* run Modbus TCP client */
var task_client = Task.Run(() =>
{
client.Connect();
try
{
DoClientWork(client, clientLogger);
}
catch (Exception ex)
{
clientLogger.LogError(ex.Message);
}
client.Disconnect();
Console.WriteLine("Tests finished. Press any key to continue.");
Console.ReadKey(intercept: true);
});
最后,等待客户端完成其工作,然后使用取消令牌源停止服务器。
// wait for client task to finish
await task_client;
// stop server
cts.Cancel();
await task_server;
server.Stop();
serverLogger.LogInformation("Server stopped.");
}
服务端操作
当服务端运行时,以下方法每秒定期执行一次。此方法显示服务器如何更新其寄存器以向客户端提供新数据。
static void DoServerWork(ModbusTcpServer server)
{
var random = new Random();
// Option A: normal performance version, more flexibility
/* get buffer in standard form (Span<short>) */
var registers = server.GetHoldingRegisters();
registers.SetLittleEndian<int>(startingAddress: 5, random.Next());
// Option B: high performance version, less flexibility
/* interpret buffer as array of bytes (8 bit) */
var byte_buffer = server.GetHoldingRegisterBuffer<byte>();
byte_buffer[20] = (byte)(random.Next() >> 24);
/* interpret buffer as array of shorts (16 bit) */
var short_buffer = server.GetHoldingRegisterBuffer<short>();
short_buffer[30] = (short)(random.Next(0, 100) >> 16);
/* interpret buffer as array of ints (32 bit) */
var int_buffer = server.GetHoldingRegisterBuffer<int>();
int_buffer[40] = random.Next(0, 100);
}
客户端操作
当客户端启动时,以下方法只执行一次。之后,客户端停止,并提示用户输入任何键以完成程序
static void DoClientWork(ModbusTcpClient client, ILogger logger)
{
Span<byte> data;
var sleepTime = TimeSpan.FromMilliseconds(100);
var unitIdentifier = 0x00;
var startingAddress = 0;
var registerAddress = 0;
// ReadHoldingRegisters = 0x03, // FC03
data = client.ReadHoldingRegisters<byte>(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC03 - ReadHoldingRegisters: Done");
Thread.Sleep(sleepTime);
// WriteMultipleRegisters = 0x10, // FC16
client.WriteMultipleRegisters(unitIdentifier, startingAddress, new byte[] { 10, 00, 20, 00, 30, 00, 255, 00, 255, 01 });
logger.LogInformation("FC16 - WriteMultipleRegisters: Done");
Thread.Sleep(sleepTime);
// ReadCoils = 0x01, // FC01
data = client.ReadCoils(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC01 - ReadCoils: Done");
Thread.Sleep(sleepTime);
// ReadDiscreteInputs = 0x02, // FC02
data = client.ReadDiscreteInputs<byte>(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC02 - ReadDiscreteInputs: Done");
Thread.Sleep(sleepTime);
// ReadInputRegisters = 0x04, // FC04
data = client.ReadInputRegisters(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC04 - ReadInputRegisters: Done");
Thread.Sleep(sleepTime);
// WriteSingleCoil = 0x05, // FC05
client.WriteSingleCoil(unitIdentifier, registerAddress, true);
logger.LogInformation("FC05 - WriteSingleCoil: Done");
Thread.Sleep(sleepTime);
// WriteSingleRegister = 0x06, // FC06
client.WriteSingleRegister(unitIdentifier, registerAddress, 127);
logger.LogInformation("FC06 - WriteSingleRegister: Done");
}
ModbusRTU案例
首先,创建服务器和客户端实例以及记录器以将进度打印到控制台
static async Task Main(string[] args)
{
/* define COM ports */
var serverPort = "COM1";
var clientPort = "COM2";
/* create logger */
var loggerFactory = LoggerFactory.Create(loggingBuilder =>
{
loggingBuilder.SetMinimumLevel(LogLevel.Debug);
loggingBuilder.AddConsole();
});
var serverLogger = loggerFactory.CreateLogger("Server");
var clientLogger = loggerFactory.CreateLogger("Client");
/* create Modbus RTU server */
using var server = new ModbusRtuServer(unitIdentifier: 1)
{
// see 'RegistersChanged' event below
EnableRaisingEvents = true
};
/* subscribe to the 'RegistersChanged' event (in case you need it) */
server.RegistersChanged += (sender, registerAddresses) =>
{
// the variable 'registerAddresses' contains a list of modified register addresses
};
/* create Modbus RTU client */
var client = new ModbusRtuClient();
一切准备就绪后,首先启动服务端.
/* run Modbus RTU server */
var cts = new CancellationTokenSource();
var task_server = Task.Run(async () =>
{
server.Start(serverPort);
serverLogger.LogInformation("Server started.");
while (!cts.IsCancellationRequested)
{
// lock is required to synchronize buffer access between this application and the Modbus client
lock (server.Lock)
{
DoServerWork(server);
}
// update server buffer content once per second
await Task.Delay(TimeSpan.FromSeconds(1));
}
}, cts.Token);
..然后是客户端:
/* run Modbus RTU client */
var task_client = Task.Run(() =>
{
client.Connect(clientPort);
try
{
DoClientWork(client, clientLogger);
}
catch (Exception ex)
{
clientLogger.LogError(ex.Message);
}
client.Close();
Console.WriteLine("Tests finished. Press any key to continue.");
Console.ReadKey(intercept: true);
});
最后,等待客户端完成其工作,然后使用取消令牌源停止服务端。
// wait for client task to finish
await task_client;
// stop server
cts.Cancel();
await task_server;
server.Stop();
serverLogger.LogInformation("Server stopped.");
}
服务端操作
当服务端运行时,以下方法每秒定期执行一次。此方法显示服务器如何更新其寄存器以向客户端提供新数据。
static void DoServerWork(ModbusRtuServer server)
{
var random = new Random();
// Option A: normal performance version, more flexibility
/* get buffer in standard form (Span<short>) */
var registers = server.GetHoldingRegisters();
registers.SetLittleEndian<int>(startingAddress: 5, random.Next());
// Option B: high performance version, less flexibility
/* interpret buffer as array of bytes (8 bit) */
var byte_buffer = server.GetHoldingRegisterBuffer<byte>();
byte_buffer[20] = (byte)(random.Next() >> 24);
/* interpret buffer as array of shorts (16 bit) */
var short_buffer = server.GetHoldingRegisterBuffer<short>();
short_buffer[30] = (short)(random.Next(0, 100) >> 16);
/* interpret buffer as array of ints (32 bit) */
var int_buffer = server.GetHoldingRegisterBuffer<int>();
int_buffer[40] = random.Next(0, 100);
}
客户端操作
当客户端启动时,以下方法只执行一次。之后,客户端停止,并提示用户输入任何键以完成程序
static void DoClientWork(ModbusRtuClient client, ILogger logger)
{
Span<byte> data;
var sleepTime = TimeSpan.FromMilliseconds(100);
var unitIdentifier = 0x01;
var startingAddress = 0;
var registerAddress = 0;
// ReadHoldingRegisters = 0x03, // FC03
data = client.ReadHoldingRegisters<byte>(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC03 - ReadHoldingRegisters: Done");
Thread.Sleep(sleepTime);
// WriteMultipleRegisters = 0x10, // FC16
client.WriteMultipleRegisters(unitIdentifier, startingAddress, new byte[] { 10, 00, 20, 00, 30, 00, 255, 00, 255, 01 });
logger.LogInformation("FC16 - WriteMultipleRegisters: Done");
Thread.Sleep(sleepTime);
// ReadCoils = 0x01, // FC01
data = client.ReadCoils(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC01 - ReadCoils: Done");
Thread.Sleep(sleepTime);
// ReadDiscreteInputs = 0x02, // FC02
data = client.ReadDiscreteInputs(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC02 - ReadDiscreteInputs: Done");
Thread.Sleep(sleepTime);
// ReadInputRegisters = 0x04, // FC04
data = client.ReadInputRegisters<byte>(unitIdentifier, startingAddress, 10);
logger.LogInformation("FC04 - ReadInputRegisters: Done");
Thread.Sleep(sleepTime);
// WriteSingleCoil = 0x05, // FC05
client.WriteSingleCoil(unitIdentifier, registerAddress, true);
logger.LogInformation("FC05 - WriteSingleCoil: Done");
Thread.Sleep(sleepTime);
// WriteSingleRegister = 0x06, // FC06
client.WriteSingleRegister(unitIdentifier, registerAddress, 127);
logger.LogInformation("FC06 - WriteSingleRegister: Done");
}
本文结语
FluentModbus最主要的还是提供了modbus服务端的模拟功能,对于没有硬件设备的上位机开发人员,尤其是刚学习上位机的人特别方便。当然也提供了人提供了丰富的功能码协议支持,让开发者能够轻松应对各Modbus通讯需求。当然,此项目还有更多的优势,本文只是简单使用,后续持续输出更多干货。另外本人应众多朋友的要求录了一系列的C#上位机应知应会的modbus协议课程,上传于b站上,讲义放在了知识星球上,感兴趣的可以去听听,希望对大家有帮助。