Spaces:
Running
Running
Add motor control via WebRTC data channel
Browse files- Enable/Disable Motors buttons in the UI
- Motor status display
- Query motor mode when data channel opens
- Handle motor_mode responses from robot
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- index.html +63 -4
index.html
CHANGED
|
@@ -185,8 +185,19 @@
|
|
| 185 |
|
| 186 |
<!-- Control Panel -->
|
| 187 |
<div class="card">
|
| 188 |
-
<h2>3. Head Control</h2>
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
| 191 |
<div>
|
| 192 |
<label>Yaw (deg):</label>
|
|
@@ -238,6 +249,7 @@
|
|
| 238 |
window.stopStream = stopStream;
|
| 239 |
window.sendHeadPose = sendHeadPose;
|
| 240 |
window.centerHead = centerHead;
|
|
|
|
| 241 |
window.clearLog = clearLog;
|
| 242 |
|
| 243 |
// Initialize on page load
|
|
@@ -570,6 +582,8 @@
|
|
| 570 |
document.getElementById('stopStreamBtn').disabled = false;
|
| 571 |
document.getElementById('sendPoseBtn').disabled = false;
|
| 572 |
document.getElementById('centerBtn').disabled = false;
|
|
|
|
|
|
|
| 573 |
} else if (peerConnection.iceConnectionState === 'failed') {
|
| 574 |
updateWebrtcStatus('disconnected');
|
| 575 |
log('Connection failed', 'error');
|
|
@@ -579,9 +593,27 @@
|
|
| 579 |
peerConnection.ondatachannel = (event) => {
|
| 580 |
log(`Data channel: ${event.channel.label}`, 'success');
|
| 581 |
dataChannel = event.channel;
|
| 582 |
-
dataChannel.onopen = () =>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
dataChannel.onclose = () => log('Data channel closed');
|
| 584 |
-
dataChannel.onmessage = (e) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
};
|
| 586 |
|
| 587 |
log('Requesting session with robot...');
|
|
@@ -639,6 +671,9 @@
|
|
| 639 |
document.getElementById('stopStreamBtn').disabled = true;
|
| 640 |
document.getElementById('sendPoseBtn').disabled = true;
|
| 641 |
document.getElementById('centerBtn').disabled = true;
|
|
|
|
|
|
|
|
|
|
| 642 |
}
|
| 643 |
|
| 644 |
function updateWebrtcStatus(status) {
|
|
@@ -681,6 +716,30 @@
|
|
| 681 |
document.getElementById('pitchInput').value = 0;
|
| 682 |
sendHeadPose();
|
| 683 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
</script>
|
| 685 |
</body>
|
| 686 |
</html>
|
|
|
|
| 185 |
|
| 186 |
<!-- Control Panel -->
|
| 187 |
<div class="card">
|
| 188 |
+
<h2>3. Motor & Head Control</h2>
|
| 189 |
+
|
| 190 |
+
<p style="color: #888; font-size: 0.9em; margin-bottom: 10px;">Motor control (must enable before moving)</p>
|
| 191 |
+
<div class="controls" style="margin-bottom: 15px;">
|
| 192 |
+
<button id="enableMotorsBtn" onclick="setMotorMode('enabled')" disabled style="background: #00c853;">Enable Motors</button>
|
| 193 |
+
<button id="disableMotorsBtn" onclick="setMotorMode('disabled')" disabled style="background: #ff5252;">Disable Motors</button>
|
| 194 |
+
</div>
|
| 195 |
+
<div style="margin-bottom: 15px;">
|
| 196 |
+
<span>Motors: </span>
|
| 197 |
+
<span id="motorStatus" class="status disconnected">Unknown</span>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
<p style="color: #888; font-size: 0.9em;">Head pose commands</p>
|
| 201 |
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
| 202 |
<div>
|
| 203 |
<label>Yaw (deg):</label>
|
|
|
|
| 249 |
window.stopStream = stopStream;
|
| 250 |
window.sendHeadPose = sendHeadPose;
|
| 251 |
window.centerHead = centerHead;
|
| 252 |
+
window.setMotorMode = setMotorMode;
|
| 253 |
window.clearLog = clearLog;
|
| 254 |
|
| 255 |
// Initialize on page load
|
|
|
|
| 582 |
document.getElementById('stopStreamBtn').disabled = false;
|
| 583 |
document.getElementById('sendPoseBtn').disabled = false;
|
| 584 |
document.getElementById('centerBtn').disabled = false;
|
| 585 |
+
document.getElementById('enableMotorsBtn').disabled = false;
|
| 586 |
+
document.getElementById('disableMotorsBtn').disabled = false;
|
| 587 |
} else if (peerConnection.iceConnectionState === 'failed') {
|
| 588 |
updateWebrtcStatus('disconnected');
|
| 589 |
log('Connection failed', 'error');
|
|
|
|
| 593 |
peerConnection.ondatachannel = (event) => {
|
| 594 |
log(`Data channel: ${event.channel.label}`, 'success');
|
| 595 |
dataChannel = event.channel;
|
| 596 |
+
dataChannel.onopen = () => {
|
| 597 |
+
log('Data channel open', 'success');
|
| 598 |
+
// Query motor status when channel opens
|
| 599 |
+
dataChannel.send(JSON.stringify({ get_motor_mode: true }));
|
| 600 |
+
};
|
| 601 |
dataChannel.onclose = () => log('Data channel closed');
|
| 602 |
+
dataChannel.onmessage = (e) => {
|
| 603 |
+
try {
|
| 604 |
+
const data = JSON.parse(e.data);
|
| 605 |
+
if (data.motor_mode) {
|
| 606 |
+
updateMotorStatus(data.motor_mode);
|
| 607 |
+
log(`Motor mode: ${data.motor_mode}`, 'info');
|
| 608 |
+
} else if (data.error) {
|
| 609 |
+
log(`Error: ${data.error}`, 'error');
|
| 610 |
+
} else {
|
| 611 |
+
log(`Received: ${e.data}`);
|
| 612 |
+
}
|
| 613 |
+
} catch {
|
| 614 |
+
log(`Received: ${e.data}`);
|
| 615 |
+
}
|
| 616 |
+
};
|
| 617 |
};
|
| 618 |
|
| 619 |
log('Requesting session with robot...');
|
|
|
|
| 671 |
document.getElementById('stopStreamBtn').disabled = true;
|
| 672 |
document.getElementById('sendPoseBtn').disabled = true;
|
| 673 |
document.getElementById('centerBtn').disabled = true;
|
| 674 |
+
document.getElementById('enableMotorsBtn').disabled = true;
|
| 675 |
+
document.getElementById('disableMotorsBtn').disabled = true;
|
| 676 |
+
updateMotorStatus('unknown');
|
| 677 |
}
|
| 678 |
|
| 679 |
function updateWebrtcStatus(status) {
|
|
|
|
| 716 |
document.getElementById('pitchInput').value = 0;
|
| 717 |
sendHeadPose();
|
| 718 |
}
|
| 719 |
+
|
| 720 |
+
// Motor Control
|
| 721 |
+
function setMotorMode(mode) {
|
| 722 |
+
if (!dataChannel || dataChannel.readyState !== 'open') {
|
| 723 |
+
log('Data channel not ready', 'error');
|
| 724 |
+
return;
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
dataChannel.send(JSON.stringify({ set_motor_mode: mode }));
|
| 728 |
+
log(`Setting motor mode to: ${mode}`);
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
function updateMotorStatus(mode) {
|
| 732 |
+
const el = document.getElementById('motorStatus');
|
| 733 |
+
if (!el) return;
|
| 734 |
+
el.textContent = mode.charAt(0).toUpperCase() + mode.slice(1);
|
| 735 |
+
if (mode === 'enabled') {
|
| 736 |
+
el.className = 'status connected';
|
| 737 |
+
} else if (mode === 'disabled') {
|
| 738 |
+
el.className = 'status disconnected';
|
| 739 |
+
} else {
|
| 740 |
+
el.className = 'status connecting';
|
| 741 |
+
}
|
| 742 |
+
}
|
| 743 |
</script>
|
| 744 |
</body>
|
| 745 |
</html>
|