devagent-physical-engine 0.10.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.
- devagent_physical_engine/__init__.py +44 -0
- devagent_physical_engine/agent/__init__.py +40 -0
- devagent_physical_engine/agent/compiler.py +285 -0
- devagent_physical_engine/agent/contracts.py +129 -0
- devagent_physical_engine/agent/coordinator.py +108 -0
- devagent_physical_engine/agent/critic.py +72 -0
- devagent_physical_engine/agent/evidence.py +34 -0
- devagent_physical_engine/agent/interpreter.py +179 -0
- devagent_physical_engine/agent/planner.py +105 -0
- devagent_physical_engine/agent/recovery.py +54 -0
- devagent_physical_engine/agent/routing.py +76 -0
- devagent_physical_engine/agent/runtime.py +270 -0
- devagent_physical_engine/agent/semantic.py +304 -0
- devagent_physical_engine/agent/structured.py +423 -0
- devagent_physical_engine/ai_cli.py +226 -0
- devagent_physical_engine/cli.py +392 -0
- devagent_physical_engine/doctor.py +20 -0
- devagent_physical_engine/engineering_agent.py +243 -0
- devagent_physical_engine/engineering_request.py +630 -0
- devagent_physical_engine/execution.py +90 -0
- devagent_physical_engine/models.py +143 -0
- devagent_physical_engine/operating_envelope.py +120 -0
- devagent_physical_engine/optimization/__init__.py +50 -0
- devagent_physical_engine/optimization/benchmark.py +122 -0
- devagent_physical_engine/optimization/candidates.py +198 -0
- devagent_physical_engine/optimization/contracts.py +235 -0
- devagent_physical_engine/optimization/evaluator.py +107 -0
- devagent_physical_engine/optimization/evidence.py +53 -0
- devagent_physical_engine/optimization/experience.py +105 -0
- devagent_physical_engine/optimization/measured.py +125 -0
- devagent_physical_engine/optimization/optimizer.py +215 -0
- devagent_physical_engine/optimization/orchestrator.py +155 -0
- devagent_physical_engine/physical_campaign.py +413 -0
- devagent_physical_engine/physical_evidence.py +214 -0
- devagent_physical_engine/physical_motion.py +196 -0
- devagent_physical_engine/planning.py +80 -0
- devagent_physical_engine/preexecution_contract.py +65 -0
- devagent_physical_engine/provider_adapters/__init__.py +22 -0
- devagent_physical_engine/provider_adapters/anthropic.py +112 -0
- devagent_physical_engine/provider_adapters/common.py +187 -0
- devagent_physical_engine/provider_adapters/factory.py +20 -0
- devagent_physical_engine/provider_adapters/gemini.py +126 -0
- devagent_physical_engine/provider_adapters/openai.py +95 -0
- devagent_physical_engine/provider_qualification.py +268 -0
- devagent_physical_engine/providers.py +94 -0
- devagent_physical_engine/qualification.py +44 -0
- devagent_physical_engine/qualification_cli.py +195 -0
- devagent_physical_engine/qualification_harness.py +917 -0
- devagent_physical_engine/robot_platform.py +411 -0
- devagent_physical_engine/robots.py +76 -0
- devagent_physical_engine/ros2/__init__.py +35 -0
- devagent_physical_engine/ros2/acceptance.py +324 -0
- devagent_physical_engine/ros2/commands.py +175 -0
- devagent_physical_engine/ros2/doctor.py +116 -0
- devagent_physical_engine/ros2/fk_probe.py +83 -0
- devagent_physical_engine/ros2/frame_alignment.py +61 -0
- devagent_physical_engine/ros2/gazebo_world.py +125 -0
- devagent_physical_engine/ros2/joint_state_recorder.py +64 -0
- devagent_physical_engine/ros2/measured_motion.py +233 -0
- devagent_physical_engine/ros2/moveit_scene.py +121 -0
- devagent_physical_engine/ros2/preexecution.py +113 -0
- devagent_physical_engine/ros2/qualification.py +81 -0
- devagent_physical_engine/ros2/qualification_v10.py +252 -0
- devagent_physical_engine/ros2/scene_probe.py +219 -0
- devagent_physical_engine/ros2/state_validity_probe.py +125 -0
- devagent_physical_engine/ros2/tf_probe.py +51 -0
- devagent_physical_engine/ros2/trajectory.py +188 -0
- devagent_physical_engine/ros2/ur5e.py +59 -0
- devagent_physical_engine/ros2/ur5e_adapter.py +349 -0
- devagent_physical_engine/ros2/ur5e_v10_adapter.py +292 -0
- devagent_physical_engine/setup_profile.py +356 -0
- devagent_physical_engine/simulation.py +32 -0
- devagent_physical_engine/simulation_platform.py +269 -0
- devagent_physical_engine/trajectory_qualification.py +201 -0
- devagent_physical_engine/twin.py +939 -0
- devagent_physical_engine/twin_builder.py +309 -0
- devagent_physical_engine/twin_materialization.py +404 -0
- devagent_physical_engine/verification.py +46 -0
- devagent_physical_engine-0.10.0.dist-info/METADATA +315 -0
- devagent_physical_engine-0.10.0.dist-info/RECORD +84 -0
- devagent_physical_engine-0.10.0.dist-info/WHEEL +5 -0
- devagent_physical_engine-0.10.0.dist-info/entry_points.txt +3 -0
- devagent_physical_engine-0.10.0.dist-info/licenses/NOTICE +2 -0
- devagent_physical_engine-0.10.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum, IntEnum
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import Any, Iterable, Mapping
|
|
7
|
+
|
|
8
|
+
from .models import Capability, Resource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RobotPlatformError(ValueError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QualificationState(str, Enum):
|
|
16
|
+
QUALIFIED = "qualified"
|
|
17
|
+
EXPERIMENTAL = "experimental"
|
|
18
|
+
NOT_QUALIFIED = "not_qualified"
|
|
19
|
+
NOT_SUPPORTED = "not_supported"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SimulationTier(str, Enum):
|
|
23
|
+
DETERMINISTIC = "deterministic"
|
|
24
|
+
KINEMATIC = "kinematic"
|
|
25
|
+
PHYSICS = "physics"
|
|
26
|
+
VISUAL = "visual"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RobotLifecycleLevel(IntEnum):
|
|
30
|
+
DISCOVERED = 0
|
|
31
|
+
SIM_READY = 1
|
|
32
|
+
SIM_QUALIFIED = 2
|
|
33
|
+
HARDWARE_VERIFIED = 3
|
|
34
|
+
SIM_REAL_CORRELATED = 4
|
|
35
|
+
SITE_QUALIFIED = 5
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class RosIntegration:
|
|
40
|
+
"""Robot-facing integration contract, not a claim that a package is installed."""
|
|
41
|
+
|
|
42
|
+
ros2_compatible: bool
|
|
43
|
+
joint_state_interface: str | None = None
|
|
44
|
+
trajectory_interface: str | None = None
|
|
45
|
+
robot_description: str | None = None
|
|
46
|
+
moveit_config: str | None = None
|
|
47
|
+
hardware_driver: str | None = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
for name in (
|
|
51
|
+
"joint_state_interface",
|
|
52
|
+
"trajectory_interface",
|
|
53
|
+
"robot_description",
|
|
54
|
+
"moveit_config",
|
|
55
|
+
"hardware_driver",
|
|
56
|
+
):
|
|
57
|
+
value = getattr(self, name)
|
|
58
|
+
if value is not None and not value.strip():
|
|
59
|
+
raise RobotPlatformError(f"empty_ros_integration_field:{name}")
|
|
60
|
+
if not self.ros2_compatible and any(
|
|
61
|
+
getattr(self, name) is not None
|
|
62
|
+
for name in (
|
|
63
|
+
"joint_state_interface",
|
|
64
|
+
"trajectory_interface",
|
|
65
|
+
"robot_description",
|
|
66
|
+
"moveit_config",
|
|
67
|
+
"hardware_driver",
|
|
68
|
+
)
|
|
69
|
+
):
|
|
70
|
+
raise RobotPlatformError("non_ros_profile_cannot_declare_ros_packages")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, slots=True)
|
|
74
|
+
class RobotQualification:
|
|
75
|
+
simulation: Mapping[SimulationTier, QualificationState]
|
|
76
|
+
hardware_shadow: QualificationState = QualificationState.NOT_QUALIFIED
|
|
77
|
+
sim_real_correlation: QualificationState = QualificationState.NOT_QUALIFIED
|
|
78
|
+
site_qualification: QualificationState = QualificationState.NOT_QUALIFIED
|
|
79
|
+
|
|
80
|
+
def __post_init__(self) -> None:
|
|
81
|
+
normalized = {
|
|
82
|
+
tier: self.simulation.get(tier, QualificationState.NOT_QUALIFIED)
|
|
83
|
+
for tier in SimulationTier
|
|
84
|
+
}
|
|
85
|
+
object.__setattr__(self, "simulation", MappingProxyType(normalized))
|
|
86
|
+
|
|
87
|
+
def state_for(self, tier: SimulationTier) -> QualificationState:
|
|
88
|
+
return self.simulation[tier]
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def highest_qualified_simulation_tier(self) -> SimulationTier | None:
|
|
92
|
+
order = (
|
|
93
|
+
SimulationTier.DETERMINISTIC,
|
|
94
|
+
SimulationTier.KINEMATIC,
|
|
95
|
+
SimulationTier.PHYSICS,
|
|
96
|
+
SimulationTier.VISUAL,
|
|
97
|
+
)
|
|
98
|
+
qualified = [
|
|
99
|
+
tier
|
|
100
|
+
for tier in order
|
|
101
|
+
if self.simulation[tier] is QualificationState.QUALIFIED
|
|
102
|
+
]
|
|
103
|
+
return qualified[-1] if qualified else None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True, slots=True)
|
|
107
|
+
class RobotLimits:
|
|
108
|
+
payload_kg: float | None = None
|
|
109
|
+
reach_m: float | None = None
|
|
110
|
+
|
|
111
|
+
def __post_init__(self) -> None:
|
|
112
|
+
if self.payload_kg is not None and self.payload_kg <= 0:
|
|
113
|
+
raise RobotPlatformError("payload_limit_invalid")
|
|
114
|
+
if self.reach_m is not None and self.reach_m <= 0:
|
|
115
|
+
raise RobotPlatformError("reach_limit_invalid")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True, slots=True)
|
|
119
|
+
class RobotProfile:
|
|
120
|
+
key: str
|
|
121
|
+
vendor: str
|
|
122
|
+
model: str
|
|
123
|
+
capabilities: frozenset[Capability]
|
|
124
|
+
qualification: RobotQualification
|
|
125
|
+
ros: RosIntegration
|
|
126
|
+
limits: RobotLimits = field(default_factory=RobotLimits)
|
|
127
|
+
aliases: tuple[str, ...] = ()
|
|
128
|
+
resource_type: str = "industrial_arm"
|
|
129
|
+
controller_family: str | None = None
|
|
130
|
+
legacy_qualification: str | None = None
|
|
131
|
+
notes: tuple[str, ...] = ()
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
key = self.key.strip().lower()
|
|
135
|
+
vendor = self.vendor.strip().lower()
|
|
136
|
+
model = self.model.strip().lower()
|
|
137
|
+
if not key or not vendor or not model:
|
|
138
|
+
raise RobotPlatformError("robot_profile_identity_required")
|
|
139
|
+
if not self.resource_type.strip():
|
|
140
|
+
raise RobotPlatformError("robot_resource_type_required")
|
|
141
|
+
if not self.capabilities:
|
|
142
|
+
raise RobotPlatformError("robot_capabilities_required")
|
|
143
|
+
if any(not alias.strip() for alias in self.aliases):
|
|
144
|
+
raise RobotPlatformError("empty_robot_alias")
|
|
145
|
+
if len(set(alias.strip().lower() for alias in self.aliases)) != len(self.aliases):
|
|
146
|
+
raise RobotPlatformError("duplicate_robot_alias")
|
|
147
|
+
if self.legacy_qualification is not None and not self.legacy_qualification.strip():
|
|
148
|
+
raise RobotPlatformError("empty_legacy_qualification")
|
|
149
|
+
if any(not note.strip() for note in self.notes):
|
|
150
|
+
raise RobotPlatformError("empty_robot_note")
|
|
151
|
+
object.__setattr__(self, "key", key)
|
|
152
|
+
object.__setattr__(self, "vendor", vendor)
|
|
153
|
+
object.__setattr__(self, "model", model)
|
|
154
|
+
object.__setattr__(
|
|
155
|
+
self,
|
|
156
|
+
"aliases",
|
|
157
|
+
tuple(alias.strip().lower() for alias in self.aliases),
|
|
158
|
+
)
|
|
159
|
+
if self.legacy_qualification is not None:
|
|
160
|
+
object.__setattr__(
|
|
161
|
+
self,
|
|
162
|
+
"legacy_qualification",
|
|
163
|
+
self.legacy_qualification.strip().lower(),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def lifecycle_level(self) -> RobotLifecycleLevel:
|
|
168
|
+
if self.qualification.site_qualification is QualificationState.QUALIFIED:
|
|
169
|
+
return RobotLifecycleLevel.SITE_QUALIFIED
|
|
170
|
+
if self.qualification.sim_real_correlation is QualificationState.QUALIFIED:
|
|
171
|
+
return RobotLifecycleLevel.SIM_REAL_CORRELATED
|
|
172
|
+
if self.qualification.hardware_shadow is QualificationState.QUALIFIED:
|
|
173
|
+
return RobotLifecycleLevel.HARDWARE_VERIFIED
|
|
174
|
+
|
|
175
|
+
physical_states = tuple(
|
|
176
|
+
self.qualification.state_for(tier)
|
|
177
|
+
for tier in (
|
|
178
|
+
SimulationTier.KINEMATIC,
|
|
179
|
+
SimulationTier.PHYSICS,
|
|
180
|
+
SimulationTier.VISUAL,
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
if any(state is QualificationState.QUALIFIED for state in physical_states):
|
|
184
|
+
return RobotLifecycleLevel.SIM_QUALIFIED
|
|
185
|
+
if any(state is QualificationState.EXPERIMENTAL for state in physical_states):
|
|
186
|
+
return RobotLifecycleLevel.SIM_READY
|
|
187
|
+
return RobotLifecycleLevel.DISCOVERED
|
|
188
|
+
|
|
189
|
+
def to_resource(self, resource_id: str = "robot_01") -> Resource:
|
|
190
|
+
if not resource_id.strip():
|
|
191
|
+
raise RobotPlatformError("resource_id_required")
|
|
192
|
+
highest = self.qualification.highest_qualified_simulation_tier
|
|
193
|
+
metadata: dict[str, Any] = {
|
|
194
|
+
"robot_profile": self.key,
|
|
195
|
+
"vendor": self.vendor,
|
|
196
|
+
"model": self.model,
|
|
197
|
+
"qualification": self.legacy_qualification
|
|
198
|
+
or (highest.value if highest is not None else "not_qualified"),
|
|
199
|
+
"simulation_qualification": {
|
|
200
|
+
tier.value: self.qualification.state_for(tier).value
|
|
201
|
+
for tier in SimulationTier
|
|
202
|
+
},
|
|
203
|
+
"hardware_shadow_qualification": self.qualification.hardware_shadow.value,
|
|
204
|
+
"sim_real_qualification": self.qualification.sim_real_correlation.value,
|
|
205
|
+
"site_qualification": self.qualification.site_qualification.value,
|
|
206
|
+
"lifecycle_level": int(self.lifecycle_level),
|
|
207
|
+
"lifecycle_name": self.lifecycle_level.name.lower(),
|
|
208
|
+
"real_execution_locked": True,
|
|
209
|
+
}
|
|
210
|
+
if self.controller_family:
|
|
211
|
+
metadata["controller_family"] = self.controller_family
|
|
212
|
+
return Resource(
|
|
213
|
+
resource_id,
|
|
214
|
+
self.resource_type,
|
|
215
|
+
self.capabilities,
|
|
216
|
+
metadata,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class RobotProfileRegistry:
|
|
221
|
+
"""Vendor-neutral profile registry with deterministic alias resolution."""
|
|
222
|
+
|
|
223
|
+
def __init__(self, profiles: Iterable[RobotProfile] = ()) -> None:
|
|
224
|
+
self._profiles: dict[str, RobotProfile] = {}
|
|
225
|
+
self._aliases: dict[str, str] = {}
|
|
226
|
+
for profile in profiles:
|
|
227
|
+
self.register(profile)
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def _normalize(value: str) -> str:
|
|
231
|
+
return "_".join(value.strip().lower().replace("-", " ").split())
|
|
232
|
+
|
|
233
|
+
def register(self, profile: RobotProfile, *, replace: bool = False) -> None:
|
|
234
|
+
key = self._normalize(profile.key)
|
|
235
|
+
aliases = {
|
|
236
|
+
self._normalize(profile.key),
|
|
237
|
+
self._normalize(f"{profile.vendor} {profile.model}"),
|
|
238
|
+
*(self._normalize(alias) for alias in profile.aliases),
|
|
239
|
+
}
|
|
240
|
+
if key in self._profiles and not replace:
|
|
241
|
+
raise RobotPlatformError(f"duplicate_robot_profile:{key}")
|
|
242
|
+
for alias in aliases:
|
|
243
|
+
owner = self._aliases.get(alias)
|
|
244
|
+
# replace=True may update the same profile, but it must never steal
|
|
245
|
+
# an alias owned by a different robot profile.
|
|
246
|
+
if owner is not None and owner != key:
|
|
247
|
+
raise RobotPlatformError(f"duplicate_robot_alias:{alias}")
|
|
248
|
+
|
|
249
|
+
if replace and key in self._profiles:
|
|
250
|
+
stale = [alias for alias, owner in self._aliases.items() if owner == key]
|
|
251
|
+
for alias in stale:
|
|
252
|
+
del self._aliases[alias]
|
|
253
|
+
|
|
254
|
+
self._profiles[key] = profile
|
|
255
|
+
for alias in aliases:
|
|
256
|
+
self._aliases[alias] = key
|
|
257
|
+
|
|
258
|
+
def resolve(self, name: str) -> RobotProfile:
|
|
259
|
+
normalized = self._normalize(name)
|
|
260
|
+
try:
|
|
261
|
+
key = self._aliases[normalized]
|
|
262
|
+
return self._profiles[key]
|
|
263
|
+
except KeyError as exc:
|
|
264
|
+
raise RobotPlatformError(f"unknown_robot_profile:{normalized}") from exc
|
|
265
|
+
|
|
266
|
+
def get(self, key: str) -> RobotProfile:
|
|
267
|
+
normalized = self._normalize(key)
|
|
268
|
+
try:
|
|
269
|
+
return self._profiles[normalized]
|
|
270
|
+
except KeyError as exc:
|
|
271
|
+
raise RobotPlatformError(f"unknown_robot_profile:{normalized}") from exc
|
|
272
|
+
|
|
273
|
+
def keys(self) -> tuple[str, ...]:
|
|
274
|
+
return tuple(sorted(self._profiles))
|
|
275
|
+
|
|
276
|
+
def all(self) -> tuple[RobotProfile, ...]:
|
|
277
|
+
return tuple(self._profiles[key] for key in self.keys())
|
|
278
|
+
|
|
279
|
+
def capability_matrix(self) -> tuple[dict[str, Any], ...]:
|
|
280
|
+
rows: list[dict[str, Any]] = []
|
|
281
|
+
for profile in self.all():
|
|
282
|
+
rows.append(
|
|
283
|
+
{
|
|
284
|
+
"robot": profile.key,
|
|
285
|
+
"vendor": profile.vendor,
|
|
286
|
+
"model": profile.model,
|
|
287
|
+
"ros2": profile.ros.ros2_compatible,
|
|
288
|
+
"lifecycle_level": int(profile.lifecycle_level),
|
|
289
|
+
"lifecycle_name": profile.lifecycle_level.name.lower(),
|
|
290
|
+
"simulation": {
|
|
291
|
+
tier.value: profile.qualification.state_for(tier).value
|
|
292
|
+
for tier in SimulationTier
|
|
293
|
+
},
|
|
294
|
+
"hardware_shadow": profile.qualification.hardware_shadow.value,
|
|
295
|
+
"sim_real": profile.qualification.sim_real_correlation.value,
|
|
296
|
+
"site": profile.qualification.site_qualification.value,
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
return tuple(rows)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
ARM_CAPABILITIES = frozenset(
|
|
303
|
+
{
|
|
304
|
+
Capability.MOVE,
|
|
305
|
+
Capability.PICK,
|
|
306
|
+
Capability.PLACE,
|
|
307
|
+
Capability.WAIT,
|
|
308
|
+
Capability.TRANSFER,
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _qualification(
|
|
314
|
+
*,
|
|
315
|
+
deterministic: QualificationState = QualificationState.QUALIFIED,
|
|
316
|
+
kinematic: QualificationState = QualificationState.NOT_QUALIFIED,
|
|
317
|
+
physics: QualificationState = QualificationState.NOT_QUALIFIED,
|
|
318
|
+
visual: QualificationState = QualificationState.NOT_QUALIFIED,
|
|
319
|
+
) -> RobotQualification:
|
|
320
|
+
return RobotQualification(
|
|
321
|
+
{
|
|
322
|
+
SimulationTier.DETERMINISTIC: deterministic,
|
|
323
|
+
SimulationTier.KINEMATIC: kinematic,
|
|
324
|
+
SimulationTier.PHYSICS: physics,
|
|
325
|
+
SimulationTier.VISUAL: visual,
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# These statuses describe only evidence already established inside DevAgent.
|
|
331
|
+
# They intentionally do not infer hardware or sim-real qualification from the
|
|
332
|
+
# existence of an external vendor/ROS package.
|
|
333
|
+
BUILTIN_ROBOT_PROFILES = RobotProfileRegistry(
|
|
334
|
+
(
|
|
335
|
+
RobotProfile(
|
|
336
|
+
key="ur5e",
|
|
337
|
+
vendor="universal_robots",
|
|
338
|
+
model="ur5e",
|
|
339
|
+
capabilities=ARM_CAPABILITIES,
|
|
340
|
+
qualification=_qualification(
|
|
341
|
+
kinematic=QualificationState.QUALIFIED,
|
|
342
|
+
physics=QualificationState.QUALIFIED,
|
|
343
|
+
visual=QualificationState.QUALIFIED,
|
|
344
|
+
),
|
|
345
|
+
ros=RosIntegration(
|
|
346
|
+
ros2_compatible=True,
|
|
347
|
+
joint_state_interface="/joint_states",
|
|
348
|
+
trajectory_interface="joint_trajectory_controller/follow_joint_trajectory",
|
|
349
|
+
robot_description="ur_description",
|
|
350
|
+
moveit_config="ur_moveit_config",
|
|
351
|
+
hardware_driver="ur_robot_driver",
|
|
352
|
+
),
|
|
353
|
+
aliases=("ur 5e", "universal robots ur5e", "universal robot ur5e"),
|
|
354
|
+
legacy_qualification="primary",
|
|
355
|
+
notes=(
|
|
356
|
+
"DevAgent visual Gazebo/MoveIt workstation qualification exists; real execution remains locked.",
|
|
357
|
+
),
|
|
358
|
+
),
|
|
359
|
+
RobotProfile(
|
|
360
|
+
key="fanuc_crx",
|
|
361
|
+
vendor="fanuc",
|
|
362
|
+
model="crx",
|
|
363
|
+
capabilities=ARM_CAPABILITIES,
|
|
364
|
+
qualification=_qualification(),
|
|
365
|
+
ros=RosIntegration(
|
|
366
|
+
ros2_compatible=True,
|
|
367
|
+
joint_state_interface="/joint_states",
|
|
368
|
+
trajectory_interface="follow_joint_trajectory",
|
|
369
|
+
),
|
|
370
|
+
aliases=("fanuc crx", "crx"),
|
|
371
|
+
legacy_qualification="primary",
|
|
372
|
+
notes=(
|
|
373
|
+
"Deterministic DevAgent qualification only until an exact ROS/simulator/controller profile is qualified.",
|
|
374
|
+
),
|
|
375
|
+
),
|
|
376
|
+
RobotProfile(
|
|
377
|
+
key="kuka_kr",
|
|
378
|
+
vendor="kuka",
|
|
379
|
+
model="kr",
|
|
380
|
+
capabilities=ARM_CAPABILITIES,
|
|
381
|
+
qualification=_qualification(),
|
|
382
|
+
ros=RosIntegration(
|
|
383
|
+
ros2_compatible=True,
|
|
384
|
+
joint_state_interface="/joint_states",
|
|
385
|
+
trajectory_interface="follow_joint_trajectory",
|
|
386
|
+
),
|
|
387
|
+
aliases=("kuka kr",),
|
|
388
|
+
legacy_qualification="primary",
|
|
389
|
+
notes=(
|
|
390
|
+
"Deterministic DevAgent qualification only until an exact KR/controller profile is qualified.",
|
|
391
|
+
),
|
|
392
|
+
),
|
|
393
|
+
RobotProfile(
|
|
394
|
+
key="abb_irb",
|
|
395
|
+
vendor="abb",
|
|
396
|
+
model="irb",
|
|
397
|
+
capabilities=ARM_CAPABILITIES,
|
|
398
|
+
qualification=_qualification(),
|
|
399
|
+
ros=RosIntegration(
|
|
400
|
+
ros2_compatible=True,
|
|
401
|
+
joint_state_interface="/joint_states",
|
|
402
|
+
trajectory_interface="follow_joint_trajectory",
|
|
403
|
+
),
|
|
404
|
+
aliases=("abb irb",),
|
|
405
|
+
legacy_qualification="simulation",
|
|
406
|
+
notes=(
|
|
407
|
+
"Generic IRB family placeholder; exact ABB model/controller qualification is required before physical claims.",
|
|
408
|
+
),
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterator, Mapping
|
|
4
|
+
|
|
5
|
+
from .models import Resource
|
|
6
|
+
from .robot_platform import (
|
|
7
|
+
ARM_CAPABILITIES,
|
|
8
|
+
BUILTIN_ROBOT_PROFILES,
|
|
9
|
+
RobotPlatformError,
|
|
10
|
+
RobotProfileRegistry,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
PROFILE_REGISTRY: RobotProfileRegistry = BUILTIN_ROBOT_PROFILES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resource(profile_key: str, resource_id: str) -> Resource:
|
|
18
|
+
return PROFILE_REGISTRY.get(profile_key).to_resource(resource_id)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ur5e(resource_id: str = "robot_01") -> Resource:
|
|
22
|
+
return _resource("ur5e", resource_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def fanuc_crx(resource_id: str = "robot_01") -> Resource:
|
|
26
|
+
return _resource("fanuc_crx", resource_id)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def kuka_kr(resource_id: str = "robot_01") -> Resource:
|
|
30
|
+
return _resource("kuka_kr", resource_id)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def abb_irb(resource_id: str = "robot_01") -> Resource:
|
|
34
|
+
return _resource("abb_irb", resource_id)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _RegistryBackedCatalog(Mapping[str, Callable[..., Resource]]):
|
|
38
|
+
"""Compatibility view for older core code that expects CATALOG[key]().
|
|
39
|
+
|
|
40
|
+
Membership, iteration, and lookup are backed by RobotProfileRegistry. New
|
|
41
|
+
profiles therefore do not require changes in request/qualification core.
|
|
42
|
+
Aliases resolve to the profile's canonical identity before a Resource is built.
|
|
43
|
+
|
|
44
|
+
``Mapping`` requires missing keys to raise ``KeyError``. The registry uses
|
|
45
|
+
``RobotPlatformError`` for unknown robot identities, so that exception is
|
|
46
|
+
translated at this compatibility boundary. This preserves legacy
|
|
47
|
+
``key in CATALOG`` / ``key not in CATALOG`` semantics without weakening the
|
|
48
|
+
registry's explicit error contract elsewhere.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, registry: RobotProfileRegistry) -> None:
|
|
52
|
+
self.registry = registry
|
|
53
|
+
|
|
54
|
+
def __getitem__(self, key: str) -> Callable[..., Resource]:
|
|
55
|
+
try:
|
|
56
|
+
profile = self.registry.resolve(key)
|
|
57
|
+
except RobotPlatformError as exc:
|
|
58
|
+
raise KeyError(key) from exc
|
|
59
|
+
|
|
60
|
+
def factory(resource_id: str = "robot_01") -> Resource:
|
|
61
|
+
return profile.to_resource(resource_id)
|
|
62
|
+
|
|
63
|
+
return factory
|
|
64
|
+
|
|
65
|
+
def __iter__(self) -> Iterator[str]:
|
|
66
|
+
return iter(self.registry.keys())
|
|
67
|
+
|
|
68
|
+
def __len__(self) -> int:
|
|
69
|
+
return len(self.registry.keys())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Backward-compatible callable catalog used by v0.6/v0.7. New code should use
|
|
73
|
+
# PROFILE_REGISTRY directly for qualification and integration metadata.
|
|
74
|
+
CATALOG: Mapping[str, Callable[..., Resource]] = _RegistryBackedCatalog(
|
|
75
|
+
PROFILE_REGISTRY
|
|
76
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .acceptance import AcceptanceStage, LaptopAcceptanceReport, UR5eLaptopAcceptance
|
|
2
|
+
from .commands import CommandResult, SubprocessRosRunner
|
|
3
|
+
from .doctor import RosCheck, RosDoctor, RosDoctorReport
|
|
4
|
+
from .trajectory import (
|
|
5
|
+
JointStateSnapshot,
|
|
6
|
+
RosTrajectoryError,
|
|
7
|
+
execute_joint_trajectory,
|
|
8
|
+
follow_joint_trajectory_command,
|
|
9
|
+
read_joint_state,
|
|
10
|
+
)
|
|
11
|
+
from .ur5e import UR5eSimulationLauncher, official_ur5e_moveit_command, official_ur_motion_smoke_command
|
|
12
|
+
from .ur5e_adapter import UR5E_JOINTS, UR5eGazeboAdapter
|
|
13
|
+
from .ur5e_v10_adapter import UR5eCanonicalTwinAdapter
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AcceptanceStage",
|
|
17
|
+
"LaptopAcceptanceReport",
|
|
18
|
+
"UR5eLaptopAcceptance",
|
|
19
|
+
"CommandResult",
|
|
20
|
+
"SubprocessRosRunner",
|
|
21
|
+
"RosCheck",
|
|
22
|
+
"RosDoctor",
|
|
23
|
+
"RosDoctorReport",
|
|
24
|
+
"JointStateSnapshot",
|
|
25
|
+
"RosTrajectoryError",
|
|
26
|
+
"execute_joint_trajectory",
|
|
27
|
+
"follow_joint_trajectory_command",
|
|
28
|
+
"read_joint_state",
|
|
29
|
+
"UR5E_JOINTS",
|
|
30
|
+
"UR5eGazeboAdapter",
|
|
31
|
+
"UR5eCanonicalTwinAdapter",
|
|
32
|
+
"UR5eSimulationLauncher",
|
|
33
|
+
"official_ur5e_moveit_command",
|
|
34
|
+
"official_ur_motion_smoke_command",
|
|
35
|
+
]
|