has 5 motors, time slider, can insert keyframes but not remove or move

node_mode
Jake 2025-09-27 18:52:11 +08:00
commit 92cf94c02b
1 changed files with 229 additions and 0 deletions

229
index.html Normal file
View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 Animation Creator</title>
<script src="https://unpkg.com/nexusui"></script>
<style>
body {
font-family: sans-serif;
text-align: center;
}
.dial-container {
display: flex;
justify-content: center;
gap: 30px;
margin: 20px 0;
}
.dial {
display: flex;
flex-direction: column;
align-items: center;
}
textarea {
margin-top: 20px;
}
</style>
</head>
<body>
<h2>ESP32 Animation Creator</h2>
<button id="connect">Connect</button>
<button id="sendFrame">Send Frame</button>
<div class="dial-container">
<div class="dial">
<label>Motor 1</label>
<div id="dial0"></div>
<span id="value0">512</span>
</div>
<div class="dial">
<label>Motor 2</label>
<div id="dial1"></div>
<span id="value1">512</span>
</div>
<div class="dial">
<label>Motor 3</label>
<div id="dial2"></div>
<span id="value2">512</span>
</div>
<div class="dial">
<label>Motor 4</label>
<div id="dial3"></div>
<span id="value3">512</span>
</div>
<div class="dial">
<label>Motor 5</label>
<div id="dial4"></div>
<span id="value4">512</span>
</div>
</div>
<canvas id="timelineCanvas" width="800" height="30" style="margin-bottom: 5px;"></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>
<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>
let isInterpolating = false;
let currentFrame = 0;
let dialKeyframes = Array.from({ length: 5 }, () => ({}));
const frameSlider = document.getElementById('frameSlider');
const frameDisplay = document.getElementById('frameDisplay');
frameSlider.oninput = () => {
currentFrame = parseInt(frameSlider.value);
frameDisplay.textContent = currentFrame;
isInterpolating = true;
for (let ch = 0; ch < 5; ch++) {
const keyframes = dialKeyframes[ch];
let prevFrame = null, nextFrame = null;
for (let f = currentFrame; f >= 0; f--) {
if (keyframes[f] !== undefined) {
prevFrame = f;
break;
}
}
for (let f = currentFrame; f <= 399; 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 = (currentFrame - 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;
}
dials[ch].value = value;
document.getElementById(`value${ch}`).textContent = value;
}
drawTimelineMarkers();
isInterpolating = false;
};
let port, reader, writer;
const dials = [];
// Initialize rotary dials
for (let i = 0; i < 5; i++) {
dials[i] = new Nexus.Dial(`#dial${i}`, {
size: [80, 80],
min: 0,
max: 1023,
value: 512
});
// Update display when dial changes
dials[i].on('change', (v) => {
if (isInterpolating) return;
const val = Math.round(v);
document.getElementById(`value${i}`).textContent = val;
dialKeyframes[i][currentFrame] = val;
drawTimelineMarkers();
});
}
// Connect to ESP32
document.getElementById('connect').onclick = async () => {
port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
writer = port.writable.getWriter();
reader = port.readable.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
document.getElementById('log').value += decoder.decode(value);
}
};
// Send typed message
document.getElementById('send').onclick = async () => {
const text = document.getElementById('input').value + '\n';
const encoder = new TextEncoder();
await writer.write(encoder.encode(text));
document.getElementById('input').value = '';
};
// Send current dial positions as a frame
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));
};
const canvas = document.getElementById('timelineCanvas');
const ctx = canvas.getContext('2d');
const totalFrames = 400;
function drawTimelineMarkers() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const width = canvas.width;
const height = canvas.height;
// Draw keyframe ticks for all motors
for (let ch = 0; ch < 5; ch++) {
for (let frame in dialKeyframes[ch]) {
const x = (frame / 400) * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.strokeStyle = ['red', 'green', 'blue', 'orange', 'purple'][ch];
ctx.lineWidth = 1;
ctx.stroke();
}
}
// Draw current frame marker
const currentX = (currentFrame / 400) * width;
ctx.beginPath();
ctx.moveTo(currentX, 0);
ctx.lineTo(currentX, height);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
</script>
</body>
</html>