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

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:

Documentation: Check out the official GitHub repository for setup guidance.

Setup: Install the required drivers and libraries in the Arduino IDE. Test the board with the “Hello World” example to ensure everything is functioning.

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. 😄