MODBUS TCP Communication with Strings and Floating Point Data Using NModbus

Installing NModbus Library

Search for NModbus in NuGet Package Manager and install the package.

Creating a ModbusTCP Wrapper Class

public class ModbusTcpClient
{
    private ModbusFactory factory;
    private IModbusMaster modbusMaster;
    private TcpClient tcpConnection;

    public string HostAddress { get; set; }
    public int ConnectionPort { get; set; }

    public bool IsConnected
    {
        get => tcpConnection.Connected;
    }

    public ModbusTcpClient(string ipAddress, int portNumber)
    {
        HostAddress = ipAddress;
        ConnectionPort = portNumber;

        factory = new ModbusFactory();
        tcpConnection = new TcpClient(HostAddress, ConnectionPort);
        modbusMaster = factory.CreateMaster(tcpConnection);
        modbusMaster.Transport.ReadTimeout = 2000;
        modbusMaster.Transport.Retries = 10;
    }

    public bool[] ReadDiscreteOutputs(byte deviceAddress, ushort startingAddress, ushort quantity)
    {
        return modbusMaster.ReadCoils(deviceAddress, startingAddress, quantity);
    }

    public bool[] ReadDiscreteInputs(byte deviceAddress, ushort startingAddress, ushort quantity)
    {
        return modbusMaster.ReadInputs(deviceAddress, startingAddress, quantity);
    }

    public ushort[] ReadOutputRegisters(byte deviceAddress, ushort startingAddress, ushort quantity)
    {
        return modbusMaster.ReadHoldingRegisters(deviceAddress, startingAddress, quantity);
    }

    public ushort[] ReadInputRegisters(byte deviceAddress, ushort startingAddress, ushort quantity)
    {
        return modbusMaster.ReadInputRegisters(deviceAddress, startingAddress, quantity);
    }

    public void WriteSingleCoil(byte deviceAddress, ushort address, bool data)
    {
        modbusMaster.WriteSingleCoil(deviceAddress, address, data);
    }

    public void WriteSingleRegister(byte deviceAddress, ushort address, ushort data)
    {
        modbusMaster.WriteSingleRegister(deviceAddress, address, data);
    }

    public void WriteMultipleCoils(byte deviceAddress, ushort startingAddress, bool[] values)
    {
        modbusMaster.WriteMultipleCoils(deviceAddress, startingAddress, values);
    }

    public void WriteMultipleRegisters(byte deviceAddress, ushort startingAddress, ushort[] values)
    {
        modbusMaster.WriteMultipleRegisters(deviceAddress, startingAddress, values);
    }
}

Connection Testing Setup

Download Modbus Slave simulator software Link: https://pan.baidu.com/s/1ydX_9KElkOTA7h-E3SIadwPassword: 7fh5 Configure the settings accordingly.

Establishing Connection

private ModbusTcpClient modbusClient;
private void EstablishConnection()
{
   try
   {
      modbusClient = new ModbusTcpClient(ServerIP, ServerPort);
      pollTimer.Start();
   }
   catch (Exception ex)
   {
      MessageBox.Show(ex.Message);
   }
}

Data Writing Implementation

Since NModbus only provides ushort type register operations, conversion utilities are needed. Create conversion helper methods:

public class ModbusDataConverter
{
    public static void AssignString(ushort[] buffer, int offset, string text)
    {
        byte[] byteData = Encoding.UTF8.GetBytes(text);
        ushort[] convertedData = ConvertBytesToUshorts(byteData);
        Array.Copy(convertedData, 0, buffer, offset, convertedData.Length);
    }

    public static string ExtractString(ushort[] buffer, int offset, int length)
    {
        ushort[] tempBuffer = new ushort[length];
        Array.Copy(buffer, offset, tempBuffer, 0, length);
        
        byte[] byteData = ConvertUshortsToBytes(tempBuffer);
        string result = Encoding.UTF8.GetString(byteData).Trim('\0');
        return result;
    }

    public static void AssignFloat(ushort[] buffer, int offset, float value)
    {
        byte[] floatBytes = BitConverter.GetBytes(value);
        ushort[] convertedValues = ConvertBytesToUshorts(floatBytes);
        Array.Copy(convertedValues, 0, buffer, offset, convertedValues.Length);
    }

    public static float ExtractFloat(ushort[] buffer, int offset)
    {
        ushort[] tempBuffer = new ushort[2];
        Array.Copy(buffer, offset, tempBuffer, 0, 2);
        
        byte[] byteData = ConvertUshortsToBytes(tempBuffer);
        float result = BitConverter.ToSingle(byteData, 0);
        return result;
    }

    public static void AssignInt16(ushort[] buffer, int offset, short value)
    {
        byte[] shortBytes = BitConverter.GetBytes(value);
        ushort[] convertedValues = ConvertBytesToUshorts(shortBytes);
        Array.Copy(convertedValues, 0, buffer, offset, convertedValues.Length);
    }

