2025-05-26 14:40:25 +08:00

1650 lines
58 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 全局变量
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 = '<table style="width:100%;">';
// 手指压力传感器
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 += '</table>';
// 更新最后更新时间
const lastUpdate = new Date(data.lastUpdate).toLocaleTimeString();
html += `<div style="text-align:right;font-size:0.8em;margin-top:5px;">最后更新: ${lastUpdate}</div>`;
sensorDisplay.innerHTML = html;
},
// 创建传感器行
createSensorRow: function (label, value) {
if (value === undefined || value === null) value = 0;
return `<tr>
<td>${label}</td>
<td style="filter:blur(10px)"><progress value="${value}" max="100"></progress></td>
<td style="filter:blur(10px)">${value}%</td>
</tr>`;
}
};
// 页面加载时初始化
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 = '<div style="text-align: center; color: #666; padding: 20px;">没有可用的CAN接口</div>';
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 = `
<div class="hand-header">
<input type="checkbox" class="hand-checkbox" id="${config.id}_checkbox" ${config.enabled ? 'checked' : ''}>
<span class="hand-title">${handEmoji} ${config.interface} - ${handLabel}</span>
</div>
<div class="hand-controls">
<div class="control-group">
<label class="control-label">CAN 接口</label>
<select class="hand-select interface-select" id="${config.id}_interface">
${availableInterfaces.map(iface =>
`<option value="${iface}" ${iface === config.interface ? 'selected' : ''}>${iface}</option>`
).join('')}
</select>
</div>
<div class="control-group">
<label class="control-label">手型 (CAN ID: 0x${handId.toString(16).toUpperCase()})</label>
<select class="hand-select hand-type-select" id="${config.id}_handtype">
<option value="right" ${config.handType === 'right' ? 'selected' : ''}>🤚 右手 (0x27)</option>
<option value="left" ${config.handType === 'left' ? 'selected' : ''}>✋ 左手 (0x28)</option>
</select>
</div>
</div>
<div class="hand-status">
<span class="status-dot loading" id="${config.id}_status_dot"></span>
<span id="${config.id}_status_text">检查中...</span>
</div>
`;
// 使用 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 = '<span style="color: #e74c3c;">❌ 没有启用的手部</span>';
} 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('<br>');
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 = `
<span class="status-indicator ${statusClass}"></span>
<span class="log-timestamp">${timestamp}</span>
${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;