/*  generated by ziphu for our prototype boards first useful deployment application into the market locally

 * ESP32-S3 Ultrasonic Rodent Repeller

 * Features:

 * 1. Generates 20kHz - 60kHz ultrasonic sweep using LEDC (Hardware PWM).

 * 2. Modulates the signal (Pulses it) at a lower frequency to prevent habituation.

 * 3. Randomizes the sweep speed and center frequency slightly.

 */


// --- Configuration ---

const int PWM_PIN = 5;          // GPIO pin connected to the transducer (+)

const int PWM_CHANNEL = 0;      // LEDC channel (0-15)


// Frequency Settings

const int FREQ_MIN = 20000;     // 20 kHz (Lower bound of rodent hearing)

const int FREQ_MAX = 60000;     // 60 kHz (Upper effective range)


// Modulation (Pulsing) Settings

// This determines how fast the sound "blinks" on and off (e.g., 4 Hz = 4 times a second)

const int PULSE_RATE_MIN = 2;   // Hz

const int PULSE_RATE_MAX = 10;  // Hz


// Timing

unsigned long previousMillis = 0;

unsigned long sweepPrevMillis = 0;


// State Variables

int currentFreq = FREQ_MIN;

int freqDirection = 1;          // 1 for going up, -1 for going down

int pulseInterval = 250;        // ms (starts at 4Hz)

bool isSoundOn = true;


void setup() {

  Serial.begin(115200);

  

  // Configure LEDC for the PWM channel

  // 13-bit resolution gives us steps = 8192. 

  // At 60kHz, this is still plenty of resolution.

  ledcSetup(PWM_CHANNEL, currentFreq, 13); 

  

  // Attach the channel to the GPIO pin

  ledcAttachPin(PWM_PIN, PWM_CHANNEL);

  

  // Random Seed using floating pin A0 (if available) or time

  // If your board doesn't have A0, just use randomSeed(analogRead(0));

  // On S3, usually randomSeed(millis()) is fine if no noise pin is connected.

  randomSeed(analogRead(0)); 


  Serial.println("Rodent Repeller Started");

}


void loop() {

  unsigned long currentMillis = millis();


  // --- 1. Handle Frequency Sweeping (The "Screech") ---

  // We change the frequency every X milliseconds to sweep through the range

  int sweepSpeed = random(10, 50); // Change freq every 10 to 50ms (makes it erratic)

  

  if (currentMillis - sweepPrevMillis >= sweepSpeed) {

    sweepPrevMillis = currentMillis;


    // Update frequency

    currentFreq += (100 * freqDirection); // Jump 100Hz per step


    // Reverse direction at limits

    if (currentFreq >= FREQ_MAX) {

      currentFreq = FREQ_MAX;

      freqDirection = -1;

    } else if (currentFreq <= FREQ_MIN) {

      currentFreq = FREQ_MIN;

      freqDirection = 1;

      

      // When we hit the bottom, maybe randomize the pulse rate for variety

      randomizePulseRate();

    }


    // Update the Hardware PWM

    ledcWriteTone(PWM_CHANNEL, currentFreq);

  }


  // --- 2. Handle Modulation / Pulsing (The "Stutter") ---

  // We toggle the sound on and off to mimic erratic noise

  if (currentMillis - previousMillis >= pulseInterval) {

    previousMillis = currentMillis;

    

    isSoundOn = !isSoundOn;


    if (isSoundOn) {

      ledcWrite(PWM_CHANNEL, 4095); // 50% Duty Cycle (Max volume for square wave)

    } else {

      ledcWrite(PWM_CHANNEL, 0);    // Mute

    }

  }

}


void randomizePulseRate() {

  // Calculate a new random pulse interval in milliseconds based on Hz

  int randomHz = random(PULSE_RATE_MIN, PULSE_RATE_MAX);

  pulseInterval = 1000 / randomHz;

  

  // Debug output (optional)

  // Serial.print("New Pulse Rate: ");

  // Serial.print(randomHz);

  // Serial.println(" Hz");

}