use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use asynchronix::model::{InitializedModel, Model, Output};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::{EventKey, MonotonicTime, Scheduler};
pub struct Pump {
pub flow_rate: Output<f64>,
nominal_flow_rate: f64,
}
impl Pump {
pub fn new(nominal_flow_rate: f64) -> Self {
Self {
nominal_flow_rate,
flow_rate: Output::default(),
}
}
pub async fn command(&mut self, cmd: PumpCommand) {
let flow_rate = match cmd {
PumpCommand::On => self.nominal_flow_rate,
PumpCommand::Off => 0.0,
};
self.flow_rate.send(flow_rate).await;
}
}
impl Model for Pump {}
pub struct Controller {
pub pump_cmd: Output<PumpCommand>,
brew_time: Duration,
water_sense: WaterSenseState,
stop_brew_key: Option<EventKey>,
}
impl Controller {
const DEFAULT_BREW_TIME: Duration = Duration::new(25, 0);
pub fn new() -> Self {
Self {
brew_time: Self::DEFAULT_BREW_TIME,
pump_cmd: Output::default(),
stop_brew_key: None,
water_sense: WaterSenseState::Empty, }
}
pub async fn water_sense(&mut self, state: WaterSenseState) {
if state == WaterSenseState::Empty && self.water_sense == WaterSenseState::NotEmpty {
if let Some(key) = self.stop_brew_key.take() {
key.cancel();
self.pump_cmd.send(PumpCommand::Off).await;
}
}
self.water_sense = state;
}
pub async fn brew_time(&mut self, brew_time: Duration) {
assert!(!brew_time.is_zero());
self.brew_time = brew_time;
}
pub async fn brew_cmd(&mut self, _: (), scheduler: &Scheduler<Self>) {
if let Some(key) = self.stop_brew_key.take() {
self.pump_cmd.send(PumpCommand::Off).await;
key.cancel();
return;
}
if self.water_sense == WaterSenseState::Empty {
return;
}
self.stop_brew_key = Some(
scheduler
.schedule_keyed_event(self.brew_time, Self::stop_brew, ())
.unwrap(),
);
self.pump_cmd.send(PumpCommand::On).await;
}
async fn stop_brew(&mut self) {
if self.stop_brew_key.take().is_some() {
self.pump_cmd.send(PumpCommand::Off).await;
}
}
}
impl Model for Controller {}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum PumpCommand {
On,
Off,
}
pub struct Tank {
pub water_sense: Output<WaterSenseState>,
volume: f64,
dynamic_state: Option<TankDynamicState>,
}
impl Tank {
pub fn new(water_volume: f64) -> Self {
assert!(water_volume >= 0.0);
Self {
volume: water_volume,
dynamic_state: None,
water_sense: Output::default(),
}
}
pub async fn fill(&mut self, added_volume: f64, scheduler: &Scheduler<Self>) {
if added_volume <= 0.0 {
return;
}
let was_empty = self.volume == 0.0;
self.volume += added_volume;
if let Some(state) = self.dynamic_state.take() {
state.set_empty_key.cancel();
let time = scheduler.time();
let elapsed_time = time.duration_since(state.last_volume_update).as_secs_f64();
self.volume = (self.volume - state.flow_rate * elapsed_time).max(0.0);
self.schedule_empty(state.flow_rate, time, scheduler).await;
return;
}
if was_empty {
self.water_sense.send(WaterSenseState::NotEmpty).await;
}
}
pub async fn set_flow_rate(&mut self, flow_rate: f64, scheduler: &Scheduler<Self>) {
assert!(flow_rate >= 0.0);
let time = scheduler.time();
if let Some(state) = self.dynamic_state.take() {
state.set_empty_key.cancel();
let elapsed_time = time.duration_since(state.last_volume_update).as_secs_f64();
self.volume = (self.volume - state.flow_rate * elapsed_time).max(0.0);
}
self.schedule_empty(flow_rate, time, scheduler).await;
}
async fn schedule_empty(
&mut self,
flow_rate: f64,
time: MonotonicTime,
scheduler: &Scheduler<Self>,
) {
let duration_until_empty = if self.volume == 0.0 {
0.0
} else {
self.volume / flow_rate
};
if duration_until_empty.is_infinite() {
return;
}
let duration_until_empty = Duration::from_secs_f64(duration_until_empty);
match scheduler.schedule_keyed_event(duration_until_empty, Self::set_empty, ()) {
Ok(set_empty_key) => {
let state = TankDynamicState {
last_volume_update: time,
set_empty_key,
flow_rate,
};
self.dynamic_state = Some(state);
}
Err(_) => {
self.volume = 0.0;
self.water_sense.send(WaterSenseState::Empty).await;
}
}
}
async fn set_empty(&mut self) {
self.volume = 0.0;
self.dynamic_state = None;
self.water_sense.send(WaterSenseState::Empty).await;
}
}
impl Model for Tank {
fn init(
mut self,
_scheduler: &Scheduler<Self>,
) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>> {
Box::pin(async move {
self.water_sense
.send(if self.volume == 0.0 {
WaterSenseState::Empty
} else {
WaterSenseState::NotEmpty
})
.await;
self.into()
})
}
}
struct TankDynamicState {
last_volume_update: MonotonicTime,
set_empty_key: EventKey,
flow_rate: f64,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum WaterSenseState {
Empty,
NotEmpty,
}
fn main() {
let pump_flow_rate = 4.5e-6;
let init_tank_volume = 1.5e-3;
let mut pump = Pump::new(pump_flow_rate);
let mut controller = Controller::new();
let mut tank = Tank::new(init_tank_volume);
let pump_mbox = Mailbox::new();
let controller_mbox = Mailbox::new();
let tank_mbox = Mailbox::new();
controller.pump_cmd.connect(Pump::command, &pump_mbox);
tank.water_sense
.connect(Controller::water_sense, &controller_mbox);
pump.flow_rate.connect(Tank::set_flow_rate, &tank_mbox);
let mut flow_rate = pump.flow_rate.connect_slot().0;
let controller_addr = controller_mbox.address();
let tank_addr = tank_mbox.address();
let t0 = MonotonicTime::EPOCH;
let mut simu = SimInit::new()
.add_model(controller, controller_mbox)
.add_model(pump, pump_mbox)
.add_model(tank, tank_mbox)
.init(t0);
let mut t = t0;
assert_eq!(simu.time(), t);
simu.send_event(Controller::brew_cmd, (), &controller_addr);
assert_eq!(flow_rate.take(), Some(pump_flow_rate));
simu.step();
t += Controller::DEFAULT_BREW_TIME;
assert_eq!(simu.time(), t);
assert_eq!(flow_rate.take(), Some(0.0));
let volume_per_shot = pump_flow_rate * Controller::DEFAULT_BREW_TIME.as_secs_f64();
let shots_per_tank = (init_tank_volume / volume_per_shot) as u64; for _ in 0..(shots_per_tank - 1) {
simu.send_event(Controller::brew_cmd, (), &controller_addr);
assert_eq!(flow_rate.take(), Some(pump_flow_rate));
simu.step();
t += Controller::DEFAULT_BREW_TIME;
assert_eq!(simu.time(), t);
assert_eq!(flow_rate.take(), Some(0.0));
}
simu.send_event(Controller::brew_cmd, (), &controller_addr);
simu.step();
assert!(simu.time() < t + Controller::DEFAULT_BREW_TIME);
t = simu.time();
assert_eq!(flow_rate.take(), Some(0.0));
simu.send_event(Controller::brew_cmd, (), &controller_addr);
assert!(flow_rate.take().is_none());
let brew_time = Duration::new(30, 0);
simu.send_event(Controller::brew_time, brew_time, &controller_addr);
simu.send_event(Tank::fill, 1.0e-3, tank_addr);
simu.send_event(Controller::brew_cmd, (), &controller_addr);
assert_eq!(flow_rate.take(), Some(pump_flow_rate));
simu.step();
t += brew_time;
assert_eq!(simu.time(), t);
assert_eq!(flow_rate.take(), Some(0.0));
simu.schedule_event(
Duration::from_secs(15),
Controller::brew_cmd,
(),
&controller_addr,
)
.unwrap();
simu.send_event(Controller::brew_cmd, (), &controller_addr);
assert_eq!(flow_rate.take(), Some(pump_flow_rate));
simu.step();
t += Duration::from_secs(15);
assert_eq!(simu.time(), t);
assert_eq!(flow_rate.take(), Some(0.0));
}