PistonDispenser API Reference¶
The PistonDispenser class manages containers that hold and dispense cylindrical pistons. It tracks the number of available pistons and their positions.
Overview¶
PistonDispensers:
- Hold multiple pistons in a vertical stack
- Dispense pistons one at a time from the top
- Track remaining piston count
- Integrate with the state machine for validated retrieval
Class Reference¶
PistonDispenser
¶
PistonDispenser is a class representing the piston dispensers on the side of the Jubilee. It is used to keep track of the number of pistons in each dispenser.
Usage Examples¶
Creating a Dispenser¶
from src.PistonDispenser import PistonDispenser
# Create dispenser with state machine reference
dispenser = PistonDispenser(
index=0, # Dispenser index (0, 1, etc.)
num_pistons=10, # Initial piston count
state_machine=state_machine # Reference to state machine
)
Basic Operations¶
# Check piston count
print(f"Pistons available: {dispenser.num_pistons}")
# Dispense a piston (decrements count)
dispenser.dispense_piston()
print(f"Pistons remaining: {dispenser.num_pistons}")
# Check if empty
if dispenser.num_pistons == 0:
print("Dispenser is empty!")
Integration with JubileeManager¶
Typically used through JubileeManager:
from src.JubileeManager import JubileeManager
manager = JubileeManager(
num_piston_dispensers=2,
num_pistons_per_dispenser=10
)
manager.connect()
# Get piston from dispenser 0
manager._move_to_dispenser(dispenser_index=0)
manager.get_piston_from_dispenser(dispenser_index=0)
# Check remaining pistons
for dispenser in manager.piston_dispensers:
print(f"Dispenser {dispenser.index}: {dispenser.num_pistons} pistons")
Piston Tracking¶
Automatic Count Management¶
The dispenser automatically decrements the piston count:
dispenser = PistonDispenser(index=0, num_pistons=5, state_machine=sm)
print(dispenser.num_pistons) # 5
dispenser.dispense_piston()
print(dispenser.num_pistons) # 4
dispenser.dispense_piston()
print(dispenser.num_pistons) # 3
Checking Availability¶
Before attempting to retrieve a piston:
if dispenser.num_pistons > 0:
dispenser.dispense_piston()
print("Piston retrieved")
else:
print("Dispenser empty - needs refilling")
Position Configuration¶
Dispenser positions are configured in mold_labware.json:
{
"deck": {
"labware": {
"piston_dispenser_0": {
"type": "piston_dispenser",
"name": "Dispenser 0",
"position": {"x": 200, "y": 50, "z": 20},
"ready_pos": "dispenser_0_ready",
"capacity": 20,
"piston_height": 50.0,
"piston_diameter": 10.0,
"stack_spacing": 2.0
}
}
}
}
Corresponding state machine position in motion_platform_positions.json:
{
"positions": {
"dispenser_0_ready": {
"coordinates": {"x": 200, "y": 50, "z": 100, "safe_z": 150},
"description": "Ready position for piston dispenser 0",
"requires_tool": "manipulator",
"allowed_payloads": ["mold"]
}
}
}
Height Calculations¶
Stack Height¶
The dispenser calculates the height of the top piston based on:
- Base position
- Number of remaining pistons
- Piston height
- Stack spacing
# Internal calculation (simplified)
top_piston_z = base_z + (num_pistons * piston_height) + ((num_pistons - 1) * stack_spacing)
Retrieval Position¶
When retrieving a piston:
- Calculate current top piston height
- Move V-axis to approach height (slightly above piston)
- Descend to grip height
- Close gripper
- Ascend with piston
- Decrement count
Multiple Dispensers¶
Working with Multiple Dispensers¶
# Initialize multiple dispensers via JubileeManager
manager = JubileeManager(
num_piston_dispensers=3,
num_pistons_per_dispenser=10
)
manager.connect()
# Access all dispensers
for dispenser in manager.piston_dispensers:
print(f"Dispenser {dispenser.index}: {dispenser.num_pistons} pistons")
# Get piston from specific dispenser
manager._move_to_dispenser(dispenser_index=0)
manager.get_piston_from_dispenser(dispenser_index=0)
# Check which dispenser has pistons
for dispenser in manager.piston_dispensers:
if dispenser.num_pistons > 0:
print(f"Dispenser {dispenser.index} has pistons available")
break
Load Balancing¶
Distribute piston usage across dispensers:
def get_piston_from_any_dispenser(manager):
"""Get piston from first available dispenser."""
for dispenser in manager.piston_dispensers:
if dispenser.num_pistons > 0:
manager._move_to_dispenser(dispenser.index)
manager.get_piston_from_dispenser(dispenser.index)
return dispenser.index
raise RuntimeError("No dispensers have pistons available")
State Machine Integration¶
Validated Retrieval¶
Piston retrieval goes through state machine validation:
from src.MotionPlatformStateMachine import MotionPlatformStateMachine
# Retrieve piston with validation
result = state_machine.validated_retrieve_piston(
piston_dispenser=dispenser,
manipulator_config=manipulator._get_config_dict()
)
if result.valid:
print("Piston retrieved successfully")
else:
print(f"Retrieval failed: {result.reason}")
Requirements for Retrieval¶
- Must be at dispenser ready position
- Manipulator tool must be active
- Payload must be
"mold"(retrieving into a mold) - Dispenser must have pistons available
Error Handling¶
Empty Dispenser¶
try:
if dispenser.num_pistons == 0:
raise RuntimeError("Dispenser is empty")
dispenser.dispense_piston()
except RuntimeError as e:
print(f"Cannot dispense: {e}")
# Handle refill or switch dispenser
Failed Retrieval¶
result = state_machine.validated_retrieve_piston(
piston_dispenser=dispenser,
manipulator_config=config
)
if not result.valid:
print(f"Retrieval failed: {result.reason}")
# Common reasons:
# - "Dispenser has no pistons"
# - "Not at dispenser position"
# - "Wrong payload state"
# - "Wrong tool active"
Refilling Dispensers¶
Manual Refill¶
def refill_dispenser(dispenser, num_pistons):
"""Manually refill a dispenser."""
dispenser.num_pistons = num_pistons
print(f"Dispenser {dispenser.index} refilled with {num_pistons} pistons")
# Refill when empty
for dispenser in manager.piston_dispensers:
if dispenser.num_pistons == 0:
refill_dispenser(dispenser, 10)
Physical Refill Required
The software doesn't physically refill the dispenser. You must manually add pistons to the physical dispenser, then update the count in software.
Tracking Usage¶
def track_dispenser_usage(manager, operation_count):
"""Track piston usage statistics."""
initial_counts = {d.index: d.num_pistons for d in manager.piston_dispensers}
# Perform operations...
final_counts = {d.index: d.num_pistons for d in manager.piston_dispensers}
print("Piston Usage:")
for index in initial_counts:
used = initial_counts[index] - final_counts[index]
print(f" Dispenser {index}: {used} pistons used")
Best Practices¶
Check Before Dispensing¶
Always verify pistons are available:
# GOOD
if dispenser.num_pistons > 0:
dispenser.dispense_piston()
else:
print("Need to refill")
# BAD
dispenser.dispense_piston() # Might go negative!
Monitor Levels¶
Track dispenser levels during operations:
def dispense_with_monitoring(manager, well_list):
"""Dispense to wells with dispenser monitoring."""
for well_id in well_list:
# Check dispenser levels
available = sum(d.num_pistons for d in manager.piston_dispensers)
if available == 0:
print("All dispensers empty - stopping")
break
if available < 5:
print(f"Warning: Only {available} pistons remaining")
# Perform dispense
manager.dispense_to_well(well_id, target_weight=50.0)
Coordinate Multiple Dispensers¶
Use all dispensers efficiently:
def select_dispenser(manager):
"""Select dispenser with most pistons."""
return max(manager.piston_dispensers, key=lambda d: d.num_pistons)
# Use the fullest dispenser
dispenser = select_dispenser(manager)
if dispenser.num_pistons > 0:
manager._move_to_dispenser(dispenser.index)
manager.get_piston_from_dispenser(dispenser.index)
Configuration Examples¶
Single Dispenser Setup¶
{
"deck": {
"labware": {
"main_dispenser": {
"type": "piston_dispenser",
"name": "Main Piston Dispenser",
"position": {"x": 200, "y": 50, "z": 20},
"ready_pos": "dispenser_0_ready",
"capacity": 30,
"piston_height": 45.0,
"piston_diameter": 8.0,
"stack_spacing": 1.5
}
}
}
}
Multiple Dispenser Setup¶
{
"deck": {
"labware": {
"dispenser_0": {
"type": "piston_dispenser",
"position": {"x": 200, "y": 50, "z": 20},
"ready_pos": "dispenser_0_ready",
"capacity": 20
},
"dispenser_1": {
"type": "piston_dispenser",
"position": {"x": 250, "y": 50, "z": 20},
"ready_pos": "dispenser_1_ready",
"capacity": 20
}
}
}
}
See Also¶
- JubileeManager - High-level dispenser operations
- MotionPlatformStateMachine - Validated piston retrieval
- Manipulator - Gripper operations
- Configuration Guide - Setting up dispensers