C++ | 程序暂停功能
文章目录
- 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 <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>
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 <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include<opencv2/opencv.hpp>
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
命令给程序发送信号,可以自定义 SIGUSR1
和 SIGUSR2
,这样程序就可以随时接收到信号了。
具体kill信号及其设置可参考《Linux信号sigaction / signal》篇。具体代码如下
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <signal.h> /*for signal() and raise()*/
#include <chrono>
#include <thread>
#include <unistd.h>
#include <atomic>
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 脚本包装下,实现空格发送,效果如下:
Reference
Linux信号sigaction / signal