Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: "LinkerBot CI"

on:
push:
branches: [ "main" ] # 每次推送到 main 分支时触发
pull_request:
branches: [ "main" ] # 提交 PR 时触发

jobs:
test_requirements:
runs-on: ubuntu-latest
env:
# 强制使用官方源,防止任何遗留的镜像源配置导致 403
UV_DEFAULT_INDEX: "https://pypi.org/simple"
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
with:
version: "latest"
enable-cache: true # 开启缓存加速后续构建

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install Dependencies
run: |
uv sync
- name: Run Pytest
run: |
# 运行 tests 目录下的所有测试,并输出详细信息
uv run pytest tests/test.py -v
18 changes: 18 additions & 0 deletions robodriver/robots/Universal_Client-master/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Python
__pycache__/
*.py[cod]
*.so
.env
venv/
*.egg-info/

# ROS
build/
devel/
logs/

# IDE
.vscode/
.idea/

repomix-output.xml
9 changes: 9 additions & 0 deletions robodriver/robots/Universal_Client-master/.repomixignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Add patterns to ignore here, one per line
# Example:
# *.log
# tmp/
.git/
.venv/
__pycache__/
*.egg-info/
uv.lock
21 changes: 21 additions & 0 deletions robodriver/robots/Universal_Client-master/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# LinkerBot_Client

## get start
1. 克隆仓库
```
git clone https://github.com/Yaoxing-Technology/LinkerBot_Client.git && cd LinkerBot_Client
```
2. 安装依赖
```
pip install uv
uv sync
```
3. 启动环境
```
source .venv/bin/activate
```
4. 测试机器人
```
python tests/test_robot.py
```

101 changes: 101 additions & 0 deletions robodriver/robots/Universal_Client-master/config/settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
app:
name: "LinkerBot-ZMQ-Hub"
middleware: "ros2" # 可选: "ros1", "ros2"
fps: 30

zmq:
pub_address: "tcp://0.0.0.0:5577" # 状态广播
sub_address: "tcp://0.0.0.0:5578" # 控制端广播
debug: false

adapter:
pub_address: "tcp://0.0.0.0:6000" # 旧版系统监听的地址
fps: 30 # 数据采集发送频率

# 硬件设备树:已彻底解耦为 sensors(获取) 和 actuators(控制)
devices:
# ==========================================
# Sensors: 用于订阅 ROS 话题并将数据上报给 ZMQ
# ==========================================
sensors:
image_top:
topic: "/camera/color/image_raw"
msg_class: "sensor_msgs.msg.Image"

# --- 主臂数据源 ---
leader_left_joints:
topic: "/left_arm_joint_control"
msg_class: "sensor_msgs.msg.JointState"
format: "generic"
leader_right_joints:
topic: "/right_arm_joint_control"
msg_class: "sensor_msgs.msg.JointState"
format: "generic"
leader_left_hand:
topic: "/cb_left_hand_raw_data"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"
leader_right_hand:
topic: "/cb_right_hand_raw_data"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"

# --- 从臂数据源 ---
follower_left_joints:
topic: "/robot1/left_arm/joint_states"
msg_class: "sensor_msgs.msg.JointState"
format: "generic"
follower_left_pose:
topic: "/robot1/left_arm/pose_states"
msg_class: "geometry_msgs.msg.PoseStamped"
format: "pose"
follower_right_joints:
topic: "/robot1/right_arm/joint_states"
msg_class: "sensor_msgs.msg.JointState"
format: "generic"
follower_right_pose:
topic: "/robot1/right_arm/pose_states"
msg_class: "geometry_msgs.msg.PoseStamped"
format: "pose"

# --- 灵巧手 (支持无名关节的脏数据清洗) ---
follower_left_hand:
topic: "/cb_left_hand_state"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"
follower_right_hand:
topic: "/cb_right_hand_state"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"

# ==========================================
# Actuators: 用于接收 ZMQ 指令并发布为 ROS 话题
# ==========================================
actuators:
follower_left_arm_control:
topic: "/robot1/left_arm/joint_follow"
msg_class: "lbot_arm_interfaces.msg.FollowJoint"
format: "generic"

follower_right_arm_control:
topic: "/robot1/right_arm/joint_follow"
msg_class: "lbot_arm_interfaces.msg.FollowJoint"
format: "generic"

