From ec56b45c280768f3c1be8b3626767aaf258f7f09 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 21 Dec 2025 08:59:46 -0600 Subject: [PATCH 1/2] Add wifi_scanner app --- docs/gifs/wifi_scanner.gif | Bin 0 -> 14444 bytes examples/wifi_scanner/.gitignore | 6 + examples/wifi_scanner/README.md | 38 +++ examples/wifi_scanner/partitions.csv | 5 + examples/wifi_scanner/platformio.ini | 12 + examples/wifi_scanner/src/config.h | 15 ++ examples/wifi_scanner/src/main.cpp | 365 +++++++++++++++++++++++++++ 7 files changed, 441 insertions(+) create mode 100644 docs/gifs/wifi_scanner.gif create mode 100644 examples/wifi_scanner/.gitignore create mode 100644 examples/wifi_scanner/README.md create mode 100644 examples/wifi_scanner/partitions.csv create mode 100644 examples/wifi_scanner/platformio.ini create mode 100644 examples/wifi_scanner/src/config.h create mode 100644 examples/wifi_scanner/src/main.cpp diff --git a/docs/gifs/wifi_scanner.gif b/docs/gifs/wifi_scanner.gif new file mode 100644 index 0000000000000000000000000000000000000000..4048fb61065e3a1b3cda52556049a28c81768fdc GIT binary patch literal 14444 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 BNnHQ{ literal 0 HcmV?d00001 diff --git a/examples/wifi_scanner/.gitignore b/examples/wifi_scanner/.gitignore new file mode 100644 index 0000000..bbe17cb --- /dev/null +++ b/examples/wifi_scanner/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +.config diff --git a/examples/wifi_scanner/README.md b/examples/wifi_scanner/README.md new file mode 100644 index 0000000..83116dd --- /dev/null +++ b/examples/wifi_scanner/README.md @@ -0,0 +1,38 @@ +# WiFi Scanner App +This is a WiFi scanner app for the device32 (ESP32-based board with OLED display). It scans and displays nearby WiFi access points with their details. + +## Requirements +- device32 hardware +- PlatformIO development environment +- USB connection for flashing + +## Configuration +Edit the constants in `src/config.h` to customize pin assignments if needed: + +- **OLED Display Pins**: + ```cpp + #define OLED_SDA 21 + #define OLED_SCL 22 + #define OLED_RST -1 + #define OLED_ADDR 0x3C + ``` + +- **Button Pin**: + ```cpp + #define BUTTON_PIN 0 + ``` + +## Setup +1. Open this folder (`examples/wifi_scanner/`) in VSCode with PlatformIO installed. +2. Connect your device32 via USB. +3. Use PlatformIO to build and upload the project (`pio run --target upload`). + +## Usage +- Power on the device—it will scan for nearby WiFi access points and display them. +- The display shows a list of access points with their SSIDs. +- Use the button for navigation: + - **Short press**: Move to next item in list or next detail field. + - **Long press**: Enter detail view for selected AP or return to list. +- In list view, select "Rescan" at the bottom to refresh the AP list (shows "Scanning.." during scan). +- In detail view, navigate through fields: SSID, BSSID, RSSI, Channel, Encryption. +- Long SSIDs/BSSIDs scroll horizontally when selected. \ No newline at end of file diff --git a/examples/wifi_scanner/partitions.csv b/examples/wifi_scanner/partitions.csv new file mode 100644 index 0000000..022b834 --- /dev/null +++ b/examples/wifi_scanner/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0x9000, 0x5000 +otadata, data, ota, 0xe000, 0x2000 +ota_0, app, ota_0, 0x10000, 0x1C0000 +ota_1, app, ota_1, 0x1D0000, 0x1C0000 \ No newline at end of file diff --git a/examples/wifi_scanner/platformio.ini b/examples/wifi_scanner/platformio.ini new file mode 100644 index 0000000..f38cbb2 --- /dev/null +++ b/examples/wifi_scanner/platformio.ini @@ -0,0 +1,12 @@ +[env:seeed_xiao_esp32c3] +platform = espressif32 @6.12.0 +board = seeed_xiao_esp32c3 +framework = arduino +monitor_speed = 115200 +board_build.partitions = partitions.csv + +lib_archive = no +lib_deps = + adafruit/Adafruit SSD1306@^2.5.7 + adafruit/Adafruit GFX Library@^1.11.3 + bblanchon/ArduinoJson@^7.0.3 \ No newline at end of file diff --git a/examples/wifi_scanner/src/config.h b/examples/wifi_scanner/src/config.h new file mode 100644 index 0000000..eadce0f --- /dev/null +++ b/examples/wifi_scanner/src/config.h @@ -0,0 +1,15 @@ +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_SDA_PIN 7 // D5 +#define OLED_SCL_PIN 6 // D4 + +// button config +#define BUTTON_PIN 5 // D3 +#define BUTTON_TAP_TIME 20 + +// NTP server +#define NTP_SERVER "pool.ntp.org" + +// globals +#include +extern Adafruit_SSD1306 display; \ No newline at end of file diff --git a/examples/wifi_scanner/src/main.cpp b/examples/wifi_scanner/src/main.cpp new file mode 100644 index 0000000..26ccd6e --- /dev/null +++ b/examples/wifi_scanner/src/main.cpp @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include +#include +#include "config.h" + +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); + +#define GAME_WIDTH 64 +#define GAME_HEIGHT 128 + +String macToString(const uint8_t *mac) { + char buf[18]; + sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(buf); +} + +bool isBroadcast(const String &mac) { + return mac == "FF:FF:FF:FF:FF:FF"; +} + +struct AP { + String ssid; + String bssid; + int rssi; + int channel; + int enc; +}; + +std::vector aps; +int current_index = 0; +int state = 0; // 0: list, 1: detail +int last_button_state = HIGH; +unsigned long press_start = 0; +bool force_scan = false; +int start_index = 0; +int scroll_pos = 0; +unsigned long last_scroll = 0; +int detail_index = 0; +bool long_press_triggered = false; +bool is_scanning = false; + +void showBootScreen() { + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + String wifiText = "WIFI"; + String scannerText = "Scanner"; + int16_t x1, y1; + uint16_t w1, h1, w2, h2; + display.getTextBounds(wifiText, 0, 0, &x1, &y1, &w1, &h1); + display.getTextBounds(scannerText, 0, 0, &x1, &y1, &w2, &h2); + int textX1 = (GAME_WIDTH - w1) / 2; + int textX2 = (GAME_WIDTH - w2) / 2; + int totalH = h1 + h2 + 5; + int textY1 = (GAME_HEIGHT - totalH) / 2; + int textY2 = textY1 + h1 + 5; + int padding = 6; + int rectX = min(textX1, textX2) - padding; + int rectY = textY1 - padding; + int rectW = max(textX1 + w1, textX2 + w2) - rectX + padding; + int rectH = totalH + (padding * 2); + display.drawRoundRect(rectX, rectY, rectW, rectH, 3, SSD1306_WHITE); + display.setCursor(textX1, textY1); + display.println(wifiText); + display.setCursor(textX2, textY2); + display.println(scannerText); + display.display(); + delay(800); +} + +void setup() { + Serial.begin(115200); + Serial.println("Starting WiFi scanner"); + + // Initialize display + Wire.begin(OLED_SDA_PIN, OLED_SCL_PIN); + if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { + for (;;); + } + display.setRotation(1); // Rotate 90 degrees for vertical orientation + display.clearDisplay(); + display.display(); + + // Show boot screen + showBootScreen(); + + // Set up WiFi + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + delay(100); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + + // Initial scan + Serial.println("Initial scan..."); + int n = WiFi.scanNetworks(); + for (int i = 0; i < n; ++i) { + AP ap = {WiFi.SSID(i), WiFi.BSSIDstr(i), WiFi.RSSI(i), WiFi.channel(i), WiFi.encryptionType(i)}; + aps.push_back(ap); + } + WiFi.scanDelete(); + Serial.printf("Found %d networks\n", n); +} + +void loop() { + // Scan if forced + if (force_scan) { + Serial.println("Rescanning..."); + aps.clear(); + int n = WiFi.scanNetworks(); + for (int i = 0; i < n; ++i) { + AP ap = {WiFi.SSID(i), WiFi.BSSIDstr(i), WiFi.RSSI(i), WiFi.channel(i), WiFi.encryptionType(i)}; + aps.push_back(ap); + } + WiFi.scanDelete(); + Serial.printf("Found %d networks\n", n); + force_scan = false; + is_scanning = false; + current_index = 0; // reset to first + } + + // Handle button + int button_state = digitalRead(BUTTON_PIN); + if (button_state == LOW && last_button_state == HIGH) { + press_start = millis(); + long_press_triggered = false; + } + if (button_state == LOW && !long_press_triggered && millis() - press_start > 1000) { + // Trigger long press immediately + long_press_triggered = true; + if (state == 0 && current_index < aps.size()) { + state = 1; // enter detail + detail_index = 0; + } else if (state == 0 && current_index == aps.size()) { + force_scan = true; // rescan + is_scanning = true; + } else if (state == 1) { + state = 0; // back from detail + } + } + if (button_state == HIGH && last_button_state == LOW) { + unsigned long press_duration = millis() - press_start; + if (press_duration <= 1000) { // short press + if (state == 0) { + int total_items = aps.size() + 1; // +1 for rescan + current_index = (current_index + 1) % total_items; + } else if (state == 1) { + detail_index = (detail_index + 1) % 5; + } else { + state = 0; // back to list + } + } + // Long press already handled above + } + last_button_state = button_state; + + // Adjust start_index for scrolling list + int total_items = aps.size() + 1; + if (current_index < start_index) { + start_index = current_index; + } else if (current_index > start_index + 9) { + start_index = current_index - 9; + } + if (start_index < 0) start_index = 0; + if (start_index > total_items - 10) start_index = max(0, total_items - 10); + + // Scroll text for selected item + if (((state == 0 && current_index < aps.size()) || (state == 1 && current_index < aps.size())) && aps.size() > 0 && millis() - last_scroll > 200) { + String text_to_scroll; + if (state == 0) { + text_to_scroll = aps[current_index].ssid; + } else { + AP ap = aps[current_index]; + switch (detail_index) { + case 0: text_to_scroll = ap.ssid; break; + case 1: text_to_scroll = ap.bssid; break; + case 2: text_to_scroll = String(ap.rssi); break; + case 3: text_to_scroll = String(ap.channel); break; + case 4: { + switch (ap.enc) { + case WIFI_AUTH_OPEN: text_to_scroll = "Open"; break; + case WIFI_AUTH_WEP: text_to_scroll = "WEP"; break; + case WIFI_AUTH_WPA_PSK: text_to_scroll = "WPA"; break; + case WIFI_AUTH_WPA2_PSK: text_to_scroll = "WPA2"; break; + case WIFI_AUTH_WPA_WPA2_PSK: text_to_scroll = "WPA+WPA2"; break; + case WIFI_AUTH_WPA2_ENTERPRISE: text_to_scroll = "WPA2-EAP"; break; + case WIFI_AUTH_WPA3_PSK: text_to_scroll = "WPA3"; break; + case WIFI_AUTH_WPA2_WPA3_PSK: text_to_scroll = "WPA2+WPA3"; break; + case WIFI_AUTH_WAPI_PSK: text_to_scroll = "WAPI"; break; + default: text_to_scroll = "Unknown"; break; + } + break; + } + } + } + if (text_to_scroll.length() > 9) { + scroll_pos = (scroll_pos + 1) % (text_to_scroll.length() - 8); + } else { + scroll_pos = 0; + } + last_scroll = millis(); + } + + // Draw display + display.clearDisplay(); + display.drawRoundRect(0, 0, GAME_WIDTH, GAME_HEIGHT, 4, SSD1306_WHITE); + + if (is_scanning) { + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setTextWrap(false); + String scan_msg = "Scanning.."; + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(scan_msg, 0, 0, &x1, &y1, &w, &h); + int center_x = (GAME_WIDTH - w) / 2; + int center_y = (GAME_HEIGHT - h) / 2; + display.setCursor(center_x, center_y); + display.printf("%s", scan_msg.c_str()); + } else { + // Normal display code + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setTextWrap(false); + + if (state == 0) { // List view + int y = 5; + String aps_line = "APs: " + String(aps.size()); + display.setCursor(5, y); + display.printf("%s", aps_line.c_str()); + display.setCursor(6, y); // offset for bold + display.printf("%s", aps_line.c_str()); + y += 10; + + int num_to_show = 10; + int total_items = aps.size() + 1; + for (int i = 0; i < num_to_show && start_index + i < total_items; ++i) { + int idx = start_index + i; + display.setCursor(5, y); + if (idx < aps.size()) { + String ssid = aps[idx].ssid; + if (idx == current_index) { + String display_ssid; + if (ssid.length() > 9) { + display_ssid = ssid.substring(scroll_pos, scroll_pos + 9); + } else { + display_ssid = ssid; + } + display.printf(">%s", display_ssid.c_str()); + } else { + if (ssid.length() > 9) ssid = ssid.substring(0, 9); + display.printf(" %s", ssid.c_str()); + } + } else { + // Rescan option + if (idx == current_index) { + display.setCursor(5, y); + display.printf(">* Rescan"); + display.setCursor(6, y); + display.printf(">* Rescan"); + } else { + display.setCursor(5, y); + display.printf(" * Rescan"); + display.setCursor(6, y); + display.printf(" * Rescan"); + } + } + y += 10; + } + } else { // Detail view + if (current_index < aps.size()) { + AP ap = aps[current_index]; + int y = 5; + // SSID + String label_ssid = detail_index == 0 ? ">SSID" : "SSID"; + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(label_ssid, 0, 0, &x1, &y1, &w, &h); + int center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); + display.printf("%s", label_ssid.c_str()); + display.setCursor(center_x + 1, y); + display.printf("%s", label_ssid.c_str()); + y += 10; + display.setCursor(5, y); + String ssid_val = ap.ssid; + String display_ssid = (detail_index == 0 && ssid_val.length() > 9) ? ssid_val.substring(scroll_pos, scroll_pos + 9) : ssid_val; + display.printf("%s", display_ssid.c_str()); + y += 10; + // BSSID + String label_bssid = detail_index == 1 ? ">BSSID" : "BSSID"; + display.getTextBounds(label_bssid, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); + display.printf("%s", label_bssid.c_str()); + display.setCursor(center_x + 1, y); + display.printf("%s", label_bssid.c_str()); + y += 10; + display.setCursor(5, y); + String bssid_val = ap.bssid; + String display_bssid = (detail_index == 1 && bssid_val.length() > 9) ? bssid_val.substring(scroll_pos, scroll_pos + 9) : bssid_val; + display.printf("%s", display_bssid.c_str()); + y += 10; + // RSSI + String label_rssi = detail_index == 2 ? ">RSSI" : "RSSI"; + display.getTextBounds(label_rssi, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); + display.printf("%s", label_rssi.c_str()); + display.setCursor(center_x + 1, y); + display.printf("%s", label_rssi.c_str()); + y += 10; + display.setCursor(5, y); + String rssi_str = String(ap.rssi); + display.printf("%s", rssi_str.c_str()); + y += 10; + // Channel + String label_ch = detail_index == 3 ? ">Ch" : "Ch"; + display.getTextBounds(label_ch, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); + display.printf("%s", label_ch.c_str()); + display.setCursor(center_x + 1, y); + display.printf("%s", label_ch.c_str()); + y += 10; + display.setCursor(5, y); + String ch_str = String(ap.channel); + display.printf("%s", ch_str.c_str()); + y += 10; + // Encryption + String label_enc = detail_index == 4 ? ">Enc" : "Enc"; + display.getTextBounds(label_enc, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); + display.printf("%s", label_enc.c_str()); + display.setCursor(center_x + 1, y); + display.printf("%s", label_enc.c_str()); + y += 10; + display.setCursor(5, y); + String enc_str; + switch (ap.enc) { + case WIFI_AUTH_OPEN: enc_str = "Open"; break; + case WIFI_AUTH_WEP: enc_str = "WEP"; break; + case WIFI_AUTH_WPA_PSK: enc_str = "WPA"; break; + case WIFI_AUTH_WPA2_PSK: enc_str = "WPA2"; break; + case WIFI_AUTH_WPA_WPA2_PSK: enc_str = "WPA+WPA2"; break; + case WIFI_AUTH_WPA2_ENTERPRISE: enc_str = "WPA2-EAP"; break; + case WIFI_AUTH_WPA3_PSK: enc_str = "WPA3"; break; + case WIFI_AUTH_WPA2_WPA3_PSK: enc_str = "WPA2+WPA3"; break; + case WIFI_AUTH_WAPI_PSK: enc_str = "WAPI"; break; + default: enc_str = "Unknown"; break; + } + String display_enc = (detail_index == 4 && enc_str.length() > 9) ? enc_str.substring(scroll_pos, scroll_pos + 9) : enc_str; + display.printf("%s", display_enc.c_str()); + } + } + } + + display.display(); + delay(100); +} \ No newline at end of file From fa45e09ad65f4e57648e373dfc126d7e78552c69 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 21 Dec 2025 09:34:06 -0600 Subject: [PATCH 2/2] Update detail page. Center UI elements --- docs/gifs/wifi_scanner.gif | Bin 14444 -> 18072 bytes examples/wifi_scanner/src/main.cpp | 26 +++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/gifs/wifi_scanner.gif b/docs/gifs/wifi_scanner.gif index 4048fb61065e3a1b3cda52556049a28c81768fdc..0593c84ff6a3748d829ebc1f33634b0e4b9f0d78 100644 GIT binary patch delta 10245 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 diff --git a/examples/wifi_scanner/src/main.cpp b/examples/wifi_scanner/src/main.cpp index 26ccd6e..1963575 100644 --- a/examples/wifi_scanner/src/main.cpp +++ b/examples/wifi_scanner/src/main.cpp @@ -275,12 +275,13 @@ void loop() { if (current_index < aps.size()) { AP ap = aps[current_index]; int y = 5; - // SSID - String label_ssid = detail_index == 0 ? ">SSID" : "SSID"; int16_t x1, y1; uint16_t w, h; + int center_x; + // SSID + String label_ssid = detail_index == 0 ? ">SSID" : "SSID"; display.getTextBounds(label_ssid, 0, 0, &x1, &y1, &w, &h); - int center_x = (GAME_WIDTH - w) / 2; + center_x = (GAME_WIDTH - w) / 2; display.setCursor(center_x, y); display.printf("%s", label_ssid.c_str()); display.setCursor(center_x + 1, y); @@ -289,6 +290,9 @@ void loop() { display.setCursor(5, y); String ssid_val = ap.ssid; String display_ssid = (detail_index == 0 && ssid_val.length() > 9) ? ssid_val.substring(scroll_pos, scroll_pos + 9) : ssid_val; + display.getTextBounds(display_ssid, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); display.printf("%s", display_ssid.c_str()); y += 10; // BSSID @@ -303,6 +307,9 @@ void loop() { display.setCursor(5, y); String bssid_val = ap.bssid; String display_bssid = (detail_index == 1 && bssid_val.length() > 9) ? bssid_val.substring(scroll_pos, scroll_pos + 9) : bssid_val; + display.getTextBounds(display_bssid, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); display.printf("%s", display_bssid.c_str()); y += 10; // RSSI @@ -316,10 +323,13 @@ void loop() { y += 10; display.setCursor(5, y); String rssi_str = String(ap.rssi); + display.getTextBounds(rssi_str, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); display.printf("%s", rssi_str.c_str()); y += 10; // Channel - String label_ch = detail_index == 3 ? ">Ch" : "Ch"; + String label_ch = detail_index == 3 ? ">Channel" : "Channel"; display.getTextBounds(label_ch, 0, 0, &x1, &y1, &w, &h); center_x = (GAME_WIDTH - w) / 2; display.setCursor(center_x, y); @@ -329,10 +339,13 @@ void loop() { y += 10; display.setCursor(5, y); String ch_str = String(ap.channel); + display.getTextBounds(ch_str, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); display.printf("%s", ch_str.c_str()); y += 10; // Encryption - String label_enc = detail_index == 4 ? ">Enc" : "Enc"; + String label_enc = detail_index == 4 ? ">Encryption" : "Encryption"; display.getTextBounds(label_enc, 0, 0, &x1, &y1, &w, &h); center_x = (GAME_WIDTH - w) / 2; display.setCursor(center_x, y); @@ -355,6 +368,9 @@ void loop() { default: enc_str = "Unknown"; break; } String display_enc = (detail_index == 4 && enc_str.length() > 9) ? enc_str.substring(scroll_pos, scroll_pos + 9) : enc_str; + display.getTextBounds(display_enc, 0, 0, &x1, &y1, &w, &h); + center_x = (GAME_WIDTH - w) / 2; + display.setCursor(center_x, y); display.printf("%s", display_enc.c_str()); } }