luckyrobots 0.1.72__py3-none-any.whl → 0.1.73__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 +5 -32
- luckyrobots/client.py +20 -491
- luckyrobots/config/robots.yaml +72 -48
- luckyrobots/engine/__init__.py +5 -20
- luckyrobots/models/__init__.py +2 -14
- luckyrobots/models/observation.py +4 -33
- luckyrobots/utils.py +1 -43
- {luckyrobots-0.1.72.dist-info → luckyrobots-0.1.73.dist-info}/METADATA +1 -1
- {luckyrobots-0.1.72.dist-info → luckyrobots-0.1.73.dist-info}/RECORD +11 -15
- luckyrobots/engine/check_updates.py +0 -264
- luckyrobots/engine/download.py +0 -125
- luckyrobots/models/camera.py +0 -97
- luckyrobots/models/randomization.py +0 -77
- {luckyrobots-0.1.72.dist-info → luckyrobots-0.1.73.dist-info}/WHEEL +0 -0
- {luckyrobots-0.1.72.dist-info → luckyrobots-0.1.73.dist-info}/licenses/LICENSE +0 -0
luckyrobots/engine/download.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
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
|
|
8
|
-
import os
|
|
9
|
-
import platform
|
|
10
|
-
from typing import Optional
|
|
11
|
-
|
|
12
|
-
import requests
|
|
13
|
-
from tqdm import tqdm
|
|
14
|
-
|
|
15
|
-
from .check_updates import check_updates
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger("luckyrobots.engine.download")
|
|
18
|
-
|
|
19
|
-
BASE_URL = "https://builds.luckyrobots.xyz/"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_base_url() -> str:
|
|
23
|
-
"""
|
|
24
|
-
Get the base URL for downloads, checking local server first.
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
Base URL string (local or remote).
|
|
28
|
-
"""
|
|
29
|
-
local_url = "http://192.168.1.148/builds"
|
|
30
|
-
remote_url = "https://builds.luckyrobots.xyz"
|
|
31
|
-
|
|
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.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
"mac", "win", or "linux"
|
|
50
|
-
|
|
51
|
-
Raises:
|
|
52
|
-
ValueError: If the OS is not supported.
|
|
53
|
-
"""
|
|
54
|
-
os_type = platform.system().lower()
|
|
55
|
-
if os_type == "darwin":
|
|
56
|
-
return "mac"
|
|
57
|
-
elif os_type == "windows":
|
|
58
|
-
return "win"
|
|
59
|
-
elif os_type == "linux":
|
|
60
|
-
return "linux"
|
|
61
|
-
else:
|
|
62
|
-
raise ValueError(f"Unsupported operating system: {os_type}")
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
76
|
-
for item in changes:
|
|
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"]:
|
|
81
|
-
if item.get("type") == "directory":
|
|
82
|
-
# Create the directory
|
|
83
|
-
os.makedirs(item_path, exist_ok=True)
|
|
84
|
-
logger.debug(f"Created directory: {item_path}")
|
|
85
|
-
else:
|
|
86
|
-
# Handle file download with progress bar
|
|
87
|
-
file_url = f"{base_url}{os_type}/{item['path']}"
|
|
88
|
-
|
|
89
|
-
# Ensure the directory exists
|
|
90
|
-
item_dir = os.path.dirname(item_path)
|
|
91
|
-
os.makedirs(item_dir, exist_ok=True)
|
|
92
|
-
|
|
93
|
-
# Download the file with progress bar
|
|
94
|
-
try:
|
|
95
|
-
response = requests.get(file_url, stream=True, timeout=30)
|
|
96
|
-
response.raise_for_status()
|
|
97
|
-
total_size = int(response.headers.get("content-length", 0))
|
|
98
|
-
|
|
99
|
-
with open(item_path, "wb") as f, tqdm(
|
|
100
|
-
desc=f"{item['path'][:8]}...{item['path'][-16:]}",
|
|
101
|
-
total=total_size,
|
|
102
|
-
unit="iB",
|
|
103
|
-
unit_scale=True,
|
|
104
|
-
unit_divisor=1024,
|
|
105
|
-
ascii=" ▆",
|
|
106
|
-
) as progress_bar:
|
|
107
|
-
for data in response.iter_content(chunk_size=1024):
|
|
108
|
-
size = f.write(data)
|
|
109
|
-
progress_bar.update(size)
|
|
110
|
-
|
|
111
|
-
logger.debug(f"Downloaded: {item_path}")
|
|
112
|
-
except requests.RequestException as e:
|
|
113
|
-
logger.error(f"Error downloading {item_path}: {e}")
|
|
114
|
-
|
|
115
|
-
elif change_type == "deleted":
|
|
116
|
-
# Delete the file or directory
|
|
117
|
-
try:
|
|
118
|
-
if os.path.isdir(item_path):
|
|
119
|
-
os.rmdir(item_path)
|
|
120
|
-
logger.debug(f"Deleted directory: {item_path}")
|
|
121
|
-
else:
|
|
122
|
-
os.remove(item_path)
|
|
123
|
-
logger.debug(f"Deleted file: {item_path}")
|
|
124
|
-
except OSError as e:
|
|
125
|
-
logger.error(f"Error deleting {item_path}: {e}")
|
luckyrobots/models/camera.py
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Camera models for LuckyRobots.
|
|
3
|
-
|
|
4
|
-
These models handle camera frame data from LuckyEngine.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import Any, Dict, Optional, Union
|
|
8
|
-
from pydantic import BaseModel, Field, ConfigDict
|
|
9
|
-
import numpy as np
|
|
10
|
-
import cv2
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CameraShape(BaseModel):
|
|
14
|
-
"""Shape of camera images."""
|
|
15
|
-
|
|
16
|
-
width: float = Field(description="Width of the image")
|
|
17
|
-
height: float = Field(description="Height of the image")
|
|
18
|
-
channel: int = Field(description="Number of color channels")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class CameraData(BaseModel):
|
|
22
|
-
"""Camera frame data."""
|
|
23
|
-
|
|
24
|
-
model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
|
|
25
|
-
|
|
26
|
-
camera_name: str = Field(alias="CameraName", description="Name of the camera")
|
|
27
|
-
dtype: str = Field(default="uint8", description="Data type of the image")
|
|
28
|
-
shape: Optional[Union[CameraShape, Dict[str, Union[float, int]]]] = Field(
|
|
29
|
-
default=None, description="Shape of the image"
|
|
30
|
-
)
|
|
31
|
-
time_stamp: Optional[str] = Field(
|
|
32
|
-
None, alias="TimeStamp", description="Camera timestamp"
|
|
33
|
-
)
|
|
34
|
-
image_data: Optional[Any] = Field(
|
|
35
|
-
None, alias="ImageData", description="Image data (bytes or numpy array)"
|
|
36
|
-
)
|
|
37
|
-
width: Optional[int] = Field(default=None, description="Image width")
|
|
38
|
-
height: Optional[int] = Field(default=None, description="Image height")
|
|
39
|
-
channels: Optional[int] = Field(default=None, description="Number of channels")
|
|
40
|
-
format: Optional[str] = Field(
|
|
41
|
-
default=None, description="Image format (raw, jpeg, png)"
|
|
42
|
-
)
|
|
43
|
-
frame_number: Optional[int] = Field(default=None, description="Frame number")
|
|
44
|
-
|
|
45
|
-
def process_image(self) -> None:
|
|
46
|
-
"""Process the image data into a numpy array."""
|
|
47
|
-
if self.image_data is None:
|
|
48
|
-
return None
|
|
49
|
-
|
|
50
|
-
# If already a numpy array, skip processing
|
|
51
|
-
if isinstance(self.image_data, np.ndarray):
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
# Handle bytes data
|
|
55
|
-
if isinstance(self.image_data, bytes):
|
|
56
|
-
nparr = np.frombuffer(self.image_data, np.uint8)
|
|
57
|
-
|
|
58
|
-
# Check if it's raw RGBA/RGB data or encoded
|
|
59
|
-
if self.format == "raw" and self.width and self.height:
|
|
60
|
-
channels = self.channels or 4
|
|
61
|
-
try:
|
|
62
|
-
self.image_data = nparr.reshape((self.height, self.width, channels))
|
|
63
|
-
# Convert RGBA to BGR for OpenCV compatibility
|
|
64
|
-
if channels == 4:
|
|
65
|
-
self.image_data = cv2.cvtColor(
|
|
66
|
-
self.image_data, cv2.COLOR_RGBA2BGR
|
|
67
|
-
)
|
|
68
|
-
elif channels == 3:
|
|
69
|
-
self.image_data = cv2.cvtColor(
|
|
70
|
-
self.image_data, cv2.COLOR_RGB2BGR
|
|
71
|
-
)
|
|
72
|
-
except ValueError:
|
|
73
|
-
# Fallback to decode
|
|
74
|
-
self.image_data = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
75
|
-
else:
|
|
76
|
-
# Encoded image (JPEG, PNG)
|
|
77
|
-
self.image_data = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
78
|
-
|
|
79
|
-
@classmethod
|
|
80
|
-
def from_grpc_frame(cls, frame: Any, camera_name: str = "camera") -> "CameraData":
|
|
81
|
-
"""Create CameraData from a gRPC ImageFrame message."""
|
|
82
|
-
return cls(
|
|
83
|
-
camera_name=camera_name,
|
|
84
|
-
dtype="uint8",
|
|
85
|
-
width=frame.width,
|
|
86
|
-
height=frame.height,
|
|
87
|
-
channels=frame.channels,
|
|
88
|
-
format=frame.format,
|
|
89
|
-
time_stamp=str(frame.timestamp_ms),
|
|
90
|
-
frame_number=frame.frame_number,
|
|
91
|
-
image_data=frame.data,
|
|
92
|
-
shape=CameraShape(
|
|
93
|
-
width=frame.width,
|
|
94
|
-
height=frame.height,
|
|
95
|
-
channel=frame.channels,
|
|
96
|
-
),
|
|
97
|
-
)
|
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
)
|
|
File without changes
|
|
File without changes
|