世代继承的意志,时代的变迁,人的梦想,这些都是无法阻止的。只要人们继续追求自由的答案,这一切都将永不停止!
——哥尔·D·罗杰
参考:
钉钉蓝牙打卡是通过验证打卡设备的mac地址和广播数据实现的,所以为了实现远程打卡,我们需要使用一个设备修改mac地址模拟考勤机发送广播。
按照广播数据的类型来分类的话,钉钉蓝牙打卡设备分为两类,分别为静态蓝牙广播设备与动态蓝牙广播设备,无论是哪种打卡设备,都需要mac地址与考勤蓝牙设备相同
实现远程蓝牙打卡的具体思路为通过使用自己的蓝牙设备模拟考勤机发送广播从而实现远程打卡,如果考勤机的广播数据是动态生成的我们还需要额外一个设备放在考勤机旁用于获取动态的广播数据并将数据上传至服务器,然后我们获取广播数据并应用于我们自己的模拟打卡设备,于是我们可以实现远程打卡
下载的软件功能如下:
需要准备的工具并不必须为esp32C3开发板,只需要是能发送和扫描广播数据的设备都可以,你也可以实现适合你自己的蓝牙打卡方案。
蓝牙广播 | 优点 | 缺点 |
---|---|---|
手机广播 | 用软件模拟蓝牙广播,图形化操作界面,无需编写代码 | 贵 |
esp32C3广播 | 便宜 | 门槛较高,过程相对复杂 |
对于动态蓝牙广播设备来说,你需要一个额外的设备进行蓝牙广播数据的获取,那么如何判断考勤机是否是动态蓝牙广播设备呢?从实用角度出发,我们可以简单地通过观察广播数据来作出判断,这在后面的流程中会提到。
蓝牙扫描(获取动态数据) | 优点 | 缺点 |
---|---|---|
手机扫描 | 1.有成熟的软件可以进行蓝牙扫描 2.不用外接电源,无需考虑续航问题 3.可以使用移动数据进行通信 |
1.贵 2.体积较大,易被发现 |
esp32C3扫描 | 1.轻便小巧,可以轻易藏起来 2.成本低,丢了也不心疼 |
1.没有外接电源无法正常工作 2.放置位置必须有网络,内网也行,但必须得有网络 |
这里有几个建议和说明:
下载: Arduino
参考: 不爱笑的张杰-dingBle
接下来我们设置虚拟mac和广播数据,并进行广播!
详细的代码及注释如下,对于这部分代码有几个需要说明的点:
bleRaw
变量值为{ 0x04, 0x22, 0x33, 0xFF, 0xEE}
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "esp_sleep.h"
BLEAdvertising *pAdvertising;
uint8_t bleMac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; // 假设考勤机mac地址为11:22:33:44:55:66
// 0-30 前31组
uint8_t bleRaw[] = {
0x02, 0x01, 0x06, 0x03, 0x03, 0x3C, 0xFE, 0x17, 0xFF, 0x00, 0x02, 0x72, 0x00, 0x1F, 0x71, 0x5A, 0x4B, 0xA6, 0xBA, 0xB5, 0x00, 0xC4, 0xF8, 0xA7, 0x63, 0x9E, 0xCA, 0xCC, 0x00, 0x02, 0x20,
}; // 去除复制得到的文本最前面的0x后,两两一组填入,由于数据很长,我们可以写一个简单的脚本完成这个工作
// 如果复制出来的raw超过31组 那么把它改为true并维护下面的数组
boolean rawMoreThan31 = false;
// 31-end
uint8_t bleRaw32[] = {0x0C,0x09,0x52,0x54,0x4B,0x5F,0x42,0x54,0x5F,0x34,0x2E,0x31,0x00};
#define SERVICE_UUID "0000FE3C-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_ONE_UUID "0000FE1B-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_TWO_UUID "0000FE1C-0000-1000-8000-00805F9B34FB"
// 回调函数,用于处理特性通知
class MyCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" read: ");
Serial.println(value.c_str());
}
void onWrite(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" write: ");
Serial.println(value.c_str());
}
void onNotify(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" notify: ");
Serial.println(value.c_str());
}
};
void setup() {
Serial.begin(115200); // 初始化串口
delay(2000);
// esp32没有提供设置蓝牙mac地址的api 通过查看esp32的源代码
// 此操作将根据蓝牙mac算出base mac
if (UNIVERSAL_MAC_ADDR_NUM == FOUR_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 2;
} else if (UNIVERSAL_MAC_ADDR_NUM == TWO_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 1;
}
esp_base_mac_addr_set(bleMac);
// 初始化
BLEDevice::init("");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
BLECharacteristic *pCharacteristic_one = pService->createCharacteristic(
CHARACTERISTIC_ONE_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
BLECharacteristic *pCharacteristic_two = pService->createCharacteristic(
CHARACTERISTIC_TWO_UUID,
BLECharacteristic::PROPERTY_WRITE
);
// 设置回调函数
pCharacteristic_two->setCallbacks(new MyCallbacks());
pCharacteristic_one->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
pAdvertising = pServer->getAdvertising();
// 设备信息设置成空白的
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
pAdvertising->setScanResponseData(oScanResponseData);
// 里面有个 m_customScanResponseData = true; 和 m_customScanResponseData = true; 所以只能先随便设置一下
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
pAdvertising->setAdvertisementData(oAdvertisementData);
// 简单粗暴直接底层api重新设置一下抓到的raw
esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw(bleRaw, 31);
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_adv_data_raw: %d\n", errRc);
}
// 超过31
if (rawMoreThan31) {
errRc = ::esp_ble_gap_config_scan_rsp_data_raw(bleRaw32, sizeof(bleRaw32)/sizeof(bleRaw32[0]));
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_scan_rsp_data_raw: %d\n", errRc);
}
}
pAdvertising->start();
}
void loop() {
// 闪灯灯 至于为什么是串口输出,因为并没有内置led,但拥有串口指示灯
Serial.println("Sparkle");
delay(1000);
// 20分钟去待机避免忘了关
if (millis() > 1200000) {
esp_deep_sleep_start();
}
}
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "esp_sleep.h"
BLEAdvertising *pAdvertising;
uint8_t bleMac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; // 假设考勤机mac地址为11:22:33:44:55:66
// 0-30 前31组
uint8_t bleRaw[] = {
0x02, 0x01, 0x06, 0x03, 0x03, 0x3C, 0xFE, 0x17, 0xFF, 0x00, 0x02, 0x72, 0x00, 0x1F, 0x71, 0x5A, 0x4B, 0xA6, 0xBA, 0xB5, 0x00, 0xC4, 0xF8, 0xA7, 0x63, 0x9E, 0xCA, 0xCC, 0x00, 0x02, 0x20,
}; // 去除复制得到的文本最前面的0x后,两两一组填入,由于数据很长,我们可以写一个简单的脚本完成这个工作
// 如果复制出来的raw超过31组 那么把它改为true并维护下面的数组
boolean rawMoreThan31 = false;
// 31-end
uint8_t bleRaw32[] = {0x0C,0x09,0x52,0x54,0x4B,0x5F,0x42,0x54,0x5F,0x34,0x2E,0x31,0x00};
#define SERVICE_UUID "0000FE3C-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_ONE_UUID "0000FE1B-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_TWO_UUID "0000FE1C-0000-1000-8000-00805F9B34FB"
// 回调函数,用于处理特性通知
class MyCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" read: ");
Serial.println(value.c_str());
}
void onWrite(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" write: ");
Serial.println(value.c_str());
}
void onNotify(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" notify: ");
Serial.println(value.c_str());
}
};
void setup() {
Serial.begin(115200); // 初始化串口
delay(2000);
// esp32没有提供设置蓝牙mac地址的api 通过查看esp32的源代码
// 此操作将根据蓝牙mac算出base mac
if (UNIVERSAL_MAC_ADDR_NUM == FOUR_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 2;
} else if (UNIVERSAL_MAC_ADDR_NUM == TWO_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 1;
}
esp_base_mac_addr_set(bleMac);
// 初始化
BLEDevice::init("");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
BLECharacteristic *pCharacteristic_one = pService->createCharacteristic(
CHARACTERISTIC_ONE_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
BLECharacteristic *pCharacteristic_two = pService->createCharacteristic(
CHARACTERISTIC_TWO_UUID,
BLECharacteristic::PROPERTY_WRITE
);
// 设置回调函数
pCharacteristic_two->setCallbacks(new MyCallbacks());
pCharacteristic_one->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
pAdvertising = pServer->getAdvertising();
// 设备信息设置成空白的
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
pAdvertising->setScanResponseData(oScanResponseData);
// 里面有个 m_customScanResponseData = true; 和 m_customScanResponseData = true; 所以只能先随便设置一下
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
pAdvertising->setAdvertisementData(oAdvertisementData);
// 简单粗暴直接底层api重新设置一下抓到的raw
esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw(bleRaw, 31);
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_adv_data_raw: %d\n", errRc);
}
// 超过31
if (rawMoreThan31) {
errRc = ::esp_ble_gap_config_scan_rsp_data_raw(bleRaw32, sizeof(bleRaw32)/sizeof(bleRaw32[0]));
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_scan_rsp_data_raw: %d\n", errRc);
}
}
pAdvertising->start();
}
void loop() {
// 闪灯灯 至于为什么是串口输出,因为并没有内置led,但拥有串口指示灯
Serial.println("Sparkle");
delay(1000);
// 20分钟去待机避免忘了关
if (millis() > 1200000) {
esp_deep_sleep_start();
}
}
如果你的考勤机是静态广播类型的那么你应该已经可以实现远程蓝牙打卡了,但是多数资本家会愿意花更多钱使用更难作弊的动态广播打卡机,这种打卡机的广播数据是在实时变化的,所以之前我们在代码中写死广播数据的做法不再通用。那么很自然地我们想到,能不能实时地获取打卡机的广播数据呢?由于只有在打卡机的蓝牙范围内才能获取广播数据,所以我们必须要有额外的一个设备放在它的蓝牙范围内,通过该设备获取广播数据后,将数据写入实现打卡。
可以使用公共的服务器进行测试和使用,但是建立自己的服务器能保证信息的时效性,公共服务响应时间为一分钟左右,即esp32发布消息后,客户端需要过一分钟才能看到更新的消息,我使用 EMQX Platform 建立服务器,这是完全免费的!
下载:mqtt explore
WiFiClientSecure
类型而不是WiFiClient
WiFiClientSecure
类型而不是WiFiClient
❤️将蓝牙扫描代码烧录入esp32后接通电源并放在蓝牙考勤机旁边,然后打开mqtt客户端查看上传的广播数据,将广播数据进行处理后替换蓝牙广播代码的广播数据,然后烧录程序打开钉钉即可实现打卡。❤️
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "esp_sleep.h"
BLEAdvertising *pAdvertising;
uint8_t bleMac[6] = {0xF8, 0xA7, 0x63, 0x9C, 0x69, 0x05};
// 0-30 前31组
uint8_t bleRaw[] = {
0x02, 0x01, 0x06, 0x03, 0x03, 0x3C, 0xFE, 0x17, 0xFF, 0x00, 0x02, 0x68, 0x00, 0x1F, 0x44, 0x3B, 0x0C, 0x52, 0x44, 0x34, 0x00, 0xC4, 0xF8, 0xA7, 0x63, 0x9E, 0xCC, 0x39, 0x00, 0x03, 0x20,
};
// 如果复制出来的raw超过31组 那么把它改为true并维护下面的数组
boolean rawMoreThan31 = false;
// 31-end
uint8_t bleRaw32[] = {0x0C,0x09,0x52,0x54,0x4B,0x5F,0x42,0x54,0x5F,0x34,0x2E,0x31,0x00};
#define SERVICE_UUID "0000FE3C-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_ONE_UUID "0000FE1B-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_TWO_UUID "0000FE1C-0000-1000-8000-00805F9B34FB"
// 回调函数,用于处理特性通知
class MyCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" read: ");
Serial.println(value.c_str());
}
void onWrite(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" write: ");
Serial.println(value.c_str());
}
void onNotify(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" notify: ");
Serial.println(value.c_str());
}
};
void setup() {
Serial.begin(115200);
delay(2000);
// esp32没有提供设置蓝牙mac地址的api 通过查看esp32的源代码
// 此操作将根据蓝牙mac算出base mac
if (UNIVERSAL_MAC_ADDR_NUM == FOUR_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 2;
} else if (UNIVERSAL_MAC_ADDR_NUM == TWO_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 1;
}
esp_base_mac_addr_set(bleMac);
// 初始化
BLEDevice::init("");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
BLECharacteristic *pCharacteristic_one = pService->createCharacteristic(
CHARACTERISTIC_ONE_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
BLECharacteristic *pCharacteristic_two = pService->createCharacteristic(
CHARACTERISTIC_TWO_UUID,
BLECharacteristic::PROPERTY_WRITE
);
// 设置回调函数
pCharacteristic_two->setCallbacks(new MyCallbacks());
pCharacteristic_one->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
pAdvertising = pServer->getAdvertising();
// 设备信息设置成空白的
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
pAdvertising->setScanResponseData(oScanResponseData);
// 里面有个 m_customScanResponseData = true; 和 m_customScanResponseData = true; 所以只能先随便设置一下
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
pAdvertising->setAdvertisementData(oAdvertisementData);
// 简单粗暴直接底层api重新设置一下抓到的raw
esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw(bleRaw, 31);
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_adv_data_raw: %d\n", errRc);
}
// 超过31
if (rawMoreThan31) {
errRc = ::esp_ble_gap_config_scan_rsp_data_raw(bleRaw32, sizeof(bleRaw32)/sizeof(bleRaw32[0]));
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_scan_rsp_data_raw: %d\n", errRc);
}
}
pAdvertising->start();
}
void loop() {
// 闪灯灯 至于为什么是串口输出,因为并没有内置led,但拥有串口指示灯
Serial.println("Sparkle");
delay(1000);
// 20分钟去待机避免忘了关
if (millis() > 1200000) {
esp_deep_sleep_start();
}
}
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "esp_sleep.h"
BLEAdvertising *pAdvertising;
uint8_t bleMac[6] = {0xF8, 0xA7, 0x63, 0x9C, 0x69, 0x05};
// 0-30 前31组
uint8_t bleRaw[] = {
0x02, 0x01, 0x06, 0x03, 0x03, 0x3C, 0xFE, 0x17, 0xFF, 0x00, 0x02, 0x68, 0x00, 0x1F, 0x44, 0x3B, 0x0C, 0x52, 0x44, 0x34, 0x00, 0xC4, 0xF8, 0xA7, 0x63, 0x9E, 0xCC, 0x39, 0x00, 0x03, 0x20,
};
// 如果复制出来的raw超过31组 那么把它改为true并维护下面的数组
boolean rawMoreThan31 = false;
// 31-end
uint8_t bleRaw32[] = {0x0C,0x09,0x52,0x54,0x4B,0x5F,0x42,0x54,0x5F,0x34,0x2E,0x31,0x00};
#define SERVICE_UUID "0000FE3C-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_ONE_UUID "0000FE1B-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_TWO_UUID "0000FE1C-0000-1000-8000-00805F9B34FB"
// 回调函数,用于处理特性通知
class MyCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" read: ");
Serial.println(value.c_str());
}
void onWrite(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" write: ");
Serial.println(value.c_str());
}
void onNotify(BLECharacteristic* pCharacteristic) {
std::string value = pCharacteristic->getValue();
Serial.print("Characteristic ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(" notify: ");
Serial.println(value.c_str());
}
};
void setup() {
Serial.begin(115200);
delay(2000);
// esp32没有提供设置蓝牙mac地址的api 通过查看esp32的源代码
// 此操作将根据蓝牙mac算出base mac
if (UNIVERSAL_MAC_ADDR_NUM == FOUR_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 2;
} else if (UNIVERSAL_MAC_ADDR_NUM == TWO_UNIVERSAL_MAC_ADDR) {
bleMac[5] -= 1;
}
esp_base_mac_addr_set(bleMac);
// 初始化
BLEDevice::init("");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
BLECharacteristic *pCharacteristic_one = pService->createCharacteristic(
CHARACTERISTIC_ONE_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
BLECharacteristic *pCharacteristic_two = pService->createCharacteristic(
CHARACTERISTIC_TWO_UUID,
BLECharacteristic::PROPERTY_WRITE
);
// 设置回调函数
pCharacteristic_two->setCallbacks(new MyCallbacks());
pCharacteristic_one->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
pAdvertising = pServer->getAdvertising();
// 设备信息设置成空白的
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
pAdvertising->setScanResponseData(oScanResponseData);
// 里面有个 m_customScanResponseData = true; 和 m_customScanResponseData = true; 所以只能先随便设置一下
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
pAdvertising->setAdvertisementData(oAdvertisementData);
// 简单粗暴直接底层api重新设置一下抓到的raw
esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw(bleRaw, 31);
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_adv_data_raw: %d\n", errRc);
}
// 超过31
if (rawMoreThan31) {
errRc = ::esp_ble_gap_config_scan_rsp_data_raw(bleRaw32, sizeof(bleRaw32)/sizeof(bleRaw32[0]));
if (errRc != ESP_OK) {
Serial.printf("esp_ble_gap_config_scan_rsp_data_raw: %d\n", errRc);
}
}
pAdvertising->start();
}
void loop() {
// 闪灯灯 至于为什么是串口输出,因为并没有内置led,但拥有串口指示灯
Serial.println("Sparkle");
delay(1000);
// 20分钟去待机避免忘了关
if (millis() > 1200000) {
esp_deep_sleep_start();
}
}
#include <WiFi.h>
#include <ArduinoBLE.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
// WiFi credentials
const char *ssid = "xxxxxx"; // 替换为你的WiFi热点名,不要使用中文
const char *password = "12345678"; // 替换为你的Wifi密码
// 指定的蓝牙MAC地址
const char *targetMACAddress = "11:22:33:44:55:66"; // 替换为考勤机的mac地址
// MQTT Broker settings
const char *mqtt_broker = "test.mosquitto.org"; // (可选)替换为你的broker地址
// const char *mqtt_username = "skysilksock"; // 替换为你设置的用户名
// const char *mqtt_password = "2680613764fwy"; // 替换为你设置的密码
const int mqtt_port = 1883; // 端口号设置,如果使用自己的服务器则设置为8883
// WiFi and MQTT client initialization
WiFiClient esp_client;
// WiFiClientSecure esp_client; // 如果使用自己的服务器则使用WiFiClientSecure,注释上行
PubSubClient mqtt_client(esp_client);
char *mqtt_topic = "dingding";
// 定义 MQTT 主题
String topic_commands = String(mqtt_topic) + "/commands";
String topic_status = String(mqtt_topic) + "/status";
String topic_advertisementData = String(mqtt_topic) + "/advData";
String topic_connect = String(mqtt_topic) + "/connect";
// Root CA Certificate
// Load DigiCert Global Root G2, which is used by EMQX Public Broker: broker.emqx.io
// const char *ca_cert = R"EOF(
// -----BEGIN CERTIFICATE-----
// MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
// MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
// d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
// MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
// MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
// b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
// 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
// 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
// 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
// q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
// tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
// vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
// BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
// 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
// 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
// NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
// Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
// 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
// pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
// MrY=
// -----END CERTIFICATE-----
// )EOF";
// Load DigiCert Global Root CA ca_cert, which is used by EMQX Platform Serverless Deployment
const char *ca_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)EOF";
// Function Declarations
void connectToWiFi();
void connectToMQTT();
void mqttCallback(char *topic, byte *payload, unsigned int length);
void setup() {
Serial.begin(115200); // 初始化串口
connectToWiFi();
// 初始化蓝牙
if (!BLE.begin()) {
Serial.println("Starting BLE failed!");
while (1)
;
}
// 设置根证书
// esp_client.setCACert(ca_cert); // 如果使用自己的服务器需要设置根证书
mqtt_client.setServer(mqtt_broker, mqtt_port); // 设置mqtt服务
mqtt_client.setKeepAlive(60);
mqtt_client.setCallback(mqttCallback); // 设置回调函数
connectToMQTT();
}
// 用于连接WiFi的函数
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to WiFi");
}
// 用于连接MQTT服务的函数
void connectToMQTT() {
while (!mqtt_client.connected()) {
String client_id = "esp32-client-" + String(WiFi.macAddress());
Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
if (mqtt_client.connect(client_id.c_str())) {
// if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) // 使用自己的服务器需要传入账户密码参数
Serial.println("Connected to MQTT broker");
mqtt_client.subscribe(topic_commands.c_str());
mqtt_client.publish(topic_status.c_str(), "Hi EMQX I'm ESP32 ^^"); // Publish message upon connection
} else {
Serial.print("Failed to connect to MQTT broker, rc=");
Serial.print(mqtt_client.state());
Serial.println(" Retrying in 5 seconds.");
delay(5000);
}
}
}
// 该函数指示了如果收到来自mqtt服务器的消息该如何操作
void mqttCallback(char *topic, byte *payload, unsigned int length) {
Serial.print("Message received on topic: ");
Serial.println(topic);
Serial.print("Message: ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println("\n-----------------------");
mqtt_client.publish(topic_connect.c_str(), "Hi EMQX I'm ESP32 ^^");
}
void loop() {
// 如果断开与服务器的连接则尝试重连
if (!mqtt_client.connected()) {
connectToMQTT();
}
mqtt_client.loop(); // 类似app.run()
unsigned long lastMsg = millis(); // 当前时间
BLE.scan(); // 开启蓝牙扫描
Serial.println("Scanning for BLE devices...");
// 持续扫描20秒,试图找到对应mac地址的广播数据
while (millis() - lastMsg < 20 * 1000) {
// delay(1000); // 每次得出结果后等待1秒
String s = getAdvertisementData(targetMACAddress);
if (s.length()) {
Serial.println("hello");
mqtt_client.publish(topic_advertisementData.c_str(), s.c_str());
}
}
BLE.stopScan(); // 结束蓝牙扫描
// 扫描结束后更新消息,用于判断是否有连接
String msg = "Hello from ESP32" + String(lastMsg);
Serial.print("Publish message: ");
Serial.println(msg);
mqtt_client.publish(topic_status.c_str(), msg.c_str());
delay(10 * 1000); // 每次扫描结束后等待10秒再进行扫描
}
// 获取蓝牙设备并判断是否是指定设备,如果是则返回其广播数据,未扫描到设备则返回空字符串
String getAdvertisementData(const char *targetMACAddress) {
// 获取扫描结果
BLEDevice peripheral = BLE.available();
if (peripheral) {
// 打印设备地址
Serial.print("Found device: ");
Serial.println(peripheral.address());
// 检查是否是指定的MAC地址
if (peripheral.address() == targetMACAddress) {
Serial.println("Target device found!");
// 获取广播数据
int len = peripheral.advertisementDataLength();
if (len > 0) {
byte advData[len];
peripheral.advertisementData(advData, len);
// 创建 String 用于记录广播数据
String advDataStr = "";
// 将 byte 数组数据复制到String
for (int i = 0; i < len; i++) {
char hex[3]; // 两位十六进制数加上终止符
sprintf(hex, "%02X", advData[i]);
advDataStr += hex;
}
return advDataStr;
}
}
return "";
}
return "";
}
#include <WiFi.h>
#include <ArduinoBLE.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
// WiFi credentials
const char *ssid = "xxxxxx"; // 替换为你的WiFi热点名,不要使用中文
const char *password = "12345678"; // 替换为你的Wifi密码
// 指定的蓝牙MAC地址
const char *targetMACAddress = "11:22:33:44:55:66"; // 替换为考勤机的mac地址
// MQTT Broker settings
const char *mqtt_broker = "test.mosquitto.org"; // (可选)替换为你的broker地址
// const char *mqtt_username = "skysilksock"; // 替换为你设置的用户名
// const char *mqtt_password = "2680613764fwy"; // 替换为你设置的密码
const int mqtt_port = 1883; // 端口号设置,如果使用自己的服务器则设置为8883
// WiFi and MQTT client initialization
WiFiClient esp_client;
// WiFiClientSecure esp_client; // 如果使用自己的服务器则使用WiFiClientSecure,注释上行
PubSubClient mqtt_client(esp_client);
char *mqtt_topic = "dingding";
// 定义 MQTT 主题
String topic_commands = String(mqtt_topic) + "/commands";
String topic_status = String(mqtt_topic) + "/status";
String topic_advertisementData = String(mqtt_topic) + "/advData";
String topic_connect = String(mqtt_topic) + "/connect";
// Root CA Certificate
// Load DigiCert Global Root G2, which is used by EMQX Public Broker: broker.emqx.io
// const char *ca_cert = R"EOF(
// -----BEGIN CERTIFICATE-----
// MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
// MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
// d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
// MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
// MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
// b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
// 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
// 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
// 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
// q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
// tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
// vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
// BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
// 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
// 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
// NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
// Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
// 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
// pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
// MrY=
// -----END CERTIFICATE-----
// )EOF";
// Load DigiCert Global Root CA ca_cert, which is used by EMQX Platform Serverless Deployment
const char *ca_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)EOF";
// Function Declarations
void connectToWiFi();
void connectToMQTT();
void mqttCallback(char *topic, byte *payload, unsigned int length);
void setup() {
Serial.begin(115200); // 初始化串口
connectToWiFi();
// 初始化蓝牙
if (!BLE.begin()) {
Serial.println("Starting BLE failed!");
while (1)
;
}
// 设置根证书
// esp_client.setCACert(ca_cert); // 如果使用自己的服务器需要设置根证书
mqtt_client.setServer(mqtt_broker, mqtt_port); // 设置mqtt服务
mqtt_client.setKeepAlive(60);
mqtt_client.setCallback(mqttCallback); // 设置回调函数
connectToMQTT();
}
// 用于连接WiFi的函数
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to WiFi");
}
// 用于连接MQTT服务的函数
void connectToMQTT() {
while (!mqtt_client.connected()) {
String client_id = "esp32-client-" + String(WiFi.macAddress());
Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
if (mqtt_client.connect(client_id.c_str())) {
// if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) // 使用自己的服务器需要传入账户密码参数
Serial.println("Connected to MQTT broker");
mqtt_client.subscribe(topic_commands.c_str());
mqtt_client.publish(topic_status.c_str(), "Hi EMQX I'm ESP32 ^^"); // Publish message upon connection
} else {
Serial.print("Failed to connect to MQTT broker, rc=");
Serial.print(mqtt_client.state());
Serial.println(" Retrying in 5 seconds.");
delay(5000);
}
}
}
// 该函数指示了如果收到来自mqtt服务器的消息该如何操作
void mqttCallback(char *topic, byte *payload, unsigned int length) {
Serial.print("Message received on topic: ");
Serial.println(topic);
Serial.print("Message: ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println("\n-----------------------");
mqtt_client.publish(topic_connect.c_str(), "Hi EMQX I'm ESP32 ^^");
}
void loop() {
// 如果断开与服务器的连接则尝试重连
if (!mqtt_client.connected()) {
connectToMQTT();
}
mqtt_client.loop(); // 类似app.run()
unsigned long lastMsg = millis(); // 当前时间
BLE.scan(); // 开启蓝牙扫描
Serial.println("Scanning for BLE devices...");
// 持续扫描20秒,试图找到对应mac地址的广播数据
while (millis() - lastMsg < 20 * 1000) {
// delay(1000); // 每次得出结果后等待1秒
String s = getAdvertisementData(targetMACAddress);
if (s.length()) {
Serial.println("hello");
mqtt_client.publish(topic_advertisementData.c_str(), s.c_str());
}
}
BLE.stopScan(); // 结束蓝牙扫描
// 扫描结束后更新消息,用于判断是否有连接
String msg = "Hello from ESP32" + String(lastMsg);
Serial.print("Publish message: ");
Serial.println(msg);
mqtt_client.publish(topic_status.c_str(), msg.c_str());
delay(10 * 1000); // 每次扫描结束后等待10秒再进行扫描
}
// 获取蓝牙设备并判断是否是指定设备,如果是则返回其广播数据,未扫描到设备则返回空字符串
String getAdvertisementData(const char *targetMACAddress) {
// 获取扫描结果
BLEDevice peripheral = BLE.available();
if (peripheral) {
// 打印设备地址
Serial.print("Found device: ");
Serial.println(peripheral.address());
// 检查是否是指定的MAC地址
if (peripheral.address() == targetMACAddress) {
Serial.println("Target device found!");
// 获取广播数据
int len = peripheral.advertisementDataLength();
if (len > 0) {
byte advData[len];
peripheral.advertisementData(advData, len);
// 创建 String 用于记录广播数据
String advDataStr = "";
// 将 byte 数组数据复制到String
for (int i = 0; i < len; i++) {
char hex[3]; // 两位十六进制数加上终止符
sprintf(hex, "%02X", advData[i]);
advDataStr += hex;
}
return advDataStr;
}
}
return "";
}
return "";
}