use crate::error::{MmError, MmResult};
use crate::property::PropertyMap;
use crate::traits::{Device, Shutter};
use crate::transport::Transport;
use crate::types::{DeviceType, PropertyValue};
const SEP: char = '\u{00A7}';
pub struct OmicronLaser {
props: PropertyMap,
transport: Option<Box<dyn Transport>>,
initialized: bool,
power_pct: f64,
laser_on: bool,
max_power_mw: f64,
}
impl OmicronLaser {
pub fn new() -> Self {
let mut props = PropertyMap::new();
props.define_property("Port", PropertyValue::String("Undefined".into()), false).unwrap();
props.define_property("FirmwareVersion", PropertyValue::String(String::new()), true).unwrap();
props.define_property("SerialNumber", PropertyValue::String(String::new()), true).unwrap();
props.define_property("WavelengthNm", PropertyValue::Integer(0), true).unwrap();
props.define_property("MaxPower_mW", PropertyValue::Float(0.0), true).unwrap();
props.define_property("Power_Percent", PropertyValue::Float(0.0), false).unwrap();
Self {
props,
transport: None,
initialized: false,
power_pct: 0.0,
laser_on: false,
max_power_mw: 0.0,
}
}
pub fn with_transport(mut self, t: Box<dyn Transport>) -> Self {
self.transport = Some(t);
self
}
fn call_transport<R, F>(&mut self, f: F) -> MmResult<R>
where F: FnOnce(&mut dyn Transport) -> MmResult<R> {
match self.transport.as_mut() {
Some(t) => f(t.as_mut()),
None => Err(MmError::NotConnected),
}
}
fn cmd(&mut self, command: &str) -> MmResult<String> {
let full = format!("{}\r", command);
let echo = format!("!{}", &command[1..]);
self.call_transport(|t| {
let r = t.send_recv(&full)?;
let trimmed = r.trim();
let data = trimmed.strip_prefix(&echo).unwrap_or(trimmed);
Ok(data.to_string())
})
}
fn hex_to_pct(hex: &str) -> f64 {
let val = u32::from_str_radix(hex.trim(), 16).unwrap_or(0);
(val as f64 / 4095.0) * 100.0
}
fn pct_to_hex(pct: f64) -> String {
let val = ((pct / 100.0) * 4095.0).round() as u32;
let val = val.min(0xFFF);
format!("{:03X}", val)
}
}
impl Default for OmicronLaser { fn default() -> Self { Self::new() } }
impl Device for OmicronLaser {
fn name(&self) -> &str { "OmicronLaser" }
fn description(&self) -> &str { "Omicron PhoxX/LuxX/BrixX Laser" }
fn initialize(&mut self) -> MmResult<()> {
if self.transport.is_none() { return Err(MmError::NotConnected); }
let mode_hex = self.cmd("?GOM")?;
let mode = u32::from_str_radix(mode_hex.trim(), 16).unwrap_or(0);
let mode = mode & !(1u32 << 13);
self.cmd(&format!("?SOM{:04X}", mode))?;
let fw = self.cmd("?GFw")?;
let fw_fields: Vec<&str> = fw.split(SEP).filter(|s| !s.is_empty()).collect();
let version = fw_fields.get(0).unwrap_or(&"").to_string();
self.props.entry_mut("FirmwareVersion").map(|e| e.value = PropertyValue::String(version));
let gsi = self.cmd("?GSI")?;
let fields: Vec<&str> = gsi.split(SEP).filter(|s| !s.is_empty()).collect();
if let Some(nm_str) = fields.get(0) {
let nm: i64 = nm_str.trim().parse().unwrap_or(0);
self.props.entry_mut("WavelengthNm").map(|e| e.value = PropertyValue::Integer(nm));
}
if let Some(mw_str) = fields.get(1) {
let mw: f64 = mw_str.trim().parse().unwrap_or(0.0);
self.max_power_mw = mw;
self.props.entry_mut("MaxPower_mW").map(|e| e.value = PropertyValue::Float(mw));
}
let sn = self.cmd("?GSN")?;
self.props.entry_mut("SerialNumber").map(|e| e.value = PropertyValue::String(sn));
let lp = self.cmd("?GLP")?;
self.power_pct = Self::hex_to_pct(lp.trim());
self.props.entry_mut("Power_Percent").map(|e| e.value = PropertyValue::Float(self.power_pct));
let gas = self.cmd("?GAS")?;
let status = u32::from_str_radix(gas.trim(), 16).unwrap_or(0);
self.laser_on = (status >> 1) & 1 == 1;
self.initialized = true;
Ok(())
}
fn shutdown(&mut self) -> MmResult<()> {
if self.initialized {
let _ = self.set_open(false);
self.initialized = false;
}
Ok(())
}
fn get_property(&self, name: &str) -> MmResult<PropertyValue> {
match name {
"Power_Percent" => Ok(PropertyValue::Float(self.power_pct)),
_ => self.props.get(name).cloned(),
}
}
fn set_property(&mut self, name: &str, val: PropertyValue) -> MmResult<()> {
if name == "Power_Percent" {
let pct = val.as_f64().ok_or(MmError::InvalidPropertyValue)?;
let pct = pct.clamp(0.0, 100.0);
if self.initialized {
self.cmd(&format!("?SLP{}", Self::pct_to_hex(pct)))?;
}
self.power_pct = pct;
self.props.entry_mut(name).map(|e| e.value = PropertyValue::Float(pct));
return Ok(());
}
self.props.set(name, val)
}
fn property_names(&self) -> Vec<String> { self.props.property_names().to_vec() }
fn has_property(&self, name: &str) -> bool { self.props.has_property(name) }
fn is_property_read_only(&self, name: &str) -> bool {
self.props.entry(name).map(|e| e.read_only).unwrap_or(false)
}
fn device_type(&self) -> DeviceType { DeviceType::Shutter }
fn busy(&self) -> bool { false }
}
impl Shutter for OmicronLaser {
fn set_open(&mut self, open: bool) -> MmResult<()> {
if open {
self.cmd("?LOn")?;
} else {
self.cmd("?LOf")?;
}
self.laser_on = open;
Ok(())
}
fn get_open(&self) -> MmResult<bool> { Ok(self.laser_on) }
fn fire(&mut self, _delta_t: f64) -> MmResult<()> { Ok(()) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::MockTransport;
fn make_init_transport() -> MockTransport {
MockTransport::new()
.expect("?GOM\r", "!GOM2010") .expect("?SOM0010\r", "!SOM0010") .expect("?GFw\r", "!GFw\u{00A7}2.1.0\u{00A7}3") .expect("?GSI\r", "!GSI\u{00A7}488\u{00A7}50") .expect("?GSN\r", "!GSN12345678")
.expect("?GLP\r", "!GLP7FF") .expect("?GAS\r", "!GAS002") }
#[test]
fn initialize() {
let mut dev = OmicronLaser::new().with_transport(Box::new(make_init_transport()));
dev.initialize().unwrap();
assert!((dev.power_pct - 49.98).abs() < 0.1);
assert!(dev.laser_on); assert_eq!(dev.max_power_mw, 50.0);
}
#[test]
fn power_encoding() {
assert_eq!(OmicronLaser::pct_to_hex(0.0), "000");
assert_eq!(OmicronLaser::pct_to_hex(100.0), "FFF");
assert_eq!(OmicronLaser::pct_to_hex(50.0), "800");
assert!((OmicronLaser::hex_to_pct("7FF") - 49.98).abs() < 0.1);
}
#[test]
fn laser_on_off() {
let t = make_init_transport()
.expect("?LOn\r", "!LOn")
.expect("?LOf\r", "!LOf");
let mut dev = OmicronLaser::new().with_transport(Box::new(t));
dev.initialize().unwrap();
dev.set_open(true).unwrap();
assert!(dev.get_open().unwrap());
dev.set_open(false).unwrap();
assert!(!dev.get_open().unwrap());
}
#[test]
fn set_power() {
let t = make_init_transport()
.expect("?SLP800\r", "!SLP800");
let mut dev = OmicronLaser::new().with_transport(Box::new(t));
dev.initialize().unwrap();
dev.set_property("Power_Percent", PropertyValue::Float(50.0)).unwrap();
assert!((dev.power_pct - 50.0).abs() < 0.01);
}
#[test]
fn no_transport_error() { assert!(OmicronLaser::new().initialize().is_err()); }
}