#![warn(missing_docs)]
mod parse;
use itertools::Itertools;
pub use pair_macro::Triplet;
pub use parse::ParseError;
pub use serialport;
use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
use std::borrow::Cow;
use std::convert::TryInto;
use std::fmt::{self, Display, Formatter};
use std::time::Duration;
pub struct LeptrinoSensor {
product: Product,
port: Box<dyn SerialPort>,
last_raw_wrench: Wrench,
rated_wrench: Wrench,
offset: Wrench,
}
impl LeptrinoSensor {
pub fn open<'a>(
product: Product,
path: impl Into<Cow<'a, str>>,
) -> Result<LeptrinoSensor, Error> {
let mut port = serialport::new(path, 9600)
.data_bits(DataBits::Eight)
.flow_control(FlowControl::None)
.parity(Parity::None)
.stop_bits(StopBits::One)
.timeout(Duration::from_millis(1))
.open()
.map_err(Error::SerialPort)?;
send_command(&mut port, &[0x04, 0xFF, 0x2B, 0x00])?;
std::thread::sleep(port.timeout());
let res = receive_message(&mut port)?;
let (fx, fy, fz, mx, my, mz) = (0..6)
.map(|i| 4 + i * 4)
.filter_map(|start| res.get(start..start + 4))
.map(|res| f32::from_le_bytes(res.try_into().unwrap()))
.map(|digital| digital as f64)
.next_tuple()
.ok_or(Error::ParseData)?;
let force = Triplet::new(fx, fy, fz);
let torque = Triplet::new(mx, my, mz);
let rated_wrench = Wrench::new(force, torque);
let mut sensor = Self {
product,
port,
last_raw_wrench: Wrench::zeroed(),
rated_wrench,
offset: Wrench::zeroed(),
};
sensor.request_next_wrench()?;
Ok(sensor)
}
pub fn last_wrench(&self) -> Wrench {
let f = self
.last_raw_wrench
.force
.map_entrywise(self.offset.force, |raw, o| raw - o);
let t = self
.last_raw_wrench
.torque
.map_entrywise(self.offset.torque, |raw, o| raw - o);
Wrench::new(f, t)
}
pub fn update(&mut self) -> Result<Wrench, Error> {
let res = receive_message(&mut self.port);
self.request_next_wrench()?;
let res = match res {
Ok(res) => res,
Err(e) => {
return Err(e);
}
};
let (fx, fy, fz, mx, my, mz) = (0..6)
.map(|i| 4 + i * 2)
.filter_map(|start| res.get(start..start + 2))
.map(|res| i16::from_le_bytes(res.try_into().unwrap()))
.map(|digital| digital as f64)
.next_tuple()
.ok_or(Error::ParseData)?;
let rated_binary = self.product.rated_binary();
let force = Triplet::new(fx, fy, fz)
.map_entrywise(self.rated_wrench.force, |left, right| {
left / rated_binary * right
});
let torque = Triplet::new(mx, my, mz)
.map_entrywise(self.rated_wrench.torque, |left, right| {
left / rated_binary * right
});
self.last_raw_wrench = Wrench::new(force, torque);
Ok(self.last_wrench())
}
pub fn zeroed(&mut self) {
self.offset = self.last_raw_wrench;
}
pub fn receive_product_info(&mut self) -> Result<ProductInfo, Error> {
self.communicate_pausing_wrench(|sensor| {
send_command(&mut sensor.port, &[0x04, 0xFF, 0x2A, 0x00])?;
std::thread::sleep(sensor.port.timeout());
let res = receive_message(&mut sensor.port)?;
let parse = |bytes: Option<&[u8]>| {
bytes
.map(|bytes| bytes.to_vec())
.and_then(|bytes| String::from_utf8(bytes).ok())
.ok_or(Error::ParseData)
};
let product_type = parse(res.get(4..20))?;
let serial = parse(res.get(20..28))?;
let firmware_version = parse(res.get(28..32))?;
let output_rate = parse(res.get(32..38))?;
let product_info = ProductInfo {
product_type,
serial,
firmware_version,
output_rate,
};
Ok(product_info)
})
}
pub fn receive_builtin_filter_cutoff_hertz(&mut self) -> Result<Option<u32>, Error> {
self.communicate_pausing_wrench(|sensor| {
send_command(&mut sensor.port, &[0x04, 0xFF, 0xB6, 0x00])?;
std::thread::sleep(sensor.port.timeout());
let res = receive_message(&mut sensor.port)?;
let raw = res.get(4).copied().ok_or(Error::ParseData)?;
sensor.product.builtin_filter_cutoff_hertz(raw)
})
}
pub fn set_builtin_filter_cutoff_hertz(
&mut self,
cutoff_hertz: Option<u32>,
) -> Result<(), Error> {
self.communicate_pausing_wrench(|sensor| {
let raw = sensor.product.builtin_filter_raw(cutoff_hertz)?;
let command = [0x08, 0xFF, 0xA6, 0x00, raw, 0x00, 0x00, 0x00];
send_command(&mut sensor.port, &command)?;
std::thread::sleep(sensor.port.timeout());
Ok(())
})
}
pub fn inner_port(&self) -> &Box<dyn SerialPort> {
&self.port
}
fn request_next_wrench(&mut self) -> Result<(), Error> {
send_command(&mut self.port, &[0x04, 0xFF, 0x30, 0x00])
}
fn communicate_pausing_wrench<T, F>(&mut self, mut f: F) -> Result<T, Error>
where
F: FnMut(&mut Self) -> Result<T, Error>,
{
let value = self
.port
.clear(serialport::ClearBuffer::All)
.map_err(Error::SerialPort)
.and_then(|_| f(self));
self.request_next_wrench()?;
value
}
}
fn send_command(port: &mut Box<dyn SerialPort>, command: &[u8]) -> Result<(), Error> {
let mut message = parse::parse_command(&command);
port.write_all(&mut message).map_err(Error::IO)
}
fn receive_message(port: &mut Box<dyn SerialPort>) -> Result<Vec<u8>, Error> {
let count = port.bytes_to_read().map_err(Error::SerialPort)? as usize;
let mut buf = vec![0; count as usize];
port.read_exact(&mut buf).map_err(Error::IO)?;
parse::parse_reception(&buf).map_err(Error::ParseResponse)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Product {
Pfs055Ya251U6,
}
impl Product {
fn rated_binary(&self) -> f64 {
match self {
Product::Pfs055Ya251U6 => 10000.0,
}
}
fn builtin_filter_cutoff_hertz(&self, raw_value: u8) -> Result<Option<u32>, Error> {
match self {
Product::Pfs055Ya251U6 => match raw_value {
0 => Ok(None),
1 => Ok(Some(10)),
2 => Ok(Some(100)),
3 => Ok(Some(200)),
_ => Err(Error::ParseData),
},
}
}
fn builtin_filter_raw(&self, cutoff_hertz: Option<u32>) -> Result<u8, Error> {
match self {
Product::Pfs055Ya251U6 => match cutoff_hertz {
Some(10) => Ok(1),
Some(100) => Ok(2),
Some(200) => Ok(3),
Some(_) => Err(Error::InvalidParameter),
None => Ok(0),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProductInfo {
pub product_type: String,
pub serial: String,
pub firmware_version: String,
pub output_rate: String,
}
#[derive(Debug)]
pub enum Error {
SerialPort(serialport::Error),
IO(std::io::Error),
ParseResponse(ParseError),
ParseData,
InvalidParameter,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Error::SerialPort(e) => write!(f, "SerialPort: {}", e),
Error::IO(e) => write!(f, "IO: {}", e),
Error::ParseResponse(e) => write!(f, "Parse: {}", e),
Error::ParseData => write!(f, "Failed to parse the response into data."),
Error::InvalidParameter => write!(f, "An invalid parameter was specified."),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::SerialPort(e) => Some(e),
Error::IO(e) => Some(e),
Error::ParseResponse(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Wrench {
pub force: Triplet<f64>,
pub torque: Triplet<f64>,
}
impl Wrench {
pub fn new(force: Triplet<f64>, torque: Triplet<f64>) -> Wrench {
Self { force, torque }
}
pub fn zeroed() -> Wrench {
Wrench::new(Triplet::default(), Triplet::default())
}
}