EonaCat.Modbus 1.2.0

The ID prefix of this package has been reserved for one of the owners of this package by NuGet.org. Prefix Reserved
dotnet add package EonaCat.Modbus --version 1.2.0
NuGet\Install-Package EonaCat.Modbus -Version 1.2.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.Modbus" Version="1.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EonaCat.Modbus --version 1.2.0
#r "nuget: EonaCat.Modbus, 1.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install EonaCat.Modbus as a Cake Addin
#addin nuget:?package=EonaCat.Modbus&version=1.2.0

// Install EonaCat.Modbus as a Cake Tool
#tool nuget:?package=EonaCat.Modbus&version=1.2.0

EonaCat.Modbus

Below you can find a console example for a client and server: (Tcp and Serial (RS485))

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

using System.IO.Ports;
using System.Net;
using System.Text;
using EonaCat.Modbus.Extensions;
using EonaCat.Modbus.Interfaces;
using EonaCat.Modbus.Models;
using EonaCat.Modbus.Serial;
using SerialClient = EonaCat.Modbus.Serial.Client.ModbusClient;
using SerialOverTcpClient = EonaCat.Modbus.SerialOverTcp.Client.ModbusClient;
using TcpClient = EonaCat.Modbus.Tcp.Client.ModbusClient;
using SerialServer = EonaCat.Modbus.Serial.Server.ModbusServer;
using TcpServer = EonaCat.Modbus.Tcp.Server.ModbusServer;

namespace EonaCat.Modbus.Tester;

internal class Program
{
    private static readonly string[] _trueList = { "y", "j", "yes", "ja" };

    private static async Task<int> Main(string[] _)
    {
        var cancellationTokenSource = new CancellationTokenSource();
        Console.CancelKeyPress += (sender, consoleCancelEventArgs) =>
        {
            cancellationTokenSource.Cancel();
            consoleCancelEventArgs.Cancel = true;
        };

        Console.WriteLine("EonaCat Modbus Tester");
        Console.WriteLine();

        try
        {
            Console.Write("What do you want? [1] Client, [2] Server: ");
            var type = Convert.ToInt32(Console.ReadLine().Trim());

            return type switch
            {
                1 => await RunClientAsync(cancellationTokenSource.Token).ConfigureAwait(false),
                2 => await RunServerAsync(cancellationTokenSource.Token).ConfigureAwait(false),
                _ => throw new ArgumentException($"Unknown option: {type}")
            };
        }
        catch (Exception ex)
        {
            Console.WriteLine($"App terminated unexpectedly: {ex.InnerException?.Message ?? ex.Message}");
            return 1;
        }
    }

    private static async Task<int> RunClientAsync(CancellationToken cancellationToken)
    {
        Console.Write("Connection Type [1] Tcp, [2] RS485 [3] SerialOverTcp");
        var connectionType = Convert.ToInt32(Console.ReadLine().Trim());

        IModbusClient client = null;
        try
        {
            client = connectionType switch
            {
                1 => CreateTcpClient(),
                2 => CreateSerialClient(),
                3 => CreateSerialOverTcpClient(),
                _ => throw new ArgumentException($"Unknown type: {connectionType}")
            };

            await ConnectClientAsync(client, cancellationToken).ConfigureAwait(false);

            while (!cancellationToken.IsCancellationRequested)
            {
                var id = GetUserInput<byte>("Device ID");
                var fn = GetUserInput<int>("Function [1] Read Register, [2] Write Register, [3] Device Info");

                switch (fn)
                {
                    case 1:
                        await ProcessReadRegisterAsync(client, id, cancellationToken).ConfigureAwait(false);
                        break;

                    case 2:
                        await ProcessWriteRegistersAsync(client, id).ConfigureAwait(false);
                        break;

                    case 3:
                        await ProcessDeviceInfoAsync(client, id).ConfigureAwait(false);
                        break;

                    default:
                        Console.WriteLine("Invalid function selection");
                        break;
                }

                Console.WriteLine("New Request? [y/N]: ");
                var again = Console.ReadLine().Trim().ToLower();
                if (!_trueList.Contains(again))
                {
                    return 0;
                }
            }

            return 0;
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            return 0;
        }
        finally
        {
            Console.WriteLine("Disposing");
            client?.Dispose();
            Console.WriteLine("Disposed");
        }
    }

    private static IModbusClient CreateSerialOverTcpClient()
    {
        Console.Write("Hostname: ");
        var host = Console.ReadLine().Trim();
        Console.Write("Port: ");
        var port = Convert.ToInt32(Console.ReadLine().Trim());
        return new SerialOverTcpClient(host, port);
    }

    private static TcpClient CreateTcpClient()
    {
        Console.Write("Hostname: ");
        var host = Console.ReadLine().Trim();
        Console.Write("Port: ");
        var port = Convert.ToInt32(Console.ReadLine().Trim());
        return new TcpClient(host, port);
    }

