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:
@@ -0,0 +1,149 @@
|
|||||||
|
# V2X-Sniffer Firmware — SD-Karte Speicherung
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Die Firmware speichert V2X-Daten (BSM, CAM, DENM) **komplett autonom** auf einer SD-Karte im ESP32-C5.
|
||||||
|
Kein Host-PC ist erforderlich.
|
||||||
|
|
||||||
|
### Was wurde geändert?
|
||||||
|
|
||||||
|
| Alt | Neu |
|
||||||
|
|-----|-----|
|
||||||
|
| NVS-Flash (4 MB, begrenzt) | SD-Karte via SDMMC (FAT32, unbegrenzt) |
|
||||||
|
| Keine Rohdatenspeicherung | CSV mit Rohdaten (Hex) pro Frame |
|
||||||
|
| Manuell / interaktiv | Komplett autonom, channel-hopping |
|
||||||
|
| C++ Lambda in C-Code (Bug) | Korrekter C-Code mit Queue |
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
**T-Dongle-C5** mit SD-Karte (FAT32 formatiert)
|
||||||
|
|
||||||
|
### SD-Karte Pinbelegung (ESP32-C5 native SDMMC)
|
||||||
|
|
||||||
|
| Signal | GPIO |
|
||||||
|
|--------|------|
|
||||||
|
| SD_CMD | GPIO 2 |
|
||||||
|
| SD_CLK | GPIO 6 |
|
||||||
|
| SD_D0 | GPIO 7 |
|
||||||
|
| SD_CS | GPIO 23 |
|
||||||
|
|
||||||
|
### SD-Karte vorbereiten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auf dem Host: SD-Karte mit FAT32 formatieren
|
||||||
|
mkfs.fat -F 32 /dev/mmcblk0 # Linux
|
||||||
|
diskutil eraseDisk FAT32 V2X /dev/disk2 # macOS
|
||||||
|
format /dev/sdX /FS:FAT32 /Q # Windows
|
||||||
|
|
||||||
|
# Oder direkt auf dem ESP nach Boot:
|
||||||
|
# Die Firmware formatiert automatisch falls nötig
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ausgabe
|
||||||
|
|
||||||
|
### v2x_frames.csv
|
||||||
|
|
||||||
|
Jeder gefangene Frame als eine Zeile CSV:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
timestamp_us,msg_type,channel,frequency_mhz,rssi_dbm,noise_dbm,rate_index,frame_len,mac_addr,bssid,raw_hex
|
||||||
|
1715234567890,2,157,5845.0,-45,-92,8,314,AA:BB:CC:DD:EE:FF,11:22:33:44:55:66,AABBCCDD...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Spalten:**
|
||||||
|
| Spalte | Beschreibung |
|
||||||
|
|--------|-------------|
|
||||||
|
| timestamp_us | Mikrosekunden seit Systemstart |
|
||||||
|
| msg_type | 1=BSM, 2=CAM, 3=DENM, 0=Unknown |
|
||||||
|
| channel | Kanal (157-164, 17, 18) |
|
||||||
|
| frequency_mhz | Frequenz in MHz (5845-5925 oder 4300) |
|
||||||
|
| rssi_dbm | Empfangsleistung |
|
||||||
|
| noise_dbm | Rauschleistung |
|
||||||
|
| rate_index | Datenraten-Index (802.11p) |
|
||||||
|
| frame_len | Paketlänge in Bytes |
|
||||||
|
| mac_addr | MAC-Adresse des Absenders |
|
||||||
|
| bssid | BSSID der Basisstation |
|
||||||
|
| raw_hex | Rohdaten als Hex-String |
|
||||||
|
|
||||||
|
### v2x_log.txt
|
||||||
|
|
||||||
|
System-Log mit:
|
||||||
|
- Start-Zeitstempel
|
||||||
|
- Periodischen Statistiken (alle 60 Sekunden)
|
||||||
|
- Fehlermeldungen
|
||||||
|
- CSV-Rotation-Ereignissen
|
||||||
|
|
||||||
|
### CSV-Rotation
|
||||||
|
|
||||||
|
Wenn die CSV-Datei > 50 MB erreicht, wird sie automatisch umbenannt:
|
||||||
|
```
|
||||||
|
v2x_frames_20260509_143022.csv
|
||||||
|
```
|
||||||
|
und eine neue leere CSV mit Header erstellt.
|
||||||
|
|
||||||
|
## Kanal-Hopping
|
||||||
|
|
||||||
|
Die Firmware wechselt alle 30 Sekunden durch alle V2X-Kanäle:
|
||||||
|
```
|
||||||
|
5845, 5855, 5865, 5875, 5885, 5895, 5905, 5915, 5925, 4300 MHz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Datenanalyse
|
||||||
|
|
||||||
|
### Auf dem PC öffnen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CSV auf SD-Karte kopieren
|
||||||
|
cp /media/user/V2X/v2x_frames.csv ./v2x_frames.csv
|
||||||
|
|
||||||
|
# Mit Python analysieren
|
||||||
|
python3 -c "
|
||||||
|
import csv, pandas as pd
|
||||||
|
df = pd.read_csv('v2x_frames.csv')
|
||||||
|
print(df.head())
|
||||||
|
print(df.describe())
|
||||||
|
print('Message Types:')
|
||||||
|
print(df['msg_type'].value_counts())
|
||||||
|
"
|
||||||
|
|
||||||
|
# Mit Excel/Numbers öffnen
|
||||||
|
# Direkt per Doppelklick in der SD-Karte
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rohdaten dekodieren
|
||||||
|
|
||||||
|
Die `raw_hex` Spalte enthält die unveränderten V2X-PDUs (ASN.1 DER / BER-TLC).
|
||||||
|
Diese können mit einem C-ITS Decoder weiterverarbeitet werden.
|
||||||
|
|
||||||
|
## Bauen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd v2x_fw
|
||||||
|
idf.py set-target esp32c5
|
||||||
|
idf.py build
|
||||||
|
idf.py flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Betrieb
|
||||||
|
|
||||||
|
1. SD-Karte (FAT32) in Slot stecken
|
||||||
|
2. Firmware flashen
|
||||||
|
3. Gerät starten — Daten landen automatisch in `v2x_frames.csv` auf der SD-Karte
|
||||||
|
4. SD-Karte auslesen wenn benötigt
|
||||||
|
|
||||||
|
## Fehlerbehebung
|
||||||
|
|
||||||
|
**SD-Karte wird nicht erkannt:**
|
||||||
|
- FAT32 Format prüfen
|
||||||
|
- GPIO-Pins richtig verkabelt (ESP32-C5 SDMMC)
|
||||||
|
- SD-Karte ≤ 32GB (SDSC) oder ≤ 2TB (SDHC/SDXC)
|
||||||
|
|
||||||
|
**CSV-Schreibfehler:**
|
||||||
|
- SD-Karte schreibgeschützt?
|
||||||
|
- Genug freier Speicher?
|
||||||
|
- FAT32-Dateisystem korrupt?
|
||||||
|
|
||||||
|
**Keine Frames im CSV:**
|
||||||
|
- Kanal-Hopping funktioniert? (Log prüfen)
|
||||||
|
- Promiscuous Mode aktiv?
|
||||||
|
- Andere Geräte senden auf V2X-Kanälen?
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "v2x_driver.c"
|
SRCS "v2x_driver.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES esp_vfs_fat sdmmc esp_timer driver
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* 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 "esp_log.h"
|
||||||
#include "v2x_db.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_driver";
|
static const char *TAG = "v2x-sd";
|
||||||
|
|
||||||
esp_err_t v2x_driver_init(void) {
|
/* Globale Variablen */
|
||||||
ESP_LOGI(TAG, "V2X Driver initialisiert");
|
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;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-30
@@ -3,43 +3,71 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
/* V2X-Datenbank Struktur */
|
#define V2X_MAX_FRAME_LEN 320
|
||||||
|
#define SD_CARD_MOUNT_POINT "/sdcard"
|
||||||
|
#define SD_CSV_FILE "/sdcard/v2x_frames.csv"
|
||||||
|
#define SD_LOG_FILE "/sdcard/v2x_log.txt"
|
||||||
|
|
||||||
|
#define CSV_LINE_MAX 512
|
||||||
|
#define RAW_HEX_BUF 2048
|
||||||
|
|
||||||
|
/* Message-Typen */
|
||||||
|
typedef enum {
|
||||||
|
V2X_MSG_UNKNOWN = 0,
|
||||||
|
V2X_MSG_BSM = 1,
|
||||||
|
V2X_MSG_CAM = 2,
|
||||||
|
V2X_MSG_DENM = 3,
|
||||||
|
V2X_MSG_SOA3 = 4,
|
||||||
|
V2X_MSG_SAE2619 = 5,
|
||||||
|
V2X_MSG_SAE2725 = 6,
|
||||||
|
} v2x_msg_type_t;
|
||||||
|
|
||||||
|
/* Ein Frame: alle Felder werden in CSV geschrieben */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t id;
|
uint64_t timestamp_us; /* esp_timer_get_time() */
|
||||||
uint64_t timestamp;
|
uint8_t msg_type; /* V2X_MSG_* */
|
||||||
uint8_t msg_type;
|
uint8_t channel; /* 157-164 (5 GHz) oder 17/18 (4300 MHz) */
|
||||||
uint8_t channel;
|
float frequency_mhz; /* z.B. 5845.0 */
|
||||||
float frequency_mhz;
|
|
||||||
int8_t rssi_dbm;
|
int8_t rssi_dbm;
|
||||||
float power_dbm;
|
int8_t noise_dbm;
|
||||||
uint32_t data_rate_bps;
|
uint8_t rate_index;
|
||||||
uint16_t frame_len;
|
uint16_t frame_len; /* 802.11 PDU length */
|
||||||
|
uint8_t mac_addr[6]; /* Source MAC */
|
||||||
/* C-ITS Message spezifisch */
|
uint8_t bssid[6]; /* BSSID */
|
||||||
uint64_t station_id;
|
uint8_t raw_data[V2X_MAX_FRAME_LEN];
|
||||||
int32_t lat;
|
|
||||||
int32_t lon;
|
|
||||||
int16_t alt;
|
|
||||||
uint8_t speed;
|
|
||||||
uint16_t heading;
|
|
||||||
int8_t acceleration;
|
|
||||||
|
|
||||||
/* Rohdaten */
|
|
||||||
uint8_t raw_data[256];
|
|
||||||
uint16_t raw_len;
|
uint16_t raw_len;
|
||||||
} v2x_db_entry_t;
|
} v2x_frame_t;
|
||||||
|
|
||||||
/* Datenbank initialisieren */
|
#define CSV_HEADER \
|
||||||
esp_err_t v2x_db_init(void);
|
"timestamp_us,msg_type,channel,frequency_mhz,rssi_dbm,noise_dbm,rate_index,frame_len,mac_addr,bssid,raw_hex"
|
||||||
|
|
||||||
/* V2X-Nachricht in Datenbank speichern */
|
/* === SD-Karte === */
|
||||||
esp_err_t v2x_db_insert(const v2x_frame_t *frame);
|
esp_err_t v2x_sd_init(void);
|
||||||
|
|
||||||
/* Letzte N Nachrichten abfragen */
|
/* === CSV-Schreiber === */
|
||||||
esp_err_t v2x_db_query_recent(v2x_db_entry_t *entries, uint32_t count);
|
esp_err_t v2x_sd_write_frame(const v2x_frame_t *frame);
|
||||||
|
|
||||||
/* Statistiken abrufen */
|
/* === Log === */
|
||||||
esp_err_t v2x_db_get_statistics(uint32_t *total, uint32_t *cam_count, uint32_t *denm_count);
|
esp_err_t v2x_sd_log_write(const char *fmt, ...);
|
||||||
|
|
||||||
|
/* === Statistik === */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t total_frames;
|
||||||
|
uint32_t cam_count;
|
||||||
|
uint32_t denm_count;
|
||||||
|
uint32_t bsm_count;
|
||||||
|
uint32_t unknown_count;
|
||||||
|
uint32_t sd_write_errors;
|
||||||
|
uint64_t start_timestamp_us;
|
||||||
|
} v2x_stats_t;
|
||||||
|
|
||||||
|
const v2x_stats_t* v2x_sd_get_stats(void);
|
||||||
|
void v2x_sd_reset_stats(void);
|
||||||
|
esp_err_t v2x_sd_dump_stats(void);
|
||||||
|
|
||||||
|
/* === Hilfsfunktionen === */
|
||||||
|
void frame_to_csv_line(const v2x_frame_t *frame, char *out, size_t out_len);
|
||||||
|
|
||||||
#endif /* V2X_DB_H */
|
#endif /* V2X_DB_H */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "main.c"
|
SRCS "main.c"
|
||||||
INCLUDE_DIRS "../include"
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
+279
-105
@@ -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:
|
* VOLLSTÄNDIG UNABHÄNGIG - Speichert V2X-Daten auf SD-Karte
|
||||||
* - phy_11p_set(1, 0) - Aktiviert 802.11p PHY
|
* KEIN Host-PC erforderlich
|
||||||
* - phy_change_channel(5900, 1, 0, 0) - Kanal 5900 MHz
|
*
|
||||||
* - C-ITS BSM, CAM, DENM Decodierung
|
* Funktion:
|
||||||
* - Separate V2X-Datenbank (v2x_sniffer.db)
|
* - 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 <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/event_groups.h"
|
#include "freertos/queue.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "esp_codec_dev.h"
|
#include "esp_codec_dev.h"
|
||||||
|
|
||||||
/* V2X-Datenbank */
|
|
||||||
#include "v2x_db.h"
|
#include "v2x_db.h"
|
||||||
|
|
||||||
static const char *TAG = "V2X-Sniffer";
|
static const char *TAG = "V2X-Sniffer";
|
||||||
|
|
||||||
/* 802.11p V2X-Band Konfiguration */
|
/* ====== 802.11p V2X-Kanäle (ETSI EN 302 637) ====== */
|
||||||
#define V2X_FREQUENCY_MHZ 5900
|
/* Kanal 157-164: 5845-5925 MHz (V2X Hauptband)
|
||||||
#define V2X_CHANNEL_NUMBER 59
|
Kanal 17-18: 4300/4320 MHz (nicht V2X, nur zum Scannen) */
|
||||||
#define V2X_BANDWIDTH_MHZ 10
|
#define V2X_NUM_CHANNELS 10
|
||||||
#define V2X_PHY_MODE 802.11p
|
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 */
|
/* Channel-Hopping: alle 30 Sekunden */
|
||||||
#define BSM_ID_SPAT 0x01
|
#define CHANNEL_HOP_INTERVAL_MS (30 * 1000)
|
||||||
#define BSM_ID_MAP 0x02
|
/* Frame-Waiter Task */
|
||||||
#define BSM_ID_TLMM 0x03
|
#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 */
|
/* Frame Queue */
|
||||||
#define CAM_CONTAINER_TYPE 0x04
|
static QueueHandle_t s_frame_queue;
|
||||||
#define CAM_PROTOCOL_VERSION 2
|
|
||||||
|
|
||||||
/* DENM (Decentralized Environmental Notification Message) */
|
/* ====== Message-Type Erkennung ====== */
|
||||||
#define DENM_EVENT_TYPE 0x05
|
static v2x_msg_type_t detect_msg_type(const uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
if (!data || len < 4) return V2X_MSG_UNKNOWN;
|
||||||
|
|
||||||
typedef struct {
|
/* Versuche SAE J2735 BSM ( ASN.1 DER) zu erkennen
|
||||||
uint16_t message_id;
|
* BSM hat spezifische Tag-Offsets:
|
||||||
uint8_t channel;
|
* - Header mit messageType = 2 (BSM)
|
||||||
float frequency_mhz;
|
* - ID mit messageCounter
|
||||||
int8_t rssi_dbm;
|
|
||||||
float power_dbm;
|
|
||||||
uint32_t data_rate_bps;
|
|
||||||
uint16_t frame_len;
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialisiere ESP32-C5 für 802.11p V2X
|
|
||||||
*/
|
*/
|
||||||
esp_err_t v2x_phy_init(void) {
|
/* Einfachere Heuristik: Längen-Check */
|
||||||
ESP_LOGI(TAG, "Initialisiere ESP32-C5 V2X PHY...");
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== 802.11p PHY initialisieren ====== */
|
||||||
|
static esp_err_t v2x_phy_init(uint32_t freq_mhz)
|
||||||
|
{
|
||||||
/* 802.11p PHY aktivieren */
|
/* 802.11p PHY aktivieren */
|
||||||
phy_11p_set(1, 0);
|
phy_11p_set(1, 0);
|
||||||
ESP_LOGI(TAG, "✓ 802.11p PHY aktiviert");
|
|
||||||
|
|
||||||
/* Auf Kanal 5900 MHz schalten */
|
/* Kanal schalten */
|
||||||
phy_change_channel(5900, 1, 0, 0);
|
phy_change_channel(freq_mhz, 1, 0, 0);
|
||||||
ESP_LOGI(TAG, "✓ Kanal %d (5900 MHz) aktiv", V2X_CHANNEL_NUMBER);
|
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PHY aktiviert: %lu MHz", (unsigned long)freq_mhz);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* ====== Promiscuous RX Callback ====== */
|
||||||
* 802.11p Frames schnüffeln
|
static void v2x_promiscuous_cb(void *arg, wifi_promiscuous_pkt_type_t pkt_type)
|
||||||
*/
|
{
|
||||||
void v2x_sniffer_task(void *pvParameters) {
|
(void)arg;
|
||||||
ESP_LOGI(TAG, "V2X-Sniffer gestartet!");
|
(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);
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== Frame Writer Task ====== */
|
||||||
|
static void v2x_frame_writer_task(void *pvParameters)
|
||||||
|
{
|
||||||
|
v2x_frame_t frame;
|
||||||
|
uint32_t flush_counter = 0;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
/* Warte auf 802.11p Frames */
|
/* Warte auf Frame in Queue */
|
||||||
wifi_promiscuous_pkt_t *pkt = NULL;
|
if (xQueueReceive(s_frame_queue, &frame, pdMS_TO_TICKS(500)) == pdTRUE) {
|
||||||
|
/* Auf SD schreiben */
|
||||||
/* Frame empfangen und decodieren */
|
esp_err_t ret = v2x_sd_write_frame(&frame);
|
||||||
if (wifi_promiscuous_get_pkt(&pkt) == ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
v2x_frame_t frame;
|
ESP_LOGW(TAG, "SD-Schreibfehler");
|
||||||
|
|
||||||
/* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
/* 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 ====== */
|
||||||
* Application entry point
|
uint32_t g_v2x_current_freq = 5900;
|
||||||
*/
|
|
||||||
void app_main(void) {
|
|
||||||
ESP_LOGI(TAG, "V2X-Sniffer Firmware gestartet");
|
|
||||||
|
|
||||||
/* NVS initialisieren */
|
static void v2x_channel_hopper_task(void *pvParameters)
|
||||||
nvs_flash_init();
|
{
|
||||||
|
uint8_t hop_idx = 0;
|
||||||
|
|
||||||
/* WiFi initialisieren */
|
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_netif_init();
|
||||||
esp_event_loop_create_default();
|
esp_event_loop_create_default();
|
||||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
esp_wifi_init(&cfg);
|
esp_wifi_init(&cfg);
|
||||||
|
|
||||||
/* V2X PHY initialisieren */
|
/* WiFi Station Mode (für WiFi-Manager falls needed) */
|
||||||
v2x_phy_init();
|
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||||
|
esp_wifi_start();
|
||||||
|
|
||||||
/* Promiscuous Mode für Sniffing aktivieren */
|
/* === SD-Karte initialisieren === */
|
||||||
esp_wifi_set_promiscuous(true);
|
ESP_LOGI(TAG, "Initialisiere SD-Karte (GPIO6/7/2/23)...");
|
||||||
esp_wifi_set_promiscuous_rx_cb([](void *arg, wifi_promiscuous_pkt_t *pkt) {
|
ret = v2x_sd_init();
|
||||||
vTaskNotifyGiveFromISR(v2x_sniffer_handle, pdTRUE);
|
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)");
|
||||||
/* V2X-Datenbank initialisieren */
|
} else {
|
||||||
v2x_db_init();
|
/* CSV-Header schreiben */
|
||||||
|
FILE *fp = fopen(SD_CSV_FILE, "a+");
|
||||||
/* V2X-Sniffer Task starten */
|
if (fp) {
|
||||||
xTaskCreate(v2x_sniffer_task, "v2x_sniffer", 4096, NULL, 5, NULL);
|
fprintf(fp, "%s\n", CSV_HEADER);
|
||||||
|
fclose(fp);
|
||||||
ESP_LOGI(TAG, "✓ V2X-Sniffer bereit!");
|
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_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, "=======================================");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user