Communication between C# upper computer and Schneider PLC

Posted by tourer on Fri, 18 Feb 2022 09:35:51 +0100

Communication between C# upper computer and Schneider PLC

Introduction to ModbusTCP communication protocol

Schneider PLC communication is a Modbus protocol based on Ethernet TCP/IP, in which TCP is what we call ModbusTCP protocol.
Modbus protocol is an application layer message transmission protocol, including ASCII, RTU, TCP and other message types. This article will introduce ModbusTCP communication protocol; We know that the standard Modbus protocol physical layer interfaces include RS232, RS422, RS485 and Ethernet interfaces, which communicate in master / slave mode.
Scope of application: Schneider PLC, various customized hardware devices that can realize ModbusTcp communication protocol.

Data message frame structure

First, each frame of Modbus data packet consists of the following five parts:

   01      03         40            00           00              02
  address    Function code start address high order start address low order    Number of registers high order number of registers low order

Mainly introduce the function code, as shown in the figure below:

explain:
1. 0X01 reading coil
Coil address range (00001~09999) bool type value, ON = 1, off = 0
Request: address function code start address H start address l length H length L
Response: address function code data length packet (data of one address is 1 bit)
For example, we read the coil data from the starting address: 0X0001 and read 8
Then the content of the request message is:

02 01 00 00 00           06         01     01     00 01     00 08
|----fixed----|    Number of address registers for sending data message length address function code

The contents of the response message are:

02 01 00 00 00           04             01     01     01       00
|----fixed----|     Received data message length address function code data length data

Convert data 0X00 into binary string 00000000, indicating that all coil states are OFF.

2. 0X05 write single coil
Coil address range (00001~09999) bool type value, 0xFF00 request output is ON,0X0000 request output is OFF
Request: address function code output address h output address l output value H output value L
Response: address function code output address h output address l output value H output value L
Set our address to x0001, for example
Then the content of the request message is:

02 01 00 00 00           06         01     05     00 01     FF 00
|----fixed----|    Send data message length address function code output address output value

The contents of the response message are:

02 01 00 00 00           06            01    05     00 01    FF 00       
|----fixed----|     Received data message length address function code output address output value

The following only introduces the message format, and the specific form can be followed by analogy

3. 0X0F write multiple coils
Coil address range (00001~09999) bool type value, 0xFF00 request output is ON,0X0000 request output is OFF
Request: address function code start address h start address l output quantity h output quantity l byte length output value H output value L
Response: address function code starting address H starting address l output quantity H output quantity L

4. 0X02 read discrete input
bool type value, ON=1,OFF=0
Request: address function code starting address H starting address l quantity H quantity L
Response: address function code data length data (9 + coil quantity / 8)
The data length is a hexadecimal byte composed of 8 bits

5. 0X04 read input register
Address range (30001 ~ 39999) integer
Request: address function code starting address H starting address L number of registers H number of registers L
Response: address function code data length register data (length: 9 + number of registers) × 2)
The data length is two hexadecimal bytes to form an INT type data

6. 0X03 read hold register
Address range (40001 ~ 49999) integer
Request: address function code starting address H starting address L number of registers H number of registers L
Response: address function code data length register data (length: 9 + number of registers) × 2)
The data length is two hexadecimal bytes to form an INT type data

7. 0X06 write single holding register
Address range (40001 ~ 49999) integer
Request: address function code register address h register address l register value H register value L
Response: address function code register address h register address l register value H register value L

8. 0X10 write multiple holding registers
Address range (40001 ~ 49999) integer
Request: address function code start address H start address L number of registers H number of registers l byte length register value (13 + number of registers) × 2)
Response: address function code start address H start address L number of registers H number of registers L

code implementation

