⚠️ THE VENDORED CALIB IS A PLACEHOLDER (identity transform).
Pick-and-place will silently miss every grasp until you replace config/calibrations/my_eih_calib.calib with a real calibration result. See Replacing the calibration.
What it does
Runs upstream easy_handeye2‘s handeye_publisher node, which broadcasts a single static TF on /tf_static based on a calibration YAML. In eye-in-hand mode (the default we ship), that’s:
link6 → camera_color_optical_frame
link6 is published by primitive-agilex-piper-description-rbnx.
camera_color_optical_frame is published by primitive-orbbec-dabai_dcw-camera-rbnx once the camera is up.
With this package running, the chain base_link → link1..link6 → camera_color_optical_frame is complete and downstream consumers (service-grasp-pose-rbnx, skill-pick-rbnx) can transform pixel detections into base-frame poses.
Capability surface
capabilities: []
Nothing routed on atlas. TF is a global ROS 2 side-channel (/tf_static); atlas-routing would add indirection with no benefit — every consumer uses tf2_ros::Buffer to look up transforms, not an atlas-routed RPC. This package just RegisterPrimitive on atlas so rbnx boot‘s wait_for_registration loop unblocks.
Driver-init lifecycle
There is no Driver(CMD_INIT) handler. The package registers a provider on atlas so rbnx boot proceeds, but does not receive a config JSON. start.sh:
stages the calibration file into ~/.ros2/easy_handeye2/calibrations/<name>.calib (upstream hardcodes that path);
…and handeye_publisher reads <CALIBRATIONS_DIRECTORY>/<name>.calib where <name> is the launch arg. We do not modify upstream (stays vendor-clean for future re-imports), so at start time atlas_register_and_launch.py copies config/calibrations/<name>.calib into ~/.ros2/easy_handeye2/calibrations/<name>.calib and then runs the launch.
If ~/.ros2/easy_handeye2/calibrations/<name>.calib already exists (operator placed a fresh calib there directly via the upstream calibration GUI), we leave it alone and warn — overwriting would clobber the operator’s most recent calibration with a stale vendored copy. Set EASY_HANDEYE2_FORCE_STAGE=1 if you really do want to overwrite (i.e. you’ve intentionally committed a new calib to this package and want it to win).
Boot ordering
Independent of primitive-agilex-piper-description-rbnx — the two publish disjoint /tf_static subtrees. Recommend listing this after description just for readability.
This package has no */driver capability, so rbnx boot v0.1 cannot deliver per-package config via Driver(CMD_INIT, config_json). The config: block in the deploy manifest is ignored. Use the top-level env: block instead:
The shipped config/calibrations/my_eih_calib.calib is an identity transform with eye-in-hand metadata — robot_effector_frame: link6 / tracking_base_frame: camera_color_optical_frame. The TF it publishes will be zero-translation, identity-rotation. You must replace it.
Option A — Calibrate fresh on the real hardware (recommended)
Standard easy_handeye2 workflow. Requires the camera to actually see an ArUco marker mounted at tracking_marker_frame.
# 1. Bring up Piper + Orbbec + Piper URDF.
cd <deploy_dir>
rbnx boot
# 2. In another shell — run the upstream calibration GUI.
ros2 launch easy_handeye2 calibrate.launch.py \
calibration_type:=eye_in_hand \
name:=my_eih_calib \
robot_base_frame:=base_link \
robot_effector_frame:=link6 \
tracking_base_frame:=camera_color_optical_frame \
tracking_marker_frame:=tracking_marker
# 3. Take 10-20 samples (move arm to varied poses, click "Take
# Sample"), then "Compute" → "Save". Upstream writes:
# ~/.ros2/easy_handeye2/calibrations/my_eih_calib.calib
# 4. Copy that result back into this package:
cp ~/.ros2/easy_handeye2/calibrations/my_eih_calib.calib \
<this_package>/config/calibrations/
# 5. git commit + push. Other deploy machines pick up the new calib
# automatically next time they pull + rebuild.
The basename (minus .calib) becomes the launch name:=.
Verification
rbnx caps | grep easy_handeye2
# expect: easy_handeye2 provider, 0 capabilities
ros2 run tf2_ros tf2_echo link6 camera_color_optical_frame
# expect: a constant transform (the static calibration result)
# - with the placeholder calib, this is identity (translation 0,
# quaternion (0,0,0,1)) which means "camera at the center of
# link6 with no rotation" — physically wrong.
# Full chain check:
ros2 run tf2_ros tf2_echo base_link camera_color_optical_frame
# expect: a transform that varies as the arm moves (joint TF
# from RSP) but with the calibration step constant.
Frame contract
frame
published by
role
base_link
primitive-agilex-piper-description-rbnx
arm root
link1..link6
primitive-agilex-piper-description-rbnx
joint-driven
camera_color_optical_frame
primitive-orbbec-dabai_dcw-camera-rbnx
RGB sensor frame
link6 → camera_color_optical_frame
this package
static hand-eye TF
If tracking_base_frame in the calib differs from camera_color_optical_frame (e.g. you use the depth sensor frame instead), update both this calib AND the grasp-pose consumer’s frame expectation.
Why we vendor instead of apt install ros-humble-easy-handeye2
Reproducibility across deploy machines — vendoring guarantees byte-for-byte the same publisher behavior across different Jetsons.
Calib file format pinning — the YAML schema is upstream’s HandeyeCalibration msg with set_message_fields. If upstream adds a field and we apt-upgrade, our placeholder + operator’s old .calib files break. Vendoring isolates us from that.
No build surprises — upstream’s package.xml declares python-transforms3d-pip, python3-opencv, rqt_gui as exec_depend for the calibration GUI. We only run handeye_publisher (which does not import any of those), so we silently tolerate them being absent on the build machine.
Multi-arm note
If two arms are present (future), each must have its own calib file with a unique name. Spawn this package twice in the deploy manifest with different EASY_HANDEYE2_CALIB_NAME per instance. The link6 / camera_color_optical_frame frame names will need to differ between instances too — that is a piper_description / OrbbecSDK URDF/namespace issue, outside this package’s scope.
License
This package: MulanPSL-2.0. Vendored easy_handeye2 / easy_handeye2_msgs: see their respective LICENSE files.
primitive-agilex-piper-handeye-rbnx
Robonix package for hand-eye calibration TF publishing on the Piper + Orbbec Dabai DCW grasp pipeline. Vendors
easy_handeye20.5.0.Catalog name:
robonix.primitive.agilex.piper.handeye.What it does
Runs upstream
easy_handeye2‘shandeye_publishernode, which broadcasts a single static TF on/tf_staticbased on a calibration YAML. In eye-in-hand mode (the default we ship), that’s:link6is published byprimitive-agilex-piper-description-rbnx.camera_color_optical_frameis published byprimitive-orbbec-dabai_dcw-camera-rbnxonce the camera is up.With this package running, the chain
base_link → link1..link6 → camera_color_optical_frameis complete and downstream consumers (service-grasp-pose-rbnx,skill-pick-rbnx) can transform pixel detections into base-frame poses.Capability surface
Nothing routed on atlas. TF is a global ROS 2 side-channel (
/tf_static); atlas-routing would add indirection with no benefit — every consumer usestf2_ros::Bufferto look up transforms, not an atlas-routed RPC. This package justRegisterPrimitiveon atlas sorbnx boot‘swait_for_registrationloop unblocks.Driver-init lifecycle
There is no
Driver(CMD_INIT)handler. The package registers a provider on atlas sorbnx bootproceeds, but does not receive a config JSON.start.sh:~/.ros2/easy_handeye2/calibrations/<name>.calib(upstream hardcodes that path);ros2 launch easy_handeye2 publish.launch.py name:=<name>.Calib staging quirk (important for debugging)
Upstream’s
easy_handeye2/easy_handeye2/__init__.pyhardcodes:…and
handeye_publisherreads<CALIBRATIONS_DIRECTORY>/<name>.calibwhere<name>is the launch arg. We do not modify upstream (stays vendor-clean for future re-imports), so at start timeatlas_register_and_launch.pycopiesconfig/calibrations/<name>.calibinto~/.ros2/easy_handeye2/calibrations/<name>.caliband then runs the launch.If
~/.ros2/easy_handeye2/calibrations/<name>.calibalready exists (operator placed a fresh calib there directly via the upstream calibration GUI), we leave it alone and warn — overwriting would clobber the operator’s most recent calibration with a stale vendored copy. SetEASY_HANDEYE2_FORCE_STAGE=1if you really do want to overwrite (i.e. you’ve intentionally committed a new calib to this package and want it to win).Boot ordering
Independent of
primitive-agilex-piper-description-rbnx— the two publish disjoint/tf_staticsubtrees. Recommend listing this after description just for readability.Layout
Config / overrides
This package has no
*/drivercapability, sorbnx boot v0.1cannot deliver per-package config viaDriver(CMD_INIT, config_json). Theconfig:block in the deploy manifest is ignored. Use the top-levelenv:block instead:EASY_HANDEYE2_CALIB_PATH.calibfile. Basename (minus.calib) becomes the launchname:=.EASY_HANDEYE2_CALIB_NAMEmy_eih_calib<pkg>/config/calibrations/<name>.calib.EASY_HANDEYE2_FORCE_STAGE01= overwrite~/.ros2/easy_handeye2/calibrations/<name>.calib.ROBONIX_ATLAS127.0.0.1:50051Replacing the calibration
The shipped
config/calibrations/my_eih_calib.calibis an identity transform with eye-in-hand metadata —robot_effector_frame: link6/tracking_base_frame: camera_color_optical_frame. The TF it publishes will be zero-translation, identity-rotation. You must replace it.Option A — Calibrate fresh on the real hardware (recommended)
Standard
easy_handeye2workflow. Requires the camera to actually see an ArUco marker mounted attracking_marker_frame.Option B — Swap in a pre-existing calib file
…or point at it via env (no copy needed):
The basename (minus
.calib) becomes the launchname:=.Verification
Frame contract
base_linkprimitive-agilex-piper-description-rbnxlink1..link6primitive-agilex-piper-description-rbnxcamera_color_optical_frameprimitive-orbbec-dabai_dcw-camera-rbnxlink6 → camera_color_optical_frameIf
tracking_base_framein the calib differs fromcamera_color_optical_frame(e.g. you use the depth sensor frame instead), update both this calib AND the grasp-pose consumer’s frame expectation.Why we vendor instead of
apt install ros-humble-easy-handeye2HandeyeCalibrationmsg withset_message_fields. If upstream adds a field and we apt-upgrade, our placeholder + operator’s old.calibfiles break. Vendoring isolates us from that.package.xmldeclarespython-transforms3d-pip,python3-opencv,rqt_guiasexec_dependfor the calibration GUI. We only runhandeye_publisher(which does not import any of those), so we silently tolerate them being absent on the build machine.Multi-arm note
If two arms are present (future), each must have its own calib file with a unique
name. Spawn this package twice in the deploy manifest with differentEASY_HANDEYE2_CALIB_NAMEper instance. Thelink6/camera_color_optical_frameframe names will need to differ between instances too — that is apiper_description/ OrbbecSDK URDF/namespace issue, outside this package’s scope.License
This package: MulanPSL-2.0. Vendored
easy_handeye2/easy_handeye2_msgs: see their respective LICENSE files.