Skip to content

Commit ef46541

Browse files
committed
refactor(palletizing): move conveyor handshake into HAL
Add HAL.WaitForBox() / HAL.BoxDone(name) wrapping the /box_ready and /box_done topics, and drop the FeederLink class + rclpy imports from solution.py so the exercise template uses only the HAL API.
1 parent ce157d7 commit ef46541

2 files changed

Lines changed: 64 additions & 51 deletions

File tree

exercises/palletizing/python_template/HAL_Harmonic.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,54 @@ def publish_graspable_objects():
6161
# Periodic republish so late-joining subscribers receive the list
6262
HAL.create_timer(1.0, publish_graspable_objects)
6363

64+
# ==============================================================
65+
# CONVEYOR HANDSHAKE (feeder coordination)
66+
# ==============================================================
67+
# The conveyor feeder (box_spawner) and the robot coordinate with a two-message
68+
# handshake so boxes are picked one at a time and never pile up:
69+
# - the feeder publishes a box name on /box_ready when a box is stopped at the
70+
# pickup point, then waits;
71+
# - the robot calls BoxDone(name) once the box is palletized, which publishes
72+
# on /box_done and releases the next box.
73+
# Keeping this on the HAL node means the student's solution only ever calls
74+
# WaitForBox()/BoxDone() and never touches ROS topics directly.
75+
76+
HAL._ready_box = None
77+
HAL._processed_boxes = set()
78+
79+
80+
def _on_box_ready(msg):
81+
# Ignore re-announcements of a box we've already handed to the solution.
82+
if msg.data not in HAL._processed_boxes:
83+
HAL._ready_box = msg.data
84+
85+
86+
HAL.create_subscription(String, "/box_ready", _on_box_ready, 10)
87+
HAL.box_done_pub = HAL.create_publisher(String, "/box_done", 10)
88+
89+
90+
def WaitForBox():
91+
"""Block until the feeder announces a box at the pickup point.
92+
93+
Returns the box name (str). Use it as the handle to pass to BoxDone() once
94+
the box has been stacked on the pallet.
95+
"""
96+
HAL._ready_box = None
97+
while rclpy.ok() and HAL._ready_box is None:
98+
rclpy.spin_once(HAL, timeout_sec=0.1)
99+
HAL._processed_boxes.add(HAL._ready_box)
100+
return HAL._ready_box
101+
102+
103+
def BoxDone(name):
104+
"""Tell the feeder the box has been palletized; releases the next box.
105+
106+
Pass the name returned by WaitForBox().
107+
"""
108+
HAL.box_done_pub.publish(String(data=name))
109+
print(f"[HAL] BoxDone({name}) -> released next box")
110+
111+
64112
# ==============================================================
65113
# MoveAbsJ (IDÉNTICO a classic)
66114
# ==============================================================

