mod display_string;
mod messages;
mod stateful_list;
mod visualization_helpers;
mod visualizator_app;
use std::{
error::Error,
io,
time::{Duration, Instant},
};
use log::LevelFilter;
use log4rs::{
append::console::ConsoleAppender,
config::{Appender, Root},
Config,
};
use messages::{DataUpdate, RosCommand};
use visualizator_app::VisualizatorApp;
use mio_06::{Events, Poll, PollOpt, Ready, Token};
use mio_extras::channel as mio_channel;
use rustdds::ros2::{builtin_datatypes::NodeInfo, RosParticipant};
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use tui::{
backend::{Backend, CrosstermBackend},
Terminal,
};
const ROS2_COMMAND_TOKEN: Token = Token(1000);
const ROS2_NODE_RECEIVED_TOKEN: Token = Token(1001);
const TOPIC_UPDATE_TIMER_TOKEN: Token = Token(1002);
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
mut app: VisualizatorApp,
tick_rate: Duration,
command_sender: mio_channel::SyncSender<RosCommand>,
) -> io::Result<()> {
let mut last_tick = Instant::now();
loop {
while let Ok(data) = &mut app.receiver.try_recv() {
match data {
DataUpdate::NewROSParticipantFound { participant } => {
&mut app.add_new_ros_participant(participant.clone())
}
DataUpdate::DiscoveredTopics { topics } => &mut app.set_discovered_topics(topics.clone()),
DataUpdate::DiscoveredNodes { nodes } => &mut app.set_node_infos(nodes.clone()),
};
}
terminal.draw(|f| app.ui(f))?;
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if app.handle_user_input(&timeout) {
let _send_command_result = command_sender.send(RosCommand::StopRosLoop);
return Ok(());
}
if last_tick.elapsed() >= tick_rate {
last_tick = Instant::now();
}
}
}
fn ros2_loop(
sender: mio_channel::SyncSender<DataUpdate>,
command_receiver: mio_channel::Receiver<RosCommand>,
) {
let mut ros_participant = RosParticipant::new().unwrap();
let poll = Poll::new().unwrap();
let mut update_timer = mio_extras::timer::Timer::default();
update_timer.set_timeout(Duration::from_secs(1), ());
poll
.register(
&ros_participant,
ROS2_NODE_RECEIVED_TOKEN,
Ready::readable(),
PollOpt::edge(),
)
.unwrap();
poll
.register(
&command_receiver,
ROS2_COMMAND_TOKEN,
Ready::readable(),
PollOpt::edge(),
)
.unwrap();
poll
.register(
&update_timer,
TOPIC_UPDATE_TIMER_TOKEN,
Ready::readable(),
PollOpt::edge(),
)
.unwrap();
loop {
let mut events = Events::with_capacity(100);
poll.poll(&mut events, None).unwrap();
for event in events.iter() {
if event.token() == ROS2_NODE_RECEIVED_TOKEN {
let new_participants = ros_participant.handle_node_read();
let mut node_infos: Vec<NodeInfo> = ros_participant
.get_all_discovered_local_ros_node_infos()
.values()
.cloned()
.collect();
let node_infos_external_vec: Vec<Vec<NodeInfo>> = ros_participant
.get_all_discovered_external_ros_node_infos()
.values()
.cloned()
.collect();
let mut node_infos_external = vec![];
for mut v in node_infos_external_vec {
node_infos_external.append(&mut v);
}
node_infos.append(&mut node_infos_external);
let _res_nodes_send = sender.send(DataUpdate::DiscoveredNodes { nodes: node_infos });
for participant in new_participants {
let _res_participant_send =
sender.send(DataUpdate::NewROSParticipantFound { participant });
}
let new_topics = ros_participant.discovered_topics();
let _res_topics_send = sender.send(DataUpdate::DiscoveredTopics { topics: new_topics });
} else if event.token() == ROS2_COMMAND_TOKEN {
match command_receiver.try_recv() {
Ok(command) => {
match command {
RosCommand::StopRosLoop => {
return;
}
}
}
Err(_e) => {}
}
} else if event.token() == TOPIC_UPDATE_TIMER_TOKEN {
let new_topics = ros_participant.discovered_topics();
let _res_topics_send = sender.send(DataUpdate::DiscoveredTopics { topics: new_topics });
update_timer.set_timeout(Duration::from_secs(1), ());
}
}
}
}
fn configure_logging() {
log4rs::init_file(
"examples/ros_visualizer/logging-config.yaml",
log4rs::config::Deserializers::default(),
)
.unwrap_or_else(|e| {
match e.downcast_ref::<io::Error>() {
Some(os_err) if os_err.kind() == io::ErrorKind::NotFound => {
println!("No config file found in current working directory.");
let stdout = ConsoleAppender::builder().build();
let conf = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.build(Root::builder().appender("stdout").build(LevelFilter::Error))
.unwrap();
log4rs::init_config(conf).unwrap();
}
other_error => panic!("Config problem: {other_error:?}"),
}
});
}
fn main() -> Result<(), Box<dyn Error>> {
configure_logging();
enable_raw_mode().unwrap();
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).unwrap();
let (sender, receiver) = mio_channel::sync_channel::<DataUpdate>(10);
let (command_sender, command_receiver) = mio_channel::sync_channel::<RosCommand>(10);
let jhandle = std::thread::spawn(move || ros2_loop(sender, command_receiver));
let visualizor_app = VisualizatorApp::new(receiver);
let tick_rate = Duration::from_millis(250);
let _res = run_app(&mut terminal, visualizor_app, tick_rate, command_sender);
jhandle.join().unwrap();
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}