数据包解析是不同设备(如电脑、ESP32 等嵌入式设备)之间通信的核心环节。简单说,就是把收到的 "一串数据" 翻译成双方都能理解的 "具体信息"(比如温度、湿度、命令等)。下面介绍几种常见的数据包格式,以及 Python 和 ESP32(基于 Arduino 框架)的解析代码,尽量用通俗的语言解释。

一、文本格式(最容易理解)

文本格式的数据包由字符串组成,人类可以直接看懂,适合简单场景。常见的有CSV 格式JSON 格式

1. CSV 格式(逗号分隔值)

特点:用逗号(或其他符号)分隔不同字段,结构简单,类似表格。
适用场景:传感器批量数据(如温度、湿度、时间)、简单配置信息。
示例数据包"25.6,60.2,202310011200"(温度 = 25.6℃,湿度 = 60.2%,时间 = 2023-10-01 12:00)

Python 解析代码
用内置的split函数分割字符串即可。

# 假设收到的CSV数据包
recv_data = "25.6,60.2,202310011200"# 解析步骤
fields = recv_data.split(',')  # 用逗号分割
if len(fields) == 3:  # 检查字段数量是否正确temperature = float(fields[0])humidity = float(fields[1])time = fields[2]print(f"温度:{temperature}℃,湿度:{humidity}%,时间:{time}")
else:print("数据格式错误")

ESP32 解析代码
ESP32 通过串口 / 网络收到字符串后,用strtok函数分割(类似 Python 的 split)。

#include <Arduino.h>void setup() {Serial.begin(115200);  // 初始化串口
}void loop() {if (Serial.available()) {  // 检查是否有数据String recv_str = Serial.readStringUntil('\n');  // 读取一行数据(假设以换行结束)recv_str.trim();  // 去掉首尾空格和换行// 用逗号分割字符串char* data_ptr = strtok((char*)recv_str.c_str(), ",");  // 第一个字段float temperature, humidity;String time_str;int count = 0;while (data_ptr != NULL) {if (count == 0) {temperature = atof(data_ptr);  // 转成浮点数(温度)} else if (count == 1) {humidity = atof(data_ptr);  // 转成浮点数(湿度)} else if (count == 2) {time_str = String(data_ptr);  // 时间字符串}data_ptr = strtok(NULL, ",");  // 下一个字段count++;}if (count == 3) {  // 确认解析到3个字段Serial.printf("温度:%.1f℃,湿度:%.1f%,时间:%s\n", temperature, humidity, time_str.c_str());} else {Serial.println("CSV格式错误");}}
}
2. JSON 格式(键值对结构)

特点:用{键:值}的结构表示数据,支持嵌套(比如数组、对象),适合复杂数据。
适用场景:API 接口、带层级的配置信息(如 "设备信息 + 传感器数据")。
示例数据包{"device_id":"esp32_01","data":{"temp":25.6,"hum":60.2},"time":"202310011200"}

Python 解析代码
用内置的json模块直接转成字典,方便取值。

import json# 假设收到的JSON数据包
recv_data = '{"device_id":"esp32_01","data":{"temp":25.6,"hum":60.2},"time":"202310011200"}'try:# 解析JSON字符串为字典data_dict = json.loads(recv_data)# 提取数据device_id = data_dict["device_id"]temp = data_dict["data"]["temp"]hum = data_dict["data"]["hum"]time = data_dict["time"]print(f"设备:{device_id},温度:{temp}℃,湿度:{hum}%,时间:{time}")
except json.JSONDecodeError:print("JSON格式错误")
except KeyError as e:print(f"缺少字段:{e}")

ESP32 解析代码
需要用ArduinoJson库(需在 Arduino 库管理器中安装),处理 JSON 结构。

#include <Arduino.h>
#include <ArduinoJson.h>  // 引入JSON库void setup() {Serial.begin(115200);
}void loop() {if (Serial.available()) {String recv_str = Serial.readStringUntil('\n');recv_str.trim();// 分配JSON缓冲区(根据数据大小调整,这里用1024字节)StaticJsonDocument<1024> doc;// 解析JSONDeserializationError error = deserializeJson(doc, recv_str);if (error) {  // 解析失败Serial.printf("JSON解析错误:%s\n", error.c_str());return;}// 提取数据(注意判断字段是否存在)if (doc.containsKey("device_id") && doc.containsKey("data") && doc.containsKey("time")) {const char* device_id = doc["device_id"];float temp = doc["data"]["temp"];float hum = doc["data"]["hum"];const char* time_str = doc["time"];Serial.printf("设备:%s,温度:%.1f℃,湿度:%.1f%,时间:%s\n", device_id, temp, hum, time_str);} else {Serial.println("JSON缺少必要字段");}}
}

二、二进制格式(更高效)

文本格式虽然易读,但占空间大(比如数字 "25.6" 要占 4 个字符),传输效率低。二进制格式直接用字节存储数据(比如 25.6℃可以用 2 个字节存储),更适合嵌入式设备(如 ESP32)之间的高速通信。常见的有固定长度格式TLV 格式

1. 固定长度格式

特点:每个字段的长度固定(比如温度占 2 字节,湿度占 2 字节),解析时按固定位置取数据,速度快。
适用场景:实时性要求高的场景(如传感器每秒传输一次数据)。
示例数据包结构(共 5 字节):

字段长度(字节)说明
温度2用 int16_t 存储(实际值 = 存储值 / 10,比如 0x00FA=250 → 25.0℃)
湿度2同上(0x0258=600 → 60.0%)
状态10 = 正常,1 = 异常

对应的二进制数据:0x00 0xFA 0x02 0x58 0x00(即温度 25.0℃,湿度 60.0%,状态正常)

Python 解析代码
struct模块解析二进制数据(需要知道每个字段的类型和顺序)。

import struct# 假设收到的二进制数据(5字节)
recv_bytes = b'\x00\xFA\x02\x58\x00'if len(recv_bytes) == 5:  # 检查长度是否正确# 解析:2字节温度(int16)、2字节湿度(int16)、1字节状态(uint8)temp_raw, hum_raw, status = struct.unpack('>hhu', recv_bytes)# 转换为实际值(除以10)temperature = temp_raw / 10.0humidity = hum_raw / 10.0print(f"温度:{temperature}℃,湿度:{humidity}%,状态:{'正常' if status == 0 else '异常'}")
else:print("二进制数据长度错误")

>hhu表示:>大端模式,hint16,hint16,uuint8)

