Disabling input while printing with sys.stdout.write - python-3.x

As the title says, is there any way to disable user input while printing with sys.stdout.write?
-
For instance if I was using this function to print out a string slowly:
import time
import sys
def printSlowly(text, delay):
for char in text:
sys.stdout.write(char)
sys.stdout.flush()
time.sleep(delay)
How could I stop the user from typing anything while the function is printing things?
Note that this would be for a Linux Ubuntu terminal.
Thanks!

Figured it out! Involved a combination of disabling echoing and flushing input.
# from https://gist.github.com/kgriffs/5726314
def enable_echo(enable):
fd = sys.stdin.fileno()
new = termios.tcgetattr(fd)
if enable:
new[3] |= termios.ECHO
else:
new[3] &= ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, new)
# from https://stackoverflow.com/questions/26555070/linux-python-clear-input-buffer-before-raw-input
def flushInput():
termios.tcflush(sys.stdin, termios.TCIFLUSH)

Related

Carriage return failing on wrapped lines in a linux terminal python3

I am trying to get live updating of the input prompt
working (How to live-update an input prompt in python3 (preferrably without (n)curses)?) and I used \r, but it breaks on wrap:
Although if you use cursor_save (\033[s), then clear from cursor using \033[J and then use cursor_load (\033[u) it works, although the cursor gets
broken and doesn't follow the string anymore:
The line changed:
sys.stdout.write(f"\033[s\033[J{len(s)}: {s}\033[u")
Is there a way to combine them and make it
Not break
Follow the cursor
I have been stuck here for quite a bit looking for a solution,
anything close to it would even be helpful
Here's the full code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Example: reading input and updating the prompt
Dependencies:
- getch <https://pypi.org/project/getch/>"""
import sys
from warnings import filterwarnings as filter_warnings
from getch import getch # type: ignore
BACK: str = "\b"
def main() -> int:
"""Entry/main function"""
s: str = ""
while True:
# sys.stdout.write(f"\033[2K\r{len(s)}: {s}")
sys.stdout.write(f"\033[s\033[J{len(s)}: {s}\033[u")
sys.stdout.flush()
c: str = getch()
if c == "\x7f":
s = s[:-1]
elif c in ("\n", "\r", "\r\n", "\x04"):
break
else:
# Debug:
# print(ord(c), repr(chr(ord(c))))
s += c
print(f"\nValue: {s}\n")
return 0
if __name__ == "__main__":
assert main.__annotations__.get("return") is int, "main() should return an integer"
filter_warnings("error", category=Warning)
sys.exit(main())
thanks for the answers in advance

How to convert python-to-arduino over serial code to python3-to-arduino over serial code?

The code below used to work in spyder to communicate over serial with an arduino. In the console window of spyder, I would see lines of data being printed out:
78.7,77.9,100,80
78.7,77.9,100,80
78.7,77.9,100,80
78.7,77.9,100,80 ...
Data is from two temperature probes, a flowmeter, and the thermostat set temp.
I upgraded my Kubuntu 18.04 system to all things python3. Now, the code runs, but the spyder3 console window shows no visible characters, but scrolls blank lines. The rest of my python code for parsing and plotting this data doesn't work.
I've spent all day trying to fix this with no luck. I'm guessing it's a simple fix for someone with more experience than me.
The only difference between the old working code and the code below is that the print statements have parentheses added to remove the syntax error.
python
""" This code was originally copied from:
Listen to serial, return most recent numeric values
Lots of help from here:
http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device
"""
from threading import Thread
import time
import serial
last_received = ''
def receiving(ser):
global last_received
buffer = ''
while True:
buffer = buffer + ser.read(ser.inWaiting())
if '\n' in buffer:
lines = buffer.split('\n') # Guaranteed to have at least 2 entries
last_received = lines[-2]
#If the Arduino sends lots of empty lines, you'll lose the
#last filled line, so you could make the above statement conditional
#like so: if lines[-2]: last_received = lines[-2]
buffer = lines[-1]
class SerialData(object):
def __init__(self, init=50):
try:
self.ser = serial.Serial(
port='/dev/ttyACM0',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1,
xonxoff=0,
rtscts=0,
interCharTimeout=None
)
except serial.serialutil.SerialException:
#no serial connection
self.ser = None
else:
Thread(target=receiving, args=(self.ser,)).start()
def next(self):
if not self.ser:
return '81.3,78.1,10.0,60.0,0' #100 #return anything so we can test when Arduino isn't connected
#return a float value or try a few times until we get one
for i in range(40):
raw_line = last_received
try:
# return float(raw_line.strip())
return str(raw_line.strip())
except ValueError:
print('bogus data',raw_line)
time.sleep(.005)
return 0.
def __del__(self):
if self.ser:
self.ser.close()
def write(self,val):
self.ser.write(val)
if __name__=='__main__':
s = SerialData()
for i in range(500):
time.sleep(.015)
print( s.next())
One of the most significant differences between Python 2.x and 3.x is the way text strings are encoded. For Python 3.x everything is Unicode, as compared to ASCII for 2.x, so you just need to decode the raw bytes you read from the serial port:
buffer = buffer + ser.read(ser.inWaiting()).decode('utf-8')
EDIT: now you seem to have a different problem involving an exception. It looks like your port is open, to be sure you can change the way you handle the exception when you instantiate the port:
except serial.serialutil.SerialException as e:
print(e)
self.ser = None
Once you know the error you should be able to handle it. Quite likely your port was not properly closed in an earlier session.
Bingo! That last suggestion fixed the program and the python GUI interface I wrote for it is working with these corrections.
python
from threading import Thread
import time
import serial
last_received = ''
def receiving(ser):
global last_received
buffer = ''
while True:
buffer = buffer + ser.read(ser.inWaiting()).decode('utf-8')
if '\n' in buffer:
lines = buffer.split('\n') # Guaranteed to have at least 2 entries
last_received = lines[-2]
#If the Arduino sends lots of empty lines, you'll lose the
#last filled line, so you could make the above statement conditional
#like so: if lines[-2]: last_received = lines[-2]
buffer = lines[-1]
class SerialData(object):
def __init__(self, init=50):
try:
self.ser = serial.Serial(
port='/dev/ttyACM1',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1,
xonxoff=0,
rtscts=0,
interCharTimeout=None
)
except serial.serialutil.SerialException as e:
print(e)
#no serial connection
self.ser = None
else:
Thread(target=receiving, args=(self.ser,)).start()
def next(self):
# if not self.ser:
# return '81.3,78.1,10.0,60.0,0' #100 #return anything so we can test when Arduino isn't connected
#return a float value or try a few times until we get one
for i in range(40):
raw_line = last_received
try:
# return float(raw_line.strip())
return str(raw_line.strip())
except ValueError:
print('bogus data',raw_line)
time.sleep(.005)
return 0.
def __del__(self):
if self.ser:
self.ser.close()
def write(self,val):
self.ser.write(val)
if __name__=='__main__':
s = SerialData()
for i in range(500):
time.sleep(.015)
print (s.next())

Python CMD2 Interrupt printing promp

I am using CMD2 to create a server. I have broken my code down to the smallest bit of code that still produces the problem.
import socket
import _thread
from cmd2 import *
def grab_data(conn):
while True:
try:
data = conn.recv(1024)
print(data)
except:
print("disconnected.")
break
def grab_client(sock):
while True:
conn, addr = sock.accept()
print("New connection")
_thread.start_new_thread(grab_data, (conn,))
def start_conn(ip, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((ip, int(port)))
sock.listen(10)
print("Socket listening")
_thread.start_new_thread(grab_client, (sock,))
class CLI(Cmd):
def __init__(self):
Cmd.__init__(self)
self.prompt = "Prompt> "
def do_listen(self, arg):
start_conn('0.0.0.0', '4446')
def emptyline(self):
pass
def do_quit(self, arg):
return True
cli = CLI()
cli.cmdloop("Welcome to the server.")
The issue I run into is when a client connects. It does not reprint the prompt. It hangs at a empty line with just the cursor. I am not sure how to get the prompt to print back.
You're blocking trying to read 1024 bytes, so it's got to wait until that entire buffer is filled. To do proper line-based buffering is a bit tricky, but a simple (albeit non-performant) implementation is to read a character at a time and check if it's a newline:
line = ""
while True:
data = conn.recv(1)
line += data
if data in "\n\r":
break
(This is not great code, but let's see if that solves the problem and we can improve it.)

Non-blocking input in python 3 [duplicate]

I'm working on a bot for a competition that receives its input through sys.stdin and uses Python's print() for output. I have the following:
import sys
def main():
while True:
line = sys.stdin.readline()
parts = line.split()
if len(parts) > 0:
# do stuff
The problem is that the input comes in through a stream and using the above, blocks me from printing anything back until the stream is closed. What can I do to make this work?
By turning blocking off you can only read a character at a time. So, there is no way to get readline() to work in a non-blocking context. I assume you just want to read key presses to control the robot.
I have had no luck using select.select() on Linux and created a way with tweaking termios settings. So, this is Linux specific but works for me:
import atexit, termios
import sys, os
import time
old_settings=None
def init_any_key():
global old_settings
old_settings = termios.tcgetattr(sys.stdin)
new_settings = termios.tcgetattr(sys.stdin)
new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags
new_settings[6][termios.VMIN] = 0 # cc
new_settings[6][termios.VTIME] = 0 # cc
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
#atexit.register
def term_any_key():
global old_settings
if old_settings:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
def any_key():
ch_set = []
ch = os.read(sys.stdin.fileno(), 1)
while ch is not None and len(ch) > 0:
ch_set.append( ord(ch[0]) )
ch = os.read(sys.stdin.fileno(), 1)
return ch_set
init_any_key()
while True:
key = any_key()
if key is not None:
print(key)
else:
time.sleep(0.1)
A better Windows or cross-platform answer is here: Non-blocking console input?
You can use selectors for handle I/O multiplexing:
https://docs.python.org/3/library/selectors.html
Try this out:
#! /usr/bin/python3
import sys
import fcntl
import os
import selectors
# set sys.stdin non-blocking
orig_fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL)
fcntl.fcntl(sys.stdin, fcntl.F_SETFL, orig_fl | os.O_NONBLOCK)
# function to be called when enter is pressed
def got_keyboard_data(stdin):
print('Keyboard input: {}'.format(stdin.read()))
# register event
m_selector = selectors.DefaultSelector()
m_selector.register(sys.stdin, selectors.EVENT_READ, got_keyboard_data)
while True:
sys.stdout.write('Type something and hit enter: ')
sys.stdout.flush()
for k, mask in m_selector.select():
callback = k.data
callback(k.fileobj)
The above code will hold on the line
for k, mask in m_selector.select():
until a registered event occurs, returning a selector_key instance (k) and a mask of monitored events.
In the above example we registered only one event (Enter key press):
m_selector.register(sys.stdin, selectors.EVENT_READ, got_keyboard_data)
The selector key instance is defined as follows:
abstractmethod register(fileobj, events, data=None)
Therefore, the register method sets k.data as our callback function got_keyboard_data, and calls it when the Enter key is pressed:
callback = k.data
callback(k.fileobj)
A more complete example (and hopefully more useful) would be to multiplex stdin data from user with incoming connections from network:
import selectors
import socket
import sys
import os
import fcntl
m_selector = selectors.DefaultSelector()
# set sys.stdin non-blocking
def set_input_nonblocking():
orig_fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL)
fcntl.fcntl(sys.stdin, fcntl.F_SETFL, orig_fl | os.O_NONBLOCK)
def create_socket(port, max_conn):
server_addr = ('localhost', port)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setblocking(False)
server.bind(server_addr)
server.listen(max_conn)
return server
def read(conn, mask):
global GO_ON
client_address = conn.getpeername()
data = conn.recv(1024)
print('Got {} from {}'.format(data, client_address))
if not data:
GO_ON = False
def accept(sock, mask):
new_conn, addr = sock.accept()
new_conn.setblocking(False)
print('Accepting connection from {}'.format(addr))
m_selector.register(new_conn, selectors.EVENT_READ, read)
def quit():
global GO_ON
print('Exiting...')
GO_ON = False
def from_keyboard(arg1, arg2):
line = arg1.read()
if line == 'quit\n':
quit()
else:
print('User input: {}'.format(line))
GO_ON = True
set_input_nonblocking()
# listen to port 10000, at most 10 connections
server = create_socket(10000, 10)
m_selector.register(server, selectors.EVENT_READ, accept)
m_selector.register(sys.stdin, selectors.EVENT_READ, from_keyboard)
while GO_ON:
sys.stdout.write('>>> ')
sys.stdout.flush()
for k, mask in m_selector.select():
callback = k.data
callback(k.fileobj, mask)
# unregister events
m_selector.unregister(sys.stdin)
# close connection
server.shutdown()
server.close()
# close select
m_selector.close()
You can test using two terminals.
first terminal:
$ python3 test.py
>>> bla
open another terminal and run:
$ nc localhost 10000
hey!
back to the first
>>> qwerqwer
Result (seen on the main terminal):
$ python3 test.py
>>> bla
User input: bla
>>> Accepting connection from ('127.0.0.1', 39598)
>>> Got b'hey!\n' from ('127.0.0.1', 39598)
>>> qwerqwer
User input: qwerqwer
>>>
#-----------------------------------------------------------------------
# Get a character from the keyboard. If Block is True wait for input,
# else return any available character or throw an exception if none is
# available. Ctrl+C isn't handled and continues to generate the usual
# SIGINT signal, but special keys like the arrows return the expected
# escape sequences.
#
# This requires:
#
# import sys, select
#
# This was tested using python 2.7 on Mac OS X. It will work on any
# Linux system, but will likely fail on Windows due to select/stdin
# limitations.
#-----------------------------------------------------------------------
def get_char(block = True):
if block or select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
return sys.stdin.read(1)
raise error('NoChar')
This is a posix solution, similar to the answer by swdev.
As they stated, you have to play with termios.VMIN and termios.VTIME to catch more than one char without requiring user to press Enter. Trying to only use raw mode will be a problem as special keys like arrows can mess next keypress.
Here we use tty.setcbreak() or tty.setraw() as a shortcut, but they have short internals.
import termios
import tty
import sys
import select
def get_enter_key():
fd = sys.stdin.fileno()
orig_fl = termios.tcgetattr(fd)
try:
tty.setcbreak(fd) # use tty.setraw() instead to catch ^C also
mode = termios.tcgetattr(fd)
CC = 6
mode[CC][termios.VMIN] = 0
mode[CC][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSAFLUSH, mode)
keypress, _, _ = select.select([fd], [], [])
if keypress:
return sys.stdin.read(4095)
finally:
termios.tcsetattr(fd, termios.TCSANOW, orig_fl)
try:
while True:
print(get_enter_key())
except KeyboardInterrupt:
print('exiting')
sys.exit()
note that there are two potential timeouts you could add here:
one is adding last parameter to select.select()
another is playing with VMIN and VTIME
Might I suggest nobreak? If'n you are willing to use curses.
https://docs.python.org/3/library/curses.html#curses.window.nodelay
You should be able to get read of a stream with either
sys.stdin.read(1)
to read utf-8 decoded chars or:
sys.stdin.buffer.read(1)
to read raw chars.
I would do this if I wanted to get raw data from the stdin and do something with it in a timely manner, without reading a newline or filling up the internal buffer first. This is suitable for running programs remotely via ssh where tty is not available, see:
ssh me#host '/usr/bin/python -c "import sys; print(sys.stdin.isatty())"'
There are some other things to think about to make programs work as expected in this scenario. You need to flush the output when you're done to avoid buffering delays, and it could be easy to assume a program hasn't read the input, when you've simply not flushed the output.
stdout.write("my data")
stdout.flush()
But usually it's not the input reading that's the problem but that the terminal (or program) supplying the input stream is not handing it over when you expect, or perhaps it's not reading your output when you expect. If you have a tty to start with (see ssh check above) you can put it into raw mode with the tty module.
import sys
import termios
import tty
old = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin)
c = None
try:
c = sys.stdin.read(1)[0]
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
print(c)
... if using Mac/Linux. If using Windows you could use msvcrt.getch().
Use a generator - thankfully sys.stdin is already a generator!
A generator enables you to work on an infinite stream. Always when you call it, it returns the next element. In order to build a generator you need the yield keyword.
for line in sys.stdin:
print line
if a_certain_situation_happens:
break
Do not forget to place a break statement into the loop if a certain, wished situation happens.
You can find more information about generators on:
http://www.dabeaz.com/generators/index.html
http://linuxgazette.net/100/pramode.html

Python 2.7: How do I read single keypress in Linux (Including special characters)?

I have seen a lot of questions asked about how to read single keypress in python. For Windows, the answers say to use the msvcrt module, which works. But for Linux, they use something like this:
import termios, fcntl, sys, os
def kbhit():
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while True:
try:
c = sys.stdin.read(1)
return True
except IOError:
return False
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
or this:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _Getch()
The problem with those two is that they don't read special characters like cntrl-C (^C) or Arrow Keys. Is there a Linux way to read single keypress including special characters like arrow keys?
Check this piece of code:
def AllKeys(NormalInput=1):
""" Detect Key Input """
import termios, sys, tty
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
tty.setraw(fd)
tty.setcbreak(fd) # Control + C
try:
#while True:
ch = sys.stdin.read(NormalInput)
if ch == '\x1b':
ch = ch.lstrip(' ')
ch += sys.stdin.read(2)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
In my case it works as intended! It detect ctrl + 'char'
Just check this simple example of UDP client:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
try:
InKey = AllKeys()
print InKey
if InKey == "\x03":
raise KeyboardInterrupt
elif InKey == '~':
break
else:
sock.sendto(InKey, (UDP_IP, UDP_PORT))
except KeyboardInterrupt:
break
sock.shutdown()
sock.close()
You can expand this easily with a server that process the characters received for virtual keyboard input with Uinput (Virtual HID Device). Then secure it with port control using Iptables rules. Add ssh tunneling for security. Otherwise you'll see plain data traffic in an ARP Cache Poisoning (MITM) testing.

Resources