mod serial_driver;
use serial_driver::{ FramedSerialDriver, FramedDriver, LssCommand };
use std::{ str, error::Error };
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LedColor {
Off = 0,
Red = 1,
Green = 2,
Blue = 3,
Yellow = 4,
Cyan = 5,
Magenta = 6,
White = 7,
}
impl LedColor {
fn from_i32(number: i32) -> Result<LedColor, Box<dyn Error>> {
match number {
0 => Ok(LedColor::Off),
1 => Ok(LedColor::Red),
2 => Ok(LedColor::Green),
3 => Ok(LedColor::Blue),
4 => Ok(LedColor::Yellow),
5 => Ok(LedColor::Cyan),
6 => Ok(LedColor::Magenta),
7 => Ok(LedColor::White),
_ => Err("Could not find color")?,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MotorStatus {
Unknown = 0,
Limp = 1,
FreeMoving = 2,
Accelerating = 3,
Traveling = 4,
Decelerating = 5,
Holding = 6,
OutsideLimits = 7,
Stuck = 8,
Blocked = 9,
SafeMode = 10,
}
impl MotorStatus {
fn from_i32(number: i32) -> Result<MotorStatus, Box<dyn Error>> {
match number {
0 => Ok(MotorStatus::Unknown),
1 => Ok(MotorStatus::Limp),
2 => Ok(MotorStatus::FreeMoving),
3 => Ok(MotorStatus::Accelerating),
4 => Ok(MotorStatus::Traveling),
5 => Ok(MotorStatus::Decelerating),
6 => Ok(MotorStatus::Holding),
7 => Ok(MotorStatus::OutsideLimits),
8 => Ok(MotorStatus::Stuck),
9 => Ok(MotorStatus::Blocked),
10 => Ok(MotorStatus::SafeMode),
_ => Err("Could not find status")?,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SafeModeStatus {
NoLimits = 0,
CurrentLimit = 1,
InputVoltageOutOfRange = 2,
TemperatureLimit = 3,
}
impl SafeModeStatus {
fn from_i32(number: i32) -> Result<SafeModeStatus, Box<dyn Error>> {
match number {
0 => Ok(SafeModeStatus::NoLimits),
1 => Ok(SafeModeStatus::CurrentLimit),
2 => Ok(SafeModeStatus::InputVoltageOutOfRange),
3 => Ok(SafeModeStatus::TemperatureLimit),
_ => Err("Could not find safe status")?,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Model {
ST1,
HS1,
HT1,
Other(String),
}
impl Model {
fn from_str(model: &str) -> Model {
match model {
"LSS-ST1" => Model::ST1,
"LSS-HS1" => Model::HS1,
"LSS-HT1" => Model::HT1,
other => Model::Other(other.to_owned()),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LedBlinking {
NoBlinking = 0,
Limp = 1,
Holding = 2,
Accelerating = 4,
Decelerating = 8,
Free = 16,
Travelling = 32,
AlwaysBlink = 63,
}
pub const BROADCAST_ID: u8 = 254;
pub struct LSSDriver {
driver: Box<dyn FramedDriver>,
}
impl LSSDriver {
pub fn new(port: &str) -> Result<LSSDriver, Box<dyn Error>> {
let driver = FramedSerialDriver::new(port)?;
Ok(LSSDriver {
driver: Box::new(driver),
})
}
pub fn with_baud_rate(port: &str, baud_rate: u32) -> Result<LSSDriver, Box<dyn Error>> {
let driver = FramedSerialDriver::with_baud_rate(port, baud_rate)?;
Ok(LSSDriver {
driver: Box::new(driver),
})
}
pub fn with_driver(driver: Box<dyn FramedDriver>) -> LSSDriver {
LSSDriver {
driver,
}
}
pub async fn query_id(&mut self, id: u8) -> Result<u8, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QID")).await?;
let response = self.driver.receive().await?;
let value = response.get_val("QID")?;
Ok(value as u8)
}
pub async fn set_id(&mut self, id: u8, new_id: u8) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "CID", new_id as i32)).await?;
Ok(())
}
pub async fn set_color(&mut self, id: u8, color: LedColor) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "LED", color as i32)).await?;
Ok(())
}
pub async fn query_color(&mut self, id: u8) -> Result<LedColor, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QLED")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QLED")?;
Ok(LedColor::from_i32(value)?)
}
pub async fn move_to_position(&mut self, id: u8, position: f32) -> Result<(), Box<dyn Error>> {
let angle = (position * 10.0).round() as i32;
self.driver.send(LssCommand::with_param(id, "D", angle)).await?;
Ok(())
}
pub async fn query_position(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QD")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QD")?;
Ok(value as f32 / 10.0)
}
pub async fn query_target_position(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QDT")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QDT")?;
Ok(value as f32 / 10.0)
}
pub async fn set_rotation_speed(&mut self, id: u8, speed: f32) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "WD", speed as i32)).await?;
Ok(())
}
pub async fn query_rotation_speed(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QWD")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QWD")?;
Ok(value as f32)
}
pub async fn query_status(&mut self, id: u8) -> Result<MotorStatus, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "Q")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("Q")?;
Ok(MotorStatus::from_i32(value)?)
}
pub async fn query_safety_status(&mut self, id: u8) -> Result<SafeModeStatus, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "Q1")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("Q")?;
Ok(SafeModeStatus::from_i32(value)?)
}
pub async fn set_motion_profile(
&mut self,
id: u8,
motion_profile: bool,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "EM", motion_profile as i32)).await?;
Ok(())
}
pub async fn query_motion_profile(
&mut self,
id: u8) -> Result<bool, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QEM")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QEM")?;
Ok(value != 0)
}
pub async fn set_filter_position_count(
&mut self,
id: u8,
filter_position_count: u8,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "FPC", filter_position_count as i32)).await?;
Ok(())
}
pub async fn query_filter_position_count(
&mut self,
id: u8) -> Result<u8, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QFPC")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QFPC")?;
Ok(value as u8)
}
pub async fn set_angular_stiffness(
&mut self,
id: u8,
angular_stiffness: i32,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "AS", angular_stiffness)).await?;
Ok(())
}
pub async fn query_angular_stiffness(
&mut self,
id: u8) -> Result<i32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QAS")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QAS")?;
Ok(value)
}
pub async fn set_angular_holding_stiffness(
&mut self,
id: u8,
angular_holding: i32,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "AH", angular_holding)).await?;
Ok(())
}
pub async fn query_angular_holding_stiffness(
&mut self,
id: u8) -> Result<i32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QAH")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QAH")?;
Ok(value)
}
pub async fn set_angular_acceleration(
&mut self,
id: u8,
angular_acceleration: i32,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "AA", angular_acceleration)).await?;
Ok(())
}
pub async fn query_angular_acceleration(
&mut self,
id: u8) -> Result<i32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QAA")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QAA")?;
Ok(value)
}
pub async fn set_angular_deceleration(
&mut self,
id: u8,
angular_deceleration: i32,
) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::with_param(id, "AD", angular_deceleration)).await?;
Ok(())
}
pub async fn query_angular_deceleration(
&mut self,
id: u8) -> Result<i32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QAD")).await?;
let response = self.driver.receive().await?;
let (_, value)= response.separate("QAD")?;
Ok(value)
}
pub async fn limp(&mut self, id: u8) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "L")).await?;
Ok(())
}
pub async fn halt_hold(&mut self, id: u8) -> Result<(), Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "H")).await?;
Ok(())
}
pub async fn query_voltage(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QV")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate("QV")?;
Ok(value as f32 / 1000.0)
}
pub async fn query_temperature(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QT")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate("QT")?;
Ok(value as f32 / 10.0)
}
pub async fn query_current(&mut self, id: u8) -> Result<f32, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QC")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate("QC")?;
Ok(value as f32 / 1000.0)
}
pub async fn query_model(&mut self, id: u8) -> Result<Model, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QMS")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate_string("QMS")?;
Ok(Model::from_str(&value))
}
pub async fn query_firmware_version(&mut self, id: u8) -> Result<String, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QF")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate_string("QF")?;
Ok(value)
}
pub async fn query_serial_number(&mut self, id: u8) -> Result<String, Box<dyn Error>> {
self.driver.send(LssCommand::simple(id, "QN")).await?;
let response = self.driver.receive().await?;
let (_, value) = response.separate_string("QN")?;
Ok(value)
}
pub async fn set_led_blinking(&mut self, id: u8, blinking_mode: Vec<LedBlinking>) -> Result<(), Box<dyn Error>> {
let sum = blinking_mode
.iter()
.map(|item| *item as i32)
.sum::<i32>()
.min(LedBlinking::AlwaysBlink as i32);
self.driver.send(LssCommand::with_param(id, "CLB", sum)).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio;
use async_trait::async_trait;
use super::serial_driver::LssResponse;
struct MockedDriver {
expected_send: Vec<String>,
receive: Vec<String>,
}
#[async_trait]
impl FramedDriver for MockedDriver {
async fn send(&mut self, command: LssCommand) -> Result<(), Box<dyn Error>> {
let expected = self.expected_send.pop().unwrap();
assert_eq!(expected, command.as_str().to_owned());
Ok(())
}
async fn receive(&mut self) -> Result<LssResponse, Box<dyn Error>> {
Ok(LssResponse::new(self.receive.pop().unwrap()))
}
}
#[tokio::test]
async fn async_test_builds() {}
#[tokio::test]
async fn test_limp_color_move_hold() {
let mocked_framed_driver = MockedDriver {
expected_send: vec![
"#5QV\r".to_owned(),
"#4H\r".to_owned(),
"#3D1800\r".to_owned(),
"#2LED1\r".to_owned(),
"#1L\r".to_owned(),
],
receive: vec![
"*5QV11200\r".to_owned(),
],
};
let mut driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
driver.limp(1).await.unwrap();
driver.set_color(2, LedColor::Red).await.unwrap();
driver.move_to_position(3, 180.0).await.unwrap();
driver.halt_hold(4).await.unwrap();
let voltage = driver.query_voltage(5).await.unwrap();
assert_eq!(voltage, 11.2);
}
#[test]
fn model_parses_other() {
let model = Model::from_str("something");
assert_eq!(model, Model::Other("something".to_owned()));
}
macro_rules! test_command {
($name:ident, $expected:expr, $command:expr) => {
#[tokio::test]
async fn $name() {
let mocked_framed_driver = MockedDriver {
expected_send: vec![
$expected.to_owned(),
],
receive: vec![],
};
let mut driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
$command;
}
}
}
macro_rules! test_query {
($name:ident, $expected:expr, $recv:expr, $command:expr, $val:expr) => {
#[tokio::test]
async fn $name() {
let mocked_framed_driver = MockedDriver {
expected_send: vec![
$expected.to_owned(),
],
receive: vec![
$recv.to_owned(),
],
};
let mut driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
let res = $command;
assert_eq!(res, $val);
}
}
}
test_command!(test_hold_command, "#4H\r", driver.halt_hold(4).await.unwrap());
test_query!(test_query_id, "#254QID\r", "*QID5\r", driver.query_id(BROADCAST_ID).await.unwrap(), 5);
test_command!(test_set_id, "#1CID2\r", driver.set_id(1, 2).await.unwrap());
test_command!(test_move_to, "#1D200\r", driver.move_to_position(1, 20.0).await.unwrap());
test_query!(test_query_current_position, "#5QD\r", "*5QD132\r", driver.query_position(5).await.unwrap(), 13.2);
test_query!(test_query_target_position, "#5QDT\r", "*5QDT6783\r", driver.query_target_position(5).await.unwrap(), 678.3);
test_command!(test_set_rotation_speed_degrees, "#5WD90\r", driver.set_rotation_speed(5, 90.0).await.unwrap());
test_query!(test_query_rotation_speed_degrees, "#5QWD\r", "*5QWD90\r", driver.query_rotation_speed(5).await.unwrap(), 90.0);
test_query!(test_unknown_status, "#5Q\r", "*5Q0\r", driver.query_status(5).await.unwrap(), MotorStatus::Unknown);
test_query!(test_holding_status, "#5Q\r", "*5Q6\r", driver.query_status(5).await.unwrap(), MotorStatus::Holding);
test_query!(test_safety_status, "#5Q1\r", "*5Q3\r", driver.query_safety_status(5).await.unwrap(), SafeModeStatus::TemperatureLimit);
test_command!(test_limp, "#5L\r", driver.limp(5).await.unwrap());
test_command!(test_halt_hold, "#5H\r", driver.halt_hold(5).await.unwrap());
test_command!(test_set_led, "#5LED3\r", driver.set_color(5, LedColor::Blue).await.unwrap());
test_query!(test_query_led, "#5QLED\r", "*5QLED5\r", driver.query_color(5).await.unwrap(), LedColor::Cyan);
test_command!(test_motion_profile_on, "#5EM1\r", driver.set_motion_profile(5, true).await.unwrap());
test_command!(test_motion_profile_off, "#5EM0\r", driver.set_motion_profile(5, false).await.unwrap());
test_query!(test_query_motion_profile_on, "#5QEM\r", "*5QEM1\r", driver.query_motion_profile(5).await.unwrap(), true);
test_query!(test_query_motion_profile_off, "#5QEM\r", "*5QEM0\r", driver.query_motion_profile(5).await.unwrap(), false);
test_command!(test_set_filter_position_count, "#5FPC10\r", driver.set_filter_position_count(5, 10).await.unwrap());
test_query!(test_query_filter_position_count, "#5QFPC\r", "*5QFPC10\r", driver.query_filter_position_count(5).await.unwrap(), 10);
test_command!(test_set_angular_stiffness, "#5AS-2\r", driver.set_angular_stiffness(5, -2).await.unwrap());
test_query!(test_query_angular_stiffness, "#5QAS\r", "*5QAS-2\r", driver.query_angular_stiffness(5).await.unwrap(), -2);
test_command!(test_set_angular_holding_stiffness, "#5AH3\r", driver.set_angular_holding_stiffness(5, 3).await.unwrap());
test_query!(test_query_angular_holding_stiffness, "#5QAH\r", "*5QAH3\r", driver.query_angular_holding_stiffness(5).await.unwrap(), 3);
test_command!(test_set_angular_acceleration, "#5AA30\r", driver.set_angular_acceleration(5, 30).await.unwrap());
test_query!(test_query_angular_acceleration, "#5QAA\r", "*5QAA30\r", driver.query_angular_acceleration(5).await.unwrap(), 30);
test_command!(test_set_angular_deceleration, "#5AD30\r", driver.set_angular_deceleration(5, 30).await.unwrap());
test_query!(test_query_angular_deceleration, "#5QAD\r", "*5QAD30\r", driver.query_angular_deceleration(5).await.unwrap(), 30);
test_query!(test_query_voltage, "#5QV\r", "*5QV11200\r", driver.query_voltage(5).await.unwrap(), 11.2);
test_query!(test_query_temperature, "#5QT\r", "*5QT564\r", driver.query_temperature(5).await.unwrap(), 56.4);
test_query!(test_query_current, "#5QC\r", "*5QC140\r", driver.query_current(5).await.unwrap(), 0.14);
test_query!(test_query_model_string, "#5QMS\r", "*5QMSLSS-HS1\r", driver.query_model(5).await.unwrap(), Model::HS1);
test_query!(test_query_firmware_version, "#5QF\r", "*5QF368\r", driver.query_firmware_version(5).await.unwrap(), "368".to_owned());
test_query!(test_query_serial_number, "#5QN\r", "*5QN12345678\r", driver.query_serial_number(5).await.unwrap(), "12345678".to_owned());
test_command!(test_blinking_mode_1, "#5CLB0\r", driver.set_led_blinking(5, vec![LedBlinking::NoBlinking]).await.unwrap());
test_command!(test_blinking_mode_2, "#5CLB1\r", driver.set_led_blinking(5, vec![LedBlinking::Limp]).await.unwrap());
test_command!(test_blinking_mode_3, "#5CLB2\r", driver.set_led_blinking(5, vec![LedBlinking::Holding]).await.unwrap());
test_command!(test_blinking_mode_4, "#5CLB12\r", driver.set_led_blinking(5, vec![LedBlinking::Accelerating, LedBlinking::Decelerating]).await.unwrap());
test_command!(test_blinking_mode_5, "#5CLB48\r", driver.set_led_blinking(5, vec![LedBlinking::Free, LedBlinking::Travelling]).await.unwrap());
test_command!(test_blinking_mode_6, "#5CLB63\r", driver.set_led_blinking(5, vec![LedBlinking::AlwaysBlink]).await.unwrap());
}