/* ESP-NOW ADDRESSED CALL-AND-RESPONSE — Bulletproof Loop-Kicker Version Board: Seeed XIAO ESP32-C3 */ #include #include #include // Required for forcing Wi-Fi channel // ======================================================== // 🛠️ MANUAL CONFIGURATION: COMMENT / UNCOMMENT TO CHOOSE // ======================================================== #define IS_NODE_A // Leave uncommented for Node A (D2). Comment out for Node B (D10). // ======================================================== #define WIFI_CHANNEL 1 uint8_t MY_ID; uint8_t PEER_ID; uint8_t BUZZER_PIN; // State flag to track if the musical conversation has officially started volatile bool conversationStarted = false; // ---------------- PACKET FORMAT ---------------- typedef struct __attribute__((packed)) { uint8_t sender_id; uint8_t receiver_id; uint16_t frequency_hz; uint16_t duration_ms; } message_t; uint8_t peerMac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // ---------------- BUZZER FUNCTION ---------------- void playTone(uint16_t freq, uint16_t dur) { Serial.printf("[BUZZER] Starting %d Hz tone for %d ms on Pin %d...\n", freq, dur, BUZZER_PIN); ledcAttach(BUZZER_PIN, freq, 8); ledcWriteTone(BUZZER_PIN, freq); delay(dur); ledcWriteTone(BUZZER_PIN, 0); ledcDetach(BUZZER_PIN); Serial.println("[BUZZER] Tone finished."); } // ---------------- SENDING ---------------- void sendMessage(uint8_t receiver, uint16_t freq, uint16_t dur) { message_t msg; msg.sender_id = MY_ID; msg.receiver_id = receiver; msg.frequency_hz = freq; msg.duration_ms = dur; Serial.printf("[TX TRIGGER] Sending: Node %d -> Node %d (%d Hz, %d ms)\n", msg.sender_id, msg.receiver_id, msg.frequency_hz, msg.duration_ms); esp_err_t result = esp_now_send(peerMac, (uint8_t*)&msg, sizeof(msg)); if (result == ESP_OK) { Serial.println("[TX SUCCESS] Packet sent into the air."); } else { Serial.printf("[TX ERROR] Send failed! Code: 0x%X\n", result); } } // ---------------- RECEIVING ---------------- void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) { if (len != sizeof(message_t)) return; message_t msg; memcpy(&msg, data, sizeof(msg)); // Software Address Filter if (msg.receiver_id != MY_ID) return; // SUCCESS! We received a valid message targeted at us. // Mark the conversation as active so Node A stops its auto-pings. conversationStarted = true; Serial.printf("\n[RX ACCEPTED] From Node %d: %d Hz, %d ms\n", msg.sender_id, msg.frequency_hz, msg.duration_ms); playTone(msg.frequency_hz, msg.duration_ms); delay(200); // Quick breath before replying uint16_t newFreq = random(400, 1600); uint16_t newDur = 200 + random(0, 200); sendMessage(msg.sender_id, newFreq, newDur); } void OnDataSent(const wifi_tx_info_t *info, esp_now_send_status_t status) { // Broadcasts do not wait for ACKs, this just acknowledges radio egress. } // ---------------- SETUP ---------------- void setup() { Serial.begin(115200); unsigned long startWait = millis(); while (!Serial && (millis() - startWait < 3000)); Serial.println("\n=========================================="); Serial.println(" ESP-NOW CALL-AND-RESPONSE SYSTEM "); Serial.println("=========================================="); #ifdef IS_NODE_A MY_ID = 0x01; PEER_ID = 0x02; BUZZER_PIN = D2; Serial.println("[CONFIG] Role: NODE A (Initiator) | Buzzer: Pin D2"); #else MY_ID = 0x02; PEER_ID = 0x01; BUZZER_PIN = D10; Serial.println("[CONFIG] Role: NODE B (Receiver) | Buzzer: Pin D10"); #endif WiFi.mode(WIFI_STA); // Force Wi-Fi hardware to Channel 1 esp_wifi_set_promiscuous(true); esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(false); if (esp_now_init() != ESP_OK) { Serial.println("[FATAL] ESP-NOW Initialization Failed!"); return; } esp_now_register_recv_cb(OnDataRecv); esp_now_register_send_cb(OnDataSent); esp_now_peer_info_t peerInfo = {}; memcpy(peerInfo.peer_addr, peerMac, 6); peerInfo.channel = 0; // 0 matches current system Wi-Fi channel dynamically peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println("[ESP-NOW] Failed to add broadcast peer!"); } randomSeed(analogRead(0)); Serial.println("[SYSTEM] Setup complete. Ready."); } // ---------------- LOOP ---------------- void loop() { static unsigned long lastPingTime = 0; #ifdef IS_NODE_A // 🔄 THE LOOP-KICKER // If we are Node A, and Node B hasn't responded yet, fire a fresh // invite ping every 3 seconds so the loop never breaks at startup. if (!conversationStarted && (millis() - lastPingTime > 3000)) { lastPingTime = millis(); Serial.println("\n[NODE A] Standing by... Sending invite ping to Node B..."); sendMessage(PEER_ID, 880, 200); } #endif }