ESP32 W.R.C

Here is an image of a self-contained “Brain Box” for the ESP32 W.R.C

Here is an image to help you with your wiring.

And the code you will need to upload to your ESP32

#include <WiFi.h>
#include <WebServer.h>

// ===== Pins & PWM Setup =====
#define MOTOR_PIN 14
#define H1 33
#define H2 34
#define MOTOR_CHANNEL 0
#define SERVO_PIN 2
#define SERVO_CHANNEL 1

// Motor PWM
#define MOTOR_FREQ 5000
#define MOTOR_RES 12  // 0-4095

// Servo PWM
#define SERVO_FREQ 50
#define SERVO_RES 16  // 0-65535

// ===== WiFi Access Point =====
const char* ssid = "YTK_UNIT00";
const char* password = "12345678";

WebServer server(80);

// ===== Failsafe =====
unsigned long lastCommandTime = 0;
const unsigned long FAILSAFE_TIMEOUT = 5000;  // 5 seconds

// ===== Helper: Map servo angle to duty cycle =====
uint32_t angleToDuty(int angle) {
  int us = map(angle, 0, 180, 500, 2500);   // 500–2500 µs
  uint32_t maxDuty = (1 << SERVO_RES) - 1;
  return (uint32_t)((float)us / 20000.0 * maxDuty);
}

// ===== HTML Page =====
void handleRoot() {
  server.send(200, "text/html", R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 Motor & Servo Control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  body { font-family: sans-serif; margin: 40px; }
  .container { display: flex; justify-content: space-between; }
  .control { width: 48%; text-align: center; }
  .vertical-slider {
    -webkit-appearance: slider-vertical;
    width: 30px;
    height: 300px;
    padding: 0 5px;
  }
  .horizontal-slider { width: 80%; }
  h2 { margin-bottom: 30px; }
</style>
</head>
<body>
<h2>ESP32 Motor & Servo Control</h2>
<div class="container">
  <div class="control">
    <h3>Motor Speed</h3>
    <input type="range" min="0" max="255" value="0" class="vertical-slider" id="speedSlider" oninput="updateSpeed(this.value)">
    <p>Speed: <span id="speedVal">0</span>%</p>
  </div>
  <div class="control">
    <h3>Servo Angle</h3>
    <input type="range" min="0" max="180" value="90" class="horizontal-slider" id="angleSlider" oninput="updateAngle(this.value)">
    <p>Angle: <span id="angleVal">90</span>°</p>
  </div>
</div>

<script>
  function updateSpeed(val) {
    document.getElementById("speedVal").innerText = Math.round(val / 255 * 100);
    fetch("/setSpeed", {
      method: "POST",
      headers: {"Content-Type": "application/x-www-form-urlencoded"},
      body: "val=" + val
    });
  }
  function updateAngle(val) {
    document.getElementById("angleVal").innerText = val;
    fetch("/setAngle", {
      method: "POST",
      headers: {"Content-Type": "application/x-www-form-urlencoded"},
      body: "angle=" + val
    });
  }
</script>
</body>
</html>
)rawliteral");
}

// ===== Motor Handler =====
void handleSetSpeed() {
  if (server.hasArg("val")) {
    int sliderVal = constrain(server.arg("val").toInt(), 0, 255);
    int speed = map(sliderVal, 0, 255, 0, 16000);

    digitalWrite(H1, HIGH);
    digitalWrite(H2, LOW);
    ledcWrite(MOTOR_CHANNEL, speed);
    lastCommandTime = millis();

    float percent = (sliderVal / 255.0) * 100.0;
    Serial.printf("[CMD] Motor speed set: %d (%.1f%%)\n", speed, percent);
    server.send(200, "text/plain", String(percent, 1) + "%");
  } else {
    server.send(400, "text/plain", "Missing val");
  }
}

// ===== Servo Handler =====
void handleSetAngle() {
  if (server.hasArg("angle")) {
    int angle = constrain(server.arg("angle").toInt(), 0, 180);
    uint32_t duty = angleToDuty(angle);
    ledcWrite(SERVO_CHANNEL, duty);
    lastCommandTime = millis();

    Serial.printf("[CMD] Servo angle set: %d° (duty=%u)\n", angle, duty);
    server.send(200, "text/plain", String(angle));
  } else {
    server.send(400, "text/plain", "Missing angle");
  }
}

// ===== Setup =====
void setup() {
  Serial.begin(115200);
  Serial.println("Starting Access Point...");

  WiFi.softAP(ssid, password);
  Serial.println("AP Started!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.softAPIP());

  // Motor setup
  ledcSetup(MOTOR_CHANNEL, MOTOR_FREQ, MOTOR_RES);
  ledcAttachPin(MOTOR_PIN, MOTOR_CHANNEL);
  ledcWrite(MOTOR_CHANNEL, 0);
  pinMode(H1, OUTPUT);
  pinMode(H2, OUTPUT);
  digitalWrite(H1, LOW);
  digitalWrite(H2, LOW);

  // Servo setup
  ledcSetup(SERVO_CHANNEL, SERVO_FREQ, SERVO_RES);
  ledcAttachPin(SERVO_PIN, SERVO_CHANNEL);
  ledcWrite(SERVO_CHANNEL, angleToDuty(90));

  // Server routes
  server.on("/", HTTP_GET, handleRoot);
  server.on("/setSpeed", HTTP_POST, handleSetSpeed);
  server.on("/setAngle", HTTP_POST, handleSetAngle);
  server.begin();
  Serial.println("Server started!");

  lastCommandTime = millis();  // failsafe init
}

// ===== Loop =====
void loop() {
  server.handleClient();

  // Failsafe
  if (millis() - lastCommandTime > FAILSAFE_TIMEOUT) {
    ledcWrite(MOTOR_CHANNEL, 0);
    digitalWrite(H1, LOW);
    digitalWrite(H2, LOW);
    ledcWrite(SERVO_CHANNEL, angleToDuty(90));
    Serial.println("[FAILSAFE] Timeout: Motor stopped, Servo centered");
    lastCommandTime = millis();
  }
}

Finally here is the laser cutting file if you wish to cop the Brain Box design. Although I would recommend that you try and build one yourself.