A Radar for Under 400 Rubles: Based on ESP32 and Wi-Fi

A hands-on guide to building a motion-detection radar using ESP32 microcontrollers and Wi-Fi RSSI signals, capable of detecting even hand movements in a room, with email notifications and full firmware code included.

Introduction

Wi-Fi technology contains capabilities that most people never use. In this article, we'll explore how to turn a cheap ESP32 microcontroller into a motion detection radar using nothing but built-in wireless signals — no external sensors required.

Key Technology: CSI and RSSI

Channel State Information (CSI)

Channel State Information is radio channel metadata that allows you to assess changes in the surrounding environment. CSI parameters include:

  • Signal amplitude
  • Phase shift
  • Subcarrier data

While CSI offers the most detailed data, its implementation proved problematic in practice, so I pivoted to a simpler approach.

Received Signal Strength Indicator (RSSI)

Since CSI implementation failed, the solution uses RSSI — measurements of received signal strength in decibel-milliwatts (dBm). Despite being a simpler metric, the results were surprisingly impressive.

System Capabilities

During my tests, in any point of the room (approximately 3.5 x 5.5 meters), it was enough not just to take a step, but even to change hand position for the system to detect it. The sensitivity exceeded all expectations.

The system also supports email notifications via Mail.ru when movement is detected.

Hardware Considerations

Antenna Orientation

  • ESP32 antenna polarization is horizontal
  • The device must be oriented vertically for optimal detection
  • Horizontal placement on a table significantly reduces responsiveness

Detection Directionality

The antenna exhibits a figure-eight directional response, with optimal sensitivity within approximately +/-45 degrees relative to the board's vertical plane. This means the sensor has clear "blind spots" and "hot zones" that you need to account for during placement.

Signal Reflection

Movement detection works through multipath propagation: the receiver captures not only the signal that directly "shines" on it, but also all other signals reflected from objects and walls. This is what makes the system sensitive enough to detect subtle movements.

Mail Configuration for Notifications

To receive email alerts, users must:

  1. Enable external program access in Mail.ru settings
  2. Generate an application-specific password
  3. Configure SMTP credentials in the code

Complete Firmware Code

/*Created with the help of AI DeepSeek.
Code is provided without any guarantees of functionality.*/

#include <WiFi.h>
#include <ESP_Mail_Client.h>
#include <time.h>

// WiFi access point settings
const char* SSID = "";
const char* PASSWORD = "";

// SMTP settings (Mail.ru)
#define SMTP_HOST "smtp.mail.ru"
#define SMTP_PORT 465
#define EMAIL_SENDER "sender email address"
#define EMAIL_PASSWORD "application password from mail.ru"
#define EMAIL_RECIPIENT "recipient email address"

#define NTP_SERVER "pool.ntp.org"
#define GMT_OFFSET_SEC 3 * 3600 // +3 for Moscow

// Detector parameters
const float BASE_THRESHOLD = 1.2f;
const int SAMPLE_INTERVAL = 200;
const unsigned long EMAIL_COOLDOWN = 30000;

// Global variables
SMTPSession mailSession;
unsigned long lastEmailTime = 0;
float baselineRSSI = -60.0f;
bool isCalibrated = false;

void setup() {
  Serial.begin(115200);
  Serial.println("\n=== Wi-Fi Motion Detector ===");
  
  connectToWiFi();
  configTime(GMT_OFFSET_SEC, 0, NTP_SERVER);
  calibrateSensor();
}

void loop() {
  float currentRSSI = readFilteredRSSI();
  float deviation = abs(currentRSSI - baselineRSSI);
  float threshold = max(BASE_THRESHOLD, abs(baselineRSSI * 0.03f));

  Serial.print("RSSI: ");
  Serial.print(currentRSSI, 1);
  Serial.print(" dB | Dev: ");
  Serial.print(deviation, 1);
  Serial.print("/");
  Serial.print(threshold, 1);
  Serial.println(" dB");

  if (deviation > threshold && isCalibrated) {
    handleMotionDetected(deviation, currentRSSI);
    baselineRSSI = baselineRSSI * 0.7f + currentRSSI * 0.3f;
  } else {
    baselineRSSI = baselineRSSI * 0.98f + currentRSSI * 0.02f;
  }

  if (WiFi.status() != WL_CONNECTED) connectToWiFi();
  delay(SAMPLE_INTERVAL);
}

void connectToWiFi() {
  if (WiFi.status() == WL_CONNECTED) return;
  
  Serial.print("Connecting to ");
  Serial.print(SSID);
  WiFi.begin(SSID, PASSWORD);
  
  byte attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nSuccess! RSSI: " + String(WiFi.RSSI()) + " dB");
  } else {
    Serial.println("\nConnection error!");
  }
}

void calibrateSensor() {
  Serial.println("Calibrating... Don't move for 5 sec");
  float sum = 0;
  for (byte i = 0; i < 20; i++) {
    sum += WiFi.RSSI();
    delay(250);
  }
  baselineRSSI = sum / 20.0f;
  isCalibrated = true;
  Serial.print("Baseline level: ");
  Serial.println(baselineRSSI, 1);
}

void handleMotionDetected(float deviation, float rssi) {
  Serial.println(">>> MOTION DETECTED! <<<");
  
  if (millis() - lastEmailTime > EMAIL_COOLDOWN) {
    if (sendAlert(deviation, rssi)) {
      lastEmailTime = millis();
    }
  }
}

bool sendAlert(float deviation, float rssi) {
  ESP_Mail_Session config;
  config.server.host_name = SMTP_HOST;
  config.server.port = SMTP_PORT;
  config.login.email = EMAIL_SENDER;
  config.login.password = EMAIL_PASSWORD;

  SMTP_Message message;
  message.sender.name = "ESP32 Motion Alert";
  message.sender.email = EMAIL_SENDER;
  message.subject = "Motion detected";
  message.addRecipient("User", EMAIL_RECIPIENT);
  
  struct tm timeinfo;
  String timeStr = getLocalTime(&timeinfo) ? 
    String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec) : "N/A";
  
  message.text.content = ("Details:\n"
    "• RSSI: " + String(rssi, 1) + " dB\n" +
    "• Deviation: " + String(deviation, 1) + " dB\n" +
    "• Time: " + timeStr).c_str();

  if (!mailSession.connect(&config) || !MailClient.sendMail(&mailSession, &message)) {
    Serial.println("Send error!");
    return false;
  }

  Serial.println("Email sent at " + timeStr);
  return true;
}

float readFilteredRSSI() {
  static float filtered = WiFi.RSSI();
  filtered = filtered * 0.6f + WiFi.RSSI() * 0.4f;
  return filtered;
}

Future: Full Radar with Four ESP32 Units

The author proposes using four ESP32 units (two transmitter-receiver pairs) to create a complete radar system capable of:

  • Distance measurement
  • X-Y coordinate tracking
  • Approximate 10-30 cm accuracy
  • Full short-range radar for under 1,600 rubles

Practical example: you could radar-track a cat that wants to jump on the kitchen table at night and spray it with water from an automated sprayer.

Conclusion

This solution demonstrates practical Wi-Fi-based motion detection without specialized hardware. It's immune to lighting and temperature variations, and its applications extend beyond simple motion sensors to directional tracking systems. All you need is an ESP32 board costing under 400 rubles.

FAQ

What is this article about in one sentence?

This article explains the core idea in practical terms and focuses on what you can apply in real work.

Who is this article for?

It is written for engineers, technical leaders, and curious readers who want a clear, implementation-focused explanation.

What should I read next?

Use the related articles below to continue with closely connected topics and concrete examples.