public string ToString(bool includeOriginalMessage) { StringBuilder strBuilder = new StringBuilder(); strBuilder.Append('-', 60).AppendLine(); strBuilder.AppendFormat("{0:D2} (0x{0:X2}) ", FunctionCode); var baseAddress = 0; switch (FunctionCode) { case 1: strBuilder.AppendLine("Read Coils"); break; case 2: strBuilder.AppendLine("Read Input Status"); break; case 3: strBuilder.AppendLine("Read Holding Registers"); baseAddress = ModbusHoldingRegisterBaseAddress; break; case 4: strBuilder.AppendLine("Read Input Registers"); baseAddress = ModbusInputRegisterBaseAddress; break; case 5: strBuilder.AppendLine("Force Single Coil"); baseAddress = ModbusCoilBaseAddress; break; case 6: strBuilder.AppendLine("Preset Single Register"); baseAddress = ModbusHoldingRegisterBaseAddress; break; case 8: strBuilder.AppendLine("Diagnostic"); break; case 15: strBuilder.AppendLine("Write Multiple Coils"); baseAddress = ModbusCoilBaseAddress; break; case 16: strBuilder.AppendLine("Write Multiple Holding Registers"); baseAddress = ModbusHoldingRegisterBaseAddress; break; default: strBuilder.AppendLine("Unknown Function Code"); break; } strBuilder.Append('-', 60).AppendLine(); if (includeOriginalMessage) { strBuilder.AppendLine(string.Format("{0,-20}{1}", "Original Message:", OriginalMessageString)); } if (MessageType == ModbusMessageType.Receive) { strBuilder.AppendLine(string.Format("{0,-20}{1}", "Message Type:", "Receive")); } else if (MessageType == ModbusMessageType.Transmit) { strBuilder.AppendLine(string.Format("{0,-20}{1}", "Message Type:", "Transmit")); } if (MessageRole == ModbusMessageRole.Request) { strBuilder.AppendLine(string.Format("{0,-20}{1}", "Message Role:", "Request")); } else if (MessageRole == ModbusMessageRole.Response) { strBuilder.AppendLine(string.Format("{0,-20}{1}", "Message Role:", "Response")); } strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X2})", "Slave ID:", SlaveId)); strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X2})", "Function Code:", FunctionCode)); if (StartAddress.HasValue) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X4})", "Start Address:", StartAddress)); } if (CoilCount.HasValue) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X4})", "Coil Count:", CoilCount)); } if (RegisterCount.HasValue) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X4})", "Register Count:", RegisterCount)); } if (SingleRegisterValue.HasValue) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X4})", "Single Register Value:", SingleRegisterValue)); } if (ByteCount.HasValue && ByteCount > 0) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X2})", "Byte Count:", ByteCount)); } strBuilder.AppendLine(string.Format("{0,-20}{1,5} ({2})", "Checksum:", Checksum, ChecksumOk ? "GOOD" : "BAD")); if (ModbusException) { strBuilder.AppendLine(string.Format("{0,-20}{1,5} (0x{1:X2})", "Exception Code:", ExceptionCode)); strBuilder.AppendLine(string.Format("{0,-20}{1,5}", "Exception Text:", ModbusUtility.GetModbusExceptionName(ExceptionCode))); } if (Coils.Count > 0) { strBuilder.AppendFormat("Coil Values ({0} byte):", Coils.Count).AppendLine(); var lineNumber = 1; foreach (var value in Coils) { // I wish this could be done more elegantly in the string.Format() method, but apparently not string binaryString = Convert.ToString(value, 2); binaryString = binaryString.PadLeft(8, '0'); strBuilder.AppendLine(string.Format("{0,11} {1:D3}: 0x{2:X2} -> 0b{3}", "Byte", lineNumber++, value, binaryString)); } } if (FloatValues.Count > 0) { strBuilder.AppendFormat("Float Values ({0}):", FloatValues.Count).AppendLine(); if (UseModiconFormat) { strBuilder.AppendLine("Using Modicon format!"); } var lineNumber = 1; var address = baseAddress + StartAddress; foreach (var value in FloatValues) { if (StartAddress.HasValue) { strBuilder.AppendLine(string.Format("{0,10:D3} {1:D5}: {2} -> {3} -> {4}", lineNumber++, address, value.RawString, value.FloatString, value.Value)); address += 2; } else { strBuilder.AppendLine(string.Format("{0,10:D3}: {1} -> {2} -> {3}", lineNumber++, value.RawString, value.FloatString, value.Value)); } } } if (IntValues.Count > 0) { strBuilder.AppendFormat("Integer Values ({0}):", IntValues.Count).AppendLine(); var lineNumber = 1; var address = baseAddress + StartAddress; foreach (var value in IntValues) { strBuilder.Append(string.Format("{0,10:D3}", lineNumber++)); if (StartAddress.HasValue) { strBuilder.Append(string.Format(" {0:D5}", address)); address += 1; } strBuilder.AppendLine(string.Format(": {0} -> {1} -> {2,5} or {3,6}", value.RawString, value.IntString, value.UnsignedValue, value.SignedValue)); } } if (LongIntValues.Count > 0) { strBuilder.AppendFormat("Long Integer Values ({0}):", LongIntValues.Count).AppendLine(); if (UseModiconFormat) { strBuilder.AppendLine("Using Modicon format!"); } var lineNumber = 1; var address = baseAddress + StartAddress; foreach (var value in LongIntValues) { strBuilder.Append(string.Format("{0,10:D3}", lineNumber++)); if (StartAddress.HasValue) { strBuilder.Append(string.Format(" {0:D5}", address)); address += 2; } strBuilder.AppendLine(string.Format(": {0} -> {1} -> {2,10} or {3,11}", value.RawString, value.IntString, value.UnsignedValue, value.SignedValue)); } } return(strBuilder.ToString()); }
/// <summary> /// Decodes a message string from Mdbus.exe (Calta Software) /// </summary> /// <param name="message">Message string from Mdbus Monitor logging, starting with the Slave ID</param> /// <param name="useModiconFormat">True if Modicon format for Float or LongInt are used (the least significant bytes are sent in the first register and the most significant bytes in the second register of a pair)</param> /// <param name="mode">Set the mode to Master or Slave. Used to distinguish between request and response when decoding</param> /// <param name="dataType">Set the data type to decode, either float, int or long int (default is float)</param> /// <example> /// /// Example responses: /// /// 3 (0x03) Read Holding Registers /// SlaveID=1, FC=0x03, ByteCount=0x10 (16), values: 60 3A 46 .... /// 01 03 10 60 3A 46 33 69 89 44 57 33 CE 43 06 8B 59 3B 72 C4 8E /// /// 4 (0x04) Read Input Registers /// SlaveID=1, FC=0x04, ByteCount=0x10 (16), values: 60 3A 46 .... /// 01 04 10 60 3A 46 33 69 89 44 57 33 CE 43 06 8B 59 3B 72 C4 8E /// /// 16 (0x10) Write Multiple Registers /// SlaveID=1, FC=0x10 (16), StartAddr=0x0064 (100), Registers=0x0032 (50), ByteCount=0x64 (100), Values:48 9C 1C ... /// 01 10 00 64 00 32 64 48 9C 1C B6 48 94 27 C8 48 98 95 47 48 87 F7 BD 42 AC 07 2B 42 AE 57 91 42 AC 89 5E 42 AF DE 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 45 C9 F0 4D 45 CA 95 4D 45 C9 23 FE 45 C9 64 DF 42 0A 66 66 42 0C CC CD 42 13 33 33 42 11 33 33 42 9E CC CD 9D A4 /// </example> /// <returns></returns> /// private void DecodeMessage(string message, bool useModiconFormat, ModbusMessageMode mode, ModbusDataType dataType) { if (!message.Contains(' ')) { // TODO: create string array from text without spaces. throw new ArgumentException("Given message string does not contain spaces. Must use a valid string from Mdbus Monitor log"); } OriginalMessageString = message; UseModiconFormat = useModiconFormat; MessageMode = mode; DataType = dataType; // Check if there are RX or TX in beginning MessageType = ModbusMessageType.Unknown; if (message.Trim().StartsWith("RX")) { MessageType = ModbusMessageType.Receive; message = message.Replace("RX", ""); } else if (message.Trim().StartsWith("TX")) { MessageType = ModbusMessageType.Transmit; message = message.Replace("TX", ""); } // If we are slave then a TX would be a request (from master) and RX would be a response // If we are master, then a TX would be a request (from us) and RX would be a response // By default we want to decode as response (we are slave and RX message is important) MessageRole = ModbusMessageRole.Response; if (MessageMode == ModbusMessageMode.Master && MessageType == ModbusMessageType.Transmit) { MessageRole = ModbusMessageRole.Request; } else if (MessageMode == ModbusMessageMode.Slave && MessageType == ModbusMessageType.Receive) { MessageRole = ModbusMessageRole.Request; } string[] hexValuesSplit = message.Trim().Split(' '); if (hexValuesSplit.Length > 0) { SlaveId = Convert.ToInt32(hexValuesSplit[0], 16); } if (hexValuesSplit.Length > 1) { var byteValue = Convert.ToInt16(hexValuesSplit[1], 16); FunctionCode = byteValue & 0x7F; // Ignore the first (error) bit ModbusException = ((byteValue & 0x80) == 0x80); // Use the first (error) bit if (ModbusException && (hexValuesSplit.Length > 2)) { ExceptionCode = Convert.ToInt16(hexValuesSplit[2], 16); } } if (hexValuesSplit.Length > 1) { Checksum = hexValuesSplit[hexValuesSplit.Length - 2] + hexValuesSplit[hexValuesSplit.Length - 1]; ChecksumOk = ModbusUtility.CheckModbusCRC(hexValuesSplit); } int startByte = 0; bool hasDataValues = false; switch (FunctionCode) { case 1: case 2: if (MessageRole == ModbusMessageRole.Request) { StartAddress = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 2); CoilCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 4, 2); } else { ByteCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 1); startByte = 3; } break; case 3: case 4: // Request and response differs (we are slave): // RX 01 03 00 00 00 2C 44 17 // TX 01 03 58 01 14 00 00 46 81 38 00 45 48 40 00 44 F1 80 00 42 47 99 9A 47 26 C9 00 47 1D 38 00 47 1D 3A 00 47 27 F8 00 43 82 F3 33 3F B3 33 33 43 2C 66 66 00 00 00 00 00 00 00 00 00 00 00 00 45 34 50 00 00 00 00 00 00 00 00 00 00 00 00 00 43 C9 80 00 41 49 99 9A 41 48 00 00 60 34 if (MessageRole == ModbusMessageRole.Response) { ByteCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 1); startByte = 3; hasDataValues = true; } else { StartAddress = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 2); RegisterCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 4, 2); } break; case 6: // Request and response are the same: // RX 01 06 00 00 02 FD 49 2B // TX 01 06 00 00 02 FD 49 2B StartAddress = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 2); SingleRegisterValue = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 4, 2); startByte = 6; hasDataValues = true; break; case 16: // Request and response differs (we are slave): // RX 01 10 00 5B 00 10 20 00 00 3F 80 00 00 3F 80 CC CD 42 0C CC CD 41 DC 51 EC 41 2C 1E B8 40 B5 CC CD 40 F4 E1 48 40 FA FB DB // TX 01 10 00 5B 00 10 B0 16 StartAddress = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 2, 2); RegisterCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 4, 2); // Only request has byte count if (MessageRole == ModbusMessageRole.Request) { ByteCount = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, 6, 1); startByte = 7; hasDataValues = true; } break; default: break; } if (((FunctionCode == 1) || (FunctionCode == 2)) && (MessageRole == ModbusMessageRole.Response)) { for (int i = startByte; (i - startByte < ByteCount) && (i < hexValuesSplit.Length - 2); i++) { var byteValue = ModbusUtility.ConvertHexStringToInt(hexValuesSplit, i, 1); if (byteValue.HasValue) { Coils.Add(byteValue.Value); } } } if (hasDataValues) { switch (DataType) { case ModbusDataType.Float: // convert all float values from hex string ConvertDataToFloats(useModiconFormat, hexValuesSplit, startByte); break; case ModbusDataType.Integer: // convert all integer values from hex string ConvertDataToIntegers(hexValuesSplit, startByte); break; case ModbusDataType.LongInt: // convert all integer values from hex string ConvertDataToLongInts(useModiconFormat, hexValuesSplit, startByte); break; default: break; } } }