From ce7e6eeece71787de64b92769396daa7c8a6c1bb Mon Sep 17 00:00:00 2001 From: Clawdia Date: Thu, 7 May 2026 09:34:20 +0200 Subject: [PATCH] =?UTF-8?q?V2X-Sniffer=20Firmware=20f=C3=BCr=20ESP32-C5=20?= =?UTF-8?q?(Lilygo=20T-Dongle-C5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- python/v2x_db.py | 261 ++++++++++++++++++++++++++++++++++++++++++ python/v2x_sniffer.db | Bin 0 -> 32768 bytes 2 files changed, 261 insertions(+) create mode 100644 python/v2x_db.py create mode 100644 python/v2x_sniffer.db diff --git a/python/v2x_db.py b/python/v2x_db.py new file mode 100644 index 0000000..62590ee --- /dev/null +++ b/python/v2x_db.py @@ -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}") diff --git a/python/v2x_sniffer.db b/python/v2x_sniffer.db new file mode 100644 index 0000000000000000000000000000000000000000..f6e8077c4a11baf1edf8e6dd1d687a7708a8aeba GIT binary patch literal 32768 zcmeI)%WvaE90zdQePrA1Do7l1*ozxcy4AMDNtSVgC(;#FYaF?t2CaZb(Q-+>sbNc3!o!5xub0*KJZeGj_(G-*0A;H1da! z_cf1-rN-~@r&5=D;V1a`Z{ZqYK%R{UGE@X^YUzXG@X^!(2BKU`{Jm%IJs z_QcfA^iQe7iQQCY$X+Od00bcLS_wScolIrs=J>Ckc%-V+X1*miXh`Xu9v+p7 zm6BK~zPDczdrQQ7(_+t}sp3w#Qra#ZiHAo!2gRckaj$eD7LO~BcFOD^2c>dlv2PF8 zBc5iNGTXCK`luovmvyGD0PTx-O|vS+N?X z*JXfLOS(8;7 zyQfw&i7vaIL(Q7k-oK-{u%4RC?9K6(;a8L@Kke zz<)j8$$}s(uBRzM9(1=RuVg~E?Ap1Y5;m&`Sr8vs&+^#WhmmumK!^!JQ;0EIbX97O zShMPmSkdz8)EVvBNwX$9uB*i)gQ1y;MWmHf)G)R424zVSbgUSrZpM7k8VkDrmn)sX zD|KR;R1d-)t&d*`dQ@`lkcS77O=5(hA3gv$l%1QYT*^lMX&{y`Qgn{nFsjt)q)L2V z*>!e2OAQ+krPKTaDZiY{+4KE9y-G~=LQ}nZw+Yc6Ica%d;83ZnIGXK`a6w{s>DR|! z)mbCsc4v*YCfNKEyudvaCNuZ%^Cv;%!rtz-b?Ct6SYP}G3h(*!i)_z@=Wp{tHhh!r zWJA=~{NW;67y6FYTXOAe@HF-$9x?1;qd?e>_Fz0@vO-vJ8WQ+tF{}n@2SK1Wu47v;l$aWZS6tK^oVA%rwAv>{wiTdk1rf)=F+?d zOa7SY%Q9<8WkmOqivIijMihnWlxg{|2-pOfk82dObl5Zy)J>{%NiN#xYAm|yXia)S zF6FQz8}ay6hn%$s(ne<(9k92Fb7t#}TKoYnG@C`u4^YwpqWG``U=zA*B(x-|HoU!2 zErq=TN@%ccxmuuWJ5qJ40jX&Vn$k?TTZ$u455(XsxKb#^$cW;^g;`puhLi zs#IY^vMPV#UKu;@urZKNZ*%My3j`nl0SG_<0uX=z1Rwwb2tWV=!v#KHpnr1grlu_>)>t`?RH4>ne$!o%XhyP27ne{w(E<+z_c z_Q9R(e_AC;xngB$`>43Y*0#2v&wTgoAHQ!c|2Fy7FYnytH@6NyZa>5KKZgI8ms&Y# zQEKJ=yA}U#Rr)k*xV0TMyD4V<<7OAdY?C_v2NLX|{Ad3Aey8r+H literal 0 HcmV?d00001