luckyrobots 0.1.68__py3-none-any.whl → 0.1.70__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.
- luckyrobots/__init__.py +23 -12
- luckyrobots/client.py +800 -0
- luckyrobots/config/robots.yaml +231 -71
- luckyrobots/engine/__init__.py +23 -0
- luckyrobots/{utils → engine}/check_updates.py +108 -48
- luckyrobots/{utils → engine}/download.py +61 -39
- luckyrobots/engine/manager.py +427 -0
- luckyrobots/grpc/__init__.py +6 -0
- luckyrobots/grpc/generated/__init__.py +18 -0
- luckyrobots/grpc/generated/agent_pb2.py +61 -0
- luckyrobots/grpc/generated/agent_pb2_grpc.py +255 -0
- luckyrobots/grpc/generated/camera_pb2.py +45 -0
- luckyrobots/grpc/generated/camera_pb2_grpc.py +155 -0
- luckyrobots/grpc/generated/common_pb2.py +39 -0
- luckyrobots/grpc/generated/common_pb2_grpc.py +27 -0
- luckyrobots/grpc/generated/media_pb2.py +35 -0
- luckyrobots/grpc/generated/media_pb2_grpc.py +27 -0
- luckyrobots/grpc/generated/mujoco_pb2.py +47 -0
- luckyrobots/grpc/generated/mujoco_pb2_grpc.py +248 -0
- luckyrobots/grpc/generated/scene_pb2.py +54 -0
- luckyrobots/grpc/generated/scene_pb2_grpc.py +248 -0
- luckyrobots/grpc/generated/telemetry_pb2.py +43 -0
- luckyrobots/grpc/generated/telemetry_pb2_grpc.py +154 -0
- luckyrobots/grpc/generated/viewport_pb2.py +48 -0
- luckyrobots/grpc/generated/viewport_pb2_grpc.py +155 -0
- luckyrobots/grpc/proto/agent.proto +152 -0
- luckyrobots/grpc/proto/camera.proto +41 -0
- luckyrobots/grpc/proto/common.proto +36 -0
- luckyrobots/grpc/proto/hazel_rpc.proto +32 -0
- luckyrobots/grpc/proto/media.proto +26 -0
- luckyrobots/grpc/proto/mujoco.proto +64 -0
- luckyrobots/grpc/proto/scene.proto +70 -0
- luckyrobots/grpc/proto/telemetry.proto +43 -0
- luckyrobots/grpc/proto/viewport.proto +45 -0
- luckyrobots/luckyrobots.py +212 -0
- luckyrobots/models/__init__.py +13 -0
- luckyrobots/models/camera.py +97 -0
- luckyrobots/models/observation.py +135 -0
- luckyrobots/{utils/helpers.py → utils.py} +75 -40
- luckyrobots-0.1.70.dist-info/METADATA +262 -0
- luckyrobots-0.1.70.dist-info/RECORD +44 -0
- {luckyrobots-0.1.68.dist-info → luckyrobots-0.1.70.dist-info}/WHEEL +1 -1
- luckyrobots/core/luckyrobots.py +0 -628
- luckyrobots/core/manager.py +0 -236
- luckyrobots/core/models.py +0 -68
- luckyrobots/core/node.py +0 -273
- luckyrobots/message/__init__.py +0 -18
- luckyrobots/message/pubsub.py +0 -145
- luckyrobots/message/srv/client.py +0 -81
- luckyrobots/message/srv/service.py +0 -135
- luckyrobots/message/srv/types.py +0 -83
- luckyrobots/message/transporter.py +0 -427
- luckyrobots/utils/event_loop.py +0 -94
- luckyrobots/utils/sim_manager.py +0 -413
- luckyrobots-0.1.68.dist-info/METADATA +0 -253
- luckyrobots-0.1.68.dist-info/RECORD +0 -24
- {luckyrobots-0.1.68.dist-info → luckyrobots-0.1.70.dist-info}/licenses/LICENSE +0 -0
luckyrobots/utils/sim_manager.py
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import platform
|
|
3
|
-
import subprocess
|
|
4
|
-
import tempfile
|
|
5
|
-
import threading
|
|
6
|
-
import time
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
from typing import Optional
|
|
10
|
-
|
|
11
|
-
if not os.getenv("PYTEST_CURRENT_TEST") and not os.getenv("LUCKYROBOTS_NO_LOGS"):
|
|
12
|
-
logging.basicConfig(
|
|
13
|
-
level=logging.INFO,
|
|
14
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
15
|
-
)
|
|
16
|
-
logger = logging.getLogger("luckyworld")
|
|
17
|
-
|
|
18
|
-
LOCK_FILE = os.path.join(tempfile.gettempdir(), "luckyworld_lock")
|
|
19
|
-
_process = None
|
|
20
|
-
_monitor_thread = None
|
|
21
|
-
_shutdown_event = threading.Event()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def remove_lock_file() -> None:
|
|
25
|
-
"""Remove the lock file"""
|
|
26
|
-
try:
|
|
27
|
-
if os.path.exists(LOCK_FILE):
|
|
28
|
-
os.remove(LOCK_FILE)
|
|
29
|
-
logger.debug("Lock file removed successfully")
|
|
30
|
-
else:
|
|
31
|
-
logger.debug("Lock file doesn't exist, nothing to remove")
|
|
32
|
-
except Exception as e:
|
|
33
|
-
logger.error(f"Error removing lock file: {e}")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def monitor_process():
|
|
37
|
-
"""Monitor the LuckyWorld process and handle its termination"""
|
|
38
|
-
global _process
|
|
39
|
-
try:
|
|
40
|
-
while not _shutdown_event.is_set():
|
|
41
|
-
if _process is None or _process.poll() is not None:
|
|
42
|
-
logger.info("LuckyWorld process has terminated")
|
|
43
|
-
# Don't kill the main process, just break out of monitoring
|
|
44
|
-
break
|
|
45
|
-
time.sleep(1)
|
|
46
|
-
except Exception as e:
|
|
47
|
-
logger.error(f"Error in process monitor: {e}")
|
|
48
|
-
finally:
|
|
49
|
-
# Ensure cleanup happens when monitoring stops
|
|
50
|
-
if not _shutdown_event.is_set():
|
|
51
|
-
remove_lock_file()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def is_luckyworld_running() -> bool:
|
|
55
|
-
"""Check if LuckyWorld is running by checking the lock file"""
|
|
56
|
-
return os.path.exists(LOCK_FILE)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def create_lock_file(pid: int) -> None:
|
|
60
|
-
"""Create a lock file with the process ID"""
|
|
61
|
-
with open(LOCK_FILE, "w") as f:
|
|
62
|
-
f.write(str(pid))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def find_luckyworld_executable() -> Optional[str]:
|
|
66
|
-
"""Automatically find LuckyWorld executable in common locations"""
|
|
67
|
-
is_wsl = "microsoft" in platform.uname().release.lower()
|
|
68
|
-
|
|
69
|
-
# 1. Check environment variable first (highest priority)
|
|
70
|
-
env_path = os.environ.get("LUCKYWORLD_PATH")
|
|
71
|
-
if env_path:
|
|
72
|
-
logger.info(f"Using LUCKYWORLD_PATH environment variable: {env_path}")
|
|
73
|
-
if os.path.exists(env_path):
|
|
74
|
-
return env_path
|
|
75
|
-
else:
|
|
76
|
-
logger.warning(f"LUCKYWORLD_PATH points to non-existent file: {env_path}")
|
|
77
|
-
|
|
78
|
-
# 2. Check LUCKYWORLD_HOME environment variable
|
|
79
|
-
env_home = os.environ.get("LUCKYWORLD_HOME")
|
|
80
|
-
if env_home:
|
|
81
|
-
logger.info(f"Using LUCKYWORLD_HOME environment variable: {env_home}")
|
|
82
|
-
if platform.system() == "Linux" and not is_wsl:
|
|
83
|
-
env_executable = os.path.join(env_home, "LuckyWorld.sh")
|
|
84
|
-
elif platform.system() == "Darwin":
|
|
85
|
-
env_executable = os.path.join(
|
|
86
|
-
env_home, "LuckyWorld.app", "Contents", "MacOS", "LuckyWorld"
|
|
87
|
-
)
|
|
88
|
-
else:
|
|
89
|
-
env_executable = os.path.join(env_home, "LuckyWorldV2.exe")
|
|
90
|
-
|
|
91
|
-
if os.path.exists(env_executable):
|
|
92
|
-
return env_executable
|
|
93
|
-
else:
|
|
94
|
-
logger.warning(
|
|
95
|
-
f"LUCKYWORLD_HOME does not contain executable: {env_executable}"
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
# 3. System installation paths
|
|
99
|
-
if platform.system() == "Linux" and not is_wsl:
|
|
100
|
-
system_paths = [
|
|
101
|
-
"/opt/LuckyWorld/LuckyWorld.sh",
|
|
102
|
-
"/usr/local/bin/LuckyWorld.sh",
|
|
103
|
-
os.path.expanduser("~/.local/share/LuckyWorld/LuckyWorld.sh"),
|
|
104
|
-
os.path.expanduser("~/LuckyWorld/LuckyWorld.sh"),
|
|
105
|
-
]
|
|
106
|
-
elif platform.system() == "Darwin":
|
|
107
|
-
system_paths = [
|
|
108
|
-
"/Applications/LuckyWorld/LuckyWorld.app/Contents/MacOS/LuckyWorld",
|
|
109
|
-
os.path.expanduser(
|
|
110
|
-
"~/Applications/LuckyWorld/LuckyWorld.app/Contents/MacOS/LuckyWorld"
|
|
111
|
-
),
|
|
112
|
-
]
|
|
113
|
-
else: # Windows or WSL2
|
|
114
|
-
system_paths = [
|
|
115
|
-
"C:\\Program Files\\LuckyWorld\\LuckyWorldV2.exe",
|
|
116
|
-
"C:\\Program Files (x86)\\LuckyWorld\\LuckyWorldV2.exe",
|
|
117
|
-
os.path.expanduser("~\\AppData\\Local\\LuckyWorld\\LuckyWorldV2.exe"),
|
|
118
|
-
]
|
|
119
|
-
|
|
120
|
-
if is_wsl:
|
|
121
|
-
system_paths.extend(
|
|
122
|
-
[
|
|
123
|
-
"/mnt/c/Program Files/LuckyWorld/LuckyWorldV2.exe",
|
|
124
|
-
"/mnt/c/Program Files (x86)/LuckyWorld/LuckyWorldV2.exe",
|
|
125
|
-
]
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
for path in system_paths:
|
|
129
|
-
if os.path.exists(path):
|
|
130
|
-
logger.info(f"Found LuckyWorld at: {path}")
|
|
131
|
-
return path
|
|
132
|
-
|
|
133
|
-
return None
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def launch_luckyworld(
|
|
137
|
-
scene: str = "ArmLevel",
|
|
138
|
-
robot: str = "so100",
|
|
139
|
-
task: Optional[str] = None,
|
|
140
|
-
debug: bool = False,
|
|
141
|
-
executable_path: Optional[str] = None,
|
|
142
|
-
headless: bool = False,
|
|
143
|
-
windowed: bool = True,
|
|
144
|
-
verbose: bool = False,
|
|
145
|
-
) -> bool:
|
|
146
|
-
"""Launch LuckyWorld with simplified parameters"""
|
|
147
|
-
global _process, _monitor_thread
|
|
148
|
-
|
|
149
|
-
# Check if already running
|
|
150
|
-
if is_luckyworld_running():
|
|
151
|
-
logger.error(
|
|
152
|
-
"LuckyWorld is already running. \
|
|
153
|
-
Stop the existing instance or remove the luckyworld lock file located at /tmp/luckyworld_lock."
|
|
154
|
-
)
|
|
155
|
-
return False
|
|
156
|
-
|
|
157
|
-
# Find executable if not provided
|
|
158
|
-
if executable_path is None:
|
|
159
|
-
executable_path = find_luckyworld_executable()
|
|
160
|
-
if executable_path is None:
|
|
161
|
-
logger.error(
|
|
162
|
-
"Could not find LuckyWorld executable. Please provide the path manually."
|
|
163
|
-
)
|
|
164
|
-
logger.info("You can set the path using environment variables:")
|
|
165
|
-
logger.info(" LUCKYWORLD_PATH=/full/path/to/LuckyWorldV2.exe")
|
|
166
|
-
logger.info(" LUCKYWORLD_HOME=/path/to/luckyworld/directory")
|
|
167
|
-
logger.info("Or check these common locations:")
|
|
168
|
-
logger.info(" Development: Builds/Windows/LuckyWorldV2.exe")
|
|
169
|
-
logger.info(" Windows: C:\\Program Files\\LuckyWorld\\LuckyWorldV2.exe")
|
|
170
|
-
logger.info(" WSL2: /mnt/c/Program Files/LuckyWorld/LuckyWorldV2.exe")
|
|
171
|
-
return False
|
|
172
|
-
|
|
173
|
-
if not os.path.exists(executable_path):
|
|
174
|
-
logger.error(f"Executable not found at: {executable_path}")
|
|
175
|
-
return False
|
|
176
|
-
|
|
177
|
-
try:
|
|
178
|
-
# Set execute permissions on Unix systems
|
|
179
|
-
if platform.system() != "Windows":
|
|
180
|
-
os.chmod(executable_path, 0o755)
|
|
181
|
-
|
|
182
|
-
logger.info(f"Launching LuckyWorld: {executable_path}")
|
|
183
|
-
|
|
184
|
-
# Build command
|
|
185
|
-
command = [executable_path]
|
|
186
|
-
command.append(f"-Scene={scene}")
|
|
187
|
-
command.append(f"-Robot={robot}")
|
|
188
|
-
|
|
189
|
-
if task:
|
|
190
|
-
command.append(f"-Task={task}")
|
|
191
|
-
|
|
192
|
-
if debug:
|
|
193
|
-
command.append("-Debug")
|
|
194
|
-
|
|
195
|
-
if headless:
|
|
196
|
-
command.append("-Headless")
|
|
197
|
-
else:
|
|
198
|
-
if windowed:
|
|
199
|
-
command.append("-windowed")
|
|
200
|
-
else:
|
|
201
|
-
command.append("-fullscreen")
|
|
202
|
-
|
|
203
|
-
command.append("-Realtime")
|
|
204
|
-
|
|
205
|
-
logger.info(f"Command: {' '.join(command)}")
|
|
206
|
-
|
|
207
|
-
# Launch process
|
|
208
|
-
if platform.system() == "Windows":
|
|
209
|
-
DETACHED_PROCESS = 0x00000008
|
|
210
|
-
_process = subprocess.Popen(
|
|
211
|
-
command,
|
|
212
|
-
creationflags=DETACHED_PROCESS,
|
|
213
|
-
close_fds=True,
|
|
214
|
-
stdout=None if verbose else subprocess.DEVNULL,
|
|
215
|
-
stderr=None if verbose else subprocess.DEVNULL,
|
|
216
|
-
)
|
|
217
|
-
else:
|
|
218
|
-
_process = subprocess.Popen(
|
|
219
|
-
command,
|
|
220
|
-
start_new_session=True,
|
|
221
|
-
stdout=None if verbose else subprocess.DEVNULL,
|
|
222
|
-
stderr=None if verbose else subprocess.DEVNULL,
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
# Start monitoring
|
|
226
|
-
_shutdown_event.clear()
|
|
227
|
-
_monitor_thread = threading.Thread(target=monitor_process, daemon=True)
|
|
228
|
-
_monitor_thread.start()
|
|
229
|
-
|
|
230
|
-
create_lock_file(_process.pid)
|
|
231
|
-
|
|
232
|
-
logger.info(f"LuckyWorld started successfully (PID: {_process.pid})")
|
|
233
|
-
logger.info(f"Scene: {scene}, Robot: {robot}, Task: {task or 'None'}")
|
|
234
|
-
|
|
235
|
-
return True
|
|
236
|
-
|
|
237
|
-
except Exception as e:
|
|
238
|
-
logger.error(f"Failed to launch LuckyWorld: {e}")
|
|
239
|
-
stop_luckyworld()
|
|
240
|
-
return False
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def kill_processes():
|
|
244
|
-
"""Kill all LuckyWorld processes"""
|
|
245
|
-
system = platform.system()
|
|
246
|
-
is_wsl = "microsoft" in platform.uname().release.lower()
|
|
247
|
-
|
|
248
|
-
if is_wsl:
|
|
249
|
-
return _kill_wsl_processes()
|
|
250
|
-
elif system == "Windows":
|
|
251
|
-
return _kill_windows_processes()
|
|
252
|
-
elif system == "Darwin":
|
|
253
|
-
return _kill_macos_processes()
|
|
254
|
-
else:
|
|
255
|
-
return _kill_linux_processes()
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def _kill_wsl_processes():
|
|
259
|
-
try:
|
|
260
|
-
result = subprocess.run(
|
|
261
|
-
[
|
|
262
|
-
"/mnt/c/Windows/System32/taskkill.exe",
|
|
263
|
-
"/F",
|
|
264
|
-
"/IM",
|
|
265
|
-
"LuckyWorldV2.exe", # NOTE: Make sure this is the name of the LuckyWorld executable
|
|
266
|
-
],
|
|
267
|
-
capture_output=True,
|
|
268
|
-
text=True,
|
|
269
|
-
timeout=10,
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
if result.returncode == 0:
|
|
273
|
-
return True
|
|
274
|
-
elif result.returncode == 128: # Process not found
|
|
275
|
-
logger.error("No LuckyWorld processes found running")
|
|
276
|
-
return False
|
|
277
|
-
else:
|
|
278
|
-
logger.warning(
|
|
279
|
-
f"taskkill failed with code {result.returncode}: {result.stderr}"
|
|
280
|
-
)
|
|
281
|
-
return False
|
|
282
|
-
|
|
283
|
-
except subprocess.TimeoutExpired:
|
|
284
|
-
logger.error("taskkill command timed out")
|
|
285
|
-
return False
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.error(f"Failed to kill processes: {e}")
|
|
288
|
-
return False
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def _kill_windows_processes():
|
|
292
|
-
"""Windows-specific process killing"""
|
|
293
|
-
try:
|
|
294
|
-
# Kill LuckyWorldV2.exe
|
|
295
|
-
result = subprocess.run(
|
|
296
|
-
["taskkill", "/F", "/IM", "LuckyWorldV2.exe"],
|
|
297
|
-
capture_output=True,
|
|
298
|
-
text=True,
|
|
299
|
-
timeout=10,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
if result.returncode == 0:
|
|
303
|
-
logger.info("Successfully killed LuckyWorldV2.exe processes")
|
|
304
|
-
return True
|
|
305
|
-
elif result.returncode == 128: # Process not found
|
|
306
|
-
logger.info("No LuckyWorldV2.exe processes found")
|
|
307
|
-
return True
|
|
308
|
-
else:
|
|
309
|
-
logger.warning(f"taskkill failed: {result.stderr}")
|
|
310
|
-
return False
|
|
311
|
-
|
|
312
|
-
except subprocess.TimeoutExpired:
|
|
313
|
-
logger.error("taskkill command timed out")
|
|
314
|
-
return False
|
|
315
|
-
except FileNotFoundError:
|
|
316
|
-
logger.error("taskkill command not found")
|
|
317
|
-
return False
|
|
318
|
-
except Exception as e:
|
|
319
|
-
logger.error(f"Failed to kill Windows processes: {e}")
|
|
320
|
-
return False
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def _kill_macos_processes():
|
|
324
|
-
"""macOS-specific process killing"""
|
|
325
|
-
try:
|
|
326
|
-
# Kill LuckyWorld processes
|
|
327
|
-
result = subprocess.run(
|
|
328
|
-
["pkill", "-f", "LuckyWorld"],
|
|
329
|
-
capture_output=True,
|
|
330
|
-
text=True,
|
|
331
|
-
timeout=10,
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
if result.returncode == 0:
|
|
335
|
-
logger.info("Successfully killed LuckyWorld processes")
|
|
336
|
-
return True
|
|
337
|
-
elif result.returncode == 1: # No processes found
|
|
338
|
-
logger.info("No LuckyWorld processes found")
|
|
339
|
-
return True
|
|
340
|
-
else:
|
|
341
|
-
logger.warning(f"pkill failed: {result.stderr}")
|
|
342
|
-
return False
|
|
343
|
-
|
|
344
|
-
except subprocess.TimeoutExpired:
|
|
345
|
-
logger.error("pkill command timed out")
|
|
346
|
-
return False
|
|
347
|
-
except FileNotFoundError:
|
|
348
|
-
logger.error("pkill command not found")
|
|
349
|
-
return False
|
|
350
|
-
except Exception as e:
|
|
351
|
-
logger.error(f"Failed to kill macOS processes: {e}")
|
|
352
|
-
return False
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _kill_linux_processes():
|
|
356
|
-
"""Linux-specific process killing"""
|
|
357
|
-
try:
|
|
358
|
-
# Kill LuckyWorld processes
|
|
359
|
-
result = subprocess.run(
|
|
360
|
-
["pkill", "-f", "LuckyWorld"],
|
|
361
|
-
capture_output=True,
|
|
362
|
-
text=True,
|
|
363
|
-
timeout=10,
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
if result.returncode == 0:
|
|
367
|
-
logger.info("Successfully killed LuckyWorld processes")
|
|
368
|
-
return True
|
|
369
|
-
elif result.returncode == 1: # No processes found
|
|
370
|
-
logger.info("No LuckyWorld processes found")
|
|
371
|
-
return True
|
|
372
|
-
else:
|
|
373
|
-
logger.warning(f"pkill failed: {result.stderr}")
|
|
374
|
-
return False
|
|
375
|
-
|
|
376
|
-
except subprocess.TimeoutExpired:
|
|
377
|
-
logger.error("pkill command timed out")
|
|
378
|
-
return False
|
|
379
|
-
except FileNotFoundError:
|
|
380
|
-
logger.error("pkill command not found")
|
|
381
|
-
return False
|
|
382
|
-
except Exception as e:
|
|
383
|
-
logger.error(f"Failed to kill Linux processes: {e}")
|
|
384
|
-
return False
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def stop_luckyworld() -> bool:
|
|
388
|
-
"""Stop the running LuckyWorld instance"""
|
|
389
|
-
global _process
|
|
390
|
-
|
|
391
|
-
if not is_luckyworld_running():
|
|
392
|
-
logger.info("LuckyWorld is not running")
|
|
393
|
-
return True
|
|
394
|
-
|
|
395
|
-
try:
|
|
396
|
-
if _process:
|
|
397
|
-
logger.info("Stopping LuckyWorld...")
|
|
398
|
-
|
|
399
|
-
try:
|
|
400
|
-
_process.terminate()
|
|
401
|
-
_process.wait(timeout=5)
|
|
402
|
-
except subprocess.TimeoutExpired:
|
|
403
|
-
logger.info("Graceful termination timeout, using kill_processes...")
|
|
404
|
-
except Exception:
|
|
405
|
-
logger.info("Graceful termination failed, using kill_processes...")
|
|
406
|
-
|
|
407
|
-
kill_processes()
|
|
408
|
-
|
|
409
|
-
remove_lock_file()
|
|
410
|
-
return True
|
|
411
|
-
except Exception as e:
|
|
412
|
-
logger.error(f"Error stopping LuckyWorld: {e}")
|
|
413
|
-
return False
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: luckyrobots
|
|
3
|
-
Version: 0.1.68
|
|
4
|
-
Summary: Robotics-AI Training in Hyperrealistic Game Environments
|
|
5
|
-
Project-URL: Homepage, https://github.com/luckyrobots/luckyrobots
|
|
6
|
-
Project-URL: Bug Tracker, https://github.com/luckyrobots/luckyrobots/issues
|
|
7
|
-
Author-email: Devrim Yasar <braces.verbose03@icloud.com>, Ethan Clark <ethan@luckyrobots.com>
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: asyncio
|
|
14
|
-
Requires-Dist: beautifulsoup4
|
|
15
|
-
Requires-Dist: fastapi
|
|
16
|
-
Requires-Dist: msgpack
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist: opencv-python
|
|
19
|
-
Requires-Dist: packaging
|
|
20
|
-
Requires-Dist: psutil
|
|
21
|
-
Requires-Dist: pydantic
|
|
22
|
-
Requires-Dist: pyyaml
|
|
23
|
-
Requires-Dist: requests
|
|
24
|
-
Requires-Dist: tqdm
|
|
25
|
-
Requires-Dist: ultralytics
|
|
26
|
-
Requires-Dist: uvicorn[standard]
|
|
27
|
-
Requires-Dist: watchdog
|
|
28
|
-
Requires-Dist: websocket-client
|
|
29
|
-
Requires-Dist: websockets
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: black; extra == 'dev'
|
|
32
|
-
Requires-Dist: build; extra == 'dev'
|
|
33
|
-
Requires-Dist: pre-commit; extra == 'dev'
|
|
34
|
-
Requires-Dist: twine; extra == 'dev'
|
|
35
|
-
Description-Content-Type: text/markdown
|
|
36
|
-
|
|
37
|
-
<p align="center">
|
|
38
|
-
<img width="384" alt="Default_Logo_Horizontal@2x" src="https://github.com/user-attachments/assets/ae6ad53a-741e-4e7a-94cb-5a46a8e81398" />
|
|
39
|
-
</p>
|
|
40
|
-
|
|
41
|
-
<p align="center">
|
|
42
|
-
Infinite synthetic data generation for embodied AI
|
|
43
|
-
</p>
|
|
44
|
-
|
|
45
|
-
<!--
|
|
46
|
-
<p align="center">
|
|
47
|
-
<a href="https://luckyrobots.github.io/ReleaseV0.1/" target="_blank">
|
|
48
|
-
<img src="https://img.shields.io/badge/Explore_V0.1-Get_Started-grey?style=for-the-badge&labelColor=grey&color=blue" alt="Get Started" />
|
|
49
|
-
</a>
|
|
50
|
-
</p>
|
|
51
|
-
-->
|
|
52
|
-
|
|
53
|
-
<div align="center">
|
|
54
|
-
|
|
55
|
-
[](https://pypi.org/project/luckyrobots/)
|
|
56
|
-
[](https://luckyrobots.readthedocs.io)
|
|
57
|
-
[](https://opensource.org/licenses/MIT)
|
|
58
|
-
[](https://pypi.org/project/luckyrobots/)
|
|
59
|
-
[](https://pypi.org/project/luckyrobots/)
|
|
60
|
-
[](https://discord.gg/5CH3wx3tAs)
|
|
61
|
-
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
https://github.com/user-attachments/assets/0ab2953d-b188-4af7-a225-71decdd2378c
|
|
65
|
-
|
|
66
|
-
# Lucky Robots
|
|
67
|
-
|
|
68
|
-
Hyperrealistic robotics simulation framework with Python API for embodied AI training and testing.
|
|
69
|
-
|
|
70
|
-
<p align="center">
|
|
71
|
-
<img width="49%" alt="Bedroom environment in Lucky World" src="https://github.com/user-attachments/assets/279a7864-9a8b-453e-8567-3a174f5db8ab" />
|
|
72
|
-
<img width="49%" alt="Open floor plan in Lucky World" src="https://github.com/user-attachments/assets/68c72b97-98ab-42b0-a065-8a4247b014c7" />
|
|
73
|
-
</p>
|
|
74
|
-
|
|
75
|
-
## Quick Start
|
|
76
|
-
|
|
77
|
-
1. **Download Lucky World Executable from our [releases page](https://github.com/luckyrobots/luckyrobots/releases/latest) and add its path to your system variables**
|
|
78
|
-
```bash
|
|
79
|
-
# Set environment variables (choose one method):
|
|
80
|
-
|
|
81
|
-
# Method 1: Set LUCKYWORLD_PATH directly to the executable
|
|
82
|
-
export LUCKYWORLD_PATH=/path/to/LuckyWorld.exe # Windows
|
|
83
|
-
export LUCKYWORLD_PATH=/path/to/LuckyWorld # Linux/Mac
|
|
84
|
-
|
|
85
|
-
# Method 2: Set LUCKYWORLD_HOME to the directory containing the executable
|
|
86
|
-
export LUCKYWORLD_HOME=/path/to/luckyworld/directory
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
2. **Create conda environment (recommended)**
|
|
90
|
-
```bash
|
|
91
|
-
conda create -n luckyrobots python
|
|
92
|
-
conda activate luckyrobots
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
3. **Install**
|
|
96
|
-
```bash
|
|
97
|
-
pip install luckyrobots
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
4. **Run Example**
|
|
101
|
-
```bash
|
|
102
|
-
git clone https://github.com/luckyrobots/luckyrobots.git
|
|
103
|
-
cd luckyrobots/examples
|
|
104
|
-
python controller.py
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Basic Usage
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
from luckyrobots import LuckyRobots, Node
|
|
111
|
-
import numpy as np
|
|
112
|
-
|
|
113
|
-
# Create controller node
|
|
114
|
-
class RobotController(Node):
|
|
115
|
-
async def control_loop(self):
|
|
116
|
-
# Reset environment
|
|
117
|
-
reset_response = await self.reset_client.call(Reset.Request())
|
|
118
|
-
|
|
119
|
-
# Send actions
|
|
120
|
-
actuator_values = np.array([0.1, 0.2, -0.1, 0.0, 0.5, 1.0])
|
|
121
|
-
step_response = await self.step_client.call(Step.Request(actuator_values=actuator_values))
|
|
122
|
-
|
|
123
|
-
# Access observations
|
|
124
|
-
observation = step_response.observation
|
|
125
|
-
joint_states = observation.observation_state
|
|
126
|
-
camera_data = observation.observation_cameras
|
|
127
|
-
|
|
128
|
-
# Start simulation
|
|
129
|
-
luckyrobots = LuckyRobots()
|
|
130
|
-
controller = RobotController()
|
|
131
|
-
luckyrobots.register_node(controller)
|
|
132
|
-
luckyrobots.start(scene="kitchen", robot="so100", task="pickandplace")
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Available Robots & Environments
|
|
136
|
-
|
|
137
|
-
### Robots
|
|
138
|
-
- **so100**: 6-DOF manipulator with gripper
|
|
139
|
-
- **stretch_v1**: Mobile manipulator
|
|
140
|
-
- **dji300**: Quadcopter drone
|
|
141
|
-
|
|
142
|
-
### Scenes
|
|
143
|
-
- **kitchen**: Residential kitchen environment
|
|
144
|
-
- **loft**: Open floor plan apartment
|
|
145
|
-
- **drone_flight**: Outdoor flight area
|
|
146
|
-
|
|
147
|
-
### Tasks
|
|
148
|
-
- **pickandplace**: Object manipulation
|
|
149
|
-
- **navigation**: Path planning and movement
|
|
150
|
-
|
|
151
|
-
## API Reference
|
|
152
|
-
|
|
153
|
-
### Core Classes
|
|
154
|
-
|
|
155
|
-
**LuckyRobots**: Main simulation manager
|
|
156
|
-
- `start(scene, robot, task, observation_type)`: Initialize simulation
|
|
157
|
-
- `register_node(node)`: Add controller node
|
|
158
|
-
- `spin()`: Run main loop
|
|
159
|
-
|
|
160
|
-
**Node**: Base class for robot controllers
|
|
161
|
-
- `create_client(service_type, service_name)`: Create service client
|
|
162
|
-
- `create_service(service_type, service_name, handler)`: Create service server
|
|
163
|
-
|
|
164
|
-
### Services
|
|
165
|
-
|
|
166
|
-
**Reset**: Reset robot to initial state
|
|
167
|
-
```python
|
|
168
|
-
request = Reset.Request(seed=42, options={})
|
|
169
|
-
response = await reset_client.call(request)
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**Step**: Send action and get observation
|
|
173
|
-
```python
|
|
174
|
-
request = Step.Request(actuator_values=[0.1, 0.2, -0.1])
|
|
175
|
-
response = await step_client.call(request)
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Observations
|
|
179
|
-
|
|
180
|
-
Access sensor data from step responses:
|
|
181
|
-
```python
|
|
182
|
-
# Joint positions and velocities
|
|
183
|
-
joint_states = response.observation.observation_state
|
|
184
|
-
|
|
185
|
-
# Camera images (RGB + depth)
|
|
186
|
-
for camera in response.observation.observation_cameras:
|
|
187
|
-
image = camera.image_data # numpy array
|
|
188
|
-
name = camera.camera_name # "head_cam", "hand_cam", etc.
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Command Line Interface
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
# Basic usage
|
|
195
|
-
python controller.py --robot so100 --scene kitchen --task pickandplace
|
|
196
|
-
|
|
197
|
-
# With camera display
|
|
198
|
-
python controller.py --show-camera --rate 30
|
|
199
|
-
|
|
200
|
-
# Custom host/port
|
|
201
|
-
python controller.py --host 192.168.1.100 --port 3001
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Configuration
|
|
205
|
-
|
|
206
|
-
Robot configurations are defined in `src/luckyrobots/config/robots.yaml`:
|
|
207
|
-
|
|
208
|
-
```yaml
|
|
209
|
-
so100:
|
|
210
|
-
action_space:
|
|
211
|
-
actuator_names: [shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper]
|
|
212
|
-
actuator_limits:
|
|
213
|
-
- name: shoulder_pan
|
|
214
|
-
lower: -2.2
|
|
215
|
-
upper: 2.2
|
|
216
|
-
available_scenes: [kitchen]
|
|
217
|
-
available_tasks: [pickandplace]
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Architecture
|
|
221
|
-
|
|
222
|
-
Lucky Robots uses a distributed node architecture:
|
|
223
|
-
|
|
224
|
-
- **Manager Node**: Central message routing
|
|
225
|
-
- **LuckyRobots Node**: Simulation interface
|
|
226
|
-
- **Controller Nodes**: User-defined robot controllers
|
|
227
|
-
- **WebSocket Transport**: Inter-node communication
|
|
228
|
-
- **Lucky World**: Physics simulation backend
|
|
229
|
-
|
|
230
|
-
## Development
|
|
231
|
-
|
|
232
|
-
### Setup Development Environment
|
|
233
|
-
```bash
|
|
234
|
-
git clone https://github.com/luckyrobots/luckyrobots.git
|
|
235
|
-
cd luckyrobots
|
|
236
|
-
pip install -e .
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Contributing
|
|
240
|
-
1. Fork the repository
|
|
241
|
-
2. Create a feature branch
|
|
242
|
-
3. Make changes and add tests
|
|
243
|
-
4. Submit a pull request
|
|
244
|
-
|
|
245
|
-
## License
|
|
246
|
-
|
|
247
|
-
MIT License - see [LICENSE](LICENSE) file.
|
|
248
|
-
|
|
249
|
-
## Support
|
|
250
|
-
|
|
251
|
-
- **Issues**: [GitHub Issues](https://github.com/luckyrobots/luckyrobots/issues)
|
|
252
|
-
- **Discussions**: [GitHub Discussions](https://github.com/luckyrobots/luckyrobots/discussions)
|
|
253
|
-
- **Discord**: [Community Server](https://discord.gg/5CH3wx3tAs)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
luckyrobots/__init__.py,sha256=yr8nMUVzAe8ohD7XNywTDwLX98HwjT4cagxARkld1HQ,426
|
|
2
|
-
luckyrobots/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
luckyrobots/config/robots.yaml,sha256=w0HwB35WRd7X0-hQRgtYQaRWmOk32-e6pMxT88aereE,1806
|
|
4
|
-
luckyrobots/core/luckyrobots.py,sha256=IhAtQoHExMLm6j7HrjUUe6o56IzEY2X0Nt3CPgL9_5g,23312
|
|
5
|
-
luckyrobots/core/manager.py,sha256=p9Ehs2SKLpBEEJf8hvTFUG_foPrQszgH6BUbIzBTtBo,9212
|
|
6
|
-
luckyrobots/core/models.py,sha256=fbAKpIOZM8D6rKxRgo1_9rioNa99bT_phCZ9xrwWFaE,2108
|
|
7
|
-
luckyrobots/core/node.py,sha256=lDKqAQSMEctjU-v4F9Lifz_ZsKG6wxsNuyRqtOWrQRA,10512
|
|
8
|
-
luckyrobots/message/__init__.py,sha256=7oM16w34XBc8xuKZh3rNRPnqHMjhyYYG0bqLS3j2PjQ,432
|
|
9
|
-
luckyrobots/message/pubsub.py,sha256=iPMhRCq7IASqSO8eXs8LwMUayJp-piYdPSc741a3qKs,5490
|
|
10
|
-
luckyrobots/message/transporter.py,sha256=5I1YCv8jlG-swM__ZTQ4L8BLmUfOSfs_3iLWXeprC6M,15815
|
|
11
|
-
luckyrobots/message/srv/client.py,sha256=wT8Q-cuu3aXeRmkO_8CTFSO9aJqtrzyyxBopyUl3HNw,2971
|
|
12
|
-
luckyrobots/message/srv/service.py,sha256=zmOFt6VBGUiy5btZo5Wh4-vr2oJfhzHjOV3hB2IzAFw,4495
|
|
13
|
-
luckyrobots/message/srv/types.py,sha256=j-VuYsum_98gQBj9-37vsHWC8PcvPd15UP-NkoEtPYw,1948
|
|
14
|
-
luckyrobots/utils/check_updates.py,sha256=5CpAe2jK1igjJVIoZKzRWgg9jwKsmiyEwqgldyek6H8,6439
|
|
15
|
-
luckyrobots/utils/download.py,sha256=UWQkB6yp-UsOK93pw0noHmv2rVLXcjY5JKd9EkKUG-c,3593
|
|
16
|
-
luckyrobots/utils/event_loop.py,sha256=r2sn7G9JHFKUri4aUnZ_1D82CTZnEsr3woLLKiDx8Dw,2638
|
|
17
|
-
luckyrobots/utils/helpers.py,sha256=4o3Jy0-otnVl0duqncoIuEufEo2AP9BDdgKSEYUOCYQ,2193
|
|
18
|
-
luckyrobots/utils/sim_manager.py,sha256=wQlfsmRk6ZdrZBdBwnth4UBlrcsLA7_oO_4VVvegcNU,13237
|
|
19
|
-
luckyrobots/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
luckyrobots/config/robots.yaml,sha256=w0HwB35WRd7X0-hQRgtYQaRWmOk32-e6pMxT88aereE,1806
|
|
21
|
-
luckyrobots-0.1.68.dist-info/METADATA,sha256=kQmpIJA3C-GiGUpe5ptDGDMShZm4W8YBcoz7u7oEj3Q,7683
|
|
22
|
-
luckyrobots-0.1.68.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
-
luckyrobots-0.1.68.dist-info/licenses/LICENSE,sha256=xsPYvRJPH_fW_sofTUknI_KvZOsD4-BqjSOTZqI6Nmw,1069
|
|
24
|
-
luckyrobots-0.1.68.dist-info/RECORD,,
|
|
File without changes
|