Reputation: 79
There are a lot of great answers on how to read from stdin
in python, but I just can't find anything about reading single characters instead of whole lines. Let me explain:
I need to read information send by an arduino on a serial port, which is forwarded to stdin. This information is processed and stored in a text file. I wrote the programm on the arduino, so I could change how the information is send. The plan was to send the information with an start- (<
) and an endcharacter (>
), so it would look like this: <height:2342>
There will also be a lot of irrelevant data being written, thats why I decided to use the above form, so the python script can detect the relevant information and capture it.
My python script would check every char individually for the start-character <
, ideally as it is entered, and then capture the information until >
is received. I tried getting the input using inputchar = sys.stdin.read(1)
. But the problem here is that this reads from stdin
forever, until a newline (\n
) is captured, and then returns the first character entered.
I want this function to return the entered character as soon as it got send to stdin
, and not wait for a newline. How do I achive this?
Platform: Raspberry Pi Zero W, Raspbian Jessy, Python 2.7
I know I could use inputline = sys.stdin.readline()
and change the Arduino programm to send a newline after the information. Then analyze the whole line (which could be very long) and extract the information. But I don't feel this would be a clean way of doing this.
Update on Serial Port: Sadly I can't access the serial port directly from python because there is a second python script which has to write to the serial port. Since only one can access the port, the solution is to redirect the serial port to stdin
and stdout
. See my question Access one Serial Port with two Python Scripts
Upvotes: 3
Views: 7011
Reputation: 8483
An alternative approach is to set the TTY to raw mode. I wanted to time the duration of a keypress and needed this so that I didn't have to hit the enter key:
#!/usr/bin/env python3
import datetime
import sys
import tty
import termios
# read a single character on a unix system
# https://exceptionshub.com/python-read-a-single-character-from-the-user.html
def read_unix(count: int):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(count)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
while True:
start = datetime.datetime.now()
char = read_unix(1)
end = datetime.datetime.now()
delta = end - start
print(f'char={char}, delta={delta}')
Upvotes: 1
Reputation: 15877
This is because your terminal is in cooked mode. You can use e.g. tty.setcbreak or curses.cbreak to disable the line buffering. This is a Unix thing, not particularly Python specific.
Example:
import sys, tty
tty.setcbreak(sys.stdin.fileno())
Note that this has other effects such as disabling echo and will persist when your program exits. Typically a higher level interface such as a curses context manager is used to handle key parsing (arrow keys, for instance, send escape sequences) and cleanup.
The primary command line tool outside of Python for this is stty.
Upvotes: 4
Reputation: 2712
sys.stdin.read(1)
is a correct approach.
Before you hit the enter, there is no input at sys.stdin
. That line buffering is performed outside your program and you can do nothing about it if you want to use sys.stdin
.
Upvotes: 1
Reputation: 11258
I can't reproduce the behavior you are describing...
Executing on my Raspbian - on an RPI2:
$ cat a.py
#!/usr/bin/env python2
import sys
while True:
print "Got", ord(sys.stdin.read(1))
$ echo -n "Test" | hexdump -C
00000000 54 65 73 74 |Test|
00000004
$ # No newline in the data sent by echo, as shown by hexdump
$ echo -n "Test" | python2 a.py
Got 84
Got 101
Got 115
Got 116
Got
Traceback (most recent call last):
File "a.py", line 5, in <module>
print "Got", ord(sys.stdin.read(1))
TypeError: ord() expected a character, but string of length 0 found
Basically, the sys.stdin.read(1)
returns with whatever character was sent from stdin, without waiting for any newline.
Upvotes: 0