C#实现数据采集系统-分组查询
分组查询
单点查询:1000个点,10000个点,就要查询一千次,一万次
如果50个一组,100个一组,1千个点就减少到20次,10次,大大提供了通信效率
ModbusTcp查询报文
00 01 00 00 00 06 FF 01 00 01 00 10 -01
00 01 00 00 00 06 01 03 00 05 00 02 -03
创建点位查询组的类
根据modbustcp查询报文可以知道,必要的字段包括:功能码,起始地址,查询地址数量(长度)
然后增加一个点位集合,存储该组相关的点位,用于查询之后的处理
public class PointGroup
{
/// <summary>
/// 功能码
/// </summary>
public byte FuncNum { get; set; }
public int StartAddress { get; set; }
public int Length { get; set; }
public List<RegisterPoint> Points { get; set; }
}
在ModbusTcp中实现地址分组功能
定义一个PointGroup的集合,和一个地址实现的方法
private List<PointGroup> _pointGroups = new List<PointGroup>();
void AddressGroup()
{
//实现地址分组,添加到集合中去
}
先根据寄存器类型分组,不同的寄存器,如线圈,保持寄存器不能一起查询
var groups = _registers.GroupBy(x => x.RegisterType);
然后在每个寄存器组中进行分组
- 获取到功能码
- 按地址从小打大排序
foreach (var item in groups)
{
var func = ReadFuncCodes[item.Key];
var points = item.OrderBy(x => x.Address).ToList();
}
我们这里设置分组查询最长长度100,正常寄存器不超过127个,实现一下分组,然后再初始化中调用就行
/// <summary>
/// 地址分组处理
/// </summary>
void AddressGroup()
{
var groups = _registers.GroupBy(x => x.RegisterType);
foreach (var item in groups)
{
var func = ReadFuncCodes[item.Key];
var points = item.OrderBy(x => x.Address).ToList();
PointGroup group = new PointGroup()
{
FuncNum = func,
StartAddress = points[0].Address,
Length = points[0].Length,
};
group.Points.Add(points[0]);
//长度100
for (int i = 1; i < points.Count; i++)
{
var length = points[i].Address + points[i].Length - StartAddress;
if (length <= 100)
{
group.Length = length;
group.Points.Add(points[i]);
}
else
{
_pointGroups.Add(group);
group = new PointGroup()
{
FuncNum = func,
StartAddress = points[i].Address,
Length = points[i].Length,
};
group.Points.Add(points[i]);
}
}
_pointGroups.Add(group);
}
}
根据分组进行查询
修改查询代码,根据分组进行查询
修改内容
- 修改遍历点位组
- 修改设置发送报文
- 修改数据处理
新的查询
修改循环,和创建两个新的方法,主体中发送、结束,数据报文提取部分不变
foreach (var group in _pointGroups)
{
SetSendBytes(group);
//发送读报文
_client.Send(_readbytes);
int len = ReceviceDataLength(group.Length);
var errLen = 9;
var normalLen = 9 + len;
//接收报文
byte[] recvBytes = Recevice(normalLen, errLen);
//提取数据部分
byte[] dataBytes = new byte[len];
Array.Copy(recvBytes, 9, dataBytes, 0, len);
//数据处理
DealData(group, dataBytes);
}
//SetSendBytes
private void SetSendBytes(PointGroup group)
{
//...
}
//数据处理方法
void DealData(PointGroup group, byte[] bytes)
{
//...
}
设置发送报文方法
private void SetSendBytes(PointGroup group)
{
//修改功能码
_readbytes[7] = group.FuncNum;
//修改起始地址
var addressBytes = BitConverter.GetBytes(group.StartAddress);
_readbytes[8] = addressBytes[1];
_readbytes[9] = addressBytes[0];
//修改读寄存器数量
var LengthBytes = BitConverter.GetBytes(group.Length);
_readbytes[10] = LengthBytes[1];
_readbytes[11] = LengthBytes[0];
}
实现数据处理
ModbusTcp
IO位处理
计算所在字节数
$$
\frac{Address-Start}8
$$
计算所在位数:(Address-Start) % 8
字节
起始:01
查询:16个
数据:0A 02
[0]:0A→0000 1010
[1]:02→0000 0010
08位 在第0个字节中第7位(0-7)
字节位:(8-1)/8=0
位数:(8-1)%8=7
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
地址 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 |
值 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
地址 | 10 | 0F | OE | 0D | 0C | 0B | 0A | 09 |
值 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
然后通过位运算算出当前点位的值
位运算
//IO类型
//当前点位Address-(StartAddress-1)
var num = (item.Address - group.StartAddress) / 8;
var bit = (item.Address - group.StartAddress) % 8;
value = (bytes[num] >> bit & 1) == 1;
寄存器处理
在之前的数据处理方法中已经完整实现了一个点位的数据处理,包括字节序处理,转换类型等,在这里就是要做的就是根据每个的地址和长度信息,从总的数据报文中提取出当前点的数据部分
var start = item.Address - group.StartAddress;
var valueBytes = new byte[item.Length * 2];
Array.Copy(bytes, start*2, valueBytes, 0, item.Length * 2);
value = DealData(item, valueBytes);
然后修改原方法,让其返回计算出的value值,(删除里面的IO线圈处理操作)
/// <summary>
/// 数据处理方法
/// </summary>
/// <param name="point"></param>
/// <param name="bytes"></param>
object DealData(RegisterPoint point, byte[] bytes)
{
object value;
#region 原方法寄存器处理
if (point.Type == typeof(byte))
{
value = bytes[1];
}
else
{
//字节序处理
byte[] newbytes = new byte[point.Length * 2];
//优化
for (int i = 0; i < point.Length; i++)
{
newbytes[2 * i] = bytes[2 * i + 1];
newbytes[2 * i + 1] = bytes[2 * i];
}
// 优化
var converterMothed = typeof(BitConverter).GetMethod(
"To" + point.Type.Name,
BindingFlags.Public | BindingFlags.Static,
new[] { typeof(byte[]), typeof(int) }
);
value = converterMothed.Invoke(null, new object[] { newbytes, 0 });
}
#endregion
return value;
}
完整的实现
根据自己需要修改命名
void DealData(PointGroup group, byte[] bytes)
{
foreach (var item in group.Points)
{
object value;
if (group.FuncNum <= 2)
{
//IO类型
//当前点位Address-(StartAddress-1)
var num = (item.Address - group.StartAddress) / 8;
var bit = (item.Address - group.StartAddress) % 8;
value = (bytes[num] >> bit & 1) == 1;
}
else
{
var start = item.Address - group.StartAddress;
var valueBytes = new byte[item.Length * 2];
Array.Copy(bytes, start, valueBytes, 0, item.Length * 2);
value = DealData(item, valueBytes);
}
//赋值
if (!value.Equals(item.Value))
{
item.Value = value;
ValueUpdated?.Invoke(item, value);
}
}
}
/// <summary>
/// 数据处理方法
/// </summary>
/// <param name="point"></param>
/// <param name="bytes"></param>
object DealData(RegisterPoint point, byte[] bytes)
{
object value;
#region 原方法寄存器处理
if (point.Type == typeof(byte))
{
value = bytes[1];
}
else
{
//字节序处理
byte[] newbytes = new byte[point.Length * 2];
//优化
for (int i = 0; i < point.Length; i++)
{
newbytes[2 * i] = bytes[2 * i + 1];
newbytes[2 * i + 1] = bytes[2 * i];
}
// 优化
var converterMothed = typeof(BitConverter).GetMethod(
"To" + point.Type.Name,
BindingFlags.Public | BindingFlags.Static,
new[] { typeof(byte[]), typeof(int) }
);
value = converterMothed.Invoke(null, new object[] { newbytes, 0 });
}
#endregion
return value;
}