    private static SerialClient CreateSerialClient()
    {
        Console.Write("Interface: ");
        var port = Console.ReadLine().Trim();
        var baudRate = GetUserInput<int>("Baud");
        var stopBits = GetUserInput<int>("Stop-Bits [0|1|2|3=1.5]");
        var parity = GetUserInput<int>("Parity [0] None [1] Odd [2] Even [3] Mark [4] Space");
        var handshake = GetUserInput<int>("Handshake [0] None [1] X-On/Off [2] RTS [3] RTS+X-On/Off");
        var timeout = GetUserInput<int>("Timeout (ms)");
        var setDriver = GetUserInput<int>("Set Driver to RS485 [0] No [1] Yes");

        var client = new SerialClient(port)
        {
            BaudRate = (BaudRate)baudRate,
            DataBits = 8,
            StopBits = (StopBits)stopBits,
            Parity = (Parity)parity,
            Handshake = (Handshake)handshake,
            SendTimeout = TimeSpan.FromMilliseconds(timeout),
            ReceiveTimeout = TimeSpan.FromMilliseconds(timeout)
        };

        if (setDriver == 1)
        {
            client.DriverEnableRs485 = true;
        }

        return client;
    }

    private static async Task ConnectClientAsync(IModbusClient client, CancellationToken cancellationToken)
    {
        await Task.WhenAny(client.Connect(cancellationToken), Task.Delay(Timeout.Infinite, cancellationToken)).ConfigureAwait(false);
        if (cancellationToken.IsCancellationRequested)
        {
            Environment.Exit(0);
        }
    }

    private static async Task ProcessReadRegisterAsync(IModbusClient client, byte id,
        CancellationToken cancellationToken = default)
    {
        ushort address = 0;
        ushort count = 1;

        Console.WriteLine();
        Console.Write("Address : ");
        address = GetUserInput<ushort>();
        Console.Write("DataType: ");
        var type = Console.ReadLine().Trim();
        if (type == "string")
        {
            Console.Write("Register Count: ");
            count = GetUserInput<ushort>();
        }

        Console.WriteLine();
        Console.Write("Run as loop? [y/N]: ");
        var loop = Console.ReadLine().Trim().ToLower();
        var interval = _trueList.Contains(loop) ? GetUserInput<int>("Loop interval (milliseconds)") : 0;

        Console.WriteLine();
        do
        {
            try
            {
                Console.Write("Result  : ");
                var result = await ReadAndPrintResultAsync(client, id, type, address, count).ConfigureAwait(false);
            }
            catch
            {
                // Do nothing
            }

            await Task.Delay(TimeSpan.FromMilliseconds(interval), cancellationToken).ConfigureAwait(false);
        } while (interval > 0 && !cancellationToken.IsCancellationRequested);
    }

    private static async Task ProcessDeviceInfoAsync(IModbusClient client, byte id)
    {
        Console.Write("[1] Basic, [2] Regular, [3] Extended: ");
        var cat = GetUserInput<int>();

        Dictionary<DeviceIdObject, string> info = null;
        switch (cat)
        {
            case 1:
            case 2:
            case 3:
                info = await client.ReadDeviceInformation(id, (DeviceIdCategory)cat,
                    cancellationToken: CancellationToken.None).ConfigureAwait(false);
                break;
        }

        PrintDeviceInfo(info);
    }

    private static async Task ProcessWriteRegistersAsync(IModbusClient client, byte id)
    {
        Console.Write("Address: ");
        var address = GetUserInput<ushort>();

        Console.Write("Bytes (HEX): ");
        var byteStr = Console.ReadLine().Trim().Replace(" ", "").ToLower();

        var bytes = Enumerable.Range(0, byteStr.Length)
            .Where(i => i % 2 == 0)
            .Select(i => Convert.ToByte(byteStr.Substring(i, 2), 16))
            .ToArray();

        var registers = Enumerable.Range(0, bytes.Length / 2)
            .Select(i => new Register
            {
                Type = ModbusObjectType.HoldingRegister,
                Address = (ushort)(address + i),
                HighByte = bytes[i * 2],
                LowByte = bytes[i * 2 + 1]
            })
            .ToList();

        if (client != null && !await client.WriteRegisters(id, registers, CancellationToken.None).ConfigureAwait(false))
        {
            throw new Exception($"Writing '{byteStr}' to address {address} failed");
        }
    }

