Search code examples
c++ros

read() from command line returns EISDIR error


I'm trying to read arrow keys character from the terminal and use the to send String type of messages through ROS. For doing so I'm using termios read() function.

Firstly, I have done a small test that works fine under C, compiled using gcc. This is the code:

#include <unistd.h>
#include <stdlib.h>
#include <termios.h> // for keyboard input
#include <string.h>
#include <stdio.h>

bool quit_requested = false;

void processKeyboardInput (char c)
{
   switch (c)
   {
     case 68:// Left arrow pressed
     {
       puts("Left pressed");
       break;
     }
     case 67://Right arrow pressed
     {
       puts("Right pressed");
       break;
     }
     case 65://Up arrow pressed
     {
       puts("Up pressed");
       break;
     }
     case 66://Down arrow pressed
     {
       puts("Down pressed");
       break;
     }
     case 'q'://Quit arrow pressed
     {
       quit_requested = true;
       puts("q pressed, quitting...");
     }
     default:
     {
       break;
     }
 }

}

int main(int argc, char const *argv[]) {
  int key_file_descriptor;
  struct termios raw;
  struct termios original_terminal_state;

  tcgetattr(key_file_descriptor, &original_terminal_state); // get       terminal properties
  memcpy(&raw, &original_terminal_state, sizeof(struct termios));

  raw.c_lflag &= ~(ICANON | ECHO);//local modes, enable canonical mode and echo
  // Setting a new line, then end of file
  raw.c_cc[VEOL] = 1;//special characters
  raw.c_cc[VEOF] = 2;
  tcsetattr(key_file_descriptor, TCSANOW, &raw);

  puts("Reading from keyboard");
  puts("---------------------------");
  puts("Press the arrow keys to move and q to quit");

  char c;
  while (!quit_requested)
  {
    if (read(key_file_descriptor, &c, 1) < 0)
    {
      perror("read char failed():");
      exit(-1);
    }
    processKeyboardInput(c);
  }
      tcsetattr(key_file_descriptor, TCSANOW,      &original_terminal_state);
      puts("Exit");
    return 0;
    }

Once done this, I have added ROS stuff to send the keys using a String message:

#include <unistd.h>
#include <termios.h> // for keyboard input
#include <cstring>
#include <stdio.h>

#include "ros/ros.h"
#include "std_msgs/String.h"


bool quit_requested = false;


std::string processKeyboardInput (char c)
{

   switch (c)
   {
     case 68:// Left arrow pressed
     {
       puts("Left pressed");
       return ("left");
     }
     case 67://Right arrow pressed
     {
       puts("Right pressed");
       return ("right");
     }
     case 65://Up arrow pressed
     {
       puts("Up pressed");
       return ("forward");
     }
     case 66://Down arrow pressed
     {
       puts("Down pressed");
       return ("backward");
     }
     case 'q'://Quit arrow pressed
     {
       quit_requested = true;
       puts("q pressed, quitting...");
     }
     default:
     {
       return ("");
     }
 }

}

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

  std::string cmd;
  int key_file_descriptor;
  char c;
  int error;
  struct termios raw;
  struct termios original_terminal_state;
  //std::string name = "key_teleop";

  /**** INIT ROS STUFFS  *****/
  ros::init(argc, argv, "key_teleop");
  std_msgs::String msg;
  ros::NodeHandle n;
  ros::Publisher key_pub = n.advertise<std_msgs::String>("key_teleop", 1000);
  ros::Rate loop_rate(1);
  /***************************/

  tcgetattr(key_file_descriptor, &original_terminal_state); // get terminal properties
  memcpy(&raw, &original_terminal_state, sizeof(struct termios));

  raw.c_lflag &= ~(ICANON | ECHO);//local modes, enable canonical mode and echo
  // Setting a new line, then end of file
  raw.c_cc[VEOL] = 1;//special characters
  raw.c_cc[VEOF] = 2;
  tcsetattr(key_file_descriptor, TCSANOW, &raw);

  puts("Reading from keyboard");
  puts("---------------------------");
  puts("Press the arrow keys to move and q to quit");


  while (ros::ok() & (!quit_requested))
  {
  //if (read(key_file_descriptor, &c, 1) < 0)
    error = read(key_file_descriptor, &c, 1);
    if ( error < 0)//Process the error
    {//TODO I get errors, not ROSify code doesn't, C / C++ code mixture issue??
      if (error == EAGAIN)
        puts ("EAGAIN");
      else if (error == EBADF)
        puts ("EBADF");
      else if (error == EFAULT)
        puts ("EFAULT");
      else if (error == EINTR)
        puts ("EINTR");
      else if (error == EINVAL)
        puts ("EINVAL");
      else if (error == EIO)
        puts ("EIO");
      else if (error = EISDIR)
        puts ("EISDIR");
      perror("read char failed():");
      exit(-1);
    }
    cmd = processKeyboardInput(c);
    if (!cmd.empty())
    {
      msg.data = cmd;
      key_pub.publish(msg);
      ros::spinOnce();
      //loop_rate.sleep();
    }
  }
  tcsetattr(key_file_descriptor, TCSANOW, &original_terminal_state);
  puts("Exit");
return 0;
}

Once created the CMakeLists.txt and package.xml, I have compiled using catkin_make. Test it out and now doesn't work, most of the times I receive a error return at read(), it enter into EISDIR case and prints it. Any idea why now I'm receiving this error while the previous c example is working fine??

Thanks in advance!


Solution

  • Following the reccomendation of @Jonathan, I have added the flags -Wall -Werror -Wextra to the CMake compiling flags and discovered the next message thrown by the compiler:

    error: ‘key_file_descriptor’ may be used uninitialized in this function [-Werror=maybe-uninitialized]

    I have found this nice article and changed the int key_file_descriptorI was using for STDIN_FILENO. So I have replaced it all over the code and now is working! I have deleted also the comparison typo. Thanks guys!