1

I am trying to write a c++ program for my linux machine that can interact with some instrumentation that responds to simple ascii commands. The problem I'm running into, I would think, would be a fairly common request but my searches of various forums came up with nothing quite the same.

My problem is this: When I connect to the instrument, due to some communication issues, it often pukes up a bunch of data of varying length that I don't want. The data the machine prints has line endings with '\r'. I have been trying to write a simple loop what will keep reading and ignoring data until the machine is quiet for two seconds, then carry on to perform some data requests once the storm is over.

When searching forums, I found gobs and gobs of threads about cin.ignore, cin.sync, getline and cin.getline. These all seemed quite useful but when I attempted to implement them in a way that should be simple, they never behaved quite as I expected them to.

I apologize in advance if this is a duplicate post as I would have thought I wasn't the first person to want to throw away garbage input but I have found no such post.

The code I have been trying a few different arrangements of looks something like this:

sleep(2);
cin.clear();
while ( cin.peek() != char_traits<char>::eof()) {
    //cin.sync();
    //cin.ignore(numeric_limits<streamsize>::max(),char_traits<char>::eof());
    cin.clear();
    char tmp[1];
    while ( cin.getline(tmp,80,'\r') ) {}
    cin.clear();
    sleep(2);
}

I understand from my searches that doing some sort of while(!cin.eof()) is bad practice but tried it anyway for grins as well as while(getline(cin,str,'\r')) and while(cin.ignore()). I am at a loss here as there is clearly something I'm missing.

Thoughts?

EDIT: --final code--

Alright! This did it! Thanks for point me to termios @MatsPetersson! I wound up stealing quite a lot of your code, but I'm glad I had the opportunity to figure out what was going on. This website helped me make sense of the tcassert manual page: http://en.wikibooks.org/wiki/Serial_Programming/termios

#include <cstdlib>
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <limits>
#include <termios.h>
#include <errno.h>
#include <cassert>

using namespace std;
const int STDIN_HANDLE=fileno(stdin);
int main()
{
    string str;

    //Configuring terminal behavior
    termios tios, original;
    assert( tcgetattr(STDIN_HANDLE, &tios)==0 );
    original = tios;

    tios.c_lflag &= ~ICANON;   // Don't read a whole line at a time. 
    tios.c_cc[VTIME] = 20;   // 0.5 second timeout. 
    tios.c_cc[VMIN]  = 0;   // Read single character at a time.

    assert( tcsetattr(STDIN_HANDLE, TCSAFLUSH, &tios)==0 );

    const int size=999; //numeric_limits<streamsize>::max() turns out to be too big.
    char tmp[size];
    int res;
    cerr << "---------------STDIN_HANDLE=" << STDIN_HANDLE << endl;
    cerr << "---------------enter loop" << endl;
    while ( res=read(STDIN_HANDLE, tmp, sizeof(tmp)) ) {
        cerr << "----read: " << tmp << endl;
    }
    cerr << "--------------exit loop" << endl;

    cout << "END";
    assert( tcsetattr(STDIN_HANDLE, TCSANOW, &original)==0 );
    return 0;
}

That wasn't as bad as I began to fear it would be! Works perfectly! Obviously all the cerr << -- lines are not necessary. As well as some of the #include's but I'll use them in the full program so I left them in for my own purposes.

Well... It mostly works anyway. It works fine so long as I don't redirect the stdio for the program to a tcp-ip address using socat. Then it gives me a "Not a Typewriter" error which is what I guess happens when it attempts to control something that isn't a tty. That sounds like a different question though, so I'll have to leave it here and start again I guess.

Thanks folks!

Justin Frahm
  • 193
  • 9
  • Seems a bit like a "random" solution, would it not work better to try to find something meaningful in the input? – Mats Petersson Sep 23 '14 at 20:38
  • I would avoid writing this in C++ if I could, because a proper implementation would force you to deal with things like buffered I/O and signals and timers which you don't seem to be familiar with. Look into using the Expect package instead, which works with Perl or Tcl--which are more forgiving languages-- and is designed to deal with exactly this sort of conversation with another program or device. – antlersoft Sep 23 '14 at 20:43
  • It's not necessary to change language, but if you indeed do want to "wait for no input in a certain amount of time", using standard iostream is probably not going to work that well, because you will need to change the io to a timeout mode, and that will lead to error conditons that streams don't like, and you'll have no end of trouble. As well as buffering until newline and other things like that. – Mats Petersson Sep 23 '14 at 20:49
  • Okay. I had tried for a short while to do this in Bash as I had some familiarity with that language but had little luck since I'm not terribly good with i/o operations there either. I've looked at Expect before and found it confusing. Maybe I'll have another look. – Justin Frahm Sep 23 '14 at 20:50
  • @MatsPetersson, can you expand a little? Seems like I may be getting in over my head rather quickly here. Not that I'm shying away from giving it a try! – Justin Frahm Sep 23 '14 at 20:51
  • I thing that `while ( cin.getline(tmp,80,'\r') ) {}`will loop forever... – Christophe Sep 23 '14 at 21:15

2 Answers2

1

Here's a quick sample of how to do console input (and can easily be adapted to do input from another input source, such as a serial port).

