dashboard: add passive CAN capture markers
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rank passive CAN bytes against timestamped scan-tool coolant readings."""
|
||||
|
||||
import argparse
|
||||
import math
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def correlation(xs, ys):
|
||||
if len(xs) < 3 or len(set(xs)) < 2 or len(set(ys)) < 2:
|
||||
return 0.0
|
||||
xm, ym = sum(xs) / len(xs), sum(ys) / len(ys)
|
||||
numerator = sum((x - xm) * (y - ym) for x, y in zip(xs, ys))
|
||||
scale = math.sqrt(sum((x - xm) ** 2 for x in xs) * sum((y - ym) ** 2 for y in ys))
|
||||
return numerator / scale if scale else 0.0
|
||||
|
||||
|
||||
def parse_capture(path):
|
||||
frames = defaultdict(list)
|
||||
temperatures = []
|
||||
for line in path.read_text(errors="replace").splitlines():
|
||||
parts = [part.strip() for part in line.split(",")]
|
||||
try:
|
||||
if len(parts) >= 3 and parts[0] == "TEMP":
|
||||
temperatures.append((int(parts[1]), float(parts[2])))
|
||||
elif len(parts) >= 6 and parts[0] == "CAN":
|
||||
timestamp, can_id, dlc = int(parts[1]), parts[2].upper(), int(parts[4])
|
||||
for index, value in enumerate(parts[5 : 5 + min(dlc, 8)]):
|
||||
if value:
|
||||
frames[(can_id, index)].append((timestamp, int(value, 16)))
|
||||
except ValueError:
|
||||
continue
|
||||
return frames, sorted(temperatures)
|
||||
|
||||
|
||||
def value_at(samples, timestamp):
|
||||
latest = None
|
||||
for sample_time, value in samples:
|
||||
if sample_time > timestamp:
|
||||
break
|
||||
latest = value
|
||||
return latest
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Rank CAN bytes against TEMP markers.")
|
||||
parser.add_argument("capture", type=Path)
|
||||
parser.add_argument("--limit", type=int, default=20)
|
||||
args = parser.parse_args()
|
||||
frames, temperatures = parse_capture(args.capture)
|
||||
if len(temperatures) < 3:
|
||||
parser.error("capture needs at least three TEMP markers")
|
||||
|
||||
ranked = []
|
||||
for (can_id, byte_index), samples in frames.items():
|
||||
pairs = [(value_at(samples, timestamp), temp) for timestamp, temp in temperatures]
|
||||
pairs = [(value, temp) for value, temp in pairs if value is not None]
|
||||
values, temps = [p[0] for p in pairs], [p[1] for p in pairs]
|
||||
score = correlation(values, temps)
|
||||
if len(values) >= 3 and len(set(values)) >= 2:
|
||||
ranked.append((abs(score), score, can_id, byte_index, len(values), min(values), max(values)))
|
||||
|
||||
ranked.sort(reverse=True)
|
||||
print("rank id byte corr samples raw-range")
|
||||
for rank, (_, score, can_id, byte_index, count, low, high) in enumerate(ranked[:args.limit], 1):
|
||||
print(f"{rank:>4} 0x{can_id:<4} {byte_index:>4} {score:>+7.3f} {count:>7} {low:>3}..{high:<3}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user