ESP8266 - Wi-Fi Scanner (Web Server) using Flask & uvicorn.

Configure:

const char* ssid = "";  // Replace with your Wi-Fi SSID
const char* password = "";       // Replace with your Wi-Fi password

#include <WiFi.h>
#include <WebSocketsClient.h>
#include <FS.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>  // Include Arduino JSON library for easier manipulation

const char* deviceHostName = "DirectSec_ESP32_ToolKit";  // Set a static hostname here
const char* ssid = "";  // Replace with your Wi-Fi SSID
const char* password = "";       // Replace with your Wi-Fi password
const char* serverAddress = "";   // Your PC's IP  
const int serverPort = 8000;
const char* deviceID = "ESP32_1";  

WebSocketsClient webSocket;

void saveWiFiData(String wifiData) {
    // Load existing Wi-Fi data
    String existingData = loadWiFiData();
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, existingData);

    JsonArray networks = doc.as<JsonArray>();

    // Parse the new Wi-Fi data to check for duplicates
    DynamicJsonDocument newDoc(1024);
    deserializeJson(newDoc, wifiData);
    JsonArray newNetworks = newDoc.as<JsonArray>();

    for (JsonObject network : newNetworks) {
        String ssid = network["SSID"].as<String>();
        String signal = network["Signal"].as<String>();

        // 🔹 Check if the SSID already exists in saved data
        bool exists = false;
        for (JsonObject savedNetwork : networks) {
            if (savedNetwork["SSID"].as<String>() == ssid) {
                exists = true;
                break;
            }
        }

        // Only add if it doesn't already exist
        if (!exists) {
            networks.add(network);
        }
    }

    // Save the updated list
    File file = SPIFFS.open("/wifi_data.json", "w");
    if (file) {
        serializeJson(doc, file);
        file.close();
        Serial.println("[+] Wi-Fi data saved!");
    }
}

String loadWiFiData() {
    File file = SPIFFS.open("/wifi_data.json", "r");
    if (!file) {
        return "[]";  // Return empty array if no data found
    }
    String data = file.readString();
    file.close();
    return data;
}

void scanWiFi() {
    Serial.println("[*] Scanning Wi-Fi...");
    int networksFound = WiFi.scanNetworks();
    DynamicJsonDocument wifiDoc(1024);
    JsonArray wifiArray = wifiDoc.to<JsonArray>();

    for (int i = 0; i < networksFound; i++) {
        JsonObject network = wifiArray.createNestedObject();
        network["SSID"] = WiFi.SSID(i);
        network["Signal"] = String(WiFi.RSSI(i));

        // Format the output (For debugging, you can check the structure of wifiDoc)
        Serial.println("  " + String(i + 1) + ". " + WiFi.SSID(i) + " (Signal: " + String(WiFi.RSSI(i)) + " dBm)");
    }

    // Save the scanned Wi-Fi networks
    saveWiFiData(wifiDoc.as<String>());

    // Send the formatted result over WebSocket
    String wifiData = "Wi-Fi Networks Found:\n";
    for (int i = 0; i < wifiArray.size(); i++) {
        JsonObject network = wifiArray[i];
        wifiData += "  " + String(i + 1) + ". " + network["SSID"].as<String>() + " (Signal: " + network["Signal"].as<String>() + ")\n";
    }
    webSocket.sendTXT(wifiData);
}

void loadWiFiDataFormatted() {
    String savedData = loadWiFiData();
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, savedData);

    JsonArray networks = doc.as<JsonArray>();
    String formattedData = "Wi-Fi Networks Found:\n";

    for (int i = 0; i < networks.size(); i++) {
        JsonObject network = networks[i];
        String ssid = network["SSID"].as<String>();
        String signal = network["Signal"].as<String>();

        // Add the network to the formatted string with numbering and signal strength
        formattedData += "  " + String(i + 1) + ". " + ssid + " (Signal: " + signal + ")\n";
    }

    // Send the formatted result over WebSocket
    webSocket.sendTXT(formattedData);
}

void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) {
    switch (type) {
        case WStype_TEXT: {
            String command = String((char*)payload);
            Serial.println("Received: " + command);

            if (command == "PING") {
                webSocket.sendTXT("PONG");
            }
            else if (command == "UPTIME") {
                webSocket.sendTXT("Uptime: " + String(millis() / 1000) + " seconds");
            }
            else if (command == "FREE_MEM") {
                webSocket.sendTXT("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
            }
            else if (command == "SCAN_WIFI") {
                scanWiFi();
            }
            else if (command == "LOAD_WIFI") {
                loadWiFiDataFormatted();  // Call the formatted load function
            }
            else {
                webSocket.sendTXT("[ERROR] Unknown Command: " + command);
            }
            break;
        }

        case WStype_CONNECTED:
            Serial.println("[+] WebSocket Connected!");
            break;

        case WStype_DISCONNECTED:
            Serial.println("[-] WebSocket Disconnected. Reconnecting...");
            break;
    }
}

void connectWiFi() {
    Serial.print("[*] Connecting to WiFi...");
    WiFi.begin(ssid, password);
    
    int attempt = 0;
    while (WiFi.status() != WL_CONNECTED && attempt < 20) {
        delay(500);
        Serial.print(".");
        attempt++;
    }

    if (WiFi.status() == WL_CONNECTED) {
        Serial.println("\n[+] WiFi Connected! IP: " + WiFi.localIP().toString());
    } else {
        Serial.println("\n[-] WiFi connection failed. Restarting...");
        ESP.restart();
    }
}

void connectWebSocket() {
    Serial.println("[*] Connecting to WebSocket...");
    webSocket.begin(serverAddress, serverPort, (String("/ws/") + deviceID).c_str());
    webSocket.onEvent(webSocketEvent);
    webSocket.setReconnectInterval(5000);
}

void setup() {
    Serial.begin(115200);
    SPIFFS.begin(true);
    connectWiFi();
    connectWebSocket();
}

void loop() {
    webSocket.loop();
    delay(10);
}

Server Code:

import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Allow all origins (adjust for security in production)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Store active WebSocket connections
esp_connections = {}  # {device_id: websocket}
browser_connections = set()  # Stores browser websockets

from fastapi.responses import HTMLResponse

@app.get("/", response_class=HTMLResponse)
async def get_terminal():
    with open("templates/index.html", "r") as file:
        return file.read()

@app.websocket("/ws/{device_id}")
async def websocket_endpoint(websocket: WebSocket, device_id: str):
    """Handle WebSocket connections from ESP32 and browsers."""
    await websocket.accept()
    print(f"[+] {device_id} connected.")

    # Store connections
    if device_id.startswith("ESP32"):
        esp_connections[device_id] = websocket
    else:
        browser_connections.add(websocket)

    try:
        while True:
            message = await websocket.receive_text()
            print(f"[{device_id}]: {message}")

            # Send ESP32 messages to all browsers
            if device_id.startswith("ESP32"):
                for browser in browser_connections:
                    try:
                        await browser.send_text(f"{device_id}: {message}")
                    except SyntaxError:
                        browser_connections.discard(browser)

            # Send browser messages to all ESP32 devices
            elif device_id.startswith("Browser"):
                for esp_id, esp in esp_connections.items():
                    try:
                        await esp.send_text(message)  # Fix: Now sends to ESP32
                    except SyntaxError:
                        esp_connections.pop(esp_id, None)

    except WebSocketDisconnect:
        print(f"[-] {device_id} disconnected.")
        if device_id.startswith("ESP32"):
            esp_connections.pop(device_id, None)
        else:
            browser_connections.discard(websocket)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000, ws_ping_interval=60, ws_ping_timeout=180)

templates/index.html

  • Change YOUR_LOCAL_IP with you local ip address
  • “let serverUrl = “ws://YOUR_LOCAL_IP:8000/ws/Browser_1”;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP32 CNC Terminal - DirectSec</title>
    <style>
        body { background-color: black; color: lime; font-family: monospace; padding: 20px; }
        #terminal { width: 80%; height: 300px; border: 1px solid lime; padding: 10px; overflow-y: auto; white-space: pre-wrap; background-color: black; }
        #command { width: 80%; background: black; color: lime; border: 1px solid lime; font-family: monospace; padding: 5px; }
        button { background: black; color: lime; border: 1px solid lime; padding: 5px; margin-top: 10px; cursor: pointer; }
        .esp-msg { color: cyan; }
        .error-msg { color: red; }
    </style>
</head>
<body>
    <h2>ESP32 CNC Terminal</h2>
    <div id="terminal"></div>
    <input type="text" id="command" placeholder="Enter command..." autofocus>
    <button onclick="sendCommand('SCAN_WIFI')">Scan Wi-Fi</button>
    <button onclick="sendCommand('LOAD_WIFI')">Load Saved Wi-Fi</button>
    <button onclick="sendCommand('DEAUTH')">DeAuth Attack</button>
    <input type="file" id="binUploader" />
    <button onclick="uploadBin()">Upload Payload</button>


<script>
    let terminal = document.getElementById("terminal");
    let commandInput = document.getElementById("command");
    let socket = null;
    let serverUrl = "ws://YOUR_LOCAL_IP:8000/ws/Browser_1";

    function connectWebSocket() {
        socket = new WebSocket(serverUrl);

        socket.onopen = function() {
            appendToTerminal("[+] Connected to WebSocket server\n", "esp-msg");
        };

        socket.onmessage = function(event) {
            let message = event.data.trim();

            // Clean up unwanted metadata (e.g., "[ESP32_1]:")
            message = cleanMessage(message);

            // Directly append the message (no need for JSON formatting)
            appendToTerminal(message, "esp-msg");
        };

        socket.onerror = function() {
            appendToTerminal("[-] WebSocket Error\n", "error-msg");
        };

        socket.onclose = function() {
            appendToTerminal("[-] WebSocket Disconnected. Reconnecting...\n", "error-msg");
            setTimeout(connectWebSocket, 3000);
        };
    }

    function sendCommand(command) {
        if (socket.readyState === WebSocket.OPEN) {
            appendToTerminal("You: " + command, "");
            socket.send(command);
        } else {
            appendToTerminal("[!] WebSocket not connected", "error-msg");
        }
    }

    commandInput.addEventListener("keypress", function(event) {
        if (event.key === "Enter") {
            let command = commandInput.value.trim();
            if (command !== "") {
                sendCommand(command);
                commandInput.value = "";
            }
        }
    });

    function appendToTerminal(message, className) {
        let lines = message.split("\n");
        lines.forEach(line => {
            let div = document.createElement("div");
            div.textContent = line;
            if (className) div.classList.add(className);
        terminal.appendChild(div);
        });
        terminal.scrollTop = terminal.scrollHeight;
    }
    function uploadBin() {
    let file = document.getElementById('binUploader').files[0];
    if (!file) return;

    let reader = new FileReader();
    reader.onload = function(event) {
        let arrayBuffer = event.target.result;
        let base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));

        // Wrap it in a JSON message for ESP32 to process
        socket.send(JSON.stringify({
            type: "flash_payload",
            filename: file.name,
            data: base64
        }));
    };
    reader.readAsArrayBuffer(file);
    }

    // Optional: Keep this if you want to clean any remaining metadata
    //function cleanMessage(message) {
    //   return message.replace(/\[ESP32_\d\]:\s*/g, '');  // Clean any ESP32 metadata if needed
    //}
    function cleanMessage(message) {
        return message.replace(/^\[ESP32.*?\]:\s*/g, '');
    }
    connectWebSocket();
</script>


</body>
</html>