S7WriteMultiple.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using System;
  2. using System.Collections.Generic;
  3. using S7.Net.Types;
  4. namespace S7.Net.Protocol
  5. {
  6. internal static class S7WriteMultiple
  7. {
  8. public static int CreateRequest(ByteArray message, DataItem[] dataItems)
  9. {
  10. message.Add(Header.Template);
  11. message[Header.Offsets.ParameterCount] = (byte) dataItems.Length;
  12. var paramSize = dataItems.Length * Parameter.Template.Length;
  13. Serialization.SetWordAt(message, Header.Offsets.ParameterSize,
  14. (ushort) (2 + paramSize));
  15. var paramOffset = Header.Template.Length;
  16. var data = new ByteArray();
  17. var itemCount = 0;
  18. foreach (var item in dataItems)
  19. {
  20. itemCount++;
  21. message.Add(Parameter.Template);
  22. var value = Serialization.SerializeDataItem(item);
  23. var wordLen = item.Value is bool ? 1 : 2;
  24. message[paramOffset + Parameter.Offsets.WordLength] = (byte) wordLen;
  25. Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.Amount, (ushort) value.Length);
  26. Serialization.SetWordAt(message, paramOffset + Parameter.Offsets.DbNumber, (ushort) item.DB);
  27. message[paramOffset + Parameter.Offsets.Area] = (byte) item.DataType;
  28. data.Add(0x00);
  29. if (item.Value is bool b)
  30. {
  31. if (item.BitAdr > 7)
  32. throw new ArgumentException(
  33. $"Cannot read bit with invalid {nameof(item.BitAdr)} '{item.BitAdr}'.", nameof(dataItems));
  34. Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr,
  35. item.BitAdr);
  36. data.Add(0x03);
  37. data.AddWord(1);
  38. data.Add(b ? (byte)1 : (byte)0);
  39. if (itemCount != dataItems.Length) {
  40. data.Add(0);
  41. }
  42. }
  43. else
  44. {
  45. Serialization.SetAddressAt(message, paramOffset + Parameter.Offsets.Address, item.StartByteAdr, 0);
  46. var len = value.Length;
  47. data.Add(0x04);
  48. data.AddWord((ushort) (len << 3));
  49. data.Add(value);
  50. if ((len & 0b1) == 1 && itemCount != dataItems.Length)
  51. {
  52. data.Add(0);
  53. }
  54. }
  55. paramOffset += Parameter.Template.Length;
  56. }
  57. message.Add(data.Array);
  58. Serialization.SetWordAt(message, Header.Offsets.MessageLength, (ushort) message.Length);
  59. Serialization.SetWordAt(message, Header.Offsets.DataLength, (ushort) (message.Length - paramOffset));
  60. return message.Length;
  61. }
  62. public static void ParseResponse(byte[] message, int length, DataItem[] dataItems)
  63. {
  64. if (length < 12) throw new Exception("Not enough data received to parse write response.");
  65. var messageError = Serialization.GetWordAt(message, 10);
  66. if (messageError != 0)
  67. throw new Exception($"Write failed with error {messageError}.");
  68. if (length < 14 + dataItems.Length)
  69. throw new Exception("Not enough data received to parse individual item responses.");
  70. IList<byte> itemResults = new ArraySegment<byte>(message, 14, dataItems.Length);
  71. List<Exception>? errors = null;
  72. for (int i = 0; i < dataItems.Length; i++)
  73. {
  74. try
  75. {
  76. Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]);
  77. }
  78. catch(Exception e)
  79. {
  80. if (errors == null) errors = new List<Exception>();
  81. errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}."));
  82. }
  83. }
  84. if (errors != null)
  85. throw new AggregateException(
  86. $"Write failed for {errors.Count} items. See the innerExceptions for details.", errors);
  87. }
  88. private static class Header
  89. {
  90. public static byte[] Template { get; } =
  91. {
  92. 0x03, 0x00, 0x00, 0x00, // TPKT
  93. 0x02, 0xf0, 0x80, // ISO DT
  94. 0x32, // S7 protocol ID
  95. 0x01, // JobRequest
  96. 0x00, 0x00, // Reserved
  97. 0x05, 0x00, // PDU reference
  98. 0x00, 0x0e, // Parameters length
  99. 0x00, 0x00, // Data length
  100. 0x05, // Function: Write var
  101. 0x00, // Number of items to write
  102. };
  103. public static class Offsets
  104. {
  105. public const int MessageLength = 2;
  106. public const int ParameterSize = 13;
  107. public const int DataLength = 15;
  108. public const int ParameterCount = 18;
  109. }
  110. }
  111. private static class Parameter
  112. {
  113. public static byte[] Template { get; } =
  114. {
  115. 0x12, // Spec
  116. 0x0a, // Length of remaining bytes
  117. 0x10, // Addressing mode
  118. 0x02, // Transport size
  119. 0x00, 0x00, // Number of elements
  120. 0x00, 0x00, // DB number
  121. 0x84, // Area type
  122. 0x00, 0x00, 0x00 // Area offset
  123. };
  124. public static class Offsets
  125. {
  126. public const int WordLength = 3;
  127. public const int Amount = 4;
  128. public const int DbNumber = 6;
  129. public const int Area = 8;
  130. public const int Address = 9;
  131. }
  132. }
  133. }
  134. }