added Function to create CAN-Traces from WebUI
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				CI-Build/Kettenoeler/pipeline/head There was a failure building this commit
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	CI-Build/Kettenoeler/pipeline/head There was a failure building this commit
				
			This commit is contained in:
		| @@ -23,4 +23,4 @@ document | ||||
|     var fileName = document.getElementById("fw-update-file").files[0].name; | ||||
|     var nextSibling = e.target.nextElementSibling; | ||||
|     nextSibling.innerText = fileName; | ||||
|   }); | ||||
|   }); | ||||
| @@ -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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user