ROS 2 for Robot Arms, step 2
Listening to the robot
Subscribe to /joint_states, find each joint by name, and publish where the gripper is as a stamped point in base_link.
Callbacks
self.create_subscription(JointState, "/joint_states", self.on_joints, 10) asks ROS to call on_joints(msg) with every message on the topic. Code that listens is event-driven: it never waits in a loop for data. Each callback reacts to one message and keeps whatever it needs on self.
sensor_msgs/msg/JointState
| Field | Type | Meaning |
|---|---|---|
header.stamp | builtin_interfaces/Time | when the joints were read |
name | string[] | the joints' names |
position | float64[] | angles (rad) or slides (m), in the same order |
velocity, effort | float64[] | the same order again (effort is empty here) |
The arrays run in parallel: position[i] belongs to name[i]. Drivers list joints in different orders and add extra ones, like this arm's two fingers. Here the order even changes from run to run, so look joints up by name, never by index:
pos = dict(zip(msg.name, msg.position))
Stamped messages
A geometry_msgs/msg/PointStamped is a point with a header. The header's frame_id says which coordinate frame the numbers are in; the arm's base is base_link. Its stamp says when they were true. Your point describes the moment the joints were read, so copy the joint state's stamp rather than taking the time now. Later nodes can then match it with other data from that instant.
Two nodes, one process
The given Sweeper node moves the arm. main() adds both nodes to one SingleThreadedExecutor and spins it, so both nodes' callbacks run. On a robot they'd more often be separate programs; the topics would connect them just the same.
Your task
In EePublisher, publish the grasp point on /ee_position once for every joint state. Use forward_kinematics from Pick & Place (given), feed it [base, shoulder, elbow, wrist] looked up by name, and send a PointStamped in base_link carrying the joint state's stamp.