//! Host-side control interface for B&K Precision 16xB Power Supplies. //! //! The library optimizes for the 1687B and 1688B models. This library //! doe not support two decimal places for current on the 1685B model. //! //! This library has no affiliation with B&K Precision. use std::{ fmt::{Debug, Display}, str::from_utf8, }; use serialport::SerialPort; pub use serialport::{Error as SerialError, ErrorKind as SerialErrorKind}; pub enum Error { UnexpectedResponse, Parse, Serial(SerialError), } impl From for Error { fn from(value: SerialError) -> Self { Self::Serial(value) } } impl From for Error { fn from(value: std::io::Error) -> Self { Self::Serial(value.into()) } } impl From for Error { fn from(_: std::num::ParseFloatError) -> Self { Self::Parse } } impl From for Error { fn from(_: std::str::Utf8Error) -> Self { Self::Parse } } pub type Result = std::result::Result; /// An interface to the power supply. pub struct PowerSupply { port: Box, } impl PowerSupply { pub fn open(path: impl AsRef) -> Result { serialport::new(path.as_ref(), 9600) .data_bits(serialport::DataBits::Eight) .parity(serialport::Parity::None) .stop_bits(serialport::StopBits::One) .flow_control(serialport::FlowControl::None) .timeout(std::time::Duration::from_millis(100)) .open() .map(|port| PowerSupply { port }) .map_err(From::from) } fn parse_volts(&mut self) -> Result { let mut buff = [0u8; 3]; self.port.read_exact(&mut buff)?; let volts: f64 = from_utf8(&buff)?.parse()?; Ok(volts / 10.0) } fn parse_current(&mut self) -> Result { let mut buff = [0u8; 3]; self.port.read_exact(&mut buff)?; let current: f64 = from_utf8(&buff)?.parse()?; Ok(current / 10.0) } fn parse_cr(&mut self) -> Result<()> { let mut buff = [0; 1]; self.port.read_exact(&mut buff)?; if buff[0] == b'\r' { Ok(()) } else { Err(Error::UnexpectedResponse) } } fn parse_ok(&mut self) -> Result<()> { let mut buff = [0; 2]; self.port.read_exact(&mut buff)?; if &buff == b"OK" { Ok(()) } else { Err(Error::UnexpectedResponse) } } fn parse_end(&mut self) -> Result<()> { self.parse_ok()?; self.parse_cr()?; Ok(()) } fn write_command(&mut self, cmd: &[u8]) -> Result<()> { self.port.write_all(cmd)?; self.port.flush()?; Ok(()) } /// Set the supply output state. pub fn set_output(&mut self, output: Output) -> Result<()> { self.write_command(match output { Output::Off => b"SOUT1\r", Output::On => b"SOUT0\r", })?; self.parse_end()?; Ok(()) } /// Read the supply's maximum voltage and current. pub fn max_voltage_current(&mut self) -> Result<(f64, f64)> { self.write_command(b"GMAX\r")?; let volts = self.parse_volts()?; let amps = self.parse_current()?; self.parse_end()?; Ok((volts, amps)) } } /// The power supply output state. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Output { /// Supply off. Off, /// Supply on. On, } impl From for Output { fn from(value: bool) -> Self { if value { Self::On } else { Self::Off } } } /// Power supply model #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum Model { /// BK Precision 1687B. /// /// 36V - 10A. Bkp1687B, } /// A voltage setting. /// /// This has a 0.1V resolution across all power supply models. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Voltage( u32, /* Internally represented as mV; see GETD command. */ ); impl Display for Voltage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {} } impl Voltage { pub const fn from_f64_v(volts: f64) -> Voltage { Voltage((volts * 1000.0f64) as u32) } pub const fn from_f32_v(volts: f32) -> Voltage { Voltage((volts * 1000.0f32) as u32) } pub const fn from_mv(millivolts: u32) -> Voltage { Voltage(millivolts / 10 * 10) } } /// A current setting. /// /// On the 1687B and 1688B models, this has /// a 0.1A resolution. On the 1685B model, this /// has a 0.01A resolution. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Current( u32, /* Internally represented as mA; see GETD command. */ ); impl Current { pub const fn from_f64_a(amps: f64) -> Current { Current((amps * 1000.0f64) as u32) } pub const fn from_f32_a(amps: f32) -> Current { Current((amps * 1000.0f32) as u32) } pub const fn from_ma(milliamps: u32) -> Current { Current(milliamps) } }