Add live capture visuals: progress bar, blinking active targets, per-network state
- Progress bar under status line shows captured/total WPA targets - Three visual states per network: Green (pending) -> Orange blinking (active target) -> Red + checkmark (captured) - Active targets show live info: client count during observation, EAPOL progress during capture (e.g. "⚡ NetworkName 1/2") - Status line shows channel number and phase context - LVGL symbols: lightning bolt for active, checkmark for captured - Title updated to "⚡ Capture" with WiFi icon Made-with: Cursor
This commit is contained in:
@@ -8,6 +8,8 @@ static lv_obj_t *list_label = nullptr;
|
|||||||
static lv_obj_t *network_container = nullptr;
|
static lv_obj_t *network_container = nullptr;
|
||||||
static lv_obj_t *network_labels[20] = {nullptr};
|
static lv_obj_t *network_labels[20] = {nullptr};
|
||||||
static int network_count = 0;
|
static int network_count = 0;
|
||||||
|
static lv_obj_t *progress_bar = nullptr;
|
||||||
|
static lv_obj_t *progress_label = nullptr;
|
||||||
|
|
||||||
void Ui_SetWifiStatus(const char *text) {
|
void Ui_SetWifiStatus(const char *text) {
|
||||||
if (status_label != nullptr) {
|
if (status_label != nullptr) {
|
||||||
@@ -51,6 +53,32 @@ void Ui_SetNetworkText(int index, const char* text) {
|
|||||||
lv_label_set_text(network_labels[index], text);
|
lv_label_set_text(network_labels[index], text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Ui_SetProgress(int current, int total) {
|
||||||
|
if (progress_bar == nullptr) return;
|
||||||
|
if (total <= 0) {
|
||||||
|
lv_bar_set_value(progress_bar, 0, LV_ANIM_OFF);
|
||||||
|
if (progress_label) lv_label_set_text(progress_label, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int pct = (current * 100) / total;
|
||||||
|
lv_bar_set_value(progress_bar, pct, LV_ANIM_ON);
|
||||||
|
|
||||||
|
char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), "%d/%d", current, total);
|
||||||
|
if (progress_label) lv_label_set_text(progress_label, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ui_ShowProgress(bool visible) {
|
||||||
|
if (progress_bar == nullptr) return;
|
||||||
|
if (visible) {
|
||||||
|
lv_obj_clear_flag(progress_bar, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
if (progress_label) lv_obj_clear_flag(progress_label, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
} else {
|
||||||
|
lv_obj_add_flag(progress_bar, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
if (progress_label) lv_obj_add_flag(progress_label, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Lvgl_Display_LCD(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
|
void Lvgl_Display_LCD(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
|
||||||
LCD_addWindow(area->x1, area->y1, area->x2, area->y2, (uint16_t*)&color_p->full);
|
LCD_addWindow(area->x1, area->y1, area->x2, area->y2, (uint16_t*)&color_p->full);
|
||||||
lv_disp_flush_ready(disp_drv);
|
lv_disp_flush_ready(disp_drv);
|
||||||
@@ -88,21 +116,46 @@ void Lvgl_Init(void) {
|
|||||||
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0);
|
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0);
|
||||||
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
|
||||||
|
|
||||||
|
// Title
|
||||||
lv_obj_t *title = lv_label_create(lv_scr_act());
|
lv_obj_t *title = lv_label_create(lv_scr_act());
|
||||||
lv_obj_set_style_text_color(title, lv_palette_main(LV_PALETTE_GREEN), 0);
|
lv_obj_set_style_text_color(title, lv_palette_main(LV_PALETTE_GREEN), 0);
|
||||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
|
lv_obj_set_style_text_font(title, &lv_font_montserrat_16, 0);
|
||||||
lv_label_set_text(title, "WiFi Networks");
|
lv_label_set_text(title, LV_SYMBOL_WIFI " Capture");
|
||||||
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 10, 8);
|
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 10, 6);
|
||||||
|
|
||||||
|
// Status line
|
||||||
status_label = lv_label_create(lv_scr_act());
|
status_label = lv_label_create(lv_scr_act());
|
||||||
lv_obj_set_style_text_color(status_label, lv_palette_lighten(LV_PALETTE_GREEN, 2), 0);
|
lv_obj_set_style_text_color(status_label, lv_palette_lighten(LV_PALETTE_GREEN, 2), 0);
|
||||||
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_12, 0);
|
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_12, 0);
|
||||||
lv_label_set_text(status_label, "Starting...");
|
lv_label_set_text(status_label, "Starting...");
|
||||||
lv_obj_align(status_label, LV_ALIGN_TOP_LEFT, 10, 30);
|
lv_obj_align(status_label, LV_ALIGN_TOP_LEFT, 10, 28);
|
||||||
|
|
||||||
|
// Progress bar (hidden until capture starts)
|
||||||
|
progress_bar = lv_bar_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(progress_bar, 114, 6);
|
||||||
|
lv_obj_align(progress_bar, LV_ALIGN_TOP_LEFT, 10, 44);
|
||||||
|
lv_bar_set_range(progress_bar, 0, 100);
|
||||||
|
lv_bar_set_value(progress_bar, 0, LV_ANIM_OFF);
|
||||||
|
lv_obj_set_style_bg_color(progress_bar, lv_color_make(25, 25, 25), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_bg_opa(progress_bar, LV_OPA_COVER, LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_bg_color(progress_bar, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
|
||||||
|
lv_obj_set_style_bg_opa(progress_bar, LV_OPA_COVER, LV_PART_INDICATOR);
|
||||||
|
lv_obj_set_style_radius(progress_bar, 2, LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_radius(progress_bar, 2, LV_PART_INDICATOR);
|
||||||
|
lv_obj_add_flag(progress_bar, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
|
// Progress count label (right of bar)
|
||||||
|
progress_label = lv_label_create(lv_scr_act());
|
||||||
|
lv_obj_set_style_text_color(progress_label, lv_palette_main(LV_PALETTE_GREEN), 0);
|
||||||
|
lv_obj_set_style_text_font(progress_label, &lv_font_montserrat_12, 0);
|
||||||
|
lv_label_set_text(progress_label, "");
|
||||||
|
lv_obj_align(progress_label, LV_ALIGN_TOP_LEFT, 130, 41);
|
||||||
|
lv_obj_add_flag(progress_label, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
|
||||||
|
// Network list panel
|
||||||
lv_obj_t *list_panel = lv_obj_create(lv_scr_act());
|
lv_obj_t *list_panel = lv_obj_create(lv_scr_act());
|
||||||
lv_obj_set_size(list_panel, 164, 260);
|
lv_obj_set_size(list_panel, 164, 254);
|
||||||
lv_obj_align(list_panel, LV_ALIGN_TOP_LEFT, 4, 52);
|
lv_obj_align(list_panel, LV_ALIGN_TOP_LEFT, 4, 54);
|
||||||
lv_obj_set_style_bg_color(list_panel, lv_color_black(), 0);
|
lv_obj_set_style_bg_color(list_panel, lv_color_black(), 0);
|
||||||
lv_obj_set_style_bg_opa(list_panel, LV_OPA_TRANSP, 0);
|
lv_obj_set_style_bg_opa(list_panel, LV_OPA_TRANSP, 0);
|
||||||
lv_obj_set_style_border_width(list_panel, 1, 0);
|
lv_obj_set_style_border_width(list_panel, 1, 0);
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ void Ui_ClearNetworkList(void);
|
|||||||
void Ui_AddNetwork(const char* text, int index);
|
void Ui_AddNetwork(const char* text, int index);
|
||||||
void Ui_SetNetworkColor(int index, lv_color_t color);
|
void Ui_SetNetworkColor(int index, lv_color_t color);
|
||||||
void Ui_SetNetworkText(int index, const char* text);
|
void Ui_SetNetworkText(int index, const char* text);
|
||||||
|
void Ui_SetProgress(int current, int total);
|
||||||
|
void Ui_ShowProgress(bool visible);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* WiFi Handshake Capture - ESP32-C6 1.47" LCD
|
* WiFi Handshake Capture - ESP32-C6 1.47" LCD
|
||||||
* Multi-target capture: up to 3 networks per channel, deauth all, capture all.
|
* Multi-target capture: up to 3 networks per channel.
|
||||||
* Display: green = not captured, red = captured.
|
* Green = pending, orange blink = active target, red = captured.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Display_ST7789.h"
|
#include "Display_ST7789.h"
|
||||||
@@ -20,6 +20,90 @@ CaptureState captureState = STATE_SCANNING;
|
|||||||
unsigned long capture_start_time = 0;
|
unsigned long capture_start_time = 0;
|
||||||
const unsigned long CHANNEL_TIMEOUT_MS = 30000;
|
const unsigned long CHANNEL_TIMEOUT_MS = 30000;
|
||||||
|
|
||||||
|
// ─── Live capture visual update ───
|
||||||
|
|
||||||
|
void updateCaptureVisuals() {
|
||||||
|
static unsigned long last_vis = 0;
|
||||||
|
static bool blink = false;
|
||||||
|
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - last_vis < 400) return;
|
||||||
|
last_vis = now;
|
||||||
|
blink = !blink;
|
||||||
|
|
||||||
|
// Overall progress: captured / total WPA targets
|
||||||
|
int captured = 0, total = 0;
|
||||||
|
for (int i = 0; i < MAX_NETWORKS; i++) {
|
||||||
|
if (networks[i].ssid.isEmpty()) break;
|
||||||
|
if (!isCaptureable(i)) continue;
|
||||||
|
total++;
|
||||||
|
if (networks[i].handshake_captured) captured++;
|
||||||
|
}
|
||||||
|
Ui_SetProgress(captured, total);
|
||||||
|
|
||||||
|
// Per-network visual state
|
||||||
|
for (int i = 0; i < MAX_NETWORKS; i++) {
|
||||||
|
if (networks[i].ssid.isEmpty()) break;
|
||||||
|
|
||||||
|
// Is this network in an active capture slot?
|
||||||
|
int si = -1;
|
||||||
|
for (int s = 0; s < MAX_SLOTS; s++) {
|
||||||
|
if (slots[s].active && slots[s].network_index == i) {
|
||||||
|
si = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[48];
|
||||||
|
|
||||||
|
if (networks[i].handshake_captured) {
|
||||||
|
// ✓ captured — red, static
|
||||||
|
snprintf(buf, sizeof(buf), LV_SYMBOL_OK " %s", networks[i].ssid.c_str());
|
||||||
|
Ui_SetNetworkText(i, buf);
|
||||||
|
Ui_SetNetworkColor(i, lv_palette_main(LV_PALETTE_RED));
|
||||||
|
}
|
||||||
|
else if (si >= 0) {
|
||||||
|
// ⚡ active target — orange, blinking
|
||||||
|
CaptureSlot* slot = &slots[si];
|
||||||
|
if (capturePhase == PHASE_OBSERVING) {
|
||||||
|
snprintf(buf, sizeof(buf), LV_SYMBOL_CHARGE " %s +%dc",
|
||||||
|
slot->ssid, slot->client_count);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), LV_SYMBOL_CHARGE " %s %d/2",
|
||||||
|
slot->ssid, slot->eapol_count);
|
||||||
|
}
|
||||||
|
Ui_SetNetworkText(i, buf);
|
||||||
|
|
||||||
|
lv_color_t color = blink ?
|
||||||
|
lv_palette_main(LV_PALETTE_ORANGE) :
|
||||||
|
lv_palette_darken(LV_PALETTE_ORANGE, 2);
|
||||||
|
Ui_SetNetworkColor(i, color);
|
||||||
|
}
|
||||||
|
// else: pending — stays green from scan, no update needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status line reflects current phase
|
||||||
|
static CapturePhase last_phase = PHASE_CAPTURING;
|
||||||
|
if (capturePhase != last_phase) {
|
||||||
|
if (capturePhase == PHASE_OBSERVING)
|
||||||
|
Ui_SetWifiStatus("Mapping clients...");
|
||||||
|
else
|
||||||
|
Ui_SetWifiStatus("Capturing...");
|
||||||
|
last_phase = capturePhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int last_eapol = -1;
|
||||||
|
if (capturePhase == PHASE_CAPTURING && latest_eapol_count != last_eapol) {
|
||||||
|
char ebuf[32];
|
||||||
|
snprintf(ebuf, sizeof(ebuf), "Ch %d " LV_SYMBOL_CHARGE " EAPOL %d",
|
||||||
|
current_channel, latest_eapol_count);
|
||||||
|
Ui_SetWifiStatus(ebuf);
|
||||||
|
last_eapol = latest_eapol_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Setup ───
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
LCD_Init();
|
LCD_Init();
|
||||||
@@ -37,6 +121,8 @@ void setup() {
|
|||||||
captureState = STATE_SCANNING;
|
captureState = STATE_SCANNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Main loop ───
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
Timer_Loop();
|
Timer_Loop();
|
||||||
handshakeCaptureProcessPending();
|
handshakeCaptureProcessPending();
|
||||||
@@ -44,6 +130,7 @@ void loop() {
|
|||||||
|
|
||||||
switch (captureState) {
|
switch (captureState) {
|
||||||
case STATE_SCANNING: {
|
case STATE_SCANNING: {
|
||||||
|
Ui_ShowProgress(false);
|
||||||
if (getCaptureableCount() == 0) {
|
if (getCaptureableCount() == 0) {
|
||||||
Ui_SetWifiStatus("Rescan");
|
Ui_SetWifiStatus("Rescan");
|
||||||
WiFiScanner_Refresh();
|
WiFiScanner_Refresh();
|
||||||
@@ -54,7 +141,8 @@ void loop() {
|
|||||||
if (is_capturing) {
|
if (is_capturing) {
|
||||||
captureState = STATE_CAPTURING;
|
captureState = STATE_CAPTURING;
|
||||||
capture_start_time = millis();
|
capture_start_time = millis();
|
||||||
Ui_SetWifiStatus("Mapping clients");
|
Ui_ShowProgress(true);
|
||||||
|
Ui_SetWifiStatus("Mapping clients...");
|
||||||
} else {
|
} else {
|
||||||
Ui_SetWifiStatus("No targets");
|
Ui_SetWifiStatus("No targets");
|
||||||
delay(1000);
|
delay(1000);
|
||||||
@@ -63,6 +151,8 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case STATE_CAPTURING: {
|
case STATE_CAPTURING: {
|
||||||
|
updateCaptureVisuals();
|
||||||
|
|
||||||
bool any_active = false;
|
bool any_active = false;
|
||||||
for (int i = 0; i < MAX_SLOTS; i++) {
|
for (int i = 0; i < MAX_SLOTS; i++) {
|
||||||
if (slots[i].active) {
|
if (slots[i].active) {
|
||||||
@@ -71,23 +161,8 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase-aware status display
|
|
||||||
static CapturePhase last_shown_phase = PHASE_CAPTURING;
|
|
||||||
if (capturePhase != last_shown_phase) {
|
|
||||||
Ui_SetWifiStatus(capturePhase == PHASE_OBSERVING ? "Mapping clients" : "Capturing...");
|
|
||||||
last_shown_phase = capturePhase;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int last_eapol = -1;
|
|
||||||
if (capturePhase == PHASE_CAPTURING && latest_eapol_count != last_eapol) {
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), "EAPOL %d/2", latest_eapol_count);
|
|
||||||
Ui_SetWifiStatus(buf);
|
|
||||||
last_eapol = latest_eapol_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!any_active && getCaptureableCount() == 0) {
|
if (!any_active && getCaptureableCount() == 0) {
|
||||||
Ui_SetWifiStatus("Done");
|
Ui_SetWifiStatus(LV_SYMBOL_OK " All done");
|
||||||
stopCapture();
|
stopCapture();
|
||||||
captureState = STATE_SCANNING;
|
captureState = STATE_SCANNING;
|
||||||
delay(2000);
|
delay(2000);
|
||||||
|
|||||||
Reference in New Issue
Block a user