recieving file data, adding keyframes broken
parent
b30f9acefe
commit
b4c6ad200a
276
script.js
276
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,23 +55,128 @@ window.onload = () => {
|
|||
}
|
||||
|
||||
loadButton.addEventListener('click', () => {
|
||||
if (selectedFile) {
|
||||
console.log(`Loading file: ${selectedFile}`);
|
||||
if (!selectedFile || loadButton.disabled) return;
|
||||
|
||||
const filename = "/" + selectedFile;
|
||||
console.log(`Loading file: ${filename}`);
|
||||
|
||||
// 🧷 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(selectedFile));
|
||||
const payload = Array.from(encoder.encode(filename));
|
||||
const CMD_LOAD_FILE = 0x03;
|
||||
|
||||
sendCommand(port, CMD_LOAD_FILE, payload)
|
||||
.then(response => {
|
||||
console.log("File content received:", response);
|
||||
// You can now parse and display response.payload.content
|
||||
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) {
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue