Skip to content

feat: run gripper teleop over the validated zenoh link - #15

Merged
yd-sl merged 2 commits into
mainfrom
feat/zenoh-teleop-link
Sep 29, 2026
Merged

yd-sl merged 2 commits into
mainfrom
feat/zenoh-teleop-link

Conversation

@yd-sl

@yd-sl yd-sl commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The teleop shipped in #11 invented its own network layer — a plain-UDP transport on litegrip/teleop/{master_id} — and was merged on hardware-free tests alone. The system actually validated on hardware is a zenoh point-to-point link, so the SDK now uses that structure, and picks up the safety rules from the validated design spec that #11 was missing.

zenoh becomes an optional extra. litegrip-python is deliberately zero-dependency, so import litegrip must keep working on a bare controller that will never teleoperate: the new module is resolved lazily, never at package import time.

Changes

  • src/litegrip/zenoh_link.py (new) — Listener (leader: listens, publishes, matching), Connector (follower: connects, subscribes), LatestSlot (latest-wins slot whose take() does not clear the receive timestamp, and whose peek_age() returns None for "never received"), and ZenohTeleopTransport, which adapts both onto the existing TeleopTransport seam. Discovery is off (mode="peer", no multicast, no gossip), so only the explicit endpoint connects the two ends.
  • src/litegrip/teleop.py — topic is now litearm/v4/{grip_id}/gripper_teleop and the default port is 17448 (the arm uses 17447), matching grip_wire; non-finite frames are rejected at the wire boundary on both ends, including the first frame used for the align; the follower clamps its target into its own calibrated travel every cycle; send_mit_frame returning False counts as send_failed and a non-enabled error_code sets fault; follow gains fall back to the calibration's kp/kd; staleness reads the slot's "never received" state instead of a 0.0 timestamp sentinel; the align wait keeps sending hold frames; a non-positive watchdog is rejected at construction; TeleopNotReady / check_ready gate the session.
  • src/litegrip/gripper.py — teleop_start(..., link="zenoh", grip_id=..., port=17448); the leader's Listener is resident for the life of the gripper and closed by disconnect(), while the follower's Connector stays per-session.
  • src/litegrip/__init__.py — lazy __getattr__ re-exports for the zenoh classes, with an "install litegrip[zenoh]" message when the dependency is absent.
  • pyproject.toml — [project.optional-dependencies] zenoh = ["eclipse-zenoh>=1.0"].
  • tests/test_zenoh_link.py (new) and tests/test_teleop.py — config keys, slot semantics, a real loopback Listener → Connector roundtrip, and the safety rules above.
  • README.md / readme_zn.md / examples/teleop.py — zenoh install, topic/port, leader/follower roles, and the non-finite-frame note.

Note for consumers: teleop_start's master_id= keyword is replaced by grip_id=, and the default transport changes from UDP to zenoh. Under this repository's policy that publishes as a minor, though it does rename a public keyword — say so if you want it treated as a major before merging.

Testing

$ cd /home/qql/sl/litegrip-python && python3 -m unittest discover -s tests -t tests
Ran 142 tests in 1.880s

OK

$ npx --yes markdownlint-cli2@0.23.3 "README.md" "readme_zn.md"
Summary: 0 issues in 0 files

The zenoh loopback cases run for real against the installed eclipse-zenoh 1.7.2 (13 zenoh tests, none skipped). import litegrip with the zenoh module blocked still succeeds, and litegrip.ZenohTeleopTransport then raises the "install litegrip[zenoh]" message.

Teleoperation itself was exercised on real hardware on 2026-09-29 (leader on can0, follower on can1, hand-driven jaws), with tracking confirmed in both directions and no rejected / fault. That run covered the follower algorithm and the safety rules; I did not separately record which transport that session used, so treat the zenoh wire itself as covered by the loopback tests here rather than by that run.

Issues

None — the repository has no issue tracker entries, matching the previous PRs.

The teleop shipped in #11 invented its own network layer -- a plain-UDP
transport on `litegrip/teleop/{master_id}` -- and was merged on hardware-free
tests alone. The system actually validated on hardware is a zenoh
point-to-point link, so the SDK now uses that structure: the topic and port
come from the shared litearm namespace, both ends run in peer mode with
multicast and gossip off, the leader listens on a TCP port and the follower
connects to it, and the frame stays byte-identical to the litearm stack's.

zenoh is an optional extra (`pip install litegrip[zenoh]`), resolved lazily,
so `import litegrip` still works on a machine that will never teleoperate.
The UDP transport stays available through `link="udp"` and is no longer the
default.

Alongside the transport, port the safety rules the validated design spec has
and #11 was missing: non-finite frames are dropped at the wire boundary
rather than clamped onto a hard stop; the follower's target is clamped into
its own calibrated travel every cycle; a `send_mit_frame` that returns False
and a gripper `error_code` other than "enabled" are counted instead of
swallowed; follow gains fall back to the calibration's own kp/kd; a
non-positive watchdog is rejected at construction; staleness is read from the
slot's "never received" state rather than a 0.0 timestamp sentinel; teleop
refuses to start on an uncalibrated or zero-travel gripper; and the leader's
zenoh listener is resident for the life of the gripper, because rebuilding it
per session leaves the port bound and makes matching fail intermittently.
`litegrip.teleop` has no zenoh dependency, so the import belongs at module
level. Behind the `HAVE_ZENOH` guard it left `teleop_topic` undefined when
zenoh is absent, and the module failed to import instead of skipping — which
is exactly the machine CI runs on.
@yd-sl
yd-sl merged commit 6db8148 into main Sep 29, 2026
1 check passed
@yd-sl
yd-sl deleted the feat/zenoh-teleop-link branch September 29, 2026 07:49
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant