Skip to content

Commit 10b7b35

Browse files
authored
Match motor names with ids lekiwi (#1261)
1 parent 459c951 commit 10b7b35

File tree

5 files changed

+34
-35
lines changed

5 files changed

+34
-35
lines changed

examples/lekiwi/record.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
dataset_features = {**action_features, **obs_features}
2424

2525
dataset = LeRobotDataset.create(
26-
repo_id="user/lekiwi" + str(int(time.time())),
26+
repo_id="pepijn223/lekiwi" + str(int(time.time())),
2727
fps=10,
2828
features=dataset_features,
2929
robot_type=robot.name,
@@ -36,7 +36,7 @@
3636
if not robot.is_connected or not leader_arm.is_connected or not keyboard.is_connected:
3737
exit()
3838

39-
print("Starting LeKiwi teleoperation")
39+
print("Starting LeKiwi recording")
4040
i = 0
4141
while i < NB_CYCLES_CLIENT_CONNECTION:
4242
arm_action = leader_arm.get_action()

lerobot/common/robots/lekiwi/config_lekiwi.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@
2020
from ..config import RobotConfig
2121

2222

23+
def lekiwi_cameras_config() -> dict[str, CameraConfig]:
24+
return {
25+
"front": OpenCVCameraConfig(
26+
index_or_path="/dev/video0", fps=30, width=640, height=480, rotation=Cv2Rotation.ROTATE_180
27+
),
28+
"wrist": OpenCVCameraConfig(
29+
index_or_path="/dev/video2", fps=30, width=480, height=640, rotation=Cv2Rotation.ROTATE_90
30+
),
31+
}
32+
33+
2334
@RobotConfig.register_subclass("lekiwi")
2435
@dataclass
2536
class LeKiwiConfig(RobotConfig):
@@ -32,14 +43,7 @@ class LeKiwiConfig(RobotConfig):
3243
# the number of motors in your follower arms.
3344
max_relative_target: int | None = None
3445

35-
cameras: dict[str, CameraConfig] = field(
36-
default_factory=lambda: {
37-
"front": OpenCVCameraConfig(index_or_path="/dev/video0", fps=30, width=640, height=480),
38-
"wrist": OpenCVCameraConfig(
39-
index_or_path="/dev/video2", fps=30, width=640, height=480, rotation=Cv2Rotation.ROTATE_180
40-
),
41-
}
42-
)
46+
cameras: dict[str, CameraConfig] = field(default_factory=lekiwi_cameras_config)
4347

4448
# Set to `True` for backward compatibility with previous policies/dataset
4549
use_degrees: bool = False
@@ -86,5 +90,7 @@ class LeKiwiClientConfig(RobotConfig):
8690
}
8791
)
8892

93+
cameras: dict[str, CameraConfig] = field(default_factory=lekiwi_cameras_config)
94+
8995
polling_timeout_ms: int = 15
9096
connect_timeout_s: int = 5

lerobot/common/robots/lekiwi/lekiwi.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import numpy as np
2424

