「 Modbus-RTU报文解析」解析030610功能码报文示例

Posted 谁吃薄荷糖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「 Modbus-RTU报文解析」解析030610功能码报文示例相关的知识,希望对你有一定的参考价值。

背景介绍

项目用到modbus-rtu通讯协议与三方平台通讯,由于三方平台没有寄存器地址点表信息,只提供了报文数据,故需要对报文进行二次解析,从而获得三方平台使用到的寄存器地址信息。

方案思路

报文示例

报文示例无包尾校验位,从站地址为1
01030001002B

从站地址功能码寄存器起始地址(高位)寄存器起始地址(低位)读取寄存器个数(高位)读取寄存器个数(低位)
0x010x030x000x040x000x2B

010600580000

从站地址功能码寄存器地址(高位)寄存器地址(低位)写入值(高位)写入值((低位)
0x010x060x000x580x000x00

0110003300020400010001

从站地址功能码寄存器起始地址(高位)寄存器起始地址(低位)设置寄存器个数(高位)设置寄存器个数(低位)字节数字节1字节2字节3字节4
0x010x100x000x330x000x020x040x000x010x000x01

需求分解

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实现下这个功能,还有余力的话,可以了解了解mapunordered_map两者的异同、优缺点等。
最后祝大家代码一把过,全都没bug!互相学习,共同进行!

以上是关于「 Modbus-RTU报文解析」解析030610功能码报文示例的主要内容,如果未能解决你的问题,请参考以下文章

MODBUS RTU协议原理及功能码解析

(高分请教)如何解析报文?

java怎么解析文本文件中的soap报文

IEC60870-104报文解析 —— 利用Wireshark对报文逐字节进行解析详细解析IEC60870-104附含模拟器以及pcap包

php如何解析多级xml报文?

jetbrick-template模板语言解析数据