use std::iter;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use nexosim::model::{Context, Model, schedulable};
use nexosim::ports::{EventSinkReader, EventSource, Output, SinkState, event_queue};
use nexosim::simulation::{Mailbox, SimInit};
use nexosim::time::MonotonicTime;
#[derive(Serialize, Deserialize)]
pub struct Motor {
pub position: Output<u16>,
pos: u16,
torque: f64,
}
#[Model]
impl Motor {
pub const STEPS_PER_REV: u16 = 200;
pub const TORQUE_CONSTANT: f64 = 1.0;
pub fn new(position: u16) -> Self {
Self {
position: Default::default(),
pos: position % Self::STEPS_PER_REV,
torque: 0.0,
}
}
#[nexosim(init)]
async fn init(&mut self) {
self.position.send(self.pos).await;
}
pub async fn current_in(&mut self, current: (f64, f64)) {
assert!(!current.0.is_nan() && !current.1.is_nan());
let (target_phase, abs_current) = match (current.0 != 0.0, current.1 != 0.0) {
(false, false) => return,
(true, false) => (if current.0 > 0.0 { 0 } else { 2 }, current.0.abs()),
(false, true) => (if current.1 > 0.0 { 1 } else { 3 }, current.1.abs()),
_ => panic!("current detected in both coils"),
};
if abs_current < Self::TORQUE_CONSTANT * self.torque {
return;
}
let pos_delta = match target_phase - (self.pos % 4) as i8 {
0 | 2 | -2 => return,
1 | -3 => 1,
-1 | 3 => Self::STEPS_PER_REV - 1,
_ => unreachable!(),
};
self.pos = (self.pos + pos_delta) % Self::STEPS_PER_REV;
self.position.send(self.pos).await;
}
pub fn load(&mut self, torque: f64) {
assert!(torque >= 0.0);
self.torque = torque;
}
}
#[derive(Serialize, Deserialize)]
pub struct Driver {
pub current_out: Output<(f64, f64)>,
pps: f64,
next_phase: u8,
current: f64,
}
#[Model]
impl Driver {
const MIN_PPS: f64 = 1.0;
const MAX_PPS: f64 = 1_000.0;
pub fn new(nominal_current: f64) -> Self {
Self {
current_out: Default::default(),
pps: 0.0,
next_phase: 0,
current: nominal_current,
}
}
pub async fn pulse_rate(&mut self, pps: f64, cx: &Context<Self>) {
let pps = pps.signum() * pps.abs().clamp(Self::MIN_PPS, Self::MAX_PPS);
if pps == self.pps {
return;
}
let is_idle = self.pps == 0.0;
self.pps = pps;
if is_idle {
self.send_pulse((), cx).await;
}
}
#[nexosim(schedulable)]
async fn send_pulse(&mut self, _: (), cx: &Context<Self>) {
let current_out = match self.next_phase {
0 => (self.current, 0.0),
1 => (0.0, self.current),
2 => (-self.current, 0.0),
3 => (0.0, -self.current),
_ => unreachable!(),
};
self.current_out.send(current_out).await;
if self.pps == 0.0 {
return;
}
self.next_phase = (self.next_phase + (self.pps.signum() + 4.0) as u8) % 4;
let pulse_duration = Duration::from_secs_f64(1.0 / self.pps.abs());
cx.schedule_event(pulse_duration, schedulable!(Self::send_pulse), ())
.unwrap();
}
}
#[allow(dead_code)]
fn main() -> Result<(), nexosim::simulation::SimulationError> {
let init_pos = 123;
let mut motor = Motor::new(init_pos);
let mut driver = Driver::new(1.0);
let motor_mbox = Mailbox::new();
let driver_mbox = Mailbox::new();
driver.current_out.connect(Motor::current_in, &motor_mbox);
let mut bench = SimInit::new();
let pulse_rate = EventSource::new()
.connect(Driver::pulse_rate, &driver_mbox)
.register(&mut bench);
let motor_load = EventSource::new()
.connect(Motor::load, &motor_mbox)
.register(&mut bench);
let (sink, mut position) = event_queue(SinkState::Enabled);
motor.position.connect_sink(sink);
let t0 = MonotonicTime::EPOCH; let mut simu = bench
.add_model(driver, driver_mbox, "driver")
.add_model(motor, motor_mbox, "motor")
.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());
simu.process_event(&motor_load, 2.0)?;
simu.step()?;
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
assert!(position.try_read().is_none());
simu.step()?;
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
assert!(position.try_read().is_none());
simu.process_event(&motor_load, 0.5)?;
simu.step()?;
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
pos = (pos + Motor::STEPS_PER_REV - 1) % Motor::STEPS_PER_REV;
assert_eq!(position.try_read(), Some(pos));
simu.step_until(Duration::new(0, 700_000_000))?;
t += Duration::new(0, 700_000_000);
assert_eq!(simu.time(), t);
for _ in 0..7 {
pos = (pos + 1) % Motor::STEPS_PER_REV;
assert_eq!(position.try_read(), Some(pos));
}
assert!(position.try_read().is_none());
simu.process_event(&pulse_rate, -10.0)?;
simu.step()?;
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
pos = (pos + 1) % Motor::STEPS_PER_REV;
assert_eq!(position.try_read(), Some(pos));
simu.step_until(Duration::new(1, 900_000_000))?;
t += Duration::new(1, 900_000_000);
assert_eq!(simu.time(), t);
pos = (pos + Motor::STEPS_PER_REV - 19) % Motor::STEPS_PER_REV;
let last_pos = iter::from_fn(|| position.try_read()).last();
assert_eq!(last_pos, Some(pos));
Ok(())
}