874dd969b6
- NVS-Flash (4MB) durch SD-Karte via SDMMC ersetzt - GPIO: SD_CMD=2, SD_CLK=6, SD_D0=7, SD_CS=23 - CSV-Ausgabe mit Rohdaten-Hex: timestamp, msg_type, channel, RSSI, MAC, raw_hex - Channel-Hopping über alle 10 V2X-Kanäle (5845-5925 MHz) - CSV-Auto-Rotation bei >50MB - v2x_log.txt mit periodischen Statistiken - Frame Queue (64 Frames) zwischen Promisc-CB und Writer Task - Kanal-Hopper, Stats-Task, Frame-Writer als eigene Tasks - README mit SD-Vorbereitung und Analyse-Hinweisen
267 lines
7.8 KiB
C
267 lines
7.8 KiB
C
/**
|
|
* 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 <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
|
|
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;
|
|
}
|