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, }; /// /// Modbus Byte Offset for write data telegram /// private enum BaseOffset { ID = 0, FuncCode } /// /// Modbus Byte Offset for write data telegram /// private enum TxOffset_W { AdrHigh = (int)BaseOffset.FuncCode + 1, AdrLow, RegCount_High, RegCount_Low, ByteCount, Data_Start }; /// /// Modbus Byte Offset for read data telegram /// private enum TxOffset_R { AdrHigh = (int)BaseOffset.FuncCode + 1, AdrLow, RegCount_High, RegCount_Low }; /// /// Modbus Byte Offset for read data telegram /// private enum RxOffset_R { ByteCount = (int)BaseOffset.FuncCode + 1, Data_Start }; /// /// Modbus Byte Offset for read data telegram /// private enum RxOffset_W { AdrHigh = (int)BaseOffset.FuncCode + 1, AdrLow, RegCount_High, RegCount_Low }; /// /// Modbus Byte Offset for exception data telegram /// private enum TxRxOffset_Exc { ExceptionCode = (int)BaseOffset.FuncCode + 1 }; /// /// Calc CRC /// /// /// /// 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 } /// /// Create a tx modbus message /// /// /// /// /// /// /// /// 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; } /// /// Added to IProtocolTranslateClass to use in Comli /// public byte[] CreateTxData(int slaveID, int rwFuncNo, int adr, int dataByteSize, byte[] writeData, bool IsDeviceResponse, ref int pageNumberNow) { throw new NotImplementedException(); } #endregion } }