use oxictl::core::signal::{Feedback, Setpoint};
use oxictl::core::traits::{Controller, Plant};
use oxictl::pid::{AntiWindupMethod, PidConfig};
use oxictl::safety::{FaultResponse, FaultSeverity, SafetyMonitor, Watchdog};
use oxictl::sim::{Scope, ThermalPlant};
fn main() {
let tau = 10.0; let gain = 100.0; let ambient = 25.0; let mut plant = ThermalPlant::new(ambient, tau, gain, ambient);
let mut pid = PidConfig::pid(0.5, 0.1, 2.0)
.with_limits(0.0, 1.0) .with_anti_windup(AntiWindupMethod::Clamping)
.build();
let mut watchdog = Watchdog::<f64>::new(0.5);
let mut safety = SafetyMonitor::<f64, 1, 1, 0>::new();
safety.set_range(0, -10.0, 150.0, FaultSeverity::Critical);
safety.set_rate(0, 50.0, FaultSeverity::Warning);
let dt = 0.01; let total_time = 60.0; let steps = (total_time / dt) as usize;
let mut scope_temp = Scope::with_capacity("temperature", steps);
let mut scope_setpoint = Scope::with_capacity("setpoint", steps);
let mut scope_output = Scope::with_capacity("heater_power", steps);
let setpoint_fn = |t: f64| -> f64 {
if t < 30.0 {
60.0
} else {
80.0
}
};
println!("time,setpoint,temperature,heater_power,safety_ok");
let mut time = 0.0;
for _ in 0..steps {
let sp_value = setpoint_fn(time);
let temp = plant.output();
let safety_status = safety.evaluate(&[temp], &[temp], dt);
watchdog.kick();
let _ = watchdog.check(dt);
let heater_power = if safety_status.response == FaultResponse::EmergencyStop {
0.0 } else {
let sp = Setpoint::new(sp_value);
let fb = Feedback::new(temp);
let out = pid.update(&sp, &fb, dt);
out.value().clamp(0.0, 1.0)
};
scope_temp.record(time, temp);
scope_setpoint.record(time, sp_value);
scope_output.record(time, heater_power);
if (time * 100.0) as u64 % 100 == 0 {
println!(
"{:.2},{:.2},{:.4},{:.4},{}",
time, sp_value, temp, heater_power, !safety_status.any_violation
);
}
if (time - 45.0).abs() < dt / 2.0 {
plant.add_disturbance(-10.0);
eprintln!(">>> Disturbance injected at t={:.2}s: -10 deg", time);
}
plant.step(heater_power, dt);
time += dt;
}
eprintln!("\n=== Simulation Summary ===");
eprintln!(
"Temperature range: {:.2} .. {:.2}",
scope_temp.min_value().unwrap(),
scope_temp.max_value().unwrap()
);
eprintln!("Final temperature: {:.2}", plant.output());
eprintln!("Final setpoint: {:.2}", setpoint_fn(total_time));
eprintln!(
"Steady-state error: {:.4}",
(plant.output() - setpoint_fn(total_time)).abs()
);
eprintln!("Total data points: {}", scope_temp.len());
}