using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Diagnostics;
namespace DataService
{
public class ModbusTCPReader : IPLCDevice
{
public const byte fctReadCoil = 1;
public const byte fctReadDiscreteInputs = 2;
public const byte fctReadHoldingRegister = 3;
public const byte fctReadInputRegister = 4;
public const byte fctWriteSingleCoil = 5;
public const byte fctWriteSingleRegister = 6;
public const byte fctWriteMultipleCoils = 15;
public const byte fctWriteMultipleRegister = 16;
public const byte fctReadWriteMultipleRegister = 23;
/// Constant for exception illegal function.
public const byte excIllegalFunction = 1;
/// Constant for exception illegal data address.
public const byte excIllegalDataAdr = 2;
/// Constant for exception illegal data value.
public const byte excIllegalDataVal = 3;
/// Constant for exception slave device failure.
public const byte excSlaveDeviceFailure = 4;
/// Constant for exception acknowledge.
public const byte excAck = 5;
/// Constant for exception slave is busy/booting up.
public const byte excSlaveIsBusy = 6;
/// Constant for exception gate path unavailable.
public const byte excGatePathUnavailable = 10;
/// Constant for exception not connected.
public const byte excExceptionNotConnected = 253;
/// Constant for exception connection lost.
public const byte excExceptionConnectionLost = 254;
/// Constant for exception response timeout.
public const byte excExceptionTimeout = 255;
/// Constant for exception wrong offset.
private const byte excExceptionOffset = 128;
/// Constant for exception send failt.
private const byte excSendFailt = 100;
private static int _timeout;
private Socket tcpSynCl;
private byte[] tcpSynClBuffer = new byte[256];
/// Response data event. This event is called when new data arrives
public delegate void ResponseData(ushort id, byte function, byte[] data);
/// Exception data event. This event is called when the data is incorrect
public delegate void ExceptionData(int id, byte function, byte exception);
/// Exception data event. This event is called when the data is incorrect
short _id;
public short ID
{
get
{
return _id;
}
}
string _ip;
public string ServerName
{
get { return _ip; }
set { _ip = value; }
}
public bool IsClosed
{
get
{
return tcpSynCl == null || tcpSynCl.Connected == false;
}
}
public int TimeOut
{
get { return _timeout; }
set{ _timeout = value; }
}
public DeviceDriver Driver
{
get
{
return DeviceDriver.ModbusTcp;
}
}
List _grps = new List(20);
public IEnumerable Groups
{
get { return _grps; }
}
IDataServer _server;
public IDataServer Parent
{
get { return _server; }
}
public ModbusTCPReader(IDataServer server,short id, string ip, int timeOut = 500)
{
_id = id;
_server = server;
_ip = ip;
_timeout = timeOut;
}
public bool Connect()
{
try
{
int port = 502;
//IPAddress ip = IPAddress.Parse(_ip);
// ----------------------------------------------------------------
// Connect synchronous client
tcpSynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpSynCl.Connect(_ip, port);
tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout);
tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout);
tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
return true;
}
catch (SocketException error)
{
if (OnClose != null)
OnClose(this, new ShutdownRequestEventArgs(error.Message));
return false;
}
}
private byte[] CreateReadHeader(int id, int startAddress, ushort length, byte function)
{
byte[] data = new byte[12];
byte[] _id = BitConverter.GetBytes((short)id);
data[0] = _id[0]; // Slave id high byte
data[1] = _id[1]; // Slave id low byte
data[5] = 6; // Message size
data[6] = 0; // Slave address
data[7] = function; // Function code
byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress));
data[8] = _adr[0]; // Start address
data[9] = _adr[1]; // Start address
byte[] _length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)length));
data[10] = _length[0]; // Number of data to read
data[11] = _length[1]; // Number of data to read
return data;
}
private byte[] CreateWriteHeader(int id, int startAddress, ushort numData, ushort numBytes, byte function)
{
byte[] data = new byte[numBytes + 11];
byte[] _id = BitConverter.GetBytes(id);
data[0] = _id[0]; // Slave id high byte
data[1] = _id[1]; // Slave id low byte+
byte[] _size = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)(5 + numBytes)));
data[4] = _size[0]; // Complete message size in bytes
data[5] = _size[1]; // Complete message size in bytes
data[6] = 0; // Slave address
data[7] = function; // Function code
byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress));
data[8] = _adr[0]; // Start address
data[9] = _adr[1]; // Start address
if (function >= fctWriteMultipleCoils)
{
byte[] _cnt = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)numData));
data[10] = _cnt[0]; // Number of bytes
data[11] = _cnt[1]; // Number of bytes
data[12] = (byte)(numBytes - 2);
}
return data;
}
private byte[] WriteSyncData(byte[] write_data)
{
short id = BitConverter.ToInt16(write_data, 0);
if (IsClosed) CallException(id, write_data[7], excExceptionConnectionLost);
else
{
try
{
tcpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None);
int result = tcpSynCl.Receive(tcpSynClBuffer, 0, tcpSynClBuffer.Length, SocketFlags.None);
byte function = tcpSynClBuffer[7];
byte[] data;
if (result == 0) CallException(id, write_data[7], excExceptionConnectionLost);
// ------------------------------------------------------------
// Response data is slave exception
if (function > excExceptionOffset)
{
function -= excExceptionOffset;
CallException(id, function, tcpSynClBuffer[8]);
return null;
}
// ------------------------------------------------------------
// Write response data
else if ((function >= fctWriteSingleCoil) && (function != fctReadWriteMultipleRegister))
{
data = new byte[2];
Array.Copy(tcpSynClBuffer, 10, data, 0, 2);
}
// ------------------------------------------------------------
// Read response data
else
{
data = new byte[tcpSynClBuffer[8]];
Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]);
}
return data;
}
catch (SocketException)
{
CallException(id, write_data[7], excExceptionConnectionLost);
}
}
return null;
}
public byte[] WriteSingleCoils(int id, int startAddress, bool OnOff)
{
byte[] data;
data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleCoil);
if (OnOff == true) data[10] = 255;
else data[10] = 0;
return WriteSyncData(data);
}
public byte[] WriteMultipleCoils(int id, int startAddress, ushort numBits, byte[] values)
{
byte numBytes = Convert.ToByte(values.Length);
byte[] data;
data = CreateWriteHeader(id, startAddress, numBits, (byte)(numBytes + 2), fctWriteMultipleCoils);
Array.Copy(values, 0, data, 13, numBytes);
return WriteSyncData(data);
}
public byte[] WriteSingleRegister(int id, int startAddress, byte[] values)
{
byte[] data;
data = CreateWriteHeader(id, startAddress, 1, 1, fctWriteSingleRegister);
data[10] = values[0];
data[11] = values[1];
return WriteSyncData(data);
}
public byte[] WriteMultipleRegister(int id, int startAddress, byte[] values)
{
ushort numBytes = Convert.ToUInt16(values.Length);
if (numBytes % 2 > 0) numBytes++;
byte[] data;
data = CreateWriteHeader(id, startAddress, Convert.ToUInt16(numBytes / 2), Convert.ToUInt16(numBytes + 2), fctWriteMultipleRegister);
Array.Copy(values, 0, data, 13, values.Length);
return WriteSyncData(data);
}
public int PDU
{
get { return 256; }
}
public int GetBlockSize(int area)
{
return area > 2 ? PDU / 2 : PDU;
}
public DeviceAddress GetDeviceAddress(string address)
{
DeviceAddress dv = DeviceAddress.Empty;
if (address == null || address.Length < 3)
return dv;
else
{
int index = address.IndexOf('.');
switch (address[0])
{
case '0':
dv.Area = fctReadCoil;
if (index > 0)
{
dv.Start = 64 * int.Parse(address.Substring(1, index - 1));
dv.Bit = byte.Parse(address.Substring(index + 1));
}
break;
case '1':
dv.Area = fctReadDiscreteInputs;
if (index > 0)
{
dv.Start = 64 * int.Parse(address.Substring(1, index - 1));
dv.Bit = byte.Parse(address.Substring(index + 1));
}
break;
case '4':
dv.Area = fctReadHoldingRegister;
if (index > 0)
{
dv.Start = int.Parse(address.Substring(1, index - 1));
dv.Bit = byte.Parse(address.Substring(index + 1));
}
else
dv.Start = int.Parse(address.Mid(1));
break;
case '3':
dv.Area = fctReadInputRegister;
if (index > 0)
{
dv.Start = int.Parse(address.Substring(1, index - 1));
dv.Bit = byte.Parse(address.Substring(index + 1));
}
else
dv.Start = int.Parse(address.Mid(1));
break;
}
}
return dv;
}
public string GetAddress(DeviceAddress address)
{
return string.Empty;
}
public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false)
{
ModbusGroup grp = new ModbusGroup(id, name, updateRate, active, this);
_grps.Add(grp);
return grp;
}
public bool RemoveGroup(IGroup grp)
{
grp.IsActive = false;
return _grps.Remove(grp);
}
public void Dispose()
{
if (tcpSynCl != null)
{
if (tcpSynCl.Connected)
{
try { tcpSynCl.Shutdown(SocketShutdown.Both); }
catch { }
tcpSynCl.Close();
}
tcpSynCl = null;
}
foreach (IGroup grp in _grps)
{
grp.Dispose();
}
_grps.Clear();
}
internal string GetErrorString(byte exception)
{
switch (exception)
{
case excIllegalFunction:
return "Constant for exception illegal function.";
case excIllegalDataAdr:
return "Constant for exception illegal data address.";
case excIllegalDataVal:
return "Constant for exception illegal data value.";
case excSlaveDeviceFailure:
return "Constant for exception slave device failure.";
case excAck:
return "Constant for exception acknowledge.";
case excSlaveIsBusy:
return "Constant for exception slave is busy/booting up.";
case excGatePathUnavailable:
return "Constant for exception gate path unavailable.";
case excExceptionNotConnected:
return "Constant for exception not connected.";
case excExceptionConnectionLost:
return "Constant for exception connection lost.";
case excExceptionTimeout:
return "Constant for exception response timeout.";
case excExceptionOffset:
return "Constant for exception wrong offset.";
case excSendFailt:
return "Constant for exception send failt.";
}
return string.Empty;
}
internal void CallException(int id, byte function, byte exception)
{
if (tcpSynCl == null) return;
if (exception == excExceptionConnectionLost && IsClosed == false)
{
if (OnClose != null)
OnClose(this, new ShutdownRequestEventArgs(GetErrorString(exception)));
}
}
public byte[] ReadBytes(DeviceAddress address, ushort size)
{
return WriteSyncData(CreateReadHeader(address.Area, address.Start, size, (byte)address.Area));
}
public ItemData ReadInt32(DeviceAddress address)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 4, (byte)address.Area));
if (data == null)
return new ItemData(0, 0, QUALITIES.QUALITY_BAD);
else
return new ItemData(IPAddress.HostToNetworkOrder(BitConverter.ToInt32(data, 0)), 0, QUALITIES.QUALITY_GOOD);
}
public ItemData ReadInt16(DeviceAddress address)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 2, (byte)address.Area));
if (data == null)
return new ItemData(0, 0, QUALITIES.QUALITY_BAD);
else
return new ItemData(IPAddress.HostToNetworkOrder(BitConverter.ToInt16(data, 0)), 0, QUALITIES.QUALITY_GOOD);
}
public ItemData ReadByte(DeviceAddress address)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 1, (byte)address.Area));
if (data == null)
return new ItemData(0, 0, QUALITIES.QUALITY_BAD);
else
return new ItemData(data[0], 0, QUALITIES.QUALITY_GOOD);
}
public ItemData ReadString(DeviceAddress address, ushort size)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, size, (byte)address.Area));
if (data == null)
return new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD);
else
return new ItemData(Encoding.Default.GetString(data, 0, size), 0, QUALITIES.QUALITY_GOOD);//是否考虑字节序问题?
}
public unsafe ItemData ReadFloat(DeviceAddress address)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 4, (byte)address.Area));
if (data == null)
return new ItemData(0.0f, 0, QUALITIES.QUALITY_BAD);
else
{
int value = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(data, 0));
return new ItemData(*(((float*)&value)), 0, QUALITIES.QUALITY_GOOD);
}
}
public unsafe ItemData ReadBit(DeviceAddress address)
{
byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 1, (byte)address.Area));
if (data == null)
return new ItemData(false, 0, QUALITIES.QUALITY_BAD);
else
{
fixed (byte* p = data)
{
int* p1 = (int*)p;
return new ItemData((*p1 & (1 << (address.Bit < 8 ? address.Bit + 8 : address.Bit - 8)))
!= 0, 0, QUALITIES.QUALITY_GOOD);
}
}
}
public ItemData