Skip to content

MotionPlatformStateMachine API Reference

The MotionPlatformStateMachine provides validated movement control for the Jubilee powder dispensing system. It ensures all operations are safe by tracking system state and enforcing constraints.

Overview

The state machine:

  • Tracks current position, tool, and payload state
  • Validates all requested movements
  • Enforces safety constraints
  • Provides detailed error messages when operations are invalid

Advanced Usage

Most users should interact with JubileeManager instead of using the state machine directly. Only use the state machine when you need operations not provided by JubileeManager.

Class Reference

MotionPlatformStateMachine

MotionPlatformStateMachine(registry, machine, *, context=None, scale=None, feedrate=None)

Bases: StateMachine

Finite state machine responsible for validating and sequencing platform moves.

The machine relies on python-statemachine to model the control flow. It maintains awareness of both high-level state (idle, moving, tool engaged) and the current logical position descriptor.

Attributes

idle class-attribute instance-attribute

idle = State('Idle', initial=True)

moving class-attribute instance-attribute

moving = State('Moving')

tool_engaged class-attribute instance-attribute

tool_engaged = State('Tool Engaged')

begin_motion class-attribute instance-attribute

begin_motion = to(moving)

complete_motion class-attribute instance-attribute

complete_motion = to(idle)

complete_motion_with_tool class-attribute instance-attribute

complete_motion_with_tool = to(tool_engaged)

engage_tool class-attribute instance-attribute

engage_tool = to(tool_engaged)

disengage_tool class-attribute instance-attribute

disengage_tool = to(idle)

abort_motion class-attribute instance-attribute

abort_motion = to(idle)

_registry instance-attribute

_registry = registry

_actions instance-attribute

_actions = actions

context instance-attribute

context = context

_executor instance-attribute

_executor = MovementExecutor(machine, scale=scale, feedrate=feedrate)

machine property

machine

Read-only access to machine for state queries (position, status, etc).

Functions

from_config_file classmethod

from_config_file(path, machine, *, context_overrides=None, scale=None, feedrate=None)

initialize_deck

initialize_deck(deck_name='weight_well_deck', config_path=None)

Initialize the deck with weight wells in each slot.

PARAMETER DESCRIPTION
deck_name

Name of the deck configuration

TYPE: str DEFAULT: 'weight_well_deck'

config_path

Path to the deck configuration files

TYPE: Optional[str] DEFAULT: None

initialize_dispensers

initialize_dispensers(num_piston_dispensers=0, num_pistons_per_dispenser=0)

Initialize piston dispensers.

PARAMETER DESCRIPTION
num_piston_dispensers

Number of piston dispensers

TYPE: int DEFAULT: 0

num_pistons_per_dispenser

Number of pistons in each dispenser

TYPE: int DEFAULT: 0

get_mold_from_deck

get_mold_from_deck(well_id)

Get a mold object from the deck by mold slot ID.

PARAMETER DESCRIPTION
well_id

Mold slot identifier (numerical string "0" through "17")

TYPE: str

RETURNS DESCRIPTION
Optional[object]

Mold object if found, None otherwise

validated_pick_mold

validated_pick_mold(well_id, manipulator_config)

Validate and execute picking up a mold from a mold slot.

PARAMETER DESCRIPTION
well_id

Mold slot identifier (numerical string "0" through "17")

TYPE: str

manipulator_config

Configuration dict for the manipulator

TYPE: Dict[str, object]

validated_place_mold

validated_place_mold(well_id, manipulator_config=None)

Validate and execute placing a mold in a mold slot.

PARAMETER DESCRIPTION
well_id

Well identifier (numerical string "0" through "17")

TYPE: str

manipulator_config

Configuration dict for the manipulator

TYPE: Optional[Dict[str, object]] DEFAULT: None

validated_place_mold_on_scale

validated_place_mold_on_scale(manipulator_config)

Validate and execute placing mold on scale.

validated_pick_mold_from_scale

validated_pick_mold_from_scale(manipulator_config)

Validate and execute picking mold from scale.

validated_place_top_piston

