Tiny Wi-Fi Scanner with ESP32-C3-0.42 OLED

Built a mini Wi-Fi scanner with an ESP32-C3 and 0.42" OLED, surprisingly useful and super tiny.

I love compact devices. I picked up a ESP32-C3 with a 0.42-inch OLED and wanted to build something practical. Ended up with mini Wi-Fi signal scanner. This device is tiny.

Specs:

  • Board Size: 20.5mm x 25mm
  • Display Resolution: 72x40 pixels

Here's the exact board I used: AliExpress Link.

Preparation:

  1. Documentation: Check out the official GitHub repository for setup guidance.
  2. Setup: Install the required drivers and libraries in the Arduino IDE. Test the board with the “Hello World” example to ensure everything is functioning.
  3. Configuration:
    • Set the board type in Arduino IDE to ESP32C3 Dev Module.
    • I didn’t need the boot sequence recommended in the documentation for flashing via Arduino IDE.

What Does It Do?

This project creates a tiny Wi-Fi network scanner that:

  • Continuously scans for available Wi-Fi networks.
  • Displays each network’s SSID, frequency (2.4GHz/5GHz), and signal strength.
  • Allows you to cycle through the detected networks using the Boot button.

Why Build This?

It's a practical tool for checking network strength when you don’t have your phone handy. For example, you might use it to evaluate Wi-Fi signal quality in remote areas.

While an additional wireless antenna could improve its range, I prioritized keeping the design small and portable. The device uses only one button, so there's no “Back” functionality—but you can reboot it to restart the scan.

Features:

  • Splash Screen: A simple aesthetic touch displays "ambientnode" on startup.
  • Scrolling Text: Handles long SSIDs dynamically using a scrolling effect.
  • Signal Strength: Converts the raw RSSI value into a user-friendly percentage.

Limitations:

  • SSIDs are displayed in the order returned by WiFi.scanNetworks(), which is not sorted.
  • There's no external antenna for extended range due to size constraints.
#include <Arduino.h>
#include <U8g2lib.h>
#include <WiFi.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// Pin Definitions
#define SDA_PIN 5
#define SCL_PIN 6
#define BUTTON_PIN 9 // Define the button pin

// Display Configuration
U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);   // EastRising 0.42" OLED

// Variables
int currentNetwork = 0;           // Track the currently displayed network
int totalNetworks = 0;            // Total number of networks found
unsigned long lastScanTime = 0;   // Last Wi-Fi scan time
const unsigned long scanInterval = 10000; // Refresh Wi-Fi scan every 10 seconds
bool firstBoot = true;            // Track whether it's the first boot

// Button Debounce Variables
bool lastButtonState = HIGH;      // Previous button state
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;  // Debounce delay in milliseconds

// Scrolling Variables
int ssidScrollPosition = 0;       // Scroll position for the current SSID
unsigned long lastScrollTime = 0; // Last time the SSID scrolled
const int scrollSpeed = 4;        // Speed of scrolling
const unsigned long scrollDelay = 50; // Delay between scroll updates in ms

void setup() {
  Serial.begin(115200); // Start serial monitor for debugging
  // Initialize I2C and OLED display
  Wire.begin(SDA_PIN, SCL_PIN);
  u8g2.begin();

  // Initialize Wi-Fi in station mode
  WiFi.mode(WIFI_STA);
  WiFi.disconnect(); // Ensure we start fresh

  // Initialize button
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Use internal pull-up resistor

  // Display "ambientnode" on first boot
  displayAmbientnode();
  delay(2000); // Show for 2 seconds

  // Debug message
  Serial.println("Setup complete.");
}

void loop() {
  // Perform Wi-Fi scan periodically
  if (millis() - lastScanTime > scanInterval) {
    displayScanning(); // Show "Scanning ->" before scanning
    performWiFiScan();
    lastScanTime = millis();
  }

  // Handle button press with debounce logic
  handleButtonPress();

  // Display the current Wi-Fi network or a message if none are found
  if (totalNetworks > 0) {
    displayNetworkInfo(currentNetwork);
  } else {
    displayNoNetworks();
  }

  delay(10); // Small delay to avoid rapid updates
}

