Community project

WiFi Monkey Deterrent Speaker

Photo of WiFi Monkey Deterrent Speaker
Generated with AI

Saurabh Save

Published September 28, 2026

This WiFi-enabled speaker system deters monkeys and other wildlife by playing deterrent sounds on demand or on a schedule. Built around an ESP32 microcontroller, it combines offline audio playback via a DFPlayer Mini module with a weatherproof 12V horn speaker, powered by a buck converter and Class-D amplifier for reliable outdoor operation.

The guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for building the deterrent into a weatherproof enclosure. Firmware with WiFi control, a local web interface, and cooldown logic is included, allowing remote triggering of up to four pre-loaded deterrent sounds from a microSD card, plus a physical test button for on-site verification.

Wiring diagram

Wiring diagram for WiFi Monkey Deterrent Speaker
Loading diagram…

Gather all the parts

QtyComponent
1

DFPlayer Mini MP3 Audio Module

Small UART-controlled MP3 playback module with a microSD card slot and onboard audio DAC/amplifier interface. The MCU controls playback over serial while speaker/DAC pins connect to the audio output path.

1

Push Button

Momentary push button switch

1

12V Barrel-Jack Adapter

12 V / 2 A wall adapter with a 5.5 mm / 2.1 mm barrel jack. Used to power motor drivers, LED strips, or boards that need a higher rail.

1

MP1584 Buck Converter

Adjustable buck (step-down) DC-DC converter, 4.5-28 V in -> 0.8-20 V out, ~3 A. Configured to 5 V to step a 9 V / 12 V supply or battery pack down to the board's 5 V rail.

1

12 V Class-D Mono Audio Amplifier Module

A ready-built audio amplifier module that boosts the MP3 player’s line-level signal strongly enough to drive the outdoor horn speaker.

1

12 V / 8 Ω Weatherproof Horn Speaker

A weatherproof 8-ohm horn speaker for projecting the amplified deterrent sound across an outdoor garden.

1

1 kΩ Series Resistor

1 kΩ

A resistor that protects the DFPlayer UART input from the ESP32’s signal and reduces noise on that connection.

1

microSD Card

A removable memory card that holds the locally stored MP3 deterrent sounds for the DFPlayer module.

Assemble it in 7 steps

1. Prepare the weatherproof enclosure

Choose an IP65-or-better plastic enclosure with cable glands. Mount the ESP32, DFPlayer, 5 V converter, and amplifier inside it on standoffs, with the amplifier where it can shed heat without exposing its circuit board to rain.

  • Put the enclosure under an eave if possible, even though it is weatherproof.
  • Use downward-facing cable glands so water cannot run along a cable into the box.
  • Do not put mains-voltage wiring inside this enclosure unless a qualified electrician provides the protected mains connection; use a ready-made approved 12 V DC adapter instead.

2. Set the 5 V converter before connecting electronics

With the 12 V adapter unplugged, connect supply_12v_1 +12V to buck_5v_1 VIN and supply_12v_1 GND to buck_5v_1 GND. Plug in the adapter and adjust the small converter screw until a meter reads exactly 5.0 V between buck_5v_1 VOUT and GND. Unplug the adapter again.

  • Use a 12 V adapter rated for at least 3 A so the horn amplifier has enough current.
  • Mark the converter’s adjusted screw with paint or nail varnish so vibration cannot move it.
  • Do not connect the ESP32 or DFPlayer before checking the converter output; a wrongly adjusted converter can damage 5 V electronics.

3. Wire the shared power safely

