// 全局变量 let availableInterfaces = []; let interfaceStatus = {}; let handConfigs = {}; // 存储每个手的配置 let handTypeIds = { 'left': 0x28, // HAND_TYPE_LEFT 'right': 0x27 // HAND_TYPE_RIGHT }; // 主要控制模块 const LinkerHandController = { // 常量定义 DEFAULTS: { FINGER: { OPEN: 64, // 完全张开值 CLOSED: 192, // 完全闭合值 NEUTRAL: 128 // 中间值 }, PALM: { NEUTRAL: 128, // 中间值 LEFT: 48, // 左侧 RIGHT: 208 // 右侧 }, ANIMATION: { DEFAULT_SPEED: 500 // 默认动画速度 } }, // 预设姿势配置 PRESETS: { FIST: [64, 64, 64, 64, 64, 64], // 握拳 OPEN: [192, 192, 192, 192, 192, 192], // 张开 THUMBSUP: [255, 255, 0, 0, 0, 0], // 竖起大拇指 POINT: [0, 0, 255, 0, 0, 0], // 食指指点 YO: [255, 255, 255, 0, 0, 255], // Yo! GUN: [255, 255, 255, 255, 0, 0], // PONG! WAVE: [40, 60, 80, 100, 120, 140], // 波浪形 PALM_LEFT: [48, 48, 48, 48], // 掌部左移 PALM_RIGHT: [208, 208, 208, 208], // 掌部右移 PALM_NEUTRAL: [128, 128, 128, 128], // 掌部中立 PALM_GUN: [0, 0, 0, 128], // 掌部 GUN PINCH: [114, 63, 136, 0, 0, 0], // 捏取姿势 PALM_PINCH: [255, 163, 255, 127], OK: [124, 31, 132, 255, 255, 255], PALM_OK: [255, 163, 255, 127], BIG_FIST: [49, 32, 40, 36, 41, 46], // 大握拳 PALM_BIG_FIST: [255, 235, 128, 128], // 大握拳掌部 BIG_OPEN: [255, 255, 255, 255, 255, 255], // 大张开 PALM_BIG_OPEN: [128, 128, 128, 128], // 大张开掌部 YEAH: [0, 103, 255, 255, 0, 0], // Yeah! PALM_YEAH: [255, 235, 128, 128], // Yeah! 掌部 // 数字手势预设 ONE: [0, 57, 255, 0, 0, 0], PALM_ONE: [255, 109, 255, 118], TWO: [0, 57, 255, 255, 0, 0], PALM_TWO: [255, 109, 255, 118], THREE: [0, 57, 255, 255, 255, 0], PALM_THREE: [255, 109, 255, 118], FOUR: [0, 57, 255, 255, 255, 255], PALM_FOUR: [255, 109, 255, 118], FIVE: [255, 255, 255, 255, 255, 255], PALM_FIVE: [255, 109, 255, 118], SIX: [255, 255, 0, 0, 0, 255], PALM_SIX: [255, 255, 255, 255], SEVEN: [110, 137, 130, 109, 0, 0], PALM_SEVEN: [255, 200, 199, 76], EIGHT: [216, 240, 255, 36, 41, 46], PALM_EIGHT: [106, 200, 199, 76], NINE: [0, 255, 159, 0, 0, 0], PALM_NINE: [255, 38, 195, 51] }, // 防抖函数 debounce: function (func, delay) { let timer; return function () { clearTimeout(timer); timer = setTimeout(func, delay); }; }, // 初始化滑块显示与实时控制发送(带防抖) initSliderDisplays: function () { const fingerSliders = Array.from({ length: 6 }, (_, i) => document.getElementById(`finger${i}`)); const palmSliders = Array.from({ length: 4 }, (_, i) => document.getElementById(`palm${i}`)); const delayDefault = 30; const updateFingerPose = this.debounce(() => { const pose = this.getFingerPoseValues(); this.sendFingerPoseToAll(pose); }, delayDefault); const updatePalmPose = this.debounce(() => { const pose = this.getPalmPoseValues(); this.sendPalmPoseToAll(pose); }, delayDefault); // 初始化手指滑块监听器 fingerSliders.forEach((slider, i) => { slider.addEventListener('input', () => { document.getElementById(`finger${i}-value`).textContent = slider.value; updateFingerPose(); }); }); // 初始化掌部滑块监听器 palmSliders.forEach((slider, i) => { slider.addEventListener('input', () => { document.getElementById(`palm${i}-value`).textContent = slider.value; updatePalmPose(); }); }); // 动画速度滑块更新 const animationSlider = document.getElementById('animation-speed'); animationSlider.addEventListener('input', function () { document.getElementById('speed-value').textContent = this.value; }); }, // 获取手指姿态值 getFingerPoseValues: function () { const pose = []; for (let i = 0; i < 6; i++) { pose.push(parseInt(document.getElementById(`finger${i}`).value)); } return pose; }, // 获取掌部姿态值 getPalmPoseValues: function () { const pose = []; for (let i = 0; i < 4; i++) { pose.push(parseInt(document.getElementById(`palm${i}`).value)); } return pose; }, // 设置手指滑块值 applyFingerPreset: function (values) { if (!Array.isArray(values) || values.length !== 6) { logMessage('error', '无效的手指预设值'); return; } // 设置滑块值 for (let i = 0; i < 6; i++) { const slider = document.getElementById(`finger${i}`); slider.value = values[i]; document.getElementById(`finger${i}-value`).textContent = values[i]; } logMessage('info', '已应用手指预设姿势'); }, // 设置掌部滑块值 applyPalmPreset: function (values) { if (!Array.isArray(values) || values.length !== 4) { logMessage('error', '无效的掌部预设值'); return; } // 设置滑块值 for (let i = 0; i < 4; i++) { const slider = document.getElementById(`palm${i}`); slider.value = values[i]; document.getElementById(`palm${i}-value`).textContent = values[i]; } logMessage('info', '已应用掌部预设姿势'); }, // 发送手指姿态到所有启用手部 sendFingerPoseToAll: function (pose) { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } logMessage('info', `发送手指姿态到 ${enabledHands.length} 个启用的手部: [${pose.join(', ')}]`); enabledHands.forEach(async (config) => { await sendFingerPoseToHand(config, pose); }); }, // 发送掌部姿态到所有启用手部 sendPalmPoseToAll: function (pose) { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } logMessage('info', `发送掌部姿态到 ${enabledHands.length} 个启用的手部:[${pose.join(', ')}]`); enabledHands.forEach(async (config) => { await sendPalmPoseToHand(config, pose); }); }, // 启动传感器数据轮询 startSensorDataPolling: function () { // 立即获取一次数据 this.fetchSensorData(); // 设置定时获取 setInterval(() => { this.fetchSensorData(); }, 2000); // 每 2 秒更新一次 }, // 获取传感器数据 fetchSensorData: function () { fetch('/api/sensors') .then(response => response.json()) .then(data => { if (data.status === 'success') { this.updateSensorDisplay(data.data); } }) .catch(error => { console.error('获取传感器数据失败:', error); }); }, // 更新传感器显示 updateSensorDisplay: function (data) { const sensorDisplay = document.getElementById('sensor-data'); if (!sensorDisplay || !data) return; // 创建进度条显示 let html = ''; // 手指压力传感器 html += this.createSensorRow('拇指压力', data.thumb); html += this.createSensorRow('食指压力', data.index); html += this.createSensorRow('中指压力', data.middle); html += this.createSensorRow('无名指压力', data.ring); html += this.createSensorRow('小指压力', data.pinky); html += '
'; // 更新最后更新时间 const lastUpdate = new Date(data.lastUpdate).toLocaleTimeString(); html += `
最后更新:${lastUpdate}
`; sensorDisplay.innerHTML = html; }, // 创建传感器行 createSensorRow: function (label, value) { if (value === undefined || value === null) value = 0; return ` ${label} ${value}% `; } }; // 页面加载时初始化 document.addEventListener('DOMContentLoaded', function() { initializeSystem(); setupEventListeners(); setupSliderEvents(); LinkerHandController.initSliderDisplays(); LinkerHandController.startSensorDataPolling(); startStatusUpdater(); }); // 初始化系统 - 添加更详细的错误处理和调试 async function initializeSystem() { try { logMessage('info', '开始初始化系统...'); // 步骤 1: 加载可用接口 logMessage('info', '步骤 1/3: 加载可用接口'); await loadAvailableInterfaces(); // 验证接口加载是否成功 if (!availableInterfaces || availableInterfaces.length === 0) { throw new Error('未能获取到任何可用接口'); } // 步骤 2: 生成手部配置 logMessage('info', '步骤 2/3: 生成手部配置'); generateHandConfigs(); // 验证手部配置是否成功 if (!handConfigs || Object.keys(handConfigs).length === 0) { throw new Error('未能生成手部配置'); } // 步骤 3: 检查接口状态 logMessage('info', '步骤 3/3: 检查接口状态'); await checkAllInterfaceStatus(); logMessage('success', '系统初始化完成'); } catch (error) { logMessage('error', `系统初始化失败:${error.message}`); console.error('InitializeSystem Error:', error); // 尝试使用默认配置恢复 if (!availableInterfaces || availableInterfaces.length === 0) { logMessage('info', '尝试使用默认配置恢复...'); availableInterfaces = ['can0', 'can1', 'vcan0', 'vcan1']; generateHandConfigs(); } } } // 加载可用接口 async function loadAvailableInterfaces() { try { logMessage('info', '正在获取可用 CAN 接口...'); const response = await fetch('/api/interfaces'); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.status === 'success') { availableInterfaces = data.data.availableInterfaces || []; logMessage('success', `获取到 ${availableInterfaces.length} 个可用接口:${availableInterfaces.join(', ')}`); hideConnectionWarning(); } else { throw new Error(data.error || '获取接口失败'); } } catch (error) { logMessage('error', `获取接口失败:${error.message}`); showConnectionWarning(); // 设置默认值 availableInterfaces = ['can0', 'can1', 'vcan0', 'vcan1']; } } // 生成手部配置 - 添加调试和错误处理 function generateHandConfigs() { const handsGrid = document.getElementById('hands-grid'); if (!handsGrid) { console.error('Hands grid element not found'); logMessage('error', '无法找到手部配置容器'); return; } // 清空现有内容 handsGrid.innerHTML = ''; if (!availableInterfaces || availableInterfaces.length === 0) { handsGrid.innerHTML = '
没有可用的 CAN 接口
'; logMessage('warning', '没有可用接口,无法生成手部配置'); return; } logMessage('info', `为 ${availableInterfaces.length} 个接口生成手部配置...`); // 清空现有配置 handConfigs = {}; // 为每个接口创建配置项 availableInterfaces.forEach((iface, index) => { const handId = `hand_${iface}`; try { // 创建默认配置 handConfigs[handId] = { id: handId, interface: iface, handType: index % 2 === 0 ? 'right' : 'left', // 交替默认左右手 enabled: index < 2, // 默认启用前两个 status: 'offline' }; // 创建HTML元素 const handElement = createHandElement(handConfigs[handId]); if (handElement) { handsGrid.appendChild(handElement); } else { throw new Error('创建手部元素失败'); } } catch (error) { console.error(`Failed to create hand element for ${iface}:`, error); logMessage('error', `创建 ${iface} 的手部配置失败: ${error.message}`); } }); // 延迟更新状态,确保DOM完全构建 setTimeout(() => { updateEnabledHandsStatus(); logMessage('success', `成功生成 ${Object.keys(handConfigs).length} 个手部配置`); }, 100); } // 添加一个安全的 DOM 检查函数 function validateHandElement(handId) { const element = document.getElementById(handId); if (!element) { console.error(`validateHandElement: 找不到元素 ${handId}`); return false; } const requiredElements = [ `.hand-title`, `#${handId}_checkbox`, `#${handId}_interface`, `#${handId}_handtype`, `#${handId}_status_dot`, `#${handId}_status_text` ]; let isValid = true; requiredElements.forEach(selector => { const el = selector.startsWith('#') ? document.getElementById(selector.slice(1)) : element.querySelector(selector); if (!el) { console.error(`validateHandElement: 在 ${handId} 中找不到 ${selector}`); isValid = false; } }); return isValid; } // 增强的错误处理包装器 function safeUpdateHandElement(handId) { try { if (validateHandElement(handId)) { updateHandElement(handId); } else { logMessage('error', `手部元素 ${handId} 验证失败,跳过更新`); } } catch (error) { console.error(`Error updating hand element ${handId}:`, error); logMessage('error', `更新手部元素 ${handId} 时出错:${error.message}`); } } // 创建手部配置元素 function createHandElement(config) { const div = document.createElement('div'); div.className = `hand-item ${config.enabled ? 'enabled' : 'disabled'}`; div.id = config.id; const handEmoji = config.handType === 'left' ? '✋' : '🤚'; const handLabel = config.handType === 'left' ? '左手' : '右手'; const handId = handTypeIds[config.handType]; // 确保 HTML 结构完整且正确 div.innerHTML = `
${handEmoji} ${config.interface} - ${handLabel}
检查中...
`; // 使用 requestAnimationFrame 确保 DOM 完全渲染后再设置事件监听器 requestAnimationFrame(() => { setTimeout(() => { setupHandEventListeners(config.id); }, 0); }); return div; } // 设置手部事件监听器 function setupHandEventListeners(handId) { // 使用更安全的元素获取方式 const checkbox = document.getElementById(`${handId}_checkbox`); const interfaceSelect = document.getElementById(`${handId}_interface`); const handTypeSelect = document.getElementById(`${handId}_handtype`); // 检查所有必需的元素是否存在 if (!checkbox) { console.error(`setupHandEventListeners: 找不到 checkbox - ${handId}_checkbox`); return; } if (!interfaceSelect) { console.error(`setupHandEventListeners: 找不到 interfaceSelect - ${handId}_interface`); return; } if (!handTypeSelect) { console.error(`setupHandEventListeners: 找不到 handTypeSelect - ${handId}_handtype`); return; } // 移除现有的事件监听器(如果有的话) checkbox.removeEventListener('change', checkbox._changeHandler); interfaceSelect.removeEventListener('change', interfaceSelect._changeHandler); handTypeSelect.removeEventListener('change', handTypeSelect._changeHandler); // 创建新的事件处理器 checkbox._changeHandler = function() { if (handConfigs[handId]) { handConfigs[handId].enabled = this.checked; updateHandElement(handId); updateEnabledHandsStatus(); logMessage('info', `${handId}: ${this.checked ? '启用' : '禁用'}`); } }; interfaceSelect._changeHandler = function() { if (handConfigs[handId]) { handConfigs[handId].interface = this.value; logMessage('info', `${handId}: 接口切换到 ${this.value}`); checkSingleInterfaceStatus(handId); } }; handTypeSelect._changeHandler = function() { if (handConfigs[handId]) { handConfigs[handId].handType = this.value; updateHandElement(handId); const handName = this.value === 'left' ? '左手' : '右手'; const handIdHex = handTypeIds[this.value]; logMessage('info', `${handId}: 切换到${handName}模式 (0x${handIdHex.toString(16).toUpperCase()})`); } }; // 添加事件监听器 checkbox.addEventListener('change', checkbox._changeHandler); interfaceSelect.addEventListener('change', interfaceSelect._changeHandler); handTypeSelect.addEventListener('change', handTypeSelect._changeHandler); } // 更新手部元素 function updateHandElement(handId) { const config = handConfigs[handId]; const element = document.getElementById(handId); // 添加安全检查 if (!element || !config) { console.warn(`updateHandElement: 找不到元素或配置 - handId: ${handId}`); return; } const handEmoji = config.handType === 'left' ? '✋' : '🤚'; const handLabel = config.handType === 'left' ? '左手' : '右手'; const handIdHex = handTypeIds[config.handType]; // 更新样式 element.className = `hand-item ${config.enabled ? 'enabled' : 'disabled'}`; // 安全地更新标题 const title = element.querySelector('.hand-title'); if (title) { title.textContent = `${handEmoji} ${config.interface} - ${handLabel}`; } else { console.warn(`updateHandElement: 找不到 .hand-title 元素 - handId: ${handId}`); } // 安全地更新手型标签 const handTypeLabels = element.querySelectorAll('.control-label'); if (handTypeLabels.length >= 2) { const handTypeLabel = handTypeLabels[1]; // 第二个 label 是手型的 if (handTypeLabel) { handTypeLabel.textContent = `手型 (CAN ID: 0x${handIdHex.toString(16).toUpperCase()})`; } } else { console.warn(`updateHandElement: 找不到手型标签 - handId: ${handId}`); } // 确保选择框的值也同步更新 const handTypeSelect = document.getElementById(`${handId}_handtype`); if (handTypeSelect) { handTypeSelect.value = config.handType; } const interfaceSelect = document.getElementById(`${handId}_interface`); if (interfaceSelect) { interfaceSelect.value = config.interface; } const checkbox = document.getElementById(`${handId}_checkbox`); if (checkbox) { checkbox.checked = config.enabled; } } // 更新启用手部状态显示 function updateEnabledHandsStatus() { const enabledHands = Object.values(handConfigs).filter(config => config.enabled); const statusDiv = document.getElementById('enabled-hands-status'); if (enabledHands.length === 0) { statusDiv.innerHTML = '❌ 没有启用的手部'; } else { const statusList = enabledHands.map(config => { const emoji = config.handType === 'left' ? '✋' : '🤚'; const handName = config.handType === 'left' ? '左手' : '右手'; const statusDot = config.status === 'online' ? '🟢' : '🔴'; return `${statusDot} ${emoji} ${config.interface} (${handName})`; }).join('
'); statusDiv.innerHTML = statusList; } } // 检查所有接口状态 - 修复错误处理 async function checkAllInterfaceStatus() { try { const response = await fetch('/api/status'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); if (!data || data.status !== 'success') { throw new Error(data?.error || '获取状态失败'); } // 安全地获取接口状态 const responseData = data.data || {}; interfaceStatus = responseData.interfaces || {}; updateAllHandStatus(); hideConnectionWarning(); } catch (error) { logMessage('error', `状态检查失败:${error.message}`); console.error('CheckAllInterfaceStatus Error:', error); showConnectionWarning(); setAllHandStatusOffline(); } } // 检查单个接口状态 async function checkSingleInterfaceStatus(handId) { await checkAllInterfaceStatus(); } // 更新所有手部状态 function updateAllHandStatus() { Object.keys(handConfigs).forEach(handId => { const config = handConfigs[handId]; const status = interfaceStatus[config.interface]; if (status && status.active) { config.status = 'online'; updateHandStatusDisplay(handId, 'online', '在线'); } else { config.status = 'offline'; updateHandStatusDisplay(handId, 'offline', '离线'); } }); updateEnabledHandsStatus(); } // 设置所有手部状态为离线 function setAllHandStatusOffline() { Object.keys(handConfigs).forEach(handId => { handConfigs[handId].status = 'offline'; updateHandStatusDisplay(handId, 'offline', '连接失败'); }); updateEnabledHandsStatus(); } // 更新手部状态显示 function updateHandStatusDisplay(handId, status, text) { const statusDot = document.getElementById(`${handId}_status_dot`); const statusText = document.getElementById(`${handId}_status_text`); if (statusDot && statusText) { statusDot.className = `status-dot ${status}`; statusText.textContent = text; } } // 显示连接警告 function showConnectionWarning() { document.getElementById('connection-warning').style.display = 'block'; } // 隐藏连接警告 function hideConnectionWarning() { document.getElementById('connection-warning').style.display = 'none'; } // 获取启用的手部配置 function getEnabledHands() { return Object.values(handConfigs).filter(config => config.enabled); } // 设置事件监听器 function setupEventListeners() { const delayDefault = 30; // 刷新所有接口按钮 document.getElementById('refresh-all').addEventListener('click', function() { logMessage('info', '手动刷新所有接口...'); initializeSystem(); }); // 全局控制按钮 document.getElementById('send-all-finger-poses').addEventListener('click', sendAllFingerPoses); document.getElementById('send-all-palm-poses').addEventListener('click', sendAllPalmPoses); document.getElementById('reset-all-hands').addEventListener('click', resetAllHands); document.getElementById('stop-all-animations').addEventListener('click', stopAllAnimations); // 动画按钮 document.getElementById('start-wave').addEventListener('click', () => startAnimationForAll('wave')); document.getElementById('start-sway').addEventListener('click', () => startAnimationForAll('sway')); document.getElementById('stop-animation').addEventListener('click', stopAllAnimations); // 预设姿势按钮 - 使用 LinkerHandController 的预设 setupPresetButtons(); // 数字手势按钮事件 setupNumericPresets(); // Refill core 按钮 setupRefillCore(); } // 设置预设按钮 function setupPresetButtons() { const delayDefault = 30; // 基础预设姿势 const presets = { 'pose-fist': { finger: 'FIST', palm: null }, 'pose-open': { finger: 'OPEN', palm: null }, 'pose-pinch': { finger: 'PINCH', palm: 'PALM_PINCH' }, 'pose-point': { finger: 'POINT', palm: null }, 'pose-thumbs-up': { finger: 'THUMBSUP', palm: null }, 'pose-yeah': { finger: 'YEAH', palm: 'PALM_YEAH' }, 'pose-wave': { finger: 'WAVE', palm: null }, 'pose-big-fist': { finger: 'BIG_FIST', palm: 'PALM_BIG_FIST' }, 'pose-big-open': { finger: 'BIG_OPEN', palm: 'PALM_BIG_OPEN' }, 'pose-yo': { finger: 'YO', palm: null }, 'pose-gun': { finger: 'GUN', palm: 'PALM_GUN' }, 'pose-ok': { finger: 'OK', palm: 'PALM_OK' } }; Object.entries(presets).forEach(([id, preset]) => { const button = document.getElementById(id); if (button) { button.addEventListener('click', () => { if (preset.palm) { LinkerHandController.applyPalmPreset(LinkerHandController.PRESETS[preset.palm]); const palmPose = LinkerHandController.getPalmPoseValues(); LinkerHandController.sendPalmPoseToAll(palmPose); setTimeout(() => { LinkerHandController.applyFingerPreset(LinkerHandController.PRESETS[preset.finger]); const fingerPose = LinkerHandController.getFingerPoseValues(); LinkerHandController.sendFingerPoseToAll(fingerPose); }, delayDefault); } else { LinkerHandController.applyFingerPreset(LinkerHandController.PRESETS[preset.finger]); const fingerPose = LinkerHandController.getFingerPoseValues(); LinkerHandController.sendFingerPoseToAll(fingerPose); } }); } }); } // 设置数字预设 function setupNumericPresets() { const delayDefault = 30; // 数字 1-9 的预设 for (let i = 1; i <= 9; i++) { const button = document.getElementById(`pose-${i}`); if (button) { button.addEventListener('click', () => { const palmPreset = LinkerHandController.PRESETS[`PALM_${getNumberName(i)}`]; const fingerPreset = LinkerHandController.PRESETS[getNumberName(i)]; if (palmPreset) { LinkerHandController.applyPalmPreset(palmPreset); const palmPose = LinkerHandController.getPalmPoseValues(); LinkerHandController.sendPalmPoseToAll(palmPose); } setTimeout(() => { if (fingerPreset) { LinkerHandController.applyFingerPreset(fingerPreset); const fingerPose = LinkerHandController.getFingerPoseValues(); LinkerHandController.sendFingerPoseToAll(fingerPose); } }, delayDefault); }); } } } // 获取数字名称 function getNumberName(num) { const names = ['', 'ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE']; return names[num] || ''; } // 设置 Refill Core 功能 function setupRefillCore() { document.getElementById("refill-core").addEventListener("click", () => { event.preventDefault(); event.stopPropagation(); console.log("refill-core"); const rukaPoseList = [ [[246, 188, 128, 128], [149, 30, 145, 36, 41, 46]], // 食指 [[246, 155, 154, 66], [138, 80, 0, 154, 41, 46]], // 中指 [[246, 155, 154, 40], [140, 80, 0, 15, 155, 46]], // 无名指 [[246, 155, 154, 25], [140, 62, 0, 15, 29, 143]], // 小指 ]; const delayTime = 350; // 设定延迟时间为 350ms // 创建完整的序列:从第一个到最后一个,再从最后一个回到第二个 const forwardIndices = [...Array(rukaPoseList.length).keys()]; // [0,1,2,3] const backwardIndices = [...forwardIndices].reverse().slice(1); // [3,2,1] const sequenceIndices = [...forwardIndices, ...backwardIndices]; // 遍历序列索引,为每个索引创建两个操作(palm 和 finger) sequenceIndices.forEach((index, step) => { const targetPose = rukaPoseList[index]; // 应用 palm 预设 setTimeout(() => { console.log(`Step ${step+1}a: Applying palm preset for pose ${index+1}`); LinkerHandController.applyPalmPreset(targetPose[0]); const palmPose = LinkerHandController.getPalmPoseValues(); LinkerHandController.sendPalmPoseToAll(palmPose); }, delayTime * (step * 2)); // 每个完整步骤有两个操作,所以是 step*2 // 应用 finger 预设 setTimeout(() => { console.log(`Step ${step+1}b: Applying finger preset for pose ${index+1}`); LinkerHandController.applyFingerPreset(targetPose[1]); const fingerPose = LinkerHandController.getFingerPoseValues(); LinkerHandController.sendFingerPoseToAll(fingerPose); }, delayTime * (step * 2 + 1)); // 偏移一个 delayTime }); }); } // 设置滑块事件 function setupSliderEvents() { // 手指滑块 for (let i = 0; i < 6; i++) { const slider = document.getElementById(`finger${i}`); const valueDisplay = document.getElementById(`finger${i}-value`); slider.addEventListener('input', function() { valueDisplay.textContent = this.value; }); } // 掌部滑块 for (let i = 0; i < 4; i++) { const slider = document.getElementById(`palm${i}`); const valueDisplay = document.getElementById(`palm${i}-value`); slider.addEventListener('input', function() { valueDisplay.textContent = this.value; }); } // 速度滑块 const speedSlider = document.getElementById('animation-speed'); const speedDisplay = document.getElementById('speed-value'); speedSlider.addEventListener('input', function() { speedDisplay.textContent = this.value; }); } // 发送所有启用手部的手指姿态 async function sendAllFingerPoses() { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } const pose = []; for (let i = 0; i < 6; i++) { pose.push(parseInt(document.getElementById(`finger${i}`).value)); } logMessage('info', `发送手指姿态到 ${enabledHands.length} 个启用的手部...`); for (const config of enabledHands) { await sendFingerPoseToHand(config, pose); } } // 发送所有启用手部的掌部姿态 async function sendAllPalmPoses() { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } const pose = []; for (let i = 0; i < 4; i++) { pose.push(parseInt(document.getElementById(`palm${i}`).value)); } logMessage('info', `发送掌部姿态到 ${enabledHands.length} 个启用的手部...`); for (const config of enabledHands) { await sendPalmPoseToHand(config, pose); } } // 发送手指姿态到指定手部 async function sendFingerPoseToHand(config, pose) { try { const response = await fetch('/api/fingers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ interface: config.interface, pose: pose, handType: config.handType, handId: handTypeIds[config.handType] }) }); const data = await response.json(); if (data.status === 'success') { const handName = config.handType === 'left' ? '左手' : '右手'; logMessage('success', `${config.interface} (${handName}): 手指姿态发送成功 [${pose.join(', ')}]`); } else { logMessage('error', `${config.interface}: ${data.error}`); } } catch (error) { logMessage('error', `${config.interface}: 发送失败 - ${error.message}`); } } // 发送掌部姿态到指定手部 async function sendPalmPoseToHand(config, pose) { try { const response = await fetch('/api/palm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ interface: config.interface, pose: pose, handType: config.handType, handId: handTypeIds[config.handType] }) }); const data = await response.json(); if (data.status === 'success') { const handName = config.handType === 'left' ? '左手' : '右手'; logMessage('success', `${config.interface} (${handName}): 掌部姿态发送成功 [${pose.join(', ')}]`); } else { logMessage('error', `${config.interface}: ${data.error}`); } } catch (error) { logMessage('error', `${config.interface}: 发送失败 - ${error.message}`); } } // 为所有启用手部设置预设姿势 async function setPresetPoseForAll(preset) { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } logMessage('info', `设置预设姿势 "${preset}" 到 ${enabledHands.length} 个启用的手部...`); for (const config of enabledHands) { await setPresetPoseToHand(config, preset); } } // 为指定手部设置预设姿势 async function setPresetPoseToHand(config, preset) { try { const response = await fetch(`/api/preset/${preset}?interface=${config.interface}&handType=${config.handType}`, { method: 'POST' }); const data = await response.json(); if (data.status === 'success') { const handName = config.handType === 'left' ? '左手' : '右手'; logMessage('success', `${config.interface} (${handName}): ${data.message}`); } else { logMessage('error', `${config.interface}: ${data.error}`); } } catch (error) { logMessage('error', `${config.interface}: 预设姿势失败 - ${error.message}`); } } // 为所有启用手部启动动画 async function startAnimationForAll(type) { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } const speed = parseInt(document.getElementById('animation-speed').value); logMessage('info', `启动 "${type}" 动画到 ${enabledHands.length} 个启用的手部...`); for (const config of enabledHands) { await startAnimationForHand(config, type, speed); } } // 为指定手部启动动画 async function startAnimationForHand(config, type, speed) { try { const response = await fetch('/api/animation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ interface: config.interface, type: type, speed: speed, handType: config.handType, handId: handTypeIds[config.handType] }) }); const data = await response.json(); if (data.status === 'success') { const handName = config.handType === 'left' ? '左手' : '右手'; logMessage('success', `${config.interface} (${handName}): ${data.message}`); } else { logMessage('error', `${config.interface}: ${data.error}`); } } catch (error) { logMessage('error', `${config.interface}: 动画启动失败 - ${error.message}`); } } // 停止所有启用手部的动画 async function stopAllAnimations() { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } logMessage('info', `停止 ${enabledHands.length} 个启用手部的动画...`); for (const config of enabledHands) { await stopAnimationForHand(config); } } // 停止指定手部的动画 async function stopAnimationForHand(config) { try { const response = await fetch('/api/animation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ interface: config.interface, type: 'stop', handType: config.handType, handId: handTypeIds[config.handType] }) }); const data = await response.json(); if (data.status === 'success') { const handName = config.handType === 'left' ? '左手' : '右手'; logMessage('success', `${config.interface} (${handName}): ${data.message}`); } else { logMessage('error', `${config.interface}: ${data.error}`); } } catch (error) { logMessage('error', `${config.interface}: 停止动画失败 - ${error.message}`); } } // 重置所有启用手部 async function resetAllHands() { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } // 重置滑块值 LinkerHandController.applyFingerPreset(LinkerHandController.PRESETS.OPEN); LinkerHandController.applyPalmPreset(LinkerHandController.PRESETS.PALM_NEUTRAL); logMessage('info', `重置 ${enabledHands.length} 个启用的手部...`); // 停止所有动画 await stopAllAnimations(); // 发送重置姿态 await sendAllFingerPoses(); await sendAllPalmPoses(); logMessage('info', '所有启用手部已重置完成'); } // 自动触发按钮序列(数字手势) async function triggerButtonsSequentially(interval = 2000) { const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } logMessage('info', `开始自动数字手势序列 (${enabledHands.length} 个手部)`); const buttons = [ document.getElementById('pose-1'), document.getElementById('pose-2'), document.getElementById('pose-3'), document.getElementById('pose-4'), document.getElementById('pose-5'), document.getElementById('pose-6'), document.getElementById('pose-7'), document.getElementById('pose-8'), document.getElementById('pose-9'), ]; for (const button of buttons) { if (button) { button.click(); await new Promise(resolve => setTimeout(resolve, interval)); } } // 然后执行所有预设手势 const presetButtons = document.querySelectorAll('.preset-grid button:not(.preset-num-pose)'); for (const button of presetButtons) { button.click(); await new Promise(resolve => setTimeout(resolve, interval)); } logMessage('success', '数字手势序列完成'); } // 日志消息 function logMessage(type, message) { const statusLog = document.getElementById('status-log'); const timestamp = new Date().toLocaleTimeString(); const logEntry = document.createElement('div'); logEntry.className = 'log-entry'; let statusClass = 'status-info'; if (type === 'success') statusClass = 'status-success'; else if (type === 'error') statusClass = 'status-error'; logEntry.innerHTML = ` ${timestamp} ${message} `; statusLog.appendChild(logEntry); statusLog.scrollTop = statusLog.scrollHeight; // 保持最多 50 条日志 const entries = statusLog.querySelectorAll('.log-entry'); if (entries.length > 50) { statusLog.removeChild(entries[0]); } } // 启动状态更新器 function startStatusUpdater() { // 每 5 秒检查一次接口状态 setInterval(async () => { await checkAllInterfaceStatus(); }, 5000); // 每 30 秒刷新一次接口列表 setInterval(async () => { const oldInterfaces = [...availableInterfaces]; await loadAvailableInterfaces(); // 如果接口发生变化,重新生成配置 if (JSON.stringify(oldInterfaces) !== JSON.stringify(availableInterfaces)) { generateHandConfigs(); } }, 30000); } // 添加调试功能 async function debugSystemStatus() { logMessage('info', '🔍 开始系统调试...'); // 检查 HTML 元素 const elements = { 'hands-grid': document.getElementById('hands-grid'), 'status-log': document.getElementById('status-log'), 'enabled-hands-status': document.getElementById('enabled-hands-status'), 'sensor-data': document.getElementById('sensor-data') }; Object.entries(elements).forEach(([name, element]) => { if (element) { logMessage('success', `✅ 元素 ${name} 存在`); } else { logMessage('error', `❌ 元素 ${name} 不存在`); } }); // 检查全局变量 logMessage('info', `可用接口:[${availableInterfaces.join(', ')}]`); logMessage('info', `手部配置数量:${Object.keys(handConfigs).length}`); logMessage('info', `启用手部数量:${getEnabledHands().length}`); // 测试 API 连通性 try { logMessage('info', '测试 /api/health 连接...'); const response = await fetch('/api/health'); if (response.ok) { const data = await response.json(); logMessage('success', '✅ 健康检查通过'); console.log('Health Check Data:', data); } else { logMessage('error', `❌ 健康检查失败:HTTP ${response.status}`); } } catch (error) { logMessage('error', `❌ 健康检查异常:${error.message}`); } // 测试接口 API try { logMessage('info', '测试 /api/interfaces 连接...'); const response = await fetch('/api/interfaces'); if (response.ok) { const data = await response.json(); logMessage('success', '✅ 接口 API 通过'); console.log('Interfaces API Data:', data); } else { logMessage('error', `❌ 接口 API 失败:HTTP ${response.status}`); } } catch (error) { logMessage('error', `❌ 接口 API 异常:${error.message}`); } } // 导出全局函数供 HTML 按钮使用 window.triggerButtonsSequentially = triggerButtonsSequentially; window.debugSystemStatus = debugSystemStatus; // 添加全局错误处理 window.addEventListener('error', function(event) { logMessage('error', `全局错误:${event.error?.message || event.message}`); console.error('Global Error:', event.error); }); window.addEventListener('unhandledrejection', function(event) { logMessage('error', `未处理的 Promise 拒绝:${event.reason?.message || event.reason}`); console.error('Unhandled Promise Rejection:', event.reason); }); // 页面可见性变化时的处理 document.addEventListener('visibilitychange', function() { if (!document.hidden) { // 页面变为可见时,刷新状态 checkAllInterfaceStatus(); } }); // 处理网络错误时的重连逻辑 window.addEventListener('online', function() { logMessage('info', '网络连接已恢复,正在重新连接...'); initializeSystem(); }); window.addEventListener('offline', function() { logMessage('error', '网络连接已断开'); showConnectionWarning(); }); // 键盘快捷键支持 document.addEventListener('keydown', function(e) { // Ctrl+R 刷新接口 if (e.ctrlKey && e.key === 'r') { e.preventDefault(); logMessage('info', '快捷键刷新接口列表...'); initializeSystem(); } // Ctrl+Space 停止所有动画 if (e.ctrlKey && e.code === 'Space') { e.preventDefault(); stopAllAnimations(); } // Ctrl+A 选择/取消选择所有手部 if (e.ctrlKey && e.key === 'a') { e.preventDefault(); toggleAllHands(); } // 数字键 1-9 快速设置预设姿势 if (e.key >= '1' && e.key <= '9' && !e.ctrlKey && !e.altKey) { const activeElement = document.activeElement; // 确保不在输入框中 if (activeElement.tagName !== 'INPUT' && activeElement.tagName !== 'SELECT') { const button = document.getElementById(`pose-${e.key}`); if (button) button.click(); } } }); // 切换所有手部启用状态 function toggleAllHands() { const enabledCount = Object.values(handConfigs).filter(config => config.enabled).length; const shouldEnable = enabledCount === 0; Object.keys(handConfigs).forEach(handId => { handConfigs[handId].enabled = shouldEnable; const checkbox = document.getElementById(`${handId}_checkbox`); if (checkbox) { checkbox.checked = shouldEnable; } updateHandElement(handId); }); updateEnabledHandsStatus(); logMessage('info', `${shouldEnable ? '启用' : '禁用'}所有手部`); } // 工具提示功能 function addTooltips() { const tooltips = { 'refresh-all': '刷新所有可用接口列表', 'send-all-finger-poses': '向所有启用的手部发送当前手指关节位置', 'send-all-palm-poses': '向所有启用的手部发送当前掌部关节位置', 'reset-all-hands': '重置所有启用手部到默认位置', 'stop-all-animations': '停止所有启用手部的动画', 'start-wave': '启动所有启用手部的手指波浪动画', 'start-sway': '启动所有启用手部的掌部摆动动画', 'stop-animation': '停止所有启用手部的动画', 'refill-core': '执行 Refill Core 动作序列' }; Object.entries(tooltips).forEach(([id, text]) => { const element = document.getElementById(id); if (element) { element.title = text; } }); } // 页面加载完成后添加工具提示 document.addEventListener('DOMContentLoaded', function() { addTooltips(); }); // ---eof // 六手依次动画函数 async function startSequentialHandAnimation(animationType = 'wave', interval = 500, cycles = 3) { const enabledHands = getEnabledHands(); // 检查是否有足够的手部 if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } // 确保按接口名称排序(can0, can1, can2...) const sortedHands = enabledHands.sort((a, b) => { const getInterfaceNumber = (iface) => { const match = iface.match(/(\d+)$/); return match ? parseInt(match[1]) : 0; }; return getInterfaceNumber(a.interface) - getInterfaceNumber(b.interface); }); logMessage('info', `开始六手依次动画 - 类型:${animationType}, 间隔:${interval}ms, 循环:${cycles}次`); logMessage('info', `动画顺序:${sortedHands.map(h => h.interface).join(' → ')}`); // 定义动画预设 const animationPresets = { wave: { name: '手指波浪', fingerPoses: [ [255, 255, 255, 255, 255, 255], // 完全张开 [128, 128, 128, 128, 128, 128], // 中间位置 [64, 64, 64, 64, 64, 64], // 握拳 [128, 128, 128, 128, 128, 128], // 回到中间 ], palmPose: [128, 128, 128, 128] // 掌部保持中立 }, thumbsUp: { name: '竖拇指传递', fingerPoses: [ [255, 255, 0, 0, 0, 0], // 竖拇指 [128, 128, 128, 128, 128, 128], // 恢复中立 ], palmPose: [128, 128, 128, 128] }, point: { name: '食指指点传递', fingerPoses: [ [0, 0, 255, 0, 0, 0], // 食指指点 [128, 128, 128, 128, 128, 128], // 恢复中立 ], palmPose: [128, 128, 128, 128] }, fistOpen: { name: '握拳张开', fingerPoses: [ [64, 64, 64, 64, 64, 64], // 握拳 [255, 255, 255, 255, 255, 255], // 张开 [128, 128, 128, 128, 128, 128], // 中立 ], palmPose: [128, 128, 128, 128] }, numbers: { name: '数字倒计时', fingerPoses: [ [255, 255, 255, 255, 255, 255], // 5 [0, 57, 255, 255, 255, 255], // 4 [0, 57, 255, 255, 255, 0], // 3 [0, 57, 255, 255, 0, 0], // 2 [0, 57, 255, 0, 0, 0], // 1 [64, 64, 64, 64, 64, 64], // 握拳 (0) ], palmPoses: [ [255, 109, 255, 118], // 5 对应的掌部 [255, 109, 255, 118], // 4 对应的掌部 [255, 109, 255, 118], // 3 对应的掌部 [255, 109, 255, 118], // 2 对应的掌部 [255, 109, 255, 118], // 1 对应的掌部 [128, 128, 128, 128], // 0 对应的掌部 ] }, mexican: { name: '墨西哥波浪', fingerPoses: [ [64, 64, 64, 64, 64, 64], // 起始握拳 [128, 64, 64, 64, 64, 64], // 拇指起 [255, 128, 64, 64, 64, 64], // 拇指 + 食指起 [255, 255, 128, 64, 64, 64], // 前三指起 [255, 255, 255, 128, 64, 64], // 前四指起 [255, 255, 255, 255, 128, 64], // 前五指起 [255, 255, 255, 255, 255, 255], // 全部张开 [128, 255, 255, 255, 255, 128], // 波浪形 [64, 128, 255, 255, 128, 64], // 继续波浪 [64, 64, 128, 255, 128, 64], // 波浪收尾 [64, 64, 64, 128, 64, 64], // 几乎回到握拳 [64, 64, 64, 64, 64, 64], // 完全握拳 ], palmPose: [128, 128, 128, 128] } }; const preset = animationPresets[animationType] || animationPresets.wave; const fingerPoses = preset.fingerPoses; const palmPoses = preset.palmPoses || Array(fingerPoses.length).fill(preset.palmPose); // 执行动画循环 for (let cycle = 0; cycle < cycles; cycle++) { logMessage('info', `${preset.name} - 第 ${cycle + 1}/${cycles} 轮`); // 每个动作姿势 for (let poseIndex = 0; poseIndex < fingerPoses.length; poseIndex++) { const currentFingerPose = fingerPoses[poseIndex]; const currentPalmPose = palmPoses[poseIndex]; // 依次激活每只手 for (let handIndex = 0; handIndex < sortedHands.length; handIndex++) { const hand = sortedHands[handIndex]; const handName = hand.handType === 'left' ? '左手' : '右手'; // 先发送掌部姿态 await sendPalmPoseToHand(hand, currentPalmPose); // 短暂延迟后发送手指姿态 setTimeout(async () => { await sendFingerPoseToHand(hand, currentFingerPose); }, 50); logMessage('info', `${hand.interface}(${handName}) 执行姿势 ${poseIndex + 1}/${fingerPoses.length}`); // 等待间隔时间再激活下一只手 await new Promise(resolve => setTimeout(resolve, interval)); } } // 循环间隔(如果有多轮) if (cycle < cycles - 1) { logMessage('info', `等待下一轮动画...`); await new Promise(resolve => setTimeout(resolve, interval * 2)); } } // 动画结束后,让所有手回到中立位置 logMessage('info', '动画完成,恢复中立位置...'); const neutralFingerPose = [128, 128, 128, 128, 128, 128]; const neutralPalmPose = [128, 128, 128, 128]; for (const hand of sortedHands) { await sendPalmPoseToHand(hand, neutralPalmPose); setTimeout(async () => { await sendFingerPoseToHand(hand, neutralFingerPose); }, 50); await new Promise(resolve => setTimeout(resolve, 100)); } logMessage('success', `六手依次动画 "${preset.name}" 完成!`); } // 扩展的动画控制函数 async function startCustomSequentialAnimation(config) { const { animationType = 'wave', interval = 500, cycles = 3, direction = 'forward', // 'forward', 'backward', 'bounce' simultaneousHands = 1, // 同时激活的手数 staggerDelay = 100 // 同时激活手之间的错开延迟 } = config; const enabledHands = getEnabledHands(); if (enabledHands.length === 0) { logMessage('error', '没有启用的手部'); return; } // 根据方向排序手部 let sortedHands = enabledHands.sort((a, b) => { const getInterfaceNumber = (iface) => { const match = iface.match(/(\d+)$/); return match ? parseInt(match[1]) : 0; }; return getInterfaceNumber(a.interface) - getInterfaceNumber(b.interface); }); if (direction === 'backward') { sortedHands = sortedHands.reverse(); } logMessage('info', `开始自定义六手动画 - 方向:${direction}, 同时手数:${simultaneousHands}`); // 执行动画逻辑... // 这里可以根据 simultaneousHands 参数同时控制多只手 // 实现类似的动画逻辑,但支持更多自定义选项 } // 预定义的快捷动画函数 async function startWaveAnimation() { await startSequentialHandAnimation('wave', 300, 2); } async function startThumbsUpRelay() { await startSequentialHandAnimation('thumbsUp', 400, 3); } async function startPointingRelay() { await startSequentialHandAnimation('point', 350, 2); } async function startNumberCountdown() { await startSequentialHandAnimation('numbers', 800, 1); } async function startMexicanWave() { await startSequentialHandAnimation('mexican', 200, 3); } async function startFistOpenWave() { await startSequentialHandAnimation('fistOpen', 400, 2); } // 高级组合动画:先正向再反向 async function startBidirectionalWave() { logMessage('info', '开始双向波浪动画...'); // 正向波浪 await startSequentialHandAnimation('wave', 300, 1); await new Promise(resolve => setTimeout(resolve, 500)); // 反向波浪(通过反转手部顺序实现) const originalGetEnabledHands = window.getEnabledHands; window.getEnabledHands = function() { return originalGetEnabledHands().reverse(); }; await startSequentialHandAnimation('wave', 300, 1); // 恢复原始函数 window.getEnabledHands = originalGetEnabledHands; logMessage('success', '双向波浪动画完成!'); } // 导出函数到全局作用域 window.startSequentialHandAnimation = startSequentialHandAnimation; window.startCustomSequentialAnimation = startCustomSequentialAnimation; window.startWaveAnimation = startWaveAnimation; window.startThumbsUpRelay = startThumbsUpRelay; window.startPointingRelay = startPointingRelay; window.startNumberCountdown = startNumberCountdown; window.startMexicanWave = startMexicanWave; window.startFistOpenWave = startFistOpenWave; window.startBidirectionalWave = startBidirectionalWave;