class ModbusTCPClient
{
private NetworkStream _stream = default( NetworkStream );
private TcpClient _tcpClient = default( TcpClient );
#region Request / Response messages charasteristic fields
#region Function codes
/// <summary>
/// Function code specifying reading registers values.
/// </summary>
private const byte READ_INPUT_REGISTERS_FUNCTION_CODE = 4;
/// <summary>
/// Function code specifying reading device informations.
/// </summary>
private const byte READ_DEVICE_IDENTIFICATION_FUNCTION_CODE = 43;
#endregion
/// <summary>
/// Identification of a MODBUS Request / Response transaction.
/// </summary>
private ushort transactionIdentifierInternal = 0;
/// <summary>
/// 0 = MODBUS protocol.
/// </summary>
private readonly byte[] protocolIdentifier
= new byte[ 2 ] { 0 ,
0 };
/// <summary>
/// Identification of a remote server. Should be neutral (=255) in MODBUS TCP
/// </summary>
private readonly byte unitIdentifier = 255;
/// <summary>
/// CRC-32 error check code.
/// </summary>
private byte[] crc
= new byte[ 2 ] { 132 ,
10 };
#region Reading registers messages characteristic fields
/// <summary>
/// Number of following bytes.
/// </summary>
private byte[] length
= new byte[ 2 ] { 6 ,
0 };
/// <summary>
/// The address of the firs register in sequential registers read
/// </summary>
private byte[] startingAddress
= new byte[ 2 ] { 0 ,
0 };
/// <summary>
/// Number of registers to read
/// </summary>
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++ )
{
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
}