119

I am searching for a simple method to list all available com port on a PC.

I have found this method but it is Windows-specific: Listing serial (COM) ports on Windows?

I am using Python 3 with pySerial on a Windows 7 PC.

I have found in the pySerial API (http://pyserial.sourceforge.net/pyserial_api.html) a function serial.tools.list_ports.comports() that lists com ports (exactly what I want).

import serial.tools.list_ports
print(list(serial.tools.list_ports.comports()))

But it seems that it doesn't work. When my USB to COM gateway is connected to the PC (I see the COM5 in the Device Manager), this COM port isn't included in the list returned by list_ports.comports(). Instead I only get COM4 which seems to be connected to a modem (I don't see it in the COM&LPT section of Device Manager)!

Do you know why it doesn't work? Have you got another solution which is not system specific?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
doom
  • 3,276
  • 4
  • 30
  • 41
  • 54
    New readers: note that it's been over five years since this question was asked, and the bug in pySerial's `comports()` function that was described in this question (without precise information on how to reproduce it) has probably been fixed. Start by trying `import serial.tools.list_ports; print([comport.device for comport in serial.tools.list_ports.comports()])`. Only if that doesn't work for you are any of the answers below relevant to you. – Mark Amery Dec 05 '17 at 23:56
  • 4
    Also to new readers: apparently due to changes in pySerial, the code described by the OP (and some of the answers) no longer produces a list of COM port names, whether complete or incomplete. Instead, it generates a list of object references to `ListPortInfo` objects. To get the names or other information you must use the attributes of these objects when building the list. See: https://pythonhosted.org/pyserial/tools.html#serial.tools.list_ports.ListPortInfo – JDM Dec 17 '18 at 12:55

13 Answers13

187

This is the code I use.

Successfully tested on Windows 8.1 x64, Windows 10 x64, Mac OS X 10.9.x / 10.10.x / 10.11.x and Ubuntu 14.04 / 14.10 / 15.04 / 15.10 with both Python 2 and Python 3.

import sys
import glob
import serial


def serial_ports():
    """ Lists serial port names

        :raises EnvironmentError:
            On unsupported or unknown platforms
        :returns:
            A list of the serial ports available on the system
    """
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result


if __name__ == '__main__':
    print(serial_ports())
tfeldmann
  • 3,108
  • 1
  • 23
  • 34
  • 1
    I might add to this some system version detection and call it a day. I'm not aware of "all systems" being equal in that ports are acquired differently depending on your OS. I'd simply detect the OS version, then based on that have a switch available for the various cases. – jml Feb 28 '13 at 06:22
  • 1
    I also had to add `except OSError: pass` when testing on OSX. – nvahalik Aug 14 '14 at 02:35
  • 8
    What would happen if a port is already opened ? It would return an error right ? But the port would still be available. I'm just wondering how to make a viable COM port deconnection on windows with python. – Manny42 Nov 09 '15 at 15:35
  • @Manny42 I don't understand what you are trying to accomplish, but if a port is already opened it surely is not available for other programs and would not be listed by the `serial_ports()` function (and wouldn't raise an exception). Why don't you use the `close()` function provided by pyserial to disconnect a COM-port? – tfeldmann Nov 10 '15 at 15:29
  • @Thomas - is there a way to get some description of the port aswell ?I have a need to pick the port of my choice based on the description –  Jul 14 '16 at 19:47
  • I had to add an `except IOError` on Linux. – Chris Gregg Jan 30 '17 at 18:21
  • How can have the device name in the result so i can identify a particular device ? – mrid May 26 '18 at 07:33
  • If already opened serial ports should be included too, do: `except (OSError, serial.SerialException) as e: if "PermissionError" in str(e): result.append(port)` – Nils Dec 04 '20 at 23:27
  • If you want the description or device name, you'll need to use another method like `serial.tools.list_ports.comports()`. – Will Jan 07 '22 at 17:33
  • This solution seems not to work anymore. AttributeError: module 'serial' has no attribute 'SerialException' – Ilya Mar 02 '23 at 13:45
  • 1
    This worked today ! without modification flawlessly on my Raspberry Pi Zero W V1.1 (Python 3). – TTorai Apr 20 '23 at 19:57
58

Basically mentioned this in pyserial documentation https://pyserial.readthedocs.io/en/latest/tools.html#module-serial.tools.list_ports

import serial.tools.list_ports
ports = serial.tools.list_ports.comports()

for port, desc, hwid in sorted(ports):
        print("{}: {} [{}]".format(port, desc, hwid))

Result :

COM1: Communications Port (COM1) [ACPI\PNP0501\1]

COM7: MediaTek USB Port (COM7) [USB VID:PID=0E8D:0003 SER=6 LOCATION=1-2.1]

Ozgur Oz
  • 740
  • 6
  • 9
29

You can use:

python -c "import serial.tools.list_ports;print serial.tools.list_ports.comports()"

Filter by know port: python -c "import serial.tools.list_ports;print [port for port in serial.tools.list_ports.comports() if port[2] != 'n/a']"

See more info here: https://pyserial.readthedocs.org/en/latest/tools.html#module-serial.tools.list_ports

moylop260
  • 1,288
  • 2
  • 13
  • 20
  • This works great on OSX 10.11.5 with python 2.7.11 and seems to be much faster than the solution from Thomas. On OSX 10.11.5 with python 3.4.4 it returns an empty array, so it's definitely missing some com ports. – doizuc Jun 15 '16 at 22:38
  • Works great with Windows7 and Python 2.7. Very usefully, it returns a tuple like this: `('COM114', 'USB Serial Port (COM114)', 'FTDIBUS\\VID_0403+PID_6001+7&2A8DEF85&0&2\\0000')` Which allows you to filter by Vendor USB PID/VID, which is exactly what I was after. – Richard Aplin Jun 24 '16 at 20:55
  • Just tested on a Linux board (Debian, 3.4 kernel), equally good results, this time including the USB device serial number `('/dev/ttyACM1', 'ttyACM1', 'USB VID:PID=0483:5752 SNR=8D7B179D5354')` Great solution. Thx! – Richard Aplin Jun 24 '16 at 21:01
  • There's a very similar answer here: http://stackoverflow.com/questions/24214643/python-to-automatically-select-serial-ports-for-arduino – Gladclef Apr 15 '17 at 21:13
  • 2
    -1; the OP already mentioned `serial.tools.list_ports.comports()` and explained that it didn't work correctly on the asker's platform. You're adding no information that wasn't in the question. – Mark Amery Dec 05 '17 at 23:40
18

one line solution with pySerial package.

python -m serial.tools.list_ports
12

A possible refinement to Thomas's excellent answer is to have Linux and possibly OSX also try to open ports and return only those which could be opened. This is because Linux, at least, lists a boatload of ports as files in /dev/ which aren't connected to anything. If you're running in a terminal, /dev/tty is the terminal in which you're working and opening and closing it can goof up your command line, so the glob is designed to not do that. Code:

    # ... Windows code unchanged ...

    elif sys.platform.startswith ('linux'):
        temp_list = glob.glob ('/dev/tty[A-Za-z]*')

    result = []
    for a_port in temp_list:

        try:
            s = serial.Serial(a_port)
            s.close()
            result.append(a_port)
        except serial.SerialException:
            pass

    return result

This modification to Thomas's code has been tested on Ubuntu 14.04 only.

E.Solicito
  • 101
  • 9
Ngerf
  • 121
  • 1
  • 2
10

refinement on moylop260's answer:

import serial.tools.list_ports
comlist = serial.tools.list_ports.comports()
connected = []
for element in comlist:
    connected.append(element.device)
print("Connected COM ports: " + str(connected))

This lists the ports that exist in hardware, including ones that are in use. A whole lot more information exists in the list, per the pyserial tools documentation

grambo
  • 283
  • 2
  • 10
  • 4
    -1 for at least a couple of reasons: it's bad practice to override the names of builtins like `list`, and your four-statement construction of `connected` could be written significantly more succinctly as `connected = [port.device for port in serial.tools.list_ports.comports()]`. – Mark Amery Dec 05 '17 at 23:52
  • 8
    for the guy who's spent 1/2 a career in embedded C, I'm not one for the single line do everything here style. You certainly can do it all on one line. I've corrected the 'list' faux pas, not sure how I missed something glaring like that. Cheers. – grambo Apr 12 '18 at 14:46
  • 6
    @Mark Amery. Do you really think one-liners are a good idea for a question and answer site? – cstrutton Sep 30 '18 at 11:47
  • @cstrutton I don't understand your question. – Mark Amery Sep 30 '18 at 12:23
  • 5
    @Mark Avery. One-liners obscure key concepts from novice users. For example, if you use a list comprehension to demonstrate a simple concept, a novice might get lost in the list comprehension and miss the base concept. If a list comprehension is the best way to do it, show the long hand version first, then how to shorten it. You teach your point and reinforce the list comp at the same time. – cstrutton Sep 30 '18 at 12:39
  • @cstrutton Meh; to my sensibilities the comprehension is the most readable way of writing it, but maybe you're right that a Python novice would prefer this way. There's little reason I see to show both - teaching about comprehensions isn't the point of the answer, and the objective should just be to make what's going on as understandable to the reader as possible. – Mark Amery Sep 30 '18 at 12:46
  • FWIW, I'm keeping my -1 on this despite the edit because (like moylop260's answer) it doesn't really address the question posed, which was about why some ports weren't appearing in the list returned by `serial.tools.list_ports.comports()`. – Mark Amery Sep 30 '18 at 12:52
  • `python -m pip install pyserial` the module – Peyman Majidi Aug 10 '20 at 17:49
