V2X-Sniffer Firmware für ESP32-C5 (Lilygo T-Dongle-C5)
- ESP-IDF Firmware mit 802.11p Support - phy_11p_set() und phy_change_channel(5900) - C-ITS BSM, CAM, DENM Decodierung - Separate V2X-Datenbank (v2x_sniffer.db) - Python API für Datenmanagement
This commit is contained in:
@@ -0,0 +1,261 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
V2X Sniffer - Separate SQLite-Datenbank für V2X-Daten
|
||||||
|
(Trenn von home_tracker.db - nur für Essen & Aktivitäten!)
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "v2x_sniffer.db")
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection():
|
||||||
|
"""Verbindung zur V2X-Datenbank aufbauen."""
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL")
|
||||||
|
conn.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
"""Datenbank initialisieren."""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# V2X Messages Tabelle (C-ITS, BSM, CAM, DENM)
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS v2x_messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp TEXT NOT NULL,
|
||||||
|
message_type TEXT NOT NULL,
|
||||||
|
channel INTEGER NOT NULL,
|
||||||
|
frequency_mhz REAL NOT NULL,
|
||||||
|
rssi_dbm REAL,
|
||||||
|
power_dbm REAL,
|
||||||
|
data_rate_bps REAL,
|
||||||
|
frame_len INTEGER,
|
||||||
|
cam_container_id TEXT,
|
||||||
|
station_id TEXT,
|
||||||
|
vehicle_type INTEGER,
|
||||||
|
latitude REAL,
|
||||||
|
longitude REAL,
|
||||||
|
altitude REAL,
|
||||||
|
speed_kmh REAL,
|
||||||
|
heading_deg REAL,
|
||||||
|
acceleration_kmh2 REAL,
|
||||||
|
lateral_accel_kmh2 REAL,
|
||||||
|
yaw_rate_dps REAL,
|
||||||
|
driveway_width_mm INTEGER,
|
||||||
|
brake_status INTEGER,
|
||||||
|
sparkplug INTEGER,
|
||||||
|
wiper_status INTEGER,
|
||||||
|
vehicle_length_mm INTEGER,
|
||||||
|
vehicle_width_mm INTEGER,
|
||||||
|
vehicle_class INTEGER,
|
||||||
|
position_precision INTEGER,
|
||||||
|
mission_status INTEGER,
|
||||||
|
crossroad_status INTEGER,
|
||||||
|
message_id TEXT,
|
||||||
|
raw_data TEXT,
|
||||||
|
decoded_json TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Kanäle und Frequenzen
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS v2x_channels (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
channel_name TEXT UNIQUE NOT NULL,
|
||||||
|
frequency_mhz REAL NOT NULL,
|
||||||
|
channel_number INTEGER NOT NULL,
|
||||||
|
phy_mode TEXT DEFAULT '802.11p',
|
||||||
|
bandwidth_mhz REAL DEFAULT 10.0,
|
||||||
|
description TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Statistik pro Tag
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS v2x_statistics (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
total_messages INTEGER DEFAULT 0,
|
||||||
|
cam_count INTEGER DEFAULT 0,
|
||||||
|
denm_count INTEGER DEFAULT 0,
|
||||||
|
bsm_count INTEGER DEFAULT 0,
|
||||||
|
other_count INTEGER DEFAULT 0,
|
||||||
|
avg_rssi REAL,
|
||||||
|
min_rssi REAL,
|
||||||
|
max_rssi REAL,
|
||||||
|
unique_stations INTEGER DEFAULT 0
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# C-ITS Tabellen
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS v2x_vehicles (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
station_id TEXT UNIQUE NOT NULL,
|
||||||
|
first_seen TEXT NOT NULL,
|
||||||
|
last_seen TEXT NOT NULL,
|
||||||
|
message_count INTEGER DEFAULT 0,
|
||||||
|
vehicle_type INTEGER,
|
||||||
|
lat REAL,
|
||||||
|
lng REAL,
|
||||||
|
speed_kmh REAL,
|
||||||
|
heading_deg REAL,
|
||||||
|
signal_strength REAL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def insert_message(msg_type, data):
|
||||||
|
"""Eine V2X-Nachricht in die Datenbank einfügen."""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Entpacken der Rohdaten
|
||||||
|
lat = data.get('latitude', 0.0)
|
||||||
|
lng = data.get('longitude', 0.0)
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO v2x_messages (
|
||||||
|
timestamp, message_type, channel, frequency_mhz,
|
||||||
|
rssi_dbm, power_dbm, data_rate_bps, frame_len,
|
||||||
|
cam_container_id, station_id, vehicle_type,
|
||||||
|
latitude, longitude, altitude, speed_kmh,
|
||||||
|
heading_deg, acceleration_kmh2,
|
||||||
|
raw_data, decoded_json
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (
|
||||||
|
datetime.now().isoformat(),
|
||||||
|
msg_type,
|
||||||
|
data.get('channel', 0),
|
||||||
|
data.get('frequency_mhz', 5900.0),
|
||||||
|
data.get('rssi', 0.0),
|
||||||
|
data.get('power', 0.0),
|
||||||
|
data.get('data_rate', 0.0),
|
||||||
|
data.get('frame_len', 0),
|
||||||
|
data.get('cam_container_id', ''),
|
||||||
|
data.get('station_id', ''),
|
||||||
|
data.get('vehicle_type', 0),
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
data.get('altitude', 0.0),
|
||||||
|
data.get('speed_kmh', 0.0),
|
||||||
|
data.get('heading_deg', 0.0),
|
||||||
|
data.get('acceleration_kmh2', 0.0),
|
||||||
|
data.get('raw_data', ''),
|
||||||
|
json.dumps(data.get('decoded', {}))
|
||||||
|
))
|
||||||
|
|
||||||
|
# Fahrzeug aktualisieren
|
||||||
|
station_id = data.get('station_id', '')
|
||||||
|
if station_id:
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO v2x_vehicles (
|
||||||
|
station_id, first_seen, last_seen, message_count,
|
||||||
|
vehicle_type, lat, lng, speed_kmh, heading_deg, signal_strength
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(station_id) DO UPDATE SET
|
||||||
|
last_seen = excluded.last_seen,
|
||||||
|
message_count = v2x_vehicles.message_count + 1,
|
||||||
|
vehicle_type = COALESCE(excluded.vehicle_type, v2x_vehicles.vehicle_type),
|
||||||
|
lat = COALESCE(excluded.lat, v2x_vehicles.lat),
|
||||||
|
lng = COALESCE(excluded.lng, v2x_vehicles.lng),
|
||||||
|
speed_kmh = COALESCE(excluded.speed_kmh, v2x_vehicles.speed_kmh),
|
||||||
|
heading_deg = COALESCE(excluded.heading_deg, v2x_vehicles.heading_deg),
|
||||||
|
signal_strength = COALESCE(excluded.signal_strength, v2x_vehicles.signal_strength)
|
||||||
|
""", (
|
||||||
|
station_id,
|
||||||
|
datetime.now().isoformat(),
|
||||||
|
datetime.now().isoformat(),
|
||||||
|
1,
|
||||||
|
data.get('vehicle_type', 0),
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
data.get('speed_kmh', 0.0),
|
||||||
|
data.get('heading_deg', 0.0),
|
||||||
|
data.get('rssi', 0.0)
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def query_recent(limit=50):
|
||||||
|
"""Die letzten V2X-Nachrichten abfragen."""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT * FROM v2x_messages
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT ?
|
||||||
|
""", (limit,))
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def get_statistics():
|
||||||
|
"""Statistiken der V2X-Datenbank abrufen."""
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
total = cursor.execute("SELECT COUNT(*) FROM v2x_messages").fetchone()[0]
|
||||||
|
cams = cursor.execute("SELECT COUNT(*) FROM v2x_messages WHERE message_type='CAM'").fetchone()[0]
|
||||||
|
denms = cursor.execute("SELECT COUNT(*) FROM v2x_messages WHERE message_type='DENM'").fetchone()[0]
|
||||||
|
bsm = cursor.execute("SELECT COUNT(*) FROM v2x_messages WHERE message_type='BSM'").fetchone()[0]
|
||||||
|
|
||||||
|
stats = cursor.execute("""
|
||||||
|
SELECT AVG(rssi_dbm), MIN(rssi_dbm), MAX(rssi_dbm)
|
||||||
|
FROM v2x_messages WHERE rssi_dbm IS NOT NULL
|
||||||
|
""").fetchone()
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
return {
|
||||||
|
'total': total,
|
||||||
|
'cam': cams,
|
||||||
|
'denm': denms,
|
||||||
|
'bsm': bsm,
|
||||||
|
'avg_rssi': stats[0],
|
||||||
|
'min_rssi': stats[1],
|
||||||
|
'max_rssi': stats[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
init_db()
|
||||||
|
print(f"✅ V2X-Datenbank initialisiert: {DB_PATH}")
|
||||||
|
|
||||||
|
# Testdaten einfügen
|
||||||
|
test_data = {
|
||||||
|
'channel': 59,
|
||||||
|
'frequency_mhz': 5900.0,
|
||||||
|
'rssi': -45.0,
|
||||||
|
'power': -20.0,
|
||||||
|
'data_rate': 12000000.0,
|
||||||
|
'frame_len': 200,
|
||||||
|
'cam_container_id': 'test_cam_001',
|
||||||
|
'station_id': 'AT-GRA-001',
|
||||||
|
'vehicle_type': 1,
|
||||||
|
'latitude': 47.0707,
|
||||||
|
'longitude': 15.4395,
|
||||||
|
'altitude': 314.0,
|
||||||
|
'speed_kmh': 65.5,
|
||||||
|
'heading_deg': 180.0,
|
||||||
|
'acceleration_kmh2': -2.5,
|
||||||
|
'raw_data': '0x10,0x20,0x30,0x40',
|
||||||
|
'decoded': {'msgId': 'test', 'version': 2}
|
||||||
|
}
|
||||||
|
insert_message('CAM', test_data)
|
||||||
|
print("✅ Test-Nachricht eingefügt")
|
||||||
|
|
||||||
|
stats = get_statistics()
|
||||||
|
print(f"📊 Statistik: {stats}")
|
||||||
Binary file not shown.
Reference in New Issue
Block a user