协议设计
为了通过蓝牙(BLE)从 ESP32-S3 向微信小程序传输图片,设计一个简单的协议来确保数据的完整性和顺利传输。下面是协议设计的详细内容:
1. 协议设计
1.1 数据传输结构
由于图片通常较大,BLE 的 MTU 大小限制(通常为 23 字节)无法一次传输整个图片,因此我们将图片数据分块,并定义一个简单的协议来确保每个数据块的顺利传输。
每个数据块的结构如下:
- 数据包头部(Header):包含数据包的基本信息,例如数据块的索引、总数据块数和数据类型等。
- 数据包体(Payload):图片数据的实际内容。
数据包结构(每个数据包的大小应控制在 MTU 限制内):
字段名 | 长度 | 说明 |
---|---|---|
头部标识符 | 1 字节 | 固定值,例如 0x01 用于表示图片数据传输 |
图片总块数 | 2 字节 | 图片数据总共的块数(从 1 开始,最大值为 0xFFFF ) |
当前块索引 | 2 字节 | 当前传输块的索引(从 1 开始,最大值为 0xFFFF ) |
数据长度 | 2 字节 | 当前数据块的实际数据长度(最大值为 MTU - 5 ) |
数据内容 | N 字节 | 当前块的图片数据 |
校验和 | 1 字节 | 可选,用于确保数据块传输过程中没有损坏(例如简单的 XOR 校验) |
1.2 数据传输流程
-
连接与初始化:
- 小程序连接到 ESP32,并获取设备的服务和特征。
- ESP32 启动服务,等待数据传输请求。
-
数据发送:
- ESP32 将图片数据分成多个块,每块不超过 MTU 大小。
- 每个数据块按照协议格式发送到小程序端。
- 每个数据块在发送时都会包含图片的总块数、当前块索引及数据内容。
-
数据接收:
- 小程序接收到每个数据块后,根据协议中的块索引重组数据。
- 小程序将所有接收到的块数据合并为完整的图片数据。
-
数据重组与显示:
- 小程序完成所有数据块接收后,将完整的图片数据保存并显示。
2. ESP32-S3 代码实现(使用 ESP-IDF)
2.1 配置蓝牙服务
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_device.h"
#include "esp_gatt_common_api.h"
#include "nvs_flash.h"
#define GATTS_TAG "BLE_IMAGE_TRANSFER"
// 蓝牙服务 UUID 和 特征 UUID
#define GATTS_SERVICE_UUID 0x00FF
#define GATTS_CHAR_UUID 0xFF01
#define GATTS_NUM_HANDLE 4
// 图片数据模拟
static const uint8_t image_data[] = { /* 图片数据字节流 */ };
static const size_t image_size = sizeof(image_data);
// 数据包分块
#define IMAGE_CHUNK_SIZE 512
#define MTU_SIZE 517
// 发送数据块的索引
static uint16_t chunk_index = 0;
static esp_gatt_char_prop_t char_property = 0;
static uint16_t gatt_service_handle;
static uint16_t char_handle;
static void send_image_chunk(uint16_t conn_id, uint8_t *data, uint16_t length) {
esp_err_t err = esp_ble_gatts_send_indicate(conn_id, gatt_service_handle, char_handle, length, data, false);
if (err == ESP_OK) {
ESP_LOGI(GATTS_TAG, "Sent chunk %d, size: %d bytes", chunk_index, length);
} else {
ESP_LOGE(GATTS_TAG, "Error sending data chunk %d", chunk_index);
}
}
static void send_next_chunk(uint16_t conn_id) {
if (chunk_index * IMAGE_CHUNK_SIZE >= image_size) {
ESP_LOGI(GATTS_TAG, "Image transfer complete");
return;
}
uint16_t chunk_start = chunk_index * IMAGE_CHUNK_SIZE;
uint16_t chunk_end = chunk_start + IMAGE_CHUNK_SIZE;
if (chunk_end > image_size) {
chunk_end = image_size;
}
uint16_t chunk_length = chunk_end - chunk_start;
uint8_t chunk_data[MTU_SIZE];
// 构建数据包头部
chunk_data[0] = 0x01; // 数据包标识符
chunk_data[1] = (chunk_index >> 8) & 0xFF; // 当前块索引高字节
chunk_data[2] = chunk_index & 0xFF; // 当前块索引低字节
chunk_data[3] = (image_size / IMAGE_CHUNK_SIZE) >> 8; // 总块数高字节
chunk_data[4] = image_size / IMAGE_CHUNK_SIZE; // 总块数低字节
memcpy(&chunk_data[5], &image_data[chunk_start], chunk_length);
send_image_chunk(conn_id, chunk_data, 5 + chunk_length);
chunk_index++;
}
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_REG_EVT:
esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, GATTS_NUM_HANDLE, 0);
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Client connected");
send_next_chunk(param->connect.conn_id); // 开始发送第一块数据
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Client disconnected");
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_WRITE_EVT:
break;
default:
break;
}
}


// 初始化蓝牙
void ble_init(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
ESP_ERROR_CHECK(esp_bluedroid_init());
ESP_ERROR_CHECK(esp_bluedroid_enable());
esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);
esp_ble_gatts_app_register(0);
esp_ble_gap_set_device_name("ESP32_IMAGE_TRANSFER");
esp_ble_gap_config_adv_data(&adv_data);
}
void app_main(void) {
ble_init();
}
3. 微信小程序代码实现
3.1 初始化蓝牙并建立连接
Page({
data: {
deviceId: "",
serviceId: "",
characteristicId: "",
receivedData: [],
},
startBluetooth() {
wx.openBluetoothAdapter({
success: () => {
wx.startBluetoothDevicesDiscovery({
success: console.log,
});
},
});
},
connectDevice(e) {
const deviceId = e.currentTarget.dataset.deviceId;
wx.createBLEConnection({
deviceId,
success: () => {
wx.getBLEDeviceServices({
deviceId,
success: (res) => {
const serviceId = res.services[0].uuid;
this.setData({ deviceId, serviceId });
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
const characteristicId = res.characteristics[0].uuid;
this.setData({ characteristicId });
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId,
state: true,
success: () => {
console.log("Listening for data...");
},
});
wx.onBLECharacteristicValueChange((res) => {
const data = this.ab2str(res.value);
console.log("Received data:", data);
this.setData({
receivedData: [...this.data.receivedData, data],
});
});
},
});
},
});
},
});
},
ab2str(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
},
});
总结
通过以上设计和代码实现,您可以实现通过 ESP32-S3 通过蓝牙向微信小程序传输图片。该方案使用简单的分块传输协议确保数据传输的稳定性,微信小程序端则负责接收和重组图片数据。