exercises/palletizing/python_template/solution.py

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
gripper through the palletizing task: a conveyor feeds boxes one at a time to a
55
fixed pickup point, and the robot stacks them into an ordered grid on the pallet.
66
7-
Coordination with the feeder (box_spawner.py) is a two-message handshake:
8-
- the feeder publishes the box name on /box_ready when a box is stopped at the
9-
pickup point, and then waits;
10-
- this code picks and stacks the box, then publishes the name on /box_done,
11-
which releases the next box.
7+
Coordination with the feeder (box_spawner.py) is a two-call handshake, fully
8+
wrapped by the HAL so this code never touches ROS directly:
9+
- HAL.WaitForBox() blocks until a box is stopped at the pickup point and
10+
returns its name;
11+
- HAL.BoxDone(name) reports the box is stacked, which releases the next box.
1212
1313
IMPORTANT — coordinate frames and tuning
1414
========================================
@@ -24,10 +24,6 @@
2424
cup just touches the box. Everything marked `# TUNE` is a candidate to adjust.
2525
"""
2626

27-
import rclpy
28-
from rclpy.node import Node
29-
from std_msgs.msg import String
30-
3127
import HAL_Harmonic as HAL
3228

3329
# --- Geometry ---------------------------------------------------------------
@@ -101,34 +97,7 @@ def grid_cell(index):
10197
return x, y, z, layer, row, col
10298

10399

104-
class FeederLink(Node):
105-
"""Handshake node: receive /box_ready, acknowledge with /box_done."""
106-
107-
def __init__(self):
108-
super().__init__("palletizing_solution")
109-
self._ready_box = None
110-
self._processed_boxes = set()
111-
self.create_subscription(String, "/box_ready", self._on_ready, 10)
112-
self._done_pub = self.create_publisher(String, "/box_done", 10)
113-
114-
def _on_ready(self, msg):
115-
if msg.data not in self._processed_boxes:
116-
self._ready_box = msg.data
117-
118-
def wait_for_box(self):
119-
"""Block until a box is announced at the pickup point; return its name."""
120-
self._ready_box = None
121-
while rclpy.ok() and self._ready_box is None:
122-
rclpy.spin_once(self, timeout_sec=0.1)
123-
self._processed_boxes.add(self._ready_box)
124-
return self._ready_box
125-
126-
def box_done(self, name):
127-
"""Tell the feeder the box has been palletized; release the next."""
128-
self._done_pub.publish(String(data=name))
129-
130-
131-
def pick(link):
100+
def pick():
132101
"""Lower onto the box at the pickup point, grip, and lift clear."""
133102
bx = PICK_WORLD_X
134103
by = PICK_WORLD_Y
@@ -137,17 +106,17 @@ def pick(link):
137106

138107
# Swing gracefully from home to a safe high clearance point directly over the box
139108
HAL.MoveJoint([bx, by, approach_z + 0.3], CUP_DOWN_YPR, SPEED, SETTLE)
140-
109+
141110
# Smooth, single vertical descent onto the box
142111
HAL.MoveLinear([bx, by, grip_z], CUP_DOWN_YPR, SPEED, SETTLE)
143-
112+
144113
HAL.SuctionSet(True, GRIP_PAUSE) # vacuum on
145-
114+
146115
# Lift the box clear in a single motion
147116
HAL.MoveLinear([bx, by, approach_z + 0.3], CUP_DOWN_YPR, SPEED, SETTLE)
148117

149118

150-
def place(link, index):
119+
def place(index):
151120
"""Carry the held box to grid cell `index` and release it."""
152121
wx, wy, wz, layer, row, col = grid_cell(index)
153122
# tool0 sits CUP_REACH + box height above the target box centre.
@@ -163,30 +132,26 @@ def place(link, index):
163132

164133

165134
def main():
166-
# rclpy is already initialised by HAL; just add our coordination node.
167-
link = FeederLink()
168-
169135
# Natural 'up' home pose: [pan, lift, elbow, w1, w2, w3] in DEGREES
170136
# This keeps the arm safely clear of the workspace without awkward IK contortions.
171137
home_joints = [0.0, -90.0, 0.0, -90.0, 0.0, 0.0]
172138
HAL.MoveAbsJ(home_joints, SPEED, SETTLE)
173139

174140
total = GRID_COLS * GRID_ROWS * GRID_LAYERS
175141
for index in range(total):
176-
name = link.wait_for_box()
142+
name = HAL.WaitForBox()
177143
if name is None:
178144
break
179145
print(f"box {name} ready -> placing as #{index}")
180-
181-
pick(link)
182-
place(link, index)
183-
link.box_done(name)
184-
146+
147+
pick()
148+
place(index)
149+
HAL.BoxDone(name)
150+
185151
# Return to home pose before waiting for the next box to ensure a clean sweep
186152
HAL.MoveAbsJ(home_joints, SPEED, SETTLE)
187153

188154
print("palletizing complete")
189-
link.destroy_node()
190155

191156

192157
if __name__ == "__main__":

0 commit comments

Comments
 (0)