1use anyhow::{Result, anyhow};
2use serialport::SerialPort;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
5use std::time::Duration;
6
7pub mod projection_window;
8
9#[allow(dead_code)]
11mod msg {
12 pub const MOT_MOVE_HOME: u16 = 0x0443;
13 pub const MOT_MOVE_VELOCITY: u16 = 0x0457;
14 pub const MOT_STOP_IMMEDIATE: u16 = 0x0465;
15 pub const MOT_REQ_POSN: u16 = 0x0411;
16 pub const MOT_GET_POSN: u16 = 0x0412;
17 pub const MOT_MOVE_COMPLETED: u16 = 0x0464;
18}
19
20pub trait Motor {
22 fn get_position(&mut self) -> Result<f32>;
24 fn start_rotation(&mut self, velocity: f32) -> Result<()>;
26 fn stop(&mut self) -> Result<()>;
28}
29
30pub struct ThorlabsMotor {
32 port: Box<dyn SerialPort>,
33 counts_per_deg: f32, channel: u8,
35}
36
37impl ThorlabsMotor {
38 pub fn new(port_name: &str) -> Result<Self> {
39 let port = serialport::new(port_name, 115_200)
40 .timeout(Duration::from_millis(50))
41 .open()?;
42
43 Ok(Self {
44 port,
45 counts_per_deg: 1919.64, channel: 1,
47 })
48 }
49
50 fn send_command(&mut self, msg_id: u16, param1: u8, param2: u8) -> Result<()> {
52 let mut buf = [0u8; 6];
53 buf[0..2].copy_from_slice(&msg_id.to_le_bytes());
54 buf[2] = param1;
55 buf[3] = param2;
56 buf[4] = 0x50; buf[5] = 0x01; self.port.write_all(&buf)?;
59 Ok(())
60 }
61
62 fn read_position_response(&mut self) -> Result<f32> {
64 let mut header = [0u8; 6];
65 self.port.read_exact(&mut header)?;
66
67 let msg_id = u16::from_le_bytes([header[0], header[1]]);
68 if msg_id != msg::MOT_GET_POSN {
69 return Err(anyhow!("Unexpected message ID: 0x{:04X}", msg_id));
70 }
71
72 let mut data = [0u8; 6];
74 self.port.read_exact(&mut data)?;
75
76 let counts = i32::from_le_bytes([data[2], data[3], data[4], data[5]]);
78
79 Ok(counts as f32 / self.counts_per_deg)
80 }
81}
82
83impl Motor for ThorlabsMotor {
84 fn get_position(&mut self) -> Result<f32> {
85 self.send_command(msg::MOT_REQ_POSN, self.channel, 0)?;
86 self.read_position_response()
87 }
88
89 fn start_rotation(&mut self, velocity: f32) -> Result<()> {
90 self.send_command(
94 msg::MOT_MOVE_VELOCITY,
95 self.channel,
96 if velocity >= 0.0 { 1 } else { 2 },
97 )
98 }
99
100 fn stop(&mut self) -> Result<()> {
101 self.send_command(msg::MOT_STOP_IMMEDIATE, self.channel, 0)
102 }
103}
104
105pub struct MockMotor {
107 start_time: Option<std::time::Instant>,
108 velocity: f32,
109}
110
111impl MockMotor {
112 pub fn new() -> Self {
113 Self {
114 start_time: None,
115 velocity: 0.0,
116 }
117 }
118}
119
120impl Default for MockMotor {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl Motor for MockMotor {
127 fn get_position(&mut self) -> Result<f32> {
128 if let Some(start) = self.start_time {
129 let elapsed = start.elapsed().as_secs_f32();
130 Ok((elapsed * self.velocity) % 360.0)
131 } else {
132 Ok(0.0)
133 }
134 }
135
136 fn start_rotation(&mut self, velocity: f32) -> Result<()> {
137 self.start_time = Some(std::time::Instant::now());
138 self.velocity = velocity;
139 Ok(())
140 }
141
142 fn stop(&mut self) -> Result<()> {
143 self.start_time = None;
144 Ok(())
145 }
146}
147
148pub struct ProjectionController<M: Motor + Send + 'static> {
150 pub motor: Arc<std::sync::Mutex<M>>,
151 _projections: ndarray::Array3<f32>, angles: Vec<f32>,
153 shared_frame_index: Arc<AtomicUsize>,
154}
155
156impl<M: Motor + Send + 'static> ProjectionController<M> {
157 pub fn new(motor: M, projections: ndarray::Array3<f32>, angles: Vec<f32>) -> Self {
158 Self {
159 motor: Arc::new(std::sync::Mutex::new(motor)),
160 _projections: projections,
161 angles,
162 shared_frame_index: Arc::new(AtomicUsize::new(0)),
163 }
164 }
165
166 pub fn shared_frame_index(&self) -> Arc<AtomicUsize> {
167 Arc::clone(&self.shared_frame_index)
168 }
169
170 pub fn get_frame_index(&self, current_angle: f32) -> usize {
172 let mut closest_idx = 0;
173 let mut min_diff = f32::MAX;
174
175 for (i, &angle) in self.angles.iter().enumerate() {
176 let diff = (angle - current_angle).abs();
177 let wrapped_diff = diff.min(360.0 - diff);
178 if wrapped_diff < min_diff {
179 min_diff = wrapped_diff;
180 closest_idx = i;
181 }
182 }
183 closest_idx
184 }
185
186 pub fn spawn_sync_thread(&self, stop_signal: Arc<AtomicBool>) {
188 let motor = Arc::clone(&self.motor);
189 let shared_idx = Arc::clone(&self.shared_frame_index);
190 let angles = self.angles.clone();
191
192 std::thread::spawn(move || {
193 while !stop_signal.load(Ordering::Relaxed) {
194 if let Ok(mut m) = motor.lock() {
195 if let Ok(pos) = m.get_position() {
196 let mut closest_idx = 0;
198 let mut min_diff = f32::MAX;
199 for (i, &angle) in angles.iter().enumerate() {
200 let diff = (angle - pos).abs();
201 let wrapped_diff = diff.min(360.0 - diff);
202 if wrapped_diff < min_diff {
203 min_diff = wrapped_diff;
204 closest_idx = i;
205 }
206 }
207 shared_idx.store(closest_idx, Ordering::Relaxed);
208 }
209 }
210 std::thread::sleep(Duration::from_millis(5)); }
212 });
213 }
214
215 pub fn poll_and_render(&mut self, window: &projection_window::ProjectionWindow) -> Result<()> {
216 let frame_idx = self.shared_frame_index.load(Ordering::Relaxed);
217 window.render(frame_idx)?;
218 Ok(())
219 }
220
221 pub fn run_sync_loop(&mut self) -> Result<()> {
222 {
223 let mut m = self
224 .motor
225 .lock()
226 .map_err(|_| anyhow!("Mutex lock failed"))?;
227 m.start_rotation(10.0)?; }
229
230 loop {
231 let frame_idx = self.shared_frame_index.load(Ordering::Relaxed);
232
233 println!("Frame Index: {}", frame_idx);
235
236 std::thread::sleep(Duration::from_millis(16)); }
238 }
239}