6

Probably late, but might help someone in need.

import serial.tools.list_ports


class COMPorts:

    def __init__(self, data: list):
        self.data = data

    @classmethod
    def get_com_ports(cls):
        data = []
        ports = list(serial.tools.list_ports.comports())

        for port_ in ports:
            obj = Object(data=dict({"device": port_.device, "description": port_.description.split("(")[0].strip()}))
            data.append(obj)

        return cls(data=data)

    @staticmethod
    def get_description_by_device(device: str):
        for port_ in COMPorts.get_com_ports().data:
            if port_.device == device:
                return port_.description

    @staticmethod
    def get_device_by_description(description: str):
        for port_ in COMPorts.get_com_ports().data:
            if port_.description == description:
                return port_.device


class Object:
    def __init__(self, data: dict):
        self.data = data
        self.device = data.get("device")
        self.description = data.get("description")


if __name__ == "__main__":
    for port in COMPorts.get_com_ports().data:
        print(port.device)
        print(port.description)

    print(COMPorts.get_device_by_description(description="Arduino Leonardo"))
    print(COMPorts.get_description_by_device(device="COM3"))
Malcolm Who
  • 433
  • 5
  • 13
4

Please, try this code:

import serial
ports = serial.tools.list_ports.comports(include_links=False)
for port in ports :
    print(port.device)