Idea: due to TCP interaction, our previous blog introduced the Socket client protocol source code and specific links C#Socket client program source code
Since ModubusTcp uses the specified message protocol, we can modify and expand it on the basis of this. The specific modification details can be modified on the basis of the client source code, or use the ModbusTcp program I wrote below, which needs to be provided by ourselves. Source code:

    public class ModbusTcpHelper
    {
        #region variable definition
        /// <summary>
        ///socket client communicating with PLC
        /// </summary>
        private Socket socket;
        /// <summary>
        ///Whether PLC is connected, true: PLC is connected, false: not connected
        /// </summary>
        public bool isConnectPLC = false;
        #endregion

        #region connection PLC
        /// <summary>
        ///Connection to PLC, asynchronous connection
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public bool ConnectPLC(string ip, int port)
        {
            isConnectPLC = false;
            try
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IAsyncResult asyncResult = socket.BeginConnect(ip, port, CallbackConnect, socket);
                asyncResult.AsyncWaitHandle.WaitOne();
                socket.ReceiveTimeout = 2000;//Timeout if no data is received in 2000ms
                Thread.Sleep(600);//Asynchronous connection, waiting for the status to return
                return isConnectPLC;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("connect Modbus_TCP Failed:" + ex.Message);
                isConnectPLC = false;
            }
            return isConnectPLC;
        }
        #endregion

        #region asynchronous connection PLC
        /// <summary>
        ///Asynchronous connection PLC
        /// </summary>
        /// <param name="ar"></param>
        private void CallbackConnect(IAsyncResult ar)
        {
            isConnectPLC = false;
            try
            {
                Socket skt = ar.AsyncState as Socket;
                skt.EndConnect(ar);
                isConnectPLC = true;
            }
            catch (Exception ex)
            {
                //MessageBox.Show("connection to Modbus_TCP failed:" + ex.Message);
                System.Diagnostics.Debug.WriteLine("connect Modbus_TCP Failed:" + ex.Message);
            }
        }
        #endregion

        #region close socket connection
        /// <summary>
        ///Close socket connection
        /// </summary>
        public void CloseConnect()
        {
            if (this.socket != null)
            {
                try
                {
                    this.socket.Close(1000);
                    isConnectPLC = false;
                }
                catch { }
            }
        }
        #endregion

        #region holding register operation register range 40001 ~ 49999
        #region reads the value of a single holding register [40001 ~ 49999 pay attention to high and low]
        /// <summary>
        ///Read the value of a single holding register [40001 ~ 49999 pay attention to high and low]
        /// </summary>
        ///< typeparam name = "t" > basic data types, such as short, int, double, etc. < / typeparam >
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "value" > the specific information returned refers to < / param >
        ///< returns > true: reading succeeded; false: reading failed < / returns >
        public bool ReadSingleHoldingRegisterValue<T>(int startAddress, out T value)
        {
            value = default(T);
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            byte wordLength = 0;//Number of addresses read [how many words] int, float needs two words, long,double needs four words
            if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte) || typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
            {
                wordLength = 1;
            }
            else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float))
            {
                wordLength = 2;
            }
            else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double))
            {
                wordLength = 4;
            }
            else
            {
                //Char (that is, ushort, two bytes), decimal (sixteen bytes) and other types are not considered temporarily
                System.Diagnostics.Debug.WriteLine("read Modbus Other types of data are not supported temporarily:" + value.GetType());
                return false;
            }
            byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, addrArray[1], addrArray[0], 0x00, wordLength };
            socket.Send(sendBuffer);

            DisplayBuffer(sendBuffer, sendBuffer.Length, true);
            Thread.Sleep(50);//Wait for 50ms

            byte[] receiveBuffer = new byte[1024];
            try
            {
                //When the protocol is wrong, an exception will occur in the Receive function
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
                //receiveBuffer[8]: the total number of byte streams of real data
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }

            if (typeof(T) == typeof(sbyte))
            {
                byte b = receiveBuffer[10];
                sbyte sb = (sbyte)b;
                value = (T)(object)sb;
            }
            else if (typeof(T) == typeof(byte))
            {
                byte b = receiveBuffer[10];
                value = (T)(object)b;
            }
            else if (typeof(T) == typeof(short))
            {
                short s = BitConverter.ToInt16(new byte[] { receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)s;
            }
            else if (typeof(T) == typeof(ushort))
            {
                ushort us = BitConverter.ToUInt16(new byte[] { receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)us;
            }
            else if (typeof(T) == typeof(int))
            {
                int i = BitConverter.ToInt32(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)i;
            }
            else if (typeof(T) == typeof(uint))
            {
                uint ui = BitConverter.ToUInt32(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)ui;
            }
            else if (typeof(T) == typeof(long))
            {
                long l = BitConverter.ToInt64(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)l;
            }
            else if (typeof(T) == typeof(ulong))
            {
                ulong ul = BitConverter.ToUInt64(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)ul;
            }
            else if (typeof(T) == typeof(float))
            {
                float f = BitConverter.ToSingle(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)f;
            }
            else if (typeof(T) == typeof(double))
            {
                double d = BitConverter.ToDouble(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0);
                value = (T)(object)d;
            }
            return true;
        }
        #endregion
        #region reads multiple holding register values [40001 ~ 49999 note high and low]
        /// <summary>
        ///Read multiple holding register values [40001 ~ 49999 note high and low]
        /// </summary>
        ///< param name = "startaddress" > start register address% MW startaddr < / param >
        ///< param name = "length" > number of bytes read < / param >
        ///< param name = "value" > returned byte stream data < / param >
        ///< returns > true: reading succeeded; false: reading failed < / returns >
        public bool ReadManyHoldingRegisterValue(int startAddress, int length, out byte[] value)
        {
            value = new byte[length];
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            //The number of read registers held in the range 0x03 to 0x125. Because a register [one Word] stores two bytes, the length range of byte array is 1 ~ 250
            if (length < 1 || length > 250)
            {
                System.Diagnostics.Debug.WriteLine("The length range of the returned byte array is 1~250");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            //Number of registers read: length/2 if length is even, and (length+1)/2 if length is odd. Because of integer division, the remainder is not considered in the result, so it is common as follows:
            byte registerCount = (byte)((length + 1) / 2);
            byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, addrArray[1], addrArray[0], 0x00, registerCount };
            socket.Send(sendBuffer);

            DisplayBuffer(sendBuffer, sendBuffer.Length, true);
            Thread.Sleep(50);//Wait for 50ms

            byte[] receiveBuffer = new byte[1024];
            try
            {
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            //Number of actual data bytes received
            byte receiveLength = receiveBuffer[8];
            if (receiveLength != registerCount * 2)
            {
                System.Diagnostics.Debug.WriteLine("Parsing the received data is illegal. The actual data length received [is not] twice the number of read registers");
                return false;
            }
            value = new byte[receiveLength];
            for (int i = 0; i < receiveLength; i++)
            {
                value[i] = receiveBuffer[9 + i];
            }
            return true;
        }
        #endregion
        #region write a single holding register [40001 ~ 49999 note high and low]
        /// <summary>
        ///Write a single holding register [40001 ~ 49999 note high and low]
        /// </summary>
        ///< typeparam name = "t" > basic data types, such as short, int, double, etc. < / typeparam >
        ///< param name = "startaddress" > register start address, range: [0x0000~0xFFFF] < / param >
        ///< param name = "value" > value written < / param >
        ///< returns > true: write succeeded; false: write failed < / returns >
        public bool WriteSingleHoldingRegisterValue<T>(int startAddress, T value)
        {
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            //If sbyte, byte, short, ushort occupy a register (Word) range, you can use function code 0x06: write a single register
            //Int, long, float and double need to use two or more registers, so only function code 0x10 can be used: write multiple registers, in which int, uint and float occupy two registers, long, ulong and double occupy four registers
            byte[] buffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, addrArray[1], addrArray[0], 0x00, 0x00 };
            if (typeof(T) == typeof(sbyte))
            {
                sbyte sb = Convert.ToSByte(value);
                byte b = (byte)sb;
                buffer[11] = b;
            }
            else if (typeof(T) == typeof(byte))
            {
                byte b = Convert.ToByte(value);
                buffer[11] = b;
            }
            else if (typeof(T) == typeof(short))
            {
                short s = Convert.ToInt16(value);
                byte[] writeValueArray = BitConverter.GetBytes(s);
                buffer[10] = writeValueArray[1];
                buffer[11] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(ushort))
            {
                ushort us = Convert.ToUInt16(value);
                byte[] writeValueArray = BitConverter.GetBytes(us);
                buffer[10] = writeValueArray[1];
                buffer[11] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(int))
            {
                int i = Convert.ToInt32(value);
                byte[] writeValueArray = BitConverter.GetBytes(i);
                buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[3];
                buffer[14] = writeValueArray[2];
                buffer[15] = writeValueArray[1];
                buffer[16] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(uint))
            {
                uint ui = Convert.ToUInt32(value);
                byte[] writeValueArray = BitConverter.GetBytes(ui);
                buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[3];
                buffer[14] = writeValueArray[2];
                buffer[15] = writeValueArray[1];
                buffer[16] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(long))
            {
                long l = Convert.ToInt64(value);
                byte[] writeValueArray = BitConverter.GetBytes(l);
                buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[7];
                buffer[14] = writeValueArray[6];
                buffer[15] = writeValueArray[5];
                buffer[16] = writeValueArray[4];
                buffer[17] = writeValueArray[3];
                buffer[18] = writeValueArray[2];
                buffer[19] = writeValueArray[1];
                buffer[20] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(ulong))
            {
                ulong ul = Convert.ToUInt64(value);
                byte[] writeValueArray = BitConverter.GetBytes(ul);
                buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[7];
                buffer[14] = writeValueArray[6];
                buffer[15] = writeValueArray[5];
                buffer[16] = writeValueArray[4];
                buffer[17] = writeValueArray[3];
                buffer[18] = writeValueArray[2];
                buffer[19] = writeValueArray[1];
                buffer[20] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(float))
            {
                float f = Convert.ToSingle(value);
                byte[] writeValueArray = BitConverter.GetBytes(f);
                buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[3];
                buffer[14] = writeValueArray[2];
                buffer[15] = writeValueArray[1];
                buffer[16] = writeValueArray[0];
            }
            else if (typeof(T) == typeof(double))
            {
                double d = Convert.ToDouble(value);
                byte[] writeValueArray = BitConverter.GetBytes(d);
                buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
                buffer[13] = writeValueArray[7];
                buffer[14] = writeValueArray[6];
                buffer[15] = writeValueArray[5];
                buffer[16] = writeValueArray[4];
                buffer[17] = writeValueArray[3];
                buffer[18] = writeValueArray[2];
                buffer[19] = writeValueArray[1];
                buffer[20] = writeValueArray[0];
            }
            else
            {
                //Char (that is, ushort, two bytes), decimal (sixteen bytes) and other types are not considered temporarily
                System.Diagnostics.Debug.WriteLine("write Modbus Other types of data are not supported temporarily:" + value.GetType());
                return false;
            }
            try
            {
                socket.Send(buffer);
                DisplayBuffer(buffer, buffer.Length, true);
                Thread.Sleep(50);//Wait for 50ms
                byte[] receiveBuffer = new byte[1024];
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            return true;
        }
        #endregion
        #region writes the values of multiple holding registers [40001 ~ 49999 need to pay attention to high and low]
        /// <summary>
        ///Write the value of multiple holding registers [40001 ~ 49999 need to pay attention to high and low]
        /// </summary>
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "buffer" > byte array to be written, buffer array length range: [1 ~ 240 (0x01~0xF0)] < / param >
        ///< returns > true: write succeeded; false: write failed < / returns >
        public bool WriteManyHoldingRegisterValue(int startAddress, byte[] buffer)
        {
            //It is divided into odd bytes and even bytes
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            if (buffer == null || buffer.Length < 1 || buffer.Length > 240)
            {
                System.Diagnostics.Debug.WriteLine("Write continuous register block range:(1 To 120 registers)");//Each register divides the data into two bytes
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            //Number of registers to be written
            byte registerCount = (byte)((buffer.Length + 1) / 2);
            //Number of bytes actually written: note that when the length of buffer array is odd, the high bit of the last register needs to be set to 0
            byte writeCount = (byte)(registerCount * 2);
            byte[] sendBuffer = new byte[13 + writeCount];
            sendBuffer[0] = 0x02;
            sendBuffer[1] = 0x01;
            sendBuffer[5] = (byte)(7 + writeCount);
            sendBuffer[6] = 0x01;
            sendBuffer[7] = 0x10;
            sendBuffer[8] = addrArray[1];
            sendBuffer[9] = addrArray[0];
            sendBuffer[11] = registerCount;
            sendBuffer[12] = writeCount;
            for (int i = 0; i < writeCount - 2; i++)
            {
                sendBuffer[13 + i] = buffer[i];
            }

            //Processing of the last two elements [last register]
            if (buffer.Length % 2 == 1)
            {
                //If it is an odd number, you need to set the high bit of the last register to 0
                sendBuffer[13 + writeCount - 2] = 0;
                sendBuffer[13 + writeCount - 1] = buffer[buffer.Length - 1];
            }
            else
            {
                //If it is an even number, it corresponds one by one
                sendBuffer[13 + writeCount - 2] = buffer[buffer.Length - 2];
                sendBuffer[13 + writeCount - 1] = buffer[buffer.Length - 1];
            }

            try
            {
                socket.Send(sendBuffer);
                DisplayBuffer(sendBuffer, sendBuffer.Length, true);
                Thread.Sleep(50);//Wait for 50ms
                byte[] receiveBuffer = new byte[1024];
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            return true;
        }
        #endregion
        #endregion

        #region coil status operation register range 00001 ~ 09999
        #region read single coil status (bit) 00001 ~ 09999
        /// <summary>
        ///Read single coil status (bit) 00001 ~ 09999
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool ReadSingleCoilStatus(int startAddress, out bool value)
        {
            bool[] boolArr = new bool[1] { false };
            bool result = ReadManyCoilStatus(startAddress, 1, out boolArr);
            if (boolArr != null && boolArr.Length == 1)
            {
                value = boolArr[0];
            }
            else
            {
                value = false;
            }
            return result;
        }
        #endregion
        #region read multiple coil status (bits) 00001 ~ 09999 function code 0X01
        /// <summary>
        ///Read multiple coil status (bits) 00001 ~ 09999 function code 0X01
        /// </summary>
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "length" > length < / param >
        ///< param name = "value" > output bool type array < / param >
        /// <returns></returns>
        public bool ReadManyCoilStatus(int startAddress, int length, out bool[] value)
        {
            value = new bool[length];
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            //Read coil status 0x01. The reading range is 1 ~ 2000 (0X7D0). Because one coil [one bit]
            if (length < 1 || length > 2000)
            {
                System.Diagnostics.Debug.WriteLine("The length range of the returned byte array is 1~2000");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            //Number of coil states read:
            byte[] registerCount = BitConverter.GetBytes(length);
            byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, addrArray[1], addrArray[0], registerCount[1], registerCount[0] };
            socket.Send(sendBuffer);

            DisplayBuffer(sendBuffer, sendBuffer.Length, true);
            Thread.Sleep(50);//Wait for 50ms

            byte[] receiveBuffer = new byte[1024];
            try
            {
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            //Number of actual data bytes received
            byte receiveLength = receiveBuffer[8];
            if (receiveLength == 0)
            {
                System.Diagnostics.Debug.WriteLine("Parsing the received data is illegal. The actual data length received is not the number of read coil states (bits)");
                return false;
            }
            else
            {
                value = new bool[length];
                byte[] byteNew = new byte[receiveLength];
                for (int i = 0; i < byteNew.Length; i++)
                {
                    byteNew[i] = receiveBuffer[9 + i];
                }
                string strTemp = byteArrToBinaryString(byteNew);
                if (strTemp != "")
                {
                    for (int i = 0; i < length; i++)
                    {
                        value[i] = strTemp.Substring(i, 1) == "1";
                    }
                }
            }
            return true;
        }
        #endregion
        #region write single coil status (function code 0X05)
        /// <summary>
        ///Write single coil status (function code 0X05)
        /// </summary>
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "buffer" > the integer array to be written. The length range of buffer array: [1 ~ 1968 (0x0001~0x07B0)] < / param >
        ///< returns > true: write succeeded; false: write failed < / returns >
        public bool WriteSingleCoilStatus(int startAddress, int buffer)
        {
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            if (startAddress < 0 || startAddress > 9999)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~9999 between");
                return false;
            }
            if (buffer > 1)
            {
                System.Diagnostics.Debug.WriteLine("Write coil status value range:(0 Or 1)");
                return false;
            }
            return WriteManyCoilStatus(startAddress, new int[] { buffer });
        }
        #endregion
        #region write multiple coil states (function code 0X15)
        /// <summary>
        ///Write multiple coil states (function code 0X15)
        /// </summary>
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "buffer" > the integer array to be written. The length range of buffer array: [1 ~ 1968 (0x0001~0x07B0)] < / param >
        ///< returns > true: write succeeded; false: write failed < / returns >
        public bool WriteManyCoilStatus(int startAddress, int[] buffer)
        {
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            if (startAddress < 0 || startAddress > 9999)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~9999 between");
                return false;
            }
            if (buffer == null || buffer.Length < 1 || buffer.Length > 1968)
            {
                System.Diagnostics.Debug.WriteLine("Write coil status range:(1 To 1968 registers)");
                return false;
            }
            for (int i = 0; i < buffer.Length; i++)
            {
                if (buffer[i] > 1)
                {
                    System.Diagnostics.Debug.WriteLine("buffer All data values must be 0 or 1...");
                    return false;
                }
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            byte[] lengthArray = BitConverter.GetBytes((ushort)(buffer.Length));
            int intTemp = buffer.Length % 8;
            int byteTemp = buffer.Length / 8;
            intTemp = intTemp > 0 ? 1 : 0;
            //Number of coil states actually written: note the length of buffer array
            byte[] sendBuffer = new byte[13 + intTemp + byteTemp];
            sendBuffer[0] = 0x02;
            sendBuffer[1] = 0x01;
            sendBuffer[2] = 0x00;
            sendBuffer[3] = 0x00;
            sendBuffer[4] = 0x00;
            sendBuffer[5] = (byte)(7 + byteTemp + intTemp);
            sendBuffer[6] = 0x01;

            sendBuffer[7] = 0x0F;
            sendBuffer[8] = addrArray[1];
            sendBuffer[9] = addrArray[0];
            sendBuffer[10] = lengthArray[1];
            sendBuffer[11] = lengthArray[0];
            sendBuffer[12] = (byte)(byteTemp + intTemp);


            for (int i = 0; i < intTemp + byteTemp; i++)
            {
                string strTemp = "";
                if (intTemp == 0)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        strTemp += buffer[i * 8 + 7 - j];
                    }
                }
                else
                {
                    if (i < byteTemp)
                    {
                        for (int j = 0; j < 8; j++)
                        {
                            strTemp += buffer[i * 8 + 7 - j];
                        }
                    }
                    else
                    {
                        for (int j = 0; j < buffer.Length % 8; j++)
                        {
                            strTemp += buffer[buffer.Length - 1 - j];
                        }
                    }
                }
                sendBuffer[13 + i] = GetByteValueFromBinaryStr(strTemp);
            }
            try
            {
                socket.Send(sendBuffer);
                DisplayBuffer(sendBuffer, sendBuffer.Length, true);
                Thread.Sleep(50);//Wait for 50ms
                byte[] receiveBuffer = new byte[1024];
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            return true;
        }
        #endregion
        #endregion

        #region input register operation register range 30001 ~ 39999
        #region reads multiple input register values [30001 ~ 39999 note high and low]
        /// <summary>
        ///Read multiple input register values [30001 ~ 39999 note high and low]
        /// </summary>
        ///< param name = "startaddress" > start register address < / param >
        ///< param name = "length" > number of bytes read < / param >
        ///< param name = "value" > returned byte stream data < / param >
        ///< returns > true: reading succeeded; false: reading failed < / returns >
        public bool ReadManyInputRegisterValue(int startAddress, int length, out byte[] value)
        {
            value = new byte[length];
            if (socket == null || !socket.Connected)
            {
                System.Diagnostics.Debug.WriteLine("socket Is empty or has not been established with PLC_Modbus Connection of...");
                return false;
            }
            //The number of registers read by read holding register 0x03 ranges from 1 to 125. Because a register [one Word] stores two bytes, the length range of byte array is 1 ~ 250
            if (length < 1 || length > 250)
            {
                System.Diagnostics.Debug.WriteLine("The length range of the returned byte array is 1~250");
                return false;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                System.Diagnostics.Debug.WriteLine("Modbus The starting address of must be 0~65535 between");
                return false;
            }
            byte[] addrArray = BitConverter.GetBytes((ushort)startAddress);
            //Number of registers read: length/2 if length is even, and (length+1)/2 if length is odd. Because of integer division, the remainder is not considered in the result, so it is common as follows:
            byte registerCount = (byte)((length + 1) / 2);
            byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, addrArray[1], addrArray[0], 0x00, registerCount };
            socket.Send(sendBuffer);

            DisplayBuffer(sendBuffer, sendBuffer.Length, true);
            Thread.Sleep(50);//Wait for 50ms

            byte[] receiveBuffer = new byte[1024];
            try
            {
                int receiveCount = socket.Receive(receiveBuffer);
                DisplayBuffer(receiveBuffer, receiveCount, false);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("receive Modbus The response data of is abnormal. Please check whether the sent message format is incorrect:" + ex.Message);
                return false;
            }
            //Number of actual data bytes received
            byte receiveLength = receiveBuffer[8];
            if (receiveLength != registerCount * 2)
            {
                System.Diagnostics.Debug.WriteLine("Parsing the received data is illegal. The actual data length received [is not] twice the number of read registers");
                return false;
            }
            value = new byte[receiveLength];
            for (int i = 0; i < receiveLength; i++)
            {
                value[i] = receiveBuffer[9 + i];
            }
            return true;
        }
        #endregion
        #endregion

        #region reads the barcode stored from the starting address. By default, it reads the barcode string with a maximum length of 100
        /// <summary>
        ///Read the barcode stored at the starting address. By default, read the barcode string with the maximum length of 100
        /// </summary>
        ///< param name = "startaddress" > start address < / param >
        ///< param name = "barcode" > returned barcode string < / param >
        ///< returns > true: reading succeeded; false: reading failed < / returns >
        public bool ReadBarcode(int startAddress, out string barcode)
        {
            barcode = string.Empty;
            byte[] dataBuffer = new byte[100];
            bool result = ReadManyHoldingRegisterValue(startAddress, 100, out dataBuffer);
            if (!result)
            {
                return false;
            }
            List<byte> list = new List<byte>();
            for (int i = 0; i < dataBuffer.Length; i += 2)
            {
                //Because the data stored in a register is a Word word, which is divided into two bytes Byte [high Byte, low Byte], and the stored bar code is the low Byte first, so the order needs to be exchanged every two
                list.Add(dataBuffer[i + 1]);
                list.Add(dataBuffer[i]);
                //Encountered invalid data after '\ 0'
                if (dataBuffer[i] == 0 || dataBuffer[i + 1] == 0)
                {
                    break;
                }
            }
            byte[] actualBuffer = list.ToArray();
            barcode = Encoding.ASCII.GetString(actualBuffer).Trim('\0').Trim();
            return result;
        }
        #endregion

        #region print Debug sends or receives byte array information
        /// <summary>
        ///Print the byte array information sent or received by Debug
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="count"></param>
        /// <param name="isSend"></param>
        public void DisplayBuffer(byte[] buffer, int count, bool isSend)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append((isSend ? "send out" : "Received") + "Byte stream:\n");
            for (int i = 0; i < count; i++)
            {
                if (i > 0)
                {
                    sb.Append(" ");
                }
                sb.Append(buffer[i].ToString("X2"));
            }
            string content = sb.ToString();
            System.Diagnostics.Debug.WriteLine(content);
        }
        #endregion

        #region byte[]Arr to int
        /// <summary>
        ///byte[]Arr to int
        /// </summary>
        /// <param name="byteIn"></param>
        /// <returns></returns>
        private int byteArrToInt(byte[] byteIn)
        {
            int value = 0;
            for (int i = 0; i < 4; i++)
            {
                int shift = (4 - 1 - i) * 8;
                value += (byteIn[i] & 0X000000FF) << shift;
            }
            return value;
        }
        #endregion
        #region byteArr to binary string
        /// <summary>
        ///byte to binary string (high order first)
        /// </summary>
        /// <param name="byteIn"></param>
        /// <returns></returns>
        private string byteArrToBinaryString(byte[] byteIn)
        {
            string result = "";
            for (int i = 0; i < byteIn.Length; i++)
            {
                string str = Convert.ToString(byteIn[i], 2).PadLeft(8, '0');
                for (int j = 0; j < str.Length; j++)
                {
                    result += str[8 - 1 - j];
                }
            }
            return result;
        }
        #endregion
        #region is used for byte writing when writing bit status coil
        private byte GetByteValueFromBinaryStr(string strIn)
        {
            int result = 0;
            if (strIn.Length != 8)
            {
                strIn = strIn.PadLeft(8, '0');
            }
            for (int i = 0; i < strIn.Length; i++)
            {
                int intTemp = int.Parse(strIn.Substring(i, 1));
                result += intTemp * ((int)Math.Pow(2, (7 - i)));
            }
            return (byte)result;
        }
        #endregion
    }

remarks

Next, we share the ModbustTcp simulation gadget, which can also be downloaded by the bobbins needed. On the one hand, after our program development is completed, we can conduct our own simulation test.
ModbusTcp client simulation tool: https://pan.baidu.com/s/1w657nPv_pneWLQZvDAPHYQ
Extraction code: jnel

ModbusTcp server simulation tool: https://pan.baidu.com/s/1GioalFIJEmEWcaorvDyLIg
Extraction code: 0l9e

The specific use of Baidu is easy to understand. Over!

Topics: C# TCPIP modbus