2

I am writing a program in Python to run on my Raspberry Pi. As many people knows, Raspberry can receive many ways of input. I am using a keyboard and another external input source. This is just for contextualize, not really important for the question itself.

On my program, I wait for a keyboard input and if there is none during a short period of time, I skip and look for the input from the other source. In order to do this I am using the following code:

import sys
import time
from select import select

timeout = 4  
prompt = "Type any number from 0 up to 9"
default = 99 

def input_with(prompt, timeout, default):
    """Read an input from the user or timeout"""
    print prompt,
    sys.stdout.flush()
    rlist, _, _ = select([sys.stdin], [], [], timeout)
    if rlist:
        s = int(sys.stdin.read().replace('\n',''))
    else:
        s = default
        print s
    return s

I am going to run the Raspberry Pi without a full keyboard, this means I won't have the return key. It will be impossible to validate the keyboard input on this way.

My doubt is if it is possible to get the user input without pressing enter and keeping the timeout for the input.

I've seen many topics talking about both issues (timeout and input without pressing return) but nothing with both together.

Thanks in advance for any help !

Breno Ben
  • 21
  • 4
  • That's an interesting question. `stdin` doesn't work like this, it's line by line. You would have to look at capturing the tty directly somehow. Think of how when you're entering your password on login, how it bypasses stdin. – robert Feb 03 '15 at 21:53
  • [It should be simple to adapt `readchar` to accept `timeout` parameter](https://github.com/magmax/python-readchar/blob/e020e152f1787074ef915e50f46c407ca8ac355b/readchar/readchar_linux.py). It is taken from [the answer](http://stackoverflow.com/a/25342814/4279) to [Python read a single character from the user](http://stackoverflow.com/q/510357/4279) – jfs Feb 04 '15 at 00:38

1 Answers1

0

I don't think it is straightforward to do it the way you want i.e. to read the waiting contents on the line, even if enter hasn't been pressed (that's right?).

The best suggestion I can offer is that you capture each character as it is pressed, and invoke once the time has passed. You can capture input on a per character basis by setting cbreak mode: tty.setcbreak()

import sys
from select import select
import tty
import termios

try:
    # more correct to use monotonic time where available ...
    from time33 import clock_gettime
    def time(): return clock_gettime(0)
except ImportError:
    # ... but plain old 'time' may be good enough if not.
    from time import time

timeout = 4  
prompt = "Type any number from 0 up to 9"
default = 99 

def input_with(prompt, timeout, default):
    """Read an input from the user or timeout"""
    print prompt,
    sys.stdout.flush()

    # store terminal settings
    old_settings = termios.tcgetattr(sys.stdin)

    buff = ''
    try:    
        tty.setcbreak(sys.stdin) # flush per-character

        break_time = time() + timeout

        while True:

            rlist, _, _ = select([sys.stdin], [], [], break_time - time())
            if rlist:
                c = sys.stdin.read(1)

                # swallow CR (in case running on Windows)
                if c == '\r':
                    continue
                # newline EOL or EOF are also end of input
                if c in ('\n', None, '\x04'):
                    break # newline is also end of input
                buff += c

                sys.stdout.write(c) # echo back
                sys.stdout.flush()

            else: # must have timed out
                break

    finally:
        # put terminal back the way it was
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

    if buff:
        return int(buff.replace('\n','').strip())
    else:
        sys.stdout.write('%d' % default)
        return default
robert
  • 4,612
  • 2
  • 29
  • 39
  • `time.time()` can be set back. Here's [`time.monotonic()` implementation for Python 2.7 on Ubuntu](https://gist.github.com/zed/5073409) – jfs Feb 04 '15 at 00:52
  • 1
    you should probably also allow `eof` and `eol` characters to terminate the input (in addition to `b'\n'`) -- see `stty -a`. – jfs Feb 04 '15 at 00:55
  • Thanks, I'm not sure what you mean about `time.time` though. Is the implication that it is unreliable for this reason? I don't believe EOL is a concern on raspberry pi. On a non-*nix platform you might support it but it should probably be outright ignored. – robert Feb 04 '15 at 10:44
  • [Use a monotonic clock to compute timeouts](http://bugs.python.org/issue22043#msg223715). Try your code with `Ctrl+D` (eof). – jfs Feb 04 '15 at 10:48
  • 1
    `eol` that I have mentioned in my comment has *nothing* to do with Windows. See [Special Characters](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_09) – jfs Feb 04 '15 at 11:10
  • interesting ... I thought we had carriage-returns and linefeeds. This EOL as a distinct entity is new to me. How does it manifest? – robert Feb 04 '15 at 11:17
  • looks as though `time.monotonic` is only available in python 3k. I'll make use of this, where available but `time` should "good enough" for OP's stated requirements otherwise. Actually, question is tagged as python 2.7. – robert Feb 04 '15 at 11:27
  • the very first comment has [the link that shows how to implement time.monotonic in Python 2.7 on Ubuntu](https://gist.github.com/zed/5073409) – jfs Feb 04 '15 at 11:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70214/discussion-between-robert-and-j-f-sebastian). – robert Feb 04 '15 at 12:15
  • This answer works but it is not the perfect one. Since the time for timeout I am using it is pretty fast (0.2) it works pretty good. Thanks ! – Breno Ben Feb 04 '15 at 17:50
  • Oh you should've said. That changes the nature of the problem slightly :) – robert Feb 04 '15 at 18:11