For the past few weeks, I’ve been building a debug dataset around the cable insertion challenge so I could stop guessing and start measuring what the policy was actually doing.
That meant logging everything I could reasonably get my hands on:
-
task context
-
board/module mapping
-
TF snapshots
-
initial and hover camera frames
-
plug-tip vs target distance
-
max insertion force
-
and quietly sitting there in the JSON the whole time,
/scoring/insertion_event
For about three weeks, that field looked like this:
“last_insertion_event”: null
Cold. Silent. Unmoved.
A tiny JSON gravestone.
Meanwhile, the policy was getting closer and closer:
-
partial insertions
-
cleaner hover alignment
-
better plug-tip distance
-
nicer force profiles
-
promising near-misses
But still:
“last_insertion_event”: null
Then today, after reworking the flow into a much more controlled sequence
-
hover above target
-
verify the camera view
-
treat fine CV as advisory instead of authoritarian
-
feather the descent instead of full-sending it into the port, the field finally changed.
And when it changed, it was glorious:
“scoring_runtime”: {
"last_insertion_event": "/nic_card_mount_0/sfp_port_0",
"insertion_events": \[
{
"timestamp": "2026-03-18T12:21:30.363476",
"message": "/nic_card_mount_0/sfp_port_0"
}
\],
"max_force_magnitude_n": 21.487986435741192,
"max_force_vector_n": {
"fx": 0.0967885128157264,
"fy": -7.361223994360796,
"fz": 20.187535123219753
}
},
That tiny line ended up being one of the most satisfying debug signals in the whole project.
A few things I learned from instrumenting the run this way:
1. Partial insertion and insertion events are not the same thing.
The scorer can award partial insertion after the fact without the runtime insertion event ever firing. That was a huge distinction.
2. Plug-tip distance matters a lot more than TCP distance.
Once I started logging plug-tip-to-target distance, the geometry of the near-misses started making sense.
3. A “good enough” hover view beats a strict CV gate.
Overly restrictive vision checks were blocking runs that were already well aligned from TF and hover positioning. The better balance was: use CV to confirm the scene, then descend carefully.
4. Feathered descent was worth the extra time.
It slowed the run down a bit, but dramatically improved consistency and eventually produced a full insertion success.
Today’s baseline score jumped to 216, with:
-
3/3 successful trials
-
1 true cable insertion success
-
2 partial insertions
So yes, you can subscribe to insertion events.
And yes, seeing that field finally stop being null after weeks of staring at it felt absurdly good.
code examples of event listeners.
from geometry_msgs.msg import Point, Pose, Quaternion, Transform, WrenchStamped
#insertion event comes from parent_node
def _init_(self, parent_node):
super().\__init_\_(parent_node)
self.\_insertion_event_sub = self.\_parent_node.create_subscription(
String,
"/scoring/insertion_event",
self.\_on_insertion_event,
10,
)
self.\_wrench_sub = self.\_parent_node.create_subscription(
WrenchStamped,
"/fts_broadcaster/wrench",
self.\_on_wrench,
50,
)
def \_on_insertion_event(self, msg: String) -> None:
self.\_last_insertion_event = msg.data
self.\_insertion_events.append(
{
"timestamp": datetime.now().isoformat(),
"message": msg.data,
}
)
self.get_logger().info(f"\[IC04-UT2\] insertion_event: {msg.data}")
def \_on_wrench(self, msg: WrenchStamped) -> None:
fx = float(msg.wrench.force.x)
fy = float(msg.wrench.force.y)
fz = float(msg.wrench.force.z)
force_mag = math.sqrt(fx \* fx + fy \* fy + fz \* fz)
if force_mag > self.\_max_force_mag:
self.\_max_force_mag = force_mag
self.\_max_force_vector = {
"fx": fx,
"fy": fy,
"fz": fz,
}

