sync option implemented, motors move with dials and timeline shifts

node_mode
Jake 2025-09-29 23:04:45 +08:00
parent 5bd92fe30d
commit 42e6a93f6b
4 changed files with 142 additions and 55 deletions

View File

@ -13,7 +13,6 @@
<button id="connect">Connect</button> <button id="connect">Connect</button>
<button id="disconnect" hidden>Disconnect</button> <button id="disconnect" hidden>Disconnect</button>
</div> </div>
<button id="sendFrame">Send Frame</button>
<div id="connectionStatus"> <div id="connectionStatus">
<span>Status: <strong id="statusText">Disconnected</strong></span> <span>Status: <strong id="statusText">Disconnected</strong></span>
@ -36,7 +35,9 @@
<div id="dial4"></div><span id="value4">512</span> <div id="dial4"></div><span id="value4">512</span>
</div> </div>
</div> </div>
<label>
<input type="checkbox" id="syncCheckbox"> Sync
</label>
<canvas id="timelineCanvas" width="800" height="30"></canvas> <canvas id="timelineCanvas" width="800" height="30"></canvas>
@ -45,8 +46,8 @@
<input type="range" id="frameSlider" min="0" max="399" value="0" style="width: 80%"> <input type="range" id="frameSlider" min="0" max="399" value="0" style="width: 80%">
</div> </div>
<label for="filenameInput">Filename:</label> <label for="filenameInput">Filename:</label>
<input type="text" id="filenameInput" /> <input type="text" id="filenameInput" />
<button id="saveAnimation">Save Animation</button> <button id="saveAnimation">Save Animation</button>
<div id="fileListWrapper"> <div id="fileListWrapper">

View File

