feat(lekiwi): release V0.10.1 初步集成 LeKiwi,优化碰撞模型
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled

集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
This commit is contained in:
2026-09-20 14:42:30 +08:00
parent da4d59c31a
commit 3ad29356c9
103 changed files with 23055 additions and 280 deletions
+98
View File
@@ -0,0 +1,98 @@
"""Hardware-free LeRobot loop: observe -> compose action -> send, at 30 Hz.
Only the robot implementation and input source differ from the upstream example.
This is real-time control, NOT lockstep RL or dataset recording.
"""
import argparse
import json
import math
import os
import time
from importlib.metadata import version
from lerobot.robots.config import RobotConfig
from lerobot.robots.utils import make_robot_from_config
from lerobot.utils.import_utils import register_third_party_plugins
def make_robot(endpoint):
register_third_party_plugins()
config_type = RobotConfig.get_choice_class("lekiwi_sim")
return make_robot_from_config(config_type(endpoint=endpoint, id="lekiwi-sim-demo"))
def run_demo(endpoint, duration=10.0):
if not math.isfinite(duration) or not 1 <= duration <= 3600:
raise ValueError("演示时长必须为1–3600秒")
robot = make_robot(endpoint)
robot.connect()
try:
start = time.monotonic()
first = robot.get_sim_observation()
latencies, gaps, max_motion = [], [], 0.0
steps = math.ceil(duration * 30)
previous_sim_time = first["simTime"]
for i in range(steps):
tick = time.monotonic()
observation = robot.get_observation()
t = i / 30
action = {
"arm_shoulder_pan.pos": 10 * math.sin(t),
"arm_shoulder_lift.pos": 6 * math.sin(t + 0.2),
"arm_elbow_flex.pos": 8 * math.sin(t + 0.4),
"arm_wrist_flex.pos": 6 * math.sin(t + 0.6),
"arm_wrist_roll.pos": 12 * math.sin(t + 0.8),
"arm_gripper.pos": 50 + 25 * math.sin(t),
"x.vel": 0.05 * math.cos(t),
"y.vel": 0.05 * math.sin(t),
"theta.vel": 10 * math.sin(t / 2),
}
accepted = robot.send_action(action)
platform = robot.get_sim_observation()
latencies.append((time.monotonic() - tick) * 1000)
gaps.append(platform["simTime"] - previous_sim_time)
previous_sim_time = platform["simTime"]
max_motion = max(
max_motion,
math.hypot(
platform["values"]["base.x"] - first["values"]["base.x"],
platform["values"]["base.y"] - first["values"]["base.y"],
),
)
time.sleep(max(0, start + (i + 1) / 30 - time.monotonic()))
ordered = sorted(latencies)
return {
"lerobot": version("lerobot"),
"torch": version("torch"),
"steps": steps,
"elapsedSeconds": time.monotonic() - start,
"simSeconds": platform["simTime"] - first["simTime"],
"rttMeanMs": sum(latencies) / len(latencies),
"rttP95Ms": ordered[int(0.95 * (len(ordered) - 1))],
"rttMaxMs": max(latencies),
"maxObservationSimGap": max(gaps),
"maxTranslationM": max_motion,
"observation": observation,
"accepted": accepted,
"sequence": platform["sequence"],
"appliedActionSeq": platform["appliedActionSeq"],
"timeouts": 0,
"droppedRequests": 0,
}
finally:
robot.disconnect()
def main():
parser = argparse.ArgumentParser(description="LeKiwi 仿真:先播放并在浏览器显式授权")
parser.add_argument(
"--endpoint", default=os.environ.get("MUJOCO_CONTROL_ENDPOINT", "http://127.0.0.1:8766")
)
parser.add_argument("--duration", type=float, default=10)
args = parser.parse_args()
print(json.dumps(run_demo(args.endpoint, args.duration), ensure_ascii=False))
if __name__ == "__main__":
main()