ESP32 解析代码
直接操作字节数组,按固定位置取数据。

#include <Arduino.h>// 定义数据包结构(5字节)
struct DataPacket {int16_t temp_raw;  // 温度原始值(2字节)int16_t hum_raw;   // 湿度原始值(2字节)uint8_t status;    // 状态(1字节)
};void setup() {Serial.begin(115200);
}void loop() {if (Serial.available() >= 5) {  // 至少收到5字节uint8_t recv_buf[5];Serial.readBytes(recv_buf, 5);  // 读取5字节到缓冲区// 解析数据(大端模式:高位字节在前)DataPacket data;data.temp_raw = (recv_buf[0] << 8) | recv_buf[1];  // 第0-1字节 → 温度data.hum_raw = (recv_buf[2] << 8) | recv_buf[3];   // 第2-3字节 → 湿度data.status = recv_buf[4];                         // 第4字节 → 状态// 转换为实际值float temperature = data.temp_raw / 10.0;float humidity = data.hum_raw / 10.0;Serial.printf("温度:%.1f℃,湿度:%.1f%,状态:%s\n",temperature, humidity, (data.status == 0) ? "正常" : "异常");}
}
2. TLV 格式(Type-Length-Value)

特点:每个字段由 "类型(Type)+ 长度(Length)+ 值(Value)" 组成,字段数量和长度可以不固定,灵活性高。
适用场景:需要扩展的协议(比如有时传温度,有时传温度 + 湿度 + 电量)。
示例数据包

类型(1 字节)长度(1 字节)值(n 字节)说明
0x010x020x00 0xFA温度(25.0℃)
0x020x020x02 0x58湿度(60.0%)

对应的二进制数据:0x01 0x02 0x00 0xFA 0x02 0x02 0x02 0x58

Python 解析代码
循环解析每个 TLV 单元(先读类型,再读长度,再读对应的值)。

def parse_tlv(data):parsed = {}index = 0while index < len(data):if index + 2 > len(data):  # 至少需要类型(1)+长度(1)breaktype_ = data[index]        # 类型(1字节)length = data[index + 1]   # 长度(1字节)index += 2if index + length > len(data):  # 检查值是否完整breakvalue = data[index:index + length]  # 值(length字节)index += length# 解析具体值(根据类型转换)if type_ == 0x01:  # 温度temp_raw = (value[0] << 8) | value[1]parsed["temperature"] = temp_raw / 10.0elif type_ == 0x02:  # 湿度hum_raw = (value[0] << 8) | value[1]parsed["humidity"] = hum_raw / 10.0return parsed# 假设收到的TLV二进制数据
recv_bytes = bytes([0x01, 0x02, 0x00, 0xFA, 0x02, 0x02, 0x02, 0x58])
result = parse_tlv(recv_bytes)
print(f"解析结果:{result}")  # 输出:{'temperature': 25.0, 'humidity': 60.0}

ESP32 解析代码
同样循环读取每个 TLV 单元,按类型处理值。

#include <Arduino.h>void parseTLV(uint8_t* data, int len) {int index = 0;float temp = -1, hum = -1;while (index < len) {if (index + 2 > len) break;  // 不够类型+长度的字节uint8_t type = data[index];uint8_t length = data[index + 1];index += 2;if (index + length > len) break;  // 不够值的字节// 解析值(根据类型)if (type == 0x01 && length == 2) {  // 温度(2字节)int16_t raw = (data[index] << 8) | data[index + 1];temp = raw / 10.0;} else if (type == 0x02 && length == 2) {  // 湿度(2字节)int16_t raw = (data[index] << 8) | data[index + 1];hum = raw / 10.0;}index += length;}// 打印结果if (temp != -1) Serial.printf("温度:%.1f℃ ", temp);if (hum != -1) Serial.printf("湿度:%.1f% ", hum);Serial.println();
}void setup() {Serial.begin(115200);
}void loop() {// 假设收到8字节TLV数据if (Serial.available() >= 8) {uint8_t recv_buf[8];Serial.readBytes(recv_buf, 8);parseTLV(recv_buf, 8);}
}

