refactor: remove pkg directory for better project layout
This commit is contained in:
parent
a8a8821489
commit
53eae1cc51
122
communication/communicator.go
Normal file
122
communication/communicator.go
Normal file
@ -0,0 +1,122 @@
|
||||
package communication
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: ID 的作用是什么
|
||||
// RawMessage 代表发送给 can-bridge 服务或从其接收的原始消息结构
|
||||
type RawMessage struct {
|
||||
Interface string `json:"interface"` // 目标 CAN 接口名,例如 "can0", "vcan1"
|
||||
ID uint32 `json:"id"` // CAN 帧的 ID
|
||||
Data []byte `json:"data"` // CAN 帧的数据负载
|
||||
}
|
||||
|
||||
// Communicator 定义了与 can-bridge Web 服务进行通信的接口
|
||||
type Communicator interface {
|
||||
// SendMessage 将 RawMessage 通过 HTTP POST 请求发送到 can-bridge 服务
|
||||
SendMessage(msg RawMessage) error
|
||||
|
||||
// GetInterfaceStatus 获取指定 CAN 接口的状态
|
||||
GetInterfaceStatus(ifName string) (isActive bool, err error)
|
||||
|
||||
// GetAllInterfaceStatuses 获取所有已知 CAN 接口的状态
|
||||
GetAllInterfaceStatuses() (statuses map[string]bool, err error)
|
||||
|
||||
// SetServiceURL 设置 can-bridge 服务的 URL
|
||||
SetServiceURL(url string)
|
||||
|
||||
// IsConnected 检查与 can-bridge 服务的连接状态
|
||||
IsConnected() bool
|
||||
}
|
||||
|
||||
// CanBridgeClient 实现与 can-bridge 服务的 HTTP 通信
|
||||
type CanBridgeClient struct {
|
||||
serviceURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewCanBridgeClient(serviceURL string) Communicator {
|
||||
return &CanBridgeClient{
|
||||
serviceURL: serviceURL,
|
||||
client: &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CanBridgeClient) SendMessage(msg RawMessage) error {
|
||||
jsonData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化消息失败:%w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/can", c.serviceURL)
|
||||
resp, err := c.client.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送 HTTP 请求失败:%w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("can-bridge服务返回错误: %d, %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
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) {
|
||||
url := fmt.Sprintf("%s/api/status", c.serviceURL)
|
||||
resp, err := c.client.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取所有接口状态失败:%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 {
|
||||
return nil, fmt.Errorf("解析状态响应失败:%w", err)
|
||||
}
|
||||
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
func (c *CanBridgeClient) SetServiceURL(url string) { c.serviceURL = url }
|
||||
|
||||
func (c *CanBridgeClient) IsConnected() bool {
|
||||
_, err := c.GetAllInterfaceStatuses()
|
||||
return err == nil
|
||||
}
|
78
component/pressure_sensor.go
Normal file
78
component/pressure_sensor.go
Normal file
@ -0,0 +1,78 @@
|
||||
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
|
||||
}
|
42
component/sensor.go
Normal file
42
component/sensor.go
Normal file
@ -0,0 +1,42 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"hands/device"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Sensor 传感器组件接口
|
||||
type Sensor interface {
|
||||
device.Component
|
||||
ReadData() (device.SensorData, error)
|
||||
GetDataType() string
|
||||
GetSamplingRate() int
|
||||
SetSamplingRate(rate int) error
|
||||
}
|
||||
|
||||
// SensorDataImpl 传感器数据的具体实现
|
||||
type SensorDataImpl struct {
|
||||
timestamp time.Time
|
||||
values map[string]any
|
||||
sensorID string
|
||||
}
|
||||
|
||||
func NewSensorData(sensorID string, values map[string]any) *SensorDataImpl {
|
||||
return &SensorDataImpl{
|
||||
timestamp: time.Now(),
|
||||
values: values,
|
||||
sensorID: sensorID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SensorDataImpl) Timestamp() time.Time {
|
||||
return s.timestamp
|
||||
}
|
||||
|
||||
func (s *SensorDataImpl) Values() map[string]any {
|
||||
return s.values
|
||||
}
|
||||
|
||||
func (s *SensorDataImpl) SensorID() string {
|
||||
return s.sensorID
|
||||
}
|
12
device/animation.go
Normal file
12
device/animation.go
Normal file
@ -0,0 +1,12 @@
|
||||
package device
|
||||
|
||||
// Animation 定义了一个动画序列的行为
|
||||
type Animation interface {
|
||||
// Run 执行动画的一个周期或直到被停止
|
||||
// executor: 用于执行姿态指令
|
||||
// stop: 接收停止信号的通道
|
||||
// speedMs: 动画执行的速度(毫秒)
|
||||
Run(executor PoseExecutor, stop <-chan struct{}, speedMs int) error
|
||||
// Name 返回动画的名称
|
||||
Name() string
|
||||
}
|
80
device/commands.go
Normal file
80
device/commands.go
Normal file
@ -0,0 +1,80 @@
|
||||
package device
|
||||
|
||||
// FingerPoseCommand 手指姿态指令
|
||||
type FingerPoseCommand struct {
|
||||
fingerID string
|
||||
poseData []byte
|
||||
targetComp string
|
||||
}
|
||||
|
||||
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) Payload() []byte {
|
||||
return c.poseData
|
||||
}
|
||||
|
||||
func (c *FingerPoseCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
||||
|
||||
// PalmPoseCommand 手掌姿态指令
|
||||
type PalmPoseCommand struct {
|
||||
poseData []byte
|
||||
targetComp string
|
||||
}
|
||||
|
||||
func NewPalmPoseCommand(poseData []byte) *PalmPoseCommand {
|
||||
return &PalmPoseCommand{
|
||||
poseData: poseData,
|
||||
targetComp: "palm",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PalmPoseCommand) Type() string {
|
||||
return "SetPalmPose"
|
||||
}
|
||||
|
||||
func (c *PalmPoseCommand) Payload() []byte {
|
||||
return c.poseData
|
||||
}
|
||||
|
||||
func (c *PalmPoseCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
||||
|
||||
// GenericCommand 通用指令
|
||||
type GenericCommand struct {
|
||||
cmdType string
|
||||
payload []byte
|
||||
targetComp string
|
||||
}
|
||||
|
||||
func NewGenericCommand(cmdType string, payload []byte, targetComp string) *GenericCommand {
|
||||
return &GenericCommand{
|
||||
cmdType: cmdType,
|
||||
payload: payload,
|
||||
targetComp: targetComp,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GenericCommand) Type() string {
|
||||
return c.cmdType
|
||||
}
|
||||
|
||||
func (c *GenericCommand) Payload() []byte {
|
||||
return c.payload
|
||||
}
|
||||
|
||||
func (c *GenericCommand) TargetComponent() string {
|
||||
return c.targetComp
|
||||
}
|
64
device/device.go
Normal file
64
device/device.go
Normal file
@ -0,0 +1,64 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"hands/define"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Device 代表一个可控制的设备单元
|
||||
type Device interface {
|
||||
GetID() string // 获取设备唯一标识
|
||||
GetModel() string // 获取设备型号 (例如 "L10", "L20")
|
||||
GetHandType() define.HandType // 获取设备手型
|
||||
SetHandType(handType define.HandType) error // 设置设备手型
|
||||
ExecuteCommand(cmd Command) error // 执行一个通用指令
|
||||
ReadSensorData(sensorID string) (SensorData, error) // 读取特定传感器数据
|
||||
GetComponents(componentType ComponentType) []Component // 获取指定类型的组件
|
||||
GetStatus() (DeviceStatus, error) // 获取设备状态
|
||||
Connect() error // 连接设备
|
||||
Disconnect() error // 断开设备连接
|
||||
|
||||
// --- 新增 ---
|
||||
PoseExecutor // 嵌入 PoseExecutor 接口,Device 需实现它
|
||||
GetAnimationEngine() *AnimationEngine // 获取设备的动画引擎
|
||||
}
|
||||
|
||||
// Command 代表一个发送给设备的指令
|
||||
type Command interface {
|
||||
Type() string // 指令类型,例如 "SetFingerPose", "SetPalmAngle"
|
||||
Payload() []byte // 指令的实际数据
|
||||
TargetComponent() string // 目标组件 ID
|
||||
}
|
||||
|
||||
// SensorData 代表从传感器读取的数据
|
||||
type SensorData interface {
|
||||
Timestamp() time.Time
|
||||
Values() map[string]any // 例如 {"pressure": 100, "angle": 30.5}
|
||||
SensorID() string
|
||||
}
|
||||
|
||||
// ComponentType 定义组件类型
|
||||
type ComponentType string
|
||||
|
||||
const (
|
||||
SensorComponent ComponentType = "sensor"
|
||||
SkinComponent ComponentType = "skin"
|
||||
ActuatorComponent ComponentType = "actuator"
|
||||
)
|
||||
|
||||
// Component 代表设备的一个可插拔组件
|
||||
type Component interface {
|
||||
GetID() string
|
||||
GetType() ComponentType
|
||||
GetConfiguration() map[string]interface{} // 组件的特定配置
|
||||
IsActive() bool
|
||||
}
|
||||
|
||||
// DeviceStatus 代表设备状态
|
||||
type DeviceStatus struct {
|
||||
IsConnected bool
|
||||
IsActive bool
|
||||
LastUpdate time.Time
|
||||
ErrorCount int
|
||||
LastError string
|
||||
}
|
191
device/engine.go
Normal file
191
device/engine.go
Normal file
@ -0,0 +1,191 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// defaultAnimationSpeedMs 定义默认动画速度(毫秒)
|
||||
const defaultAnimationSpeedMs = 500
|
||||
|
||||
// AnimationEngine 管理和执行动画
|
||||
type AnimationEngine struct {
|
||||
executor PoseExecutor // 关联的姿态执行器
|
||||
animations map[string]Animation // 注册的动画
|
||||
stopChan chan struct{} // 当前动画的停止通道
|
||||
current string // 当前运行的动画名称
|
||||
isRunning bool // 是否有动画在运行
|
||||
engineMutex sync.Mutex // 保护引擎状态 (isRunning, current, stopChan)
|
||||
registerMutex sync.RWMutex // 保护动画注册表 (animations)
|
||||
}
|
||||
|
||||
// NewAnimationEngine 创建一个新的动画引擎
|
||||
func NewAnimationEngine(executor PoseExecutor) *AnimationEngine {
|
||||
return &AnimationEngine{
|
||||
executor: executor,
|
||||
animations: make(map[string]Animation),
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册一个动画
|
||||
func (e *AnimationEngine) Register(anim Animation) {
|
||||
e.registerMutex.Lock()
|
||||
defer e.registerMutex.Unlock()
|
||||
|
||||
if anim == nil {
|
||||
log.Printf("⚠️ 尝试注册一个空动画")
|
||||
return
|
||||
}
|
||||
|
||||
name := anim.Name()
|
||||
if _, exists := e.animations[name]; exists {
|
||||
log.Printf("⚠️ 动画 %s 已注册,将被覆盖", name)
|
||||
}
|
||||
e.animations[name] = anim
|
||||
log.Printf("✅ 动画 %s 已注册", name)
|
||||
}
|
||||
|
||||
// getAnimation 安全地获取一个已注册的动画
|
||||
func (e *AnimationEngine) getAnimation(name string) (Animation, bool) {
|
||||
e.registerMutex.RLock()
|
||||
defer e.registerMutex.RUnlock()
|
||||
anim, exists := e.animations[name]
|
||||
return anim, exists
|
||||
}
|
||||
|
||||
// getDeviceName 尝试获取设备 ID 用于日志记录
|
||||
func (e *AnimationEngine) getDeviceName() string {
|
||||
// 尝试通过接口断言获取 ID
|
||||
if idProvider, ok := e.executor.(interface{ GetID() string }); ok {
|
||||
return idProvider.GetID()
|
||||
}
|
||||
return "设备" // 默认名称
|
||||
}
|
||||
|
||||
// Start 启动一个动画
|
||||
func (e *AnimationEngine) Start(name string, speedMs int) error {
|
||||
e.engineMutex.Lock()
|
||||
defer e.engineMutex.Unlock() // 确保在任何情况下都释放锁
|
||||
|
||||
anim, exists := e.getAnimation(name)
|
||||
if !exists {
|
||||
return fmt.Errorf("❌ 动画 %s 未注册", name)
|
||||
}
|
||||
|
||||
// 如果有动画在运行,先发送停止信号
|
||||
if e.isRunning {
|
||||
log.Printf("ℹ️ 正在停止当前动画 %s 以启动 %s...", e.current, name)
|
||||
close(e.stopChan)
|
||||
// 注意:我们不在此处等待旧动画结束。
|
||||
// 新动画将立即启动,旧动画的 goroutine 在收到信号后会退出。
|
||||
// 其 defer 中的 `stopChan` 比较会确保它不会干扰新动画的状态。
|
||||
}
|
||||
|
||||
// 设置新动画状态
|
||||
e.stopChan = make(chan struct{}) // 创建新的停止通道
|
||||
e.isRunning = true
|
||||
e.current = name
|
||||
|
||||
// 验证并设置速度
|
||||
actualSpeedMs := speedMs
|
||||
if actualSpeedMs <= 0 {
|
||||
actualSpeedMs = defaultAnimationSpeedMs
|
||||
}
|
||||
|
||||
log.Printf("🚀 准备启动动画 %s (设备: %s, 速度: %dms)", name, e.getDeviceName(), actualSpeedMs)
|
||||
|
||||
// 启动动画 goroutine
|
||||
go e.runAnimationLoop(anim, e.stopChan, actualSpeedMs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止当前正在运行的动画
|
||||
func (e *AnimationEngine) Stop() error {
|
||||
e.engineMutex.Lock()
|
||||
defer e.engineMutex.Unlock()
|
||||
|
||||
if !e.isRunning {
|
||||
log.Printf("ℹ️ 当前没有动画在运行 (设备: %s)", e.getDeviceName())
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("⏳ 正在发送停止信号给动画 %s (设备: %s)...", e.current, e.getDeviceName())
|
||||
close(e.stopChan) // 发送停止信号
|
||||
e.isRunning = false // 立即标记为未运行,防止重复停止
|
||||
e.current = ""
|
||||
// 动画的 goroutine 将在下一次检查通道时退出,
|
||||
// 并在其 defer 块中执行最终的清理(包括 ResetPose)。
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning 检查是否有动画在运行
|
||||
func (e *AnimationEngine) IsRunning() bool {
|
||||
e.engineMutex.Lock()
|
||||
defer e.engineMutex.Unlock()
|
||||
return e.isRunning
|
||||
}
|
||||
|
||||
// runAnimationLoop 是动画执行的核心循环,在单独的 Goroutine 中运行。
|
||||
func (e *AnimationEngine) runAnimationLoop(anim Animation, stopChan <-chan struct{}, speedMs int) {
|
||||
deviceName := e.getDeviceName()
|
||||
animName := anim.Name()
|
||||
|
||||
// 使用 defer 确保无论如何都能执行清理逻辑
|
||||
defer e.handleLoopExit(stopChan, deviceName, animName)
|
||||
|
||||
log.Printf("▶️ %s 动画 %s 已启动", deviceName, animName)
|
||||
|
||||
// 动画主循环
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
log.Printf("🛑 %s 动画 %s 被显式停止", deviceName, animName)
|
||||
return // 接收到停止信号,退出循环
|
||||
default:
|
||||
// 执行一轮动画
|
||||
err := anim.Run(e.executor, stopChan, speedMs)
|
||||
if err != nil {
|
||||
log.Printf("❌ %s 动画 %s 执行出错: %v", deviceName, animName, err)
|
||||
return // 出错则退出
|
||||
}
|
||||
|
||||
// 再次检查停止信号,防止 Run 结束后才收到信号
|
||||
select {
|
||||
case <-stopChan:
|
||||
log.Printf("🛑 %s 动画 %s 在周期结束时被停止", deviceName, animName)
|
||||
return
|
||||
default:
|
||||
// 继续下一个循环
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleLoopExit 是动画 Goroutine 退出时执行的清理函数。
|
||||
func (e *AnimationEngine) handleLoopExit(stopChan <-chan struct{}, deviceName, animName string) {
|
||||
e.engineMutex.Lock()
|
||||
defer e.engineMutex.Unlock()
|
||||
|
||||
// --- 关键并发控制 ---
|
||||
// 检查当前引擎的 stopChan 是否与此 Goroutine 启动时的 stopChan 相同。
|
||||
// 如果不相同,说明一个新的动画已经启动,并且接管了引擎状态。
|
||||
// 这种情况下,旧的 Goroutine 不应该修改引擎状态或重置姿态,
|
||||
// 以避免干扰新动画。
|
||||
if stopChan == e.stopChan {
|
||||
// 只有当自己仍然是“活跃”的动画时,才更新状态并重置姿态
|
||||
e.isRunning = false
|
||||
e.current = ""
|
||||
log.Printf("👋 %s 动画 %s 已完成或停止,正在重置姿态...", deviceName, animName)
|
||||
if err := e.executor.ResetPose(); err != nil {
|
||||
log.Printf("⚠️ %s 动画结束后重置姿态失败: %v", deviceName, err)
|
||||
} else {
|
||||
log.Printf("✅ %s 姿态已重置", deviceName)
|
||||
}
|
||||
} else {
|
||||
// 如果 stopChan 不同,说明自己是旧的 Goroutine,只需安静退出
|
||||
log.Printf("ℹ️ 旧的 %s 动画 %s goroutine 退出,但新动画已启动,无需重置。", deviceName, animName)
|
||||
}
|
||||
}
|
35
device/factory.go
Normal file
35
device/factory.go
Normal file
@ -0,0 +1,35 @@
|
||||
package device
|
||||
|
||||
import "fmt"
|
||||
|
||||
// DeviceFactory 设备工厂
|
||||
type DeviceFactory struct {
|
||||
constructors map[string]func(config map[string]any) (Device, error)
|
||||
}
|
||||
|
||||
var defaultFactory = &DeviceFactory{
|
||||
constructors: make(map[string]func(config map[string]any) (Device, error)),
|
||||
}
|
||||
|
||||
// RegisterDeviceType 注册设备类型
|
||||
func RegisterDeviceType(modelName string, constructor func(config map[string]any) (Device, error)) {
|
||||
defaultFactory.constructors[modelName] = constructor
|
||||
}
|
||||
|
||||
// CreateDevice 创建设备实例
|
||||
func CreateDevice(modelName string, config map[string]any) (Device, error) {
|
||||
constructor, ok := defaultFactory.constructors[modelName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("未知的设备型号: %s", modelName)
|
||||
}
|
||||
return constructor(config)
|
||||
}
|
||||
|
||||
// GetSupportedModels 获取支持的设备型号列表
|
||||
func GetSupportedModels() []string {
|
||||
models := make([]string, 0, len(defaultFactory.constructors))
|
||||
for model := range defaultFactory.constructors {
|
||||
models = append(models, model)
|
||||
}
|
||||
return models
|
||||
}
|
63
device/manager.go
Normal file
63
device/manager.go
Normal file
@ -0,0 +1,63 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DeviceManager 管理设备实例
|
||||
type DeviceManager struct {
|
||||
devices map[string]Device
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDeviceManager() *DeviceManager { return &DeviceManager{devices: make(map[string]Device)} }
|
||||
|
||||
func (m *DeviceManager) RegisterDevice(dev Device) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
id := dev.GetID()
|
||||
if _, exists := m.devices[id]; exists {
|
||||
return fmt.Errorf("设备 %s 已存在", id)
|
||||
}
|
||||
|
||||
m.devices[id] = dev
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DeviceManager) GetDevice(id string) (Device, error) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
dev, exists := m.devices[id]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("设备 %s 不存在", id)
|
||||
}
|
||||
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func (m *DeviceManager) GetAllDevices() []Device {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
devices := make([]Device, 0, len(m.devices))
|
||||
for _, dev := range m.devices {
|
||||
devices = append(devices, dev)
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
func (m *DeviceManager) RemoveDevice(id string) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if _, exists := m.devices[id]; !exists {
|
||||
return fmt.Errorf("设备 %s 不存在", id)
|
||||
}
|
||||
|
||||
delete(m.devices, id)
|
||||
return nil
|
||||
}
|
8
device/models/init.go
Normal file
8
device/models/init.go
Normal file
@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
import "hands/device"
|
||||
|
||||
func init() {
|
||||
// 注册 L10 设备类型
|
||||
device.RegisterDeviceType("L10", NewL10Hand)
|
||||
}
|
330
device/models/l10.go
Normal file
330
device/models/l10.go
Normal file
@ -0,0 +1,330 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"hands/define"
|
||||
"hands/communication"
|
||||
"hands/component"
|
||||
"hands/device"
|
||||
)
|
||||
|
||||
// L10Hand L10 型号手部设备实现
|
||||
type L10Hand struct {
|
||||
id string
|
||||
model string
|
||||
handType define.HandType
|
||||
communicator communication.Communicator
|
||||
components map[device.ComponentType][]device.Component
|
||||
status device.DeviceStatus
|
||||
mutex sync.RWMutex
|
||||
canInterface string // CAN 接口名称,如 "can0"
|
||||
animationEngine *device.AnimationEngine // 动画引擎
|
||||
}
|
||||
|
||||
// 在 base 基础上进行 ±delta 的扰动,范围限制在 [0, 255]
|
||||
func perturb(base byte, delta int) byte {
|
||||
offset := rand.IntN(2*delta+1) - delta
|
||||
v := int(base) + offset
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
if v > 255 {
|
||||
v = 255
|
||||
}
|
||||
return byte(v)
|
||||
}
|
||||
|
||||
// NewL10Hand 创建 L10 手部设备实例
|
||||
func NewL10Hand(config map[string]any) (device.Device, error) {
|
||||
id, ok := config["id"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("缺少设备 ID 配置")
|
||||
}
|
||||
|
||||
serviceURL, ok := config["can_service_url"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("缺少 can 服务 URL 配置")
|
||||
}
|
||||
|
||||
canInterface, ok := config["can_interface"].(string)
|
||||
if !ok {
|
||||
canInterface = "can0" // 默认接口
|
||||
}
|
||||
|
||||
handTypeStr, ok := config["hand_type"].(string)
|
||||
handType := define.HAND_TYPE_RIGHT // 默认右手
|
||||
if ok && handTypeStr == "left" {
|
||||
handType = define.HAND_TYPE_LEFT
|
||||
}
|
||||
|
||||
// 创建通信客户端
|
||||
comm := communication.NewCanBridgeClient(serviceURL)
|
||||
|
||||
hand := &L10Hand{
|
||||
id: id,
|
||||
model: "L10",
|
||||
handType: handType,
|
||||
communicator: comm,
|
||||
components: make(map[device.ComponentType][]device.Component),
|
||||
canInterface: canInterface,
|
||||
status: device.DeviceStatus{
|
||||
IsConnected: false,
|
||||
IsActive: false,
|
||||
LastUpdate: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化动画引擎,将 hand 自身作为 PoseExecutor
|
||||
hand.animationEngine = device.NewAnimationEngine(hand)
|
||||
|
||||
// 注册默认动画
|
||||
hand.animationEngine.Register(NewL10WaveAnimation())
|
||||
hand.animationEngine.Register(NewL10SwayAnimation())
|
||||
|
||||
// 初始化组件
|
||||
if err := hand.initializeComponents(config); err != nil {
|
||||
return nil, fmt.Errorf("初始化组件失败:%w", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ 设备 L10 (%s, %s) 创建成功", id, handType.String())
|
||||
return hand, nil
|
||||
}
|
||||
|
||||
// GetHandType 获取设备手型
|
||||
func (h *L10Hand) GetHandType() define.HandType {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
return h.handType
|
||||
}
|
||||
|
||||
// SetHandType 设置设备手型
|
||||
func (h *L10Hand) SetHandType(handType define.HandType) error {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
if handType != define.HAND_TYPE_LEFT && handType != define.HAND_TYPE_RIGHT {
|
||||
return fmt.Errorf("无效的手型:%d", handType)
|
||||
}
|
||||
h.handType = handType
|
||||
log.Printf("🔧 设备 %s 手型已更新: %s", h.id, handType.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAnimationEngine 获取动画引擎
|
||||
func (h *L10Hand) GetAnimationEngine() *device.AnimationEngine {
|
||||
return h.animationEngine
|
||||
}
|
||||
|
||||
// SetFingerPose 设置手指姿态 (实现 PoseExecutor)
|
||||
func (h *L10Hand) SetFingerPose(pose []byte) error {
|
||||
if len(pose) != 6 {
|
||||
return fmt.Errorf("无效的手指姿态数据长度,需要 6 个字节")
|
||||
}
|
||||
|
||||
// 添加随机扰动
|
||||
perturbedPose := make([]byte, len(pose))
|
||||
for i, v := range pose {
|
||||
perturbedPose[i] = perturb(v, 5)
|
||||
}
|
||||
|
||||
// 创建指令
|
||||
cmd := device.NewFingerPoseCommand("all", perturbedPose)
|
||||
|
||||
// 执行指令
|
||||
err := h.ExecuteCommand(cmd)
|
||||
if err == nil {
|
||||
log.Printf("✅ %s (%s) 手指动作已发送: [%X %X %X %X %X %X]",
|
||||
h.id, h.GetHandType().String(), perturbedPose[0], perturbedPose[1], perturbedPose[2],
|
||||
perturbedPose[3], perturbedPose[4], perturbedPose[5])
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPalmPose 设置手掌姿态 (实现 PoseExecutor)
|
||||
func (h *L10Hand) SetPalmPose(pose []byte) error {
|
||||
if len(pose) != 4 {
|
||||
return fmt.Errorf("无效的手掌姿态数据长度,需要 4 个字节")
|
||||
}
|
||||
|
||||
// 添加随机扰动
|
||||
perturbedPose := make([]byte, len(pose))
|
||||
for i, v := range pose {
|
||||
perturbedPose[i] = perturb(v, 8)
|
||||
}
|
||||
|
||||
// 创建指令
|
||||
cmd := device.NewPalmPoseCommand(perturbedPose)
|
||||
|
||||
// 执行指令
|
||||
err := h.ExecuteCommand(cmd)
|
||||
if err == nil {
|
||||
log.Printf("✅ %s (%s) 掌部姿态已发送: [%X %X %X %X]",
|
||||
h.id, h.GetHandType().String(), perturbedPose[0], perturbedPose[1], perturbedPose[2], perturbedPose[3])
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ResetPose 重置到默认姿态 (实现 PoseExecutor)
|
||||
func (h *L10Hand) ResetPose() error {
|
||||
log.Printf("🔄 正在重置设备 %s (%s) 到默认姿态...", h.id, h.GetHandType().String())
|
||||
defaultFingerPose := []byte{64, 64, 64, 64, 64, 64} // 0x40 - 半开
|
||||
defaultPalmPose := []byte{128, 128, 128, 128} // 0x80 - 居中
|
||||
|
||||
if err := h.SetFingerPose(defaultFingerPose); err != nil {
|
||||
log.Printf("❌ %s 重置手指姿势失败: %v", h.id, err)
|
||||
return err
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond) // 短暂延时
|
||||
if err := h.SetPalmPose(defaultPalmPose); err != nil {
|
||||
log.Printf("❌ %s 重置掌部姿势失败: %v", h.id, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("✅ 设备 %s 已重置到默认姿态", h.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// commandToRawMessage 将通用指令转换为 L10 特定的 CAN 消息
|
||||
func (h *L10Hand) commandToRawMessage(cmd device.Command) (communication.RawMessage, error) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
var data []byte
|
||||
canID := uint32(h.handType)
|
||||
|
||||
switch cmd.Type() {
|
||||
case "SetFingerPose":
|
||||
// 添加 0x01 前缀
|
||||
data = append([]byte{0x01}, cmd.Payload()...)
|
||||
if len(data) > 8 { // CAN 消息数据长度限制
|
||||
return communication.RawMessage{}, fmt.Errorf("手指姿态数据过长")
|
||||
}
|
||||
case "SetPalmPose":
|
||||
// 添加 0x04 前缀
|
||||
data = append([]byte{0x04}, cmd.Payload()...)
|
||||
if len(data) > 8 { // CAN 消息数据长度限制
|
||||
return communication.RawMessage{}, fmt.Errorf("手掌姿态数据过长")
|
||||
}
|
||||
default:
|
||||
return communication.RawMessage{}, fmt.Errorf("L10 不支持的指令类型: %s", cmd.Type())
|
||||
}
|
||||
|
||||
return communication.RawMessage{
|
||||
Interface: h.canInterface,
|
||||
ID: canID,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteCommand 执行一个通用指令
|
||||
func (h *L10Hand) ExecuteCommand(cmd device.Command) error {
|
||||
h.mutex.Lock() // 使用写锁,因为会更新状态
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if !h.status.IsConnected || !h.status.IsActive {
|
||||
return fmt.Errorf("设备 %s 未连接或未激活", h.id)
|
||||
}
|
||||
|
||||
// 转换指令为 CAN 消息
|
||||
rawMsg, err := h.commandToRawMessage(cmd)
|
||||
if err != nil {
|
||||
h.status.ErrorCount++
|
||||
h.status.LastError = err.Error()
|
||||
return fmt.Errorf("转换指令失败:%w", err)
|
||||
}
|
||||
|
||||
// 发送到 can-bridge 服务
|
||||
if err := h.communicator.SendMessage(rawMsg); err != nil {
|
||||
h.status.ErrorCount++
|
||||
h.status.LastError = err.Error()
|
||||
log.Printf("❌ %s (%s) 发送指令失败: %v (ID: 0x%X, Data: %X)", h.id, h.handType.String(), err, rawMsg.ID, rawMsg.Data)
|
||||
return fmt.Errorf("发送指令失败:%w", err)
|
||||
}
|
||||
|
||||
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"}),
|
||||
}
|
||||
h.components[device.SensorComponent] = sensors
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetID() string {
|
||||
return h.id
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetModel() string {
|
||||
return h.model
|
||||
}
|
||||
|
||||
func (h *L10Hand) ReadSensorData(sensorID string) (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()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("传感器 %s 不存在", sensorID)
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetComponents(componentType device.ComponentType) []device.Component {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
if components, exists := h.components[componentType]; exists {
|
||||
result := make([]device.Component, len(components))
|
||||
copy(result, components)
|
||||
return result
|
||||
}
|
||||
return []device.Component{}
|
||||
}
|
||||
|
||||
func (h *L10Hand) GetStatus() (device.DeviceStatus, error) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
return h.status, nil
|
||||
}
|
||||
|
||||
func (h *L10Hand) Connect() error {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
// TODO: 假设连接总是成功,除非有显式错误
|
||||
h.status.IsConnected = true
|
||||
h.status.IsActive = true
|
||||
h.status.LastUpdate = time.Now()
|
||||
log.Printf("🔗 设备 %s 已连接", h.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *L10Hand) Disconnect() error {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
h.status.IsConnected = false
|
||||
h.status.IsActive = false
|
||||
h.status.LastUpdate = time.Now()
|
||||
log.Printf("🔌 设备 %s 已断开", h.id)
|
||||
return nil
|
||||
}
|
125
device/models/l10_animation.go
Normal file
125
device/models/l10_animation.go
Normal file
@ -0,0 +1,125 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"hands/device"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// --- L10WaveAnimation ---
|
||||
|
||||
// L10WaveAnimation 实现 L10 的波浪动画
|
||||
type L10WaveAnimation struct{}
|
||||
|
||||
// NewL10WaveAnimation 创建 L10 波浪动画实例
|
||||
func NewL10WaveAnimation() *L10WaveAnimation { return &L10WaveAnimation{} }
|
||||
|
||||
func (w *L10WaveAnimation) Name() string { return "wave" }
|
||||
|
||||
func (w *L10WaveAnimation) Run(executor device.PoseExecutor, stop <-chan struct{}, speedMs int) error {
|
||||
fingerOrder := []int{0, 1, 2, 3, 4, 5}
|
||||
open := byte(64) // 0x40
|
||||
close := byte(192) // 0xC0
|
||||
delay := time.Duration(speedMs) * time.Millisecond
|
||||
|
||||
deviceName := "L10"
|
||||
|
||||
// 波浪张开
|
||||
for _, idx := range fingerOrder {
|
||||
pose := make([]byte, 6)
|
||||
for j := 0; j < 6; j++ {
|
||||
if j == idx {
|
||||
pose[j] = open
|
||||
} else {
|
||||
pose[j] = close
|
||||
}
|
||||
}
|
||||
|
||||
if err := executor.SetFingerPose(pose); err != nil {
|
||||
log.Printf("❌ %s 动画 %s 发送失败: %v", deviceName, w.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil // 动画被停止
|
||||
case <-time.After(delay):
|
||||
// 继续
|
||||
}
|
||||
}
|
||||
|
||||
// 波浪握拳
|
||||
for _, idx := range fingerOrder {
|
||||
pose := make([]byte, 6)
|
||||
for j := 0; j < 6; j++ {
|
||||
if j == idx {
|
||||
pose[j] = close
|
||||
} else {
|
||||
pose[j] = open
|
||||
}
|
||||
}
|
||||
|
||||
if err := executor.SetFingerPose(pose); err != nil {
|
||||
log.Printf("❌ %s 动画 %s 发送失败: %v", deviceName, w.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil // 动画被停止
|
||||
case <-time.After(delay):
|
||||
// 继续
|
||||
}
|
||||
}
|
||||
|
||||
return nil // 完成一个周期
|
||||
}
|
||||
|
||||
// --- L10SwayAnimation ---
|
||||
|
||||
// L10SwayAnimation 实现 L10 的横向摆动动画
|
||||
type L10SwayAnimation struct{}
|
||||
|
||||
// NewL10SwayAnimation 创建 L10 摆动动画实例
|
||||
func NewL10SwayAnimation() *L10SwayAnimation { return &L10SwayAnimation{} }
|
||||
|
||||
func (s *L10SwayAnimation) Name() string { return "sway" }
|
||||
|
||||
func (s *L10SwayAnimation) Run(executor device.PoseExecutor, stop <-chan struct{}, speedMs int) error {
|
||||
leftPose := []byte{48, 48, 48, 48} // 0x30
|
||||
rightPose := []byte{208, 208, 208, 208} // 0xD0
|
||||
delay := time.Duration(speedMs) * time.Millisecond
|
||||
|
||||
deviceName := "L10"
|
||||
if idProvider, ok := executor.(interface{ GetID() string }); ok {
|
||||
deviceName = idProvider.GetID()
|
||||
}
|
||||
|
||||
// 向左移动
|
||||
if err := executor.SetPalmPose(leftPose); err != nil {
|
||||
log.Printf("❌ %s 动画 %s 发送失败: %v", deviceName, s.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil // 动画被停止
|
||||
case <-time.After(delay):
|
||||
// 继续
|
||||
}
|
||||
|
||||
// 向右移动
|
||||
if err := executor.SetPalmPose(rightPose); err != nil {
|
||||
log.Printf("❌ %s 动画 %s 发送失败: %v", deviceName, s.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil // 动画被停止
|
||||
case <-time.After(delay):
|
||||
// 继续
|
||||
}
|
||||
|
||||
return nil // 完成一个周期
|
||||
}
|
20
device/pose_executor.go
Normal file
20
device/pose_executor.go
Normal file
@ -0,0 +1,20 @@
|
||||
package device
|
||||
|
||||
import "hands/define"
|
||||
|
||||
// PoseExecutor 定义了执行基本姿态指令的能力
|
||||
type PoseExecutor interface {
|
||||
// SetFingerPose 设置手指姿态
|
||||
// pose: 6 字节数据,代表 6 个手指的位置
|
||||
SetFingerPose(pose []byte) error
|
||||
|
||||
// SetPalmPose 设置手掌姿态
|
||||
// pose: 4 字节数据,代表手掌的 4 个自由度
|
||||
SetPalmPose(pose []byte) error
|
||||
|
||||
// ResetPose 重置到默认姿态
|
||||
ResetPose() error
|
||||
|
||||
// GetHandType 获取当前手型
|
||||
GetHandType() define.HandType
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user