JubileeViewModel API Reference¶
The JubileeViewModel class coordinates between the GUI and JubileeManager, following an MVVM-inspired architecture pattern.
Overview¶
JubileeViewModel serves as the coordination layer that:
- Manages hardware configuration before connection
- Drives the JubileeManager to execute operations
- Executes multi-well dispensing jobs systematically
- Provides callbacks to update the GUI on progress
- Handles errors and provides user-friendly feedback
Architecture Role¶
The ViewModel:
- Receives requests from the GUI
- Coordinates operations through JubileeManager
- Notifies GUI of changes via callbacks
Class Reference¶
JubileeViewModel
¶
JubileeViewModel(on_connection_changed=None, on_weight_changed=None, on_status_changed=None, on_job_progress=None, on_job_completed=None, on_error=None)
ViewModel for coordinating GUI and JubileeManager.
This class acts as the coordination layer between the View (GUI) and Model (JubileeManager). It drives the hardware through the JubileeManager while providing callbacks to update the GUI on progress and state changes.
Responsibilities: - Manage connection to hardware via JubileeManager - Store and update hardware configuration (dispensers, pistons) - Execute dispensing jobs systematically - Provide progress updates via callbacks - Handle errors and provide meaningful feedback
Example
# Create ViewModel with callbacks
view_model = JubileeViewModel(
on_connection_changed=lambda connected: print(f"Connected: {connected}"),
on_weight_changed=lambda weight: print(f"Weight: {weight}g"),
on_status_changed=lambda status: print(status),
on_job_progress=lambda completed, total, well: print(f"{completed}/{total}")
)
# Configure hardware
view_model.set_hardware_config(num_dispensers=2, pistons_per_dispenser=10)
# Connect to hardware
if view_model.connect():
# Start a job
jobs = [
DispensingJob("0", 50.0),
DispensingJob("1", 45.0)
]
view_model.start_job(jobs)
Initialize the ViewModel.
| PARAMETER | DESCRIPTION |
|---|---|
on_connection_changed
|
Callback when connection state changes (connected: bool)
TYPE:
|
on_weight_changed
|
Callback when scale weight updates (weight: float)
TYPE:
|
on_status_changed
|
Callback when status message changes (status: str)
TYPE:
|
on_job_progress
|
Callback for job progress (completed: int, total: int, current_well: str)
TYPE:
|
on_job_completed
|
Callback when job finishes successfully
TYPE:
|
on_error
|
Callback when an error occurs (error_message: str)
TYPE:
|
Attributes¶
pistons_per_dispenser
property
¶
Get current number of pistons per dispenser configured
Functions¶
set_hardware_config
¶
Update hardware configuration.
Sets the hardware configuration that will be used when connecting to the JubileeManager. Can only be called when not connected.
| PARAMETER | DESCRIPTION |
|---|---|
num_dispensers
|
Number of piston dispensers available
TYPE:
|
pistons_per_dispenser
|
Number of pistons in each dispenser
TYPE:
|
feedrate
|
Movement speed (SLOW, MEDIUM, FAST)
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If called while connected to hardware |
connect
¶
Connect to hardware using current configuration.
Creates a JubileeManager with the current hardware configuration and connects to the Jubilee machine and scale. This is a blocking operation that can take 30-60 seconds due to homing.
| PARAMETER | DESCRIPTION |
|---|---|
machine_address
|
IP address of Jubilee (None = use config file)
TYPE:
|
scale_port
|
Serial port for scale connection
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if connection successful, False otherwise |
disconnect
¶
Disconnect from hardware and clean up resources.
Stops any running jobs, stops weight monitoring, and disconnects from the JubileeManager.
get_current_weight
¶
Get current weight from scale.
| RETURNS | DESCRIPTION |
|---|---|
float
|
Current weight in grams, or 0.0 if not connected |
start_job
¶
Start a dispensing job with the given wells.
Executes dispensing operations for each well in the job list, calling the progress callback after each well is completed.
| PARAMETER | DESCRIPTION |
|---|---|
jobs
|
List of DispensingJob objects specifying wells and target weights
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if job started successfully, False if already running or not connected |
stop_job
¶
Stop the currently running job.
Sets a flag to stop the job after the current well is completed. The job will not stop immediately.
get_dispenser_status
¶
Get status of all piston dispensers.
| RETURNS | DESCRIPTION |
|---|---|
List[Dict[str, any]]
|
List of dicts with dispenser information: |
List[Dict[str, any]]
|
|
List[Dict[str, any]]
|
|
update_dispenser_pistons
¶
Update the number of pistons in a specific dispenser.
This allows the user to modify the piston count if they manually reload or change dispensers.
| PARAMETER | DESCRIPTION |
|---|---|
dispenser_index
|
Index of dispenser to update
TYPE:
|
num_pistons
|
New number of pistons
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if update successful, False if invalid index or not connected |
DispensingJob¶
DispensingJob
dataclass
¶
Represents a single well in a dispensing job
Usage Examples¶
Basic Setup¶
from gui.jubilee_view_model import JubileeViewModel, DispensingJob
# Define callbacks for GUI updates
def on_status(status: str):
print(f"Status: {status}")
def on_progress(completed: int, total: int, current_well: str):
print(f"Progress: {completed}/{total} - {current_well}")
# Create ViewModel
view_model = JubileeViewModel(
on_status_changed=on_status,
on_job_progress=on_progress
)
# Configure hardware before connecting
view_model.set_hardware_config(
num_dispensers=2,
pistons_per_dispenser=10
)
Connecting to Hardware¶
# Connect (this will create JubileeManager with configured settings)
if view_model.connect(machine_address="192.168.1.100"):
print("Connected successfully!")
print(f"Dispensers: {view_model.num_dispensers}")
print(f"Pistons per dispenser: {view_model.pistons_per_dispenser}")
else:
print("Connection failed")
Running a Dispensing Job¶
# Define wells to fill
jobs = [
DispensingJob(well_id="0", target_weight=50.0),
DispensingJob(well_id="1", target_weight=45.0),
DispensingJob(well_id="2", target_weight=52.0),
]
# Start job (runs in background thread)
if view_model.start_job(jobs):
print("Job started")
# Job runs asynchronously
# Progress updates come via on_job_progress callback
# Completion notification via on_job_completed callback
Monitoring Progress¶
# Callbacks provide real-time updates
def on_connection_changed(connected: bool):
if connected:
print("Hardware connected")
else:
print("Hardware disconnected")
def on_weight_changed(weight: float):
print(f"Current weight: {weight:.3f}g")
def on_job_progress(completed: int, total: int, current_well: str):
print(f"Completed {completed}/{total} wells")
print(f"Currently processing: {current_well}")
def on_job_completed():
print("Job finished successfully!")
def on_error(error_msg: str):
print(f"Error: {error_msg}")
# Create ViewModel with all callbacks
view_model = JubileeViewModel(
on_connection_changed=on_connection_changed,
on_weight_changed=on_weight_changed,
on_status_changed=lambda s: print(s),
on_job_progress=on_job_progress,
on_job_completed=on_job_completed,
on_error=on_error
)
Checking Dispenser Status¶
# Get status of all dispensers
status = view_model.get_dispenser_status()
for dispenser in status:
print(f"Dispenser {dispenser['index']}: "
f"{dispenser['pistons_remaining']} pistons")
Updating Piston Counts¶
# User manually reloaded dispenser 0 with 20 pistons
success = view_model.update_dispenser_pistons(
dispenser_index=0,
num_pistons=20
)
if success:
print("Dispenser piston count updated")
Stopping a Job¶
# User wants to stop the current job
view_model.stop_job()
# Job will stop after current well completes
# Status update via on_status_changed callback
Callback System¶
The ViewModel uses callbacks to notify the GUI of changes. This allows the GUI to remain responsive while operations run in background threads.
Available Callbacks¶
| Callback | Parameters | When Called |
|---|---|---|
on_connection_changed |
connected: bool |
Connection state changes |
on_weight_changed |
weight: float |
Scale weight updates (500ms) |
on_status_changed |
status: str |
Status message changes |
on_job_progress |
completed: int, total: int, current_well: str |
Job progress updates |
on_job_completed |
None | Job finishes successfully |
on_error |
error_msg: str |
Error occurs |
Thread Safety¶
All callbacks are called from background threads. GUI frameworks typically require updates to happen on the main thread. Use your framework's thread-safe update mechanism:
Kivy Example:
from kivy.clock import Clock
def on_status_changed(status: str):
# Schedule update on main thread
def update(dt):
self.status_label.text = status
Clock.schedule_once(update, 0)
Design Notes¶
Hardware Configuration¶
Hardware configuration (dispensers, pistons) is set in the ViewModel before connection. When connect() is called, the ViewModel creates a JubileeManager with these settings. The actual hardware state is then stored in the JubileeManager.
Job Execution¶
Jobs are executed systematically in a background thread:
- Validate piston availability
- For each well in order:
- Call
JubileeManager.dispense_to_well() - Update progress via callback
- Check for stop flag
- Notify completion via callback
Error Handling¶
Errors are caught and reported via the on_error callback. Jobs stop on first error to prevent cascading failures.
See Also¶
- JubileeManager - Hardware operations layer
- GUI Application - Full GUI application
- Using the GUI - GUI user guide