/** * Frame-Scroller — rollende Liste der letzten Frames * * Zeigt bis zu 7 Frames an, jeder Frame = 1 Zeile (12px hoch): * [RSSI-Balk] X #####-#####-##### CH:161 RSSI:-45 * * Der Scroller aktualisiert sich nur wenn sich ein Frame ändert. */ #include "esp_log.h" #include "v2x_display.h" #include "esp_lcd_panel_ops.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" extern esp_lcd_panel_handle_t s_panel; extern bool s_initialized; static const char *TAG = "v2x-scroller"; /* Scroller-Konfiguration */ #define SCROLLER_FRAMES_VISIBLE 7 /* Frames sichtbar */ #define SCROLLER_LINE_HEIGHT 13 /* Pixel pro Zeile */ #define SCROLLER_START_Y 28 /* Erste Zeile beginnt bei Y=28 */ #define SCROLLER_MAX_ENTRIES 64 /* Frames im Buffer */ /* Frame-Daten für den Scroller */ typedef struct { char msg_char; /* 'B', 'C', 'D', '?' */ int8_t rssi; /* RSSI dBm */ uint8_t ch; /* Kanal */ char bssid_hex[9]; /* #####-#####-##### */ } scroller_entry_t; static scroller_entry_t s_entries[SCROLLER_MAX_ENTRIES]; static uint32_t s_entry_count = 0; /* RSSI-Balk als ASCII (20 Zeichen breit) */ static void rssi_to_bar(int8_t rssi, char *bar, int len) { if (rssi <= -100) rssi = -100; if (rssi >= -20) rssi = -20; float pct = (float)(rssi + 100) / 80.0f; int filled = (int)(pct * 20); for (int i = 0; i < len - 1; i++) { bar[i] = (i < filled) ? '\x5C' : '\xC1'; /* ▌ und ░ (block chars) */ } bar[len - 1] = '\0'; } /* BSSID als #####-#####-##### hex */ static void bssid_to_str(const uint8_t *bssid, char *str) { snprintf(str, 14, "%02X-%02X-%02X-%02X-%02X-%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); } /* Scroller-Eintrag hinzufügen */ void scroller_add_entry(uint8_t msg_type, int8_t rssi, uint8_t ch, const uint8_t *bssid) { scroller_entry_t *e = &s_entries[s_entry_count % SCROLLER_MAX_ENTRIES]; /* Msg-Type Character */ switch (msg_type) { case 1: e->msg_char = 'B'; break; /* BSM */ case 2: e->msg_char = 'C'; break; /* CAM */ case 3: e->msg_char = 'D'; break; /* DENM */ default: e->msg_char = '?'; break; } e->rssi = rssi; e->ch = ch; if (bssid) { bssid_to_str(bssid, e->bssid_hex); } else { snprintf(e->bssid_hex, 14, "----------"); } s_entry_count++; } /* Scroller neu zeichnen (vollständig) */ void scroller_redraw(void) { if (!s_initialized) return; int32_t total = s_entry_count; int start = (total > SCROLLER_FRAMES_VISIBLE) ? (total - SCROLLER_FRAMES_VISIBLE) : 0; /* Hintergrund zeichnen */ int total_h = SCROLLER_FRAMES_VISIBLE * SCROLLER_LINE_HEIGHT; v2x_display_rect(0, SCROLLER_START_Y, DISP_W, total_h, DISP_COLOR_BLACK, true); /* Zeilenweise rendern (von oben nach unten = neueste Frames zuerst) */ for (int i = SCROLLER_FRAMES_VISIBLE - 1; i >= 0; i--) { int idx = (start + i) % SCROLLER_MAX_ENTRIES; scroller_entry_t *e = &s_entries[idx]; int y = SCROLLER_START_Y + (SCROLLER_FRAMES_VISIBLE - 1 - i) * SCROLLER_LINE_HEIGHT; /* RSSI-Farbbalk links (5px breit, 11px hoch) */ uint16_t bar_color = DISP_COLOR_RED; float pct = (float)(e->rssi + 100) / 80.0f; if (pct > 0.6f) bar_color = DISP_COLOR_GREEN; else if (pct > 0.3f) bar_color = DISP_COLOR_YELLOW; v2x_display_rect(2, y + 1, 5, 11, bar_color, true); /* Msg-Type Zeichen (grün/cyan/rot/gray) */ uint16_t msg_color = DISP_COLOR_GRAY; if (e->msg_char == 'B') msg_color = DISP_COLOR_GREEN; else if (e->msg_char == 'C') msg_color = DISP_COLOR_CYAN; else if (e->msg_char == 'D') msg_color = DISP_COLOR_RED; char msg_line[2] = { e->msg_char, '\0' }; v2x_display_text(9, y + 1, msg_line, msg_color, FONT_6x8); /* BSSID als Hex (blau) */ v2x_display_text(16, y + 1, e->bssid_hex, DISP_COLOR_BLUE, FONT_6x8); /* Kanal + RSSI (weiß) */ char ch_buf[20]; snprintf(ch_buf, sizeof(ch_buf), "CH:%d %3d", e->ch, e->rssi); v2x_display_text(60, y + 1, ch_buf, DISP_COLOR_WHITE, FONT_6x8); } } /* Geänderte Zeile neu zeichnen (nur die betroffene Zeile — schnell) */ void scroller_redraw_line(uint32_t entry_idx) { if (!s_initialized) return; int32_t total = s_entry_count; if (entry_idx < (total > SCROLLER_FRAMES_VISIBLE ? total - SCROLLER_FRAMES_VISIBLE : 0)) { return; /* Zu alt, nicht sichtbar */ } int start = (total > SCROLLER_FRAMES_VISIBLE) ? (total - SCROLLER_FRAMES_VISIBLE) : 0; int line_idx = (int)(entry_idx - start); if (line_idx < 0 || line_idx >= SCROLLER_FRAMES_VISIBLE) return; int idx = entry_idx % SCROLLER_MAX_ENTRIES; scroller_entry_t *e = &s_entries[idx]; int y = SCROLLER_START_Y + (SCROLLER_FRAMES_VISIBLE - 1 - line_idx) * SCROLLER_LINE_HEIGHT; /* Zeile löschen */ v2x_display_rect(0, y, DISP_W, SCROLLER_LINE_HEIGHT, DISP_COLOR_BLACK, true); /* Neu zeichnen */ uint16_t bar_color = DISP_COLOR_RED; float pct = (float)(e->rssi + 100) / 80.0f; if (pct > 0.6f) bar_color = DISP_COLOR_GREEN; else if (pct > 0.3f) bar_color = DISP_COLOR_YELLOW; v2x_display_rect(2, y + 1, 5, 11, bar_color, true); uint16_t msg_color = DISP_COLOR_GRAY; if (e->msg_char == 'B') msg_color = DISP_COLOR_GREEN; else if (e->msg_char == 'C') msg_color = DISP_COLOR_CYAN; else if (e->msg_char == 'D') msg_color = DISP_COLOR_RED; char msg_line[2] = { e->msg_char, '\0' }; v2x_display_text(9, y + 1, msg_line, msg_color, FONT_6x8); v2x_display_text(16, y + 1, e->bssid_hex, DISP_COLOR_BLUE, FONT_6x8); char ch_buf[20]; snprintf(ch_buf, sizeof(ch_buf), "CH:%d %3d", e->ch, e->rssi); v2x_display_text(60, y + 1, ch_buf, DISP_COLOR_WHITE, FONT_6x8); }