Connect supply_12v_1 +12V to amp_12v_1 VCC and to buck_5v_1 VIN (12 V power). Connect supply_12v_1 GND, amp_12v_1 GND, and buck_5v_1 GND together (ground). Connect buck_5v_1 VOUT to the ESP32 5V/VIN pin and dfplayer_1 VCC (5 V power). Connect the ESP32 GND to dfplayer_1 GND and the same shared ground (ground).

  • Use adequately sized wire for the 12 V amplifier and horn path; thin breadboard jumper wire is not suitable for the speaker power circuit.
  • Keep the amplifier’s 12 V and speaker wiring away from the ESP32 antenna where practical.
  • Make sure the 12 V positive wire never touches the 5 V output; putting 12 V into the ESP32 or DFPlayer will damage it.

4. Fit the offline sound card and audio lead

Format audio_card_1 as FAT32 on a computer. Make a folder named mp3 and place at least four MP3 files inside named 0001.mp3, 0002.mp3, 0003.mp3, and 0004.mp3; use varied, lawful deterrent recordings and keep each one short. Insert the card into dfplayer_1. Connect dfplayer_1 DAC_R to amp_12v_1 AUDIO_IN (audio signal).

  • Use sound files you have permission to use, and set their volume consistently before copying them.
  • The device chooses one numbered file at random for each accepted trigger.
  • Do not connect the DFPlayer’s SPK1 and SPK2 terminals to the amplifier input; use DAC_R only, because SPK terminals are amplified speaker outputs.

5. Wire the ESP32 control leads

Connect dfplayer_1 TX to ESP32 GPIO16 (data). Connect ESP32 GPIO17 to rx_resistor_1 END1, then rx_resistor_1 END2 to dfplayer_1 RX (data); the 1 kΩ resistor protects the DFPlayer input from the ESP32’s 3.3 V signal edge. Connect one test_button_1 terminal to ESP32 GPIO27 and its other terminal to GND (manual test signal and ground).

  • The push button can be a sealed momentary outdoor push button mounted through the enclosure wall.
  • Keep these low-voltage signal wires short and away from the amplifier speaker output wires.
  • Do not wire the button to 5 V; GPIO27 is meant to be connected only to ground when the button is pressed.

6. Connect and mount the outdoor horn

Connect amp_12v_1 SPK+ to horn_speaker_1 + and amp_12v_1 SPK- to horn_speaker_1 - (speaker signals). Mount the weatherproof horn outside, pointing down across the garden rather than at homes, paths, or people. Seal the cable entry with a properly tightened cable gland.

  • Mount the horn high enough that people cannot easily touch it, and angle it slightly down so rain does not collect in its mouth.
  • Start with the amplifier gain low, then raise it only enough to cover the intended garden area.
  • A horn at close range can injure hearing. Keep people clear when testing, never aim it toward a neighbour, and follow local noise and wildlife rules.

7. Seal, configure, and test locally

Close the enclosure only after checking every wire. Power the unit from supply_12v_1. On first power-up, join the device’s temporary Wi-Fi network named GardenDeterrent-Setup using password change-me-now, open its local setup page at 192.168.4.1, and enter the garden router Wi-Fi details. After it restarts, the camera computer can send an HTTP POST to http://monkey-deterrent.local/trigger with form field key=replace-this-local-key. Press test_button_1 once to test without the camera.

  • Change the two example passwords/keys in the firmware before field use; the trigger remains local to the garden Wi-Fi and does not use the internet.
  • After any trigger, the unit rejects further triggers for 60 seconds so it cannot repeatedly blast while an animal remains present.
  • Test at a safe distance with the horn aimed away from people and animals you do not intend to deter; loud sound can disturb neighbours and pets.

Review all connections

1. Connections between "supply_12v_1" and "ESP32"

Functionsupply_12v_1ESP32
groundGNDGND
power+12VVIN

2. Connections between "buck_5v_1" and "ESP32"

Functionbuck_5v_1ESP32
powerVOUT5V
groundGNDGND
powerVINVIN

3. Connections between "amp_12v_1" and "ESP32"

Functionamp_12v_1ESP32
powerVCCVIN
groundGNDGND
dataSPK+ → 12 V / 8 Ω Weatherproof Horn Speaker +EXT
dataSPK- → 12 V / 8 Ω Weatherproof Horn Speaker -EXT

