「 Modbus-RTU报文解析」解析030610功能码报文示例
Posted 谁吃薄荷糖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「 Modbus-RTU报文解析」解析030610功能码报文示例相关的知识,希望对你有一定的参考价值。
背景介绍
项目用到modbus-rtu通讯协议与三方平台通讯,由于三方平台没有寄存器地址点表信息,只提供了报文数据,故需要对报文进行二次解析,从而获得三方平台使用到的寄存器地址信息。
方案思路
报文示例
报文示例无包尾校验位,从站地址为1
01030001002B
从站地址 | 功能码 | 寄存器起始地址(高位) | 寄存器起始地址(低位) | 读取寄存器个数(高位) | 读取寄存器个数(低位) |
---|---|---|---|---|---|
0x01 | 0x03 | 0x00 | 0x04 | 0x00 | 0x2B |
010600580000
从站地址 | 功能码 | 寄存器地址(高位) | 寄存器地址(低位) | 写入值(高位) | 写入值((低位) |
---|---|---|---|---|---|
0x01 | 0x06 | 0x00 | 0x58 | 0x00 | 0x00 |
0110003300020400010001
从站地址 | 功能码 | 寄存器起始地址(高位) | 寄存器起始地址(低位) | 设置寄存器个数(高位) | 设置寄存器个数(低位) | 字节数 | 字节1 | 字节2 | 字节3 | 字节4 |
---|---|---|---|---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x33 | 0x00 | 0x02 | 0x04 | 0x00 | 0x01 | 0x00 | 0x01 |
需求分解
1.三方平台报文以excel形式提供;
2.三方平台报文里很多重复报文需要剔除;
3.统计输出时希望可以按照顺序输出,便于统计与观看;
简要思路
需要根据提供的报文,解析出modbus主站所使用的寄存器地址与个数,方便点表的统计与维护。
1.把excel报文复制到txt文档中,解析程序读取txt文档,这样方便后期其他报文导入解析,只需要替换txt文档即可,程序灵活,可扩展性强;
2.报文有重复,需要去重,由于我近期使用unordered_map
比较多,所以使用了unordered_map
,其实map
更合适;
3.由于使用了unordered_map
,所以还需要做下排序处理;
代码示例
#include <stdio.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <cctype>
#include <unordered_map>
#include <algorithm>
using namespace std;
vector<string> str_apps_input; ///< 文本输入
///< 利用unordered_map实现去重
unordered_map<string,int> str_apps_output_F703; ///< 文本输出 F703
unordered_map<string,int> str_apps_output_F706; ///< 文本输出 F706
unordered_map<string,int> str_apps_output_F710; ///< 文本输出 F710
void parseProtocolFile(const char *input_file);
int hex2dec(char *hex);
int c2i(char ch);
///< 对unordered_map进行排序
bool comp(const pair<string, int>& a, const pair<string, int> &b)
return a.second < b.second;
int main(int argc, char ** argv)
int count = 0;
//打开本地文件,解析报文
char filepath[128] = "./test.txt";
parseProtocolFile(filepath);
int n = str_apps_input.size();
for (int i = 0; i < n ; i++)
//printf("(*str_apps[i].c_str()) is %s\\n", *str_apps[i].c_str());
char str_addr1[16] = 0 ;
char str_addr2[16] = 0 ;
char str_addr3[16] = 0 ;
char str_addr[128] = 0 ;
if (str_apps_input[i].find("F703") != string::npos)
sprintf_s(str_addr1, "%c%c", *((char*)(str_apps_input[i].c_str()) + 4), *((char*)(str_apps_input[i].c_str()) + 5));
sprintf_s(str_addr2, "%c%c", *((char*)(str_apps_input[i].c_str()) + 6), *((char*)(str_apps_input[i].c_str()) + 7));
sprintf_s(str_addr3, "%c%c", *((char*)(str_apps_input[i].c_str()) + 10), *((char*)(str_apps_input[i].c_str()) + 11));
int address = hex2dec(str_addr1) * 256 + hex2dec(str_addr2);
int number = hex2dec(str_addr3);
sprintf_s(str_addr, "address is %d, number is %d\\n", address, number);
str_apps_output_F703[str_addr] = address;
else if (str_apps_input[i].find("F706") != string::npos)
sprintf_s(str_addr1, "%c%c", *((char*)(str_apps_input[i].c_str()) + 4), *((char*)(str_apps_input[i].c_str()) + 5));
sprintf_s(str_addr2, "%c%c", *((char*)(str_apps_input[i].c_str()) + 6), *((char*)(str_apps_input[i].c_str()) + 7));
int address = hex2dec(str_addr1) * 256 + hex2dec(str_addr2);
sprintf_s(str_addr, "address is %d\\n", address);
str_apps_output_F706[str_addr] = address;
else if (str_apps_input[i].find("F710") != string::npos)
sprintf_s(str_addr1, "%c%c", *((char*)(str_apps_input[i].c_str()) + 4), *((char*)(str_apps_input[i].c_str()) + 5));
sprintf_s(str_addr2, "%c%c", *((char*)(str_apps_input[i].c_str()) + 6), *((char*)(str_apps_input[i].c_str()) + 7));
sprintf_s(str_addr3, "%c%c", *((char*)(str_apps_input[i].c_str()) + 10), *((char*)(str_apps_input[i].c_str()) + 11));
int address = hex2dec(str_addr1) * 256 + hex2dec(str_addr2);
int number = hex2dec(str_addr3);
sprintf_s(str_addr, "address is %d, number is %d\\n", address, number);
str_apps_output_F710[str_addr] = address;
else
printf("can not parse:%s\\n", str_apps_input[i].c_str());
///< F703
vector<pair<string, int>> vec_F703;
for (auto x : str_apps_output_F703)
vec_F703.push_back(x);
sort(vec_F703.begin(), vec_F703.end(),comp);
printf("\\nF703 protocol\\n");
for (auto x : vec_F703)
printf("%s", x.first.c_str());
///< F706
vector<pair<string, int>> vec_F706;
for (auto x : str_apps_output_F706)
vec_F706.push_back(x);
sort(vec_F706.begin(), vec_F706.end(), comp);
printf("\\nF706 protocol\\n");
for (auto x : vec_F706)
printf("%s", x.first.c_str());
///< F710
vector<pair<string, int>> vec_F710;
for (auto x : str_apps_output_F710)
vec_F710.push_back(x);C
sort(vec_F710.begin(), vec_F710.end(), comp);
printf("\\nF710 protocol\\n");
for (auto x : vec_F710)
printf("%s", x.first.c_str());KJ98=]
\\2
ZXXJ
///< 解析规约报文文件
void parseProtocolFile(const char *map_file)
FILE* fp_cfg;
if (0 == (fopen_s(&fp_cfg,(const char*)map_file, (const char*)"rt")))
char line[1024] = 0 ;
void* ret = NULL;
while (true)
memset(line, 0, sizeof(line));
ret = fgets(line, sizeof(line), fp_cfg);
if (ret == NULL) break;
if (line[strlen(line) - 1] == '\\n' || line[strlen(line) - 1] == '\\r')
line[strlen(line) - 1] = '\\0';
if (line[strlen(line) - 1] == '\\r' || line[strlen(line) - 1] == '\\n')
line[strlen(line) - 1] = '\\0';
//printf("%s\\n", line);
str_apps_input.push_back(line);
fclose(fp_cfg);
int c2i(char ch)
if (isdigit(ch))
return ch - 48;
if (ch < 'A' || (ch > 'F' && ch < 'a') || ch > 'z')
return -1;
if (isalpha(ch))
return isupper(ch) ? ch - 55 : ch - 87;
return -1;
int hex2dec(char *hex)
int len;
int num = 0;
int temp;
int bits;
int i;
len = strlen(hex);
for (i = 0, temp = 0; i < len; i++, temp = 0)
temp = c2i(*(hex + i));
bits = (len - i - 1) * 4;
temp = temp << bits;
num = num | temp;
// 返回结果
return num;
共赢共享
示例代码已上传,可运行。vs2019工程可运行,供参考
番外
由于我这里需求只需要寄存器地址与个数,所以只解析了部分。有全部解析需求的同学可以在此基础上做二次开发。
1.针对技术方案选型,我分享几点想法吧。考虑到程序的适应性,我选择了从文件读取数据的方式,这样比较灵活,只要把海量的数据拷贝进文件即可,无需其他复杂的操作。后期还方便其他功能码报文的导入与解析,只需要加解析分支即可。
2.针对乱序报文的情况,我采用预筛选的策略,读取报文文件时按行读取内容,根据报文类型,插入到对应的unordered_map
中,便于后面打印输出;
3.另外针对去重与排序的需求点,应该是使用map
更合适,有兴趣的同学可以使用map
实现下这个功能,还有余力的话,可以了解了解map
、unordered_map
两者的异同、优缺点等。
最后祝大家代码一把过,全都没bug!互相学习,共同进行!
以上是关于「 Modbus-RTU报文解析」解析030610功能码报文示例的主要内容,如果未能解决你的问题,请参考以下文章
IEC60870-104报文解析 —— 利用Wireshark对报文逐字节进行解析详细解析IEC60870-104附含模拟器以及pcap包