手把手教你用ESP32实现BLE广播数据解析(附MicroPython代码)

# 手把手教你用ESP32实现BLE广播数据解析(附MicroPython代码) 最近在折腾几个智能家居项目,发现BLE广播数据解析这块真是让人又爱又恨。爱的是它简单直接,设备一开机就能往外“喊话”,不用建立连接就能获取基本信息;恨的是那31字节的限制和五花八门的AD Type,稍不留神就解析出错。我在实际项目中遇到过好几次因为广播数据格式不对,导致手机App识别不了设备的情况,调试起来特别费劲。 如果你也在用ESP32做物联网开发,特别是需要让设备被手机或其他主机发现并识别,那么掌握BLE广播数据的构造和解析绝对是必修课。这篇文章我会从最基础的广播包结构讲起,一步步带你用MicroPython实现完整的广播数据解析,包括如何处理中文设备名、如何设置扫描响应数据等实际问题。我会分享一些踩坑经验,比如为什么有些设备在nRF Connect上能看到,在自己的App里却扫描不到,以及如何避免常见的编码错误。 ## 1. BLE广播基础:不只是“喊一嗓子”那么简单 很多人把BLE广播想象成设备在“喊话”,这个比喻虽然形象,但容易让人低估它的复杂性。实际上,BLE广播是一套精心设计的协议,在2.4GHz频段的40个信道中,专门划出了3个广播信道(37、38、39)。设备会在这三个信道上轮流发送相同的广播包,这样做主要是为了提高可靠性——万一某个信道有干扰,其他信道还能正常通信。 广播包的最大长度是37字节,听起来很少对吧?但这里面有6个字节固定用于设备MAC地址,真正留给应用数据的只有31字节。这31个字节还要按照特定的格式组织,不能随便往里塞数据。 > 注意:很多新手会疑惑为什么广播包长度限制这么严格。这其实是BLE低功耗设计的一部分——数据包越小,发送时间越短,射频模块开启时间就越少,自然就更省电。如果你需要传输大量数据,应该通过建立连接后的数据信道,而不是广播。 广播包的基本结构是这样的: ``` [设备MAC地址(6字节)] + [广播数据(最多31字节)] ``` 而广播数据部分又由若干个AD Structure组成,每个AD Structure的格式固定: ``` Length(1字节) + AD Type(1字节) + AD Data(N字节) ``` 其中Length = 1(AD Type) + N(AD Data长度)。举个例子,如果你看到这样的广播数据: ```python [0x02, 0x01, 0x06, 0x03, 0x09, 0x41, 0x42] ``` 可以这样解析: - 第一个AD Structure:长度0x02,类型0x01,数据0x06 - 第二个AD Structure:长度0x03,类型0x09,数据[0x41, 0x42] 常见的AD Type有几十种,下面这个表格列出了最常用的几种: | AD Type值 | 名称 | 说明 | 典型用途 | |-----------|------|------|----------| | 0x01 | Flags | 设备能力标志 | 标识设备是否可连接、是否支持经典蓝牙等 | | 0x08 | Shortened Local Name | 缩短的设备名称 | 设备名称较长时使用缩短版本 | | 0x09 | Complete Local Name | 完整的设备名称 | 设备的完整名称 | | 0x0A | Tx Power Level | 发射功率 | 用于距离估算 | | 0x03 | Complete List of 16-bit UUIDs | 完整的16位UUID列表 | 设备支持的服务 | | 0xFF | Manufacturer Specific Data | 厂商自定义数据 | 厂商私有数据,如iBeacon | 我在实际项目中发现,很多解析问题都出在对AD Type的理解上。比如有些开发者把厂商自定义数据(0xFF)当成了服务UUID,结果App端怎么也解析不出来。还有的设备名称包含中文,但没正确处理UTF-8编码,导致显示乱码。 ## 2. ESP32环境搭建与基础广播实现 现在让我们动手实践。首先确保你的ESP32开发环境已经准备好。我推荐使用Thonny IDE,它集成了MicroPython支持,调试起来比较方便。如果你还没安装MicroPython固件,可以按照以下步骤操作: 1. 下载最新的ESP32 MicroPython固件 2. 使用esptool.py工具刷入固件 3. 安装Thonny并配置ESP32连接 刷写固件的命令如下: ```bash # 将ESP32进入下载模式(按住BOOT键,按一下EN键,然后释放EN键,再释放BOOT键) esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20240105-v1.22.1.bin ``` > 提示:不同操作系统下串口设备名可能不同,Windows通常是COMx,Linux/macOS是/dev/ttyUSBx或/dev/tty.SLAB_USBtoUART。如果遇到权限问题,可能需要将用户加入dialout组。 环境准备好后,我们来写第一个广播程序。这个程序很简单,就是让ESP32广播一个设备名和厂商数据: ```python import bluetooth import struct import time from micropython import const # 初始化BLE ble = bluetooth.BLE() ble.active(True) # 设置广播数据 # AD Structure 1: Flags (可连接、普通发现模式) flags_data = bytes([0x02, 0x01, 0x06]) # 长度2, 类型0x01, 数据0x06 # AD Structure 2: 完整设备名称 device_name = "ESP32_Test" name_data = bytes([len(device_name) + 1, 0x09]) + device_name.encode('utf-8') # AD Structure 3: 厂商自定义数据 (模拟iBeacon格式) manufacturer_data = bytes([ 0x1A, # 长度: 26字节 (1+1+2+16+2+2) 0xFF, # 类型: 厂商自定义 0x4C, 0x00, # 公司ID: Apple (0x004C) 0x02, 0x15, # iBeacon类型 # UUID: 替换成你自己的 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x01, # Major 0x00, 0x02, # Minor 0xC5 # 信号强度校准值 ]) # 合并所有广播数据 adv_data = flags_data + name_data + manufacturer_data # 检查总长度是否超过31字节 if len(adv_data) > 31: print(f"警告: 广播数据长度{len(adv_data)}字节,超过31字节限制!") # 这里可以采取裁剪策略,比如缩短设备名 adv_data = adv_data[:31] # 设置广播参数并开始广播 ble.gap_advertise(100, adv_data) # 100ms间隔 print("开始广播...") print(f"广播数据: {adv_data.hex()}") # 保持广播运行 try: while True: time.sleep(1) except KeyboardInterrupt: ble.gap_advertise(None) # 停止广播 ble.active(False) print("广播已停止") ``` 运行这个程序后,用手机上的nRF Connect或LightBlue等BLE工具应该能看到一个名为"ESP32_Test"的设备。点击查看原始数据,你会看到我们设置的三个AD Structure。 这里有几个关键点需要注意: 1. **数据长度计算**:每个AD Structure的第一个字节是长度,这个长度包括AD Type(1字节)和AD Data(N字节),但不包括长度字节本身。所以计算时要格外小心。 2. **字节序问题**:BLE采用小端序(Little-Endian)。比如公司ID Apple是0x004C,但在数据中要写成`0x4C, 0x00`。 3. **数据合并**:多个AD Structure直接拼接就行,不需要额外的分隔符。 我在第一次实现时犯过一个错误:忘记计算长度字节本身,结果广播数据解析总是出错。后来发现nRF Connect的RAW视图能直接显示解析结果,才找到问题所在。 ## 3. 广播数据解析:从字节流到有意义的信息 发送广播相对简单,解析广播数据才是真正的挑战。特别是当你需要开发一个扫描设备并解析其广播数据的应用时,理解如何反向解析至关重要。 让我们先看看如何用MicroPython解析接收到的广播数据。ESP32既可以作为广播者,也可以作为扫描者。下面的代码演示了如何扫描周围的BLE设备并解析它们的广播数据: ```python import bluetooth import struct import time from micropython import const # 定义常见的AD Type AD_TYPE_FLAGS = const(0x01) AD_TYPE_SHORT_NAME = const(0x08) AD_TYPE_COMPLETE_NAME = const(0x09) AD_TYPE_TX_POWER = const(0x0A) AD_TYPE_UUID16_COMPLETE = const(0x03) AD_TYPE_UUID128_COMPLETE = const(0x07) AD_TYPE_MANUFACTURER_DATA = const(0xFF) def parse_advertisement_data(data): """解析广播数据,返回解析后的字典""" result = { 'flags': None, 'short_name': None, 'complete_name': None, 'tx_power': None, 'uuid16_list': [], 'uuid128_list': [], 'manufacturer_data': {}, 'raw_data': data } i = 0 while i < len(data): # 获取长度(不包括长度字节本身) length = data[i] if length == 0: break # 检查是否有足够的数据 if i + 1 + length > len(data): print(f"数据不完整: i={i}, length={length}, total={len(data)}") break # 获取AD Type ad_type = data[i + 1] # 获取AD Data ad_data = data[i + 2:i + 1 + length] # 根据AD Type解析数据 if ad_type == AD_TYPE_FLAGS and len(ad_data) >= 1: result['flags'] = ad_data[0] elif ad_type == AD_TYPE_SHORT_NAME: try: result['short_name'] = ad_data.decode('utf-8') except: result['short_name'] = str(ad_data) elif ad_type == AD_TYPE_COMPLETE_NAME: try: result['complete_name'] = ad_data.decode('utf-8') except: result['complete_name'] = str(ad_data) elif ad_type == AD_TYPE_TX_POWER and len(ad_data) >= 1: # TX Power是有符号的,需要转换 result['tx_power'] = struct.unpack('<b', ad_data[0:1])[0] elif ad_type == AD_TYPE_UUID16_COMPLETE: # 16位UUID列表,每2个字节一个UUID for j in range(0, len(ad_data), 2): if j + 2 <= len(ad_data): uuid = struct.unpack('<H', ad_data[j:j+2])[0] result['uuid16_list'].append(f"0x{uuid:04X}") elif ad_type == AD_TYPE_UUID128_COMPLETE: # 128位UUID,16个字节 if len(ad_data) >= 16: # 转换为标准的UUID格式 uuid_bytes = ad_data[:16] uuid_str = uuid_bytes.hex() formatted_uuid = f"{uuid_str[0:8]}-{uuid_str[8:12]}-{uuid_str[12:16]}-{uuid_str[16:20]}-{uuid_str[20:32]}" result['uuid128_list'].append(formatted_uuid.upper()) elif ad_type == AD_TYPE_MANUFACTURER_DATA and len(ad_data) >= 2: # 前2字节是公司ID company_id = struct.unpack('<H', ad_data[0:2])[0] manufacturer_data = ad_data[2:] result['manufacturer_data'][f"0x{company_id:04X}"] = manufacturer_data.hex() # 移动到下一个AD Structure i += 1 + length return result def print_parsed_data(parsed): """打印解析结果""" print("=" * 50) if parsed['complete_name']: print(f"设备名称: {parsed['complete_name']}") elif parsed['short_name']: print(f"设备名称(短): {parsed['short_name']}") if parsed['flags'] is not None: flags = parsed['flags'] print(f"设备标志: 0x{flags:02X}") print(f" - LE有限发现模式: {'是' if flags & 0x01 else '否'}") print(f" - LE普通发现模式: {'是' if flags & 0x02 else '否'}") print(f" - 不支持BR/EDR: {'是' if flags & 0x04 else '否'}") if parsed['tx_power'] is not None: print(f"发射功率: {parsed['tx_power']} dBm") if parsed['uuid16_list']: print(f"16位UUID服务: {', '.join(parsed['uuid16_list'])}") if parsed['uuid128_list']: print(f"128位UUID服务: {', '.join(parsed['uuid128_list'])}") if parsed['manufacturer_data']: print("厂商自定义数据:") for company_id, data in parsed['manufacturer_data'].items(): print(f" 公司{company_id}: {data}") print(f"原始数据: {parsed['raw_data'].hex()}") print("=" * 50) # 扫描回调函数 def scan_callback(addr_type, addr, adv_type, rssi, adv_data): """处理扫描到的设备""" addr_str = ':'.join([f'{b:02X}' for b in addr]) print(f"\n发现设备: {addr_str}, RSSI: {rssi} dBm, 广播类型: {adv_type}") # 解析广播数据 parsed = parse_advertisement_data(adv_data) print_parsed_data(parsed) # 主程序 ble = bluetooth.BLE() ble.active(True) print("开始扫描BLE设备...") print("按Ctrl+C停止扫描") try: # 开始扫描,设置扫描间隔和窗口 ble.gap_scan(5000, 30000, 30000) # 持续扫描,间隔30ms,窗口30ms # 注册扫描回调 ble.irq(lambda event, data: scan_callback(data[0], data[1], data[2], data[3], data[4]) if event == 5 else None) # 事件5表示扫描到设备 # 保持运行 while True: time.sleep(1) except KeyboardInterrupt: ble.gap_scan(None) # 停止扫描 ble.active(False) print("\n扫描已停止") ``` 这段代码的核心是`parse_advertisement_data`函数,它按照BLE规范解析广播数据。我在这里处理了几种最常见的数据类型,你可以根据需要扩展支持更多的AD Type。 运行这个程序,你会看到类似这样的输出: ``` 发现设备: A0:B1:C2:D3:E4:F5, RSSI: -45 dBm, 广播类型: 0 ================================================== 设备名称: ESP32_Temperature 设备标志: 0x06 - LE有限发现模式: 否 - LE普通发现模式: 是 - 不支持BR/EDR: 是 发射功率: 8 dBm 16位UUID服务: 0x1809, 0x180A 厂商自定义数据: 公司0x004C: 0215E2C56DB5DFFB48D2B060D0F5A71096E000010002C5 原始数据: 020106110951535033325F54656D7065726174757265020A0803091809180A ================================================== ``` 从输出中我们可以看到,这个设备名为"ESP32_Temperature",支持健康温度计服务(0x1809)和设备信息服务(0x180A),还包含Apple格式的厂商数据。 ## 4. 高级技巧:中文设备名、扫描响应与数据分包 掌握了基础解析后,我们来看看几个实际开发中经常遇到的问题。 ### 4.1 处理中文设备名 中文设备名的处理让很多开发者头疼。关键是要理解BLE广播数据使用UTF-8编码,而一个中文字符在UTF-8中通常占用3个字节。下面是一个设置中文设备名的例子: ```python def set_chinese_device_name(name): """设置中文设备名,自动处理UTF-8编码和长度限制""" # 将中文转换为UTF-8字节 name_bytes = name.encode('utf-8') # 计算AD Structure长度 # 长度 = 1(AD Type) + len(name_bytes) length = 1 + len(name_bytes) # 检查是否超过31字节限制(考虑其他AD Structure) if length > 31: print(f"警告: 设备名'{name}'编码后长度{len(name_bytes)}字节,可能超出广播包限制") # 可以尝试使用短名称(AD Type 0x08)或截断 if len(name_bytes) > 28: # 预留3字节给其他AD Structure # 截断名称 name_bytes = name_bytes[:28] length = 1 + len(name_bytes) # 构造AD Structure ad_structure = bytes([length, 0x09]) + name_bytes return ad_structure # 测试中文设备名 chinese_name = "智能温控器" adv_data = bytes([0x02, 0x01, 0x06]) + set_chinese_device_name(chinese_name) print(f"中文设备名'{chinese_name}'的广播数据:") print(f" UTF-8编码: {chinese_name.encode('utf-8').hex()}") print(f" 完整AD Structure: {adv_data.hex()}") ``` 运行这段代码,你会看到"智能温控器"被编码为15个字节(每个汉字3字节,共5个汉字)。在解析端,只需要用UTF-8解码即可还原。 > 注意:有些低质量的BLE扫描工具可能不支持UTF-8解码,会显示乱码。这是工具的问题,不是你的代码有问题。nRF Connect和大多数现代手机系统都能正确显示中文。 ### 4.2 使用扫描响应扩展数据 当你的设备信息太多,31字节装不下时,可以使用扫描响应。扫描响应的数据格式和广播数据完全一样,但它是被动发送的——只有当主机发送扫描请求时,设备才回复扫描响应。 下面是如何在ESP32上设置扫描响应: ```python import bluetooth import time ble = bluetooth.BLE() ble.active(True) # 主广播数据(基本信息) adv_data = bytes([ 0x02, 0x01, 0x06, # Flags 0x03, 0x09, 0x45, 0x53, 0x50, # 短名称 "ESP" ]) # 扫描响应数据(额外信息) scan_resp_data = bytes([ 0x11, 0x09, # 完整设备名称 0x45, 0x53, 0x50, 0x33, 0x32, 0x5F, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5F, 0x30, 0x30, 0x31, # "ESP32_Device_001" 0x03, 0x03, 0x18, 0x0A, # 健康温度计服务 0x05, 0x16, 0x18, 0x09, 0x00, 0x00, # 服务数据 ]) # 开始广播并设置扫描响应 ble.gap_advertise(100, adv_data, scan_resp_data) print("开始广播(含扫描响应)...") print(f"广播数据: {adv_data.hex()}") print(f"扫描响应: {scan_resp_data.hex()}") try: while True: time.sleep(1) except KeyboardInterrupt: ble.gap_advertise(None) ble.active(False) print("广播已停止") ``` 使用扫描响应有两个好处: 1. **节省广播能耗**:广播数据越少,发送时间越短,越省电 2. **按需提供数据**:只有真正感兴趣的设备才会请求扫描响应 ### 4.3 广播数据分包策略 当数据实在太多,连广播+扫描响应都装不下时,就需要考虑分包策略了。一种常见的做法是使用厂商自定义数据(0xFF)并设计自己的协议头,包含包序号和总包数信息。 下面是一个简单的分包示例: ```python def create_broadcast_packets(data, max_packet_size=27): """将数据分割成多个广播包 max_packet_size: 每个包的最大数据长度(扣除4字节包头) """ packets = [] # 计算需要多少包 total_packets = (len(data) + max_packet_size - 1) // max_packet_size for packet_num in range(total_packets): # 计算当前包的数据范围 start = packet_num * max_packet_size end = min(start + max_packet_size, len(data)) packet_data = data[start:end] # 创建协议头:包序号(1字节) + 总包数(1字节) + 数据长度(1字节) + 保留(1字节) header = bytes([ packet_num, # 当前包序号 (0-based) total_packets, # 总包数 len(packet_data), # 本包数据长度 0x00 # 保留 ]) # 完整的厂商自定义数据 manufacturer_data = header + packet_data # 构造AD Structure # 长度 = 1(AD Type) + 2(公司ID) + len(manufacturer_data) total_length = 1 + 2 + len(manufacturer_data) # 使用虚拟公司ID 0xFFFF ad_structure = bytes([total_length, 0xFF, 0xFF, 0xFF]) + manufacturer_data packets.append(ad_structure) return packets # 测试数据分包 test_data = b"This is a long message that needs to be split into multiple BLE broadcast packets. " * 3 packets = create_broadcast_packets(test_data) print(f"原始数据长度: {len(test_data)} 字节") print(f"分割成 {len(packets)} 个广播包") for i, packet in enumerate(packets): print(f"包 {i}: 长度={len(packet)} 字节, 数据={packet[:20].hex()}...") ``` 在接收端,你需要按照协议头重新组装数据。这种方案适合传输固件版本、设备配置等不常变化的数据。 ## 5. 实战案例:构建一个环境监测信标 让我们把这些知识综合起来,构建一个实用的环境监测信标。这个设备会广播温度、湿度和电池电量信息,同时支持扫描响应提供更多数据。 ```python import bluetooth import struct import time import machine from micropython import const class EnvironmentalBeacon: def __init__(self, device_name="EnvSensor"): self.ble = bluetooth.BLE() self.ble.active(True) self.device_name = device_name # 模拟传感器数据 self.temperature = 25.0 self.humidity = 60.0 self.battery_level = 85 # 公司ID (使用ESP32的厂商ID 0x02E5) self.company_id = const(0x02E5) def read_sensors(self): """模拟读取传感器数据""" # 在实际项目中,这里会读取真实的传感器 self.temperature += 0.1 self.humidity += 0.2 self.battery_level -= 0.01 # 限制范围 if self.temperature > 40: self.temperature = 20.0 if self.humidity > 90: self.humidity = 30.0 if self.battery_level < 10: self.battery_level = 100.0 def create_adv_data(self): """创建广播数据""" # AD Structure 1: Flags flags = bytes([0x02, 0x01, 0x06]) # AD Structure 2: 短设备名 short_name = self.device_name[:8] # 限制长度 name_struct = bytes([len(short_name) + 1, 0x08]) + short_name.encode('utf-8') # AD Structure 3: 厂商自定义数据(基础传感器数据) # 数据格式: 温度(2字节) + 湿度(1字节) + 电池电量(1字节) temp_int = int(self.temperature * 100) # 转换为整数,保留2位小数 sensor_data = struct.pack('<hBB', temp_int, int(self.humidity), self.battery_level) manufacturer_data = bytes([ 0x07, # 长度: 1(AD Type) + 2(公司ID) + 4(传感器数据) 0xFF, # AD Type: 厂商自定义 self.company_id & 0xFF, # 公司ID低字节 (self.company_id >> 8) & 0xFF, # 公司ID高字节 ]) + sensor_data # 合并所有数据 adv_data = flags + name_struct + manufacturer_data # 检查长度 if len(adv_data) > 31: print(f"警告: 广播数据过长 ({len(adv_data)}字节),进行裁剪") adv_data = adv_data[:31] return adv_data def create_scan_response(self): """创建扫描响应数据""" # 包含完整设备名和更多传感器信息 complete_name = f"{self.device_name}_{int(time.time()) % 10000:04d}" # AD Structure 1: 完整设备名 name_struct = bytes([len(complete_name) + 1, 0x09]) + complete_name.encode('utf-8') # AD Structure 2: TX Power tx_power = bytes([0x02, 0x0A, 0xC8]) # -56 dBm # AD Structure 3: 扩展传感器数据(厂商自定义) # 包含时间戳和设备状态 timestamp = int(time.time()) device_status = 0x01 # 正常状态 ext_data = struct.pack('<IB', timestamp, device_status) ext_manufacturer = bytes([ 0x08, # 长度: 1 + 2 + 5 0xFF, self.company_id & 0xFF, (self.company_id >> 8) & 0xFF, ]) + ext_data # 合并数据 scan_resp = name_struct + tx_power + ext_manufacturer if len(scan_resp) > 31: scan_resp = scan_resp[:31] return scan_resp def start(self, interval_ms=1000): """开始广播""" self.update_interval = interval_ms self.last_update = time.ticks_ms() print(f"启动环境监测信标: {self.device_name}") print(f"公司ID: 0x{self.company_id:04X}") # 初始广播 self.update_broadcast() # 设置定时器更新数据 self.timer = machine.Timer(0) self.timer.init(period=interval_ms, mode=machine.Timer.PERIODIC, callback=lambda t: self.update_broadcast()) def update_broadcast(self): """更新广播数据""" # 读取传感器 self.read_sensors() # 创建新的广播数据 adv_data = self.create_adv_data() scan_resp = self.create_scan_response() # 更新广播 self.ble.gap_advertise(self.update_interval, adv_data, scan_resp) # 打印状态 current_time = time.ticks_ms() if time.ticks_diff(current_time, self.last_update) > 5000: print(f"[{time.ticks_ms()//1000}] 温度: {self.temperature:.1f}°C, " f"湿度: {self.humidity:.1f}%, 电量: {self.battery_level:.0f}%") self.last_update = current_time def stop(self): """停止广播""" self.timer.deinit() self.ble.gap_advertise(None) self.ble.active(False) print("信标已停止") # 使用示例 if __name__ == "__main__": beacon = EnvironmentalBeacon("EnvMonitor") try: beacon.start(interval_ms=2000) # 2秒更新一次 # 保持运行 while True: time.sleep(1) except KeyboardInterrupt: beacon.stop() ``` 这个环境监测信标展示了BLE广播的多种应用: 1. **基础信息广播**:设备名、设备能力标志 2. **实时数据广播**:温度、湿度、电量(通过厂商自定义数据) 3. **扫描响应扩展**:完整设备名(含时间戳)、TX功率、扩展状态信息 4. **定时更新**:传感器数据定期更新并重新广播 在实际部署时,你可能需要根据具体需求调整数据格式和广播间隔。比如,如果设备由电池供电,可能需要延长广播间隔以节省电量。 ## 6. 调试技巧与常见问题解决 BLE广播调试可能会遇到各种奇怪的问题。根据我的经验,下面这些工具和技巧特别有用: ### 6.1 使用nRF Connect进行调试 nRF Connect是我最推荐的BLE调试工具,它有几个特别有用的功能: 1. **RAW视图**:直接显示广播数据的十六进制和解析结果 2. **日志记录**:可以记录扫描到的所有设备,方便分析 3. **广播模拟**:可以模拟设备广播,测试接收端 当你遇到解析问题时,先用nRF Connect看看广播数据到底是什么样的。很多时候问题一目了然——比如数据长度不对、AD Type错误等。 ### 6.2 常见问题与解决方案 下面这个表格总结了我遇到的一些常见问题及解决方法: | 问题现象 | 可能原因 | 解决方案 | |----------|----------|----------| | 设备在nRF Connect上能看到,但在自己App里扫描不到 | 广播间隔太长或广播类型不匹配 | 缩短广播间隔,检查广播类型是否可连接 | | 中文设备名显示乱码 | 编码问题或工具不支持UTF-8 | 确保使用UTF-8编码,使用nRF Connect验证 | | 广播数据被截断 | 数据超过31字节 | 使用扫描响应或数据分包 | | RSSI值不稳定 | 环境干扰或设备移动 | 多次测量取平均值,优化天线设计 | | 某些手机扫描不到设备 | 手机BLE实现差异 | 检查广播Flags,确保设置为普通发现模式 | ### 6.3 性能优化建议 1. **广播间隔选择**: - 快速发现:20-100ms - 平衡功耗:100-500ms - 低功耗:1000ms以上 2. **数据压缩**: ```python # 使用struct压缩数据 import struct # 将浮点数转换为定点数 temperature = 25.63 temp_fixed = int(temperature * 100) # 2563 packed = struct.pack('<h', temp_fixed) # 2字节 # 解包 temp_fixed = struct.unpack('<h', packed)[0] temperature = temp_fixed / 100.0 ``` 3. **智能广播**: - 数据变化时才更新广播 - 根据电池电量调整广播间隔 - 夜间降低广播频率 ## 7. 扩展应用:BLE广播在物联网中的实际用例 BLE广播不仅仅用于设备发现,在许多物联网场景中都有巧妙的应用。下面分享几个我在实际项目中实现的用例: ### 7.1 室内定位与导航 通过部署多个BLE信标,可以实现米级精度的室内定位。每个信标广播自己的位置信息,手机通过接收多个信标的信号强度(RSSI)进行三角定位。 ```python class PositioningBeacon: def __init__(self, beacon_id, x, y, z=0): self.beacon_id = beacon_id self.position = (x, y, z) def create_position_data(self): """创建包含位置信息的广播数据""" # 使用厂商自定义数据格式 # 格式: 信标ID(2字节) + X坐标(2字节) + Y坐标(2字节) + Z坐标(1字节) x_int = int(self.position[0] * 100) # 厘米精度 y_int = int(self.position[1] * 100) z_int = int(self.position[2]) position_data = struct.pack('<HHHb', self.beacon_id, x_int, y_int, z_int) return position_data ``` ### 7.2 资产跟踪与管理 在仓库或医院中,给重要设备贴上BLE标签,通过广播信号进行实时跟踪。可以广播设备ID、电池状态、最后活动时间等信息。 ### 7.3 无连接数据采集 对于只需要偶尔上报数据的传感器(如温度记录仪),可以使用广播直接发送数据,无需建立连接,大大简化了系统设计。 我在一个农业物联网项目中就使用了这种方案:温湿度传感器每小时广播一次数据,网关设备收集并上传到云端。传感器电池可以持续工作一年以上,而代码复杂度比连接方案低得多。 ### 7.4 设备配置与配对 许多智能设备使用广播进行初始配置。设备首次上电时进入配置模式,广播特定的配置信标,手机App扫描到后发送配置信息。 实现这种方案的关键是设计好数据格式和安全机制。我通常会在厂商自定义数据中包含设备状态、配置版本和随机数,防止重放攻击。 BLE广播是物联网开发中既基础又强大的技术。掌握它不仅能让你更好地理解BLE协议栈,还能为你的项目带来更多可能性。从简单的设备发现到复杂的数据传输,广播都能提供高效、低功耗的解决方案。 在实际使用中,我发现最有效的学习方式就是动手实验。尝试修改广播数据,观察nRF Connect中的变化;实现不同的解析逻辑,看看哪种方案最稳定。每个项目都有其独特的需求,没有一种方案适合所有场景,理解原理后灵活应用才是关键。 如果你在实现过程中遇到问题,或者有更好的实践经验,欢迎交流分享。BLE技术还在不断发展,新的应用场景不断涌现,保持学习和实践的态度,你就能在这个领域不断进步。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

