「 Modbus-RTU报文解析」解析03、06、10功能码报文示例

本文档介绍了如何使用C++解析Modbus RTU通讯协议的报文,以获取寄存器地址信息。通过从Excel导出的报文数据,去除重复项并按顺序输出,实现点表的统计与维护。代码示例展示了如何处理不同功能码的报文,并对解析结果进行排序展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


背景介绍

项目用到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);
	}

	sort(vec_F710.begin(), vec_F710.end(), comp);

	printf("\nF710 protocol\n");
	for (auto x : vec_F710)
	{
		printf("%s", x.first.c_str());
	}

///< 解析规约报文文件
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!互相学习,共同进行!

评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁吃薄荷糖

你获得知识,我获得财富,双赢!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值