📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
📢本文作者:由webmote 原创
📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !
序言
Modbus 协议是工控领域常见的一种通信协议,而Modbus Poll无疑是其中最好用的Master软件了,通过自定义的点表,可以通过查表的方式,快速的去响应主从机的动作和状态。
其中使用的点表配置文件格式为mbp,今天,我们的目标就是这个文件!
其中,对我们有意义的数据有起始地址,功能代码,点表列表数据,如何获取呢?
1. 分析文件格式
先谷歌一下,在百度一下,并未发现有人解析过mbp格式的文件,经过在官网的浏览,没有发现任何有用的信息,可见该文件格式是私密格式,如果给作者发邮件,不知道能否得到作者大大的指点,有社牛的朋友,可以试试。
据chatgpt
说: Modbus Poll 是一个用于 Modbus 通信协议的 Windows 应用程序,它允许用户进行 Modbus 通信的监视和测试。Modbus Poll 使用 MBP 格式来保存配置文件,其中包含了 Modbus 通信配置和设置。要解析 Modbus Poll 的 MBP 格式文件,需要了解该文件的具体结构和存储的内容。通常来说,MBP 文件是一个二进制文件,保存了 Modbus Poll 的配置信息,如串口设置、Modbus 寄存器的地址、功能码、数据格式等。
不过Chatgpt
并没有鸟用。
好鸟一身毛,让我们打开mbp,直接查看吧。
好家伙,这文件也太不精简了,这么多预留字段,如何下手啊?
好的思路,胜过一千行代码,来吧,让我们打开Beyond Compare
屠龙软件,多加一行点表,看看HEX有啥不同。
经过缜密的比对,已经看出来头信息的格式定义,以及点表的定义位置,那么,让我们打开VS,开启码农生活。
2. 解析文件头
本来想采用结构体定义,不过有些字段是可变长度的,处理起来并没有那么容易,干脆一不做二不休,直接使用接口定义解析。
public interface IParser
{
int Size { get; }
/// <summary>
/// 解析buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="start">开始解析的地址</param>
/// <returns>解析读取了多少字节</returns>
int Parse(byte[] buffer, int start =0);
}
通过size,返回该类的所占的字节长度,通过parse的返回值,获取buffer的当前指针。
定义好接口后,那么我们就来实现头的解析。
public class MbpHeader : IParser
{
public int Flag;
public int Version;
public int FuncCode;
public int StartAddress;
public int PointTableSize;
public byte[] Reserve;
public int Size { get; private set; }
public MbpHeader()
{
Flag = 0x2454;
Version = 0x00A8;
FuncCode = 0;
StartAddress = 0;
PointTableSize = 0;
Reserve = new byte[508];
Size = 0x20C;
}
public int Parse(byte[] buffer, int start = 0)
{
int i = start;
var flag = BitConverter.ToInt32(buffer, i);
//if(flag != 0x2454 && flag != 0x2648)
//{
// throw new NotSupportedException("报文头不对,不能解析");
//}
i += 4;
var ver = BitConverter.ToInt32(buffer, i);
//if(ver != Version)
//{
// throw new NotSupportedException("报文版本不对,不能解析");
//}
i += 4;
FuncCode = BitConverter.ToInt32(buffer, i);
i += 4;
StartAddress = BitConverter.ToInt32(buffer, i);
i += 4;
PointTableSize = BitConverter.ToInt32(buffer, i);
i += Reserve.Length;
this.Size = i;
return i;
}
}
代码中的Flag
和Version
是我们推测来的,不过经测试,大概率和版本有关。
这里注释掉对版本的限制,可以支持多个版本的解析。
3.解析点表列表
经过排查,点表的解析定在 0x20C的位置上,从这里开始进行循环检测。
代码如下:
public class MbpPointTable : IParser
{
public int[] Flags { get; set; }
public MbpTableItem Item { get; set; }
public int Size { get; private set; }
public MbpPointTable()
{
Flags = new int[12];
Item = new MbpTableItem();
}
public int Parse(byte[] buffer, int start = 0)
{
Size = 0;
if (buffer == null) return 0;
var i = start;
if(buffer.Length < start + 4 * Flags.Length)
{
return 0;
}
for(var j=0; i < start + 4*Flags.Length; i+=4,j++)
{
Flags[j] = BitConverter.ToInt32(buffer, i);
}
//0xFF FE FF
i += 3;
var size = Item.Parse(buffer, i);
i += size;
//解析值
var flag3 = BitConverter.ToInt32(buffer, i);
if(flag3 == 0x55555555)
{
Size = i + 4 - start;
return i+4;
}
else
{
return -1;
}
}
}
为了更方便的标识点表数据,还需要增加一个tableItem类。
public class MbpTableItem : IParser
{
public int Size { get; private set; }
public byte Len { get; set; }
public string Name { get; set; }
public Int16 Value { get; set; }
public int Parse(byte[] buffer, int start = 0)
{
if (buffer == null) return 0;
if(buffer.Length> start)
{
Len = buffer[start];
if(buffer.Length >= (start +1 + 2* Len))
{
if (Len > 0)
{
this.Name += Encoding.Unicode.GetString(buffer, start + 1, 2 * Len);
}
else
{
this.Name = String.Empty;
}
return 1+ 2*Len;
}
else
{
return 0;
}
}
return 0;
}
}
这里采用Unicode对名称进行解析,以支持中文名称。
4.测试例子
为了测试,还需要封装一个mbpFile的类,这里利用该类读取文件内容,并送给上述类进行解析。
代码如下:
var p = @"D:\遥测v1.0.1(7).mbp";
MbpFile mbp = new MbpFile(p);
Console.WriteLine($"header: func = {mbp.Header.FuncCode}, start = {mbp.Header.StartAddress}, item={mbp.Header.PointTableSize}");
var i = 0;
foreach(var item in mbp.PointTables)
{
Console.WriteLine($"点表定义{i++}: {item.Item.Name} {item.Item.Value}");
}
输出结果如下:
啊哈哈,终于可以采用mbp文件,直接作为程序的配置文件了。
你用过modbus吗?
是不是觉得这种方式不错?
当然,自己定义一个格式,也许更加丰富!
号外
哦哦哦,神奇的一天又结束了,modbus这个协议确实不错,优秀!
👓都看到这了,还在乎点个赞吗?
👓都点赞了,还在乎一个收藏吗?
👓都收藏了,还在乎一个评论吗?