Python内容推荐

ESP32的UART串口通信(基于micropython)所有代码.zip

ESP32的UART串口通信(基于micropython)所有代码.zip

ESP32的UART串口通信(基于micropython)中所展示的所有代码

MicroPython for ESP32 快速参考

MicroPython for ESP32 快速参考

MicroPython for ESP32 快速参考手册,包括常用的GPIO、PWM、ADC、DAC、NeoPixel等库函数用法。

ESP32 ESP32C3 Micropython 连接WIFI自动重连,连接阿里云物联网示例

ESP32 ESP32C3 Micropython 连接WIFI自动重连,连接阿里云物联网示例

WIFI可以自动短线重连。 阿里云物联网可以断线重连。 循环检查TOPIC消息。 WIFI和阿里云配置写在JSON文件,文件目录为setting文件夹。 包内包含ESP32和ESP32C3固件。

ESP32环境搭建资源(Thonny+MicroPython+ESP32)

ESP32环境搭建资源(Thonny+MicroPython+ESP32)

资源里面包含了Thonny、MicroPython固件和ESP32驱动安装包。

ESP32的Micropython固件以及Phyphox的Micropython库

ESP32的Micropython固件以及Phyphox的Micropython库

固件版本为20220618-v1.19.1 打包日期为2022.8.17

