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.
Files changed (131) hide show
  1. ate/__init__.py +6 -0
  2. ate/__main__.py +16 -0
  3. ate/auth/__init__.py +1 -0
  4. ate/auth/device_flow.py +141 -0
  5. ate/auth/token_store.py +96 -0
  6. ate/behaviors/__init__.py +100 -0
  7. ate/behaviors/approach.py +399 -0
  8. ate/behaviors/common.py +686 -0
  9. ate/behaviors/tree.py +454 -0
  10. ate/cli.py +855 -3995
  11. ate/client.py +90 -0
  12. ate/commands/__init__.py +168 -0
  13. ate/commands/auth.py +389 -0
  14. ate/commands/bridge.py +448 -0
  15. ate/commands/data.py +185 -0
  16. ate/commands/deps.py +111 -0
  17. ate/commands/generate.py +384 -0
  18. ate/commands/memory.py +907 -0
  19. ate/commands/parts.py +166 -0
  20. ate/commands/primitive.py +399 -0
  21. ate/commands/protocol.py +288 -0
  22. ate/commands/recording.py +524 -0
  23. ate/commands/repo.py +154 -0
  24. ate/commands/simulation.py +291 -0
  25. ate/commands/skill.py +303 -0
  26. ate/commands/skills.py +487 -0
  27. ate/commands/team.py +147 -0
  28. ate/commands/workflow.py +271 -0
  29. ate/detection/__init__.py +38 -0
  30. ate/detection/base.py +142 -0
  31. ate/detection/color_detector.py +399 -0
  32. ate/detection/trash_detector.py +322 -0
  33. ate/drivers/__init__.py +39 -0
  34. ate/drivers/ble_transport.py +405 -0
  35. ate/drivers/mechdog.py +942 -0
  36. ate/drivers/wifi_camera.py +477 -0
  37. ate/interfaces/__init__.py +187 -0
  38. ate/interfaces/base.py +273 -0
  39. ate/interfaces/body.py +267 -0
  40. ate/interfaces/detection.py +282 -0
  41. ate/interfaces/locomotion.py +422 -0
  42. ate/interfaces/manipulation.py +408 -0
  43. ate/interfaces/navigation.py +389 -0
  44. ate/interfaces/perception.py +362 -0
  45. ate/interfaces/sensors.py +247 -0
  46. ate/interfaces/types.py +371 -0
  47. ate/llm_proxy.py +239 -0
  48. ate/mcp_server.py +387 -0
  49. ate/memory/__init__.py +35 -0
  50. ate/memory/cloud.py +244 -0
  51. ate/memory/context.py +269 -0
  52. ate/memory/embeddings.py +184 -0
  53. ate/memory/export.py +26 -0
  54. ate/memory/merge.py +146 -0
  55. ate/memory/migrate/__init__.py +34 -0
  56. ate/memory/migrate/base.py +89 -0
  57. ate/memory/migrate/pipeline.py +189 -0
  58. ate/memory/migrate/sources/__init__.py +13 -0
  59. ate/memory/migrate/sources/chroma.py +170 -0
  60. ate/memory/migrate/sources/pinecone.py +120 -0
  61. ate/memory/migrate/sources/qdrant.py +110 -0
  62. ate/memory/migrate/sources/weaviate.py +160 -0
  63. ate/memory/reranker.py +353 -0
  64. ate/memory/search.py +26 -0
  65. ate/memory/store.py +548 -0
  66. ate/recording/__init__.py +83 -0
  67. ate/recording/demonstration.py +378 -0
  68. ate/recording/session.py +415 -0
  69. ate/recording/upload.py +304 -0
  70. ate/recording/visual.py +416 -0
  71. ate/recording/wrapper.py +95 -0
  72. ate/robot/__init__.py +221 -0
  73. ate/robot/agentic_servo.py +856 -0
  74. ate/robot/behaviors.py +493 -0
  75. ate/robot/ble_capture.py +1000 -0
  76. ate/robot/ble_enumerate.py +506 -0
  77. ate/robot/calibration.py +668 -0
  78. ate/robot/calibration_state.py +388 -0
  79. ate/robot/commands.py +3735 -0
  80. ate/robot/direction_calibration.py +554 -0
  81. ate/robot/discovery.py +441 -0
  82. ate/robot/introspection.py +330 -0
  83. ate/robot/llm_system_id.py +654 -0
  84. ate/robot/locomotion_calibration.py +508 -0
  85. ate/robot/manager.py +270 -0
  86. ate/robot/marker_generator.py +611 -0
  87. ate/robot/perception.py +502 -0
  88. ate/robot/primitives.py +614 -0
  89. ate/robot/profiles.py +281 -0
  90. ate/robot/registry.py +322 -0
  91. ate/robot/servo_mapper.py +1153 -0
  92. ate/robot/skill_upload.py +675 -0
  93. ate/robot/target_calibration.py +500 -0
  94. ate/robot/teach.py +515 -0
  95. ate/robot/types.py +242 -0
  96. ate/robot/visual_labeler.py +1048 -0
  97. ate/robot/visual_servo_loop.py +494 -0
  98. ate/robot/visual_servoing.py +570 -0
  99. ate/robot/visual_system_id.py +906 -0
  100. ate/transports/__init__.py +121 -0
  101. ate/transports/base.py +394 -0
  102. ate/transports/ble.py +405 -0
  103. ate/transports/hybrid.py +444 -0
  104. ate/transports/serial.py +345 -0
  105. ate/urdf/__init__.py +30 -0
  106. ate/urdf/capture.py +582 -0
  107. ate/urdf/cloud.py +491 -0
  108. ate/urdf/collision.py +271 -0
  109. ate/urdf/commands.py +708 -0
  110. ate/urdf/depth.py +360 -0
  111. ate/urdf/inertial.py +312 -0
  112. ate/urdf/kinematics.py +330 -0
  113. ate/urdf/lifting.py +415 -0
  114. ate/urdf/meshing.py +300 -0
  115. ate/urdf/models/__init__.py +110 -0
  116. ate/urdf/models/depth_anything.py +253 -0
  117. ate/urdf/models/sam2.py +324 -0
  118. ate/urdf/motion_analysis.py +396 -0
  119. ate/urdf/pipeline.py +468 -0
  120. ate/urdf/scale.py +256 -0
  121. ate/urdf/scan_session.py +411 -0
  122. ate/urdf/segmentation.py +299 -0
  123. ate/urdf/synthesis.py +319 -0
  124. ate/urdf/topology.py +336 -0
  125. ate/urdf/validation.py +371 -0
  126. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
  127. foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
  128. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
  129. foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
  130. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
  131. {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