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站上,讲义放在了知识星球上,感兴趣的可以去听听,希望对大家有帮助。

文档更新时间: 2024-06-10 14:46   作者:admin