validated_place_top_piston(piston_dispenser, manipulator_config)

Validate and execute placing top piston.

validated_tamp

validated_tamp(manipulator_config, tamp_depth=40.0, tamp_speed=2000)

Validate and execute tamping action.

Tamping compresses powder in a mold held by the manipulator to reduce volume. This is typically done at the scale_ready position before inserting the top piston.

Parameter bounds are loaded from system_config.json and can be customized.

PARAMETER DESCRIPTION
manipulator_config

Configuration dict for the manipulator

TYPE: Dict[str, object]

tamp_depth

Target depth for tamping movement in mm (default 40.0)

TYPE: float DEFAULT: 40.0

tamp_speed

Speed for tamping movement in mm/min (default 2000)

TYPE: int DEFAULT: 2000

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

_validate_and_execute

_validate_and_execute(target_position_id=None, action_id=None, additional_requirements=None, execution_func=None, **execution_kwargs)

Generic validation and execution for movements and tool actions.

This method performs comprehensive validation for either: - Position movements (when target_position_id is provided) - Tool actions (when action_id is provided)

Validation steps for MOVEMENTS: 1. Checks state machine is not already moving 2. Validates position transition is allowed (current → target) 3. Validates machine is actually at expected current position 4. Validates z-height policy for target position 5. Validates all requirements for target position 6. If valid, executes the provided function and transitions position

Validation steps for ACTIONS: 1. Checks state machine is not already moving 2. Validates action exists in registry 3. Validates tool engagement state (if required/blocked) 4. Validates required tool ID matches 5. Validates position scope (action allowed at current position) 6. Validates action requirements and excludes 7. If valid, executes the provided function (no position change)

PARAMETER DESCRIPTION
target_position_id

The target position identifier (for movements)

TYPE: Optional[str] DEFAULT: None

action_id

The action identifier (for tool actions)

TYPE: Optional[str] DEFAULT: None

additional_requirements

Extra requirements beyond position/action requirements

TYPE: Optional[Dict[str, object]] DEFAULT: None

execution_func

Function to execute if validation passes

DEFAULT: None

**execution_kwargs

Arguments to pass to execution function

DEFAULT: {}

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with validation outcome

RAISES DESCRIPTION
ValueError

If both or neither target_position_id and action_id are provided

_validate_and_execute_move

_validate_and_execute_move(target_position_id, additional_requirements=None, execution_func=None, **execution_kwargs)

Internal method to validate and execute position movements.

See _validate_and_execute() for full documentation.

_validate_and_execute_action

_validate_and_execute_action(action_id, additional_requirements=None, execution_func=None, **execution_kwargs)

Internal method to validate and execute tool actions.

All actions must be defined in the registry (config JSON).

See _validate_and_execute_move() for full documentation.

validated_move_to_mold_slot

validated_move_to_mold_slot(well_id)

Validate and execute movement to a specific mold slot.

PARAMETER DESCRIPTION
well_id

Mold slot identifier (numerical string "0" through "17")

TYPE: str

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_move_to_scale

validated_move_to_scale()

Validate and execute movement to the scale.

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_move_to_dispenser

validated_move_to_dispenser(piston_dispenser)

Validate and execute movement to a dispenser ready position.

PARAMETER DESCRIPTION
piston_dispenser

PistonDispenser object with ready_pos attribute

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_fill_powder

validated_fill_powder(target_weight)

Validate and execute filling mold with powder.

PARAMETER DESCRIPTION
target_weight

Target weight to fill

TYPE: float

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_home_tamper

validated_home_tamper(tamper_axis='V')

Validate and execute tamper homing (uses home_manipulator action).

Can be performed while holding a mold without a top piston. The homing process uses the mold itself as a reference: - Start position: v=2 (tamper inserted into mold) - End position: v=-7 (tamper touching bottom of mold)

This establishes accurate positioning by using the mold bottom as a reference point.

PARAMETER DESCRIPTION
tamper_axis

Axis letter for tamper (default 'V')

TYPE: str DEFAULT: 'V'

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_home_all

validated_home_all()

