luckyrobots 0.1.68__py3-none-any.whl → 0.1.69__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 +193 -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.69.dist-info/METADATA +262 -0
- luckyrobots-0.1.69.dist-info/RECORD +44 -0
- {luckyrobots-0.1.68.dist-info → luckyrobots-0.1.69.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.69.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,42 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Download and update LuckyEngine executable files.
|
|
3
|
+
|
|
4
|
+
This module handles downloading updates and applying changes to the LuckyEngine binary.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
1
8
|
import os
|
|
2
9
|
import platform
|
|
3
|
-
import
|
|
4
|
-
import sys
|
|
5
|
-
import zipfile
|
|
6
|
-
from datetime import datetime
|
|
10
|
+
from typing import Optional
|
|
7
11
|
|
|
8
12
|
import requests
|
|
9
|
-
from bs4 import BeautifulSoup
|
|
10
13
|
from tqdm import tqdm
|
|
11
14
|
|
|
12
15
|
from .check_updates import check_updates
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
logger = logging.getLogger("luckyrobots.engine.download")
|
|
15
18
|
|
|
19
|
+
BASE_URL = "https://builds.luckyrobots.xyz/"
|
|
16
20
|
|
|
17
|
-
def get_base_url():
|
|
18
|
-
import requests
|
|
19
|
-
from requests.exceptions import RequestException
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return response.status_code == 200
|
|
25
|
-
except RequestException:
|
|
26
|
-
return False
|
|
22
|
+
def get_base_url() -> str:
|
|
23
|
+
"""
|
|
24
|
+
Get the base URL for downloads, checking local server first.
|
|
27
25
|
|
|
26
|
+
Returns:
|
|
27
|
+
Base URL string (local or remote).
|
|
28
|
+
"""
|
|
28
29
|
local_url = "http://192.168.1.148/builds"
|
|
29
30
|
remote_url = "https://builds.luckyrobots.xyz"
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
try:
|
|
33
|
+
response = requests.get(local_url, timeout=1)
|
|
34
|
+
if response.status_code == 200:
|
|
35
|
+
logger.info(f"Using local server: {local_url}")
|
|
36
|
+
return local_url
|
|
37
|
+
except requests.RequestException:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
logger.info(f"Using remote server: {remote_url}")
|
|
41
|
+
return remote_url
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_os_type() -> str:
|
|
45
|
+
"""
|
|
46
|
+
Get the operating system type as a string.
|
|
37
47
|
|
|
48
|
+
Returns:
|
|
49
|
+
"mac", "win", or "linux"
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If the OS is not supported.
|
|
53
|
+
"""
|
|
40
54
|
os_type = platform.system().lower()
|
|
41
55
|
if os_type == "darwin":
|
|
42
56
|
return "mac"
|
|
@@ -48,32 +62,40 @@ def get_os_type():
|
|
|
48
62
|
raise ValueError(f"Unsupported operating system: {os_type}")
|
|
49
63
|
|
|
50
64
|
|
|
51
|
-
def apply_changes(changes):
|
|
52
|
-
|
|
65
|
+
def apply_changes(changes: list[dict], binary_path: str = "./Binary") -> None:
|
|
66
|
+
"""
|
|
67
|
+
Apply changes by downloading new/modified files and deleting removed files.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
changes: List of change dictionaries with 'change_type', 'path', etc.
|
|
71
|
+
binary_path: Base path where binary files are stored.
|
|
72
|
+
"""
|
|
73
|
+
base_url = get_base_url()
|
|
74
|
+
os_type = get_os_type()
|
|
75
|
+
|
|
53
76
|
for item in changes:
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
change_type = item.get("change_type")
|
|
78
|
+
item_path = os.path.join(binary_path, item["path"])
|
|
79
|
+
|
|
80
|
+
if change_type in ["modified", "new_file"]:
|
|
56
81
|
if item.get("type") == "directory":
|
|
57
82
|
# Create the directory
|
|
58
|
-
item_path = os.path.join("./Binary", item["path"])
|
|
59
83
|
os.makedirs(item_path, exist_ok=True)
|
|
60
|
-
|
|
84
|
+
logger.debug(f"Created directory: {item_path}")
|
|
61
85
|
else:
|
|
62
86
|
# Handle file download with progress bar
|
|
63
|
-
os_type = get_os_type()
|
|
64
87
|
file_url = f"{base_url}{os_type}/{item['path']}"
|
|
65
88
|
|
|
66
89
|
# Ensure the directory exists
|
|
67
|
-
item_path = os.path.join("./Binary", item["path"])
|
|
68
90
|
item_dir = os.path.dirname(item_path)
|
|
69
91
|
os.makedirs(item_dir, exist_ok=True)
|
|
70
92
|
|
|
71
93
|
# Download the file with progress bar
|
|
72
94
|
try:
|
|
73
|
-
response = requests.get(file_url, stream=True)
|
|
95
|
+
response = requests.get(file_url, stream=True, timeout=30)
|
|
74
96
|
response.raise_for_status()
|
|
75
97
|
total_size = int(response.headers.get("content-length", 0))
|
|
76
|
-
|
|
98
|
+
|
|
77
99
|
with open(item_path, "wb") as f, tqdm(
|
|
78
100
|
desc=f"{item['path'][:8]}...{item['path'][-16:]}",
|
|
79
101
|
total=total_size,
|
|
@@ -85,19 +107,19 @@ def apply_changes(changes):
|
|
|
85
107
|
for data in response.iter_content(chunk_size=1024):
|
|
86
108
|
size = f.write(data)
|
|
87
109
|
progress_bar.update(size)
|
|
88
|
-
|
|
110
|
+
|
|
111
|
+
logger.debug(f"Downloaded: {item_path}")
|
|
89
112
|
except requests.RequestException as e:
|
|
90
|
-
|
|
113
|
+
logger.error(f"Error downloading {item_path}: {e}")
|
|
91
114
|
|
|
92
|
-
elif
|
|
115
|
+
elif change_type == "deleted":
|
|
93
116
|
# Delete the file or directory
|
|
94
|
-
item_path = os.path.join("./Binary", item["path"])
|
|
95
117
|
try:
|
|
96
118
|
if os.path.isdir(item_path):
|
|
97
119
|
os.rmdir(item_path)
|
|
98
|
-
|
|
120
|
+
logger.debug(f"Deleted directory: {item_path}")
|
|
99
121
|
else:
|
|
100
122
|
os.remove(item_path)
|
|
101
|
-
|
|
123
|
+
logger.debug(f"Deleted file: {item_path}")
|
|
102
124
|
except OSError as e:
|
|
103
|
-
|
|
125
|
+
logger.error(f"Error deleting {item_path}: {e}")
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LuckyEngine engine lifecycle management.
|
|
3
|
+
|
|
4
|
+
This module handles launching, stopping, and managing the LuckyEngine executable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("luckyrobots.luckyengine")
|
|
17
|
+
|
|
18
|
+
# Module-level state
|
|
19
|
+
LOCK_FILE = os.path.join(tempfile.gettempdir(), "luckyengine_lock")
|
|
20
|
+
_process: Optional[subprocess.Popen] = None
|
|
21
|
+
_monitor_thread: Optional[threading.Thread] = None
|
|
22
|
+
_shutdown_event = threading.Event()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# Public API
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_luckyengine_executable() -> Optional[str]:
|
|
31
|
+
"""
|
|
32
|
+
Automatically find LuckyEngine executable in common locations.
|
|
33
|
+
|
|
34
|
+
Checks in order:
|
|
35
|
+
1. LUCKYENGINE_PATH environment variable
|
|
36
|
+
2. LUCKYENGINE_HOME environment variable
|
|
37
|
+
3. System installation paths
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Path to executable if found, None otherwise.
|
|
41
|
+
"""
|
|
42
|
+
is_wsl = "microsoft" in platform.uname().release.lower()
|
|
43
|
+
|
|
44
|
+
# 1. Check LUCKYENGINE_PATH environment variable (highest priority)
|
|
45
|
+
env_path = os.environ.get("LUCKYENGINE_PATH")
|
|
46
|
+
if env_path:
|
|
47
|
+
logger.info(f"Using LUCKYENGINE_PATH environment variable: {env_path}")
|
|
48
|
+
if os.path.exists(env_path):
|
|
49
|
+
return env_path
|
|
50
|
+
logger.warning(f"LUCKYENGINE_PATH points to non-existent file: {env_path}")
|
|
51
|
+
|
|
52
|
+
# 2. Check LUCKYENGINE_HOME environment variable
|
|
53
|
+
env_home = os.environ.get("LUCKYENGINE_HOME")
|
|
54
|
+
if env_home:
|
|
55
|
+
logger.info(f"Using LUCKYENGINE_HOME environment variable: {env_home}")
|
|
56
|
+
executable = _get_executable_for_platform(env_home, "LuckyEngine", is_wsl)
|
|
57
|
+
if executable and os.path.exists(executable):
|
|
58
|
+
return executable
|
|
59
|
+
logger.warning(f"LUCKYENGINE_HOME does not contain executable: {executable}")
|
|
60
|
+
|
|
61
|
+
# 3. System installation paths
|
|
62
|
+
system_paths = _get_system_paths(is_wsl)
|
|
63
|
+
for path in system_paths:
|
|
64
|
+
if os.path.exists(path):
|
|
65
|
+
logger.info(f"Found LuckyEngine at: {path}")
|
|
66
|
+
return path
|
|
67
|
+
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_luckyengine_running() -> bool:
|
|
72
|
+
"""
|
|
73
|
+
Check if LuckyEngine is currently running.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
True if lock file exists, False otherwise.
|
|
77
|
+
"""
|
|
78
|
+
return os.path.exists(LOCK_FILE)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def launch_luckyengine(
|
|
82
|
+
scene: str = "ArmLevel",
|
|
83
|
+
robot: str = "so100",
|
|
84
|
+
task: Optional[str] = None,
|
|
85
|
+
executable_path: Optional[str] = None,
|
|
86
|
+
headless: bool = False,
|
|
87
|
+
windowed: bool = True,
|
|
88
|
+
verbose: bool = False,
|
|
89
|
+
) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Launch LuckyEngine with the specified parameters.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
scene: Scene name to load.
|
|
95
|
+
robot: Robot name to spawn.
|
|
96
|
+
task: Optional task name.
|
|
97
|
+
executable_path: Path to executable (auto-detected if None).
|
|
98
|
+
headless: Run without rendering.
|
|
99
|
+
windowed: Run in windowed mode (vs fullscreen).
|
|
100
|
+
verbose: Show engine output.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
True if launch succeeded, False otherwise.
|
|
104
|
+
"""
|
|
105
|
+
global _process, _monitor_thread
|
|
106
|
+
|
|
107
|
+
# Check if already running
|
|
108
|
+
if is_luckyengine_running():
|
|
109
|
+
logger.error(
|
|
110
|
+
"LuckyEngine is already running. "
|
|
111
|
+
"Stop the existing instance or remove the lock file at "
|
|
112
|
+
f"{LOCK_FILE}"
|
|
113
|
+
)
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# Find executable if not provided
|
|
117
|
+
if executable_path is None:
|
|
118
|
+
executable_path = find_luckyengine_executable()
|
|
119
|
+
if executable_path is None:
|
|
120
|
+
logger.error(
|
|
121
|
+
"Could not find LuckyEngine executable. "
|
|
122
|
+
"Please provide the path manually."
|
|
123
|
+
)
|
|
124
|
+
logger.info("You can set the path using environment variables:")
|
|
125
|
+
logger.info(" LUCKYENGINE_PATH=/full/path/to/LuckyEngine.exe")
|
|
126
|
+
logger.info(" LUCKYENGINE_HOME=/path/to/luckyengine/directory")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
if not os.path.exists(executable_path):
|
|
130
|
+
logger.error(f"Executable not found at: {executable_path}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
# Set execute permissions on Unix systems
|
|
135
|
+
if platform.system() != "Windows":
|
|
136
|
+
os.chmod(executable_path, 0o755)
|
|
137
|
+
|
|
138
|
+
logger.info(f"Launching LuckyEngine: {executable_path}")
|
|
139
|
+
|
|
140
|
+
# Build command
|
|
141
|
+
command = [executable_path]
|
|
142
|
+
command.append(f"-Scene={scene}")
|
|
143
|
+
command.append(f"-Robot={robot}")
|
|
144
|
+
|
|
145
|
+
if task:
|
|
146
|
+
command.append(f"-Task={task}")
|
|
147
|
+
|
|
148
|
+
if headless:
|
|
149
|
+
command.append("-Headless")
|
|
150
|
+
else:
|
|
151
|
+
if windowed:
|
|
152
|
+
command.append("-windowed")
|
|
153
|
+
else:
|
|
154
|
+
command.append("-fullscreen")
|
|
155
|
+
|
|
156
|
+
command.append("-Realtime")
|
|
157
|
+
|
|
158
|
+
logger.info(f"Command: {' '.join(command)}")
|
|
159
|
+
|
|
160
|
+
# Launch process
|
|
161
|
+
if platform.system() == "Windows":
|
|
162
|
+
DETACHED_PROCESS = 0x00000008
|
|
163
|
+
_process = subprocess.Popen(
|
|
164
|
+
command,
|
|
165
|
+
creationflags=DETACHED_PROCESS,
|
|
166
|
+
close_fds=True,
|
|
167
|
+
stdout=None if verbose else subprocess.DEVNULL,
|
|
168
|
+
stderr=None if verbose else subprocess.DEVNULL,
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
_process = subprocess.Popen(
|
|
172
|
+
command,
|
|
173
|
+
start_new_session=True,
|
|
174
|
+
stdout=None if verbose else subprocess.DEVNULL,
|
|
175
|
+
stderr=None if verbose else subprocess.DEVNULL,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Start monitoring
|
|
179
|
+
_shutdown_event.clear()
|
|
180
|
+
_monitor_thread = threading.Thread(target=monitor_process, daemon=True)
|
|
181
|
+
_monitor_thread.start()
|
|
182
|
+
|
|
183
|
+
create_lock_file(_process.pid)
|
|
184
|
+
|
|
185
|
+
logger.info(f"LuckyEngine started successfully (PID: {_process.pid})")
|
|
186
|
+
logger.info(f"Scene: {scene}, Robot: {robot}, Task: {task or 'None'}")
|
|
187
|
+
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(f"Failed to launch LuckyEngine: {e}")
|
|
192
|
+
stop_luckyengine()
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def stop_luckyengine() -> bool:
|
|
197
|
+
"""
|
|
198
|
+
Stop the running LuckyEngine instance.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
True if stopped successfully, False otherwise.
|
|
202
|
+
"""
|
|
203
|
+
global _process
|
|
204
|
+
|
|
205
|
+
if not is_luckyengine_running():
|
|
206
|
+
logger.info("LuckyEngine is not running")
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
if _process:
|
|
211
|
+
logger.info("Stopping LuckyEngine...")
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
_process.terminate()
|
|
215
|
+
_process.wait(timeout=5)
|
|
216
|
+
except subprocess.TimeoutExpired:
|
|
217
|
+
logger.info("Graceful termination timeout, using kill_processes...")
|
|
218
|
+
except Exception:
|
|
219
|
+
logger.info("Graceful termination failed, using kill_processes...")
|
|
220
|
+
|
|
221
|
+
kill_processes()
|
|
222
|
+
|
|
223
|
+
remove_lock_file()
|
|
224
|
+
return True
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(f"Error stopping LuckyEngine: {e}")
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ============================================================================
|
|
231
|
+
# Private Helpers
|
|
232
|
+
# ============================================================================
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _get_executable_for_platform(
|
|
236
|
+
home_dir: str, base_name: str, is_wsl: bool
|
|
237
|
+
) -> Optional[str]:
|
|
238
|
+
"""Get the executable path for the current platform."""
|
|
239
|
+
if platform.system() == "Linux" and not is_wsl:
|
|
240
|
+
return os.path.join(home_dir, f"{base_name}.sh")
|
|
241
|
+
elif platform.system() == "Darwin":
|
|
242
|
+
return os.path.join(
|
|
243
|
+
home_dir, f"{base_name}.app", "Contents", "MacOS", base_name
|
|
244
|
+
)
|
|
245
|
+
else: # Windows or WSL2
|
|
246
|
+
return os.path.join(home_dir, f"{base_name}.exe")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _get_system_paths(is_wsl: bool) -> list[str]:
|
|
250
|
+
"""Get system installation paths for the current platform."""
|
|
251
|
+
paths = []
|
|
252
|
+
|
|
253
|
+
if platform.system() == "Linux" and not is_wsl:
|
|
254
|
+
paths.extend(
|
|
255
|
+
[
|
|
256
|
+
"/opt/LuckyEngine/LuckyEngine.sh",
|
|
257
|
+
"/usr/local/bin/LuckyEngine.sh",
|
|
258
|
+
os.path.expanduser("~/.local/share/LuckyEngine/LuckyEngine.sh"),
|
|
259
|
+
os.path.expanduser("~/LuckyEngine/LuckyEngine.sh"),
|
|
260
|
+
]
|
|
261
|
+
)
|
|
262
|
+
elif platform.system() == "Darwin":
|
|
263
|
+
paths.extend(
|
|
264
|
+
[
|
|
265
|
+
"/Applications/LuckyEngine/LuckyEngine.app/Contents/MacOS/LuckyEngine",
|
|
266
|
+
os.path.expanduser(
|
|
267
|
+
"~/Applications/LuckyEngine/LuckyEngine.app/Contents/MacOS/LuckyEngine"
|
|
268
|
+
),
|
|
269
|
+
]
|
|
270
|
+
)
|
|
271
|
+
else: # Windows or WSL2
|
|
272
|
+
paths.extend(
|
|
273
|
+
[
|
|
274
|
+
"C:\\Program Files\\LuckyEngine\\LuckyEngine.exe",
|
|
275
|
+
"C:\\Program Files (x86)\\LuckyEngine\\LuckyEngine.exe",
|
|
276
|
+
os.path.expanduser("~\\AppData\\Local\\LuckyEngine\\LuckyEngine.exe"),
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if is_wsl:
|
|
281
|
+
paths.extend(
|
|
282
|
+
[
|
|
283
|
+
"/mnt/c/Program Files/LuckyEngine/LuckyEngine.exe",
|
|
284
|
+
"/mnt/c/Program Files (x86)/LuckyEngine/LuckyEngine.exe",
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return paths
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def monitor_process() -> None:
|
|
292
|
+
"""Monitor the LuckyEngine process and handle its termination."""
|
|
293
|
+
global _process
|
|
294
|
+
try:
|
|
295
|
+
while not _shutdown_event.is_set():
|
|
296
|
+
if _process is None or _process.poll() is not None:
|
|
297
|
+
logger.info("LuckyEngine process has terminated")
|
|
298
|
+
break
|
|
299
|
+
time.sleep(1)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.error(f"Error in process monitor: {e}")
|
|
302
|
+
finally:
|
|
303
|
+
# Ensure cleanup happens when monitoring stops
|
|
304
|
+
if not _shutdown_event.is_set():
|
|
305
|
+
remove_lock_file()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def create_lock_file(pid: int) -> None:
|
|
309
|
+
"""Create a lock file with the process ID."""
|
|
310
|
+
with open(LOCK_FILE, "w") as f:
|
|
311
|
+
f.write(str(pid))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def remove_lock_file() -> None:
|
|
315
|
+
"""Remove the lock file."""
|
|
316
|
+
try:
|
|
317
|
+
if os.path.exists(LOCK_FILE):
|
|
318
|
+
os.remove(LOCK_FILE)
|
|
319
|
+
logger.debug("Lock file removed successfully")
|
|
320
|
+
else:
|
|
321
|
+
logger.debug("Lock file doesn't exist, nothing to remove")
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.error(f"Error removing lock file: {e}")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def kill_processes() -> None:
|
|
327
|
+
"""Kill all LuckyEngine processes."""
|
|
328
|
+
system = platform.system()
|
|
329
|
+
is_wsl = "microsoft" in platform.uname().release.lower()
|
|
330
|
+
|
|
331
|
+
if is_wsl:
|
|
332
|
+
_kill_wsl_processes()
|
|
333
|
+
elif system == "Windows":
|
|
334
|
+
_kill_windows_processes()
|
|
335
|
+
elif system == "Darwin":
|
|
336
|
+
_kill_macos_processes()
|
|
337
|
+
else:
|
|
338
|
+
_kill_linux_processes()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _kill_wsl_processes() -> None:
|
|
342
|
+
"""Kill LuckyEngine processes on WSL."""
|
|
343
|
+
try:
|
|
344
|
+
result = subprocess.run(
|
|
345
|
+
[
|
|
346
|
+
"/mnt/c/Windows/System32/taskkill.exe",
|
|
347
|
+
"/F",
|
|
348
|
+
"/IM",
|
|
349
|
+
"LuckyEngine.exe",
|
|
350
|
+
],
|
|
351
|
+
capture_output=True,
|
|
352
|
+
text=True,
|
|
353
|
+
timeout=10,
|
|
354
|
+
)
|
|
355
|
+
if result.returncode == 0:
|
|
356
|
+
logger.info("Killed LuckyEngine.exe processes")
|
|
357
|
+
elif result.returncode == 128: # Process not found
|
|
358
|
+
logger.debug("No LuckyEngine processes found running")
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.debug(f"Error killing WSL processes: {e}")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _kill_windows_processes() -> None:
|
|
364
|
+
"""Kill LuckyEngine processes on Windows."""
|
|
365
|
+
try:
|
|
366
|
+
result = subprocess.run(
|
|
367
|
+
["taskkill", "/F", "/IM", "LuckyEngine.exe"],
|
|
368
|
+
capture_output=True,
|
|
369
|
+
text=True,
|
|
370
|
+
timeout=10,
|
|
371
|
+
)
|
|
372
|
+
if result.returncode == 0:
|
|
373
|
+
logger.info("Successfully killed LuckyEngine.exe processes")
|
|
374
|
+
elif result.returncode == 128: # Process not found
|
|
375
|
+
logger.info("No LuckyEngine processes found")
|
|
376
|
+
except Exception as e:
|
|
377
|
+
logger.debug(f"Error killing Windows processes: {e}")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _kill_macos_processes() -> None:
|
|
381
|
+
"""Kill LuckyEngine processes on macOS."""
|
|
382
|
+
try:
|
|
383
|
+
result = subprocess.run(
|
|
384
|
+
["pkill", "-f", "LuckyEngine"],
|
|
385
|
+
capture_output=True,
|
|
386
|
+
text=True,
|
|
387
|
+
timeout=10,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if result.returncode == 0:
|
|
391
|
+
logger.info("Successfully killed LuckyEngine processes")
|
|
392
|
+
elif result.returncode == 1: # No processes found
|
|
393
|
+
logger.info("No LuckyEngine processes found")
|
|
394
|
+
else:
|
|
395
|
+
logger.warning(f"pkill failed: {result.stderr}")
|
|
396
|
+
|
|
397
|
+
except subprocess.TimeoutExpired:
|
|
398
|
+
logger.error("pkill command timed out")
|
|
399
|
+
except FileNotFoundError:
|
|
400
|
+
logger.error("pkill command not found")
|
|
401
|
+
except Exception as e:
|
|
402
|
+
logger.error(f"Failed to kill macOS processes: {e}")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _kill_linux_processes() -> None:
|
|
406
|
+
"""Kill LuckyEngine processes on Linux."""
|
|
407
|
+
try:
|
|
408
|
+
result = subprocess.run(
|
|
409
|
+
["pkill", "-f", "LuckyEngine"],
|
|
410
|
+
capture_output=True,
|
|
411
|
+
text=True,
|
|
412
|
+
timeout=10,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if result.returncode == 0:
|
|
416
|
+
logger.info("Successfully killed LuckyEngine processes")
|
|
417
|
+
elif result.returncode == 1: # No processes found
|
|
418
|
+
logger.info("No LuckyEngine processes found")
|
|
419
|
+
else:
|
|
420
|
+
logger.warning(f"pkill failed: {result.stderr}")
|
|
421
|
+
|
|
422
|
+
except subprocess.TimeoutExpired:
|
|
423
|
+
logger.error("pkill command timed out")
|
|
424
|
+
except FileNotFoundError:
|
|
425
|
+
logger.error("pkill command not found")
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.error(f"Failed to kill Linux processes: {e}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Checked-in generated gRPC stubs for the LuckyEngine `.proto` files.
|
|
2
|
+
|
|
3
|
+
Regenerate with:
|
|
4
|
+
python -m grpc_tools.protoc -I src/luckyrobots/rpc/proto \
|
|
5
|
+
--python_out=src/luckyrobots/rpc/generated \
|
|
6
|
+
--grpc_python_out=src/luckyrobots/rpc/generated \
|
|
7
|
+
src/luckyrobots/rpc/proto/common.proto \
|
|
8
|
+
src/luckyrobots/rpc/proto/media.proto \
|
|
9
|
+
src/luckyrobots/rpc/proto/scene.proto \
|
|
10
|
+
src/luckyrobots/rpc/proto/mujoco.proto \
|
|
11
|
+
src/luckyrobots/rpc/proto/telemetry.proto \
|
|
12
|
+
src/luckyrobots/rpc/proto/agent.proto \
|
|
13
|
+
src/luckyrobots/rpc/proto/viewport.proto \
|
|
14
|
+
src/luckyrobots/rpc/proto/camera.proto
|
|
15
|
+
|
|
16
|
+
Then adjust imports in the generated `*_pb2.py` and `*_pb2_grpc.py` to be
|
|
17
|
+
package-relative (`from . import ...`).
|
|
18
|
+
"""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: agent.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
|
|
13
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
14
|
+
_runtime_version.Domain.PUBLIC, 6, 31, 1, "", "agent.proto"
|
|
15
|
+
)
|
|
16
|
+
# @@protoc_insertion_point(imports)
|
|
17
|
+
|
|
18
|
+
_sym_db = _symbol_database.Default()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from . import common_pb2 as common__pb2
|
|
22
|
+
from . import media_pb2 as media__pb2
|
|
23
|
+
from . import mujoco_pb2 as mujoco__pb2
|
|
24
|
+
from . import telemetry_pb2 as telemetry__pb2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
|
28
|
+
b'\n\x0b\x61gent.proto\x12\x0chazel.rpc.v1\x1a\x0c\x63ommon.proto\x1a\x0bmedia.proto\x1a\x0cmujoco.proto\x1a\x0ftelemetry.proto"\x81\x01\n\x0b\x41gentSchema\x12\x12\n\nagent_name\x18\x01 \x01(\t\x12\x19\n\x11observation_names\x18\x02 \x03(\t\x12\x14\n\x0c\x61\x63tion_names\x18\x03 \x03(\t\x12\x18\n\x10observation_size\x18\x04 \x01(\r\x12\x13\n\x0b\x61\x63tion_size\x18\x05 \x01(\r"+\n\x15GetAgentSchemaRequest\x12\x12\n\nagent_name\x18\x01 \x01(\t"C\n\x16GetAgentSchemaResponse\x12)\n\x06schema\x18\x01 \x01(\x0b\x32\x19.hazel.rpc.v1.AgentSchema"<\n\x12StreamAgentRequest\x12\x12\n\nagent_name\x18\x01 \x01(\t\x12\x12\n\ntarget_fps\x18\x02 \x01(\r"\'\n\x11ResetAgentRequest\x12\x12\n\nagent_name\x18\x01 \x01(\t"6\n\x12ResetAgentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t"\x87\x01\n\nAgentFrame\x12\x14\n\x0ctimestamp_ms\x18\x01 \x01(\x04\x12\x14\n\x0c\x66rame_number\x18\x02 \x01(\r\x12\x14\n\x0cobservations\x18\x03 \x03(\x02\x12\x0f\n\x07\x61\x63tions\x18\x04 \x03(\x02\x12\x12\n\nagent_name\x18\x05 \x01(\t\x12\x12\n\ntarget_fps\x18\x06 \x01(\r"\x8a\x01\n\x15GetCameraFrameRequest\x12$\n\x02id\x18\x01 \x01(\x0b\x32\x16.hazel.rpc.v1.EntityIdH\x00\x12\x0e\n\x04name\x18\x02 \x01(\tH\x00\x12\r\n\x05width\x18\x03 \x01(\r\x12\x0e\n\x06height\x18\x04 \x01(\r\x12\x0e\n\x06\x66ormat\x18\x05 \x01(\tB\x0c\n\nidentifier"_\n\x17GetViewportFrameRequest\x12\x15\n\rviewport_name\x18\x01 \x01(\t\x12\r\n\x05width\x18\x02 \x01(\r\x12\x0e\n\x06height\x18\x03 \x01(\r\x12\x0e\n\x06\x66ormat\x18\x04 \x01(\t"\x84\x02\n\x15GetObservationRequest\x12\x12\n\nrobot_name\x18\x01 \x01(\t\x12\x12\n\nagent_name\x18\x02 \x01(\t\x12\x1b\n\x13include_joint_state\x18\x03 \x01(\x08\x12\x1b\n\x13include_agent_frame\x18\x04 \x01(\x08\x12\x19\n\x11include_telemetry\x18\x05 \x01(\x08\x12\x34\n\x07\x63\x61meras\x18\x06 \x03(\x0b\x32#.hazel.rpc.v1.GetCameraFrameRequest\x12\x38\n\tviewports\x18\x07 \x03(\x0b\x32%.hazel.rpc.v1.GetViewportFrameRequest"\xe3\x02\n\x16GetObservationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0ctimestamp_ms\x18\x03 \x01(\x04\x12\x14\n\x0c\x66rame_number\x18\x04 \x01(\r\x12-\n\x0bjoint_state\x18\x05 \x01(\x0b\x32\x18.hazel.rpc.v1.JointState\x12-\n\x0b\x61gent_frame\x18\x06 \x01(\x0b\x32\x18.hazel.rpc.v1.AgentFrame\x12/\n\ttelemetry\x18\x07 \x01(\x0b\x32\x1c.hazel.rpc.v1.TelemetryFrame\x12\x34\n\rcamera_frames\x18\x08 \x03(\x0b\x32\x1d.hazel.rpc.v1.NamedImageFrame\x12\x36\n\x0fviewport_frames\x18\t \x03(\x0b\x32\x1d.hazel.rpc.v1.NamedImageFrame2\xe6\x02\n\x0c\x41gentService\x12[\n\x0eGetAgentSchema\x12#.hazel.rpc.v1.GetAgentSchemaRequest\x1a$.hazel.rpc.v1.GetAgentSchemaResponse\x12[\n\x0eGetObservation\x12#.hazel.rpc.v1.GetObservationRequest\x1a$.hazel.rpc.v1.GetObservationResponse\x12K\n\x0bStreamAgent\x12 .hazel.rpc.v1.StreamAgentRequest\x1a\x18.hazel.rpc.v1.AgentFrame0\x01\x12O\n\nResetAgent\x12\x1f.hazel.rpc.v1.ResetAgentRequest\x1a .hazel.rpc.v1.ResetAgentResponseB\x03\xf8\x01\x01\x62\x06proto3'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_globals = globals()
|
|
32
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
33
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "agent_pb2", _globals)
|
|
34
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
35
|
+
_globals["DESCRIPTOR"]._loaded_options = None
|
|
36
|
+
_globals["DESCRIPTOR"]._serialized_options = b"\370\001\001"
|
|
37
|
+
_globals["_AGENTSCHEMA"]._serialized_start = 88
|
|
38
|
+
_globals["_AGENTSCHEMA"]._serialized_end = 217
|
|
39
|
+
_globals["_GETAGENTSCHEMAREQUEST"]._serialized_start = 219
|
|
40
|
+
_globals["_GETAGENTSCHEMAREQUEST"]._serialized_end = 262
|
|
41
|
+
_globals["_GETAGENTSCHEMARESPONSE"]._serialized_start = 264
|
|
42
|
+
_globals["_GETAGENTSCHEMARESPONSE"]._serialized_end = 331
|
|
43
|
+
_globals["_STREAMAGENTREQUEST"]._serialized_start = 333
|
|
44
|
+
_globals["_STREAMAGENTREQUEST"]._serialized_end = 393
|
|
45
|
+
_globals["_RESETAGENTREQUEST"]._serialized_start = 395
|
|
46
|
+
_globals["_RESETAGENTREQUEST"]._serialized_end = 434
|
|
47
|
+
_globals["_RESETAGENTRESPONSE"]._serialized_start = 436
|
|
48
|
+
_globals["_RESETAGENTRESPONSE"]._serialized_end = 490
|
|
49
|
+
_globals["_AGENTFRAME"]._serialized_start = 493
|
|
50
|
+
_globals["_AGENTFRAME"]._serialized_end = 628
|
|
51
|
+
_globals["_GETCAMERAFRAMEREQUEST"]._serialized_start = 631
|
|
52
|
+
_globals["_GETCAMERAFRAMEREQUEST"]._serialized_end = 769
|
|
53
|
+
_globals["_GETVIEWPORTFRAMEREQUEST"]._serialized_start = 771
|
|
54
|
+
_globals["_GETVIEWPORTFRAMEREQUEST"]._serialized_end = 866
|
|
55
|
+
_globals["_GETOBSERVATIONREQUEST"]._serialized_start = 869
|
|
56
|
+
_globals["_GETOBSERVATIONREQUEST"]._serialized_end = 1129
|
|
57
|
+
_globals["_GETOBSERVATIONRESPONSE"]._serialized_start = 1132
|
|
58
|
+
_globals["_GETOBSERVATIONRESPONSE"]._serialized_end = 1487
|
|
59
|
+
_globals["_AGENTSERVICE"]._serialized_start = 1490
|
|
60
|
+
_globals["_AGENTSERVICE"]._serialized_end = 1848
|
|
61
|
+
# @@protoc_insertion_point(module_scope)
|