recieving file data, adding keyframes broken

node_mode
Jake 2025-09-28 14:58:52 +08:00
parent b30f9acefe
commit b4c6ad200a
1 changed files with 221 additions and 71 deletions

292
script.js
View File

@ -1,7 +1,8 @@
window.onload = () => { window.onload = () => {
let isInterpolating = false; let isInterpolating = false;
let currentFrame = 0; let currentFrame = 0;
let dialKeyframes = Array.from({ length: 5 }, () => ({})); const dialKeyframes = Array.from({ length: 5 }, () => ({}));
let port, reader, writer; let port, reader, writer;
let selectedDial = null; let selectedDial = null;
let draggingKeyframe = null; // { dialIndex, originalFrame } let draggingKeyframe = null; // { dialIndex, originalFrame }
@ -54,24 +55,129 @@ window.onload = () => {
} }
loadButton.addEventListener('click', () => { loadButton.addEventListener('click', () => {
if (selectedFile) { if (!selectedFile || loadButton.disabled) return;
console.log(`Loading file: ${selectedFile}`);
const encoder = new TextEncoder(); const filename = "/" + selectedFile;
const payload = Array.from(encoder.encode(selectedFile)); console.log(`Loading file: ${filename}`);
const CMD_LOAD_FILE = 0x03;
sendCommand(port, CMD_LOAD_FILE, payload) // 🧷 Lock buttons
.then(response => { loadButton.disabled = true;
console.log("File content received:", response); deleteButton.disabled = true;
// You can now parse and display response.payload.content
}) // 🧹 Clear previous state
.catch(err => { fileAssembly[filename] = null;
console.error("Failed to load file:", err); 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', () => { deleteButton.addEventListener('click', () => {
if (selectedFile) { if (selectedFile) {
console.log(`Deleting file: ${selectedFile}`); console.log(`Deleting file: ${selectedFile}`);
@ -145,7 +251,10 @@ window.onload = () => {
} }
function dispatchResponse({ command, payload }) { 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); clearTimeout(pendingResponse.timeout);
pendingResponse.resolve(payload); pendingResponse.resolve(payload);
pendingResponse = null; pendingResponse = null;
@ -154,11 +263,12 @@ window.onload = () => {
} }
} }
function tryParseBuffer() { function tryParseBuffer() {
const HEADER1 = 0xAA; const HEADER1 = 0xAA;
const HEADER2 = 0x55; const HEADER2 = 0x55;
while (byteBuffer.length >= 5) { while (byteBuffer.length >= 6) { // Minimum size with 2-byte length
// Look for header // Look for header
if (byteBuffer[0] !== HEADER1 || byteBuffer[1] !== HEADER2) { if (byteBuffer[0] !== HEADER1 || byteBuffer[1] !== HEADER2) {
byteBuffer.shift(); // discard until we find header byteBuffer.shift(); // discard until we find header
@ -166,19 +276,22 @@ window.onload = () => {
} }
const command = byteBuffer[2]; const command = byteBuffer[2];
const length = byteBuffer[3]; const lengthHigh = byteBuffer[3];
const totalLength = 4 + length + 1; const lengthLow = byteBuffer[4];
const length = (lengthHigh << 8) | lengthLow;
const totalLength = 5 + length + 1; // header + payload + checksum
if (byteBuffer.length < totalLength) { if (byteBuffer.length < totalLength) {
// Wait for more data // Wait for more data
return; return;
} }
const payloadBytes = byteBuffer.slice(4, 4 + length); const payloadBytes = byteBuffer.slice(5, 5 + length);
const checksum = byteBuffer[4 + length]; const checksum = byteBuffer[5 + length];
// Verify checksum // Verify checksum
let computedChecksum = command ^ length; let computedChecksum = command ^ lengthHigh ^ lengthLow;
for (let b of payloadBytes) { for (let b of payloadBytes) {
computedChecksum ^= b; 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; // function parseResponse(buffer) {
const HEADER2 = 0x55; // // Log raw response as array of integers
// const byteArray = Array.from(buffer);
// console.log("Raw response bytes:", byteArray);
if (buffer.length < 5) { // const HEADER1 = 0xAA;
console.warn("Response too short"); // const HEADER2 = 0x55;
return null;
// 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) { const chunkBytes = Uint8Array.from(chunk);
console.warn("Invalid header"); fileAssembly[file].set(chunkBytes, offset);
return null;
}
const command = buffer[2]; fileStats[file].chunks += 1;
const length = buffer[3]; fileStats[file].bytes += chunkBytes.length;
if (buffer.length < 4 + length + 1) { console.log(`Chunk received: ${file} offset=${offset} size=${chunkBytes.length}`);
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 };
} }
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 () => { document.getElementById('connect').addEventListener('click', async () => {
try { try {
const port = await navigator.serial.requestPort(); port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 }); await port.open({ baudRate: 115200 });
readLoop(port); // Start listening readLoop(port); // Start listening
@ -369,10 +518,11 @@ window.onload = () => {
}; };
document.getElementById('sendFrame').onclick = async () => { document.getElementById('sendFrame').onclick = async () => {
const positions = dials.map(d => Math.round(d.value)); // const positions = dials.map(d => Math.round(d.value));
const message = `FRAME ${positions.join(',')}\n`; // const message = `FRAME ${positions.join(',')}\n`;
const encoder = new TextEncoder(); // const encoder = new TextEncoder();
await writer.write(encoder.encode(message)); // await writer.write(encoder.encode(message));
console.log(dialKeyframes);
}; };
function drawTimelineMarkers() { function drawTimelineMarkers() {