Validate and execute homing for all axes (X, Y, Z, U).

This action can be conducted from any position, but requires: - No tool picked up (active_tool_id should not be "manipulator") - No mold (payload_state should be "empty")

Returns machine to global_ready position after homing.

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_home_manipulator

validated_home_manipulator(manipulator_axis='V')

Validate and execute homing for the manipulator axis (V).

Requires no mold picked up (payload_state should be "empty").

PARAMETER DESCRIPTION
manipulator_axis

Axis letter for manipulator (default 'V')

TYPE: str DEFAULT: 'V'

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_home_trickler

validated_home_trickler(trickler_axis='W')

Validate and execute homing for the trickler axis (W).

Can be homed at any time with no requirements.

PARAMETER DESCRIPTION
trickler_axis

Axis letter for trickler (default 'W')

TYPE: str DEFAULT: 'W'

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_pickup_tool

validated_pickup_tool(tool)

Validate and execute picking up a tool.

Valid only from global_ready position. Requires no tool already picked up and mold_transfer_safe z_height. Returns to global_ready position. Only manipulator tool is currently supported.

Note: The machine's pickup_tool() method is decorated with @requires_safe_z, which automatically raises the bed height to deck.safe_z + 20 if it is not already at that height.

PARAMETER DESCRIPTION
tool

The Tool object to pick up (must be manipulator)

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_park_tool

validated_park_tool()

Validate and execute parking the current tool.

Valid from global_ready position. Requires manipulator tool to be active. Returns to global_ready position.

Note: The machine's park_tool() method is decorated with @requires_safe_z, which automatically raises the bed height to deck.safe_z + 20 if it is not already at that height.

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

validated_retrieve_piston

validated_retrieve_piston(piston_dispenser, manipulator_config)

Validate and execute retrieving a piston from a dispenser. This action retrieves the piston, partially inserts it, and then returns to ready position.

Requires: - Manipulator tool to be active - Mold without top piston (payload_state: mold_without_top_piston) - Must start from the corresponding dispenser_ready position for that dispenser

PARAMETER DESCRIPTION
piston_dispenser

The PistonDispenser object to retrieve from

manipulator_config

Configuration dict for the manipulator

TYPE: Dict[str, object]

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult with outcome

request_move

request_move(request)

Evaluate and, if permissible, initiate a move or action.

If the request references an action, the FSM validates the action without changing state. Otherwise, the FSM transitions into the moving state and records the pending move for completion tracking.

perform_action

perform_action(action_id)

Validate whether an auxiliary action is permitted.

validate_move

validate_move(request)

Core validation hook for requested moves.

This implementation verifies
  • The target position exists in the registry.
  • The transition between current and target positions is permitted.
  • Z-height and contextual requirements are satisfied.
  • Engaged tools remain constrained to their ready points.

complete_move

complete_move(*, tool_still_engaged)

Finalize a move previously initiated via request_move.

PARAMETER DESCRIPTION
tool_still_engaged

Indicates whether the tool engagement status should keep the FSM in the tool_engaged state after the move.

TYPE: bool

request_tool_engagement

request_tool_engagement()

Attempt to transition from idle to tool engaged state at the current position.

request_tool_disengagement

request_tool_disengagement()

Attempt to disengage the tool and return to idle.

register_tool

register_tool(tool_status)

Introduce or update a tool within the motion context.

update_tool_engagement

update_tool_engagement(tool_id, engaged)

Update the engagement flag for a specific tool.

update_context

update_context(*, active_tool_id=None, payload_state=None, z_height_id=None)

Convenience helper to mutate commonly updated context properties.

validate_machine_state

validate_machine_state(machine_x, machine_y, machine_z, machine_v)

Validate that the machine's physical coordinates match the FSM's expected position.

This is a safety check to ensure the machine is actually where the FSM thinks it is. Should be called before attempting moves or actions.

PARAMETER DESCRIPTION
machine_x

Current X coordinate from machine

TYPE: float

machine_y

Current Y coordinate from machine

TYPE: float

machine_z

Current Z coordinate from machine

