void display_thread( sampbuf_sync_t & sampbuf_sync, global_thread_data_t & global_thread_data, tracked_cell_list_t & tracked_cell_list, bool & expert_mode ) { global_thread_data.display_thread_id=syscall(SYS_gettid); // Initialize the curses screen initscr(); start_color(); use_default_colors(); if (LINES<LINES_MIN) { //endwin(); cerr << "Error: resize window so it has at least " << LINES_MIN << " rows and " << COLS_MIN << " columns" << endl; ABORT(-1); } if (COLS<COLS_MIN) { //endwin(); cerr << "Error: resize window so it has at least " << LINES_MIN << " rows and " << COLS_MIN << " columns" << endl; ABORT(-1); } // Do not echo input chars to screen. noecho(); // Turn off display of cursor. curs_set(0); // Enable function keys, arrow keys, etc. keypad(stdscr,true); //init_color(COLOR_BLACK,0,0,0); //init_pair(3,COLOR_GREEN,-1); init_pair(RED,COLOR_RED,-1); init_pair(GREEN,COLOR_GREEN,-1); init_pair(YELLOW,COLOR_YELLOW,-1); init_pair(BLUE,COLOR_BLUE,-1); init_pair(MAGENTA,COLOR_MAGENTA,-1); init_pair(CYAN,COLOR_CYAN,-1); init_pair(WHITE,COLOR_WHITE,-1); // Reduce delay between pressing 'Esc' and having this program // respond. // In ms. set_escdelay(50); // Start, end, and size of cell info display area. const uint16 CELL_DISP_START_ROW=2; const uint16 CELL_DISP_END_ROW=LINES-6; const uint16 CELL_DISP_N_ROWS=CELL_DISP_END_ROW-CELL_DISP_START_ROW+1; // Record where a particular cell was most recently printed. ivec disp_history(504); disp_history=-1; vector <row_desc_t> row_desc(CELL_DISP_N_ROWS); //bvec row_occupied(CELL_DISP_N_ROWS); // Settings that the user can change in realtime bool auto_refresh=true; double refresh_delay_sec=1; bool fifo_status=false; bool avg_values=true; disp_mode_t disp_mode=STD; int8 detail_type=0; #define N_DETAILS 2 // Send control chars directly to program. //cbreak(); halfdelay(round_i(refresh_delay_sec*10.0)); int16 highlight_row=-1; while (true) { clear(); //attron(COLOR_PAIR(3)); // No matter which display mode we are in, always update row_disc // and highlight_row. bool all_cells_displayed=true; for (uint16 t=0;t<CELL_DISP_N_ROWS;t++) { row_desc[t].occupied=false; row_desc[t].n_id_cell=-1; row_desc[t].duplex_mode=-3; row_desc[t].port_num=-2; //row_occupied(t)=false; } move(2,0); { boost::mutex::scoped_lock lock(tracked_cell_list.mutex); list <tracked_cell_t *>::iterator it=tracked_cell_list.tracked_cells.begin(); vector <tracked_cell_t *> pass2_display; while (it!=tracked_cell_list.tracked_cells.end()) { tracked_cell_t & tracked_cell=(*(*it)); // Deadlock possible??? boost::mutex::scoped_lock lock1(tracked_cell.fifo_mutex); boost::mutex::scoped_lock lock2(tracked_cell.meas_mutex); uint8 n_rows_required=tracked_cell.n_ports+2; // If this cell has been displayed before, try to display it // in the same location. if (disp_history(tracked_cell.n_id_cell)!=-1) { uint16 row_desired=disp_history(tracked_cell.n_id_cell); if (will_fit(row_desc,row_desired,n_rows_required-1)) { if (disp_mode==STD) display_cell(tracked_cell,row_desired+CELL_DISP_START_ROW,fifo_status,avg_values,expert_mode); set_occupied(tracked_cell,row_desc,row_desired); } else { pass2_display.push_back(&tracked_cell); } } else { pass2_display.push_back(&tracked_cell); } ++it; } // Display cells that cannot be displayed where they have previously // been displayed. for (uint8 t=0;t<pass2_display.size();t++) { tracked_cell_t & tracked_cell=(*pass2_display[t]); // Deadlock possible??? boost::mutex::scoped_lock lock1(tracked_cell.fifo_mutex); boost::mutex::scoped_lock lock2(tracked_cell.meas_mutex); uint8 n_rows_required=tracked_cell.n_ports+2; bool placed=false; for (uint16 k=0;k<CELL_DISP_N_ROWS-n_rows_required+1;k++) { if (will_fit(row_desc,k,n_rows_required)) { if (disp_mode==STD) display_cell(tracked_cell,k+CELL_DISP_START_ROW,fifo_status,avg_values,expert_mode); set_occupied(tracked_cell,row_desc,k); disp_history(tracked_cell.n_id_cell)=k; placed=true; break; } } if (!placed) all_cells_displayed=false; } } // Always highlight a row, if a cell exists. if (highlight_row==-1) { for (uint16 t=0;t<CELL_DISP_N_ROWS;t++) { if (row_desc[t].n_id_cell!=-1) { highlight_row=t; break; } } } if (disp_mode==STD) { // Header and footer { stringstream ss; ss << "LTE-Tracker www.evrytania.com; 1.0 to " << MAJOR_VERSION << "." << MINOR_VERSION << "." << PATCH_LEVEL << ": OpenCL/TDD/HACKRF/bladeRF/ext-LNB added by Jiao.([email protected])."; move(0,0); attron(COLOR_PAIR(CYAN)); print_center(ss.str()); attroff(COLOR_PAIR(CYAN)); } move(CELL_DISP_END_ROW+1,0); if (!all_cells_displayed) { attron(COLOR_PAIR(YELLOW)); printw("Warning: some tracked cells could not be displayed! (screen has too few rows)\n"); attroff(COLOR_PAIR(YELLOW)); } else { printw("\n"); } if (global_thread_data.cell_seconds_dropped()||global_thread_data.raw_seconds_dropped()) { attron(COLOR_PAIR(RED)); printw("[dropped cell/raw data: %i/%i s]\n",global_thread_data.cell_seconds_dropped(),global_thread_data.raw_seconds_dropped()); attroff(COLOR_PAIR(RED)); } else { printw("\n"); } if (expert_mode) { if (avg_values) { printw("Showing average measurements\n"); } else { printw("Showing instant measurements\n"); } } else { printw("\n"); } attron(COLOR_PAIR(MAGENTA)); printw("[InitFO: %5.0lfHz][TrackFO: %5.2lfHz]",global_thread_data.initial_frequency_offset(), global_thread_data.frequency_offset()-global_thread_data.initial_frequency_offset()); //attron(A_BOLD); // printw("[searcher delay: %.1lf s]",global_thread_data.searcher_cycle_time()); printw("[SearcherDelay: %4.2fs][rqst %5.2lfMHz][prog %5.2lfMHz][fs %5.2lfMHz][k %5.3lf][twist %d]",global_thread_data.searcher_cycle_time(), global_thread_data.fc_requested/1e6, global_thread_data.fc_programmed/1e6, global_thread_data.fs_programmed/1e6, global_thread_data.k_factor(), global_thread_data.sampling_carrier_twist()); //attroff(A_BOLD); if (fifo_status) { boost::mutex::scoped_lock lock(sampbuf_sync.mutex); stringstream ss; uint8 w=ceil(log10(sampbuf_sync.fifo_peak_size)); ss << "[inp buf: " << setw(w) << sampbuf_sync.fifo.size() << "/" << sampbuf_sync.fifo_peak_size << "]"; //attron(A_BOLD); printw("%s\n",ss.str().c_str()); //attroff(A_BOLD); } else { printw("\n"); } attroff(COLOR_PAIR(MAGENTA)); /* printw("Searcher thread ID: %5i\n",global_thread_data.searcher_thread_id); printw("Producer thread ID: %5i\n",global_thread_data.producer_thread_id); printw("Main thread ID: %5i\n",global_thread_data.main_thread_id); printw("Display thread ID: %5i\n\n",global_thread_data.display_thread_id); */ attron(COLOR_PAIR(GREEN)); //printw("Up/down = select, left/right = details, (h)elp, (q)uit\n"); print_right("right arrow > TF"); print_center("(h)elp, (q)uit"); attroff(COLOR_PAIR(GREEN)); //attroff(A_BOLD); // Highlight one of the rows if (highlight_row!=-1) { if (row_desc[highlight_row].n_id_cell==-1) { highlight_row=-1; } else { move(CELL_DISP_START_ROW+highlight_row,0); chgat(-1,A_REVERSE,0,NULL); } } } else { // Zoom into port details // Shortcuts const int16 & n_id_cell=row_desc[highlight_row].n_id_cell; const int8 & duplex_mode=row_desc[highlight_row].duplex_mode; const int8 & port_num=row_desc[highlight_row].port_num; { boost::mutex::scoped_lock lock(tracked_cell_list.mutex); list <tracked_cell_t *>::iterator it=tracked_cell_list.tracked_cells.begin(); vector <tracked_cell_t *> pass2_display; bool cell_found=false; while (it!=tracked_cell_list.tracked_cells.end()) { tracked_cell_t & tracked_cell=(*(*it)); if ((tracked_cell.n_id_cell==n_id_cell)&&((port_num==-1)||(port_num<tracked_cell.n_ports))) { cell_found=true; break; } ++it; } if (cell_found) { tracked_cell_t & tracked_cell=(*(*it)); boost::mutex::scoped_lock lock2(tracked_cell.meas_mutex); if (detail_type==0) { // Plot transfer function mag vec trace; if (port_num==-1) { trace=db10(sqr(tracked_cell.sync_ce)); } else { trace=db10(sqr(tracked_cell.ce.get_row(port_num))); } plot_trace( // Trace desc. trace,itpp_ext::matlab_range(0.0,71.0), // X axis 0,71,12,NAN, // Y axis -50,0,10, // UL corner 1,0, // LR corner LINES-2,0+72+4, true ); move(0,0); stringstream ss; if (duplex_mode==0) ss << "FDD Cell " << n_id_cell; else ss << "TDD Cell " << n_id_cell; if (port_num==-1) { ss << " Sync channel magnitude\n"; } else { ss << " port " << port_num << " magnitude\n"; } attron(COLOR_PAIR(CYAN)); print_center(ss.str()); attroff(COLOR_PAIR(CYAN)); move(LINES-1,0); attron(COLOR_PAIR(GREEN)); print_center("(h)elp, (q)uit"); print_right("right arrow > phase resp"); print_left("top menu < left arrow"); attroff(COLOR_PAIR(GREEN)); } else if (detail_type==1) { // Plot transfer function phase vec trace; double mean_ang; if (port_num==-1) { trace=arg(tracked_cell.sync_ce); mean_ang=arg(sum(exp(J*trace(5,66)))); trace=arg(tracked_cell.sync_ce*exp(J*-mean_ang)); trace(0)=NAN; trace(1)=NAN; trace(2)=NAN; trace(3)=NAN; trace(4)=NAN; trace(67)=NAN; trace(68)=NAN; trace(69)=NAN; trace(70)=NAN; trace(71)=NAN; } else { trace=arg(tracked_cell.ce.get_row(port_num)); mean_ang=arg(sum(exp(J*trace))); trace=arg(tracked_cell.ce.get_row(port_num)*exp(J*-mean_ang)); } trace=trace/pi*180; plot_trace( // Trace desc. trace,itpp_ext::matlab_range(0.0,71.0), // X axis 0,71,12,(mean_ang+pi)/(2*pi)*71, // Y axis -40,40,10, // UL corner 1,0, // LR corner LINES-2,0+72+4, false ); move(0,0); stringstream ss; if (duplex_mode==0) ss << "FDD Cell " << n_id_cell; else ss << "TDD Cell " << n_id_cell; if (port_num==-1) { ss << " Sync channel phase\n"; } else { ss << " port " << port_num << " phase\n"; } attron(COLOR_PAIR(CYAN)); print_center(ss.str()); attroff(COLOR_PAIR(CYAN)); move(LINES-1,0); attron(COLOR_PAIR(GREEN)); print_center("(h)elp, (q)uit"); //print_right("right arrow > phase response"); print_left("mag resp < left arrow"); attroff(COLOR_PAIR(GREEN)); } else if (detail_type==2) { // Frequency domain autocorrelation const vec trace=abs(tracked_cell.ac_fd); plot_trace( // Trace desc. trace,itpp_ext::matlab_range(0.0,11.0), // X axis 0,11,2,NAN, // Y axis 0,1.2,.5, // UL corner 0,0, // LR corner LINES-3,0+72+4, true ); move(LINES-2,0); if (duplex_mode==0) printw("FDD Cell ID: %i\n",n_id_cell); else printw("TDD Cell ID: %i\n",n_id_cell); printw("Frequency domain channel autocorrelation function. x-axis spans 1.26MHz\n"); } else if (detail_type==3) { // Time domain autocorrelation const vec trace=abs(tracked_cell.ac_td); plot_trace( // Trace desc. trace,itpp_ext::matlab_range(0.0,71.0)*.0005, // X axis 0,71*.0005,.010,NAN, // Y axis 0,3.2,.5, // UL corner 0,0, // LR corner LINES-3,0+72+4, true ); move(LINES-2,0); if (duplex_mode == 0) printw("FDD Cell ID: %i\n",n_id_cell); else printw("TDD Cell ID: %i\n",n_id_cell); printw("Time domain channel autocorrelation function. x-axis spans 35.5ms\n"); } } else { move(1,0); printw("Cell is no longer being tracked. Press left arrow to go back!\n"); } } } refresh(); // Handle keyboard input. // Previous halfdelay() function ensures that this will not block. int ch=getch(); switch (ch) { case 'q': case 'Q': if (global_thread_data.dev_use() == dev_type_t::RTLSDR) { #ifdef HAVE_RTLSDR if ( global_thread_data.rtlsdr_dev()!=NULL ) { rtlsdr_close( global_thread_data.rtlsdr_dev() ); global_thread_data.rtlsdr_dev(NULL); } #endif } else if (global_thread_data.dev_use() == dev_type_t::HACKRF) { #ifdef HAVE_HACKRF if (global_thread_data.hackrf_dev()!=NULL) { hackrf_close( global_thread_data.hackrf_dev() ); global_thread_data.hackrf_dev(NULL); hackrf_exit(); } #endif } else if (global_thread_data.dev_use() == dev_type_t::BLADERF) { #ifdef HAVE_BLADERF if (global_thread_data.bladerf_dev()!=NULL) { bladerf_close( global_thread_data.bladerf_dev() ); global_thread_data.bladerf_dev(NULL); } #endif } ABORT(-1); break; case 'r': case 'R': auto_refresh=!auto_refresh; if (auto_refresh) { halfdelay(1+round_i(refresh_delay_sec*10.0)); } else { cbreak(); } break; case '-': case '_': refresh_delay_sec=MIN(15,refresh_delay_sec*1.5); halfdelay(round_i(refresh_delay_sec*10.0)); break; case '+': case '=': refresh_delay_sec=MAX(0.001,refresh_delay_sec/1.5); halfdelay(round_i(refresh_delay_sec*10.0)); break; case 'f': case 'F': fifo_status=!fifo_status; break; case 'a': case 'A': avg_values=!avg_values; break; case 27: // Escape key disp_mode=STD; break; case 'k': case 'K': case KEY_UP: for (int16 t=highlight_row-1;t>=0;t--) { if (row_desc[t].n_id_cell!=-1) { highlight_row=t; break; } } break; case 'j': case 'J': case KEY_DOWN: for (uint16 t=highlight_row+1;t<CELL_DISP_N_ROWS;t++) { if (row_desc[t].n_id_cell!=-1) { highlight_row=t; break; } } break; case 'l': case 'L': case KEY_RIGHT: case '\n': if (disp_mode==STD) { disp_mode=DETAIL; detail_type=0; } else { detail_type=MIN(detail_type+1,N_DETAILS-1); } break; case KEY_LEFT: if (disp_mode==DETAIL) { if (detail_type==0) { disp_mode=STD; } else { detail_type-=1; } } break; case 'h': case 'H': clear(); move(0,0); if (disp_mode==STD) { print_center("LTE-Tracker main display help menu\n"); printw("\n"); //printw("Display frequency response:\n"); printw("up/down keys move selection bar\n"); printw("'Enter' or right-arrow displays the frequency response\n"); printw("\n"); printw("P0 : SNR received from port 0\n"); printw("S : SNR of the PSS/SSS synchronization channel\n"); printw("TO : frame timing referenced to the dongle's timescale of 19200 samples\n"); printw("FO : dongle's current residual frequency offset\n"); printw("\n"); printw("'q' exits the program\n"); printw("+/- increases or decreases screen refresh rate\n"); printw("'r' turns automatic screen refresh on/off\n"); printw("'f' displays the current status of the fifo's\n"); printw("Esc always returns to the top menu\n"); printw("\n"); printw("Health is a measure of the amount of time since the last time the MIB\n"); printw("was successfully decoded for that cell. Cells are dropped when health\n"); printw("reaches 0.\n"); printw("\n"); printw("searcher delay is the amount of time (in seconds) that it takes for the\n"); printw("searcher to complete a full search cycle\n"); printw("\n"); printw("Press any key to exit help screen!\n"); cbreak(); getch(); if (auto_refresh) { halfdelay(round_i(refresh_delay_sec*10.0)); } } else { print_center("LTE-Tracker mag/phase response help menu\n"); printw("\n"); printw("up/down keys change port and cell ID\n"); printw("left/right keys change to phase response and main menu\n"); printw("\n"); printw("'q' exits the program\n"); printw("+/- increases or decreases screen refresh rate\n"); printw("'r' turns automatic screen refresh on/off\n"); printw("Esc always returns to the top menu\n"); printw("\n"); printw("For the phase plot, the average phase offset is removed from the trace, but\n"); printw("the value of this phase offset is indicated by an asterisk on the x axis.\n"); printw("\n"); printw("Press any key to exit help screen!\n"); cbreak(); getch(); if (auto_refresh) { halfdelay(round_i(refresh_delay_sec*10.0)); } } break; } } }