Problems when storing and processing serial data - python

I am reading serial data and trying to sort and display individual data packets.
The problem is that when I receive a packet, I can print it using print(), and the packet prints perfectly OK. But when I add this packet to the list[] or try to add the packet to Listbox(tkinter), the packets sometimes get combined/scrambled and I get two packets displayed as one longer packet.
The serial data comes in irregular intervals and sometimes in bursts of few packets in a very short time.
Packet length is variable between 9 and 26 ASCII characters, and it is terminated with \r
Serial speed is at the moment limited to 115200, and this is a minimum speed to get bursts of packets transferred without chocking the communication.
Here is what I have tried so far:
tried to run processing without the queue, just directly process packets as they come – result, print() all packets printed ok, but when adding packets to list[] and/or Listbox(tkinter) some packets were combined into single packet.
tried to implement the FIFO queue, and add packets to the queue in one thread, and remove/process them in the other thread, so I thought they will be completely independent - result - same as above, print() all packets printed ok, but when adding packets to list[] and/or Listbox(tkinter) some packets were combined into single packet.
I have tried many variations of the code, and all of it failed. The print function displays packets correctly, but when I try to do anything with them, namely sort them into lists or add them to listbox, some of the packets get combined…. It seems that when packets are coming in bursts with minimal time delay between the packets, python can not process them fast enough? But the print function does keep up, and when I run separate threads for placing packets into queue and separate thread for reading them out of the queue, the timing should not be an issue, right?
Please help, I have exhausted all my ideas…
I am using serial read line function to get packets:
def serial_read_line(port):
ser = serial.Serial(port, 115200, timeout=0.01)
ser.flushInput()
while 1:
x = ser.readline()
if len(x) < 1:
pass
else:
y = str(x.decode('utf-8'))
if y != x:
x = y
return y
The above function is run in a separate thread so it does not block the program.
# *********** Scanning for packets **********
# scan for incoming packets
# set global variable for start/stop packet scan
stop = 1
q1 = queue.Queue()
def ser_packet_scan():
port = serial_port_selection.get()
global q1
while True:
packet_1 = sio.serial_read_line(port)
q1.put(packet_1)
process_packets()
if stop == 1:
break
# starting thread for packet scan
def start_thread():
global stop
stop = 0
th = Thread(target=ser_packet_scan)
th.start()
th2 = Thread(target=process_packets)
th2.start()
# stopping thread for packet scan
def stop_thread():
global stop
stop = 1
The processing is run in a separate thread, I have also tried to run it in the main program thread.
def process_packets():
# sort packets into lists
global q1
pckt_r = q1.get()
print(pckt_r)
pck.sort_into_lists(pckt_r)
# update packet lists
if list_box_selection.get() == 'Running':
list_box_display.insert(0, pckt_r)
# limit listbox display size
if list_box_display.size() > 100:
list_box_display.delete(END)
…
…
# time.sleep(0.5) have tried with the sleep and without it… no diference...
q1.task_done()

OK, I found a solution. I also found a couple of issues.
print() function does automatically new line and it understands \r, so my print output was always correct even though the packet was consisting of two or more packets.
the inbuilt function readline() did not work very well. Some packets were combined even though there was an \r character present.
Solution:
I have replaced inbuilt readline() with custom function:
def read_line_c(ser):
buf = bytearray()
i = buf.find(b"\r")
if i >= 0:
r = buf[:i + 1]
buf[i + 1:]
return r
while True:
i = max(1, min(2048, ser.in_waiting))
data = ser.read(i)
i = data.find(b"\r")
if i >= 0:
r = buf + data[:i + 1]
buf[0:] = data[i + 1:]
return r
else:
buf.extend(data)
The packets appear to be all OK with this custom readline, but I need to test it a bit more to see if there are any issues... Alternatively I could use the inbuilt readline(), and then look for packets which have \r in them and split them...

Related

How to check simultaneously for serial input and input from keyboard (simultaneous use of readchar and serial library)

