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")
Related
I'm trying to make a Tkinter based GUI for an Arduino printing sensors value and responding to user input.
The code I'm trying to use to eliminate while loops is this, which doesn't print any sensor information, the only output is "Trying.../dev/ttyACM0" followed by the tkinter window opening.
import serial
import time
from Tkinter import *
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
locations=['/dev/ttyACM0','/dev/ttyACM1','/dev/ttyACM2','/dev/ttyACM3']
for device in locations:
try:
print "Trying...",device
ser = serial.Serial(device, 9600)
break
except:
print "Failed to connect on",device
## loop until the arduino tells us it is ready
while not connected:
serin = ser.read()
connected = True
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
def serialRead():
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t')
print(data)
output_file.write(line)
root.after(1000, serialRead)
root = Tk()
root.title("Temperature Control")
serialRead()
root.mainloop()
This, on the other hand, works perfectly with the exception of not having a tkinter window. But it removes old input from the buffer and reads in the new input.
import serial
import time
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
serial_port = '/dev/ttyACM0'
baud_rate = 9600
ser = serial.Serial(serial_port, baud_rate, timeout=5)
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
while True:
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t') #ser.readline returns a binary, convert to string
print data[0] + '\t' + data[1]
output_file.write(line)
This was inspired by a different stackoverflow post from a while ago: Run an infinite loop in the backgroung in Tkinter
I've seen some example using threading but I don't know much about python nor threading so I'd really like to get it to work with root.after() if that's possible. I've also tried the example using root.after, all of which are pretty similar to the one I linked, and I couldn't get any of them working. Am I doing anything obviously wrong or in a way that's much more difficult than it needs to be? I would appreciate it greatly if someone would point me in the right direction.
I made a UI in TK for reading data from a GPS receiver and I had difficulties getting root.mainloop() to run, so instead I put a callback inside the TK app that ends up calling root.update() rather than mainloop.
The code looks something like this:
class App:
def __init__(self, master):
self.sats = tk.StringVar()
self.satsnum = tk.Label(self.frame, textvariable=self.sats, bg="blue")
self.satsnum.pack()
def update_display(self, master):
while source:
self.sats.set(n_sats)
if n_sats < 10:
satsbgcolor = 'red'
else:
satsbgcolor = 'green'
self.satsnum.configure(bg = satsbgcolor)
master.update()
time.sleep(1)
with serial_link.get_base_args_driver(args) as driver:
with Handler(Framer(driver.read, driver.write, verbose=True)) as source:
root = tk.Tk()
app = App(root)
app.update_display(root)
Note, the time.sleep(1) is necessary on MacOS as tk.update() will leak memory if the update is called too fast.
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 program in C++ and C# normally and am trying to get accustomed to how python works so forgive me if this is a fairly basic question.
I'd like to open a serial connection in python over RS-232 and send data from various functions. Here's the functions that I have to open a serial port and send data:
def sendData(data):
ser = serial.Serial('/dev/ttyUSB0', 115200)
data += "\r\n"
ser.write(data.encode())
Pretty simple, but I'd like to move the ser = serial.Serial('/dev/ttyUSB0', 115200) line outside the function so that I can just call this function on it's own.
Now in other languages I would just make the ser variable public so that other functions could access it, but I'm not sure I'm understanding how variables work in python properly yet. Ideally I'd like something like this:
def main():
ser = serial.Serial('/dev/ttyUSB0', 115200)
while 1:
#misc code here
sendData(data)
I know I could pass the ser variable through the function call and make it sendData(ser, data), but that seems unnecessary. What's the best way to do this?
Thanks!
You can use the global keyword in your main function to assign the public variable:
ser = None
def sendData(data):
data += "\r\n"
ser.write(data.encode())
def main():
global ser
ser = serial.Serial('/dev/ttyUSB0', 115200)
while 1:
#misc code here
sendData(data)
Or even better, using a class:
class SerialWrapper:
def __init__(self, device):
self.ser = serial.Serial(device, 115200)
def sendData(self, data):
data += "\r\n"
self.ser.write(data.encode())
def main():
ser = SerialWrapper('/dev/ttyUSB0')
while 1:
#misc code here
ser.sendData(data)
MY PROBLEM:
The code below achieves all of my goals for my application except for one problem: my tkinter spinbox sets baud rate and functions correctly (i think). It populates the pyserial baud rate target variable, but no matter what baud rate I specify in the spinbox, the serial communication i receive from my arduino at 9600 continues to be received and displayed fine. (SHOULDN'T IT FAIL IF I SET BAUD TO DIFFERENT RATE?)
I've pared down the code upon request. Below is the code without any UI, simply printing serial data received to the terminal. No matter what I set the baud rate to, it continues to work fine. But the arduino sending serial data is definitely sending at 9600.
#!/usr/bin/env python
#Import Libraries--------------------------
import serial
import Tkinter as tk
from time import sleep
import threading
import sys
import glob
import smtplib
import csv
import tkMessageBox
import datetime
#Setup--------------------------
data = True #I'm not sure what this is for
#Thread--------------------------
class SensorThread(threading.Thread):
def run(self):
try:
i = 0
while True:
ser = serial.Serial('/dev/ttyACM0', 90000)
happenen = ser.readline()
print(happenen)
i += 1
sleep(1)
except KeyboardInterrupt:
exit()
#------------------------------------------------------------------
if __name__ == "__main__":
SensorThread().start()
#------------------------------------------------------------------
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.