follower_left_hand_control:
topic: "/cb_left_hand_control_cmd"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"

follower_right_hand_control:
topic: "/cb_right_hand_control_cmd"
msg_class: "sensor_msgs.msg.JointState"
format: "hand"

inference:
engine: "beingh"
host: "127.0.0.1"
port: 5800

engine_params:
action_chunk_index: 0
35 changes: 35 additions & 0 deletions robodriver/robots/Universal_Client-master/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "universal-client"
version = "0.1.0"
readme = "README.md"
requires-python = ">=3.10,<3.11"

authors = [
{name = "yaoxingkeji"},
]

dependencies = [
"numpy<2",
"opencv-python<4.11",
"pyyaml>=6.0.3",
"requests>=2.32.4",
"rospkg>=1.6.0",
"torch>=2.11.0",
"zmq>=0.0.0",
]
[project.scripts]
universal = "src.main:main"

[tool.setuptools.packages.find]
where = ["."]
include = ["src*"]
namespaces = false

[dependency-groups]
dev = [
"pytest>=8.3.5",
]
43 changes: 43 additions & 0 deletions robodriver/robots/Universal_Client-master/repomix.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "https://repomix.com/schemas/latest/schema.json",
"input": {
"maxFileSize": 52428800
},
"output": {
"filePath": "repomix-output.xml",
"style": "xml",
"parsableStyle": false,
"fileSummary": true,
"directoryStructure": true,
"files": true,
"removeComments": false,
"removeEmptyLines": false,
"compress": false,
"topFilesLength": 5,
"showLineNumbers": false,
"truncateBase64": false,
"copyToClipboard": false,
"includeFullDirectoryStructure": false,
"tokenCountTree": false,
"git": {
"sortByChanges": true,
"sortByChangesMaxCommits": 100,
"includeDiffs": false,
"includeLogs": false,
"includeLogsCount": 50
}
},
"include": [],
"ignore": {
"useGitignore": true,
"useDotIgnore": true,
"useDefaultPatterns": true,
"customPatterns": []
},
"security": {
"enableSecurityCheck": true
},
"tokenCount": {
"encoding": "o200k_base"
}
}
Empty file.
Empty file.
70 changes: 70 additions & 0 deletions robodriver/robots/Universal_Client-master/src/api/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

# 引入全新的工厂方法
from src.core.factory import create_connector
from src.core.base_connector import BaseRobotConnector

class ControlCommand(BaseModel):
key: str # yaml 里 devices 下的 key,例如 "master_left"
position: List[float] # 目标角度列表
names: Optional[List[str]] = None # 关节名字 (可选)

app = FastAPI(title="LinkerBot Edge API")

# 全局变量存放硬件抽象层实例
bot: BaseRobotConnector = None

@app.on_event("startup")
def startup_event():
"""
FastAPI 启动时,根据 yaml 自动初始化 ROS 1 或 ROS 2 节点
"""
global bot
try:
bot = create_connector("config/settings.yaml")
bot.start() # 启动底层自旋线程和 ZMQ
print("✅ Robot Connector attached to API Server")
except Exception as e:
print(f"❌ Failed to initialize Robot Connector: {e}")

@app.on_event("shutdown")
def shutdown_event():
"""优雅释放资源"""
global bot
if bot:
bot.stop()
print("🛑 Robot Connector stopped")

@app.get("/")
def health_check():
return {"status": "online", "robot_connected": bot is not None}

@app.get("/state/{key}")
def get_robot_state(key: str):
if not bot:
raise HTTPException(status_code=503, detail="Robot not connected")

data = bot.get_state(key)
if data is None:
raise HTTPException(status_code=404, detail=f"Key '{key}' not found or no data yet")
return data

@app.post("/control")
def send_command(cmd: ControlCommand):
if not bot:
raise HTTPException(status_code=503, detail="Robot not connected")

# 封装为 dict 传递给底层的 send_control
command_data = {
"position": cmd.position,
"names": cmd.names if cmd.names else []
}

success = bot.send_control(cmd.key, command_data)

if not success:
raise HTTPException(status_code=400, detail="Command failed (check logs or key name)")

return {"status": "executed", "target": cmd.key}
Empty file.
Loading