Extract reusable device protocols, ports and logic analyzers from SETGUI
This commit is contained in:
61
python/set_devices/can_log_trends.py
Normal file
61
python/set_devices/can_log_trends.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Convert ESP CAN485 logs into bounded, independently timestamped curves."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
|
||||
from protocan.legacycan import PROJECTS, LegacyCanFormat, decode
|
||||
|
||||
from .can485_board import BoardFrame, SdLogImport, parse_sd_log
|
||||
|
||||
|
||||
@dataclass
|
||||
class CanLogChannel:
|
||||
name: str
|
||||
address: int
|
||||
points: deque
|
||||
|
||||
|
||||
def _values(frame: BoardFrame):
|
||||
if frame.rtr:
|
||||
return
|
||||
prefix = f"{'EXT' if frame.extended else 'STD'} {frame.identifier:08X}"
|
||||
if frame.extended:
|
||||
for project in PROJECTS:
|
||||
if project.format is not LegacyCanFormat.ROTATING_THREE_WORDS:
|
||||
continue
|
||||
node = project.node_for(frame.identifier)
|
||||
if node is None:
|
||||
continue
|
||||
packet = decode(project.format, node, frame.identifier, frame.data)
|
||||
if packet is not None:
|
||||
for address, value in packet.present_values:
|
||||
# Keep both CAN directions and nodes in separate curves.
|
||||
yield (f"{prefix} · {node.name} · R{address:04X}", address, value)
|
||||
return
|
||||
# Unknown protocols stay explicitly raw; never guess a register layout.
|
||||
for offset in range(0, len(frame.data), 2):
|
||||
part = frame.data[offset:offset + 2]
|
||||
label = f"BE16 [{offset}:{offset + 1}]" if len(part) == 2 else f"байт [{offset}]"
|
||||
yield (f"{prefix} · {label}", offset, int.from_bytes(part, "big"))
|
||||
|
||||
|
||||
def read_can_log(lines, *, reference_timestamp: float, capacity: int,
|
||||
max_total_points: int = 1_000_000,
|
||||
frame_limit: int = 100_000) -> tuple[list[CanLogChannel], SdLogImport]:
|
||||
imported = parse_sd_log(lines, reference_timestamp, limit=frame_limit)
|
||||
channels = {}
|
||||
for frame in imported.frames:
|
||||
for name, address, _value in _values(frame):
|
||||
channels.setdefault(name, address)
|
||||
if len(channels) > 4096:
|
||||
raise ValueError("CAN CSV: слишком много каналов (максимум 4096)")
|
||||
if not channels:
|
||||
raise ValueError("CAN CSV: нет данных для графиков")
|
||||
capacity = max(1, min(capacity, max_total_points // len(channels)))
|
||||
result = {name: CanLogChannel(name, address, deque(maxlen=capacity))
|
||||
for name, address in channels.items()}
|
||||
for frame in imported.frames:
|
||||
for name, _address, value in _values(frame):
|
||||
result[name].points.append((frame.timestamp * 1000, value))
|
||||
return list(result.values()), imported
|
||||
Reference in New Issue
Block a user