Files
v2x-sniffer/v2x_fw/components/v2x_driver/v2x_driver.c
T

318 lines
9.3 KiB
C

/**
* V2X-SD Driver - SD-Karte via SDSPI (ESP32-C5 kompatibel) + CSV-Ausgabe
*
* T-Dongle-C5 SD-Karte ber SPI (SDSPI):
* SD_CLK = GPIO 4
* SD_MOSI = GPIO 5
* SD_MISO = GPIO 0
* SD_CS = GPIO 1
*
* 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 "esp_timer.h"
#include <time.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "driver/spi_master.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <inttypes.h>
#include "v2x_display.h" /* Typ-Definitonen: v2x_frame_t, v2x_stats_t, v2x_msg_type_t, Konstanten */
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;
static sdmmc_card_t *s_sd_card = NULL;
/* ====== SD-Karte initialisieren (SDSPI-Modus fr ESP32-C5) ====== */
esp_err_t v2x_sd_init(void)
{
esp_err_t ret;
sdmmc_card_t *card = NULL;
ESP_LOGI(TAG, "Initialisiere SD-Karte ber SDSPI (ESP32-C5)...");
/* SPI-Bus konfigurieren */
spi_bus_config_t bus_cfg = {
.mosi_io_num = 5, // SD_MOSI
.miso_io_num = 0, // SD_MISO
.sclk_io_num = 4, // SD_CLK
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI-Bus initialisieren fehlgeschlagen: %s", esp_err_to_name(ret));
return ret;
}
/* SDSPI-Gert konfigurieren */
sdspi_device_config_t device_cfg = {
.host_id = SPI2_HOST,
.gpio_cs = GPIO_NUM_1, // SD_CS
.gpio_cd = GPIO_NUM_NC, // SD Card Detect
.gpio_wp = GPIO_NUM_NC, // SD Write Protect
.gpio_int = GPIO_NUM_NC,
.gpio_wp_polarity = false,
.duty_cycle_pos = 0,
};
/* SDSPI-Host konfigurieren */
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = SPI2_HOST;
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
/* FATFS-Mount-Konfiguration (korrekter Typ: esp_vfs_fat_mount_config_t) */
esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 4,
.allocation_unit_size = 16 * 1024,
};
/* FATFS mounten (SDSPI-Variante) */
ret = esp_vfs_fat_sdspi_mount("/sdcard", &host, &device_cfg, &mount_config, &card);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SD-FATFS mount fehlgeschlagen: %s", esp_err_to_name(ret));
return ret;
}
s_sd_card = card;
/* SD-Karten-Info ausgeben */
ESP_LOGI(TAG, "SD-Karte erkannt: %lu MB",
(unsigned long)(card->csd.capacity * card->csd.sector_size / (1024 * 1024)));
/* Schreibtest */
FILE *fp = fopen("/sdcard/test.txt", "w");
if (fp) {
fprintf(fp, "OK\n");
fclose(fp);
remove("/sdcard/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: SDSPI-Modus (/sdcard)\n");
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: /sdcard");
return ESP_OK;
}
/* ====== SD-Karte aushngen ====== */
void v2x_sd_deinit(void)
{
if (!s_initialized) return;
ESP_LOGI(TAG, "SD-Karte aushngen...");
if (s_log_fp) {
fprintf(s_log_fp, "\n===== Shutdown =====\n");
fflush(s_log_fp);
fclose(s_log_fp);
s_log_fp = NULL;
}
/* SD-Karte unmounten */
if (s_sd_card) {
esp_vfs_fat_sdcard_unmount("/sdcard", s_sd_card);
s_sd_card = NULL;
}
s_initialized = false;
ESP_LOGI(TAG, "SD-Karte entfernt");
}
/* ====== 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 anhngen */
FILE *fp = fopen("/sdcard/v2x_frames.csv", "a+");
if (!fp) {
s_stats.sd_write_errors++;
ESP_LOGE(TAG, "CSV-Datei kann nicht geffnet 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; }
esp_err_t v2x_sd_reset_stats(void)
{
memset(&s_stats, 0, sizeof(s_stats));
s_stats.start_timestamp_us = esp_timer_get_time();
return ESP_OK;
}
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("/sdcard/v2x_frames.csv", "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: v2x_frames.csv -> %s", rotated);
rename("/sdcard/v2x_frames.csv", rotated);
f = fopen("/sdcard/v2x_frames.csv", "w");
if (f) {
fprintf(f, "timestamp_us,msg_type,channel,frequency_mhz,rssi_dbm,noise_dbm,rate_index,frame_len,mac_addr,bssid,raw_hex\n");
fclose(f);
}
v2x_sd_log_write("Neue CSV: /sdcard/v2x_frames.csv");
}
}
v2x_sd_log_write("--- Ende ---\n");
return ESP_OK;
}