forked from ROMEO/nexosim
Move UniRequestor example to util crate
This commit is contained in:
174
nexosim-util/examples/uni_requestor.rs
Normal file
174
nexosim-util/examples/uni_requestor.rs
Normal file
@ -0,0 +1,174 @@
|
||||
//! Example: sensor reading data from environment model.
|
||||
//!
|
||||
//! This example demonstrates in particular:
|
||||
//!
|
||||
//! * cyclical self-scheduling methods,
|
||||
//! * model initialization,
|
||||
//! * simulation monitoring with buffered event sinks,
|
||||
//! * connection with mapping,
|
||||
//! * UniRequestor port.
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌─────────────┐ ┌──────────┐
|
||||
//! │ │ temperature │ │ overheat
|
||||
//! Temperature ●─────────►│ Environment ├──────────────►│ Sensor ├──────────►
|
||||
//! │ │ │ │
|
||||
//! └─────────────┘ └──────────┘
|
||||
//! ```
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use nexosim_util::observables::ObservableValue;
|
||||
|
||||
use nexosim::model::{Context, InitializedModel, Model};
|
||||
use nexosim::ports::{EventBuffer, Output, UniRequestor};
|
||||
use nexosim::simulation::{Mailbox, SimInit, SimulationError};
|
||||
use nexosim::time::MonotonicTime;
|
||||
|
||||
/// Sensor model
|
||||
pub struct Sensor {
|
||||
/// Temperature [deg C] -- requestor port.
|
||||
pub temp: UniRequestor<(), f64>,
|
||||
|
||||
/// Overheat detection [-] -- output port.
|
||||
pub overheat: Output<bool>,
|
||||
|
||||
/// Temperature threshold [deg C] -- parameter.
|
||||
threshold: f64,
|
||||
|
||||
/// Overheat detection [-] -- observable state.
|
||||
oh: ObservableValue<bool>,
|
||||
}
|
||||
|
||||
impl Sensor {
|
||||
/// Creates new Sensor with overheat threshold set [deg C].
|
||||
pub fn new(threshold: f64, temp: UniRequestor<(), f64>) -> Self {
|
||||
let overheat = Output::new();
|
||||
Self {
|
||||
temp,
|
||||
overheat: overheat.clone(),
|
||||
threshold,
|
||||
oh: ObservableValue::new(overheat),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cyclically scheduled method that reads data from environment and
|
||||
/// avaluates overheat state.
|
||||
pub async fn tick(&mut self) {
|
||||
let temp = self.temp.send(()).await.unwrap();
|
||||
if temp > self.threshold {
|
||||
if !self.oh.get() {
|
||||
self.oh.set(true).await;
|
||||
}
|
||||
} else if *self.oh.get() {
|
||||
self.oh.set(false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model for Sensor {
|
||||
/// Propagate state and schedule cyclic method.
|
||||
async fn init(mut self, context: &mut Context<Self>) -> InitializedModel<Self> {
|
||||
self.oh.propagate().await;
|
||||
|
||||
context
|
||||
.schedule_periodic_event(
|
||||
Duration::from_millis(500),
|
||||
Duration::from_millis(500),
|
||||
Self::tick,
|
||||
(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Environment model.
|
||||
pub struct Env {
|
||||
/// Temperature [deg F] -- internal state.
|
||||
temp: f64,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
/// Creates new environment model with the temperature [deg F] set.
|
||||
pub fn new(temp: f64) -> Self {
|
||||
Self { temp }
|
||||
}
|
||||
|
||||
/// Sets temperature [deg F].
|
||||
pub async fn set_temp(&mut self, temp: f64) {
|
||||
self.temp = temp;
|
||||
}
|
||||
|
||||
/// Gets temperature [deg F].
|
||||
pub async fn get_temp(&mut self, _: ()) -> f64 {
|
||||
self.temp
|
||||
}
|
||||
}
|
||||
|
||||
impl Model for Env {}
|
||||
|
||||
/// Converts Fahrenheit to Celsius.
|
||||
pub fn fahr_to_cels(t: f64) -> f64 {
|
||||
5.0 * (t - 32.0) / 9.0
|
||||
}
|
||||
|
||||
fn main() -> Result<(), SimulationError> {
|
||||
// ---------------
|
||||
// Bench assembly.
|
||||
// ---------------
|
||||
|
||||
// Mailboxes.
|
||||
let sensor_mbox = Mailbox::new();
|
||||
let env_mbox = Mailbox::new();
|
||||
|
||||
// Connect data line and convert Fahrenheit degrees to Celsius.
|
||||
let temp_req = UniRequestor::with_map(|x| *x, fahr_to_cels, Env::get_temp, &env_mbox);
|
||||
|
||||
// Models.
|
||||
let mut sensor = Sensor::new(100.0, temp_req);
|
||||
let env = Env::new(0.0);
|
||||
|
||||
// Model handles for simulation.
|
||||
let env_addr = env_mbox.address();
|
||||
|
||||
let mut overheat = EventBuffer::new();
|
||||
sensor.overheat.connect_sink(&overheat);
|
||||
|
||||
// Start time (arbitrary since models do not depend on absolute time).
|
||||
let t0 = MonotonicTime::EPOCH;
|
||||
|
||||
// Assembly and initialization.
|
||||
let (mut simu, scheduler) = SimInit::new()
|
||||
.add_model(sensor, sensor_mbox, "sensor")
|
||||
.add_model(env, env_mbox, "env")
|
||||
.init(t0)?;
|
||||
|
||||
// ----------
|
||||
// Simulation.
|
||||
// ----------
|
||||
|
||||
// Check initial conditions.
|
||||
assert_eq!(simu.time(), t0);
|
||||
assert_eq!(overheat.next(), Some(false));
|
||||
assert!(overheat.next().is_none());
|
||||
|
||||
// Change temperature in 2s.
|
||||
scheduler
|
||||
.schedule_event(Duration::from_secs(2), Env::set_temp, 105.0, &env_addr)
|
||||
.unwrap();
|
||||
|
||||
// Change temperature in 4s.
|
||||
scheduler
|
||||
.schedule_event(Duration::from_secs(4), Env::set_temp, 213.0, &env_addr)
|
||||
.unwrap();
|
||||
|
||||
simu.step_until(Duration::from_secs(3))?;
|
||||
assert!(overheat.next().is_none());
|
||||
|
||||
simu.step_until(Duration::from_secs(5))?;
|
||||
assert_eq!(overheat.next(), Some(true));
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user