TYPE: float

machine_v

Current V (manipulator) coordinate from machine

TYPE: float

RETURNS DESCRIPTION
MoveValidationResult

MoveValidationResult indicating if machine state matches expected position

on_enter_moving

on_enter_moving()

Hook invoked when entering the moving state.

Future implementations can trigger hardware-level commands or logging from this hook. The current framework simply asserts that a pending move exists when transitions occur.

on_enter_idle

on_enter_idle()

Reset pending move tracking when returning to idle.

_assert_engagement_exit_ready

_assert_engagement_exit_ready()

Ensure the machine satisfies engagement requirements before exiting.

_validate_requirements

_validate_requirements(requirements)

Validate context attributes against a requirements mapping.

_validate_excludes

_validate_excludes(excludes)

Validate that context attributes do not match excluded values.

_value_matches staticmethod

_value_matches(actual, expected)

Determine whether a context value satisfies an expected requirement.

_format_options staticmethod

_format_options(options)

Render a collection of options as a comma-separated string.

State Tracking

Current State

The state machine maintains:

{
    "position": "global_ready",        # Current named position
    "active_tool_id": 0,               # Active tool (or None)
    "payload_state": "empty",          # What manipulator holds
}

Payload States

State Description
empty Manipulator holds nothing
mold Manipulator holds a mold without piston
mold_with_piston Manipulator holds a mold containing a piston

Position Names

Named positions are defined in motion_platform_positions.json:

  • global_ready: Safe position away from all labware
  • scale_ready: Position to access the scale
  • mold_slot_*: Positions for specific wells (e.g., mold_ready_0, mold_ready_1)
  • dispenser_*_ready: Positions for piston dispensers

Validation Results

All validated methods return a ValidationResult:

@dataclass
class ValidationResult:
    valid: bool       # Whether operation is allowed
    reason: str = ""  # Explanation if not valid

Example Usage

result = state_machine.validated_move_to_scale()

if result.valid:
    print("Movement successful")
else:
    print(f"Movement failed: {result.reason}")

Common Operations

Homing

# Home all axes (X, Y, Z, U)
result = state_machine.validated_home_all()
if not result.valid:
    print(f"Homing failed: {result.reason}")

# Home manipulator axis (V)
result = state_machine.validated_home_manipulator(manipulator_axis='V')
if not result.valid:
    print(f"Manipulator homing failed: {result.reason}")

Requirements: - No tool picked up (for validated_home_all) - Payload must be empty - Currently at a named position

Tool Management

# Pick up a tool
from src.Manipulator import Manipulator

manipulator = Manipulator(
    index=0,
    name="manipulator",
    state_machine=state_machine
)

result = state_machine.validated_pickup_tool(manipulator)
if not result.valid:
    print(f"Tool pickup failed: {result.reason}")

# Park the current tool
result = state_machine.validated_park_tool()
if not result.valid:
    print(f"Tool parking failed: {result.reason}")

Requirements: - Must be at global_ready position - Payload must be empty - No tool already picked up (for pickup) - Manipulator tool must be active (for parking) - Z-height must be at mold_transfer_safe

Position Movements

# Move to scale
result = state_machine.validated_move_to_scale()

# Move to mold slot
result = state_machine.validated_move_to_mold_slot(well_id="0")

# Move to dispenser
from src.PistonDispenser import PistonDispenser

dispenser = PistonDispenser(index=0, state_machine=state_machine)
result = state_machine.validated_move_to_dispenser(
    piston_dispenser=dispenser
)

Requirements vary by destination: - Correct tool must be active - Payload state must be allowed at destination - Valid transition must exist from current position

Specialized Operations

# Fill powder to target weight
result = state_machine.validated_fill_powder(target_weight=50.0)

# Retrieve piston from dispenser
result = state_machine.validated_retrieve_piston(
    piston_dispenser=dispenser,
    manipulator_config=manipulator._get_config_dict()
)

Creating from Configuration

From File

from pathlib import Path
from science_jubilee.Machine import Machine
from src.Scale import Scale
from src.MotionPlatformStateMachine import MotionPlatformStateMachine
from jubilee_api_config.constants import FeedRate

