Skip to content

ConfigLoader API Reference

The ConfigLoader module provides centralized access to system configuration from JSON files.

Overview

ConfigLoader:

  • Loads configuration from JSON files
  • Provides type-safe access to configuration values
  • Handles default values
  • Validates configuration data

Module Reference

ConfigLoader

Configuration loader for Jubilee automation system. Loads system-wide configuration parameters from JSON files.

Attributes

config module-attribute

config = ConfigLoader()

Classes

ConfigLoader

ConfigLoader()

Loads and manages system configuration

Functions
get
get(key_path, default=None)

Get configuration value using dot notation (e.g., 'safety.safe_z')

get_safe_z
get_safe_z()

Get safe Z height

get_safe_z_offset
get_safe_z_offset()

Get safe Z offset

get_max_weight_per_well
get_max_weight_per_well()

Get maximum weight per well

get_weight_tolerance
get_weight_tolerance()

Get weight tolerance

get_duet_ip
get_duet_ip()

Get DUET IP address

get_tamp_depth_min
get_tamp_depth_min()

Get minimum tamp depth in mm

get_tamp_depth_max
get_tamp_depth_max()

Get maximum tamp depth in mm

get_tamp_speed_min
get_tamp_speed_min()

Get minimum tamp speed in mm/min

get_tamp_speed_max
get_tamp_speed_max()

Get maximum tamp speed in mm/min

Usage Examples

Basic Configuration Access

from src.ConfigLoader import config

# Get Duet IP address
ip = config.get_duet_ip()
print(f"Jubilee IP: {ip}")

# Get scale port
port = config.get_scale_port()
print(f"Scale port: {port}")

# Get system configuration
sys_config = config.get_system_config()
print(f"System config: {sys_config}")

Loading Custom Configurations

from src.ConfigLoader import ConfigLoader

# Create loader for custom config file
loader = ConfigLoader(config_file="custom_config.json")

# Access configuration
custom_data = loader.get_system_config()

Configuration Files

system_config.json

Main system configuration file:

{
  "system": {
    "duet_ip": "192.168.1.100",
    "scale_port": "/dev/ttyUSB0",
    "scale_baud_rate": 9600,
    "default_feedrate": "MEDIUM"
  },
  "manipulator": {
    "tamper_axis": "V",
    "tamp_depth_min": 0.0,
    "tamp_depth_max": 9.0,
    "tamp_speed_min": 500,
    "tamp_speed_max": 5000
  },
  "tools": {
    "manipulator": {
      "index": 0,
      "park_position": {"x": 0, "y": 0, "z": 100},
      "v_axis_offset": 50.0,
      "gripper_config": {
        "open_position": 5.0,
        "close_position": 0.0,
        "grip_force": 10.0
      }
    }
  },
  "safety": {
    "max_speed": 10000,
    "acceleration": 500,
    "work_envelope": {
      "x_min": 0, "x_max": 300,
      "y_min": 0, "y_max": 300,
      "z_min": 0, "z_max": 300
    }
  }
}

motion_platform_positions.json

State machine positions and transitions:

{
  "positions": {
    "global_ready": {
      "coordinates": {"x": 150, "y": 150, "z": 100, "safe_z": 150},
      "description": "Safe global position",
      "requires_tool": null,
      "allowed_payloads": ["empty", "mold", "mold_with_piston"]
    }
  },
  "transitions": {
    "global_ready": {
      "to": ["scale_ready", "mold_ready_0"]
    }
  }
}

mold_labware.json

Deck layout and labware definitions:

{
  "deck": {
    "name": "Main Deck",
    "dimensions": {"width": 300, "height": 300},
    "labware": {
      "well_plate_1": {
        "type": "well_plate",
        "name": "24-Well Plate",
        "rows": 4,
        "columns": 6,
        "origin": {"x": 50, "y": 50, "z": 10},
        "wells": {
          "0": {
            "position": {"x": 50, "y": 50, "z": 10},
            "ready_pos": "mold_ready_0",
            "capacity_ml": 10.0
          }
        }
      }
    }
  }
}

Accessing Configuration Values

System Settings

from src.ConfigLoader import config

# Network settings
duet_ip = config.get_duet_ip()
duet_port = config.get_value("system.duet_port", default=80)

# Scale settings
scale_port = config.get_scale_port()
scale_baud = config.get_value("system.scale_baud_rate", default=9600)

# Feed rate
feedrate = config.get_value("system.default_feedrate", default="MEDIUM")

Tool Configuration

# Get manipulator configuration
manipulator_config = config.get_value("tools.manipulator")

if manipulator_config:
    index = manipulator_config["index"]
    park_pos = manipulator_config["park_position"]
    gripper = manipulator_config["gripper_config"]

    print(f"Manipulator index: {index}")
    print(f"Park position: {park_pos}")
    print(f"Gripper open: {gripper['open_position']}mm")

Tamping Configuration

from src.ConfigLoader import config

# Get tamping parameter bounds
tamp_depth_min = config.get_tamp_depth_min()  # Default: 10.0 mm
tamp_depth_max = config.get_tamp_depth_max()  # Default: 60.0 mm
tamp_speed_min = config.get_tamp_speed_min()  # Default: 500 mm/min
tamp_speed_max = config.get_tamp_speed_max()  # Default: 5000 mm/min

