0

I'd like to read from two (or more) serial ports (/dev/ttyUSB0 etc) at the same time in python on Linux. I want to read complete lines from each port (whichever has data) and process the results in the order received (without race conditions). As a simple example could just write the lines to a single merged file.

I assume the way to do this is based on pyserial, but I can't quite figure out how to do it. Pyserial has non-blocking reads using asyncio and using threads. Asyncio is marked as experimental. I assume there wouldn't be any race conditions if the processing is done in asyncio.Protocol.data_received(). In the case of threads, the processing would probably have to be protected by a mutex.

Perhaps this can also be done not in pyserial. The two serial ports can be opened as files and then read from when data is available using select().

Alex I
  • 19,689
  • 9
  • 86
  • 158
  • 2
    Make two threads which read from the serial ports and put data to be processed on a queue. Look up "producer consumer python". – Alex Hall May 29 '16 at 00:52
  • @AlexHall Sounds like an easy solution to the problem. Post an answer with threads+queue+pyserial for instant upvote and accept :) – Alex I May 29 '16 at 06:22

3 Answers3

4

Consider using aioserial.

Here's an example:

import asyncio
import concurrent.futures
import queue

import aioserial


async def readline_and_put_to_queue(
        aioserial_instance: aioserial.AioSerial,
        q: queue.Queue):
    while True:
        q.put(await aioserial_instance.readline_async())


async def process_queue(q: queue.Queue):
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
        while True:
            line: bytes = await asyncio.get_running_loop().run_in_executor(
                    executor, q.get)
            print(line.decode(errors='ignore'), end='', flush=True)
            q.task_done()


q: queue.Queue = queue.Queue()
aioserial_ttyUSB0: aioserial.AioSerial = \
        aioserial.AioSerial(port='/dev/ttyUSB0')
aioserial_ttyUSB1: aioserial.AioSerial = \
        aioserial.AioSerial(port='/dev/ttyUSB1', baudrate=115200)

asyncio.run(asyncio.wait([
    readline_and_put_to_queue(aioserial_ttyUSB0, q),
    readline_and_put_to_queue(aioserial_ttyUSB1, q),
    process_queue(q),
]))
Johann Chang
  • 1,281
  • 2
  • 14
  • 25
3

As suggested by @AlexHall in a comment, here is a solution that uses one thread for each serial port and a queue to synchronize access:

import serial
import Queue
import threading

queue = Queue.Queue(1000)

def serial_read(s):
    while True:
        line = s.readline()
        queue.put(line)

serial0 = serial.Serial('/dev/ttyUSB0')
serial1 = serial.Serial('/dev/ttyUSB1')

thread1 = threading.Thread(target=serial_read, args=(serial0,),).start()
thread2 = threading.Thread(target=serial_read, args=(serial1,),).start()

while True:
    line = queue.get(True, 1)
    print line

It may be possible to write this more elegantly, but it works.

Alex I
  • 19,689
  • 9
  • 86
  • 158
0

You could try to take the values in order and memorise it in variables:

a = data1.read ()
b = data2.read ()

And after process it in order:

If len (a) != 0 or len (b ) != 0:
             Process a
             Process b

Using this method if one or both of the value has data, process it

Giorgio B
  • 193
  • 6
  • In the first solution you propose, data2 may have a whole lot of lines ready to read, but data1 has none, so the first read blocks. That's exactly what I want to avoid. In the second solution using threads, how does a single function process the lines read by both threads in order and without race conditions? – Alex I May 30 '16 at 03:21
  • You only have to insert in your code... threading is multiple activities; at the same time. Notice me if it work. – Giorgio B May 30 '16 at 05:30
  • No, it does not work. I need "do action" to happen sequentially, not at the same time, but with the code you have it can happen at the same time. – Alex I May 31 '16 at 11:16
  • So do you want to receive data from 2 serial ports and process in order even if you do not receive in one port? – Giorgio B May 31 '16 at 12:58
  • Yes, exactly, process in order. – Alex I May 31 '16 at 18:39
  • As I explained earlier, this will block. I want a solution that doesn't block but also doesn't have race conditions. If it was that easy I'd already know how to do it lol – Alex I May 31 '16 at 18:46
  • No, this do not block – Giorgio B May 31 '16 at 18:50
  • I suppose... if you found a easiest method write me please – Giorgio B May 31 '16 at 18:51