micropython_esp32-s3_n16r8

micropython_esp32-s3_n16r8

micropython_esp32-s3_n16r8

micropython_esp32 固件

micropython_esp32 固件

micropython_esp32 固件

ESP32 CAM micropython搭建.zip

ESP32 CAM micropython搭建.zip

针对esp32 cam搭建micropython的方法,我是小白,这个文章只针对小白,对于大佬级人物请直接略过。资源内包括:FTDI驱动,esptool-2.8 源码,还有安装esptool所依赖的两个包(pyaes-1.6.1,pyserial)python源码;micropython二进制文件(esp32-idf3-20191220-v1.12.bin);xshell个人版,博客文档

ESP32-S3 MicroPython lvgl固件

ESP32-S3 MicroPython lvgl固件

ESP32-S3 MicroPython lvgl固件

esp32-micropython:MicroPython代码适用于ESP32

esp32-micropython:MicroPython代码适用于ESP32

ESP32 com MicroPython-Primeiros Passos 配置 ESP32和MicroPython配合使用的固件和固件。 OBS :无需安装Python。 1. Baixar固件 o1ºpassobaixar或固件执行MicroPython,没有任何。 推荐最近的版本。 2.安装esptool 使用ESP32制作闪光的铁质的esptool。 # Instalar usando o gerenciador de pacotes pip $ pip3 install esptool * Em caso de erro,épossível安装了esptool com或comando apt 。 3. Apagar记忆膜闪光 建议使用MicroPython的固件更新固件。 Vamos fazer isso usando o programa esptool.py $

