572 lines
23 KiB
C#
572 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Sockets;
|
|
using XYLEM.Base;
|
|
|
|
namespace XYLEM.Communication
|
|
{
|
|
public class ProtocolTranslateModbusRTUClass
|
|
{
|
|
public enum FunctionNumber : byte
|
|
{
|
|
NoSupport = 0,
|
|
Read_No3 = 3,
|
|
Read_No4 = 4,
|
|
Write_No15 = 15,
|
|
Write_No16 = 16,
|
|
|
|
};
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for write data telegram
|
|
/// </summary>
|
|
private enum BaseOffset
|
|
{
|
|
ID = 0,
|
|
FuncCode
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for write data telegram
|
|
/// </summary>
|
|
private enum TxOffset_W
|
|
{
|
|
AdrHigh = (int)BaseOffset.FuncCode + 1,
|
|
AdrLow,
|
|
RegCount_High,
|
|
RegCount_Low,
|
|
ByteCount,
|
|
Data_Start
|
|
};
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for read data telegram
|
|
/// </summary>
|
|
private enum TxOffset_R
|
|
{
|
|
AdrHigh = (int)BaseOffset.FuncCode + 1,
|
|
AdrLow,
|
|
RegCount_High, RegCount_Low
|
|
};
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for read data telegram
|
|
/// </summary>
|
|
private enum RxOffset_R
|
|
{
|
|
ByteCount = (int)BaseOffset.FuncCode + 1,
|
|
Data_Start
|
|
};
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for read data telegram
|
|
/// </summary>
|
|
private enum RxOffset_W
|
|
{
|
|
AdrHigh = (int)BaseOffset.FuncCode + 1,
|
|
AdrLow,
|
|
RegCount_High, RegCount_Low
|
|
};
|
|
|
|
/// <summary>
|
|
/// Modbus Byte Offset for exception data telegram
|
|
/// </summary>
|
|
private enum TxRxOffset_Exc
|
|
{
|
|
ExceptionCode = (int)BaseOffset.FuncCode + 1
|
|
};
|
|
|
|
/// <summary>
|
|
/// Calc CRC
|
|
/// </summary>
|
|
/// <param name="bByteData"></param>
|
|
/// <param name="iDataCount"></param>
|
|
/// <returns></returns>
|
|
private UInt16 CalcCrc(Byte[] bByteData, int iDataCount)
|
|
{
|
|
int u8CountByte;
|
|
byte u8Temp, u8CountBit;
|
|
UInt16 u16RetCRCvalue;
|
|
u16RetCRCvalue = 0xffff;
|
|
for (u8CountByte = 0; u8CountByte < iDataCount; u8CountByte++)
|
|
{ // Udregn CRC værdien
|
|
u16RetCRCvalue ^= bByteData[u8CountByte];
|
|
for (u8CountBit = 0; u8CountBit < 8; u8CountBit++)
|
|
{
|
|
u8Temp = (Byte)(u16RetCRCvalue & 1);
|
|
u16RetCRCvalue >>= 1;
|
|
if (u8Temp == 1)
|
|
{
|
|
u16RetCRCvalue ^= 0xa001;
|
|
}
|
|
}
|
|
}
|
|
return u16RetCRCvalue;
|
|
}
|
|
|
|
|
|
#region IProtocolTranslateClass Members
|
|
public int MaxDataReadBSize()
|
|
{
|
|
return 250; ////(252-1"Number of bytes to read")/2=125 MODBUS over _serial line specification and implementation guide V1.02 (Page 13) + Func 4/3
|
|
}
|
|
|
|
public int MaxDataWriteBSize()
|
|
{
|
|
return 230; //
|
|
}
|
|
|
|
public void StampReset() { }
|
|
|
|
public byte StampNow
|
|
{
|
|
get { return 0; } // Off
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a tx modbus message
|
|
/// </summary>
|
|
/// <param name="slaveID"></param>
|
|
/// <param name="rwFuncNo"></param>
|
|
/// <param name="adr"></param>
|
|
/// <param name="dataByteSize"></param>
|
|
/// <param name="writeData"></param>
|
|
/// <param name="IsDeviceResponse"></param>
|
|
/// <returns></returns>
|
|
public byte[] CreateTxData(int slaveID, int rwFuncNo, int adr, int dataByteSize, Byte[] writeData, bool IsDeviceResponse)
|
|
{
|
|
try
|
|
{
|
|
Byte bTemp;
|
|
Byte[] txData = null;
|
|
int count = 0, iCRC;
|
|
// Convert to modb us func number
|
|
FunctionNumber funcNo;
|
|
switch (rwFuncNo)
|
|
{
|
|
case (int)Func_RD.No3: funcNo = FunctionNumber.Read_No3; break;
|
|
case (int)Func_RD.No4: funcNo = FunctionNumber.Read_No4; break;
|
|
case (int)Func_WR.No15: funcNo = FunctionNumber.Write_No15; break;
|
|
case (int)Func_WR.No16: funcNo = FunctionNumber.Write_No16; break;
|
|
default: throw new Exception(LogMessageTool.ToFuncErrorText(this.ToString(), "CreateTxData()", "Incorrect func = " + rwFuncNo.ToString()));
|
|
}
|
|
#region ---- Read ----
|
|
if ((funcNo == FunctionNumber.Read_No3) || (funcNo == FunctionNumber.Read_No4))
|
|
{
|
|
if (IsDeviceResponse)
|
|
{
|
|
if ((writeData == null) && (dataByteSize != writeData.Length))
|
|
{
|
|
var dataBLng = (writeData == null ? 0 : writeData.Length);
|
|
throw new Exception("Data length is not the same expected " + dataByteSize + " but is " + dataBLng);
|
|
}
|
|
count = (int)RxOffset_R.Data_Start + dataByteSize;
|
|
txData = new Byte[count + 2/*to crc*/ ];
|
|
txData[(int)BaseOffset.ID] = (byte)slaveID;
|
|
txData[(int)BaseOffset.FuncCode] = (byte)funcNo;
|
|
txData[(int)RxOffset_R.ByteCount] = (byte)dataByteSize;
|
|
// Copy and Swap High And Low byte in word
|
|
for (int offset = 0; offset < dataByteSize; offset += 2)
|
|
{
|
|
bTemp = writeData[offset];
|
|
txData[(int)RxOffset_R.Data_Start + offset] = writeData[offset + 1];
|
|
txData[(int)RxOffset_R.Data_Start + offset + 1] = bTemp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dataByteSize = (dataByteSize == 1) ? 2 : dataByteSize;
|
|
txData = new Byte[(int)TxOffset_R.RegCount_Low + 3];
|
|
txData[(int)BaseOffset.ID] = (byte)slaveID;
|
|
txData[(int)BaseOffset.FuncCode] = (byte)funcNo;
|
|
txData[(int)TxOffset_R.AdrHigh] = (byte)(adr >> 8);
|
|
txData[(int)TxOffset_R.AdrLow] = (byte)adr;
|
|
txData[(int)TxOffset_R.RegCount_High] = (byte)0;
|
|
txData[(int)TxOffset_R.RegCount_Low] = (byte)(dataByteSize >> 1);
|
|
count = (int)TxOffset_R.RegCount_Low + 1;
|
|
}
|
|
}
|
|
#endregion// Read
|
|
#region ---- Write ----
|
|
if ((funcNo == FunctionNumber.Write_No16) || (funcNo == FunctionNumber.Write_No15))
|
|
{
|
|
dataByteSize = writeData.Length;
|
|
dataByteSize = (dataByteSize == 1) ? 2 : dataByteSize;
|
|
txData = new Byte[(int)TxOffset_W.Data_Start + dataByteSize + 2];
|
|
txData[(int)BaseOffset.ID] = (byte)slaveID;
|
|
txData[(int)BaseOffset.FuncCode] = (byte)funcNo;
|
|
txData[(int)TxOffset_W.AdrHigh] = (byte)(adr >> 8);
|
|
txData[(int)TxOffset_W.AdrLow] = (byte)adr;
|
|
txData[(int)TxOffset_W.RegCount_High] = (byte)0;
|
|
txData[(int)TxOffset_W.RegCount_Low] = (byte)(dataByteSize >> 1);
|
|
txData[(int)TxOffset_W.ByteCount] = (byte)dataByteSize;
|
|
// Copy and Swap High And Low byte in word
|
|
for (count = 0; count < dataByteSize; count += 2)
|
|
{
|
|
bTemp = writeData[count];
|
|
txData[(int)TxOffset_W.Data_Start + count] = writeData[count + 1];
|
|
txData[(int)TxOffset_W.Data_Start + count + 1] = bTemp;
|
|
}
|
|
count = dataByteSize + (int)TxOffset_W.Data_Start;
|
|
}
|
|
#endregion// Write
|
|
iCRC = CalcCrc(txData, count);
|
|
txData[count] = (byte)iCRC;
|
|
txData[count + 1] = (byte)(iCRC >> 8);
|
|
return txData; // OK
|
|
}
|
|
catch
|
|
{
|
|
return null; // Error
|
|
}
|
|
}
|
|
|
|
public bool IsDataOkay(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
int iCRC;
|
|
int length = (data != null) ? data.Length : 0;
|
|
iCRC = data[length - 1];
|
|
iCRC = data[length - 2] + (iCRC << 8);
|
|
return (CalcCrc(data, length - 2) == iCRC) || (CalcCrc(data, length - 2) == (((iCRC & 0xff) << 8) | (iCRC >> 8))); // Keller Fix
|
|
}
|
|
|
|
public bool IsRxDataOkay(byte[] data)
|
|
{
|
|
return IsDataOkay(data, true);
|
|
}
|
|
|
|
|
|
public bool IsDataRW(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return (data[(int)BaseOffset.FuncCode] < 0x80);
|
|
}
|
|
|
|
public bool IsRead(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)BaseOffset.FuncCode] == (int)FunctionNumber.Read_No3
|
|
|| data[(int)BaseOffset.FuncCode] == (int)FunctionNumber.Read_No4;
|
|
}
|
|
|
|
public byte[] GetReadData(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
try
|
|
{
|
|
Byte[] retData = new Byte[(int)data[(int)RxOffset_R.ByteCount]];
|
|
int offset = 0, length = 0, func = data[(int)BaseOffset.FuncCode];
|
|
if ((func == (int)FunctionNumber.Read_No3) || (func == (int)FunctionNumber.Read_No4))
|
|
{
|
|
offset = (int)RxOffset_R.Data_Start;
|
|
length = retData.Length;
|
|
if (data.Count() < length)
|
|
{
|
|
throw new Exception($"Data lng ={data.Count()} is expecting min. {length}");
|
|
}
|
|
if (length == 0)
|
|
{
|
|
return null; // Error
|
|
}
|
|
for (int iTemp = 0; (iTemp < length); iTemp += 2)
|
|
{
|
|
retData[iTemp + 1] = data[offset + iTemp];
|
|
retData[iTemp] = data[offset + iTemp + 1];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return null; // Error
|
|
}
|
|
return retData;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var dataAsTxt = (data==null) ? "Data is null" : data.ToHex(true, false);
|
|
var message = $"Error: data={dataAsTxt}\n\r" + ex.Message + "\n\r";
|
|
Console.WriteLine(message);
|
|
AppConst.Logger.AddFuncLog(true, this.ToString(), "GetReadData()", message);
|
|
throw new Exception(message);
|
|
}
|
|
}
|
|
|
|
public bool IsWrite(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return (data[(int)BaseOffset.FuncCode] == (int)FunctionNumber.Write_No16);
|
|
}
|
|
|
|
public bool IsAdrPossible(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public int GetRxAdr(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return ((int)data[(int)RxOffset_W.AdrHigh] << 8) | (int)data[(int)RxOffset_W.AdrLow];
|
|
}
|
|
|
|
public bool IsRxWaitForCommand(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return
|
|
(data[(int)TxRxOffset_Exc.ExceptionCode] == (int)DeviceExceptionData.SpecialType.WaitForReset)
|
|
|| IsRxWaitForFinishMax5S(data, IsDeviceResponse)
|
|
|| IsRxWaitForFinishMax60S(data, IsDeviceResponse)
|
|
|| IsRxWaitForFinishMax5Min(data, IsDeviceResponse);
|
|
}
|
|
|
|
public bool IsRxWaitForFinishMax5S(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)TxRxOffset_Exc.ExceptionCode] == (int)DeviceExceptionData.SpecialType.WaitForFinishMax5s;
|
|
}
|
|
|
|
public bool IsRxWaitForFinishMax60S(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)TxRxOffset_Exc.ExceptionCode] == (int)DeviceExceptionData.SpecialType.WaitForFinishMax60s;
|
|
}
|
|
|
|
public bool IsRxWaitForFinishMax5Min(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)TxRxOffset_Exc.ExceptionCode] == (int)DeviceExceptionData.SpecialType.WaitForFinishMax5Min;
|
|
}
|
|
|
|
public bool IsAcknowledge(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)BaseOffset.FuncCode] >= 0x80;
|
|
}
|
|
|
|
public int GetRxAcknowledgeCode(byte[] data, bool IsDeviceResponse)
|
|
{
|
|
return data[(int)TxRxOffset_Exc.ExceptionCode];
|
|
}
|
|
|
|
public ProtocolType Protocol
|
|
{
|
|
get { return ProtocolType.ModbusRTU; }
|
|
}
|
|
|
|
public int GetFuncNo(byte[] rx_data, bool IsDeviceResponse)
|
|
{
|
|
return (int)rx_data[(int)BaseOffset.FuncCode];
|
|
}
|
|
|
|
public int GetStartAdr(byte[] rx_data, bool IsDeviceResponse)
|
|
{
|
|
if (IsWrite(rx_data, IsDeviceResponse))
|
|
{
|
|
return GetRxAdr(rx_data, IsDeviceResponse);
|
|
}
|
|
else if (IsRead(rx_data, IsDeviceResponse))
|
|
{
|
|
return GetRxAdr(rx_data, IsDeviceResponse); // Is master read
|
|
}
|
|
throw new Exception("Unknown protocol type and location for start adr.");
|
|
}
|
|
|
|
public int GetNumberOfDataBytes(byte[] rx_data, bool IsDeviceResponse)
|
|
{
|
|
if (IsWrite(rx_data, IsDeviceResponse))
|
|
{
|
|
return rx_data[(int)RxOffset_W.RegCount_Low] * 2;
|
|
}
|
|
else if (IsRead(rx_data, IsDeviceResponse))
|
|
{
|
|
if (IsDeviceResponse)
|
|
{
|
|
return rx_data[(int)RxOffset_R.ByteCount];
|
|
}
|
|
else
|
|
{
|
|
return rx_data[(int)RxOffset_W.RegCount_Low] * 2;
|
|
}
|
|
}
|
|
throw new Exception("Unknown protocol type and location for number of bytes");
|
|
}
|
|
|
|
public int GetComID(byte[] rx_data, bool IsDeviceResponse)
|
|
{
|
|
return rx_data[(int)BaseOffset.ID];
|
|
}
|
|
|
|
public byte[] GetWriteData(byte[] rx_data, bool IsDeviceResponse)
|
|
{
|
|
var bytesToWrite = GetNumberOfDataBytes(rx_data, IsDeviceResponse);
|
|
try
|
|
{
|
|
var data = Base.ByteArrayExtensionClass.SwapByteInWord(rx_data.Copy((int)TxOffset_W.Data_Start, bytesToWrite));
|
|
return data;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
throw new Exception("Fail to copy write data! data byte size = " + bytesToWrite);
|
|
}
|
|
}
|
|
|
|
public byte[] CreateTxData(int slaveID, int rwFuncNo, int adr, int writeResponse, bool IsDeviceResponse)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public string ToInfo(byte[] input, bool IsDeviceResponse)
|
|
{
|
|
string ret = "";
|
|
try
|
|
{
|
|
bool isOkay = IsDataOkay(input, IsDeviceResponse);
|
|
if (isOkay)
|
|
{
|
|
int id = GetComID(input, IsDeviceResponse);
|
|
int func = GetFuncNo(input, IsDeviceResponse);
|
|
ret += string.Format("Type {0}" + Environment.NewLine, IsDeviceResponse ? "Response" : "Request");
|
|
ret += string.Format("ID {0}" + Environment.NewLine, id);
|
|
bool isExeption = !IsDataRW(input, IsDeviceResponse);
|
|
try
|
|
{
|
|
var func2Test = func;
|
|
if (isExeption)
|
|
{
|
|
func2Test -= 0x80; // Get function from exeption code
|
|
ret += "Exception ";
|
|
}
|
|
ret += string.Format("Func. {0} / {1} hex", func2Test, func2Test.ToString("x"));
|
|
switch (func2Test)
|
|
{
|
|
case (int)FunctionNumber.Read_No3:
|
|
case (int)FunctionNumber.Read_No4:
|
|
ret += " is Read";
|
|
break;
|
|
case (int)FunctionNumber.Write_No15:
|
|
case (int)FunctionNumber.Write_No16:
|
|
if (IsDeviceResponse)
|
|
{
|
|
ret += " Incorrect write replay from slave!!!!";
|
|
}
|
|
else
|
|
{
|
|
ret += " is Write";
|
|
}
|
|
break;
|
|
default: ret += " Unknown func. type!"; break;
|
|
}
|
|
}
|
|
catch { ret += string.Format(" Unknown func. type!"); }
|
|
ret += Environment.NewLine;
|
|
if (isExeption)
|
|
{
|
|
var exeptioncode = (int)input[(int)BaseOffset.FuncCode + 1];
|
|
ret += string.Format("Exception code {0} / {1} hex", exeptioncode, exeptioncode.ToString("x"));
|
|
}
|
|
else
|
|
{
|
|
ret += string.Format("Number of Bytes {0}" + Environment.NewLine, GetNumberOfDataBytes(input, IsDeviceResponse));
|
|
if (IsDeviceResponse)
|
|
{
|
|
byte[] data = null;
|
|
if (IsRead(input, IsDeviceResponse))
|
|
{
|
|
data = GetReadData(input, IsDeviceResponse);
|
|
}
|
|
if (data != null)
|
|
{
|
|
data = Base.ByteArrayExtensionClass.SwapByteInWord(data);
|
|
ret += string.Format("Data is = {0}", Base.NumberConvert.ToHex(data, true, false));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
byte[] data = null;
|
|
ret += string.Format("Start Address {0}" + Environment.NewLine, GetStartAdr(input, IsDeviceResponse));
|
|
if (IsWrite(input, IsDeviceResponse))
|
|
{
|
|
data = GetWriteData(input, IsDeviceResponse);
|
|
}
|
|
if (data != null)
|
|
{
|
|
data = Base.ByteArrayExtensionClass.SwapByteInWord(data);
|
|
ret += string.Format("Data is = {0}\n", Base.NumberConvert.ToHex(data, true, false));
|
|
}
|
|
}
|
|
}
|
|
//No44, // or 68 as a Special myconnect IO module telegram for https://mjksvn.world.fluidtechnology.net:4443/svn/development/8440XX Connect and myConnect/myConnect/trunk/docs/IO modules communication.docx
|
|
}
|
|
else if ((input != null) && (input.Length >= (16 + 2)))
|
|
{ // Check if it is
|
|
var telegrams = input.SliceSameSize(0, (16 + 2));
|
|
foreach (var telegram in telegrams)
|
|
{
|
|
if (IsDataOkay(telegram, IsDeviceResponse))
|
|
{
|
|
ret += "¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤" + Environment.NewLine;
|
|
int offset = 0;
|
|
ret += "ID[" + telegram[offset++] + "]";
|
|
ret += " func[" + telegram[offset++] + "]";
|
|
int Quantity = telegram[offset++];
|
|
int[] Descriptors = new int[] { telegram[offset++], 0 };
|
|
{ // print out Status
|
|
ret += Environment.NewLine;
|
|
int Status = telegram[offset++];
|
|
switch (Status)
|
|
{
|
|
case 0x11: ret += "Both recognized"; break;
|
|
case 0x01: ret += "Lower OK, upper ERROR"; break;
|
|
case 0x10: ret += "Lower ERROR, upper OK"; break;
|
|
case 0x00: ret += "Both ERROR"; break;
|
|
default: ret += "Unknown status" + Status.ToHex(true); break;
|
|
}
|
|
}
|
|
{// Print out Descriptor
|
|
ret += Environment.NewLine;
|
|
Descriptors[1] = telegram[offset++];
|
|
foreach (var Descriptor in Descriptors)
|
|
{
|
|
ret += " Type[";
|
|
switch (Descriptor)
|
|
{
|
|
case 0: ret += "none"; break;
|
|
case 1: ret += "6DI"; break;
|
|
case 2: ret += "6DO"; break;
|
|
case 3: ret += "3AI"; break;
|
|
case 4: ret += "3AO"; break;
|
|
default: ret += "Unknown" + Descriptor.ToHex(true); break;
|
|
}
|
|
ret += "]";
|
|
}
|
|
}
|
|
{// Print out data
|
|
ret += Environment.NewLine + "Raw[" + telegram.Slice(offset, telegram.Length - offset).ToHex(true, false) + "]";
|
|
|
|
var u32Values = telegram.SliceSameSize(offset, 4);
|
|
foreach (var u32Value in u32Values)
|
|
{
|
|
ret += Environment.NewLine;
|
|
ret += " DI&DO[" + u32Value[0].ToBin(true) + "]/AI&AO[" + (new DataUnion()).PutByteArray(u32Value, DataUnion.DataByteLayoutType.SwapWords).F32Data + "]";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret += string.Format("Message is {0} is wrong!" + Environment.NewLine, telegram.ToHex(true, false));
|
|
}
|
|
ret += Environment.NewLine;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret += string.Format("Message is {0}" + Environment.NewLine, isOkay ? "okay" : "CRC is wrong!");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ret += "Error = " + ex.Message;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Added to IProtocolTranslateClass to use in Comli
|
|
/// </summary>
|
|
public byte[] CreateTxData(int slaveID, int rwFuncNo, int adr, int dataByteSize, byte[] writeData, bool IsDeviceResponse, ref int pageNumberNow)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |