|
|
@@ -54,7 +54,7 @@ class RobotController:
|
|
|
|
|
|
# Calculate total slots and initialize work queue
|
|
|
self.total_slots = sum(len(device.slots) for device in self.devices)
|
|
|
- self.work_queue: List[MeasurementResult] = []
|
|
|
+ self.pending_measurements: List[MeasurementResult] = []
|
|
|
|
|
|
self.gripper_occupied = False
|
|
|
self.suction_state = False
|
|
|
@@ -73,7 +73,7 @@ class RobotController:
|
|
|
self.mqtt_handler.register_device(
|
|
|
device_id=device.id,
|
|
|
num_slots=len(device.slots),
|
|
|
- callback=lambda measurement_result: self.work_queue.append(measurement_result)
|
|
|
+ callback=lambda measurement_result: self.pending_measurements.append(measurement_result)
|
|
|
)
|
|
|
|
|
|
async def perform_homing(self):
|
|
|
@@ -150,6 +150,14 @@ class RobotController:
|
|
|
except IndexError as e:
|
|
|
logger.error(f"Slot {slot_id} not found in device {device_id}")
|
|
|
return None
|
|
|
+
|
|
|
+ def get_mag_by_name(self, name: str) -> Optional[DefeederMagazineConfig]:
|
|
|
+ """Retrieve a defeeder magazine by its name."""
|
|
|
+ for mag in self.defeeder_magazines:
|
|
|
+ if mag.name.lower() == name.lower():
|
|
|
+ return mag
|
|
|
+ logger.error(f"Magazine '{name}' not found")
|
|
|
+ return None
|
|
|
|
|
|
def get_next_free_slot(self) -> Optional[SlotConfig]:
|
|
|
"""
|
|
|
@@ -165,37 +173,47 @@ class RobotController:
|
|
|
logger.warning("No free slots available")
|
|
|
return None
|
|
|
|
|
|
- async def process_finished_measurement(self):
|
|
|
+ async def process_finished_measurements(self):
|
|
|
"""
|
|
|
- Process the next finished measurement from the work queue.
|
|
|
- Picks the cell from the slot and sorts it based on result.
|
|
|
+ Process the all finished measurements from the work queue.
|
|
|
+ Picks the cell from the slot and adds it to the pending buffer for sorting.
|
|
|
"""
|
|
|
- if not self.work_queue:
|
|
|
+ if not self.pending_measurements:
|
|
|
logger.info("No finished measurements in queue")
|
|
|
- return
|
|
|
-
|
|
|
- measurement_result = self.work_queue.pop(0)
|
|
|
- cell = self.get_cell_by_id(measurement_result.cell_id)
|
|
|
- try:
|
|
|
- cell_status = CellStatus(measurement_result.status)
|
|
|
- except ValueError as e:
|
|
|
- logger.error(f"Measurement ({measurement_result}) could not be processed. Invalid cell status '{measurement_result.status}'")
|
|
|
- return
|
|
|
-
|
|
|
- waiting_slot = self.get_slot_by_id(measurement_result.device_id, measurement_result.slot_id)
|
|
|
- if not waiting_slot:
|
|
|
- logger.error(f"Measurement ({measurement_result}) could not be processed.")
|
|
|
- return
|
|
|
- await self.pick_cell_from_slot(waiting_slot)
|
|
|
+ return
|
|
|
|
|
|
- if not cell: # Cell not found, create new
|
|
|
- cell = Cell(measurement_result.cell_id, cell_status, health=measurement_result.health)
|
|
|
- self.cells[measurement_result.cell_id] = cell
|
|
|
- else:
|
|
|
- cell.health = measurement_result.health
|
|
|
- cell.status = cell_status
|
|
|
+ remaining = []
|
|
|
+ for measurement in self.pending_measurements:
|
|
|
+ cell = self.get_cell_by_id(measurement.cell_id)
|
|
|
+ try:
|
|
|
+ cell_status = CellStatus(measurement.status)
|
|
|
+ except ValueError as e:
|
|
|
+ logger.error(f"Measurement ({measurement}) could not be processed. Invalid cell status '{measurement.status}'")
|
|
|
+ return
|
|
|
+
|
|
|
+ waiting_slot = self.get_slot_by_id(measurement.device_id, measurement.slot_id)
|
|
|
+ if not waiting_slot:
|
|
|
+ logger.error(f"Measurement ({measurement}) could not be processed. No slot found for device {measurement.device_id} and slot {measurement.slot_id}")
|
|
|
+ return
|
|
|
+ if not await self.pick_cell_from_slot(waiting_slot):
|
|
|
+ remaining.append(measurement)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not cell: # Cell not found, create new
|
|
|
+ cell = Cell(measurement.cell_id, cell_status, health=measurement.health)
|
|
|
+ self.cells[measurement.cell_id] = cell
|
|
|
+ else:
|
|
|
+ cell.health = measurement.health
|
|
|
+ cell.status = cell_status
|
|
|
+
|
|
|
+ # Sort the cell and retry next time if unsuccessful
|
|
|
+ success = await self.sort_cell(cell)
|
|
|
+ if not success:
|
|
|
+ remaining.append(measurement)
|
|
|
+
|
|
|
+ self.pending_measurements = remaining
|
|
|
|
|
|
- await self.sort_cell(cell)
|
|
|
+
|
|
|
|
|
|
async def pick_cell_from_feeder(self):
|
|
|
"""
|
|
|
@@ -327,7 +345,7 @@ class RobotController:
|
|
|
self.activate_endeffector()
|
|
|
if not self.suction_state:
|
|
|
logger.error("Suction state is off, cannot pick cell")
|
|
|
- return False
|
|
|
+ return None
|
|
|
self.gripper_occupied = True
|
|
|
cell_id = slot.cell_id
|
|
|
slot.occupied = False
|
|
|
@@ -354,15 +372,24 @@ class RobotController:
|
|
|
if cell.id in self.cells:
|
|
|
del self.cells[cell.id] # Remove cell from our database TODO [SG]: Should we keep it for history?
|
|
|
|
|
|
+ dropoff_mag = None
|
|
|
+
|
|
|
+ # If cell is in error state, drop it off in the error magazine (if available)
|
|
|
if cell.status is CellStatus.ERROR:
|
|
|
- cell.health = 0 # will be dropped off in the lowest grade
|
|
|
+ cell.health = 0 # will be dropped off in the "error" magazine
|
|
|
+ dropoff_mag = self.get_mag_by_name("error") # Ensure error magazine exists
|
|
|
+ if not dropoff_mag:
|
|
|
+ logger.warning(f"No error magazine configured, cannot sort cell {cell.id} to error")
|
|
|
+
|
|
|
for mag in self.defeeder_magazines:
|
|
|
if cell.health >= mag.health_range[0] and cell.health <= mag.health_range[1]:
|
|
|
- await self.dropoff_cell(mag)
|
|
|
- logger.info(f"Cell {cell.id} sorted to magazine {mag.name}")
|
|
|
- return True
|
|
|
- logger.error(f"No suitable magazine found for cell {cell.id} with capacity {cell.health}")
|
|
|
- return False
|
|
|
+ dropoff_mag = mag
|
|
|
+
|
|
|
+ if not dropoff_mag:
|
|
|
+ logger.error(f"No suitable magazine found for cell {cell.id} with capacity {cell.health}")
|
|
|
+ return False
|
|
|
+
|
|
|
+ return await self.dropoff_cell(dropoff_mag)
|
|
|
|
|
|
async def dropoff_cell(self, defeeder_mag: Optional[DefeederMagazineConfig] = None):
|
|
|
"""
|
|
|
@@ -376,6 +403,12 @@ class RobotController:
|
|
|
# If no defeeddefeeder_mag_idx is given, use the last one in the list
|
|
|
if defeeder_mag is None:
|
|
|
defeeder_mag = self.defeeder_magazines[-1]
|
|
|
+
|
|
|
+ # Check if the magazine has space left
|
|
|
+ if defeeder_mag.max_num_cells is not None and defeeder_mag.current_num_cells >= defeeder_mag.max_num_cells:
|
|
|
+ logger.error(f"Magazine '{defeeder_mag.name}' is full, cannot drop off cell")
|
|
|
+ return False
|
|
|
+
|
|
|
if self.defeeder_queue is not None:
|
|
|
try:
|
|
|
await self.defeeder_queue.put(defeeder_mag)
|
|
|
@@ -384,9 +417,9 @@ class RobotController:
|
|
|
|
|
|
pos = self.config.defeeder.robot_pos
|
|
|
safe_pos = (pos[0], pos[1], self.config.movement.safe_height)
|
|
|
- logger.info(f"Moving to dropoff (safe) {safe_pos}...")
|
|
|
+ logger.debug(f"Moving to dropoff (safe) {safe_pos}...")
|
|
|
await self.movement.move_to_position(*safe_pos)
|
|
|
- logger.info(f"Moving to dropoff position {pos}...")
|
|
|
+ logger.debug(f"Moving to dropoff position {pos}...")
|
|
|
await self.movement.move_to_position(*pos)
|
|
|
# Release cell
|
|
|
if not self.deactivate_endeffector():
|
|
|
@@ -394,9 +427,9 @@ class RobotController:
|
|
|
self.gripper_occupied = False
|
|
|
|
|
|
# Move back to safe height
|
|
|
- logger.info(f"Moving to dropoff position (safe) {safe_pos}...")
|
|
|
+ logger.debug(f"Moving to dropoff position (safe) {safe_pos}...")
|
|
|
await self.movement.move_to_position(*safe_pos)
|
|
|
- logger.info(f"Cell dropped off at defeeder")
|
|
|
+ logger.info(f"Cell dropped off at defeeder {defeeder_mag.name}")
|
|
|
|
|
|
def check_cell_voltage(self, voltage, cell_id_str = ""):
|
|
|
"""
|
|
|
@@ -407,7 +440,7 @@ class RobotController:
|
|
|
logger.info(f"Cell {cell_id_str} voltage too low, discarding cell")
|
|
|
return False
|
|
|
if voltage < 0:
|
|
|
- logger.info(f"Cell {cell_id_str} has wrong polarity, discarding cell")
|
|
|
+ logger.warning(f"Cell {cell_id_str} has wrong polarity, discarding cell")
|
|
|
return False
|
|
|
logger.info(f"Cell {cell_id_str} voltage({voltage}) is good")
|
|
|
return True
|