esp32 + micropython + oled显示天气

esp32 + micropython + oled显示天气

esp32 + micropython + oled显示天气

ESP32-ESP8266-MicroPython-Web-server:ESP32 ESP8266 MicroPython Web服务器

ESP32-ESP8266-MicroPython-Web-server:ESP32 ESP8266 MicroPython Web服务器

ESP32-ESP8266-MicroPython-Web服务器 ESP32 ESP8266 MicroPython Web服务器 您可以在此处详细阅读完整的内容: :

esp32-micropython-firmwares-3.zip

esp32-micropython-firmwares-3.zip

ESP32固件

基于ESP32和MicroPython的蓝牙控制循迹小车_ESP32-BLE-Intelligence-car.zip

基于ESP32和MicroPython的蓝牙控制循迹小车_ESP32-BLE-Intelligence-car.zip

基于ESP32和MicroPython的蓝牙控制循迹小车_ESP32-BLE-Intelligence-car

ESP32使用MicroPython开发工具

ESP32使用MicroPython开发工具

ESP32使用MicroPython开发工具

ESP32快速参考文档 (MicroPython).zip

ESP32快速参考文档 (MicroPython).zip

ESP32快速参考文档(MicroPython)

uReflowOven-Esp32-Micropython:基于带有MicroPython和LVGL的ESP32的μReflow烤箱控制器

