| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace ModbusTest
- {
- public class ModbusManager
- {
- }
- /// <summary>
- /// 读取任务单元
- /// </summary>
- public class ReadBatchTask
- {
- public byte SlaveId { get; set; }
- public byte FunctionCode { get; set; }
- public ushort StartAddress { get; set; }
- public ushort Quantity { get; set; }
- /// <summary>
- /// 该批次包含的所有点位引用,用于后续解析映射
- /// </summary>
- public List<ModbusPoint> AssociatedPoints { get; set; } = new List<ModbusPoint>();
- }
- public class ModbusBatchOptimizer
- {
- /// <summary>
- /// 将点位列表优化为批量读取任务
- /// </summary>
- /// <param name="points">所有待读取点位</param>
- /// <param name="slaveId">从站ID</param>
- /// <param name="maxGap">允许的最大地址间隙,超过此间隙则断开批次</param>
- public List<ReadBatchTask> Optimize(List<ModbusPoint> points, byte slaveId, int maxGap = 5)
- {
- if (points == null || !points.Any()) return new List<ReadBatchTask>();
- var tasks = new List<ReadBatchTask>();
- // 1. 分组:按 从站ID(如果有多个) + 功能码 分组
- // 这里简化假设只有一个 SlaveId,实际可再外层循环
- var groups = points.GroupBy(p => p.FunctionCode);
- foreach (var group in groups)
- {
- // 2. 排序:按地址升序
- var sortedPoints = group.OrderBy(p => p.Address).ToList();
- // 3. 合并连续地址
- var batches = MergeContinuousPoints(sortedPoints, maxGap);
- foreach (var batchPoints in batches)
- {
- var first = batchPoints.First();
- var last = batchPoints.Last();
- // 计算总读取长度:最后一个点的结束地址 - 起始地址
- // 注意:last.Address + last.Quantity 是下一个可用地址,所以长度是差值
- ushort startAddr = first.Address;
- ushort endAddrExclusive = (ushort)(last.Address + last.Quantity);
- ushort totalQty = (ushort)(endAddrExclusive - startAddr);
- // NModbus 单次读取最大寄存器数通常限制为 125 (Function 3/4)
- // 如果超过,需要进一步拆分(此处简化处理,实际生产需增加拆分逻辑)
- if (totalQty > 125)
- {
- // TODO: 实现大批次拆分逻辑
- throw new InvalidOperationException($"Batch size {totalQty} exceeds Modbus limit 125. Split logic needed.");
- }
- tasks.Add(new ReadBatchTask
- {
- SlaveId = slaveId,
- FunctionCode = group.Key,
- StartAddress = startAddr,
- Quantity = totalQty,
- AssociatedPoints = batchPoints
- });
- }
- }
- return tasks;
- }
- private List<List<ModbusPoint>> MergeContinuousPoints(List<ModbusPoint> sortedPoints, int maxGap)
- {
- var result = new List<List<ModbusPoint>>();
- if (!sortedPoints.Any()) return result;
- var currentBatch = new List<ModbusPoint> { sortedPoints[0] };
- for (int i = 1; i < sortedPoints.Count; i++)
- {
- var prev = sortedPoints[i - 1];
- var curr = sortedPoints[i];
- // 前一个点的结束位置
- int prevEnd = prev.Address + prev.Quantity;
- // 如果当前点起始地址 与 前一个点结束位置 的差距在允许范围内
- if (curr.Address <= prevEnd + maxGap)
- {
- currentBatch.Add(curr);
- }
- else
- {
- result.Add(currentBatch);
- currentBatch = new List<ModbusPoint> { curr };
- }
- }
- result.Add(currentBatch);
- return result;
- }
- }
- /// <summary>
- /// Modbus 点位定义
- /// </summary>
- public class ModbusPoint
- {
- /// <summary>
- /// 业务唯一ID
- /// </summary>
- public string PointId { get; set; }
- /// <summary>
- /// 寄存器起始地址 (0-based, 即协议中的 40001 对应地址 0)
- /// </summary>
- public ushort Address { get; set; }
- /// <summary>
- /// 占用寄存器数量 (Int16=1, Float32=2, Double64=4)
- /// </summary>
- public ushort Quantity { get; set; } = 1;
- /// <summary>
- /// 功能码: 3 (Read Holding), 4 (Read Input)
- /// </summary>
- public byte FunctionCode { get; set; } = 3;
- /// <summary>
- /// 数据类型
- /// </summary>
- public DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
- /// <summary>
- /// 字节序 (针对多字节类型)
- /// </summary>
- public ByteOrderEnum ByteOrder { get; set; } = ByteOrderEnum.ABCD;
- /// <summary>
- /// 缩放系数: Result = Raw * Scale + Offset
- /// </summary>
- public double Scale { get; set; } = 1.0;
- /// <summary>
- /// 偏移量
- /// </summary>
- public double Offset { get; set; } = 0.0;
- /// <summary>
- /// 最新读取值
- /// </summary>
- public object CurrentValue { get; set; }
- /// <summary>
- /// 最后更新时间
- /// </summary>
- public DateTime LastUpdateTime { get; set; }
- }
- public enum DataTypeEnum
- {
- Int16,
- UInt16,
- Int32,
- UInt32,
- Float32,
- Double64
- }
- public enum ByteOrderEnum
- {
- ABCD, // Big Endian (标准)
- BADC, // Byte Swap
- CDAB, // Word Swap
- DCBA // Little Endian
- }
- }
|