C++ | 程序暂停功能

C++ | 程序暂停功能

C++ | 程序暂停功能_第1张图片

文章目录

  • C++ | 程序暂停功能
    • 初衷
      • rosbag 播包暂停功能源码
    • 识别键盘输入(需输入Enter)
    • 识别键盘输入(无需输入Enter)
    • opencv waitKey函数
    • kill 信号
      • 包装成空格命令
    • Reference
    • [C/C++ 获取键盘事件](https://www.runoob.com/w3cnote/c-get-keycode.html "C/C++ 获取键盘事件")
      • >>>>> 欢迎关注公众号【三戒纪元】 <<<<<

初衷

想实现一个像rosbag播包一样的功能,按下空格键,程序就暂停了,可以根据三维界面进行观察

rosbag 播包暂停功能源码

    while ((paused_ || delayed_ || !time_publisher_.horizonReached()) && node_handle_.ok())
    {
        bool charsleftorpaused = true;
        while (charsleftorpaused && node_handle_.ok())
        {
            ros::spinOnce();

            if (pause_change_requested_)
            {
              processPause(requested_pause_state_, horizon);
              pause_change_requested_ = false;
            }

            switch (readCharFromStdin()){
            case ' ': // 空格暂停
                processPause(!paused_, horizon);
                break;
            case 's': // 单步调试
                if (paused_) {
                    time_publisher_.stepClock();

                    ros::WallDuration shift = ros::WallTime::now() - horizon ;
                    paused_time_ = ros::WallTime::now();

                    time_translator_.shift(ros::Duration(shift.sec, shift.nsec));

                    horizon += shift;
                    time_publisher_.setWCHorizon(horizon);
            
                    (pub_iter->second).publish(m);

                    printTime();
                    return;
                }
                break;
            case 't':
                pause_for_topics_ = !pause_for_topics_;
                break;
            case EOF:
                if (paused_)
                {
                    printTime();
                    time_publisher_.runStalledClock(ros::WallDuration(.1));
                    ros::spinOnce();
                }
                else if (delayed_)
                {
                    printTime();
                    time_publisher_.runStalledClock(ros::WallDuration(.1));
                    ros::spinOnce();
                    // You need to check the rate here too.
                    if(rate_control_sub_ == NULL || (time_publisher_.getTime() - last_rate_control_).toSec() <= options_.rate_control_max_delay) {
                        delayed_ = false;
                        // Make sure time doesn't shift after leaving delay.
                        ros::WallDuration shift = ros::WallTime::now() - paused_time_;
                        paused_time_ = ros::WallTime::now();
         
                        time_translator_.shift(ros::Duration(shift.sec, shift.nsec));

                        horizon += shift;
                        time_publisher_.setWCHorizon(horizon);
                    }
                }
                else
                    charsleftorpaused = false;
            }
        }

        printTime();
        time_publisher_.runClock(ros::WallDuration(.1));
        ros::spinOnce();
    }

这里可以看出rosbag 也是用了2重 while循环进行的,暂停功能在 line 14readCharFromStdin函数,该函数源码:

int Player::readCharFromStdin() {
#ifdef __APPLE__
    fd_set testfd;
    FD_COPY(&stdin_fdset_, &testfd);
#elif !defined(_MSC_VER)
    fd_set testfd = stdin_fdset_;
#endif

#if defined(_MSC_VER)
    DWORD events = 0;
    INPUT_RECORD input_record[1];
    DWORD input_size = 1;
    BOOL b = GetNumberOfConsoleInputEvents(input_handle, &events);
    if (b && events > 0)
    {
        b = ReadConsoleInput(input_handle, input_record, input_size, &events);
        if (b)
        {
            for (unsigned int i = 0; i < events; ++i)
            {
                if (input_record[i].EventType & KEY_EVENT & input_record[i].Event.KeyEvent.bKeyDown)
                {
                    CHAR ch = input_record[i].Event.KeyEvent.uChar.AsciiChar;
                    return ch;
                }
            }
        }
    }
    return EOF;
#else
    timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 0;
    if (select(maxfd_, &testfd, NULL, NULL, &tv) <= 0)
        return EOF;
    return getc(stdin);
#endif
}

核心在line 34 使用了select函数,select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,此处作为延时函数使用。

getc就是获取字符串,如果获取到空格,则暂停。

识别键盘输入(需输入Enter)

#include 
#include 
#include 

#include 

static struct termios initial_settings, new_settings;
static int peek_character = -1;

void init_keyboard(void);
void close_keyboard(void);
int kbhit(void);
int readch(void);

void init_keyboard() {
  tcgetattr(0, &initial_settings);
  new_settings = initial_settings;
  new_settings.c_lflag |= ICANON;
  new_settings.c_lflag |= ECHO;
  new_settings.c_lflag |= ISIG;
  new_settings.c_cc[VMIN] = 1;
  new_settings.c_cc[VTIME] = 0;
  tcsetattr(0, TCSANOW, &new_settings);
}

void close_keyboard() { tcsetattr(0, TCSANOW, &initial_settings); }

int kbhit() {
  unsigned char ch;
  int nread;

  if (peek_character != -1) return 1;
  new_settings.c_cc[VMIN] = 0;
  tcsetattr(0, TCSANOW, &new_settings);
  nread = read(0, &ch, 1);
  new_settings.c_cc[VMIN] = 1;
  tcsetattr(0, TCSANOW, &new_settings);
  if (nread == 1) {
    peek_character = ch;
    return 1;
  }
  return 0;
}

int readch() {
  char ch;
  if (peek_character != -1) {
    ch = peek_character;
    peek_character = -1;
    return ch;
  }
  read(0, &ch, 1);
  return ch;
}

int main() {
  init_keyboard();
  while (1) {
    kbhit();
    printf("\n%d\n", readch());
  }
  close_keyboard();
  return 0;
}

每次输入完空格或者其他按键,必须按回车Enter,所以结果中都有回车(10)

a

97

10
q

113

10
c 

99

10
j

106

10

识别键盘输入(无需输入Enter)

#include 
#include 
#include 
#include 
#include

int in = -1;

void scanKeyboard()
{
    struct termios new_settings;
    struct termios stored_settings;
    tcgetattr(0,&stored_settings);
    new_settings = stored_settings;
    new_settings.c_lflag &= (~ICANON);
    new_settings.c_cc[VTIME] = 0;
    tcgetattr(0,&stored_settings);
    new_settings.c_cc[VMIN] = 1;
    tcsetattr(0,TCSANOW,&new_settings);
     
    in = getchar();
    std::cout << "00 in: " << in << std::endl;
    tcsetattr(0,TCSANOW,&stored_settings);
    return;
}

int main()
{
      while (1) {
        std::cout << "-- -- -- -- -- -- -- -- -- -- " << std::endl;
        sleep(3);
        scanKeyboard(); // int key = cv::waitKey(30) & 0xff;
        std::cout << "main in: " << in << std::endl;
        std::mutex mtx;
        std::unique_lock<std::mutex> lck(mtx);
        std::condition_variable cond;
        while (cond.wait_for(lck, std::chrono::seconds(2)) ==
               std::cv_status::timeout) {
          std::cout << "\nTime-Out: 2 second:";
          std::cout << "\nPlease enter the input:";
          break;
        }
    }
    std::cout << "out!" << std::endl;
    return 0;
    }

运行程序结果:

 00 in: 32 # 输入空格
main in: 32
-- -- -- -- -- -- -- -- -- -- 
f00 in: 102 # 输入 f
main in: 102
-- -- -- -- -- -- -- -- -- -- 
r00 in: 114  # 输入 r
main in: 114
-- -- -- -- -- -- -- -- -- -- 
a00 in: 97  # 输入 a
main in: 97
-- -- -- -- -- -- -- -- -- -- 
n00 in: 110  # 输入 n
main in: 110
-- -- -- -- -- -- -- -- -- -- 
d00 in: 100  # 输入 d
main in: 100
-- -- -- -- -- -- -- -- -- -- 
y00 in: 121  # 输入 y
main in: 121
-- -- -- -- -- -- -- -- -- --

opencv waitKey函数

使用opencv 的waitKey函数是可以在有限的时间内,监听键盘按键,如果没有按下键盘,则继续,如果按下键盘则特殊处理。

int key = cv::waitKey(5)

但是该函数只能在 OpenCV的GUI界面才可以使用,例如imshow()创建的创建的窗口上,waitKey才是有效的。

而在终端控制台上是没有用的。

kill 信号

回到问题本身,根本就是我通过终端,发送一个信号给程序,程序接收到了,然后改变运行的代码;我再次发送时,程序又接收到了,再恢复代码。

因此,可以通过linux中的kill 命令给程序发送信号,可以自定义 SIGUSR1SIGUSR2,这样程序就可以随时接收到信号了。

具体kill信号及其设置可参考《Linux信号sigaction / signal》篇。具体代码如下

#include 
#include 
#include 
#include  /*for signal() and raise()*/
#include 
#include 
#include 


#include 

std::atomic<bool> g_pause;

void SigPause(int32_t signum) {
  if (signum != SIGUSR2) {
    return;
  }

  if (!g_pause.load()) {
    g_pause.store(true);
    std::cout << "Pause success. sleep 3 seconds." << std::endl;
  } else {
    g_pause.store(false);
    std::cout << "continue." << std::endl;
  }
}

int main(){
  g_pause.store(false);
  signal(SIGUSR2, SigPause);

  while (1) {
    while (g_pause.load())
    {
      sleep(3);
      std::cout << "--- --- sleep --- ---" << std::endl;
    }
    std::cout << "--- --- work --- ---" << std::endl;
    sleep(3);
  }
  return 0;
}

包装成空格命令

因为发送kill信号没有空格来得方便,所以通过 shell 脚本包装下,实现空格发送,效果如下:

C++ | 程序暂停功能_第2张图片

Reference

Linux信号sigaction / signal

C/C++ 获取键盘事件

>>>>> 欢迎关注公众号【三戒纪元】 <<<<<

你可能感兴趣的:(C++,c++)