sending chunk data, need to update everything to take 2 byte lengths
parent
bc2eb3125d
commit
84d24d93a2
224
script.js
224
script.js
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue