Is it possible to achieve full-duplex communication using PySerial? Specifically, would it be possible to monitor the port continuously for input and write whenever needed? I imagine it should be possible using threads (and serial interfaces are full duplex no?). If not, what would be the best approach to monitoring a serial port when not transmitting? A timeout?
Edit: Here's my attempt at it. This code is targeting TI's CC2540 Bluetooth LE chip. On sending the GATT init message I expect a reply (detailing the operating parameters of the chip)...I'm getting nothing though
import serial
import threading
from time import sleep
serial_port = serial.Serial()
GAP_DeviceInit = \
"\x01\x00\xfe\x26\x08\x03\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
def read():
while True:
data = serial_port.read(9999);
if len(data) > 0:
print 'Got:', data
sleep(0.5)
print 'not blocked'
def main():
serial_port.baudrate = 57600
serial_port.port = '/dev/ttyACM0'
serial_port.timeout = 0
if serial_port.isOpen(): serial_port.close()
serial_port.open()
t1 = threading.Thread(target=read, args=())
while True:
try:
command = raw_input('Enter a command to send to the Keyfob: \n\t')
if (command == "1"):
serial_port.write(message)
except KeyboardInterrupt:
break
serial_port.close()
Yes serial port hardware is full duplex. Yes, you can use threads to do Rx and Tx at the same time. Alternatively, you can use a single thread loop that does reads with a short timeout and alternates between reading and writing.
You didn't specify a timeout, so the read waits for the full number of bytes to receive and only then displays anything.
Related
In my project, there's a Raspberry Pi Zero W, linked with Arduino Pro Micro over the serial TX/RX lines (through 5 - 3.3V logic level converter). At power-on, the Arduino starts sending "are you there" command over the serial, until Pi has finished booting and launched a python program that listens on serial port, and it replies with "yes". Then Arduino proceeds with sending regular updates with sensor data.
For some reason not yet understood, the Arduino can disconnect and reboot, while the Pi is still running. And, for some reason, the connection on the Python end is severed in a way that does not raise exception if connection.in_waiting is read.
import serial
import time
ser = serial.Serial('/dev/serial0')
ser.baudrate = 9600
cmd = b''
while True:
time.sleep(1)
print(ser.is_open)
while ser.in_waiting:
ch = ser.read()
if ch == b'\n':
print('New command:', cmd)
cmd = b''
ser.write(b'OK\n')
continue
else:
cmd = cmd + ch
I have tested with this simple code, but in my tests, if I disconnect the Arduino and connect it back, the ser.is_open never is False, and the old connection works just fine also with reconnected Arduino. So, unfortunately I can not exactly replicate the loss of connection scenario when the data stop coming after disconnection. However, in order to investigate further, I'd like to add some monitoring code that would log serial disconnection incidents in file. But is there a way? If connection.is_open is always true, even without Arduino connected, then how one can detect if there is no connection?
The port remains open if there's a disconnection so your example code will loop continuously.
import serial
def connect(port):
return serial.Serial(port, baudrate=9600, timeout=1)
ser = connect('/dev/serial0')
buf = bytearray()
while True:
i = buf.find(b'\n')
if i >= 0:
buf = buf[i + 1:]
ser.write('OK\n')
num_bytes = max(1, min(1024, ser.in_waiting))
data = ser.read(num_bytes)
if data:
buf.extend(data)
else:
# no data to read aka disconnection has occurred
ser = connect('/dev/serial0')
if ser.isOpen():
continue
else:
# could not reconnect
break
I am trying to talk to a Stanford Research Systems SR760 spectrum analyzer on my mac (10.7.5) via Serial, using a Serial-to-USB adapter to connect to my laptop. I am using the Prolific USB-serial driver. Not sure which but I installed it recently. It probably is the PL2303 one.
Using Python, here's some sample code
import time
import serial
# configure the serial connections (the parameters differs on the device you
# are connecting to)
ser = serial.Serial(
port='/dev/cu.PL2303-0000201A',
baudrate=19200,
parity=serial.PARITY_NONE,
bytesize=serial.EIGHTBITS,
stopbits=serial.STOPBITS_ONE,
rtscts=0,
dsrdtr=0,
timeout=2,
)
if ser.isOpen():
ser.flushInput()
ser.flushOutput()
print """Enter your commands below.\r\nInsert "exit" to leave the
application."""
while 1:
# get keyboard input
input = raw_input(">> ")
if input == 'exit':
ser.close()
exit()
else:
ser.write(input + '\r')
out = ''
# let's wait one second before reading output (let's give device
# time to answer)
lines = 0
while 1:
time.sleep(1)
out = out + ser.readline()
lines = lines + 1
if lines > 5:
break
print "read data: " + out
Using the SR760's manual, I send it: *IDN?, a basic "identify" command. I expect for something to pop up in my terminal, nothing does. It just times out. However, if I look at the send queue on the SR760, it will show the identity string, and in fact responds to a bunch of different commands. I'm just not getting anything on my computer and that is the problem. I know it is supposed to work that way because my colleague wrote code that words on his computer (a windows laptop).
How do I even start debugging this? I've tweaked the timeout, and confirmed the sr760 had the same parameters I was expecting.
I'm trying to write a function which continuously reads serial input. The function must be able to handle unexpected disconnections from the serial port and reconnect when possible. Despite reading several question posts on stackOverflow and looking through the pySerial documentation, I have yet to find a solution.
Here's my code:
def serialRead(serialPort, queue):
"""Adds serial port input to a queue."""
ser = serial.Serial(serialPort - 1, timeout = 2)
ser.parity = "O"
ser.bytesize = 7
while(True):
try:
if(ser == None):
ser = serial.Serial(serialPort - 1, timeout = 2)
ser.parity = "O"
ser.bytesize = 7
print("Reconnecting")
queue.put(ser.read(27))
ser.write(chr(6).encode())
print("Writing Data...")
except:
if(not(ser == None)):
ser.close()
ser = None
print("Disconnecting")
print("No Connection")
time.sleep(2)
Here's my output:
Enter a Serial Port: 7
Writing Data...
Writing Data...
Writing Data...
Writing Data...
I start with my device connected. After leaving the program run, neither "Disconnecting" or "No Connection" display and the program stops (it doesn't crash).
This code works. Batman tested the program on an Arduino connection and I found that my program had successfully reconnected with the device after a period of time. I hope this code will be useful for those struggling with something similar.
I am connecting with my Arduino through a USB port and sending data to it by using PySerial module. At first I can check if the device is connected by using this code:
try:
ser = serial.Serial("COM3", 9600)
except serial.serialutil.SerialException:
print "Arduino not connected"
Now what I want to do is to check periodically if the Arduino is still connected to the computer. I tried ser.isOpen() but this returns true even if the Arduino is disconnected. I would also like to know how to reconnect the device. I mean once you disconnect the device the program can no longer send any data to Arduino.
Most of the answers propose 2 approaches:
In some point of the code, send some sort of message through serial to check if your device is still alive
Start a separate thread and continuously check if the device is alive by opening a communication
The problem with the first solution is that you are not always checking the connection, but only checking in some specific points: this solution isn't very elegant and if badly written could even be not working.
The second solution solves the problem of the first solution, but introduces a new problem: checking the connection, or worst sending a message, in a threaded loop will cause problem or may even interrupt the connection to the device from other functions.
A solution that allows you to constantly check the connection without monopolizing the communication involves the reading of the existing COM:
import serial.tools.list_ports
myports = [tuple(p) for p in list(serial.tools.list_ports.comports())]
print myports
output:
[(u'COM3', u'Arduino Due Programming Port (COM3)', u'some more data...'),
(u'COM6', u'USB Serial Port (COM6)', u'some more data...'),
(u'COM100', u'com0com - serial port emulator (COM100)', u'some more data...')]
then we save the tuple that contains our port:
arduino_port = [port for port in myports if 'COM3' in port ][0]
then we create a function that checks if this port is still present:
import time
def check_presence(correct_port, interval=0.1):
while True:
myports = [tuple(p) for p in list(serial.tools.list_ports.comports())]
if arduino_port not in myports:
print "Arduino has been disconnected!"
break
time.sleep(interval)
At last, we run this function as a daemon thread:
import threading
port_controller = threading.Thread(target=check_presence, args=(arduino_port, 0.1,))
port_controller.setDaemon(True)
port_controller.start()
in this way, you'll check each 0.1 secs if the arduino is still connected, and the thread will end when arduino is disconnected or all other activities have ended
You can set a timeout.
import serial
ser = serial
try:
ser = serial.Serial("COM3", 9600, timeout=10)
while ser.read():
print 'serial open'
print 'serial closed'
ser.close()
except serial.serialutil.SerialException:
print 'exception'
Unfortunately, the best way I can find to do this is to try some communication and see if it fails. A pretty safe way would be:
try:
ser.inWaiting()
except:
print "Lost connection!"
You'll probably still want to close the connection with a ser.close() after the connection is lost, although you may need to place that in a "try:except" block also.
import serial
import time
ser = serial.Serial()
ser.braudrate = 115200
ser.port = "/dev/ttyUSB0"
ser.open()
print(ser.name)
if ser.isOpen():
print("serial is open!")
ser.close()
For example to detect ttyUSB0:
import os
x=os.system("ls /dev/ttyUSB0")
if x==0:
print "connected"
else:
print "disconnected"
i suggest to use a python thread class to istantiate a serial connection, in the run methos put your while loop , set an var that you use for kill iy at the end, the second public var that you use for store data if have receive and load data in main method.. soon paste an example
class Arduino():
def __init__(self,Port='/dev/ttyUSB0',Boud=9600,connState=0):
self.parent=self
self.port=Port
self.boud=Boud
self.connState=connState
self.timeount=1
self.ser=None
self.connect()
def connect(self):
try:
self.ser=serial.Serial(self.port,self.boud,timeout=0.0001)
self.connState=1
return [1,'connect']
except:
self.connState=0
return [0,'no hardware found']
def loadData(self):
self.buffer=self.ser.read(1)
if (self.buffer!=''):
try:
print self.buffer
except Exception, e:
pass
ard=Arduino()
while True:
if ard.connState:
ard.loadData()
else:
print "Arduino not found"
break
and start with:
import threading
class ThController( threading.Thread ):
# Override Thread's __init__ method to accept the parameters needed:
def __init__( self,parent):
self.parent = parent
threading.Thread.__init__ ( self )
def run ( self ):
while self.parent.ctrlattive:
j=json.loads(data)
self.parent.data=j
The issue I have is that my chat client is supposed to recieve and print data from server when the server sends it, and then allow the client to reply.
This works fine, except that the entire process stops when the client is prompted to reply. So messages pile up until you type something, and after you do that, then it prints all the recieved messages.
Not sure how to fix this, so I decided why not have the client's time to type a reply timeout after 5 seconds, so that the replies can come through regardless. It's pretty flawed, because the input will reset itself, but it works better anyways.
Here's the function that needs to have a timeout:
# now for outgoing data
def outgoing():
global out_buffer
while 1:
user_input=input("your message: ")+"\n"
if user_input:
out_buffer += [user_input.encode()]
# for i in wlist:
s.send(out_buffer[0])
out_buffer = []
How should I go about using a timeout? I was thinking of using time.sleep, but that just pauses the entire operation.
I tried looking for documentation. But I didn't find anything that would help me make the program count up to a set limit, then continue.
Any idea's about how to solve this? (Doesn't need to use a timeout, just needs to stop the message pileup before the clients reply can be sent) (Thanks to all who helped me get this far)
For Ionut Hulub:
from socket import *
import threading
import json
import select
import signal # for trying to create timeout
print("client")
HOST = input("connect to: ")
PORT = int(input("on port: "))
# create the socket
s = socket(AF_INET, SOCK_STREAM)
s.connect((HOST, PORT))
print("connected to:", HOST)
#--------- need 2 threads for handling incoming and outgoing messages--
# 1: create out_buffer:
out_buffer = []
# for incoming data
def incoming():
rlist,wlist,xlist = select.select([s], out_buffer, [])
while 1:
for i in rlist:
data = i.recv(1024)
if data:
print("\nreceived:", data.decode())
# now for outgoing data
def outgoing():
global out_buffer
while 1:
user_input=input("your message: ")+"\n"
if user_input:
out_buffer += [user_input.encode()]
# for i in wlist:
s.send(out_buffer[0])
out_buffer = []
thread_in = threading.Thread(target=incoming, args=())
thread_out = threading.Thread(target=outgoing, args=())
thread_in.start() # this causes the thread to run
thread_out.start()
thread_in.join() # this waits until the thread has completed
thread_out.join()
We can use signals for the same. I think the below example will be useful for you.
import signal
def timeout(signum, frame):
raise Exception
#this is an infinite loop, never ending under normal circumstances
def main():
print 'Starting Main ',
while 1:
print 'in main ',
#SIGALRM is only usable on a unix platform
signal.signal(signal.SIGALRM, timeout)
#change 5 to however many seconds you need
signal.alarm(5)
try:
main()
except:
print "whoops"