ModbusManager.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace ModbusTest
  7. {
  8. public class ModbusManager
  9. {
  10. }
  11. /// <summary>
  12. /// 读取任务单元
  13. /// </summary>
  14. public class ReadBatchTask
  15. {
  16. public byte SlaveId { get; set; }
  17. public byte FunctionCode { get; set; }
  18. public ushort StartAddress { get; set; }
  19. public ushort Quantity { get; set; }
  20. /// <summary>
  21. /// 该批次包含的所有点位引用,用于后续解析映射
  22. /// </summary>
  23. public List<ModbusPoint> AssociatedPoints { get; set; } = new List<ModbusPoint>();
  24. }
  25. public class ModbusBatchOptimizer
  26. {
  27. /// <summary>
  28. /// 将点位列表优化为批量读取任务
  29. /// </summary>
  30. /// <param name="points">所有待读取点位</param>
  31. /// <param name="slaveId">从站ID</param>
  32. /// <param name="maxGap">允许的最大地址间隙,超过此间隙则断开批次</param>
  33. public List<ReadBatchTask> Optimize(List<ModbusPoint> points, byte slaveId, int maxGap = 5)
  34. {
  35. if (points == null || !points.Any()) return new List<ReadBatchTask>();
  36. var tasks = new List<ReadBatchTask>();
  37. // 1. 分组:按 从站ID(如果有多个) + 功能码 分组
  38. // 这里简化假设只有一个 SlaveId,实际可再外层循环
  39. var groups = points.GroupBy(p => p.FunctionCode);
  40. foreach (var group in groups)
  41. {
  42. // 2. 排序:按地址升序
  43. var sortedPoints = group.OrderBy(p => p.Address).ToList();
  44. // 3. 合并连续地址
  45. var batches = MergeContinuousPoints(sortedPoints, maxGap);
  46. foreach (var batchPoints in batches)
  47. {
  48. var first = batchPoints.First();
  49. var last = batchPoints.Last();
  50. // 计算总读取长度:最后一个点的结束地址 - 起始地址
  51. // 注意:last.Address + last.Quantity 是下一个可用地址,所以长度是差值
  52. ushort startAddr = first.Address;
  53. ushort endAddrExclusive = (ushort)(last.Address + last.Quantity);
  54. ushort totalQty = (ushort)(endAddrExclusive - startAddr);
  55. // NModbus 单次读取最大寄存器数通常限制为 125 (Function 3/4)
  56. // 如果超过,需要进一步拆分(此处简化处理,实际生产需增加拆分逻辑)
  57. if (totalQty > 125)
  58. {
  59. // TODO: 实现大批次拆分逻辑
  60. throw new InvalidOperationException($"Batch size {totalQty} exceeds Modbus limit 125. Split logic needed.");
  61. }
  62. tasks.Add(new ReadBatchTask
  63. {
  64. SlaveId = slaveId,
  65. FunctionCode = group.Key,
  66. StartAddress = startAddr,
  67. Quantity = totalQty,
  68. AssociatedPoints = batchPoints
  69. });
  70. }
  71. }
  72. return tasks;
  73. }
  74. private List<List<ModbusPoint>> MergeContinuousPoints(List<ModbusPoint> sortedPoints, int maxGap)
  75. {
  76. var result = new List<List<ModbusPoint>>();
  77. if (!sortedPoints.Any()) return result;
  78. var currentBatch = new List<ModbusPoint> { sortedPoints[0] };
  79. for (int i = 1; i < sortedPoints.Count; i++)
  80. {
  81. var prev = sortedPoints[i - 1];
  82. var curr = sortedPoints[i];
  83. // 前一个点的结束位置
  84. int prevEnd = prev.Address + prev.Quantity;
  85. // 如果当前点起始地址 与 前一个点结束位置 的差距在允许范围内
  86. if (curr.Address <= prevEnd + maxGap)
  87. {
  88. currentBatch.Add(curr);
  89. }
  90. else
  91. {
  92. result.Add(currentBatch);
  93. currentBatch = new List<ModbusPoint> { curr };
  94. }
  95. }
  96. result.Add(currentBatch);
  97. return result;
  98. }
  99. }
  100. /// <summary>
  101. /// Modbus 点位定义
  102. /// </summary>
  103. public class ModbusPoint
  104. {
  105. /// <summary>
  106. /// 业务唯一ID
  107. /// </summary>
  108. public string PointId { get; set; }
  109. /// <summary>
  110. /// 寄存器起始地址 (0-based, 即协议中的 40001 对应地址 0)
  111. /// </summary>
  112. public ushort Address { get; set; }
  113. /// <summary>
  114. /// 占用寄存器数量 (Int16=1, Float32=2, Double64=4)
  115. /// </summary>
  116. public ushort Quantity { get; set; } = 1;
  117. /// <summary>
  118. /// 功能码: 3 (Read Holding), 4 (Read Input)
  119. /// </summary>
  120. public byte FunctionCode { get; set; } = 3;
  121. /// <summary>
  122. /// 数据类型
  123. /// </summary>
  124. public DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
  125. /// <summary>
  126. /// 字节序 (针对多字节类型)
  127. /// </summary>
  128. public ByteOrderEnum ByteOrder { get; set; } = ByteOrderEnum.ABCD;
  129. /// <summary>
  130. /// 缩放系数: Result = Raw * Scale + Offset
  131. /// </summary>
  132. public double Scale { get; set; } = 1.0;
  133. /// <summary>
  134. /// 偏移量
  135. /// </summary>
  136. public double Offset { get; set; } = 0.0;
  137. /// <summary>
  138. /// 最新读取值
  139. /// </summary>
  140. public object CurrentValue { get; set; }
  141. /// <summary>
  142. /// 最后更新时间
  143. /// </summary>
  144. public DateTime LastUpdateTime { get; set; }
  145. }
  146. public enum DataTypeEnum
  147. {
  148. Int16,
  149. UInt16,
  150. Int32,
  151. UInt32,
  152. Float32,
  153. Double64
  154. }
  155. public enum ByteOrderEnum
  156. {
  157. ABCD, // Big Endian (标准)
  158. BADC, // Byte Swap
  159. CDAB, // Word Swap
  160. DCBA // Little Endian
  161. }
  162. }