ModbusIpTransport.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using NModbus.Logging;
  7. using NModbus.Unme.Common;
  8. namespace NModbus.IO
  9. {
  10. /// <summary>
  11. /// Transport for Internet protocols.
  12. /// Refined Abstraction - http://en.wikipedia.org/wiki/Bridge_Pattern
  13. /// </summary>
  14. public class ModbusIpTransport : ModbusTransport
  15. {
  16. private static readonly object _transactionIdLock = new object();
  17. private ushort _transactionId;
  18. public ModbusIpTransport(IStreamResource streamResource, IModbusFactory modbusFactory, IModbusLogger logger)
  19. : base(streamResource, modbusFactory, logger)
  20. {
  21. if (streamResource == null) throw new ArgumentNullException(nameof(streamResource));
  22. }
  23. public static byte[] ReadRequestResponse(IStreamResource streamResource, IModbusLogger logger)
  24. {
  25. if (streamResource == null) throw new ArgumentNullException(nameof(streamResource));
  26. if (logger == null) throw new ArgumentNullException(nameof(logger));
  27. // read header
  28. var mbapHeader = new byte[6];
  29. int numBytesRead = 0;
  30. while (numBytesRead != 6)
  31. {
  32. int bRead = streamResource.Read(mbapHeader, numBytesRead, 6 - numBytesRead);
  33. if (bRead == 0)
  34. {
  35. throw new IOException("Read resulted in 0 bytes returned.");
  36. }
  37. numBytesRead += bRead;
  38. }
  39. logger.Debug($"MBAP header: {string.Join(", ", mbapHeader)}");
  40. var frameLength = (ushort)IPAddress.HostToNetworkOrder(BitConverter.ToInt16(mbapHeader, 4));
  41. logger.Debug($"{frameLength} bytes in PDU.");
  42. // read message
  43. var messageFrame = new byte[frameLength];
  44. numBytesRead = 0;
  45. while (numBytesRead != frameLength)
  46. {
  47. int bRead = streamResource.Read(messageFrame, numBytesRead, frameLength - numBytesRead);
  48. if (bRead == 0)
  49. {
  50. throw new IOException("Read resulted in 0 bytes returned.");
  51. }
  52. numBytesRead += bRead;
  53. }
  54. logger.Debug($"PDU: {frameLength}");
  55. var frame = mbapHeader.Concat(messageFrame).ToArray();
  56. logger.LogFrameRx(frame);
  57. return frame;
  58. }
  59. public static byte[] GetMbapHeader(IModbusMessage message)
  60. {
  61. byte[] transactionId = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)message.TransactionId));
  62. byte[] length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)(message.ProtocolDataUnit.Length + 1)));
  63. var stream = new MemoryStream(7);
  64. stream.Write(transactionId, 0, transactionId.Length);
  65. stream.WriteByte(0);
  66. stream.WriteByte(0);
  67. stream.Write(length, 0, length.Length);
  68. stream.WriteByte(message.SlaveAddress);
  69. return stream.ToArray();
  70. }
  71. /// <summary>
  72. /// Create a new transaction ID.
  73. /// </summary>
  74. public virtual ushort GetNewTransactionId()
  75. {
  76. lock (_transactionIdLock)
  77. {
  78. _transactionId = _transactionId == ushort.MaxValue ? (ushort)1 : ++_transactionId;
  79. }
  80. return _transactionId;
  81. }
  82. public IModbusMessage CreateMessageAndInitializeTransactionId<T>(byte[] fullFrame)
  83. where T : IModbusMessage, new()
  84. {
  85. byte[] mbapHeader = fullFrame.Slice(0, 6).ToArray();
  86. byte[] messageFrame = fullFrame.Slice(6, fullFrame.Length - 6).ToArray();
  87. IModbusMessage response = CreateResponse<T>(messageFrame);
  88. response.TransactionId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(mbapHeader, 0));
  89. return response;
  90. }
  91. public override byte[] BuildMessageFrame(IModbusMessage message)
  92. {
  93. byte[] header = GetMbapHeader(message);
  94. byte[] pdu = message.ProtocolDataUnit;
  95. var messageBody = new MemoryStream(header.Length + pdu.Length);
  96. messageBody.Write(header, 0, header.Length);
  97. messageBody.Write(pdu, 0, pdu.Length);
  98. return messageBody.ToArray();
  99. }
  100. public override void Write(IModbusMessage message)
  101. {
  102. message.TransactionId = GetNewTransactionId();
  103. byte[] frame = BuildMessageFrame(message);
  104. Logger.LogFrameTx(frame);
  105. StreamResource.Write(frame, 0, frame.Length);
  106. }
  107. public override byte[] ReadRequest()
  108. {
  109. return ReadRequestResponse(StreamResource, Logger);
  110. }
  111. public override IModbusMessage ReadResponse<T>()
  112. {
  113. return CreateMessageAndInitializeTransactionId<T>(ReadRequestResponse(StreamResource, Logger));
  114. }
  115. internal override void OnValidateResponse(IModbusMessage request, IModbusMessage response)
  116. {
  117. if (request.TransactionId != response.TransactionId)
  118. {
  119. string msg = $"Response was not of expected transaction ID. Expected {request.TransactionId}, received {response.TransactionId}.";
  120. throw new IOException(msg);
  121. }
  122. }
  123. public override bool OnShouldRetryResponse(IModbusMessage request, IModbusMessage response)
  124. {
  125. if (request.TransactionId > response.TransactionId && request.TransactionId - response.TransactionId < RetryOnOldResponseThreshold)
  126. {
  127. // This response was from a previous request
  128. return true;
  129. }
  130. return base.OnShouldRetryResponse(request, response);
  131. }
  132. }
  133. }