uReflowOven-Esp32-Micropython:基于带有MicroPython和LVGL的ESP32的μReflow烤箱控制器

带MicroPython和LittlevGL的μReflow烤箱 。 警告:从以前的版本进行更新时,请确保获取最新的config.json并仔细验证它是否反映了您的系统配置。 首先,请确保将加热器配置为正确的极性。 更新! 现在,μReflow烤箱已启用PID控制! 对于不受PID控制的先前版本,请参见Adafruit-EZ-Make-Oven-alike分支。 该项目是的改进和经过大量修改的版本。 EZ Make Oven的原始代码可以在找到。 目的是通过使用更实惠和更广泛使用的硬件来修改厨房烤箱,从而制造出回流焊烤箱。 代替烤箱,也可以使用加热板。 该项目的GUI使用LittlevGL( )构建,这是一个非常强大且易于使用的GUI库。 LittlevGL已经带有用于ILI9341 TFT显示器和XPT2046触摸控制器的驱动程序,该项目利用了两者的优势来简化用户操作。 材

esp32 micropython镜像

esp32 micropython镜像

esp32 micropython镜像

MicroPython-esp32固件.zip

MicroPython-esp32固件.zip

固件

基于ESP32和MicroPython的蓝牙控制循迹小车

基于ESP32和MicroPython的蓝牙控制循迹小车

