diff --git a/.github/skills/janos-uart-app/SKILL.md b/.github/skills/janos-uart-app/SKILL.md index 2434b81..95c484c 100644 --- a/.github/skills/janos-uart-app/SKILL.md +++ b/.github/skills/janos-uart-app/SKILL.md @@ -111,12 +111,20 @@ Every command response has a known end marker. Wait for it before proceeding: | Command | Completion Marker | |---------|-------------------| | `scan_networks` | `"Scan results printed"` | -| `wifi_connect` | `"SUCCESS"` or `"FAILED"` or `"Error"` | +| `wifi_connect` | `"SUCCESS"` or `"FAILED"` or `"TIMEOUT"` | +| `start_nmap` | `"Scanned"` + `"open ports"` (final summary line) | | `list_hosts` | `"Discovered Hosts"` (header line, data follows) | | `list_sd` | `"HTML files found"` (header, data follows), or timeout | | `show_pass` | Timeout (no explicit end marker) | | `list_probes` | Timeout (no explicit end marker) | | `wpasec_upload` | `"Done:"` | +| `start_pcap` | `"PCAP radio capture started"` or `"PCAP net capture started"` (initial); on stop: `"PCAP saved:"` | +| `start_beacon_spam` | `"Beacon spam started. Use 'stop' to end."` | +| `start_beacon_spam_ssids` | `"Beacon spam started. Use 'stop' to end."` (same as `start_beacon_spam`) | +| `list_ssids` | Timeout (no explicit end marker, list ends after last indexed line) | +| `add_ssid` | `"Added SSID:"` | +| `remove_ssid` | `"SSID removed."` | +| `version` | `"JanOS version: X.Y.Z"` (single line, immediate) | For commands without explicit end markers, use a timeout with empty-read detection (e.g., 3 consecutive empty reads of 500ms each). @@ -240,10 +248,46 @@ if (strstr(line, "password=")) { /* extract password after = */ } if (strstr(line, "Password verified!")) { /* attack succeeded */ } ``` -**wifi_connect result**: +**wifi_connect result** (password is optional -- omit for open networks): ```c if (strstr(rx_buffer, "SUCCESS")) { connected = true; } -if (strstr(rx_buffer, "FAILED") || strstr(rx_buffer, "Error")) { failed = true; } +if (strstr(rx_buffer, "FAILED") || strstr(rx_buffer, "TIMEOUT")) { failed = true; } +// Extract DHCP IP from: "DHCP IP: 192.168.0.5, Netmask: 255.255.255.0, GW: 192.168.0.1" +const char *dhcp = strstr(rx_buffer, "DHCP IP:"); +if (dhcp) { /* parse IP, Netmask, GW */ } +``` + +**start_nmap progress and results**: +```c +// Progress line: " Scanning 192.168.0.4 ports 21-143 [1/100] ..." +if (strstr(line, "Scanning") && strstr(line, "ports") && strchr(line, '[')) { + char ip[16]; int port_from, port_to, current, total; + sscanf(line, " Scanning %15s ports %d-%d [%d/%d]", + ip, &port_from, &port_to, ¤t, &total); + int pct = (current * 100) / total; + // update progress bar +} +// New host: "Host: 192.168.0.4 (00:C0:CA:B4:E6:3F)" +if (strncmp(trimmed, "Host:", 5) == 0) { + char ip[16], mac[18]; + if (sscanf(trimmed, "Host: %15s (%17[^)])", ip, mac) == 2) { + // new host with MAC + } else if (sscanf(trimmed, "Host: %15s (MAC unknown)", ip) == 1) { + // new host without MAC + } +} +// Open port: " 135/tcp open MSRPC" +int port; char service[32]; +if (sscanf(trimmed, "%d/tcp open %31s", &port, service) == 2 || + sscanf(trimmed, "%d/tcp open %31s", &port, service) == 2) { + // found open port +} +// Completion: "Scanned 4 hosts, found 4 open ports" +if (strstr(line, "Scanned") && strstr(line, "open ports")) { + int hosts, ports; + sscanf(line, "Scanned %d hosts, found %d open ports", &hosts, &ports); + // scan complete +} ``` ## Screen Building @@ -385,8 +429,10 @@ scan_networks → select_networks → list_sd → user picks HTML ``` // Check if password known via show_pass evil, or ask user -wifi_connect - → wait for "SUCCESS" / "FAILED" +// For open networks, omit the password: +wifi_connect // open network +wifi_connect // WPA/WPA2 network + → wait for "SUCCESS" / "FAILED" / "TIMEOUT" list_hosts → wait for "Discovered Hosts", parse IP->MAC lines @@ -399,6 +445,25 @@ arp_ban [IP] stop ``` +### 3b. Connect-NMAP (Port Scan) + +``` +wifi_connect [password] + → wait for "SUCCESS" / "FAILED" / "TIMEOUT" + +start_nmap [quick|medium|heavy] [IP] + → Phase 1 (host discovery): parse "Total: N hosts discovered" + → Phase 2 (port scanning): + for each "Host: ()" line → add host to list + for each "Scanning ports X-Y [current/total]" → update progress bar + for each "/tcp open " → add open port to current host + "(no open ports)" → mark current host as no-ports + → Completion: "Scanned N hosts, found M open ports" + +// Can be stopped anytime with: +stop → "(scan stopped by user)" +``` + ### 4. Portal/Karma Setup ``` @@ -419,7 +484,41 @@ start_wardrive // or start_wardrive_promisc → stop ``` -### 6. Bluetooth Locate +### 6. Beacon Spam from SSID File + +``` +list_ssids → show indexed SSID list + → user can add_ssid to append + → user can remove_ssid to delete +start_beacon_spam_ssids + → wait for "Beacon spam started. Use 'stop' to end." + → stop +``` + +Or with inline SSIDs: +``` +start_beacon_spam "SSID1" "SSID2" "SSID3" + → wait for "Beacon spam started. Use 'stop' to end." + → stop +``` + +### 7. PCAP Capture + +``` +// Radio mode (no prerequisites): +start_pcap radio + → wait for "PCAP radio capture started -> ..." + → stop → "PCAP saved: ... (N frames, M drops)" + +// Net mode (requires WiFi connection): +wifi_connect [password] + → wait for "SUCCESS" +start_pcap net + → wait for "PCAP net capture started -> ..." + → stop → "PCAP saved: ... (N frames, M drops)" +``` + +### 8. Bluetooth Locate ``` scan_bt → parse device list → show scrollable list diff --git a/.github/skills/janos-uart-app/commands-reference.md b/.github/skills/janos-uart-app/commands-reference.md index 39ad0d6..97faf6c 100644 --- a/.github/skills/janos-uart-app/commands-reference.md +++ b/.github/skills/janos-uart-app/commands-reference.md @@ -120,6 +120,30 @@ TP-Link_F5F8 (64:57:25:BB:90:6A) - **Parse**: Extract `#N` for counter, `AP=`, `STA=`, `Ch=`, `RSSI=` fields. - **Stop**: Send `stop`. +### `start_pcap` +- **Syntax**: `start_pcap [radio|net]` +- **Description**: Captures WiFi traffic to PCAP file on SD card. Default mode: `radio`. + - **Radio mode** (linktype 105): Promiscuous mode capturing all management/data/control frames on all channels. + - **Net mode** (linktype 1): Requires WiFi STA connection. Captures outbound packets and performs ARP spoofing MITM on detected hosts. +- **Example**: `start_pcap radio` or `start_pcap net` +- **Output**: +``` +PCAP radio capture started -> /sdcard/lab/pcaps/sniff_1.pcap +``` +- **On stop**: +``` +PCAP saved: /sdcard/lab/pcaps/sniff_1.pcap (1530 frames, 2 drops) +``` +- **Error outputs**: + - `"Usage: start_pcap radio|net"` (invalid argument) + - `"Not connected to WiFi. Use 'wifi_connect' first."` (net mode, not connected) + - `"Failed to initialize SD card: "` (SD init fail) + - `"Failed to create /sdcard/lab/pcaps directory"` (directory fail) + - `"Failed to open for writing"` (file fail) +- **Prerequisites**: SD card. For net mode: WiFi connected via `wifi_connect`. +- **Stop**: Send `stop`. +- **Notes**: Files are saved at `/sdcard/lab/pcaps/sniff_N.pcap` (N auto-increments). + --- ## Attacks @@ -181,8 +205,34 @@ Handshake #1 captured! ### `start_beacon_spam` - **Syntax**: `start_beacon_spam "SSID1" "SSID2" ...` -- **Description**: Broadcasts fake beacon frames with specified SSIDs. +- **Description**: Broadcasts fake beacon frames with specified SSIDs. Sets WiFi to APSTA mode and iterates through 2.4GHz channels (1-11), sending beacons for each SSID per channel. Max 32 SSIDs, each 1-32 characters. - **Example**: `start_beacon_spam "Free WiFi" "Starbucks" "Airport"` +- **Output**: +``` +Starting beacon spam with 3 SSIDs: + 1: Free WiFi + 2: Starbucks + 3: Airport +WiFi initialized for beacon spam... +Beacon spam started. Use 'stop' to end. +``` +- **Error outputs**: + - `"Usage: start_beacon_spam \"SSID1\" \"SSID2\" ..."` (no arguments) + - `"Beacon spam already running. Use 'stop' first."` (already active) + - `"Warning: SSID N invalid length (M), skipping"` (SSID too long/empty) + - `"No valid SSIDs provided"` (all SSIDs invalid) +- **Stop**: Send `stop`. + +### `start_beacon_spam_ssids` +- **Syntax**: `start_beacon_spam_ssids` +- **Description**: Same as `start_beacon_spam` but loads SSIDs from `/sdcard/lab/ssids.txt` (one SSID per line). Max 32 SSIDs loaded. +- **Prerequisite**: SD card with `/sdcard/lab/ssids.txt` containing SSIDs (one per line). Manage the file with `add_ssid`, `remove_ssid`, `list_ssids`. +- **Output**: Same as `start_beacon_spam` after loading SSIDs from file. +- **Error outputs**: + - `"Beacon spam already running. Use 'stop' first."` (already active) + - `"Failed to initialize SD card: "` (SD init fail) + - `"ssids.txt not found on SD card."` (file missing) + - `"ssids.txt is empty - no SSIDs to broadcast"` (file empty) - **Stop**: Send `stop`. ### `start_karma` @@ -256,9 +306,13 @@ Connect to 'MojaSiec' WiFi network to access the portal ## WiFi Connection (STA Mode) ### `wifi_connect` -- **Syntax**: `wifi_connect [ota] [ [DNS1] [DNS2]]` -- **Description**: Connects to an AP as STA. Optional static IP config. -- **Example**: `wifi_connect AX4 ruletka2022` +- **Syntax**: `wifi_connect [Password] [ota] [ [DNS1] [DNS2]]` +- **Description**: Connects to an AP as STA. Password is optional -- omit it for open (no-password) networks. Optional static IP config. +- **Examples**: + - WPA2 network: `wifi_connect AX4 ruletka2022` + - Open network: `wifi_connect BRW` + - With OTA: `wifi_connect AX4 ruletka2022 ota` + - Static IP: `wifi_connect AX4 ruletka2022 192.168.1.50 255.255.255.0 192.168.1.1` - **Output**: ``` Connecting to AP 'AX4'... @@ -268,10 +322,11 @@ WiFi initialized OK Waiting for connection result... Wi-Fi: connected to SSID='AX4' SUCCESS: Connected to 'AX4' +DHCP IP: 192.168.0.5, Netmask: 255.255.255.0, GW: 192.168.0.1 ``` - **Success marker**: `strstr("SUCCESS")` -- **Failure markers**: `strstr("FAILED")` or `strstr("Error")` -- **Notes**: `ota` flag enables OTA-related behavior. +- **Failure markers**: `strstr("FAILED")` or `strstr("TIMEOUT")` +- **Notes**: When password is omitted, firmware sets `authmode = WIFI_AUTH_OPEN`. When password is provided, `authmode = WIFI_AUTH_WPA2_PSK`. The `ota` flag triggers OTA check after successful connection. ### `wifi_disconnect` - **Syntax**: `wifi_disconnect` @@ -311,6 +366,85 @@ Sent 254 ARP requests, waiting for responses... --- +## Network Scanning (NMAP) + +### `start_nmap` +- **Syntax**: `start_nmap [quick|medium|heavy] [IP]` +- **Description**: TCP port scanner. Discovers live hosts on the LAN (ARP + ICMP) then probes each host's ports with non-blocking TCP connect (500ms timeout per port). Can scan a single IP or the full subnet. +- **Scan levels**: + - `quick` (default): 20 most common ports (FTP, SSH, HTTP, SMB, RDP, etc.) + - `medium`: 50 ports (adds LDAP, MQTT, Docker, Redis, etc.) + - `heavy`: 100 ports (adds TFTP, BGP, Modbus, MongoDB, Minecraft, etc.) +- **Examples**: + - `start_nmap` -- quick scan of entire subnet + - `start_nmap heavy` -- 100-port scan of entire subnet + - `start_nmap medium 192.168.0.4` -- 50-port scan of single host +- **Prerequisite**: `wifi_connect` (must be connected to a network). +- **Stop**: Send `stop` (checked between each port probe). +- **Output phases** (in order): + +**Phase 1 -- Host Discovery (only when not in single-host mode)**: +``` +[MEM] start_nmap: Internal=125/251KB, DMA=109/235KB, PSRAM=7944/8192KB +Scan level: heavy (100 ports) +Our IP: 192.168.0.5, Netmask: 255.255.255.0 +Phase 1: ARP scan (254 hosts)... +Sent 254 ARP requests, polling table for 4 seconds... +ARP: found 3 hosts +Phase 2: sent 251 ICMP pings, waiting for replies... +ICMP: found 1 additional hosts +Total: 4 hosts discovered (3 ARP + 1 ICMP) +``` + +**Phase 2 -- Port Scanning** (repeated per host): +``` +Scanning 4 host(s), 100 ports each (heavy)... +=== NMAP Scan Results === +Host: 192.168.0.1 (00:0B:00:00:AD:D0) + Scanning 192.168.0.1 ports 21-143 [1/100] ... + 80/tcp open HTTP + Scanning 192.168.0.1 ports 443-8443 [11/100] ... + Scanning 192.168.0.1 ports 111-636 [21/100] ... + ... +Host: 192.168.0.4 (00:C0:CA:B4:E6:3F) + Scanning 192.168.0.4 ports 21-143 [1/100] ... + 135/tcp open MSRPC + 139/tcp open NetBIOS + Scanning 192.168.0.4 ports 443-8443 [11/100] ... + 445/tcp open SMB + ... +Host: 192.168.0.128 (5C:D8:9E:8C:0C:B2) + Scanning 192.168.0.128 ports 21-143 [1/100] ... + ... + (no open ports) +========================= +Scanned 4 hosts, found 4 open ports +``` + +- **Key line formats for parsing**: + +| Line pattern | Meaning | Regex | +|---|---|---| +| `Scan level: ( ports)` | Scan started, extract level and total port count | `Scan level: (\w+) \((\d+) ports\)` | +| `Our IP: , Netmask: ` | Local IP info | `Our IP: ([\d.]+), Netmask: ([\d.]+)` | +| `Total: hosts discovered` | Host discovery complete, extract host count | `Total: (\d+) hosts discovered` | +| `Scanning host(s), ports each ()...` | Port scan phase starting | `Scanning (\d+) host.*?(\d+) ports` | +| `=== NMAP Scan Results ===` | Results header (marks start of per-host output) | literal match | +| `Host: ()` | New host block starts | `Host: ([\d.]+)\s+\(([0-9A-Fa-f:]+)\)` | +| `Host: (MAC unknown)` | New host block, no MAC | `Host: ([\d.]+)\s+\(MAC unknown\)` | +| ` Scanning ports - [/] ...` | Progress: current port batch | `Scanning ([\d.]+) ports (\d+)-(\d+) \[(\d+)/(\d+)\]` | +| ` /tcp open ` | Open port found | `(\d+)/tcp\s+open\s+(\S+)` | +| ` (no open ports)` | Host has no open ports | literal match | +| ` (scan stopped by user)` | User sent `stop` during scan | literal match | +| `=========================` | Results footer | literal match | +| `Scanned hosts, found open ports` | Final summary | `Scanned (\d+) hosts, found (\d+) open ports` | + +- **Completion marker**: `strstr("Scanned") && strstr("open ports")` -- this is the last line of output. +- **Progress tracking**: The `Scanning ports - [/]` lines are emitted every 10 ports. Use `current` and `total` to calculate percentage. Combine with the host index from counting `Host:` lines vs total from the `Scanning N host(s)` line for overall progress. +- **Single-host mode**: When an IP argument is given, host discovery is skipped entirely. Output starts with `Single-host mode, skipping host discovery.` followed directly by the port scan. + +--- + ## Bluetooth ### `scan_bt` @@ -440,9 +574,50 @@ Found 6 file(s) in /sdcard/lab/handshakes - **Description**: Deletes a file on SD card. - **Example**: `file_delete lab/handshakes/sample.pcap` -### `list_ssid` -- **Syntax**: `list_ssid` -- **Description**: Lists SSIDs from `/sdcard/lab/ssid.txt`. +### `list_ssids` +- **Syntax**: `list_ssids` +- **Description**: Lists SSIDs from `/sdcard/lab/ssids.txt` with 1-based index. +- **Output**: +``` +1 WiFi1 +2 TestNet +3 FreeHotspot +``` +- **Error outputs**: + - `"Failed to initialize SD card: "` (SD init fail) + - `"ssids.txt not found on SD card."` (file missing) + - `"ssids.txt is empty."` (file empty) +- **Notes**: No explicit completion marker — output ends after last indexed line. + +### `add_ssid` +- **Syntax**: `add_ssid ` +- **Description**: Appends a new SSID to `/sdcard/lab/ssids.txt`. +- **Example**: `add_ssid FreeWiFi` +- **Output**: `"Added SSID: FreeWiFi"` +- **Error outputs**: + - `"Usage: add_ssid "` (no argument) + - `"SSID length must be 1-32 characters"` (length invalid) + - `"Failed to initialize SD card: "` (SD init fail) + - `"Failed to open ssids.txt for writing"` (file open fail) +- **Notes**: SSID must be 1-32 characters. File is created if it doesn't exist. + +### `remove_ssid` +- **Syntax**: `remove_ssid ` +- **Description**: Removes SSID at given 1-based index from `/sdcard/lab/ssids.txt`. Use `list_ssids` to see indices. +- **Example**: `remove_ssid 2` +- **Output**: +``` +Removing SSID 2: TestNet +SSID removed. 2 SSIDs remaining. +``` +- **Error outputs**: + - `"Usage: remove_ssid "` (no argument) + - `"Index must be >= 1"` (invalid index) + - `"Failed to initialize SD card: "` (SD init fail) + - `"ssids.txt not found on SD card."` (file missing) + - `"Index N out of range (1-M)"` (index exceeds count) + - `"Failed to open ssids.txt for writing"` (file write fail) +- **Notes**: Remaining SSIDs are reindexed after removal. --- @@ -539,9 +714,9 @@ Found 6 file(s) in /sdcard/lab/handshakes - **Description**: Sets or reads display mode for attached OLED/LCD. ### `boot_button` -- **Syntax**: `boot_button read` | `boot_button list` | `boot_button set ` | `boot_button status ` -- **Description**: Configures boot button press actions. -- **Allowed commands**: `start_blackout`, `start_sniffer_dog`, `channel_view`, `packet_monitor`, `start_sniffer`, `scan_networks`, `start_gps_raw`, `start_wardrive`, `deauth_detector` +- **Syntax**: `boot_button read` | `boot_button list` | `boot_button set ` | `boot_button status ` +- **Description**: Configures boot button press actions. Multiple commands can be chained with commas, for example `list_sd, select_html 1, start_portal FreeWifi`. +- **Allowed commands**: `start_blackout`, `start_sniffer_dog`, `channel_view`, `packet_monitor`, `start_sniffer`, `scan_networks`, `start_gps_raw`, `start_wardrive`, `deauth_detector`, `list_sd`, `select_html`, `start_portal` ### `led` - **Syntax**: `led set ` | `led level <1-100>` | `led read` @@ -598,6 +773,12 @@ Found 6 file(s) in /sdcard/lab/handshakes - **Syntax**: `download` - **Description**: Reboots into ROM download (UART flashing) mode. +### `version` +- **Syntax**: `version` +- **Description**: Prints the current JanOS firmware version. +- **Output**: `"JanOS version: X.Y.Z"` +- **Notes**: Use to check which firmware version is running on the device. + ### `help` - **Syntax**: `help` or `help ` - **Description**: Lists all commands or shows help for a specific command. diff --git a/.github/workflows/esp32s3-build-master.yml b/.github/workflows/esp32s3-build-master.yml index 0c342b8..5e7eb17 100644 --- a/.github/workflows/esp32s3-build-master.yml +++ b/.github/workflows/esp32s3-build-master.yml @@ -23,7 +23,7 @@ on: - "true" default: "false" push: - branches: [main, master] + branches: [main, master, development] paths: - 'main/main.c' - '.github/scripts/**' @@ -38,7 +38,7 @@ jobs: contents: read runs-on: ubuntu-24.04 container: - image: espressif/idf:v6.0-beta1 + image: espressif/idf:release-v6.0 strategy: fail-fast: false matrix: @@ -355,6 +355,7 @@ jobs: discord: name: Discord needs: [release, package, pages] + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event_name == 'release' runs-on: ubuntu-24.04 steps: - name: Notify Discord diff --git a/.github/workflows/esp32s3-build-only.yml b/.github/workflows/esp32s3-build-only.yml index d7c9b90..0973767 100644 --- a/.github/workflows/esp32s3-build-only.yml +++ b/.github/workflows/esp32s3-build-only.yml @@ -3,7 +3,7 @@ name: ESP32S3 Build Only (IDF 6.0-dev) on: workflow_dispatch: push: - branches: [main, master] + branches: [main, master, development] paths: - 'main/main.c' - '.github/scripts/**' @@ -16,7 +16,7 @@ jobs: contents: read runs-on: [self-hosted, linux, x64] container: - image: espressif/idf:v6.0-dev + image: espressif/idf:release-v6.0 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv-full.bin b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv-full.bin index b10f284..6f1a483 100644 Binary files a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv-full.bin and b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv-full.bin differ diff --git a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv.bin b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv.bin index f102857..c8b0e8c 100644 Binary files a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv.bin and b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV-adv.bin differ diff --git a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV.bin b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV.bin index f102857..c8b0e8c 100644 Binary files a/binaries-esp32s3/adv/M5MonsterC5-CardputerADV.bin and b/binaries-esp32s3/adv/M5MonsterC5-CardputerADV.bin differ diff --git a/binaries-esp32s3/adv/bootloader-adv.bin b/binaries-esp32s3/adv/bootloader-adv.bin index 7d43238..f52a2c8 100644 Binary files a/binaries-esp32s3/adv/bootloader-adv.bin and b/binaries-esp32s3/adv/bootloader-adv.bin differ diff --git a/binaries-esp32s3/adv/bootloader.bin b/binaries-esp32s3/adv/bootloader.bin index 7d43238..f52a2c8 100644 Binary files a/binaries-esp32s3/adv/bootloader.bin and b/binaries-esp32s3/adv/bootloader.bin differ diff --git a/binaries-esp32s3/adv/flash_board.py b/binaries-esp32s3/adv/flash_board.py old mode 100644 new mode 100755 diff --git a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132-full.bin b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132-full.bin index b38bcfc..65effd6 100644 Binary files a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132-full.bin and b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132-full.bin differ diff --git a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132.bin b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132.bin index 6fa2891..3a2ca9f 100644 Binary files a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132.bin and b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV-k132.bin differ diff --git a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV.bin b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV.bin index 6fa2891..3a2ca9f 100644 Binary files a/binaries-esp32s3/k132/M5MonsterC5-CardputerADV.bin and b/binaries-esp32s3/k132/M5MonsterC5-CardputerADV.bin differ diff --git a/binaries-esp32s3/k132/bootloader-k132.bin b/binaries-esp32s3/k132/bootloader-k132.bin index 332a2ad..3929200 100644 Binary files a/binaries-esp32s3/k132/bootloader-k132.bin and b/binaries-esp32s3/k132/bootloader-k132.bin differ diff --git a/binaries-esp32s3/k132/bootloader.bin b/binaries-esp32s3/k132/bootloader.bin index 332a2ad..3929200 100644 Binary files a/binaries-esp32s3/k132/bootloader.bin and b/binaries-esp32s3/k132/bootloader.bin differ diff --git a/binaries-esp32s3/k132/flash_board.py b/binaries-esp32s3/k132/flash_board.py old mode 100644 new mode 100755 diff --git a/dependencies.lock b/dependencies.lock index d528ece..e4226e1 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -7,4 +7,4 @@ direct_dependencies: - idf manifest_hash: 8028f5a3af478070ea133ddcfcf6082b8591e09885325e7be01ce9027a8efc40 target: esp32s3 -version: 2.0.0 +version: 3.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ae5a32f..3e9ba22 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -46,12 +46,17 @@ idf_component_register( "screens/sniffer_probes_screen.c" "screens/global_attacks_screen.c" "screens/blackout_screen.c" + "screens/beacon_spam_menu_screen.c" + "screens/beacon_ssid_list_screen.c" + "screens/beacon_spam_screen.c" "screens/global_handshaker_screen.c" "screens/text_input_screen.c" "screens/global_portal_html_screen.c" "screens/global_portal_screen.c" "screens/sniffer_dog_screen.c" + "screens/wardrive_menu_screen.c" "screens/wardrive_screen.c" + "screens/wardrive_upload_screen.c" "screens/sniff_karma_menu_screen.c" "screens/global_sniffer_screen.c" "screens/karma_probes_screen.c" @@ -79,6 +84,8 @@ idf_component_register( "screens/wifi_connect_screen.c" "screens/arp_hosts_screen.c" "screens/arp_attack_screen.c" + "screens/mitm_sniffer_screen.c" + "screens/nmap_screen.c" "screens/rogue_ap_ssid_screen.c" "screens/rogue_ap_password_screen.c" "screens/rogue_ap_html_screen.c" diff --git a/main/drivers/buzzer_adv.c b/main/drivers/buzzer_adv.c index cf2c6fc..ae8c522 100644 --- a/main/drivers/buzzer_adv.c +++ b/main/drivers/buzzer_adv.c @@ -6,6 +6,7 @@ */ #include "buzzer.h" +#include "settings.h" #include "driver/i2s_std.h" #include "driver/i2c.h" #include "driver/gpio.h" @@ -29,6 +30,11 @@ static bool buzzer_initialized = false; // Fixed buffer to avoid malloc during beep static int16_t audio_buffer[CHUNK_FRAMES * 2]; +static bool buzzer_can_play(void) +{ + return buzzer_initialized && tx_handle && settings_get_sound_enabled(); +} + static esp_err_t es8311_write_reg(uint8_t reg, uint8_t val) { uint8_t data[2] = {reg, val}; @@ -173,7 +179,7 @@ esp_err_t buzzer_init(void) void buzzer_beep(uint32_t frequency_hz, uint32_t duration_ms) { - if (!buzzer_initialized || !tx_handle) { + if (!buzzer_can_play()) { return; } @@ -218,11 +224,13 @@ void buzzer_beep(uint32_t frequency_hz, uint32_t duration_ms) void buzzer_beep_attack(void) { + if (!buzzer_can_play()) return; buzzer_beep(2000, 80); } void buzzer_beep_success(void) { + if (!buzzer_can_play()) return; buzzer_beep(1000, 100); vTaskDelay(pdMS_TO_TICKS(30)); buzzer_beep(1500, 150); @@ -230,6 +238,7 @@ void buzzer_beep_success(void) void buzzer_beep_capture(void) { + if (!buzzer_can_play()) return; buzzer_beep(1200, 60); } diff --git a/main/drivers/buzzer_k132.c b/main/drivers/buzzer_k132.c index a1a4a25..e0a9af9 100644 --- a/main/drivers/buzzer_k132.c +++ b/main/drivers/buzzer_k132.c @@ -4,6 +4,7 @@ */ #include "buzzer.h" +#include "settings.h" #include "driver/i2s_std.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" @@ -22,6 +23,11 @@ static bool buzzer_initialized = false; static int16_t audio_buffer[CHUNK_FRAMES * 2]; +static bool buzzer_can_play(void) +{ + return buzzer_initialized && tx_handle && settings_get_sound_enabled(); +} + static esp_err_t init_i2s(void) { i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); @@ -95,7 +101,7 @@ esp_err_t buzzer_init(void) void buzzer_beep(uint32_t frequency_hz, uint32_t duration_ms) { - if (!buzzer_initialized || !tx_handle) { + if (!buzzer_can_play()) { return; } @@ -135,11 +141,13 @@ void buzzer_beep(uint32_t frequency_hz, uint32_t duration_ms) void buzzer_beep_attack(void) { + if (!buzzer_can_play()) return; buzzer_beep(2000, 80); } void buzzer_beep_success(void) { + if (!buzzer_can_play()) return; buzzer_beep(1000, 100); vTaskDelay(pdMS_TO_TICKS(30)); buzzer_beep(1500, 150); @@ -147,6 +155,7 @@ void buzzer_beep_success(void) void buzzer_beep_capture(void) { + if (!buzzer_can_play()) return; buzzer_beep(1200, 60); } diff --git a/main/main.c b/main/main.c index 8711d72..4702b32 100644 --- a/main/main.c +++ b/main/main.c @@ -22,7 +22,7 @@ #include "buzzer.h" #include "text_ui.h" -#define JANOS_ADV_VERSION "1.6.0" +#define JANOS_ADV_VERSION "1.7.1" // Screen timeout is now configurable via Settings (stored in NVS) diff --git a/main/screens/beacon_spam_menu_screen.c b/main/screens/beacon_spam_menu_screen.c new file mode 100644 index 0000000..c2b40b9 --- /dev/null +++ b/main/screens/beacon_spam_menu_screen.c @@ -0,0 +1,127 @@ +#include "beacon_spam_menu_screen.h" +#include "beacon_ssid_list_screen.h" +#include "beacon_spam_screen.h" +#include "text_ui.h" +#include "esp_log.h" +#include +#include + +static const char *TAG = "BEACON_MENU"; + +typedef struct { + const char *title; + screen_create_fn create_fn; +} menu_item_t; + +static const menu_item_t menu_items[] = { + {"List SSIDs", beacon_ssid_list_screen_create}, + {"Start Spam", beacon_spam_screen_create}, +}; + +#define MENU_ITEM_COUNT (sizeof(menu_items) / sizeof(menu_items[0])) + +typedef struct { + int selected_index; +} beacon_spam_menu_data_t; + +static void draw_screen(screen_t *self) +{ + beacon_spam_menu_data_t *data = (beacon_spam_menu_data_t *)self->user_data; + + ui_clear(); + ui_draw_title("Beacon Spam"); + + for (int i = 0; i < (int)MENU_ITEM_COUNT; i++) { + ui_draw_menu_item(i + 1, menu_items[i].title, i == data->selected_index, false, false); + } + + ui_draw_status("UP/DOWN:Nav ENTER:Select ESC:Back"); +} + +static void redraw_selection(beacon_spam_menu_data_t *data, int old_index, int new_index) +{ + ui_draw_menu_item(old_index + 1, menu_items[old_index].title, false, false, false); + ui_draw_menu_item(new_index + 1, menu_items[new_index].title, true, false, false); +} + +static void on_key(screen_t *self, key_code_t key) +{ + beacon_spam_menu_data_t *data = (beacon_spam_menu_data_t *)self->user_data; + + switch (key) { + case KEY_UP: + { + int old = data->selected_index; + data->selected_index = (data->selected_index > 0) ? data->selected_index - 1 : (int)MENU_ITEM_COUNT - 1; + redraw_selection(data, old, data->selected_index); + } + break; + + case KEY_DOWN: + { + int old = data->selected_index; + data->selected_index = (data->selected_index < (int)MENU_ITEM_COUNT - 1) ? data->selected_index + 1 : 0; + redraw_selection(data, old, data->selected_index); + } + break; + + case KEY_ENTER: + case KEY_SPACE: + { + const menu_item_t *item = &menu_items[data->selected_index]; + if (item->create_fn) { + screen_manager_push(item->create_fn, NULL); + screen_manager_tick(); + } + } + break; + + case KEY_ESC: + case KEY_Q: + case KEY_BACKSPACE: + screen_manager_pop(); + break; + + default: + break; + } +} + +static void on_destroy(screen_t *self) +{ + if (self->user_data) { + free(self->user_data); + } +} + +static void on_resume(screen_t *self) +{ + draw_screen(self); +} + +screen_t* beacon_spam_menu_screen_create(void *params) +{ + (void)params; + + ESP_LOGI(TAG, "Creating beacon spam menu screen..."); + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + beacon_spam_menu_data_t *data = calloc(1, sizeof(beacon_spam_menu_data_t)); + if (!data) { + free(screen); + return NULL; + } + + screen->user_data = data; + screen->on_key = on_key; + screen->on_destroy = on_destroy; + screen->on_resume = on_resume; + screen->on_draw = draw_screen; + + draw_screen(screen); + + ESP_LOGI(TAG, "Beacon spam menu screen created"); + return screen; +} diff --git a/main/screens/beacon_spam_menu_screen.h b/main/screens/beacon_spam_menu_screen.h new file mode 100644 index 0000000..fb182ee --- /dev/null +++ b/main/screens/beacon_spam_menu_screen.h @@ -0,0 +1,8 @@ +#ifndef BEACON_SPAM_MENU_SCREEN_H +#define BEACON_SPAM_MENU_SCREEN_H + +#include "screen_manager.h" + +screen_t* beacon_spam_menu_screen_create(void *params); + +#endif // BEACON_SPAM_MENU_SCREEN_H diff --git a/main/screens/beacon_spam_screen.c b/main/screens/beacon_spam_screen.c new file mode 100644 index 0000000..b253c72 --- /dev/null +++ b/main/screens/beacon_spam_screen.c @@ -0,0 +1,114 @@ +#include "beacon_spam_screen.h" +#include "uart_handler.h" +#include "text_ui.h" +#include "buzzer.h" +#include "esp_log.h" +#include +#include + +static const char *TAG = "BEACON_SPAM"; + +typedef struct { + int ssid_count; + bool needs_redraw; +} beacon_spam_data_t; + +static void draw_screen(screen_t *self) +{ + beacon_spam_data_t *data = (beacon_spam_data_t *)self->user_data; + + ui_clear(); + ui_draw_title("Beacon Spam"); + + ui_print_center(3, "Beacon spam active", UI_COLOR_HIGHLIGHT); + + if (data->ssid_count > 0) { + char info[32]; + snprintf(info, sizeof(info), "%d SSIDs broadcasting", data->ssid_count); + ui_print_center(5, info, UI_COLOR_TEXT); + } + + ui_draw_status("ESC: Stop & Exit"); +} + +static void uart_line_callback(const char *line, void *user_data) +{ + beacon_spam_data_t *data = (beacon_spam_data_t *)user_data; + if (!data) return; + + // "Starting beacon spam with N SSIDs:" + const char *match = strstr(line, "Starting beacon spam with "); + if (match) { + int n = atoi(match + 26); + if (n > 0) { + data->ssid_count = n; + data->needs_redraw = true; + } + } +} + +static void on_tick(screen_t *self) +{ + beacon_spam_data_t *data = (beacon_spam_data_t *)self->user_data; + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); + } +} + +static void on_key(screen_t *self, key_code_t key) +{ + (void)self; + + switch (key) { + case KEY_ESC: + case KEY_Q: + case KEY_BACKSPACE: + uart_send_command("stop"); + screen_manager_pop(); + break; + + default: + break; + } +} + +static void on_destroy(screen_t *self) +{ + uart_clear_line_callback(); + + if (self->user_data) { + free(self->user_data); + } +} + +screen_t* beacon_spam_screen_create(void *params) +{ + (void)params; + + ESP_LOGI(TAG, "Creating beacon spam screen..."); + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + beacon_spam_data_t *data = calloc(1, sizeof(beacon_spam_data_t)); + if (!data) { + free(screen); + return NULL; + } + + screen->user_data = data; + screen->on_key = on_key; + screen->on_destroy = on_destroy; + screen->on_draw = draw_screen; + screen->on_tick = on_tick; + + uart_register_line_callback(uart_line_callback, data); + uart_send_command("start_beacon_spam_ssids"); + buzzer_beep_attack(); + + draw_screen(screen); + + ESP_LOGI(TAG, "Beacon spam screen created, attack started"); + return screen; +} diff --git a/main/screens/beacon_spam_screen.h b/main/screens/beacon_spam_screen.h new file mode 100644 index 0000000..2611e2d --- /dev/null +++ b/main/screens/beacon_spam_screen.h @@ -0,0 +1,8 @@ +#ifndef BEACON_SPAM_SCREEN_H +#define BEACON_SPAM_SCREEN_H + +#include "screen_manager.h" + +screen_t* beacon_spam_screen_create(void *params); + +#endif // BEACON_SPAM_SCREEN_H diff --git a/main/screens/beacon_ssid_list_screen.c b/main/screens/beacon_ssid_list_screen.c new file mode 100644 index 0000000..05c6d67 --- /dev/null +++ b/main/screens/beacon_ssid_list_screen.c @@ -0,0 +1,315 @@ +#include "beacon_ssid_list_screen.h" +#include "text_input_screen.h" +#include "uart_handler.h" +#include "text_ui.h" +#include "esp_log.h" +#include +#include +#include + +static const char *TAG = "BEACON_SSID_LIST"; + +#define MAX_ENTRIES 64 +#define MAX_SSID_LEN 33 +#define VISIBLE_ITEMS 6 + +typedef struct { + char ssids[MAX_ENTRIES][MAX_SSID_LEN]; + int entry_count; + int selected_index; + int scroll_offset; + bool loading; + bool needs_redraw; + bool first_draw_done; + int ticks_since_first_draw; +} beacon_ssid_list_data_t; + +static void draw_screen(screen_t *self); + +static bool is_skip_line(const char *line) +{ + if (strlen(line) < 3) return true; + + if ((line[0] == 'I' || line[0] == 'W' || line[0] == 'E' || line[0] == 'D') + && line[1] == ' ' && line[2] == '(') { + return true; + } + + if (strstr(line, "[MEM]") != NULL) return true; + if (strncmp(line, "list_ssids", 10) == 0) return true; + + const char *p = line; + while (*p == ' ') p++; + if (*p == '>') return true; + + return false; +} + +static void uart_line_callback(const char *line, void *user_data) +{ + beacon_ssid_list_data_t *data = (beacon_ssid_list_data_t *)user_data; + if (!data || data->entry_count >= MAX_ENTRIES) return; + if (strlen(line) == 0) return; + if (is_skip_line(line)) return; + + if (strstr(line, "not found") != NULL || strstr(line, "empty") != NULL || + strstr(line, "Empty") != NULL) { + data->loading = false; + if (data->first_draw_done) { + data->needs_redraw = true; + } + return; + } + + // Parse "N SSID_TEXT" where N is a number followed by a space + const char *p = line; + while (*p && isdigit((unsigned char)*p)) p++; + if (p == line || *p != ' ') return; + p++; // skip space + + if (*p == '\0') return; + + strncpy(data->ssids[data->entry_count], p, MAX_SSID_LEN - 1); + data->ssids[data->entry_count][MAX_SSID_LEN - 1] = '\0'; + data->entry_count++; + data->loading = false; + + if (data->first_draw_done) { + data->needs_redraw = true; + } +} + +static void draw_screen(screen_t *self) +{ + beacon_ssid_list_data_t *data = (beacon_ssid_list_data_t *)self->user_data; + + ui_clear(); + + char title[32]; + snprintf(title, sizeof(title), "SSIDs (%d)", data->entry_count); + ui_draw_title(title); + + if (data->loading) { + ui_print_center(3, "Loading...", UI_COLOR_DIMMED); + } else if (data->entry_count == 0) { + ui_print_center(3, "No SSIDs found", UI_COLOR_DIMMED); + } else { + int start_row = 1; + + for (int i = 0; i < VISIBLE_ITEMS; i++) { + int entry_idx = data->scroll_offset + i; + + if (entry_idx < data->entry_count) { + char label[40]; + snprintf(label, sizeof(label), "%d.%.26s", entry_idx + 1, data->ssids[entry_idx]); + label[29] = '\0'; + + bool selected = (entry_idx == data->selected_index); + ui_draw_menu_item(start_row + i, label, selected, false, false); + } + } + + if (data->scroll_offset > 0) { + ui_print(UI_COLS - 2, 1, "^", UI_COLOR_DIMMED); + } + if (data->scroll_offset + VISIBLE_ITEMS < data->entry_count) { + ui_print(UI_COLS - 2, VISIBLE_ITEMS, "v", UI_COLOR_DIMMED); + } + } + + ui_draw_status("A-Add D-Del ESC:Back"); +} + +static void reload_list(beacon_ssid_list_data_t *data) +{ + data->entry_count = 0; + data->selected_index = 0; + data->scroll_offset = 0; + data->loading = true; + data->needs_redraw = false; + data->first_draw_done = false; + data->ticks_since_first_draw = 0; + uart_send_command("list_ssids"); +} + +static void on_ssid_added(const char *text, void *user_data) +{ + (void)user_data; + + char cmd[48]; + snprintf(cmd, sizeof(cmd), "add_ssid \"%s\"", text); + uart_send_command(cmd); + + screen_manager_pop(); +} + +static void on_tick(screen_t *self) +{ + beacon_ssid_list_data_t *data = (beacon_ssid_list_data_t *)self->user_data; + + if (!data->first_draw_done) { + data->first_draw_done = true; + data->ticks_since_first_draw = 0; + data->needs_redraw = false; + draw_screen(self); + return; + } + + data->ticks_since_first_draw++; + + if (data->ticks_since_first_draw <= 2) { + return; + } + + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); + } +} + +static void on_key(screen_t *self, key_code_t key) +{ + beacon_ssid_list_data_t *data = (beacon_ssid_list_data_t *)self->user_data; + + switch (key) { + case KEY_UP: + if (data->selected_index > 0) { + int old_idx = data->selected_index; + if (data->selected_index == data->scroll_offset && data->scroll_offset > 0) { + data->scroll_offset -= VISIBLE_ITEMS; + if (data->scroll_offset < 0) data->scroll_offset = 0; + data->selected_index = data->scroll_offset + VISIBLE_ITEMS - 1; + if (data->selected_index >= data->entry_count) { + data->selected_index = data->entry_count - 1; + } + draw_screen(self); + } else { + data->selected_index--; + int start_row = 1; + for (int idx = old_idx; idx >= data->selected_index; idx--) { + int i = idx - data->scroll_offset; + if (i >= 0 && i < VISIBLE_ITEMS && idx < data->entry_count) { + char label[40]; + snprintf(label, sizeof(label), "%d.%.26s", idx + 1, data->ssids[idx]); + label[29] = '\0'; + ui_draw_menu_item(start_row + i, label, idx == data->selected_index, false, false); + } + } + } + } else if (data->entry_count > 0) { + data->selected_index = data->entry_count - 1; + data->scroll_offset = (data->selected_index / VISIBLE_ITEMS) * VISIBLE_ITEMS; + draw_screen(self); + } + break; + + case KEY_DOWN: + if (data->selected_index < data->entry_count - 1) { + int old_idx = data->selected_index; + if (data->selected_index == data->scroll_offset + VISIBLE_ITEMS - 1) { + data->scroll_offset += VISIBLE_ITEMS; + data->selected_index = data->scroll_offset; + draw_screen(self); + } else { + data->selected_index++; + int start_row = 1; + for (int idx = old_idx; idx <= data->selected_index; idx++) { + int i = idx - data->scroll_offset; + if (i >= 0 && i < VISIBLE_ITEMS && idx < data->entry_count) { + char label[40]; + snprintf(label, sizeof(label), "%d.%.26s", idx + 1, data->ssids[idx]); + label[29] = '\0'; + ui_draw_menu_item(start_row + i, label, idx == data->selected_index, false, false); + } + } + } + } else if (data->entry_count > 0) { + data->selected_index = 0; + data->scroll_offset = 0; + draw_screen(self); + } + break; + + case KEY_A: + { + text_input_params_t *params = malloc(sizeof(text_input_params_t)); + if (params) { + params->title = "Add SSID"; + params->hint = "Type SSID, ENTER to add"; + params->on_submit = on_ssid_added; + params->user_data = NULL; + screen_manager_push(text_input_screen_create, params); + } + } + break; + + case KEY_D: + if (data->entry_count > 0 && data->selected_index < data->entry_count) { + char cmd[32]; + snprintf(cmd, sizeof(cmd), "remove_ssid %d", data->selected_index + 1); + uart_send_command(cmd); + + uart_register_line_callback(uart_line_callback, data); + reload_list(data); + draw_screen(self); + } + break; + + case KEY_ESC: + case KEY_BACKSPACE: + screen_manager_pop(); + break; + + default: + break; + } +} + +static void on_destroy(screen_t *self) +{ + uart_clear_line_callback(); + + if (self->user_data) { + free(self->user_data); + } +} + +static void on_resume(screen_t *self) +{ + beacon_ssid_list_data_t *data = (beacon_ssid_list_data_t *)self->user_data; + uart_register_line_callback(uart_line_callback, data); + reload_list(data); + draw_screen(self); +} + +screen_t* beacon_ssid_list_screen_create(void *params) +{ + (void)params; + + ESP_LOGI(TAG, "Creating beacon SSID list screen..."); + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + beacon_ssid_list_data_t *data = calloc(1, sizeof(beacon_ssid_list_data_t)); + if (!data) { + free(screen); + return NULL; + } + + data->loading = true; + data->first_draw_done = false; + + screen->user_data = data; + screen->on_key = on_key; + screen->on_destroy = on_destroy; + screen->on_resume = on_resume; + screen->on_draw = draw_screen; + screen->on_tick = on_tick; + + uart_register_line_callback(uart_line_callback, data); + uart_send_command("list_ssids"); + + ESP_LOGI(TAG, "Beacon SSID list screen created"); + return screen; +} diff --git a/main/screens/beacon_ssid_list_screen.h b/main/screens/beacon_ssid_list_screen.h new file mode 100644 index 0000000..e31f1b5 --- /dev/null +++ b/main/screens/beacon_ssid_list_screen.h @@ -0,0 +1,8 @@ +#ifndef BEACON_SSID_LIST_SCREEN_H +#define BEACON_SSID_LIST_SCREEN_H + +#include "screen_manager.h" + +screen_t* beacon_ssid_list_screen_create(void *params); + +#endif // BEACON_SSID_LIST_SCREEN_H diff --git a/main/screens/data_detail_screen.c b/main/screens/data_detail_screen.c index 0a5e766..1e1208b 100644 --- a/main/screens/data_detail_screen.c +++ b/main/screens/data_detail_screen.c @@ -315,7 +315,7 @@ static void on_key(screen_t *self, key_code_t key) // Send connect command char cmd[128]; - snprintf(cmd, sizeof(cmd), "wifi_connect %s %s", + snprintf(cmd, sizeof(cmd), "wifi_connect \"%s\" \"%s\"", data->connect_ssid, data->connect_password); uart_send_command(cmd); } diff --git a/main/screens/evil_twin_passwords_screen.c b/main/screens/evil_twin_passwords_screen.c index 3429305..8e67f4c 100644 --- a/main/screens/evil_twin_passwords_screen.c +++ b/main/screens/evil_twin_passwords_screen.c @@ -207,7 +207,6 @@ static void on_tick(screen_t *self) // Block redraws for 2 ticks after first render to avoid double draw if (data->ticks_since_first_draw <= 2) { - data->needs_redraw = false; return; } diff --git a/main/screens/global_attacks_screen.c b/main/screens/global_attacks_screen.c index c27a3f4..7db1a4f 100644 --- a/main/screens/global_attacks_screen.c +++ b/main/screens/global_attacks_screen.c @@ -9,7 +9,8 @@ #include "text_input_screen.h" #include "global_portal_html_screen.h" #include "sniffer_dog_screen.h" -#include "wardrive_screen.h" +#include "wardrive_menu_screen.h" +#include "beacon_spam_menu_screen.h" #include "placeholder_screen.h" #include "settings.h" #include "text_ui.h" @@ -25,6 +26,7 @@ typedef enum { GLOBAL_ATK_HANDSHAKER, GLOBAL_ATK_PORTAL, GLOBAL_ATK_SNIFFER_DOG, + GLOBAL_ATK_BEACON_SPAM, GLOBAL_ATK_WARDRIVE, GLOBAL_ATK_COUNT } global_attack_type_t; @@ -42,11 +44,12 @@ static const global_attack_def_t all_global_attacks[] = { {"Handshaker", GLOBAL_ATK_HANDSHAKER, true}, {"Portal", GLOBAL_ATK_PORTAL, false}, {"Sniffer Dog", GLOBAL_ATK_SNIFFER_DOG, true}, + {"Beacon Spam", GLOBAL_ATK_BEACON_SPAM, false}, {"Wardrive", GLOBAL_ATK_WARDRIVE, false}, }; #define ALL_GLOBAL_ATTACKS_COUNT (sizeof(all_global_attacks) / sizeof(all_global_attacks[0])) -#define MAX_VISIBLE_GLOBAL_ATTACKS 5 +#define MAX_VISIBLE_GLOBAL_ATTACKS 6 /** * @brief Callback when portal SSID is entered @@ -137,6 +140,7 @@ static void on_key(screen_t *self, key_code_t key) { // Get the actual attack type from the filtered list global_attack_type_t attack = data->visible_attacks[data->selected_index]; + ESP_LOGI(TAG, "Select idx=%d attack=%d", data->selected_index, attack); switch (attack) { case GLOBAL_ATK_BLACKOUT: @@ -164,8 +168,12 @@ static void on_key(screen_t *self, key_code_t key) screen_manager_push(sniffer_dog_screen_create, NULL); break; + case GLOBAL_ATK_BEACON_SPAM: + screen_manager_push(beacon_spam_menu_screen_create, NULL); + break; + case GLOBAL_ATK_WARDRIVE: - screen_manager_push(wardrive_screen_create, NULL); + screen_manager_push(wardrive_menu_screen_create, NULL); break; default: diff --git a/main/screens/handshakes_screen.c b/main/screens/handshakes_screen.c index f757355..150e30b 100644 --- a/main/screens/handshakes_screen.c +++ b/main/screens/handshakes_screen.c @@ -213,7 +213,6 @@ static void on_tick(screen_t *self) // Block redraws for 2 ticks after first render to avoid double draw if (data->ticks_since_first_draw <= 2) { - data->needs_redraw = false; return; } diff --git a/main/screens/home_screen.c b/main/screens/home_screen.c index cfbe8e3..6cbda4a 100644 --- a/main/screens/home_screen.c +++ b/main/screens/home_screen.c @@ -11,6 +11,7 @@ #include "deauth_detector_screen.h" #include "compromised_menu_screen.h" #include "network_attacks_screen.h" +#include "wardrive_menu_screen.h" #include "settings_screen.h" #include "placeholder_screen.h" #include "settings.h" @@ -30,12 +31,13 @@ typedef struct { static const menu_item_t menu_items[] = { {"WiFi Scan & Attack", "WiFi Scan & Test", wifi_scan_screen_create, NULL}, - {"Global WiFi Attacks", "Global WiFi Tests", global_attacks_screen_create, NULL}, - {"WiFi Sniff&Karma", "WiFi Sniff&Karma", sniff_karma_menu_screen_create, NULL}, - {"Deauth Detector", "Deauth Detector", deauth_detector_screen_create, NULL}, {"Bluetooth", "Bluetooth", bt_menu_screen_create, NULL}, {"Compromised data", "Compromised data", compromised_menu_screen_create, NULL}, - {"Network Attacks", "Network Tests", network_attacks_screen_create, NULL}, + {"Deauth Detector", "Deauth Detector", deauth_detector_screen_create, NULL}, + {"Global WiFi Attacks", "Global WiFi Tests", global_attacks_screen_create, NULL}, + {"Network Tools", "Network Tools", network_attacks_screen_create, NULL}, + {"Wardrive", "Wardrive", wardrive_menu_screen_create, NULL}, + {"WiFi Sniff&Karma", "WiFi Sniff&Karma", sniff_karma_menu_screen_create, NULL}, {"Settings", "Settings", settings_screen_create, NULL}, }; diff --git a/main/screens/mitm_sniffer_screen.c b/main/screens/mitm_sniffer_screen.c new file mode 100644 index 0000000..cc949c7 --- /dev/null +++ b/main/screens/mitm_sniffer_screen.c @@ -0,0 +1,67 @@ +/** + * @file mitm_sniffer_screen.c + * @brief MITM network sniffer screen implementation + * + * Sends start_pcap net on create, shows sniffing status. + * On ESC sends stop and pops back. + */ + +#include "mitm_sniffer_screen.h" +#include "uart_handler.h" +#include "text_ui.h" +#include "esp_log.h" +#include + +static const char *TAG = "MITM_SNIFF"; + +static void draw_screen(screen_t *self) +{ + (void)self; + + ui_clear(); + ui_draw_title("MITM Sniffer"); + ui_print_center(3, "Sniffing...", UI_COLOR_HIGHLIGHT); + ui_draw_status("ESC: Stop & Exit"); +} + +static void on_key(screen_t *self, key_code_t key) +{ + switch (key) { + case KEY_ESC: + case KEY_Q: + case KEY_BACKSPACE: + uart_send_command("stop"); + screen_manager_pop(); + break; + + default: + break; + } +} + +static void on_destroy(screen_t *self) +{ + (void)self; +} + +screen_t* mitm_sniffer_screen_create(void *params) +{ + (void)params; + + ESP_LOGI(TAG, "Creating MITM sniffer screen..."); + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + screen->user_data = NULL; + screen->on_key = on_key; + screen->on_destroy = on_destroy; + screen->on_draw = draw_screen; + + uart_send_command("start_pcap net"); + + draw_screen(screen); + + ESP_LOGI(TAG, "MITM sniffer screen created, capture started"); + return screen; +} diff --git a/main/screens/mitm_sniffer_screen.h b/main/screens/mitm_sniffer_screen.h new file mode 100644 index 0000000..aeb4e73 --- /dev/null +++ b/main/screens/mitm_sniffer_screen.h @@ -0,0 +1,18 @@ +/** + * @file mitm_sniffer_screen.h + * @brief MITM network sniffer screen + */ + +#ifndef MITM_SNIFFER_SCREEN_H +#define MITM_SNIFFER_SCREEN_H + +#include "screen_manager.h" + +/** + * @brief Create the MITM sniffer screen + * @param params Not used + * @return Screen instance + */ +screen_t* mitm_sniffer_screen_create(void *params); + +#endif // MITM_SNIFFER_SCREEN_H diff --git a/main/screens/network_attacks_screen.c b/main/screens/network_attacks_screen.c index 4a19234..5152613 100644 --- a/main/screens/network_attacks_screen.c +++ b/main/screens/network_attacks_screen.c @@ -7,6 +7,8 @@ #include "wifi_connect_screen.h" #include "arp_hosts_screen.h" #include "wpasec_upload_screen.h" +#include "mitm_sniffer_screen.h" +#include "nmap_screen.h" #include "settings.h" #include "uart_handler.h" #include "text_ui.h" @@ -20,8 +22,10 @@ static const char *TAG = "NET_ATTACKS"; #define MENU_WIFI 0 #define MENU_ARP 1 #define MENU_WPASEC 2 +#define MENU_MITM 3 +#define MENU_NMAP 4 -#define MAX_MENU_ITEMS 3 +#define MAX_MENU_ITEMS 5 // Screen user data typedef struct { @@ -37,6 +41,8 @@ static const char* get_menu_text(int item_id) case MENU_WIFI: return uart_is_wifi_connected() ? "Disconnect from WiFi" : "Connect to WiFi"; case MENU_ARP: return "ARP Poisoning"; case MENU_WPASEC: return "WPA-SEC Upload"; + case MENU_MITM: return "MITM Sniffer"; + case MENU_NMAP: return "Nmap Scanner"; default: return ""; } } @@ -119,9 +125,12 @@ static void on_key(screen_t *self, key_code_t key) } else if (item_id == MENU_ARP) { // ARP Poisoning - push hosts screen screen_manager_push(arp_hosts_screen_create, NULL); + } else if (item_id == MENU_MITM) { + screen_manager_push(mitm_sniffer_screen_create, NULL); } else if (item_id == MENU_WPASEC) { - // WPA-SEC Upload screen_manager_push(wpasec_upload_screen_create, NULL); + } else if (item_id == MENU_NMAP) { + screen_manager_push(nmap_screen_create, NULL); } break; } @@ -173,7 +182,9 @@ screen_t* network_attacks_screen_create(void *params) data->items[idx++] = MENU_WIFI; if (red_team) { data->items[idx++] = MENU_ARP; + data->items[idx++] = MENU_MITM; } + data->items[idx++] = MENU_NMAP; data->items[idx++] = MENU_WPASEC; data->menu_count = idx; diff --git a/main/screens/network_info_screen.c b/main/screens/network_info_screen.c index ba89253..c1d3452 100644 --- a/main/screens/network_info_screen.c +++ b/main/screens/network_info_screen.c @@ -188,7 +188,7 @@ static void on_password_submitted(const char *text, void *user_data) // Send connect command char cmd[128]; - snprintf(cmd, sizeof(cmd), "wifi_connect %s %s", data->network.ssid, data->password); + snprintf(cmd, sizeof(cmd), "wifi_connect \"%s\" \"%s\"", data->network.ssid, data->password); uart_send_command(cmd); } diff --git a/main/screens/nmap_screen.c b/main/screens/nmap_screen.c new file mode 100644 index 0000000..10aacdc --- /dev/null +++ b/main/screens/nmap_screen.c @@ -0,0 +1,867 @@ +/** + * @file nmap_screen.c + * @brief Nmap port scanner screen implementation + * + * Flow: Check WiFi -> Target method (All/Select) -> [Host scan -> Pick host] + * -> Scan type (quick/medium/heavy) -> Scanning ports -> Results + */ + +#include "nmap_screen.h" +#include "uart_handler.h" +#include "text_ui.h" +#include "display.h" +#include "esp_log.h" +#include "esp_timer.h" +#include +#include +#include + +static const char *TAG = "NMAP"; + +#define MAX_HOSTS 32 +#define MAX_PORTS 64 +#define VISIBLE_ITEMS 5 + +/* ---- Data structures ---- */ + +typedef enum { + STATE_CHECK_WIFI, + STATE_TARGET_METHOD, + STATE_SCANNING_HOSTS, + STATE_SELECT_HOST, + STATE_SELECT_SCAN_TYPE, + STATE_SCANNING_PORTS, + STATE_RESULTS +} nmap_state_t; + +typedef struct { + int port; + char service[16]; +} open_port_t; + +typedef struct { + char ip[16]; + char mac[18]; + open_port_t ports[MAX_PORTS]; + int port_count; + bool no_open_ports; +} nmap_host_t; + +typedef struct { + char ip[16]; + char mac[18]; +} discovered_host_t; + +typedef struct { + nmap_state_t state; + screen_t *self; + bool needs_redraw; + + /* Target method: 0=All hosts, 1=Scan & select */ + int method_index; + + /* Host discovery */ + discovered_host_t hosts[MAX_HOSTS]; + int host_count; + bool host_scan_started; + bool host_scan_done; + int selected_host; + int host_scroll; + int animation_frame; + esp_timer_handle_t update_timer; + + /* Selected target */ + char target_ip[16]; /* empty = all hosts */ + + /* Scan type: 0=quick, 1=medium, 2=heavy */ + int scan_type_index; + + /* Nmap scan progress */ + char current_scan_ip[16]; + int current_progress; /* 0-100 for current host */ + int total_hosts_scanned; + int total_ports_found; + + /* Results */ + nmap_host_t results[MAX_HOSTS]; + int result_count; + int current_result_host; /* index into results[] being populated */ + bool scan_complete; + int result_scroll; + int result_selected; + int result_total_lines; /* total lines in result view */ +} nmap_data_t; + +/* Forward declarations */ +static void draw_screen(screen_t *self); +static void stop_timer(nmap_data_t *data); + +/* ---- Scan type helpers ---- */ + +static const char* scan_type_str(int idx) +{ + switch (idx) { + case 0: return "quick"; + case 1: return "medium"; + case 2: return "heavy"; + default: return "quick"; + } +} + +static const char* scan_type_label(int idx) +{ + switch (idx) { + case 0: return "Quick FTP,SSH,HTTP,SMB,RDP"; + case 1: return "Medium LDAP,MQTT,Docker,Redis"; + case 2: return "Heavy TFTP,BGP,Modbus,Mongo"; + default: return ""; + } +} + +/* ---- UART callback for host discovery (list_hosts) ---- */ + +static void host_scan_line_cb(const char *line, void *user_data) +{ + nmap_data_t *data = (nmap_data_t *)user_data; + if (!data || data->state != STATE_SCANNING_HOSTS) return; + + /* Start marker */ + if (strstr(line, "=== Discovered Hosts ===") != NULL) { + data->host_scan_started = true; + data->host_count = 0; + return; + } + + /* End marker: "Found N hosts" */ + if (strstr(line, "Found") != NULL && strstr(line, "hosts") != NULL) { + data->host_scan_done = true; + data->needs_redraw = true; + return; + } + + /* Parse host line: " IP -> MAC" */ + if (data->host_scan_started && data->host_count < MAX_HOSTS) { + const char *arrow = strstr(line, "->"); + if (!arrow) return; + + const char *ip_start = line; + while (*ip_start == ' ') ip_start++; + const char *ip_end = ip_start; + while (*ip_end && *ip_end != ' ') ip_end++; + size_t ip_len = ip_end - ip_start; + if (ip_len == 0 || ip_len >= sizeof(data->hosts[0].ip)) return; + + discovered_host_t *h = &data->hosts[data->host_count]; + memcpy(h->ip, ip_start, ip_len); + h->ip[ip_len] = '\0'; + + const char *mac_start = arrow + 2; + while (*mac_start == ' ') mac_start++; + const char *mac_end = mac_start; + while (*mac_end && *mac_end != ' ' && *mac_end != '[') mac_end++; + size_t mac_len = mac_end - mac_start; + if (mac_len >= sizeof(h->mac)) mac_len = sizeof(h->mac) - 1; + memcpy(h->mac, mac_start, mac_len); + h->mac[mac_len] = '\0'; + + data->host_count++; + } +} + +/* ---- UART callback for nmap scan ---- */ + +static void nmap_scan_line_cb(const char *line, void *user_data) +{ + nmap_data_t *data = (nmap_data_t *)user_data; + if (!data || data->state != STATE_SCANNING_PORTS) return; + + /* Skip log/debug lines */ + if (strstr(line, "[MEM]") != NULL) return; + + /* Trim leading spaces */ + const char *trimmed = line; + while (*trimmed == ' ') trimmed++; + + /* New host: "Host: 192.168.0.4 (00:C0:CA:B4:E6:3F)" or "Host: 192.168.0.5 (MAC unknown)" */ + if (strncmp(trimmed, "Host:", 5) == 0) { + if (data->result_count < MAX_HOSTS) { + nmap_host_t *h = &data->results[data->result_count]; + memset(h, 0, sizeof(nmap_host_t)); + + char ip[16] = {0}; + char mac[18] = {0}; + if (sscanf(trimmed, "Host: %15s (%17[^)])", ip, mac) >= 1) { + strncpy(h->ip, ip, sizeof(h->ip) - 1); + if (strstr(trimmed, "MAC unknown") != NULL) { + strcpy(h->mac, "unknown"); + } else { + strncpy(h->mac, mac, sizeof(h->mac) - 1); + } + data->current_result_host = data->result_count; + data->result_count++; + } + } + data->needs_redraw = true; + return; + } + + /* Progress: " Scanning 192.168.0.4 ports 21-143 [1/100] ..." */ + if (strstr(line, "Scanning") != NULL && strstr(line, "ports") != NULL && strchr(line, '[')) { + char ip[16] = {0}; + int port_from = 0, port_to = 0, current = 0, total = 0; + if (sscanf(trimmed, "Scanning %15s ports %d-%d [%d/%d]", + ip, &port_from, &port_to, ¤t, &total) == 5 && total > 0) { + strncpy(data->current_scan_ip, ip, sizeof(data->current_scan_ip) - 1); + data->current_progress = (current * 100) / total; + } + data->needs_redraw = true; + return; + } + + /* Open port: " 80/tcp open HTTP" */ + { + int port = 0; + char service[16] = {0}; + if (sscanf(trimmed, "%d/tcp open %15s", &port, service) == 2 || + sscanf(trimmed, "%d/tcp open %15s", &port, service) == 2) { + if (data->result_count > 0) { + nmap_host_t *h = &data->results[data->current_result_host]; + if (h->port_count < MAX_PORTS) { + h->ports[h->port_count].port = port; + strncpy(h->ports[h->port_count].service, service, sizeof(h->ports[0].service) - 1); + h->port_count++; + } + } + data->needs_redraw = true; + return; + } + } + + /* No open ports marker */ + if (strstr(trimmed, "(no open ports)") != NULL) { + if (data->result_count > 0) { + data->results[data->current_result_host].no_open_ports = true; + } + data->needs_redraw = true; + return; + } + + /* Completion: "Scanned N hosts, found M open ports" */ + if (strstr(line, "Scanned") != NULL && strstr(line, "open ports") != NULL) { + sscanf(trimmed, "Scanned %d hosts, found %d open ports", + &data->total_hosts_scanned, &data->total_ports_found); + data->scan_complete = true; + data->needs_redraw = true; + return; + } + + /* Host discovery phases (informational - update display) */ + if (strstr(line, "ARP scan") != NULL || strstr(line, "ICMP ping") != NULL || + strstr(line, "hosts discovered") != NULL) { + data->needs_redraw = true; + } +} + +/* ---- Timer callback for animations ---- */ + +static void timer_callback(void *arg) +{ + nmap_data_t *data = (nmap_data_t *)arg; + + if (data->state == STATE_SCANNING_HOSTS) { + if (data->host_scan_done) { + stop_timer(data); + if (data->host_count > 0) { + data->state = STATE_SELECT_HOST; + data->selected_host = 0; + data->host_scroll = 0; + } else { + /* No hosts found - go back to method select */ + data->state = STATE_TARGET_METHOD; + } + data->needs_redraw = true; + return; + } + data->animation_frame = (data->animation_frame + 1) % 4; + const char *spinner[] = {"|", "/", "-", "\\"}; + int y3 = 3 * 16; + display_fill_rect(0, y3, DISPLAY_WIDTH, 16, UI_COLOR_BG); + char status[32]; + snprintf(status, sizeof(status), "Scanning hosts... %s", spinner[data->animation_frame]); + ui_print_center(3, status, UI_COLOR_TEXT); + return; + } + + if (data->state == STATE_SCANNING_PORTS) { + if (data->scan_complete) { + stop_timer(data); + uart_clear_line_callback(); + data->state = STATE_RESULTS; + data->result_scroll = 0; + data->result_selected = 0; + data->needs_redraw = true; + return; + } + /* Refresh screen periodically during scan */ + data->needs_redraw = true; + } +} + +static void stop_timer(nmap_data_t *data) +{ + if (data->update_timer) { + esp_timer_stop(data->update_timer); + esp_timer_delete(data->update_timer); + data->update_timer = NULL; + } +} + +static void start_timer(nmap_data_t *data) +{ + stop_timer(data); + esp_timer_create_args_t timer_args = { + .callback = timer_callback, + .arg = data, + .name = "nmap_timer" + }; + esp_timer_create(&timer_args, &data->update_timer); + esp_timer_start_periodic(data->update_timer, 300000); /* 300ms */ +} + +/* ---- Start host scan ---- */ + +static void start_host_scan(nmap_data_t *data) +{ + data->state = STATE_SCANNING_HOSTS; + data->host_count = 0; + data->host_scan_started = false; + data->host_scan_done = false; + data->animation_frame = 0; + + draw_screen(data->self); + + uart_register_line_callback(host_scan_line_cb, data); + uart_send_command("list_hosts"); + start_timer(data); +} + +/* ---- Start nmap scan ---- */ + +static void start_nmap_scan(nmap_data_t *data) +{ + data->state = STATE_SCANNING_PORTS; + data->result_count = 0; + data->current_result_host = -1; + data->current_progress = 0; + data->current_scan_ip[0] = '\0'; + data->total_hosts_scanned = 0; + data->total_ports_found = 0; + data->scan_complete = false; + + draw_screen(data->self); + + uart_register_line_callback(nmap_scan_line_cb, data); + + char cmd[64]; + if (data->target_ip[0] != '\0') { + snprintf(cmd, sizeof(cmd), "start_nmap %s %s", data->target_ip, scan_type_str(data->scan_type_index)); + } else { + snprintf(cmd, sizeof(cmd), "start_nmap %s", scan_type_str(data->scan_type_index)); + } + uart_send_command(cmd); + start_timer(data); +} + +/* ---- Drawing ---- */ + +static void draw_host_row(nmap_data_t *data, int host_idx) +{ + int row_on_screen = host_idx - data->host_scroll; + if (row_on_screen < 0 || row_on_screen >= VISIBLE_ITEMS) return; + + char label[30]; + snprintf(label, sizeof(label), "%s", data->hosts[host_idx].ip); + bool selected = (host_idx == data->selected_host); + ui_draw_menu_item(1 + row_on_screen, label, selected, false, false); +} + +static void redraw_host_list(nmap_data_t *data) +{ + ui_draw_title("Select Host"); + for (int i = 0; i < VISIBLE_ITEMS; i++) { + int idx = data->host_scroll + i; + if (idx < data->host_count) { + draw_host_row(data, idx); + } else { + int y = (1 + i) * 16; + display_fill_rect(0, y, DISPLAY_WIDTH, 16, UI_COLOR_BG); + } + } + /* Scroll indicators */ + if (data->host_scroll > 0) { + ui_print(UI_COLS - 2, 1, "^", UI_COLOR_DIMMED); + } + if (data->host_scroll + VISIBLE_ITEMS < data->host_count) { + ui_print(UI_COLS - 2, VISIBLE_ITEMS, "v", UI_COLOR_DIMMED); + } +} + +static int count_result_lines(nmap_data_t *data) +{ + int lines = 1; /* summary line */ + for (int i = 0; i < data->result_count; i++) { + lines++; /* host header */ + if (data->results[i].port_count > 0) { + lines += data->results[i].port_count; + } else { + lines++; /* "(no open ports)" */ + } + } + return lines; +} + +static void draw_results(nmap_data_t *data) +{ + ui_draw_title("Nmap Results"); + + /* Build flat list of result lines and draw visible window */ + int line_idx = 0; + int draw_row = 1; + int max_rows = UI_ROWS - 2; /* rows 1..6, leave 0 for title and 7 for status */ + + /* Summary line */ + if (line_idx >= data->result_scroll && draw_row <= max_rows) { + char summary[30]; + snprintf(summary, sizeof(summary), "%d hosts, %d open ports", + data->total_hosts_scanned, data->total_ports_found); + ui_print(0, draw_row, summary, UI_COLOR_HIGHLIGHT); + draw_row++; + } + line_idx++; + + for (int i = 0; i < data->result_count && draw_row <= max_rows; i++) { + nmap_host_t *h = &data->results[i]; + + /* Host header */ + if (line_idx >= data->result_scroll && draw_row <= max_rows) { + char hdr[30]; + snprintf(hdr, sizeof(hdr), "%s", h->ip); + ui_print(0, draw_row, hdr, UI_COLOR_TEXT); + draw_row++; + } + line_idx++; + + if (h->port_count > 0) { + for (int p = 0; p < h->port_count && draw_row <= max_rows; p++) { + if (line_idx >= data->result_scroll && draw_row <= max_rows) { + char port_line[30]; + snprintf(port_line, sizeof(port_line), " %d/%s", + h->ports[p].port, h->ports[p].service); + ui_print(0, draw_row, port_line, UI_COLOR_HIGHLIGHT); + draw_row++; + } + line_idx++; + } + } else { + if (line_idx >= data->result_scroll && draw_row <= max_rows) { + ui_print(0, draw_row, " (no open ports)", UI_COLOR_DIMMED); + draw_row++; + } + line_idx++; + } + } + + /* Clear remaining rows */ + while (draw_row <= max_rows) { + display_fill_rect(0, draw_row * 16, DISPLAY_WIDTH, 16, UI_COLOR_BG); + draw_row++; + } + + /* Scroll indicators */ + if (data->result_scroll > 0) { + ui_print(UI_COLS - 2, 1, "^", UI_COLOR_DIMMED); + } + data->result_total_lines = count_result_lines(data); + int max_visible = UI_ROWS - 2; + if (data->result_scroll + max_visible < data->result_total_lines) { + ui_print(UI_COLS - 2, max_rows, "v", UI_COLOR_DIMMED); + } +} + +static void draw_screen(screen_t *self) +{ + nmap_data_t *data = (nmap_data_t *)self->user_data; + ui_clear(); + + switch (data->state) { + case STATE_CHECK_WIFI: + ui_draw_title("Nmap Scanner"); + ui_print_center(2, "Not connected to WiFi", UI_COLOR_TEXT); + ui_print_center(4, "Connect first via", UI_COLOR_DIMMED); + ui_print_center(5, "Network Attacks menu", UI_COLOR_DIMMED); + ui_draw_status("ESC:Back"); + break; + + case STATE_TARGET_METHOD: + ui_draw_title("Nmap Scanner"); + ui_draw_menu_item(1, "All hosts", data->method_index == 0, false, false); + ui_draw_menu_item(2, "Scan & select host", data->method_index == 1, false, false); + ui_draw_status("UP/DOWN ENTER:Select ESC:Back"); + break; + + case STATE_SCANNING_HOSTS: + ui_draw_title("Host Discovery"); + ui_print_center(3, "Scanning hosts...", UI_COLOR_TEXT); + ui_draw_status("ESC:Cancel"); + break; + + case STATE_SELECT_HOST: + ui_clear(); + redraw_host_list(data); + ui_draw_status("ENTER:Select ESC:Back"); + break; + + case STATE_SELECT_SCAN_TYPE: + ui_draw_title("Scan Type"); + for (int i = 0; i < 3; i++) { + ui_draw_menu_item(i + 1, scan_type_label(i), + data->scan_type_index == i, false, false); + } + ui_draw_status("UP/DOWN ENTER:Select ESC:Back"); + break; + + case STATE_SCANNING_PORTS: { + ui_draw_title("Nmap Scanning"); + + /* Show what we're scanning */ + if (data->current_scan_ip[0]) { + char info[30]; + snprintf(info, sizeof(info), "Host: %s", data->current_scan_ip); + ui_print(0, 2, info, UI_COLOR_TEXT); + } else { + ui_print_center(2, "Starting scan...", UI_COLOR_DIMMED); + } + + /* Progress bar */ + char pct_text[16]; + snprintf(pct_text, sizeof(pct_text), "%d%%", data->current_progress); + ui_draw_progress(3, data->current_progress, pct_text); + + /* Stats line */ + char stats[30]; + int found = 0; + for (int i = 0; i < data->result_count; i++) { + found += data->results[i].port_count; + } + snprintf(stats, sizeof(stats), "Hosts:%d Ports:%d", data->result_count, found); + ui_print(0, 5, stats, UI_COLOR_DIMMED); + + ui_draw_status("ESC:Stop"); + break; + } + + case STATE_RESULTS: + draw_results(data); + ui_draw_status("UP/DOWN:Scroll ESC:Back"); + break; + } +} + +/* ---- Key handler ---- */ + +static void on_key(screen_t *self, key_code_t key) +{ + nmap_data_t *data = (nmap_data_t *)self->user_data; + + switch (data->state) { + case STATE_CHECK_WIFI: + if (key == KEY_ESC || key == KEY_BACKSPACE) { + screen_manager_pop(); + } + break; + + case STATE_TARGET_METHOD: + switch (key) { + case KEY_UP: + if (data->method_index > 0) { + int old = data->method_index; + data->method_index--; + ui_draw_menu_item(old + 1, old == 0 ? "All hosts" : "Scan & select host", + false, false, false); + ui_draw_menu_item(data->method_index + 1, + data->method_index == 0 ? "All hosts" : "Scan & select host", + true, false, false); + } else { + int old = data->method_index; + data->method_index = 1; + ui_draw_menu_item(old + 1, "All hosts", false, false, false); + ui_draw_menu_item(2, "Scan & select host", true, false, false); + } + break; + case KEY_DOWN: + if (data->method_index < 1) { + int old = data->method_index; + data->method_index++; + ui_draw_menu_item(old + 1, "All hosts", false, false, false); + ui_draw_menu_item(data->method_index + 1, "Scan & select host", + true, false, false); + } else { + int old = data->method_index; + data->method_index = 0; + ui_draw_menu_item(old + 1, "Scan & select host", false, false, false); + ui_draw_menu_item(1, "All hosts", true, false, false); + } + break; + case KEY_ENTER: + case KEY_SPACE: + if (data->method_index == 0) { + /* All hosts - go to scan type */ + data->target_ip[0] = '\0'; + data->state = STATE_SELECT_SCAN_TYPE; + data->scan_type_index = 0; + draw_screen(self); + } else { + /* Scan & select host */ + start_host_scan(data); + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + screen_manager_pop(); + break; + default: + break; + } + break; + + case STATE_SCANNING_HOSTS: + if (key == KEY_ESC || key == KEY_BACKSPACE) { + stop_timer(data); + uart_clear_line_callback(); + data->state = STATE_TARGET_METHOD; + draw_screen(self); + } + break; + + case STATE_SELECT_HOST: + switch (key) { + case KEY_UP: + if (data->selected_host > 0) { + int old = data->selected_host; + if (data->selected_host == data->host_scroll && data->host_scroll > 0) { + data->host_scroll -= VISIBLE_ITEMS; + if (data->host_scroll < 0) data->host_scroll = 0; + data->selected_host = data->host_scroll + VISIBLE_ITEMS - 1; + if (data->selected_host >= data->host_count) + data->selected_host = data->host_count - 1; + ui_clear(); + redraw_host_list(data); + ui_draw_status("ENTER:Select ESC:Back"); + } else { + data->selected_host--; + draw_host_row(data, old); + draw_host_row(data, data->selected_host); + } + } else { + data->selected_host = data->host_count - 1; + data->host_scroll = data->selected_host - VISIBLE_ITEMS + 1; + if (data->host_scroll < 0) data->host_scroll = 0; + ui_clear(); + redraw_host_list(data); + ui_draw_status("ENTER:Select ESC:Back"); + } + break; + case KEY_DOWN: + if (data->selected_host < data->host_count - 1) { + int old = data->selected_host; + if (data->selected_host == data->host_scroll + VISIBLE_ITEMS - 1) { + data->host_scroll += VISIBLE_ITEMS; + data->selected_host = data->host_scroll; + ui_clear(); + redraw_host_list(data); + ui_draw_status("ENTER:Select ESC:Back"); + } else { + data->selected_host++; + draw_host_row(data, old); + draw_host_row(data, data->selected_host); + } + } else { + data->selected_host = 0; + data->host_scroll = 0; + ui_clear(); + redraw_host_list(data); + ui_draw_status("ENTER:Select ESC:Back"); + } + break; + case KEY_ENTER: + case KEY_SPACE: + if (data->selected_host >= 0 && data->selected_host < data->host_count) { + strncpy(data->target_ip, data->hosts[data->selected_host].ip, + sizeof(data->target_ip) - 1); + data->target_ip[sizeof(data->target_ip) - 1] = '\0'; + data->state = STATE_SELECT_SCAN_TYPE; + data->scan_type_index = 0; + draw_screen(self); + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + data->state = STATE_TARGET_METHOD; + draw_screen(self); + break; + default: + break; + } + break; + + case STATE_SELECT_SCAN_TYPE: + switch (key) { + case KEY_UP: + if (data->scan_type_index > 0) { + int old = data->scan_type_index; + data->scan_type_index--; + ui_draw_menu_item(old + 1, scan_type_label(old), false, false, false); + ui_draw_menu_item(data->scan_type_index + 1, + scan_type_label(data->scan_type_index), true, false, false); + } else { + int old = data->scan_type_index; + data->scan_type_index = 2; + ui_draw_menu_item(old + 1, scan_type_label(old), false, false, false); + ui_draw_menu_item(3, scan_type_label(2), true, false, false); + } + break; + case KEY_DOWN: + if (data->scan_type_index < 2) { + int old = data->scan_type_index; + data->scan_type_index++; + ui_draw_menu_item(old + 1, scan_type_label(old), false, false, false); + ui_draw_menu_item(data->scan_type_index + 1, + scan_type_label(data->scan_type_index), true, false, false); + } else { + int old = data->scan_type_index; + data->scan_type_index = 0; + ui_draw_menu_item(old + 1, scan_type_label(old), false, false, false); + ui_draw_menu_item(1, scan_type_label(0), true, false, false); + } + break; + case KEY_ENTER: + case KEY_SPACE: + start_nmap_scan(data); + break; + case KEY_ESC: + case KEY_BACKSPACE: + /* Go back to target method or host select */ + if (data->target_ip[0] != '\0') { + data->state = STATE_SELECT_HOST; + } else { + data->state = STATE_TARGET_METHOD; + } + draw_screen(self); + break; + default: + break; + } + break; + + case STATE_SCANNING_PORTS: + if (key == KEY_ESC || key == KEY_BACKSPACE) { + uart_send_command("stop"); + stop_timer(data); + uart_clear_line_callback(); + screen_manager_pop(); + } + break; + + case STATE_RESULTS: { + int max_visible = UI_ROWS - 2; + switch (key) { + case KEY_UP: + if (data->result_scroll > 0) { + data->result_scroll--; + draw_screen(self); + } + break; + case KEY_DOWN: + if (data->result_scroll + max_visible < data->result_total_lines) { + data->result_scroll++; + draw_screen(self); + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + case KEY_ENTER: + screen_manager_pop(); + break; + default: + break; + } + break; + } + } +} + +/* ---- Tick ---- */ + +static void on_tick(screen_t *self) +{ + nmap_data_t *data = (nmap_data_t *)self->user_data; + + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); + } +} + +/* ---- Lifecycle ---- */ + +static void on_destroy(screen_t *self) +{ + nmap_data_t *data = (nmap_data_t *)self->user_data; + if (data) { + stop_timer(data); + uart_clear_line_callback(); + free(data); + } +} + +static void on_resume(screen_t *self) +{ + draw_screen(self); +} + +screen_t* nmap_screen_create(void *params) +{ + (void)params; + + ESP_LOGI(TAG, "Creating nmap screen..."); + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + nmap_data_t *data = calloc(1, sizeof(nmap_data_t)); + if (!data) { + free(screen); + return NULL; + } + + data->self = screen; + + /* Check WiFi connection */ + if (!uart_is_wifi_connected()) { + data->state = STATE_CHECK_WIFI; + } else { + data->state = STATE_TARGET_METHOD; + } + + screen->user_data = data; + screen->on_key = on_key; + screen->on_destroy = on_destroy; + screen->on_resume = on_resume; + screen->on_draw = draw_screen; + screen->on_tick = on_tick; + + draw_screen(screen); + + ESP_LOGI(TAG, "Nmap screen created"); + return screen; +} diff --git a/main/screens/nmap_screen.h b/main/screens/nmap_screen.h new file mode 100644 index 0000000..07b67ef --- /dev/null +++ b/main/screens/nmap_screen.h @@ -0,0 +1,18 @@ +/** + * @file nmap_screen.h + * @brief Nmap port scanner screen + */ + +#ifndef NMAP_SCREEN_H +#define NMAP_SCREEN_H + +#include "screen_manager.h" + +/** + * @brief Create the nmap scanner screen + * @param params Not used + * @return Screen instance + */ +screen_t* nmap_screen_create(void *params); + +#endif // NMAP_SCREEN_H diff --git a/main/screens/portal_data_screen.c b/main/screens/portal_data_screen.c index f06ed64..1bd09d1 100644 --- a/main/screens/portal_data_screen.c +++ b/main/screens/portal_data_screen.c @@ -221,7 +221,6 @@ static void on_tick(screen_t *self) // Block redraws for 2 ticks after first render to avoid double draw if (data->ticks_since_first_draw <= 2) { - data->needs_redraw = false; return; } diff --git a/main/screens/settings_screen.c b/main/screens/settings_screen.c index b2096ca..0f205cd 100644 --- a/main/screens/settings_screen.c +++ b/main/screens/settings_screen.c @@ -26,8 +26,10 @@ static const char *TAG = "SETTINGS_SCREEN"; #define MENU_CHANNEL_TIME 3 #define MENU_SCR_TIMEOUT 4 #define MENU_SCR_BRIGHT 5 -#define MENU_RED_TEAM 6 -#define MENU_ITEM_COUNT 7 +#define MENU_SOUND 6 +#define MENU_RED_TEAM 7 +#define MENU_ITEM_COUNT 8 +#define VISIBLE_ITEMS 6 // Screen dimming timeout options (in ms) static const uint32_t timeout_options[] = { 10000, 30000, 60000, 300000, 0 }; @@ -37,6 +39,7 @@ static const char *timeout_labels[] = { "10s", "30s", "1min", "5min", "Stays On" // Screen user data typedef struct { int selected_index; + int scroll_offset; bool awaiting_disclaimer_confirm; // Waiting for user to confirm disclaimer } settings_screen_data_t; @@ -65,7 +68,7 @@ static void format_setting_line(char *buf, size_t buf_size, const char *label, c int total_cols = 29; // Leave 1 col margin int padding = total_cols - label_len - value_len; if (padding < 1) padding = 1; - + snprintf(buf, buf_size, "%s", label); int pos = label_len; for (int i = 0; i < padding && pos < (int)buf_size - 1; i++) { @@ -74,29 +77,30 @@ static void format_setting_line(char *buf, size_t buf_size, const char *label, c snprintf(buf + pos, buf_size - pos, "%s", value); } -static void draw_menu_item_at(int index, bool selected) +static void draw_menu_item_at(int row, int index, bool selected) { bool red_team = settings_get_red_team_enabled(); + bool sound_enabled = settings_get_sound_enabled(); char line[40]; - + switch (index) { case MENU_UART_PINS: - ui_draw_menu_item(index + 1, "UART Pins", selected, false, false); + ui_draw_menu_item(row, "UART Pins", selected, false, false); break; case MENU_VENDOR_LOOKUP: - ui_draw_menu_item(index + 1, "Vendor Lookup", selected, false, false); + ui_draw_menu_item(row, "Vendor Lookup", selected, false, false); break; case MENU_GPS_MODULE: - ui_draw_menu_item(index + 1, "GPS", selected, false, false); + ui_draw_menu_item(row, "GPS", selected, false, false); break; case MENU_CHANNEL_TIME: - ui_draw_menu_item(index + 1, "Channel Time", selected, false, false); + ui_draw_menu_item(row, "Channel Time", selected, false, false); break; case MENU_SCR_TIMEOUT: { int idx = get_timeout_option_index(); format_setting_line(line, sizeof(line), "Dimming", timeout_labels[idx]); - ui_draw_menu_item(index + 1, line, selected, false, false); + ui_draw_menu_item(row, line, selected, false, false); break; } case MENU_SCR_BRIGHT: @@ -104,40 +108,66 @@ static void draw_menu_item_at(int index, bool selected) char val[8]; snprintf(val, sizeof(val), "%d%%", settings_get_screen_brightness()); format_setting_line(line, sizeof(line), "Brightness", val); - ui_draw_menu_item(index + 1, line, selected, false, false); + ui_draw_menu_item(row, line, selected, false, false); break; } + case MENU_SOUND: + ui_draw_menu_item(row, "Enable Sound", selected, true, sound_enabled); + break; case MENU_RED_TEAM: - ui_draw_menu_item(index + 1, "Enable Red Team", selected, true, red_team); + ui_draw_menu_item(row, "Enable Red Team", selected, true, red_team); break; } } +static int get_menu_row(const settings_screen_data_t *data, int index) +{ + return (index - data->scroll_offset) + 1; +} + static void draw_screen(screen_t *self) { settings_screen_data_t *data = (settings_screen_data_t *)self->user_data; - + char status[32]; + uint8_t page_current = (uint8_t)((data->scroll_offset / VISIBLE_ITEMS) + 1); + uint8_t page_total = (uint8_t)((MENU_ITEM_COUNT + VISIBLE_ITEMS - 1) / VISIBLE_ITEMS); + ui_clear(); - + // Draw title ui_draw_title("Settings"); - - // Draw menu items (7 items + title = 8 rows, no room for status bar) - for (int i = 0; i < MENU_ITEM_COUNT; i++) { - draw_menu_item_at(i, i == data->selected_index); + + int visible_end = data->scroll_offset + VISIBLE_ITEMS; + if (visible_end > MENU_ITEM_COUNT) { + visible_end = MENU_ITEM_COUNT; } + + for (int i = data->scroll_offset; i < visible_end; i++) { + draw_menu_item_at(get_menu_row(data, i), i, i == data->selected_index); + } + + snprintf(status, + sizeof(status), + "UP/DN Nav ENT/Arw Pg %u/%u", + (unsigned)page_current, + (unsigned)page_total); + ui_draw_status(status); } // Optimized: redraw only two changed rows static void redraw_selection(settings_screen_data_t *data, int old_index, int new_index) { - draw_menu_item_at(old_index, false); - draw_menu_item_at(new_index, true); + if (old_index >= data->scroll_offset && old_index < data->scroll_offset + VISIBLE_ITEMS) { + draw_menu_item_at(get_menu_row(data, old_index), old_index, false); + } + if (new_index >= data->scroll_offset && new_index < data->scroll_offset + VISIBLE_ITEMS) { + draw_menu_item_at(get_menu_row(data, new_index), new_index, true); + } } static void show_red_team_disclaimer(void) { - ui_show_message("DISCLAIMER", + ui_show_message("DISCLAIMER", "Test YOUR networks only!"); } @@ -171,7 +201,7 @@ static void adjust_brightness(int delta) static void on_key(screen_t *self, key_code_t key) { settings_screen_data_t *data = (settings_screen_data_t *)self->user_data; - + // If awaiting disclaimer confirmation if (data->awaiting_disclaimer_confirm) { if (key == KEY_ENTER || key == KEY_SPACE) { @@ -186,101 +216,127 @@ static void on_key(screen_t *self, key_code_t key) } return; } - + switch (key) { case KEY_UP: if (data->selected_index > 0) { int old = data->selected_index; - data->selected_index--; - redraw_selection(data, old, data->selected_index); + if (data->selected_index == data->scroll_offset && data->scroll_offset > 0) { + data->scroll_offset -= VISIBLE_ITEMS; + if (data->scroll_offset < 0) data->scroll_offset = 0; + data->selected_index = data->scroll_offset + VISIBLE_ITEMS - 1; + if (data->selected_index >= MENU_ITEM_COUNT) { + data->selected_index = MENU_ITEM_COUNT - 1; + } + draw_screen(self); + } else { + data->selected_index--; + redraw_selection(data, old, data->selected_index); + } } else { - int old = data->selected_index; data->selected_index = MENU_ITEM_COUNT - 1; - redraw_selection(data, old, data->selected_index); + data->scroll_offset = (data->selected_index / VISIBLE_ITEMS) * VISIBLE_ITEMS; + draw_screen(self); } break; - + case KEY_DOWN: if (data->selected_index < MENU_ITEM_COUNT - 1) { int old = data->selected_index; data->selected_index++; - redraw_selection(data, old, data->selected_index); + if (data->selected_index >= data->scroll_offset + VISIBLE_ITEMS) { + data->scroll_offset += VISIBLE_ITEMS; + data->selected_index = data->scroll_offset; + draw_screen(self); + } else { + redraw_selection(data, old, data->selected_index); + } } else { - int old = data->selected_index; data->selected_index = 0; - redraw_selection(data, old, data->selected_index); + data->scroll_offset = 0; + draw_screen(self); } break; - + case KEY_LEFT: if (data->selected_index == MENU_SCR_TIMEOUT) { cycle_timeout(-1); - draw_menu_item_at(MENU_SCR_TIMEOUT, true); + draw_menu_item_at(get_menu_row(data, MENU_SCR_TIMEOUT), MENU_SCR_TIMEOUT, true); } else if (data->selected_index == MENU_SCR_BRIGHT) { // Without Shift: -10%, with Shift: -1% int step = keyboard_is_shift_held() ? -1 : -10; adjust_brightness(step); - draw_menu_item_at(MENU_SCR_BRIGHT, true); + draw_menu_item_at(get_menu_row(data, MENU_SCR_BRIGHT), MENU_SCR_BRIGHT, true); + } else if (data->selected_index == MENU_SOUND && settings_get_sound_enabled()) { + settings_set_sound_enabled(false); + draw_menu_item_at(get_menu_row(data, MENU_SOUND), MENU_SOUND, true); } break; - + case KEY_RIGHT: if (data->selected_index == MENU_SCR_TIMEOUT) { cycle_timeout(+1); - draw_menu_item_at(MENU_SCR_TIMEOUT, true); + draw_menu_item_at(get_menu_row(data, MENU_SCR_TIMEOUT), MENU_SCR_TIMEOUT, true); } else if (data->selected_index == MENU_SCR_BRIGHT) { // Without Shift: +10%, with Shift: +1% int step = keyboard_is_shift_held() ? 1 : 10; adjust_brightness(step); - draw_menu_item_at(MENU_SCR_BRIGHT, true); + draw_menu_item_at(get_menu_row(data, MENU_SCR_BRIGHT), MENU_SCR_BRIGHT, true); + } else if (data->selected_index == MENU_SOUND && !settings_get_sound_enabled()) { + settings_set_sound_enabled(true); + draw_menu_item_at(get_menu_row(data, MENU_SOUND), MENU_SOUND, true); } break; - + case KEY_ENTER: case KEY_SPACE: - { - switch (data->selected_index) { - case MENU_UART_PINS: - screen_manager_push(uart_pins_screen_create, NULL); - break; - case MENU_VENDOR_LOOKUP: - screen_manager_push(vendor_lookup_screen_create, NULL); - break; - case MENU_GPS_MODULE: - screen_manager_push(gps_module_screen_create, NULL); - break; - case MENU_CHANNEL_TIME: - screen_manager_push(channel_time_settings_screen_create, NULL); - break; - case MENU_SCR_TIMEOUT: - // ENTER also cycles timeout forward - cycle_timeout(+1); - draw_menu_item_at(MENU_SCR_TIMEOUT, true); - break; - case MENU_SCR_BRIGHT: - // No action on ENTER for brightness (use arrows) - break; - case MENU_RED_TEAM: - if (settings_get_red_team_enabled()) { - // Already enabled - just disable it - settings_set_red_team_enabled(false); - draw_menu_item_at(MENU_RED_TEAM, true); - } else { - // Show disclaimer before enabling - show_red_team_disclaimer(); - data->awaiting_disclaimer_confirm = true; - } - break; - } + { + switch (data->selected_index) { + case MENU_UART_PINS: + screen_manager_push(uart_pins_screen_create, NULL); + break; + case MENU_VENDOR_LOOKUP: + screen_manager_push(vendor_lookup_screen_create, NULL); + break; + case MENU_GPS_MODULE: + screen_manager_push(gps_module_screen_create, NULL); + break; + case MENU_CHANNEL_TIME: + screen_manager_push(channel_time_settings_screen_create, NULL); + break; + case MENU_SCR_TIMEOUT: + // ENTER also cycles timeout forward + cycle_timeout(+1); + draw_menu_item_at(get_menu_row(data, MENU_SCR_TIMEOUT), MENU_SCR_TIMEOUT, true); + break; + case MENU_SCR_BRIGHT: + // No action on ENTER for brightness (use arrows) + break; + case MENU_SOUND: + settings_set_sound_enabled(!settings_get_sound_enabled()); + draw_menu_item_at(get_menu_row(data, MENU_SOUND), MENU_SOUND, true); + break; + case MENU_RED_TEAM: + if (settings_get_red_team_enabled()) { + // Already enabled - just disable it + settings_set_red_team_enabled(false); + draw_menu_item_at(get_menu_row(data, MENU_RED_TEAM), MENU_RED_TEAM, true); + } else { + // Show disclaimer before enabling + show_red_team_disclaimer(); + data->awaiting_disclaimer_confirm = true; + } + break; } + } break; - + case KEY_ESC: case KEY_Q: case KEY_BACKSPACE: screen_manager_pop(); break; - + default: break; } @@ -301,28 +357,28 @@ static void on_resume(screen_t *self) screen_t* settings_screen_create(void *params) { (void)params; - + ESP_LOGI(TAG, "Creating settings screen..."); - + screen_t *screen = screen_alloc(); if (!screen) return NULL; - + // Allocate user data settings_screen_data_t *data = calloc(1, sizeof(settings_screen_data_t)); if (!data) { free(screen); return NULL; } - + screen->user_data = data; screen->on_key = on_key; screen->on_destroy = on_destroy; screen->on_resume = on_resume; screen->on_draw = draw_screen; - + // Draw initial screen draw_screen(screen); - + ESP_LOGI(TAG, "Settings screen created"); return screen; } diff --git a/main/screens/text_input_screen.c b/main/screens/text_input_screen.c index 3413d4a..0fa75de 100644 --- a/main/screens/text_input_screen.c +++ b/main/screens/text_input_screen.c @@ -20,6 +20,7 @@ typedef struct { int cursor_pos; text_input_callback_t on_submit; void *callback_user_data; + bool allow_empty; } text_input_data_t; /** @@ -130,8 +131,8 @@ static void on_key(screen_t *self, key_code_t key) switch (key) { case KEY_ENTER: - // Submit if we have input - if (data->cursor_pos > 0 && data->on_submit) { + // Submit if we have input (or allow_empty is set) + if ((data->cursor_pos > 0 || data->allow_empty) && data->on_submit) { data->on_submit(data->input, data->callback_user_data); } break; @@ -209,6 +210,7 @@ screen_t* text_input_screen_create(void *params) } data->on_submit = input_params->on_submit; data->callback_user_data = input_params->user_data; + data->allow_empty = input_params->allow_empty; data->cursor_pos = 0; data->input[0] = '\0'; diff --git a/main/screens/text_input_screen.h b/main/screens/text_input_screen.h index 7e0f062..0b1e61f 100644 --- a/main/screens/text_input_screen.h +++ b/main/screens/text_input_screen.h @@ -20,6 +20,7 @@ typedef struct { const char *hint; // Hint text below input text_input_callback_t on_submit; // Called when ENTER pressed void *user_data; // Passed to callback + bool allow_empty; // Allow submitting empty input } text_input_params_t; /** diff --git a/main/screens/wardrive_menu_screen.c b/main/screens/wardrive_menu_screen.c new file mode 100644 index 0000000..e87f021 --- /dev/null +++ b/main/screens/wardrive_menu_screen.c @@ -0,0 +1,125 @@ +/** + * @file wardrive_menu_screen.c + * @brief Wardrive sub-menu + */ + +#include "wardrive_menu_screen.h" +#include "wardrive_screen.h" +#include "wardrive_upload_screen.h" +#include "text_ui.h" +#include +#include + +typedef struct { + int selected; + bool trace_enabled; +} wardrive_menu_data_t; + +static void draw_screen(screen_t *self) +{ + wardrive_menu_data_t *data = (wardrive_menu_data_t *)self->user_data; + ui_clear(); + ui_draw_title("Wardrive"); + + char trace[24]; + snprintf(trace, sizeof(trace), "Trace: %s", data->trace_enabled ? "Yes" : "No"); + + ui_draw_menu_item(1, "Start Wardrive", data->selected == 0, false, false); + ui_draw_menu_item(2, "Upload to Wdgwars", data->selected == 1, false, false); + ui_draw_menu_item(3, "Upload to Wigle", data->selected == 2, false, false); + ui_draw_menu_item(4, trace, data->selected == 3, false, false); + ui_draw_status("UP/DOWN OK BACK RIGHT"); +} + +static void start_wardrive_run(wardrive_menu_data_t *data) +{ + wardrive_run_params_t *p = malloc(sizeof(wardrive_run_params_t)); + if (!p) return; + p->trace = data->trace_enabled; + screen_manager_push(wardrive_screen_create, p); +} + +static void start_upload(wardrive_upload_target_t target) +{ + wardrive_upload_params_t *p = malloc(sizeof(wardrive_upload_params_t)); + if (!p) return; + p->target = target; + screen_manager_push(wardrive_upload_screen_create, p); +} + +static void on_key(screen_t *self, key_code_t key) +{ + wardrive_menu_data_t *data = (wardrive_menu_data_t *)self->user_data; + if (!data) return; + + switch (key) { + case KEY_UP: + data->selected = (data->selected > 0) ? data->selected - 1 : 3; + draw_screen(self); + break; + case KEY_DOWN: + data->selected = (data->selected < 3) ? data->selected + 1 : 0; + draw_screen(self); + break; + case KEY_RIGHT: + if (data->selected == 3) { + data->trace_enabled = !data->trace_enabled; + draw_screen(self); + } + break; + case KEY_ENTER: + case KEY_SPACE: + if (data->selected == 0) { + start_wardrive_run(data); + } else if (data->selected == 1) { + start_upload(WARDRIVE_UPLOAD_TARGET_WDGWARS); + } else if (data->selected == 2) { + start_upload(WARDRIVE_UPLOAD_TARGET_WIGLE); + } else if (data->selected == 3) { + data->trace_enabled = !data->trace_enabled; + draw_screen(self); + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + case KEY_Q: + screen_manager_pop(); + break; + default: + break; + } +} + +static void on_resume(screen_t *self) +{ + draw_screen(self); +} + +static void on_destroy(screen_t *self) +{ + if (self->user_data) free(self->user_data); +} + +screen_t* wardrive_menu_screen_create(void *params) +{ + (void)params; + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + wardrive_menu_data_t *data = calloc(1, sizeof(wardrive_menu_data_t)); + if (!data) { + free(screen); + return NULL; + } + data->selected = 0; + data->trace_enabled = true; + + screen->user_data = data; + screen->on_draw = draw_screen; + screen->on_key = on_key; + screen->on_resume = on_resume; + screen->on_destroy = on_destroy; + + draw_screen(screen); + return screen; +} diff --git a/main/screens/wardrive_menu_screen.h b/main/screens/wardrive_menu_screen.h new file mode 100644 index 0000000..e24e41b --- /dev/null +++ b/main/screens/wardrive_menu_screen.h @@ -0,0 +1,13 @@ +/** + * @file wardrive_menu_screen.h + * @brief Wardrive sub-menu screen + */ + +#ifndef WARDRIVE_MENU_SCREEN_H +#define WARDRIVE_MENU_SCREEN_H + +#include "screen_manager.h" + +screen_t* wardrive_menu_screen_create(void *params); + +#endif // WARDRIVE_MENU_SCREEN_H diff --git a/main/screens/wardrive_screen.c b/main/screens/wardrive_screen.c index 215ca15..48e9962 100644 --- a/main/screens/wardrive_screen.c +++ b/main/screens/wardrive_screen.c @@ -1,362 +1,519 @@ /** * @file wardrive_screen.c - * @brief Wardrive screen implementation + * @brief Wardrive monitor screen */ #include "wardrive_screen.h" +#include "wardrive_upload_screen.h" #include "uart_handler.h" #include "text_ui.h" #include "buzzer.h" #include "settings.h" #include "cap_gps.h" #include "esp_log.h" +#include "esp_err.h" #include "esp_timer.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" #include +#include #include #include +#include static const char *TAG = "WARDRIVE"; -// Refresh timer interval (200ms) #define REFRESH_INTERVAL_US 200000 - -// CAP GPS position update interval (~2 seconds = 10 ticks of 200ms) #define CAP_GPS_UPDATE_TICKS 10 +#define WARDRIVE_RING_SIZE 100 -// Wardrive states typedef enum { - STATE_WAITING_GPS, - STATE_RUNNING, - STATE_GPS_LOST + STATE_STARTING = 0, + STATE_SCANNING, + STATE_GPS_LOST, + STATE_STOPPED } wardrive_state_t; -// Screen user data +typedef struct { + char ssid[33]; + char bssid[18]; + char security[28]; + char lat[14]; + char lon[14]; + char kind[8]; +} wardrive_entry_t; + typedef struct { wardrive_state_t state; - char last_ssid[64]; - char lat[16]; - char lon[16]; + wardrive_entry_t entries[WARDRIVE_RING_SIZE]; + int ring_head; + int ring_count; + + int wardrive_wifi_count; + int wardrive_bt_count; + int wardrive_sat_count; int gps_wait_elapsed; - int gps_wait_timeout; - int unique_networks; - bool needs_redraw; - esp_timer_handle_t refresh_timer; - screen_t *self; - // CAP GPS + float wardrive_distance_m; + + char status_main[40]; + char gps_overlay[40]; + + char cur_lat[14]; + char cur_lon[14]; + bool is_cap_gps; + bool trace_enabled; bool wardrive_started; + bool start_pending; + bool no_sd_overlay; + bool no_sd_continue_yes; + bool stop_confirm_overlay; + bool stop_confirm_yes; int cap_tick_counter; + bool needs_redraw; + + esp_timer_handle_t refresh_timer; + screen_t *self; } wardrive_data_t; -// Forward declaration static void draw_screen(screen_t *self); +extern bool is_board_sd_missing(void); + +static void wardrive_send(wardrive_data_t *data, const char *cmd) +{ + (void)data; + ESP_LOGI(TAG, "Wardrive TX > %s", cmd); + esp_err_t ret = uart_send_command(cmd); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "uart_send_command failed for '%s': %s", cmd, esp_err_to_name(ret)); + } +} + +static bool is_mac_prefix(const char *s) +{ + if (!s) return false; + for (int i = 0; i < 17; i++) { + if ((i + 1) % 3 == 0) { + if (s[i] != ':') return false; + } else { + if (!isxdigit((unsigned char)s[i])) return false; + } + } + return s[17] == ','; +} + +static void trim_brackets(char *s) +{ + if (!s) return; + size_t len = strlen(s); + if (len >= 2 && s[0] == '[' && s[len - 1] == ']') { + memmove(s, s + 1, len - 2); + s[len - 2] = '\0'; + } +} + +static bool parse_wardrive_csv_line(const char *line, wardrive_entry_t *entry) +{ + if (!line || !entry) return false; + if (!(strstr(line, ",WIFI") || strstr(line, ",BLE"))) return false; + if (!is_mac_prefix(line)) return false; + + char copy[320]; + snprintf(copy, sizeof(copy), "%s", line); + + char *fields[16] = {0}; + int field_count = 0; + char *save = NULL; + char *tok = strtok_r(copy, ",", &save); + while (tok && field_count < 16) { + fields[field_count++] = tok; + tok = strtok_r(NULL, ",", &save); + } + if (field_count < 11) return false; + + snprintf(entry->bssid, sizeof(entry->bssid), "%s", fields[0]); + snprintf(entry->ssid, sizeof(entry->ssid), "%s", fields[1][0] ? fields[1] : ""); + snprintf(entry->security, sizeof(entry->security), "%s", fields[2][0] ? fields[2] : "-"); + snprintf(entry->lat, sizeof(entry->lat), "%s", fields[6][0] ? fields[6] : "0"); + snprintf(entry->lon, sizeof(entry->lon), "%s", fields[7][0] ? fields[7] : "0"); + snprintf(entry->kind, sizeof(entry->kind), "%s", strstr(line, ",BLE") ? "BLE" : "WIFI"); + + if (strcasecmp(entry->kind, "WIFI") == 0) { + trim_brackets(entry->security); + } + if (entry->ssid[0] == '\0') { + snprintf(entry->ssid, sizeof(entry->ssid), "%s", + strcasecmp(entry->kind, "BLE") == 0 ? "" : ""); + } + return true; +} + +static void push_entry(wardrive_data_t *data, const wardrive_entry_t *entry) +{ + if (!data || !entry) return; + data->entries[data->ring_head] = *entry; + data->ring_head = (data->ring_head + 1) % WARDRIVE_RING_SIZE; + if (data->ring_count < WARDRIVE_RING_SIZE) { + data->ring_count++; + } + + snprintf(data->cur_lat, sizeof(data->cur_lat), "%s", entry->lat); + snprintf(data->cur_lon, sizeof(data->cur_lon), "%s", entry->lon); + if (strcasecmp(entry->kind, "BLE") == 0) { + data->wardrive_bt_count++; + } else { + data->wardrive_wifi_count++; + } +} + +static bool parse_sat_dist(const char *line, int *sats, float *dist) +{ + const char *s = strstr(line, "sats:"); + const char *d = strstr(line, "dist:"); + if (!s || !d) return false; + int sat_tmp = -1; + float dist_tmp = -1.0f; + if (sscanf(s, "sats: %d", &sat_tmp) != 1) return false; + if (sscanf(d, "dist: %f", &dist_tmp) != 1) return false; + *sats = sat_tmp; + *dist = dist_tmp; + return true; +} + +static bool parse_bt_devices(const char *line, int *bt_count) +{ + const char *p = strstr(line, "BT devices"); + if (!p) return false; + int v = -1; + const char *s = p; + while (s > line && !isdigit((unsigned char)*(s - 1))) s--; + while (s > line && (isdigit((unsigned char)*(s - 1)) || *(s - 1) == ' ' || *(s - 1) == ',')) s--; + if (sscanf(s, "%d", &v) == 1 && v >= 0) { + *bt_count = v; + return true; + } + return false; +} + +static void parse_lat_lon_from_text(wardrive_data_t *data, const char *line) +{ + if (!data || !line) return; + const char *lat = strstr(line, "Lat="); + const char *lon = strstr(line, "Lon="); + if (!lat || !lon) return; + + lat += 4; + lon += 4; + + size_t i = 0; + while (lat[i] && i < sizeof(data->cur_lat) - 1) { + char c = lat[i]; + if ((c >= '0' && c <= '9') || c == '-' || c == '+' + || c == '.') { + data->cur_lat[i] = c; + i++; + } else { + break; + } + } + data->cur_lat[i] = '\0'; + + i = 0; + while (lon[i] && i < sizeof(data->cur_lon) - 1) { + char c = lon[i]; + if ((c >= '0' && c <= '9') || c == '-' || c == '+' + || c == '.') { + data->cur_lon[i] = c; + i++; + } else { + break; + } + } + data->cur_lon[i] = '\0'; +} + +static void update_status_line(wardrive_data_t *data, const char *line) +{ + if (!data || !line) return; + parse_lat_lon_from_text(data, line); + + if (strstr(line, "GPS fix obtained")) { + data->state = STATE_SCANNING; + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS fix obtained"); + } else if (strstr(line, "GPS fix lost")) { + data->state = STATE_GPS_LOST; + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS lost..."); + } else if (strstr(line, "GPS fix recovered")) { + data->state = STATE_SCANNING; + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS fix recovered"); + } else if (strstr(line, "Still waiting for GPS fix")) { + data->state = STATE_STARTING; + int elapsed = 0, timeout = 0; + if (sscanf(line, "%*[^'(](%d/%d", &elapsed, &timeout) == 2) { + data->gps_wait_elapsed = elapsed; + } + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "Waiting for GPS fix..."); + } else if (strstr(line, "No GPS fix obtained")) { + data->state = STATE_GPS_LOST; + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "No GPS fix obtained"); + } else if (strstr(line, "Promiscuous wardrive started")) { + data->state = STATE_SCANNING; + data->wardrive_started = true; + uart_set_wardrive_active(true); + snprintf(data->status_main, sizeof(data->status_main), "Scanning..."); + } else if (strstr(line, "Wardrive promisc stopped")) { + data->state = STATE_STOPPED; + data->wardrive_started = false; + uart_set_wardrive_active(false); + snprintf(data->status_main, sizeof(data->status_main), "Wardrive stopped"); + } else if (strstr(line, "Flushed") && strstr(line, "networks")) { + snprintf(data->status_main, sizeof(data->status_main), "Networks flushed"); + } else if (strstr(line, "GPS set")) { + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS mode updated"); + } + + int sats = 0; + float dist = 0.0f; + if (parse_sat_dist(line, &sats, &dist)) { + data->wardrive_sat_count = sats; + data->wardrive_distance_m = dist; + data->state = STATE_SCANNING; + } + int bt = 0; + if (parse_bt_devices(line, &bt)) { + data->wardrive_bt_count = bt; + } +} + +static void uart_line_callback(const char *line, void *user_data) +{ + wardrive_data_t *data = (wardrive_data_t *)user_data; + if (!data) return; + + wardrive_entry_t entry; + if (parse_wardrive_csv_line(line, &entry)) { + push_entry(data, &entry); + data->state = STATE_SCANNING; + snprintf(data->status_main, sizeof(data->status_main), "Scanning..."); + data->needs_redraw = true; + return; + } + + if (strstr(line, "tab_gps_read")) { + char reply[48]; + if (data->cur_lat[0] && data->cur_lon[0]) { + snprintf(reply, sizeof(reply), "%s,%s", data->cur_lat, data->cur_lon); + } else { + snprintf(reply, sizeof(reply), "%s", "No GPS fix"); + } + uart_send_command(reply); + } + + update_status_line(data, line); + data->needs_redraw = true; +} -/** - * @brief Timer callback - checks if redraw is needed and handles CAP GPS updates - */ static void refresh_timer_callback(void *arg) { wardrive_data_t *data = (wardrive_data_t *)arg; if (!data || !data->self) return; - // CAP GPS position update loop if (data->is_cap_gps) { data->cap_tick_counter++; if (data->cap_tick_counter >= CAP_GPS_UPDATE_TICKS) { data->cap_tick_counter = 0; - if (cap_gps_has_fix()) { double lat, lon, alt, hdop; if (cap_gps_get_position(&lat, &lon, &alt, &hdop)) { - // Send position to JanOS via UART1 - char cmd[96]; - snprintf(cmd, sizeof(cmd), "set_gps_position_cap %.7f %.7f %.1f %.1f", - lat, lon, alt, hdop); + char cmd[80]; + snprintf(cmd, sizeof(cmd), "set_gps_position %.7f %.7f", lat, lon); uart_send_command(cmd); - - // Update display coordinates - snprintf(data->lat, sizeof(data->lat), "%.7f", lat); - snprintf(data->lon, sizeof(data->lon), "%.7f", lon); - - if (data->state == STATE_WAITING_GPS) { - // First fix - start wardrive - ESP_LOGI(TAG, "CAP GPS fix obtained! Starting wardrive..."); - uart_send_command("start_wardrive_promisc"); - buzzer_beep_attack(); - data->wardrive_started = true; - data->state = STATE_RUNNING; - } else if (data->state == STATE_GPS_LOST) { - ESP_LOGI(TAG, "CAP GPS fix recovered!"); - data->state = STATE_RUNNING; - } - data->needs_redraw = true; + snprintf(data->cur_lat, sizeof(data->cur_lat), "%.7f", lat); + snprintf(data->cur_lon, sizeof(data->cur_lon), "%.7f", lon); + (void)alt; + (void)hdop; } } else { - // No fix - if (data->state == STATE_RUNNING) { - ESP_LOGW(TAG, "CAP GPS fix lost!"); - uart_send_command("set_gps_position_cap"); - data->state = STATE_GPS_LOST; - data->needs_redraw = true; - } else if (data->state == STATE_WAITING_GPS) { - // Update satellite count display - data->needs_redraw = true; - } + uart_send_command("set_gps_position"); } } } - if (data->needs_redraw) { - data->needs_redraw = false; - draw_screen(data->self); - } + (void)data->self; } -/** - * @brief Helper to parse "Lat=VALUE Lon=VALUE" or "Lat=VALUE Lon=VALUE." from a string. - * Handles both space-separated and end-of-line/period-terminated Lon values. - */ -static void parse_lat_lon(const char *str, wardrive_data_t *data) +static void on_tick(screen_t *self) { - const char *lat_marker = "Lat="; - const char *lat_ptr = strstr(str, lat_marker); - if (!lat_ptr) return; - - const char *lat_start = lat_ptr + strlen(lat_marker); - const char *lat_end = strchr(lat_start, ' '); - if (!lat_end) return; - - size_t lat_len = lat_end - lat_start; - if (lat_len < sizeof(data->lat)) { - strncpy(data->lat, lat_start, lat_len); - data->lat[lat_len] = '\0'; - } - - const char *lon_marker = "Lon="; - const char *lon_ptr = strstr(lat_end, lon_marker); - if (!lon_ptr) return; - - const char *lon_start = lon_ptr + strlen(lon_marker); - size_t lon_len = 0; - const char *lon_end = lon_start; - while (*lon_end && *lon_end != ' ' && *lon_end != '\n' && *lon_end != '\r') { - if ((*lon_end >= '0' && *lon_end <= '9') || *lon_end == '-') { - lon_end++; - } else if (*lon_end == '.') { - if (*(lon_end + 1) >= '0' && *(lon_end + 1) <= '9') { - lon_end++; - } else { - break; - } + wardrive_data_t *data = (wardrive_data_t *)self->user_data; + if (!data) return; + + if (data->start_pending) { + data->start_pending = false; + ESP_LOGI(TAG, "Starting wardrive session..."); + uart_register_line_callback(uart_line_callback, data); + ESP_LOGI(TAG, "Wardrive callback registered"); + wardrive_send(data, "unselect_networks"); + if (settings_get_gps_type() == GPS_TYPE_M5) { + wardrive_send(data, "gps_set m5"); + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS set: m5"); + } else if (settings_get_gps_type() == GPS_TYPE_ATGM) { + wardrive_send(data, "gps_set atgm"); + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS set: atgm"); } else { - break; + wardrive_send(data, "gps_set external"); + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "GPS set: external"); } + wardrive_send(data, data->trace_enabled ? "start_wardrive_promisc_trace" + : "start_wardrive_promisc"); + data->wardrive_started = true; + uart_set_wardrive_active(true); + if (data->is_cap_gps) { + cap_gps_init(); + } + buzzer_beep_attack(); + data->needs_redraw = true; } - lon_len = lon_end - lon_start; - if (lon_len > 0 && lon_len < sizeof(data->lon)) { - strncpy(data->lon, lon_start, lon_len); - data->lon[lon_len] = '\0'; + + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); } - - ESP_LOGI(TAG, "GPS update: %s, %s", data->lat, data->lon); } -/** - * @brief UART line callback for parsing wardrive promisc output - */ -static void uart_line_callback(const char *line, void *user_data) +static void on_resume(screen_t *self) { - wardrive_data_t *data = (wardrive_data_t *)user_data; + wardrive_data_t *data = (wardrive_data_t *)self->user_data; if (!data) return; - - // --- Network CSV lines: MAC,SSID,[AUTH],date,ch,rssi,lat,lon,alt,acc,WIFI --- - if (strlen(line) > 18 && line[2] == ':' && line[5] == ':' && - line[8] == ':' && line[11] == ':' && line[14] == ':' && line[17] == ',') { - const char *ssid_start = line + 18; - const char *ssid_end = strchr(ssid_start, ','); - if (ssid_end && ssid_end > ssid_start) { - size_t ssid_len = ssid_end - ssid_start; - if (ssid_len < sizeof(data->last_ssid)) { - strncpy(data->last_ssid, ssid_start, ssid_len); - data->last_ssid[ssid_len] = '\0'; - } - } - // Extract lat/lon from CSV columns 7 and 8 (0-indexed: 6 and 7) - const char *p = line; - int comma_count = 0; - const char *field_starts[9] = {0}; - field_starts[0] = p; - while (*p) { - if (*p == ',') { - comma_count++; - if (comma_count < 9) { - field_starts[comma_count] = p + 1; - } - } - p++; - } - // field 6 = lat, field 7 = lon - if (comma_count >= 8 && field_starts[6] && field_starts[7]) { - const char *lat_s = field_starts[6]; - const char *lat_e = strchr(lat_s, ','); - const char *lon_s = field_starts[7]; - const char *lon_e = strchr(lon_s, ','); - if (lat_e && lon_e) { - size_t ll = lat_e - lat_s; - size_t lo = lon_e - lon_s; - if (ll > 0 && ll < sizeof(data->lat)) { - strncpy(data->lat, lat_s, ll); - data->lat[ll] = '\0'; - } - if (lo > 0 && lo < sizeof(data->lon)) { - strncpy(data->lon, lon_s, lo); - data->lon[lo] = '\0'; - } - } - } - data->unique_networks++; - data->needs_redraw = true; - return; - } - - // --- GPS fix waiting countdown: "Still waiting for GPS fix... (N/M seconds)" --- - const char *still_waiting = strstr(line, "Still waiting for GPS fix"); - if (still_waiting) { - const char *paren = strchr(still_waiting, '('); - if (paren) { - int elapsed = 0, timeout = 0; - if (sscanf(paren, "(%d/%d seconds)", &elapsed, &timeout) == 2) { - data->gps_wait_elapsed = elapsed; - data->gps_wait_timeout = timeout; - } - } - data->needs_redraw = true; + uart_register_line_callback(uart_line_callback, data); + data->needs_redraw = true; +} + +static void draw_screen(screen_t *self) +{ + wardrive_data_t *data = (wardrive_data_t *)self->user_data; + + ui_clear(); + ui_draw_title("Wardrive"); + + if (data->no_sd_overlay) { + ui_print_center(2, "No SD card!", UI_COLOR_HIGHLIGHT); + ui_print_center(3, "Logs won't be saved.", UI_COLOR_TEXT); + ui_print_center(4, "Continue anyway?", UI_COLOR_TEXT); + ui_print_center(5, data->no_sd_continue_yes ? "No [Yes]" : "[No] Yes", UI_COLOR_DIMMED); + ui_draw_status("LEFT/RIGHT OK BACK"); return; } - - // --- GPS fix obtained: "GPS fix obtained: Lat=... Lon=..." --- - if (strstr(line, "GPS fix obtained") != NULL) { - ESP_LOGI(TAG, "GPS fix obtained!"); - data->state = STATE_RUNNING; - parse_lat_lon(line, data); - data->needs_redraw = true; + + if (data->stop_confirm_overlay) { + ui_print_center(3, "Stop wardrive?", UI_COLOR_HIGHLIGHT); + ui_print_center(4, data->stop_confirm_yes ? "No [Yes]" : "[No] Yes", UI_COLOR_TEXT); + ui_draw_status("LEFT/RIGHT OK BACK"); return; } - - // --- GPS fix lost: "GPS fix lost! Pausing wardrive..." --- - if (strstr(line, "GPS fix lost") != NULL) { - ESP_LOGW(TAG, "GPS fix lost!"); - data->state = STATE_GPS_LOST; - data->needs_redraw = true; - return; + + const char *state_text = "Starting..."; + if (data->state == STATE_SCANNING) state_text = "Scanning..."; + if (data->state == STATE_GPS_LOST) state_text = "GPS lost..."; + if (data->state == STATE_STOPPED) state_text = "Wardrive stopped"; + + if (data->state == STATE_STARTING && data->gps_wait_elapsed > 0) { + char wait_line[40]; + snprintf(wait_line, sizeof(wait_line), "Waiting fix: %ds", data->gps_wait_elapsed); + ui_print(0, 1, wait_line, UI_COLOR_HIGHLIGHT); + } else if (data->status_main[0]) { + ui_print(0, 1, data->status_main, UI_COLOR_HIGHLIGHT); + } else { + ui_print(0, 1, state_text, UI_COLOR_HIGHLIGHT); } - - // --- GPS fix recovered: "GPS fix recovered: Lat=... Lon=... Resuming wardrive." --- - if (strstr(line, "GPS fix recovered") != NULL) { - ESP_LOGI(TAG, "GPS fix recovered!"); - data->state = STATE_RUNNING; - parse_lat_lon(line, data); - data->needs_redraw = true; - return; + + char counter[40]; + float km = data->wardrive_distance_m / 1000.0f; + snprintf(counter, sizeof(counter), "WiFi:%d BT:%d SAT:%d %.2fkm", + data->wardrive_wifi_count, data->wardrive_bt_count, data->wardrive_sat_count, km); + ui_print(0, 2, counter, UI_COLOR_DIMMED); + + char seen_line[40]; + snprintf(seen_line, sizeof(seen_line), "Captured: %d entries", data->ring_count); + ui_print(0, 3, seen_line, UI_COLOR_TEXT); + + if (data->cur_lat[0] && data->cur_lon[0]) { + char gps_line[40]; + snprintf(gps_line, sizeof(gps_line), "GPS: %.7s, %.7s", data->cur_lat, data->cur_lon); + ui_print(0, 4, gps_line, UI_COLOR_TEXT); + } else { + ui_print(0, 4, "GPS: no fix", UI_COLOR_DIMMED); } - - // --- Wardrive promisc: N unique networks --- - const char *promisc_stat = strstr(line, "Wardrive promisc:"); - if (promisc_stat) { - int n = 0; - if (sscanf(promisc_stat, "Wardrive promisc: %d unique networks", &n) == 1) { - data->unique_networks = n; - data->needs_redraw = true; - } - return; + + if (data->gps_overlay[0]) { + ui_print(0, 5, data->gps_overlay, UI_COLOR_DIMMED); } + + ui_draw_status("ESC:Stop S:Send"); } -static void draw_screen(screen_t *self) +static void on_key(screen_t *self, key_code_t key) { wardrive_data_t *data = (wardrive_data_t *)self->user_data; - - ui_clear(); - - // Draw title - ui_draw_title("Wardrive"); - - int row = 2; - - if (data->state == STATE_WAITING_GPS) { - if (data->is_cap_gps) { - ui_print(0, row, "Acquiring CAP GPS Fix...", UI_COLOR_HIGHLIGHT); - row += 2; - int sats = cap_gps_get_satellites(); - if (sats >= 0) { - char sats_line[32]; - snprintf(sats_line, sizeof(sats_line), "Satellites: %d", sats); - ui_print(0, row, sats_line, UI_COLOR_DIMMED); - } else { - ui_print(0, row, "Waiting for CAP data...", UI_COLOR_DIMMED); - } - } else { - ui_print(0, row, "Acquiring GPS Fix...", UI_COLOR_HIGHLIGHT); - row += 2; - if (data->gps_wait_timeout > 0) { - char wait_line[48]; - snprintf(wait_line, sizeof(wait_line), "Waiting: %d/%d seconds", - data->gps_wait_elapsed, data->gps_wait_timeout); - ui_print(0, row, wait_line, UI_COLOR_DIMMED); + if (!data) return; + + if (data->no_sd_overlay) { + if (key == KEY_LEFT || key == KEY_RIGHT) { + data->no_sd_continue_yes = !data->no_sd_continue_yes; + data->needs_redraw = true; + return; + } + if (key == KEY_ENTER || key == KEY_SPACE) { + if (data->no_sd_continue_yes) { + data->no_sd_overlay = false; + data->start_pending = true; + data->needs_redraw = true; } else { - ui_print(0, row, "Need clear view of the sky.", UI_COLOR_DIMMED); + screen_manager_pop(); } + return; } - } else { - // STATE_RUNNING or STATE_GPS_LOST - char status_line[48]; - snprintf(status_line, sizeof(status_line), "Wardriving, %d networks found.", data->unique_networks); - ui_print(0, row, status_line, UI_COLOR_TEXT); - row += 2; - - if (data->last_ssid[0] != '\0') { - char ssid_line[80]; - snprintf(ssid_line, sizeof(ssid_line), "Last SSID: %s", data->last_ssid); - ui_print(0, row, ssid_line, UI_COLOR_TEXT); - } else { - ui_print(0, row, "Last SSID: -", UI_COLOR_DIMMED); + if (key == KEY_ESC || key == KEY_BACKSPACE) { + screen_manager_pop(); + return; } - row += 2; - - if (data->state == STATE_GPS_LOST) { - if (data->is_cap_gps) { - ui_print(0, row, "CAP GPS fix lost! Pausing...", UI_COLOR_HIGHLIGHT); + } + + if (data->stop_confirm_overlay) { + if (key == KEY_LEFT || key == KEY_RIGHT) { + data->stop_confirm_yes = !data->stop_confirm_yes; + data->needs_redraw = true; + return; + } + if (key == KEY_ENTER || key == KEY_SPACE) { + if (data->stop_confirm_yes) { + uart_flush_rx(); + wardrive_send(data, "stop"); + data->state = STATE_STOPPED; + data->wardrive_started = false; + uart_set_wardrive_active(false); + screen_manager_pop(); } else { - ui_print(0, row, "GPS fix lost! Pausing...", UI_COLOR_HIGHLIGHT); + data->stop_confirm_overlay = false; + data->needs_redraw = true; } - } else if (data->lat[0] != '\0' && data->lon[0] != '\0') { - char gps_line[48]; - double lat_val = strtod(data->lat, NULL); - double lon_val = strtod(data->lon, NULL); - snprintf(gps_line, sizeof(gps_line), "Last GPS: %.5f, %.5f", lat_val, lon_val); - ui_print(0, row, gps_line, UI_COLOR_DIMMED); - } else { - ui_print(0, row, "Last GPS: Waiting...", UI_COLOR_DIMMED); + return; + } + if (key == KEY_ESC || key == KEY_BACKSPACE) { + data->stop_confirm_overlay = false; + data->needs_redraw = true; + return; } } - - // Draw status bar - ui_draw_status("ESC: Stop & Exit"); -} -static void on_key(screen_t *self, key_code_t key) -{ switch (key) { case KEY_ESC: case KEY_Q: case KEY_BACKSPACE: - // Send stop command and go back - uart_send_command("stop"); - screen_manager_pop(); + data->stop_confirm_overlay = true; + data->stop_confirm_yes = false; + data->needs_redraw = true; + break; + case KEY_S: + screen_manager_push(wardrive_upload_screen_create, NULL); break; - default: break; } @@ -365,92 +522,69 @@ static void on_key(screen_t *self, key_code_t key) static void on_destroy(screen_t *self) { wardrive_data_t *data = (wardrive_data_t *)self->user_data; - - // Stop and delete timer - if (data && data->refresh_timer) { - esp_timer_stop(data->refresh_timer); - esp_timer_delete(data->refresh_timer); - } - - // Deinit CAP GPS if active - if (data && data->is_cap_gps) { - cap_gps_deinit(); - } - - // Clear UART callback uart_clear_line_callback(); - + if (data) { + if (data->refresh_timer) { + esp_timer_stop(data->refresh_timer); + esp_timer_delete(data->refresh_timer); + } + if (data->is_cap_gps) { + cap_gps_deinit(); + } + if (data->wardrive_started) { + uart_flush_rx(); + uart_send_command("stop"); + } + uart_set_wardrive_active(false); free(data); } } screen_t* wardrive_screen_create(void *params) { - (void)params; - ESP_LOGI(TAG, "Creating wardrive screen..."); - screen_t *screen = screen_alloc(); if (!screen) return NULL; - - // Allocate user data + wardrive_data_t *data = calloc(1, sizeof(wardrive_data_t)); if (!data) { free(screen); return NULL; } - + data->self = screen; - data->state = STATE_WAITING_GPS; - data->last_ssid[0] = '\0'; - data->lat[0] = '\0'; - data->lon[0] = '\0'; + data->state = STATE_STARTING; data->is_cap_gps = (settings_get_gps_type() == GPS_TYPE_CAP); - data->wardrive_started = false; - data->cap_tick_counter = 0; - + data->trace_enabled = true; + wardrive_run_params_t *in = (wardrive_run_params_t *)params; + if (in) { + data->trace_enabled = in->trace; + free(in); + } + data->no_sd_overlay = is_board_sd_missing(); + data->no_sd_continue_yes = false; + data->start_pending = !data->no_sd_overlay; + snprintf(data->status_main, sizeof(data->status_main), "Starting..."); + snprintf(data->gps_overlay, sizeof(data->gps_overlay), "Waiting for GPS"); + screen->user_data = data; + screen->on_draw = draw_screen; + screen->on_tick = on_tick; + screen->on_resume = on_resume; screen->on_key = on_key; screen->on_destroy = on_destroy; - screen->on_draw = draw_screen; - - // Create periodic refresh timer + esp_timer_create_args_t timer_args = { .callback = refresh_timer_callback, .arg = data, .name = "wardrive_refresh" }; - if (esp_timer_create(&timer_args, &data->refresh_timer) == ESP_OK) { esp_timer_start_periodic(data->refresh_timer, REFRESH_INTERVAL_US); - } else { - ESP_LOGW(TAG, "Failed to create refresh timer"); } - - // Register UART callback for parsing wardrive output - uart_register_line_callback(uart_line_callback, data); - - // Draw initial screen first (shows "Acquiring GPS Fix...") + draw_screen(screen); - - if (data->is_cap_gps) { - // CAP GPS mode: init UART2 driver, wait for fix in timer callback - ESP_LOGI(TAG, "CAP GPS mode - initializing CAP GPS driver..."); - esp_err_t ret = cap_gps_init(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to init CAP GPS: %s", esp_err_to_name(ret)); - } - // Wardrive will start when fix is obtained (in refresh_timer_callback) - } else { - // Non-CAP GPS: existing behavior - vTaskDelay(pdMS_TO_TICKS(3000)); - uart_send_command("start_wardrive_promisc"); - data->wardrive_started = true; - buzzer_beep_attack(); - } - ESP_LOGI(TAG, "Wardrive screen created"); return screen; } - diff --git a/main/screens/wardrive_screen.h b/main/screens/wardrive_screen.h index c30635d..682e32b 100644 --- a/main/screens/wardrive_screen.h +++ b/main/screens/wardrive_screen.h @@ -7,10 +7,15 @@ #define WARDRIVE_SCREEN_H #include "screen_manager.h" +#include + +typedef struct { + bool trace; +} wardrive_run_params_t; /** * @brief Create the Wardrive screen - * @param params Unused (pass NULL) + * @param params Optional wardrive_run_params_t* * @return Pointer to the created screen, or NULL on failure */ screen_t* wardrive_screen_create(void *params); diff --git a/main/screens/wardrive_upload_screen.c b/main/screens/wardrive_upload_screen.c new file mode 100644 index 0000000..b31929e --- /dev/null +++ b/main/screens/wardrive_upload_screen.c @@ -0,0 +1,709 @@ +/** + * @file wardrive_upload_screen.c + * @brief Wardrive upload screen (WiGLE/WDGWars) + */ + +#include "wardrive_upload_screen.h" +#include "wifi_connect_screen.h" +#include "uart_handler.h" +#include "text_ui.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include +#include +#include +#include + +static const char *TAG = "WARDRIVE_UPLOAD"; + +#define MAX_UPLOAD_FILES 48 +#define MAX_SCAN_DIRS 4 +#define VISIBLE_ITEMS 4 + +typedef enum { + STATE_LOADING_FILES = 0, + STATE_PICK_FILES, + STATE_WAIT_WIFI, + STATE_UPLOADING, + STATE_DONE, + STATE_ERROR +} upload_state_t; + +typedef struct { + char path[160]; + char name[96]; + bool selected; + void *checkbox_ptr; +} upload_file_t; + +typedef struct { + upload_state_t state; + wardrive_upload_target_t target; + upload_file_t files[MAX_UPLOAD_FILES]; + int file_count; + int selected_index; + int scroll_offset; + int selected_count; + + volatile bool needs_redraw; + volatile bool closing; + + volatile bool file_scan_running; + volatile bool file_scan_stop; + TaskHandle_t file_scan_task; + char active_dir[48]; + char status_line[64]; + + volatile bool task_running; + volatile bool cancel_requested; + TaskHandle_t upload_task; + int upload_uploaded; + int upload_failed; + int upload_skipped; + int upload_total; + int upload_index; + + volatile bool waiting_file_done; + volatile bool waiting_key_check; + volatile bool key_missing; + volatile bool key_checked; + volatile bool upload_fatal; + char fatal_reason[64]; + + int cur_ok; + int cur_skip; + int cur_fail; + bool cur_accounted; +} wardrive_upload_data_t; + +static const char *k_scan_dirs[MAX_SCAN_DIRS] = { + "/sdcard/lab/wardrives", + "/sdcard/lab/wardrive", + "/sdcard/lab", + "/sdcard" +}; + +static void draw_screen(screen_t *self); + +static bool contains_nocase(const char *text, const char *needle) +{ + if (!text || !needle) return false; + size_t nlen = strlen(needle); + if (nlen == 0) return true; + for (const char *p = text; *p; p++) { + if (tolower((unsigned char)*p) == tolower((unsigned char)needle[0])) { + if (strncasecmp(p, needle, nlen) == 0) return true; + } + } + return false; +} + +static bool has_upload_ext(const char *name) +{ + const char *dot = strrchr(name, '.'); + if (!dot) return false; + return strcasecmp(dot, ".csv") == 0 || + strcasecmp(dot, ".txt") == 0 || + strcasecmp(dot, ".log") == 0; +} + +static bool is_skip_line(const char *line) +{ + if (!line || !line[0]) return true; + if ((line[0] == 'I' || line[0] == 'W' || line[0] == 'E' || line[0] == 'D') && + line[1] == ' ' && line[2] == '(') return true; + if (strstr(line, "[MEM]") != NULL) return true; + if (strstr(line, "Files in ") != NULL) return true; + if (strstr(line, "Found ") != NULL) return true; + if (strncmp(line, "list_dir", 8) == 0) return true; + return false; +} + +static void trim_token(char *s) +{ + if (!s) return; + while (*s == ' ' || *s == '\t' || *s == '"' || *s == '\'' || *s == ',') { + memmove(s, s + 1, strlen(s)); + } + size_t len = strlen(s); + while (len > 0) { + char c = s[len - 1]; + if (c == ' ' || c == '\t' || c == '"' || c == '\'' || c == ',' || c == '\r' || c == '\n') { + s[--len] = '\0'; + } else { + break; + } + } +} + +static const char *file_basename(const char *path) +{ + if (!path) return ""; + const char *slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +static bool add_file_entry(wardrive_upload_data_t *data, const char *dir, const char *token) +{ + if (!data || !dir || !token || !token[0]) return false; + if (data->file_count >= MAX_UPLOAD_FILES) return false; + if (!has_upload_ext(token)) return false; + + char full_path[160]; + if (token[0] == '/') { + snprintf(full_path, sizeof(full_path), "%s", token); + } else { + snprintf(full_path, sizeof(full_path), "%s/%s", dir, token); + } + + const char *base = file_basename(full_path); + if ((strcmp(dir, "/sdcard/lab") == 0 || strcmp(dir, "/sdcard") == 0) && + !contains_nocase(base, "wardrive") && !contains_nocase(base, "wigle")) { + return false; + } + + for (int i = 0; i < data->file_count; i++) { + if (strcasecmp(data->files[i].path, full_path) == 0) { + return false; + } + } + + upload_file_t *dst = &data->files[data->file_count++]; + snprintf(dst->path, sizeof(dst->path), "%s", full_path); + snprintf(dst->name, sizeof(dst->name), "%s", base); + dst->selected = false; + dst->checkbox_ptr = (void *)dst; + return true; +} + +static void parse_list_dir_line(const char *line, wardrive_upload_data_t *data) +{ + if (!line || !data || !data->file_scan_running) return; + if (is_skip_line(line)) return; + + char tmp[256]; + snprintf(tmp, sizeof(tmp), "%s", line); + char *save = NULL; + char *tok = strtok_r(tmp, " \t", &save); + char candidate[160] = {0}; + + while (tok) { + char cleaned[160]; + snprintf(cleaned, sizeof(cleaned), "%s", tok); + trim_token(cleaned); + if (has_upload_ext(cleaned)) { + snprintf(candidate, sizeof(candidate), "%s", cleaned); + } + tok = strtok_r(NULL, " \t", &save); + } + + if (candidate[0]) { + if (add_file_entry(data, data->active_dir, candidate)) { + data->needs_redraw = true; + } + } +} + +static bool parse_upload_summary(const char *line, int *up, int *fail, int *skip) +{ + int a = 0, b = 0, c = 0; + if (sscanf(line, "up=%d fail=%d skip=%d", &a, &b, &c) == 3) { + *up = a; *fail = b; *skip = c; return true; + } + if (sscanf(line, "uploaded=%d failed=%d skipped=%d", &a, &b, &c) == 3) { + *up = a; *fail = b; *skip = c; return true; + } + if (sscanf(line, "Done: %d uploaded, %d duplicate, %d failed", &a, &b, &c) == 3) { + *up = a; *skip = b; *fail = c; return true; + } + if (sscanf(line, "Done: %d uploaded, %d skipped, %d failed", &a, &b, &c) == 3) { + *up = a; *skip = b; *fail = c; return true; + } + return false; +} + +static void uart_line_callback(const char *line, void *user_data) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)user_data; + if (!data || data->closing) return; + + if (data->file_scan_running) { + parse_list_dir_line(line, data); + } + + if (data->waiting_key_check) { + if (contains_nocase(line, "not set") || + contains_nocase(line, "missing") || + contains_nocase(line, "NO WIGLE CREDENTIALS") || + contains_nocase(line, "NO WDGWARS CREDENTIALS")) { + data->key_missing = true; + data->key_checked = true; + data->waiting_key_check = false; + data->needs_redraw = true; + return; + } + if (contains_nocase(line, "key") || contains_nocase(line, "credential")) { + data->key_checked = true; + data->waiting_key_check = false; + } + } + + if (!data->task_running) return; + if (is_skip_line(line)) return; + + if (contains_nocase(line, "NO WIGLE CREDENTIALS") || + contains_nocase(line, "NO WDGWARS CREDENTIALS") || + contains_nocase(line, "WIFI NOT CONNECTED") || + contains_nocase(line, "WDGWARS AUTH FAILED") || + contains_nocase(line, "Unrecognized command")) { + snprintf(data->fatal_reason, sizeof(data->fatal_reason), "%.63s", line); + data->upload_fatal = true; + data->waiting_file_done = false; + data->needs_redraw = true; + return; + } + + int up = 0, fail = 0, skip = 0; + if (!data->cur_accounted && parse_upload_summary(line, &up, &fail, &skip)) { + data->upload_uploaded += up; + data->upload_failed += fail; + data->upload_skipped += skip; + data->cur_accounted = true; + data->waiting_file_done = false; + data->needs_redraw = true; + return; + } + + if (contains_nocase(line, "-> OK")) data->cur_ok++; + if (contains_nocase(line, "-> skipped") || contains_nocase(line, "duplicate")) data->cur_skip++; + if (contains_nocase(line, "-> FAILED") || contains_nocase(line, "FAILED")) data->cur_fail++; + + if (contains_nocase(line, "Done:")) { + if (!data->cur_accounted) { + data->upload_uploaded += data->cur_ok; + data->upload_skipped += data->cur_skip; + data->upload_failed += data->cur_fail; + data->cur_accounted = true; + } + data->waiting_file_done = false; + data->needs_redraw = true; + } +} + +static void file_scan_task(void *arg) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)arg; + if (!data) { + vTaskDelete(NULL); + return; + } + + data->file_scan_running = true; + data->state = STATE_LOADING_FILES; + snprintf(data->status_line, sizeof(data->status_line), "Loading files..."); + data->needs_redraw = true; + + for (int i = 0; i < MAX_SCAN_DIRS && !data->file_scan_stop; i++) { + snprintf(data->active_dir, sizeof(data->active_dir), "%s", k_scan_dirs[i]); + char cmd[96]; + snprintf(cmd, sizeof(cmd), "list_dir %s", data->active_dir); + uart_flush_rx(); + uart_send_command(cmd); + for (int t = 0; t < 12 && !data->file_scan_stop; t++) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + + data->file_scan_running = false; + data->state = STATE_PICK_FILES; + snprintf(data->status_line, sizeof(data->status_line), "Found %d files", data->file_count); + data->needs_redraw = true; + data->file_scan_task = NULL; + vTaskDelete(NULL); +} + +static void upload_task(void *arg) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)arg; + if (!data) { + vTaskDelete(NULL); + return; + } + + if (uart_is_wardrive_active()) { + data->state = STATE_ERROR; + snprintf(data->status_line, sizeof(data->status_line), "Stop wardrive first"); + data->task_running = false; + data->upload_task = NULL; + data->needs_redraw = true; + vTaskDelete(NULL); + return; + } + + const char *key_cmd = (data->target == WARDRIVE_UPLOAD_TARGET_WIGLE) ? "wigle_key read" : "wdgwars_key read"; + const char *upload_prefix = (data->target == WARDRIVE_UPLOAD_TARGET_WIGLE) ? "wigle_upload" : "wdgwars_upload"; + + data->waiting_key_check = true; + data->key_checked = false; + data->key_missing = false; + uart_flush_rx(); + uart_send_command(key_cmd); + for (int i = 0; i < 40 && !data->key_checked && !data->cancel_requested; i++) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + data->waiting_key_check = false; + if (data->key_missing) { + data->state = STATE_ERROR; + snprintf(data->status_line, sizeof(data->status_line), "Credentials missing"); + data->task_running = false; + data->upload_task = NULL; + data->needs_redraw = true; + vTaskDelete(NULL); + return; + } + + data->upload_uploaded = 0; + data->upload_failed = 0; + data->upload_skipped = 0; + + int processed = 0; + for (int i = 0; i < data->file_count && !data->cancel_requested; i++) { + if (!data->files[i].selected) continue; + processed++; + data->upload_index = processed; + data->needs_redraw = true; + + data->cur_ok = 0; + data->cur_skip = 0; + data->cur_fail = 0; + data->cur_accounted = false; + data->waiting_file_done = true; + data->upload_fatal = false; + data->fatal_reason[0] = '\0'; + + char cmd[240]; + snprintf(cmd, sizeof(cmd), "%s \"%s\"", upload_prefix, data->files[i].name); + uart_flush_rx(); + uart_send_command(cmd); + + for (int t = 0; t < 250 && data->waiting_file_done && !data->cancel_requested && !data->upload_fatal; t++) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + + if (data->upload_fatal) { + data->state = STATE_ERROR; + snprintf(data->status_line, sizeof(data->status_line), "%.63s", data->fatal_reason); + data->task_running = false; + data->upload_task = NULL; + data->needs_redraw = true; + vTaskDelete(NULL); + return; + } + + if (data->waiting_file_done && !data->cur_accounted) { + data->upload_failed++; + data->waiting_file_done = false; + } + } + + if (data->cancel_requested) { + data->state = STATE_ERROR; + snprintf(data->status_line, sizeof(data->status_line), "Upload cancelled"); + } else { + data->state = STATE_DONE; + snprintf(data->status_line, sizeof(data->status_line), "Upload complete"); + } + data->task_running = false; + data->upload_task = NULL; + data->needs_redraw = true; + vTaskDelete(NULL); +} + +static void draw_files_list(wardrive_upload_data_t *data) +{ + for (int i = 0; i < VISIBLE_ITEMS; i++) { + int idx = data->scroll_offset + i; + if (idx < data->file_count) { + char label[32]; + snprintf(label, sizeof(label), "%.23s", data->files[idx].name); + ui_draw_menu_item(2 + i, label, idx == data->selected_index, true, data->files[idx].selected); + } else { + display_fill_rect(0, (2 + i) * 16, DISPLAY_WIDTH, 16, UI_COLOR_BG); + } + } +} + +static void draw_screen(screen_t *self) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)self->user_data; + + ui_clear(); + ui_draw_title("Wardrive Upload"); + + const char *target = (data->target == WARDRIVE_UPLOAD_TARGET_WIGLE) ? "WiGLE" : "WDGWars"; + char top[40]; + snprintf(top, sizeof(top), "Target:%s Sel:%d/%d", target, data->selected_count, data->file_count); + ui_print(0, 1, top, UI_COLOR_TEXT); + + if (data->state == STATE_LOADING_FILES) { + ui_print_center(3, "Loading files...", UI_COLOR_DIMMED); + ui_print_center(4, data->status_line, UI_COLOR_DIMMED); + ui_draw_status("ESC:Back"); + return; + } + + if (data->state == STATE_WAIT_WIFI) { + ui_print_center(3, "Connect to WiFi...", UI_COLOR_TEXT); + ui_print_center(4, "Returning to upload", UI_COLOR_DIMMED); + ui_draw_status("ESC:Cancel"); + return; + } + + if (data->state == STATE_UPLOADING) { + char p1[40]; + snprintf(p1, sizeof(p1), "Uploading %d/%d", data->upload_index, data->upload_total); + ui_print_center(3, p1, UI_COLOR_TEXT); + char p2[40]; + snprintf(p2, sizeof(p2), "up:%d skip:%d fail:%d", data->upload_uploaded, data->upload_skipped, data->upload_failed); + ui_print_center(4, p2, UI_COLOR_DIMMED); + ui_draw_status("ESC:Cancel"); + return; + } + + if (data->state == STATE_DONE || data->state == STATE_ERROR) { + ui_print_center(2, data->status_line, data->state == STATE_DONE ? UI_COLOR_HIGHLIGHT : UI_COLOR_TEXT); + char p2[40]; + snprintf(p2, sizeof(p2), "up:%d skip:%d fail:%d", data->upload_uploaded, data->upload_skipped, data->upload_failed); + ui_print_center(4, p2, UI_COLOR_DIMMED); + ui_draw_status("ESC:Back"); + return; + } + + draw_files_list(data); + bool send_enabled = (data->selected_count > 0 && !data->task_running); + ui_print(0, 6, send_enabled ? "Send: READY" : "Send: disabled", send_enabled ? UI_COLOR_HIGHLIGHT : UI_COLOR_DIMMED); + ui_draw_status("UP/DOWN SPACE S:Send L/R:Target ESC:Back"); +} + +static void recalc_selected(wardrive_upload_data_t *data) +{ + int count = 0; + for (int i = 0; i < data->file_count; i++) { + if (data->files[i].selected) count++; + } + data->selected_count = count; +} + +static void start_upload(wardrive_upload_data_t *data) +{ + if (data->task_running || data->selected_count <= 0) return; + if (uart_is_wardrive_active()) { + data->state = STATE_ERROR; + snprintf(data->status_line, sizeof(data->status_line), "Wardrive is active"); + data->needs_redraw = true; + return; + } + if (!uart_is_wifi_connected()) { + data->state = STATE_WAIT_WIFI; + data->needs_redraw = true; + return; + } + + data->upload_total = data->selected_count; + data->upload_index = 0; + data->cancel_requested = false; + data->task_running = true; + data->state = STATE_UPLOADING; + data->needs_redraw = true; + xTaskCreate(upload_task, "wardrive_upload", 6144, data, 8, &data->upload_task); +} + +static void on_tick(screen_t *self) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)self->user_data; + if (!data) return; + + if (data->state == STATE_WAIT_WIFI && uart_is_wifi_connected()) { + start_upload(data); + } + + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); + } +} + +static void on_key(screen_t *self, key_code_t key) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)self->user_data; + if (!data) return; + + if (data->state == STATE_WAIT_WIFI) { + if (key == KEY_ESC || key == KEY_BACKSPACE) { + data->state = STATE_PICK_FILES; + data->needs_redraw = true; + } + return; + } + + if (data->state == STATE_UPLOADING) { + if (key == KEY_ESC || key == KEY_BACKSPACE) { + data->cancel_requested = true; + snprintf(data->status_line, sizeof(data->status_line), "Cancelling..."); + data->needs_redraw = true; + } + return; + } + + if (data->state == STATE_DONE || data->state == STATE_ERROR) { + if (key == KEY_ESC || key == KEY_BACKSPACE || key == KEY_ENTER || key == KEY_SPACE) { + screen_manager_pop(); + } + return; + } + + switch (key) { + case KEY_UP: + if (data->file_count > 0) { + if (data->selected_index > 0) { + data->selected_index--; + } else { + data->selected_index = data->file_count - 1; + } + if (data->selected_index < data->scroll_offset) { + data->scroll_offset = data->selected_index; + } + if (data->selected_index >= data->scroll_offset + VISIBLE_ITEMS) { + data->scroll_offset = data->selected_index - VISIBLE_ITEMS + 1; + } + data->needs_redraw = true; + } + break; + case KEY_DOWN: + if (data->file_count > 0) { + if (data->selected_index < data->file_count - 1) { + data->selected_index++; + } else { + data->selected_index = 0; + } + if (data->selected_index < data->scroll_offset) { + data->scroll_offset = data->selected_index; + } + if (data->selected_index >= data->scroll_offset + VISIBLE_ITEMS) { + data->scroll_offset = data->selected_index - VISIBLE_ITEMS + 1; + } + data->needs_redraw = true; + } + break; + case KEY_LEFT: + case KEY_RIGHT: + data->target = (data->target == WARDRIVE_UPLOAD_TARGET_WIGLE) ? + WARDRIVE_UPLOAD_TARGET_WDGWARS : WARDRIVE_UPLOAD_TARGET_WIGLE; + data->needs_redraw = true; + break; + case KEY_SPACE: + case KEY_ENTER: + if (data->file_count > 0 && data->selected_index < data->file_count) { + upload_file_t *f = &data->files[data->selected_index]; + f->selected = !f->selected; + recalc_selected(data); + data->needs_redraw = true; + } + break; + case KEY_S: + if (data->selected_count > 0 && !data->task_running) { + start_upload(data); + if (data->state == STATE_WAIT_WIFI) { + wifi_connect_params_t *w = malloc(sizeof(wifi_connect_params_t)); + if (w) { + w->auto_close_on_success = true; + screen_manager_push(wifi_connect_screen_create, w); + } else { + screen_manager_push(wifi_connect_screen_create, NULL); + } + } + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + screen_manager_pop(); + break; + default: + break; + } +} + +static void on_resume(screen_t *self) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)self->user_data; + if (!data) return; + uart_register_line_callback(uart_line_callback, data); + if (data->state == STATE_WAIT_WIFI && !uart_is_wifi_connected()) { + data->state = STATE_PICK_FILES; + } + data->needs_redraw = true; +} + +static void on_destroy(screen_t *self) +{ + wardrive_upload_data_t *data = (wardrive_upload_data_t *)self->user_data; + uart_clear_line_callback(); + + if (data) { + data->closing = true; + data->file_scan_stop = true; + data->cancel_requested = true; + for (int i = 0; i < 25 && (data->upload_task || data->file_scan_task); i++) { + vTaskDelay(pdMS_TO_TICKS(20)); + } + if (data->upload_task) { + vTaskDelete(data->upload_task); + data->upload_task = NULL; + } + if (data->file_scan_task) { + vTaskDelete(data->file_scan_task); + data->file_scan_task = NULL; + } + free(data); + } +} + +screen_t* wardrive_upload_screen_create(void *params) +{ + (void)params; + + screen_t *screen = screen_alloc(); + if (!screen) return NULL; + + wardrive_upload_data_t *data = calloc(1, sizeof(wardrive_upload_data_t)); + if (!data) { + free(screen); + return NULL; + } + + data->state = STATE_LOADING_FILES; + data->target = WARDRIVE_UPLOAD_TARGET_WIGLE; + wardrive_upload_params_t *in = (wardrive_upload_params_t *)params; + if (in) { + data->target = in->target; + free(in); + } + snprintf(data->status_line, sizeof(data->status_line), "Loading files..."); + data->needs_redraw = true; + + screen->user_data = data; + screen->on_draw = draw_screen; + screen->on_tick = on_tick; + screen->on_key = on_key; + screen->on_resume = on_resume; + screen->on_destroy = on_destroy; + + uart_register_line_callback(uart_line_callback, data); + xTaskCreate(file_scan_task, "wardrive_files", 4096, data, 6, &data->file_scan_task); + + draw_screen(screen); + ESP_LOGI(TAG, "Wardrive upload screen created"); + return screen; +} diff --git a/main/screens/wardrive_upload_screen.h b/main/screens/wardrive_upload_screen.h new file mode 100644 index 0000000..1a8cb22 --- /dev/null +++ b/main/screens/wardrive_upload_screen.h @@ -0,0 +1,27 @@ +/** + * @file wardrive_upload_screen.h + * @brief Wardrive upload screen (WiGLE/WDGWars) + */ + +#ifndef WARDRIVE_UPLOAD_SCREEN_H +#define WARDRIVE_UPLOAD_SCREEN_H + +#include "screen_manager.h" + +typedef enum { + WARDRIVE_UPLOAD_TARGET_WIGLE = 0, + WARDRIVE_UPLOAD_TARGET_WDGWARS = 1 +} wardrive_upload_target_t; + +typedef struct { + wardrive_upload_target_t target; +} wardrive_upload_params_t; + +/** + * @brief Create wardrive upload screen + * @param params Optional wardrive_upload_params_t* + * @return Screen instance + */ +screen_t* wardrive_upload_screen_create(void *params); + +#endif // WARDRIVE_UPLOAD_SCREEN_H diff --git a/main/screens/wifi_connect_screen.c b/main/screens/wifi_connect_screen.c index 98eceba..323f156 100644 --- a/main/screens/wifi_connect_screen.c +++ b/main/screens/wifi_connect_screen.c @@ -2,7 +2,8 @@ * @file wifi_connect_screen.c * @brief WiFi connection screen implementation * - * Flow: SSID input -> Password input -> send wifi_connect -> show result + * Flow: Choose method (Scan/Manual) -> SSID selection -> Password input + * -> send wifi_connect -> show result */ #include "wifi_connect_screen.h" @@ -10,20 +11,25 @@ #include "uart_handler.h" #include "text_ui.h" #include "esp_log.h" +#include "esp_timer.h" #include #include static const char *TAG = "WIFI_CONNECT"; -// Connection state +#define VISIBLE_ITEMS 5 + typedef enum { + STATE_CHOOSE_METHOD, + STATE_SCANNING, + STATE_PICK_NETWORK, STATE_ENTER_SSID, + STATE_LOOKING_UP_PASS, STATE_ENTER_PASSWORD, STATE_CONNECTING, STATE_RESULT } connect_state_t; -// Screen user data typedef struct { connect_state_t state; char ssid[33]; @@ -31,14 +37,31 @@ typedef struct { bool success; char result_msg[64]; bool needs_redraw; - bool needs_push_ssid_input; // Flag to push SSID input on first tick + bool needs_push_ssid_input; + bool needs_push_password_input; + bool needs_start_pass_lookup; + int pass_timeout_ticks; + bool pass_found; screen_t *self; + bool auto_close_on_success; + int auto_close_ticks; + + int method_index; // 0=Scan, 1=Manual + wifi_network_t *networks; + int network_count; + int selected_index; + int scroll_offset; + int animation_frame; + bool scan_complete; + esp_timer_handle_t update_timer; } wifi_connect_data_t; // Forward declarations static void draw_screen(screen_t *self); static void on_ssid_submitted(const char *text, void *user_data); static void on_password_submitted(const char *text, void *user_data); +static void start_scan(wifi_connect_data_t *data); +static void stop_scan_timer(wifi_connect_data_t *data); /** * @brief UART callback for connection result @@ -48,7 +71,6 @@ static void uart_line_callback(const char *line, void *user_data) wifi_connect_data_t *data = (wifi_connect_data_t *)user_data; if (!data || data->state != STATE_CONNECTING) return; - // Check for success if (strstr(line, "SUCCESS:") != NULL && strstr(line, "Connected") != NULL) { data->success = true; snprintf(data->result_msg, sizeof(data->result_msg), "Connected to %s", data->ssid); @@ -57,7 +79,6 @@ static void uart_line_callback(const char *line, void *user_data) data->needs_redraw = true; ESP_LOGI(TAG, "WiFi connected successfully"); } - // Check for failure else if (strstr(line, "FAILED:") != NULL) { data->success = false; snprintf(data->result_msg, sizeof(data->result_msg), "Failed to connect"); @@ -68,193 +89,617 @@ static void uart_line_callback(const char *line, void *user_data) } } +static void start_connect(wifi_connect_data_t *data) +{ + data->state = STATE_CONNECTING; + draw_screen(data->self); + + uart_register_line_callback(uart_line_callback, data); + + char cmd[128]; + if (data->password[0] != '\0') { + snprintf(cmd, sizeof(cmd), "wifi_connect \"%s\" \"%s\"", data->ssid, data->password); + } else { + snprintf(cmd, sizeof(cmd), "wifi_connect \"%s\"", data->ssid); + } + uart_send_command(cmd); +} + +/** + * @brief Parse a quoted field from a CSV-style line + */ +static bool parse_pass_quoted_field(const char **src, char *dest, size_t max_len) +{ + const char *p = *src; + while (*p == ' ' || *p == ',' || *p == '\t') p++; + if (*p != '"') return false; + p++; + size_t i = 0; + while (*p && *p != '"' && i < max_len - 1) { + dest[i++] = *p++; + } + dest[i] = '\0'; + if (*p == '"') p++; + *src = p; + return true; +} + +static bool is_pass_skip_line(const char *line) +{ + if (strlen(line) < 3) return true; + if ((line[0] == 'I' || line[0] == 'W' || line[0] == 'E' || line[0] == 'D') + && line[1] == ' ' && line[2] == '(') { + return true; + } + if (strstr(line, "[MEM]") != NULL) return true; + if (strncmp(line, "show_pass", 9) == 0) return true; + const char *p = line; + while (*p == ' ') p++; + if (*p == '>') return true; + return false; +} + +/** + * @brief UART callback for show_pass evil output + */ +static void pass_lookup_line_callback(const char *line, void *user_data) +{ + wifi_connect_data_t *data = (wifi_connect_data_t *)user_data; + if (!data || data->state != STATE_LOOKING_UP_PASS) return; + + if (strlen(line) == 0) return; + if (is_pass_skip_line(line)) return; + + if (strstr(line, "No ") != NULL || strstr(line, "no ") != NULL || + strstr(line, "empty") != NULL || strstr(line, "Empty") != NULL) { + return; + } + + char parsed_ssid[33] = {0}; + char parsed_pass[65] = {0}; + const char *p = line; + + if (!parse_pass_quoted_field(&p, parsed_ssid, sizeof(parsed_ssid))) return; + if (!parse_pass_quoted_field(&p, parsed_pass, sizeof(parsed_pass))) return; + + if (strcasecmp(parsed_ssid, data->ssid) == 0) { + strncpy(data->password, parsed_pass, sizeof(data->password) - 1); + data->password[sizeof(data->password) - 1] = '\0'; + data->pass_found = true; + ESP_LOGI(TAG, "Password found for %s", data->ssid); + } +} + +/** + * @brief Scan complete callback from uart_handler + */ +static void on_scan_complete(wifi_network_t *networks, int count, void *user_data) +{ + wifi_connect_data_t *data = (wifi_connect_data_t *)user_data; + + ESP_LOGI(TAG, "Scan complete, %d networks", count); + + if (data->networks) { + free(data->networks); + data->networks = NULL; + } + + if (count > 0) { + data->networks = malloc(count * sizeof(wifi_network_t)); + if (data->networks) { + memcpy(data->networks, networks, count * sizeof(wifi_network_t)); + data->network_count = count; + } + } else { + data->network_count = 0; + } + + data->scan_complete = true; +} + +/** + * @brief Timer callback for scan animation and completion check + */ +static void scan_timer_callback(void *arg) +{ + wifi_connect_data_t *data = (wifi_connect_data_t *)arg; + + if (data->scan_complete) { + stop_scan_timer(data); + + if (data->network_count > 0) { + data->state = STATE_PICK_NETWORK; + data->selected_index = 0; + data->scroll_offset = 0; + } else { + data->state = STATE_CHOOSE_METHOD; + } + data->needs_redraw = true; + return; + } + + data->animation_frame = (data->animation_frame + 1) % 4; + + int y3 = 3 * 16; + display_fill_rect(0, y3, DISPLAY_WIDTH, 16, UI_COLOR_BG); + const char *spinner[] = {"|", "/", "-", "\\"}; + char status[32]; + snprintf(status, sizeof(status), "Scanning... %s", spinner[data->animation_frame]); + ui_print_center(3, status, UI_COLOR_TEXT); +} + +static void stop_scan_timer(wifi_connect_data_t *data) +{ + if (data->update_timer) { + esp_timer_stop(data->update_timer); + esp_timer_delete(data->update_timer); + data->update_timer = NULL; + } +} + +static void start_scan(wifi_connect_data_t *data) +{ + if (data->networks) { + free(data->networks); + data->networks = NULL; + } + data->network_count = 0; + data->scan_complete = false; + data->animation_frame = 0; + data->state = STATE_SCANNING; + + draw_screen(data->self); + + esp_timer_create_args_t timer_args = { + .callback = scan_timer_callback, + .arg = data, + .name = "wifi_conn_scan" + }; + esp_timer_create(&timer_args, &data->update_timer); + esp_timer_start_periodic(data->update_timer, 200000); + + esp_err_t ret = uart_start_wifi_scan(on_scan_complete, data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start scan"); + stop_scan_timer(data); + data->state = STATE_CHOOSE_METHOD; + data->needs_redraw = true; + } +} + +static void push_password_input(wifi_connect_data_t *data) +{ + data->state = STATE_ENTER_PASSWORD; + text_input_params_t *params = malloc(sizeof(text_input_params_t)); + if (params) { + params->title = "Enter Password"; + params->hint = "WiFi password (empty=open)"; + params->on_submit = on_password_submitted; + params->user_data = data; + params->allow_empty = true; + screen_manager_push(text_input_screen_create, params); + } +} + +/* ---- Drawing ---- */ + +static void draw_pick_row(wifi_connect_data_t *data, int net_idx) +{ + int row_on_screen = net_idx - data->scroll_offset; + if (row_on_screen < 0 || row_on_screen >= VISIBLE_ITEMS) return; + + wifi_network_t *net = &data->networks[net_idx]; + char label[32]; + if (net->ssid[0]) { + char ssid_short[20]; + strncpy(ssid_short, net->ssid, sizeof(ssid_short) - 1); + ssid_short[sizeof(ssid_short) - 1] = '\0'; + snprintf(label, sizeof(label), "%.18s %ddB", ssid_short, net->rssi); + } else { + snprintf(label, sizeof(label), "[%.17s]", net->bssid); + } + bool is_selected = (net_idx == data->selected_index); + ui_draw_menu_item(1 + row_on_screen, label, is_selected, false, false); +} + +static void redraw_pick_two_rows(wifi_connect_data_t *data, int old_idx, int new_idx) +{ + draw_pick_row(data, old_idx); + draw_pick_row(data, new_idx); +} + +static void redraw_pick_list(wifi_connect_data_t *data) +{ + ui_draw_title("Pick Network"); + + for (int i = 0; i < VISIBLE_ITEMS; i++) { + int net_idx = data->scroll_offset + i; + if (net_idx < data->network_count) { + draw_pick_row(data, net_idx); + } else { + int y = (1 + i) * 16; + display_fill_rect(0, y, DISPLAY_WIDTH, 16, UI_COLOR_BG); + } + } + + display_fill_rect(DISPLAY_WIDTH - 16, 1 * 16, 16, 16, UI_COLOR_BG); + display_fill_rect(DISPLAY_WIDTH - 16, VISIBLE_ITEMS * 16, 16, 16, UI_COLOR_BG); + + if (data->scroll_offset > 0) { + ui_print(UI_COLS - 2, 1, "^", UI_COLOR_DIMMED); + } + if (data->scroll_offset + VISIBLE_ITEMS < data->network_count) { + ui_print(UI_COLS - 2, VISIBLE_ITEMS, "v", UI_COLOR_DIMMED); + } +} + +static void draw_pick_network(wifi_connect_data_t *data) +{ + ui_clear(); + redraw_pick_list(data); + ui_draw_status("ENTER:Select ESC:Back"); +} + static void draw_screen(screen_t *self) { wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; ui_clear(); - ui_draw_title("WiFi Connect"); switch (data->state) { + case STATE_CHOOSE_METHOD: + ui_draw_title("WiFi Connect"); + ui_draw_menu_item(1, "Scan Networks", data->method_index == 0, false, false); + ui_draw_menu_item(2, "Enter SSID", data->method_index == 1, false, false); + ui_draw_status("UP/DOWN:Navigate ENTER:Select ESC:Back"); + break; + + case STATE_SCANNING: + ui_draw_title("WiFi Scan"); + ui_print_center(3, "Scanning...", UI_COLOR_TEXT); + ui_draw_status("ESC:Cancel"); + break; + + case STATE_PICK_NETWORK: + draw_pick_network(data); + break; + case STATE_ENTER_SSID: + ui_draw_title("WiFi Connect"); ui_print_center(3, "Enter network SSID", UI_COLOR_TEXT); + ui_draw_status("ESC:Back"); break; + case STATE_LOOKING_UP_PASS: + ui_draw_title("WiFi Connect"); + ui_print_center(2, data->ssid, UI_COLOR_HIGHLIGHT); + ui_print_center(4, "Looking up password...", UI_COLOR_DIMMED); + ui_draw_status("ESC:Back"); + break; + case STATE_ENTER_PASSWORD: + ui_draw_title("WiFi Connect"); ui_print_center(2, data->ssid, UI_COLOR_HIGHLIGHT); ui_print_center(4, "Enter password", UI_COLOR_TEXT); + ui_draw_status("ESC:Back"); break; case STATE_CONNECTING: + ui_draw_title("WiFi Connect"); ui_print_center(2, data->ssid, UI_COLOR_HIGHLIGHT); ui_print_center(4, "Connecting...", UI_COLOR_DIMMED); + ui_draw_status("ESC:Back"); break; case STATE_RESULT: + ui_draw_title("WiFi Connect"); if (data->success) { ui_print_center(3, data->result_msg, UI_COLOR_HIGHLIGHT); } else { ui_print_center(3, data->result_msg, UI_COLOR_TEXT); } + ui_draw_status("ESC:Back"); break; } - - ui_draw_status("ESC:Back"); } -static void on_tick(screen_t *self) -{ - wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; - - // Push SSID input screen on first tick (after we're on the stack) - if (data->needs_push_ssid_input) { - data->needs_push_ssid_input = false; - - text_input_params_t *input_params = malloc(sizeof(text_input_params_t)); - if (input_params) { - input_params->title = "Enter SSID"; - input_params->hint = "Network name"; - input_params->on_submit = on_ssid_submitted; - input_params->user_data = data; - screen_manager_push(text_input_screen_create, input_params); - } - return; - } - - if (data->needs_redraw) { - data->needs_redraw = false; - draw_screen(self); - } -} +/* ---- Callbacks ---- */ -/** - * @brief Called when SSID is submitted - */ static void on_ssid_submitted(const char *text, void *user_data) { wifi_connect_data_t *data = (wifi_connect_data_t *)user_data; - // Store SSID strncpy(data->ssid, text, sizeof(data->ssid) - 1); data->ssid[sizeof(data->ssid) - 1] = '\0'; ESP_LOGI(TAG, "SSID entered: %s", data->ssid); - // Pop the text input screen screen_manager_pop(); - // Update state - data->state = STATE_ENTER_PASSWORD; - - // Push password input screen - text_input_params_t *params = malloc(sizeof(text_input_params_t)); - if (params) { - params->title = "Enter Password"; - params->hint = "WiFi password"; - params->on_submit = on_password_submitted; - params->user_data = data; - screen_manager_push(text_input_screen_create, params); - } + data->needs_start_pass_lookup = true; } -/** - * @brief Called when password is submitted - */ static void on_password_submitted(const char *text, void *user_data) { wifi_connect_data_t *data = (wifi_connect_data_t *)user_data; - // Store password strncpy(data->password, text, sizeof(data->password) - 1); data->password[sizeof(data->password) - 1] = '\0'; ESP_LOGI(TAG, "Password entered, connecting to %s", data->ssid); - // Pop the text input screen screen_manager_pop(); + start_connect(data); +} + +/* ---- Tick / Key / Lifecycle ---- */ + +static void on_tick(screen_t *self) +{ + wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; - // Update state - data->state = STATE_CONNECTING; - draw_screen(data->self); - - // Register UART callback - uart_register_line_callback(uart_line_callback, data); + if (data->needs_push_ssid_input) { + data->needs_push_ssid_input = false; + + text_input_params_t *input_params = malloc(sizeof(text_input_params_t)); + if (input_params) { + input_params->title = "Enter SSID"; + input_params->hint = "Network name"; + input_params->on_submit = on_ssid_submitted; + input_params->user_data = data; + screen_manager_push(text_input_screen_create, input_params); + } + return; + } + + if (data->needs_start_pass_lookup) { + data->needs_start_pass_lookup = false; + data->pass_found = false; + data->pass_timeout_ticks = 0; + data->password[0] = '\0'; + data->state = STATE_LOOKING_UP_PASS; + draw_screen(self); + uart_register_line_callback(pass_lookup_line_callback, data); + uart_send_command("show_pass evil"); + return; + } + + if (data->needs_push_password_input) { + data->needs_push_password_input = false; + push_password_input(data); + return; + } + + if (data->state == STATE_LOOKING_UP_PASS) { + if (data->pass_found) { + uart_clear_line_callback(); + ESP_LOGI(TAG, "Auto-connecting to %s with found password", data->ssid); + start_connect(data); + return; + } + data->pass_timeout_ticks++; + if (data->pass_timeout_ticks > 40) { + uart_clear_line_callback(); + ESP_LOGI(TAG, "Password not found for %s, asking user", data->ssid); + data->needs_push_password_input = true; + } + return; + } + + if (data->state == STATE_RESULT && data->success && data->auto_close_on_success) { + data->auto_close_ticks++; + if (data->auto_close_ticks >= 2) { + screen_manager_pop(); + return; + } + } - // Send connect command - char cmd[128]; - snprintf(cmd, sizeof(cmd), "wifi_connect %s %s", data->ssid, data->password); - uart_send_command(cmd); + if (data->needs_redraw) { + data->needs_redraw = false; + draw_screen(self); + } } static void on_key(screen_t *self, key_code_t key) { wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; - - switch (key) { - case KEY_ESC: - case KEY_BACKSPACE: - screen_manager_pop(); + + switch (data->state) { + case STATE_CHOOSE_METHOD: + switch (key) { + case KEY_UP: + if (data->method_index > 0) { + int old = data->method_index; + data->method_index--; + ui_draw_menu_item(old + 1, old == 0 ? "Scan Networks" : "Enter SSID", + false, false, false); + ui_draw_menu_item(data->method_index + 1, + data->method_index == 0 ? "Scan Networks" : "Enter SSID", + true, false, false); + } else { + int old = data->method_index; + data->method_index = 1; + ui_draw_menu_item(old + 1, "Scan Networks", false, false, false); + ui_draw_menu_item(2, "Enter SSID", true, false, false); + } + break; + case KEY_DOWN: + if (data->method_index < 1) { + int old = data->method_index; + data->method_index++; + ui_draw_menu_item(old + 1, + old == 0 ? "Scan Networks" : "Enter SSID", + false, false, false); + ui_draw_menu_item(data->method_index + 1, + data->method_index == 0 ? "Scan Networks" : "Enter SSID", + true, false, false); + } else { + int old = data->method_index; + data->method_index = 0; + ui_draw_menu_item(old + 1, "Enter SSID", false, false, false); + ui_draw_menu_item(1, "Scan Networks", true, false, false); + } + break; + case KEY_ENTER: + case KEY_SPACE: + if (data->method_index == 0) { + start_scan(data); + } else { + data->state = STATE_ENTER_SSID; + data->needs_push_ssid_input = true; + draw_screen(self); + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + screen_manager_pop(); + break; + default: + break; + } break; - - case KEY_ENTER: - case KEY_SPACE: - // If showing result, go back - if (data->state == STATE_RESULT) { + + case STATE_SCANNING: + if (key == KEY_ESC || key == KEY_BACKSPACE) { + stop_scan_timer(data); + data->state = STATE_CHOOSE_METHOD; + draw_screen(self); + } + break; + + case STATE_PICK_NETWORK: + switch (key) { + case KEY_UP: + if (data->selected_index > 0) { + int old_idx = data->selected_index; + if (data->selected_index == data->scroll_offset && data->scroll_offset > 0) { + data->scroll_offset -= VISIBLE_ITEMS; + if (data->scroll_offset < 0) data->scroll_offset = 0; + data->selected_index = data->scroll_offset + VISIBLE_ITEMS - 1; + if (data->selected_index >= data->network_count) + data->selected_index = data->network_count - 1; + redraw_pick_list(data); + } else { + data->selected_index--; + redraw_pick_two_rows(data, old_idx, data->selected_index); + } + } else { + data->selected_index = data->network_count - 1; + data->scroll_offset = data->selected_index - VISIBLE_ITEMS + 1; + if (data->scroll_offset < 0) data->scroll_offset = 0; + redraw_pick_list(data); + } + break; + case KEY_DOWN: + if (data->selected_index < data->network_count - 1) { + int old_idx = data->selected_index; + if (data->selected_index == data->scroll_offset + VISIBLE_ITEMS - 1) { + data->scroll_offset += VISIBLE_ITEMS; + data->selected_index = data->scroll_offset; + redraw_pick_list(data); + } else { + data->selected_index++; + redraw_pick_two_rows(data, old_idx, data->selected_index); + } + } else { + data->selected_index = 0; + data->scroll_offset = 0; + redraw_pick_list(data); + } + break; + case KEY_ENTER: + case KEY_SPACE: + if (data->selected_index < data->network_count) { + wifi_network_t *net = &data->networks[data->selected_index]; + strncpy(data->ssid, net->ssid, sizeof(data->ssid) - 1); + data->ssid[sizeof(data->ssid) - 1] = '\0'; + ESP_LOGI(TAG, "Network selected: %s", data->ssid); + data->needs_start_pass_lookup = true; + } + break; + case KEY_ESC: + case KEY_BACKSPACE: + data->state = STATE_CHOOSE_METHOD; + draw_screen(self); + break; + default: + break; + } + break; + + case STATE_LOOKING_UP_PASS: + if (key == KEY_ESC || key == KEY_BACKSPACE) { + uart_clear_line_callback(); + data->state = STATE_CHOOSE_METHOD; + draw_screen(self); + } + break; + + case STATE_ENTER_SSID: + case STATE_ENTER_PASSWORD: + case STATE_CONNECTING: + if (key == KEY_ESC || key == KEY_BACKSPACE) { screen_manager_pop(); } break; - - default: + + case STATE_RESULT: + if (key == KEY_ENTER || key == KEY_SPACE || + key == KEY_ESC || key == KEY_BACKSPACE) { + screen_manager_pop(); + } break; } } static void on_destroy(screen_t *self) { + wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; + uart_clear_line_callback(); - - if (self->user_data) { - free(self->user_data); + + if (data) { + stop_scan_timer(data); + if (data->networks) { + free(data->networks); + } + free(data); } } static void on_resume(screen_t *self) { wifi_connect_data_t *data = (wifi_connect_data_t *)self->user_data; - - // When returning from text input, check state and act - if (data->state == STATE_ENTER_SSID) { - // Returned from SSID input without submitting, just redraw - draw_screen(self); - } else if (data->state == STATE_ENTER_PASSWORD) { - // State was updated by callback, just redraw - draw_screen(self); - } else if (data->state == STATE_CONNECTING) { - draw_screen(self); - } else { - draw_screen(self); + + if (data->state == STATE_ENTER_SSID && data->ssid[0] == '\0') { + data->state = STATE_CHOOSE_METHOD; } + + draw_screen(self); } screen_t* wifi_connect_screen_create(void *params) { - (void)params; - ESP_LOGI(TAG, "Creating WiFi connect screen..."); screen_t *screen = screen_alloc(); if (!screen) return NULL; - // Allocate user data wifi_connect_data_t *data = calloc(1, sizeof(wifi_connect_data_t)); if (!data) { free(screen); return NULL; } - data->state = STATE_ENTER_SSID; + data->state = STATE_CHOOSE_METHOD; data->self = screen; - data->needs_push_ssid_input = true; // Push on first tick, after we're on stack + data->auto_close_on_success = true; + data->auto_close_ticks = 0; + wifi_connect_params_t *in = (wifi_connect_params_t *)params; + if (in) { + data->auto_close_on_success = in->auto_close_on_success; + free(in); + } screen->user_data = data; screen->on_key = on_key; @@ -263,7 +708,6 @@ screen_t* wifi_connect_screen_create(void *params) screen->on_draw = draw_screen; screen->on_tick = on_tick; - // Draw initial screen draw_screen(screen); ESP_LOGI(TAG, "WiFi connect screen created"); diff --git a/main/screens/wifi_connect_screen.h b/main/screens/wifi_connect_screen.h index cf34a32..4a6f85e 100644 --- a/main/screens/wifi_connect_screen.h +++ b/main/screens/wifi_connect_screen.h @@ -7,10 +7,15 @@ #define WIFI_CONNECT_SCREEN_H #include "screen_manager.h" +#include + +typedef struct { + bool auto_close_on_success; +} wifi_connect_params_t; /** * @brief Create the WiFi connect screen - * @param params Not used + * @param params Optional wifi_connect_params_t* * @return Screen instance */ screen_t* wifi_connect_screen_create(void *params); diff --git a/main/settings.c b/main/settings.c index 6449dfb..42a4713 100644 --- a/main/settings.c +++ b/main/settings.c @@ -17,6 +17,7 @@ static const char *TAG = "SETTINGS"; #define NVS_KEY_RED_TEAM "red_team" #define NVS_KEY_SCR_TIMEOUT "scr_tmout" #define NVS_KEY_SCR_BRIGHT "scr_bright" +#define NVS_KEY_SOUND "sound" #define NVS_KEY_GPS_TYPE "gps_type" // Cached values @@ -25,6 +26,7 @@ static int uart_rx_pin = DEFAULT_UART_RX_PIN; static bool red_team_enabled = false; // Default: disabled static uint32_t screen_timeout_ms = DEFAULT_SCREEN_TIMEOUT_MS; static uint8_t screen_brightness = DEFAULT_SCREEN_BRIGHTNESS; +static bool sound_enabled = DEFAULT_SOUND_ENABLED; static gps_type_t gps_type = GPS_TYPE_ATGM; // Default: ATGM // Reserved GPIO pins that should not be used (ESP32-S3 specific) @@ -124,6 +126,12 @@ esp_err_t settings_init(void) } ESP_LOGI(TAG, "Loaded screen brightness: %d%%", screen_brightness); } + + uint8_t sound_val = 0; + if (nvs_get_u8(handle, NVS_KEY_SOUND, &sound_val) == ESP_OK) { + sound_enabled = (sound_val != 0); + ESP_LOGI(TAG, "Loaded sound enabled: %s", sound_enabled ? "true" : "false"); + } uint8_t gps_val = 0; if (nvs_get_u8(handle, NVS_KEY_GPS_TYPE, &gps_val) == ESP_OK) { @@ -326,6 +334,41 @@ esp_err_t settings_set_screen_brightness(uint8_t brightness) return ESP_OK; } +bool settings_get_sound_enabled(void) +{ + return sound_enabled; +} + +esp_err_t settings_set_sound_enabled(bool enabled) +{ + nvs_handle_t handle; + esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS for writing: %s", esp_err_to_name(ret)); + return ret; + } + + ret = nvs_set_u8(handle, NVS_KEY_SOUND, enabled ? 1 : 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to write sound setting: %s", esp_err_to_name(ret)); + nvs_close(handle); + return ret; + } + + ret = nvs_commit(handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(ret)); + nvs_close(handle); + return ret; + } + + nvs_close(handle); + sound_enabled = enabled; + + ESP_LOGI(TAG, "Sound setting saved: %s", enabled ? "enabled" : "disabled"); + return ESP_OK; +} + gps_type_t settings_get_gps_type(void) { return gps_type; diff --git a/main/settings.h b/main/settings.h index cfb6082..5eb6ecf 100644 --- a/main/settings.h +++ b/main/settings.h @@ -17,6 +17,7 @@ // Default screen settings #define DEFAULT_SCREEN_TIMEOUT_MS 30000 // 30 seconds #define DEFAULT_SCREEN_BRIGHTNESS 100 // 100% +#define DEFAULT_SOUND_ENABLED true // Valid GPIO pin range for ESP32-S3 #define MIN_GPIO_PIN 0 @@ -94,6 +95,19 @@ uint8_t settings_get_screen_brightness(void); */ esp_err_t settings_set_screen_brightness(uint8_t brightness); +/** + * @brief Get sound enabled status + * @return true if UI sounds are enabled + */ +bool settings_get_sound_enabled(void); + +/** + * @brief Set sound enabled status + * @param enabled true to enable UI sounds + * @return ESP_OK on success + */ +esp_err_t settings_set_sound_enabled(bool enabled); + // GPS module types typedef enum { GPS_TYPE_ATGM = 0, diff --git a/main/uart_handler.c b/main/uart_handler.c index 0b767f1..5f07e58 100644 --- a/main/uart_handler.c +++ b/main/uart_handler.c @@ -8,11 +8,13 @@ #include "esp_log.h" #include "esp_system.h" #include "esp_heap_caps.h" +#include "esp_timer.h" #include "driver/uart.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include +#include #include static const char *TAG = "UART"; @@ -34,12 +36,14 @@ static void *scan_callback_user_data = NULL; static wifi_network_t networks[MAX_NETWORKS]; static int network_count = 0; static char scan_status[64] = "Ready"; +static int64_t scan_last_poll_us = 0; // Mutex for thread safety static SemaphoreHandle_t uart_mutex = NULL; // WiFi client connection state static bool wifi_connected = false; +static bool wardrive_active = false; // Board ping detection state static volatile bool pong_received = false; @@ -60,50 +64,64 @@ static void log_memory_info(const char *context) (unsigned long)(esp_get_free_heap_size() / 1024)); } +/** + * @brief Parse quoted CSV fields in-place. + */ +static int parse_quoted_csv_fields(char *line, char **fields, int max_fields) +{ + if (!line || !fields || max_fields <= 0) return 0; + int field_count = 0; + char *p = line; + + while (*p && field_count < max_fields) { + while (*p == ' ' || *p == '\t' || *p == ',') p++; + if (*p != '"') break; + p++; + fields[field_count++] = p; + while (*p) { + if (*p == '"') { + *p = '\0'; + p++; + break; + } + p++; + } + while (*p == ' ' || *p == '\t') p++; + if (*p == ',') p++; + } + return field_count; +} + +static int find_network_by_bssid(const char *bssid) +{ + if (!bssid || !bssid[0]) return -1; + for (int i = 0; i < network_count; i++) { + if (strcasecmp(networks[i].bssid, bssid) == 0) { + return i; + } + } + return -1; +} + /** * @brief Parse a CSV network line - * Format: "1","SSID","","BSSID","channel","security","rssi","band" + * Format: "1","SSID","","BSSID","channel","security","rssi","band","vendor" */ static bool parse_network_line(const char *line, wifi_network_t *network) { - // Check if line starts with a quote (CSV format) - if (line[0] != '"') { + if (!line || !network || line[0] != '"') { return false; } - // Parse using simple state machine char *str = strdup(line); if (!str) return false; - char *fields[8] = {0}; - int field_count = 0; - char *ptr = str; - - while (*ptr && field_count < 8) { - // Skip leading quote - if (*ptr == '"') ptr++; - - // Find start of field - fields[field_count] = ptr; - - // Find end of field (closing quote) - while (*ptr && *ptr != '"') ptr++; - if (*ptr == '"') { - *ptr = '\0'; - ptr++; - } - - // Skip comma - if (*ptr == ',') ptr++; - - field_count++; - } - + char *fields[12] = {0}; + int field_count = parse_quoted_csv_fields(str, fields, 12); if (field_count >= 8) { network->id = atoi(fields[0]); strncpy(network->ssid, fields[1], MAX_SSID_LEN - 1); network->ssid[MAX_SSID_LEN - 1] = '\0'; - // fields[2] is empty strncpy(network->bssid, fields[3], MAX_BSSID_LEN - 1); network->bssid[MAX_BSSID_LEN - 1] = '\0'; network->channel = atoi(fields[4]); @@ -113,13 +131,10 @@ static bool parse_network_line(const char *line, wifi_network_t *network) strncpy(network->band, fields[7], MAX_BAND_LEN - 1); network->band[MAX_BAND_LEN - 1] = '\0'; network->selected = false; - - free(str); - return true; } - + bool ok = (field_count >= 8); free(str); - return false; + return ok; } /** @@ -142,7 +157,7 @@ static void process_line(const char *line) // Handle scan mode if (is_scanning) { // Check for scan completion - if (strstr(line, "Scan results printed.") != NULL) { + if (strstr(line, "Scan results printed") != NULL) { ESP_LOGI(TAG, "Scan complete, found %d networks", network_count); snprintf(scan_status, sizeof(scan_status), "Found %d networks", network_count); is_scanning = false; @@ -159,7 +174,12 @@ static void process_line(const char *line) if (line[0] == '"' && network_count < MAX_NETWORKS) { wifi_network_t network = {0}; if (parse_network_line(line, &network)) { - networks[network_count++] = network; + int existing = find_network_by_bssid(network.bssid); + if (existing >= 0) { + networks[existing] = network; + } else if (network_count < MAX_NETWORKS) { + networks[network_count++] = network; + } snprintf(scan_status, sizeof(scan_status), "Scanning... %d networks", network_count); } } @@ -201,6 +221,14 @@ static void uart_rx_task(void *arg) } } } + + if (is_scanning) { + int64_t now = esp_timer_get_time(); + if (now - scan_last_poll_us >= 1000000) { + scan_last_poll_us = now; + uart_send_command("show_scan_results"); + } + } } } @@ -269,9 +297,9 @@ esp_err_t uart_send_command(const char *cmd) int len = strlen(cmd); int written = uart_write_bytes(UART_PORT_NUM, cmd, len); - // Send newline if not present - if (len > 0 && cmd[len - 1] != '\n') { - uart_write_bytes(UART_PORT_NUM, "\n", 1); + // Commands must end with CRLF. + if (len < 2 || cmd[len - 2] != '\r' || cmd[len - 1] != '\n') { + uart_write_bytes(UART_PORT_NUM, "\r\n", 2); } xSemaphoreGive(uart_mutex); @@ -326,11 +354,13 @@ esp_err_t uart_start_wifi_scan(uart_scan_complete_callback_t callback, void *use is_scanning = true; scan_callback = callback; scan_callback_user_data = user_data; + scan_last_poll_us = esp_timer_get_time(); snprintf(scan_status, sizeof(scan_status), "Starting scan..."); xSemaphoreGive(uart_mutex); // Send scan command + uart_flush_rx(); return uart_send_command("scan_networks"); } @@ -354,6 +384,33 @@ void uart_set_wifi_connected(bool connected) wifi_connected = connected; } +void uart_flush_rx(void) +{ + // Non-blocking drain of UART RX buffer to avoid UI/task stalls. + size_t buffered = 0; + if (uart_get_buffered_data_len(UART_PORT_NUM, &buffered) == ESP_OK && buffered > 0) { + uint8_t tmp[64]; + while (buffered > 0) { + int to_read = (buffered > sizeof(tmp)) ? (int)sizeof(tmp) : (int)buffered; + int rd = uart_read_bytes(UART_PORT_NUM, tmp, to_read, 0); + if (rd <= 0) break; + buffered -= (size_t)rd; + } + } + line_pos = 0; + memset(line_buffer, 0, sizeof(line_buffer)); +} + +bool uart_is_wardrive_active(void) +{ + return wardrive_active; +} + +void uart_set_wardrive_active(bool active) +{ + wardrive_active = active; +} + /** * @brief Callback to detect pong response */ diff --git a/main/uart_handler.h b/main/uart_handler.h index bbf4df6..9084a68 100644 --- a/main/uart_handler.h +++ b/main/uart_handler.h @@ -116,6 +116,23 @@ void uart_set_wifi_connected(bool connected); */ bool uart_check_board_ping(int timeout_ms); +/** + * @brief Flush pending RX data from UART driver + */ +void uart_flush_rx(void); + +/** + * @brief Get wardrive activity flag + * @return true when wardrive is running + */ +bool uart_is_wardrive_active(void); + +/** + * @brief Set wardrive activity flag + * @param active true when wardrive is running + */ +void uart_set_wardrive_active(bool active); + #endif // UART_HANDLER_H diff --git a/main/ui/font8x16.h b/main/ui/font8x16.h index 53448cd..cf780af 100644 --- a/main/ui/font8x16.h +++ b/main/ui/font8x16.h @@ -306,6 +306,105 @@ static const uint8_t font8x16_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +// --------------------------------------------------------------------------- +// Polish diacritical characters (8x16 bitmaps) +// Covers: Ó ó Ą ą Ć ć Ę ę Ł ł Ń ń Ś ś Ź ź Ż ż +// --------------------------------------------------------------------------- +#define FONT_POLISH_COUNT 18 + +static const uint8_t font_polish_data[FONT_POLISH_COUNT * FONT_HEIGHT] = { + // [0] Ó (U+00D3) — O with acute + 0x08, 0x10, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + // [1] ó (U+00F3) — o with acute + 0x00, 0x00, 0x08, 0x10, 0x00, 0x7C, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + // [2] Ą (U+0104) — A with ogonek + 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, + 0xC6, 0xC6, 0xC6, 0xC6, 0x06, 0x0C, 0x00, 0x00, + // [3] ą (U+0105) — a with ogonek + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, + 0xCC, 0xCC, 0xCC, 0x76, 0x06, 0x0C, 0x00, 0x00, + // [4] Ć (U+0106) — C with acute + 0x08, 0x10, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + // [5] ć (U+0107) — c with acute + 0x00, 0x00, 0x08, 0x10, 0x00, 0x7C, 0xC6, 0xC0, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + // [6] Ę (U+0118) — E with ogonek + 0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, + 0x60, 0x62, 0x66, 0xFE, 0x06, 0x0C, 0x00, 0x00, + // [7] ę (U+0119) — e with ogonek + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, + 0xC0, 0xC0, 0xC6, 0x7C, 0x06, 0x0C, 0x00, 0x00, + // [8] Ł (U+0141) — L with stroke + 0x00, 0x00, 0xF0, 0x60, 0x60, 0x70, 0x60, 0xE0, + 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, + // [9] ł (U+0142) — l with stroke + 0x00, 0x00, 0x38, 0x18, 0x18, 0x1C, 0x18, 0x38, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + // [10] Ń (U+0143) — N with acute + 0x08, 0x10, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + // [11] ń (U+0144) — n with acute + 0x00, 0x00, 0x08, 0x10, 0x00, 0xDC, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + // [12] Ś (U+015A) — S with acute + 0x08, 0x10, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, + 0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + // [13] ś (U+015B) — s with acute + 0x00, 0x00, 0x08, 0x10, 0x00, 0x7C, 0xC6, 0x60, + 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + // [14] Ź (U+0179) — Z with acute + 0x08, 0x10, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30, + 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + // [15] ź (U+017A) — z with acute + 0x00, 0x00, 0x08, 0x10, 0x00, 0xFE, 0xCC, 0x18, + 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + // [16] Ż (U+017B) — Z with dot above + 0x18, 0x00, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30, + 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + // [17] ż (U+017C) — z with dot above + 0x00, 0x00, 0x18, 0x00, 0x00, 0xFE, 0xCC, 0x18, + 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, +}; + +typedef struct { + uint16_t codepoint; + uint8_t glyph_index; +} font_polish_entry_t; + +static const font_polish_entry_t font_polish_map[FONT_POLISH_COUNT] = { + { 0x00D3, 0 }, // Ó + { 0x00F3, 1 }, // ó + { 0x0104, 2 }, // Ą + { 0x0105, 3 }, // ą + { 0x0106, 4 }, // Ć + { 0x0107, 5 }, // ć + { 0x0118, 6 }, // Ę + { 0x0119, 7 }, // ę + { 0x0141, 8 }, // Ł + { 0x0142, 9 }, // ł + { 0x0143, 10 }, // Ń + { 0x0144, 11 }, // ń + { 0x015A, 12 }, // Ś + { 0x015B, 13 }, // ś + { 0x0179, 14 }, // Ź + { 0x017A, 15 }, // ź + { 0x017B, 16 }, // Ż + { 0x017C, 17 }, // ż +}; + +static inline const uint8_t* font_get_polish_glyph(uint16_t codepoint) +{ + for (int i = 0; i < FONT_POLISH_COUNT; i++) { + if (font_polish_map[i].codepoint == codepoint) { + return &font_polish_data[font_polish_map[i].glyph_index * FONT_HEIGHT]; + } + } + return NULL; +} + #endif // FONT8X16_H diff --git a/main/ui/text_ui.c b/main/ui/text_ui.c index 9da5807..dec7137 100644 --- a/main/ui/text_ui.c +++ b/main/ui/text_ui.c @@ -72,6 +72,46 @@ void ui_draw_char(int x, int y, char c, uint16_t fg, uint16_t bg) } } +static void ui_draw_glyph(int x, int y, const uint8_t *glyph, uint16_t fg, uint16_t bg) +{ + if (x < 0 || y < 0 || x + FONT_WIDTH > DISPLAY_WIDTH || y + FONT_HEIGHT > DISPLAY_HEIGHT) { + return; + } + + if (bg != UI_COLOR_BG) { + display_fill_rect(x, y, FONT_WIDTH, FONT_HEIGHT, bg); + } else { + display_fill_rect(x, y, FONT_WIDTH, FONT_HEIGHT, UI_COLOR_BG); + } + + for (int row = 0; row < FONT_HEIGHT; row++) { + uint8_t row_data = glyph[row]; + for (int col = 0; col < FONT_WIDTH; col++) { + if (row_data & (0x80 >> col)) { + display_draw_pixel(x + col, y + row, fg); + } + } + } +} + +static int ui_utf8_display_len(const char *text) +{ + int len = 0; + while (*text) { + unsigned char ch = (unsigned char)*text; + if (ch >= 0xC0 && ch <= 0xDF && text[1] && ((unsigned char)text[1] & 0xC0) == 0x80) { + text += 2; + len++; + } else if (ch >= 0x80) { + text++; + } else { + text++; + len++; + } + } + return len; +} + void ui_draw_text(int x, int y, const char *text, uint16_t fg, uint16_t bg) { if (!text) return; @@ -82,11 +122,29 @@ void ui_draw_text(int x, int y, const char *text, uint16_t fg, uint16_t bg) if (*text == '\n') { x = start_x; y += FONT_HEIGHT; + text++; } else { - ui_draw_char(x, y, *text, fg, bg); - x += FONT_WIDTH; + unsigned char ch = (unsigned char)*text; + + if (ch >= 0xC0 && ch <= 0xDF && text[1] && ((unsigned char)text[1] & 0xC0) == 0x80) { + uint16_t codepoint = ((ch & 0x1F) << 6) | ((unsigned char)text[1] & 0x3F); + const uint8_t *glyph = font_get_polish_glyph(codepoint); + if (glyph) { + ui_draw_glyph(x, y, glyph, fg, bg); + } else { + ui_draw_char(x, y, '?', fg, bg); + } + x += FONT_WIDTH; + text += 2; + } else if (ch >= 0x80) { + text++; + continue; + } else { + ui_draw_char(x, y, *text, fg, bg); + x += FONT_WIDTH; + text++; + } } - text++; // Wrap check if (x + FONT_WIDTH > DISPLAY_WIDTH) { @@ -114,7 +172,7 @@ void ui_print_center(int row, const char *text, uint16_t fg) { if (!text) return; - int len = strlen(text); + int len = ui_utf8_display_len(text); int col = (UI_COLS - len) / 2; if (col < 0) col = 0; @@ -203,7 +261,7 @@ void ui_draw_title(const char *title) // Draw title text centered if (title) { - int len = strlen(title); + int len = ui_utf8_display_len(title); int x = (DISPLAY_WIDTH - len * FONT_WIDTH) / 2; ui_draw_text(x, 1, title, UI_COLOR_TITLE, title_bg); } diff --git a/sdkconfig b/sdkconfig index 16b67eb..395ca72 100644 --- a/sdkconfig +++ b/sdkconfig @@ -23,14 +23,16 @@ CONFIG_SOC_AHB_GDMA_SUPPORTED=y # default: CONFIG_SOC_GPTIMER_SUPPORTED=y # default: -CONFIG_SOC_LCDCAM_SUPPORTED=y -# default: CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y # default: CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y # default: CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y # default: +CONFIG_SOC_LCD_I80_SUPPORTED=y +# default: +CONFIG_SOC_LCD_RGB_SUPPORTED=y +# default: CONFIG_SOC_MCPWM_SUPPORTED=y # default: CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y @@ -67,6 +69,8 @@ CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y # default: CONFIG_SOC_RTC_MEM_SUPPORTED=y # default: +CONFIG_SOC_RTC_TIMER_V1_SUPPORTED=y +# default: CONFIG_SOC_PSRAM_DMA_CAPABLE=y # default: CONFIG_SOC_XT_WDT_SUPPORTED=y @@ -221,10 +225,6 @@ CONFIG_SOC_GPIO_PIN_COUNT=49 # default: CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y # default: -CONFIG_SOC_GPIO_FILTER_CLK_SUPPORT_APB=y -# default: -CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y -# default: CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y # default: CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF @@ -239,29 +239,19 @@ CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y # default: CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 # default: -CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y -# default: CONFIG_SOC_I2C_NUM=2 # default: CONFIG_SOC_HP_I2C_NUM=2 # default: -CONFIG_SOC_I2C_FIFO_LEN=32 -# default: -CONFIG_SOC_I2C_CMD_REG_NUM=8 -# default: -CONFIG_SOC_I2C_SUPPORT_SLAVE=y -# default: -CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y -# default: CONFIG_SOC_I2C_SUPPORT_XTAL=y # default: CONFIG_SOC_I2C_SUPPORT_RTC=y # default: CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y # default: -CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +CONFIG_SOC_I2C_SUPPORT_SLAVE=y # default: -CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y # default: CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y # default: @@ -297,26 +287,6 @@ CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=14 # default: CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y # default: -CONFIG_SOC_MCPWM_GROUPS=2 -# default: -CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 -# default: -CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 -# default: -CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 -# default: -CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 -# default: -CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 -# default: -CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 -# default: -CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y -# default: -CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 -# default: -CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 -# default: CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y # default: CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1 @@ -327,60 +297,16 @@ CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 # default: CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 # default: -CONFIG_SOC_RMT_GROUPS=1 -# default: -CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 -# default: -CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 -# default: -CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 -# default: CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 # default: CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y # default: -CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y -# default: -CONFIG_SOC_RMT_SUPPORT_ASYNC_STOP=y -# default: CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y # default: CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y # default: -CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y -# default: -CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y -# default: -CONFIG_SOC_RMT_SUPPORT_XTAL=y -# default: -CONFIG_SOC_RMT_SUPPORT_RC_FAST=y -# default: -CONFIG_SOC_RMT_SUPPORT_APB=y -# default: CONFIG_SOC_RMT_SUPPORT_DMA=y # default: -CONFIG_SOC_LCD_I80_SUPPORTED=y -# default: -CONFIG_SOC_LCD_RGB_SUPPORTED=y -# default: -CONFIG_SOC_LCD_I80_BUSES=1 -# default: -CONFIG_SOC_LCD_RGB_PANELS=1 -# default: -CONFIG_SOC_LCD_I80_BUS_WIDTH=16 -# default: -CONFIG_SOC_LCD_RGB_DATA_WIDTH=16 -# default: -CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y -# default: -CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 -# default: -CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=16 -# default: -CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 -# default: -CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=16 -# default: CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128 # default: CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549 @@ -487,14 +413,6 @@ CONFIG_SOC_TWAI_CONTROLLER_NUM=1 # default: CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 # default: -CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y -# default: -CONFIG_SOC_TWAI_BRP_MIN=2 -# default: -CONFIG_SOC_TWAI_BRP_MAX=16384 -# default: -CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y -# default: CONFIG_SOC_UART_NUM=3 # default: CONFIG_SOC_UART_HP_NUM=3 @@ -503,8 +421,6 @@ CONFIG_SOC_UART_FIFO_LEN=128 # default: CONFIG_SOC_UART_BITRATE_MAX=5000000 # default: -CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y -# default: CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y # default: CONFIG_SOC_UART_SUPPORT_APB_CLK=y @@ -619,6 +535,10 @@ CONFIG_SOC_EFUSE_DIS_ICACHE=y # default: CONFIG_SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK=y # default: +CONFIG_SOC_EFUSE_XTS_AES_KEY_128=y +# default: +CONFIG_SOC_EFUSE_XTS_AES_KEY_256=y +# default: CONFIG_SOC_SECURE_BOOT_V2_RSA=y # default: CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 @@ -693,6 +613,8 @@ CONFIG_SOC_WIFI_GCMP_SUPPORT=y # default: CONFIG_SOC_WIFI_WAPI_SUPPORT=y # default: +CONFIG_SOC_WIFI_TXOP_SUPPORT=y +# default: CONFIG_SOC_WIFI_CSI_SUPPORT=y # default: CONFIG_SOC_WIFI_MESH_SUPPORT=y @@ -717,10 +639,6 @@ CONFIG_SOC_PHY_COMBO_MODULE=y # default: CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y # default: -CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1 -# default: -CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16 -# default: CONFIG_IDF_CMAKE=y # default: CONFIG_IDF_TOOLCHAIN="gcc" @@ -1121,13 +1039,6 @@ CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_NO_CHANGE=y # Component config # -# -# Application Level Tracing -# -# default: -# CONFIG_APPTRACE_ENABLE is not set -# end of Application Level Tracing - # # Bluetooth # @@ -1171,16 +1082,15 @@ CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_NO_CHANGE=y # default: # CONFIG_TWAI_ISR_IN_IRAM_LEGACY is not set # default: -# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +# CONFIG_TWAI_SUPPRESS_DEPRECATE_WARN is not set # default: -CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy TWAI Driver Configurations # # Legacy I2C Driver Configurations # -# default: -# CONFIG_I2C_SUPPRESS_DEPRECATE_WARN is not set +CONFIG_I2C_SUPPRESS_DEPRECATE_WARN=y # default: # CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy I2C Driver Configurations @@ -1212,6 +1122,8 @@ CONFIG_EFUSE_MAX_BLK_LEN=256 # default: CONFIG_ESP_TLS_USING_MBEDTLS=y # default: +# CONFIG_ESP_TLS_CUSTOM_STACK is not set +# default: # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set # default: CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y @@ -1269,6 +1181,19 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y # CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set # end of ESP-Driver:Camera Controller Configurations +# +# GDMA Configurations +# +# default: +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +# default: +CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y +# default: +CONFIG_GDMA_OBJ_DRAM_SAFE=y +# default: +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# end of GDMA Configurations + # # ESP-Driver:GPIO Configurations # @@ -1576,6 +1501,8 @@ CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 # # Hardware Settings # +# default: +CONFIG_ESP_HW_SUPPORT_FUNC_IN_IRAM=y # # Chip revision @@ -1669,6 +1596,10 @@ CONFIG_RTC_CLK_SRC_INT_RC=y # CONFIG_RTC_CLK_SRC_INT_8MD256 is not set # default: CONFIG_RTC_CLK_CAL_CYCLES=1024 +# default: +CONFIG_RTC_CLK_FUNC_IN_IRAM=y +# default: +CONFIG_RTC_TIME_FUNC_IN_IRAM=y # end of RTC Clock Config # @@ -1680,19 +1611,6 @@ CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y # end of Peripheral Control -# -# GDMA Configurations -# -# default: -CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y -# default: -CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y -# default: -CONFIG_GDMA_OBJ_DRAM_SAFE=y -# default: -# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set -# end of GDMA Configurations - # # Main XTAL Config # @@ -1755,6 +1673,8 @@ CONFIG_ESP_INTR_IN_IRAM=y # default: CONFIG_LIBC_NEWLIB=y # default: +# CONFIG_LIBC_PICOLIBC is not set +# default: CONFIG_LIBC_MISC_IN_IRAM=y # default: CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y @@ -1795,6 +1715,8 @@ CONFIG_LIBC_ASSERT_BUFFER_SIZE=200 # ESP NETIF Adapter # # default: +CONFIG_ESP_NETIF_LOST_IP_TIMER_ENABLE=y +# default: CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 # default: # CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set @@ -2115,6 +2037,23 @@ CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y CONFIG_ESP_TIMER_IMPL_SYSTIMER=y # end of ESP Timer (High Resolution Timer) +# +# ESP Trace Configuration +# +# default: +# CONFIG_ESP_TRACE_LIB_EXTERNAL is not set +# default: +CONFIG_ESP_TRACE_LIB_NONE=y +# default: +CONFIG_ESP_TRACE_LIB_NAME="none" +# default: +# CONFIG_ESP_TRACE_TRANSPORT_APPTRACE is not set +# default: +CONFIG_ESP_TRACE_TRANSPORT_NONE=y +# default: +CONFIG_ESP_TRACE_TRANSPORT_NAME="none" +# end of ESP Trace Configuration + # # Wi-Fi # @@ -2921,6 +2860,8 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y # Core Configuration # # default: +CONFIG_MBEDTLS_VER_4_X_SUPPORT=y +# default: CONFIG_MBEDTLS_COMPILER_OPTIMIZATION_NONE=y # default: # CONFIG_MBEDTLS_COMPILER_OPTIMIZATION_SIZE is not set @@ -2996,8 +2937,6 @@ CONFIG_MBEDTLS_ASN1_PARSE_C=y # default: CONFIG_MBEDTLS_ASN1_WRITE_C=y # default: -CONFIG_MBEDTLS_OID_C=y -# default: CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y # @@ -3064,17 +3003,11 @@ CONFIG_MBEDTLS_SSL_ALL_ALERT_MESSAGES=y # default: CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y # default: -CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y -# default: CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y # default: CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y # default: CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y -# default: -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y -# default: -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y # end of TLS Key Exchange Configuration # default: @@ -3097,8 +3030,6 @@ CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y # default: # CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set -# default: -CONFIG_MBEDTLS_CIPHER_C=y # # Symmetric Ciphers @@ -3112,10 +3043,6 @@ CONFIG_MBEDTLS_ARIA_C=y # default: # CONFIG_MBEDTLS_DES_C is not set # default: -# CONFIG_MBEDTLS_BLOWFISH_C is not set -# default: -# CONFIG_MBEDTLS_XTEA_C is not set -# default: CONFIG_MBEDTLS_CCM_C=y # default: CONFIG_MBEDTLS_CIPHER_MODE_CBC=y @@ -3132,16 +3059,6 @@ CONFIG_MBEDTLS_GCM_C=y # default: # CONFIG_MBEDTLS_NIST_KW_C is not set # default: -CONFIG_MBEDTLS_CIPHER_PADDING=y -# default: -CONFIG_MBEDTLS_CIPHER_PADDING_PKCS7=y -# default: -CONFIG_MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS=y -# default: -CONFIG_MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN=y -# default: -CONFIG_MBEDTLS_CIPHER_PADDING_ZEROS=y -# default: CONFIG_MBEDTLS_AES_ROM_TABLES=y # default: # CONFIG_MBEDTLS_AES_FEWER_TABLES is not set @@ -3157,8 +3074,6 @@ CONFIG_MBEDTLS_CMAC_C=y # default: CONFIG_MBEDTLS_BIGNUM_C=y # default: -CONFIG_MBEDTLS_GENPRIME=y -# default: CONFIG_MBEDTLS_RSA_C=y # default: CONFIG_MBEDTLS_ECP_C=y @@ -3167,20 +3082,12 @@ CONFIG_MBEDTLS_ECP_C=y # Supported Curves # # default: -CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y -# default: -CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y -# default: CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y # default: CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y # default: CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y # default: -CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y -# default: -CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y -# default: CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y # default: CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y @@ -3222,10 +3129,6 @@ CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y # Hash functions # # default: -# CONFIG_MBEDTLS_HKDF_C is not set -# default: -# CONFIG_MBEDTLS_POLY1305_C is not set -# default: # CONFIG_MBEDTLS_RIPEMD160_C is not set # default: CONFIG_MBEDTLS_MD_C=y @@ -3263,6 +3166,8 @@ CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 # default: CONFIG_MBEDTLS_HARDWARE_AES=y # default: +# CONFIG_MBEDTLS_AES_SOFT_FALLBACK is not set +# default: CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y # default: CONFIG_MBEDTLS_AES_USE_INTERRUPT=y @@ -3271,19 +3176,17 @@ CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 # default: CONFIG_MBEDTLS_AES_HW_SMALL_DATA_LEN_OPTIM=y # default: -CONFIG_MBEDTLS_PK_RSA_ALT_SUPPORT=y -# default: # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set # default: # CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +# default: +CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL=y # end of Hardware Acceleration # # Entropy and Random Number Generation # # default: -CONFIG_MBEDTLS_ENTROPY_C=y -# default: # CONFIG_MBEDTLS_ENTROPY_FORCE_SHA256 is not set # default: CONFIG_MBEDTLS_CTR_DRBG_C=y @@ -3301,8 +3204,6 @@ CONFIG_MBEDTLS_PKCS5_C=y # default: CONFIG_MBEDTLS_PKCS7_C=y # default: -CONFIG_MBEDTLS_PKCS12_C=y -# default: CONFIG_MBEDTLS_PKCS1_V15=y # default: CONFIG_MBEDTLS_PKCS1_V21=y @@ -3316,33 +3217,6 @@ CONFIG_MBEDTLS_PKCS1_V21=y # end of Stream Cipher # end of mbedTLS -# -# ESP-MQTT Configurations -# -# default: -CONFIG_MQTT_PROTOCOL_311=y -# default: -# CONFIG_MQTT_PROTOCOL_5 is not set -# default: -CONFIG_MQTT_TRANSPORT_SSL=y -# default: -CONFIG_MQTT_TRANSPORT_WEBSOCKET=y -# default: -CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y -# default: -# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set -# default: -# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set -# default: -# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set -# default: -# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set -# default: -# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set -# default: -# CONFIG_MQTT_CUSTOM_OUTBOX is not set -# end of ESP-MQTT Configurations - # # NVS # @@ -3352,6 +3226,8 @@ CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y # CONFIG_NVS_ASSERT_ERROR_CHECK is not set # default: # CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# default: +# CONFIG_NVS_BDL_STACK is not set # end of NVS # @@ -3360,15 +3236,6 @@ CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y # default: # CONFIG_OPENTHREAD_ENABLED is not set -# -# Thread Console -# -# default: -CONFIG_OPENTHREAD_CLI=y -# default: -CONFIG_OPENTHREAD_CONSOLE_COMMAND_PREFIX="ot" -# end of Thread Console - # # OpenThread Spinel # @@ -3485,8 +3352,6 @@ CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y # default: # CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set # default: -CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y -# default: # CONFIG_SPI_FLASH_ROM_IMPL is not set # default: CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y @@ -3728,7 +3593,6 @@ CONFIG_STACK_CHECK_NONE=y # CONFIG_STACK_CHECK_STRONG is not set # CONFIG_STACK_CHECK_ALL is not set # CONFIG_WARN_WRITE_STRINGS is not set -# CONFIG_ESP32_APPTRACE_ENABLE is not set # CONFIG_EXTERNAL_COEX_ENABLE is not set # CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set # CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set @@ -3825,6 +3689,7 @@ CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y CONFIG_ESP32S3_DEBUG_OCDAWARE=y CONFIG_IPC_TASK_STACK_SIZE=1280 CONFIG_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_ESP32_APPTRACE_ENABLE is not set CONFIG_ESP32_WIFI_ENABLED=y CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 1b20d4c..365458c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -25,3 +25,4 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_LOG_DEFAULT_LEVEL_INFO=y # Allow orphan sections to be reported as warnings (fixes .init from toolchain) CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +CONFIG_I2C_SUPPRESS_DEPRECATE_WARN=y