4. Connections between "dfplayer_1" and "ESP32"

Functiondfplayer_1ESP32
powerVCC5V
groundGNDGND
uartTXGPIO 16
analogDAC_R → 12 V Class-D Mono Audio Amplifier Module AUDIO_INEXT

5. Connections between "rx_resistor_1" and "ESP32"

Functionrx_resistor_1ESP32
dataEND1GPIO 17
dataEND2 → DFPlayer Mini MP3 Audio Module RXEXT

6. Connections between "test_button_1" and "ESP32"

Functiontest_button_1ESP32
digitalSIGNALGPIO 27
groundGNDGND

Deploy the firmware

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Preferences.h>
#include <DFRobotDFPlayerMini.h>


// Forward declarations
bool cooldownActive();
String statusJson();
bool playDeterrent(const char *source);
void handleTrigger();
void handleStatus();
void handleSetupForm();
void handleSaveWifi();
void startSetupAccessPoint();
bool connectToLocalWifi();
void setupNetwork();

constexpr int DFPLAYER_RX_PIN = 16;  // DFPlayer TX -> ESP32 RX
constexpr int DFPLAYER_TX_PIN = 17;  // ESP32 TX -> 1k resistor -> DFPlayer RX
constexpr int TEST_BUTTON_PIN = 27;
constexpr uint32_t COOLDOWN_MS = 60000UL;
constexpr uint8_t FIRST_TRACK = 1;
constexpr uint8_t LAST_TRACK = 4;
constexpr uint8_t PLAYBACK_VOLUME = 26;  // DFPlayer range: 0 to 30

const char *SETUP_AP_NAME = "GardenDeterrent-Setup";
const char *SETUP_AP_PASSWORD = "change-me-now";
const char *TRIGGER_KEY = "replace-this-local-key";

HardwareSerial dfSerial(2);
DFRobotDFPlayerMini player;
WebServer server(80);
Preferences preferences;

bool playerReady = false;
bool wasButtonPressed = false;
uint32_t lastTriggerMs = 0;
String stationSsid;
String stationPassword;

bool cooldownActive() {
  return lastTriggerMs != 0 && (uint32_t)(millis() - lastTriggerMs) < COOLDOWN_MS;
}

String statusJson() {
  String json = "{\"ready\":" + String(playerReady ? "true" : "false");
  json += ",\"cooldown\":" + String(cooldownActive() ? "true" : "false");
  json += ",\"cooldown_remaining_seconds\":";
  if (cooldownActive()) {
    json += String((COOLDOWN_MS - (millis() - lastTriggerMs) + 999UL) / 1000UL);
  } else {
    json += "0";
  }
  json += ",\"ip\":\"" + WiFi.localIP().toString() + "\"}";
  return json;
}

bool playDeterrent(const char *source) {
  if (!playerReady) {
    server.send(503, "application/json", "{\"accepted\":false,\"reason\":\"audio_player_not_ready\"}");
    return false;
  }
  if (cooldownActive()) {
    server.send(429, "application/json", "{\"accepted\":false,\"reason\":\"cooldown_active\"}");
    return false;
  }

  uint8_t track = random(FIRST_TRACK, LAST_TRACK + 1);
  player.playMp3Folder(track);
  lastTriggerMs = millis();
  Serial.printf("Triggered by %s; playing /mp3/%04u.mp3\n", source, track);
  return true;
}

void handleTrigger() {
  String suppliedKey = server.hasArg("key") ? server.arg("key") : "";
  if (suppliedKey != TRIGGER_KEY) {
    server.send(403, "application/json", "{\"accepted\":false,\"reason\":\"invalid_key\"}");
    return;
  }
  if (playDeterrent("network")) {
    server.send(202, "application/json", "{\"accepted\":true,\"cooldown_seconds\":60}");
  }
}

