DIY ELECTRONICSTR

← All projects

Wi-Fi Scanner (ESP32 + LCD)

Beginner20 min

A pocket scanner for the Wi-Fi networks around you: the ESP32 scans for networks, sorts them from the strongest to the weakest and shows each one's name, its signal strength (in dBm and as bars) and whether it is locked on the LCD, one after another; the Serial Monitor gets the full list with channels and encryption. Plug it into a power bank and walk around the house to find where the router reaches best. It joins no network and needs no password. The same four wires as the Internet Clock; it teaches scanning for networks, sorting an array and laying out a screen line with snprintf.

Get it on Google Play

What you need

Wiring steps

  1. 1

    Connect the LCD's power (GND and VCC)

    Connect the LCD's power (GND and VCC)

    Do not put the ESP32 on the breadboard: a 30-pin board covers nearly all of it. In this project the board lies on the table and the LCD is connected straight to its pins with four female-female jumper wires. Hold the board with its USB socket facing you; the pin names are printed on the board next to the pins. On the 4-pin header of the I2C module on the back of the LCD, connect GND to the GND pin on the ESP32's right edge, just above 3V3, and VCC to the VIN pin at the bottom of the left edge. VIN gives 5V while the ESP32 is plugged into USB; the LCD's text is clear at 5V.

    • ESP32 GND (next to 3V3) → LCD GND (black wire)
    • ESP32 VIN (the USB's 5V) → LCD VCC (red wire)
  2. 2

    Connect the I2C wires (SDA and SCL)

    Connect the I2C wires (SDA and SCL)

    Connect the LCD's SDA pin to the ESP32's D21 pin and its SCL pin to D22. These are the pins the ESP32 uses for I2C; if you use other pins, change SDA_PIN and SCL_PIN in the code too. This code has no Wi-Fi name or password: the ESP32 joins no network, it only listens to what the networks around it broadcast. If you have built the Internet Clock, the circuit is the same; just upload the new code.

    • ESP32 D21 → LCD SDA (yellow wire)
    • ESP32 D22 → LCD SCL (green wire)

Arduino sketch

Download the sketch (.ino)Try it on Wokwi

Run the circuit in your browser before buying a part: start the sketch on Wokwi and watch the Serial Monitor.

// Install from the Library Manager:
// "LiquidCrystal I2C"
// Board: "ESP32 Dev Module"
#include <WiFi.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// The ESP32's I2C pins
const int SDA_PIN = 21;
const int SCL_PIN = 22;
// How long each network stays on (ms)
const unsigned long SHOW_EACH = 3000;
// At most this many networks are listed
const int MAX_NETWORKS = 40;
// A network that hides its name
const char* HIDDEN = "(hidden)";

// I2C address: mostly 0x27, some 0x3F
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Networks found, strongest first
int order[MAX_NETWORKS];
int found = 0;
// The next network to show
int next = 0;
unsigned long shownAt = 0;

// Signal strength (dBm) as 0-4 bars
int bars(int rssi) {
  if (rssi >= -55) return 4;
  if (rssi >= -67) return 3;
  if (rssi >= -75) return 2;
  if (rssi >= -85) return 1;
  return 0;
}

// The encryption's short name
const char* security(wifi_auth_mode_t mode) {
  switch (mode) {
    case WIFI_AUTH_OPEN: return "Open";
    case WIFI_AUTH_WEP: return "WEP";
    case WIFI_AUTH_WPA_PSK: return "WPA";
    case WIFI_AUTH_WPA2_PSK: return "WPA2";
    case WIFI_AUTH_WPA_WPA2_PSK: return "WPA/WPA2";
    case WIFI_AUTH_WPA3_PSK: return "WPA3";
    case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2/WPA3";
    default: return "Other";
  }
}

// The network's name, or HIDDEN
String networkName(int k) {
  String name = WiFi.SSID(k);
  if (name.length() == 0) name = HIDDEN;
  return name;
}

// Scans, then sorts strongest first
void scan() {
  lcd.clear();
  lcd.print("Scanning...");
  Serial.println("Scanning...");
  int count = WiFi.scanNetworks();
  found = constrain(count, 0, MAX_NETWORKS);
  for (int i = 0; i < found; i++) {
    order[i] = i;
  }
  // Sorting: each network moves ahead of
  // the weaker ones before it
  for (int i = 1; i < found; i++) {
    int k = order[i];
    int j = i - 1;
    while (j >= 0 &&
           WiFi.RSSI(order[j]) < WiFi.RSSI(k)) {
      order[j + 1] = order[j];
      j--;
    }
    order[j + 1] = k;
  }

  Serial.print(found);
  Serial.println(" networks found");
  for (int i = 0; i < found; i++) {
    int k = order[i];
    Serial.printf("%2d. %4d dBm  channel %2d  %s (%s)\n",
                  i + 1, WiFi.RSSI(k), WiFi.channel(k),
                  networkName(k).c_str(),
                  security(WiFi.encryptionType(k)));
  }
  next = 0;
  shownAt = millis();
}

// Writes one network: its name on top;
// rank, strength, bars and lock (*) below
void show(int i) {
  int k = order[i];
  int rssi = WiFi.RSSI(k);
  char barText[] = "    ";
  for (int b = 0; b < bars(rssi); b++) {
    barText[b] = '#';
  }
  bool locked =
      WiFi.encryptionType(k) != WIFI_AUTH_OPEN;
  char line[17];
  snprintf(line, sizeof line, "%2d/%-2d%4d %s %c",
           i + 1, found, rssi, barText,
           locked ? '*' : ' ');

  lcd.clear();
  lcd.print(networkName(k).substring(0, 16));
  lcd.setCursor(0, 1);
  lcd.print(line);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.init();
  lcd.backlight();
  // Only listens; joins no network
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  scan();
}

void loop() {
  if (found == 0) {
    lcd.setCursor(0, 1);
    lcd.print("None found");
    delay(SHOW_EACH);
    scan();
    return;
  }
  // The first one at once, then in turn
  if (next > 0 && millis() - shownAt < SHOW_EACH) {
    return;
  }
  // All shown: scan again
  if (next >= found) {
    scan();
    return;
  }
  show(next);
  next++;
  shownAt = millis();
}

What the Serial Monitor shows

SERIAL MONITOR · 115200 BAUD
Scanning...
4 networks found
 1.  -42 dBm  channel  6  HomeNet (WPA2)
 2.  -63 dBm  channel  1  TurkTelekom_ZX7K2 (WPA2/WPA3)
 3.  -77 dBm  channel 11  Neighbour (WPA2)
 4.  -88 dBm  channel  6  Cafe_Guest (Open)
Scanning...
4 networks found

If a wire is loose

SERIAL MONITOR · 115200 BAUD
Scanning...
0 networks found
Scanning...
0 networks found

What you will see

A one-time ESP32 set-up in the Arduino IDE: in File → Preferences, write https://espressif.github.io/arduino-esp32/package_esp32_index.json into "Additional boards manager URLs"; in Tools → Board → Boards Manager search for "esp32" and install "esp32 by Espressif Systems". Then pick "ESP32 Dev Module" and the ESP32's port under Tools → Board, and install "LiquidCrystal I2C" from the Library Manager (ignore its "AVR architecture" warning). Upload the code and open the Serial Monitor at 115200 baud. The LCD says "Scanning..." for a few seconds, then shows the networks it found, the strongest first, three seconds each: the network's name on the top line; on the bottom line its rank and the number of networks (1/4), the signal strength in dBm (-42), a four-bar strength meter and, at the end, a star (*) if the network is locked. When all have been shown it scans again; the Serial Monitor prints the full list with channels and encryption on every scan. The closer dBm is to zero, the stronger the signal: -50 and up is very good, down to -67 is enough for video, below -80 the connection may drop. The ESP32 only sees 2.4 GHz networks; a router's 5 GHz network does not show up. Letters such as ş, ğ or ı in a network's name show as odd signs on the LCD and correctly in the Serial Monitor. If the screen keeps saying "None found", there is no 2.4 GHz network around, or the board is inside a metal box; keep the end of the ESP32's module past its metal can, where the antenna is, in the open. If the LCD's light is on but there is no text, turn the blue trimmer on the I2C module with a small screwdriver; if it still shows nothing, change 0x27 in the code to 0x3F. If uploading says "Connecting..." and fails, hold the ESP32's BOOT button down while it uploads. If the ESP32 never shows up in the port list, use a data cable, not a charging-only one, and on Windows install the driver for the USB chip on the board (CP2102 or CH340).

Similar projects

In the app, tick off every wire, match the parts to your inventory and pick up where you left off.

Get it on Google Play