liftof_tui/tabs/
tab_commands.rs

1//! Commands tab
2//! 
3//use chrono::Utc;
4
5use ratatui::prelude::*;
6use ratatui::widgets::{
7    Block,
8    BorderType,
9    Borders,
10    Paragraph,
11    //BarChart,
12    List,
13    ListItem,
14    ListState,
15};
16
17use std::collections::VecDeque;
18use crossbeam_channel::{
19    //unbounded,
20    //Sender,
21    Receiver
22};
23
24use std::sync::Mutex;
25use gondola_core::prelude::*;
26
27use crate::colors::ColorTheme;
28
29#[derive(Debug, Copy, Clone, PartialEq)]
30pub enum CommandTabView {
31  General,
32  LatestConfig
33}
34
35
36pub struct CommandTab<'a> {
37  pub resp_rc            : Receiver<TofPacket>,
38  pub theme              : ColorTheme,
39  pub cmd_sender         : zmq::Socket,
40  // list for the command selector
41  pub cmdl_state         : ListState,
42  pub cmdl_items         : Vec::<ListItem<'a>>,
43  pub cmdl_active        : bool,
44  pub cmdl_selector      : usize,
45  pub allow_commands     : bool,
46  pub active_cmd         : TofCommand,
47  pub commands           : Vec<TofCommand>,
48  pub queue_size         : usize,
49  pub tr_queue           : VecDeque<TofResponse>,
50  pub latest_config      : Arc<Mutex<String>>,
51  pub view               : CommandTabView,
52}
53
54impl CommandTab<'_> {
55  pub fn new<'a>(resp_rc        : Receiver<TofPacket>,
56                 cmd_pub_addr   : String,
57                 theme          : ColorTheme,
58                 allow_commands : bool,
59                 latest_config  : Arc<Mutex<String>>) -> CommandTab<'a> {  
60    let mut ping_cmd        = TofCommand::new();
61    ping_cmd.command_code   = TofCommandCode::Ping;
62    let mut start_cmd       = TofCommand::new();
63    start_cmd.command_code  = TofCommandCode::DataRunStart;
64    let mut stop_cmd        = TofCommand::new();
65    stop_cmd.command_code   = TofCommandCode::DataRunStop;
66    let mut cali_cmd        = TofCommand::new();
67    cali_cmd.command_code   = TofCommandCode::RBCalibration;
68    
69    let mut send_te         = TofCommand::new();
70    send_te.command_code    = TofCommandCode::SendTofEvents;
71    let mut no_send_te      = TofCommand::new();
72    no_send_te.command_code = TofCommandCode::NoSendTofEvents;
73    
74    let mut send_rw         = TofCommand::new();
75    send_rw.command_code    = TofCommandCode::SendRBWaveforms;
76    let mut no_send_rw      = TofCommand::new();
77    no_send_rw.command_code = TofCommandCode::NoSendRBWaveforms;
78
79    let mut kill_cmd        = TofCommand::new();
80    kill_cmd.command_code   = TofCommandCode::Kill;
81
82    let commands = vec![ping_cmd,
83                        start_cmd,
84                        stop_cmd,
85                        cali_cmd,
86                        send_te,
87                        no_send_te,
88                        send_rw,
89                        no_send_rw,
90                        kill_cmd];
91
92    let mut cmd_select_items = Vec::<ListItem>::new();
93    for k in &commands {
94      let this_item = format!("{:?}", k.command_code);
95      cmd_select_items.push(ListItem::new(Line::from(this_item)));
96    } 
97    let ctx = zmq::Context::new();
98    let cmd_sender = ctx.socket(zmq::PUB).expect("Can not create 0MQ PUB socket!"); 
99    if allow_commands {
100      cmd_sender.bind(&cmd_pub_addr).expect("Unable to bind to (PUB) socket!");
101    }
102    //thread::sleep(10*one_second);
103
104    CommandTab {
105      theme          ,
106      resp_rc        ,
107      cmd_sender     ,
108      cmdl_state     : ListState::default(),
109      cmdl_items     : cmd_select_items,
110      cmdl_active    : false,
111      cmdl_selector  : 0,
112      allow_commands : allow_commands,
113      active_cmd     : TofCommand::new(),
114      commands       : commands,
115      queue_size     : 1000,
116      tr_queue       : VecDeque::<TofResponse>::new(),
117      latest_config  : latest_config,
118      view           : CommandTabView::General
119    }
120  }
121  
122  pub fn next_cmd(&mut self) {
123    let i = match self.cmdl_state.selected() {
124      Some(i) => {
125        if i >= self.cmdl_items.len() - 1 {
126          self.cmdl_items.len() - 1
127        } else {
128          i + 1
129        }
130      }
131      None => 0,
132    };
133    self.cmdl_state.select(Some(i));
134    self.active_cmd = self.commands.get(i).unwrap().clone();
135    //info!("Selecting {}", i);
136  }
137
138  pub fn prev_cmd(&mut self) {
139    let i = match self.cmdl_state.selected() {
140      Some(i) => {
141        if i == 0 {
142          0 
143        } else {
144          i - 1
145        }
146      }
147      None => 0,
148    };
149    self.cmdl_state.select(Some(i));
150    self.active_cmd = self.commands.get(i).unwrap().clone();
151  }
152 
153  /// send the selected dommand
154  pub fn send_command(&self) {
155    if !self.allow_commands {
156      error!("To send commands, run program with --send-commands!");
157      return;
158    }
159    let payload = self.active_cmd.pack().to_bytestream();
160    match self.cmd_sender.send(&payload, 0) {
161      Err(err) => {
162        error!("Unable to send command! {err}");
163      },
164      Ok(_) => {
165        info!("TOF cmd {} sent!", self.active_cmd);
166      }
167    }  
168  }
169
170  /// Get the responses from the main program packet distributor
171  pub fn receive_packet(&mut self) -> Result<(), SerializationError> {
172    match self.resp_rc.try_recv() {
173      Err(_err)   => {
174        debug!("Unable to receive ACK TofPacket!");  
175      }
176      Ok(pack)    => {
177        //println!("Received {}", pack);
178        match pack.packet_type {
179          TofPacketType::TofResponse => {
180            let tr : TofResponse = pack.unpack()?;
181            self.tr_queue.push_back(tr);
182            if self.tr_queue.len() > self.queue_size {
183              self.tr_queue.pop_front(); 
184            }
185          }
186          _ => () // we don't care
187        }
188      }
189    }
190    Ok(())
191  }
192
193  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
194    match self.view {
195      CommandTabView::General => {  
196        let main_lo = Layout::default()
197          .direction(Direction::Horizontal)
198          .constraints(
199              [Constraint::Percentage(20), Constraint::Percentage(80)].as_ref(),
200          )
201          .split(*main_window);
202           let par_title_string = String::from("Select Command");
203           let (first, rest) = par_title_string.split_at(1);
204        
205        let par_title = Line::from(vec![
206             Span::styled(
207                 first,
208                 Style::default()
209                     .fg(self.theme.hc)
210                     .add_modifier(Modifier::UNDERLINED),
211             ),
212             Span::styled(rest, self.theme.style()),
213           ]);
214
215        let cmds = Block::default()
216          .borders(Borders::ALL)
217          .style(self.theme.style())
218          .title(par_title)
219          .border_type(BorderType::Plain);
220        
221        let cmd_select_list = List::new(self.cmdl_items.clone()).block(cmds)
222          .highlight_style(self.theme.highlight().add_modifier(Modifier::BOLD))
223          .highlight_symbol(">>")
224          .repeat_highlight_symbol(true);
225        
226        match self.cmdl_state.selected() {
227          None    => {
228            self.cmdl_selector = 0;
229          },
230          Some(cmd_id) => {
231            // entry 0 is for all paddles
232            let selector =  cmd_id;
233            if self.cmdl_selector != selector {
234              //self.paddle_changed = true;
235              //self.init_histos();
236              self.cmdl_selector = selector;
237            } else {
238              //self.paddle_changed = false;
239            }
240          },
241        }
242        if self.allow_commands {
243          frame.render_stateful_widget(cmd_select_list, main_lo[0], &mut self.cmdl_state );
244        }
245      } 
246      CommandTabView::LatestConfig => {
247        let main_lo = Layout::default()
248          .direction(Direction::Horizontal)
249          .constraints(
250              [Constraint::Percentage(10),
251               //Constraint::Percentage(20),
252               Constraint::Percentage(90)].as_ref(),
253          )
254          .split(*main_window);
255        //println!("CMD VIEW {}", self.latest_config);
256        //let latest_config_view = *self.latest_config;
257        match self.latest_config.lock() {
258          Ok(latest_config_view) => {
259            let liftof_view = Paragraph::new(&*(*latest_config_view))
260              .style(self.theme.style())
261              .alignment(Alignment::Left)
262              //.scroll((5, 10))
263              .block(
264              Block::default()
265                .borders(Borders::ALL)
266                .style(self.theme.style())
267                .title("Latest config")
268                .border_type(BorderType::Rounded),
269            );
270            frame.render_widget(liftof_view, main_lo[1]);
271          }
272          Err(err) => {
273            error!("Can not lock {err}");
274          }
275        }
276      }
277    }
278  }
279} // end impl
280