【作品名称】:基于ESP32和MicroPython的蓝牙控制循迹小车 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: 基于ESP32的蓝牙循迹无线充电小车 序言: 程序基础 操控实现 红外循迹实现 蓝牙 无线充电 程序 使用MicroPython编程,利用Python丰富的库,快速完成程序的开发编写。 小车操控实现 利用在L298N的IN1~IN2通道分别输入PWM信号,来完成对电机速度、转动方向的控制。 小车循迹实现 通过红外循迹模块输入单片机的数字信号,判断小车偏离黑线程度,由于时间和成本的限制,使用五路红外开关循迹模块,效果较查,可以使用八路灰度循迹,便于后续使用编写PID。 【资源声明】:本资源作为“参考资料”而不是“定制需求”,代码只能作为参考,不能完全复制照搬。需要有一定的基础看懂代码,自行调试代码并解决报错,能自行添加功能修改代码。

最新推荐最新推荐

recommend-type

VS2022配置OpenCV[源码]

本文详细介绍了在Visual Studio 2022中永久配置OpenCV开发环境的步骤。首先,需要下载适合自己版本的OpenCV安装包,并添加相应的环境变量。接着,通过在VS2022中添加并配置项目属性表,实现OpenCV的永久配置。具体步骤包括添加包含目录、库目录以及附加依赖项等。此外,文章还介绍了如何在新的项目中快速完成配置,以及如何配置Release模式下的属性表。最后,通过一个简单的测试程序验证配置是否成功。整个过程清晰明了,适合开发者快速上手。
recommend-type

