foodforthought-cli 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ate/__init__.py +6 -0
- ate/__main__.py +16 -0
- ate/auth/__init__.py +1 -0
- ate/auth/device_flow.py +141 -0
- ate/auth/token_store.py +96 -0
- ate/behaviors/__init__.py +100 -0
- ate/behaviors/approach.py +399 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +855 -3995
- ate/client.py +90 -0
- ate/commands/__init__.py +168 -0
- ate/commands/auth.py +389 -0
- ate/commands/bridge.py +448 -0
- ate/commands/data.py +185 -0
- ate/commands/deps.py +111 -0
- ate/commands/generate.py +384 -0
- ate/commands/memory.py +907 -0
- ate/commands/parts.py +166 -0
- ate/commands/primitive.py +399 -0
- ate/commands/protocol.py +288 -0
- ate/commands/recording.py +524 -0
- ate/commands/repo.py +154 -0
- ate/commands/simulation.py +291 -0
- ate/commands/skill.py +303 -0
- ate/commands/skills.py +487 -0
- ate/commands/team.py +147 -0
- ate/commands/workflow.py +271 -0
- ate/detection/__init__.py +38 -0
- ate/detection/base.py +142 -0
- ate/detection/color_detector.py +399 -0
- ate/detection/trash_detector.py +322 -0
- ate/drivers/__init__.py +39 -0
- ate/drivers/ble_transport.py +405 -0
- ate/drivers/mechdog.py +942 -0
- ate/drivers/wifi_camera.py +477 -0
- ate/interfaces/__init__.py +187 -0
- ate/interfaces/base.py +273 -0
- ate/interfaces/body.py +267 -0
- ate/interfaces/detection.py +282 -0
- ate/interfaces/locomotion.py +422 -0
- ate/interfaces/manipulation.py +408 -0
- ate/interfaces/navigation.py +389 -0
- ate/interfaces/perception.py +362 -0
- ate/interfaces/sensors.py +247 -0
- ate/interfaces/types.py +371 -0
- ate/llm_proxy.py +239 -0
- ate/mcp_server.py +387 -0
- ate/memory/__init__.py +35 -0
- ate/memory/cloud.py +244 -0
- ate/memory/context.py +269 -0
- ate/memory/embeddings.py +184 -0
- ate/memory/export.py +26 -0
- ate/memory/merge.py +146 -0
- ate/memory/migrate/__init__.py +34 -0
- ate/memory/migrate/base.py +89 -0
- ate/memory/migrate/pipeline.py +189 -0
- ate/memory/migrate/sources/__init__.py +13 -0
- ate/memory/migrate/sources/chroma.py +170 -0
- ate/memory/migrate/sources/pinecone.py +120 -0
- ate/memory/migrate/sources/qdrant.py +110 -0
- ate/memory/migrate/sources/weaviate.py +160 -0
- ate/memory/reranker.py +353 -0
- ate/memory/search.py +26 -0
- ate/memory/store.py +548 -0
- ate/recording/__init__.py +83 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +415 -0
- ate/recording/upload.py +304 -0
- ate/recording/visual.py +416 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +221 -0
- ate/robot/agentic_servo.py +856 -0
- ate/robot/behaviors.py +493 -0
- ate/robot/ble_capture.py +1000 -0
- ate/robot/ble_enumerate.py +506 -0
- ate/robot/calibration.py +668 -0
- ate/robot/calibration_state.py +388 -0
- ate/robot/commands.py +3735 -0
- ate/robot/direction_calibration.py +554 -0
- ate/robot/discovery.py +441 -0
- ate/robot/introspection.py +330 -0
- ate/robot/llm_system_id.py +654 -0
- ate/robot/locomotion_calibration.py +508 -0
- ate/robot/manager.py +270 -0
- ate/robot/marker_generator.py +611 -0
- ate/robot/perception.py +502 -0
- ate/robot/primitives.py +614 -0
- ate/robot/profiles.py +281 -0
- ate/robot/registry.py +322 -0
- ate/robot/servo_mapper.py +1153 -0
- ate/robot/skill_upload.py +675 -0
- ate/robot/target_calibration.py +500 -0
- ate/robot/teach.py +515 -0
- ate/robot/types.py +242 -0
- ate/robot/visual_labeler.py +1048 -0
- ate/robot/visual_servo_loop.py +494 -0
- ate/robot/visual_servoing.py +570 -0
- ate/robot/visual_system_id.py +906 -0
- ate/transports/__init__.py +121 -0
- ate/transports/base.py +394 -0
- ate/transports/ble.py +405 -0
- ate/transports/hybrid.py +444 -0
- ate/transports/serial.py +345 -0
- ate/urdf/__init__.py +30 -0
- ate/urdf/capture.py +582 -0
- ate/urdf/cloud.py +491 -0
- ate/urdf/collision.py +271 -0
- ate/urdf/commands.py +708 -0
- ate/urdf/depth.py +360 -0
- ate/urdf/inertial.py +312 -0
- ate/urdf/kinematics.py +330 -0
- ate/urdf/lifting.py +415 -0
- ate/urdf/meshing.py +300 -0
- ate/urdf/models/__init__.py +110 -0
- ate/urdf/models/depth_anything.py +253 -0
- ate/urdf/models/sam2.py +324 -0
- ate/urdf/motion_analysis.py +396 -0
- ate/urdf/pipeline.py +468 -0
- ate/urdf/scale.py +256 -0
- ate/urdf/scan_session.py +411 -0
- ate/urdf/segmentation.py +299 -0
- ate/urdf/synthesis.py +319 -0
- ate/urdf/topology.py +336 -0
- ate/urdf/validation.py +371 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
- foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
- foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
ate/robot/profiles.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Robot profiles - saved configurations for easy robot setup.
|
|
3
|
+
|
|
4
|
+
Profiles are stored in ~/.ate/robots/ as JSON files.
|
|
5
|
+
Users can create, edit, and share profiles.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import dataclass, field, asdict
|
|
11
|
+
from typing import Dict, List, Optional, Any
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Default profile directory
|
|
16
|
+
def get_profiles_dir() -> Path:
|
|
17
|
+
"""Get the profiles directory, creating if needed."""
|
|
18
|
+
ate_dir = Path.home() / ".ate"
|
|
19
|
+
profiles_dir = ate_dir / "robots"
|
|
20
|
+
profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
return profiles_dir
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RobotProfile:
|
|
26
|
+
"""
|
|
27
|
+
A saved robot configuration.
|
|
28
|
+
|
|
29
|
+
Contains everything needed to connect to and use a robot.
|
|
30
|
+
"""
|
|
31
|
+
# Identity
|
|
32
|
+
name: str # User-chosen name for this robot
|
|
33
|
+
robot_type: str # ID of robot type from registry
|
|
34
|
+
|
|
35
|
+
# Serial connection
|
|
36
|
+
serial_port: Optional[str] = None
|
|
37
|
+
baud_rate: int = 115200
|
|
38
|
+
|
|
39
|
+
# Network connection
|
|
40
|
+
ip_address: Optional[str] = None
|
|
41
|
+
camera_ip: Optional[str] = None
|
|
42
|
+
camera_port: int = 80
|
|
43
|
+
camera_stream_port: int = 81
|
|
44
|
+
|
|
45
|
+
# BLE connection
|
|
46
|
+
use_ble: bool = False # Use BLE instead of serial
|
|
47
|
+
ble_address: Optional[str] = None # BLE device address
|
|
48
|
+
ble_service_uuid: str = "0000ffe0-0000-1000-8000-00805f9b34fb" # ESP32/HM-10 default
|
|
49
|
+
ble_char_uuid: str = "0000ffe1-0000-1000-8000-00805f9b34fb" # RX/TX characteristic
|
|
50
|
+
|
|
51
|
+
# Robot-specific config
|
|
52
|
+
has_arm: bool = False
|
|
53
|
+
has_camera: bool = False
|
|
54
|
+
has_gripper: bool = False
|
|
55
|
+
|
|
56
|
+
# Custom settings
|
|
57
|
+
settings: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
# Metadata
|
|
60
|
+
description: str = ""
|
|
61
|
+
created_at: Optional[str] = None
|
|
62
|
+
updated_at: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
65
|
+
"""Convert to dictionary for YAML serialization."""
|
|
66
|
+
return asdict(self)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RobotProfile":
|
|
70
|
+
"""Create from dictionary."""
|
|
71
|
+
return cls(**data)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load_profile(name: str) -> Optional[RobotProfile]:
|
|
75
|
+
"""
|
|
76
|
+
Load a robot profile by name.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: Profile name (without .json extension)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
RobotProfile or None if not found
|
|
83
|
+
"""
|
|
84
|
+
profiles_dir = get_profiles_dir()
|
|
85
|
+
profile_path = profiles_dir / f"{name}.json"
|
|
86
|
+
|
|
87
|
+
if not profile_path.exists():
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
with open(profile_path, "r") as f:
|
|
92
|
+
data = json.load(f)
|
|
93
|
+
return RobotProfile.from_dict(data)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
print(f"Error loading profile: {e}")
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def save_profile(profile: RobotProfile) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Save a robot profile.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
profile: Profile to save
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if saved successfully
|
|
108
|
+
"""
|
|
109
|
+
from datetime import datetime
|
|
110
|
+
|
|
111
|
+
profiles_dir = get_profiles_dir()
|
|
112
|
+
profile_path = profiles_dir / f"{profile.name}.json"
|
|
113
|
+
|
|
114
|
+
# Update timestamps
|
|
115
|
+
now = datetime.now().isoformat()
|
|
116
|
+
if profile.created_at is None:
|
|
117
|
+
profile.created_at = now
|
|
118
|
+
profile.updated_at = now
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
with open(profile_path, "w") as f:
|
|
122
|
+
json.dump(profile.to_dict(), f, indent=2)
|
|
123
|
+
return True
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Error saving profile: {e}")
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def list_profiles() -> List[RobotProfile]:
|
|
130
|
+
"""
|
|
131
|
+
List all saved robot profiles.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of profiles
|
|
135
|
+
"""
|
|
136
|
+
profiles_dir = get_profiles_dir()
|
|
137
|
+
profiles = []
|
|
138
|
+
|
|
139
|
+
for file in profiles_dir.glob("*.json"):
|
|
140
|
+
try:
|
|
141
|
+
with open(file, "r") as f:
|
|
142
|
+
data = json.load(f)
|
|
143
|
+
profiles.append(RobotProfile.from_dict(data))
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
return profiles
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def delete_profile(name: str) -> bool:
|
|
151
|
+
"""
|
|
152
|
+
Delete a robot profile.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name: Profile name
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
True if deleted
|
|
159
|
+
"""
|
|
160
|
+
profiles_dir = get_profiles_dir()
|
|
161
|
+
profile_path = profiles_dir / f"{name}.json"
|
|
162
|
+
|
|
163
|
+
if profile_path.exists():
|
|
164
|
+
profile_path.unlink()
|
|
165
|
+
return True
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def profile_exists(name: str) -> bool:
|
|
170
|
+
"""Check if a profile exists."""
|
|
171
|
+
profiles_dir = get_profiles_dir()
|
|
172
|
+
return (profiles_dir / f"{name}.json").exists()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_default_profile() -> Optional[RobotProfile]:
|
|
176
|
+
"""
|
|
177
|
+
Get the default profile (if set).
|
|
178
|
+
|
|
179
|
+
The default profile is stored as 'default.yaml' or
|
|
180
|
+
a profile named 'default'.
|
|
181
|
+
"""
|
|
182
|
+
profiles_dir = get_profiles_dir()
|
|
183
|
+
|
|
184
|
+
# Check for default marker
|
|
185
|
+
default_marker = profiles_dir / ".default"
|
|
186
|
+
if default_marker.exists():
|
|
187
|
+
with open(default_marker, "r") as f:
|
|
188
|
+
default_name = f.read().strip()
|
|
189
|
+
return load_profile(default_name)
|
|
190
|
+
|
|
191
|
+
# Fall back to profile named 'default'
|
|
192
|
+
return load_profile("default")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def set_default_profile(name: str) -> bool:
|
|
196
|
+
"""
|
|
197
|
+
Set a profile as the default.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
name: Profile name
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
True if set successfully
|
|
204
|
+
"""
|
|
205
|
+
if not profile_exists(name):
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
profiles_dir = get_profiles_dir()
|
|
209
|
+
default_marker = profiles_dir / ".default"
|
|
210
|
+
|
|
211
|
+
with open(default_marker, "w") as f:
|
|
212
|
+
f.write(name)
|
|
213
|
+
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def create_profile_from_discovery(
|
|
218
|
+
name: str,
|
|
219
|
+
serial_port: Optional[str] = None,
|
|
220
|
+
camera_ip: Optional[str] = None,
|
|
221
|
+
robot_type: str = "hiwonder_mechdog",
|
|
222
|
+
) -> RobotProfile:
|
|
223
|
+
"""
|
|
224
|
+
Create a profile from discovered devices.
|
|
225
|
+
|
|
226
|
+
Helper for the setup wizard.
|
|
227
|
+
"""
|
|
228
|
+
profile = RobotProfile(
|
|
229
|
+
name=name,
|
|
230
|
+
robot_type=robot_type,
|
|
231
|
+
serial_port=serial_port,
|
|
232
|
+
camera_ip=camera_ip,
|
|
233
|
+
has_camera=camera_ip is not None,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return profile
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Built-in profile templates
|
|
240
|
+
PROFILE_TEMPLATES: Dict[str, RobotProfile] = {
|
|
241
|
+
"mechdog_basic": RobotProfile(
|
|
242
|
+
name="mechdog_basic",
|
|
243
|
+
robot_type="hiwonder_mechdog",
|
|
244
|
+
description="Basic MechDog setup without camera or arm",
|
|
245
|
+
serial_port="/dev/cu.usbserial-10", # Common macOS port
|
|
246
|
+
has_camera=False,
|
|
247
|
+
has_arm=False,
|
|
248
|
+
),
|
|
249
|
+
"mechdog_camera": RobotProfile(
|
|
250
|
+
name="mechdog_camera",
|
|
251
|
+
robot_type="hiwonder_mechdog",
|
|
252
|
+
description="MechDog with visual module (camera)",
|
|
253
|
+
serial_port="/dev/cu.usbserial-10",
|
|
254
|
+
has_camera=True,
|
|
255
|
+
camera_ip="192.168.1.100",
|
|
256
|
+
),
|
|
257
|
+
"mechdog_full": RobotProfile(
|
|
258
|
+
name="mechdog_full",
|
|
259
|
+
robot_type="hiwonder_mechdog",
|
|
260
|
+
description="Full MechDog setup with arm and camera",
|
|
261
|
+
serial_port="/dev/cu.usbserial-10",
|
|
262
|
+
has_camera=True,
|
|
263
|
+
has_arm=True,
|
|
264
|
+
camera_ip="192.168.1.100",
|
|
265
|
+
),
|
|
266
|
+
"simulation": RobotProfile(
|
|
267
|
+
name="simulation",
|
|
268
|
+
robot_type="simulation",
|
|
269
|
+
description="Simulated robot for testing without hardware",
|
|
270
|
+
),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_template(template_name: str) -> Optional[RobotProfile]:
|
|
275
|
+
"""Get a profile template."""
|
|
276
|
+
return PROFILE_TEMPLATES.get(template_name)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def list_templates() -> List[str]:
|
|
280
|
+
"""List available profile templates."""
|
|
281
|
+
return list(PROFILE_TEMPLATES.keys())
|
ate/robot/registry.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Registry of known robot types and their configurations.
|
|
3
|
+
|
|
4
|
+
This is how we know what robots are supported and how to configure them.
|
|
5
|
+
Community contributions can add new robots here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Dict, List, Set, Optional, Type, Any
|
|
10
|
+
from enum import Enum, auto
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConnectionType(Enum):
|
|
14
|
+
"""How to connect to the robot."""
|
|
15
|
+
SERIAL = auto() # USB serial (pyserial)
|
|
16
|
+
WIFI = auto() # WiFi/HTTP
|
|
17
|
+
ROS2 = auto() # ROS2 topics/services
|
|
18
|
+
BLUETOOTH = auto() # Bluetooth serial
|
|
19
|
+
ETHERNET = auto() # Direct ethernet
|
|
20
|
+
SIMULATION = auto() # No hardware, simulated
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class RobotType:
|
|
25
|
+
"""Definition of a known robot type."""
|
|
26
|
+
id: str # Unique identifier
|
|
27
|
+
name: str # Display name
|
|
28
|
+
manufacturer: str # Who makes it
|
|
29
|
+
archetype: str # quadruped, humanoid, arm, etc.
|
|
30
|
+
description: str # Human description
|
|
31
|
+
|
|
32
|
+
# Connection
|
|
33
|
+
connection_types: Set[ConnectionType] = field(default_factory=set)
|
|
34
|
+
default_connection: Optional[ConnectionType] = None
|
|
35
|
+
|
|
36
|
+
# Serial settings
|
|
37
|
+
serial_patterns: List[str] = field(default_factory=list) # USB patterns to match
|
|
38
|
+
baud_rate: int = 115200
|
|
39
|
+
|
|
40
|
+
# Network settings
|
|
41
|
+
default_ports: Dict[str, int] = field(default_factory=dict) # camera_port, stream_port, etc.
|
|
42
|
+
mdns_service: Optional[str] = None # mDNS service type to discover
|
|
43
|
+
|
|
44
|
+
# Capabilities
|
|
45
|
+
capabilities: Set[str] = field(default_factory=set)
|
|
46
|
+
optional_capabilities: Set[str] = field(default_factory=set)
|
|
47
|
+
|
|
48
|
+
# Driver info
|
|
49
|
+
driver_module: str = "" # Python module path
|
|
50
|
+
driver_class: str = "" # Class name
|
|
51
|
+
config_class: str = "" # Config class name
|
|
52
|
+
|
|
53
|
+
# Documentation
|
|
54
|
+
setup_url: Optional[str] = None
|
|
55
|
+
image_url: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Registry of known robot types
|
|
59
|
+
KNOWN_ROBOTS: Dict[str, RobotType] = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def register_robot(robot: RobotType) -> None:
|
|
63
|
+
"""Register a robot type."""
|
|
64
|
+
KNOWN_ROBOTS[robot.id] = robot
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_robot_info(robot_id: str) -> Optional[RobotType]:
|
|
68
|
+
"""Get information about a robot type."""
|
|
69
|
+
return KNOWN_ROBOTS.get(robot_id)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def list_robot_types() -> List[RobotType]:
|
|
73
|
+
"""List all known robot types."""
|
|
74
|
+
return list(KNOWN_ROBOTS.values())
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def find_by_archetype(archetype: str) -> List[RobotType]:
|
|
78
|
+
"""Find robots by archetype (quadruped, humanoid, etc.)."""
|
|
79
|
+
return [r for r in KNOWN_ROBOTS.values() if r.archetype == archetype]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# =============================================================================
|
|
83
|
+
# Built-in robot definitions
|
|
84
|
+
# =============================================================================
|
|
85
|
+
|
|
86
|
+
# HiWonder MechDog
|
|
87
|
+
register_robot(RobotType(
|
|
88
|
+
id="hiwonder_mechdog",
|
|
89
|
+
name="MechDog",
|
|
90
|
+
manufacturer="HiWonder",
|
|
91
|
+
archetype="quadruped",
|
|
92
|
+
description="12 DOF quadruped robot with optional arm and camera. ESP32-based with MicroPython. Supports USB, WiFi, and Bluetooth LE.",
|
|
93
|
+
|
|
94
|
+
connection_types={ConnectionType.SERIAL, ConnectionType.WIFI, ConnectionType.BLUETOOTH},
|
|
95
|
+
default_connection=ConnectionType.SERIAL,
|
|
96
|
+
|
|
97
|
+
serial_patterns=[
|
|
98
|
+
"/dev/cu.usbserial-*", # macOS
|
|
99
|
+
"/dev/ttyUSB*", # Linux
|
|
100
|
+
"COM*", # Windows
|
|
101
|
+
],
|
|
102
|
+
baud_rate=115200,
|
|
103
|
+
|
|
104
|
+
default_ports={
|
|
105
|
+
"camera_port": 80,
|
|
106
|
+
"camera_stream_port": 81,
|
|
107
|
+
# BLE configuration - ESP32 default BLE serial service
|
|
108
|
+
"ble_service_uuid": "0000ffe0-0000-1000-8000-00805f9b34fb", # HM-10 compatible
|
|
109
|
+
"ble_char_uuid": "0000ffe1-0000-1000-8000-00805f9b34fb", # RX/TX characteristic
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
capabilities={
|
|
113
|
+
"quadruped_locomotion",
|
|
114
|
+
"body_pose",
|
|
115
|
+
"imu",
|
|
116
|
+
},
|
|
117
|
+
optional_capabilities={
|
|
118
|
+
"camera",
|
|
119
|
+
"arm",
|
|
120
|
+
"gripper",
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
driver_module="ate.drivers.mechdog",
|
|
124
|
+
driver_class="MechDogDriver",
|
|
125
|
+
config_class="MechDogConfig",
|
|
126
|
+
|
|
127
|
+
setup_url="https://docs.kindly.fyi/robots/mechdog",
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Unitree Go1
|
|
132
|
+
register_robot(RobotType(
|
|
133
|
+
id="unitree_go1",
|
|
134
|
+
name="Go1",
|
|
135
|
+
manufacturer="Unitree",
|
|
136
|
+
archetype="quadruped",
|
|
137
|
+
description="High-performance quadruped robot with cameras and optional arm.",
|
|
138
|
+
|
|
139
|
+
connection_types={ConnectionType.WIFI, ConnectionType.ETHERNET},
|
|
140
|
+
default_connection=ConnectionType.WIFI,
|
|
141
|
+
|
|
142
|
+
default_ports={
|
|
143
|
+
"control_port": 8082,
|
|
144
|
+
"camera_port": 8080,
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
capabilities={
|
|
148
|
+
"quadruped_locomotion",
|
|
149
|
+
"body_pose",
|
|
150
|
+
"imu",
|
|
151
|
+
"camera",
|
|
152
|
+
"depth_camera",
|
|
153
|
+
},
|
|
154
|
+
optional_capabilities={
|
|
155
|
+
"arm",
|
|
156
|
+
"gripper",
|
|
157
|
+
"lidar",
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
driver_module="ate.drivers.go1",
|
|
161
|
+
driver_class="Go1Driver",
|
|
162
|
+
config_class="Go1Config",
|
|
163
|
+
))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Unitree Go2
|
|
167
|
+
register_robot(RobotType(
|
|
168
|
+
id="unitree_go2",
|
|
169
|
+
name="Go2",
|
|
170
|
+
manufacturer="Unitree",
|
|
171
|
+
archetype="quadruped",
|
|
172
|
+
description="Advanced quadruped with AI capabilities, LiDAR, and ROS2 support.",
|
|
173
|
+
|
|
174
|
+
connection_types={ConnectionType.ROS2, ConnectionType.WIFI},
|
|
175
|
+
default_connection=ConnectionType.ROS2,
|
|
176
|
+
|
|
177
|
+
capabilities={
|
|
178
|
+
"quadruped_locomotion",
|
|
179
|
+
"body_pose",
|
|
180
|
+
"imu",
|
|
181
|
+
"camera",
|
|
182
|
+
"depth_camera",
|
|
183
|
+
"lidar",
|
|
184
|
+
},
|
|
185
|
+
optional_capabilities={
|
|
186
|
+
"arm",
|
|
187
|
+
"gripper",
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
driver_module="ate.drivers.go2",
|
|
191
|
+
driver_class="Go2Driver",
|
|
192
|
+
config_class="Go2Config",
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# Boston Dynamics Spot
|
|
197
|
+
register_robot(RobotType(
|
|
198
|
+
id="boston_dynamics_spot",
|
|
199
|
+
name="Spot",
|
|
200
|
+
manufacturer="Boston Dynamics",
|
|
201
|
+
archetype="quadruped",
|
|
202
|
+
description="Industrial quadruped robot with arm, cameras, and high autonomy.",
|
|
203
|
+
|
|
204
|
+
connection_types={ConnectionType.WIFI, ConnectionType.ETHERNET},
|
|
205
|
+
default_connection=ConnectionType.WIFI,
|
|
206
|
+
|
|
207
|
+
capabilities={
|
|
208
|
+
"quadruped_locomotion",
|
|
209
|
+
"body_pose",
|
|
210
|
+
"imu",
|
|
211
|
+
"camera",
|
|
212
|
+
"depth_camera",
|
|
213
|
+
},
|
|
214
|
+
optional_capabilities={
|
|
215
|
+
"arm",
|
|
216
|
+
"gripper",
|
|
217
|
+
"lidar",
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
driver_module="ate.drivers.spot",
|
|
221
|
+
driver_class="SpotDriver",
|
|
222
|
+
config_class="SpotConfig",
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# Generic ROS2 robot
|
|
227
|
+
register_robot(RobotType(
|
|
228
|
+
id="ros2_generic",
|
|
229
|
+
name="Generic ROS2 Robot",
|
|
230
|
+
manufacturer="Various",
|
|
231
|
+
archetype="custom",
|
|
232
|
+
description="Any robot accessible via ROS2 topics and services.",
|
|
233
|
+
|
|
234
|
+
connection_types={ConnectionType.ROS2},
|
|
235
|
+
default_connection=ConnectionType.ROS2,
|
|
236
|
+
|
|
237
|
+
capabilities=set(), # Discovered from ROS2 topics
|
|
238
|
+
optional_capabilities={
|
|
239
|
+
"quadruped_locomotion",
|
|
240
|
+
"bipedal_locomotion",
|
|
241
|
+
"wheeled_locomotion",
|
|
242
|
+
"arm",
|
|
243
|
+
"gripper",
|
|
244
|
+
"camera",
|
|
245
|
+
"depth_camera",
|
|
246
|
+
"lidar",
|
|
247
|
+
"imu",
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
driver_module="ate.drivers.ros2_bridge",
|
|
251
|
+
driver_class="ROS2Bridge",
|
|
252
|
+
config_class="ROS2Config",
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# Simulation robot
|
|
257
|
+
register_robot(RobotType(
|
|
258
|
+
id="simulation",
|
|
259
|
+
name="Simulated Robot",
|
|
260
|
+
manufacturer="FoodforThought",
|
|
261
|
+
archetype="custom",
|
|
262
|
+
description="Software simulation for testing without hardware.",
|
|
263
|
+
|
|
264
|
+
connection_types={ConnectionType.SIMULATION},
|
|
265
|
+
default_connection=ConnectionType.SIMULATION,
|
|
266
|
+
|
|
267
|
+
capabilities={
|
|
268
|
+
"quadruped_locomotion",
|
|
269
|
+
"body_pose",
|
|
270
|
+
"camera",
|
|
271
|
+
},
|
|
272
|
+
optional_capabilities={
|
|
273
|
+
"arm",
|
|
274
|
+
"gripper",
|
|
275
|
+
"lidar",
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
driver_module="ate.drivers.simulation",
|
|
279
|
+
driver_class="SimulationDriver",
|
|
280
|
+
config_class="SimulationConfig",
|
|
281
|
+
))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class RobotRegistry:
|
|
285
|
+
"""
|
|
286
|
+
Registry interface for robot types.
|
|
287
|
+
|
|
288
|
+
Provides methods to find and filter known robots.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def list_all() -> List[RobotType]:
|
|
293
|
+
"""List all known robot types."""
|
|
294
|
+
return list(KNOWN_ROBOTS.values())
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def get(robot_id: str) -> Optional[RobotType]:
|
|
298
|
+
"""Get robot type by ID."""
|
|
299
|
+
return KNOWN_ROBOTS.get(robot_id)
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def find_by_archetype(archetype: str) -> List[RobotType]:
|
|
303
|
+
"""Find robots by archetype."""
|
|
304
|
+
return [r for r in KNOWN_ROBOTS.values() if r.archetype == archetype]
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def find_by_capability(capability: str) -> List[RobotType]:
|
|
308
|
+
"""Find robots that have a specific capability."""
|
|
309
|
+
return [
|
|
310
|
+
r for r in KNOWN_ROBOTS.values()
|
|
311
|
+
if capability in r.capabilities or capability in r.optional_capabilities
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def find_by_connection(conn_type: ConnectionType) -> List[RobotType]:
|
|
316
|
+
"""Find robots that support a connection type."""
|
|
317
|
+
return [r for r in KNOWN_ROBOTS.values() if conn_type in r.connection_types]
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def register(robot: RobotType) -> None:
|
|
321
|
+
"""Register a new robot type."""
|
|
322
|
+
KNOWN_ROBOTS[robot.id] = robot
|