三、自定义格式(最灵活,适合嵌入式)

实际项目中,常自定义格式(结合文本或二进制),并加入帧头、帧尾、校验位(防止数据错乱)。比如:

格式定义帧头(0xAA) + 数据长度(1字节) + 数据内容 + 校验和(1字节) + 帧尾(0x55)

  • 帧头 / 帧尾:标记数据包的开始和结束(比如 0xAA 开始,0x55 结束)。
  • 数据长度:告诉接收方 "数据内容" 有多少字节,方便解析。
  • 校验和:所有数据字节的和(取低 8 位),用于检查数据是否传输错误。

示例数据包(数据内容是温度 25.0℃,即 0x00FA):
0xAA 0x02 0x00 0xFA 0xFA 0x55

  • 帧头:0xAA
  • 数据长度:0x02(数据内容占 2 字节)
  • 数据内容:0x00 0xFA(温度原始值)
  • 校验和:0x00 + 0xFA = 0xFA
  • 帧尾:0x55

Python 解析代码
先找帧头,再验证帧尾和校验和,最后解析数据。

def parse_custom(data):index = 0while index < len(data):# 找帧头(0xAA)if data[index] != 0xAA:index += 1continue# 检查是否有足够的字节(帧头+长度+校验+帧尾至少4字节)if index + 4 > len(data):breaklength = data[index + 1]  # 数据长度# 检查总长度是否足够(帧头+长度+数据+校验+帧尾)total_len = 2 + length + 1 + 1  # 2=帧头+长度,1=校验,1=帧尾if index + total_len > len(data):index += 1continue# 提取数据内容content = data[index + 2 : index + 2 + length]# 提取校验和checksum = data[index + 2 + length]# 提取帧尾tail = data[index + 2 + length + 1]# 验证帧尾和校验和if tail != 0x55:index += 1continue# 计算校验和(数据内容的和)calc_checksum = sum(content) & 0xFFif calc_checksum != checksum:index += 1continue# 解析数据内容(这里是温度)if length == 2:temp_raw = (content[0] << 8) | content[1]temperature = temp_raw / 10.0print(f"解析成功:温度={temperature}℃")# 移动到下一个数据包index += total_lenreturn# 假设收到的自定义格式数据
recv_bytes = bytes([0xAA, 0x02, 0x00, 0xFA, 0xFA, 0x55])
parse_custom(recv_bytes)  # 输出:解析成功:温度=25.0℃

ESP32 解析代码
逻辑和 Python 类似,先找帧头,再验证校验和和帧尾。

