#include <WiFi.h> // Necesario para WiFi.mode(WIFI_OFF)
#include <SPI.h> // Necesario para la comunicación con nRF24L01
#include <nRF24L01.h> // Headers específicos del nRF24L01
#include <RF24.h> // Librería principal RF24
// --- Constantes de Configuración ---
// Pines LEDs Indicadores
WIFI_LED_PINconst int = 2; // GPIO2 (LED integrado común en ESP32 DevKitC)
const int BT_LED_PIN = 4; // GPIO4 (Otro pin disponible)
// Pines nRF24L01 #1 (Asociado a "WiFi" en este sketch)
const int CE_PIN_WIFI = 15; // Chip Enable para Radio 1 - era el 15
const int CSN_PIN_WIFI = 5; // Chip Select Not (SPI SS) para Radio 1 (HSPI_CS) - era el 5
// Pines nRF24L01 #2 (Asociado a "BT" en este sketch)
const int CE_PIN_BT = 16; // Chip Enable para Radio 2 (RX2)
const int CSN_PIN_BT = 17; // Chip Select Not (SPI SS) para Radio 2 (TX2)
// Pines Pulsadores de Control
// IMPORTANTE: GPIO 34 y 35 son SOLO ENTRADA, no tienen PULLUP interno.
// Necesitarás resistencias pull-up externas (e.g., 10k Ohm conectadas a 3.3V)
// Si cambias a pines con pull-up interno (e.g., GPIO25, GPIO26), usa INPUT_PULLUP.
const int WIFI_SWITCH_PIN = 34; // Pulsador para controlar Radio 1 (Necesita PULL-UP EXTERNA)
const int BT_SWITCH_PIN = 35; // Pulsador para controlar Radio 2 (Necesita PULL-UP EXTERNA)
// Configuración nRF24L01
const byte NRF_ADDRESS[6] = "JAMM1"; // Dirección de transmisión (puede ser cualquiera, no es crítica aquí)
const rf24_pa_dbm_e NRF_PA_LEVEL = RF24_PA_MAX; // Potencia de transmisión (Máxima)
const uint8_t NRF_CHANNEL_MIN = 0; // Canal mínimo nRF24L01+
const uint8_t NRF_CHANNEL_MAX = 125; // Canal máximo nRF24L01+ (Bandwidth: 1MHz/canal)
const size_t NRF_PAYLOAD_SIZE = 32; // Tamaño máximo del payload para nRF24L01
// Configuración Debounce (Anti-rebote) para los pulsadores
const unsigned long DEBOUNCE_DELAY = 50; // Tiempo (ms) para considerar estable una lectura de botón
// --- Objetos Globales ---
// Usar el bus SPI por defecto (VSPI en la mayoría de ESP32 dev kits)
// SCK: GPIO18
// MISO: GPIO19
// MOSI: GPIO23
// SS/CSN se controlan manualmente por la librería RF24 con los pines CSN definidos.
RF24 radioWiFi(CE_PIN_WIFI, CSN_PIN_WIFI); // Objeto para el radio 1
RF24 radioBT(CE_PIN_BT, CSN_PIN_BT); // Objeto para el radio 2
// --- Variables Globales de Estado ---
bool wifiTransmissionActive = false; // Estado de transmisión para Radio 1
bool btTransmissionActive = false; // Estado de transmisión para Radio 2
// Variables para Debounce Pulsador "WiFi"
int lastWifiButtonState = HIGH; // Asume estado inicial no presionado (con pull-up)
unsigned long lastWifiDebounceTime = 0;
// Variables para Debounce Pulsador "BT"
int lastBtButtonState = HIGH; // Asume estado inicial no presionado (con pull-up)
unsigned long lastBtDebounceTime = 0;
// Canal actual de transmisión nRF24L01 (común para ambos si están activos)
uint8_t currentNrfChannel = NRF_CHANNEL_MIN;
// Buffer para los datos a enviar (payload)
char jammingPayload[NRF_PAYLOAD_SIZE];
// --- Prototipos de Funciones ---
bool initNRF24(RF24& radio, const char* radioName, int cePin, int csnPin);
void transmitRandomData(RF24& radio);
bool handleButtonToggle(int pin, bool& currentState, int& lastButtonState, unsigned long& lastDebounceTime);
// --- Función Setup: Se ejecuta una vez al inicio ---
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000); // Esperar un poco por el Serial Monitor
Serial.println("\n--- ESP32 Dual nRF24L01 Transmisor - Sketch Mejorado ---");
Serial.println("INFO: Transmite datos aleatorios en canales nRF24L01.");
Serial.println(" Simula ruido, NO interfiere eficazmente con WiFi/BT reales.");
Serial.println("IMPORTANTE: ¡Asegura resistencias PULL-UP externas en pines 34 y 35!");
// Configurar LEDs como salida
pinMode(WIFI_LED_PIN, OUTPUT);
pinMode(BT_LED_PIN, OUTPUT);
digitalWrite(WIFI_LED_PIN, LOW); // Apagados al inicio
digitalWrite(BT_LED_PIN, LOW);
// Configurar Pulsadores como entrada
// Recordatorio: SIN PULLUP interno para pines 34/35. Necesitan hardware externo.
pinMode(WIFI_SWITCH_PIN, INPUT);
pinMode(BT_SWITCH_PIN, INPUT);
// Si usas pines diferentes con pull-up interno, cambia a: pinMode(PIN, INPUT_PULLUP);
// Desactivar WiFi y Bluetooth del ESP32 para:
// 1. Liberar recursos (energía, CPU).
// 2. Evitar auto-interferencia con los nRF24L01 (que operan en la misma banda ISM de 2.4GHz).
Serial.print("Desactivando WiFi del ESP32... ");
if (WiFi.mode(WIFI_OFF)) {
Serial.println("OK.");
} else {
Serial.println("Fallo.");
}
// btStop(); // Generalmente no es necesario si no se inicializa Bluetooth explícitamente.
// La librería principal de ESP32 para BT es pesada, si no se incluye/usa, no consume recursos.
// Inicializar bus SPI
Serial.print("Inicializando SPI Bus (VSPI: SCK=18, MISO=19, MOSI=23)... ");
SPI.begin(); // Usa los pines por defecto para VSPI
Serial.println("OK.");
// Inicializar radios nRF24L01
bool wifiRadioOK = initNRF24(radioWiFi, "Radio#1(WiFi)", CE_PIN_WIFI, CSN_PIN_WIFI);
bool btRadioOK = initNRF24(radioBT, "Radio#2(BT)", CE_PIN_BT, CSN_PIN_BT);
wifiRadioOK = true;
btRadioOK= true;
// Comprobar si la inicialización fue exitosa para ambos radios
if (!wifiRadioOK || !btRadioOK) {
Serial.println("\nError Crítico: Fallo al inicializar uno o ambos radios nRF24L01.");
Serial.println("-> Verifica conexiones SPI (MOSI, MISO, SCK).");
Serial.println("-> Verifica conexiones CE y CSN de cada radio.");
Serial.println("-> Asegura alimentación estable de 3.3V para los nRF24L01.");
Serial.println("Sistema detenido. Parpadeo de LEDs indica error.");
// Bucle infinito de error con parpadeo alterno
while (true) {
digitalWrite(WIFI_LED_PIN, HIGH); digitalWrite(BT_LED_PIN, LOW); delay(300);
digitalWrite(WIFI_LED_PIN, LOW); digitalWrite(BT_LED_PIN, HIGH); delay(300);
}
}
// Inicializar generador de números aleatorios usando el generador de hardware del ESP32
Serial.print("Inicializando generador aleatorio (HW)... ");
esp_random();//devuelve un número aleatorio de 32 bits del generador de hardware.
randomSeed(esp_random());
Serial.println("OK.");
Serial.println("\nSetup completo. Sistema listo.");
Serial.println("Presiona los botones (Pines 34/35) para activar/desactivar la transmisión en cada radio.");
}
// --- Función Loop: Se ejecuta repetidamente ---
void loop() {
// 1. Leer y procesar los botones para cambiar los estados de transmisión
bool wifiStateChanged = handleButtonToggle(WIFI_SWITCH_PIN, wifiTransmissionActive, lastWifiButtonState, lastWifiDebounceTime);
bool btStateChanged = handleButtonToggle(BT_SWITCH_PIN, btTransmissionActive, lastBtButtonState, lastBtDebounceTime);
delay(3000);
// Imprimir estado si ha cambiado (opcional, útil para depuración)
if (wifiStateChanged) {
Serial.print("Transmisor WiFi (Radio 1) "); Serial.println(wifiTransmissionActive ? "ACTIVADO" : "DESACTIVADO");
}
if (btStateChanged) {
Serial.print("Transmisor BT (Radio 2) "); Serial.println(btTransmissionActive ? "ACTIVADO" : "DESACTIVADO");
}
Serial.print("wifi ---> ");Serial.println(wifiStateChanged);
Serial.print("Bluetooth ---> ");Serial.println(btStateChanged );
// 2. Realizar acciones de transmisión según el estado actual
bool channelNeedsUpdate = false; // Bandera para cambiar el canal solo si algo se transmitió
// Acción para el Transmisor "WiFi" (Radio 1)
if (wifiTransmissionActive) {
digitalWrite(WIFI_LED_PIN, HIGH); // Encender LED indicador
transmitRandomData(radioWiFi); // Transmitir paquete aleatorio
channelNeedsUpdate = true; // Marcar que el canal debe actualizarse
} else {
digitalWrite(WIFI_LED_PIN, LOW); // Apagar LED indicador
}
// Acción para el Transmisor "BT" (Radio 2)
if (btTransmissionActive) {
digitalWrite(BT_LED_PIN, HIGH); // Encender LED indicador
transmitRandomData(radioBT); // Transmitir paquete aleatorio
channelNeedsUpdate = true; // Marcar que el canal debe actualizarse
} else {
digitalWrite(BT_LED_PIN, LOW); // Apagar LED indicador
}
// 3. Cambiar el canal de nRF24L01 si al menos un transmisor estuvo activo
if (channelNeedsUpdate) {
// Avanzar al siguiente canal
currentNrfChannel++;
// Volver al inicio si se supera el máximo
if (currentNrfChannel > NRF_CHANNEL_MAX) {
currentNrfChannel = NRF_CHANNEL_MIN;
}
// Establecer el NUEVO canal en AMBOS radios para la SIGUIENTE transmisión.
// Esto mantiene los canales sincronizados si ambos están activos.
// Nota: setChannel() tiene una pequeña latencia.
radioWiFi.setChannel(currentNrfChannel);
radioBT.setChannel(currentNrfChannel);
// Descomentar para depuración de cambio de canal (puede ralentizar la transmisión)
// Serial.print("Cambiando radios al canal nRF: "); Serial.println(currentNrfChannel);
}
// 4. Pequeña pausa (Opcional)
// Un delay pequeño puede reducir la carga de la CPU y el consumo,
// pero también disminuye la tasa de paquetes transmitidos por segundo.
// Para máxima velocidad de transmisión, comentar o quitar el delay.
// delay(1); // Pausa de 1 milisegundo
}
// --- Implementación de Funciones Auxiliares ---
/**
* @brief Inicializa un módulo nRF24L01 para operar como transmisor.
* @param radio Referencia al objeto RF24 a inicializar.
* @param radioName Nombre descriptivo para los mensajes de log.
* @param cePin Pin CE conectado al módulo.
* @param csnPin Pin CSN (SPI SS) conectado al módulo.
* @return true si la inicialización fue exitosa, false en caso contrario.
*/
bool initNRF24(RF24& radio, const char* radioName, int cePin, int csnPin) {
Serial.print(" Inicializando nRF24L01 ("); Serial.print(radioName);
Serial.print(") en pines CE="); Serial.print(cePin); Serial.print(", CSN="); Serial.print(csnPin);
Serial.print("... ");
if (!radio.begin()) {
Serial.println("¡FALLO! Radio no detectado o no responde.");
return false;
}
// Configuración específica para transmisión continua/rápida:
radio.setChannel(currentNrfChannel); // Establecer canal inicial
radio.setPALevel(NRF_PA_LEVEL); // Máxima potencia de transmisión
radio.setDataRate(RF24_2MBPS); // Velocidad de datos alta (reduce alcance, aumenta paquetes/seg)
radio.openWritingPipe(NRF_ADDRESS); // Abrir pipe para escribir (la dirección no es crítica si no se usa ACK)
radio.stopListening(); // Poner explícitamente en modo transmisor (TX)
radio.setAutoAck(false); // Desactivar Auto Acknowledge (no esperamos respuesta, más rápido)
radio.setRetries(0, 0); // No reintentar envíos fallidos (0 reintentos, delay 0)
Serial.print("Configurado: Canal="); Serial.print(radio.getChannel());
Serial.print(", Potencia="); Serial.print(NRF_PA_LEVEL); // Nota: getPALevel() puede no devolver el enum exacto
Serial.print(", DataRate=2Mbps, AutoAck=OFF, Retries=0. ");
Serial.println("OK.");
return true;
}
/**
* @brief Genera datos aleatorios y los envía usando el radio nRF24L01 especificado.
* @param radio Referencia al objeto RF24 que debe transmitir.
*/
void transmitRandomData(RF24& radio) {
// Llenar el payload con bytes pseudo-aleatorios
for (size_t i = 0; i < NRF_PAYLOAD_SIZE; i++) {
jammingPayload[i] = (char)random(0, 256); // Genera un byte aleatorio (0-255)
}
// Enviar los datos. radio.write() es bloqueante por defecto (espera a que se complete el envío).
// Para este propósito (simulación de ruido), es suficientemente rápido.
// No se comprueba el valor de retorno porque AutoAck está desactivado,
// write() devolverá true rápidamente a menos que haya un fallo grave de hardware SPI.
radio.write(&jammingPayload, NRF_PAYLOAD_SIZE);
}
/**
* @brief Maneja la lógica de un pulsador con debounce para cambiar un estado booleano (toggle).
* Detecta la pulsación (flanco descendente si se usa PULLUP).
* @param pin El número de pin GPIO del pulsador.
* @param currentState Referencia a la variable booleana que se debe cambiar (toggle).
* @param lastButtonState Referencia al último estado estable leído del botón.
* @param lastDebounceTime Referencia al último tiempo en que se detectó un cambio inestable.
* @return true si el estado (currentState) fue cambiado en esta llamada, false si no.
*/
bool handleButtonToggle(int pin, bool& currentState, int& lastButtonState, unsigned long& lastDebounceTime) {
int reading = digitalRead(pin);
unsigned long currentMillis = millis();
bool stateChanged = false;
// Si la lectura actual es diferente del último estado estable,
// podría ser ruido o el inicio de una pulsación. Reiniciar el temporizador de debounce.
if (reading != lastButtonState) {
lastDebounceTime = currentMillis;
}
// Si ha pasado suficiente tiempo desde el último cambio detectado (debounce)
if ((currentMillis - lastDebounceTime) > DEBOUNCE_DELAY) {
// Y si el estado del botón es REALMENTE diferente al último estado estable guardado
if (reading != lastButtonState) {
lastButtonState = reading; // Actualizar el estado estable del botón
// Si el nuevo estado estable es PRESIONADO (LOW con PULLUP)
if (lastButtonState == LOW) {
currentState = !currentState; // Cambiar (toggle) el estado asociado
stateChanged = true; // Marcar que el estado ha cambiado
}
// No hacemos nada en el flanco ascendente (cuando se suelta el botón)
}
}
// Guardamos 'reading' en 'lastButtonState' SOLO después de que el debounce confirma un estado estable.
return stateChanged; // Devuelve si el estado principal (currentState) cambió
}