use std::iter;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use nexosim::model::{BuildContext, Model, ProtoModel};
use nexosim::ports::{EventSinkReader, EventSource, Output, SinkState, event_queue};
use nexosim::simulation::{Mailbox, SimInit, SimulationError};
use nexosim::time::MonotonicTime;
mod stepper_motor;
pub use stepper_motor::{Driver, Motor};
pub struct ProtoMotorAssembly {
pub position: Output<u16>,
init_pos: u16,
}
impl ProtoMotorAssembly {
pub fn new(init_pos: u16) -> Self {
Self {
position: Default::default(),
init_pos,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct MotorAssembly {
pps: Output<f64>,
load: Output<f64>,
}
#[Model]
impl MotorAssembly {
fn new() -> Self {
Self {
pps: Default::default(),
load: Default::default(),
}
}
pub async fn pulse_rate(&mut self, pps: f64) {
self.pps.send(pps).await
}
pub async fn load(&mut self, torque: f64) {
self.load.send(torque).await
}
}
impl ProtoModel for ProtoMotorAssembly {
type Model = MotorAssembly;
fn build(self, cx: &mut BuildContext<Self>) -> (MotorAssembly, ()) {
let mut assembly = MotorAssembly::new();
let mut motor = Motor::new(self.init_pos);
let mut driver = Driver::new(1.0);
let motor_mbox = Mailbox::new();
let driver_mbox = Mailbox::new();
assembly.pps.connect(Driver::pulse_rate, &driver_mbox);
assembly.load.connect(Motor::load, &motor_mbox);
driver.current_out.connect(Motor::current_in, &motor_mbox);
motor.position = self.position;
cx.add_submodel(driver, driver_mbox, "driver");
cx.add_submodel(motor, motor_mbox, "motor");
(assembly, ())
}
}
fn main() -> Result<(), SimulationError> {
let init_pos = 123;
let mut assembly = ProtoMotorAssembly::new(init_pos);
let assembly_mbox = Mailbox::new();
let assembly_addr = assembly_mbox.address();
let mut bench = SimInit::new();
let pulse_rate = EventSource::new()
.connect(MotorAssembly::pulse_rate, &assembly_addr)
.register(&mut bench);
let (sink, mut position) = event_queue(SinkState::Enabled);
assembly.position.connect_sink(sink);
let t0 = MonotonicTime::EPOCH; let mut simu = bench
.add_model(assembly, assembly_mbox, "assembly")
.init(t0)?;
let scheduler = simu.scheduler();
let mut t = t0;
assert_eq!(simu.time(), t);
assert_eq!(position.try_read(), Some(init_pos));
assert!(position.try_read().is_none());
scheduler
.schedule_event(Duration::from_secs(2), &pulse_rate, 10.0)
.unwrap();
simu.step()?;
t += Duration::new(2, 0);
assert_eq!(simu.time(), t);
simu.step()?;
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
let mut pos = (((init_pos + 1) / 4) * 4 + 1) % Motor::STEPS_PER_REV;
let last_pos = iter::from_fn(|| position.try_read()).last();
assert_eq!(last_pos, Some(pos));
simu.step_until(Duration::new(0, 900_000_000))?;
t += Duration::new(0, 900_000_000);
assert_eq!(simu.time(), t);
for _ in 0..9 {
pos = (pos + 1) % Motor::STEPS_PER_REV;
assert_eq!(position.try_read(), Some(pos));
}
assert!(position.try_read().is_none());
Ok(())
}