laatzen/Common/modbus_master_csharp/Communication/ProtocolTranslateModbusRTUClass.cs

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
}
}