print(f"Tamp depth range: {tamp_depth_min}-{tamp_depth_max} mm")
print(f"Tamp speed range: {tamp_speed_min}-{tamp_speed_max} mm/min")

# These bounds are enforced by the state machine during tamping operations

Configuration in system_config.json:

{
  "manipulator": {
    "tamper_axis": "V",
    "tamp_depth_min": 0.0,
    "tamp_depth_max": 9.0,
    "tamp_speed_min": 500,
    "tamp_speed_max": 5000
  }
}

Safety Parameters

# Get safety limits
safety = config.get_value("safety")

if safety:
    max_speed = safety["max_speed"]
    envelope = safety["work_envelope"]

    print(f"Max speed: {max_speed} mm/min")
    print(f"Work envelope: X={envelope['x_min']}-{envelope['x_max']}mm")

Nested Values

# Access deeply nested values using dot notation
gripper_open = config.get_value(
    "tools.manipulator.gripper_config.open_position",
    default=5.0
)

# Or access step by step
tools = config.get_value("tools")
manipulator = tools.get("manipulator", {})
gripper_config = manipulator.get("gripper_config", {})
gripper_open = gripper_config.get("open_position", 5.0)

Default Values

Providing Defaults

Always provide sensible defaults. For critical values (mold positions, feedrate, other physical parameters), it is better to throw an error instead of proceeding with a potentially inaccurate default:

# GOOD - with default
value = config.get_value("optional.setting", default=100)

# RISKY - no default (might be None)
value = config.get_value("optional.setting")
if value is None:
    value = 100

Common Defaults

# Network
duet_ip = config.get_duet_ip() or "192.168.1.100"
duet_port = config.get_value("system.duet_port", default=80)

# Serial
scale_port = config.get_scale_port() or "/dev/ttyUSB0"
scale_baud = config.get_value("system.scale_baud_rate", default=9600)

# Performance
feedrate = config.get_value("system.default_feedrate", default="MEDIUM")
max_speed = config.get_value("safety.max_speed", default=10000)

Configuration Validation

Validating Required Fields

def validate_system_config(config):
    """Validate that required configuration fields exist."""
    required_fields = [
        "system.duet_ip",
        "system.scale_port",
        "tools.manipulator.index"
    ]

    missing = []
    for field in required_fields:
        if config.get_value(field) is None:
            missing.append(field)

    if missing:
        raise ValueError(f"Missing required config fields: {missing}")

    print("Configuration validation passed")

# Usage
try:
    validate_system_config(config)
except ValueError as e:
    print(f"Configuration error: {e}")

Validating Value Ranges

def validate_safety_limits(config):
    """Validate safety parameters are within acceptable ranges."""
    safety = config.get_value("safety", default={})

    max_speed = safety.get("max_speed", 10000)
    if max_speed > 20000:
        print(f"WARNING: max_speed ({max_speed}) exceeds recommended limit")

    envelope = safety.get("work_envelope", {})
    for axis in ["x", "y", "z"]:
        min_val = envelope.get(f"{axis}_min", 0)
        max_val = envelope.get(f"{axis}_max", 300)

        if min_val >= max_val:
            raise ValueError(f"Invalid work envelope: {axis}_min >= {axis}_max")

    print("Safety limits validation passed")

Configuration Modification

Runtime Modification

# Modify configuration at runtime (not persistent)
config._config["system"]["duet_ip"] = "192.168.1.200"

# Verify change
new_ip = config.get_duet_ip()
print(f"New IP: {new_ip}")

Not Persistent

Runtime modifications are not saved to the configuration file. They only affect the current program execution.

Saving Configuration

To make persistent changes, edit the JSON files directly:

import json
from pathlib import Path

def update_duet_ip(new_ip):
    """Update Duet IP in configuration file."""
    config_file = Path("jubilee_api_config/system_config.json")

    # Read current config
    with open(config_file, 'r') as f:
        config_data = json.load(f)

    # Update value
    config_data["system"]["duet_ip"] = new_ip

    # Write back
    with open(config_file, 'w') as f:
        json.dump(config_data, f, indent=2)

    print(f"Updated Duet IP to {new_ip}")

# Usage
update_duet_ip("192.168.1.200")

Best Practices

Use Centralized Config

# GOOD - use centralized config
from src.ConfigLoader import config

ip = config.get_duet_ip()

# BAD - hardcoded values
ip = "192.168.1.100"

Validate on Startup

def main():
    # Validate configuration before using it
    try:
        validate_system_config(config)
        validate_safety_limits(config)
    except ValueError as e:
        print(f"Configuration error: {e}")
        return

    # Continue with validated configuration
    # ...

Document Configuration

Keep documentation of configuration fields:

"""
Configuration Fields:

system.duet_ip: str
    IP address of Jubilee Duet controller
    Default: "192.168.1.100"

system.scale_port: str
    Serial port for scale connection
    Default: "/dev/ttyUSB0"

system.default_feedrate: str
    Default movement speed (SLOW, MEDIUM, FAST)
    Default: "MEDIUM"
"""

See Also