From b4c6ad200ab91eec135ce14dbc1f61439218fe2e Mon Sep 17 00:00:00 2001 From: Jake Date: Sun, 28 Sep 2025 14:58:52 +0800 Subject: [PATCH] recieving file data, adding keyframes broken --- script.js | 292 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 221 insertions(+), 71 deletions(-) diff --git a/script.js b/script.js index 1279cbb..70eace4 100644 --- a/script.js +++ b/script.js @@ -1,7 +1,8 @@ window.onload = () => { let isInterpolating = false; let currentFrame = 0; - let dialKeyframes = Array.from({ length: 5 }, () => ({})); + const dialKeyframes = Array.from({ length: 5 }, () => ({})); + let port, reader, writer; let selectedDial = null; let draggingKeyframe = null; // { dialIndex, originalFrame } @@ -54,24 +55,129 @@ window.onload = () => { } loadButton.addEventListener('click', () => { - if (selectedFile) { - console.log(`Loading file: ${selectedFile}`); + if (!selectedFile || loadButton.disabled) return; - const encoder = new TextEncoder(); - const payload = Array.from(encoder.encode(selectedFile)); - const CMD_LOAD_FILE = 0x03; + const filename = "/" + selectedFile; + console.log(`Loading file: ${filename}`); - sendCommand(port, CMD_LOAD_FILE, payload) - .then(response => { - console.log("File content received:", response); - // You can now parse and display response.payload.content - }) - .catch(err => { - console.error("Failed to load file:", err); - }); - } + // ๐Ÿงท Lock buttons + loadButton.disabled = true; + deleteButton.disabled = true; + + // ๐Ÿงน Clear previous state + fileAssembly[filename] = null; + fileStats[filename] = { chunks: 0, bytes: 0 }; + + const encoder = new TextEncoder(); + const payload = Array.from(encoder.encode(filename)); + const CMD_LOAD_FILE = 0x03; + + sendCommand(port, CMD_LOAD_FILE, payload) + .then(response => { + const { file, status, chunks, bytesSent } = response; + const stats = fileStats[file]; + + if (status === "complete" && stats) { + const chunkMatch = stats.chunks === chunks; + const byteMatch = stats.bytes === bytesSent; + + console.log(`Chunks match: ${chunkMatch} (${stats.chunks} vs ${chunks})`); + console.log(`Bytes match: ${byteMatch} (${stats.bytes} vs ${bytesSent})`); + + if (chunkMatch && byteMatch) { + console.log(`โœ… File ${file} loaded successfully`); + console.log("Reassembled file bytes:", fileAssembly[file]); + // TODO: trigger animation preview or playback + parseAnimFile(fileAssembly[file]); + } else { + console.warn(`โš ๏ธ Mismatch detected for ${file}`); + } + } else { + console.warn("Unexpected final response:", response); + } + }) + .catch(err => { + console.error("Failed to load file:", err); + }) + .finally(() => { + // ๐Ÿ”“ Unlock buttons + loadButton.disabled = false; + deleteButton.disabled = false; + }); }); + + function parseAnimFile(buffer) { + console.log("decoding anim file"); + const view = new DataView(buffer.buffer); + let offset = 0; + + // 1. Parse header + const magic = String.fromCharCode(...buffer.slice(offset, offset + 4)); + offset += 4; + + const frameCount = view.getUint16(4, true); + const version = view.getUint8(6); + const frameRate = view.getUint8(7); + + offset += 8; // reserved + + if (magic !== "ANIM" || version !== 1) { + throw new Error("Invalid animation file"); + } + + console.log("Version: " + version); + console.log("Framerate: " + frameRate); + console.log("Frame Count: " + frameCount); + + // 2. Parse frame data + const frames = []; + for (let frame = 0; frame < frameCount; frame++) { + const positions = []; + for (let ch = 0; ch < 5; ch++) { + positions.push(view.getUint16(offset, true)); + offset += 2; + } + frames.push(positions); + } + + // 3. Parse keyframe count + const keyframeCount = view.getUint16(offset, true); offset += 2; + + + + //const dialKeyframes = Array.from({ length: 5 }, () => ({})); + console.log("keyframecount:" + keyframeCount, offset); + for (let i = 0; i < keyframeCount; i++) { + console.log(view.getUint8(offset++)); + console.log(view.getUint16(offset, true)); + offset += 2; + console.log(view.getUint16(offset, true)); + offset += 2; + + + // const motorId = view.getUint8(offset++); + // const frame = view.getUint16(offset, true); offset += 2; + // const position = view.getUint16(offset, true); offset += 2; + // console.log({motorId, frame, position}); + + // if (motorId >= 0 && motorId < dialKeyframes.length) { + // dialKeyframes[motorId][frame] = position; + // } else { + // console.warn(`Invalid motorId: ${motorId}`); + // } + } + console.log(dialKeyframes); + + return { + header: { magic, version, frameCount, frameRate }, + frames, + dialKeyframes + }; + } + + + deleteButton.addEventListener('click', () => { if (selectedFile) { console.log(`Deleting file: ${selectedFile}`); @@ -145,7 +251,10 @@ window.onload = () => { } function dispatchResponse({ command, payload }) { - if (pendingResponse && pendingResponse.commandCode === command) { + if (command === 0x05) { // CMD_LOAD_FILE_CHUNK + console.log("Raw chunk payload:", payload); + handleChunkResponse(payload); + } else if (pendingResponse && pendingResponse.commandCode === command) { clearTimeout(pendingResponse.timeout); pendingResponse.resolve(payload); pendingResponse = null; @@ -154,11 +263,12 @@ window.onload = () => { } } + function tryParseBuffer() { const HEADER1 = 0xAA; const HEADER2 = 0x55; - while (byteBuffer.length >= 5) { + while (byteBuffer.length >= 6) { // Minimum size with 2-byte length // Look for header if (byteBuffer[0] !== HEADER1 || byteBuffer[1] !== HEADER2) { byteBuffer.shift(); // discard until we find header @@ -166,19 +276,22 @@ window.onload = () => { } const command = byteBuffer[2]; - const length = byteBuffer[3]; - const totalLength = 4 + length + 1; + const lengthHigh = byteBuffer[3]; + const lengthLow = byteBuffer[4]; + const length = (lengthHigh << 8) | lengthLow; + + const totalLength = 5 + length + 1; // header + payload + checksum if (byteBuffer.length < totalLength) { // Wait for more data return; } - const payloadBytes = byteBuffer.slice(4, 4 + length); - const checksum = byteBuffer[4 + length]; + const payloadBytes = byteBuffer.slice(5, 5 + length); + const checksum = byteBuffer[5 + length]; // Verify checksum - let computedChecksum = command ^ length; + let computedChecksum = command ^ lengthHigh ^ lengthLow; for (let b of payloadBytes) { computedChecksum ^= b; } @@ -207,60 +320,96 @@ window.onload = () => { } - function parseResponse(buffer) { - // Log raw response as array of integers - const byteArray = Array.from(buffer); - console.log("Raw response bytes:", byteArray); - const HEADER1 = 0xAA; - const HEADER2 = 0x55; + // function parseResponse(buffer) { + // // Log raw response as array of integers + // const byteArray = Array.from(buffer); + // console.log("Raw response bytes:", byteArray); - if (buffer.length < 5) { - console.warn("Response too short"); - return null; + // const HEADER1 = 0xAA; + // const HEADER2 = 0x55; + + // if (buffer.length < 5) { + // console.warn("Response too short"); + // return null; + // } + + // if (buffer[0] !== HEADER1 || buffer[1] !== HEADER2) { + // console.warn("Invalid header"); + // return null; + // } + + // const command = buffer[2]; + // const length = buffer[3]; + + // if (buffer.length < 4 + length + 1) { + // console.warn("Incomplete payload"); + // return null; + // } + + // const payloadBytes = buffer.slice(4, 4 + length); + // const checksum = buffer[4 + length]; + + // let computedChecksum = command ^ length; + // for (let i = 0; i < payloadBytes.length; i++) { + // computedChecksum ^= payloadBytes[i]; + // } + + // if (checksum !== computedChecksum) { + // console.warn("Checksum mismatch"); + // return null; + // } + + // const payloadText = new TextDecoder().decode(payloadBytes); + + // let payload; + // try { + // payload = JSON.parse(payloadText); + // } catch (err) { + // console.warn("Failed to parse JSON:", payloadText); + // payload = payloadText; + // } + + // return { command, payload }; + // } + + + + const fileAssembly = {}; + const fileStats = {}; + + function handleChunkResponse({ file, offset, totalSize, chunk }) { + if (!fileAssembly[file]) { + fileAssembly[file] = new Uint8Array(totalSize); + fileStats[file] = { chunks: 0, bytes: 0 }; } - if (buffer[0] !== HEADER1 || buffer[1] !== HEADER2) { - console.warn("Invalid header"); - return null; - } + const chunkBytes = Uint8Array.from(chunk); + fileAssembly[file].set(chunkBytes, offset); - const command = buffer[2]; - const length = buffer[3]; + fileStats[file].chunks += 1; + fileStats[file].bytes += chunkBytes.length; - if (buffer.length < 4 + length + 1) { - console.warn("Incomplete payload"); - return null; - } - - const payloadBytes = buffer.slice(4, 4 + length); - const checksum = buffer[4 + length]; - - let computedChecksum = command ^ length; - for (let i = 0; i < payloadBytes.length; i++) { - computedChecksum ^= payloadBytes[i]; - } - - if (checksum !== computedChecksum) { - console.warn("Checksum mismatch"); - return null; - } - - const payloadText = new TextDecoder().decode(payloadBytes); - - let payload; - try { - payload = JSON.parse(payloadText); - } catch (err) { - console.warn("Failed to parse JSON:", payloadText); - payload = payloadText; - } - - return { command, payload }; + console.log(`Chunk received: ${file} offset=${offset} size=${chunkBytes.length}`); } + function flattenKeyframes(dialKeyframes) { + const flat = []; + + dialKeyframes.forEach((frameMap, motorId) => { + Object.entries(frameMap).forEach(([frameStr, position]) => { + flat.push({ + motorId, + frame: parseInt(frameStr), + position + }); + }); + }); + + return flat; + } @@ -343,7 +492,7 @@ window.onload = () => { document.getElementById('connect').addEventListener('click', async () => { try { - const port = await navigator.serial.requestPort(); + port = await navigator.serial.requestPort(); await port.open({ baudRate: 115200 }); readLoop(port); // Start listening @@ -369,10 +518,11 @@ window.onload = () => { }; document.getElementById('sendFrame').onclick = async () => { - const positions = dials.map(d => Math.round(d.value)); - const message = `FRAME ${positions.join(',')}\n`; - const encoder = new TextEncoder(); - await writer.write(encoder.encode(message)); + // const positions = dials.map(d => Math.round(d.value)); + // const message = `FRAME ${positions.join(',')}\n`; + // const encoder = new TextEncoder(); + // await writer.write(encoder.encode(message)); + console.log(dialKeyframes); }; function drawTimelineMarkers() {