Merge pull request #11 from eli-yip/refactor
refactor: implement interfaces handler
This commit is contained in:
commit
2f20c36139
@ -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
|
||||
|
||||
|
@ -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"]
|
207
api/legacy/compatibility.go
Normal file
207
api/legacy/compatibility.go
Normal file
@ -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)
|
||||
}
|
571
api/legacy/handlers.go
Normal file
571
api/legacy/handlers.go
Normal file
@ -0,0 +1,571 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"hands/config"
|
||||
"hands/define"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// handleHealth 健康检查处理函数
|
||||
func (s *LegacyServer) handleHealth(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: "CAN Control Service is running",
|
||||
Data: map[string]any{
|
||||
"timestamp": time.Now(),
|
||||
"availableInterfaces": config.Config.AvailableInterfaces,
|
||||
"defaultInterface": config.Config.DefaultInterface,
|
||||
"serviceVersion": "1.0.0-hand-type-support",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// handleInterfaces 获取可用接口列表处理函数
|
||||
func (s *LegacyServer) handleInterfaces(c *gin.Context) {
|
||||
responseData := map[string]any{
|
||||
"availableInterfaces": config.Config.AvailableInterfaces,
|
||||
"defaultInterface": config.Config.DefaultInterface,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Data: responseData,
|
||||
})
|
||||
}
|
||||
|
||||
// handleHandConfigs 获取手型配置处理函数
|
||||
func (s *LegacyServer) handleHandConfigs(c *gin.Context) {
|
||||
allHandConfigs := s.mapper.GetAllHandConfigs()
|
||||
|
||||
result := make(map[string]any)
|
||||
for _, ifName := range config.Config.AvailableInterfaces {
|
||||
if handConfig, exists := allHandConfigs[ifName]; exists {
|
||||
result[ifName] = map[string]any{
|
||||
"handType": handConfig.HandType,
|
||||
"handId": handConfig.HandId,
|
||||
}
|
||||
} else {
|
||||
// 返回默认配置
|
||||
result[ifName] = map[string]any{
|
||||
"handType": "right",
|
||||
"handId": define.HAND_TYPE_RIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
// handleHandType 手型设置处理函数
|
||||
func (s *LegacyServer) handleHandType(c *gin.Context) {
|
||||
var req HandTypeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的手型设置请求:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(req.Interface) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手型 ID
|
||||
if req.HandType == "left" && req.HandId != uint32(define.HAND_TYPE_LEFT) {
|
||||
req.HandId = uint32(define.HAND_TYPE_LEFT)
|
||||
} else if req.HandType == "right" && req.HandId != uint32(define.HAND_TYPE_RIGHT) {
|
||||
req.HandId = uint32(define.HAND_TYPE_RIGHT)
|
||||
}
|
||||
|
||||
// 设置手型配置
|
||||
if err := s.mapper.SetHandConfig(req.Interface, req.HandType, req.HandId); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "设置手型失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
handTypeName := "右手"
|
||||
if req.HandType == "left" {
|
||||
handTypeName = "左手"
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: fmt.Sprintf("接口 %s 手型已设置为%s (0x%X)", req.Interface, handTypeName, req.HandId),
|
||||
Data: map[string]any{
|
||||
"interface": req.Interface,
|
||||
"handType": req.HandType,
|
||||
"handId": req.HandId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// handleFingers 手指姿态处理函数
|
||||
func (s *LegacyServer) handleFingers(c *gin.Context) {
|
||||
var req FingerPoseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的手指姿态数据:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证每个值是否在范围内
|
||||
for _, v := range req.Pose {
|
||||
if v < 0 || v > 255 {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "手指姿态值必须在 0-255 范围内",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未指定接口,使用默认接口
|
||||
if req.Interface == "" {
|
||||
req.Interface = config.Config.DefaultInterface
|
||||
}
|
||||
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(req.Interface) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(req.Interface)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取设备失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 停止当前动画
|
||||
if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "停止动画失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置手指姿态
|
||||
if err := dev.SetFingerPose(req.Pose); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "发送手指姿态失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: "手指姿态指令发送成功",
|
||||
Data: map[string]any{"interface": req.Interface, "pose": req.Pose},
|
||||
})
|
||||
}
|
||||
|
||||
// handlePalm 掌部姿态处理函数
|
||||
func (s *LegacyServer) handlePalm(c *gin.Context) {
|
||||
var req PalmPoseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的掌部姿态数据:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证每个值是否在范围内
|
||||
for _, v := range req.Pose {
|
||||
if v < 0 || v > 255 {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "掌部姿态值必须在 0-255 范围内",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果未指定接口,使用默认接口
|
||||
if req.Interface == "" {
|
||||
req.Interface = config.Config.DefaultInterface
|
||||
}
|
||||
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(req.Interface) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(req.Interface)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取设备失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 停止当前动画
|
||||
if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "停止动画失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置掌部姿态
|
||||
if err := dev.SetPalmPose(req.Pose); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "发送掌部姿态失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: "掌部姿态指令发送成功",
|
||||
Data: map[string]any{"interface": req.Interface, "pose": req.Pose},
|
||||
})
|
||||
}
|
||||
|
||||
// handlePreset 预设姿势处理函数
|
||||
func (s *LegacyServer) handlePreset(c *gin.Context) {
|
||||
pose := c.Param("pose")
|
||||
|
||||
// 从查询参数获取接口名称和手型
|
||||
ifName := c.Query("interface")
|
||||
// handType := c.Query("handType") // TODO: 旧版 API 中声明但未使用,先放着,等 reivew 时候看看
|
||||
|
||||
if ifName == "" {
|
||||
ifName = config.Config.DefaultInterface
|
||||
}
|
||||
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(ifName) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(ifName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取设备失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 停止当前动画
|
||||
if err := s.mapper.StopAllAnimations(ifName); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "停止动画失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取预设姿势详细信息(用于返回具体参数)
|
||||
presetDetails, exists := dev.GetPresetDetails(pose)
|
||||
if !exists {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的预设姿势",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用设备的预设姿势方法
|
||||
if err := dev.ExecutePreset(pose); err != nil {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的预设姿势",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取预设姿势的描述
|
||||
description := dev.GetPresetDescription(pose)
|
||||
message := fmt.Sprintf("已设置预设姿势: %s", pose)
|
||||
if description != "" {
|
||||
message = fmt.Sprintf("已设置%s", description)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: message,
|
||||
Data: map[string]any{"interface": ifName, "pose": presetDetails.FingerPose},
|
||||
})
|
||||
}
|
||||
|
||||
// handleAnimation 动画控制处理函数
|
||||
func (s *LegacyServer) handleAnimation(c *gin.Context) {
|
||||
var req AnimationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的动画请求:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果未指定接口,使用默认接口
|
||||
if req.Interface == "" {
|
||||
req.Interface = config.Config.DefaultInterface
|
||||
}
|
||||
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(req.Interface) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", req.Interface, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(req.Interface)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取设备失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取动画引擎
|
||||
animEngine := dev.GetAnimationEngine()
|
||||
|
||||
// 停止当前动画
|
||||
if err := s.mapper.StopAllAnimations(req.Interface); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "停止动画失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是停止命令,直接返回
|
||||
if req.Type == "stop" {
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: fmt.Sprintf("%s 动画已停止", req.Interface),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理速度参数
|
||||
if req.Speed <= 0 {
|
||||
req.Speed = 500 // 默认速度
|
||||
}
|
||||
|
||||
// 根据类型启动动画
|
||||
switch req.Type {
|
||||
case "wave":
|
||||
if err := animEngine.Start("wave", req.Speed); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("启动波浪动画失败:%v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: fmt.Sprintf("%s 波浪动画已启动", req.Interface),
|
||||
Data: map[string]any{"interface": req.Interface, "speed": req.Speed},
|
||||
})
|
||||
case "sway":
|
||||
if err := animEngine.Start("sway", req.Speed); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("启动横向摆动动画失败:%v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Message: fmt.Sprintf("%s 横向摆动动画已启动", req.Interface),
|
||||
Data: map[string]any{"interface": req.Interface, "speed": req.Speed},
|
||||
})
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "无效的动画类型",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// handleSensors 获取传感器数据处理函数
|
||||
func (s *LegacyServer) handleSensors(c *gin.Context) {
|
||||
// 从查询参数获取接口名称
|
||||
ifName := c.Query("interface")
|
||||
|
||||
if ifName != "" {
|
||||
// 验证接口
|
||||
if !s.mapper.IsValidInterface(ifName) {
|
||||
c.JSON(http.StatusBadRequest, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: fmt.Sprintf("无效的接口 %s,可用接口: %v", ifName, config.Config.AvailableInterfaces),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(ifName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取设备失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sensorData, err := dev.ReadSensorData()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, define.ApiResponse{
|
||||
Status: "error",
|
||||
Error: "获取传感器数据失败:" + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Data: sensorData.Values(),
|
||||
})
|
||||
} else {
|
||||
// 返回所有接口的传感器数据
|
||||
allSensorData := make(map[string]any)
|
||||
|
||||
for _, ifName := range config.Config.AvailableInterfaces {
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(ifName)
|
||||
if err != nil {
|
||||
allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()}
|
||||
continue
|
||||
}
|
||||
|
||||
sensorData, err := dev.ReadSensorData()
|
||||
if err != nil {
|
||||
allSensorData[ifName] = map[string]any{"error": "设备不可用:" + err.Error()}
|
||||
continue
|
||||
}
|
||||
|
||||
allSensorData[ifName] = sensorData.Values()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Data: allSensorData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// handleStatus 系统状态处理函数
|
||||
func (s *LegacyServer) handleStatus(c *gin.Context) {
|
||||
// 构建动画状态
|
||||
animationStatus := make(map[string]bool)
|
||||
|
||||
// 获取手型配置
|
||||
allHandConfigs := s.mapper.GetAllHandConfigs()
|
||||
handConfigsData := make(map[string]any)
|
||||
|
||||
// 构建接口状态信息
|
||||
interfaceStatuses := make(map[string]any)
|
||||
|
||||
// 检查 CAN 服务状态 - 通过尝试获取设备状态来判断
|
||||
canStatus := make(map[string]bool)
|
||||
|
||||
for ifName, handConfig := range allHandConfigs {
|
||||
handConfigsData[ifName] = map[string]any{
|
||||
"handType": handConfig.HandType,
|
||||
"handId": handConfig.HandId,
|
||||
}
|
||||
}
|
||||
|
||||
for _, ifName := range config.Config.AvailableInterfaces {
|
||||
// 获取对应的设备
|
||||
dev, err := s.mapper.GetDeviceForInterface(ifName)
|
||||
if err != nil {
|
||||
// 设备不可用
|
||||
animationStatus[ifName] = false
|
||||
canStatus[ifName] = false
|
||||
handConfigsData[ifName] = map[string]any{
|
||||
"handType": "right",
|
||||
"handId": define.HAND_TYPE_RIGHT,
|
||||
}
|
||||
} else {
|
||||
// 获取动画状态
|
||||
animEngine := dev.GetAnimationEngine()
|
||||
animationStatus[ifName] = animEngine.IsRunning()
|
||||
|
||||
// 获取设备状态来判断 CAN 服务状态
|
||||
rawCanStatus, err := dev.GetCanStatus()
|
||||
if err != nil {
|
||||
canStatus[ifName] = false
|
||||
} else {
|
||||
canStatus[ifName] = rawCanStatus[ifName]
|
||||
}
|
||||
}
|
||||
|
||||
// 构建接口状态
|
||||
interfaceStatuses[ifName] = map[string]any{
|
||||
"active": canStatus[ifName],
|
||||
"animationActive": animationStatus[ifName],
|
||||
"handConfig": handConfigsData[ifName],
|
||||
}
|
||||
}
|
||||
|
||||
// 计算活跃接口数量
|
||||
activeInterfacesCount := 0
|
||||
for _, active := range canStatus {
|
||||
if active {
|
||||
activeInterfacesCount++
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, define.ApiResponse{
|
||||
Status: "success",
|
||||
Data: map[string]any{
|
||||
"interfaces": interfaceStatuses,
|
||||
"uptime": time.Since(s.startTime).String(),
|
||||
"canServiceURL": config.Config.CanServiceURL,
|
||||
"defaultInterface": config.Config.DefaultInterface,
|
||||
"availableInterfaces": config.Config.AvailableInterfaces,
|
||||
"activeInterfaces": activeInterfacesCount,
|
||||
"handConfigs": handConfigsData,
|
||||
},
|
||||
})
|
||||
}
|
33
api/legacy/models.go
Normal file
33
api/legacy/models.go
Normal file
@ -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"`
|
||||
}
|
64
api/legacy/router.go
Normal file
64
api/legacy/router.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -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) // 获取所有传感器数据
|
||||
}
|
||||
|
||||
// 设备状态路由
|
||||
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
@ -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 }
|
||||
|
@ -1,80 +1,40 @@
|
||||
package device
|
||||
|
||||
// FingerPoseCommand 手指姿态指令
|
||||
type FingerPoseCommand struct {
|
||||
fingerID string
|
||||
poseData []byte
|
||||
targetComp string
|
||||
type FingerPoseCommand struct{ poseData []byte }
|
||||
|
||||
func NewFingerPoseCommand(poseData []byte) *FingerPoseCommand {
|
||||
return &FingerPoseCommand{poseData: poseData}
|
||||
}
|
||||
|
||||
func NewFingerPoseCommand(fingerID string, poseData []byte) *FingerPoseCommand {
|
||||
return &FingerPoseCommand{
|
||||
fingerID: fingerID,
|
||||
poseData: poseData,
|
||||
targetComp: "finger_" + fingerID,
|
||||
}
|
||||
}
|
||||
func (c *FingerPoseCommand) Type() string { return "SetFingerPose" }
|
||||
|
||||
func (c *FingerPoseCommand) Type() string {
|
||||
return "SetFingerPose"
|
||||
}
|
||||
|
||||
func (c *FingerPoseCommand) Payload() []byte {
|
||||
return c.poseData
|
||||
}
|
||||
|
||||
func (c *FingerPoseCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
||||
func (c *FingerPoseCommand) Payload() []byte { return c.poseData }
|
||||
|
||||
// PalmPoseCommand 手掌姿态指令
|
||||
type PalmPoseCommand struct {
|
||||
poseData []byte
|
||||
targetComp string
|
||||
}
|
||||
type PalmPoseCommand struct{ poseData []byte }
|
||||
|
||||
func NewPalmPoseCommand(poseData []byte) *PalmPoseCommand {
|
||||
return &PalmPoseCommand{
|
||||
poseData: poseData,
|
||||
targetComp: "palm",
|
||||
}
|
||||
return &PalmPoseCommand{poseData: poseData}
|
||||
}
|
||||
|
||||
func (c *PalmPoseCommand) Type() string {
|
||||
return "SetPalmPose"
|
||||
}
|
||||
func (c *PalmPoseCommand) Type() string { return "SetPalmPose" }
|
||||
|
||||
func (c *PalmPoseCommand) Payload() []byte {
|
||||
return c.poseData
|
||||
}
|
||||
|
||||
func (c *PalmPoseCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
||||
func (c *PalmPoseCommand) Payload() []byte { return c.poseData }
|
||||
|
||||
// GenericCommand 通用指令
|
||||
type GenericCommand struct {
|
||||
cmdType string
|
||||
payload []byte
|
||||
targetComp string
|
||||
cmdType string
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func NewGenericCommand(cmdType string, payload []byte, targetComp string) *GenericCommand {
|
||||
return &GenericCommand{
|
||||
cmdType: cmdType,
|
||||
payload: payload,
|
||||
targetComp: targetComp,
|
||||
cmdType: cmdType,
|
||||
payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GenericCommand) Type() string {
|
||||
return c.cmdType
|
||||
}
|
||||
func (c *GenericCommand) Type() string { return c.cmdType }
|
||||
|
||||
func (c *GenericCommand) Payload() []byte {
|
||||
return c.payload
|
||||
}
|
||||
|
||||
func (c *GenericCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
||||
func (c *GenericCommand) Payload() []byte { return c.payload }
|
||||
|
@ -12,8 +12,9 @@ type Device interface {
|
||||
GetHandType() define.HandType // 获取设备手型
|
||||
SetHandType(handType define.HandType) error // 设置设备手型
|
||||
ExecuteCommand(cmd Command) error // 执行一个通用指令
|
||||
ReadSensorData(sensorID string) (SensorData, error) // 读取特定传感器数据
|
||||
ReadSensorData() (SensorData, error) // 读取特定传感器数据
|
||||
GetComponents(componentType ComponentType) []Component // 获取指定类型的组件
|
||||
GetCanStatus() (map[string]bool, error)
|
||||
GetStatus() (DeviceStatus, error) // 获取设备状态
|
||||
Connect() error // 连接设备
|
||||
Disconnect() error // 断开设备连接
|
||||
@ -23,23 +24,23 @@ type Device interface {
|
||||
GetAnimationEngine() *AnimationEngine // 获取设备的动画引擎
|
||||
|
||||
// --- 预设姿势相关方法 ---
|
||||
GetSupportedPresets() []string // 获取支持的预设姿势列表
|
||||
ExecutePreset(presetName string) error // 执行预设姿势
|
||||
GetPresetDescription(presetName string) string // 获取预设姿势描述
|
||||
GetSupportedPresets() []string // 获取支持的预设姿势列表
|
||||
ExecutePreset(presetName string) error // 执行预设姿势
|
||||
GetPresetDescription(presetName string) string // 获取预设姿势描述
|
||||
GetPresetDetails(presetName string) (PresetPose, bool) // 获取预设姿势详细信息
|
||||
}
|
||||
|
||||
// Command 代表一个发送给设备的指令
|
||||
type Command interface {
|
||||
Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle"
|
||||
Payload() []byte // 指令的实际数据
|
||||
TargetComponent() string // 目标组件 ID
|
||||
Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle"
|
||||
Payload() []byte // 指令的实际数据
|
||||
}
|
||||
|
||||
// SensorData 代表从传感器读取的数据
|
||||
type SensorData interface {
|
||||
Timestamp() time.Time
|
||||
// Timestamp() time.Time
|
||||
Values() map[string]any // 例如 {"pressure": 100, "angle": 30.5}
|
||||
SensorID() string
|
||||
// SensorID() string
|
||||
}
|
||||
|
||||
// ComponentType 定义组件类型
|
||||
@ -53,10 +54,10 @@ const (
|
||||
|
||||
// Component 代表设备的一个可插拔组件
|
||||
type Component interface {
|
||||
GetID() string
|
||||
GetType() ComponentType
|
||||
GetConfiguration() map[string]interface{} // 组件的特定配置
|
||||
IsActive() bool
|
||||
// GetID() string
|
||||
// GetType() ComponentType
|
||||
// GetConfiguration() map[string]interface{} // 组件的特定配置
|
||||
// IsActive() bool
|
||||
}
|
||||
|
||||
// DeviceStatus 代表设备状态
|
||||
|
@ -142,7 +142,7 @@ func (h *L10Hand) SetFingerPose(pose []byte) error {
|
||||
}
|
||||
|
||||
// 创建指令
|
||||
cmd := device.NewFingerPoseCommand("all", perturbedPose)
|
||||
cmd := device.NewFingerPoseCommand(perturbedPose)
|
||||
|
||||
// 执行指令
|
||||
err := h.ExecuteCommand(cmd)
|
||||
@ -197,11 +197,9 @@ func (h *L10Hand) ResetPose() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// commandToRawMessage 将通用指令转换为 L10 特定的 CAN 消息
|
||||
func (h *L10Hand) commandToRawMessage(cmd device.Command) (communication.RawMessage, error) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
// commandToRawMessageUnsafe 将通用指令转换为 L10 特定的 CAN 消息(不加锁版本)
|
||||
// 注意:此方法不是线程安全的,只应在已获取适当锁的情况下调用
|
||||
func (h *L10Hand) commandToRawMessageUnsafe(cmd device.Command) (communication.RawMessage, error) {
|
||||
var data []byte
|
||||
canID := uint32(h.handType)
|
||||
|
||||
@ -238,8 +236,8 @@ func (h *L10Hand) ExecuteCommand(cmd device.Command) error {
|
||||
return fmt.Errorf("设备 %s 未连接或未激活", h.id)
|
||||
}
|
||||
|
||||
// 转换指令为 CAN 消息
|
||||
rawMsg, err := h.commandToRawMessage(cmd)
|
||||
// 转换指令为 CAN 消息(使用不加锁版本,因为已经在写锁保护下)
|
||||
rawMsg, err := h.commandToRawMessageUnsafe(cmd)
|
||||
if err != nil {
|
||||
h.status.ErrorCount++
|
||||
h.status.LastError = err.Error()
|
||||
@ -259,22 +257,15 @@ func (h *L10Hand) ExecuteCommand(cmd device.Command) error {
|
||||
}
|
||||
|
||||
h.status.LastUpdate = time.Now()
|
||||
// 成功的日志记录移到 SetFingerPose 和 SetPalmPose 中,因为那里有更详细的信息
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- 其他 L10Hand 方法 (initializeComponents, GetID, GetModel, ReadSensorData, etc.) 保持不变 ---
|
||||
// --- 确保它们存在且与您上传的版本一致 ---
|
||||
|
||||
func (h *L10Hand) initializeComponents(_ map[string]any) error {
|
||||
// 初始化传感器组件
|
||||
sensors := []device.Component{
|
||||
component.NewPressureSensor("pressure_thumb", map[string]any{"location": "thumb"}),
|
||||
component.NewPressureSensor("pressure_index", map[string]any{"location": "index"}),
|
||||
component.NewPressureSensor("pressure_middle", map[string]any{"location": "middle"}),
|
||||
component.NewPressureSensor("pressure_ring", map[string]any{"location": "ring"}),
|
||||
component.NewPressureSensor("pressure_pinky", map[string]any{"location": "pinky"}),
|
||||
}
|
||||
defaultSensor := component.NewSensorData(h.canInterface)
|
||||
defaultSensor.MockData()
|
||||
sensors := []device.Component{defaultSensor}
|
||||
h.components[device.SensorComponent] = sensors
|
||||
return nil
|
||||
}
|
||||
@ -287,19 +278,17 @@ func (h *L10Hand) GetModel() string {
|
||||
return h.model
|
||||
}
|
||||
|
||||
func (h *L10Hand) ReadSensorData(sensorID string) (device.SensorData, error) {
|
||||
func (h *L10Hand) ReadSensorData() (device.SensorData, error) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
sensors := h.components[device.SensorComponent]
|
||||
for _, comp := range sensors {
|
||||
if comp.GetID() == sensorID {
|
||||
if sensor, ok := comp.(component.Sensor); ok {
|
||||
return sensor.ReadData()
|
||||
}
|
||||
if sensor, ok := comp.(component.Sensor); ok {
|
||||
return sensor.ReadData()
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("传感器 %s 不存在", sensorID)
|
||||
return nil, fmt.Errorf("传感器不存在")
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetComponents(componentType device.ComponentType) []device.Component {
|
||||
@ -378,3 +367,12 @@ func (h *L10Hand) ExecutePreset(presetName string) error {
|
||||
func (h *L10Hand) GetPresetDescription(presetName string) string {
|
||||
return h.presetManager.GetPresetDescription(presetName)
|
||||
}
|
||||
|
||||
// GetPresetDetails 获取预设姿势详细信息
|
||||
func (h *L10Hand) GetPresetDetails(presetName string) (device.PresetPose, bool) {
|
||||
return h.presetManager.GetPreset(presetName)
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetCanStatus() (map[string]bool, error) {
|
||||
return h.communicator.GetAllInterfaceStatuses()
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ type Device interface {
|
||||
GetSupportedPresets() []string
|
||||
ExecutePreset(presetName string) error
|
||||
GetPresetDescription(presetName string) string
|
||||
GetPresetDetails(presetName string) (PresetPose, bool)
|
||||
}
|
||||
```
|
||||
|
||||
|
10
main.go
10
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)
|
||||
|
2759
static/script.js
2759
static/script.js
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user