mirror of
https://kevinblog.sytes.net/Code/Jibo-Revival-Group/Re-Commander.git
synced 2026-06-16 12:36:02 +00:00
Robot Ip input & cache
This commit is contained in:
220
public/animator.js
Normal file
220
public/animator.js
Normal file
@@ -0,0 +1,220 @@
|
||||
'use strict';
|
||||
|
||||
// ── WebSocket to server ──────────────────────────────────────────────────────
|
||||
|
||||
let ws;
|
||||
let connected = false;
|
||||
let sessionActive = false;
|
||||
let videoActive = false;
|
||||
|
||||
function connectWS() {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
ws = new WebSocket(`${proto}://${location.host}/ws`);
|
||||
|
||||
ws.onopen = () => {
|
||||
connected = true;
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
connected = false;
|
||||
setStatus(false, 'Disconnected — reconnecting…');
|
||||
setTimeout(connectWS, 2000);
|
||||
};
|
||||
|
||||
ws.onerror = () => {};
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
let msg;
|
||||
try { msg = JSON.parse(e.data); } catch { return; }
|
||||
handleServerMessage(msg);
|
||||
};
|
||||
}
|
||||
|
||||
function handleServerMessage(msg) {
|
||||
if (msg.type === 'status') {
|
||||
sessionActive = !!msg.sessionID;
|
||||
setStatus(msg.connected && sessionActive,
|
||||
msg.connected
|
||||
? (sessionActive ? 'Connected • ' + msg.sessionID.slice(0, 8) + '…' : 'Connecting…')
|
||||
: 'Disconnected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === 'jiboEvent') {
|
||||
handleJiboEvent(msg.body, msg.txId);
|
||||
}
|
||||
}
|
||||
|
||||
function handleJiboEvent(body, txId) {
|
||||
if (!body) return;
|
||||
const evt = body.Event || body.ResponseString || '?';
|
||||
logEvent(evt, body, txId);
|
||||
}
|
||||
|
||||
// ── REST helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function api(method, path, body) {
|
||||
try {
|
||||
const opts = { method, headers: { 'Content-Type': 'application/json' } };
|
||||
if (body) opts.body = JSON.stringify(body);
|
||||
const res = await fetch(path, opts);
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
logEvent('api-error: ' + path, { error: err.message }, null);
|
||||
}
|
||||
}
|
||||
|
||||
const post = (path, body) => api('POST', path, body);
|
||||
const get = (path) => api('GET', path);
|
||||
|
||||
// ── Status ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function setStatus(ok, label) {
|
||||
const dot = document.getElementById('status-dot');
|
||||
const lbl = document.getElementById('status-label');
|
||||
dot.className = ok ? 'ok' : '';
|
||||
lbl.textContent = label;
|
||||
}
|
||||
|
||||
// ── Animation Control ────────────────────────────────────────────────────────
|
||||
|
||||
document.getElementById('btn-play-anim').addEventListener('click', () => {
|
||||
const name = document.getElementById('anim-select').value;
|
||||
if (name) {
|
||||
post('/api/display/anim', { name });
|
||||
logEvent('Animation Played', { animation: name }, null);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btn-play-sequence').addEventListener('click', () => {
|
||||
const sequence = document.getElementById('sequence-list').value
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0);
|
||||
|
||||
if (sequence.length === 0) {
|
||||
alert('Please enter at least one animation');
|
||||
return;
|
||||
}
|
||||
|
||||
const repeatCount = parseInt(document.getElementById('repeat-count').value) || 1;
|
||||
const delay = parseInt(document.getElementById('anim-delay').value) || 500;
|
||||
|
||||
post('/api/display/anim-sequence', { sequence, repeatCount, delay });
|
||||
logEvent('Sequence Started', { count: sequence.length, repeats: repeatCount }, null);
|
||||
});
|
||||
|
||||
document.getElementById('btn-stop-sequence').addEventListener('click', () => {
|
||||
post('/api/cancel', {});
|
||||
logEvent('Sequence Stopped', {}, null);
|
||||
});
|
||||
|
||||
// ── Camera / Video ────────────────────────────────────────────────────────────
|
||||
|
||||
let videoTxId = null;
|
||||
|
||||
document.getElementById('btn-video-start').addEventListener('click', async () => {
|
||||
const r = await post('/api/video/start', { duration: 0 });
|
||||
if (r) videoTxId = r.txId;
|
||||
document.getElementById('video-status').textContent = 'Waiting for VideoReady…';
|
||||
});
|
||||
|
||||
document.getElementById('btn-video-stop').addEventListener('click', () => {
|
||||
post('/api/video/stop', {});
|
||||
stopVideoFeed();
|
||||
});
|
||||
|
||||
function startVideoFeed(uri) {
|
||||
const feed = document.getElementById('camera-feed');
|
||||
const noFeed = document.getElementById('camera-no-feed');
|
||||
feed.src = '/proxy/stream?uri=' + encodeURIComponent(uri);
|
||||
feed.style.display = 'block';
|
||||
noFeed.style.display = 'none';
|
||||
videoActive = true;
|
||||
}
|
||||
|
||||
function stopVideoFeed() {
|
||||
const feed = document.getElementById('camera-feed');
|
||||
feed.src = '';
|
||||
feed.style.display = 'none';
|
||||
document.getElementById('camera-no-feed').style.display = '';
|
||||
videoActive = false;
|
||||
}
|
||||
|
||||
// ── Take Photo ────────────────────────────────────────────────────────────────
|
||||
|
||||
document.getElementById('btn-photo').addEventListener('click', () => {
|
||||
post('/api/photo', {
|
||||
camera: 'right',
|
||||
resolution: document.getElementById('photo-res').value
|
||||
});
|
||||
});
|
||||
|
||||
function addPhoto(url) {
|
||||
const strip = document.getElementById('photo-strip');
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.title = url;
|
||||
img.addEventListener('click', () => openPhotoModal(img.src));
|
||||
strip.prepend(img);
|
||||
}
|
||||
|
||||
// ── Photo modal ───────────────────────────────────────────────────────────────
|
||||
|
||||
function openPhotoModal(src) {
|
||||
document.getElementById('photo-modal-img').src = src;
|
||||
document.getElementById('photo-modal').classList.add('open');
|
||||
}
|
||||
|
||||
document.getElementById('photo-modal').addEventListener('click', (e) => {
|
||||
if (e.target === e.currentTarget || e.target.id === 'photo-modal-close') {
|
||||
document.getElementById('photo-modal').classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
// ── Tabs ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
document.getElementById(this.dataset.tab).classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Event log ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const MAX_LOG = 200;
|
||||
|
||||
function logEvent(eventName, body, txId) {
|
||||
const log = document.getElementById('event-log');
|
||||
const now = new Date().toLocaleTimeString();
|
||||
|
||||
let cls = 'evt';
|
||||
if (eventName.toLowerCase().includes('error')) cls = 'evt-error';
|
||||
|
||||
let detail = '';
|
||||
if (body.animation) detail = ' ' + body.animation;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'log-entry';
|
||||
el.innerHTML = `<span class="ts">${now}</span> <span class="${cls}">${eventName}</span>${detail}`;
|
||||
log.prepend(el);
|
||||
|
||||
while (log.children.length > MAX_LOG) log.removeChild(log.lastChild);
|
||||
}
|
||||
|
||||
document.getElementById('btn-clear-log').addEventListener('click', () => {
|
||||
document.getElementById('event-log').innerHTML = '';
|
||||
});
|
||||
|
||||
// ── Back to home ──────────────────────────────────────────────────────────────
|
||||
|
||||
document.getElementById('btn-back-home')?.addEventListener('click', () => {
|
||||
window.location.href = 'index.html';
|
||||
});
|
||||
|
||||
// ── Initialization ────────────────────────────────────────────────────────────
|
||||
|
||||
connectWS();
|
||||
Reference in New Issue
Block a user