first of all, you need to import package for serial port communication, so:

import serial

then you create the list of all the serial ports currently available:

ports = serial.tools.list_ports.comports(include_links=False)

and then, walking along whole list, you can for example print port names:

for port in ports :
    print(port.device)

This is just an example how to get the list of ports and print their names, but there some other options you can do with this data. Just try print different variants after

port.

Alexander Lyapin
  • 305
  • 1
  • 14
  • I think you need to do import serial.tools.list_ports, or at least that is what I needed to do on my version of serial on Python 3.7 with PySerial 3.4 – Tom Myddeltyn Dec 23 '19 at 02:51
4

something simple but I use it a lot.

import serial.tools.list_ports as ports

com_ports = list(ports.comports()) # create a list of com ['COM1','COM2'] 
    for i in com_ports:            
        print(i.device) # returns 'COMx'        
1

try this code

import serial.tools.list_ports
for i in serial.tools.list_ports.comports():
    print(i) 

it returns

COM1 - Port de communication (COM1)
COM5 - USB-SERIAL CH340 (COM5)

if you just wont the name of the port for exemple COM1

import serial.tools.list_ports
for i in serial.tools.list_ports.comports():
    print(str(i).split(" ")[0])

it returns

COM1
COM5

as in my case py 3.7 64bits

1

Works only on Windows:

import winreg
import itertools

def serial_ports() -> list:
    path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM'
    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)

    ports = []
    for i in itertools.count():
        try:
            ports.append(winreg.EnumValue(key, i)[1])
        except EnvironmentError:
            break

    return ports

if __name__ == "__main__":
    ports = serial_ports()
1

One thing to note, codes like this:

for i in serial.tools.list_ports.comports():
print(i) 

Return the following:

COM7 - Standard Serial over Bluetooth link (COM7) COM1 - Communications Port (COM1) COM8 - Standard Serial over Bluetooth link (COM8) COM4 - USB-SERIAL CH340 (COM4)

If you want the ports listed in order, and only the ones available to you, try:(credit to tfeldmann)

   def serial_ports():
    """ Lists serial port names

        :raises EnvironmentError:
            On unsupported or unknown platforms
        :returns:
            A list of the serial ports available on the system
    """
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result

This returns the following:

['COM1', 'COM4', 'COM8']

So unlike the first example, where the result was ['COM7', 'COM1', 'COM8', 'COM4'], this time I get all of the com ports in order, and only the ones available. Very handy if you need them in order, and tested to see if they're available.

0

Several options are available:

Call QueryDosDevice with a NULL lpDeviceName to list all DOS devices. Then use CreateFile and GetCommConfig with each device name in turn to figure out whether it's a serial port.

Call SetupDiGetClassDevs with a ClassGuid of GUID_DEVINTERFACE_COMPORT.

WMI is also available to C/C++ programs.

There's some conversation on the win32 newsgroup and a CodeProject, er, project.

Hip Hip Array
  • 4,665
  • 11
  • 49
  • 80
  • 3
    -1; this was a Python question asking for a platform-agnostic solution, and you've responded with an answer that's Windows-specific and gives no indication of how to do any of this in Python in particular. This could be a great answer to a slightly different question, but is out of place here. – Mark Amery Dec 05 '17 at 23:49