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