    public static short ExtractInt16(ushort[] buffer, int offset)
    {
        ushort[] tempBuffer = new ushort[1];
        tempBuffer[0] = buffer[offset];
        
        byte[] byteData = ConvertUshortsToBytes(tempBuffer);
        short result = BitConverter.ToInt16(byteData, 0);
        return result;
    }

    public static bool[] ExtractBooleans(ushort[] buffer, int offset, int count)
    {
        ushort[] tempBuffer = new ushort[count];
        Array.Copy(buffer, offset, tempBuffer, 0, count);
        
        byte[] byteData = ConvertUshortsToBytes(tempBuffer);
        bool[] result = ConvertBytesToBooleans(byteData);
        return result;
    }

    private static bool[] ConvertBytesToBooleans(byte[] input)
    {
        bool[] output = new bool[input.Length * 8];

        for (int i = 0; i < input.Length; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                output[i * 8 + j] = (input[i] & (1 << j)) != 0;
            }
        }
        return output;
    }

    private static byte ConvertBooleansToByte(bool[] input)
    {
        if (input != null && input.Length > 0)
        {
            byte result = 0;
            for (int i = 0; i < Math.Min(input.Length, 8); i++)
            {
                if (input[i])
                {
                    result |= (byte)(1 << i);
                }
            }
            return result;
        }
        return 0;
    }

    private static ushort[] ConvertBytesToUshorts(byte[] input, bool swapOrder = false)
    {
        int byteCount = input.Length;
        int ushortCount = (byteCount + 1) / 2;
        ushort[] output = new ushort[ushortCount];

        for (int i = 0; i < ushortCount; i++)
        {
            int lowIndex = i * 2;
            int highIndex = Math.Min(lowIndex + 1, byteCount - 1);
            
            if (swapOrder)
            {
                output[i] = (ushort)((input[lowIndex] << 8) | input[highIndex]);
            }
            else
            {
                output[i] = (ushort)(input[lowIndex] | (input[highIndex] << 8));
            }
        }
        return output;
    }

    private static byte[] ConvertUshortsToBytes(ushort[] input, bool swapOrder = false)
    {
        byte[] output = new byte[input.Length * 2];
        
        for (int i = 0; i < input.Length; i++)
        {
            if (swapOrder)
            {
                output[i * 2] = (byte)(input[i] >> 8);
                output[i * 2 + 1] = (byte)(input[i] & 0xFF);
            }
            else
            {
                output[i * 2] = (byte)(input[i] & 0xFF);
                output[i * 2 + 1] = (byte)(input[i] >> 8);
            }
        }
        return output;
    }
}

Writing data based on selected data type:

private void PerformWriteOperation()
{
    try
    {
        if (DataType == "float")
        {
            ushort[] writeBuffer = new ushort[2];
            float numericValue = float.Parse(InputValue);
            ModbusDataConverter.AssignFloat(writeBuffer, 0, numericValue);
            modbusClient.WriteMultipleRegisters(DeviceId, WriteRegisterAddress, writeBuffer);
        }
        else if (DataType == "text")
        {
            ushort[] writeBuffer = new ushort[10];
            ModbusDataConverter.AssignString(writeBuffer, 0, InputValue);
            modbusClient.WriteMultipleRegisters(DeviceId, WriteRegisterAddress, writeBuffer);
        }
        else if (DataType == "int16")
        {
            ushort[] writeBuffer = new ushort[1];
            short numericValue = short.Parse(InputValue);
            ModbusDataConverter.AssignInt16(writeBuffer, 0, numericValue);
            modbusClient.WriteMultipleRegisters(DeviceId, WriteRegisterAddress, writeBuffer);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

As observed in the Modbus Slave simulator, floating point data occupies two registers (4 bytes). After proper configuration, successful writing can be verified.

Data Reading Implementation

private void PerformReadOperation()
{
    try
    {
        if (DataType == "float")
        {
            ushort[] readBuffer = modbusClient.ReadOutputRegisters(DeviceId, ReadRegisterAddress, 2);
            float extractedValue = ModbusDataConverter.ExtractFloat(readBuffer, 0);
            DisplayValue = extractedValue.ToString();
        }
        else if (DataType == "text")
        {
            ushort[] readBuffer = modbusClient.ReadOutputRegisters(DeviceId, ReadRegisterAddress, 10);
            DisplayValue = ModbusDataConverter.ExtractString(readBuffer, 0, 10);
        }
        else if (DataType == "int16")
        {
            ushort[] readBuffer = modbusClient.ReadOutputRegisters(DeviceId, ReadRegisterAddress, 1);
            short extractedValue = ModbusDataConverter.ExtractInt16(readBuffer, 0);
            DisplayValue = extractedValue.ToString();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

String Operations

Refer to the DataType=="text" implementation sections for string handling operations.

Tags: nmodbus modbus-tcp floating-point string-conversion register-operations

Posted on Mon, 02 Nov 2026 16:13:24 +0000 by rantsh