class ModbusTCPClient { private NetworkStream _stream = default( NetworkStream ); private TcpClient _tcpClient = default( TcpClient ); #region Request / Response messages charasteristic fields #region Function codes /// /// Function code specifying reading registers values. /// private const byte READ_INPUT_REGISTERS_FUNCTION_CODE = 4; /// /// Function code specifying reading device informations. /// private const byte READ_DEVICE_IDENTIFICATION_FUNCTION_CODE = 43; #endregion /// /// Identification of a MODBUS Request / Response transaction. /// private ushort transactionIdentifierInternal = 0; /// /// 0 = MODBUS protocol. /// private readonly byte[] protocolIdentifier = new byte[ 2 ] { 0 , 0 }; /// /// Identification of a remote server. Should be neutral (=255) in MODBUS TCP /// private readonly byte unitIdentifier = 255; /// /// CRC-32 error check code. /// private byte[] crc = new byte[ 2 ] { 132 , 10 }; #region Reading registers messages characteristic fields /// /// Number of following bytes. /// private byte[] length = new byte[ 2 ] { 6 , 0 }; /// /// The address of the firs register in sequential registers read /// private byte[] startingAddress = new byte[ 2 ] { 0 , 0 }; /// /// Number of registers to read /// private byte[] quantity = new byte[ 2 ] { 255 , 0 }; #endregion #region Reading device information characteristic fields #endregion #endregion #region Delegates and Events public delegate void ConnectedChanged( string message ); public delegate void ErrorHandler( string message ); public event ConnectedChanged _connectedChangedEvent = null; public event ErrorHandler _connectionErrorEvent = null; public event ErrorHandler _readingDataErrorEvent = null; #endregion #region Construction and connection maintenance public ModbusTCPClient() { } public void Connect( int timeout ) { try { this._tcpClient = new TcpClient( "192.168.0.152" , 502 ); this._stream = this._tcpClient.GetStream(); this._stream.WriteTimeout = timeout; this._stream.ReadTimeout = timeout; } catch ( Exception e ) { this._connectionErrorEvent( e.Message ); return; } this._connectedChangedEvent( "Connected to the server." ); } public void CloseConnection() { if ( this._stream != null ) { this._stream.Dispose(); this._stream = null; } if ( this._tcpClient != null ) { this._tcpClient.Close(); this._tcpClient = null; } this._connectedChangedEvent( "Disconnected from the server." ); } #endregion public void ReadDeviceIdentification() { if ( this._stream != null && this._stream.CanRead ) { byte[] transactionIdentifier = this.GetTransactionIdentifier(); byte MEIType = 14; byte deviceIDCode = 1; byte[] requestArray = new byte[] { transactionIdentifier[1] , transactionIdentifier[0] , protocolIdentifier[1] , protocolIdentifier[0] , length[1] , length[0] , unitIdentifier , READ_DEVICE_IDENTIFICATION_FUNCTION_CODE , MEIType , 0 , deviceIDCode , startingAddress[1] , startingAddress[0] , crc[0] , crc[1] }; byte[] responseArray = new byte[ 2100 ]; //TODO: get reasonable value, MODBUS ADU = 260 bytes (253 - PDU and 7 MBAP) //The register data in the response message are packed as two bytes per register, with the //binary contents right justified within each byte.For each register, the first byte contains the //high order bits and the second contains the low order bits. if ( ( this._tcpClient == null ) || ( !this._tcpClient.Connected ) ) { return; } try { this._stream.Write( requestArray , 0 , checked(( int )requestArray.Length - 2) ); this._stream.Read( responseArray , 0 , responseArray.Length ); } catch ( Exception e ) { this._connectionErrorEvent( e.Message ); return; } } else { this._readingDataErrorEvent( "TCP stream is not ready to be read." ); } } public short[] ReadInputRegisters( int registersQuantity ) //TODO int[] - check what valueas are returned from server { if ( this._stream != null && this._stream.CanRead ) { byte[] transactionIdentifier = new byte[ 2 ]; transactionIdentifier = this.GetTransactionIdentifier(); byte[] requestArray = new byte[] { transactionIdentifier[1] , transactionIdentifier[0] , protocolIdentifier[1] , protocolIdentifier[0] , length[1] , length[0] , unitIdentifier , READ_INPUT_REGISTERS_FUNCTION_CODE , startingAddress[1] , startingAddress[0] , quantity[1] , quantity[0] , crc[0] , crc[1] }; byte[] responseArray = new byte[ 2100 ]; //TODO: get reasonable value if ( ( this._tcpClient == null ) || ( !this._tcpClient.Connected ) ) { return null; } try { this._stream.Write( requestArray , 0 , checked(( int )requestArray.Length - 2) ); this._stream.Read( responseArray , 0 , responseArray.Length ); } catch ( Exception e ) { this._connectionErrorEvent( e.Message ); return null; } if ( this.CheckResponseForExceptions( READ_INPUT_REGISTERS_FUNCTION_CODE , responseArray[ 7 ] , responseArray[ 8 ] ) ) { short[] valuesFromServer = new short[ registersQuantity ]; for ( int i = 0 ; i < registersQuantity ; i++ ) { byte num3 = responseArray[ checked(9 + checked(i * 2)) ]; byte num4 = responseArray[ checked(checked(9 + checked(i * 2)) + 1) ]; responseArray[ checked(9 + checked(i * 2)) ] = num4; responseArray[ checked(checked(9 + checked(i * 2)) + 1) ] = num3; valuesFromServer[ i ] = BitConverter.ToInt16( responseArray , checked(9 + checked(i * 2)) ); } return valuesFromServer; } } else { this._readingDataErrorEvent( "TCP stream is not ready to be read." ); } return null; } #region Helper methods private bool CheckResponseForExceptions( byte functionCode , byte response7 , byte response8 ) { if ( response7 != functionCode ) { switch ( response8 ) { case 1: { this._readingDataErrorEvent( "Function code not supported by master" ); return false; } case 2: { this._readingDataErrorEvent( "Starting address invalid or starting address + quantity invalid" ); return false; } case 3: { this._readingDataErrorEvent( "Quantity of registers invalid" ); return false; } case 4: { this._readingDataErrorEvent( "Error reading" ); return false; } default: { this._readingDataErrorEvent( "Unspecified error" ); return false; } } } return true; } private byte[] GetTransactionIdentifier() { this.transactionIdentifierInternal++; return BitConverter.GetBytes( this.transactionIdentifierInternal ); } #endregion }