JubileeManager API Reference¶
The JubileeManager class is the primary interface for controlling the Jubilee powder dispensing system. It provides high-level methods for common operations and coordinates multiple hardware components.
Overview¶
JubileeManager is designed to be the main entry point for:
- Connecting to and managing hardware
- Performing dispense operations
- Reading scale weights
- Coordinating complex multi-step movements
All movements are validated through an internal MotionPlatformStateMachine which cannot be bypassed, ensuring safety and consistency.
Class Reference¶
JubileeManager
¶
High-level manager for Jubilee powder dispensing operations.
JubileeManager provides a simplified interface for controlling the Jubilee for powder dispensing tasks. It coordinates multiple hardware components (machine, scale, dispensers, manipulator) and ensures all operations are safe through state machine validation.
All movements are validated through the MotionPlatformStateMachine, which is owned by this manager and cannot be bypassed. This ensures safety and prevents invalid state transitions.
| ATTRIBUTE | DESCRIPTION |
|---|---|
scale |
Connected scale instance for weight measurements, or None if not connected.
TYPE:
|
manipulator |
Manipulator tool instance for mold handling, or None if not initialized.
TYPE:
|
state_machine |
Internal state machine for movement validation, or None before connection.
TYPE:
|
connected |
Boolean indicating whether hardware is connected and ready.
TYPE:
|
Example
Basic usage pattern::
manager = JubileeManager(num_piston_dispensers=2, num_pistons_per_dispenser=10)
try:
if manager.connect():
weight = manager.get_weight_stable()
manager.dispense_to_well("0", 50.0)
finally:
manager.disconnect()
Note
- Always call
disconnect()when done to properly release hardware resources - Check
connectedproperty before performing operations - Use
machine_read_onlyonly for queries, never for movements
Initialize the JubileeManager.
Creates a new manager instance with specified dispenser configuration.
Does not connect to hardware - call connect() to establish connections.
| PARAMETER | DESCRIPTION |
|---|---|
num_piston_dispensers
|
Number of piston dispenser units to initialize. Each dispenser can hold multiple pistons. Default is 0.
TYPE:
|
num_pistons_per_dispenser
|
Initial number of pistons in each dispenser. Used to track available pistons. Default is 0.
TYPE:
|
feedrate
|
Default movement speed for operations. Options are SLOW, MEDIUM, or FAST from the FeedRate enum. Default is MEDIUM.
TYPE:
|
Example
Note
- No hardware connection is established during initialization
- Dispenser counts can be zero if pistons are not needed
- Feedrate affects all subsequent movements after connection
Attributes¶
machine_read_only
property
¶
Read-only access to the underlying Jubilee Machine instance.
Provides access to the Machine object for read operations only (queries, status checks, position reads). While it's technically possible to perform write operations through this property, doing so bypasses the state machine safety guarantee and should be avoided.
| RETURNS | DESCRIPTION |
|---|---|
Optional[Machine]
|
The Machine instance if connected, None otherwise. |
Warning
This property is named "read_only" as a strong hint that it should ONLY be used for read operations. Performing movements or state changes through this property bypasses the state machine safety guarantee and can lead to:
- Collisions with labware
- Invalid state transitions
- Unsafe operations
- Loss of state tracking
Example
Note
Always use JubileeManager's high-level methods or the state machine's validated methods for any operations that change machine state.
deck
property
¶
Access to the deck configuration and labware layout.
Provides access to the Deck object which contains information about labware positions, well plates, and deck layout.
| RETURNS | DESCRIPTION |
|---|---|
Optional[Deck]
|
The Deck instance if state machine is initialized, None otherwise. |
piston_dispensers
property
¶
Access to all configured piston dispensers.
Provides access to the list of PistonDispenser instances managed by the state machine. Each dispenser tracks its piston count and position.
| RETURNS | DESCRIPTION |
|---|---|
List[PistonDispenser]
|
List of PistonDispenser instances. Empty list if none configured |
List[PistonDispenser]
|
or state machine not initialized. |
Example
Functions¶
connect
¶
Connect to all hardware and initialize the system.
Establishes connections to the Jubilee machine controller and scale, initializes the state machine with configuration, sets up dispensers, and performs homing operations to establish a known state.
This method performs the following sequence:
- Connect to Jubilee machine (Duet controller)
- Connect to precision scale
- Initialize state machine with configuration
- Initialize deck layout and piston dispensers
- Create and configure manipulator tool
- Home all machine axes (X, Y, Z, U)
- Pick up manipulator tool
- Home manipulator axis (V)
| PARAMETER | DESCRIPTION |
|---|---|
machine_address
|
IP address of the Jubilee's Duet controller. If None, uses the IP address from system configuration file. Examples: "192.168.1.100", "10.0.0.50".
TYPE:
|
scale_port
|
Serial port path for scale connection. Common values: Linux: "/dev/ttyUSB0", "/dev/ttyACM0" Windows: "COM3", "COM4" macOS: "/dev/tty.usbserial-*"
TYPE:
|
state_machine_config
|
Path to JSON file defining state machine positions and transitions. Relative or absolute path accepted.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if all connections and initializations succeeded, False if any |
bool
|
step failed. Check the |
| RAISES | DESCRIPTION |
|---|---|
FileNotFoundError
|
If state_machine_config file does not exist. |
RuntimeError
|
If homing, tool pickup, or manipulator homing fails. |
ConnectionError
|
If unable to connect to machine or scale. |
Example
manager = JubileeManager(num_piston_dispensers=2, num_pistons_per_dispenser=10)
# Connect with explicit IP
if manager.connect(machine_address="192.168.1.100", scale_port="/dev/ttyUSB0"):
print("Connected successfully!")
else:
print("Connection failed - check hardware and configuration")
# Connect using config file IP
if manager.connect(): # Uses IP from system_config.json
print("Connected using configured IP")
Note
- This operation can take 30-60 seconds due to homing
- All axes must be clear of obstacles before homing
- Ensure no tool is already picked up before calling
- Connection state is stored in
self.connectedproperty - On failure, partial connections are not cleaned up automatically
Warning
If connection fails partway through (e.g., after machine connects but before homing completes), you may need to manually reset the hardware before attempting to connect again.
disconnect
¶
Disconnect from all hardware components and release resources.
Cleanly disconnects from the Jubilee machine and scale, releasing any held resources. This should always be called when done using the manager.
Example
Note
- Safe to call multiple times
- Safe to call even if not fully connected
- Does not raise exceptions on disconnection errors
- Sets
connectedproperty to False
get_weight_stable
¶
Get current weight from scale, waiting for stability.
Reads the scale weight, waiting for the reading to stabilize before returning. This is the recommended method for measurements that will be recorded or used for decisions.
| RETURNS | DESCRIPTION |
|---|---|
float
|
Weight in grams. Returns 0.0 if scale is not connected or on error. |
Example
Note
- Waits for scale to report stable reading (may take 1-3 seconds)
- More accurate than
get_weight_unstable() - Returns 0.0 on error rather than raising exceptions
- Check
scale.is_connectedif you need to distinguish no scale from zero weight
See Also
get_weight_unstable: For real-time weight monitoring without waiting
get_weight_unstable
¶
Get instantaneous weight from scale without waiting for stability.
Reads the current scale weight immediately, without waiting for the reading to stabilize. Useful for real-time monitoring but not recommended for recorded measurements.
| RETURNS | DESCRIPTION |
|---|---|
float
|
Current weight in grams. Returns 0.0 if scale is not connected or on error. |
Example
Note
- Returns immediately without waiting
- Reading may still be changing (unstable)
- Not suitable for decisions or permanent records
- Use
get_weight_stable()for measurements you'll record - Returns 0.0 on error rather than raising exceptions
See Also
get_weight_stable: For accurate measurements after stabilization
dispense_to_well
¶
Perform complete powder dispense operation to a well.
This is the primary high-level operation for dispensing powder. It performs a complete workflow including picking up the mold, filling with powder to target weight, retrieving a piston, and returning the mold to its slot.
The operation sequence is:
- Move to mold slot position
- Pick up empty mold from slot
- Move to scale
- Place mold on scale
- Fill with powder to target weight
- Pick up filled mold from scale
- Move to piston dispenser
- Retrieve piston from dispenser
- Move back to mold slot
- Place mold (now with powder and piston) back in slot
| PARAMETER | DESCRIPTION |
|---|---|
well_id
|
Identifier for the target well/mold slot using numerical indexing. Must match an entry in the deck configuration (e.g., "0", "1", "2").
TYPE:
|
target_weight
|
Target weight of powder to dispense, in grams. The system will fill until this weight is reached (within tolerance).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the entire operation completed successfully, False if any step |
bool
|
failed or if not connected. |
| RAISES | DESCRIPTION |
|---|---|
ToolStateError
|
If manipulator or scale is not available. |
RuntimeError
|
If state machine is not configured. |
ValueError
|
If well_id is not found in deck configuration. |
Example
manager = JubileeManager(num_piston_dispensers=2, num_pistons_per_dispenser=10)
if manager.connect():
# Dispense 50g of powder to mold 0
success = manager.dispense_to_well("0", target_weight=50.0)
if success:
print("Dispense completed successfully!")
weight = manager.get_weight_stable()
print(f"Final weight: {weight}g")
else:
print("Dispense failed - check logs for details")
manager.disconnect()
Note
- Requires at least one dispenser with available pistons
- All movements are validated through state machine
- Operation can take 2-5 minutes depending on target weight
- If operation fails partway through, system may be in intermediate state
- Check return value before assuming success
Warning
If the operation fails after picking up the mold but before returning it, the mold may be left at an intermediate position. Manual intervention may be required to return to a safe state.
Usage Examples¶
Basic Connection and Usage¶
from src.JubileeManager import JubileeManager
# Create manager instance
manager = JubileeManager(
num_piston_dispensers=2,
num_pistons_per_dispenser=10
)
# Connect to hardware
if manager.connect(machine_address="192.168.1.100"):
print("Connected successfully!")
# Use the manager
weight = manager.get_weight_stable()
print(f"Current weight: {weight}g")
# Clean up
manager.disconnect()
Performing Dispense Operations¶
# After connecting...
success = manager.dispense_to_well(
well_id="0",
target_weight=50.0
)
if success:
print("Dispense completed successfully!")
else:
print("Dispense failed - check logs for details")
Accessing Hardware Components¶
# Read-only access to machine (for queries, not movements)
if manager.machine_read_only:
position = manager.machine_read_only.get_position()
print(f"Current position: {position}")
# Access deck for labware information
if manager.deck:
labware = manager.deck.get_labware()
print(f"Available labware: {labware}")
# Access piston dispensers
for dispenser in manager.piston_dispensers:
print(f"Dispenser {dispenser.index}: {dispenser.num_pistons} pistons")
Function Call Error Handling¶
from src.JubileeManager import JubileeManager
manager = JubileeManager()
try:
if not manager.connect():
raise ConnectionError("Failed to connect to Jubilee")
# Perform operations
success = manager.dispense_to_well("0", 50.0)
if not success:
print("Operation failed but system is still connected")
except Exception as e:
print(f"Error occurred: {e}")
finally:
# Always disconnect
manager.disconnect()
Internal Methods¶
The following methods are primarily for internal use but are documented for developers:
_move_to_mold_slot
¶
Move to a specific mold slot position.
Internal method that moves to the position where the manipulator can pick up or place a mold in the specified well. The target position is determined by the well's configuration in the deck layout.
| PARAMETER | DESCRIPTION |
|---|---|
well_id
|
Identifier for the target well using numerical indexing (e.g., "0", "1", "2"). Must exist in the deck configuration's labware definition.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if movement succeeded. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If state machine is not configured or movement validation fails. Validation failure reasons include wrong position, wrong tool, or invalid payload state. |
KeyError
|
If well_id is not found in deck configuration. |
Note
- This is an internal method; typically called by
dispense_to_well() - Uses the well's
ready_posfield from deck configuration - Movement is validated through state machine
- Does not pick up or place the mold, only positions for access
_move_to_scale
¶
Move to the scale ready position.
Internal method that moves the manipulator to the position where it can place or pick up molds on the scale. Movement is validated through the state machine.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if movement succeeded, False if scale is not configured. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If state machine is not configured or movement validation fails. Common failure reasons include wrong tool active, invalid payload state, or unable to transition from current position. |
Note
- This is an internal method; typically called by
dispense_to_well() - Moves to scale_ready position defined in state machine config
- Does not place or pick up mold, only positions for access
- Movement is validated against current state
_move_to_dispenser
¶
Move to the ready position for a specific piston dispenser.
Internal method that moves the manipulator to the position where it can retrieve a piston from the specified dispenser. Movement is validated through the state machine.
| PARAMETER | DESCRIPTION |
|---|---|
dispenser_index
|
Index of the target dispenser (0-based). Must be less than the number of configured dispensers.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if movement succeeded, False if not connected, no dispensers, or |
bool
|
movement validation failed. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If state machine is not configured or movement validation fails. |
ValueError
|
If dispenser_index is out of range. |
Note
- This is an internal method; typically called by
dispense_to_well() - Movement is validated against current state (tool, payload, position)
- Does not retrieve the piston, only positions for retrieval
_fill_powder
¶
Fill mold with powder to target weight.
Internal method that dispenses powder into a mold using the trickler mechanism, monitoring the scale until the target weight is reached. The mold must already be placed on the scale.
| PARAMETER | DESCRIPTION |
|---|---|
target_weight
|
Target weight of powder to dispense, in grams.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if filling succeeded, False if scale is not configured. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If state machine is not configured or fill operation validation fails. Validation ensures mold is on scale and system is in correct state for powder dispensing. |
Note
- This is an internal method; typically called by
dispense_to_well() - Mold must already be on the scale before calling
- Operation continues until target weight is reached (within tolerance)
- Duration depends on target weight and trickler speed (typically 1-3 min)
- Continuously monitors scale during filling
Warning
Calling this method without a mold on the scale will result in powder being dispensed directly onto the scale, which is incorrect operation.
get_piston_from_dispenser
¶
Retrieve the top piston from a specific dispenser.
Retrieves a piston from the specified dispenser and places it into the mold currently held by the manipulator. The operation is validated through the state machine to ensure safety.
| PARAMETER | DESCRIPTION |
|---|---|
dispenser_index
|
Index of the dispenser to retrieve from (0-based). Must be less than the number of configured dispensers.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if piston was successfully retrieved, False if not connected, |
bool
|
no dispensers available, or retrieval failed. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If state machine is not configured or retrieval validation fails. |
ValueError
|
If dispenser_index is out of range. |
Example
Note
- Must already be at the dispenser ready position (call
_move_to_dispenser()first) - Requires mold to be held by manipulator
- Automatically decrements piston count in the dispenser
- Validation ensures proper state before and after retrieval
Warning
Calling this method without first moving to the dispenser position
will fail validation. Always call _move_to_dispenser() first.
Design Notes¶
State Machine Ownership¶
JubileeManager owns the MotionPlatformStateMachine instance. This design ensures:
- All movements must go through validation
- No external code can bypass safety checks
- Consistent state tracking across the system
Read-Only Machine Access¶
The machine_read_only property provides access to the underlying Machine object for read operations only. While it's technically possible to perform movement operations through this property, this bypasses safety validation without updating the JubileeManager's internal state. Doing so is strongly discouraged.
Use machine_read_only only for:
- Querying current position
- Reading sensor values
- Checking machine state
Never use it for: - Moving axes - Picking/parking tools - Any operation that changes machine state
Connection Sequence¶
The connect() method performs several initialization steps:
- Connects to the Duet controller
- Connects to the scale
- Initializes the state machine with configuration
- Initializes the deck and dispensers
- Homes all axes (X, Y, Z, U)
- Picks up the manipulator tool
- Homes the manipulator axis (V)
This ensures the system is in a known, safe state before operations begin.
See Also¶
- MotionPlatformStateMachine - For advanced movement control
- Manipulator - Gripper tool details
- Scale - Scale interface
- Quick Start Guide - Getting started tutorial