    private static async Task<object> ReadAndPrintResultAsync(IModbusClient client, byte id, string dataType,
        ushort address, ushort count)
    {
        List<Register> result = null;
        switch (dataType.Trim().ToLower())
        {
            case "byte":
                result = await client.ReadHoldingRegisters(id, address, count, CancellationToken.None).ConfigureAwait(false);
                break;
            case "ushort":
                result = await client.ReadHoldingRegisters(id, address, count, CancellationToken.None).ConfigureAwait(false);
                break;
            case "uint":
                result = await client.ReadHoldingRegisters(id, address, 2, CancellationToken.None).ConfigureAwait(false);
                break;
            case "ulong":
                result = await client.ReadHoldingRegisters(id, address, 4, CancellationToken.None).ConfigureAwait(false);
                break;
            case "sbyte":
                result = await client.ReadHoldingRegisters(id, address, count, CancellationToken.None).ConfigureAwait(false);
                break;
            case "short":
                result = await client.ReadHoldingRegisters(id, address, count, CancellationToken.None).ConfigureAwait(false);
                break;
            case "int":
                result = await client.ReadHoldingRegisters(id, address, 2, CancellationToken.None).ConfigureAwait(false);
                break;
            case "long":
                result = await client.ReadHoldingRegisters(id, address, 4, CancellationToken.None).ConfigureAwait(false);
                break;
            case "float":
                result = await client.ReadHoldingRegisters(id, address, 2, CancellationToken.None).ConfigureAwait(false);
                break;
            case "double":
                result = await client.ReadHoldingRegisters(id, address, 4, CancellationToken.None).ConfigureAwait(false);
                break;
            case "string":
                var stringResult = await client.ReadHoldingRegisters(id, address, count, CancellationToken.None).ConfigureAwait(false);
                Console.WriteLine();
                Console.WriteLine("UTF8:             " + stringResult?.GetString(count));
                Console.WriteLine("Unicode:          " + stringResult?.GetString(count, 0, Encoding.Unicode));
                Console.WriteLine("BigEndianUnicode: " + stringResult?.GetString(count, 0, Encoding.BigEndianUnicode));
                return stringResult;

            default:
                Console.WriteLine("DataType unknown");
                return null;
        }

        Console.WriteLine(result?.GetValue(dataType));
        return result;
    }

    private static void PrintDeviceInfo(Dictionary<DeviceIdObject, string> info)
    {
        if (info != null)
        {
            foreach (var kvp in info)
                Console.WriteLine($"{kvp.Key}: {kvp.Value}");
        }
    }

    private static T GetUserInput<T>(string prompt = null)
    {
        if (!string.IsNullOrEmpty(prompt))
        {
            Console.Write($"{prompt}: ");
        }

        var input = Console.ReadLine().Trim();
        return (T)Convert.ChangeType(input, typeof(T));
    }

    private static void DisposeClient(IModbusClient client)
    {
        Console.WriteLine("Disposing");
        client?.Dispose();
        Console.WriteLine("Disposed");
    }

    private static async Task<int> RunServerAsync(CancellationToken cancellationToken)
    {
        Console.Write("Connection Type [1] Tcp, [2] RS485: ");
        var cType = Convert.ToInt32(Console.ReadLine().Trim());

        IModbusServer server = null;
        try
        {
            server = cType switch
            {
                1 => CreateTcpServer(),
                2 => CreateSerialServer(),
                _ => throw new ArgumentException("Type unknown")
            };

            await InitializeServerAsync(server, cancellationToken).ConfigureAwait(false);

            Console.WriteLine("EonaCat Modbus: Server is running... press CTRL+C to exit.");
            await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false);
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            // Do nothing
        }
        finally
        {
            DisposeServer(server);
        }

        return 0;
    }

    private static TcpServer CreateTcpServer()
    {
        Console.Write("Bind IP address: ");
        var ip = IPAddress.Parse(Console.ReadLine().Trim());
        Console.Write("Port: ");
        var port = Convert.ToInt32(Console.ReadLine().Trim());

        var tcp = new TcpServer(port, ip)
        {
            Timeout = TimeSpan.FromSeconds(3)
        };

        return tcp;
    }

    private static SerialServer CreateSerialServer()
    {
        Console.Write("Interface: ");
        var port = Console.ReadLine().Trim();
        var baudRate = GetUserInput<int>("Baud");
        var stopBits = GetUserInput<int>("Stop-Bits [0|1|2|3=1.5]");
        var parity = GetUserInput<int>("Parity [0] None [1] Odd [2] Even [3] Mark [4] Space");
        var handshake = GetUserInput<int>("Handshake [0] None [1] X-On/Off [2] RTS [3] RTS+X-On/Off");
        var timeout = GetUserInput<int>("Timeout (ms)");

        var server = new SerialServer(port)
        {
            BaudRate = (BaudRate)baudRate,
            DataBits = 8,
            StopBits = (StopBits)stopBits,
            Parity = (Parity)parity,
            Handshake = (Handshake)handshake,
            Timeout = TimeSpan.FromMilliseconds(timeout)
        };

        return server;
    }

    private static async Task InitializeServerAsync(IModbusServer server, CancellationToken cancellationToken)
    {
        // Add fake modbus devices
        server.AddDevice(1);
        server.AddDevice(2);
        server.AddDevice(3);

        // Create modbus registers
        Register.Create(123.45f, 100).ForEach(register => server.SetHoldingRegister(1, register));
        Register.Create(543.21f, 65534).ForEach(register => server.SetHoldingRegister(2, register));
        Register.Create(999.99f, 102).ForEach(register => server.SetHoldingRegister(3, register));

        await Task.Delay(0, cancellationToken).ConfigureAwait(false); // Ensure the method is async
    }

    private static void DisposeServer(IDisposable server)
    {
        server?.Dispose();
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.0 98 2/23/2024