0

I have a piece of code in which I want to read a character from the terminal. The goal is to give keyboard input for moving a robot.

If there is no input in the terminal , the code should return -1, which it does, but even when I do give inputs, it always returns -1 with errno set to EAGAIN. According to the documentation, it means: no data available. Does anyone has an idea why is that happening?

Here is the code, reduced to a minimal version:


#include <termios.h>
#include <cstdio>

int getch()
{
    int ch;
    struct termios oldt;
    struct termios newt;

    // Store old settings, and copy to new settings
    tcgetattr(0, &oldt);
    newt = oldt;

    // Make required changes and apply the settings
    newt.c_lflag &= ~(ICANON | ECHO);
    newt.c_iflag |= IGNBRK;
    newt.c_iflag &= ~(INLCR | ICRNL | IXON | IXOFF);
    newt.c_lflag &= ~(ICANON | ECHO | ECHOK | ECHOE | ECHONL | ISIG | IEXTEN);
    newt.c_cc[VMIN] = 0;
    newt.c_cc[VTIME] = 1;
    tcsetattr(fileno(stdin), TCSANOW, &newt);

    // Get the current character
    ch = getchar();

    // Reapply old settings
    tcsetattr(0, TCSANOW, &oldt);

    return ch;
}

int main(int argc, char **argv)
{

    long count = 0;

    while (true)
    {

        int ch = 0;

        ch = getch();

        printf("%ld\n", count++);
        printf("char = %d\n\n", ch);

        switch (ch)
        {
        case 'q':
            printf("already quit!\n");
            return 0;

        case 'w':
            printf("move forward!\n");
            break;

        case 's':
            printf("move backward!\n");
            break;

        case 'a':
            printf("move left!\n");
            break;

        case 'd':
            printf("move right!\n");
            break;

        case 'j':
            printf("turn left!\n");
            break;

        case 'l':
            printf("turn right!\n");
            break;

        default:
            printf("Stop!\n");
            break;
        }

    }

    return 0;
}

I precise that the code works when I change VMIN and VTIME: it can read the inputs from the terminal, but the problem is that then it blocks and if I do not give any input, it doesn't exit, while the expected result is that it outputs -1. I'm on Ubuntu 20.04, using gnome-terminal. Thanks for any help!

What I did, what I expected to happen, what actually happens: I tried to run my code and expected that when I hit a key of my keyboard, it returns the key I pressed, but instead, it always returns -1.

S07
  • 1
  • 1
  • Instead of just a code fragment, you need to post a [minimal, reroducible example](https://stackoverflow.com/help/minimal-reproducible-example). But from what you have shown so far looks like unreliable code to accomplish your goal. Your code fragment would have to be synchronized with user input, and that's nearly impossible to achieve. So it's not surprising that it does not do what you expect. It's also a really bad idea to muck with the termios attributes for each syscall. That assumes yet another level of synchronization between the terminal driver, the termios subsystem, and userspace. – sawdust Dec 12 '22 at 00:14
  • Thank you @sawdust, I just edited the piece of code to a minimal reproducible example. It is actually a modified version of https://github.com/methylDragon/teleop_twist_keyboard_cpp/blob/master/src/teleop_twist_keyboard.cpp. So do you know if there is any other way to enable this with termios without requiring the perfect synchronization with user input? – S07 Dec 12 '22 at 10:03
  • "*It is actually a modified version ..*" -- But there's a salient difference in the VMIN & VTIME values. The original is not optimal. There is no demonstrated need for reconfiguring termios for each **getchar()**. And then your version makes timing (aka synchronization) crucial. The original code performs blocking I/O, whereas you have a a timed read of just 100 milliseconds. What are you really trying to do? You can't simply modify that code from blocking mode to "nonblocking", and then ignore the different results (i.e. obtaining EAGAIN because of a timeout instead of valid data). – sawdust Dec 13 '22 at 02:38
  • See https://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read/26006680#26006680 and https://stackoverflow.com/questions/4163405/how-to-change-termios-configuration-so-that-getc-immediately-returns-when-use/4522819#4522819 – sawdust Dec 13 '22 at 02:43
  • Ok I just read the two links you shared, in the second one, it is recommended to use cbreak() and nocbreak(), which I tried, but the result is not as expected, i.e., it behaves in the same way as the termios in a counted read (VMIN>0 and VTIME=0): it returns only when a key is pressed. Regarding the first link you send, I understand that what would be needed is a non-canonical mode with VMIN = 0 and VTIME > 0, which is what I'm doing, I think. The problem here you said is the synchronization, but I have no idea how to perform that, would you have any resource to share please? – S07 Jan 17 '23 at 16:23
  • @sawdust said: "What are you really trying to do?" I want to send keyboard commands to a robot, through the terminal. My expected result is that at a defined frequency (say 10Hz), the keyboard is read and the command is sent to the robot. As soon as no key is pressed, the output should be, for example 0. It is very important here that if no key is pressed, the function getch() returns something, so that the robot stops. Otherwise the robot would continue doing the last command it received. Seeing your answers, termios might not be the best tool to do that, but I'm not aware of a better tool. – S07 Jan 17 '23 at 16:34
  • Your description of what you're trying to accomplish is IMO worded awkwardly, and would seem to be difficult to represent by [state diagram](https://en.wikipedia.org/wiki/State_diagram). You seem to be conflating *events* (the keyboard input) with *state* (what you want the robot to do). You need to separate the two concepts, and have an input either (a) trigger a change to another state, or (b) cause no state change. – sawdust Jan 18 '23 at 01:08
  • I experimented with your code, and (despite my prior comments) it seemed to perform per your expectations rather than your reported failure. Instead of "*it always returns -1*", the program properly reported the just-typed keystroke. Only while I didn't type anything would the program report "-1", which was correct. Test was performed on an old PC. Simple experiment for you to try: modify your posted code to increase the timeout to a full second: `newt.c_cc[VTIME] = 10;` If you still only get "-1", then your program is probably not reading from the keyboard you're typing on. – sawdust Jan 18 '23 at 01:18
  • Thank you! Yes indeed the code is meant to do what I expect already, the only problem is that there is this -1 returned even when I pressed a key. I tried your suggestion of increasing VTIME to a bigger number (20) and found what is happening, but I cannot find why yet. With VTIME=20, it works UNTIL I don't press a key. As soon I let the state machine go into the "default" case, then there is no way to get out of there, and the code publishes "stop" at a very high rate, not taking into account the VTIME=20 – S07 Jan 18 '23 at 09:09
  • My prior experiments with your code was actually tweaked and compiled with the C compiler. So today I used an unmodified copy of your posted code, and used the g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 compiler. Performs as expected: `-1` reported about every 0.1 sec while nothing is typed, and proper response when w/s/a/d/q keys are typed. IOW **unable to duplicate the bad symptoms that you are describing.** What's goofy about your host system? – sawdust Jan 19 '23 at 01:35
  • I really don't know why it's behaving differently... Anyway, if it is not a problem linked to termios but rather to my system, I'll close this thread. For information to anyone landing here: I solved my problem by writing my program in python instead of C++. Thanks @sawdust for your support! – S07 Jan 19 '23 at 14:58

0 Answers0