RuntimeParams
A typed key-value store for runtime configuration. Loads defaults from .horus/config/params.yaml, supports typed get/set with validation, concurrent access, and persistence to disk.
Quick Start
use horus::prelude::*;
let params = RuntimeParams::new()?;
// Typed get with default
let speed: f64 = params.get_or("max_speed", 1.0);
// Typed set
params.set("max_speed", 2.0)?;
// Check and iterate
if params.has("pid_kp") {
let keys = params.list_keys();
println!("Parameters: {:?}", keys);
}
// Persist to disk
params.save_to_disk()?;
Creating
// Load from .horus/config/params.yaml (or built-in defaults)
let params = RuntimeParams::new()?;
// Load from explicit file
let params = RuntimeParams::new()?;
params.load_from_disk(Path::new("config/robot.yaml"))?;
If .horus/config/params.yaml exists, parameters are loaded from it. Otherwise, built-in defaults are used.
Reading Parameters
// Option-based (returns None if missing or type mismatch)
let speed: Option<f64> = params.get("max_speed");
// With default value
let kp: f64 = params.get_or("pid_kp", 1.0);
// With explicit error reporting
let speed: f64 = params.get_typed("max_speed")?; // Returns HorusError if missing
// Check existence
if params.has("emergency_stop_distance") {
// ...
}
Writing Parameters
// Set any serde-serializable value
params.set("max_speed", 1.5_f64)?;
params.set("sensor_ids", vec![1, 2, 3])?;
params.set("robot_name", "atlas")?;
// Optimistic locking (concurrent edit protection)
let version = params.get_version("max_speed");
params.set_with_version("max_speed", 2.0, version)?; // Fails if modified since
// Remove a parameter
params.remove("obsolete_param");
// Reset to defaults
params.reset()?;
Persistence
// Save current state to .horus/config/params.yaml
params.save_to_disk()?;
// Load from specific file
params.load_from_disk(Path::new("config/prod.yaml"))?;
Built-in Defaults
These parameters are available out of the box:
| Key | Default | Description |
|---|---|---|
tick_rate | 30 | Default scheduler tick rate (Hz) |
max_memory_mb | 512 | Memory limit |
max_speed | 1.0 | Maximum linear speed (m/s) |
max_angular_speed | 1.0 | Maximum angular speed (rad/s) |
acceleration_limit | 0.5 | Acceleration limit (m/s²) |
lidar_rate | 10 | LiDAR scan rate (Hz) |
camera_fps | 30 | Camera frame rate |
sensor_timeout_ms | 1000 | Sensor timeout (ms) |
emergency_stop_distance | 0.3 | E-stop trigger distance (m) |
collision_threshold | 0.5 | Collision detection threshold (m) |
pid_kp | 1.0 | PID proportional gain |
pid_ki | 0.1 | PID integral gain |
pid_kd | 0.05 | PID derivative gain |
Usage in Nodes
use horus::prelude::*;
struct MotorController {
params: RuntimeParams,
}
impl MotorController {
fn new() -> Result<Self> {
Ok(Self {
params: RuntimeParams::new()?,
})
}
}
impl Node for MotorController {
fn name(&self) -> &str { "motor_ctrl" }
fn tick(&mut self) {
let kp: f64 = self.params.get_or("pid_kp", 1.0);
let max_speed: f64 = self.params.get_or("max_speed", 1.0);
// ... use params in control loop
}
}
Python Equivalent
import horus
p = horus.Params()
kp = p["pid_kp"] # 1.0
p["pid_kp"] = 2.5
p.save()
See Python Bindings — Runtime Parameters for full Python API.
API Reference
| Method | Returns | Description |
|---|---|---|
new() | Result<Self> | Load from .horus/config/params.yaml or defaults |
get::<T>(key) | Option<T> | Typed get, None if missing |
get_or::<T>(key, default) | T | Get with default |
get_typed::<T>(key) | Result<T> | Get with error reporting |
set(key, value) | Result<()> | Set (validates if metadata exists) |
has(key) | bool | Check existence |
list_keys() | Vec<String> | All parameter names |
remove(key) | Option<Value> | Remove and return |
reset() | Result<()> | Reset to built-in defaults |
save_to_disk() | Result<()> | Persist to YAML |
load_from_disk(path) | Result<()> | Load from YAML file |
get_version(key) | u64 | Version for optimistic locking |
set_with_version(key, value, version) | Result<()> | Set with version check |
Validation Rules
Parameters can have validation rules that are enforced on every set() call. Rules are defined through ParamMetadata and checked automatically.
| Rule | Applies To | Description |
|---|---|---|
MinValue(f64) | Numbers | Value must be >= minimum |
MaxValue(f64) | Numbers | Value must be <= maximum |
Range(f64, f64) | Numbers | Value must be within [min, max] |
RegexPattern(String) | Strings | Value must match regex pattern |
Enum(Vec<String>) | Strings | Value must be one of the allowed values |
MinLength(usize) | Strings/Arrays | Minimum length/element count |
MaxLength(usize) | Strings/Arrays | Maximum length/element count |
RequiredKeys(Vec<String>) | Objects | JSON object must contain all listed keys |
When a set() violates a rule, it returns Err(HorusError::Validation(...)) with a descriptive message.
use horus::prelude::*;
// PID gain with range validation
// If metadata exists with Range(0.0, 100.0), this fails:
let result = params.set("pid.kp", 150.0);
// Err: "Parameter 'pid.kp' value 150 exceeds maximum 100"
// Enum-validated parameter
let result = params.set("mode", "turbo");
// Err: "Parameter 'mode' value 'turbo' not in allowed values: [manual, auto, standby]"
Metadata
ParamMetadata provides descriptions, units, validation, and read-only flags for parameters.
| Field | Type | Description |
|---|---|---|
description | Option<String> | Human-readable description |
unit | Option<String> | Unit of measurement (e.g., "m/s", "Hz", "rad") |
validation | Vec<ValidationRule> | Validation rules (checked on set) |
read_only | bool | If true, set() is rejected |
// Query metadata for a parameter
if let Some(meta) = params.get_metadata("max_speed") {
println!("Description: {:?}", meta.description);
println!("Unit: {:?}", meta.unit);
println!("Read-only: {}", meta.read_only());
}
Versioned Updates (Optimistic Locking)
For concurrent parameter tuning (e.g., monitor UI + code both updating the same parameter), use versioned updates to prevent lost writes:
use horus::prelude::*;
// Read current version
let version = params.get_version("pid.kp");
// Set with version check — fails if someone else modified it since our read
match params.set_with_version("pid.kp", 2.5, version) {
Ok(()) => println!("Updated successfully"),
Err(e) => println!("Conflict: {}", e), // Someone else changed it
}
This implements optimistic concurrency control:
- Read the current version with
get_version(key) - Compute your new value
- Call
set_with_version(key, value, version)— if the version hasn't changed, the write succeeds - If it fails, re-read and retry
Persistence
Parameters can be saved to and loaded from YAML files:
// Save all parameters to disk (uses default path)
params.save_to_disk()?;
// Load parameters from a specific file
params.load_from_disk(Path::new("robot_config.yaml"))?;
The YAML format is a flat key-value map:
pid.kp: 2.5
pid.ki: 0.1
pid.kd: 0.05
max_speed: 1.0
mode: "auto"
Method Reference
new
pub fn new() -> HorusResult<Self>
Create a parameter store. Loads defaults and any existing values from .horus/config/params.yaml.
Example:
let params = RuntimeParams::new()?;
get
pub fn get<T: Deserialize>(&self, key: &str) -> Option<T>
Get a parameter value, deserialized to type T. Returns None if the key doesn't exist.
Example:
let speed: Option<f64> = params.get("max_speed");
get_typed
pub fn get_typed<T: Deserialize>(&self, key: &str) -> HorusResult<T>
Get a parameter, returning an error if missing or wrong type. Use when the parameter is required.
Example:
let kp: f64 = params.get_typed("pid_kp")?; // error if missing
get_or
pub fn get_or<T: Deserialize>(&self, key: &str, default: T) -> T
Get a parameter with a fallback default.
Example:
let speed = params.get_or("max_speed", 1.5); // 1.5 if not set
set
pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), HorusError>
Set a parameter. Validates against metadata constraints (min/max, regex, read-only) if configured. Logs the change to the audit log.
Errors: ValidationError::OutOfRange if value violates constraints, or if the key is read-only.
Example:
params.set("pid_kp", 2.5)?;
params.set("max_speed", 0.8)?;
set_with_version
pub fn set_with_version<T: Serialize>(&self, key: &str, value: T, expected_version: u64) -> Result<(), HorusError>
Optimistic locking — set only if the parameter hasn't been modified since expected_version. Prevents concurrent edit conflicts.
Example:
let version = params.get_version("pid_kp");
// ... compute new value ...
params.set_with_version("pid_kp", new_kp, version)?; // fails if someone else changed it
list_keys
pub fn list_keys(&self) -> Vec<String>
Returns all parameter names.
Example:
for key in params.list_keys() {
println!("{} = {:?}", key, params.get::<serde_json::Value>(&key));
}
has
pub fn has(&self, key: &str) -> bool
Check if a parameter exists.
remove
pub fn remove(&self, key: &str) -> Option<Value>
Remove a parameter. Returns the old value if it existed.
reset
pub fn reset(&self) -> Result<(), HorusError>
Reset all parameters to defaults. Clears user-set values.
get_metadata
pub fn get_metadata(&self, key: &str) -> Option<ParamMetadata>
Get metadata for a parameter: description, unit, read-only flag, validation rules.
Example:
if let Some(meta) = params.get_metadata("max_speed") {
println!("Unit: {:?}, Read-only: {}", meta.unit, meta.read_only);
}
get_version
pub fn get_version(&self, key: &str) -> u64
Get the current version counter for a parameter. Used with set_with_version() for optimistic locking.
See Also
- Python Params — Python dict-like API
- horus.toml Configuration — Project-level configuration
- Parameters CLI —
horus param get/set/list