# Connect to hardware
machine = Machine(address="192.168.1.100")
machine.connect()

scale = Scale(port="/dev/ttyUSB0")
scale.connect()

# Create state machine from config
config_path = Path("jubilee_api_config/motion_platform_positions.json")
state_machine = MotionPlatformStateMachine.from_config_file(
    config_file=config_path,
    machine=machine,
    scale=scale,
    feedrate=FeedRate.MEDIUM
)

# Initialize components
state_machine.initialize_deck()
state_machine.initialize_dispensers(
    num_piston_dispensers=2,
    num_pistons_per_dispenser=10
)

State Updates

Manual State Updates

In some cases, you may need to manually update the state:

# Update multiple state fields
state_machine.update_context(
    active_tool_id=0,
    payload_state="mold"
)

# Update position
state_machine.context.position = "scale_ready"

Manual Updates

Manual state updates bypass validation. Only use when you're certain the physical state matches what you're setting.

Validation Logic

Position Validation

The state machine validates movements based on:

  1. Current position: Must be at a valid starting position
  2. Target position: Must be defined in configuration
  3. Transition exists: Direct path must be allowed
  4. Tool requirement: Correct tool must be active
  5. Payload constraint: Payload must be allowed at target

Example Validation Failure

# Trying to move to scale with wrong payload
state_machine.context.payload_state = "invalid_state"
result = state_machine.validated_move_to_scale()

# Result:
# valid = False
# reason = "Payload state 'invalid_state' not allowed at position 'scale_ready'"

Configuration Structure

The state machine reads from motion_platform_positions.json:

{
  "positions": {
    "position_name": {
      "coordinates": {"x": 100, "y": 100, "z": 50, "safe_z": 150},
      "description": "Position description",
      "requires_tool": "manipulator",
      "allowed_payloads": ["empty", "mold"]
    }
  },
  "transitions": {
    "from_position": {
      "to": ["target_position1", "target_position2"]
    }
  }
}

Advanced Usage

Custom Movement Sequences

For complex operations, chain validated movements:

def custom_operation(state_machine, well_id, target_weight):
    """Example custom operation using state machine."""

    # Move to mold slot
    result = state_machine.validated_move_to_mold_slot(well_id)
    if not result.valid:
        return False, f"Move to slot failed: {result.reason}"

    # Update payload (after picking mold)
    state_machine.update_context(payload_state="mold")

    # Move to scale
    result = state_machine.validated_move_to_scale()
    if not result.valid:
        return False, f"Move to scale failed: {result.reason}"

    # Fill powder
    result = state_machine.validated_fill_powder(target_weight)
    if not result.valid:
        return False, f"Fill failed: {result.reason}"

    return True, "Success"

Accessing Internal State

# Get current state
position = state_machine.context.position
tool = state_machine.context.active_tool_id
payload = state_machine.context.payload_state

print(f"State: pos={position}, tool={tool}, payload={payload}")

# Access components
machine = state_machine.machine
scale = state_machine.context.scale
deck = state_machine.context.deck
dispensers = state_machine.context.piston_dispensers

Error Handling

Common Validation Errors

Error Reason Cause Solution
"Position not found" Target position not in config Add position to configuration
"No transition defined" Movement not allowed Add transition or use intermediate position
"Wrong tool active" Tool requirement not met Pick up required tool first
"Payload not allowed" Payload state incompatible Change payload or destination
"Tool already picked up" Tool state conflict Park current tool first

Debugging Validation Failures

result = state_machine.validated_move_to_scale()

if not result.valid:
    print(f"Validation failed: {result.reason}")
    print(f"Current state:")
    print(f"  Position: {state_machine.context.position}")
    print(f"  Tool: {state_machine.context.active_tool_id}")
    print(f"  Payload: {state_machine.context.payload_state}")

Thread Safety

Not Thread-Safe

The state machine is not thread-safe. Do not call methods from multiple threads simultaneously. Use appropriate locking if multi-threaded access is required.

See Also