@ -6,6 +6,11 @@ window.onload = () => {
const statusText = document.getElementById('statusText'); const statusText = document.getElementById('statusText');
const disconnectBtn = document.getElementById('disconnect'); const disconnectBtn = document.getElementById('disconnect');
const connectBtn = document.getElementById('connect'); const connectBtn = document.getElementById('connect');
const syncCheckbox = document.getElementById("syncCheckbox");
// Limits rate of move commands sent while sliding timeslider
let lastSyncTime = 0; // global or outer-scope variable
const syncIntervalMs = 20; // e.g. 100ms = max 10 times per second
let isInterpolating = false; let isInterpolating = false;
let currentFrame = 0; let currentFrame = 0;
@ -81,9 +86,8 @@ window.onload = () => {
deleteButton.addEventListener('click', () => { deleteButton.addEventListener('click', () => {
if (selectedFile) { if (selectedFile) {
console.log(`Deleting file: ${selectedFile}`); console.log(`Deleting file: ${selectedFile}`);
// Add logic to send delete command to ESP32 sendDeleteToESP32();
// Remove from UI
const selectedLi = fileListElement.querySelector('.selected'); const selectedLi = fileListElement.querySelector('.selected');
if (selectedLi) selectedLi.remove(); if (selectedLi) selectedLi.remove();
selectedFile = null; selectedFile = null;
@ -98,6 +102,7 @@ window.onload = () => {
// Timeline // Timeline
frameSlider.oninput = () => { frameSlider.oninput = () => {
@ -142,6 +147,11 @@ window.onload = () => {
drawTimelineMarkers(); drawTimelineMarkers();
isInterpolating = false; isInterpolating = false;
syncMotorsWithTimeline();
}; };
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
@ -160,8 +170,42 @@ window.onload = () => {
document.getElementById(`value${i}`).textContent = val; document.getElementById(`value${i}`).textContent = val;
dialKeyframes[i][currentFrame] = val; dialKeyframes[i][currentFrame] = val;
drawTimelineMarkers(); drawTimelineMarkers();
syncMotorsWithTimeline();
}); });
} }
function syncMotorsWithTimeline() {
const now = Date.now();
if (syncCheckbox.checked && now - lastSyncTime >= syncIntervalMs) {
lastSyncTime = now;
const motorPayloads = [];
for (let ch = 0; ch < dials.length; ch++) {
const value = dials[ch].value;
motorPayloads.push({ motorId: ch, position: value });
}
const buffer = new ArrayBuffer(motorPayloads.length * 3);
const view = new DataView(buffer);
motorPayloads.forEach(({ motorId, position }, i) => {
const offset = i * 3;
view.setUint8(offset, motorId);
view.setUint16(offset + 1, position, true); // little-endian
});
const payload = new Uint8Array(buffer);
serial.sendSetPositions(payload);
}
}
document.querySelectorAll('.dial').forEach(el => { document.querySelectorAll('.dial').forEach(el => {
el.onclick = () => { el.onclick = () => {
selectedDial = parseInt(el.dataset.index); selectedDial = parseInt(el.dataset.index);
@ -208,6 +252,14 @@ window.onload = () => {
// 🔹 Do something with the file // 🔹 Do something with the file
handleLoadedFile(fileData); handleLoadedFile(fileData);
break; break;
case 0x04: // CMD_DELETE_FILE
console.log(`File deleted`);
console.log(new Uint8Array(payload));
document.getElementById('log').value += `File deleted\n`;
// 🔹 Do something with the file
//handleLoadedFile(fileData);
break;
case 0x05: // CMD_SAVE_FILE case 0x05: // CMD_SAVE_FILE
console.log(`Saved file Response Recieved`); console.log(`Saved file Response Recieved`);
@ -228,6 +280,14 @@ window.onload = () => {
// 🔹 Do something with the file // 🔹 Do something with the file
//handleLoadedFile(fileData); //handleLoadedFile(fileData);
break; break;
case 0x07: // CMD_SET_POSITION
console.log(`Positions set`);
console.log(new Uint8Array(payload));
document.getElementById('log').value += `Positions set\n`;
// 🔹 Do something with the file
//handleLoadedFile(fileData);
break;
// Add more cases as needed // Add more cases as needed
default: default:
document.getElementById('log').value += `Unknown command ${command}\n`; document.getElementById('log').value += `Unknown command ${command}\n`;
@ -419,6 +479,27 @@ window.onload = () => {
serial.saveFile(payload); // CMD_SAVE_ANIMATION serial.saveFile(payload); // CMD_SAVE_ANIMATION
} }
async function sendDeleteToESP32() {
if (!selectedFile) return;
const filename = selectedFile;
console.log("Sanitized filename for delete:", filename);
const filenameBytes = new TextEncoder().encode(filename);
const filenameLength = filenameBytes.length;
// Total size: 2 bytes for length + filename bytes
const buffer = new ArrayBuffer(2 + filenameLength);
const view = new DataView(buffer);
let offset = 0;
// 🔹 Filename block
view.setUint16(offset, filenameLength, true); offset += 2;
filenameBytes.forEach(byte => view.setUint8(offset++, byte));
const payload = new Uint8Array(buffer);
serial.deleteFile(payload); // CMD_DELETE_FILE
}
// Disconnect button // Disconnect button
@ -439,14 +520,6 @@ window.onload = () => {
document.getElementById('input').value = ''; document.getElementById('input').value = '';
}; };
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));
console.log(dialKeyframes);
};
function drawTimelineMarkers() { function drawTimelineMarkers() {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
const width = canvas.width; const width = canvas.width;

View File

@ -9,6 +9,7 @@ const CMD_FILE_LIST = 0x02;
const CMD_LOAD_FILE = 0x03; const CMD_LOAD_FILE = 0x03;
const CMD_SAVE_FILE = 0x05; const CMD_SAVE_FILE = 0x05;
const CMD_DELETE_FILE = 0x04; const CMD_DELETE_FILE = 0x04;
const CMD_SET_POSITION = 0x07;
export class SerialManager { export class SerialManager {
constructor() { constructor() {
@ -62,6 +63,15 @@ export class SerialManager {
await this.send(CMD_SAVE_FILE, payload); await this.send(CMD_SAVE_FILE, payload);
} }
async deleteFile(payload) {
console.log("Deleting File: " + "filename");
await this.send(CMD_DELETE_FILE, payload);
}
async sendSetPositions(payload){
console.log("Setting positions");
await this.send(CMD_SET_POSITION, payload);
}
startReading(onPacket) { startReading(onPacket) {
const decoder = new TextDecoder(); const decoder = new TextDecoder();

3
todo.md Normal file
View File

@ -0,0 +1,3 @@
Add play animation command
play combined animations
play combined animations with masks