
/** * 无线寻物器 - 接收端 (Receiver) * 功能:接收ESP-NOW信号,触发声光报警 * 硬件:ESP32 + 有源蜂鸣器 + WS2812环形灯 */#include <WiFi.h>#include <esp_now.h>#include <Adafruit_NeoPixel.h>// ========== 引脚定义 ==========const int BUZZER_PIN = 25; // 有源蜂鸣器(高电平触发)const int LED_RING_PIN = 26; // WS2812数据引脚const int LED_COUNT = 12; // 灯环LED数量// 初始化灯环对象Adafruit_NeoPixel ring(LED_COUNT, LED_RING_PIN, NEO_GRB + NEO_KHZ800);void setup() { Serial.begin(115200); Serial.println("\n=== 无线寻物器 - 接收端启动 ==="); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); ring.begin(); ring.setBrightness(255); setRingColor(0, 0, 0); // 初始关闭 // TODO: 初始化ESP-NOW}void loop() { // TODO: 处理报警逻辑}void setRingColor(uint8_t r, uint8_t g, uint8_t b) { for (int i = 0; i < LED_COUNT; i++) { ring.setPixelColor(i, ring.Color(r, g, b)); } ring.show();}边写边讲:"这是通信的'合同',必须与发射端一字不差!"
// ========== 关键:必须与发射端完全一致的结构体 ==========typedef struct __attribute__((packed)) { uint8_t command; uint32_t timestamp; uint16_t checksum;} Packet;const uint8_t CMD_FIND = 1; // 同样的命令定义// 全局变量volatile bool alarmTriggered = false; // 报警标志(volatile用于中断)unsigned long alarmStartTime = 0; // 报警开始时间uint32_t lastPacketTime = 0; // 上次接收时间(防重放)// 颜色常量const uint32_t COLOR_RED = ring.Color(255, 0, 0);const uint32_t COLOR_GREEN = ring.Color(0, 255, 0);const uint32_t COLOR_BLUE = ring.Color(0, 0, 255);const uint32_t COLOR_OFF = ring.Color(0, 0, 0);边写边讲:"网络安全第一课:永远不要信任外部数据!"
// 校验和验证:确保数据未被篡改bool verifyChecksum(Packet *pkt) { uint16_t expected = pkt->command + (pkt->timestamp & 0xFFFF) + (pkt->timestamp >> 16); bool valid = (pkt->checksum == expected); if (!valid) { Serial.printf("校验和错误!收到:%d 期望:%d\n", pkt->checksum, expected); } return valid;}// 防重放检查:防止旧数据包重复触发bool isReplayAttack(Packet *pkt) { if (pkt->timestamp - lastPacketTime < 500) { // 500ms内视为重复 Serial.println("警告:检测到重复数据包(可能的重放攻击)"); return true; } lastPacketTime = pkt->timestamp; return false;}边写边讲:"这是整个系统的'大脑',处理所有无线消息"
// ESP-NOW接收回调(在中断上下文中执行,要快!)void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { Serial.printf("\n收到数据 | 长度:%d字节 | 期望:%d字节\n", len, sizeof(Packet)); // 严格长度检查:防止缓冲区溢出 if (len != sizeof(Packet)) { Serial.println("❌ 错误:数据长度不匹配,丢弃!"); return; } // 复制数据到本地结构体(避免直接操作指针) Packet received; memcpy(&received, incomingData, sizeof(Packet)); Serial.printf("解析 | 命令:%d | 时间戳:%lu | 校验和:%d\n", received.command, received.timestamp, received.checksum); // 完整性验证 if (!verifyChecksum(&received)) { Serial.println("❌ 校验失败,丢弃数据包"); return; } // 防重放检查 if (isReplayAttack(&received)) { return; } // 命令处理 if (received.command == CMD_FIND) { Serial.println(">>> ✅ 校验通过!触发寻物报警!"); alarmTriggered = true; alarmStartTime = millis(); } else { Serial.printf("⚠️ 未知命令: %d\n", received.command); }}void setup() { // ... 前面的初始化代码 ... WiFi.mode(WIFI_STA); Serial.print("本机MAC地址: "); Serial.println(WiFi.macAddress()); // 这个地址要给发射端! // 初始化ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("ESP-NOW初始化失败!"); setRingColor(255, 0, 0); // 红灯表示错误 return; } esp_now_register_recv_cb(onDataRecv); Serial.println("初始化完成,等待信号..."); setRingColor(0, 0, 255); // 蓝灯表示待机 delay(1000); setRingColor(0, 0, 0);}// 流水灯效果(非阻塞)void runningLight(uint32_t color, int speed) { static int pos = 0; static unsigned long lastUpdate = 0; if (millis() - lastUpdate > speed) { lastUpdate = millis(); ring.clear(); // 创建3个LED的渐变拖尾效果 for (int i = -2; i <= 2; i++) { int index = (pos + i + LED_COUNT) % LED_COUNT; uint8_t brightness = 255 - abs(i) * 80; uint32_t dimmed = ring.Color( ((color >> 16) & 0xFF) * brightness / 255, ((color >> 8) & 0xFF) * brightness / 255, (color & 0xFF) * brightness / 255 ); ring.setPixelColor(index, dimmed); } ring.show(); pos = (pos + 1) % LED_COUNT; }}// 蜂鸣器控制(非阻塞)void updateBuzzer(int frequency) { static unsigned long lastBeep = 0; static bool beepState = false; unsigned long interval = 1000 / (frequency * 2); // 计算间隔 if (millis() - lastBeep > interval) { lastBeep = millis(); beepState = !beepState; digitalWrite(BUZZER_PIN, beepState ? HIGH : LOW); }}void stopAlarm() { alarmTriggered = false; digitalWrite(BUZZER_PIN, LOW); setRingColor(0, 0, 0); Serial.println("报警结束,进入待机");}void loop() { if (alarmTriggered) { // 检查是否超时(10秒) if (millis() - alarmStartTime > 10000) { stopAlarm(); return; } runningLight(COLOR_RED, 50); // 红色流水灯,50ms移动一次 updateBuzzer(4); // 4Hz蜂鸣(每秒4次) } else { // 待机心跳:每5秒闪一下绿灯表示"我还活着" static unsigned long lastHeartbeat = 0; if (millis() - lastHeartbeat > 5000) { lastHeartbeat = millis(); setRingColor(0, 255, 0); delay(50); setRingColor(0, 0, 0); } }}84:1F:E8:26:85:8C// 替换为实际的接收端MAC地址uint8_t receiverMacAddress[] = {0x84, 0x1F, 0xE8, 0x26, 0x85, 0x8C};测试项 | 操作 | 预期结果 | 状态 |
待机指示 | 接收端上电 | 蓝灯亮1秒后熄灭,每5秒绿灯闪烁 | ☐ |
按键发送 | 按下发射端按键 | 发射端LED闪烁,串口显示"发送成功" | ☐ |
接收报警 | 观察接收端 | 红灯流水灯+蜂鸣器响 | ☐ |
超时停止 | 等待10秒 | 自动停止报警,恢复待机 | ☐ |
防重放测试 | 快速连续按按键 | 只触发一次报警(500ms内忽略) | ☐ |
0xFF...有什么风险?// 挑战1:添加电池电量检测typedef struct __attribute__((packed)) { uint8_t command; uint32_t timestamp; uint16_t checksum; uint8_t batteryLevel; // 新增:电量百分比} Packet;// 挑战2:双向通信(接收端回复确认)void onDataRecv(...) { // 收到寻物指令后,发送确认包给发射端 // 发射端收到确认后才停止重发}Adafruit_NeoPixel
现象 | 可能原因 | 解决方案 |
发送成功但接收无反应 | MAC地址不匹配 | 检查接收端串口输出的MAC,更新到发射端 |
数据长度不匹配 | 结构体未packed | 检查两端都有 |
校验和错误 | 结构体定义不一致 | 对比两端结构体字段顺序和类型 |
蜂鸣器不响 | 引脚错误或蜂鸣器类型 | 确认是有源蜂鸣器,测试引脚输出 |
灯环不亮 | 电源不足或引脚错误 | 灯环需要较大电流,建议单独供电 |
本文标签:#科创编程 #物联网 #教案 #编程思维 #电子爱好者 #少儿编程
![]() | ![]() |