use cursive::align::*;
use cursive::direction::Orientation;
use cursive::event::Key;
use cursive::traits::View;
use cursive::view::SizeConstraint;
use cursive::views::*;
use cursive::Cursive;
use serialport::{available_ports, open_with_settings};
use serialport::{SerialPort, SerialPortInfo, SerialPortSettings};
use drs_0x01::addr::WritableEEPAddr;
use drs_0x01::builder::HerkulexMessage;
use drs_0x01::{Rotation, Servo};
use std::cell::RefCell;
use std::error::Error;
use std::io;
use std::rc::Rc;
use std::{thread, time};
type SharedState<T> = Rc<RefCell<T>>;
#[derive(Debug)]
enum Action {
Quit,
SetId,
SendMessage,
}
#[derive(Debug)]
enum Message {
EnableTorque,
DisableTorque,
Reboot,
ChangeId,
ForceChangeId,
SetSpeed,
SetPosition,
ClearErrors,
GoBack,
}
fn send_messages(
cursives: &mut Cursive,
connection: SharedState<Option<Box<dyn SerialPort>>>,
messages: Vec<HerkulexMessage>,
) {
if let Some(ref mut c) = *connection.borrow_mut() {
for message in messages {
match c.write(&message) {
Ok(_) => {}
Err(e) => {
cursives.add_layer(Dialog::info(format!("Failed to send message. \n\n {}", e)));
return;
}
}
let ten_millis = time::Duration::from_millis(10);
thread::sleep(ten_millis);
}
cursives.add_layer(Dialog::info("Message succesfully sent."));
} else {
cursives.add_layer(Dialog::info(
"Error in application logic : the connection is not initialized.",
));
}
}
fn send_message(
cursives: &mut Cursive,
connection: SharedState<Option<Box<dyn SerialPort>>>,
message: HerkulexMessage,
) {
if let Some(ref mut c) = *connection.borrow_mut() {
match c.write(&message) {
Ok(_) => {
cursives.add_layer(Dialog::info("Message succesfully sent."));
}
Err(e) => {
cursives.add_layer(Dialog::info(format!("Failed to send message. \n\n {}", e)));
}
}
} else {
cursives.add_layer(Dialog::info(
"Error in application logic : the connection is not initialized.",
));
}
}
fn ask_user_input<T: Into<String>>(
label: T,
cursives: &mut Cursive,
connection: SharedState<Option<Box<dyn SerialPort>>>,
id: SharedState<u8>,
on_submit: impl Fn(&mut Cursive, u16, SharedState<u8>, SharedState<Option<Box<dyn SerialPort>>>)
+ 'static,
) {
let mut id_input = EditView::new();
id_input.set_max_content_width(Some(5));
id_input.set_secret(false);
id_input.set_on_submit(move |mut c, data| match data.parse::<u16>() {
Ok(d) => {
c.pop_layer();
on_submit(&mut c, d, id.clone(), connection.clone())
}
Err(e) => c.add_layer(Dialog::info(format!(
"This is not a valid number. \n {}",
e
))),
});
let mut layout = LinearLayout::new(Orientation::Vertical);
layout.add_child(TextView::new(label));
layout.add_child(ResizedView::new(
SizeConstraint::AtMost(5),
SizeConstraint::AtMost(5),
id_input,
));
cursives.add_layer(Dialog::around(layout))
}
fn init_message_menu(
connection: SharedState<Option<Box<dyn SerialPort>>>,
id: SharedState<u8>,
) -> Box<dyn View> {
let mut view: SelectView<Message> = SelectView::new().h_align(HAlign::Center);
view.add_item("Reboot", Message::Reboot);
view.add_item("Clear Errors", Message::ClearErrors);
view.add_item("Set Speed", Message::SetSpeed);
view.add_item("Set Position", Message::SetPosition);
view.add_item("Enable Torque", Message::EnableTorque);
view.add_item("Disable Torque", Message::DisableTorque);
view.add_item("Set ID", Message::ChangeId);
view.add_item("Force Set ID", Message::ForceChangeId);
view.add_item("Go Back", Message::GoBack);
view.set_on_submit(move |s, message| {
match message {
Message::EnableTorque => {
let servo = Servo::new(*id.borrow());
let message = servo.enable_torque();
send_message(s, connection.clone(), message);
}
Message::DisableTorque => {
let servo = Servo::new(*id.borrow());
let message = servo.disable_torque();
send_message(s, connection.clone(), message);
}
Message::Reboot => {
let servo = Servo::new(*id.borrow());
let message = servo.reboot();
send_message(s, connection.clone(), message);
}
Message::ChangeId => {
ask_user_input("Enter the new ID (0-253). \n Please note that you have to reboot the servo to take it into account", s, connection.clone(),
id.clone(),
|c, value, servo_id,conn| {
if value < 254 {
let servo = Servo::new(*servo_id.borrow() as u8);
let message_to_send = servo.eep_write(WritableEEPAddr::ID(value as u8));
send_message(c, conn, message_to_send);
} else {
c.add_layer(Dialog::info("Invalid ID it has to be between 0 and 253."))
}
}
);
},
Message::SetSpeed => {
ask_user_input("Enter the speed ID (0-1023).", s, connection.clone(),
id.clone(),
|c, value, servo_id,conn| {
if value < 1023 {
let servo = Servo::new(*servo_id.borrow() as u8);
let message_to_send = servo.set_speed(value, Rotation::CounterClockwise);
send_message(c, conn, message_to_send);
} else {
c.add_layer(Dialog::info("Invalid speed it has to be between 0 and 1023."))
}
}
);
}
Message::SetPosition => {
ask_user_input("Enter the new position (0-1023). \n Please note that outside the safe range (21 to 1002), the serovomotor will go into error", s, connection.clone(),
id.clone(),
|c, value, servo_id,conn| {
if value < 1023 {
let servo = Servo::new(*servo_id.borrow() as u8);
let message_to_send = servo.set_position(value);
send_message(c, conn, message_to_send);
} else {
c.add_layer(Dialog::info("Invalid speed it has to be between 0 and 1023."))
}
}
);
}
Message::ClearErrors => {
let servo = Servo::new(*id.borrow());
let message = servo.clear_errors();
send_message(s, connection.clone(), message);
}
Message::GoBack => {
s.pop_layer();
s.add_layer(init_main_menu(connection.clone(), id.clone()));
}
Message::ForceChangeId => {
ask_user_input("Enter the new ID (0-253). \n Please note that you have to reboot the servo to take it into account", s, connection.clone(),
id.clone(),
|c, value, _servo_id,conn| {
if value < 254 {
let mut messages= vec!();
for i in 0..=253 {
let servo = Servo::new(i);
let message_to_send = servo.eep_write(WritableEEPAddr::ID(value as u8));
messages.push(message_to_send)
}
send_messages(c, conn.clone(), messages);
} else {
c.add_layer(Dialog::info("Invalid ID it has to be between 0 and 253."))
}
}
);
}
}
});
let mut layout = LinearLayout::new(Orientation::Vertical);
layout.add_child(TextView::new("Choose your message type\n\n"));
layout.add_child(view);
Box::new(Panel::new(layout))
}
fn ask_id(
cursive: &mut Cursive,
id: SharedState<u8>,
connection: SharedState<Option<Box<dyn SerialPort>>>,
) {
let mut id_input = EditView::new();
id_input.set_max_content_width(Some(5));
id_input.set_secret(false);
let cloned_id = id.clone();
id_input.set_on_submit(move |c, data| match data.parse::<u8>() {
Ok(d) => {
cloned_id.replace(d);
c.pop_layer();
c.add_layer(init_main_menu(connection.clone(), cloned_id.clone()));
}
Err(e) => c.add_layer(Dialog::info(format!(
"This is not a valid number. \n {}",
e
))),
});
let mut layout = LinearLayout::new(Orientation::Vertical);
layout.add_child(TextView::new("Enter the id, then press <Enter>"));
layout.add_child(ResizedView::new(
SizeConstraint::Fixed(5),
SizeConstraint::AtMost(1),
id_input,
));
cursive.add_layer(Panel::new(layout))
}
fn init_main_menu(
connection: SharedState<Option<Box<dyn SerialPort>>>,
id: SharedState<u8>,
) -> Box<dyn View> {
let id_str = format!("{:#x}", *id.borrow());
let mut result: SelectView<Action> = SelectView::new().h_align(HAlign::Center);
result.add_item(format!("Select ID | {} selected", id_str), Action::SetId);
result.add_item("Send Message", Action::SendMessage);
result.add_item("Quit", Action::Quit);
result.set_on_submit(move |s, action| match action {
Action::Quit => s.quit(),
Action::SetId => {
s.pop_layer();
ask_id(s, id.clone(), connection.clone());
}
Action::SendMessage => {
s.pop_layer();
s.add_layer(init_message_menu(connection.clone(), id.clone()))
}
});
let mut layout = LinearLayout::new(Orientation::Vertical);
layout.add_child(TextView::new("Choose your action\n------------------").center());
layout.add_child(result);
Box::new(Panel::new(layout))
}
fn initialize_connection<T: Into<String>>(addr: T) -> Result<Box<dyn SerialPort>, Box<dyn Error>> {
let mut settings = SerialPortSettings::default();
settings.baud_rate = 115_200;
let connection = open_with_settings(&addr.into(), &settings);
match connection {
Ok(c) => Ok(c),
Err(e) => Err(e.into()),
}
}
fn make_dialog<T: Into<String>, L: Into<String>, F>(data: T, label: L, f: F) -> Dialog
where
F: 'static + Fn(&mut Cursive),
{
Dialog::around(TextView::new(data))
.button(label, move |s| f(s))
.h_align(HAlign::Center)
}
fn generate_connection_view(
list: Vec<SerialPortInfo>,
id: SharedState<u8>,
state: SharedState<Option<Box<dyn SerialPort>>>,
) -> SelectView<String> {
let mut result: SelectView<String> = SelectView::new().h_align(HAlign::Center);
result.add_all_str(list.into_iter().map(|v| v.port_name));
result.set_on_submit(move |s: &mut Cursive, connection: &String| {
s.pop_layer();
match initialize_connection(connection) {
Ok(c) => {
state.replace(Some(c));
s.add_layer(init_main_menu(state.clone(), id.clone()));
}
Err(e) => {
s.add_layer(
Dialog::around(TextView::new(format!("Connection failed {}", e))).button(
"Close",
|v| {
v.pop_layer();
},
),
);
}
}
});
result
}
fn init_connection_view(
state: SharedState<Option<Box<dyn SerialPort>>>,
id: SharedState<u8>,
) -> Result<SelectView<String>, Box<dyn Error>> {
match available_ports() {
Ok(list) => {
if list.is_empty() {
Err(Box::new(io::Error::new(
io::ErrorKind::NotFound,
"No connection available",
)))
} else {
Ok(generate_connection_view(list, id, state))
}
}
Err(e) => Err(Box::new(e)),
}
}
pub fn init(id_provided: Option<u8>) -> Cursive {
let mut siv = Cursive::new();
let shared_connection: SharedState<Option<Box<dyn SerialPort>>> = Rc::new(RefCell::new(None));
let shared_id: SharedState<u8> = Rc::new(RefCell::new(if let Some(val) = id_provided {
val
} else {
0xFE
}));
match init_connection_view(shared_connection.clone(), shared_id.clone()) {
Ok(view) => {
let mut layer = LinearLayout::new(Orientation::Vertical);
layer.add_child(TextView::new(
"Select your serial connection from the list below : \n \n",
));
layer.add_child(Layer::new(view));
siv.add_layer(Panel::new(layer));
}
Err(e) => siv.add_layer(make_dialog(
format!("Connection error : {}", e),
"Quit",
|s| {
s.quit();
},
)),
};
siv.add_global_callback('q', |s| s.quit());
siv.add_global_callback(Key::Esc, |s| s.quit());
siv
}