added Function to create CAN-Traces from WebUI
Some checks failed
CI-Build/Kettenoeler/pipeline/head There was a failure building this commit

This commit is contained in:
2025-08-26 23:31:35 +02:00
parent c8c67551fd
commit 98629b744d
10 changed files with 1011 additions and 123 deletions

View File

@@ -23,4 +23,4 @@ document
var fileName = document.getElementById("fw-update-file").files[0].name;
var nextSibling = e.target.nextElementSibling;
nextSibling.innerText = fileName;
});
});

View File

@@ -5,6 +5,106 @@ var statusMapping;
var staticMapping;
var overlay;
let traceActive = false;
let traceMode = null;
let traceFileName = "";
let traceUseFsAccess = false;
// File-System-Access-Stream (Chromium)
let traceWriter = null;
let traceEncoder = null;
let traceWriteQueue = Promise.resolve(); // für geordnete Writes
// Fallback: In-Memory-Sammeln (für Blob-Download bei STOP)
let traceMemParts = [];
// Textarea & Status
const TRACE_MAX_CHARS = 200000; // ~200 KB für die Anzeige
function $(id) {
return document.getElementById(id);
}
function $(id) {
return document.getElementById(id);
}
function nowIsoCompact() {
return new Date().toISOString().replace(/[:.]/g, "-");
}
function genTraceFileName(mode) {
return `cantrace-${mode}-${nowIsoCompact()}.log`;
}
function setTraceUI(active, mode, infoText) {
traceActive = !!active;
traceMode = active ? mode : null;
const btnRaw = $("trace-start");
const btnObd = $("trace-start-obd");
const btnStop = $("trace-stop");
const status = $("trace-status");
if (btnRaw) btnRaw.disabled = active;
if (btnObd) btnObd.disabled = active;
if (btnStop) btnStop.disabled = !active;
if (status)
status.textContent =
infoText || (active ? `Trace aktiv (${mode})` : "Trace inaktiv");
}
function traceClear() {
const out = $("trace-out");
if (out) out.value = "";
}
function traceAppend(text) {
const out = $("trace-out");
if (!out || !text) return;
out.value += text;
if (out.value.length > TRACE_MAX_CHARS) {
out.value = out.value.slice(-TRACE_MAX_CHARS);
}
out.scrollTop = out.scrollHeight;
}
function triggerBlobDownload(filename, blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
}
function parseKv(s) {
const out = Object.create(null);
s.split(";").forEach((part) => {
const eq = part.indexOf("=");
if (eq > 0) {
const k = part.slice(0, eq).trim();
const v = part.slice(eq + 1).trim();
if (k) out[k] = v;
}
});
return out;
}
// geordnete Writes auf File System Access Writer
function writeToFs(chunk) {
if (!traceUseFsAccess || !traceWriter) return;
const data = traceEncoder
? traceEncoder.encode(chunk)
: new TextEncoder().encode(chunk);
traceWriteQueue = traceWriteQueue
.then(() => traceWriter.write(data))
.catch(console.error);
}
document.addEventListener("DOMContentLoaded", function () {
// Ihr JavaScript-Code hier, einschließlich der onLoad-Funktion
overlay = document.getElementById("overlay");
@@ -45,16 +145,32 @@ function initSettingInputs() {
function onOpen(event) {
console.log("Connection opened");
setTraceUI(false, null, "Verbunden Trace inaktiv");
}
function onClose(event) {
console.log("Connection closed");
setTimeout(initWebSocket, 1000);
overlay.style.display = "flex";
// Falls Trace noch aktiv war: lokal finalisieren
if (traceActive) {
const note = "Trace beendet (Verbindung getrennt)";
if (traceUseFsAccess && traceWriter) {
traceWriteQueue.then(() => traceWriter.close()).catch(console.error);
traceWriter = null;
} else if (traceMemParts.length) {
const blob = new Blob(traceMemParts, { type: "text/plain" });
triggerBlobDownload(traceFileName || "cantrace.log", blob);
traceMemParts = [];
}
setTraceUI(false, null, note);
showNotification(note, "warning");
}
}
function sendButton(event) {
var targetElement = event.target;
async function sendButton(event) {
const targetElement = event.target;
if (
targetElement.classList.contains("confirm") &&
@@ -62,7 +178,46 @@ function sendButton(event) {
)
return;
websocket_sendevent("btn-" + targetElement.id, targetElement.value);
const wsid = targetElement.dataset.wsid || targetElement.id; // z.B. "trace-start"
const val = targetElement.value || "";
// File-Ziel *vor* dem WS-Start öffnen (nur bei trace-start; wegen User-Gesture!)
if (wsid === "trace-start") {
const mode = val || "raw";
traceFileName = genTraceFileName(mode);
// Anzeige schon mal leeren
traceClear();
setTraceUI(false, null, "Trace wird gestartet…");
traceUseFsAccess = false;
traceWriter = null;
traceEncoder = null;
traceWriteQueue = Promise.resolve();
traceMemParts = []; // Fallback-Puffer leeren
if (window.showSaveFilePicker) {
try {
const fh = await showSaveFilePicker({
suggestedName: traceFileName,
types: [
{
description: "Text Log",
accept: { "text/plain": [".log", ".txt"] },
},
],
});
traceWriter = await fh.createWritable();
traceEncoder = new TextEncoder();
traceUseFsAccess = true;
} catch (e) {
// Nutzer hat evtl. abgebrochen → Fallback in RAM
traceUseFsAccess = false;
}
}
}
websocket_sendevent("btn-" + wsid, val);
}
function onMessage(event) {
@@ -101,6 +256,58 @@ function onMessage(event) {
fillValuesToHTML(result);
overlay.style.display = "none";
}
// --- Trace: Start ---
else if (data.startsWith("STARTTRACE;")) {
const kv = parseKv(data.slice(11)); // mode=..., ts=...
const mode = kv.mode || "?";
setTraceUI(true, mode, `Trace gestartet (${mode})`);
// Fallback: wenn kein FS-Access → in RAM sammeln
// (sonst haben wir traceWriter bereits im Klick vorbereitet)
}
// --- Trace: Lines (ggf. mehrere in einer WS-Nachricht) ---
else if (data.startsWith("TRACELINE;")) {
const payload = data.replace(/TRACELINE;/g, ""); // reiner Text inkl. '\n'
traceAppend(payload);
if (traceUseFsAccess && traceWriter) {
writeToFs(payload);
} else {
traceMemParts.push(payload);
}
}
// --- Trace: Stop/Summary ---
else if (data.startsWith("STOPTRACE;")) {
const kv = parseKv(data.slice(10));
const msg = `Trace beendet (${kv.mode || "?"}), Zeilen=${
kv.lines || "0"
}, Drops=${kv.drops || "0"}${kv.reason ? ", Grund=" + kv.reason : ""}`;
// Datei finalisieren
if (traceUseFsAccess && traceWriter) {
traceWriteQueue.then(() => traceWriter.close()).catch(console.error);
traceWriter = null;
} else if (traceMemParts.length) {
const blob = new Blob(traceMemParts, { type: "text/plain" });
triggerBlobDownload(traceFileName || "cantrace.log", blob);
traceMemParts = [];
}
setTraceUI(false, null, msg);
showNotification(msg, "info");
}
// --- Busy/Fehler/Ack ---
else if (data.startsWith("TRACEBUSY;")) {
const kv = parseKv(data.slice(10));
const owner = kv.owner ? " (Owner #" + kv.owner + ")" : "";
showNotification("Trace bereits aktiv" + owner, "warning");
} else if (data.startsWith("TRACEERROR;")) {
const kv = parseKv(data.slice(11));
showNotification("Trace-Fehler: " + (kv.msg || "unbekannt"), "danger");
} else if (data.startsWith("TRACEACK;")) {
// optional
const kv = parseKv(data.slice(9));
console.log("TRACEACK", kv);
}
}
function createMapping(mappingString) {