/** * V2X-SD Driver - SD-Karte via SDMMC (ESP32-C5 native) + CSV-Ausgabe * * T-Dongle-C5 SD-Karte: * SD_CMD = GPIO 2 * SD_CLK = GPIO 6 * SD_D0 = GPIO 7 * SD_CS = GPIO 23 * * Format v2x_frames.csv: * timestamp_us,msg_type,channel,frequency_mhz,rssi_dbm,noise_dbm,rate_index,frame_len,mac_addr,bssid,raw_hex * * Format v2x_log.txt: * Start-Info, periodische Stats, Fehler, CSV-Rotation * * Rotation: CSV-Dateien > 50MB werden in v2x_frames_YYYYMMDD_HHMMSS.csv umbenannt */ #include "esp_log.h" #include "v2x_db.h" #include "esp_vfs_fat.h" #include "sdmmc_cmd.h" #include "driver/sdmmc_host.h" #include #include #include #include static const char *TAG = "v2x-sd"; /* Globale Variablen */ static v2x_stats_t s_stats; static FILE *s_log_fp = NULL; static bool s_initialized = false; /* ====== SD-Karte initialisieren (ESP-IDF v5.x SDMMC, 1-Bit) ====== */ esp_err_t v2x_sd_init(void) { esp_err_t ret; sdmmc_card_t *card = NULL; FILE *fp; ESP_LOGI(TAG, "Initialisiere SD-Karte via SDMMC (1-Bit)..."); /* SDMMC Host */ sdmmc_host_t host = SDMMC_HOST_DEFAULT(); host.flags = SDMMC_HOST_FLAG_1BIT; host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; /* Slot konfigurieren (ESP32-C5 native SDMMC) */ sdmmc_slot_config_t slot = SDMMC_SLOT_DEFAULT_CONFIG(); slot.width = 1; slot.clk = GPIO_NUM_6; slot.cmd = GPIO_NUM_2; slot.d0 = GPIO_NUM_7; slot.cd = -1; /* Card detect nicht verwendet */ slot.wr = -1; /* Write protect nicht verwendet */ /* FATFS mounten */ esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 4, .allocation_unit_size = 16 * 1024, /* 16 KB Blöcke */ }; ret = esp_vfs_fat_sdmmc_mount(SD_CARD_MOUNT_POINT, &host, &slot, &mount_config, &card); if (ret != ESP_OK) { ESP_LOGE(TAG, "SD mount fehlgeschlagen: %s", esp_err_to_name(ret)); return ret; } ESP_LOGI(TAG, "SD-Karte erkannt: %lu MB (%s)", (unsigned long)(card->card_size / (1024 * 1024)), card->type == SDMMC_TYPE_SDHC ? "SDHC" : "SD"); /* Schreibtest */ fp = fopen(SD_CARD_MOUNT_POINT "/test.txt", "w"); if (fp) { fprintf(fp, "OK\n"); fclose(fp); remove(SD_CARD_MOUNT_POINT "/test.txt"); ESP_LOGI(TAG, "SD Schreibtest OK"); } else { ESP_LOGW(TAG, "SD Schreibtest fehlgeschlagen"); } /* Log-Datei erstellen */ s_log_fp = fopen(SD_LOG_FILE, "w"); if (s_log_fp) { time_t now = time(NULL); char tbuf[64]; struct tm *tm_info = localtime(&now); strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", tm_info); fprintf(s_log_fp, "= V2X-Sniffer Log =\n"); fprintf(s_log_fp, "Start: %s\n", tbuf); fprintf(s_log_fp, "SD-Karte: %lu MB\n", (unsigned long)(card->card_size / (1024 * 1024))); fprintf(s_log_fp, "CSV: %s\n", SD_CSV_FILE); fprintf(s_log_fp, "==============\n\n"); fflush(s_log_fp); } s_initialized = true; s_stats.start_timestamp_us = esp_timer_get_time(); ESP_LOGI(TAG, "SD-Karte eingebunden: %s", SD_CARD_MOUNT_POINT); return ESP_OK; } /* ====== MAC-Adresse als Hex-String ====== */ static void mac_to_str(const uint8_t *mac, char *out) { snprintf(out, 18, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } /* ====== Rohdaten als Hex-String ====== */ static void raw_to_hex(const uint8_t *raw, uint16_t len, char *out, size_t out_len) { size_t pos = 0; for (uint16_t i = 0; i < len && pos < out_len - 2; i++) { int n = snprintf(out + pos, out_len - pos, "%02X", raw[i]); if (n < 0 || (size_t)n >= out_len - pos) break; pos += (size_t)n; } out[pos] = '\0'; } /* ====== Frame in CSV-Zeile konvertieren ====== */ void frame_to_csv_line(const v2x_frame_t *frame, char *out, size_t out_len) { char mac_hex[18], bssid_hex[18], raw_hex[RAW_HEX_BUF]; mac_to_str(frame->mac_addr, mac_hex); mac_to_str(frame->bssid, bssid_hex); raw_to_hex(frame->raw_data, frame->raw_len, raw_hex, sizeof(raw_hex)); snprintf(out, out_len, "%" PRIu64 ",%d,%d,%.1f,%d,%d,%d,%d,%s,%s,%s", frame->timestamp_us, frame->msg_type, frame->channel, frame->frequency_mhz, frame->rssi_dbm, frame->noise_dbm, frame->rate_index, frame->frame_len, mac_hex, bssid_hex, raw_hex); } /* ====== Frame auf SD schreiben ====== */ esp_err_t v2x_sd_write_frame(const v2x_frame_t *frame) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } /* Stats updaten */ s_stats.total_frames++; switch (frame->msg_type) { case V2X_MSG_BSM: s_stats.bsm_count++; break; case V2X_MSG_CAM: s_stats.cam_count++; break; case V2X_MSG_DENM: s_stats.denm_count++; break; default: s_stats.unknown_count++; break; } /* CSV-Zeile */ char line[RAW_HEX_BUF + 128]; frame_to_csv_line(frame, line, sizeof(line)); /* An CSV anhängen */ FILE *fp = fopen(SD_CSV_FILE, "a+"); if (!fp) { s_stats.sd_write_errors++; ESP_LOGE(TAG, "CSV-Datei kann nicht geöffnet werden"); return ESP_FAIL; } fprintf(fp, "%s\n", line); fclose(fp); return ESP_OK; } /* ====== Log schreiben ====== */ esp_err_t v2x_sd_log_write(const char *fmt, ...) { if (!s_initialized || !s_log_fp) { return ESP_ERR_INVALID_STATE; } va_list args; va_start(args, fmt); vfprintf(s_log_fp, fmt, args); va_end(args); fprintf(s_log_fp, "\n"); fflush(s_log_fp); return ESP_OK; } /* ====== Statistik ====== */ const v2x_stats_t* v2x_sd_get_stats(void) { return &s_stats; } void v2x_sd_reset_stats(void) { memset(&s_stats, 0, sizeof(s_stats)); s_stats.start_timestamp_us = esp_timer_get_time(); } esp_err_t v2x_sd_dump_stats(void) { if (!s_initialized) return ESP_ERR_INVALID_STATE; const v2x_stats_t *stats = v2x_sd_get_stats(); time_t now = time(NULL); struct tm *tm_info = localtime(&now); char tbuf[64]; strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", tm_info); uint64_t uptime_s = (esp_timer_get_time() - s_stats.start_timestamp_us) / 1000000; v2x_sd_log_write("--- Statistik ---"); v2x_sd_log_write("Zeit: %s", tbuf); v2x_sd_log_write("Uptime: %lu s", (unsigned long)uptime_s); v2x_sd_log_write("Frames: %lu Gesamt", (unsigned long)stats->total_frames); v2x_sd_log_write(" BSM: %lu | CAM: %lu | DENM: %lu | Sonst: %lu", (unsigned long)stats->bsm_count, (unsigned long)stats->cam_count, (unsigned long)stats->denm_count, (unsigned long)stats->unknown_count); v2x_sd_log_write("SD-Fehler: %lu", (unsigned long)stats->sd_write_errors); FILE *f = fopen(SD_CSV_FILE, "r"); if (f) { fseek(f, 0, SEEK_END); long fsize = ftell(f); fclose(f); v2x_sd_log_write("CSV: %lu KB", (unsigned long)(fsize / 1024)); /* Rotation bei > 50 MB */ if (fsize > 50 * 1024 * 1024) { char rotated[128]; strftime(tbuf, sizeof(tbuf), "%Y%m%d_%H%M%S", tm_info); snprintf(rotated, sizeof(rotated), "/sdcard/v2x_frames_%s.csv", tbuf); ESP_LOGW(TAG, "CSV rotieren: %s -> %s", SD_CSV_FILE, rotated); rename(SD_CSV_FILE, rotated); f = fopen(SD_CSV_FILE, "w"); if (f) { fprintf(f, "%s\n", CSV_HEADER); fclose(f); } v2x_sd_log_write("Neue CSV: %s", SD_CSV_FILE); } } v2x_sd_log_write("--- Ende ---\n"); return ESP_OK; }