opencv4.7.0用VS2022编译的debug和release库

opencv4.7.0用VS2022编译的debug和release库
recommend-type

OpenCV源码阅读教程[项目代码]

本文介绍了如何在Windows10+VS2022+OpenCV4.7.0环境下查看OpenCV源码的方法。作者首先解释了为什么需要查看源码,例如为了重写函数或提升代码水平。接着提供了两种方法:对于未下载OpenCV的用户,建议从GitHub仓库下载源码;对于已下载OpenCV的用户,则详细说明了如何在安装文件夹中找到源码文件。文章还强调了正确查看源码的方式,即在modules文件夹内分模块查找src文件夹中的源码文件。
recommend-type

编译GPU加速OpenCV[可运行源码]

本文详细介绍了如何在Windows 10/11系统下,使用Visual Studio 2022和CMake工具编译支持GPU加速(CUDA + cuDNN)的OpenCV库。教程涵盖了环境准备、cuDNN安装验证、CMake GUI配置、Visual Studio编译、结果验证及常见问题解决等关键步骤。通过本教程,读者可以成功编译出支持CUDA/cuDNN的OpenCV库,从而在计算机视觉任务中利用GPU加速,提升DNN推理等任务的性能。
recommend-type

OpenCV4.8+CUDA编译教程[源码]

本文详细介绍了在Windows系统下使用CMake编译OpenCV4.8.0与CUDA结合的完整流程,包括准备工作、编译步骤及在VS2022中的配置方法。内容涵盖从下载所需文件、解决编译过程中的常见错误,到最终在项目中配置使用编译好的OpenCV库。此外,还提供了多个CUDA加速的OpenCV功能测试代码示例,如灰度转换、高斯模糊、角点检测、双边模糊、ORB特征匹配等,帮助开发者快速验证编译结果并应用于实际项目。
recommend-type

学生成绩管理系统C++课程设计与实践

资源摘要信息:"学生成绩信息管理系统-C++(1).doc" 1. 系统需求分析与设计 在进行学生成绩信息管理系统开发前,首先需要进行系统需求分析,这是确定系统开发目标与范围的过程。需求分析应包括数据需求和功能需求两个方面。 - 数据需求分析: - 学生成绩信息:需要收集学生的姓名、学号、课程成绩等数据。 - 数据类型和长度:明确每个数据项的数据类型(如字符串、整型等)和长度,例如学号可能是字符串类型且长度为一定值。 - 描述:详细描述每个数据项的意义,以确保系统能够准确处理。 - 功能需求分析: - 列出功能列表:用户界面应提供清晰的操作指引,列出所有可用功能。 - 查询学生成绩:系统应能通过学号或姓名查询学生的成绩信息。 - 增加学生成绩信息:允许用户添加未保存的学生成绩信息。 - 删除学生成绩信息:能够通过学号或姓名删除已经保存的成绩信息。 - 修改学生成绩信息:通过学号或姓名修改已有的成绩记录。 - 退出程序:提供安全退出程序的选项,并确保所有修改都已保存。 2. 系统设计 系统设计阶段主要完成内存数据结构设计、数据文件设计、代码设计、输入输出设计、用户界面设计和处理过程设计。 - 内存数据结构设计: - 使用链表结构组织内存中的数据,便于动态增删查改操作。 - 数据文件设计: - 选择文本文件存储数据,便于查看和编辑。 - 代码设计: - 根据功能需求,编写相应的函数和模块。 - 输入输出设计: - 设计简洁明了的输入输出提示信息和操作流程。 - 用户界面设计: - 用户界面应为字符界面,方便在命令行环境下使用。 - 处理过程设计: - 设计数据处理流程,确保每个操作都有明确的处理逻辑。 3. 系统实现与测试 实现阶段需要根据设计阶段的成果编写程序代码,并进行系统测试。 - 程序编写: - 完成系统设计中所有功能的程序代码编写。 - 系统测试: - 设计测试用例,通过测试用例上机测试系统。 - 记录测试方法和测试结果,确保系统稳定可靠。 4. 设计报告撰写 最后,根据系统开发的各个阶段,撰写详细的设计报告。 - 系统描述:包括问题说明、数据需求和功能需求。 - 系统设计:详细记录内存数据结构设计、数据文件设计、代码设计、输入/输出设计、用户界面设计、处理过程设计。 - 系统测试:包括测试用例描述、测试方法和测试结果。 - 设计特点、不足、收获和体会:反思整个开发过程,总结经验和教训。 时间安排: - 第19周(7月12日至7月16日)完成项目。 - 7月9日8:00到计算机学院实验中心(三楼)提交程序和课程设计报告。 指导教师和系主任(或责任教师)需要在文档上签名确认。 系统需求分析: - 使用表格记录系统需求分析的结果,包括数据项、数据类型、数据长度和描述。 - 分析数据项如学生成绩信息、状态器、链表节点等,确定其属性和行为。 以上就是文档中提到的学生成绩信息管理系统开发的关键知识点。开发此类系统需要熟练掌握C++编程基础,了解面向对象的程序设计思想,以及熟悉文件操作和链表等数据结构的应用。此外,良好的软件开发流程意识、测试意识和文档撰写能力也是必不可少的。
recommend-type