void performWiFiScan() {
  totalNetworks = WiFi.scanNetworks(); // Scan for Wi-Fi networks
  Serial.print("Total networks found: ");
  Serial.println(totalNetworks);
}

void handleButtonPress() {
  int currentButtonState = digitalRead(BUTTON_PIN);

  // Check for button state changes
  if (currentButtonState != lastButtonState) {
    lastDebounceTime = millis(); // Reset debounce timer
  }

  // If debounce time has passed and the button is pressed
  if ((millis() - lastDebounceTime) > debounceDelay && currentButtonState == LOW && lastButtonState == HIGH) {
    currentNetwork = (currentNetwork + 1) % totalNetworks; // Move to the next network
    ssidScrollPosition = 0; // Reset scrolling for the new SSID
    Serial.print("Button pressed. Showing network: ");
    Serial.println(currentNetwork);
  }

  lastButtonState = currentButtonState; // Update the last button state
}

void displayAmbientnode() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.setCursor(4, 10);
  u8g2.print("ambientnode");
  u8g2.sendBuffer();
}

void displayScanning() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(4, 10);
  u8g2.print("scanning...");
  u8g2.sendBuffer();
}

void displayNetworkInfo(int index) {
  // Get details of the indexed network
  String ssid = WiFi.SSID(index);
  int rssi = WiFi.RSSI(index);
  int channel = WiFi.channel(index);
  String frequency = channel > 14 ? "5GHz" : "2.4GHz";
  int signalPercent = convertDbmToPercent(rssi);

  // Debugging: Show network details in serial monitor
  Serial.println("---------------------------");
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("Signal: ");
  Serial.println(signalPercent);
  Serial.print("Frequency: ");
  Serial.println(frequency);
  Serial.println("---------------------------");

  // Clear display and set fonts
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);

  // Display SSID and Frequency (scrolling if needed)
  String ssidLine = ssid + " (" + frequency + ")";
  scrollSSID(ssidLine, 4, 10, 68); // Shifted right by 4 pixels

  // Display Signal Strength
  u8g2.setFont(u8g2_font_6x10_tr);
  String signalLine = "signal: " + String(signalPercent) + "%";
  u8g2.setCursor(4, 25); // Shifted right by 4 pixels
  u8g2.print(signalLine);

  // Update the display
  u8g2.sendBuffer();
}

void displayNoNetworks() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(4, 10); // Shifted right by 4 pixels
  u8g2.print("no network");
  u8g2.sendBuffer();
}

void scrollSSID(String text, int x, int y, int width) {
  // Calculate text length in pixels
  int textWidth = u8g2.getStrWidth(text.c_str());

  // If scrolling is needed
  if (textWidth > width) {
    // Handle scroll timing
    if (millis() - lastScrollTime > scrollDelay) {
      ssidScrollPosition -= scrollSpeed; // Move text left
      lastScrollTime = millis();

      // Reset scroll position when text scrolls out completely
      if (ssidScrollPosition < -textWidth) {
        ssidScrollPosition = width;
      }
    }

    // Draw the scrolling text
    u8g2.setCursor(ssidScrollPosition, y);
    u8g2.print(text);
  } else {
    // If text fits, display it statically
    u8g2.setCursor(x, y);
    u8g2.print(text);
  }
}

int convertDbmToPercent(int dbm) {
  // Approximate conversion of dBm to percentage
  // -30 dBm = 100%, -90 dBm = 0%
  int percent = map(dbm, -90, -30, 0, 100);
  return constrain(percent, 0, 100); // Ensure the value stays within 0-100%
}

If you're into micro builds and want a functional little tool on your keychain or in your pocket, this project is a fun one. Plus, it’s a solid excuse to play around with the ESP32-C3’s capabilities. 😄