C#

비동기 모드버스 (HOLDING REGISTERS)

sheepone 2024. 11. 18. 17:00
반응형

기존 모드버스들이 동기 방식이라 비동기 방식으로 HOLDING REGISTERS 구성.

 

 

왼쪽이 서버의 홀딩 레지스터 1,2,3,3,2,1 

우측이 클라이언트의 홀딩 레지스터 1,2,3,3,2,1

서버의 홀딩 레지스터를 변경하면 우측에서 변경된 값을 확인할수 있습니다.

 

1. AsyncModbus.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncModbusClient
{
    public class AsyncModbus
    {
        public static AsyncModbus Ins
        {
            get { return ins == null ? (ins = new AsyncModbus("127.0.0.1",0,20)) : ins; }
        }
        private static AsyncModbus ins = null;

        Thread MainThread ;//= new Thread(new ThreadStart(Update));

        //TCP
        TcpClient mTcpClient;
        byte[] mRx;
        bool bConnected  = false ;
        bool bConnecting = false ;
        bool bReciveComplete = false ;
        private bool Connected { get => mTcpClient == null ? false : mTcpClient.Connected && bConnected; }

        //접속시 사용한 IP PORT
        int iPort;
        IPAddress iPAddress;

        //재접속 타이머
        System.Timers.Timer timer = new System.Timers.Timer();
        
        string sIpAddress  = "" ;
		string sServerPort = "" ;

        //Modbus
        public int [] HoldingReg ; 
        public int iQuantityRead ;
        public int iSttAddress   ;
        public AsyncModbus(string _sIpAddress, int _iSttAddress, int _iQuantityRead)
        {
			sIpAddress  = _sIpAddress ;
			sServerPort = "502";

            iSttAddress   = _iSttAddress  ;
            iQuantityRead = _iQuantityRead;
            HoldingReg = new int[iQuantityRead];

            timer.Interval = 1000;
            timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elasped);
            timer.Enabled  = true;

            bReciveComplete = true ; //초기에 한번

            MainThread = new Thread(new ThreadStart(Update));
            //MainThread.Priority = ThreadPriority.Highest;
            MainThread.Start();
			//Connect(); //이거는 밖에서
        }

        public void Close() //이거도 밖에서
        {
            m_bReqThreadStop = true;
        }

		public bool Connect()
		{

            if (mTcpClient != null && Connected /*mTcpClient.Connected*/) return false; //연결이 되어 있는데 또 연결 방지용, 서버쪽에 연결된 노드가 늘어남

            IPAddress ipa;
            int nPort;

            try
            {
                if (string.IsNullOrEmpty(sIpAddress) || string.IsNullOrEmpty(sServerPort)) return false;

                if (!IPAddress.TryParse(sIpAddress, out ipa))
                {
                    MessageBox.Show("Please supply an IP Address.");
                    return false;
                }

                if (!int.TryParse(sServerPort, out nPort))
                {
                    nPort = 23000;
                }

                mTcpClient = new TcpClient();
                mTcpClient.BeginConnect(ipa, nPort, onCompleteConnect, mTcpClient);

                iPAddress   = ipa  ;
                iPort       = nPort;
                bConnecting = true ;
                //timer1.Start();
            }
            catch (Exception exc)
            {
                bConnected = false;
                MessageBox.Show(exc.Message);
            }
			return true;
		}

        void onCompleteConnect(IAsyncResult iar)
        {
            TcpClient tcpc;
            try
            {
                //iar.AsyncWaitHandle.WaitOne(1000, false);

                tcpc = (TcpClient)iar.AsyncState;
                tcpc.EndConnect(iar);
                mRx = new byte[512];
                tcpc.GetStream().BeginRead(mRx, 0, mRx.Length, onCompleteReadFromServerStream, tcpc);
                bConnected = true; //연결 성공

            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message);
                bConnected = false; //연결 실패

            }
            bConnecting = false;
        }

        void onCompleteReadFromServerStream(IAsyncResult iar)
        {
            TcpClient tcpc;
            int nCountBytesReceivedFromServer;
            //string strReceived;
            try
            {
                tcpc = (TcpClient)iar.AsyncState;
                nCountBytesReceivedFromServer = tcpc.GetStream().EndRead(iar);

                if (nCountBytesReceivedFromServer == 0)
                {
                    MessageBox.Show("Connection broken.");
                    return;
                }
                //strReceived = Encoding.ASCII.GetString(mRx, 0, nCountBytesReceivedFromServer);
                //if(mRx[nCountBytesReceivedFromServer-1] != '\n') return;
                ReadHoldingRegisters(mRx);

                mRx = new byte[512];
                tcpc.GetStream().BeginRead(mRx, 0, mRx.Length, onCompleteReadFromServerStream, tcpc);

                bReciveComplete = true ;
                bConnected = true; //연결 성공

            }
            catch (Exception exc)
            {
                bConnected = false; //연결 실패
                MessageBox.Show(exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        byte[] tx;
        public void Send(string _sMsg) 
        { 
            bReciveComplete = false ;

            if (string.IsNullOrEmpty(_sMsg)) return;

            try
            {
                if (!Connected) return;

                //tx = Encoding.ASCII.GetBytes(_sMsg + "\r\n");
                tx = Encoding.ASCII.GetBytes(_sMsg);

                if (mTcpClient != null)
                {
                    if (mTcpClient.Client.Connected)
                    {
                        mTcpClient.GetStream().BeginWrite(tx, 0, tx.Length, onCompleteWriteToServer, mTcpClient);
                    }
                }
            }
            catch (Exception exc)
            {
                bConnected = false;
                MessageBox.Show(exc.Message);
            }
        }
        public void Send(byte[] _sMsg) 
        { 
            bReciveComplete = false ;

            try
            {
                if (!Connected) return;

                //tx = Encoding.ASCII.GetBytes(_sMsg + "\r\n");
                tx = _sMsg;//Encoding.ASCII.GetBytes(_sMsg);

                if (mTcpClient != null)
                {
                    if (mTcpClient.Client.Connected)
                    {
                        mTcpClient.GetStream().BeginWrite(tx, 0, tx.Length, onCompleteWriteToServer, mTcpClient);
                    }
                }
            }
            catch (Exception exc)
            {
                bConnected = false;
                MessageBox.Show(exc.Message);
            }
        }

        void onCompleteWriteToServer(IAsyncResult iar)
        {
            TcpClient tcpc;

            try
            {
                tcpc = (TcpClient)iar.AsyncState;
                tcpc.GetStream().EndWrite(iar);
                //bCompleted = true;
            }
            catch (Exception exc)
            {
                //bCompleted = false;
                MessageBox.Show(exc.Message);
            }
        }

        //----------------------------------------------------------------------------------------------------------
        //MODBUS TCP HOLDING REGISTERS
        //----------------------------------------------------------------------------------------------------------
        private uint transactionIdentifierInternal = 0;
		private byte [] transactionIdentifier = new byte[2];
		private byte [] protocolIdentifier = new byte[2];
        private byte [] crc = new byte[2];
		private byte [] length = new byte[2];
		private byte unitIdentifier = 0x01;
		private byte functionCode;
		private byte [] startingAddress = new byte[2];
		private byte [] quantity = new byte[2];

        /// <summary>
        /// Read Holding Registers from Master device (FC3).
        /// </summary>
        /// <param name="startingAddress">First holding register to be read</param>
        /// <param name="quantity">Number of holding registers to be read</param>
        /// <returns>Int Array which contains the holding registers</returns>
        public bool RequestReadHoldingRegisters(int startingAddress, int quantity)
		{
			if (startingAddress > 65535 | quantity > 125)
			{
				throw new ArgumentException("Starting address must be 0 - 65535; quantity must be 0 - 125");
			}
			//int[] response;
			this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal);
			this.protocolIdentifier = BitConverter.GetBytes((int) 0x0000);
			this.length = BitConverter.GetBytes((int)0x0006);
			this.functionCode = 0x03;
			this.startingAddress = BitConverter.GetBytes(startingAddress);
			this.quantity = BitConverter.GetBytes(quantity);
			Byte[] data = new byte[]{	this.transactionIdentifier[1],
							this.transactionIdentifier[0],
							this.protocolIdentifier[1],
							this.protocolIdentifier[0],
							this.length[1],
							this.length[0],
							this.unitIdentifier,
							this.functionCode,
							this.startingAddress[1],
							this.startingAddress[0],
							this.quantity[1],
							this.quantity[0],
                            this.crc[0],
                            this.crc[1]
            };
            crc = BitConverter.GetBytes(calculateCRC(data, 6, 6));
            data[12] = crc[0];
            data[13] = crc[1];
            //stream.Write(data, 0, data.Length-2);
            Send(data);

            return true;
		}
        private bool ReadHoldingRegisters(byte[] _mRx)
        {
            try
            {
                byte[] receiveData = new byte[_mRx.Length];
                Array.Copy(_mRx, 0, receiveData, 0, _mRx.Length);

                if (receiveData[7] == 0x97 & receiveData[8] == 0x01)
                {
    			    throw new ArgumentException("Function code not supported by master");
                }
                if (receiveData[7] == 0x97 & receiveData[8] == 0x02)
                {
    			    throw new ArgumentException("Starting address invalid or starting address + quantity invalid");
                }
                if (receiveData[7] == 0x97 & receiveData[8] == 0x03)
                {
    			    throw new ArgumentException("quantity invalid");
                }
                if (receiveData[7] == 0x97 & receiveData[8] == 0x04)
                {
    			    throw new ArgumentException("error reading");
                }

                //int quantityRead = 20 ;
                int [] response = new int[iQuantityRead];
                for (int i = 0; i < iQuantityRead; i++)
                {
                    byte lowByte ;
                    byte highByte;
                    highByte = receiveData[9 + i * 2];
                    lowByte  = receiveData[9 + i * 2 + 1];

                    receiveData[9 + i * 2]     = lowByte ;
                    receiveData[9 + i * 2 + 1] = highByte;
                    //if(i < HoldingReg.Length)
                    {
                        response[i] = BitConverter.ToInt16(receiveData, (9 + i * 2));
                        if(HoldingReg[i] != response[i])
                        {
                            //Log.Trace(sIpAddress , "Reg(" + (i+iSttAddress) +")" + HoldingReg[i] + "->" + response[i]);
                            Debug.WriteLine(sIpAddress , "HoldingReg(" + (i+iSttAddress) +") " + HoldingReg[i] + "->" + response[i]);
                        }
                        HoldingReg[i] = response[i];
                    }
                }
            }
            catch (Exception _e)
            {
                MessageBox.Show(_e.Message);
            }
            return true;
        }

        public void WriteSingleRegister(int startingAddress, int value)
        {
            transactionIdentifierInternal++;

            byte[] registerValue = new byte[2];
            this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal);
            this.protocolIdentifier = BitConverter.GetBytes((int)0x0000);
            this.length = BitConverter.GetBytes((int)0x0006);
            this.functionCode = 0x06;
            this.startingAddress = BitConverter.GetBytes(startingAddress);
                   registerValue = BitConverter.GetBytes((int)value);

            Byte[] data = new byte[]{	this.transactionIdentifier[1],
							this.transactionIdentifier[0],
							this.protocolIdentifier[1],
							this.protocolIdentifier[0],
							this.length[1],
							this.length[0],
							this.unitIdentifier,
							this.functionCode,
							this.startingAddress[1],
							this.startingAddress[0],
							registerValue[1],
							registerValue[0],
                            this.crc[0],
                            this.crc[1]    
                            };
            crc = BitConverter.GetBytes(calculateCRC(data, 6, 6));
            data[12] = crc[0];
            data[13] = crc[1];
            Send(data);
        }
        

        public void WriteMultipleRegisters(int startingAddress, int[] values)
        {
            transactionIdentifierInternal++;
            byte byteCount = (byte)(values.Length * 2);
            byte[] quantityOfOutputs = BitConverter.GetBytes((int)values.Length);

            this.transactionIdentifier = BitConverter.GetBytes((uint)transactionIdentifierInternal);
            this.protocolIdentifier = BitConverter.GetBytes((int)0x0000);
            this.length = BitConverter.GetBytes((int)(7+values.Length*2));
            this.functionCode = 0x10;
            this.startingAddress = BitConverter.GetBytes(startingAddress);

            Byte[] data = new byte[13+2 + values.Length*2];
            data[0] = this.transactionIdentifier[1];
            data[1] = this.transactionIdentifier[0];
            data[2] = this.protocolIdentifier[1];
            data[3] = this.protocolIdentifier[0];
            data[4] = this.length[1];
            data[5] = this.length[0];
            data[6] = this.unitIdentifier;
            data[7] = this.functionCode;
            data[8] = this.startingAddress[1];
            data[9] = this.startingAddress[0];
            data[10] = quantityOfOutputs[1];
            data[11] = quantityOfOutputs[0];
            data[12] = byteCount;
            for (int i = 0; i < values.Length; i++)
            {
                byte[] singleRegisterValue = BitConverter.GetBytes((int)values[i]);
                data[13 + i*2] = singleRegisterValue[1];
                data[14 + i*2] = singleRegisterValue[0];
            }
            crc = BitConverter.GetBytes(calculateCRC(data, (ushort)(data.Length - 8), 6));
            data[data.Length - 2] = crc[0];
            data[data.Length - 1] = crc[1];
            Send(data);
        }

        /// <summary>
        /// Calculates the CRC16 for Modbus-RTU
        /// </summary>
        /// <param name="data">Byte buffer to send</param>
        /// <param name="numberOfBytes">Number of bytes to calculate CRC</param>
        /// <param name="startByte">First byte in buffer to start calculating CRC</param>
        public static UInt16 calculateCRC(byte[] data, UInt16 numberOfBytes, int startByte)
        { 
            byte[] auchCRCHi = {
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
            0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
            0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
            0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
            0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
            0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
            0x40
            };
		
            byte[] auchCRCLo = {
            0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
            0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
            0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
            0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
            0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
            0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
            0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
            0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
            0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
            0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
            0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
            0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
            0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
            0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
            0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
            0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
            0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
            0x40
            };
            UInt16 usDataLen = numberOfBytes;
            byte  uchCRCHi = 0xFF ; 
            byte uchCRCLo = 0xFF ; 
            int i = 0;
            int uIndex ;
            while (usDataLen>0) 
            {
                usDataLen--;
                if ((i + startByte) < data.Length)
                {
                    uIndex = uchCRCLo ^ data[i + startByte];
                    uchCRCLo = (byte)(uchCRCHi ^ auchCRCHi[uIndex]);
                    uchCRCHi = auchCRCLo[uIndex];
                }
                i++;
            }
            return (UInt16)((UInt16)uchCRCHi << 8 | uchCRCLo);           
        }

        static bool   m_bReqThreadStop = false ;
        public void Update()
        {   
            Stopwatch swDelay = new Stopwatch();
            swDelay.Start();

            while (!m_bReqThreadStop) 
            {
                Thread.Sleep(1);

                //if(swDelay.ElapsedMilliseconds > 100 && bReciveComplete)
                if(Connected && bReciveComplete)
                {
                    RequestReadHoldingRegisters(iSttAddress,iQuantityRead);
                    swDelay.Restart();
                }
                
            }
        }

        void timer_Elasped(object sender, System.Timers.ElapsedEventArgs e)
        {
            timer.Enabled = false;
            bConnected = mTcpClient.Connected ;
            if (!mTcpClient.Connected && !bConnecting)
            {
                bConnecting = true;
                //IAsyncResult Result = mTcpClient.BeginConnect(iPAddress, iPort,onCompleteConnect,mTcpClient);
                mTcpClient.Close();
                mTcpClient = new TcpClient();
                mTcpClient.BeginConnect(iPAddress, iPort, onCompleteConnect, mTcpClient);
            }
            timer.Enabled = true;
        }
    }
}

 

Client로 구성하였으며 재접속가능.

생성자에서 IP와 시작주소 수량을 정해주고 Thread에서 이값을 계속해서 확인함.

 

추가 수정 쓰기 추가

 

사용방법

밖에서 AsyncModbus.Ins.Connect(); 로 접속

종료시 AsyncModbus.Ins.Close();

데이터 확인은 AsyncModbus.Ins.HoldingReg[i] 로 사용

쓰기싱글 AsyncModbus.Ins.WriteSingleRegister(iSttAddress,iValue);

쓰기멀티 AsyncModbus.Ins.WriteMultipleRegisters(iSttAddress,iArray);

 

예제파일

WindowsFormsAppServer.zip - 기존의 이지모드버스 서버

AsyncModbusClient.zip - 라이브러리 없는 비동기 홀딩 레지스터 클라이언트

 

WindowsFormsAppServer.zip
0.17MB
AsyncModbusClient.zip
0.06MB

반응형