Note that it's hard to "type fast enough" for this to read more than one character at a time, but if you copy'n'paste, it will indeed read 256 characters at once, so assuming your machine that you are connecting to is indeed feeding out a large amount of stuff, it should work just fine to read large-ish chunks - I tested it by marking a region in one window, and middle-button-clicking in the window running this code.

I have added SOME comments, but for FULL details, you need to do man tcsetattr - there are a whole lot of settings that may or may not help you. This is configured to read data of "any" kind, and exit if you hit escape (it also exits if you hit an arrow-key or similar, because those translate to an ESC-something sequence, and thus will trigger the "exit" functionality. It's a good idea to not crash out of, or set up some handler to restore the terminal behaviour, as if you do accidentally exit before you've restored to original setting, the console will act a tad weird.

#include <termios.h>
#include <unistd.h>
#include <cassert>
#include <iostream>

const int STDIN_HANDLE = 0;

int main()
{
    termios tios, original;
    int     status;

    status = tcgetattr(STDIN_HANDLE, &tios);
    assert(status >= 0);

    original = tios;

    // Set some input flags
    tios.c_iflag &= ~IXOFF;   // Turn off XON/XOFF... 
    tios.c_iflag &= ~INLCR;   // Don't translate NL to CR.

    // Set some output flags
    // tios.c_oflag = ... // not needed, I think. 

    // Local modes flags.
    tios.c_lflag &= ~ISIG;    // Don't signal on CTRL-C, CTRL-Z, etc.
    tios.c_lflag &= ~ICANON;   // Don't read a whole line at a time. 
    tios.c_lflag &= ~(ECHO | ECHOE | ECHOK);    // Don't show the input. 

    // Set some other parameters
    tios.c_cc[VTIME] = 5;   // 0.5 second timeout. 
    tios.c_cc[VMIN]  = 0;   // Read single character at a time.

    status = tcsetattr(STDIN_HANDLE, TCSANOW, &tios);
    assert(status >= 0);

    char buffer[256];
    int tocount = 0;

    for(;;)
    {
    int count = read(STDIN_HANDLE, buffer, sizeof(buffer));
    if (count < 0)
    {
        std::cout << "Error..." << std::endl;
        break;
    }
    if (count == 0)
    {
        // No input for VTIME * 0.1s. 
        tocount++;
        if (tocount > 5)
        {
        std::cout << "Hmmm. No input for a bit..." << std::endl;
        tocount = 0;
        }
    }
    else
    {
        tocount = 0;
        if (buffer[0]== 27)  // Escape
        {
        break;
        }
        for(int i = 0; i < count; i++)
        {
        std::cout << std::hex << (unsigned)buffer[i] << " ";
        if (!(i % 16))
        {
            std::cout << std::endl;
        }
        }

    std::cout << std::endl;
    }
    }
    status = tcsetattr(STDIN_HANDLE, TCSANOW, &original);
    return 0;
}
Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Hmm... This looks promising. I'm going to have to look more into this but at first read, I think this should be what I need! I see that `read` will actually continue on when it sees an EOF! That was the big hold-up for me with the other functions! – Justin Frahm Sep 23 '14 at 22:15
  • Well, technically, it doesn't "continue" on EOF, rather it doesn't RECOGNISE Ctrl-D as a EOF... That's quite different... But yes, when `ICANON` is not set, it will not recognise CTRL-D as a EOF character, but simply pass it on. – Mats Petersson Sep 23 '14 at 22:16
  • Ok, I see. I'm trying to modify my code but what I don't understand is the `int fd` portion of `read(int fd, void *buff, size_t count)`. I don't understand what you are doing with `STDIN_HANDLE` and how it feeds stdin to the read function. – Justin Frahm Sep 23 '14 at 22:24
  • I happen to know that the stdin file-descriptor is 0 [at least in a non-redirected setup]. However, if your device is a serial device, you probably want to simply open the serial device (e.g `int fd = open("/dev/ttyS0", O_RDONLY);` and then use `fd` instead of `STDIN_HANDLE`. Note that I'm not here to write your code for you, you have to actually think and study a bit for yourself... – Mats Petersson Sep 23 '14 at 22:27
  • I wouldn't have it any other way. I'm just trying to get enough background to know what's going on so I can make the leap :) I updated my additional code edit to show what I'm trying now. I seem to still have an issue with the file descriptor despite the fact that I am using 0 as well (I am actually interfacing with stdin and stdout rather than a serial port but that is a whole other story). – Justin Frahm Sep 23 '14 at 22:44
  • Check the return-value from `read` and `errno`... By the way, what value is `size`? – Mats Petersson Sep 23 '14 at 22:55
  • `errno` comes back as 14 (bad address according to http://www.virtsync.com/c-error-codes-include-errno) and `read` gives -1. `size` is just the max size of my cin buffer. – Justin Frahm Sep 23 '14 at 23:15
0

If your instrumentation offers a stream interface, and assuming that it would wait before returning whenever no input is available, I'd suggest to simply use :

cin.ignore(numeric_limits<streamsize>::max(),'\r'); // ignore everything until '\r'

Another alternative could be to use poll, which provides a mechanism for multiplexing (and waiting for) input/output over a set of file descriptors. This has the advantage of letting you read several instrumentation devices if you'd need.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • So that gets me most the way there. I'll alter my code and enter it in an edit above. For some reason it still sticks at the conditional in the while loop. – Justin Frahm Sep 23 '14 at 21:42