recieving file data, adding keyframes broken
parent
b30f9acefe
commit
b4c6ad200a
292
script.js
292
script.js
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue