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::{MonotonicTime, Scheduler};
pub struct Motor {
pub position: Output<u16>,
pos: u16,
torque: f64,
}
impl Motor {
pub const STEPS_PER_REV: u16 = 200;
pub const TORQUE_CONSTANT: f64 = 1.0;
fn new(position: u16) -> Self {
Self {
position: Default::default(),
pos: position % Self::STEPS_PER_REV,
torque: 0.0,
}
}
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;
}
}
impl Model for Motor {
fn init(
mut self,
_scheduler: &Scheduler<Self>,
) -> Pin<Box<dyn Future<Output = InitializedModel<Self>> + Send + '_>> {
Box::pin(async move {
self.position.send(self.pos).await;
self.into()
})
}
}
pub struct Driver {
pub current_out: Output<(f64, f64)>,
pps: f64,
next_phase: u8,
current: f64,
}
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, scheduler: &Scheduler<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((), scheduler).await;
}
}
fn send_pulse<'a>(
&'a mut self,
_: (),
scheduler: &'a Scheduler<Self>,
) -> impl Future<Output = ()> + Send + 'a {
async move {
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());
scheduler
.schedule_event(pulse_duration, Self::send_pulse, ())
.unwrap();
}
}
}
impl Model for Driver {}
fn main() {
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 position = motor.position.connect_stream().0;
let motor_addr = motor_mbox.address();
let driver_addr = driver_mbox.address();
let t0 = MonotonicTime::EPOCH;
let mut simu = SimInit::new()
.add_model(driver, driver_mbox)
.add_model(motor, motor_mbox)
.init(t0);
let mut t = t0;
assert_eq!(simu.time(), t);
assert_eq!(position.next(), Some(init_pos));
assert!(position.next().is_none());
simu.schedule_event(
Duration::from_secs(2),
Driver::pulse_rate,
10.0,
&driver_addr,
)
.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;
assert_eq!(position.by_ref().last().unwrap(), pos);
simu.step_by(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.next(), Some(pos));
}
assert!(position.next().is_none());
simu.send_event(Motor::load, 2.0, &motor_addr);
simu.step();
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
assert!(position.next().is_none());
simu.step();
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
assert!(position.next().is_none());
simu.send_event(Motor::load, 0.5, &motor_addr);
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.next(), Some(pos));
simu.step_by(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.next(), Some(pos));
}
assert!(position.next().is_none());
simu.send_event(Driver::pulse_rate, -10.0, &driver_addr);
simu.step();
t += Duration::new(0, 100_000_000);
assert_eq!(simu.time(), t);
pos = (pos + 1) % Motor::STEPS_PER_REV;
assert_eq!(position.next(), Some(pos));
simu.step_by(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;
assert_eq!(position.by_ref().last(), Some(pos));
}