sending chunk data, need to update everything to take 2 byte lengths

node_mode
Jake 2025-09-28 18:24:17 +08:00
parent bc2eb3125d
commit 84d24d93a2
1 changed files with 118 additions and 106 deletions

224
script.js
View File

@ -2,6 +2,7 @@ window.onload = () => {
let isInterpolating = false; let isInterpolating = false;
let currentFrame = 0; let currentFrame = 0;
const dialKeyframes = Array.from({ length: 5 }, () => ({})); const dialKeyframes = Array.from({ length: 5 }, () => ({}));
let currentAnimation = null;
let port, reader, writer; let port, reader, writer;
let selectedDial = null; let selectedDial = null;
@ -88,7 +89,8 @@ window.onload = () => {
console.log(`✅ File ${file} loaded successfully`); console.log(`✅ File ${file} loaded successfully`);
console.log("Reassembled file bytes:", fileAssembly[file]); console.log("Reassembled file bytes:", fileAssembly[file]);
// TODO: trigger animation preview or playback // TODO: trigger animation preview or playback
parseAnimFile(fileAssembly[file]); currentAnimation = parseAnimFile(fileAssembly[file]);
console.log(currentAnimation);
} else { } else {
console.warn(`⚠️ Mismatch detected for ${file}`); console.warn(`⚠️ Mismatch detected for ${file}`);
} }
@ -169,7 +171,7 @@ window.onload = () => {
return { return {
header: { magic, version, frameCount, frameRate }, header: { magic, version, frameCount, frameRate },
frames frames, keyFrameCount, keyframes
}; };
} }
@ -248,8 +250,15 @@ window.onload = () => {
} }
function dispatchResponse({ command, payload }) { function dispatchResponse({ command, payload }) {
if (command === 0x05) { // CMD_LOAD_FILE_CHUNK // Notify any listeners waiting for "ok"
console.log("Raw chunk payload:", payload); if (payload && typeof payload === "object" && payload.status === "ok") {
console.log(command, payload);
console.log("Chunk acknowledged ✅");
return;
}
// Your existing logic...
if (command === 0x05) {
handleChunkResponse(payload); handleChunkResponse(payload);
} else if (pendingResponse && pendingResponse.commandCode === command) { } else if (pendingResponse && pendingResponse.commandCode === command) {
clearTimeout(pendingResponse.timeout); clearTimeout(pendingResponse.timeout);
@ -261,6 +270,7 @@ window.onload = () => {
} }
function tryParseBuffer() { function tryParseBuffer() {
const HEADER1 = 0xAA; const HEADER1 = 0xAA;
const HEADER2 = 0x55; const HEADER2 = 0x55;
@ -317,61 +327,6 @@ 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;
// 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 fileAssembly = {};
const fileStats = {}; const fileStats = {};
@ -391,6 +346,109 @@ window.onload = () => {
} }
function buildAnimationPayload({ header, frames, keyframes }) {
const frameCount = frames.length;
const channelCount = frames[0].length;
const frameDataSize = frameCount * channelCount * 2;
const keyframeCount = keyframes.length;
const keyframeBlockSize = 2 + keyframeCount * 5;
const totalSize = 16 + frameDataSize + keyframeBlockSize;
const buffer = new ArrayBuffer(totalSize);
const view = new DataView(buffer);
let offset = 0;
// 🔹 Header (16 bytes)
for (let i = 0; i < 4; i++) view.setUint8(offset++, header.magic.charCodeAt(i));
view.setUint16(offset, header.frameCount, true); offset += 2;
view.setUint8(offset++, header.version);
view.setUint8(offset++, header.frameRate);
for (let i = 0; i < 8; i++) view.setUint8(offset++, 0); // reserved
// 🔹 Frame Data (5000 bytes)
for (let i = 0; i < frameCount; i++) {
for (let j = 0; j < channelCount; j++) {
view.setUint16(offset, frames[i][j], true);
offset += 2;
}
}
// 🔹 Keyframes Block
view.setUint16(offset, keyframeCount, true); offset += 2;
keyframes.forEach(({ motorID, frame, position }) => {
view.setUint8(offset++, motorID);
view.setUint16(offset, frame, true); offset += 2;
view.setUint16(offset, position, true); offset += 2;
});
return buffer;
}
async function sendAnimationToESP32(port, commandCode, filename, currentAnimation, chunkSize = 256) {
const { header, frames, keyframes } = currentAnimation;
const payloadBuffer = buildAnimationPayload({ header, frames, keyframes });
const totalSize = payloadBuffer.byteLength;
const payloadArray = new Uint8Array(payloadBuffer);
for (let offset = 0; offset < totalSize; offset += chunkSize) {
const end = Math.min(offset + chunkSize, totalSize);
const chunkData = payloadArray.slice(offset, end);
const HEADER1 = 0xAA;
const HEADER2 = 0x55;
const offsetHigh = (offset >> 8) & 0xFF;
const offsetLow = offset & 0xFF;
const totalHigh = (totalSize >> 8) & 0xFF;
const totalLow = totalSize & 0xFF;
const length = 4 + chunkData.length; // offset(2) + total(2) + chunk
const lengthHigh = (length >> 8) & 0xFF;
const lengthLow = length & 0xFF;
const packet = new Uint8Array(5 + length + 1);
packet[0] = HEADER1;
packet[1] = HEADER2;
packet[2] = commandCode;
packet[3] = lengthHigh;
packet[4] = lengthLow;
packet[5] = offsetHigh;
packet[6] = offsetLow;
packet[7] = totalHigh;
packet[8] = totalLow;
packet.set(chunkData, 9);
let checksum = commandCode ^ lengthHigh ^ lengthLow ^ offsetHigh ^ offsetLow ^ totalHigh ^ totalLow;
for (let b of chunkData) checksum ^= b;
packet[5 + length] = checksum;
const writer = port.writable.getWriter();
await writer.write(packet);
writer.releaseLock();
console.log(`Sent chunk: ${filename} offset=${offset} size=${chunkData.length}`);
}
}
function waitForOkResponse(timeoutMs = 1000) {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
const index = okResponseQueue.indexOf(resolve);
if (index !== -1) okResponseQueue.splice(index, 1);
resolve(false);
}, timeoutMs);
okResponseQueue.push(() => {
clearTimeout(timeout);
resolve(true);
});
});
}
function flattenKeyframes(dialKeyframes) { function flattenKeyframes(dialKeyframes) {
const flat = []; const flat = [];
@ -659,54 +717,8 @@ window.onload = () => {
document.getElementById('saveAnimation').onclick = async () => { document.getElementById('saveAnimation').onclick = async () => {
if (!writer) { await sendAnimationToESP32(port, 0x05, "anim1.bin", currentAnimation);
alert("Serial not connected.");
return;
}
const buffer = new ArrayBuffer(totalFrames * 5 * 2);
const view = new DataView(buffer);
for (let frame = 0; frame < totalFrames; frame++) {
for (let ch = 0; ch < 5; ch++) {
const keyframes = dialKeyframes[ch];
let prevFrame = null, nextFrame = null;
for (let f = frame; f >= 0; f--) {
if (keyframes[f] !== undefined) {
prevFrame = f;
break;
}
}
for (let f = frame; f <= totalFrames - 1; f++) {
if (keyframes[f] !== undefined) {
nextFrame = f;
break;
}
}
let value;
if (prevFrame !== null && nextFrame !== null && prevFrame !== nextFrame) {
const prevVal = keyframes[prevFrame];
const nextVal = keyframes[nextFrame];
const t = (frame - prevFrame) / (nextFrame - prevFrame);
value = Math.round(prevVal + (nextVal - prevVal) * t);
} else if (prevFrame !== null) {
value = keyframes[prevFrame];
} else if (nextFrame !== null) {
value = keyframes[nextFrame];
} else {
value = 512;
}
view.setUint16((frame * 5 + ch) * 2, value, true);
}
}
await writer.write(buffer);
alert("Animation sent over serial.");
}; };
drawTimelineMarkers(); drawTimelineMarkers();