A year ago I needed a script to capture input from a serial device and send it to a web browser. (A touch sensor attached to a 3d printed Egyptian tablet in a Museum.) I had originally intended to use Perl but as that wasn't playing ball and I only had a few hours before launch I opted for Python (I'm not a python dev). I made a script that worked fine and has been for some time, with the only issue being that the script uses 100% CPU. How can I get Python to read from the serial port without using the whole CPU, while still bring responsive regardless of when the input is pressed?
My script is below:
#!/usr/bin/python
import time
import serial
import sys
from subprocess import call
import traceback
myport = 0
ser = serial.Serial()
def readkey():
while 1:
out = '';
while ser.inWaiting() > 0:
out += ser.read(1);
if out != '\xf8' and out != '\xf9':
call(["xdotool", "key", "F8"])
call(["xdotool", "type", str(ord(out))])
call(["xdotool", "key", "F9"])
out = ''
def main_sys():
global ser
print "Opening Stela serial port"
ser.open();
ser.isOpen();
print "Starting Stela subsystem"
while 1:
try:
readkey()
break
except Exception as e:
print "caught os error".format(e)
time.sleep(1)
main_sys()
def init():
global ser
global myport
while 1:
try:
theport = '/dev/ttyACM'+str(myport)
print "Trying " + theport
ser = serial.Serial(
port=theport,
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS
)
main_sys()
break;
except Exception as e:
traceback.print_exc()
myport += 1
if myport > 5:
myport = 0
time.sleep(1)
init()
init()
Add a time.sleep for a short period at the end of your readKey-loop. It will let other processes run.
Also, be aware that call is blocking until the operation is finished.
I would try to avoid the ser.inWaiting() call. Instead I'd directly call ser.read(1). This way the call will block until more data is available. During this time the CPU will be free to do other things:
def readkey()
out = ser.read(1);
if out != '\xf8' and out != '\xf9':
call(["xdotool", "key", "F8"])
call(["xdotool", "type", str(ord(out))])
call(["xdotool", "key", "F9"])
This should behave identically but with almost no CPU load.
Related
I'm working on an automation project with python and an Arduino. I need to run a test that ensures that the Arduino is connected (and not some other device). I have a simple test case that works, but doesn't manage the serial object's memory at all. I've got a solution that I feel should work, however it causes an issue with the serial connection. The result is that the first read returns an empty string. Subsequent reads return the correct value. For my application I need it to read correctly on the first try.
Here is the test code I used. This code works just fine (it's how I know the issue isn't on the Arduino side of things)
ser = serial.Serial('/dev/tty.usbmodem14101', 9600, timeout=1)
ser.flush()
while True:
text = input("Input: ")
ser.write(bytes(text, 'utf-8'))
time.sleep(0.2)
line = ser.readline().decode('utf-8').rstrip()
print(line)
This is my attempt at creating a better way to manage the serial resource using the context manager and the with statement. I tried 2 things. The ArduinoWith class only returns false (and prints and empty string) with no indication of any input on the Arduino. The ArduinoSelf has the same result the first time, but returns True the second time.
import serial
import time
from contextlib import contextmanager
#contextmanager
def arduino_serial_connection(path, rate, timeout=1):
connection = serial.Serial(path, rate, timeout=timeout)
yield connection
connection.close()
class ArduinoWith:
def __init__(self):
pass
def connection_test(self):
try:
with arduino_serial_connection('/dev/tty.usbmodem14101', 9600) as connection:
connection.write(b"S\n")
time.sleep(0.2)
answer = connection.readline().decode('utf-8').rstrip()#connection.read(8)
if answer == "H":
return True
else:
print("Answer: \"" + answer +"\"")
except serial.serialutil.SerialTimeoutException:
print("Timeout")
except Exception:
print("Execption")
return False
class ArduinoSelf:
def __init__(self):
self.ser = serial.Serial('/dev/tty.usbmodem14101', 9600, timeout=1)
def connection_test(self):
try:
self.ser.write(b"S\n")
time.sleep(0.2)
answer = self.ser.readline().decode('utf-8').rstrip()#connection.read(8)
if answer == "H":
return True
else:
print("Answer: \"" + answer +"\"")
except serial.serialutil.SerialTimeoutException:
print("Timeout")
except Exception:
print("Execption")
return False
a1 = ArduinoWith()
print(a1.connection_test())
time.sleep(1)
a2 = ArduinoSelf()
print(a2.connection_test())
time.sleep(1)
print(a2.connection_test())
I'm not exactly sure why running the method a second time on the ArduinoSelf class worked, but it did and that made me think there must be something that I'm not initializing correctly.
I am building a GUI in Tkinter and I want to receive incoming data from Serial (from a microcontroller).
Also, I want to print that out onto the text-editor widget in Tkinter (similar to the serial monitor console).
For that, I am right now trying to write a receiving code in Python without Tkinter but I am unable to receive any data in the console.
The code is as follows:
import serial
import time
import threading
global serial_open
serial_open = False
def serial_read():
global ser
global var
while True:
if serial_open == True:
var = ser.readline()
if var != "":
print(var)
else:
pass
ser = serial.Serial('COM3', baudrate = 19200)
time.sleep(3)
serial_open = True
print("COM3 Connected")
threading.Thread(target = serial_read).start()
time.sleep(10)
ser.close()
print("Disconnected")
I have the following Python code which is just reading from an Arduino, and writing to a file:
import serial
from datetime import datetime
import time
ser = serial.Serial('COM3', 9600, timeout=0)
text_file = open("voltages.txt", 'w')
while 1:
x=ser.readline()
print(str(datetime.now()), x)
data = [str(datetime.now()),x]
text_file.write("{}\n".format(data))
text_file.flush()
time.sleep(1)
Whenever I interrupt the script, I always have to type ser.close() in the console, otherwise I cannot start the script again (in the same console).
How can I close the serial communication automatically whenever I interrupt the script?
You can use the with statement.
import serial
from datetime import datetime
import time
with serial.Serial('COM3', 9600, timeout=0) as ser, open("voltages.txt", 'w') as text_file:
while True:
x=ser.readline()
print(str(datetime.now()), x)
data = [str(datetime.now()),x]
text_file.write("{}\n".format(data))
text_file.flush()
time.sleep(1)
The with statement will automatically close ser and text_file when execution leaves the with statement. This can be an exception in your case. The with statement has been introduced by PEP 343 to remove try/finaly statements by using context managers.
Use a try/finally block to insure close is called:
try:
while 1:
x = ser.readline()
....
finally:
ser.close()
I'm a new to python, hoping someone knows something about the issue I have. I want to control a device using serial console. A command will be sent to reboot the device, while the device is rebooting, a string is printed. I want to catch the string and then send a character "h" which will abort the reboot. Code looks like this
#! /bin/env python
import serial
import sys
import pexpect
from time import sleep
from serial import SerialException
ser = serial.Serial()
ser.baudrate = 9600
ser.port="/dev/ttyUSB0"
ser.stopbits=serial.STOPBITS_ONE
ser.xonxoff=0
try:
ser.open()
except:
sys.exit ("Error opening port")
print "Serial port opened"
ser.write('rebootnow\r')
temp = ser.expect('press h now to abort reboot..')
if i == 0:
print ('Gotcha, pressing h')
ser.sendline('h')
print ('Reboot aborted')
else:
print ('Ouch, got nothing')
time.sleep(1)
ser.close()
exit()
When I run the code, I get the error
AttributeError: 'Serial' object has no attribute 'expect'
at line
temp = ser.expect('press h now to abort reboot..')
Any ideas?
This thread is quite old, but I hope my answer can still be useful to you.
The expect methods are from pexpect, not from pyserial. You should do something like:
temp = pexpect.fdpexpect.fdspawn(ser)
temp.expect("message")
Basically, from temp onwards you should call methods on the temp object, not on the serial object. This includes the sendline() call, later on.
How to truly implement timeout in python? http://eventlet.net/doc/modules/timeout.html
Code looks like:
#!/usr/bin/python
import eventlet
import time
import sys
import random
while True:
try:
with eventlet.timeout.Timeout(1, False):
print 'limited by timeout execution'
while True:
print '\r' + str(random.random()),
sys.stdout.flush()
eventlet.sleep(0)
print ' Never printed Secret! '
except Exception as e:
print ' Exception: ', e
finally:
print ''
print ' Timeout reached '
print ''
Time out will never reached. Where am I wrong?
P.s. I replaced:
time.sleep(0.1)
with:
eventlet.sleep(0)
Add False for exception, now it works well:
with eventlet.timeout.Timeout(1):
change to:
with eventlet.timeout.Timeout(1, False):
But it works only with eventlet.sleep(0.1)
E.g this code wrong:
#!/usr/bin/python
import eventlet
import time
start_time = time.time()
data = 0
with eventlet.timeout.Timeout(1, False):
while True:
data +=1
print 'Catch data ', data, ' in ', time.time() - start_time
I simply add sleep 0 seconds:
eventlet.sleep(0)
And it works like a charm.
Solved
eventlet's Timeout isn't as magical as you'd hoped. It can only detect timeouts in "greenthreaded" code -- code that uses eventlet's system of cooperative multihtreading. As noted in the Timeout docs, "you cannot time out CPU-only operations with this class". time.sleep pauses with Python's internal threading system, not eventlet's greenthreads.
Instead, use eventlet.sleep which works correctly with greenthreads.