|
9| 0
|
[ESP8266/ESP32] DFRobot FireBeetle 2 ESP32-P4 电子工牌服务端实现 |
|
[url=][/url]1. 项目概述 本项目是一套基于 DFRobot FireBeetle 2 ESP32-P4 服务端 + M5Paper/ESP32-C3 客户端的智能电子工牌系统。DFRobot FireBeetle 2 ESP32-P4 是整个系统的核心枢纽,承担 BLE 扫描发现、打卡判定、Web 服务管理、数据持久化、配置下发等全部服务端职责。 本文档重点介绍 DFRobot FireBeetle 2 ESP32-P4 服务端的实现细节,包括 BLE 扫描与打卡逻辑、SQLite 数据库设计、Web API 接口、以及作为 BLE 中心设备与客户端的交互。M5Paper 和 ESP32-C3 客户端仅作为交互对象简要说明。 项目的源码,可以直接访问:https://github.com/HonestQiao/e-badge [url=][/url]2. 硬件平台 服务端采用 DFRobot FireBeetle 2 ESP32-P4 开发板,核心硬件参数如下: [td]
ESP32-P4 本身无内置 WiFi/BLE,FireBeetle 2 开发板通过板载 ESP32-C6 协处理器提供无线连接能力。在 Arduino 环境中,直接使用标准 WiFi.h、BLEDevice.h 等库即可,底层由 ESP-IDF 自动通过 SPI/内部总线与 C6 通信,开发者无需关心协处理器细节。 [url=][/url]2.2 分区方案ESP32-P4 固件体积较大(约 1.5MB,含 SQLite + WebServer + BLE + WiFi),必须使用 8MB 分区方案:
[url=][/url]3. 系统架构 ![]() ESP32-P4 在系统中承担四个核心角色:
下图展示了 ESP32-P4 服务端从启动到主循环的完整运行逻辑,包括 BLE 扫描、打卡判定、新设备处理、异步任务调度和 Web 请求处理等核心流程: ![]() 流程说明:
[url=][/url]4. ESP32-P4 核心功能模块[url=][/url]4.1 BLE 扫描与设备检测 ESP32-P4 以 BLE 中心设备(Central)身份持续扫描周围广播,只关注名称前缀为 M5B_(M5Paper)或 C3B_(ESP32-C3)的设备。 [url=][/url]4.1.1 扫描参数配置#define BLE_SCAN_INTERVAL 100 // 扫描间隔 (ms)#define BLE_SCAN_WINDOW 99 // 扫描窗口 (ms)扫描间隔和窗口几乎相等,意味着 ESP32-P4 几乎持续在扫描状态,确保不遗漏设备广播。 [url=][/url]4.1.2 扫描回调与过滤class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { String name = advertisedDevice.getName().c_str(); int rssi = advertisedDevice.getRSSI(); // 只处理 M5B_ 或 C3B_ 开头的设备 if (!name.startsWith(DEVICE_NAME_PREFIX) && !name.startsWith(DEVICE_NAME_PREFIX_C3)) return; String deviceId = gScanner->getDeviceIdFromName(name); BLEAddress address = advertisedDevice.getAddress(); unsigned long now = getUnixTime(); // 更新内存检测记录(按设备去重) gScanner->updateDetection(deviceId, name, address, rssi, now); // 打卡/检测逻辑 gScanner->handleDetection(deviceId, name, rssi, address, now); }};关键设计:扫描回调中只做两件事——更新内存检测记录、触发打卡逻辑。所有耗时操作(如 BLE 连接、数据库写入)都异步处理,避免阻塞扫描循环。 [url=][/url]4.1.3 内存检测记录ESP32-P4 维护一个内存数组记录检测到的设备(最多 100 个,按设备去重): struct DetectionRecord { String deviceId; // 设备 ID(8 位十六进制) String deviceName; // 广播名称(如 M5B_D71F8A3C) BLEAddress address; // BLE 物理地址 int rssi; // 信号强度 unsigned long timestamp;// 最近一次检测的 UNIX 时间戳 bool clearConfigSent; // 是否已发送过清空配置通知};该数组用于:
[url=][/url]4.2 打卡判定逻辑 打卡判定是 ESP32-P4 的核心业务逻辑,在 handleDetection() 中实现。 [url=][/url]4.2.1 判定流程检测到设备广播 │ ▼提取 deviceId + RSSI │ ▼查询数据库:该设备是否有工牌配置? │ ├── 无配置 ──→ 发送 CLEAR_CONFIG(新设备处理) │ └── 标记已发送,避免重复 │ └── 有配置 ──→ RSSI ≥ -70dBm? │ ├── 否 ──→ 仅记录检测,不打卡 │ └── 是 ──→ 今日是否已打卡? │ ├── 已打卡 ──→ 更新 last_detected 时间 │ (10 分钟间隔,减少写操作) │ └── 未打卡 ──→ 写入打卡记录到 SQLite └── 发送 CHECKIN 通知到设备[url=][/url]4.2.2 核心代码void BLEScanner::handleDetection(const String& deviceId, const String& name, int rssi, BLEAddress address, unsigned long now) { // 获取今日日期 String today = getTodayDate(); // 从数据库查工牌配置 BadgeConfigDBRecord config; bool hasConfig = pDb->getBadgeConfig(deviceId, config); // ===== 新设备检测:无配置记录的设备,发送清空配置通知 ===== if (!hasConfig && rssi >= -80) { // 检查是否已发送过(避免重复通知) bool alreadySent = false; for (int i = 0; i < detectionCount; i++) { if (detections[i.deviceId == deviceId && detections[i.clearConfigSent) { alreadySent = true; break; } } if (!alreadySent) { // 标记为已发送,发送 CLEAR_CONFIG notifyClearConfig(address); } } // 未配置设备不打卡 if (!hasConfig) return; // ===== 打卡逻辑 ===== if (rssi >= BLE_RSSI_THRESHOLD) { // -70dBm if (!pDb->hasCheckInToday(deviceId, today)) { // 今日首次打卡 String checkinTime = formatTime(now); bool ok = pDb->addCheckIn(deviceId, config.name, config.dept, config.title, config.badgeId, today, checkinTime, now, rssi); if (ok) { // 通知客户端 notifyCheckIn(address, checkinTime); } } else { // 已打卡过,仅更新最近检测时间(10 分钟间隔) unsigned long lastDetected = pDb->getLastDetected(deviceId, today); if (now - lastDetected > 600) { // 10 分钟 = 600 秒 pDb->updateLastDetected(deviceId, today, now); } } }} [url=][/url]4.2.3 设计要点[td]
[url=][/url]4.3 新设备处理(CLEAR_CONFIG) 当检测到无配置记录的设备时,ESP32-P4 需要通知该设备清空本地配置并进入等待配置状态。 [url=][/url]4.3.1 处理流程
通过 clearConfigSent 标志确保只发送一次: if (!alreadySent) { detections[i.clearConfigSent = true; // 标记已发送 notifyClearConfig(address); // 设置异步通知标志}这样即使设备持续在扫描范围内广播,也只会收到一次 CLEAR_CONFIG。 [url=][/url]4.4 BLE 客户端连接与配置下发 ESP32-P4 作为 BLE 中心设备,需要主动连接客户端(M5Paper/C3)来发送通知或配置。这部分由 BLEClientManager 类实现。 [url=][/url]4.4.1 连接流程bool BLEClientManager::doConnect(BLEAddress address) { // BLE 扫描和连接不能同时进行,先停止扫描 BLEScan* pScan = BLEDevice::getScan(); if (pScan && pScan->isScanning()) { pScan->stop(); delay(300); } // 重试 3 次 for (int retry = 0; retry < 3; retry++) { // 先尝试 PUBLIC 地址类型 if (pClient->connect(address, BLE_ADDR_PUBLIC)) { // 等待连接回调确认 unsigned long start = millis(); while (!connected && millis() - start < 10000) { delay(100); } if (connected) return true; } // 失败则尝试 RANDOM 地址类型 if (pClient->connect(address, BLE_ADDR_RANDOM)) { // ... } } return false;}关键设计:
连接成功后,向特征值写入数据: bool BLEClientManager::writeToCharacteristic(BLEAddress address, const String& data, const String& extra) { // 1. 连接设备 if (!doConnect(address)) return false; // 2. 查找服务和特征值 BLERemoteService* pService = pClient->getService(SERVICE_UUID); BLERemoteCharacteristic* pChar = pService->getCharacteristic(CHARACTERISTIC_UUID); // 3. 写入主数据 pChar->writeValue(data.c_str(), data.length()); // 4. 如有额外数据(如时间同步),延迟 200ms 后写入 if (!extra.isEmpty()) { delay(200); pChar->writeValue(extra.c_str(), extra.length()); } // 5. 延迟 500ms 后断开连接 delay(500); pClient->disconnect(); return true;}[url=][/url]4.4.3 支持的消息类型[td]
所有消息都附带 TIME:YYYY-MM-DD 作为额外数据,实现时间同步。 [url=][/url]4.5 Web 服务器与 API ESP32-P4 内置 WebServer(端口 80),提供管理页面和 REST API。 [url=][/url]4.5.1 API 列表[td]
由于 ArduinoJson 在 ESP32 上处理大 JSON 容易内存不足,采用手动字符串拼接方式构建响应。 [url=][/url]4.5.3 工牌配置下发 APIserver.on("/api/config", HTTP_POST, [() { // 1. 解析 JSON 请求体 String body = server.arg("plain"); // ... // 2. 保存到 SQLite db.saveBadgeConfig(deviceId, name, dept, title, badgeId, getUnixTime()); // 3. 从内存检测数组查找设备 BLE 地址 DetectionRecord* records = bleScanner.getDetectionRecords(); int count = bleScanner.getDetectionCount(); for (int i = 0; i < count; i++) { if (records[i.deviceId == deviceId) { targetAddress = records[i.address; found = true; break; } } if (!found) { // 设备不在蓝牙范围内 server.send(400, ..., "{\"success\":false,...}"); return; } // 4. 通过 BLE 发送配置到设备 String configStr = "{\"name\":\"...\",\"dept\":\"...\",...}"; String today = getTodayDate(); bool sent = bleClient.sendConfigToAddress(targetAddress, configStr, today); // 5. 返回结果 server.send(200, ..., sent ? "配置已下发" : "配置已保存但下发失败");});关键流程:保存到数据库 → 查找 BLE 地址 → 连接设备 → 下发配置 → 返回结果。即使 BLE 下发失败,配置也已保存在数据库中,下次设备靠近时会自动处理。 [url=][/url]4.6 SQLite 数据库设计 ESP32-P4 使用 SQLite 持久化存储数据。由于 ESP32 SPIFFS 对 sqlite3 的支持有限(不支持 AUTOINCREMENT、UNIQUE 等需要额外索引的操作),采用双数据库文件方案: [td]
[url=][/url]4.6.2 工牌配置表CREATE TABLE IF NOT EXISTS badge_configs ( id INTEGER PRIMARY KEY, device_id TEXT NOT NULL, name TEXT, dept TEXT, title TEXT, badge_id TEXT, updated_at INTEGER); [url=][/url]4.6.3 Database 类封装class Database {public: bool init(); // 初始化 SPIFFS + sqlite3 + 建表 void close(); // 打卡记录 bool addCheckIn(...); // 新增打卡记录 bool updateLastDetected(...); // 更新最近检测时间 bool hasCheckInToday(...); // 查询今日是否已打卡 unsigned long getLastDetected(...); // 获取最近检测时间 int getCheckInRecords(...); // 获取打卡记录列表 int getTodayCheckInCount(...); // 获取今日打卡数 // 工牌配置 bool saveBadgeConfig(...); // 保存/更新配置 bool getBadgeConfig(...); // 查询单个配置 int getAllBadgeConfigs(...); // 获取所有配置 bool deleteBadgeConfig(...); // 删除配置private: sqlite3* dbCheckIn = nullptr; // 打卡记录数据库 sqlite3* dbConfig = nullptr; // 工牌配置数据库}; [url=][/url]4.6.4 SPIFFS 初始化bool Database::init() { // SPIFFS 挂载,失败则格式化 if (!SPIFFS.begin(true)) { SPIFFS.format(); SPIFFS.begin(true); } sqlite3_initialize(); // 分别打开两个数据库文件 openDb("/spiffs/checkin.db", &dbCheckIn); openDb("/spiffs/config.db", &dbConfig); // 分别创建表 exec(dbCheckIn, "CREATE TABLE IF NOT EXISTS checkin_records (...)"); exec(dbConfig, "CREATE TABLE IF NOT EXISTS badge_configs (...)");} 为什么用双文件? 单文件多表在 SPIFFS 上容易出现 I/O 错误(文件系统碎片、写入冲突等),将两个表拆分到独立文件可以提高稳定性。 [url=][/url]5. Web 管理界面 ESP32-P4 提供完整的 Web 管理页面,用户通过手机或电脑浏览器访问 ESP32-P4 的 IP 地址即可使用。 [url=][/url]5.1 页面结构页面采用四标签设计,所有数据通过 AJAX 异步加载: [td]
![]() ![]()
![]() "工牌信息"标签页展示所有已配置的设备,每条记录右侧有删除按钮:
![]() [url=][/url]6. BLE 通信协议 ESP32-P4 与客户端(M5Paper/ESP32-C3)通过 BLE GATT 通信,基于自定义 UUID 的服务和特征值。 [url=][/url]6.1 服务定义[td]
ESP32-P4 同时支持两种客户端: [td]
两种客户端在打卡逻辑、配置流程上完全一致,ESP32-P4 通过广播名称前缀区分设备类型。 [url=][/url]7. M5Paper / ESP32-C3 客户端(简要) 客户端作为 BLE 外围设备,职责相对简单:
M5Paper 额外负责墨水屏 UI 展示(工牌信息、打卡状态、等待配置界面),ESP32-C3 则通过串口输出所有状态。 [url=][/url]M5Paper 界面效果M5Paper 墨水屏在不同状态下的显示效果: 等待配置界面(首次启动或配置被清空后): ![]() 工牌展示界面(已配置并打卡后): ![]() [url=][/url]8. 完整使用流程 ![]() ESP32-P4 上电后:
M5Paper / C3 上电后:
ESP32-P4 扫描到新的客户端广播:
用户浏览器访问 ESP32-P4 管理页面:
ESP32-P4 收到配置请求:
客户端重启后重新广播,ESP32-P4 再次检测到:
[url=][/url]9. 代码结构esp32-p4/├── esp32-p4.ino # 主程序:WiFi/NTP/WebServer初始化,主循环├── config.h # 配置常量:WiFi、BLE、Web、NTP├── index_html.h # Web 管理页面 HTML(内嵌到固件中)├── database.h/cpp # SQLite 数据库封装(打卡记录 + 工牌配置)├── ble_scanner.h/cpp # BLE 扫描:设备发现、打卡判定、新设备处理├── ble_client.h/cpp # BLE 客户端:连接设备、下发配置/通知└── slave/ # ESP32-C6 协处理器固件(预置,通常不需修改) [url=][/url]各文件职责[td]
[url=][/url]10. 配置参数 esp32-p4/config.h 中的可调参数: [td]
[url=][/url]附录:编译烧录cd esp32-p4# 编译(必须指定 8MB 分区方案)arduino-cli compile --fqbn esp32:esp32:dfrobot_firebeetle2_esp32p4:PartitionScheme=default_8MB .# 烧录arduino-cli upload -p /dev/cu.usbmodem14101 --fqbn esp32:esp32:dfrobot_firebeetle2_esp32p4:PartitionScheme=default_8MB . 开发板信息: [td]
|
编辑选择奖
沪公网安备31011502402448© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed