use anyhow::{Result, anyhow};
use serialport::SerialPort;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
pub mod projection_window;
#[allow(dead_code)]
mod msg {
pub const MOT_MOVE_HOME: u16 = 0x0443;
pub const MOT_MOVE_VELOCITY: u16 = 0x0457;
pub const MOT_STOP_IMMEDIATE: u16 = 0x0465;
pub const MOT_REQ_POSN: u16 = 0x0411;
pub const MOT_GET_POSN: u16 = 0x0412;
pub const MOT_MOVE_COMPLETED: u16 = 0x0464;
}
pub trait Motor {
fn get_position(&mut self) -> Result<f32>;
fn start_rotation(&mut self, velocity: f32) -> Result<()>;
fn stop(&mut self) -> Result<()>;
}
pub struct ThorlabsMotor {
port: Box<dyn SerialPort>,
counts_per_deg: f32, channel: u8,
}
impl ThorlabsMotor {
pub fn new(port_name: &str) -> Result<Self> {
let port = serialport::new(port_name, 115_200)
.timeout(Duration::from_millis(50))
.open()?;
Ok(Self {
port,
counts_per_deg: 1919.64, channel: 1,
})
}
fn send_command(&mut self, msg_id: u16, param1: u8, param2: u8) -> Result<()> {
let mut buf = [0u8; 6];
buf[0..2].copy_from_slice(&msg_id.to_le_bytes());
buf[2] = param1;
buf[3] = param2;
buf[4] = 0x50; buf[5] = 0x01; self.port.write_all(&buf)?;
Ok(())
}
fn read_position_response(&mut self) -> Result<f32> {
let mut header = [0u8; 6];
self.port.read_exact(&mut header)?;
let msg_id = u16::from_le_bytes([header[0], header[1]]);
if msg_id != msg::MOT_GET_POSN {
return Err(anyhow!("Unexpected message ID: 0x{:04X}", msg_id));
}
let mut data = [0u8; 6];
self.port.read_exact(&mut data)?;
let counts = i32::from_le_bytes([data[2], data[3], data[4], data[5]]);
Ok(counts as f32 / self.counts_per_deg)
}
}
impl Motor for ThorlabsMotor {
fn get_position(&mut self) -> Result<f32> {
self.send_command(msg::MOT_REQ_POSN, self.channel, 0)?;
self.read_position_response()
}
fn start_rotation(&mut self, velocity: f32) -> Result<()> {
self.send_command(
msg::MOT_MOVE_VELOCITY,
self.channel,
if velocity >= 0.0 { 1 } else { 2 },
)
}
fn stop(&mut self) -> Result<()> {
self.send_command(msg::MOT_STOP_IMMEDIATE, self.channel, 0)
}
}
pub struct MockMotor {
start_time: Option<std::time::Instant>,
velocity: f32,
}
impl MockMotor {
pub fn new() -> Self {
Self {
start_time: None,
velocity: 0.0,
}
}
}
impl Default for MockMotor {
fn default() -> Self {
Self::new()
}
}
impl Motor for MockMotor {
fn get_position(&mut self) -> Result<f32> {
if let Some(start) = self.start_time {
let elapsed = start.elapsed().as_secs_f32();
Ok((elapsed * self.velocity) % 360.0)
} else {
Ok(0.0)
}
}
fn start_rotation(&mut self, velocity: f32) -> Result<()> {
self.start_time = Some(std::time::Instant::now());
self.velocity = velocity;
Ok(())
}
fn stop(&mut self) -> Result<()> {
self.start_time = None;
Ok(())
}
}
pub struct ProjectionController<M: Motor + Send + 'static> {
pub motor: Arc<std::sync::Mutex<M>>,
_projections: ndarray::Array3<f32>, angles: Vec<f32>,
shared_frame_index: Arc<AtomicUsize>,
}
impl<M: Motor + Send + 'static> ProjectionController<M> {
pub fn new(motor: M, projections: ndarray::Array3<f32>, angles: Vec<f32>) -> Self {
Self {
motor: Arc::new(std::sync::Mutex::new(motor)),
_projections: projections,
angles,
shared_frame_index: Arc::new(AtomicUsize::new(0)),
}
}
pub fn shared_frame_index(&self) -> Arc<AtomicUsize> {
Arc::clone(&self.shared_frame_index)
}
pub fn get_frame_index(&self, current_angle: f32) -> usize {
let mut closest_idx = 0;
let mut min_diff = f32::MAX;
for (i, &angle) in self.angles.iter().enumerate() {
let diff = (angle - current_angle).abs();
let wrapped_diff = diff.min(360.0 - diff);
if wrapped_diff < min_diff {
min_diff = wrapped_diff;
closest_idx = i;
}
}
closest_idx
}
pub fn spawn_sync_thread(&self, stop_signal: Arc<AtomicBool>) {
let motor = Arc::clone(&self.motor);
let shared_idx = Arc::clone(&self.shared_frame_index);
let angles = self.angles.clone();
std::thread::spawn(move || {
while !stop_signal.load(Ordering::Relaxed) {
if let Ok(mut m) = motor.lock() {
if let Ok(pos) = m.get_position() {
let mut closest_idx = 0;
let mut min_diff = f32::MAX;
for (i, &angle) in angles.iter().enumerate() {
let diff = (angle - pos).abs();
let wrapped_diff = diff.min(360.0 - diff);
if wrapped_diff < min_diff {
min_diff = wrapped_diff;
closest_idx = i;
}
}
shared_idx.store(closest_idx, Ordering::Relaxed);
}
}
std::thread::sleep(Duration::from_millis(5)); }
});
}
pub fn poll_and_render(&mut self, window: &projection_window::ProjectionWindow) -> Result<()> {
let frame_idx = self.shared_frame_index.load(Ordering::Relaxed);
window.render(frame_idx)?;
Ok(())
}
pub fn run_sync_loop(&mut self) -> Result<()> {
{
let mut m = self
.motor
.lock()
.map_err(|_| anyhow!("Mutex lock failed"))?;
m.start_rotation(10.0)?; }
loop {
let frame_idx = self.shared_frame_index.load(Ordering::Relaxed);
println!("Frame Index: {}", frame_idx);
std::thread::sleep(Duration::from_millis(16)); }
}
}