273 lines
8.7 KiB
Markdown
273 lines
8.7 KiB
Markdown
# Kettenöler – CAN Reverse-Engineering Toolkit
|
||
|
||
Toolsuite (GUI + CLI) zum Analysieren von CAN-Logs im **Kettenöler-Format**.
|
||
Funktionen: Logs **splitten** (pro CAN-ID), **explorative Visualisierung** (8-/16-Bit, LE/BE), **Batch-Analysen** über viele `.trace`, **Ranking** plausibler Signale und **Range-Fit** (lineare Abbildung `phys = raw*scale + offset`), optional **unsupervised** ohne vorgegebene Range.
|
||
|
||
---
|
||
|
||
## Features (Überblick)
|
||
|
||
* **Einheitliche GUI** (`main.py`) mit globalem Header (Workdir, Ordnerstruktur, Log-Auswahl).
|
||
* **Gemeinsame Trace-Auswahl** in allen Trace-Tabs (gleiches Panel, synchronisiert über Tabs):
|
||
|
||
* **ID Explorer** (Multi-Select)
|
||
* **Traces Batch-Analyse** (Multi-Select oder kompletter Ordner)
|
||
* **Range-Fit** (Single-Select, supervised *oder* unsupervised)
|
||
* **Splitter**: Logs → `.trace` pro CAN-ID (`traces/…`, inkl. `overview_ids.csv`).
|
||
* **Einzel-ID-Explorer**: Plots aller Byte-Kanäle (8-Bit) und Nachbar-Wortkombis (16-Bit LE/BE) + Kurzstatistik.
|
||
* **Batch-Analyzer**: Kennzahlen/Plots für alle `.trace` in einem Ordner, globales Ranking.
|
||
* **Range-/Unsupervised-Fit**:
|
||
|
||
* *Supervised*: findet `scale` & `offset` für Zielbereich `[rmin, rmax]` (Offset via Intervall-Überdeckung, Scale aus plausibler Menge).
|
||
* *Unsupervised*: identifiziert „ruhige“ physikalische Kandidaten ohne Range (Smoothness/Varianz/Rate/Spannweite).
|
||
* **Output-Hygiene**: Ergebnisse stets unter `analyze_out/<timestamp>_<tool>/…`, optionale Zeitstempel-Unterordner verhindern Überschreiben.
|
||
* **Projektdatei** (`Projekt.json`): speichert Workdir, Subfolder, Log-Auswahl, aktiven Traces-Ordner, etc.
|
||
* **„Neuester Split“**-Button: springt in den jüngsten Unterordner von `traces/`.
|
||
|
||
---
|
||
|
||
## Repository-Komponenten
|
||
|
||
* **GUI**
|
||
|
||
* `main.py` – zentrales Frontend mit Tabs (Multi-Log Analyse, ID Explorer, Traces Batch-Analyse, Range-Fit).
|
||
* **CLI-Tools**
|
||
|
||
* `can_split_by_id.py` – Splittet Logs nach CAN-ID → `.trace`.
|
||
* `id_signal_explorer.py` – Visualisiert/analysiert eine `.trace` (8-Bit, 16-Bit LE/BE) + `summary_stats.csv`.
|
||
* `trace_batch_analyzer.py` – Batch-Analyse für viele `.trace` + globales Ranking.
|
||
* `trace_signal_fitter.py` – **Range-Fit** (scale/offset) **oder** **Unsupervised-Fit** (ohne Range).
|
||
|
||
> Optional/Alt: `can_universal_signal_finder.py` – ursprünglicher Multi-Log-Analyzer (Ranking auf Rohdatenebene).
|
||
|
||
---
|
||
|
||
## Installation
|
||
|
||
* **Python** ≥ 3.10
|
||
* Abhängigkeiten: `pandas`, `numpy`, `matplotlib`
|
||
* Setup:
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
---
|
||
|
||
## Logformat (Kettenöler)
|
||
|
||
Eine Zeile pro Frame:
|
||
|
||
```
|
||
<timestamp_ms> <TX|RX> 0x<ID_HEX> <DLC> <byte0> <byte1> ... <byte7>
|
||
```
|
||
|
||
Beispiel:
|
||
|
||
```
|
||
123456 RX 0x208 8 11 22 33 44 55 66 77 88
|
||
```
|
||
|
||
---
|
||
|
||
## Projekt-/Ordnerstruktur
|
||
|
||
Ein **Workdir** bündelt alles zu einem Fahrzeug/Projekt:
|
||
|
||
```
|
||
<Workdir>/
|
||
Projekt.json # GUI-Einstellungen
|
||
logs/ # Input-Logs
|
||
traces/ # per-ID .trace (vom Split)
|
||
analyze_out/ # Ergebnisse; je Run eigener Timestamp-Unterordner
|
||
```
|
||
|
||
**Namenskonventionen**
|
||
|
||
* Split-Ergebnisse: `traces/<timestamp?>/0x<ID>_<ursprungslog>.trace`
|
||
* Outputs: `analyze_out/<YYYYMMDD_HHMMSS>_<tool>/…`
|
||
|
||
---
|
||
|
||
## Modelle-Ordner & Git
|
||
|
||
Wenn du pro Modell arbeitest, z. B.:
|
||
|
||
```
|
||
models/
|
||
Triumph 2023/
|
||
logs/
|
||
traces/
|
||
analyze_out/
|
||
Projekt.json
|
||
```
|
||
|
||
Lege in `models/` folgende **`.gitignore`** ab, damit `traces/` und `analyze_out/` **in jedem Modell-Unterordner** ignoriert werden – `logs/` und `.json` bleiben versioniert:
|
||
|
||
```gitignore
|
||
*/traces/
|
||
*/traces/**
|
||
*/analyze_out/
|
||
*/analyze_out/**
|
||
|
||
traces/
|
||
traces/**
|
||
analyze_out/
|
||
analyze_out/**
|
||
|
||
# optional: typos
|
||
*/analyze.out/
|
||
*/analyze.out/**
|
||
analyze.out/
|
||
analyze.out/**
|
||
```
|
||
|
||
Leere Ordner wie `logs/` ggf. mit `.gitkeep` befüllen.
|
||
|
||
---
|
||
|
||
## GUI-Benutzung
|
||
|
||
```bash
|
||
python3 main.py
|
||
```
|
||
|
||
### Globaler Header (immer oben)
|
||
|
||
* **Workdir** wählen, **Logs scannen** → Liste aller gefundenen Logfiles (Multi-Select).
|
||
* Subfolder einstellen: `logs`, `traces`, `analyze_out` (alle **parallel** im Workdir).
|
||
* **Projekt speichern/laden** (`Projekt.json`).
|
||
* Beim Workdir-Wechsel/Projekt-Laden setzt die GUI den **aktiven Traces-Ordner** automatisch auf `traces/` bzw. den **jüngsten** Unterordner.
|
||
|
||
### Einheitliches Trace-Panel (in allen Trace-Tabs)
|
||
|
||
* Links: Liste der `.trace`
|
||
* Rechts: **Traces-Ordner wählen**, **Workdir/traces**, **Neuester Split**, **Refresh**, (optional **Alle**, **Keine**)
|
||
* Änderungen am Ordner/Liste wirken **sofort in allen Tabs**.
|
||
|
||
### Tab: Multi-Log Analyse
|
||
|
||
* Ranking direkt aus Logs (Include/Exclude-IDs, optional Range mit `scale/offset`).
|
||
* Output: `analyze_out/<ts>_multilog/…`
|
||
* Optional: „Jede Logdatei separat“ → je Log eigener Unterordner.
|
||
|
||
### Tab: ID Explorer
|
||
|
||
* **Split** (aus Header-Logauswahl): Logs → `.trace` nach `traces[/<ts>]`, plus `overview_ids.csv`.
|
||
Danach wird der neue Traces-Pfad **automatisch aktiviert**.
|
||
* **Einzel-ID Analyse** (Multi-Select):
|
||
|
||
* Plots: Byte\[0..7] (8-Bit) + LE/BE für Paare (0-1 … 6-7)
|
||
* `summary_stats.csv` pro Trace
|
||
* Output: `analyze_out/<ts>_id_explore/…`
|
||
|
||
### Tab: Traces Batch-Analyse
|
||
|
||
* Nutzt die gemeinsame Trace-Liste.
|
||
* **Ohne Auswahl** → kompletter Ordner; **mit Auswahl** → es wird ein Subset-Ordner gebaut (Hardlinks/Kopie) und nur dieses analysiert.
|
||
* Parameter: `--rx-only`, `scale`, `offset`, `range-min/max`, `top`, `--plots`.
|
||
* Output:
|
||
|
||
* je Trace: `*_combostats.csv` (+ Plots),
|
||
* global: `summary_top_combinations.csv`
|
||
* unter `analyze_out/<ts>_trace_batch/…`
|
||
|
||
### Tab: Range-Fit (Single-Select)
|
||
|
||
* **Zwei Modi**:
|
||
|
||
1. **Supervised** (Range-Min/Max gesetzt): findet `scale` & `offset`, maximiert **Hit-Ratio** im Zielbereich.
|
||
Output: `<trace>_encoding_candidates.csv` + phys-Plots (Top-N).
|
||
2. **Unsupervised** (Range leer): bewertet Kandidaten nach **Smoothness**, **Spannweite**, **Varianz**, **Rate**, **Uniqueness**.
|
||
Output: `<trace>_unsupervised_candidates.csv` + Roh-Plots (Top-N).
|
||
* Optionen: `nur RX`, `negative Scale erlauben` (nur supervised), `Min. Hit-Ratio`, `Min. Smoothness`, `Plots Top-N`, `Output-Label`.
|
||
* Output: `analyze_out/<ts>_rangefit/…`
|
||
|
||
---
|
||
|
||
## CLI-Quickstart
|
||
|
||
### 1) Splitten
|
||
|
||
```bash
|
||
python3 can_split_by_id.py logs/run1.log logs/run2.log \
|
||
--outdir <Workdir>/traces/20250827_1200 \
|
||
--rx-only
|
||
```
|
||
|
||
### 2) Einzel-ID-Explorer
|
||
|
||
```bash
|
||
python3 id_signal_explorer.py <Workdir>/traces/20250827_1200/0x208_run1.trace \
|
||
--outdir <Workdir>/analyze_out/20250827_1210_id_explore
|
||
```
|
||
|
||
### 3) Batch-Analyse
|
||
|
||
```bash
|
||
python3 trace_batch_analyzer.py \
|
||
--traces-dir <Workdir>/traces/20250827_1200 \
|
||
--outdir <Workdir>/analyze_out/20250827_1220_trace_batch \
|
||
--rx-only --plots --top 8 \
|
||
--range-min 31 --range-max 80
|
||
```
|
||
|
||
### 4) Range-/Unsupervised-Fit (eine `.trace`)
|
||
|
||
```bash
|
||
# Supervised (z. B. Kühlmittel 31..80°C)
|
||
python3 trace_signal_fitter.py <trace> \
|
||
--rmin 31 --rmax 80 \
|
||
--outdir <Workdir>/analyze_out/20250827_1230_rangefit \
|
||
--plots-top 8 --min-hit 0.5 --allow-neg-scale
|
||
|
||
# Unsupervised (ohne Range)
|
||
python3 trace_signal_fitter.py <trace> \
|
||
--outdir <Workdir>/analyze_out/20250827_1240_unsupervised \
|
||
--plots-top 8 --min-smooth 0.2
|
||
```
|
||
|
||
---
|
||
|
||
## Algorithmen & Heuristiken
|
||
|
||
* **Kombinationen**:
|
||
|
||
* 8-Bit: `D0..D7`
|
||
* 16-Bit (adjazent): LE & BE für Paare `(0,1)…(6,7)`
|
||
*(32-Bit & bit-gepackte Felder: auf der Roadmap)*
|
||
|
||
* **Prefilter** (für „ruhige“ physikalische Größen):
|
||
Mindestanzahl Samples, nicht (nahezu) konstant, keine exzessiven Sprünge (p95 der |Δ| relativ zur Spannweite).
|
||
|
||
* **Range-Fit**:
|
||
Für jeden Kandidaten `raw` wird über eine Menge plausibler **Scales** gesucht; für jedes `scale` wird das **Offset** via **Intervall-Überdeckung** bestimmt (`rmin ≤ scale*raw_i + offset ≤ rmax`). Ranking: Hit-Ratio ↓, dann Glattheit (p95 phys) ↑, Rate ↓, n ↓.
|
||
|
||
* **Unsupervised**:
|
||
**Smoothness** = `1 − clamp(p95(|Δ|)/span, 0..1)`; zusätzlich **span**, **var**, **rate**, **uniq\_ratio**. Ranking auf diese Metriken.
|
||
|
||
---
|
||
|
||
## Tipps & Troubleshooting
|
||
|
||
* **Keine Kandidaten (Range-Fit)**: `--min-hit` senken, `--allow-neg-scale` testen, Range prüfen, längeres/variableres Log nutzen.
|
||
* **Alles wird gefiltert (Unsupervised)**: `--min-smooth` senken; ggf. `--rx-only` aktivieren.
|
||
* **Leere/komische Plots**: DLC < 8 → teils keine 16-Bit-Kombis; Frames sehr selten → Rate niedrig.
|
||
* **Ordner stets sauber**: Zeitstempel-Unterordner aktiv lassen; pro Run eigene Artefakte.
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
* 32-Bit-Kombinationen, bit-gepackte Felder.
|
||
* Histogramme, Autokorrelation, Ausreißer-Detektoren.
|
||
* vordefinierte Signal-Profile (z. B. *WheelSpeed*, *CoolantTemp*).
|
||
|
||
---
|
||
|
||
## Lizenz / Haftung
|
||
|
||
Nur zu Analyse-/Reverse-Engineering-Zwecken. Nutzung auf eigene Verantwortung.
|