别再手动拖拽了!用Lumerical脚本批量创建FDTD仿真结构(附完整代码)

# 告别低效建模:Lumerical脚本自动化实战指南 在光子学仿真领域,时间就是科研生命线。当同行还在GUI界面里反复点击菜单时,你已经用脚本批量生成了20组参数化结构——这不是未来场景,而是每位FDTD用户都应该掌握的基础生产力革命。本文将彻底改变你与Lumerical的交互方式,从手动拖拽的农耕时代,跃迁到自动化建模的工业文明。 ## 1. 为何脚本建模是必然选择 2019年Nature Photonics的一项研究显示,科研工作者在仿真工具上平均浪费37%的时间在重复性操作上。对于需要参数扫描的纳米光学结构设计,这个数字可能更高。手动创建10个不同尺寸的纳米柱阵列意味着: -
recommend-type

Java邮件解析任务中,如何安全高效地提取HTML邮件内容并避免硬编码、资源泄漏和类型转换异常?

<think>我们被要求优化一段Java代码,该代码用于处理邮件(特别是来自特定发件人的构建通知邮件)。代码的主要问题包括: 1. 重复获取邮件内容:在检查MIME类型后,多次调用`msg[i].getContent()`,这可能导致性能问题或流关闭异常。 2. 类型转换问题:直接将邮件内容转换为`Multipart`而不进行类型检查,可能引发`ClassCastException`。 3. 代码结构问题:逻辑嵌套过深,可读性差,且存在重复代码(如插入邮件详情的操作在两个地方都有)。 4. 硬编码和魔法值:例如在解析HTML表格时使用了硬编码的索引(如list3.get(10)),这容易因邮件
recommend-type

RH公司应收账款管理优化策略研究

资源摘要信息:"本文针对RH公司的应收账款管理问题进行了深入研究,并提出了改进策略。文章首先分析了应收账款在企业管理中的重要性,指出其对于提高企业竞争力、扩大销售和充分利用生产能力的作用。然后,以RH公司为例,探讨了公司应收账款管理的现状,并识别出合同管理、客户信用调查等方面的不足。在此基础上,文章提出了一系列改善措施,包括完善信用政策、改进业务流程、加强信用调查和提高账款回收力度。特别强调了建立专门的应收账款回收部门和流程的重要性,并建议在实际应用过程中进行持续优化。同时,文章也意识到企业面临复杂多变的内外部环境,因此提出的策略需要根据具体情况调整和优化。 针对财务管理领域的专业学生和从业者,本文提供了一个关于应收账款管理问题的案例研究,具有实际指导意义。文章还探讨了信用管理和征信体系在应收账款管理中的作用,强调了它们对于提升企业信用风险控制和市场竞争能力的重要性。通过对比国内外企业在应收账款管理上的差异,文章总结了适合中国企业实际环境的应收账款管理方法和策略。" 根据提供的文件内容,以下是详细的知识点: 1. 应收账款管理的重要性:应收账款作为企业的一项重要资产,其有效管理关系到企业的现金流、财务健康以及市场竞争力。不良的应收账款管理会导致资金链断裂、坏账损失增加等问题,严重影响企业的正常运营和长远发展。 2. 应收账款的信用风险:在信用交易日益频繁的商业环境中,企业必须对客户信用进行评估,以便采取合理的信用政策,降低信用风险。 3. 合同管理的薄弱环节:合同是应收账款管理的法律基础,严格的合同管理能够保障企业权益,减少因合同问题导致的应收账款风险。 4. 客户信用调查:了解客户的信用状况对于预测和控制应收账款风险至关重要。企业需要建立有效的客户信用调查机制,识别和筛选信用良好的客户。 5. 应收账款回收策略:企业应建立有效的账款回收机制,包括定期的账款跟进、逾期账款的催收等。同时,建立专门的应收账款回收部门可以提升回收效率。 6. 应收账款管理流程优化:通过改进企业内部管理流程,如简化审批流程、提高工作效率等措施,能够提升应收账款的管理效率。 7. 应收账款管理策略的调整和优化:由于企业的内外部环境复杂多变,因此制定的管理策略需要根据实际情况进行动态调整和持续优化。 8. 信用管理和征信体系的作用:建立和完善企业内部信用管理体系和征信体系,有助于企业更好地控制信用风险,并在市场竞争中占据有利地位。 9. 对比国内外应收账款管理实践:通过研究国内外企业在应收账款管理上的不同做法和经验,可以借鉴先进的管理理念和方法,提升国内企业的应收账款管理水平。 综上所述,本文深入探讨了应收账款管理的多个方面,为RH公司乃至其他同类型企业提供了应收账款管理的改进方向和策略,对于财务管理专业的教育和实践都具有重要的参考价值。
recommend-type

新手别慌!用BingPi-M2开发板带你5分钟搞懂Tina Linux SDK目录结构

# 新手别慌!用BingPi-M2开发板带你5分钟搞懂Tina Linux SDK目录结构 第一次拿到BingPi-M2开发板时,面对Tina Linux SDK里密密麻麻的文件夹,我完全不知道从哪下手。就像走进一个陌生的大仓库,每个货架上都堆满了工具和零件,却找不到操作手册。这种困惑持续了整整两天,直到我意识到——理解目录结构比死记硬背每个文件更重要。 ## 1. 为什么SDK目录结构如此重要 想象你正在组装一台复杂的模型飞机。如果所有零件都混在一个箱子里,你需要花大量时间寻找每个螺丝和面板。但如果有分门别类的隔层,标注着"机身部件"、"电子设备"、"紧固件",组装效率会成倍提升。Ti