#include <Arduino.h>void parseCustom(uint8_t* data, int len) {int index = 0;while (index < len) {// 找帧头0xAAif (data[index] != 0xAA) {index++;continue;}// 检查最小长度(帧头+长度+校验+帧尾=4字节)if (index + 4 > len) break;uint8_t length = data[index + 1];  // 数据长度uint16_t total_len = 2 + length + 1 + 1;  // 总长度// 检查总长度是否足够if (index + total_len > len) {index++;continue;}// 提取各部分uint8_t* content = &data[index + 2];  // 数据内容uint8_t checksum = data[index + 2 + length];  // 校验和uint8_t tail = data[index + 2 + length + 1];  // 帧尾// 验证帧尾if (tail != 0x55) {index++;continue;}// 计算校验和uint8_t calc_checksum = 0;for (int i = 0; i < length; i++) {calc_checksum += content[i];}if (calc_checksum != checksum) {index++;continue;}// 解析数据(温度)if (length == 2) {int16_t temp_raw = (content[0] << 8) | content[1];float temperature = temp_raw / 10.0;Serial.printf("解析成功:温度=%.1f℃\n", temperature);}// 移动到下一个数据包index += total_len;}
}void setup() {Serial.begin(115200);
}void loop() {// 假设收到6字节自定义数据if (Serial.available() >= 6) {uint8_t recv_buf[6];Serial.readBytes(recv_buf, 6);parseCustom(recv_buf, 6);}
}

总结:如何选择格式?

  • 简单场景(少量数据,需要人看懂):用 CSV 或 JSON。
  • 实时性高、数据量大(如传感器每秒 10 次传输):用固定长度二进制。
  • 需要灵活扩展(字段不固定):用 TLV。
  • 嵌入式设备通信(怕数据错乱):用带帧头、校验的自定义格式。

核心原则:在 "易读性" 和 "效率 / 可靠性" 之间找平衡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916803.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916803.shtml
英文地址,请注明出处:http://en.pswp.cn/news/916803.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开发杂谈】用AI玩AI聊天游戏:使用 Electron 和 Python 开发大模型语音聊天软件

项目地址&#xff1a; GitHub | wfts-ai-chathttps://github.com/HiMeditator/wfts-ai-chat 前言 最近一个基于 AI 的聊天游戏 Whispers from the Stars&#xff08;群星低语&#xff09;的 Demo 版本发布了。《Whispers from the Star》是一款科幻主题互动游戏。背景设定在…

SQL优化系统解析

MySQL的安装就不讲述了, 本篇文章着重讲解sql优化 本篇是对B站颜群老师视频讲解的笔记梳理, 感兴趣的可以去看下老师的原视频: SQL优化 MySQL原理 1. MySQL逻辑分层: 连接层->服务层->引擎层->存储层(如图) 连接层&#xff1a;提供与客户端连接的服务服务层&#…

【机器学习案列-25】电信用户流失预测:从数据处理到模型评估

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

【MATLAB代码】灰色预测与多项式预测、指数平滑预测的对比,包含预处理、模型构建和和可视化输出。模拟预测若干年的GDP,订阅后可查看完整代码,有中文注释

代码实现了灰色预测模型GM(1,1)在GDP预测中的应用,并结合线性回归、二次多项式回归和指数平滑模型进行对比分析。代码包含数据预处理、模型构建、可视化输出和误差验证四个核心模块,实现了从数据输入到预测结果展示的全流程。 文章目录 运行结果 MATLAB源代码 GM(1,1)模型数学…

