Search code examples
pythonpyserialcontextmanager

how does PySerial manage multiple processes/clients accessing the same device at the same time? (context management maybe?)


I've got a couple of IOT-type toys (power meter, specifically) that provide an RS485 interface for configuration and monitoring. With a basic USB<->RS485 bridge I'm able to communicate with the device, read the output data and have it respond to basic commands... all well and good.

the script is a super basic use of pySerial. The get_values_str is just the series of bytes to write that "asks" the power meter for a set of readings, and then readline() grabs that data.

import serial
from time import sleep

with serial.Serial('/dev/ttyUSB1', baudrate=115200, timeout=1) as serialHandle:
    get_values_str = b':R50=1,2,1,\n'
    for n in range(100):
        serialHandle.write(get_values_str)
        print(serialHandle.readline())
        sleep(10)

I was (pleasantly?) surprised today when I fired up a second instance of the script by mistake, and both of them proceeded to return valid data at the same time. I was under the impression that access to the serial port/device was an exclusive thing (like a file handle?) and that I wouldn't be allowed to have multiple open contexts at the same time.

In a slightly more general sense, I'm looking to understand what the best practice might be for allowing multiple clients/processes to access this info... it seems like constantly opening and closing a context manager is the wrong thing to do -- maybe it's not expensive but it sure seems like a bad idea. If the ability to access the USB device from multiple places is a normal/supported thing then maybe it's not a big deal to have a bunch of context managers open at the same time, but this is the part where we're getting into an area that I don't have much knowledge on.

I guess ideally I'd like to just have an "always available" function that returns one line of data, but it's totally unclear to me what combination of context managers and/or generators and/or locking issues I need to think about here.


Solution

  • I was under the impression that access to the serial port/device was an exclusive thing (like a file handle?) and that I wouldn't be allowed to have multiple open contexts at the same time.

    You're allowed to open a device multiple times, but you can't really do so usefully. You're lucky because your code spends the bulk of its time sleeping, so as long as your two scripts aren't in sync there's an excellent chance that when one script is writing to/reading from the serial port, the other script is asleep.

    Unfortunately, if they both happen to be interacting with the port at the same time, you'll get chaos.

    If you want multiple things to have access to a serial port, you write one service that has exclusive access to the port, and then accepts connections from client an "broadcasts" data from the port to connected clients (the gpsd service works exactly like this).

    Alternately, you set up some alternate communication channel so that your multiple scripts can coordinate their access to the port.


    To avoid accidentally ending up in this situation, you can use the exclusive parameter in the serial.Serial constructor:

    with serial.Serial('/dev/ttyUSB1', baudrate=115200, timeout=1, exclusive=True) as serialHandle: