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:
Martino Russi
2026-07-19 12:39:28 +02:00
parent 48d8db1f3e
commit 437248d823
+40 -29
View File
@@ -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()