I am trying to code the following using python3 in a raspberry pi:
1) wait for a 14 digits bar code (barcode scanner connected through usb port and input received as keyboard data)
2) after a barcode is read, wait for serial communication (device connected to usb port and sends serial commands. could be one, or more....) the idea is that all commands received are going to be associated with the scanned barcode
3) the process of waiting for serial commands has to stop when a new barcode is read. THIS IS THE PART I HAVE NOT FIGURED OUT HOW TO DO IT
After some research, I decided to use the "readchar" library for the barcode scanner and the "serial" library for the serial communication received. Both of them work by themselves but the problem is when I try to detect both things at the same time.
In the following code, I managed to read a barcode and then wait for 5 lines of serial communication to finally repeat the process and read a barcode again. The program works as it is right now BUT the problem is that I don't know how many lines of serial communication I will receive so I need to somehow detect a new barcode while also waiting to receive the serial communication.
import readchar
import time
import serial
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate = 115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
print("Waiting for barcode...")
while 1:
inputStr = ""
while len(inputStr) != 14: #detect only 14 digit barcodes
inputStr += str(readchar.readchar())
inputStr = ''.join(e for e in inputStr if e.isalnum()) #had to add this to strip non alphanumeric characters
currentCode = inputStr
inputStr = ""
print(currentCode)
ser.flushInput()
time.sleep(.1)
# Wait for 5 lines of serial communication
# BUT it should break the while loop when a new barcode is read!
count = 0
while count < 5:
dataRead=ser.readline()
if len(dataRead) > 0:
print(dataRead)
count+=1
print("Waiting for barcode...")
If I add a condition to the while loop that reading the serial communication using (ser.readline()) so that if a character is read from the scanner (readchar.readchar()) then it messes thing up. It is like if readline and reacher can not be in the same while loop.
Doing some research I think I need to use Asynchronous IO, or threads or something like that, but I have no clue. Also I don't know if I could keep using the same libraries (serial and readchar). Please help
I cannot be sure (I don't have your barcode reader and serial port device) but based on what you say I don't think you need threads, you just have to rely on the buffers to keep your data stored until you have time to read them.
Simply change the condition on your second while loop to:
while serial.inWaiting() != 0:
This way you will make sure the RX buffer on your serial port will empty. This approach might or might not work depending on the speed and timing of your devices.
You could also try to add a short delay after the buffer is emptied:
import serial
import time
ser=serial.Serial(port="/dev/ttyUSB0",baudrate=115200, timeout=1.0)
time.sleep(1)
data=b""
timeout = time.time() + 1.0
while ser.inWaiting() or time.time()-timeout < 0.0: #keep reading until the RX buffer is empty and wait for 1 seconds to make sure no more data is coming
if ser.inWaiting() > 0:
data+=ser.read(ser.inWaiting())
timeout = time.time() + 1.0
else:
print("waiting...")
This keeps trying to read from the port for 1 second after the last byte is received, to make sure nothing else is coming. You might want to play with the duration of the delay depending, again, on the speed and timing of your devices.
Again, I don't have your devices, so I'm in no position to judge, but the way you read characters from the barcode/keyboard looks far from optimum. I doubt readchar is the best approach. At the end of the day, your barcode reader is probably a serial port. You might want to dig into that and/or find a more efficient way to read several keyboard strokes in one go.
I found this answer in another question:
How to read keyboard-input?
I have tried it and it works! I´ll also give a try to the method proposed by Marcos G.

PySerial timeout and callback

I have this short snippet of code here that works fine, but I have problem of getting rid of the hardcoded part.
ser = serial.Serial()
ser.baudrate = 38400
ser.port = '/dev/ttyUSB0'
ser.parity = serial.PARITY_EVEN
ser.timeout = 1
ser.open()
ser.flushInput()
ser.write(command) #command here is a simple request for data to my device
msg = ser.read(200)
ser.close()
While this works fine, the problem I'm having is this. The length of the returned message can vary from 8 byte to almost 200 bytes depending on what was registered. By using a timeout, I prevent my read command from stalling if it doesn't receive 200 bytes. I also don't know ahead the length of the returned message I therefore can't change dynamically the ser.read. Also, there is no constant endline or constant character at the end of the transmission to lock on in a while loop.
Is there a more stable/dynamic way to do this? I could run out of time if the request is too long or I could bust my read buffer without having the complete data transmission. On the other end, increasing the timer mean that my request rate will be slowed down (there is no problem in increasing the read buffer however).
If the reply had a header containing a length field, you could do a fixed-size read() to get the header, then a variable read() to get the rest...
If there is truly no way to tell how big the reply is, then a timeout is the only conceivable solution. However, you have apparently missed the detail that PySerial has two different timeout values: one that applies to the overall operation, and one that applies to gaps between characters. You could set timeout to multiple seconds, so that you never prematurely end a valid reply, and set inter_byte_timeout (was interCharTimeout in older versions) to perhaps 0.1 second, so that your read() will end almost immediately once the device stops sending data. (This assumes that the device never inserts pauses in the middle of sending a reply.)
If the responses (from 8 bytes to 200 bytes) are contiguous, then you could have a loop which concatenates bytes received from calls to ser.read(200), but having set the timeout to something like 1/100th second. Then when you have two timeouts in succession where no bytes were received, then you know you are at the end of the message.
exit = 0
while exit < 2:
more = ser.read(200)
msg += more
if len(more) == 0:
exit += 1
else:
exit = 0

recv() function too slow

Hi i'm quite a newbie to Python. I' writting a simple LAN game (not simple for me) using a pygame module.
Here's the problem - I have two computers (one old intel Atom netbook, the other intel i5 NTB). I want to achieve at least 5 FPS (the netbook is slowering the NTB, but not so much, now i have around 1,5 FPS), but calling recv() function twice a main loop takes total around 0,5 seconds on each machine. The wifi signal is strong and the router is 300Mbit/s and it sends a short roughly 500-character string. As you can see for measuring time i use time.clock().
Here's the part of the "server" code, which i usually run on the i5 NTB:
while 1:
start = time.clock()
messagelen = c.recv(4) #length of the following message (fixed 4 character)
if " " in messagelen:
messagelen = messagelen.replace(" ","")
message = cPickle.loads(c.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
arrowsmod = message[0]
modtankposan = message[1]
removelistmod = message[2]
for i in removelistmod:
try:
randopos.remove(i)
except ValueError:
randopossv.remove(i)
print time.clock()-start
tosendlist=[]
if len(arrows) == 0: #if there are no arrows it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(arrows)
tosendlist.append([zeltankpos, 360-angle])
if len(removelist) == 0: #if there are no changes of the map it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(removelist)
removelist=[]
tosend=cPickle.dumps(tosendlist)
tosendlen = str(len(tosend))
while len(tosendlen)<4:
tosendlen+=" "
c.sendall(tosendlen) #sends the length to client
c.sendall(tosend) #sends the actual message(dumped list of lists) to client
...something else which takes <0,05 sec on the NTB
Here's the part of the "client" game code (just inverted the beginning - sending/receiving parts):
while 1:
tosendlist=[]
if len(arrows) == 0: #if there are no arrows it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(arrows)
tosendlist.append([zeltankpos, 360-angle])
if len(removelist) == 0: #if there are no changes of the map it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(removelist)
removelist=[]
tosend=cPickle.dumps(tosendlist)
tosendlen = str(len(tosend))
while len(tosendlen)<4:
tosendlen+=" "
s.sendall(tosendlen) #sends the length to server
s.sendall(tosend) #sends the actual message(dumped list of lists) to server
start = time.clock()
messagelen = s.recv(4) #length of the following message (fixed 4 character)
if " " in messagelen:
messagelen = messagelen.replace(" ","")
message = cPickle.loads(s.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
arrowsmod = message[0]
modtankposan = message[1]
removelistmod = message[2]
for i in removelistmod:
try:
randopos.remove(i)
except ValueError:
randopossv.remove(i)
print time.clock()-start
... rest which takes on the old netbook <0,17 sec
When I run let's say a single player version of the game on one machine (without the socket module) on the i5 NTB it has 50 FPS in the up left corner of the map and 25 FPS in the down right corner (the 1000x1000 pixel map contains 5x5 pixel squares, i think it's slower because of the bigger coordinates, but i can't believe that so much. BTW recv while ran as a LAN game in the down right corner of the map takes approx. the same time)
on the Atom netbook it has 4-8 FPS.
So could you please tell me, why it's so slow? The computers are not synchronized, one is faster, the other slower, but it can't be that they are waiting for each other, it would be max 0,17 secs delay, right? And plus the long recv calling would be only on the faster computer?
Also I don't exactly know how the send/recv function work. It's weird the sendall takes literally no time while receiving takes 0,5 secs. Maybe sendall
is trying to send in the background while the rest of the program continues forward.
As mentioned by Armin Rigo, recv will return after packets are received by the socket, but packets don't necessarily need to be transmitted immediately after calling send. While send returns immediately, OS caches the data internally and might wait some time for more data being written to the the socket before actually transmitting it; this is called Nagle's algorithm and avoids sending lots of small packets over the network. You can disable it and push packets quicker to the wire; try enabling TCP_NODELAY options on the sending socket (or both if your communication is bidirectional), by calling this:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
This could potentially reduce amount of time recv is sleeping due to no data.
As the Wikipedia states:
This algorithm interacts badly with TCP delayed acknowledgments, a
feature introduced into TCP at roughly the same time in the early
1980s, but by a different group. With both algorithms enabled,
applications that do two successive writes to a TCP connection,
followed by a read that will not be fulfilled until after the data
from the second write has reached the destination, experience a
constant delay of up to 500 milliseconds, the "ACK delay". For this
reason, TCP implementations usually provide applications with an
interface to disable the Nagle algorithm. This is typically called the
TCP_NODELAY option.
There is a mention of 0.5s which you're seeing in your benchmark, so this might be a reason.
Yes, send() or sendall() will occur in the background (unless the connexion is saturated right now, i.e. there is already too much data waiting to be sent). By contrast, recv() will immediately get the data only if it arrived already, but if none did, it waits. Then it returns possibly a fraction of it. (I am assuming that c is a TCP socket, not a UDP one.) Note that you should not assume that recv(N) returns N bytes; you should write a function like this:
def recvall(c, n):
data = []
while n > 0:
s = c.recv(n)
if not s: raise EOFError
data.append(s)
n -= len(s)
return ''.join(data)
Anyway, to the point. The issue is not the speed of recv(). If I understood correctly, there are four operations:
the server renders (1/25th sec)
the server sends something on the socket, received by the client;
the client renters (1/4th sec);
the client send something back on the socket.
This takes almost (0.3 + 2 * network_delay) seconds. Nothing occurs in parallel. If you want more frames-per-second, you need to parallelize some of these four operations. For example, let's assume reasonably that the operation 3 is by far the slowest. Here's how we can make 3 run in parallel with the three other operations. You should change the client so that it receives data, process it, and immediately sends an answer to the server; and only then it proceeds to render it. This should be enough in this case, as it takes 1/4th seconds to do this rendering, which should be enough time for the answer to reach the server, the server to render, and the next packet to be sent again.
I ended up here when having same issue with it appearing that socket recv in python to be super slow. The fix for me (after days) was to do something along the lines:
recv_buffer = 2048 # ? guess & check
...
rx_buffer_temp = self._socket.recv(recv_buffer)
rx_buffer_temp_length = len(rx_buffer_temp)
recv_buffer = max(recv_buffer, rx_buffer_temp_length) # keep to the max needed/found
The gist of it is set to the amount of bytes trying to receive closest to the actual expected.

Reading serial data in realtime in Python

I am using a script in Python to collect data from a PIC microcontroller via serial port at 2Mbps.
The PIC works with perfect timing at 2Mbps, also the FTDI usb-serial port works great at 2Mbps (both verified with oscilloscope)
Im sending messages (size of about 15 chars) about 100-150x times a second and the number there increments (to check if i have messages being lost and so on)
On my laptop I have Xubuntu running as virtual machine, I can read the serial port via Putty and via my script (python 2.7 and pySerial)
The problem:
When opening the serial port via Putty I see all messages (the counter in the message increments 1 by 1). Perfect!
When opening the serial port via pySerial I see all messages but instead of receiving 100-150x per second i receive them at about 5 per second (still the message increments 1 by 1) but they are probably stored in some buffer as when I power off the PIC, i can go to the kitchen and come back and im still receiving messages.
Here is the code (I omitted most part of the code, but the loop is the same):
ser = serial.Serial('/dev/ttyUSB0', 2000000, timeout=2, xonxoff=False, rtscts=False, dsrdtr=False) #Tried with and without the last 3 parameters, and also at 1Mbps, same happens.
ser.flushInput()
ser.flushOutput()
While True:
data_raw = ser.readline()
print(data_raw)
Anyone knows why pySerial takes so much time to read from the serial port till the end of the line?
Any help?
I want to have this in real time.
Thank you
You can use inWaiting() to get the amount of bytes available at the input queue.
Then you can use read() to read the bytes, something like that:
While True:
bytesToRead = ser.inWaiting()
ser.read(bytesToRead)
Why not to use readline() at this case from Docs:
Read a line which is terminated with end-of-line (eol) character (\n by default) or until timeout.
You are waiting for the timeout at each reading since it waits for eol. the serial input Q remains the same it just a lot of time to get to the "end" of the buffer, To understand it better: you are writing to the input Q like a race car, and reading like an old car :)
A very good solution to this can be found here:
Here's a class that serves as a wrapper to a pyserial object. It
allows you to read lines without 100% CPU. It does not contain any
timeout logic. If a timeout occurs, self.s.read(i) returns an empty
string and you might want to throw an exception to indicate the
timeout.
It is also supposed to be fast according to the author:
The code below gives me 790 kB/sec while replacing the code with
pyserial's readline method gives me just 170kB/sec.
class ReadLine:
def __init__(self, s):
self.buf = bytearray()
self.s = s
def readline(self):
i = self.buf.find(b"\n")
if i >= 0:
r = self.buf[:i+1]
self.buf = self.buf[i+1:]
return r
while True:
i = max(1, min(2048, self.s.in_waiting))
data = self.s.read(i)
i = data.find(b"\n")
if i >= 0:
r = self.buf + data[:i+1]
self.buf[0:] = data[i+1:]
return r
else:
self.buf.extend(data)
ser = serial.Serial('COM7', 9600)
rl = ReadLine(ser)
while True:
print(rl.readline())
You need to set the timeout to "None" when you open the serial port:
ser = serial.Serial(**bco_port**, timeout=None, baudrate=115000, xonxoff=False, rtscts=False, dsrdtr=False)
This is a blocking command, so you are waiting until you receive data that has newline (\n or \r\n) at the end:
line = ser.readline()
Once you have the data, it will return ASAP.
From the manual:
Possible values for the parameter timeout:
…
x set timeout to x seconds
and
readlines(sizehint=None, eol='\n') Read a list of lines,
until timeout. sizehint is ignored and only present for API
compatibility with built-in File objects.
Note that this function only returns on a timeout.
So your readlines will return at most every 2 seconds. Use read() as Tim suggested.

Using PySerial is it possible to wait for data?

I've got a Python program which is reading data from a serial port via the PySerial module. The two conditions I need to keep in mind are: I don't know how much data will arrive, and I don't know when to expect data.
Based on this I have came up with the follow code snippets:
#Code from main loop, spawning thread and waiting for data
s = serial.Serial(5, timeout=5) # Open COM5, 5 second timeout
s.baudrate = 19200
#Code from thread reading serial data
while 1:
tdata = s.read(500) # Read 500 characters or 5 seconds
if(tdata.__len__() > 0): #If we got data
if(self.flag_got_data is 0): #If it's the first data we recieved, store it
self.data = tdata
else: #if it's not the first, append the data
self.data += tdata
self.flag_got_data = 1
So this code will loop forever getting data off the serial port. We'll get up to 500 characters store the data, then alert the main loop by setting a flag. If no data is present we'll just go back to sleep and wait.
The code is working, but I don't like the 5s timeout. I need it because I don't know how much data to expect, but I don't like that it's waking up every 5 seconds even when no data is present.
Is there any way to check when data becomes available before doing the read? I'm thinking something like the select command in Linux.
Note: I found the inWaiting() method, but really that seems it just change my "sleep" to a poll, so that's not what I want here. I just want to sleep until data comes in, then go get it.
Ok, I actually got something together that I like for this. Using a combination of read() with no timeout and the inWaiting() method:
#Modified code from main loop:
s = serial.Serial(5)
#Modified code from thread reading the serial port
while 1:
tdata = s.read() # Wait forever for anything
time.sleep(1) # Sleep (or inWaiting() doesn't give the correct value)
data_left = s.inWaiting() # Get the number of characters ready to be read
tdata += s.read(data_left) # Do the read and combine it with the first character
... #Rest of the code
This seems to give the results I wanted, I guess this type of functionality doesn't exist as a single method in Python
You can set timeout = None, then the read call will block until the requested number of bytes are there. If you want to wait until data arrives, just do a read(1) with timeout None. If you want to check data without blocking, do a read(1) with timeout zero, and check if it returns any data.
(see documentation https://pyserial.readthedocs.io/en/latest/)
def cmd(cmd,serial):
out='';prev='101001011'
serial.flushInput();serial.flushOutput()
serial.write(cmd+'\r');
while True:
out+= str(serial.read(1))
if prev == out: return out
prev=out
return out
call it like this:
cmd('ATZ',serial.Serial('/dev/ttyUSB0', timeout=1, baudrate=115000))

Categories

Resources