接到一个客户需求,利用ESP32+摄像头完成家庭燃气表数据的采集接入,大概流程:
一、设备配网:
通过微信小程序BLE连接到ESP32配网,配网完成后,把设备id绑定到相应的账号下。
让ChatGPT设计协议及代码如下:
设计一个通过微信小程序和BLE(蓝牙低功耗)给ESP32设备配网的协议需要考虑数据安全性、网络配置的有效性以及用户体验的流畅性。以下是一个建议的协议设计步骤:
1. 总体流程概述
- 用户通过微信小程序扫描设备的二维码:获取设备的蓝牙MAC地址或设备唯一标识。
- 微信小程序通过BLE与ESP32设备建立连接:启动BLE连接,进行初步的设备认证和握手。
- 传输Wi-Fi配置信息:将Wi-Fi的SSID和密码加密后通过BLE发送到ESP32。
- ESP32连接Wi-Fi并返回结果:ESP32尝试连接Wi-Fi,并通过BLE将连接结果返回给微信小程序。
- 小程序显示连接结果:如果连接成功,小程序显示成功页面;如果失败,显示错误信息。
2. 协议设计详细步骤
Step 1: 设备扫描和BLE连接
- 设备二维码扫描:
- 用户通过微信小程序扫描设备上的二维码,获取设备的蓝牙MAC地址或者设备唯一标识符。
- BLE连接:
- 小程序通过获取到的蓝牙MAC地址与ESP32设备建立BLE连接。
- 使用标准的BLE连接过程,建立GATT(通用属性协议)会话,选择适当的服务和特征值来进行数据传输。
Step 2: 设备认证与握手
- 设备身份认证:
- ESP32生成一个随机数(nonce),通过BLE发送给小程序。
- 小程序接收到nonce后,使用设备提供的公钥对其进行签名并将签名返回ESP32。
- ESP32验证签名是否正确,以此确认微信小程序的合法性。
- 握手完成:
- 认证成功后,双方交换加密密钥(如AES对称密钥)以保护后续的数据传输。
Step 3: Wi-Fi配置信息传输
-
数据加密:
- 小程序加密Wi-Fi的SSID和密码,确保传输过程中的数据安全。
- 可以使用AES对称加密算法来加密SSID和密码。
-
传输Wi-Fi配置信息:
- 小程序将加密后的Wi-Fi SSID和密码通过BLE发送给ESP32设备。
- 建议分块传输数据,以确保每个BLE包大小不超过BLE的最大传输单元(MTU)。
-
配置确认:
- ESP32接收到数据后解密并验证配置。
- ESP32确认数据完整性后,通过BLE发送一个确认信号给小程序。
Step 4: Wi-Fi连接尝试与结果返回
- ESP32连接Wi-Fi:
- ESP32使用接收到的SSID和密码尝试连接指定的Wi-Fi网络。
- 连接结果返回:
- 连接成功时,ESP32发送成功状态和IP地址回微信小程序。
- 连接失败时,ESP32发送错误代码或错误信息回微信小程序,指出具体的失败原因(如密码错误、网络不可达等)。
Step 5: 小程序显示结果
- 成功界面:
- 如果ESP32成功连接Wi-Fi,小程序显示成功界面,并提示用户设备已成功连接至Wi-Fi网络。
- 失败处理:
- 如果连接失败,小程序根据ESP32返回的错误信息提示用户重新尝试,或者手动输入Wi-Fi信息进行再次配置。
3. 通信数据包结构
握手阶段数据包
- 请求握手(微信小程序 -> ESP32):
Opcode
: 0x01 (握手请求)Nonce
: 16字节的随机数
- 握手响应(ESP32 -> 微信小程序):
Opcode
: 0x02 (握手响应)Nonce
: 16字节的随机数Signature
: 使用设备私钥签名的Nonce
- 密钥交换(微信小程序 -> ESP32):
Opcode
: 0x03 (密钥交换)Encrypted_Key
: AES加密的对称密钥
Wi-Fi配置阶段数据包
- 传输Wi-Fi信息(微信小程序 -> ESP32):
Opcode
: 0x10 (Wi-Fi信息传输)SSID_Length
: SSID的长度SSID
: 加密后的SSIDPassword_Length
: 密码的长度Password
: 加密后的密码
- 确认Wi-Fi配置(ESP32 -> 微信小程序):
Opcode
: 0x11 (配置确认)Status
: 0x00(成功),0x01(失败)
- 连接结果返回(ESP32 -> 微信小程序):
Opcode
: 0x12 (连接结果)Status
: 0x00(成功),0x01(失败)IP_Address
: 成功连接时返回的IP地址(可选)
4. 安全性考虑
- 数据加密:确保所有的Wi-Fi配置数据都经过AES加密,以防止中间人攻击。
- 认证机制:使用设备证书或公钥进行认证,防止非法设备或程序的接入。
- 重放攻击防护:使用随机数(Nonce)来防止重放攻击,确保每次会话都是唯一的。
5. 用户体验优化
- 自动重连:如果连接失败,微信小程序可以提示用户重新尝试或自动重试。
- 详细提示:对于常见的错误情况(如密码错误、网络不可达),提供详细的错误提示和解决建议。
- 配网状态监控:在小程序中显示配网进度,让用户清楚知道配网过程的每一步。
6. 测试与验证
- 多设备测试:确保该协议在不同的手机、不同的ESP32设备上均能正常工作。
- 极端情况测试:测试低信号强度、网络拥塞等情况下的配网成功率。
- 兼容性测试:确保协议在微信小程序的不同版本和操作系统版本(iOS、Android)上都能正常使用。
通过这个协议设计,微信小程序可以通过BLE给ESP32设备配网,实现设备联网的便捷体验。这种方法不仅安全可靠,还能提供良好的用户体验。
下面是基于所设计的协议,实现 ESP32 配网功能的代码。我们将分别提供基于 Arduino 版本和 ESP-IDF 版本的实现。
1. Arduino 版本
Arduino 版本的代码使用 BLE
库进行蓝牙通信,使用 WiFi
库进行网络连接。
a. Arduino BLE 代码实现
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <WiFi.h>
// BLE UUIDs
#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
#define CHARACTERISTIC_UUID_RX "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
#define CHARACTERISTIC_UUID_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
// WiFi SSID and Password
char ssid[32] = {0};
char password[32] = {0};
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
class MyBLEServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
class MyBLECallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("Received Wi-Fi Credentials");
// Assume SSID and Password are separated by a comma
int commaIndex = rxValue.find(",");
if (commaIndex != std::string::npos) {
strncpy(ssid, rxValue.substr(0, commaIndex).c_str(), sizeof(ssid));
strncpy(password, rxValue.substr(commaIndex + 1).c_str(), sizeof(password));
Serial.print("SSID: ");
Serial.println(ssid);
Serial.print("Password: ");
Serial.println(password);
// Try to connect to Wi-Fi
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Connection Failed!");
pTxCharacteristic->setValue("FAIL");
} else {
Serial.println("WiFi Connected");
pTxCharacteristic->setValue(WiFi.localIP().toString().c_str());
}
pTxCharacteristic->notify();
}
}
}
};
void setup() {
Serial.begin(115200);
// Initialize BLE
BLEDevice::init("ESP32_BLE_WIFI_SETUP");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyBLEServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
pRxCharacteristic->setCallbacks(new MyBLECallbacks());
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Waiting for a client connection to notify...");
}
void loop() {
// do nothing
}
2. ESP-IDF 版本
ESP-IDF 版本的代码使用官方的 ESP32 BLE
和 Wi-Fi
库进行实现。
a. ESP-IDF BLE 代码实现
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#define TAG "BLE_WIFI_SETUP"
// BLE UUIDs
#define GATTS_SERVICE_UUID 0x00FF
#define GATTS_CHAR_UUID_RX 0xFF01
#define GATTS_CHAR_UUID_TX 0xFF02
#define DEVICE_NAME "ESP32_BLE_SETUP"
#define CHAR_VAL_LEN_MAX 0x40
static uint8_t adv_config_done = 0;
#define adv_config_flag (1 << 0)
#define scan_rsp_config_flag (1 << 1)
static uint8_t char1_str[] = {0x11, 0x22, 0x33};
static esp_gatt_char_prop_t a_property = 0;
static esp_attr_value_t gatts_demo_char1_val = {
.attr_max_len = CHAR_VAL_LEN_MAX,
.attr_len = sizeof(char1_str),
.attr_value = char1_str,
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_handle;
esp_gatt_srvc_id_t service_id;
uint16_t char_handle;
esp_bt_uuid_t char_uuid;
esp_gatt_perm_t perm;
esp_gatt_char_prop_t property;
uint16_t descr_handle;
esp_bt_uuid_t descr_uuid;
} gatts_profile = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
};
// WiFi SSID and Password
char ssid[32] = {0};
char password[32] = {0};
// WiFi connection handler
void wifi_init_sta(char *ssid, char *password) {
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = "",
.password = "",
},
};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_connect();
}
// BLE events handler
static void gatts_profile_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_gap_set_device_name(DEVICE_NAME);
esp_ble_gap_config_adv_data(&adv_data);
break;
case ESP_GATTS_WRITE_EVT:
if (!param->write.is_prep) {
// Received data
esp_log_buffer_hex(TAG, param->write.value, param->write.len);
// Process received data
// Assume SSID and Password are separated by a comma
char *received_data = (char *)param->write.value;
char *comma_pos = strchr(received_data, ',');
if (comma_pos) {
strncpy(ssid, received_data, comma_pos - received_data);
strncpy(password, comma_pos + 1, sizeof(password));
// Connect to WiFi
wifi_init_sta(ssid, password);
// Notify result back
// You would need to implement logic to handle the connection status and notify the client.
}
}
break;
// Other BLE event cases...
default:
break;
}
}
void app_main(void) {
esp_err_t ret;
// Initialize NVS
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);
// Initialize Bluetooth
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();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(TAG, "%s init bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
// Register BLE GATT server
esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);
esp_ble_gatts_app_register(PROFILE_A_APP_ID);
// Initialize WiFi
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
}
3. 总结
- Arduino 版本:更易于理解和开发,适合快速原型和小型项目。
- ESP-IDF 版本:提供更高的灵活性和控制,更适合复杂的项目和商业产品。
这些代码示例提供了基本的 BLE 和 Wi-Fi 配网逻辑,您可以根据具体需求进一步扩展和优化功能
二、拍照上传,后台调用baidu的图片接口
以下是如何在ESP32上通过HTTP POST上传拍照所得图片文件的完整代码实现。这个示例将继续基于之前的Arduino版本和ESP-IDF版本。
1. Arduino 版本
Arduino版本中,我们将使用ESP32-Camera
库进行拍照,然后使用HTTPClient
库将图片通过HTTP POST上传。
a. Arduino 代码实现
#include <WiFi.h>
#include <HTTPClient.h>
#include "esp_camera.h"
// Replace with your network credentials
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
// Replace with your server's URL
const char* serverName = "http://yourserver.com/upload";
// Camera Pin configuration
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Camera configuration
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
// Init with high specs to pre-allocate larger buffers
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
}
void loop() {
// Capture a photo
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// Create HTTP client and set the target URL
HTTPClient http;
http.begin(serverName);
// Set headers for multipart/form-data POST request
String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
http.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
// Build the multipart form data body
String body = "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"file\"; filename=\"photo.jpg\"\r\n";
body += "Content-Type: image/jpeg\r\n\r\n";
// Send the HTTP POST request
http.addHeader("Content-Length", String(fb->len + body.length() + boundary.length() + 6));
int httpResponseCode = http.POST((uint8_t*)body.c_str(), body.length());
httpResponseCode += http.POST(fb->buf, fb->len);
// Send the closing boundary
String endBoundary = "\r\n--" + boundary + "--\r\n";
httpResponseCode += http.POST((uint8_t*)endBoundary.c_str(), endBoundary.length());
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println(httpResponseCode);
Serial.println(response);
}
else {
Serial.print("Error on sending POST: ");
Serial.println(httpResponseCode);
}
// End the HTTP request
http.end();
// Return the frame buffer back to the driver for reuse
esp_camera_fb_return(fb);
// Wait a few seconds before taking another photo
delay(10000);
}
2. ESP-IDF 版本
ESP-IDF版本会稍微复杂一些,需要手动构建HTTP请求并处理相应的Wi-Fi连接。
a. ESP-IDF 代码实现
#include <stdio.h>
#include <string.h>
#include "esp_camera.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "esp_system.h"
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_netif.h"
// Camera Pin configuration (same as Arduino example)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define TAG "HTTP_POST_IMAGE"
static const char *post_url = "http://yourserver.com/upload";
void take_and_send_photo() {
// Initialize camera
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera Init Failed");
return;
}
// Take picture
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
ESP_LOGE(TAG, "Camera Capture Failed");
return;
}
// Create HTTP client config
esp_http_client_config_t config_client = {
.url = post_url,
.method = HTTP_METHOD_POST,
};
esp_http_client_handle_t client = esp_http_client_init(&config_client);
// Prepare headers for multipart form data
esp_http_client_set_header(client, "Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
// Build body content
char *body_start = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"photo.jpg\"\r\n"
"Content-Type: image/jpeg\r\n\r\n";
esp_http_client_set_post_field(client, body_start, strlen(body_start));
esp_http_client_perform(client);
// Send image data
esp_http_client_write(client, (const char *)fb->buf, fb->len);
// End of body content
三、经过测试,稍微修改就可以工作,AI真是太强大了,
注意:
当然,也可以本地识别,需要设定的参数比较多,具体见:
这个项目允许你使用廉价且易于获取的硬件,将你的模拟水表、煤气表、电表以及其他表进行数字化。
标签:include,图像识别,esp,NUM,智联,GPIO,识别,config,define From: https://blog.csdn.net/weixin_37694490/article/details/141566562