搜索二维矩阵Ⅱ C++

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {i…

如何在 Apache Ignite 中创建和使用自定义 SQL 函数(Custom SQL Functions)

这段内容讲的是 如何在 Apache Ignite 中创建和使用自定义 SQL 函数&#xff08;Custom SQL Functions&#xff09;。我们可以分步骤来理解它的含义和用法。&#x1f4da; 一、什么是 Custom SQL Function&#xff1f; Apache Ignite 的 SQL 引擎支持 标准 SQL 函数&#xff08…

Oracle 11g RAC数据库实例重启的两种方式

Oracle 11g RAC数据库实例重启的两种方式 使用SQLPlus重启数据库实例 使用SRVCTL重启数据库实例 Administrator-Managed还是Policy-Managed 📖 关于关闭RAC的数据库实例: 在Oracle RAC中,单独关闭一个实例不会影响到其他正在运行的实例。 要完全关闭Oracle RAC数据库,需要…

分别使用 Java 8 和 Python 调用 Elasticsearch 接口简单获取数据

使用 Java 8 首先,确保在您的 pom.xml 文件中添加了正确的 Maven 依赖: <dependency><groupId>co.elastic.clients</groupId><artifactId>elastic

【通识】数据结构

数据结构逻辑结构物理结构&#xff08;存储结构&#xff09;&#xff0c;数据结构是计算机中存储、组织数据的方式。 其中物理结构是数据的逻辑结构在计算机中的存储形式。而存储器针对内存而言&#xff0c;像硬盘、软盘、光盘等外部存储器的数据组织常用文件结构描述。1. 基础…

Ubuntu22.04提示找不到python命令的解决方案

Ubuntu22.04提示找不到python命令的解决方案 问题背景 在Ubuntu22.04中按照获取Openharmony源码中的如下命令&#xff1a; // 方式一&#xff08;推荐&#xff09;&#xff1a;通过repo ssh下载&#xff08;需注册公钥&#xff0c;请参考码云帮助中心&#xff09;。repo in…

RabbitMQ面试精讲 Day 6:消息确认与事务机制

【RabbitMQ面试精讲 Day 6】消息确认与事务机制 开篇 欢迎来到"RabbitMQ面试精讲"系列的第6天&#xff01;今天我们将深入探讨RabbitMQ中确保消息可靠性的两大核心机制&#xff1a;消息确认与事务机制。这两个特性是面试中高频出现的热点问题&#xff0c;也是生产环…

被困扰的elementplus样式修改问题:select选择器修改和el-input修改

一、Select选择器的原生样式的本来面貌这是原生的没有经过任何加工的面貌&#xff1a;这是没有经过任何加工的选中时出现下拉框的面貌&#xff1a;这是没有经过加工的悬浮下拉菜单的面貌&#xff1a;这是没有经过加工的选中时的面貌&#xff1a;二、如何修改Select选择器&#…

GO 从入门到精通2

Go语言的反射&#xff08;Reflection&#xff09;机制通过 reflect 包实现&#xff0c;允许程序在运行时动态检查、修改和操作变量的类型信息和值。以下是反射的核心概念、用法及注意事项的详细解析&#xff1a;一、反射的基本概念reflect.Type 表示变量的类型信息&#xff0c;…

常用设计模式系列(十二)—享元模式

常用设计模式系列&#xff08;十二&#xff09;—享元模式 第一节 前言 昏昏沉沉的两天过去了&#xff0c;也不知道为什么&#xff0c;突然总觉得很困&#xff0c;可能之前熬夜熬的多了&#xff0c;所以现在可能年纪大了&#xff0c;需要蹦一蹦才能把自己从颓废的边缘拉扯回来&…

基于spring boot的医院挂号就诊系统(源码+论文)

一、开发环境 技术/工具描述MYSQL数据库1. 体积小&#xff0c;安装便捷&#xff1a;MySQL数据库体积小&#xff0c;占用内存小&#xff0c;不影响电脑上其他软件的运行&#xff0c;并且不需要因为安装维护MySQL数据库而重装系统。2. 适合老旧电脑&#xff1a;作为学习开发的电…

spring-security

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>spring: security: user: name: root password: 123456 这个配置在访问接口时候根据您提供的Spring Secur…

搭建一个自定义的 React 图标库

搭建一个自定义的 React 图标库可以让你在多个项目中复用统一的图标资源&#xff0c;同时支持按需加载、主题化和灵活的配置。以下是详细的步骤指南&#xff1a; 1. 设计图标库结构 首先规划图标库的目录结构和功能&#xff1a; my-react-icons/ ├── src/ │ ├── ico…

宝塔面板如何升级OpenSSL

宝塔面板如何升级OpenSSL&#xff08;亲测可用&#xff09;目前一些服务器的OpenSSL还是1.0.1e版本&#xff0c;今天进行服务器漏洞检测出现OpenSSL存在漏洞&#xff0c;那只能升级OpenSSL了。1、登录SSH&#xff0c;查看OpenSSL版本openssl version2、下载源代码wget https://…

深入理解 C++ 红黑树:从理论到实践

引言 在计算机科学领域&#xff0c;数据结构是构建高效算法的基石。而在众多的数据结构中&#xff0c;平衡二叉搜索树因其优秀的查找、插入和删除性能而备受关注。红黑树&#xff08;Red-Black Tree&#xff09;作为一种自平衡的二叉搜索树&#xff0c;更是在 C 标准库&#x…

外星人笔记本装win11哪个版本好_外星人笔记本装win11专业版教程

外星人笔记本安装win11哪个版本好&#xff1f;答&#xff1a;外星人笔记本还是建议安装win11专业版。Win分为多个版本&#xff0c;其中家庭版&#xff08;Home&#xff09;和专业版&#xff08;Pro&#xff09;是用户选择最多的两个版本。win11专业版在功能以及安全性方面有着明…