void handleStatus() {
  server.send(200, "application/json", statusJson());
}

void handleSetupForm() {
  const char page[] =
      "<!doctype html><html><body><h2>Garden deterrent Wi-Fi setup</h2>"
      "<p>This setup page is local to the device and never uses the internet.</p>"
      "<form method='POST' action='/save'><label>Garden Wi-Fi name <input name='ssid' required></label><br>"
      "<label>Garden Wi-Fi password <input type='password' name='password' required></label><br>"
      "<button type='submit'>Save and restart</button></form></body></html>";
  server.send(200, "text/html", page);
}

void handleSaveWifi() {
  if (!server.hasArg("ssid") || !server.hasArg("password") || server.arg("ssid").length() == 0) {
    server.send(400, "text/plain", "Wi-Fi name and password are required.");
    return;
  }
  preferences.begin("deterrent", false);
  preferences.putString("ssid", server.arg("ssid"));
  preferences.putString("password", server.arg("password"));
  preferences.end();
  server.send(200, "text/html", "Saved. The device will restart now and join the local garden Wi-Fi.");
  delay(1000);
  ESP.restart();
}

void startSetupAccessPoint() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(SETUP_AP_NAME, SETUP_AP_PASSWORD);
  server.on("/", HTTP_GET, handleSetupForm);
  server.on("/save", HTTP_POST, handleSaveWifi);
  server.on("/status", HTTP_GET, handleStatus);
  server.begin();
  Serial.print("Setup hotspot IP: ");
  Serial.println(WiFi.softAPIP());
}

bool connectToLocalWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.setHostname("monkey-deterrent");
  WiFi.begin(stationSsid.c_str(), stationPassword.c_str());
  Serial.print("Connecting to saved local Wi-Fi");
  uint32_t started = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - started < 20000UL) {
    delay(250);
    Serial.print('.');
  }
  Serial.println();
  return WiFi.status() == WL_CONNECTED;
}

void setupNetwork() {
  preferences.begin("deterrent", true);
  stationSsid = preferences.getString("ssid", "");
  stationPassword = preferences.getString("password", "");
  preferences.end();

  if (stationSsid.length() == 0 || !connectToLocalWifi()) {
    Serial.println("No usable saved Wi-Fi; starting local setup hotspot.");
    startSetupAccessPoint();
    return;
  }

  if (MDNS.begin("monkey-deterrent")) {
    MDNS.addService("http", "tcp", 80);
  }
  server.on("/", HTTP_GET, []() {
    server.send(200, "text/plain", "Garden deterrent is online. POST /trigger with key=...; GET /status.");
  });
  server.on("/trigger", HTTP_POST, handleTrigger);
  server.on("/status", HTTP_GET, handleStatus);
  server.begin();
  Serial.print("Local network IP: ");
  Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  pinMode(TEST_BUTTON_PIN, INPUT_PULLUP);
  randomSeed(esp_random());

  dfSerial.begin(9600, SERIAL_8N1, DFPLAYER_RX_PIN, DFPLAYER_TX_PIN);
  if (player.begin(dfSerial, true, true)) {
    playerReady = true;
    player.volume(PLAYBACK_VOLUME);
    Serial.println("DFPlayer ready.");
  } else {
    Serial.println("DFPlayer not found; check its 5 V power, microSD card, and UART wires.");
  }
  setupNetwork();
}

void loop() {
  server.handleClient();

  bool pressed = digitalRead(TEST_BUTTON_PIN) == LOW;
  if (pressed && !wasButtonPressed) {
    if (playerReady && !cooldownActive()) {
      uint8_t track = random(FIRST_TRACK, LAST_TRACK + 1);
      player.playMp3Folder(track);
      lastTriggerMs = millis();
      Serial.printf("Manual test; playing /mp3/%04u.mp3\n", track);
    }
  }
  wasButtonPressed = pressed;
  delay(10);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik