/* main.c -- Wi-Fi Signal Mapper
* Runs in STA+AP mode. The STA interface connects to your home network;
* the AP interface ("ESP32-Mapper") lets any device connect directly.
* Performs periodic Wi-Fi scans and serves results as an HTML table on port 80.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "esp_http_server.h"
#include "esp_smartconfig.h"
/* ---------- Configuration ---------- */
static const char *TAG = "wifi_mapper";
#define MAX_AP_RECORDS 30
#define SCAN_INTERVAL_MS 10000
#define AP_SSID "ESP32-Mapper"
/* STA credentials: set via menuconfig or SmartConfig */
#define DEFAULT_STA_SSID CONFIG_WIFI_SSID
#define DEFAULT_STA_PASS CONFIG_WIFI_PASSWORD
/* ---------- Global state ---------- */
static EventGroupHandle_t s_wifi_event_group;
#define STA_CONNECTED_BIT BIT0
#define STA_GOT_IP_BIT BIT1
#define SMARTCONFIG_DONE_BIT BIT2
static wifi_ap_record_t s_ap_records[MAX_AP_RECORDS];
static uint16_t s_ap_count = 0;
static SemaphoreHandle_t s_scan_mutex;
static httpd_handle_t s_server = NULL;
static int s_retry_count = 0;
/* ---------- Auth mode to string ---------- */
static const char *auth_mode_str(wifi_auth_mode_t authmode)
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";
case WIFI_AUTH_WAPI_PSK: return "WAPI";
default: return "Unknown";
/* ---------- RSSI to signal bar string ---------- */
static const char *rssi_bar(int8_t rssi)
if (rssi >= -50) return "█████";
if (rssi >= -60) return "████";
if (rssi >= -70) return "███";
if (rssi >= -80) return "██";
/* ---------- Event handlers ---------- */
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
if (event_base == WIFI_EVENT) {
case WIFI_EVENT_STA_START:
ESP_LOGI(TAG, "STA started, connecting...");
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI(TAG, "STA connected to AP");
xEventGroupSetBits(s_wifi_event_group, STA_CONNECTED_BIT);
case WIFI_EVENT_STA_DISCONNECTED: {
wifi_event_sta_disconnected_t *d =
(wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "STA disconnected, reason=%d", d->reason);
xEventGroupClearBits(s_wifi_event_group,
STA_CONNECTED_BIT | STA_GOT_IP_BIT);
if (s_retry_count < MAX_RETRY) {
ESP_LOGI(TAG, "Reconnect attempt %d/%d",
s_retry_count, MAX_RETRY);
ESP_LOGE(TAG, "Max retries reached");
case WIFI_EVENT_AP_STACONNECTED: {
wifi_event_ap_staconnected_t *e =
(wifi_event_ap_staconnected_t *)event_data;
ESP_LOGI(TAG, "AP client connected, MAC=" MACSTR,
case WIFI_EVENT_AP_STADISCONNECTED: {
wifi_event_ap_stadisconnected_t *e =
(wifi_event_ap_stadisconnected_t *)event_data;
ESP_LOGI(TAG, "AP client disconnected, MAC=" MACSTR,
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, STA_GOT_IP_BIT);
static void smartconfig_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
if (event_id == SC_EVENT_GOT_SSID_PSWD) {
smartconfig_event_got_ssid_pswd_t *evt =
(smartconfig_event_got_ssid_pswd_t *)event_data;
wifi_config_t wifi_config = {0};
memcpy(wifi_config.sta.ssid, evt->ssid,
sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, evt->password,
sizeof(wifi_config.sta.password));
ESP_LOGI(TAG, "SmartConfig SSID: %s", (char *)evt->ssid);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
if (event_id == SC_EVENT_SEND_ACK_DONE) {
ESP_LOGI(TAG, "SmartConfig ACK done");
xEventGroupSetBits(s_wifi_event_group, SMARTCONFIG_DONE_BIT);
/* ---------- Wi-Fi initialization ---------- */
static void wifi_init(void)
s_wifi_event_group = xEventGroupCreate();
s_scan_mutex = xSemaphoreCreateMutex();
/* TCP/IP and event loop */
esp_event_loop_create_default();
/* Create both STA and AP interfaces */
esp_netif_create_default_wifi_sta();
esp_netif_create_default_wifi_ap();
/* Initialize the Wi-Fi driver */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
/* Store config in flash so it persists across reboots */
esp_wifi_set_storage(WIFI_STORAGE_FLASH);
/* Register event handlers */
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL, NULL);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL, NULL);
esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID,
&smartconfig_event_handler, NULL, NULL);
esp_wifi_set_mode(WIFI_MODE_APSTA);
wifi_config_t sta_config = {
.ssid = DEFAULT_STA_SSID,
.password = DEFAULT_STA_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
wifi_config_t ap_config = {
.ssid_len = strlen(AP_SSID),
.max_connection = AP_MAX_CONN,
.authmode = WIFI_AUTH_OPEN,
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
ESP_LOGI(TAG, "Wi-Fi initialized in STA+AP mode");
ESP_LOGI(TAG, "AP SSID: %s (open, connect to access web UI)", AP_SSID);
/* ---------- Scan task ---------- */
static void scan_task(void *pvParameters)
/* Wait briefly for Wi-Fi to fully start */
vTaskDelay(pdMS_TO_TICKS(3000));
ESP_LOGI(TAG, "Scan task started on core %d", xPortGetCoreID());
wifi_scan_config_t scan_cfg = {
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.active = { .min = 120, .max = 150 },
esp_err_t err = esp_wifi_scan_start(&scan_cfg, true);
ESP_LOGW(TAG, "Scan failed: %s", esp_err_to_name(err));
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS));
esp_wifi_scan_get_ap_num(&count);
if (count > MAX_AP_RECORDS) {
wifi_ap_record_t temp[MAX_AP_RECORDS];
esp_wifi_scan_get_ap_records(&count, temp);
/* Sort by RSSI descending (strongest first) */
for (int i = 0; i + 1 < count; i++) {
for (int j = i + 1; j < count; j++) {
if (temp[j].rssi > temp[i].rssi) {
wifi_ap_record_t swap = temp[i];
/* Copy results under mutex protection */
if (xSemaphoreTake(s_scan_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
memcpy(s_ap_records, temp, sizeof(wifi_ap_record_t) * count);
xSemaphoreGive(s_scan_mutex);
ESP_LOGI(TAG, "Scan complete: %d APs found", count);
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS));
/* ---------- HTTP server ---------- */
static esp_err_t root_get_handler(httpd_req_t *req)
httpd_resp_set_type(req, "text/html");
/* HTML head with auto-refresh and embedded CSS */
httpd_resp_sendstr_chunk(req,
"<!DOCTYPE html><html><head>"
"<meta http-equiv='refresh' content='10'>"
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
"<title>Wi-Fi Signal Mapper</title>"
"body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px;"
" background: #f5f5f5; color: #333; }"
"h1 { color: #2c3e50; margin-bottom: 5px; }"
"p.info { color: #666; font-size: 14px; margin-top: 0; }"
"table { border-collapse: collapse; width: 100%;"
" background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }"
"th { background: #2c3e50; color: white; padding: 12px 15px;"
" text-align: left; font-size: 14px; }"
"td { padding: 10px 15px; border-bottom: 1px solid #eee;"
"tr:hover { background: #f0f7ff; }"
".rssi-excellent { color: #27ae60; font-weight: bold; }"
".rssi-good { color: #2ecc71; }"
".rssi-fair { color: #f39c12; }"
".rssi-weak { color: #e74c3c; }"
".bar { font-size: 10px; letter-spacing: -1px; }"
"</style></head><body>");
httpd_resp_sendstr_chunk(req,
"<h1>Wi-Fi Signal Mapper</h1>"
"<p class='info'>Page refreshes every 10 seconds. ");
if (xSemaphoreTake(s_scan_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {
snprintf(info, sizeof(info),
"Detected <strong>%d</strong> access point%s.</p>",
s_ap_count, s_ap_count == 1 ? "" : "s");
httpd_resp_sendstr_chunk(req, info);
httpd_resp_sendstr_chunk(req,
for (int i = 0; i < s_ap_count; i++) {
if (s_ap_records[i].rssi >= -50) css_class = "rssi-excellent";
else if (s_ap_records[i].rssi >= -60) css_class = "rssi-good";
else if (s_ap_records[i].rssi >= -70) css_class = "rssi-fair";
else css_class = "rssi-weak";
/* Handle hidden SSIDs */
const char *ssid = (char *)s_ap_records[i].ssid;
snprintf(row, sizeof(row),
"<td><strong>%s</strong></td>"
"<td class='bar %s'>%s</td>"
MAC2STR(s_ap_records[i].bssid),
css_class, s_ap_records[i].rssi,
css_class, rssi_bar(s_ap_records[i].rssi),
auth_mode_str(s_ap_records[i].authmode));
httpd_resp_sendstr_chunk(req, row);
xSemaphoreGive(s_scan_mutex);
httpd_resp_sendstr_chunk(req,
"Scan data temporarily unavailable.</p>");
httpd_resp_sendstr_chunk(req, "</tbody></table></body></html>");
httpd_resp_sendstr_chunk(req, NULL); /* End chunked response */
static void start_webserver(void)
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
if (httpd_start(&s_server, &config) == ESP_OK) {
.handler = root_get_handler,
httpd_register_uri_handler(s_server, &root);
ESP_LOGI(TAG, "HTTP server started on port 80");
ESP_LOGE(TAG, "Failed to start HTTP server");
/* ---------- SmartConfig task ---------- */
static void smartconfig_task(void *pvParameters)
/* Only run SmartConfig if no stored credentials exist.
* We check by waiting a few seconds for the STA to connect. */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
if (bits & STA_GOT_IP_BIT) {
ESP_LOGI(TAG, "STA connected, SmartConfig not needed");
ESP_LOGI(TAG, "STA not connected, starting SmartConfig...");
ESP_LOGI(TAG, "Use the ESP-Touch app to send credentials");
esp_smartconfig_set_type(SC_TYPE_ESPTOUCH);
smartconfig_start_config_t sc_cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
esp_smartconfig_start(&sc_cfg);
/* Wait for SmartConfig to complete or STA to connect */
xEventGroupWaitBits(s_wifi_event_group,
STA_GOT_IP_BIT | SMARTCONFIG_DONE_BIT,
false, false, portMAX_DELAY);
ESP_LOGI(TAG, "SmartConfig task done");
/* ---------- Entry point ---------- */
ESP_LOGI(TAG, "Wi-Fi Signal Mapper starting");
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* Initialize Wi-Fi in STA+AP mode */
/* Start the HTTP server immediately (AP is already up) */
/* Start the scanning task */
xTaskCreatePinnedToCore(scan_task, "scan_task", 4096,
/* Start SmartConfig fallback task */
xTaskCreate(smartconfig_task, "smartconfig_task", 4096,
ESP_LOGI(TAG, "Initialization complete");
ESP_LOGI(TAG, "Connect to AP '%s' and open http://192.168.4.1", AP_SSID);
Comments