Sending data over serial in python from different functions - python

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)

Related

How do I correctly manage the serial resource? [Python]

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.

Incoming serial data is not getting printed on the console using pyserial

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")

How to call a python function with parameters from a batch file

I want to call a python function with one or more arguments using a batch file. Is this possible?
Not sure where to start with this one!
Python function code
def my_function(port):
#Import Serial
import serial
# Set COM Port.....
ser = serial.Serial('COM' + port, 115200, timeout=0,
parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, rtscts=0)
ser.close()
You have to do something like this:
import sys
if __name__ == '__main__':
myfunc(sys.argv)
EDIT
Note that the first element of sys.argv is your script's name, so if your function is declared this way myfunc(a, b) you have to call it like myfunc(*sys.argv[1:]). You would also to do some validations.
Maybe you want some more and easy to use functionality. Use click! https://click.palletsprojects.com/en/7.x/

Is this the correct way to use variable between 2 Threads?

I have a script that read data from Serial Port, so I have an infinite loop that always fill my data to global variable, and also I schedule a function that run every X seconds to post in the database, and this function also use the same global variable.
Here's a small example I create it to show you my situation :
import serial
import schedule
import threading
shared_var = []
def save_to_db():
print(threading.current_thread())
global shared_var
for l in shared_var:
print(l)
shared_var.clear()
def run_threaded(job_func):
job_thread = threading.Thread(target=job_func)
job_thread.start()
ser = serial.Serial() # initialize the serial
ser.baudrate = 115200 # set the baud rate : default 115200
ser.port = "/dev/ttyUSB0" # set the port to use
ser.timeout = 30
ser.write_timeout = None
if not ser.is_open:
ser.open() # Open port
ser.write(b'scan=01\r\n') # Stop scan if already started
schedule.every(5).seconds.do(run_threaded, save_to_db)
while 1:
schedule.run_pending()
line = ser.readline()
shared_var.append(line)
print(threading.current_thread())
Is this code can cause a problem ? more specific what will happen if the MainThread (the one that read from Serail Port and write to shared_var) write to the shared variable between the 2 thread and in the same moment the other Thread read from the variable, is This will cause a problem because the 2 threads gonna access the same global variable in the same time ? and if yes It's a problem should I use mutex mechanism for that ?
Yes it is sure that you will have a problem if 2 processes affect the same variable at the same time.
To overcome this you must use threading.Lock() (this is Threading's mutex system).
lock = threading.Lock()
lock.acquire()
try:
yourVariable += 1
finally:
lock.release()
I hope I helped you.

Reading serial input and printing to Tkinter GUI

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.

Categories