diff --git a/Dockerfile b/Dockerfile
index 6dfa4f3..db73a74 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@ COPY --link . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o dashboard-server .
# ---- Runtime Stage ----
-FROM alpine:3.21
+FROM alpine:3.22
WORKDIR /app
diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser
index 6a8f670..402f6aa 100644
--- a/Dockerfile.goreleaser
+++ b/Dockerfile.goreleaser
@@ -1,4 +1,4 @@
-FROM alpine:3.21
+FROM alpine:3.22
WORKDIR /app
@@ -7,4 +7,4 @@ COPY --link . .
EXPOSE 9099
ENV SERVER_PORT="9099"
-CMD ["/app/dashboard-server"]
+CMD ["/app/dashboard-server"]
\ No newline at end of file
diff --git a/api/legacy/compatibility.go b/api/legacy/compatibility.go
new file mode 100644
index 0000000..5b7e15c
--- /dev/null
+++ b/api/legacy/compatibility.go
@@ -0,0 +1,207 @@
+package legacy
+
+import (
+ "fmt"
+ "log"
+ "sync"
+
+ "hands/config"
+ "hands/define"
+ "hands/device"
+)
+
+// InterfaceDeviceMapper 管理接口和设备的映射关系
+type InterfaceDeviceMapper struct {
+ interfaceToDevice map[string]string // interface -> deviceId
+ deviceToInterface map[string]string // deviceId -> interface
+ handConfigs map[string]HandConfig // interface -> hand config
+ deviceManager *device.DeviceManager
+ mutex sync.RWMutex
+}
+
+// HandConfig 存储接口的手型配置(兼容旧版 API)
+type HandConfig struct {
+ HandType string
+ HandId uint32
+}
+
+// NewInterfaceDeviceMapper 创建新的接口设备映射器
+func NewInterfaceDeviceMapper(deviceManager *device.DeviceManager) (*InterfaceDeviceMapper, error) {
+ mapper := &InterfaceDeviceMapper{
+ interfaceToDevice: make(map[string]string),
+ deviceToInterface: make(map[string]string),
+ handConfigs: make(map[string]HandConfig),
+ deviceManager: deviceManager,
+ }
+
+ if err := mapper.initializeDevices(); err != nil {
+ return nil, fmt.Errorf("初始化设备映射失败:%w", err)
+ }
+
+ return mapper, nil
+}
+
+// initializeDevices 为每个可用接口创建对应的设备实例
+func (m *InterfaceDeviceMapper) initializeDevices() error {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ log.Printf("🔧 开始为 %d 个接口创建设备映射...", len(config.Config.AvailableInterfaces))
+
+ for _, ifName := range config.Config.AvailableInterfaces {
+ deviceId := ifName + "_default"
+
+ // 创建设备配置
+ deviceConfig := map[string]any{
+ "id": deviceId,
+ "can_service_url": config.Config.CanServiceURL,
+ "can_interface": ifName,
+ "hand_type": "right", // 默认右手
+ }
+
+ // 创建设备实例
+ dev, err := device.CreateDevice("L10", deviceConfig)
+ if err != nil {
+ return fmt.Errorf("创建接口 %s 的设备失败: %w", ifName, err)
+ }
+
+ // 注册设备到管理器
+ if err := m.deviceManager.RegisterDevice(dev); err != nil {
+ return fmt.Errorf("注册接口 %s 的设备失败: %w", ifName, err)
+ }
+
+ // 建立映射关系
+ m.interfaceToDevice[ifName] = deviceId
+ m.deviceToInterface[deviceId] = ifName
+
+ // 初始化手型配置
+ m.handConfigs[ifName] = HandConfig{
+ HandType: "right",
+ HandId: uint32(define.HAND_TYPE_RIGHT),
+ }
+
+ log.Printf("✅ 接口 %s -> 设备 %s 映射创建成功", ifName, deviceId)
+ }
+
+ log.Printf("🎉 设备映射初始化完成,共创建 %d 个设备", len(config.Config.AvailableInterfaces))
+ return nil
+}
+
+// GetDeviceForInterface 根据接口名获取对应的设备
+func (m *InterfaceDeviceMapper) GetDeviceForInterface(ifName string) (device.Device, error) {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
+
+ deviceId, exists := m.interfaceToDevice[ifName]
+ if !exists {
+ return nil, fmt.Errorf("接口 %s 没有对应的设备", ifName)
+ }
+
+ return m.deviceManager.GetDevice(deviceId)
+}
+
+// GetInterfaceForDevice 根据设备 ID 获取对应的接口名
+func (m *InterfaceDeviceMapper) GetInterfaceForDevice(deviceId string) (string, bool) {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
+
+ ifName, exists := m.deviceToInterface[deviceId]
+ return ifName, exists
+}
+
+// SetHandConfig 设置接口的手型配置
+func (m *InterfaceDeviceMapper) SetHandConfig(ifName string, handType string, handId uint32) error {
+ m.mutex.Lock()
+ defer m.mutex.Unlock()
+
+ // 验证接口是否存在
+ if !config.IsValidInterface(ifName) {
+ return fmt.Errorf("无效的接口: %s", ifName)
+ }
+
+ // 获取对应的设备
+ deviceId, exists := m.interfaceToDevice[ifName]
+ if !exists {
+ return fmt.Errorf("接口 %s 没有对应的设备", ifName)
+ }
+
+ dev, err := m.deviceManager.GetDevice(deviceId)
+ if err != nil {
+ return fmt.Errorf("获取设备失败:%w", err)
+ }
+
+ // 转换手型
+ var deviceHandType define.HandType
+ switch handType {
+ case "left":
+ deviceHandType = define.HAND_TYPE_LEFT
+ case "right":
+ deviceHandType = define.HAND_TYPE_RIGHT
+ default:
+ return fmt.Errorf("无效的手型: %s", handType)
+ }
+
+ // 设置设备手型
+ if err := dev.SetHandType(deviceHandType); err != nil {
+ return fmt.Errorf("设置设备手型失败:%w", err)
+ }
+
+ // 更新本地配置
+ m.handConfigs[ifName] = HandConfig{
+ HandType: handType,
+ HandId: handId,
+ }
+
+ log.Printf("🔧 接口 %s 手型已设置为 %s (0x%X)", ifName, handType, handId)
+ return nil
+}
+
+// GetHandConfig 获取接口的手型配置
+func (m *InterfaceDeviceMapper) GetHandConfig(ifName string) (HandConfig, bool) {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
+
+ config, exists := m.handConfigs[ifName]
+ return config, exists
+}
+
+// GetAllHandConfigs 获取所有接口的手型配置
+func (m *InterfaceDeviceMapper) GetAllHandConfigs() map[string]HandConfig {
+ m.mutex.RLock()
+ defer m.mutex.RUnlock()
+
+ result := make(map[string]HandConfig)
+ for ifName, config := range m.handConfigs {
+ result[ifName] = config
+ }
+ return result
+}
+
+// StopAllAnimations 停止指定接口对应设备的动画
+func (m *InterfaceDeviceMapper) StopAllAnimations(ifName string) error {
+ dev, err := m.GetDeviceForInterface(ifName)
+ if err != nil {
+ return err
+ }
+
+ animEngine := dev.GetAnimationEngine()
+ if animEngine.IsRunning() {
+ return animEngine.Stop()
+ }
+ return nil
+}
+
+// GetDeviceStatus 获取指定接口对应设备的状态
+func (m *InterfaceDeviceMapper) GetDeviceStatus(ifName string) (device.DeviceStatus, error) {
+ dev, err := m.GetDeviceForInterface(ifName)
+ if err != nil {
+ return device.DeviceStatus{}, err
+ }
+
+ return dev.GetStatus()
+}
+
+// IsValidInterface 验证接口是否有效
+func (m *InterfaceDeviceMapper) IsValidInterface(ifName string) bool {
+ return config.IsValidInterface(ifName)
+}
diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go
new file mode 100644
index 0000000..07ce395
--- /dev/null
+++ b/api/legacy/handlers.go
@@ -0,0 +1,571 @@
+package legacy
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "hands/config"
+ "hands/define"
+
+ "github.com/gin-gonic/gin"
+)
+
+// handleHealth 健康检查处理函数
+func (s *LegacyServer) handleHealth(c *gin.Context) {
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: "CAN Control Service is running",
+ Data: map[string]any{
+ "timestamp": time.Now(),
+ "availableInterfaces": config.Config.AvailableInterfaces,
+ "defaultInterface": config.Config.DefaultInterface,
+ "serviceVersion": "1.0.0-hand-type-support",
+ },
+ })
+}
+
+// handleInterfaces 获取可用接口列表处理函数
+func (s *LegacyServer) handleInterfaces(c *gin.Context) {
+ responseData := map[string]any{
+ "availableInterfaces": config.Config.AvailableInterfaces,
+ "defaultInterface": config.Config.DefaultInterface,
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Data: responseData,
+ })
+}
+
+// handleHandConfigs 获取手型配置处理函数
+func (s *LegacyServer) handleHandConfigs(c *gin.Context) {
+ allHandConfigs := s.mapper.GetAllHandConfigs()
+
+ result := make(map[string]any)
+ for _, ifName := range config.Config.AvailableInterfaces {
+ if handConfig, exists := allHandConfigs[ifName]; exists {
+ result[ifName] = map[string]any{
+ "handType": handConfig.HandType,
+ "handId": handConfig.HandId,
+ }
+ } else {
+ // 返回默认配置
+ result[ifName] = map[string]any{
+ "handType": "right",
+ "handId": define.HAND_TYPE_RIGHT,
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Data: result,
+ })
+}
+
+// handleHandType 手型设置处理函数
+func (s *LegacyServer) handleHandType(c *gin.Context) {
+ var req HandTypeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的手型设置请求:" + err.Error(),
+ })
+ return
+ }
+
+ // 验证接口
+ if !s.mapper.IsValidInterface(req.Interface) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 验证手型 ID
+ if req.HandType == "left" && req.HandId != uint32(define.HAND_TYPE_LEFT) {
+ req.HandId = uint32(define.HAND_TYPE_LEFT)
+ } else if req.HandType == "right" && req.HandId != uint32(define.HAND_TYPE_RIGHT) {
+ req.HandId = uint32(define.HAND_TYPE_RIGHT)
+ }
+
+ // 设置手型配置
+ if err := s.mapper.SetHandConfig(req.Interface, req.HandType, req.HandId); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "设置手型失败:" + err.Error(),
+ })
+ return
+ }
+
+ handTypeName := "右手"
+ if req.HandType == "left" {
+ handTypeName = "左手"
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: fmt.Sprintf("接口 %s 手型已设置为%s (0x%X)", req.Interface, handTypeName, req.HandId),
+ Data: map[string]any{
+ "interface": req.Interface,
+ "handType": req.HandType,
+ "handId": req.HandId,
+ },
+ })
+}
+
+// handleFingers 手指姿态处理函数
+func (s *LegacyServer) handleFingers(c *gin.Context) {
+ var req FingerPoseRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的手指姿态数据:" + err.Error(),
+ })
+ return
+ }
+
+ // 验证每个值是否在范围内
+ for _, v := range req.Pose {
+ if v < 0 || v > 255 {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "手指姿态值必须在 0-255 范围内",
+ })
+ return
+ }
+ }
+
+ // 如果未指定接口,使用默认接口
+ if req.Interface == "" {
+ req.Interface = config.Config.DefaultInterface
+ }
+
+ // 验证接口
+ if !s.mapper.IsValidInterface(req.Interface) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(req.Interface)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取设备失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 停止当前动画
+ if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "停止动画失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 设置手指姿态
+ if err := dev.SetFingerPose(req.Pose); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "发送手指姿态失败:" + err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: "手指姿态指令发送成功",
+ Data: map[string]any{"interface": req.Interface, "pose": req.Pose},
+ })
+}
+
+// handlePalm 掌部姿态处理函数
+func (s *LegacyServer) handlePalm(c *gin.Context) {
+ var req PalmPoseRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的掌部姿态数据:" + err.Error(),
+ })
+ return
+ }
+
+ // 验证每个值是否在范围内
+ for _, v := range req.Pose {
+ if v < 0 || v > 255 {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "掌部姿态值必须在 0-255 范围内",
+ })
+ return
+ }
+ }
+
+ // 如果未指定接口,使用默认接口
+ if req.Interface == "" {
+ req.Interface = config.Config.DefaultInterface
+ }
+
+ // 验证接口
+ if !s.mapper.IsValidInterface(req.Interface) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(req.Interface)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取设备失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 停止当前动画
+ if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "停止动画失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 设置掌部姿态
+ if err := dev.SetPalmPose(req.Pose); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "发送掌部姿态失败:" + err.Error(),
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: "掌部姿态指令发送成功",
+ Data: map[string]any{"interface": req.Interface, "pose": req.Pose},
+ })
+}
+
+// handlePreset 预设姿势处理函数
+func (s *LegacyServer) handlePreset(c *gin.Context) {
+ pose := c.Param("pose")
+
+ // 从查询参数获取接口名称和手型
+ ifName := c.Query("interface")
+ // handType := c.Query("handType") // TODO: 旧版 API 中声明但未使用,先放着,等 reivew 时候看看
+
+ if ifName == "" {
+ ifName = config.Config.DefaultInterface
+ }
+
+ // 验证接口
+ if !s.mapper.IsValidInterface(ifName) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(ifName)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取设备失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 停止当前动画
+ if err := s.mapper.StopAllAnimations(ifName); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "停止动画失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 获取预设姿势详细信息(用于返回具体参数)
+ presetDetails, exists := dev.GetPresetDetails(pose)
+ if !exists {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的预设姿势",
+ })
+ return
+ }
+
+ // 使用设备的预设姿势方法
+ if err := dev.ExecutePreset(pose); err != nil {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的预设姿势",
+ })
+ return
+ }
+
+ // 获取预设姿势的描述
+ description := dev.GetPresetDescription(pose)
+ message := fmt.Sprintf("已设置预设姿势: %s", pose)
+ if description != "" {
+ message = fmt.Sprintf("已设置%s", description)
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: message,
+ Data: map[string]any{"interface": ifName, "pose": presetDetails.FingerPose},
+ })
+}
+
+// handleAnimation 动画控制处理函数
+func (s *LegacyServer) handleAnimation(c *gin.Context) {
+ var req AnimationRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的动画请求:" + err.Error(),
+ })
+ return
+ }
+
+ // 如果未指定接口,使用默认接口
+ if req.Interface == "" {
+ req.Interface = config.Config.DefaultInterface
+ }
+
+ // 验证接口
+ if !s.mapper.IsValidInterface(req.Interface) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(req.Interface)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取设备失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 获取动画引擎
+ animEngine := dev.GetAnimationEngine()
+
+ // 停止当前动画
+ if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "停止动画失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 如果是停止命令,直接返回
+ if req.Type == "stop" {
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: fmt.Sprintf("%s 动画已停止", req.Interface),
+ })
+ return
+ }
+
+ // 处理速度参数
+ if req.Speed <= 0 {
+ req.Speed = 500 // 默认速度
+ }
+
+ // 根据类型启动动画
+ switch req.Type {
+ case "wave":
+ if err := animEngine.Start("wave", req.Speed); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("启动波浪动画失败:%v", err),
+ })
+ return
+ }
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: fmt.Sprintf("%s 波浪动画已启动", req.Interface),
+ Data: map[string]any{"interface": req.Interface, "speed": req.Speed},
+ })
+ case "sway":
+ if err := animEngine.Start("sway", req.Speed); err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("启动横向摆动动画失败:%v", err),
+ })
+ return
+ }
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Message: fmt.Sprintf("%s 横向摆动动画已启动", req.Interface),
+ Data: map[string]any{"interface": req.Interface, "speed": req.Speed},
+ })
+ default:
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: "无效的动画类型",
+ })
+ }
+}
+
+// handleSensors 获取传感器数据处理函数
+func (s *LegacyServer) handleSensors(c *gin.Context) {
+ // 从查询参数获取接口名称
+ ifName := c.Query("interface")
+
+ if ifName != "" {
+ // 验证接口
+ if !s.mapper.IsValidInterface(ifName) {
+ c.JSON(http.StatusBadRequest, define.ApiResponse{
+ Status: "error",
+ Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces),
+ })
+ return
+ }
+
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(ifName)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取设备失败:" + err.Error(),
+ })
+ return
+ }
+
+ sensorData, err := dev.ReadSensorData()
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, define.ApiResponse{
+ Status: "error",
+ Error: "获取传感器数据失败:" + err.Error(),
+ })
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Data: sensorData.Values(),
+ })
+ } else {
+ // 返回所有接口的传感器数据
+ allSensorData := make(map[string]any)
+
+ for _, ifName := range config.Config.AvailableInterfaces {
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(ifName)
+ if err != nil {
+ allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()}
+ continue
+ }
+
+ sensorData, err := dev.ReadSensorData()
+ if err != nil {
+ allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()}
+ continue
+ }
+
+ allSensorData[ifName] = sensorData.Values()
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Data: allSensorData,
+ })
+ }
+}
+
+// handleStatus 系统状态处理函数
+func (s *LegacyServer) handleStatus(c *gin.Context) {
+ // 构建动画状态
+ animationStatus := make(map[string]bool)
+
+ // 获取手型配置
+ allHandConfigs := s.mapper.GetAllHandConfigs()
+ handConfigsData := make(map[string]any)
+
+ // 构建接口状态信息
+ interfaceStatuses := make(map[string]any)
+
+ // 检查 CAN 服务状态 - 通过尝试获取设备状态来判断
+ canStatus := make(map[string]bool)
+
+ for ifName, handConfig := range allHandConfigs {
+ handConfigsData[ifName] = map[string]any{
+ "handType": handConfig.HandType,
+ "handId": handConfig.HandId,
+ }
+ }
+
+ for _, ifName := range config.Config.AvailableInterfaces {
+ // 获取对应的设备
+ dev, err := s.mapper.GetDeviceForInterface(ifName)
+ if err != nil {
+ // 设备不可用
+ animationStatus[ifName] = false
+ canStatus[ifName] = false
+ handConfigsData[ifName] = map[string]any{
+ "handType": "right",
+ "handId": define.HAND_TYPE_RIGHT,
+ }
+ } else {
+ // 获取动画状态
+ animEngine := dev.GetAnimationEngine()
+ animationStatus[ifName] = animEngine.IsRunning()
+
+ // 获取设备状态来判断 CAN 服务状态
+ rawCanStatus, err := dev.GetCanStatus()
+ if err != nil {
+ canStatus[ifName] = false
+ } else {
+ canStatus[ifName] = rawCanStatus[ifName]
+ }
+ }
+
+ // 构建接口状态
+ interfaceStatuses[ifName] = map[string]any{
+ "active": canStatus[ifName],
+ "animationActive": animationStatus[ifName],
+ "handConfig": handConfigsData[ifName],
+ }
+ }
+
+ // 计算活跃接口数量
+ activeInterfacesCount := 0
+ for _, active := range canStatus {
+ if active {
+ activeInterfacesCount++
+ }
+ }
+
+ c.JSON(http.StatusOK, define.ApiResponse{
+ Status: "success",
+ Data: map[string]any{
+ "interfaces": interfaceStatuses,
+ "uptime": time.Since(s.startTime).String(),
+ "canServiceURL": config.Config.CanServiceURL,
+ "defaultInterface": config.Config.DefaultInterface,
+ "availableInterfaces": config.Config.AvailableInterfaces,
+ "activeInterfaces": activeInterfacesCount,
+ "handConfigs": handConfigsData,
+ },
+ })
+}
diff --git a/api/legacy/models.go b/api/legacy/models.go
new file mode 100644
index 0000000..3403823
--- /dev/null
+++ b/api/legacy/models.go
@@ -0,0 +1,33 @@
+package legacy
+
+// FingerPoseRequest 手指姿态设置请求
+type FingerPoseRequest struct {
+ Interface string `json:"interface,omitempty"`
+ Pose []byte `json:"pose" binding:"required,len=6"`
+ HandType string `json:"handType,omitempty"` // 新增:手型类型
+ HandId uint32 `json:"handId,omitempty"` // 新增:CAN ID
+}
+
+// PalmPoseRequest 掌部姿态设置请求
+type PalmPoseRequest struct {
+ Interface string `json:"interface,omitempty"`
+ Pose []byte `json:"pose" binding:"required,len=4"`
+ HandType string `json:"handType,omitempty"` // 新增:手型类型
+ HandId uint32 `json:"handId,omitempty"` // 新增:CAN ID
+}
+
+// AnimationRequest 动画控制请求
+type AnimationRequest struct {
+ Interface string `json:"interface,omitempty"`
+ Type string `json:"type" binding:"required,oneof=wave sway stop"`
+ Speed int `json:"speed" binding:"min=0,max=2000"`
+ HandType string `json:"handType,omitempty"` // 新增:手型类型
+ HandId uint32 `json:"handId,omitempty"` // 新增:CAN ID
+}
+
+// HandTypeRequest 手型设置请求
+type HandTypeRequest struct {
+ Interface string `json:"interface" binding:"required"`
+ HandType string `json:"handType" binding:"required,oneof=left right"`
+ HandId uint32 `json:"handId" binding:"required"`
+}
diff --git a/api/legacy/router.go b/api/legacy/router.go
new file mode 100644
index 0000000..0fcc11c
--- /dev/null
+++ b/api/legacy/router.go
@@ -0,0 +1,64 @@
+package legacy
+
+import (
+ "time"
+
+ "hands/device"
+
+ "github.com/gin-gonic/gin"
+)
+
+type LegacyServer struct {
+ mapper *InterfaceDeviceMapper
+ startTime time.Time
+}
+
+// NewLegacyServer 创建新的兼容层 API 服务器实例
+func NewLegacyServer(deviceManager *device.DeviceManager) (*LegacyServer, error) {
+ mapper, err := NewInterfaceDeviceMapper(deviceManager)
+ if err != nil {
+ return nil, err
+ }
+
+ return &LegacyServer{
+ mapper: mapper,
+ startTime: time.Now(),
+ }, nil
+}
+
+// SetupRoutes 设置兼容层 API 路由
+func (s *LegacyServer) SetupRoutes(r *gin.Engine) {
+ // 兼容层 API 路由组
+ legacy := r.Group("/api/legacy")
+ {
+ // 手型设置 API
+ legacy.POST("/hand-type", s.handleHandType)
+
+ // 手指姿态 API
+ legacy.POST("/fingers", s.handleFingers)
+
+ // 掌部姿态 API
+ legacy.POST("/palm", s.handlePalm)
+
+ // 预设姿势 API
+ legacy.POST("/preset/:pose", s.handlePreset)
+
+ // 动画控制 API
+ legacy.POST("/animation", s.handleAnimation)
+
+ // 获取传感器数据 API
+ legacy.GET("/sensors", s.handleSensors)
+
+ // 系统状态 API
+ legacy.GET("/status", s.handleStatus)
+
+ // 获取可用接口列表 API
+ legacy.GET("/interfaces", s.handleInterfaces)
+
+ // 获取手型配置 API
+ legacy.GET("/hand-configs", s.handleHandConfigs)
+
+ // 健康检查端点
+ legacy.GET("/health", s.handleHealth)
+ }
+}
diff --git a/api/router.go b/api/router.go
index 5ad18ea..9ae9d38 100644
--- a/api/router.go
+++ b/api/router.go
@@ -67,8 +67,7 @@ func (s *Server) SetupRoutes(r *gin.Engine) {
// 传感器数据路由
sensors := deviceRoutes.Group("/sensors")
{
- sensors.GET("", s.handleGetSensors) // 获取所有传感器数据
- sensors.GET("/:sensorId", s.handleGetSensorData) // 获取特定传感器数据
+ sensors.GET("", s.handleGetSensors) // 获取所有传感器数据
}
// 设备状态路由
diff --git a/api/sensor_handlers.go b/api/sensor_handlers.go
index 67a35f8..dfb1365 100644
--- a/api/sensor_handlers.go
+++ b/api/sensor_handlers.go
@@ -3,7 +3,6 @@ package api
import (
"fmt"
"net/http"
- "time"
"hands/device"
@@ -24,103 +23,17 @@ func (s *Server) handleGetSensors(c *gin.Context) {
return
}
- // 获取设备的传感器组件
- sensorComponents := dev.GetComponents(device.SensorComponent)
-
- sensors := make([]SensorDataResponse, 0, len(sensorComponents))
-
- // 遍历所有传感器组件,读取数据
- for _, component := range sensorComponents {
- sensorId := component.GetID()
-
- // 读取传感器数据
- sensorData, err := dev.ReadSensorData(sensorId)
- if err != nil {
- // 如果读取失败,创建一个错误状态的传感器数据
- sensors = append(sensors, SensorDataResponse{
- SensorID: sensorId,
- Timestamp: time.Now(),
- Values: map[string]any{
- "error": err.Error(),
- "status": "error",
- },
- })
- continue
- }
-
- // 转换为响应格式
- sensorResponse := SensorDataResponse{
- SensorID: sensorData.SensorID(),
- Timestamp: sensorData.Timestamp(),
- Values: sensorData.Values(),
- }
- sensors = append(sensors, sensorResponse)
- }
-
- response := SensorListResponse{
- Sensors: sensors,
- Total: len(sensors),
- }
-
- c.JSON(http.StatusOK, ApiResponse{
- Status: "success",
- Data: response,
- })
-}
-
-// handleGetSensorData 获取特定传感器数据
-func (s *Server) handleGetSensorData(c *gin.Context) {
- deviceId := c.Param("deviceId")
- sensorId := c.Param("sensorId")
-
- // 获取设备
- dev, err := s.deviceManager.GetDevice(deviceId)
- if err != nil {
- c.JSON(http.StatusNotFound, ApiResponse{
- Status: "error",
- Error: fmt.Sprintf("设备 %s 不存在", deviceId),
- })
- return
- }
-
- // 验证传感器是否存在
- sensorComponents := dev.GetComponents(device.SensorComponent)
- sensorExists := false
- for _, component := range sensorComponents {
- if component.GetID() == sensorId {
- sensorExists = true
- break
- }
- }
-
- if !sensorExists {
- c.JSON(http.StatusNotFound, ApiResponse{
- Status: "error",
- Error: fmt.Sprintf("设备 %s 上不存在传感器 %s", deviceId, sensorId),
- })
- return
- }
-
- // 读取传感器数据
- sensorData, err := dev.ReadSensorData(sensorId)
+ sensorData, err := dev.ReadSensorData()
if err != nil {
c.JSON(http.StatusInternalServerError, ApiResponse{
Status: "error",
- Error: fmt.Sprintf("读取传感器 %s 数据失败:%v", sensorId, err),
+ Error: fmt.Sprintf("读取传感器数据失败:%v", err),
})
- return
- }
-
- // 转换为响应格式
- response := SensorDataResponse{
- SensorID: sensorData.SensorID(),
- Timestamp: sensorData.Timestamp(),
- Values: sensorData.Values(),
}
c.JSON(http.StatusOK, ApiResponse{
Status: "success",
- Data: response,
+ Data: sensorData.Values(),
})
}
diff --git a/communication/communicator.go b/communication/communicator.go
index 978840d..4b19639 100644
--- a/communication/communicator.go
+++ b/communication/communicator.go
@@ -5,6 +5,8 @@ import (
"context"
"encoding/json"
"fmt"
+ "hands/config"
+ "hands/define"
"io"
"net/http"
"time"
@@ -23,9 +25,6 @@ type Communicator interface {
// SendMessage 将 RawMessage 通过 HTTP POST 请求发送到 can-bridge 服务
SendMessage(ctx context.Context, msg RawMessage) error
- // GetInterfaceStatus 获取指定 CAN 接口的状态
- GetInterfaceStatus(ifName string) (isActive bool, err error)
-
// GetAllInterfaceStatuses 获取所有已知 CAN 接口的状态
GetAllInterfaceStatuses() (statuses map[string]bool, err error)
@@ -45,9 +44,7 @@ type CanBridgeClient struct {
func NewCanBridgeClient(serviceURL string) Communicator {
return &CanBridgeClient{
serviceURL: serviceURL,
- client: &http.Client{
- Timeout: 5 * time.Second,
- },
+ client: &http.Client{Timeout: 5 * time.Second},
}
}
@@ -60,7 +57,7 @@ func (c *CanBridgeClient) SendMessage(ctx context.Context, msg RawMessage) error
url := fmt.Sprintf("%s/api/can", c.serviceURL)
// 创建带有 context 的请求
- req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("创建 HTTP 请求失败:%w", err)
}
@@ -80,47 +77,49 @@ func (c *CanBridgeClient) SendMessage(ctx context.Context, msg RawMessage) error
return nil
}
-func (c *CanBridgeClient) GetInterfaceStatus(ifName string) (bool, error) {
- url := fmt.Sprintf("%s/api/status/%s", c.serviceURL, ifName)
- resp, err := c.client.Get(url)
- if err != nil {
- return false, fmt.Errorf("获取接口状态失败:%w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return false, fmt.Errorf("can-bridge 服务返回错误:%d", resp.StatusCode)
- }
-
- var status struct {
- Active bool `json:"active"`
- }
-
- if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
- return false, fmt.Errorf("解析状态响应失败:%w", err)
- }
-
- return status.Active, nil
-}
-
func (c *CanBridgeClient) GetAllInterfaceStatuses() (map[string]bool, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
url := fmt.Sprintf("%s/api/status", c.serviceURL)
- resp, err := c.client.Get(url)
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("获取所有接口状态失败:%w", err)
}
+
+ resp, err := c.client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("发送 HTTP 请求失败:%w", err)
+ }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("can-bridge 服务返回错误:%d", resp.StatusCode)
}
- var statuses map[string]bool
- if err := json.NewDecoder(resp.Body).Decode(&statuses); err != nil {
+ var statusResp define.ApiResponse
+ if err := json.NewDecoder(resp.Body).Decode(&statusResp); err != nil {
return nil, fmt.Errorf("解析状态响应失败:%w", err)
}
- return statuses, nil
+ result := make(map[string]bool)
+ for _, ifName := range config.Config.AvailableInterfaces {
+ result[ifName] = false
+ }
+
+ if statusData, ok := statusResp.Data.(map[string]interface{}); ok {
+ if interfaces, ok := statusData["interfaces"].(map[string]interface{}); ok {
+ for ifName, ifStatus := range interfaces {
+ if status, ok := ifStatus.(map[string]interface{}); ok {
+ if active, ok := status["active"].(bool); ok {
+ result[ifName] = active
+ }
+ }
+ }
+ }
+ }
+
+ return result, nil
}
func (c *CanBridgeClient) SetServiceURL(url string) { c.serviceURL = url }
diff --git a/component/pressure_sensor.go b/component/pressure_sensor.go
deleted file mode 100644
index 1c74a69..0000000
--- a/component/pressure_sensor.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package component
-
-import (
- "fmt"
- "hands/device"
- "math/rand/v2"
- "time"
-)
-
-// PressureSensor 压力传感器实现
-type PressureSensor struct {
- id string
- config map[string]any
- isActive bool
- samplingRate int
- lastReading time.Time
-}
-
-func NewPressureSensor(id string, config map[string]any) *PressureSensor {
- return &PressureSensor{
- id: id,
- config: config,
- isActive: true,
- samplingRate: 100,
- lastReading: time.Now(),
- }
-}
-
-func (p *PressureSensor) GetID() string {
- return p.id
-}
-
-func (p *PressureSensor) GetType() device.ComponentType {
- return device.SensorComponent
-}
-
-func (p *PressureSensor) GetConfiguration() map[string]any {
- return p.config
-}
-
-func (p *PressureSensor) IsActive() bool {
- return p.isActive
-}
-
-func (p *PressureSensor) ReadData() (device.SensorData, error) {
- if !p.isActive {
- return nil, fmt.Errorf("传感器 %s 未激活", p.id)
- }
-
- // 模拟压力数据读取
- // 在实际实现中,这里应该从 can-bridge 或其他数据源读取真实数据
- pressure := rand.Float64() * 100 // 0-100 的随机压力值
-
- values := map[string]any{
- "pressure": pressure,
- "unit": "kPa",
- "location": p.config["location"],
- }
-
- p.lastReading = time.Now()
- return NewSensorData(p.id, values), nil
-}
-
-func (p *PressureSensor) GetDataType() string {
- return "pressure"
-}
-
-func (p *PressureSensor) GetSamplingRate() int {
- return p.samplingRate
-}
-
-func (p *PressureSensor) SetSamplingRate(rate int) error {
- if rate <= 0 || rate > 1000 {
- return fmt.Errorf("采样率必须在 1-1000Hz 之间")
- }
- p.samplingRate = rate
- return nil
-}
diff --git a/component/sensor.go b/component/sensor.go
index 66b9265..7ed7aa9 100644
--- a/component/sensor.go
+++ b/component/sensor.go
@@ -2,41 +2,69 @@ package component
import (
"hands/device"
+ "math/rand/v2"
"time"
)
// Sensor 传感器组件接口
type Sensor interface {
- device.Component
+ // device.Component
ReadData() (device.SensorData, error)
- GetDataType() string
- GetSamplingRate() int
- SetSamplingRate(rate int) error
+ // GetDataType() string
+ // GetSamplingRate() int
+ // SetSamplingRate(rate int) error
+ MockData()
}
// SensorDataImpl 传感器数据的具体实现
type SensorDataImpl struct {
- timestamp time.Time
- values map[string]any
- sensorID string
+ Interface string `json:"interface"`
+ Thumb int `json:"thumb"`
+ Index int `json:"index"`
+ Middle int `json:"middle"`
+ Ring int `json:"ring"`
+ Pinky int `json:"pinky"`
+ PalmPosition []byte `json:"palmPosition"`
+ LastUpdate time.Time `json:"lastUpdate"`
}
-func NewSensorData(sensorID string, values map[string]any) *SensorDataImpl {
+func NewSensorData(ifName string) *SensorDataImpl {
return &SensorDataImpl{
- timestamp: time.Now(),
- values: values,
- sensorID: sensorID,
+ Interface: ifName,
+ Thumb: 0,
+ Index: 0,
+ Middle: 0,
+ Ring: 0,
+ Pinky: 0,
+ PalmPosition: []byte{128, 128, 128, 128},
+ LastUpdate: time.Now(),
}
}
-func (s *SensorDataImpl) Timestamp() time.Time {
- return s.timestamp
+func (s *SensorDataImpl) MockData() {
+ go func() {
+ for {
+ s.Thumb = rand.IntN(101)
+ s.Index = rand.IntN(101)
+ s.Middle = rand.IntN(101)
+ s.Ring = rand.IntN(101)
+ s.Pinky = rand.IntN(101)
+ s.LastUpdate = time.Now()
+ time.Sleep(500 * time.Millisecond)
+ }
+ }()
}
func (s *SensorDataImpl) Values() map[string]any {
- return s.values
+ return map[string]any{
+ "thumb": s.Thumb,
+ "index": s.Index,
+ "middle": s.Middle,
+ "ring": s.Ring,
+ "pinky": s.Pinky,
+ "palmPosition": s.PalmPosition,
+ "lastUpdate": s.LastUpdate,
+ }
}
-func (s *SensorDataImpl) SensorID() string {
- return s.sensorID
-}
+func (s *SensorDataImpl) ReadData() (device.SensorData, error) { return s, nil }
diff --git a/device/commands.go b/device/commands.go
index fb14a71..027e8d2 100644
--- a/device/commands.go
+++ b/device/commands.go
@@ -1,80 +1,40 @@
package device
// FingerPoseCommand 手指姿态指令
-type FingerPoseCommand struct {
- fingerID string
- poseData []byte
- targetComp string
+type FingerPoseCommand struct{ poseData []byte }
+
+func NewFingerPoseCommand(poseData []byte) *FingerPoseCommand {
+ return &FingerPoseCommand{poseData: poseData}
}
-func NewFingerPoseCommand(fingerID string, poseData []byte) *FingerPoseCommand {
- return &FingerPoseCommand{
- fingerID: fingerID,
- poseData: poseData,
- targetComp: "finger_" + fingerID,
- }
-}
+func (c *FingerPoseCommand) Type() string { return "SetFingerPose" }
-func (c *FingerPoseCommand) Type() string {
- return "SetFingerPose"
-}
-
-func (c *FingerPoseCommand) Payload() []byte {
- return c.poseData
-}
-
-func (c *FingerPoseCommand) TargetComponent() string {
- return c.targetComp
-}
+func (c *FingerPoseCommand) Payload() []byte { return c.poseData }
// PalmPoseCommand 手掌姿态指令
-type PalmPoseCommand struct {
- poseData []byte
- targetComp string
-}
+type PalmPoseCommand struct{ poseData []byte }
func NewPalmPoseCommand(poseData []byte) *PalmPoseCommand {
- return &PalmPoseCommand{
- poseData: poseData,
- targetComp: "palm",
- }
+ return &PalmPoseCommand{poseData: poseData}
}
-func (c *PalmPoseCommand) Type() string {
- return "SetPalmPose"
-}
+func (c *PalmPoseCommand) Type() string { return "SetPalmPose" }
-func (c *PalmPoseCommand) Payload() []byte {
- return c.poseData
-}
-
-func (c *PalmPoseCommand) TargetComponent() string {
- return c.targetComp
-}
+func (c *PalmPoseCommand) Payload() []byte { return c.poseData }
// GenericCommand 通用指令
type GenericCommand struct {
- cmdType string
- payload []byte
- targetComp string
+ cmdType string
+ payload []byte
}
func NewGenericCommand(cmdType string, payload []byte, targetComp string) *GenericCommand {
return &GenericCommand{
- cmdType: cmdType,
- payload: payload,
- targetComp: targetComp,
+ cmdType: cmdType,
+ payload: payload,
}
}
-func (c *GenericCommand) Type() string {
- return c.cmdType
-}
+func (c *GenericCommand) Type() string { return c.cmdType }
-func (c *GenericCommand) Payload() []byte {
- return c.payload
-}
-
-func (c *GenericCommand) TargetComponent() string {
- return c.targetComp
-}
+func (c *GenericCommand) Payload() []byte { return c.payload }
diff --git a/device/device.go b/device/device.go
index 2f9d54f..ebc7df6 100644
--- a/device/device.go
+++ b/device/device.go
@@ -12,8 +12,9 @@ type Device interface {
GetHandType() define.HandType // 获取设备手型
SetHandType(handType define.HandType) error // 设置设备手型
ExecuteCommand(cmd Command) error // 执行一个通用指令
- ReadSensorData(sensorID string) (SensorData, error) // 读取特定传感器数据
+ ReadSensorData() (SensorData, error) // 读取特定传感器数据
GetComponents(componentType ComponentType) []Component // 获取指定类型的组件
+ GetCanStatus() (map[string]bool, error)
GetStatus() (DeviceStatus, error) // 获取设备状态
Connect() error // 连接设备
Disconnect() error // 断开设备连接
@@ -23,23 +24,23 @@ type Device interface {
GetAnimationEngine() *AnimationEngine // 获取设备的动画引擎
// --- 预设姿势相关方法 ---
- GetSupportedPresets() []string // 获取支持的预设姿势列表
- ExecutePreset(presetName string) error // 执行预设姿势
- GetPresetDescription(presetName string) string // 获取预设姿势描述
+ GetSupportedPresets() []string // 获取支持的预设姿势列表
+ ExecutePreset(presetName string) error // 执行预设姿势
+ GetPresetDescription(presetName string) string // 获取预设姿势描述
+ GetPresetDetails(presetName string) (PresetPose, bool) // 获取预设姿势详细信息
}
// Command 代表一个发送给设备的指令
type Command interface {
- Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle"
- Payload() []byte // 指令的实际数据
- TargetComponent() string // 目标组件 ID
+ Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle"
+ Payload() []byte // 指令的实际数据
}
// SensorData 代表从传感器读取的数据
type SensorData interface {
- Timestamp() time.Time
+ // Timestamp() time.Time
Values() map[string]any // 例如 {"pressure": 100, "angle": 30.5}
- SensorID() string
+ // SensorID() string
}
// ComponentType 定义组件类型
@@ -53,10 +54,10 @@ const (
// Component 代表设备的一个可插拔组件
type Component interface {
- GetID() string
- GetType() ComponentType
- GetConfiguration() map[string]interface{} // 组件的特定配置
- IsActive() bool
+ // GetID() string
+ // GetType() ComponentType
+ // GetConfiguration() map[string]interface{} // 组件的特定配置
+ // IsActive() bool
}
// DeviceStatus 代表设备状态
diff --git a/device/models/l10.go b/device/models/l10.go
index 221853e..de953db 100644
--- a/device/models/l10.go
+++ b/device/models/l10.go
@@ -142,7 +142,7 @@ func (h *L10Hand) SetFingerPose(pose []byte) error {
}
// 创建指令
- cmd := device.NewFingerPoseCommand("all", perturbedPose)
+ cmd := device.NewFingerPoseCommand(perturbedPose)
// 执行指令
err := h.ExecuteCommand(cmd)
@@ -197,11 +197,9 @@ func (h *L10Hand) ResetPose() error {
return nil
}
-// commandToRawMessage 将通用指令转换为 L10 特定的 CAN 消息
-func (h *L10Hand) commandToRawMessage(cmd device.Command) (communication.RawMessage, error) {
- h.mutex.RLock()
- defer h.mutex.RUnlock()
-
+// commandToRawMessageUnsafe 将通用指令转换为 L10 特定的 CAN 消息(不加锁版本)
+// 注意:此方法不是线程安全的,只应在已获取适当锁的情况下调用
+func (h *L10Hand) commandToRawMessageUnsafe(cmd device.Command) (communication.RawMessage, error) {
var data []byte
canID := uint32(h.handType)
@@ -238,8 +236,8 @@ func (h *L10Hand) ExecuteCommand(cmd device.Command) error {
return fmt.Errorf("设备 %s 未连接或未激活", h.id)
}
- // 转换指令为 CAN 消息
- rawMsg, err := h.commandToRawMessage(cmd)
+ // 转换指令为 CAN 消息(使用不加锁版本,因为已经在写锁保护下)
+ rawMsg, err := h.commandToRawMessageUnsafe(cmd)
if err != nil {
h.status.ErrorCount++
h.status.LastError = err.Error()
@@ -259,22 +257,15 @@ func (h *L10Hand) ExecuteCommand(cmd device.Command) error {
}
h.status.LastUpdate = time.Now()
- // 成功的日志记录移到 SetFingerPose 和 SetPalmPose 中,因为那里有更详细的信息
+
return nil
}
-// --- 其他 L10Hand 方法 (initializeComponents, GetID, GetModel, ReadSensorData, etc.) 保持不变 ---
-// --- 确保它们存在且与您上传的版本一致 ---
-
func (h *L10Hand) initializeComponents(_ map[string]any) error {
// 初始化传感器组件
- sensors := []device.Component{
- component.NewPressureSensor("pressure_thumb", map[string]any{"location": "thumb"}),
- component.NewPressureSensor("pressure_index", map[string]any{"location": "index"}),
- component.NewPressureSensor("pressure_middle", map[string]any{"location": "middle"}),
- component.NewPressureSensor("pressure_ring", map[string]any{"location": "ring"}),
- component.NewPressureSensor("pressure_pinky", map[string]any{"location": "pinky"}),
- }
+ defaultSensor := component.NewSensorData(h.canInterface)
+ defaultSensor.MockData()
+ sensors := []device.Component{defaultSensor}
h.components[device.SensorComponent] = sensors
return nil
}
@@ -287,19 +278,17 @@ func (h *L10Hand) GetModel() string {
return h.model
}
-func (h *L10Hand) ReadSensorData(sensorID string) (device.SensorData, error) {
+func (h *L10Hand) ReadSensorData() (device.SensorData, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
sensors := h.components[device.SensorComponent]
for _, comp := range sensors {
- if comp.GetID() == sensorID {
- if sensor, ok := comp.(component.Sensor); ok {
- return sensor.ReadData()
- }
+ if sensor, ok := comp.(component.Sensor); ok {
+ return sensor.ReadData()
}
}
- return nil, fmt.Errorf("传感器 %s 不存在", sensorID)
+ return nil, fmt.Errorf("传感器不存在")
}
func (h *L10Hand) GetComponents(componentType device.ComponentType) []device.Component {
@@ -378,3 +367,12 @@ func (h *L10Hand) ExecutePreset(presetName string) error {
func (h *L10Hand) GetPresetDescription(presetName string) string {
return h.presetManager.GetPresetDescription(presetName)
}
+
+// GetPresetDetails 获取预设姿势详细信息
+func (h *L10Hand) GetPresetDetails(presetName string) (device.PresetPose, bool) {
+ return h.presetManager.GetPreset(presetName)
+}
+
+func (h *L10Hand) GetCanStatus() (map[string]bool, error) {
+ return h.communicator.GetAllInterfaceStatuses()
+}
diff --git a/docs/contribute_CN.md b/docs/contribute_CN.md
index 2eeb08f..6a7d339 100644
--- a/docs/contribute_CN.md
+++ b/docs/contribute_CN.md
@@ -27,6 +27,7 @@ type Device interface {
GetSupportedPresets() []string
ExecutePreset(presetName string) error
GetPresetDescription(presetName string) string
+ GetPresetDetails(presetName string) (PresetPose, bool)
}
```
diff --git a/main.go b/main.go
index 9d5dcda..0d24b19 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
"hands/api"
+ "hands/api/legacy"
"hands/cli"
"hands/config"
"hands/device"
@@ -94,8 +95,15 @@ func main() {
models.RegisterDeviceTypes()
+ deviceManager := device.NewDeviceManager()
+
// 设置 API 路由
- api.NewServer(device.NewDeviceManager()).SetupRoutes(r)
+ api.NewServer(deviceManager).SetupRoutes(r)
+ legacyServer, err := legacy.NewLegacyServer(deviceManager)
+ if err != nil {
+ log.Fatalf("❌ 初始化旧版 API 失败: %v", err)
+ }
+ legacyServer.SetupRoutes(r)
// 启动服务器
log.Printf("🌐 CAN 控制服务运行在 http://localhost:%s", config.Config.WebPort)
diff --git a/static/script.js b/static/script.js
index c7d152a..2b15a95 100644
--- a/static/script.js
+++ b/static/script.js
@@ -2,1280 +2,1424 @@
let availableInterfaces = [];
let interfaceStatus = {};
let handConfigs = {}; // 存储每个手的配置
-let handTypeIds = {
- 'left': 0x28, // HAND_TYPE_LEFT
- 'right': 0x27 // HAND_TYPE_RIGHT
+const 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 // 默认动画速度
+ // 常量定义
+ 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: (func, delay) => {
+ let timer;
+ return () => {
+ 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: () => {
+ const pose = [];
+ for (let i = 0; i < 6; i++) {
+ pose.push(Number.parseInt(document.getElementById(`finger${i}`).value));
+ }
+ return pose;
+ },
+
+ // 获取掌部姿态值
+ getPalmPoseValues: () => {
+ const pose = [];
+ for (let i = 0; i < 4; i++) {
+ pose.push(Number.parseInt(document.getElementById(`palm${i}`).value));
+ }
+ return pose;
+ },
+
+ // 设置手指滑块值
+ applyFingerPreset: (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: (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: (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: (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/legacy/sensors")
+ .then((response) => response.json())
+ .then((data) => {
+ if (data.status === "success") {
+ this.updateSensorDisplay(data.data);
}
- },
+ })
+ .catch((error) => {
+ console.error("获取传感器数据失败:", error);
+ });
+ },
- // 预设姿势配置
- 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
+ // 更新传感器显示
+ updateSensorDisplay: function (data) {
+ const sensorDisplay = document.getElementById("sensor-data");
+ if (!sensorDisplay || !data) return;
- PINCH: [114, 63, 136, 0, 0, 0], // 捏取姿势
- PALM_PINCH: [255, 163, 255, 127],
+ // 创建进度条显示
+ let html = '
';
- OK: [124, 31, 132, 255, 255, 255],
- PALM_OK: [255, 163, 255, 127],
+ // 手指压力传感器
+ 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);
- BIG_FIST: [49, 32, 40, 36, 41, 46], // 大握拳
- PALM_BIG_FIST: [255, 235, 128, 128], // 大握拳掌部
+ html += "
";
- BIG_OPEN: [255, 255, 255, 255, 255, 255], // 大张开
- PALM_BIG_OPEN: [128, 128, 128, 128], // 大张开掌部
+ // 更新最后更新时间
+ const lastUpdate = new Date(data.lastUpdate).toLocaleTimeString();
+ html += `最后更新:${lastUpdate}
`;
- YEAH: [0, 103, 255, 255, 0, 0], // Yeah!
- PALM_YEAH: [255, 235, 128, 128], // Yeah! 掌部
+ sensorDisplay.innerHTML = html;
+ },
- // 数字手势预设
- 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 `
+ // 创建传感器行
+ createSensorRow: (label, value) => {
+ if (value === undefined || value === null) value = 0;
+ return `
${label} |
|
${value}% |
`;
- }
+ },
};
// 页面加载时初始化
-document.addEventListener('DOMContentLoaded', function() {
- initializeSystem();
- setupEventListeners();
- setupSliderEvents();
- LinkerHandController.initSliderDisplays();
- LinkerHandController.startSensorDataPolling();
- startStatusUpdater();
+document.addEventListener("DOMContentLoaded", () => {
+ 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();
- }
+ 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}`);
- }
+ try {
+ logMessage("info", "正在获取可用 CAN 接口...");
+ const response = await fetch("/api/legacy/interfaces");
- 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'];
+ 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;
+ 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}`);
}
-
- // 清空现有内容
- 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完全构建
+ 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 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;
}
+ });
- 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;
+ 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}`);
+ 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 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];
+ const handEmoji = config.handType === "left" ? "✋" : "🤚";
+ const handLabel = config.handType === "left" ? "左手" : "右手";
+ const handId = handTypeIds[config.handType];
- // 确保 HTML 结构完整且正确
- div.innerHTML = `
+ // 确保 HTML 结构完整且正确
+ div.innerHTML = `
-
- ${handEmoji} ${config.interface} - ${handLabel}
+
+ ${handEmoji} ${
+ config.interface
+ } - ${handLabel}
-
-
-
-
-
+
+
+
+
-
+
检查中...
`;
- // 使用 requestAnimationFrame 确保 DOM 完全渲染后再设置事件监听器
- requestAnimationFrame(() => {
- setTimeout(() => {
- setupHandEventListeners(config.id);
- }, 0);
- });
+ // 使用 requestAnimationFrame 确保 DOM 完全渲染后再设置事件监听器
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ setupHandEventListeners(config.id);
+ }, 0);
+ });
- return div;
+ return div;
}
// 设置手部事件监听器
function setupHandEventListeners(handId) {
- // 使用更安全的元素获取方式
- const checkbox = document.getElementById(`${handId}_checkbox`);
- const interfaceSelect = document.getElementById(`${handId}_interface`);
- const handTypeSelect = document.getElementById(`${handId}_handtype`);
+ // 使用更安全的元素获取方式
+ 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 (!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 ? "启用" : "禁用"}`);
}
-
- if (!interfaceSelect) {
- console.error(`setupHandEventListeners: 找不到 interfaceSelect - ${handId}_interface`);
- return;
+ };
+
+ interfaceSelect._changeHandler = function () {
+ if (handConfigs[handId]) {
+ handConfigs[handId].interface = this.value;
+ logMessage("info", `${handId}: 接口切换到 ${this.value}`);
+ checkSingleInterfaceStatus(handId);
}
-
- if (!handTypeSelect) {
- console.error(`setupHandEventListeners: 找不到 handTypeSelect - ${handId}_handtype`);
- return;
+ };
+
+ 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.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);
+ // 添加事件监听器
+ 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];
+ const config = handConfigs[handId];
+ const element = document.getElementById(handId);
- // 更新样式
- 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}`);
- }
+ // 添加安全检查
+ if (!element || !config) {
+ console.warn(`updateHandElement: 找不到元素或配置 - handId: ${handId}`);
+ return;
+ }
- // 安全地更新手型标签
- 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 handEmoji = config.handType === "left" ? "✋" : "🤚";
+ const handLabel = config.handType === "left" ? "左手" : "右手";
+ const handIdHex = handTypeIds[config.handType];
- // 确保选择框的值也同步更新
- const handTypeSelect = document.getElementById(`${handId}_handtype`);
- if (handTypeSelect) {
- handTypeSelect.value = config.handType;
- }
+ // 更新样式
+ element.className = `hand-item ${config.enabled ? "enabled" : "disabled"}`;
- const interfaceSelect = document.getElementById(`${handId}_interface`);
- if (interfaceSelect) {
- interfaceSelect.value = config.interface;
- }
+ // 安全地更新标题
+ const title = element.querySelector(".hand-title");
+ if (title) {
+ title.textContent = `${handEmoji} ${config.interface} - ${handLabel}`;
+ } else {
+ console.warn(
+ `updateHandElement: 找不到 .hand-title 元素 - handId: ${handId}`,
+ );
+ }
- const checkbox = document.getElementById(`${handId}_checkbox`);
- if (checkbox) {
- checkbox.checked = config.enabled;
+ // 安全地更新手型标签
+ 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;
- }
+ 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}`);
- }
+ try {
+ const response = await fetch("/api/legacy/status");
- 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();
+ 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();
+ 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();
+ 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();
+ 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;
- }
+ 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';
+ document.getElementById("connection-warning").style.display = "block";
}
// 隐藏连接警告
function hideConnectionWarning() {
- document.getElementById('connection-warning').style.display = 'none';
+ document.getElementById("connection-warning").style.display = "none";
}
// 获取启用的手部配置
function getEnabledHands() {
- return Object.values(handConfigs).filter(config => config.enabled);
+ return Object.values(handConfigs).filter((config) => config.enabled);
}
// 设置事件监听器
function setupEventListeners() {
- const delayDefault = 30;
+ const delayDefault = 30;
- // 刷新所有接口按钮
- document.getElementById('refresh-all').addEventListener('click', function() {
- logMessage('info', '手动刷新所有接口...');
- initializeSystem();
- });
+ // 刷新所有接口按钮
+ document.getElementById("refresh-all").addEventListener("click", () => {
+ 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("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);
+ // 动画按钮
+ 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();
+ // 预设姿势按钮 - 使用 LinkerHandController 的预设
+ setupPresetButtons();
- // 数字手势按钮事件
- setupNumericPresets();
+ // 数字手势按钮事件
+ setupNumericPresets();
- // Refill core 按钮
- setupRefillCore();
+ // Refill core 按钮
+ setupRefillCore();
}
// 设置预设按钮
function setupPresetButtons() {
- const delayDefault = 30;
+ 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' }
- };
+ // 基础预设姿势
+ 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);
- }
- });
+ 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;
+ 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)];
+ // 数字 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);
- });
+ 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] || '';
+ 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();
+ document.getElementById("refill-core").addEventListener("click", () => {
+ event.preventDefault();
+ event.stopPropagation();
- console.log("refill-core");
+ 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 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
- });
+ 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;
+ // 手指滑块
+ 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 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));
- }
+ const pose = [];
+ for (let i = 0; i < 6; i++) {
+ pose.push(Number.parseInt(document.getElementById(`finger${i}`).value));
+ }
- logMessage('info', `发送手指姿态到 ${enabledHands.length} 个启用的手部...`);
+ logMessage("info", `发送手指姿态到 ${enabledHands.length} 个启用的手部...`);
- for (const config of enabledHands) {
- await sendFingerPoseToHand(config, pose);
- }
+ for (const config of enabledHands) {
+ await sendFingerPoseToHand(config, pose);
+ }
}
// 发送所有启用手部的掌部姿态
async function sendAllPalmPoses() {
- const enabledHands = getEnabledHands();
- if (enabledHands.length === 0) {
- logMessage('error', '没有启用的手部');
- return;
- }
+ 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));
- }
+ const pose = [];
+ for (let i = 0; i < 4; i++) {
+ pose.push(Number.parseInt(document.getElementById(`palm${i}`).value));
+ }
- logMessage('info', `发送掌部姿态到 ${enabledHands.length} 个启用的手部...`);
+ logMessage("info", `发送掌部姿态到 ${enabledHands.length} 个启用的手部...`);
- for (const config of enabledHands) {
- await sendPalmPoseToHand(config, pose);
- }
+ 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]
- })
- });
+ try {
+ const response = await fetch("/api/legacy/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}`);
+ 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]
- })
- });
+ try {
+ const response = await fetch("/api/legacy/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}`);
+ 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;
- }
+ const enabledHands = getEnabledHands();
+ if (enabledHands.length === 0) {
+ logMessage("error", "没有启用的手部");
+ return;
+ }
- logMessage('info', `设置预设姿势 "${preset}" 到 ${enabledHands.length} 个启用的手部...`);
+ logMessage(
+ "info",
+ `设置预设姿势 "${preset}" 到 ${enabledHands.length} 个启用的手部...`,
+ );
- for (const config of enabledHands) {
- await setPresetPoseToHand(config, preset);
- }
+ 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'
- });
+ try {
+ const response = await fetch(
+ `/api/legacy/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}`);
+ 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 enabledHands = getEnabledHands();
+ if (enabledHands.length === 0) {
+ logMessage("error", "没有启用的手部");
+ return;
+ }
- const speed = parseInt(document.getElementById('animation-speed').value);
- logMessage('info', `启动 "${type}" 动画到 ${enabledHands.length} 个启用的手部...`);
+ const speed = Number.parseInt(
+ document.getElementById("animation-speed").value,
+ );
+ logMessage(
+ "info",
+ `启动 "${type}" 动画到 ${enabledHands.length} 个启用的手部...`,
+ );
- for (const config of enabledHands) {
- await startAnimationForHand(config, type, speed);
- }
+ 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]
- })
- });
+ try {
+ const response = await fetch("/api/legacy/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}`);
+ 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;
- }
+ const enabledHands = getEnabledHands();
+ if (enabledHands.length === 0) {
+ logMessage("error", "没有启用的手部");
+ return;
+ }
- logMessage('info', `停止 ${enabledHands.length} 个启用手部的动画...`);
+ logMessage("info", `停止 ${enabledHands.length} 个启用手部的动画...`);
- for (const config of enabledHands) {
- await stopAnimationForHand(config);
- }
+ 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]
- })
- });
+ try {
+ const response = await fetch("/api/legacy/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}`);
+ 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;
- }
+ const enabledHands = getEnabledHands();
+ if (enabledHands.length === 0) {
+ logMessage("error", "没有启用的手部");
+ return;
+ }
- // 重置滑块值
- LinkerHandController.applyFingerPreset(LinkerHandController.PRESETS.OPEN);
- LinkerHandController.applyPalmPreset(LinkerHandController.PRESETS.PALM_NEUTRAL);
+ // 重置滑块值
+ LinkerHandController.applyFingerPreset(LinkerHandController.PRESETS.OPEN);
+ LinkerHandController.applyPalmPreset(
+ LinkerHandController.PRESETS.PALM_NEUTRAL,
+ );
- logMessage('info', `重置 ${enabledHands.length} 个启用的手部...`);
+ logMessage("info", `重置 ${enabledHands.length} 个启用的手部...`);
- // 停止所有动画
- await stopAllAnimations();
-
- // 发送重置姿态
- await sendAllFingerPoses();
- await sendAllPalmPoses();
+ // 停止所有动画
+ await stopAllAnimations();
- logMessage('info', '所有启用手部已重置完成');
+ // 发送重置姿态
+ await sendAllFingerPoses();
+ await sendAllPalmPoses();
+
+ logMessage("info", "所有启用手部已重置完成");
}
// 自动触发按钮序列(数字手势)
async function triggerButtonsSequentially(interval = 2000) {
- const enabledHands = getEnabledHands();
- if (enabledHands.length === 0) {
- logMessage('error', '没有启用的手部');
- return;
+ 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));
}
+ }
- 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'),
- ];
+ // 然后执行所有预设手势
+ 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));
+ }
- 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', '数字手势序列完成');
+ 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 = `
+ 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]);
- }
+ 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);
+ // 每 5 秒检查一次接口状态
+ setInterval(async () => {
+ await checkAllInterfaceStatus();
+ }, 5000);
- // 每 30 秒刷新一次接口列表
- setInterval(async () => {
- const oldInterfaces = [...availableInterfaces];
- await loadAvailableInterfaces();
-
- // 如果接口发生变化,重新生成配置
- if (JSON.stringify(oldInterfaces) !== JSON.stringify(availableInterfaces)) {
- generateHandConfigs();
- }
- }, 30000);
+ // 每 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}`);
+ 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} 不存在`);
}
-
- // 测试接口 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}`);
+ });
+
+ // 检查全局变量
+ 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/legacy/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/legacy/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 按钮使用
@@ -1283,357 +1427,381 @@ 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("error", (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);
+window.addEventListener("unhandledrejection", (event) => {
+ logMessage(
+ "error",
+ `未处理的 Promise 拒绝:${event.reason?.message || event.reason}`,
+ );
+ console.error("Unhandled Promise Rejection:", event.reason);
});
// 页面可见性变化时的处理
-document.addEventListener('visibilitychange', function() {
- if (!document.hidden) {
- // 页面变为可见时,刷新状态
- checkAllInterfaceStatus();
- }
+document.addEventListener("visibilitychange", () => {
+ if (!document.hidden) {
+ // 页面变为可见时,刷新状态
+ checkAllInterfaceStatus();
+ }
});
// 处理网络错误时的重连逻辑
-window.addEventListener('online', function() {
- logMessage('info', '网络连接已恢复,正在重新连接...');
- initializeSystem();
+window.addEventListener("online", () => {
+ logMessage("info", "网络连接已恢复,正在重新连接...");
+ initializeSystem();
});
-window.addEventListener('offline', function() {
- logMessage('error', '网络连接已断开');
- showConnectionWarning();
+window.addEventListener("offline", () => {
+ 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();
- }
+document.addEventListener("keydown", (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;
+ 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);
- });
+ 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 ? '启用' : '禁用'}所有手部`);
+ 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 动作序列'
- };
+ 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;
- }
- });
+ Object.entries(tooltips).forEach(([id, text]) => {
+ const element = document.getElementById(id);
+ if (element) {
+ element.title = text;
+ }
+ });
}
// 页面加载完成后添加工具提示
-document.addEventListener('DOMContentLoaded', function() {
- addTooltips();
+document.addEventListener("DOMContentLoaded", () => {
+ 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]
- }
+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 ? Number.parseInt(match[1]) : 0;
};
-
- 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);
+ 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, neutralFingerPose);
+ await sendFingerPoseToHand(hand, currentFingerPose);
}, 50);
- await new Promise(resolve => setTimeout(resolve, 100));
+
+ logMessage(
+ "info",
+ `${hand.interface}(${handName}) 执行姿势 ${poseIndex + 1}/${
+ fingerPoses.length
+ }`,
+ );
+
+ // 等待间隔时间再激活下一只手
+ await new Promise((resolve) => setTimeout(resolve, interval));
+ }
}
-
- logMessage('success', `六手依次动画 "${preset.name}" 完成!`);
+
+ // 循环间隔(如果有多轮)
+ 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 参数同时控制多只手
- // 实现类似的动画逻辑,但支持更多自定义选项
+ 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 ? Number.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);
+ await startSequentialHandAnimation("wave", 300, 2);
}
async function startThumbsUpRelay() {
- await startSequentialHandAnimation('thumbsUp', 400, 3);
+ await startSequentialHandAnimation("thumbsUp", 400, 3);
}
async function startPointingRelay() {
- await startSequentialHandAnimation('point', 350, 2);
+ await startSequentialHandAnimation("point", 350, 2);
}
async function startNumberCountdown() {
- await startSequentialHandAnimation('numbers', 800, 1);
+ await startSequentialHandAnimation("numbers", 800, 1);
}
async function startMexicanWave() {
- await startSequentialHandAnimation('mexican', 200, 3);
+ await startSequentialHandAnimation("mexican", 200, 3);
}
async function startFistOpenWave() {
- await startSequentialHandAnimation('fistOpen', 400, 2);
+ 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', '双向波浪动画完成!');
+ logMessage("info", "开始双向波浪动画...");
+
+ // 正向波浪
+ await startSequentialHandAnimation("wave", 300, 1);
+ await new Promise((resolve) => setTimeout(resolve, 500));
+
+ // 反向波浪(通过反转手部顺序实现)
+ const originalGetEnabledHands = window.getEnabledHands;
+ window.getEnabledHands = () => originalGetEnabledHands().reverse();
+
+ await startSequentialHandAnimation("wave", 300, 1);
+
+ // 恢复原始函数
+ window.getEnabledHands = originalGetEnabledHands;
+
+ logMessage("success", "双向波浪动画完成!");
}
// 导出函数到全局作用域
@@ -1646,4 +1814,3 @@ window.startNumberCountdown = startNumberCountdown;
window.startMexicanWave = startMexicanWave;
window.startFistOpenWave = startFistOpenWave;
window.startBidirectionalWave = startBidirectionalWave;
-