mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 18:26:11 +00:00
feat(unitree_g1): make onboard state PUB toggleable + lower default rate
Lower observation.state publish rate to 30 Hz and allow disabling it with --state-fps <=0, so the onboard runner can reproduce the plain-teleop setup (no state thread) for A/B isolation of camera-stability issues. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,7 +74,12 @@ def main() -> None:
|
|||||||
default=STATE_PORT,
|
default=STATE_PORT,
|
||||||
help="ZMQ PUB port for observation.state feedback (29 joint .q) to the inference client",
|
help="ZMQ PUB port for observation.state feedback (29 joint .q) to the inference client",
|
||||||
)
|
)
|
||||||
p.add_argument("--state-fps", type=float, default=60.0, help="observation.state publish rate (Hz)")
|
p.add_argument(
|
||||||
|
"--state-fps",
|
||||||
|
type=float,
|
||||||
|
default=30.0,
|
||||||
|
help="observation.state publish rate (Hz); set <=0 to disable the state PUB entirely",
|
||||||
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--gravity-compensation",
|
"--gravity-compensation",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -152,36 +157,41 @@ def main() -> None:
|
|||||||
sock.bind(f"tcp://0.0.0.0:{args.action_port}")
|
sock.bind(f"tcp://0.0.0.0:{args.action_port}")
|
||||||
logger.info("Onboard controller live. Waiting for laptop actions on :%d ...", args.action_port)
|
logger.info("Onboard controller live. Waiting for laptop actions on :%d ...", args.action_port)
|
||||||
|
|
||||||
# Proprioception feedback: publish observation.state (29 joint .q) so the
|
|
||||||
# laptop-side inference client can feed it to the policy. DDS stays local to
|
|
||||||
# the robot; only this compact JSON state crosses the network (like the
|
|
||||||
# camera/action ZMQ channels).
|
|
||||||
state_sock = ctx.socket(zmq.PUB)
|
|
||||||
state_sock.setsockopt(zmq.SNDHWM, 2)
|
|
||||||
state_sock.setsockopt(zmq.LINGER, 0)
|
|
||||||
state_sock.bind(f"tcp://0.0.0.0:{args.state_port}")
|
|
||||||
logger.info("Publishing observation.state on :%d at %.0f Hz", args.state_port, args.state_fps)
|
|
||||||
|
|
||||||
stop = threading.Event()
|
stop = threading.Event()
|
||||||
signal.signal(signal.SIGINT, lambda *_: stop.set())
|
signal.signal(signal.SIGINT, lambda *_: stop.set())
|
||||||
signal.signal(signal.SIGTERM, lambda *_: stop.set())
|
signal.signal(signal.SIGTERM, lambda *_: stop.set())
|
||||||
|
|
||||||
def publish_state() -> None:
|
# Proprioception feedback: publish observation.state (29 joint .q) so the
|
||||||
period = 1.0 / args.state_fps if args.state_fps > 0 else 0.0
|
# laptop-side inference client can feed it to the policy. DDS stays local to
|
||||||
joint_names = [j.name for j in G1_29_JointIndex]
|
# the robot; only this compact JSON state crosses the network (like the
|
||||||
while not stop.is_set():
|
# camera/action ZMQ channels). get_observation() here reads only DDS lowstate
|
||||||
t0 = time.time()
|
# (the robot is built with cameras={}), so it never touches the USB cameras.
|
||||||
obs = robot.get_observation()
|
# Disable with --state-fps <=0 (e.g. to reproduce the plain-teleop setup).
|
||||||
if obs:
|
state_sock = None
|
||||||
state = {f"{name}.q": float(obs.get(f"{name}.q", 0.0)) for name in joint_names}
|
if args.state_fps > 0:
|
||||||
try:
|
state_sock = ctx.socket(zmq.PUB)
|
||||||
state_sock.send_json(state, zmq.NOBLOCK)
|
state_sock.setsockopt(zmq.SNDHWM, 2)
|
||||||
except zmq.Again:
|
state_sock.setsockopt(zmq.LINGER, 0)
|
||||||
pass
|
state_sock.bind(f"tcp://0.0.0.0:{args.state_port}")
|
||||||
if period:
|
logger.info("Publishing observation.state on :%d at %.0f Hz", args.state_port, args.state_fps)
|
||||||
|
|
||||||
|
def publish_state() -> None:
|
||||||
|
period = 1.0 / args.state_fps
|
||||||
|
joint_names = [j.name for j in G1_29_JointIndex]
|
||||||
|
while not stop.is_set():
|
||||||
|
t0 = time.time()
|
||||||
|
obs = robot.get_observation()
|
||||||
|
if obs:
|
||||||
|
state = {f"{name}.q": float(obs.get(f"{name}.q", 0.0)) for name in joint_names}
|
||||||
|
try:
|
||||||
|
state_sock.send_json(state, zmq.NOBLOCK)
|
||||||
|
except zmq.Again:
|
||||||
|
pass
|
||||||
time.sleep(max(0.0, period - (time.time() - t0)))
|
time.sleep(max(0.0, period - (time.time() - t0)))
|
||||||
|
|
||||||
threading.Thread(target=publish_state, daemon=True).start()
|
threading.Thread(target=publish_state, daemon=True).start()
|
||||||
|
else:
|
||||||
|
logger.info("observation.state PUB disabled (--state-fps<=0); inference client will get no proprio")
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
try:
|
try:
|
||||||
@@ -234,10 +244,11 @@ def main() -> None:
|
|||||||
finally:
|
finally:
|
||||||
logger.info("Shutting down onboard controller...")
|
logger.info("Shutting down onboard controller...")
|
||||||
stop.set()
|
stop.set()
|
||||||
try:
|
if state_sock is not None:
|
||||||
state_sock.close(linger=0)
|
try:
|
||||||
except Exception as e: # noqa: BLE001
|
state_sock.close(linger=0)
|
||||||
logger.warning("State socket close failed: %s", e)
|
except Exception as e: # noqa: BLE001
|
||||||
|
logger.warning("State socket close failed: %s", e)
|
||||||
if camera_server is not None:
|
if camera_server is not None:
|
||||||
try:
|
try:
|
||||||
camera_server.stop()
|
camera_server.stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user