SD-Karte: V2X-Frames direkt auf SD speichern (CSV, autonom)

- 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
This commit is contained in:
Clawdia
2026-05-09 08:44:24 +02:00
parent fdb4cca5d8
commit 874dd969b6
6 changed files with 755 additions and 146 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "../include"
REQUIRES esp_wifi nvs_flash esp_codec_dev esp_system
REQUIRES esp_wifi nvs_flash esp_timer driver sdmmc esp_vfs_fat spiffs
)
+284 -110
View File
@@ -1,154 +1,328 @@
/**
* V2X-Sniffer Firmware für ESP32-C5 (Lilygo T-Dongle-C5)
* V2X-Sniffer Firmware für ESP32-C5 (T-Dongle-C5)
*
* Konfiguration:
* - phy_11p_set(1, 0) - Aktiviert 802.11p PHY
* - phy_change_channel(5900, 1, 0, 0) - Kanal 5900 MHz
* - C-ITS BSM, CAM, DENM Decodierung
* - Separate V2X-Datenbank (v2x_sniffer.db)
* VOLLSTÄNDIG UNABHÄNGIG - Speichert V2X-Daten auf SD-Karte
* KEIN Host-PC erforderlich
*
* Funktion:
* - Sniffed 802.11p V2X-Frames (BSM, CAM, DENM) auf 5.9 GHz Band
* - Speichert jedes Frame als CSV auf SD-Karte
* - Channel-Hopping über alle V2X-Kanäle (157-164 + 17/18)
* - Periodische Statistik ins Log
* - CSV-Auto-Rotation bei >50MB
*
* SD-Karte:
* Muss FAT32 formatiert sein (exFAT wird NICHT unterstützt!)
* Mindestens 4GB empfohlen, besser 16GB+
* GPIO: CLK=6, MOSI=7, CMD=2, CS=23
*
* CSV-Format (v2x_frames.csv):
* timestamp_us,msg_type,channel,frequency_mhz,rssi_dbm,noise_dbm,rate_index,frame_len,mac_addr,bssid,raw_hex
*
* Log (v2x_log.txt):
* Start-Info, periodische Stats, Fehler, CSV-Rotation
*
* Pinbelegung T-Dongle-C5:
* SD_CLK = GPIO 6
* SD_MOSI = GPIO 7 (D0)
* SD_CMD = GPIO 2 (D2)
* SD_CS = GPIO 23
* LED_CI = GPIO 4 (APA102 Blue)
* LED_DI = GPIO 5 (APA102 Green)
* LCD_MOSI = GPIO 2
* LCD_SCK = GPIO 6
* LCD_MISO = GPIO 7
* LCD_CS = GPIO 10
* LCD_RST = GPIO 1
* LCD_BL = GPIO 0
*/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "esp_codec_dev.h"
/* V2X-Datenbank */
#include "v2x_db.h"
static const char *TAG = "V2X-Sniffer";
/* 802.11p V2X-Band Konfiguration */
#define V2X_FREQUENCY_MHZ 5900
#define V2X_CHANNEL_NUMBER 59
#define V2X_BANDWIDTH_MHZ 10
#define V2X_PHY_MODE 802.11p
/* ====== 802.11p V2X-Kanäle (ETSI EN 302 637) ====== */
/* Kanal 157-164: 5845-5925 MHz (V2X Hauptband)
Kanal 17-18: 4300/4320 MHz (nicht V2X, nur zum Scannen) */
#define V2X_NUM_CHANNELS 10
static const uint32_t v2x_freqs[] = {
5845, 5855, 5865, 5875, /* Kanal 157-160 */
5885, 5895, 5905, 5915, /* Kanal 161-164 */
5925, 4300, /* Kanal 165, 4300 MHz */
};
/* BSM (Basic Safety Message) IDs */
#define BSM_ID_SPAT 0x01
#define BSM_ID_MAP 0x02
#define BSM_ID_TLMM 0x03
/* Channel-Hopping: alle 30 Sekunden */
#define CHANNEL_HOP_INTERVAL_MS (30 * 1000)
/* Frame-Waiter Task */
#define FRAME_QUEUE_SIZE 64
/* Statistik alle 60 Sekunden */
#define STAT_DUMP_INTERVAL_MS (60 * 1000)
/* CSV-Flush alle 200 Frames */
#define CSV_FLUSH_INTERVAL 200
/* CAM (Cooperative Awareness Message) Container */
#define CAM_CONTAINER_TYPE 0x04
#define CAM_PROTOCOL_VERSION 2
/* Frame Queue */
static QueueHandle_t s_frame_queue;
/* DENM (Decentralized Environmental Notification Message) */
#define DENM_EVENT_TYPE 0x05
typedef struct {
uint16_t message_id;
uint8_t channel;
float frequency_mhz;
int8_t rssi_dbm;
float power_dbm;
uint32_t data_rate_bps;
uint16_t frame_len;
/* ====== Message-Type Erkennung ====== */
static v2x_msg_type_t detect_msg_type(const uint8_t *data, uint16_t len)
{
if (!data || len < 4) return V2X_MSG_UNKNOWN;
/* BSM/CAM spezifisch */
uint8_t msg_type; // BSM, CAM, DENM
uint8_t protocol_version;
uint64_t station_id;
int32_t lat; // in 10^-7 Grad
int32_t lon; // in 10^-7 Grad
int16_t alt; // in dm
uint8_t speed; // in 0.01 m/s
uint16_t heading; // in 0.01 Grad
int8_t acceleration; // in 0.01 m/s^2
} v2x_frame_t;
/* Versuche SAE J2735 BSM ( ASN.1 DER) zu erkennen
* BSM hat spezifische Tag-Offsets:
* - Header mit messageType = 2 (BSM)
* - ID mit messageCounter
*/
/* Einfachere Heuristik: Längen-Check */
if (len >= 20 && len <= 240) {
/* Möglicher V2X-Framesize (SAE J2735 / ETSI) */
uint8_t protocol_ver = data[0];
if (protocol_ver == 1 || protocol_ver == 2 || protocol_ver == 3) {
/* Möglicher V2X-Header, aber msgType unbekannt */
return V2X_MSG_UNKNOWN;
}
}
return V2X_MSG_UNKNOWN;
}
/**
* Initialisiere ESP32-C5 für 802.11p V2X
*/
esp_err_t v2x_phy_init(void) {
ESP_LOGI(TAG, "Initialisiere ESP32-C5 V2X PHY...");
/* ====== 802.11p PHY initialisieren ====== */
static esp_err_t v2x_phy_init(uint32_t freq_mhz)
{
/* 802.11p PHY aktivieren */
phy_11p_set(1, 0);
ESP_LOGI(TAG, "✓ 802.11p PHY aktiviert");
/* Auf Kanal 5900 MHz schalten */
phy_change_channel(5900, 1, 0, 0);
ESP_LOGI(TAG, "✓ Kanal %d (5900 MHz) aktiv", V2X_CHANNEL_NUMBER);
/* Kanal schalten */
phy_change_channel(freq_mhz, 1, 0, 0);
ESP_LOGI(TAG, "PHY aktiviert: %lu MHz", (unsigned long)freq_mhz);
return ESP_OK;
}
/**
* 802.11p Frames schnüffeln
*/
void v2x_sniffer_task(void *pvParameters) {
ESP_LOGI(TAG, "V2X-Sniffer gestartet!");
while (1) {
/* Warte auf 802.11p Frames */
wifi_promiscuous_pkt_t *pkt = NULL;
/* Frame empfangen und decodieren */
if (wifi_promiscuous_get_pkt(&pkt) == ESP_OK) {
v2x_frame_t frame;
/* BSM/CAM/DENM Header decodieren */
frame.msg_type = pkt->ctrl & 0x0F;
frame.channel = V2X_CHANNEL_NUMBER;
frame.frequency_mhz = V2X_FREQUENCY_MHZ;
frame.rssi_dbm = pkt->rssi;
frame.power_dbm = pkt->power;
frame.data_rate_bps = 12000000; // 12 Mbps
frame.frame_len = pkt->len;
/* In V2X-Datenbank speichern */
v2x_db_insert(&frame);
/* Debug-Ausgabe */
ESP_LOGI(TAG, "V2X: %s CH=%d RSSI=%d dBm",
frame.msg_type == BSM_ID_SPAT ? "BSM" :
frame.msg_type == CAM_CONTAINER_TYPE ? "CAM" : "DENM",
frame.channel, frame.rssi_dbm);
/* ====== Promiscuous RX Callback ====== */
static void v2x_promiscuous_cb(void *arg, wifi_promiscuous_pkt_type_t pkt_type)
{
(void)arg;
(void)pkt_type;
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)arg;
if (!pkt || !pkt->payload) return;
/* Frame erstellen */
v2x_frame_t frame;
memset(&frame, 0, sizeof(frame));
/* Timestamp */
frame.timestamp_us = (uint64_t)esp_timer_get_time();
/* Metadaten */
frame.rssi_dbm = pkt->rx_ctrl->rssi;
frame.noise_dbm = pkt->rx_ctrl->noise_floor;
frame.rate_index = pkt->rx_ctrl->rate;
frame.frame_len = pkt->packet_length;
/* Aktuelle Frequenz (wird vom Channel Hopper gesetzt) */
extern uint32_t g_v2x_current_freq;
frame.frequency_mhz = (float)g_v2x_current_freq;
/* MAC-Adresse extrahieren (802.11 Header) */
if (pkt->packet_length >= 30) {
uint8_t *mac = pkt->payload;
/* BSSID: Bytes 10-15, Source: Bytes 16-21 */
memcpy(frame.bssid, mac + 10, 6);
memcpy(frame.mac_addr, mac + 16, 6);
}
/* Rohdaten (ab 802.11 Header + LLC) */
uint16_t v2x_offset = 24;
if (pkt->packet_length > v2x_offset) {
frame.raw_len = pkt->packet_length - v2x_offset;
if (frame.raw_len > V2X_MAX_FRAME_LEN) {
frame.raw_len = V2X_MAX_FRAME_LEN;
}
memcpy(frame.raw_data, pkt->payload + v2x_offset, frame.raw_len);
vTaskDelay(10 / portTICK_PERIOD_MS);
/* Message-Type */
frame.msg_type = detect_msg_type(frame.raw_data, frame.raw_len);
}
/* Auf Queue pushen (non-blocking) */
if (xQueueSendFromISR(s_frame_queue, &frame, NULL) != pdTRUE) {
static uint64_t last_overflow = 0;
if (esp_timer_get_time() - last_overflow > 1000000) {
ESP_LOGW(TAG, "Frame Queue voll, Frame verworfen");
last_overflow = esp_timer_get_time();
}
}
}
/**
* Application entry point
*/
void app_main(void) {
ESP_LOGI(TAG, "V2X-Sniffer Firmware gestartet");
/* NVS initialisieren */
nvs_flash_init();
/* WiFi initialisieren */
/* ====== Frame Writer Task ====== */
static void v2x_frame_writer_task(void *pvParameters)
{
v2x_frame_t frame;
uint32_t flush_counter = 0;
while (1) {
/* Warte auf Frame in Queue */
if (xQueueReceive(s_frame_queue, &frame, pdMS_TO_TICKS(500)) == pdTRUE) {
/* Auf SD schreiben */
esp_err_t ret = v2x_sd_write_frame(&frame);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "SD-Schreibfehler");
}
/* Periodisch flushen */
flush_counter++;
if (flush_counter % CSV_FLUSH_INTERVAL == 0) {
/* CSV-Datei flushen */
FILE *fp = fopen(SD_CSV_FILE, "r");
if (fp) {
fflush(fp);
fclose(fp);
}
}
}
}
}
/* ====== Channel Hopper Task ====== */
uint32_t g_v2x_current_freq = 5900;
static void v2x_channel_hopper_task(void *pvParameters)
{
uint8_t hop_idx = 0;
ESP_LOGI(TAG, "Channel Hopper gestartet (jedes %d Sekunden)", CHANNEL_HOP_INTERVAL_MS / 1000);
while (1) {
uint32_t freq = v2x_freqs[hop_idx % V2X_NUM_CHANNELS];
/* Frequenz/Kanal wechseln */
v2x_phy_init(freq);
g_v2x_current_freq = freq;
ESP_LOGI(TAG, "CH %d: %lu MHz", hop_idx, (unsigned long)freq);
hop_idx++;
vTaskDelay(pdMS_TO_TICKS(CHANNEL_HOP_INTERVAL_MS));
}
}
/* ====== Statistik Task ====== */
static void v2x_stats_task(void *pvParameters)
{
ESP_LOGI(TAG, "Statistik-Task gestartet");
while (1) {
if (s_frame_queue) {
v2x_sd_dump_stats();
/* Queue-Status */
uint32_t queue_free = uxQueueMessagesWaiting(s_frame_queue);
uint32_t queue_items = FRAME_QUEUE_SIZE - queue_free;
ESP_LOGI(TAG, "Queue: %lu/%lu Frames wartend", (unsigned long)queue_items, (unsigned long)FRAME_QUEUE_SIZE);
}
vTaskDelay(pdMS_TO_TICKS(STAT_DUMP_INTERVAL_MS));
}
}
/* ====== Application Entry Point ====== */
void app_main(void)
{
ESP_LOGI(TAG, "=======================================");
ESP_LOGI(TAG, "V2X-Sniffer Firmware für ESP32-C5");
ESP_LOGI(TAG, "802.11p V2X-Band (5.9 GHz) | SD-Karte");
ESP_LOGI(TAG, "Vollständig autonom - kein Host-PC");
ESP_LOGI(TAG, "=======================================");
/* === NVS initialisieren === */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_LOGW(TAG, "NVS bereinigen...");
nvs_flash_erase();
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/* === WiFi initialisieren === */
esp_netif_init();
esp_event_loop_create_default();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
/* V2X PHY initialisieren */
v2x_phy_init();
/* Promiscuous Mode für Sniffing aktivieren */
/* WiFi Station Mode (für WiFi-Manager falls needed) */
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
/* === SD-Karte initialisieren === */
ESP_LOGI(TAG, "Initialisiere SD-Karte (GPIO6/7/2/23)...");
ret = v2x_sd_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SD-Karte fehlgeschlagen: %s", esp_err_to_name(ret));
ESP_LOGW(TAG, "Fahre ohne SD-Karte fort (Daten gehen verloren)");
} else {
/* CSV-Header schreiben */
FILE *fp = fopen(SD_CSV_FILE, "a+");
if (fp) {
fprintf(fp, "%s\n", CSV_HEADER);
fclose(fp);
ESP_LOGI(TAG, "CSV-Header geschrieben: %s", SD_CSV_FILE);
}
}
/* === Frame Queue erstellen === */
s_frame_queue = xQueueCreate(FRAME_QUEUE_SIZE, sizeof(v2x_frame_t));
if (!s_frame_queue) {
ESP_LOGE(TAG, "Frame Queue konnte nicht erstellt werden");
while (1) vTaskDelay(1000 / portTICK_PERIOD_MS);
}
/* === Stats initialisieren === */
v2x_sd_reset_stats();
/* === Tasks starten === */
/* Channel Hopper */
xTaskCreate(v2x_channel_hopper_task, "channel_hopper", 4096, NULL, 3, NULL);
/* Frame Writer */
xTaskCreate(v2x_frame_writer_task, "frame_writer", 4096, NULL, 6, NULL);
/* Statistik */
xTaskCreate(v2x_stats_task, "stats_task", 2048, NULL, 2, NULL);
/* === Promiscuous Mode aktivieren === */
wifi_promiscuous_filter_t filter = {
.mask = (1 << WIFI_PROMIS_FILTER_MASK_DATA) |
(1 << WIFI_PROMIS_FILTER_MASK_MGMT) |
(1 << WIFI_PROMIS_FILTER_MASK_CTRL),
};
esp_wifi_set_promiscuous_filter(&filter);
esp_wifi_set_promiscuous_rx_cb(v2x_promiscuous_cb);
esp_wifi_set_promiscuous(true);
esp_wifi_set_promiscuous_rx_cb([](void *arg, wifi_promiscuous_pkt_t *pkt) {
vTaskNotifyGiveFromISR(v2x_sniffer_handle, pdTRUE);
});
/* V2X-Datenbank initialisieren */
v2x_db_init();
/* V2X-Sniffer Task starten */
xTaskCreate(v2x_sniffer_task, "v2x_sniffer", 4096, NULL, 5, NULL);
ESP_LOGI(TAG, "✓ V2X-Sniffer bereit!");
ESP_LOGI(TAG, "=======================================");
ESP_LOGI(TAG, "✓ V2X-Sniffer BEREIT!");
ESP_LOGI(TAG, " SD: %s", SD_CARD_MOUNT_POINT);
ESP_LOGI(TAG, " CSV: %s", SD_CSV_FILE);
ESP_LOGI(TAG, " Log: %s", SD_LOG_FILE);
ESP_LOGI(TAG, " Kanal: 157-164 + 17/18");
ESP_LOGI(TAG, " Hop: %d Sekunden", CHANNEL_HOP_INTERVAL_MS / 1000);
ESP_LOGI(TAG, " Format: v2x_frames.csv (Excel/CSV-kompatibel)");
ESP_LOGI(TAG, " Rotation: >50MB automatisch");
ESP_LOGI(TAG, "=======================================");
}