sync option implemented, motors move with dials and timeline shifts
parent
5bd92fe30d
commit
42e6a93f6b
87
index.html
87
index.html
|
|
@ -13,61 +13,62 @@
|
|||
<button id="connect">Connect</button>
|
||||
<button id="disconnect" hidden>Disconnect</button>
|
||||
</div>
|
||||
<button id="sendFrame">Send Frame</button>
|
||||
<div id="connectionStatus">
|
||||
<span>Status: <strong id="statusText">Disconnected</strong></span>
|
||||
|
||||
|
||||
|
||||
<div class="dial-container">
|
||||
<div class="dial" data-index="0"><label>Motor 1</label>
|
||||
<div id="dial0"></div><span id="value0">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="1"><label>Motor 2</label>
|
||||
<div id="dial1"></div><span id="value1">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="2"><label>Motor 3</label>
|
||||
<div id="dial2"></div><span id="value2">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="3"><label>Motor 4</label>
|
||||
<div id="dial3"></div><span id="value3">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="4"><label>Motor 5</label>
|
||||
<div id="dial4"></div><span id="value4">512</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<canvas id="timelineCanvas" width="800" height="30"></canvas>
|
||||
|
||||
<div>
|
||||
<label>Frame: <span id="frameDisplay">0</span></label><br>
|
||||
<input type="range" id="frameSlider" min="0" max="399" value="0" style="width: 80%">
|
||||
</div>
|
||||
|
||||
<label for="filenameInput">Filename:</label>
|
||||
<input type="text" id="filenameInput" />
|
||||
<button id="saveAnimation">Save Animation</button>
|
||||
|
||||
<div id="fileListWrapper">
|
||||
<div id="fileListHeader">
|
||||
<span>Animations</span>
|
||||
<div id="fileActions">
|
||||
<button id="loadFile" disabled>Load</button>
|
||||
<button id="deleteFile" disabled>Delete</button>
|
||||
<div class="dial-container">
|
||||
<div class="dial" data-index="0"><label>Motor 1</label>
|
||||
<div id="dial0"></div><span id="value0">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="1"><label>Motor 2</label>
|
||||
<div id="dial1"></div><span id="value1">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="2"><label>Motor 3</label>
|
||||
<div id="dial2"></div><span id="value2">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="3"><label>Motor 4</label>
|
||||
<div id="dial3"></div><span id="value3">512</span>
|
||||
</div>
|
||||
<div class="dial" data-index="4"><label>Motor 5</label>
|
||||
<div id="dial4"></div><span id="value4">512</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="fileList"></ul>
|
||||
</div>
|
||||
<label>
|
||||
<input type="checkbox" id="syncCheckbox"> Sync
|
||||
</label>
|
||||
|
||||
<canvas id="timelineCanvas" width="800" height="30"></canvas>
|
||||
|
||||
<div>
|
||||
<label>Frame: <span id="frameDisplay">0</span></label><br>
|
||||
<input type="range" id="frameSlider" min="0" max="399" value="0" style="width: 80%">
|
||||
</div>
|
||||
|
||||
<label for="filenameInput">Filename:</label>
|
||||
<input type="text" id="filenameInput" />
|
||||
<button id="saveAnimation">Save Animation</button>
|
||||
|
||||
<div id="fileListWrapper">
|
||||
<div id="fileListHeader">
|
||||
<span>Animations</span>
|
||||
<div id="fileActions">
|
||||
<button id="loadFile" disabled>Load</button>
|
||||
<button id="deleteFile" disabled>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="fileList"></ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<textarea id="log" rows="10" cols="60" readonly></textarea><br>
|
||||
<input type="text" id="input" placeholder="Type message here">
|
||||
<button id="send">Send</button>
|
||||
<textarea id="log" rows="10" cols="60" readonly></textarea><br>
|
||||
<input type="text" id="input" placeholder="Type message here">
|
||||
<button id="send">Send</button>
|
||||
|
||||
<script type="module" src="script.js"></script>
|
||||
<script type="module" src="script.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
|
|
|||
93
script.js
93
script.js
|
|
@ -6,6 +6,11 @@ window.onload = () => {
|
|||
const statusText = document.getElementById('statusText');
|
||||
const disconnectBtn = document.getElementById('disconnect');
|
||||
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 currentFrame = 0;
|
||||
|
|
@ -81,9 +86,8 @@ window.onload = () => {
|
|||
deleteButton.addEventListener('click', () => {
|
||||
if (selectedFile) {
|
||||
console.log(`Deleting file: ${selectedFile}`);
|
||||
// Add logic to send delete command to ESP32
|
||||
sendDeleteToESP32();
|
||||
|
||||
// Remove from UI
|
||||
const selectedLi = fileListElement.querySelector('.selected');
|
||||
if (selectedLi) selectedLi.remove();
|
||||
selectedFile = null;
|
||||
|
|
@ -98,6 +102,7 @@ window.onload = () => {
|
|||
|
||||
|
||||
|
||||
|
||||
// Timeline
|
||||
|
||||
frameSlider.oninput = () => {
|
||||
|
|
@ -142,6 +147,11 @@ window.onload = () => {
|
|||
|
||||
drawTimelineMarkers();
|
||||
isInterpolating = false;
|
||||
|
||||
|
||||
syncMotorsWithTimeline();
|
||||
|
||||
|
||||
};
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
|
|
@ -160,8 +170,42 @@ window.onload = () => {
|
|||
document.getElementById(`value${i}`).textContent = val;
|
||||
dialKeyframes[i][currentFrame] = val;
|
||||
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 => {
|
||||
el.onclick = () => {
|
||||
selectedDial = parseInt(el.dataset.index);
|
||||
|
|
@ -208,6 +252,14 @@ window.onload = () => {
|
|||
// 🔹 Do something with the file
|
||||
handleLoadedFile(fileData);
|
||||
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
|
||||
|
||||
console.log(`Saved file Response Recieved`);
|
||||
|
|
@ -228,6 +280,14 @@ window.onload = () => {
|
|||
// 🔹 Do something with the file
|
||||
//handleLoadedFile(fileData);
|
||||
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
|
||||
default:
|
||||
document.getElementById('log').value += `Unknown command ${command}\n`;
|
||||
|
|
@ -419,6 +479,27 @@ window.onload = () => {
|
|||
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
|
||||
|
|
@ -439,14 +520,6 @@ window.onload = () => {
|
|||
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() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const width = canvas.width;
|
||||
|
|
|
|||
10
serial.js
10
serial.js
|
|
@ -9,6 +9,7 @@ const CMD_FILE_LIST = 0x02;
|
|||
const CMD_LOAD_FILE = 0x03;
|
||||
const CMD_SAVE_FILE = 0x05;
|
||||
const CMD_DELETE_FILE = 0x04;
|
||||
const CMD_SET_POSITION = 0x07;
|
||||
|
||||
export class SerialManager {
|
||||
constructor() {
|
||||
|
|
@ -62,6 +63,15 @@ export class SerialManager {
|
|||
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) {
|
||||
const decoder = new TextDecoder();
|
||||
|
|
|
|||
Loading…
Reference in New Issue