From 053ad4fb63626a0226383e0959dc1cc6cb7b8dbc Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 08:26:13 +0100 Subject: [PATCH 01/11] Break out serial interface to be used in other stages. Signed-off-by: Henner Zeller --- hostcontrol/src/main.rs | 3 +- hostcontrol/src/stage.rs | 84 ++++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index 4ccf95e..fd7a634 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -4,7 +4,8 @@ mod stage; fn main() { env_logger::init(); - let mut s = stage::Stage::new("/dev/ttyACM0", 1.12).unwrap(); + let stage_io = stage::StageIO::new("/dev/ttyACM0", 115200).unwrap(); + let mut s = stage::Stage::new(stage_io, 1.12).unwrap(); let origin = nalgebra::vector![3000.0, 0.0, -6000.0]; //println!("{}", s.version().unwrap()); diff --git a/hostcontrol/src/stage.rs b/hostcontrol/src/stage.rs index f853e6a..2f510b5 100644 --- a/hostcontrol/src/stage.rs +++ b/hostcontrol/src/stage.rs @@ -22,46 +22,47 @@ fn new_delta_into_cartesian(flex_h: f64, flex_a: f64, flex_b: f64) -> nalgebra:: ] } -pub struct Stage { +#[derive(Error, Debug)] +pub enum StageIOError { + #[error("could not connect to stage controller")] + Open(#[from] serialport::Error), + #[error("error when sending data to the stage controller")] + Write(std::io::Error), + #[error("error when receiving data from the stage controller")] + Read(std::io::Error), +} + +#[derive(Error, Debug)] +pub enum StageError { + #[error("Interface issue")] + Communication(#[from] StageIOError), + #[error("protocol error")] + Mumble, +} + +pub struct StageIO { serial: Box, +} + +pub struct Stage { + stage_io: StageIO, delta_pos: nalgebra::Vector3, cartesian_to_delta: nalgebra::Matrix3, delta_to_cartesian: nalgebra::Matrix3, } -#[derive(Error, Debug)] -pub enum StageError { - #[error("could not connect to stage controller")] - Open(serialport::Error), - #[error("error when sending data to the stage controller")] - Write(std::io::Error), - #[error("error when receiving data from the stage controller")] - Read(std::io::Error), - #[error("protocol error")] - Mumble, -} - -pub type Result = std::result::Result; - -impl Stage { - pub fn new(port: &str, camera_rotation: f64) -> Result { - // TODO: non-blocking moves - let serial = serialport::new(port, 115200).timeout(Duration::from_secs(60)).open().map_err(|e| StageError::Open(e))?; - let delta_to_cartesian = new_camera_rotation(camera_rotation).try_inverse().unwrap() * new_delta_into_cartesian(80.0, 50.0, 50.0); - let cartesian_to_delta = delta_to_cartesian.try_inverse().unwrap(); - let mut res = Self { serial, delta_pos: nalgebra::vector![0, 0, 0], cartesian_to_delta, delta_to_cartesian }; - - let pos = res.communicate("p?")?.split(" ").map(|v| v.parse::().map_err(|_| StageError::Mumble)).collect::, _>>()?; - res.delta_pos = nalgebra::vector![pos[0], pos[1], pos[2]]; - println!("Initialized stage at delta positions {}/{}/{}", pos[0], pos[1], pos[2]); - Ok(res) +pub type StageIOResult = std::result::Result; +impl StageIO { + pub fn new(port: &str, speed: u32) -> StageIOResult { + let serial = serialport::new(port, speed).timeout(Duration::from_secs(60)).open()?; + Ok(Self { serial }) } - fn receive_until>(&mut self, needle: S) -> Result { + fn receive_until>(&mut self, needle: S) -> StageIOResult { let mut res: Vec = vec![]; loop { let mut buf = [0u8; 1]; - self.serial.read_exact(&mut buf).map_err(|e| StageError::Read(e))?; + self.serial.read_exact(&mut buf).map_err(|e| StageIOError::Read(e))?; res.push(buf[0]); if res.ends_with(needle.as_ref().as_bytes()) { return Ok(String::from_utf8_lossy(&res).to_string()); @@ -69,11 +70,28 @@ impl Stage { } } - fn communicate>(&mut self, command: S) -> Result { - self.serial.write_all(command.as_ref().as_bytes()).map_err(|e| StageError::Write(e))?; + // Send request and wait for response deliminated with a newline + fn send_request>(&mut self, command: S) -> StageIOResult { + self.serial.write_all(command.as_ref().as_bytes()).map_err(|e| StageIOError::Write(e))?; let res = self.receive_until("\n")?; Ok(res.trim().to_string()) } +} + +pub type Result = std::result::Result; +impl Stage { + pub fn new(stage_io: StageIO, camera_rotation: f64) -> Result { + // TODO: non-blocking moves + let delta_to_cartesian = new_camera_rotation(camera_rotation).try_inverse().unwrap() * new_delta_into_cartesian(80.0, 50.0, 50.0); + let cartesian_to_delta = delta_to_cartesian.try_inverse().unwrap(); + let mut res = Self { stage_io, delta_pos: nalgebra::vector![0, 0, 0], cartesian_to_delta, delta_to_cartesian }; + + let pos = res.stage_io.send_request("p?")?.split(" ").map(|v| v.parse::().map_err(|_| StageError::Mumble)).collect::, _>>()?; + res.delta_pos = nalgebra::vector![pos[0], pos[1], pos[2]]; + println!("Initialized stage at delta positions {}/{}/{}", pos[0], pos[1], pos[2]); + Ok(res) + } + pub fn get_position_delta(&self) -> nalgebra::Vector3 { self.delta_pos.clone() @@ -85,7 +103,7 @@ impl Stage { } pub fn move_relative_delta(&mut self, d: nalgebra::Vector3) -> Result<()> { - self.communicate(format!("mr {} {} {}\n", d[0], d[1], d[2]))?; + self.stage_io.send_request(format!("mr {} {} {}\n", d[0], d[1], d[2]))?; self.delta_pos += nalgebra::vector![d[0], d[1], d[2]]; Ok(()) } @@ -121,6 +139,6 @@ impl Stage { } pub fn version(&mut self) -> Result { - self.communicate("version\n") + Ok(self.stage_io.send_request("version\n")?) } } From f65cc9236bb922312dcdd9552571407a1cc29317 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 08:32:44 +0100 Subject: [PATCH 02/11] Add debug log support for serial interface. This shows the chatter on the serial interface if debug logging is enabled. ``` RUST_LOG=debug target/debug/hostcontrol ``` Signed-off-by: Henner Zeller --- hostcontrol/Cargo.lock | 5 +++-- hostcontrol/Cargo.toml | 1 + hostcontrol/src/stage.rs | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/hostcontrol/Cargo.lock b/hostcontrol/Cargo.lock index dc422b4..54a9403 100644 --- a/hostcontrol/Cargo.lock +++ b/hostcontrol/Cargo.lock @@ -1446,6 +1446,7 @@ dependencies = [ "eframe", "egui", "env_logger", + "log", "nalgebra", "nalgebra-macros", "serialport", @@ -1759,9 +1760,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mach2" diff --git a/hostcontrol/Cargo.toml b/hostcontrol/Cargo.toml index f83ff16..16fa0d7 100644 --- a/hostcontrol/Cargo.toml +++ b/hostcontrol/Cargo.toml @@ -11,3 +11,4 @@ nalgebra-macros = "^0.2.2" env_logger = "0.11.8" egui = "0.31.1" eframe = "0.31.1" +log = "0.4.29" diff --git a/hostcontrol/src/stage.rs b/hostcontrol/src/stage.rs index 2f510b5..3d9fefb 100644 --- a/hostcontrol/src/stage.rs +++ b/hostcontrol/src/stage.rs @@ -2,6 +2,7 @@ use std::time::Duration; use thiserror::Error; +use log::debug; fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { nalgebra::matrix![ @@ -55,6 +56,8 @@ pub type StageIOResult = std::result::Result; impl StageIO { pub fn new(port: &str, speed: u32) -> StageIOResult { let serial = serialport::new(port, speed).timeout(Duration::from_secs(60)).open()?; + // TODO: read and discard initial characters; some devices like to be + // chatty on start-up. Ok(Self { serial }) } @@ -71,10 +74,12 @@ impl StageIO { } // Send request and wait for response deliminated with a newline - fn send_request>(&mut self, command: S) -> StageIOResult { + pub fn send_request>(&mut self, command: S) -> StageIOResult { + debug!("->: {:?}", command.as_ref()); self.serial.write_all(command.as_ref().as_bytes()).map_err(|e| StageIOError::Write(e))?; - let res = self.receive_until("\n")?; - Ok(res.trim().to_string()) + let response = self.receive_until("\n")?; + debug!("<-: {:?}", response); + Ok(response.trim().to_string()) } } From 7d8337bd5e4cd0a8715e506b05fdb7763be1e55d Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 08:35:36 +0100 Subject: [PATCH 03/11] Add cmdline-flag handling. For now, just interface settings for the stage control: ``` target/release/hostcontrol --help Usage: hostcontrol [OPTIONS] Options: --stage-device Interface to talk to movement stage [default: /dev/ttyACM0] --tty-speed Speed of the stage device (bps) [default: 115200] -h, --help Print help -V, --version Print version ``` Signed-off-by: Henner Zeller --- hostcontrol/Cargo.lock | 47 +++++++++++++++++++++++++++++++++++++++++ hostcontrol/Cargo.toml | 1 + hostcontrol/src/main.rs | 16 +++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/hostcontrol/Cargo.lock b/hostcontrol/Cargo.lock index 54a9403..0bf3de0 100644 --- a/hostcontrol/Cargo.lock +++ b/hostcontrol/Cargo.lock @@ -656,6 +656,46 @@ dependencies = [ "libc", ] +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -1443,6 +1483,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" name = "hostcontrol" version = "0.1.0" dependencies = [ + "clap", "eframe", "egui", "env_logger", @@ -2936,6 +2977,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" diff --git a/hostcontrol/Cargo.toml b/hostcontrol/Cargo.toml index 16fa0d7..e4099bf 100644 --- a/hostcontrol/Cargo.toml +++ b/hostcontrol/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.0", features = ["derive"] } nalgebra = { version = "0.33" } serialport = { version = "4", default-features = false } thiserror = "2" diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index fd7a634..ce9d0c1 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,10 +1,24 @@ use std::{thread::sleep, time::Duration}; +use clap::Parser; mod stage; +#[derive(clap::Parser, Debug)] +#[command(version, about, long_about = None)] +struct CliArgs { + /// Interface to talk to movement stage + #[arg(long, default_value="/dev/ttyACM0")] + stage_device: String, + + /// Speed of the stage device (bps) + #[arg(long, default_value="115200")] + tty_speed: u32, +} + fn main() { + let args = CliArgs::parse(); env_logger::init(); - let stage_io = stage::StageIO::new("/dev/ttyACM0", 115200).unwrap(); + let stage_io = stage::StageIO::new(&args.stage_device, args.tty_speed).unwrap(); let mut s = stage::Stage::new(stage_io, 1.12).unwrap(); let origin = nalgebra::vector![3000.0, 0.0, -6000.0]; //println!("{}", s.version().unwrap()); From 4e946eef776bf8f3e587bfda7866c2c6159f7105 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 08:37:46 +0100 Subject: [PATCH 04/11] Run `cargo fmt` ... to have canonical formatting. Signed-off-by: Henner Zeller --- hostcontrol/src/main.rs | 9 +++---- hostcontrol/src/stage.rs | 58 ++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index ce9d0c1..7d525f5 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,5 +1,5 @@ -use std::{thread::sleep, time::Duration}; use clap::Parser; +use std::{thread::sleep, time::Duration}; mod stage; @@ -7,11 +7,11 @@ mod stage; #[command(version, about, long_about = None)] struct CliArgs { /// Interface to talk to movement stage - #[arg(long, default_value="/dev/ttyACM0")] + #[arg(long, default_value = "/dev/ttyACM0")] stage_device: String, /// Speed of the stage device (bps) - #[arg(long, default_value="115200")] + #[arg(long, default_value = "115200")] tty_speed: u32, } @@ -43,8 +43,7 @@ fn main() { //Ok(()) } -struct App { -} +struct App {} impl Default for App { fn default() -> Self { diff --git a/hostcontrol/src/stage.rs b/hostcontrol/src/stage.rs index 3d9fefb..8a4901b 100644 --- a/hostcontrol/src/stage.rs +++ b/hostcontrol/src/stage.rs @@ -1,8 +1,8 @@ //! Stage controller for openflexure-delta like stage controllers. +use log::debug; use std::time::Duration; use thiserror::Error; -use log::debug; fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { nalgebra::matrix![ @@ -15,7 +15,7 @@ fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { fn new_delta_into_cartesian(flex_h: f64, flex_a: f64, flex_b: f64) -> nalgebra::Matrix3 { let x_fac = (2.0 / 3.0f64.sqrt()) * (flex_b / flex_h); let y_fac = -1.0 * flex_b / flex_h; - let z_fac = 1.0/3.0 * flex_b / flex_a; + let z_fac = 1.0 / 3.0 * flex_b / flex_a; nalgebra::matrix![ -x_fac, x_fac, 0.0; 0.5 * y_fac, 0.5 * y_fac, -y_fac; @@ -55,17 +55,21 @@ pub struct Stage { pub type StageIOResult = std::result::Result; impl StageIO { pub fn new(port: &str, speed: u32) -> StageIOResult { - let serial = serialport::new(port, speed).timeout(Duration::from_secs(60)).open()?; - // TODO: read and discard initial characters; some devices like to be - // chatty on start-up. - Ok(Self { serial }) + let serial = serialport::new(port, speed) + .timeout(Duration::from_secs(60)) + .open()?; + // TODO: read and discard initial characters; some devices like to be + // chatty on start-up. + Ok(Self { serial }) } fn receive_until>(&mut self, needle: S) -> StageIOResult { let mut res: Vec = vec![]; loop { let mut buf = [0u8; 1]; - self.serial.read_exact(&mut buf).map_err(|e| StageIOError::Read(e))?; + self.serial + .read_exact(&mut buf) + .map_err(|e| StageIOError::Read(e))?; res.push(buf[0]); if res.ends_with(needle.as_ref().as_bytes()) { return Ok(String::from_utf8_lossy(&res).to_string()); @@ -75,10 +79,12 @@ impl StageIO { // Send request and wait for response deliminated with a newline pub fn send_request>(&mut self, command: S) -> StageIOResult { - debug!("->: {:?}", command.as_ref()); - self.serial.write_all(command.as_ref().as_bytes()).map_err(|e| StageIOError::Write(e))?; + debug!("->: {:?}", command.as_ref()); + self.serial + .write_all(command.as_ref().as_bytes()) + .map_err(|e| StageIOError::Write(e))?; let response = self.receive_until("\n")?; - debug!("<-: {:?}", response); + debug!("<-: {:?}", response); Ok(response.trim().to_string()) } } @@ -87,28 +93,46 @@ pub type Result = std::result::Result; impl Stage { pub fn new(stage_io: StageIO, camera_rotation: f64) -> Result { // TODO: non-blocking moves - let delta_to_cartesian = new_camera_rotation(camera_rotation).try_inverse().unwrap() * new_delta_into_cartesian(80.0, 50.0, 50.0); + let delta_to_cartesian = new_camera_rotation(camera_rotation).try_inverse().unwrap() + * new_delta_into_cartesian(80.0, 50.0, 50.0); let cartesian_to_delta = delta_to_cartesian.try_inverse().unwrap(); - let mut res = Self { stage_io, delta_pos: nalgebra::vector![0, 0, 0], cartesian_to_delta, delta_to_cartesian }; + let mut res = Self { + stage_io, + delta_pos: nalgebra::vector![0, 0, 0], + cartesian_to_delta, + delta_to_cartesian, + }; - let pos = res.stage_io.send_request("p?")?.split(" ").map(|v| v.parse::().map_err(|_| StageError::Mumble)).collect::, _>>()?; + let pos = res + .stage_io + .send_request("p?")? + .split(" ") + .map(|v| v.parse::().map_err(|_| StageError::Mumble)) + .collect::, _>>()?; res.delta_pos = nalgebra::vector![pos[0], pos[1], pos[2]]; - println!("Initialized stage at delta positions {}/{}/{}", pos[0], pos[1], pos[2]); + println!( + "Initialized stage at delta positions {}/{}/{}", + pos[0], pos[1], pos[2] + ); Ok(res) } - pub fn get_position_delta(&self) -> nalgebra::Vector3 { self.delta_pos.clone() } pub fn get_position_cartesian(&self) -> nalgebra::Vector3 { - let pos = nalgebra::vector![self.delta_pos[0] as f64, self.delta_pos[1] as f64, self.delta_pos[2] as f64]; + let pos = nalgebra::vector![ + self.delta_pos[0] as f64, + self.delta_pos[1] as f64, + self.delta_pos[2] as f64 + ]; self.delta_to_cartesian * pos } pub fn move_relative_delta(&mut self, d: nalgebra::Vector3) -> Result<()> { - self.stage_io.send_request(format!("mr {} {} {}\n", d[0], d[1], d[2]))?; + self.stage_io + .send_request(format!("mr {} {} {}\n", d[0], d[1], d[2]))?; self.delta_pos += nalgebra::vector![d[0], d[1], d[2]]; Ok(()) } From cc8152a75df0ec7614d3fd4a7618d30c826ce596 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 12:48:39 +0100 Subject: [PATCH 05/11] Break out stage IO into separate file. Signed-off-by: Henner Zeller --- hostcontrol/src/main.rs | 5 +++- hostcontrol/src/stage.rs | 54 +---------------------------------- hostcontrol/src/stage_io.rs | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 hostcontrol/src/stage_io.rs diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index 7d525f5..4b98db6 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -3,6 +3,9 @@ use std::{thread::sleep, time::Duration}; mod stage; +mod stage_io; +use crate::stage_io::StageIO; + #[derive(clap::Parser, Debug)] #[command(version, about, long_about = None)] struct CliArgs { @@ -18,7 +21,7 @@ struct CliArgs { fn main() { let args = CliArgs::parse(); env_logger::init(); - let stage_io = stage::StageIO::new(&args.stage_device, args.tty_speed).unwrap(); + let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap(); let mut s = stage::Stage::new(stage_io, 1.12).unwrap(); let origin = nalgebra::vector![3000.0, 0.0, -6000.0]; //println!("{}", s.version().unwrap()); diff --git a/hostcontrol/src/stage.rs b/hostcontrol/src/stage.rs index 8a4901b..1c4060a 100644 --- a/hostcontrol/src/stage.rs +++ b/hostcontrol/src/stage.rs @@ -1,7 +1,6 @@ //! Stage controller for openflexure-delta like stage controllers. -use log::debug; -use std::time::Duration; +use crate::stage_io::{StageIO, StageIOError}; use thiserror::Error; fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { @@ -23,16 +22,6 @@ fn new_delta_into_cartesian(flex_h: f64, flex_a: f64, flex_b: f64) -> nalgebra:: ] } -#[derive(Error, Debug)] -pub enum StageIOError { - #[error("could not connect to stage controller")] - Open(#[from] serialport::Error), - #[error("error when sending data to the stage controller")] - Write(std::io::Error), - #[error("error when receiving data from the stage controller")] - Read(std::io::Error), -} - #[derive(Error, Debug)] pub enum StageError { #[error("Interface issue")] @@ -41,10 +30,6 @@ pub enum StageError { Mumble, } -pub struct StageIO { - serial: Box, -} - pub struct Stage { stage_io: StageIO, delta_pos: nalgebra::Vector3, @@ -52,43 +37,6 @@ pub struct Stage { delta_to_cartesian: nalgebra::Matrix3, } -pub type StageIOResult = std::result::Result; -impl StageIO { - pub fn new(port: &str, speed: u32) -> StageIOResult { - let serial = serialport::new(port, speed) - .timeout(Duration::from_secs(60)) - .open()?; - // TODO: read and discard initial characters; some devices like to be - // chatty on start-up. - Ok(Self { serial }) - } - - fn receive_until>(&mut self, needle: S) -> StageIOResult { - let mut res: Vec = vec![]; - loop { - let mut buf = [0u8; 1]; - self.serial - .read_exact(&mut buf) - .map_err(|e| StageIOError::Read(e))?; - res.push(buf[0]); - if res.ends_with(needle.as_ref().as_bytes()) { - return Ok(String::from_utf8_lossy(&res).to_string()); - } - } - } - - // Send request and wait for response deliminated with a newline - pub fn send_request>(&mut self, command: S) -> StageIOResult { - debug!("->: {:?}", command.as_ref()); - self.serial - .write_all(command.as_ref().as_bytes()) - .map_err(|e| StageIOError::Write(e))?; - let response = self.receive_until("\n")?; - debug!("<-: {:?}", response); - Ok(response.trim().to_string()) - } -} - pub type Result = std::result::Result; impl Stage { pub fn new(stage_io: StageIO, camera_rotation: f64) -> Result { diff --git a/hostcontrol/src/stage_io.rs b/hostcontrol/src/stage_io.rs new file mode 100644 index 0000000..d0cec47 --- /dev/null +++ b/hostcontrol/src/stage_io.rs @@ -0,0 +1,56 @@ +use log::debug; +use std::time::Duration; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StageIOError { + #[error("could not connect to stage controller")] + Open(#[from] serialport::Error), + #[error("error when sending data to the stage controller")] + Write(std::io::Error), + #[error("error when receiving data from the stage controller")] + Read(std::io::Error), +} + +pub struct StageIO { + serial: Box, +} + +pub type StageIOResult = std::result::Result; +impl StageIO { + /// Create a new serial StageIO at given serial device and speed. + pub fn new(port: &str, speed: u32) -> StageIOResult { + let serial = serialport::new(port, speed) + .timeout(Duration::from_secs(60)) + .open()?; + // TODO: read and discard initial characters; some devices like to be + // chatty on start-up. + Ok(Self { serial }) + } + + /// Receive data until the given character string "needle" is seen. + fn receive_until>(&mut self, needle: S) -> StageIOResult { + let mut res: Vec = vec![]; + loop { + let mut buf = [0u8; 1]; + self.serial + .read_exact(&mut buf) + .map_err(|e| StageIOError::Read(e))?; + res.push(buf[0]); + if res.ends_with(needle.as_ref().as_bytes()) { + return Ok(String::from_utf8_lossy(&res).to_string()); + } + } + } + + /// Send request and wait for response deliminated with a newline + pub fn send_request>(&mut self, command: S) -> StageIOResult { + debug!("->: {:?}", command.as_ref()); + self.serial + .write_all(command.as_ref().as_bytes()) + .map_err(|e| StageIOError::Write(e))?; + let response = self.receive_until("\n")?; + debug!("<-: {:?}", response); + Ok(response.trim().to_string()) + } +} From 4f620ad899b3f601c2727fbccb9a2cdee25d92d9 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 13:24:25 +0100 Subject: [PATCH 06/11] Make a XYStage trait, and have OpenFlexureStage implement it. This way, we can have multiple implementations of a stage that moves in XY direction. Signed-off-by: Henner Zeller --- hostcontrol/src/main.rs | 53 ++++--------------- .../src/{stage.rs => openflexure_stage.rs} | 40 +++++++------- hostcontrol/src/xy_stage.rs | 21 ++++++++ 3 files changed, 50 insertions(+), 64 deletions(-) rename hostcontrol/src/{stage.rs => openflexure_stage.rs} (89%) create mode 100644 hostcontrol/src/xy_stage.rs diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index 4b98db6..4004786 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,10 +1,13 @@ use clap::Parser; use std::{thread::sleep, time::Duration}; -mod stage; +mod openflexure_stage; mod stage_io; use crate::stage_io::StageIO; +use crate::xy_stage::XYStage; + +mod xy_stage; #[derive(clap::Parser, Debug)] #[command(version, about, long_about = None)] @@ -22,28 +25,14 @@ fn main() { let args = CliArgs::parse(); env_logger::init(); let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap(); - let mut s = stage::Stage::new(stage_io, 1.12).unwrap(); - let origin = nalgebra::vector![3000.0, 0.0, -6000.0]; - //println!("{}", s.version().unwrap()); - - //let a = nalgebra::vector![-10000.0, 0.0, -1250.0]; - //let b = nalgebra::vector![18000.0, -15000.0, -1600.0]; - //let c = nalgebra::vector![0.0, -15000.0, -250.0]; - + let mut s = openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap(); + let origin = nalgebra::vector![0.0, 0.0, 0.0]; s.move_absolute_cartesian(origin).unwrap(); + sleep(Duration::from_secs(1)); + let mut max_xy = s.get_range(); + max_xy[2] = 0.0; + s.move_absolute_cartesian(max_xy).unwrap(); sleep(Duration::from_secs(5)); - //s.move_absolute_cartesian(origin + a).unwrap(); - ////sleep(Duration::from_secs(5)); - ////s.move_absolute_cartesian(origin).unwrap(); - ////s.move_absolute_cartesian(origin + c).unwrap(); - ////sleep(Duration::from_secs(5)); - ////s.move_absolute_cartesian(origin + nalgebra::vector![0.0, -16000.0, -500.0]).unwrap(); - ////println!("D"); - ////sleep(Duration::from_secs(5)); - ////s.move_absolute_cartesian(origin + nalgebra::vector![0.0, 0.0, 0.0]).unwrap(); - //println!("{:?}", s.get_position_cartesian()); - - //Ok(()) } struct App {} @@ -61,25 +50,3 @@ impl eframe::App for App { }); } } - -// -// let mut s = stage::Stage::new("/dev/ttyACM0", 1.12).unwrap(); -// let origin = nalgebra::vector![3000.0, 0.0, -6000.0]; -// println!("{}", s.version().unwrap()); -// -// let a = nalgebra::vector![18000.0, 0.0, -1250.0]; -// let b = nalgebra::vector![18000.0, -15000.0, -1600.0]; -// let c = nalgebra::vector![0.0, -15000.0, -250.0]; -// -// s.move_absolute_cartesian(origin).unwrap(); -// //sleep(Duration::from_secs(5)); -// //s.move_absolute_cartesian(origin + a).unwrap(); -// //sleep(Duration::from_secs(5)); -// //s.move_absolute_cartesian(origin + c).unwrap(); -// //sleep(Duration::from_secs(5)); -// //s.move_absolute_cartesian(origin + nalgebra::vector![0.0, -16000.0, -500.0]).unwrap(); -// //println!("D"); -// //sleep(Duration::from_secs(5)); -// //s.move_absolute_cartesian(origin + nalgebra::vector![0.0, 0.0, 0.0]).unwrap(); -// println!("{:?}", s.get_position_cartesian()); -//} diff --git a/hostcontrol/src/stage.rs b/hostcontrol/src/openflexure_stage.rs similarity index 89% rename from hostcontrol/src/stage.rs rename to hostcontrol/src/openflexure_stage.rs index 1c4060a..d5b3ddc 100644 --- a/hostcontrol/src/stage.rs +++ b/hostcontrol/src/openflexure_stage.rs @@ -1,7 +1,7 @@ //! Stage controller for openflexure-delta like stage controllers. -use crate::stage_io::{StageIO, StageIOError}; -use thiserror::Error; +use crate::stage_io::StageIO; +use crate::xy_stage::{XYStage, StageError,Result}; fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { nalgebra::matrix![ @@ -22,23 +22,14 @@ fn new_delta_into_cartesian(flex_h: f64, flex_a: f64, flex_b: f64) -> nalgebra:: ] } -#[derive(Error, Debug)] -pub enum StageError { - #[error("Interface issue")] - Communication(#[from] StageIOError), - #[error("protocol error")] - Mumble, -} - -pub struct Stage { +pub struct OpenFlexureStage { stage_io: StageIO, delta_pos: nalgebra::Vector3, cartesian_to_delta: nalgebra::Matrix3, delta_to_cartesian: nalgebra::Matrix3, } -pub type Result = std::result::Result; -impl Stage { +impl OpenFlexureStage { pub fn new(stage_io: StageIO, camera_rotation: f64) -> Result { // TODO: non-blocking moves let delta_to_cartesian = new_camera_rotation(camera_rotation).try_inverse().unwrap() @@ -90,7 +81,19 @@ impl Stage { self.move_relative_delta(diff) } - pub fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3) -> Result<()> { + pub fn move_relative_cartesian(&mut self, d: nalgebra::Vector3) -> Result<()> { + let abs = self.get_position_cartesian() + d; + self.move_absolute_cartesian(abs) + } + + pub fn version(&mut self) -> Result { + Ok(self.stage_io.send_request("version\n")?) + } +} + +impl XYStage for OpenFlexureStage { + fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3) -> Result<()> { + // TODO: check range with get_movement_box() let current = self.get_position_delta(); let target = self.cartesian_to_delta * a; let target = nalgebra::vector![target[0] as i32, target[1] as i32, target[2] as i32]; @@ -110,12 +113,7 @@ impl Stage { self.move_absolute_delta(target) } - pub fn move_relative_cartesian(&mut self, d: nalgebra::Vector3) -> Result<()> { - let abs = self.get_position_cartesian() + d; - self.move_absolute_cartesian(abs) - } - - pub fn version(&mut self) -> Result { - Ok(self.stage_io.send_request("version\n")?) + fn get_range(&self) -> nalgebra::Vector3 { + nalgebra::vector![10000.0, 10000.0, 5000.0] } } diff --git a/hostcontrol/src/xy_stage.rs b/hostcontrol/src/xy_stage.rs new file mode 100644 index 0000000..8a22179 --- /dev/null +++ b/hostcontrol/src/xy_stage.rs @@ -0,0 +1,21 @@ + +use crate::stage_io::StageIOError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StageError { + #[error("Interface issue")] + Communication(#[from] StageIOError), + #[error("protocol error")] + Mumble, +} + +pub type Result = std::result::Result; +pub trait XYStage { + /// Move to absolute coordinates (x,y,z) in micrometers. + fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3) -> Result<()>; + + /// Get range of valid cartesian coordinates box. For now, assuming one + /// corner is fixed at (0,0,0), the other remote corner is this range. + fn get_range(&self) -> nalgebra::Vector3; +} From ad9b1cb8c285415aca95058e2a76eb94f0d1929b Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 14:04:15 +0100 Subject: [PATCH 07/11] Implement simple GCode stage. Easy experimenting with 3D printer or other CNC if open flexure is not available. Signed-off-by: Henner Zeller --- hostcontrol/src/gcode_stage.rs | 45 ++++++++++++++++++++++++++++ hostcontrol/src/main.rs | 29 ++++++++++++++---- hostcontrol/src/openflexure_stage.rs | 6 ++-- hostcontrol/src/xy_stage.rs | 1 - 4 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 hostcontrol/src/gcode_stage.rs diff --git a/hostcontrol/src/gcode_stage.rs b/hostcontrol/src/gcode_stage.rs new file mode 100644 index 0000000..28d5250 --- /dev/null +++ b/hostcontrol/src/gcode_stage.rs @@ -0,0 +1,45 @@ +use crate::stage_io::StageIO; +use crate::xy_stage::{Result, XYStage}; + +pub struct GCodeStage { + stage_io: StageIO, + max_range_μm: nalgebra::Vector3, +} + +impl GCodeStage { + pub fn new(stage_io: StageIO) -> Result { + let mut res = Self { + stage_io, + // TODO: determine max range with some M-code ? + max_range_μm: nalgebra::vector![100.0 * 1000.0, 100.0 * 1000.0, 50.0 * 1000.0], + }; + res.stage_io.send_request("G28\n")?; + Ok(res) + } +} + +impl XYStage for GCodeStage { + /// Move to absolute coordinates (x,y,z) in micrometers. + fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3) -> Result<()> { + // TODO: check range + self.stage_io.send_request(format!( + "G1 X{} Y{} Z{}\n", + a[0] / 1000.0, + a[1] / 1000.0, + a[2] / 1000.0 + ))?; + Ok(()) + } + + /// Get range of valid cartesian coordinates box. For now, assuming one + /// corner is fixed at (0,0,0), the other remote corner is this range. + fn get_range(&self) -> nalgebra::Vector3 { + self.max_range_μm + } +} + +impl Drop for GCodeStage { + fn drop(&mut self) { + self.stage_io.send_request("M84\n").unwrap(); // switch off motors. + } +} diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index 4004786..a3f97a8 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,6 +1,7 @@ -use clap::Parser; +use clap::{Parser, ValueEnum}; use std::{thread::sleep, time::Duration}; +mod gcode_stage; mod openflexure_stage; mod stage_io; @@ -19,19 +20,37 @@ struct CliArgs { /// Speed of the stage device (bps) #[arg(long, default_value = "115200")] tty_speed: u32, + + #[arg(long, value_enum)] + backend: Backend, +} + +// [derive(DebugCopy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Debug, Clone, ValueEnum)] +enum Backend { + /// OpenFlexure stage + OpenFlexure, + + /// GCode stage + GCode, } fn main() { let args = CliArgs::parse(); env_logger::init(); let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap(); - let mut s = openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap(); + let mut stage: Box = match args.backend { + Backend::GCode => Box::new(gcode_stage::GCodeStage::new(stage_io).unwrap()), + Backend::OpenFlexure => { + Box::new(openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap()) + } + }; let origin = nalgebra::vector![0.0, 0.0, 0.0]; - s.move_absolute_cartesian(origin).unwrap(); + stage.move_absolute_cartesian(origin).unwrap(); sleep(Duration::from_secs(1)); - let mut max_xy = s.get_range(); + let mut max_xy = stage.get_range(); max_xy[2] = 0.0; - s.move_absolute_cartesian(max_xy).unwrap(); + stage.move_absolute_cartesian(max_xy).unwrap(); sleep(Duration::from_secs(5)); } diff --git a/hostcontrol/src/openflexure_stage.rs b/hostcontrol/src/openflexure_stage.rs index d5b3ddc..2c98cda 100644 --- a/hostcontrol/src/openflexure_stage.rs +++ b/hostcontrol/src/openflexure_stage.rs @@ -1,7 +1,7 @@ //! Stage controller for openflexure-delta like stage controllers. use crate::stage_io::StageIO; -use crate::xy_stage::{XYStage, StageError,Result}; +use crate::xy_stage::{Result, StageError, XYStage}; fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3 { nalgebra::matrix![ @@ -93,7 +93,7 @@ impl OpenFlexureStage { impl XYStage for OpenFlexureStage { fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3) -> Result<()> { - // TODO: check range with get_movement_box() + // TODO: check range with get_movement_box() let current = self.get_position_delta(); let target = self.cartesian_to_delta * a; let target = nalgebra::vector![target[0] as i32, target[1] as i32, target[2] as i32]; @@ -114,6 +114,6 @@ impl XYStage for OpenFlexureStage { } fn get_range(&self) -> nalgebra::Vector3 { - nalgebra::vector![10000.0, 10000.0, 5000.0] + nalgebra::vector![10000.0, 10000.0, 5000.0] } } diff --git a/hostcontrol/src/xy_stage.rs b/hostcontrol/src/xy_stage.rs index 8a22179..1f645c3 100644 --- a/hostcontrol/src/xy_stage.rs +++ b/hostcontrol/src/xy_stage.rs @@ -1,4 +1,3 @@ - use crate::stage_io::StageIOError; use thiserror::Error; From b130450019bc23a51c53a0599fb1b5a6b8324292 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 14:12:02 +0100 Subject: [PATCH 08/11] Smallish cleanup. Signed-off-by: Henner Zeller --- hostcontrol/src/main.rs | 7 +------ hostcontrol/src/stage_io.rs | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index a3f97a8..4b1eb15 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -54,14 +54,9 @@ fn main() { sleep(Duration::from_secs(5)); } +#[derive(Default)] struct App {} -impl Default for App { - fn default() -> Self { - App {} - } -} - impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { diff --git a/hostcontrol/src/stage_io.rs b/hostcontrol/src/stage_io.rs index d0cec47..1ffca22 100644 --- a/hostcontrol/src/stage_io.rs +++ b/hostcontrol/src/stage_io.rs @@ -35,7 +35,7 @@ impl StageIO { let mut buf = [0u8; 1]; self.serial .read_exact(&mut buf) - .map_err(|e| StageIOError::Read(e))?; + .map_err(StageIOError::Read)?; res.push(buf[0]); if res.ends_with(needle.as_ref().as_bytes()) { return Ok(String::from_utf8_lossy(&res).to_string()); @@ -48,7 +48,7 @@ impl StageIO { debug!("->: {:?}", command.as_ref()); self.serial .write_all(command.as_ref().as_bytes()) - .map_err(|e| StageIOError::Write(e))?; + .map_err(StageIOError::Write)?; let response = self.receive_until("\n")?; debug!("<-: {:?}", response); Ok(response.trim().to_string()) From a6077d755e30f909e7462e42a909e35504449fc0 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 20:36:44 +0100 Subject: [PATCH 09/11] Add simple camera-capture with nokhwa. There might be better camera capture crates, this is just the first pick for now. Signed-off-by: Henner Zeller --- hostcontrol/.gitignore | 4 + hostcontrol/Cargo.lock | 649 +++++++++++++++++++++++++++++++-- hostcontrol/Cargo.toml | 3 + hostcontrol/shell.nix | 3 + hostcontrol/src/camera.rs | 32 ++ hostcontrol/src/gcode_stage.rs | 1 + hostcontrol/src/main.rs | 50 ++- 7 files changed, 696 insertions(+), 46 deletions(-) create mode 100644 hostcontrol/src/camera.rs diff --git a/hostcontrol/.gitignore b/hostcontrol/.gitignore index ea8c4bf..bba7001 100644 --- a/hostcontrol/.gitignore +++ b/hostcontrol/.gitignore @@ -1 +1,5 @@ /target +.envrc +.direnv/ +*~ + diff --git a/hostcontrol/Cargo.lock b/hostcontrol/Cargo.lock index 0bf3de0..8fc7caa 100644 --- a/hostcontrol/Cargo.lock +++ b/hostcontrol/Cargo.lock @@ -92,8 +92,8 @@ dependencies = [ "hashbrown", "paste", "static_assertions", - "windows", - "windows-core", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] @@ -122,7 +122,7 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "getrandom 0.3.3", "once_cell", "version_check", @@ -224,6 +224,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "approx" version = "0.5.1" @@ -336,7 +342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" dependencies = [ "async-lock", - "cfg-if", + "cfg-if 1.0.1", "concurrent-queue", "futures-io", "futures-lite", @@ -371,7 +377,7 @@ dependencies = [ "async-signal", "async-task", "blocking", - "cfg-if", + "cfg-if 1.0.1", "event-listener", "futures-lite", "rustix 1.0.7", @@ -398,7 +404,7 @@ dependencies = [ "async-io", "async-lock", "atomic-waker", - "cfg-if", + "cfg-if 1.0.1", "futures-core", "futures-io", "rustix 1.0.7", @@ -487,6 +493,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -635,6 +664,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.1" @@ -656,6 +700,17 @@ dependencies = [ "libc", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.60" @@ -705,6 +760,34 @@ dependencies = [ "error-code", ] +[[package]] +name = "cocoa" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.7.0", + "core-graphics 0.19.2", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.9.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -740,13 +823,23 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] @@ -756,16 +849,34 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.7.0", + "foreign-types 0.3.2", + "libc", +] + [[package]] name = "core-graphics" version = "0.23.2" @@ -774,8 +885,8 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", "libc", ] @@ -790,6 +901,42 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-media-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273bf3fc5bf51fd06a7766a84788c1540b6527130a0bce39e00567d6ab9f31f1" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "metal 0.18.0", + "objc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -805,7 +952,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", ] [[package]] @@ -897,6 +1044,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecolor" version = "0.31.1" @@ -1017,6 +1170,12 @@ dependencies = [ "winit", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "emath" version = "0.31.1" @@ -1168,12 +1327,33 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1181,7 +1361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1195,6 +1375,12 @@ dependencies = [ "syn", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1301,9 +1487,11 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1312,7 +1500,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", @@ -1329,6 +1517,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "glow" version = "0.16.0" @@ -1479,17 +1673,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "hostcontrol" version = "0.1.0" dependencies = [ + "anyhow", "clap", "eframe", "egui", "env_logger", + "image", "log", "nalgebra", "nalgebra-macros", + "nokhwa", "serialport", "thiserror 2.0.12", ] @@ -1612,6 +1818,8 @@ dependencies = [ "num-traits", "png", "tiff", + "zune-core", + "zune-jpeg", ] [[package]] @@ -1639,7 +1847,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "mach2", ] @@ -1680,7 +1888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if", + "cfg-if 1.0.1", "combine", "jni-sys", "log", @@ -1738,6 +1946,18 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.174" @@ -1750,7 +1970,7 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "windows-targets 0.52.6", ] @@ -1857,6 +2077,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa", + "core-graphics 0.19.2", + "foreign-types 0.3.2", + "log", + "objc", +] + [[package]] name = "metal" version = "0.31.0" @@ -1865,13 +2100,19 @@ checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.9.1", "block", - "core-graphics-types", - "foreign-types", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", "log", "objc", "paste", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1882,6 +2123,31 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mozjpeg" +version = "0.10.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7891b80aaa86097d38d276eb98b3805d6280708c4e0a1e6f6aed9380c51fec9" +dependencies = [ + "arrayvec", + "bytemuck", + "libc", + "mozjpeg-sys", + "rgb", +] + +[[package]] +name = "mozjpeg-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0dc668bf9bf888c88e2fb1ab16a406d2c380f1d082b20d51dd540ab2aa70c1" +dependencies = [ + "cc", + "dunce", + "libc", + "nasm-rs", +] + [[package]] name = "naga" version = "24.0.0" @@ -1931,6 +2197,25 @@ dependencies = [ "syn", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "nasm-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" +dependencies = [ + "jobserver", + "log", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1977,7 +2262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.1", "libc", ] @@ -1988,7 +2273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.9.1", - "cfg-if", + "cfg-if 1.0.1", "cfg_aliases", "libc", "memoffset", @@ -2000,6 +2285,82 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nokhwa" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cae50786bfa1214ed441f98addbea51ca1b9aaa9e4bf5369cda36654b3efaa" +dependencies = [ + "flume", + "image", + "nokhwa-bindings-linux", + "nokhwa-bindings-macos", + "nokhwa-bindings-windows", + "nokhwa-core", + "paste", + "thiserror 2.0.12", +] + +[[package]] +name = "nokhwa-bindings-linux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd666aaa41d14357817bd9a981773a73c4d00b34d344cfc244e47ebd397b1ec" +dependencies = [ + "nokhwa-core", + "v4l", +] + +[[package]] +name = "nokhwa-bindings-macos" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78eb4a2d47a68f490899aa0516070d7a972f853ec2bb374ab53be0bd39b60f" +dependencies = [ + "block", + "cocoa-foundation", + "core-foundation 0.10.1", + "core-media-sys", + "core-video-sys", + "flume", + "nokhwa-core", + "objc", + "once_cell", +] + +[[package]] +name = "nokhwa-bindings-windows" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899799275c93ef69bbe8cb888cf6f8249abe751cbc50be5299105022aec14a1c" +dependencies = [ + "nokhwa-core", + "once_cell", + "windows 0.62.2", +] + +[[package]] +name = "nokhwa-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109975552bbd690894f613bce3d408222911e317197c72b2e8b9a1912dc261ae" +dependencies = [ + "bytes", + "image", + "mozjpeg", + "thiserror 2.0.12", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2077,6 +2438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -2350,6 +2712,15 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2421,7 +2792,7 @@ version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "libc", "redox_syscall 0.5.13", "smallvec", @@ -2434,6 +2805,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2508,7 +2885,7 @@ version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "concurrent-queue", "hermit-abi", "pin-project-lite", @@ -2550,6 +2927,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -2703,6 +3090,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2828,9 +3224,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b" dependencies = [ "bitflags 2.9.1", - "cfg-if", + "cfg-if 1.0.1", "core-foundation 0.10.1", - "core-foundation-sys", + "core-foundation-sys 0.8.7", "io-kit-sys", "mach2", "nix 0.26.4", @@ -2845,7 +3241,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "cpufeatures", "digest", ] @@ -2950,6 +3346,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -3109,7 +3514,7 @@ dependencies = [ "arrayref", "arrayvec", "bytemuck", - "cfg-if", + "cfg-if 1.0.1", "log", "tiny-skia-path", ] @@ -3271,6 +3676,26 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v4l" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fbfea44a46799d62c55323f3c55d06df722fbe577851d848d328a1041c3403" +dependencies = [ + "bitflags 1.3.2", + "libc", + "v4l2-sys-mit", +] + +[[package]] +name = "v4l2-sys-mit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6779878362b9bacadc7893eac76abe69612e8837ef746573c4a5239daf11990b" +dependencies = [ + "bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -3308,7 +3733,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -3334,7 +3759,7 @@ version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ - "cfg-if", + "cfg-if 1.0.1", "js-sys", "once_cell", "wasm-bindgen", @@ -3586,7 +4011,7 @@ dependencies = [ "bitflags 2.9.1", "bytemuck", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.1.3", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -3596,7 +4021,7 @@ dependencies = [ "libc", "libloading", "log", - "metal", + "metal 0.31.0", "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", @@ -3612,7 +4037,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows", + "windows 0.58.0", ] [[package]] @@ -3627,6 +4052,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "wide" version = "0.7.19" @@ -3674,23 +4111,68 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -3702,6 +4184,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -3713,6 +4206,33 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -3722,16 +4242,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3759,6 +4297,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3805,6 +4352,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3953,7 +4509,7 @@ dependencies = [ "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics", + "core-graphics 0.23.2", "cursor-icon", "dpi", "js-sys", @@ -4283,6 +4839,21 @@ dependencies = [ "syn", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "4.2.0" diff --git a/hostcontrol/Cargo.toml b/hostcontrol/Cargo.toml index e4099bf..938e385 100644 --- a/hostcontrol/Cargo.toml +++ b/hostcontrol/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.100" clap = { version = "4.0", features = ["derive"] } nalgebra = { version = "0.33" } serialport = { version = "4", default-features = false } @@ -13,3 +14,5 @@ env_logger = "0.11.8" egui = "0.31.1" eframe = "0.31.1" log = "0.4.29" +image = { version = "0.25", default-features = false, features = ["png","jpeg"] } +nokhwa = { version = "0.10.0", features = [ "input-native" ] } diff --git a/hostcontrol/shell.nix b/hostcontrol/shell.nix index 935429c..2540fdb 100644 --- a/hostcontrol/shell.nix +++ b/hostcontrol/shell.nix @@ -5,6 +5,9 @@ pkgs.mkShell { buildInputs = with pkgs; [ cargo + # Needed for a bindgen dependency in nokhwa + rustPlatform.bindgenHook + # Useful tools clippy rustfmt diff --git a/hostcontrol/src/camera.rs b/hostcontrol/src/camera.rs new file mode 100644 index 0000000..ceaa38e --- /dev/null +++ b/hostcontrol/src/camera.rs @@ -0,0 +1,32 @@ +use anyhow::{Context, Result}; +use image::RgbImage; +use nokhwa::Camera as NokhwaCamera; +use nokhwa::pixel_format::RgbFormat; +use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType}; + +pub struct Camera { + camera: NokhwaCamera, +} +impl Camera { + pub fn new(camera_index: u32) -> Result { + let index = CameraIndex::Index(camera_index); + let format = + RequestedFormat::new::(RequestedFormatType::AbsoluteHighestResolution); + let mut camera = NokhwaCamera::new(index, format).context("could not find/access webcam")?; + camera.open_stream().context("failed to open stream")?; + + Ok(Self { + camera, + }) + } + + pub fn capture(&mut self) -> Result { + for _ in 0..5 { + // Hack to have camera flush frames in buffer. + let _ = self.camera.frame(); + } + let frame = self.camera.frame().context("Could not capture image")?; + println!("snap!"); + Ok(frame.decode_image::()?) + } +} diff --git a/hostcontrol/src/gcode_stage.rs b/hostcontrol/src/gcode_stage.rs index 28d5250..9583d4d 100644 --- a/hostcontrol/src/gcode_stage.rs +++ b/hostcontrol/src/gcode_stage.rs @@ -28,6 +28,7 @@ impl XYStage for GCodeStage { a[1] / 1000.0, a[2] / 1000.0 ))?; + self.stage_io.send_request("G4 P1\n")?; // make sure move finished. Ok(()) } diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index 4b1eb15..c418eab 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,12 +1,15 @@ use clap::{Parser, ValueEnum}; -use std::{thread::sleep, time::Duration}; +use std::path::PathBuf; +use image::RgbImage; mod gcode_stage; mod openflexure_stage; +mod camera; mod stage_io; use crate::stage_io::StageIO; use crate::xy_stage::XYStage; +use crate::camera::Camera; mod xy_stage; @@ -21,8 +24,17 @@ struct CliArgs { #[arg(long, default_value = "115200")] tty_speed: u32, + /// Stage movement backend #[arg(long, value_enum)] backend: Backend, + + /// Id of camera to fetch images from. + #[arg(long, default_value = "0")] + camera_index: u32, + + /// Directory all captured images are stored. + #[arg(long, value_name="out-dir")] + output_directory: PathBuf, } // [derive(DebugCopy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] @@ -35,9 +47,29 @@ enum Backend { GCode, } +fn store_image(number: u32, pos: nalgebra::Vector3, dir: &PathBuf, + img: &RgbImage) { + let img_file = + &dir.join(format!("img-{:05}-{:0},{:0}.png", number, pos[0], pos[1])); + + img.save(img_file).unwrap(); +} + fn main() { - let args = CliArgs::parse(); env_logger::init(); + let args = CliArgs::parse(); + + if !args.output_directory.is_dir() { + panic!("--out-dir needs to be an existing directory"); + } + + let origin = nalgebra::vector![0.0, 0.0, 0.0]; + + // Picture source + let mut cam = Camera::new(args.camera_index).unwrap(); + cam.capture().unwrap(); // Warm up camera. + + // Movement let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap(); let mut stage: Box = match args.backend { Backend::GCode => Box::new(gcode_stage::GCodeStage::new(stage_io).unwrap()), @@ -45,13 +77,17 @@ fn main() { Box::new(openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap()) } }; - let origin = nalgebra::vector![0.0, 0.0, 0.0]; - stage.move_absolute_cartesian(origin).unwrap(); - sleep(Duration::from_secs(1)); + + // Local testing. Just take a line of pictures. let mut max_xy = stage.get_range(); + // Only interested in lerp-ing X for now. + max_xy[1] = 0.0; max_xy[2] = 0.0; - stage.move_absolute_cartesian(max_xy).unwrap(); - sleep(Duration::from_secs(5)); + + for n in 0..10 { + stage.move_absolute_cartesian(max_xy * n as f64 / 10.0).unwrap(); + store_image(n, origin, &args.output_directory, &cam.capture().unwrap()); + } } #[derive(Default)] From 2da9567679cf2e187e9c83b68721f64674f35abb Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 20:38:48 +0100 Subject: [PATCH 10/11] Run cargo fmt --- hostcontrol/src/camera.rs | 21 ++++++++++----------- hostcontrol/src/main.rs | 24 ++++++++++++------------ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/hostcontrol/src/camera.rs b/hostcontrol/src/camera.rs index ceaa38e..6d1d480 100644 --- a/hostcontrol/src/camera.rs +++ b/hostcontrol/src/camera.rs @@ -1,32 +1,31 @@ use anyhow::{Context, Result}; use image::RgbImage; -use nokhwa::Camera as NokhwaCamera; use nokhwa::pixel_format::RgbFormat; use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType}; +use nokhwa::Camera as NokhwaCamera; pub struct Camera { camera: NokhwaCamera, } -impl Camera { +impl Camera { pub fn new(camera_index: u32) -> Result { - let index = CameraIndex::Index(camera_index); + let index = CameraIndex::Index(camera_index); let format = RequestedFormat::new::(RequestedFormatType::AbsoluteHighestResolution); - let mut camera = NokhwaCamera::new(index, format).context("could not find/access webcam")?; + let mut camera = + NokhwaCamera::new(index, format).context("could not find/access webcam")?; camera.open_stream().context("failed to open stream")?; - Ok(Self { - camera, - }) + Ok(Self { camera }) } pub fn capture(&mut self) -> Result { - for _ in 0..5 { - // Hack to have camera flush frames in buffer. - let _ = self.camera.frame(); + for _ in 0..5 { + // Hack to have camera flush frames in buffer. + let _ = self.camera.frame(); } let frame = self.camera.frame().context("Could not capture image")?; - println!("snap!"); + println!("snap!"); Ok(frame.decode_image::()?) } } diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index c418eab..bdccb94 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -1,15 +1,15 @@ use clap::{Parser, ValueEnum}; -use std::path::PathBuf; use image::RgbImage; +use std::path::PathBuf; +mod camera; mod gcode_stage; mod openflexure_stage; -mod camera; mod stage_io; +use crate::camera::Camera; use crate::stage_io::StageIO; use crate::xy_stage::XYStage; -use crate::camera::Camera; mod xy_stage; @@ -33,7 +33,7 @@ struct CliArgs { camera_index: u32, /// Directory all captured images are stored. - #[arg(long, value_name="out-dir")] + #[arg(long, value_name = "out-dir")] output_directory: PathBuf, } @@ -47,10 +47,8 @@ enum Backend { GCode, } -fn store_image(number: u32, pos: nalgebra::Vector3, dir: &PathBuf, - img: &RgbImage) { - let img_file = - &dir.join(format!("img-{:05}-{:0},{:0}.png", number, pos[0], pos[1])); +fn store_image(number: u32, pos: nalgebra::Vector3, dir: &PathBuf, img: &RgbImage) { + let img_file = &dir.join(format!("img-{:05}-{:0},{:0}.png", number, pos[0], pos[1])); img.save(img_file).unwrap(); } @@ -60,14 +58,14 @@ fn main() { let args = CliArgs::parse(); if !args.output_directory.is_dir() { - panic!("--out-dir needs to be an existing directory"); + panic!("--out-dir needs to be an existing directory"); } let origin = nalgebra::vector![0.0, 0.0, 0.0]; // Picture source let mut cam = Camera::new(args.camera_index).unwrap(); - cam.capture().unwrap(); // Warm up camera. + cam.capture().unwrap(); // Warm up camera. // Movement let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap(); @@ -85,8 +83,10 @@ fn main() { max_xy[2] = 0.0; for n in 0..10 { - stage.move_absolute_cartesian(max_xy * n as f64 / 10.0).unwrap(); - store_image(n, origin, &args.output_directory, &cam.capture().unwrap()); + stage + .move_absolute_cartesian(max_xy * n as f64 / 10.0) + .unwrap(); + store_image(n, origin, &args.output_directory, &cam.capture().unwrap()); } } From 3ec2add6e4b0bf00361e99342667d6cb53ed2229 Mon Sep 17 00:00:00 2001 From: Henner Zeller Date: Sun, 8 Mar 2026 20:41:59 +0100 Subject: [PATCH 11/11] Properly report the position in filename. --- hostcontrol/src/main.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hostcontrol/src/main.rs b/hostcontrol/src/main.rs index bdccb94..c46a734 100644 --- a/hostcontrol/src/main.rs +++ b/hostcontrol/src/main.rs @@ -83,10 +83,9 @@ fn main() { max_xy[2] = 0.0; for n in 0..10 { - stage - .move_absolute_cartesian(max_xy * n as f64 / 10.0) - .unwrap(); - store_image(n, origin, &args.output_directory, &cam.capture().unwrap()); + let pos = max_xy * n as f64 / 10.0; + stage.move_absolute_cartesian(pos).unwrap(); + store_image(n, pos, &args.output_directory, &cam.capture().unwrap()); } }