2525
from lerobot.common.cameras.utils import make_cameras_from_configs
26-
from lerobot.common.constants import OBS_IMAGES, OBS_STATE
2726
from lerobot.common.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
2827
from lerobot.common.motors import Motor, MotorCalibration, MotorNormMode
2928
from lerobot.common.motors.feetech import (
@@ -65,8 +64,8 @@ def __init__(self, config: LeKiwiConfig):
6564
"arm_gripper": Motor(6, "sts3215", MotorNormMode.RANGE_0_100),
6665
# base
6766
"base_left_wheel": Motor(7, "sts3215", MotorNormMode.RANGE_M100_100),
68-
"base_right_wheel": Motor(8, "sts3215", MotorNormMode.RANGE_M100_100),
69-
"base_back_wheel": Motor(9, "sts3215", MotorNormMode.RANGE_M100_100),
67+
"base_back_wheel": Motor(8, "sts3215", MotorNormMode.RANGE_M100_100),
68+
"base_right_wheel": Motor(9, "sts3215", MotorNormMode.RANGE_M100_100),
7069
},
7170
calibration=self.calibration,
7271
)
@@ -249,7 +248,7 @@ def _body_to_wheel_raw(
249248
velocity_vector = np.array([x, y, theta_rad])
250249

251250
# Define the wheel mounting angles with a -90° offset.
252-
angles = np.radians(np.array([240, 120, 0]) - 90)
251+
angles = np.radians(np.array([240, 0, 120]) - 90)
253252
# Build the kinematic matrix: each row maps body velocities to a wheel’s linear speed.
254253
# The third column (base_radius) accounts for the effect of rotation.
255254
m = np.array([[np.cos(a), np.sin(a), base_radius] for a in angles])
@@ -295,10 +294,7 @@ def _wheel_raw_to_body(
295294
base_radius : Distance from the robot center to each wheel (meters).
296295
297296
Returns:
298-
A dict (x_cmd, y_cmd, theta_cmd) where:
299-
OBS_STATE.x_cmd : Linear velocity in x (m/s).
300-
OBS_STATE.y_cmd : Linear velocity in y (m/s).
301-
OBS_STATE.theta_cmd : Rotational velocity in deg/s.
297+
A dict (x.vel, y.vel, theta.vel) all in m/s
302298
"""
303299

304300
# Convert each raw command back to an angular speed in deg/s.
@@ -316,7 +312,7 @@ def _wheel_raw_to_body(
316312
wheel_linear_speeds = wheel_radps * wheel_radius
317313

318314
# Define the wheel mounting angles with a -90° offset.
319-
angles = np.radians(np.array([240, 120, 0]) - 90)
315+
angles = np.radians(np.array([240, 0, 120]) - 90)
320316
m = np.array([[np.cos(a), np.sin(a), base_radius] for a in angles])
321317

322318
# Solve the inverse kinematics: body_velocity = M⁻¹ · wheel_linear_speeds.
@@ -347,16 +343,15 @@ def get_observation(self) -> dict[str, Any]:
347343

348344
arm_state = {f"{k}.pos": v for k, v in arm_pos.items()}
349345

350-
flat_states = {**arm_state, **base_vel}
346+
obs_dict = {**arm_state, **base_vel}
351347

352-
obs_dict = {f"{OBS_STATE}": flat_states}
353348
dt_ms = (time.perf_counter() - start) * 1e3
354349
logger.debug(f"{self} read state: {dt_ms:.1f}ms")
355350

356351
# Capture images from cameras
357352
for cam_key, cam in self.cameras.items():
358353
start = time.perf_counter()
359-
obs_dict[f"{OBS_IMAGES}.{cam_key}"] = cam.async_read()
354+
obs_dict[cam_key] = cam.async_read()
360355
dt_ms = (time.perf_counter() - start) * 1e3
361356
logger.debug(f"{self} read {cam_key}: {dt_ms:.1f}ms")
362357

lerobot/common/robots/lekiwi/lekiwi_client.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import torch
2626
import zmq
2727

28-
from lerobot.common.constants import OBS_IMAGES, OBS_STATE
2928
from lerobot.common.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
3029

3130
from ..robot import Robot
@@ -92,11 +91,8 @@ def _state_order(self) -> tuple[str, ...]:
9291
return tuple(self._state_ft.keys())
9392

9493
@cached_property
95-
def _cameras_ft(self) -> dict[str, tuple]:
96-
return {
97-
"front": (480, 640, 3),
98-
"wrist": (640, 480, 3),
99-
}
94+
def _cameras_ft(self) -> dict[str, tuple[int, int, int]]:
95+
return {name: (cfg.height, cfg.width, 3) for name, cfg in self.config.cameras.items()}
10096

10197
@cached_property
10298
def observation_features(self) -> dict[str, type | tuple]:
@@ -199,15 +195,19 @@ def _remote_state_from_obs(
199195
self, observation: Dict[str, Any]
200196
) -> Tuple[Dict[str, np.ndarray], Dict[str, Any]]:
201197
"""Extracts frames, and state from the parsed observation."""
202-
flat_state = observation[OBS_STATE]
198+
flat_state = {key: value for key, value in observation.items() if key in self._state_ft}
203199

204200
state_vec = np.array(
205201
[flat_state.get(k, 0.0) for k in self._state_order],
206202
dtype=np.float32,
207203
)
208204

209205
# Decode images
210-
image_observation = {k: v for k, v in observation.items() if k.startswith(OBS_IMAGES)}
206+
image_observation = {
207+
f"observation.images.{key}": value
208+
for key, value in observation.items()
209+
if key in self._cameras_ft
210+
}
211211
current_frames: Dict[str, np.ndarray] = {}
212212
for cam_name, image_b64 in image_observation.items():
213213
frame = self._decode_image_from_b64(image_b64)

lerobot/common/robots/lekiwi/lekiwi_host.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import cv2
2323
import zmq
2424

25-
from lerobot.common.constants import OBS_IMAGES
26-
2725
from .config_lekiwi import LeKiwiConfig, LeKiwiHostConfig
2826
from .lekiwi import LeKiwi
2927

@@ -95,12 +93,12 @@ def main():
9593
# Encode ndarrays to base64 strings
9694
for cam_key, _ in robot.cameras.items():
9795
ret, buffer = cv2.imencode(
98-
".jpg", last_observation[f"{OBS_IMAGES}.{cam_key}"], [int(cv2.IMWRITE_JPEG_QUALITY), 90]
96+
".jpg", last_observation[cam_key], [int(cv2.IMWRITE_JPEG_QUALITY), 90]
9997
)
10098
if ret:
101-
last_observation[f"{OBS_IMAGES}.{cam_key}"] = base64.b64encode(buffer).decode("utf-8")
99+
last_observation[cam_key] = base64.b64encode(buffer).decode("utf-8")
102100
else:
103-
last_observation[f"{OBS_IMAGES}.{cam_key}"] = ""
101+
last_observation[cam_key] = ""
104102

105103
# Send the observation to the remote agent
106104
try:

0 commit comments

Comments
 (0)