-
Notifications
You must be signed in to change notification settings - Fork 85
Modular Configuration
Copper's configuration system supports modular composition through file includes and parameter substitution. This document explains how to use these features to create reusable and maintainable configuration files.
- Overview
- Including Configuration Files
- Parameter Substitution
- Merging Rules
- Nested Includes
- Common Use Cases
- Best Practices
- Limitations
- Examples
As robot configurations grow more complex, it becomes important to organize and reuse configuration components. Copper's modular configuration system allows you to:
- Split large configurations into manageable, reusable chunks
- Create configuration variations without duplicating the entire RON file
- Parameterize configurations to handle different deployments and environments
You can include other RON configuration files using the includes section at the top level of your configuration:
(
tasks: [
// Your main configuration tasks...
],
cnx: [
// Your main configuration connections...
],
includes: [
(
path: "path/to/included_config.ron",
params: {}, // Optional parameter substitutions
),
],
)The path is relative to the location of the main configuration file. When Copper processes the configuration, it will:
- Read the main configuration file
- Process each included file
- Merge the included configurations according to the merging rules
You can parameterize your included configurations using template variables that will be replaced at runtime:
// included_config.ron
(
tasks: [
(
id: "task_{{instance_id}}", // Will be replaced with the provided instance_id
type: "tasks::Task{{instance_id}}",
config: {
"param_value": {{param_value}}, // Will be replaced with the provided param_value
},
),
],
cnx: [],
)Then in your main configuration:
(
tasks: [],
cnx: [],
includes: [
(
path: "included_config.ron",
params: {
"instance_id": "42", // Replaces {{instance_id}} with "42"
"param_value": 100, // Replaces {{param_value}} with 100
},
),
],
)Parameters use the {{parameter_name}} format and can appear in:
- Task IDs
- Task types
- Connection strings
- Configuration values (both keys and values)
Parameter values can be:
- Strings
- Numbers (integers, floats)
- Booleans
- Null values
- Arrays
- Maps
When merging included configurations with the main configuration, Copper follows these rules:
-
Tasks, resources, bridges, and missions:
- Entries from included files are added to the main configuration
- If an entry with the same ID already exists, the existing entry takes precedence
- The main file is processed first, so main-file entries win over included entries
- Among includes, the first definition of an ID wins; later duplicate IDs are ignored
-
Connections:
- Connections from included files are added to the main configuration
- Connections are considered duplicates only when
src,dst, andmsgall match - Duplicate connections merge their
missionsfilters instead of replacing one another
-
Monitors:
- Monitors from included files are added when their
typeis not already present - If the main file defines a monitor of the same
type, the main monitor takes precedence
- Monitors from included files are added when their
-
Logging and runtime settings:
- If
loggingorruntimeis defined in the main file, it takes precedence - If the main file omits one of these sections, the first included file that defines it supplies it
- If
Configuration files can include other configuration files, allowing for hierarchical composition:
// main.ron
(
tasks: [],
cnx: [],
includes: [
(
path: "middle.ron",
params: {},
),
],
)
// middle.ron
(
tasks: [
(
id: "middle_task",
type: "tasks::MiddleTask",
),
],
cnx: [],
includes: [
(
path: "nested.ron",
params: {},
),
],
)
// nested.ron
(
tasks: [
(
id: "nested_task",
type: "tasks::NestedTask",
),
],
cnx: [],
)When Copper processes the configuration:
- It will start with
main.ron - It will include
middle.ron(which includes its own task) - It will then include
nested.ron(which includes its own task) - The final configuration will contain tasks from all three files
Configuration files can include other configuration files, creating a hierarchy of configurations. Be careful when creating deeply nested configurations to avoid unintended recursion or excessive nesting, as this may impact performance.
Create a base configuration with common components:
// common_sensors.ron
(
tasks: [
(
id: "imu",
type: "sensors::IMU",
),
(
id: "gps",
type: "sensors::GPS",
),
],
cnx: [],
)Include it in multiple robot configurations:
// robot_config.ron
(
tasks: [
// Robot-specific tasks
],
cnx: [
// Robot-specific connections
],
includes: [
(
path: "common_sensors.ron",
params: {},
),
],
)Create configurations for different deployment environments:
// dev_environment.ron
(
tasks: [
(
id: "camera",
type: "sensors::MockCamera", // Mock camera for development
),
],
cnx: [],
)
// prod_environment.ron
(
tasks: [
(
id: "camera",
type: "sensors::RealCamera", // Real camera for production
),
],
cnx: [],
)Choose the appropriate environment in your main configuration:
// robot_config.ron
(
tasks: [
// Common tasks
],
cnx: [
// Common connections
],
includes: [
(
path: "dev_environment.ron", // Change to prod_environment.ron for production
params: {},
),
],
)Create a parameterized task template:
// motor_template.ron
(
tasks: [
(
id: "motor_{{motor_id}}",
type: "actuators::Motor",
config: {
"pin": {{pin}},
"direction": "{{direction}}",
},
),
],
cnx: [],
)Include it multiple times with different parameters:
// robot_config.ron
(
tasks: [],
cnx: [],
includes: [
(
path: "motor_template.ron",
params: {
"motor_id": "left",
"pin": 4,
"direction": "forward",
},
),
(
path: "motor_template.ron",
params: {
"motor_id": "right",
"pin": 5,
"direction": "reverse",
},
),
],
)This will generate two motor tasks with different IDs, pins, and directions.
-
Organize Configuration Files:
- Keep related components together in the same include file
- Use descriptive filenames that indicate the purpose of the configuration
- Create a directory structure that reflects the organization of your robot
-
Parameter Naming:
- Use clear, descriptive parameter names
- Follow a consistent naming convention
- Document parameters with comments in the configuration files
-
Handling Variations:
- Create small, focused configuration files for specific components
- Use parameters for values that might change between deployments
- Create environment-specific configuration files for different contexts (e.g., development, testing, production)
-
Testing:
- Test your configuration with different parameter values
- Verify that the merged configuration matches your expectations
- Consider creating a test that validates your configuration files
-
Order Dependency: The order of includes matters. For duplicate IDs, the first definition wins; later duplicates are ignored.
-
Parameter Scope: Parameters are only applied to the specific include where they're defined. If you want to use the same parameter across multiple includes, you need to specify it for each include.
-
Error Handling: If an included file cannot be found, Copper will return an error. Make sure all included files are available at the specified paths.
-
Circular Includes: Avoid include cycles. Copper processes includes recursively and does not rely on circular-include resolution as part of normal configuration loading.
Start
- Home
- Project Templates
- Copper Application Overview
- Build and Deploy a Copper Application
- Copper Configuration file Reference
- Task Automation with just
Concepts
- Copper Runtime Overview
- Copper Configuration and Mission Visualization
- Copper Tasks lifecycle overview
- Copper Bridge concept
- Resources
- Modular Configuration
Embedded
Reference
External