From 671af383711205472d305e1c9c84faaadd2c7eda Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Mon, 2 Jun 2025 21:48:22 +0800 Subject: [PATCH 01/24] build: bump docker base image to alpine:3.22 --- Dockerfile | 2 +- Dockerfile.goreleaser | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 From e9a878895a7c42daa9a69b7146d9215653d41a7b Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:07:20 +0800 Subject: [PATCH 02/24] chore: add back legacy api --- legacy-api/handler.go | 468 ++++++++++++++++++++++++++++++++++++++++++ legacy-api/models.go | 29 +++ legacy-api/router.go | 50 +++++ 3 files changed, 547 insertions(+) create mode 100644 legacy-api/handler.go create mode 100644 legacy-api/models.go create mode 100644 legacy-api/router.go diff --git a/legacy-api/handler.go b/legacy-api/handler.go new file mode 100644 index 0000000..b3a98d1 --- /dev/null +++ b/legacy-api/handler.go @@ -0,0 +1,468 @@ +package api + +import ( + "fmt" + "hands/config" + "hands/define" + "hands/hands" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// 手型设置处理函数 +func 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 !config.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) + } + + // 设置手型配置 + hands.SetHandConfig(req.Interface, req.HandType, req.HandId) + + 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, + }, + }) +} + +// 手指姿态处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(req.Interface) + + if err := hands.SendFingerPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, + }) +} + +// 掌部姿态处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(req.Interface) + + if err := hands.SendPalmPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, + }) +} + +// 预设姿势处理函数 +func HandlePreset(c *gin.Context) { + pose := c.Param("pose") + + // 从查询参数获取接口名称和手型 + ifName := c.Query("interface") + handType := c.Query("handType") + + if ifName == "" { + ifName = config.Config.DefaultInterface + } + + // 验证接口 + if !config.IsValidInterface(ifName) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(ifName) + + var fingerPose []byte + var message string + + switch pose { + case "fist": + fingerPose = []byte{64, 64, 64, 64, 64, 64} + message = "已设置握拳姿势" + case "open": + fingerPose = []byte{192, 192, 192, 192, 192, 192} + message = "已设置完全张开姿势" + case "pinch": + fingerPose = []byte{120, 120, 64, 64, 64, 64} + message = "已设置捏取姿势" + case "thumbsup": + fingerPose = []byte{64, 192, 192, 192, 192, 64} + message = "已设置竖起大拇指姿势" + case "point": + fingerPose = []byte{192, 64, 192, 192, 192, 64} + message = "已设置食指指点姿势" + // 数字手势 + case "1": + fingerPose = []byte{192, 64, 192, 192, 192, 64} + message = "已设置数字 1 手势" + case "2": + fingerPose = []byte{192, 64, 64, 192, 192, 64} + message = "已设置数字 2 手势" + case "3": + fingerPose = []byte{192, 64, 64, 64, 192, 64} + message = "已设置数字 3 手势" + case "4": + fingerPose = []byte{192, 64, 64, 64, 64, 64} + message = "已设置数字 4 手势" + case "5": + fingerPose = []byte{192, 192, 192, 192, 192, 192} + message = "已设置数字 5 手势" + case "6": + fingerPose = []byte{64, 192, 192, 192, 192, 64} + message = "已设置数字 6 手势" + case "7": + fingerPose = []byte{64, 64, 192, 192, 192, 64} + message = "已设置数字 7 手势" + case "8": + fingerPose = []byte{64, 64, 64, 192, 192, 64} + message = "已设置数字 8 手势" + case "9": + fingerPose = []byte{64, 64, 64, 64, 192, 64} + message = "已设置数字 9 手势" + default: + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: "无效的预设姿势", + }) + return + } + + // 解析手型 ID(从查询参数或使用接口配置) + handId := uint32(0) + if handType != "" { + handId = hands.ParseHandType(handType, 0, ifName) + } + + if err := hands.SendFingerPose(ifName, fingerPose, handType, handId); err != nil { + c.JSON(http.StatusInternalServerError, define.ApiResponse{ + Status: "error", + Error: "设置预设姿势失败:" + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Message: message, + Data: map[string]any{"interface": ifName, "pose": fingerPose}, + }) +} + +// 动画控制处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + // 停止当前动画 + hands.StopAllAnimations(req.Interface) + + // 如果是停止命令,直接返回 + 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": + hands.StartWaveAnimation(req.Interface, req.Speed, req.HandType, req.HandId) + 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": + hands.StartSwayAnimation(req.Interface, req.Speed, req.HandType, req.HandId) + 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: "无效的动画类型", + }) + } +} + +// 获取传感器数据处理函数 +func HandleSensors(c *gin.Context) { + // 从查询参数获取接口名称 + ifName := c.Query("interface") + + hands.SensorMutex.RLock() + defer hands.SensorMutex.RUnlock() + + if ifName != "" { + // 验证接口 + if !config.IsValidInterface(ifName) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), + }) + return + } + + // 请求特定接口的数据 + if sensorData, ok := hands.SensorDataMap[ifName]; ok { + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: sensorData, + }) + } else { + c.JSON(http.StatusInternalServerError, define.ApiResponse{ + Status: "error", + Error: "传感器数据不存在", + }) + } + } else { + // 返回所有接口的数据 + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: hands.SensorDataMap, + }) + } +} + +// 系统状态处理函数 +func HandleStatus(c *gin.Context) { + hands.AnimationMutex.Lock() + animationStatus := make(map[string]bool) + for _, ifName := range config.Config.AvailableInterfaces { + animationStatus[ifName] = hands.AnimationActive[ifName] + } + hands.AnimationMutex.Unlock() + + // 检查 CAN 服务状态 + canStatus := hands.CheckCanServiceStatus() + + // 获取手型配置 + hands.HandConfigMutex.RLock() + handConfigsData := make(map[string]any) + for ifName, handConfig := range hands.HandConfigs { + handConfigsData[ifName] = map[string]any{ + "handType": handConfig.HandType, + "handId": handConfig.HandId, + } + } + hands.HandConfigMutex.RUnlock() + + interfaceStatuses := make(map[string]any) + for _, ifName := range config.Config.AvailableInterfaces { + interfaceStatuses[ifName] = map[string]any{ + "active": canStatus[ifName], + "animationActive": animationStatus[ifName], + "handConfig": handConfigsData[ifName], + } + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: map[string]any{ + "interfaces": interfaceStatuses, + "uptime": time.Since(ServerStartTime).String(), + "canServiceURL": config.Config.CanServiceURL, + "defaultInterface": config.Config.DefaultInterface, + "availableInterfaces": config.Config.AvailableInterfaces, + "activeInterfaces": len(canStatus), + "handConfigs": handConfigsData, + }, + }) +} + +// 获取可用接口列表处理函数 +func 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, + }) +} + +// 获取手型配置处理函数 +func HandleHandConfigs(c *gin.Context) { + hands.HandConfigMutex.RLock() + defer hands.HandConfigMutex.RUnlock() + + result := make(map[string]any) + for _, ifName := range config.Config.AvailableInterfaces { + if handConfig, exists := hands.HandConfigs[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, + }) +} + +// 健康检查处理函数 +func 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", + }, + }) +} diff --git a/legacy-api/models.go b/legacy-api/models.go new file mode 100644 index 0000000..9f09550 --- /dev/null +++ b/legacy-api/models.go @@ -0,0 +1,29 @@ +package api + +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 +} + +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 +} + +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 +} + +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/legacy-api/router.go b/legacy-api/router.go new file mode 100644 index 0000000..b7987c2 --- /dev/null +++ b/legacy-api/router.go @@ -0,0 +1,50 @@ +package api + +import ( + "time" + + "github.com/gin-gonic/gin" +) + +// 全局变量 +var ( + ServerStartTime time.Time +) + +func SetupRoutes(r *gin.Engine) { + r.StaticFile("/", "./static/index.html") + r.Static("/static", "./static") + + api := r.Group("/api") + { + // 手型设置 API + api.POST("/hand-type", HandleHandType) + + // 手指姿态 API + api.POST("/fingers", HandleFingers) + + // 掌部姿态 API + api.POST("/palm", HandlePalm) + + // 预设姿势 API + api.POST("/preset/:pose", HandlePreset) + + // 动画控制 API + api.POST("/animation", HandleAnimation) + + // 获取传感器数据 API + api.GET("/sensors", HandleSensors) + + // 系统状态 API + api.GET("/status", HandleStatus) + + // 获取可用接口列表 API + api.GET("/interfaces", HandleInterfaces) + + // 获取手型配置 API + api.GET("/hand-configs", HandleHandConfigs) + + // 健康检查端点 + api.GET("/health", HandleHealth) + } +} From 7cc69bc9777b465268f71a1f68120fb292f734b0 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:20:00 +0800 Subject: [PATCH 03/24] feat: add device interface mapper --- api/legacy/compatibility.go | 207 ++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 api/legacy/compatibility.go 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) +} From e04e311ff469ce0c40db3b17d6413aee60abde5c Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:26:49 +0800 Subject: [PATCH 04/24] feat: add legacy router and models --- api/legacy/models.go | 33 +++++++++++++++++++++++ api/legacy/router.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 api/legacy/models.go create mode 100644 api/legacy/router.go 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) + } +} From cc1de7c77693b5c4238f80d3b8513442f8f2ec9f Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:31:31 +0800 Subject: [PATCH 05/24] feat(legacy): implement health check function --- api/legacy/handlers.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 api/legacy/handlers.go diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go new file mode 100644 index 0000000..bbde2af --- /dev/null +++ b/api/legacy/handlers.go @@ -0,0 +1,25 @@ +package legacy + +import ( + "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", + }, + }) +} From 43070cf76bac9a80380ebca14243d023f9d25090 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:36:26 +0800 Subject: [PATCH 06/24] feat(legacy): implement interfaces handler --- api/legacy/handlers.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index bbde2af..51da5cd 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -23,3 +23,16 @@ func (s *LegacyServer) handleHealth(c *gin.Context) { }, }) } + +// 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, + }) +} From 185646dbd2af6335fc8602aacd648f4ea6c445f3 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:40:58 +0800 Subject: [PATCH 07/24] feat(legacy): implement hand configs api --- api/legacy/handlers.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 51da5cd..eab0614 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -36,3 +36,29 @@ func (s *LegacyServer) handleInterfaces(c *gin.Context) { 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, + }) +} From a55d15b30d0e2a1bf40612b1b243d39f7d121ad9 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:43:01 +0800 Subject: [PATCH 08/24] feat(legacy): implement set hand type api --- api/legacy/handlers.go | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index eab0614..c68d9e0 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -1,6 +1,7 @@ package legacy import ( + "fmt" "net/http" "time" @@ -62,3 +63,55 @@ func (s *LegacyServer) handleHandConfigs(c *gin.Context) { 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, + }, + }) +} From 1bbc5819127d09d33339e706ecab90f57b0a0307 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:45:37 +0800 Subject: [PATCH 09/24] feat(legacy): implement finger handler --- api/legacy/handlers.go | 71 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index c68d9e0..b23cae5 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -115,3 +115,74 @@ func (s *LegacyServer) handleHandType(c *gin.Context) { }, }) } + +// 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}, + }) +} From ab2138be1ee29311ecca3ebad933cb4f3855d4c3 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:48:05 +0800 Subject: [PATCH 10/24] feat(legacy): implement palm interfaces --- api/legacy/handlers.go | 71 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index b23cae5..5075e6f 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -186,3 +186,74 @@ func (s *LegacyServer) handleFingers(c *gin.Context) { 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}, + }) +} From 98fa37952d810d071dd2763d7d2e95b62d6a9535 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 09:58:13 +0800 Subject: [PATCH 11/24] feat(legacy): implement preset handler --- api/legacy/handlers.go | 73 ++++++++++++++++++++++++++++++++++++++++++ device/device.go | 7 ++-- device/models/l10.go | 5 +++ docs/contribute_CN.md | 1 + 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 5075e6f..269d383 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -257,3 +257,76 @@ func (s *LegacyServer) handlePalm(c *gin.Context) { 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}, + }) +} diff --git a/device/device.go b/device/device.go index 2f9d54f..a8e2f05 100644 --- a/device/device.go +++ b/device/device.go @@ -23,9 +23,10 @@ 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 代表一个发送给设备的指令 diff --git a/device/models/l10.go b/device/models/l10.go index 221853e..558b23f 100644 --- a/device/models/l10.go +++ b/device/models/l10.go @@ -378,3 +378,8 @@ 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) +} 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) } ``` From 9df0f24fb872ec4d1c894398afd2d9f20e09c23e Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:06:22 +0800 Subject: [PATCH 12/24] feat(legacy): implement anim handler --- api/legacy/handlers.go | 97 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 269d383..ed98cfd 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -330,3 +330,100 @@ func (s *LegacyServer) handlePreset(c *gin.Context) { 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: "无效的动画类型", + }) + } +} From 4ce098c5141b1cebeabfdcbf0df16100c97983fc Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:14:54 +0800 Subject: [PATCH 13/24] feat(legacy): implement sensor handler --- api/legacy/handlers.go | 100 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index ed98cfd..6a7e0a7 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -7,6 +7,7 @@ import ( "hands/config" "hands/define" + "hands/device" "github.com/gin-gonic/gin" ) @@ -427,3 +428,102 @@ func (s *LegacyServer) handleAnimation(c *gin.Context) { }) } } + +// handleSensors 获取传感器数据处理函数 +// TODO: 现在的传感器数据都是模拟的,先不进行严格定义,等到有文档了之后修改设备层和这里 +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 + } + + // 获取设备的传感器组件 + sensorComponents := dev.GetComponents(device.SensorComponent) + + // 构建传感器数据响应 + sensorData := make(map[string]any) + for _, component := range sensorComponents { + sensorId := component.GetID() + data, err := dev.ReadSensorData(sensorId) + if err != nil { + // 如果读取失败,记录错误状态 + sensorData[sensorId] = map[string]any{ + "error": err.Error(), + "timestamp": time.Now(), + } + continue + } + + sensorData[sensorId] = map[string]any{ + "values": data.Values(), + "timestamp": data.Timestamp(), + } + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: sensorData, + }) + } 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 + } + + // 获取设备的传感器组件 + sensorComponents := dev.GetComponents(device.SensorComponent) + + // 构建传感器数据响应 + sensorData := make(map[string]any) + for _, component := range sensorComponents { + sensorId := component.GetID() + data, err := dev.ReadSensorData(sensorId) + if err != nil { + sensorData[sensorId] = map[string]any{ + "error": err.Error(), + "timestamp": time.Now(), + } + continue + } + + sensorData[sensorId] = map[string]any{ + "values": data.Values(), + "timestamp": data.Timestamp(), + } + } + + allSensorData[ifName] = sensorData + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: allSensorData, + }) + } +} From eb4f060cad28e05e6b532845240918f16a059b1a Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:18:00 +0800 Subject: [PATCH 14/24] feat(legacy): implement status handler --- api/legacy/handlers.go | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 6a7e0a7..6fd1993 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -527,3 +527,94 @@ func (s *LegacyServer) handleSensors(c *gin.Context) { }) } } + +// 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 := 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 服务状态 + status, err := dev.GetStatus() + if err != nil { + canStatus[ifName] = false + } else { + canStatus[ifName] = status.IsConnected && status.IsActive + } + + // 获取手型配置 + if handConfig, exists := allHandConfigs[ifName]; exists { + handConfigsData[ifName] = map[string]any{ + "handType": handConfig.HandType, + "handId": handConfig.HandId, + } + } else { + // 从设备获取当前手型 + handType := dev.GetHandType() + handTypeStr := "right" + handId := uint32(define.HAND_TYPE_RIGHT) + if handType == define.HAND_TYPE_LEFT { + handTypeStr = "left" + handId = uint32(define.HAND_TYPE_LEFT) + } + handConfigsData[ifName] = map[string]any{ + "handType": handTypeStr, + "handId": handId, + } + } + } + + // 构建接口状态 + 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, + }, + }) +} From 5e23162fe20ec8d519644ceb0eee7a4422055e88 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:19:06 +0800 Subject: [PATCH 15/24] feat(legacy): setup routers in main.go --- main.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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) From a12261204b001e2704897fa10b7facc8be7042f1 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:22:21 +0800 Subject: [PATCH 16/24] style: format script.js with biome --- static/script.js | 2759 ++++++++++++++++++++++++---------------------- 1 file changed, 1463 insertions(+), 1296 deletions(-) diff --git a/static/script.js b/static/script.js index c7d152a..f7ed740 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/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/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}
- + ${availableInterfaces + .map( + (iface) => + ``, + ) + .join("")}
- - + +
- + 检查中...
`; - // 使用 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/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/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/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/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/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/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/health"); + if (response.ok) { + const data = await response.json(); + logMessage("success", "✅ 健康检查通过"); + console.log("Health Check Data:", data); + } else { + logMessage("error", `❌ 健康检查失败:HTTP ${response.status}`); } + } catch (error) { + logMessage("error", `❌ 健康检查异常:${error.message}`); + } + + // 测试接口 API + try { + logMessage("info", "测试 /api/interfaces 连接..."); + const response = await fetch("/api/interfaces"); + if (response.ok) { + const data = await response.json(); + logMessage("success", "✅ 接口 API 通过"); + console.log("Interfaces API Data:", data); + } else { + logMessage("error", `❌ 接口 API 失败:HTTP ${response.status}`); + } + } catch (error) { + logMessage("error", `❌ 接口 API 异常:${error.message}`); + } } // 导出全局函数供 HTML 按钮使用 @@ -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; - From b3eafd798c61a915a6496c77451a55fadf59c29e Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:23:43 +0800 Subject: [PATCH 17/24] feat(legacy): use legacy api in static files --- static/script.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/static/script.js b/static/script.js index f7ed740..2b15a95 100644 --- a/static/script.js +++ b/static/script.js @@ -233,7 +233,7 @@ const LinkerHandController = { // 获取传感器数据 fetchSensorData: function () { - fetch("/api/sensors") + fetch("/api/legacy/sensors") .then((response) => response.json()) .then((data) => { if (data.status === "success") { @@ -335,7 +335,7 @@ async function initializeSystem() { async function loadAvailableInterfaces() { try { logMessage("info", "正在获取可用 CAN 接口..."); - const response = await fetch("/api/interfaces"); + const response = await fetch("/api/legacy/interfaces"); if (!response.ok) { throw new Error(`HTTP ${response.status}`); @@ -697,7 +697,7 @@ function updateEnabledHandsStatus() { // 检查所有接口状态 - 修复错误处理 async function checkAllInterfaceStatus() { try { - const response = await fetch("/api/status"); + const response = await fetch("/api/legacy/status"); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); @@ -1055,7 +1055,7 @@ async function sendAllPalmPoses() { // 发送手指姿态到指定手部 async function sendFingerPoseToHand(config, pose) { try { - const response = await fetch("/api/fingers", { + const response = await fetch("/api/legacy/fingers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1086,7 +1086,7 @@ async function sendFingerPoseToHand(config, pose) { // 发送掌部姿态到指定手部 async function sendPalmPoseToHand(config, pose) { try { - const response = await fetch("/api/palm", { + const response = await fetch("/api/legacy/palm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1136,7 +1136,7 @@ async function setPresetPoseForAll(preset) { async function setPresetPoseToHand(config, preset) { try { const response = await fetch( - `/api/preset/${preset}?interface=${config.interface}&handType=${config.handType}`, + `/api/legacy/preset/${preset}?interface=${config.interface}&handType=${config.handType}`, { method: "POST", }, @@ -1181,7 +1181,7 @@ async function startAnimationForAll(type) { // 为指定手部启动动画 async function startAnimationForHand(config, type, speed) { try { - const response = await fetch("/api/animation", { + const response = await fetch("/api/legacy/animation", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1226,7 +1226,7 @@ async function stopAllAnimations() { // 停止指定手部的动画 async function stopAnimationForHand(config) { try { - const response = await fetch("/api/animation", { + const response = await fetch("/api/legacy/animation", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1394,7 +1394,7 @@ async function debugSystemStatus() { // 测试 API 连通性 try { logMessage("info", "测试 /api/health 连接..."); - const response = await fetch("/api/health"); + const response = await fetch("/api/legacy/health"); if (response.ok) { const data = await response.json(); logMessage("success", "✅ 健康检查通过"); @@ -1409,7 +1409,7 @@ async function debugSystemStatus() { // 测试接口 API try { logMessage("info", "测试 /api/interfaces 连接..."); - const response = await fetch("/api/interfaces"); + const response = await fetch("/api/legacy/interfaces"); if (response.ok) { const data = await response.json(); logMessage("success", "✅ 接口 API 通过"); From 4dc43e208d1d754b02bba03c166a15d346fd3d4c Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 10:26:52 +0800 Subject: [PATCH 18/24] chore: remove old api --- legacy-api/handler.go | 468 ------------------------------------------ legacy-api/models.go | 29 --- legacy-api/router.go | 50 ----- 3 files changed, 547 deletions(-) delete mode 100644 legacy-api/handler.go delete mode 100644 legacy-api/models.go delete mode 100644 legacy-api/router.go diff --git a/legacy-api/handler.go b/legacy-api/handler.go deleted file mode 100644 index b3a98d1..0000000 --- a/legacy-api/handler.go +++ /dev/null @@ -1,468 +0,0 @@ -package api - -import ( - "fmt" - "hands/config" - "hands/define" - "hands/hands" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// 手型设置处理函数 -func 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 !config.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) - } - - // 设置手型配置 - hands.SetHandConfig(req.Interface, req.HandType, req.HandId) - - 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, - }, - }) -} - -// 手指姿态处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(req.Interface) - - if err := hands.SendFingerPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, - }) -} - -// 掌部姿态处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(req.Interface) - - if err := hands.SendPalmPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, - }) -} - -// 预设姿势处理函数 -func HandlePreset(c *gin.Context) { - pose := c.Param("pose") - - // 从查询参数获取接口名称和手型 - ifName := c.Query("interface") - handType := c.Query("handType") - - if ifName == "" { - ifName = config.Config.DefaultInterface - } - - // 验证接口 - if !config.IsValidInterface(ifName) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(ifName) - - var fingerPose []byte - var message string - - switch pose { - case "fist": - fingerPose = []byte{64, 64, 64, 64, 64, 64} - message = "已设置握拳姿势" - case "open": - fingerPose = []byte{192, 192, 192, 192, 192, 192} - message = "已设置完全张开姿势" - case "pinch": - fingerPose = []byte{120, 120, 64, 64, 64, 64} - message = "已设置捏取姿势" - case "thumbsup": - fingerPose = []byte{64, 192, 192, 192, 192, 64} - message = "已设置竖起大拇指姿势" - case "point": - fingerPose = []byte{192, 64, 192, 192, 192, 64} - message = "已设置食指指点姿势" - // 数字手势 - case "1": - fingerPose = []byte{192, 64, 192, 192, 192, 64} - message = "已设置数字 1 手势" - case "2": - fingerPose = []byte{192, 64, 64, 192, 192, 64} - message = "已设置数字 2 手势" - case "3": - fingerPose = []byte{192, 64, 64, 64, 192, 64} - message = "已设置数字 3 手势" - case "4": - fingerPose = []byte{192, 64, 64, 64, 64, 64} - message = "已设置数字 4 手势" - case "5": - fingerPose = []byte{192, 192, 192, 192, 192, 192} - message = "已设置数字 5 手势" - case "6": - fingerPose = []byte{64, 192, 192, 192, 192, 64} - message = "已设置数字 6 手势" - case "7": - fingerPose = []byte{64, 64, 192, 192, 192, 64} - message = "已设置数字 7 手势" - case "8": - fingerPose = []byte{64, 64, 64, 192, 192, 64} - message = "已设置数字 8 手势" - case "9": - fingerPose = []byte{64, 64, 64, 64, 192, 64} - message = "已设置数字 9 手势" - default: - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: "无效的预设姿势", - }) - return - } - - // 解析手型 ID(从查询参数或使用接口配置) - handId := uint32(0) - if handType != "" { - handId = hands.ParseHandType(handType, 0, ifName) - } - - if err := hands.SendFingerPose(ifName, fingerPose, handType, handId); err != nil { - c.JSON(http.StatusInternalServerError, define.ApiResponse{ - Status: "error", - Error: "设置预设姿势失败:" + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Message: message, - Data: map[string]any{"interface": ifName, "pose": fingerPose}, - }) -} - -// 动画控制处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - // 停止当前动画 - hands.StopAllAnimations(req.Interface) - - // 如果是停止命令,直接返回 - 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": - hands.StartWaveAnimation(req.Interface, req.Speed, req.HandType, req.HandId) - 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": - hands.StartSwayAnimation(req.Interface, req.Speed, req.HandType, req.HandId) - 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: "无效的动画类型", - }) - } -} - -// 获取传感器数据处理函数 -func HandleSensors(c *gin.Context) { - // 从查询参数获取接口名称 - ifName := c.Query("interface") - - hands.SensorMutex.RLock() - defer hands.SensorMutex.RUnlock() - - if ifName != "" { - // 验证接口 - if !config.IsValidInterface(ifName) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), - }) - return - } - - // 请求特定接口的数据 - if sensorData, ok := hands.SensorDataMap[ifName]; ok { - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: sensorData, - }) - } else { - c.JSON(http.StatusInternalServerError, define.ApiResponse{ - Status: "error", - Error: "传感器数据不存在", - }) - } - } else { - // 返回所有接口的数据 - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: hands.SensorDataMap, - }) - } -} - -// 系统状态处理函数 -func HandleStatus(c *gin.Context) { - hands.AnimationMutex.Lock() - animationStatus := make(map[string]bool) - for _, ifName := range config.Config.AvailableInterfaces { - animationStatus[ifName] = hands.AnimationActive[ifName] - } - hands.AnimationMutex.Unlock() - - // 检查 CAN 服务状态 - canStatus := hands.CheckCanServiceStatus() - - // 获取手型配置 - hands.HandConfigMutex.RLock() - handConfigsData := make(map[string]any) - for ifName, handConfig := range hands.HandConfigs { - handConfigsData[ifName] = map[string]any{ - "handType": handConfig.HandType, - "handId": handConfig.HandId, - } - } - hands.HandConfigMutex.RUnlock() - - interfaceStatuses := make(map[string]any) - for _, ifName := range config.Config.AvailableInterfaces { - interfaceStatuses[ifName] = map[string]any{ - "active": canStatus[ifName], - "animationActive": animationStatus[ifName], - "handConfig": handConfigsData[ifName], - } - } - - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: map[string]any{ - "interfaces": interfaceStatuses, - "uptime": time.Since(ServerStartTime).String(), - "canServiceURL": config.Config.CanServiceURL, - "defaultInterface": config.Config.DefaultInterface, - "availableInterfaces": config.Config.AvailableInterfaces, - "activeInterfaces": len(canStatus), - "handConfigs": handConfigsData, - }, - }) -} - -// 获取可用接口列表处理函数 -func 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, - }) -} - -// 获取手型配置处理函数 -func HandleHandConfigs(c *gin.Context) { - hands.HandConfigMutex.RLock() - defer hands.HandConfigMutex.RUnlock() - - result := make(map[string]any) - for _, ifName := range config.Config.AvailableInterfaces { - if handConfig, exists := hands.HandConfigs[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, - }) -} - -// 健康检查处理函数 -func 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", - }, - }) -} diff --git a/legacy-api/models.go b/legacy-api/models.go deleted file mode 100644 index 9f09550..0000000 --- a/legacy-api/models.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -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 -} - -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 -} - -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 -} - -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/legacy-api/router.go b/legacy-api/router.go deleted file mode 100644 index b7987c2..0000000 --- a/legacy-api/router.go +++ /dev/null @@ -1,50 +0,0 @@ -package api - -import ( - "time" - - "github.com/gin-gonic/gin" -) - -// 全局变量 -var ( - ServerStartTime time.Time -) - -func SetupRoutes(r *gin.Engine) { - r.StaticFile("/", "./static/index.html") - r.Static("/static", "./static") - - api := r.Group("/api") - { - // 手型设置 API - api.POST("/hand-type", HandleHandType) - - // 手指姿态 API - api.POST("/fingers", HandleFingers) - - // 掌部姿态 API - api.POST("/palm", HandlePalm) - - // 预设姿势 API - api.POST("/preset/:pose", HandlePreset) - - // 动画控制 API - api.POST("/animation", HandleAnimation) - - // 获取传感器数据 API - api.GET("/sensors", HandleSensors) - - // 系统状态 API - api.GET("/status", HandleStatus) - - // 获取可用接口列表 API - api.GET("/interfaces", HandleInterfaces) - - // 获取手型配置 API - api.GET("/hand-configs", HandleHandConfigs) - - // 健康检查端点 - api.GET("/health", HandleHealth) - } -} From ca2b1e8fd4250fbdb27027ce6be007325e2dabe1 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 11:13:59 +0800 Subject: [PATCH 19/24] Revert "chore: remove old api" This reverts commit 4dc43e208d1d754b02bba03c166a15d346fd3d4c. --- legacy-api/handler.go | 468 ++++++++++++++++++++++++++++++++++++++++++ legacy-api/models.go | 29 +++ legacy-api/router.go | 50 +++++ 3 files changed, 547 insertions(+) create mode 100644 legacy-api/handler.go create mode 100644 legacy-api/models.go create mode 100644 legacy-api/router.go diff --git a/legacy-api/handler.go b/legacy-api/handler.go new file mode 100644 index 0000000..b3a98d1 --- /dev/null +++ b/legacy-api/handler.go @@ -0,0 +1,468 @@ +package api + +import ( + "fmt" + "hands/config" + "hands/define" + "hands/hands" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// 手型设置处理函数 +func 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 !config.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) + } + + // 设置手型配置 + hands.SetHandConfig(req.Interface, req.HandType, req.HandId) + + 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, + }, + }) +} + +// 手指姿态处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(req.Interface) + + if err := hands.SendFingerPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, + }) +} + +// 掌部姿态处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(req.Interface) + + if err := hands.SendPalmPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, + }) +} + +// 预设姿势处理函数 +func HandlePreset(c *gin.Context) { + pose := c.Param("pose") + + // 从查询参数获取接口名称和手型 + ifName := c.Query("interface") + handType := c.Query("handType") + + if ifName == "" { + ifName = config.Config.DefaultInterface + } + + // 验证接口 + if !config.IsValidInterface(ifName) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), + }) + return + } + + hands.StopAllAnimations(ifName) + + var fingerPose []byte + var message string + + switch pose { + case "fist": + fingerPose = []byte{64, 64, 64, 64, 64, 64} + message = "已设置握拳姿势" + case "open": + fingerPose = []byte{192, 192, 192, 192, 192, 192} + message = "已设置完全张开姿势" + case "pinch": + fingerPose = []byte{120, 120, 64, 64, 64, 64} + message = "已设置捏取姿势" + case "thumbsup": + fingerPose = []byte{64, 192, 192, 192, 192, 64} + message = "已设置竖起大拇指姿势" + case "point": + fingerPose = []byte{192, 64, 192, 192, 192, 64} + message = "已设置食指指点姿势" + // 数字手势 + case "1": + fingerPose = []byte{192, 64, 192, 192, 192, 64} + message = "已设置数字 1 手势" + case "2": + fingerPose = []byte{192, 64, 64, 192, 192, 64} + message = "已设置数字 2 手势" + case "3": + fingerPose = []byte{192, 64, 64, 64, 192, 64} + message = "已设置数字 3 手势" + case "4": + fingerPose = []byte{192, 64, 64, 64, 64, 64} + message = "已设置数字 4 手势" + case "5": + fingerPose = []byte{192, 192, 192, 192, 192, 192} + message = "已设置数字 5 手势" + case "6": + fingerPose = []byte{64, 192, 192, 192, 192, 64} + message = "已设置数字 6 手势" + case "7": + fingerPose = []byte{64, 64, 192, 192, 192, 64} + message = "已设置数字 7 手势" + case "8": + fingerPose = []byte{64, 64, 64, 192, 192, 64} + message = "已设置数字 8 手势" + case "9": + fingerPose = []byte{64, 64, 64, 64, 192, 64} + message = "已设置数字 9 手势" + default: + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: "无效的预设姿势", + }) + return + } + + // 解析手型 ID(从查询参数或使用接口配置) + handId := uint32(0) + if handType != "" { + handId = hands.ParseHandType(handType, 0, ifName) + } + + if err := hands.SendFingerPose(ifName, fingerPose, handType, handId); err != nil { + c.JSON(http.StatusInternalServerError, define.ApiResponse{ + Status: "error", + Error: "设置预设姿势失败:" + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Message: message, + Data: map[string]any{"interface": ifName, "pose": fingerPose}, + }) +} + +// 动画控制处理函数 +func 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 !config.IsValidInterface(req.Interface) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), + }) + return + } + + // 停止当前动画 + hands.StopAllAnimations(req.Interface) + + // 如果是停止命令,直接返回 + 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": + hands.StartWaveAnimation(req.Interface, req.Speed, req.HandType, req.HandId) + 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": + hands.StartSwayAnimation(req.Interface, req.Speed, req.HandType, req.HandId) + 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: "无效的动画类型", + }) + } +} + +// 获取传感器数据处理函数 +func HandleSensors(c *gin.Context) { + // 从查询参数获取接口名称 + ifName := c.Query("interface") + + hands.SensorMutex.RLock() + defer hands.SensorMutex.RUnlock() + + if ifName != "" { + // 验证接口 + if !config.IsValidInterface(ifName) { + c.JSON(http.StatusBadRequest, define.ApiResponse{ + Status: "error", + Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), + }) + return + } + + // 请求特定接口的数据 + if sensorData, ok := hands.SensorDataMap[ifName]; ok { + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: sensorData, + }) + } else { + c.JSON(http.StatusInternalServerError, define.ApiResponse{ + Status: "error", + Error: "传感器数据不存在", + }) + } + } else { + // 返回所有接口的数据 + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: hands.SensorDataMap, + }) + } +} + +// 系统状态处理函数 +func HandleStatus(c *gin.Context) { + hands.AnimationMutex.Lock() + animationStatus := make(map[string]bool) + for _, ifName := range config.Config.AvailableInterfaces { + animationStatus[ifName] = hands.AnimationActive[ifName] + } + hands.AnimationMutex.Unlock() + + // 检查 CAN 服务状态 + canStatus := hands.CheckCanServiceStatus() + + // 获取手型配置 + hands.HandConfigMutex.RLock() + handConfigsData := make(map[string]any) + for ifName, handConfig := range hands.HandConfigs { + handConfigsData[ifName] = map[string]any{ + "handType": handConfig.HandType, + "handId": handConfig.HandId, + } + } + hands.HandConfigMutex.RUnlock() + + interfaceStatuses := make(map[string]any) + for _, ifName := range config.Config.AvailableInterfaces { + interfaceStatuses[ifName] = map[string]any{ + "active": canStatus[ifName], + "animationActive": animationStatus[ifName], + "handConfig": handConfigsData[ifName], + } + } + + c.JSON(http.StatusOK, define.ApiResponse{ + Status: "success", + Data: map[string]any{ + "interfaces": interfaceStatuses, + "uptime": time.Since(ServerStartTime).String(), + "canServiceURL": config.Config.CanServiceURL, + "defaultInterface": config.Config.DefaultInterface, + "availableInterfaces": config.Config.AvailableInterfaces, + "activeInterfaces": len(canStatus), + "handConfigs": handConfigsData, + }, + }) +} + +// 获取可用接口列表处理函数 +func 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, + }) +} + +// 获取手型配置处理函数 +func HandleHandConfigs(c *gin.Context) { + hands.HandConfigMutex.RLock() + defer hands.HandConfigMutex.RUnlock() + + result := make(map[string]any) + for _, ifName := range config.Config.AvailableInterfaces { + if handConfig, exists := hands.HandConfigs[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, + }) +} + +// 健康检查处理函数 +func 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", + }, + }) +} diff --git a/legacy-api/models.go b/legacy-api/models.go new file mode 100644 index 0000000..9f09550 --- /dev/null +++ b/legacy-api/models.go @@ -0,0 +1,29 @@ +package api + +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 +} + +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 +} + +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 +} + +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/legacy-api/router.go b/legacy-api/router.go new file mode 100644 index 0000000..b7987c2 --- /dev/null +++ b/legacy-api/router.go @@ -0,0 +1,50 @@ +package api + +import ( + "time" + + "github.com/gin-gonic/gin" +) + +// 全局变量 +var ( + ServerStartTime time.Time +) + +func SetupRoutes(r *gin.Engine) { + r.StaticFile("/", "./static/index.html") + r.Static("/static", "./static") + + api := r.Group("/api") + { + // 手型设置 API + api.POST("/hand-type", HandleHandType) + + // 手指姿态 API + api.POST("/fingers", HandleFingers) + + // 掌部姿态 API + api.POST("/palm", HandlePalm) + + // 预设姿势 API + api.POST("/preset/:pose", HandlePreset) + + // 动画控制 API + api.POST("/animation", HandleAnimation) + + // 获取传感器数据 API + api.GET("/sensors", HandleSensors) + + // 系统状态 API + api.GET("/status", HandleStatus) + + // 获取可用接口列表 API + api.GET("/interfaces", HandleInterfaces) + + // 获取手型配置 API + api.GET("/hand-configs", HandleHandConfigs) + + // 健康检查端点 + api.GET("/health", HandleHealth) + } +} From b0bfd95ed97ae0d99fd3a403dd8872d53b73dd24 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 12:07:04 +0800 Subject: [PATCH 20/24] chore: remove target component method in Command interface --- device/commands.go | 68 ++++++++++------------------------------------ device/device.go | 5 ++-- 2 files changed, 16 insertions(+), 57 deletions(-) diff --git a/device/commands.go b/device/commands.go index fb14a71..bba515a 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(fingerID string, poseData []byte) *FingerPoseCommand { - return &FingerPoseCommand{ - fingerID: fingerID, - poseData: poseData, - targetComp: "finger_" + fingerID, - } + return &FingerPoseCommand{poseData: poseData} } -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 a8e2f05..64296d6 100644 --- a/device/device.go +++ b/device/device.go @@ -31,9 +31,8 @@ type Device interface { // Command 代表一个发送给设备的指令 type Command interface { - Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle" - Payload() []byte // 指令的实际数据 - TargetComponent() string // 目标组件 ID + Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle" + Payload() []byte // 指令的实际数据 } // SensorData 代表从传感器读取的数据 From e1f60366c8f75f4ac04a0357fb189b7e9688da6c Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Tue, 3 Jun 2025 13:05:42 +0800 Subject: [PATCH 21/24] fix: execute command dead lock --- device/commands.go | 2 +- device/models/l10.go | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/device/commands.go b/device/commands.go index bba515a..027e8d2 100644 --- a/device/commands.go +++ b/device/commands.go @@ -3,7 +3,7 @@ package device // FingerPoseCommand 手指姿态指令 type FingerPoseCommand struct{ poseData []byte } -func NewFingerPoseCommand(fingerID string, poseData []byte) *FingerPoseCommand { +func NewFingerPoseCommand(poseData []byte) *FingerPoseCommand { return &FingerPoseCommand{poseData: poseData} } diff --git a/device/models/l10.go b/device/models/l10.go index 558b23f..83d6628 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,13 +257,10 @@ 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{ From f8aa2138ab5f53fab70c7b811861eab61ee58f9f Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Wed, 4 Jun 2025 09:27:14 +0800 Subject: [PATCH 22/24] refactor: keep legacy sensor data --- api/legacy/handlers.go | 61 +++++------------------ api/router.go | 3 +- api/sensor_handlers.go | 93 ++---------------------------------- component/pressure_sensor.go | 78 ------------------------------ component/sensor.go | 62 +++++++++++++++++------- device/device.go | 14 +++--- device/models/l10.go | 20 +++----- 7 files changed, 76 insertions(+), 255 deletions(-) delete mode 100644 component/pressure_sensor.go diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 6fd1993..77556f7 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -7,7 +7,6 @@ import ( "hands/config" "hands/define" - "hands/device" "github.com/gin-gonic/gin" ) @@ -430,7 +429,6 @@ func (s *LegacyServer) handleAnimation(c *gin.Context) { } // handleSensors 获取传感器数据处理函数 -// TODO: 现在的传感器数据都是模拟的,先不进行严格定义,等到有文档了之后修改设备层和这里 func (s *LegacyServer) handleSensors(c *gin.Context) { // 从查询参数获取接口名称 ifName := c.Query("interface") @@ -455,32 +453,17 @@ func (s *LegacyServer) handleSensors(c *gin.Context) { return } - // 获取设备的传感器组件 - sensorComponents := dev.GetComponents(device.SensorComponent) - - // 构建传感器数据响应 - sensorData := make(map[string]any) - for _, component := range sensorComponents { - sensorId := component.GetID() - data, err := dev.ReadSensorData(sensorId) - if err != nil { - // 如果读取失败,记录错误状态 - sensorData[sensorId] = map[string]any{ - "error": err.Error(), - "timestamp": time.Now(), - } - continue - } - - sensorData[sensorId] = map[string]any{ - "values": data.Values(), - "timestamp": data.Timestamp(), - } + 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, + Data: sensorData.Values(), }) } else { // 返回所有接口的传感器数据 @@ -490,35 +473,17 @@ func (s *LegacyServer) handleSensors(c *gin.Context) { // 获取对应的设备 dev, err := s.mapper.GetDeviceForInterface(ifName) if err != nil { - allSensorData[ifName] = map[string]any{ - "error": "设备不可用:" + err.Error(), - } + allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()} continue } - // 获取设备的传感器组件 - sensorComponents := dev.GetComponents(device.SensorComponent) - - // 构建传感器数据响应 - sensorData := make(map[string]any) - for _, component := range sensorComponents { - sensorId := component.GetID() - data, err := dev.ReadSensorData(sensorId) - if err != nil { - sensorData[sensorId] = map[string]any{ - "error": err.Error(), - "timestamp": time.Now(), - } - continue - } - - sensorData[sensorId] = map[string]any{ - "values": data.Values(), - "timestamp": data.Timestamp(), - } + sensorData, err := dev.ReadSensorData() + if err != nil { + allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()} + continue } - allSensorData[ifName] = sensorData + allSensorData[ifName] = sensorData.Values() } c.JSON(http.StatusOK, define.ApiResponse{ 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/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/device.go b/device/device.go index 64296d6..6b69425 100644 --- a/device/device.go +++ b/device/device.go @@ -12,7 +12,7 @@ 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 // 获取指定类型的组件 GetStatus() (DeviceStatus, error) // 获取设备状态 Connect() error // 连接设备 @@ -37,9 +37,9 @@ type Command interface { // 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 +53,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 83d6628..280816d 100644 --- a/device/models/l10.go +++ b/device/models/l10.go @@ -263,13 +263,9 @@ func (h *L10Hand) ExecuteCommand(cmd device.Command) error { 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 } @@ -282,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 { From 200f2e17519d469d2530328d8ef5d4d4d50d72a1 Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Wed, 4 Jun 2025 09:51:00 +0800 Subject: [PATCH 23/24] feat: implement legacy status api --- api/legacy/handlers.go | 32 +++++------------ communication/communicator.go | 67 +++++++++++++++++------------------ device/device.go | 1 + device/models/l10.go | 4 +++ 4 files changed, 47 insertions(+), 57 deletions(-) diff --git a/api/legacy/handlers.go b/api/legacy/handlers.go index 77556f7..07ce395 100644 --- a/api/legacy/handlers.go +++ b/api/legacy/handlers.go @@ -508,6 +508,13 @@ func (s *LegacyServer) handleStatus(c *gin.Context) { // 检查 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) @@ -525,32 +532,11 @@ func (s *LegacyServer) handleStatus(c *gin.Context) { animationStatus[ifName] = animEngine.IsRunning() // 获取设备状态来判断 CAN 服务状态 - status, err := dev.GetStatus() + rawCanStatus, err := dev.GetCanStatus() if err != nil { canStatus[ifName] = false } else { - canStatus[ifName] = status.IsConnected && status.IsActive - } - - // 获取手型配置 - if handConfig, exists := allHandConfigs[ifName]; exists { - handConfigsData[ifName] = map[string]any{ - "handType": handConfig.HandType, - "handId": handConfig.HandId, - } - } else { - // 从设备获取当前手型 - handType := dev.GetHandType() - handTypeStr := "right" - handId := uint32(define.HAND_TYPE_RIGHT) - if handType == define.HAND_TYPE_LEFT { - handTypeStr = "left" - handId = uint32(define.HAND_TYPE_LEFT) - } - handConfigsData[ifName] = map[string]any{ - "handType": handTypeStr, - "handId": handId, - } + canStatus[ifName] = rawCanStatus[ifName] } } 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/device/device.go b/device/device.go index 6b69425..ebc7df6 100644 --- a/device/device.go +++ b/device/device.go @@ -14,6 +14,7 @@ type Device interface { ExecuteCommand(cmd Command) error // 执行一个通用指令 ReadSensorData() (SensorData, error) // 读取特定传感器数据 GetComponents(componentType ComponentType) []Component // 获取指定类型的组件 + GetCanStatus() (map[string]bool, error) GetStatus() (DeviceStatus, error) // 获取设备状态 Connect() error // 连接设备 Disconnect() error // 断开设备连接 diff --git a/device/models/l10.go b/device/models/l10.go index 280816d..de953db 100644 --- a/device/models/l10.go +++ b/device/models/l10.go @@ -372,3 +372,7 @@ func (h *L10Hand) GetPresetDescription(presetName string) string { 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() +} From c3093eb57b6bdf8fb5617d0da740f4a7ebb7dd2a Mon Sep 17 00:00:00 2001 From: Eli Yip Date: Wed, 4 Jun 2025 09:52:11 +0800 Subject: [PATCH 24/24] chore: remove legacy api backup --- legacy-api/handler.go | 468 ------------------------------------------ legacy-api/models.go | 29 --- legacy-api/router.go | 50 ----- 3 files changed, 547 deletions(-) delete mode 100644 legacy-api/handler.go delete mode 100644 legacy-api/models.go delete mode 100644 legacy-api/router.go diff --git a/legacy-api/handler.go b/legacy-api/handler.go deleted file mode 100644 index b3a98d1..0000000 --- a/legacy-api/handler.go +++ /dev/null @@ -1,468 +0,0 @@ -package api - -import ( - "fmt" - "hands/config" - "hands/define" - "hands/hands" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -// 手型设置处理函数 -func 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 !config.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) - } - - // 设置手型配置 - hands.SetHandConfig(req.Interface, req.HandType, req.HandId) - - 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, - }, - }) -} - -// 手指姿态处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(req.Interface) - - if err := hands.SendFingerPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, - }) -} - -// 掌部姿态处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(req.Interface) - - if err := hands.SendPalmPose(req.Interface, req.Pose, req.HandType, req.HandId); 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}, - }) -} - -// 预设姿势处理函数 -func HandlePreset(c *gin.Context) { - pose := c.Param("pose") - - // 从查询参数获取接口名称和手型 - ifName := c.Query("interface") - handType := c.Query("handType") - - if ifName == "" { - ifName = config.Config.DefaultInterface - } - - // 验证接口 - if !config.IsValidInterface(ifName) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), - }) - return - } - - hands.StopAllAnimations(ifName) - - var fingerPose []byte - var message string - - switch pose { - case "fist": - fingerPose = []byte{64, 64, 64, 64, 64, 64} - message = "已设置握拳姿势" - case "open": - fingerPose = []byte{192, 192, 192, 192, 192, 192} - message = "已设置完全张开姿势" - case "pinch": - fingerPose = []byte{120, 120, 64, 64, 64, 64} - message = "已设置捏取姿势" - case "thumbsup": - fingerPose = []byte{64, 192, 192, 192, 192, 64} - message = "已设置竖起大拇指姿势" - case "point": - fingerPose = []byte{192, 64, 192, 192, 192, 64} - message = "已设置食指指点姿势" - // 数字手势 - case "1": - fingerPose = []byte{192, 64, 192, 192, 192, 64} - message = "已设置数字 1 手势" - case "2": - fingerPose = []byte{192, 64, 64, 192, 192, 64} - message = "已设置数字 2 手势" - case "3": - fingerPose = []byte{192, 64, 64, 64, 192, 64} - message = "已设置数字 3 手势" - case "4": - fingerPose = []byte{192, 64, 64, 64, 64, 64} - message = "已设置数字 4 手势" - case "5": - fingerPose = []byte{192, 192, 192, 192, 192, 192} - message = "已设置数字 5 手势" - case "6": - fingerPose = []byte{64, 192, 192, 192, 192, 64} - message = "已设置数字 6 手势" - case "7": - fingerPose = []byte{64, 64, 192, 192, 192, 64} - message = "已设置数字 7 手势" - case "8": - fingerPose = []byte{64, 64, 64, 192, 192, 64} - message = "已设置数字 8 手势" - case "9": - fingerPose = []byte{64, 64, 64, 64, 192, 64} - message = "已设置数字 9 手势" - default: - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: "无效的预设姿势", - }) - return - } - - // 解析手型 ID(从查询参数或使用接口配置) - handId := uint32(0) - if handType != "" { - handId = hands.ParseHandType(handType, 0, ifName) - } - - if err := hands.SendFingerPose(ifName, fingerPose, handType, handId); err != nil { - c.JSON(http.StatusInternalServerError, define.ApiResponse{ - Status: "error", - Error: "设置预设姿势失败:" + err.Error(), - }) - return - } - - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Message: message, - Data: map[string]any{"interface": ifName, "pose": fingerPose}, - }) -} - -// 动画控制处理函数 -func 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 !config.IsValidInterface(req.Interface) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces), - }) - return - } - - // 停止当前动画 - hands.StopAllAnimations(req.Interface) - - // 如果是停止命令,直接返回 - 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": - hands.StartWaveAnimation(req.Interface, req.Speed, req.HandType, req.HandId) - 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": - hands.StartSwayAnimation(req.Interface, req.Speed, req.HandType, req.HandId) - 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: "无效的动画类型", - }) - } -} - -// 获取传感器数据处理函数 -func HandleSensors(c *gin.Context) { - // 从查询参数获取接口名称 - ifName := c.Query("interface") - - hands.SensorMutex.RLock() - defer hands.SensorMutex.RUnlock() - - if ifName != "" { - // 验证接口 - if !config.IsValidInterface(ifName) { - c.JSON(http.StatusBadRequest, define.ApiResponse{ - Status: "error", - Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces), - }) - return - } - - // 请求特定接口的数据 - if sensorData, ok := hands.SensorDataMap[ifName]; ok { - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: sensorData, - }) - } else { - c.JSON(http.StatusInternalServerError, define.ApiResponse{ - Status: "error", - Error: "传感器数据不存在", - }) - } - } else { - // 返回所有接口的数据 - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: hands.SensorDataMap, - }) - } -} - -// 系统状态处理函数 -func HandleStatus(c *gin.Context) { - hands.AnimationMutex.Lock() - animationStatus := make(map[string]bool) - for _, ifName := range config.Config.AvailableInterfaces { - animationStatus[ifName] = hands.AnimationActive[ifName] - } - hands.AnimationMutex.Unlock() - - // 检查 CAN 服务状态 - canStatus := hands.CheckCanServiceStatus() - - // 获取手型配置 - hands.HandConfigMutex.RLock() - handConfigsData := make(map[string]any) - for ifName, handConfig := range hands.HandConfigs { - handConfigsData[ifName] = map[string]any{ - "handType": handConfig.HandType, - "handId": handConfig.HandId, - } - } - hands.HandConfigMutex.RUnlock() - - interfaceStatuses := make(map[string]any) - for _, ifName := range config.Config.AvailableInterfaces { - interfaceStatuses[ifName] = map[string]any{ - "active": canStatus[ifName], - "animationActive": animationStatus[ifName], - "handConfig": handConfigsData[ifName], - } - } - - c.JSON(http.StatusOK, define.ApiResponse{ - Status: "success", - Data: map[string]any{ - "interfaces": interfaceStatuses, - "uptime": time.Since(ServerStartTime).String(), - "canServiceURL": config.Config.CanServiceURL, - "defaultInterface": config.Config.DefaultInterface, - "availableInterfaces": config.Config.AvailableInterfaces, - "activeInterfaces": len(canStatus), - "handConfigs": handConfigsData, - }, - }) -} - -// 获取可用接口列表处理函数 -func 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, - }) -} - -// 获取手型配置处理函数 -func HandleHandConfigs(c *gin.Context) { - hands.HandConfigMutex.RLock() - defer hands.HandConfigMutex.RUnlock() - - result := make(map[string]any) - for _, ifName := range config.Config.AvailableInterfaces { - if handConfig, exists := hands.HandConfigs[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, - }) -} - -// 健康检查处理函数 -func 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", - }, - }) -} diff --git a/legacy-api/models.go b/legacy-api/models.go deleted file mode 100644 index 9f09550..0000000 --- a/legacy-api/models.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -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 -} - -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 -} - -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 -} - -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/legacy-api/router.go b/legacy-api/router.go deleted file mode 100644 index b7987c2..0000000 --- a/legacy-api/router.go +++ /dev/null @@ -1,50 +0,0 @@ -package api - -import ( - "time" - - "github.com/gin-gonic/gin" -) - -// 全局变量 -var ( - ServerStartTime time.Time -) - -func SetupRoutes(r *gin.Engine) { - r.StaticFile("/", "./static/index.html") - r.Static("/static", "./static") - - api := r.Group("/api") - { - // 手型设置 API - api.POST("/hand-type", HandleHandType) - - // 手指姿态 API - api.POST("/fingers", HandleFingers) - - // 掌部姿态 API - api.POST("/palm", HandlePalm) - - // 预设姿势 API - api.POST("/preset/:pose", HandlePreset) - - // 动画控制 API - api.POST("/animation", HandleAnimation) - - // 获取传感器数据 API - api.GET("/sensors", HandleSensors) - - // 系统状态 API - api.GET("/status", HandleStatus) - - // 获取可用接口列表 API - api.GET("/interfaces", HandleInterfaces) - - // 获取手型配置 API - api.GET("/hand-configs", HandleHandConfigs) - - // 健康检查端点 - api.GET("/health", HandleHealth) - } -}