luckyrobots 0.1.66__py3-none-any.whl → 0.1.72__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 (60) hide show
  1. luckyrobots/__init__.py +30 -12
  2. luckyrobots/client.py +997 -0
  3. luckyrobots/config/robots.yaml +231 -71
  4. luckyrobots/engine/__init__.py +23 -0
  5. luckyrobots/{utils → engine}/check_updates.py +108 -48
  6. luckyrobots/{utils → engine}/download.py +61 -39
  7. luckyrobots/engine/manager.py +427 -0
  8. luckyrobots/grpc/__init__.py +6 -0
  9. luckyrobots/grpc/generated/__init__.py +18 -0
  10. luckyrobots/grpc/generated/agent_pb2.py +69 -0
  11. luckyrobots/grpc/generated/agent_pb2_grpc.py +283 -0
  12. luckyrobots/grpc/generated/camera_pb2.py +47 -0
  13. luckyrobots/grpc/generated/camera_pb2_grpc.py +144 -0
  14. luckyrobots/grpc/generated/common_pb2.py +43 -0
  15. luckyrobots/grpc/generated/common_pb2_grpc.py +24 -0
  16. luckyrobots/grpc/generated/hazel_rpc_pb2.py +43 -0
  17. luckyrobots/grpc/generated/hazel_rpc_pb2_grpc.py +24 -0
  18. luckyrobots/grpc/generated/media_pb2.py +39 -0
  19. luckyrobots/grpc/generated/media_pb2_grpc.py +24 -0
  20. luckyrobots/grpc/generated/mujoco_pb2.py +51 -0
  21. luckyrobots/grpc/generated/mujoco_pb2_grpc.py +230 -0
  22. luckyrobots/grpc/generated/scene_pb2.py +66 -0
  23. luckyrobots/grpc/generated/scene_pb2_grpc.py +317 -0
  24. luckyrobots/grpc/generated/telemetry_pb2.py +47 -0
  25. luckyrobots/grpc/generated/telemetry_pb2_grpc.py +143 -0
  26. luckyrobots/grpc/generated/viewport_pb2.py +50 -0
  27. luckyrobots/grpc/generated/viewport_pb2_grpc.py +144 -0
  28. luckyrobots/grpc/proto/agent.proto +213 -0
  29. luckyrobots/grpc/proto/camera.proto +41 -0
  30. luckyrobots/grpc/proto/common.proto +36 -0
  31. luckyrobots/grpc/proto/hazel_rpc.proto +32 -0
  32. luckyrobots/grpc/proto/media.proto +26 -0
  33. luckyrobots/grpc/proto/mujoco.proto +64 -0
  34. luckyrobots/grpc/proto/scene.proto +104 -0
  35. luckyrobots/grpc/proto/telemetry.proto +43 -0
  36. luckyrobots/grpc/proto/viewport.proto +45 -0
  37. luckyrobots/luckyrobots.py +252 -0
  38. luckyrobots/models/__init__.py +15 -0
  39. luckyrobots/models/camera.py +97 -0
  40. luckyrobots/models/observation.py +135 -0
  41. luckyrobots/models/randomization.py +77 -0
  42. luckyrobots/{utils/helpers.py → utils.py} +75 -40
  43. luckyrobots-0.1.72.dist-info/METADATA +262 -0
  44. luckyrobots-0.1.72.dist-info/RECORD +47 -0
  45. {luckyrobots-0.1.66.dist-info → luckyrobots-0.1.72.dist-info}/WHEEL +1 -1
  46. luckyrobots/core/luckyrobots.py +0 -624
  47. luckyrobots/core/manager.py +0 -236
  48. luckyrobots/core/models.py +0 -68
  49. luckyrobots/core/node.py +0 -273
  50. luckyrobots/message/__init__.py +0 -18
  51. luckyrobots/message/pubsub.py +0 -145
  52. luckyrobots/message/srv/client.py +0 -81
  53. luckyrobots/message/srv/service.py +0 -135
  54. luckyrobots/message/srv/types.py +0 -83
  55. luckyrobots/message/transporter.py +0 -427
  56. luckyrobots/utils/event_loop.py +0 -94
  57. luckyrobots/utils/sim_manager.py +0 -406
  58. luckyrobots-0.1.66.dist-info/METADATA +0 -253
  59. luckyrobots-0.1.66.dist-info/RECORD +0 -24
  60. {luckyrobots-0.1.66.dist-info → luckyrobots-0.1.72.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,135 @@
1
+ """
2
+ RL observation models for LuckyRobots.
3
+
4
+ These are the primary return types for the simplified API:
5
+ - ObservationResponse: returned by get_observation()
6
+ - StateSnapshot: returned by get_state()
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional
10
+ from pydantic import BaseModel, Field, ConfigDict
11
+
12
+
13
+ class ObservationResponse(BaseModel):
14
+ """RL observation data from an agent.
15
+
16
+ This is the return type for LuckyEngineClient.get_observation() and
17
+ LuckyRobots.get_observation(). It contains the RL observation vector
18
+ with optional named access for debugging.
19
+
20
+ Usage:
21
+ obs = client.get_observation()
22
+
23
+ # Flat vector for RL training
24
+ obs.observation # [0.1, 0.2, 0.3, ...]
25
+
26
+ # Named access (if schema was fetched)
27
+ obs["proj_grav_x"] # 0.1
28
+ obs.to_dict() # {"proj_grav_x": 0.1, "proj_grav_y": 0.2, ...}
29
+ """
30
+
31
+ model_config = ConfigDict(frozen=True)
32
+
33
+ observation: List[float] = Field(
34
+ description="Flat observation vector from the agent's observation spec"
35
+ )
36
+ actions: List[float] = Field(description="Last applied actions")
37
+ timestamp_ms: int = Field(description="Wall-clock timestamp in milliseconds")
38
+ frame_number: int = Field(description="Monotonic frame counter")
39
+ agent_name: str = Field(description="Agent identifier")
40
+
41
+ # Optional named access (populated if schema is available)
42
+ observation_names: Optional[List[str]] = Field(
43
+ default=None,
44
+ description="Observation names from agent schema (enables named access)",
45
+ )
46
+ action_names: Optional[List[str]] = Field(
47
+ default=None,
48
+ description="Action names from agent schema",
49
+ )
50
+
51
+ def __getitem__(self, key: str) -> float:
52
+ """Access observation value by name.
53
+
54
+ Args:
55
+ key: Observation name (e.g., "proj_grav_x", "joint_pos_0").
56
+
57
+ Returns:
58
+ The observation value.
59
+
60
+ Raises:
61
+ KeyError: If names not available or key not found.
62
+ """
63
+ if self.observation_names is None:
64
+ raise KeyError(
65
+ f"No observation names available. "
66
+ f"Ensure client has fetched schema via get_agent_schema()."
67
+ )
68
+ try:
69
+ idx = self.observation_names.index(key)
70
+ return self.observation[idx]
71
+ except ValueError:
72
+ raise KeyError(
73
+ f"Unknown observation name: '{key}'. "
74
+ f"Available: {self.observation_names}"
75
+ )
76
+
77
+ def get(self, key: str, default: Optional[float] = None) -> Optional[float]:
78
+ """Get observation value by name with optional default.
79
+
80
+ Args:
81
+ key: Observation name.
82
+ default: Value to return if key not found.
83
+
84
+ Returns:
85
+ The observation value or default.
86
+ """
87
+ try:
88
+ return self[key]
89
+ except KeyError:
90
+ return default
91
+
92
+ def to_dict(self) -> Dict[str, float]:
93
+ """Convert observations to a name->value dictionary.
94
+
95
+ Returns:
96
+ Dict mapping observation names to values. If names not available,
97
+ uses "obs_0", "obs_1", etc.
98
+ """
99
+ if self.observation_names is not None:
100
+ return dict(zip(self.observation_names, self.observation))
101
+ return {f"obs_{i}": v for i, v in enumerate(self.observation)}
102
+
103
+ def actions_to_dict(self) -> Dict[str, float]:
104
+ """Convert actions to a name->value dictionary.
105
+
106
+ Returns:
107
+ Dict mapping action names to values. If names not available,
108
+ uses "action_0", "action_1", etc.
109
+ """
110
+ if self.action_names is not None:
111
+ return dict(zip(self.action_names, self.actions))
112
+ return {f"action_{i}": v for i, v in enumerate(self.actions)}
113
+
114
+
115
+ class StateSnapshot(BaseModel):
116
+ """Bundled snapshot of multiple data sources.
117
+
118
+ Use LuckyEngineClient.get_state() to get a bundled snapshot when you need
119
+ multiple data types in one efficient call. For streaming data like telemetry,
120
+ use the dedicated streaming methods instead.
121
+ """
122
+
123
+ model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
124
+
125
+ observation: Optional[ObservationResponse] = Field(
126
+ default=None, description="RL observation data (if include_observation=True)"
127
+ )
128
+ joint_state: Optional[Any] = Field(
129
+ default=None, description="Joint positions/velocities (if include_joint_state=True)"
130
+ )
131
+ camera_frames: Optional[List[Any]] = Field(
132
+ default=None, description="Camera frames (if camera_names provided)"
133
+ )
134
+ timestamp_ms: int = Field(default=0, description="Wall-clock timestamp in milliseconds")
135
+ frame_number: int = Field(default=0, description="Monotonic frame counter")
@@ -0,0 +1,77 @@
1
+ """
2
+ Domain randomization configuration for physics parameters.
3
+
4
+ This model maps to the DomainRandomizationConfig proto message in agent.proto.
5
+ """
6
+
7
+ from typing import Optional
8
+ from pydantic import BaseModel, Field, ConfigDict
9
+
10
+
11
+ class DomainRandomizationConfig(BaseModel):
12
+ """Domain randomization configuration for physics parameters.
13
+
14
+ All range fields are [min, max] tuples. None/empty means "use defaults".
15
+
16
+ Usage:
17
+ randomization_cfg = DomainRandomizationConfig(
18
+ friction_range=(0.5, 1.5),
19
+ mass_scale_range=(0.8, 1.2),
20
+ joint_position_noise=0.05,
21
+ )
22
+ client.reset_agent(randomization_cfg=randomization_cfg)
23
+ """
24
+
25
+ model_config = ConfigDict(frozen=True)
26
+
27
+ # Initial state randomization
28
+ pose_position_noise: Optional[tuple[float, float, float]] = Field(
29
+ default=None, description="[x, y, z] position noise std"
30
+ )
31
+ pose_orientation_noise: Optional[float] = Field(
32
+ default=None, description="Orientation noise std (radians)"
33
+ )
34
+ joint_position_noise: Optional[float] = Field(
35
+ default=None, description="Joint position noise std"
36
+ )
37
+ joint_velocity_noise: Optional[float] = Field(
38
+ default=None, description="Joint velocity noise std"
39
+ )
40
+
41
+ # Physics parameters (all [min, max] ranges)
42
+ friction_range: Optional[tuple[float, float]] = Field(
43
+ default=None, description="Surface friction coefficient [min, max]"
44
+ )
45
+ restitution_range: Optional[tuple[float, float]] = Field(
46
+ default=None, description="Bounce/restitution coefficient [min, max]"
47
+ )
48
+ mass_scale_range: Optional[tuple[float, float]] = Field(
49
+ default=None, description="Body mass multiplier [min, max]"
50
+ )
51
+ com_offset_range: Optional[tuple[float, float]] = Field(
52
+ default=None, description="Center of mass offset [min, max]"
53
+ )
54
+
55
+ # Motor/actuator randomization
56
+ motor_strength_range: Optional[tuple[float, float]] = Field(
57
+ default=None, description="Motor strength multiplier [min, max]"
58
+ )
59
+ motor_offset_range: Optional[tuple[float, float]] = Field(
60
+ default=None, description="Motor position offset [min, max]"
61
+ )
62
+
63
+ # External disturbances
64
+ push_interval_range: Optional[tuple[float, float]] = Field(
65
+ default=None, description="Time between pushes [min, max]"
66
+ )
67
+ push_velocity_range: Optional[tuple[float, float]] = Field(
68
+ default=None, description="Push velocity magnitude [min, max]"
69
+ )
70
+
71
+ # Terrain configuration
72
+ terrain_type: Optional[str] = Field(
73
+ default=None, description="Terrain type identifier"
74
+ )
75
+ terrain_difficulty: Optional[float] = Field(
76
+ default=None, description="Terrain difficulty level"
77
+ )
@@ -1,18 +1,87 @@
1
- import yaml
1
+ """
2
+ Utility functions and classes for LuckyRobots.
3
+ """
4
+
2
5
  import time
6
+ import yaml
3
7
  import importlib.resources
4
8
  from collections import deque
5
9
 
6
10
 
11
+ class FPS:
12
+ """Utility for measuring frames per second with a rolling window.
13
+
14
+ Usage:
15
+ fps = FPS(frame_window=30)
16
+ while running:
17
+ # ... do work ...
18
+ current_fps = fps.measure()
19
+ """
20
+
21
+ def __init__(self, frame_window: int = 30):
22
+ """Initialize FPS counter.
23
+
24
+ Args:
25
+ frame_window: Number of frames to average over.
26
+ """
27
+ self.frame_window = frame_window
28
+ self.frame_times: deque[float] = deque(maxlen=frame_window)
29
+ self.last_frame_time = time.perf_counter()
30
+
31
+ def measure(self) -> float:
32
+ """Record a frame and return current FPS.
33
+
34
+ Returns:
35
+ Current frames per second (averaged over window).
36
+ """
37
+ current_time = time.perf_counter()
38
+ frame_delta = current_time - self.last_frame_time
39
+ self.last_frame_time = current_time
40
+
41
+ self.frame_times.append(frame_delta)
42
+
43
+ if len(self.frame_times) >= 2:
44
+ avg_frame_time = sum(self.frame_times) / len(self.frame_times)
45
+ return 1.0 / avg_frame_time if avg_frame_time > 0 else 0.0
46
+ return 0.0
47
+
48
+
49
+ def get_robot_config(robot: str = None) -> dict:
50
+ """Get the configuration for a robot from robots.yaml.
51
+
52
+ Args:
53
+ robot: Robot name. If None, returns entire config.
54
+
55
+ Returns:
56
+ Robot configuration dict, or full config if robot is None.
57
+ """
58
+ with importlib.resources.files("luckyrobots").joinpath("config/robots.yaml").open(
59
+ "r"
60
+ ) as f:
61
+ config = yaml.safe_load(f)
62
+ if robot is not None:
63
+ return config[robot]
64
+ else:
65
+ return config
66
+
67
+
7
68
  def validate_params(
8
69
  scene: str = None,
9
70
  robot: str = None,
10
71
  task: str = None,
11
72
  observation_type: str = None,
12
- ) -> bool:
13
- """Validate the parameters passed into Lucky World"""
14
- robot_config = get_robot_config(robot)
73
+ ) -> None:
74
+ """Validate parameters for launching LuckyEngine.
75
+
76
+ Args:
77
+ scene: Scene name.
78
+ robot: Robot name.
79
+ task: Task name.
80
+ observation_type: Observation type.
15
81
 
82
+ Raises:
83
+ ValueError: If any parameter is invalid.
84
+ """
16
85
  if scene is None:
17
86
  raise ValueError("Scene is required")
18
87
  if robot is None:
@@ -22,6 +91,8 @@ def validate_params(
22
91
  if observation_type is None:
23
92
  raise ValueError("Observation type is required")
24
93
 
94
+ robot_config = get_robot_config(robot)
95
+
25
96
  if scene not in robot_config["available_scenes"]:
26
97
  raise ValueError(f"Scene {scene} not available in {robot} config")
27
98
  if task not in robot_config["available_tasks"]:
@@ -30,39 +101,3 @@ def validate_params(
30
101
  raise ValueError(
31
102
  f"Observation type {observation_type} not available in {robot} config"
32
103
  )
33
-
34
-
35
- def get_robot_config(robot: str = None) -> dict:
36
- """Get the configuration for the robot"""
37
- with importlib.resources.files("luckyrobots").joinpath("config/robots.yaml").open(
38
- "r"
39
- ) as f:
40
- config = yaml.safe_load(f)
41
- if robot is not None:
42
- return config[robot]
43
- else:
44
- return config
45
-
46
-
47
- class FPS:
48
- def __init__(self, frame_window: int = 30):
49
- self.frame_window = frame_window
50
- self.frame_times = deque(maxlen=frame_window)
51
- self.last_frame_time = time.perf_counter()
52
-
53
- def measure(self) -> float:
54
- current_time = time.perf_counter()
55
- frame_delta = current_time - self.last_frame_time
56
- self.last_frame_time = current_time
57
-
58
- # Add frame time to rolling window
59
- self.frame_times.append(frame_delta)
60
-
61
- # Calculate FPS from average frame time
62
- if len(self.frame_times) >= 2: # Need at least 2 samples
63
- avg_frame_time = sum(self.frame_times) / len(self.frame_times)
64
- fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0
65
- else:
66
- fps = 0
67
-
68
- print(f"FPS: {fps}")
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: luckyrobots
3
+ Version: 0.1.72
4
+ Summary: Robotics-AI Training in Hyperrealistic Game Environments
5
+ Project-URL: Homepage, https://github.com/luckyrobots/luckyrobots
6
+ Project-URL: Documentation, https://luckyrobots.readthedocs.io
7
+ Project-URL: Repository, https://github.com/luckyrobots/luckyrobots
8
+ Project-URL: Issues, https://github.com/luckyrobots/luckyrobots/issues
9
+ Author-email: Devrim Yasar <braces.verbose03@icloud.com>, Ethan Clark <ethan@luckyrobots.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: beautifulsoup4>=4.12.0
20
+ Requires-Dist: grpcio>=1.60.0
21
+ Requires-Dist: numpy>=1.24.0
22
+ Requires-Dist: opencv-python>=4.8.0
23
+ Requires-Dist: packaging>=23.0
24
+ Requires-Dist: protobuf>=4.25.0
25
+ Requires-Dist: psutil>=5.9.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: requests>=2.31.0
29
+ Requires-Dist: tqdm>=4.66.0
30
+ Requires-Dist: watchdog>=3.0.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ <p align="center">
34
+ <img width="384" alt="Default_Logo_Horizontal@2x" src="https://github.com/user-attachments/assets/ae6ad53a-741e-4e7a-94cb-5a46a8e81398" />
35
+ </p>
36
+
37
+ <p align="center">
38
+ Infinite synthetic data generation for embodied AI
39
+ </p>
40
+
41
+ <div align="center">
42
+
43
+ [![PyPI version](https://img.shields.io/pypi/v/luckyrobots.svg)](https://pypi.org/project/luckyrobots/)
44
+ [![Documentation](https://img.shields.io/badge/docs-read%20the%20docs-blue)](https://luckyrobots.readthedocs.io)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
46
+ [![Python Version](https://img.shields.io/pypi/pyversions/luckyrobots)](https://pypi.org/project/luckyrobots/)
47
+ [![Status](https://img.shields.io/badge/Status-Alpha-orange)](https://pypi.org/project/luckyrobots/)
48
+ [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](https://discord.gg/5CH3wx3tAs)
49
+
50
+ </div>
51
+
52
+ https://github.com/user-attachments/assets/0ab2953d-b188-4af7-a225-71decdd2378c
53
+
54
+ # Lucky Robots
55
+
56
+ Hyperrealistic robotics simulation framework with Python API for embodied AI training and testing.
57
+
58
+ <p align="center">
59
+ <img width="49%" alt="Bedroom environment in LuckyEngine" src="https://github.com/user-attachments/assets/279a7864-9a8b-453e-8567-3a174f5db8ab" />
60
+ <img width="49%" alt="Open floor plan in LuckyEngine" src="https://github.com/user-attachments/assets/68c72b97-98ab-42b0-a065-8a4247b014c7" />
61
+ </p>
62
+
63
+ ## Quick Start
64
+
65
+ 1. **Download LuckyEngine** from our [releases page](https://github.com/luckyrobots/luckyrobots/releases/latest) and set the path:
66
+ ```bash
67
+ # Set environment variable (choose one method):
68
+
69
+ # Method 1: Set LUCKYENGINE_PATH directly to the executable
70
+ export LUCKYENGINE_PATH=/path/to/LuckyEngine # Linux/Mac
71
+ export LUCKYENGINE_PATH=/path/to/LuckyEngine.exe # Windows
72
+
73
+ # Method 2: Set LUCKYENGINE_HOME to the directory containing the executable
74
+ export LUCKYENGINE_HOME=/path/to/luckyengine/directory
75
+ ```
76
+
77
+ 2. **Install**
78
+ ```bash
79
+ pip install luckyrobots
80
+ ```
81
+
82
+ 3. **Run Example**
83
+ ```bash
84
+ git clone https://github.com/luckyrobots/luckyrobots.git
85
+ cd luckyrobots/examples
86
+ python controller.py --skip-launch # If LuckyEngine is already running
87
+ ```
88
+
89
+ ## Basic Usage
90
+
91
+ ```python
92
+ from luckyrobots import LuckyEngineClient
93
+
94
+ # Connect to LuckyEngine
95
+ client = LuckyEngineClient(
96
+ host="127.0.0.1",
97
+ port=50051,
98
+ robot_name="unitreego1",
99
+ )
100
+ client.wait_for_server()
101
+
102
+ # Optional: Fetch schema for named observation access
103
+ client.fetch_schema()
104
+
105
+ # Get RL observation
106
+ obs = client.get_observation()
107
+ print(f"Observation: {obs.observation[:5]}...") # Flat vector for RL
108
+ print(f"Timestamp: {obs.timestamp_ms}")
109
+
110
+ # Named access (if schema fetched)
111
+ # obs["proj_grav_x"] # Access by name
112
+ # obs.to_dict() # Convert to dict
113
+
114
+ # Send controls
115
+ client.send_control(controls=[0.1, 0.2, -0.1, ...])
116
+
117
+ # Get joint state (separate from RL observation)
118
+ joints = client.get_joint_state()
119
+ print(f"Positions: {joints.positions}")
120
+ print(f"Velocities: {joints.velocities}")
121
+ ```
122
+
123
+ ## API Overview
124
+
125
+ ### Core Classes
126
+
127
+ **`LuckyEngineClient`** - Low-level gRPC client
128
+ - `wait_for_server(timeout)` - Wait for LuckyEngine connection
129
+ - `get_observation()` - Get RL observation vector
130
+ - `get_joint_state()` - Get joint positions/velocities
131
+ - `send_control(controls)` - Send actuator commands
132
+ - `get_agent_schema()` - Get observation/action names and sizes
133
+ - `reset_agent()` - Reset agent state
134
+
135
+ **`LuckyRobots`** - High-level wrapper (launches LuckyEngine)
136
+ - `start(scene, robot, task)` - Launch and connect
137
+ - `get_observation()` - Get observation
138
+ - `step(controls)` - Send controls and get next observation
139
+
140
+ ### Models
141
+
142
+ ```python
143
+ from luckyrobots import ObservationResponse, StateSnapshot
144
+
145
+ # ObservationResponse - returned by get_observation()
146
+ obs.observation # List[float] - flat RL observation vector
147
+ obs.actions # List[float] - last applied actions
148
+ obs.timestamp_ms # int - wall-clock timestamp
149
+ obs.frame_number # int - monotonic counter
150
+ obs["name"] # Named access (if schema fetched)
151
+ obs.to_dict() # Convert to name->value dict
152
+ ```
153
+
154
+ ## Available Robots & Environments
155
+
156
+ ### Robots
157
+ - **unitreego1**: Quadruped robot
158
+ - **so100**: 6-DOF manipulator with gripper
159
+ - **stretch_v1**: Mobile manipulator
160
+
161
+ ### Scenes
162
+ - **velocity**: Velocity control training
163
+ - **kitchen**: Residential kitchen environment
164
+
165
+ ### Tasks
166
+ - **locomotion**: Walking/movement
167
+ - **pickandplace**: Object manipulation
168
+
169
+ ## Development
170
+
171
+ ### Setup with uv (recommended)
172
+
173
+ ```bash
174
+ # Clone and enter repo
175
+ git clone https://github.com/luckyrobots/luckyrobots.git
176
+ cd luckyrobots
177
+
178
+ # Install uv if you haven't
179
+ curl -LsSf https://astral.sh/uv/install.sh | sh
180
+
181
+ # Create venv and install deps
182
+ uv sync
183
+
184
+ # Run tests
185
+ uv run pytest
186
+
187
+ # Run example
188
+ uv run python examples/controller.py --skip-launch
189
+ ```
190
+
191
+ ### Setup with pip
192
+
193
+ ```bash
194
+ git clone https://github.com/luckyrobots/luckyrobots.git
195
+ cd luckyrobots
196
+ pip install -e ".[dev]"
197
+ ```
198
+
199
+ ### Regenerating gRPC Stubs
200
+
201
+ The Python gRPC stubs are in `src/luckyrobots/grpc/generated/` and are
202
+ generated from protos in `src/luckyrobots/grpc/proto/`.
203
+
204
+ ```bash
205
+ python -m grpc_tools.protoc \
206
+ -I "src/luckyrobots/grpc/proto" \
207
+ --python_out="src/luckyrobots/grpc/generated" \
208
+ --grpc_python_out="src/luckyrobots/grpc/generated" \
209
+ src/luckyrobots/grpc/proto/*.proto
210
+ ```
211
+
212
+ ### Project Structure
213
+
214
+ ```
215
+ src/luckyrobots/
216
+ ├── client.py # LuckyEngineClient (main API)
217
+ ├── luckyrobots.py # LuckyRobots high-level wrapper
218
+ ├── models/ # Pydantic models
219
+ │ ├── observation.py # ObservationResponse, StateSnapshot
220
+ │ └── camera.py # CameraData, CameraShape
221
+ ├── engine/ # Engine management
222
+ ├── grpc/ # gRPC internals
223
+ │ ├── generated/ # Protobuf stubs
224
+ │ └── proto/ # .proto files
225
+ └── config/ # Robot configurations
226
+ ```
227
+
228
+ ### Contributing
229
+
230
+ 1. Fork the repository
231
+ 2. Create a feature branch
232
+ 3. Make changes and add tests
233
+ 4. Run `uv run ruff check .` and `uv run ruff format .`
234
+ 5. Submit a pull request
235
+
236
+ ## Architecture
237
+
238
+ Lucky Robots uses gRPC for communication:
239
+
240
+ - **LuckyEngine**: Physics + rendering backend (Unreal Engine + MuJoCo)
241
+ - **Python client**: Connects via gRPC (default `127.0.0.1:50051`)
242
+
243
+ ### gRPC Services
244
+
245
+ | Service | Status | Description |
246
+ |---------|--------|-------------|
247
+ | MujocoService | ✅ Working | Joint state, controls |
248
+ | AgentService | ✅ Working | Observations, reset |
249
+ | SceneService | 🚧 Placeholder | Scene inspection |
250
+ | TelemetryService | 🚧 Placeholder | Telemetry streaming |
251
+ | CameraService | 🚧 Placeholder | Camera frames |
252
+ | ViewportService | 🚧 Placeholder | Viewport pixels |
253
+
254
+ ## License
255
+
256
+ MIT License - see [LICENSE](LICENSE) file.
257
+
258
+ ## Support
259
+
260
+ - **Issues**: [GitHub Issues](https://github.com/luckyrobots/luckyrobots/issues)
261
+ - **Discussions**: [GitHub Discussions](https://github.com/luckyrobots/luckyrobots/discussions)
262
+ - **Discord**: [Community Server](https://discord.gg/5CH3wx3tAs)
@@ -0,0 +1,47 @@
1
+ luckyrobots/__init__.py,sha256=VWewiYhYbYCs1ZSD0KxozmG2jH2uLlBxsVgFIRivKTM,824
2
+ luckyrobots/client.py,sha256=3F0p46FndJv02D_pSZkOn4j2tkjV6RX1ziCFFEaJsr4,36129
3
+ luckyrobots/luckyrobots.py,sha256=-XkIt9h7si4GK_j5Wpknb7_h56CCWi8_d1woVMuIvxU,8957
4
+ luckyrobots/utils.py,sha256=62BqpSRBFykVRMZ5Ti2F-yKRDoyU3UNHeesDF_PVCFc,2958
5
+ luckyrobots/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ luckyrobots/config/robots.yaml,sha256=Wu2AgwhZxo9F-RLcqPJXHnqzjT8a0wR-rFBcecck_eE,5164
7
+ luckyrobots/engine/__init__.py,sha256=-3r7aLueDF7eyumqIhTvYqS8ZNnVSJUJEHG4ybsKrjc,546
8
+ luckyrobots/engine/check_updates.py,sha256=KGQHrj1LB0_vexktO01xx-ZvPOYKkto4VBb3H1KV7uI,7593
9
+ luckyrobots/engine/download.py,sha256=vIb5T40Jk2BnfjPo0f4KgW14Om6P4_mvPpZ9JAlkVvg,4029
10
+ luckyrobots/engine/manager.py,sha256=8vxSevaoXo-lw2G6y74lVc-Ms8xIELK0yLswydMiOQY,13388
11
+ luckyrobots/grpc/__init__.py,sha256=hPh-X_FEus2sxrfQB2fQjD754X5bMaUFTWm3mZmUhe0,153
12
+ luckyrobots/grpc/generated/__init__.py,sha256=u69M4E_2LwRQSD_n4wvQyMX7dryBCrIdsho59c52Crk,732
13
+ luckyrobots/grpc/generated/agent_pb2.py,sha256=5rXekXme6u5KCtEtwjkhHkUyxrfxpMKutG4KPZqNr1U,7195
14
+ luckyrobots/grpc/generated/agent_pb2_grpc.py,sha256=M9ZsjS1qhBm57-DVy6il_u_XeTpb6Qbntm45Rqp_f1k,10909
15
+ luckyrobots/grpc/generated/camera_pb2.py,sha256=2UDkgzMYdDZ53euPfLAndeI7k4wI9QH_Dvwkay6TAB4,2623
16
+ luckyrobots/grpc/generated/camera_pb2_grpc.py,sha256=hUXzsf2qCE-ffZzkF8El2UuuLu6nxjKSYPukId2uFP8,5278
17
+ luckyrobots/grpc/generated/common_pb2.py,sha256=0eZTxqlsZdTrgp2hgLy-hdDNtac5U3kkS_D9UbVy0e8,2045
18
+ luckyrobots/grpc/generated/common_pb2_grpc.py,sha256=MWix04c710srn9BtjDxa9-UkZz0wa-w8tB0EmYndSgg,886
19
+ luckyrobots/grpc/generated/hazel_rpc_pb2.py,sha256=XB1nGSVTYhpulRpS8Ja44KaCV5s-l1t_FPV4DJ5_ak8,1716
20
+ luckyrobots/grpc/generated/hazel_rpc_pb2_grpc.py,sha256=5bhVh5DmVDl2ZsdgN7mTnElDbgU142TOI1qm8a8eblw,889
21
+ luckyrobots/grpc/generated/media_pb2.py,sha256=TtGdBub2Pj1vG_jJJIZnIZealLD0s98wZ4KgGIrcn2c,1827
22
+ luckyrobots/grpc/generated/media_pb2_grpc.py,sha256=szeOZzqDnIBFEyjAtXEHNxytTb9hTvOkQj0PhvXumMM,885
23
+ luckyrobots/grpc/generated/mujoco_pb2.py,sha256=Oq1AdZ0h-svs7ntlaDZ-zvQdTPsulnaE7YaINvBIBpM,3476
24
+ luckyrobots/grpc/generated/mujoco_pb2_grpc.py,sha256=Rz8hyPvhB2nR-0Mej4Ly9ixbrdpPCxDafmL8H4x43Nk,8603
25
+ luckyrobots/grpc/generated/scene_pb2.py,sha256=ZQyv_csWjB42WLfHNe8aQVMB5U9nQnJ7fY9enac9VGQ,5382
26
+ luckyrobots/grpc/generated/scene_pb2_grpc.py,sha256=I7G2LqkKZwEPZV-gxWo_45ecBey-wuGZ26pwxlH0mGQ,12118
27
+ luckyrobots/grpc/generated/telemetry_pb2.py,sha256=LgVtxJv7ovtqlrgxG375aXADGt3qXwgpsIhIkJ3pLKE,2790
28
+ luckyrobots/grpc/generated/telemetry_pb2_grpc.py,sha256=8TuCSc7rnQZFNqhEPXUoJsWAnwvEvyyEI5GaqQUCSSs,5463
29
+ luckyrobots/grpc/generated/viewport_pb2.py,sha256=Z_5gBD23z7He_OACYkaIsi0o4LIESzOG7o_rO1lRLVs,3093
30
+ luckyrobots/grpc/generated/viewport_pb2_grpc.py,sha256=BkJqREcjIa3ZdNgZUxH_MvBWOte9VJJGi-KlFgYepog,5436
31
+ luckyrobots/grpc/proto/agent.proto,sha256=SUVR9EqNz5gB5FNt3n54sNXv3SkvYgXKywlZmZh5vio,8506
32
+ luckyrobots/grpc/proto/camera.proto,sha256=unLowpk4d0AGJgyKrbzTr1F_CxOV9dXRoXn-gO8eJVw,1097
33
+ luckyrobots/grpc/proto/common.proto,sha256=zQPj-Z8b8mAE2_eHlJ0L2_-JH-CYY_OJcv8046fAtQM,772
34
+ luckyrobots/grpc/proto/hazel_rpc.proto,sha256=gtFukd1B9WRbY2kWBgd5YQWSw4cobYf1YRYX6VEaULE,1229
35
+ luckyrobots/grpc/proto/media.proto,sha256=3Rhmiaw1zceQBFdwxX1ViFgH-cSr7mBjNEGHPaFmTsk,836
36
+ luckyrobots/grpc/proto/mujoco.proto,sha256=b7tBvYqqvcSjmqrEax6_75-P3fIHHZq63A1HVJKjx0U,1873
37
+ luckyrobots/grpc/proto/scene.proto,sha256=ZypWsUzpgplGsQPP0FXJpyY4rSxgWYGsniM4vofhnak,3075
38
+ luckyrobots/grpc/proto/telemetry.proto,sha256=PZiUoA0bpwuvsBUdoQQdBNGWOuhlBMKAiBpwqPZiJgY,1481
39
+ luckyrobots/grpc/proto/viewport.proto,sha256=gunF8cIrVgvoAX8FNER6gw_u-IlRWAR1Q6ts0zUuqr8,1324
40
+ luckyrobots/models/__init__.py,sha256=assqJIKZXvDGwCVM0grNLJCBWyHtKRBAaxdJb2dON10,332
41
+ luckyrobots/models/camera.py,sha256=e-CftAndHASfokhtUtU7ujGb2q_kcXZ6-2KKvY42CKg,3682
42
+ luckyrobots/models/observation.py,sha256=jbRiUTjOP2ZiSobsnNcks3aMPAJA6oPoKqiXwvrNQK0,4758
43
+ luckyrobots/models/randomization.py,sha256=AicqHovzG97Ge33wtNQE-_EvKxndjEHguTjCiHfhAV8,2753
44
+ luckyrobots-0.1.72.dist-info/METADATA,sha256=-liPc6bNv78N5A-PgTJ8YeiD5YIIU8BTO60zE-zV87Y,8379
45
+ luckyrobots-0.1.72.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
46
+ luckyrobots-0.1.72.dist-info/licenses/LICENSE,sha256=xsPYvRJPH_fW_sofTUknI_KvZOsD4-BqjSOTZqI6Nmw,1069
47
+ luckyrobots-0.1.72.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any