Files
chenlin 3ad29356c9
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
feat(lekiwi): release V0.10.1 初步集成 LeKiwi,优化碰撞模型
集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
2026-09-20 14:42:30 +08:00

99 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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()